@microsoft/windows-admin-center-sdk
Version:
Microsoft - Windows Admin Center Shell
522 lines (520 loc) • 19.6 kB
JavaScript
import { Globalization } from '../data/globalization';
import { Logging } from '../diagnostics/logging';
import { NotificationLinkType } from './notification-link-type';
import { NotificationState } from './notification-state';
import { PowerShellNotification } from './powershell-notification';
/**
* Notification changed event type.
*/
export var NotificationChangeEvent;
(function (NotificationChangeEvent) {
NotificationChangeEvent[NotificationChangeEvent["Initialized"] = 0] = "Initialized";
NotificationChangeEvent[NotificationChangeEvent["InitializationFailed"] = 1] = "InitializationFailed";
NotificationChangeEvent[NotificationChangeEvent["Add"] = 2] = "Add";
NotificationChangeEvent[NotificationChangeEvent["Remove"] = 3] = "Remove";
NotificationChangeEvent[NotificationChangeEvent["Change"] = 4] = "Change";
})(NotificationChangeEvent || (NotificationChangeEvent = {}));
/**
* Internal notification object converted from ClientNotification object.
* Contains data to track notifications.
*/
export class Notification {
id;
strings = MsftSme.getStrings();
/**
* Work item passed from RPC or client on shell.
*/
workItem;
/**
* The type ID of work item.
*/
typeId;
/**
* The node name.
*/
nodeName;
/**
* The module name.
*/
moduleName;
/**
* The entry point name within the module
*/
entryPointName;
/**
* The module display name.
*/
moduleDisplayName;
/**
* Object included last response.
*/
object;
/**
* The state of notification.
*/
state;
/**
* The title of work item to display user. (localized)
*/
title;
/**
* @depracated
* The description of work item to display user. (localized)
*/
description;
/**
* The message. (localized)
*/
message;
/**
* Possible solution message to address error. (localized)
*/
solutionMessage;
/**
* True if the notification was created from recover, updates from messages should be taken
*/
isFromRecover = false;
/**
* True if the notification is disabled and should not be shown to the user
*/
isDisabled = false;
/**
* The start timestamp as a formatted globalized string
*/
startTimestamp;
/**
* The last changed timestamp as a formatted globalized string
*/
changedTimestamp;
/**
* The last changed timestamp as a number
*/
changedTimestampValue;
/**
* The end timestamp as a formatted globalized string
*/
endTimestamp;
/**
* The progress percent.
*/
progressPercent;
/**
* The success link to navigate to the object view. (optional)
* At default, it brings to the home page of the module.
*/
link;
/**
* the notification link type of the link
*/
linkType;
/**
* True if we should show the link in the notification's alert
* We will show the link if there is a custom link or it is a long running request
*/
showLinkInAlert = false;
/**
* Marked it's no longer display to include list of notifications.
*/
dismissed = false;
/**
* The parent URI window.location.pathname.
*/
locationPathname;
/**
* The parent URI window.location.search
*/
locationSearch;
/**
*
* Tracks if the notification has been read.
*/
isUnread = true;
/**
* Create notification from WorkItem.
*
* @param id the notification ID.
* @param workItem the RPC work item.
* @param state the initial state.
* @param object the object from query result.
* @return notification the notification object.
*/
static createFromWorkItem(id, workItem, state, iFrameService, object) {
const notification = new Notification(id);
notification.workItem = workItem;
notification.state = state;
notification.object = object;
notification.initializeFromWorkItem(workItem, iFrameService);
return notification;
}
/**
* Create notification from recovered work item.
*
* @param recoveredWorkItem the recovered work item.
* @return notification the notification object.
*/
static createFromRecover(recoveredWorkItem, iFrameService) {
const notification = new Notification(recoveredWorkItem.id);
notification.workItem = recoveredWorkItem.metadata;
notification.isFromRecover = true;
if (recoveredWorkItem.failed) {
notification.state = NotificationState.Error;
notification.object = { errorType: 'WorkItemRecover', message: recoveredWorkItem.errorMessage };
}
else {
notification.state = NotificationState.InProgress;
notification.object = {};
}
notification.initializeFromWorkItem(recoveredWorkItem.metadata, iFrameService);
return notification;
}
/**
* Create notification from instant request.
*
* @param client the RPC notification request.
* @return notification the notification object.
*/
static createFromClient(client, iFrameService) {
const notification = new Notification(client.id);
notification.locationPathname = window.location.pathname;
notification.locationSearch = window.location.search;
notification.initializeFromInstant(client, iFrameService);
return notification;
}
/**
* Initializes a new instance of the Notification class.
*
* @param id the notification ID.
*/
constructor(id) {
this.id = id;
}
/**
* Update a notification by work item from RPC.
* @param id the notification id.
* @param workItem the work item.
* @param state the state of the notification.
* @param object the object from query result.
*/
updateFromWorkItem(id, workItem, state, object) {
let changed = false;
if (this.updateState(state)) {
changed = true;
const now = Date.now();
this.changedTimestampValue = now;
this.changedTimestamp = this.endTimestamp = this.getGlobalizedTimestamp(now);
}
if (this.object !== object) {
changed = true;
this.object = object;
}
this.updateMessageAndLinkAndTitle(workItem);
return !this.isDisabled && changed;
}
/**
* Update the notification by socket message from the gateway.
*
* @param item the socket message.
* @return boolean the changed status.
*/
updateFromMessage(item) {
let changed = false;
const now = Date.now();
if (PowerShellNotification.hasError(item)) {
Logging.logDebug('Notification', '{0}/Error/{1}'.format(this.id, JSON.stringify(item.message.errors)));
if (this.updateState(NotificationState.Error)) {
this.changedTimestampValue = now;
this.changedTimestamp = this.endTimestamp = this.getGlobalizedTimestamp(now);
}
this.object = item.message.errors && MsftSme.first(item.message.errors);
changed = true;
this.updateMessageAndLinkAndTitle(this.workItem);
return !this.isDisabled;
}
if (PowerShellNotification.hasException(item)) {
Logging.logDebug('Notification', '{0}/Exception/{1}'.format(this.id, item.message.exception));
if (this.updateState(NotificationState.Error)) {
this.changedTimestampValue = now;
this.changedTimestamp = this.endTimestamp = this.getGlobalizedTimestamp(now);
}
this.object = { message: item.message.exception };
changed = true;
this.updateMessageAndLinkAndTitle(this.workItem);
return !this.isDisabled;
}
if (PowerShellNotification.hasProgress(item)) {
Logging.logDebug('Notification', '{0}/Progress/{1}'.format(this.id, JSON.stringify(item.message.progress)));
if (this.updateState(NotificationState.InProgress)) {
this.changedTimestampValue = now;
this.changedTimestamp = this.getGlobalizedTimestamp(now);
}
this.object = item.message.progress && MsftSme.last(item.message.progress);
this.progressPercent = this.object?.percentComplete ?? this.object?.percent;
if ((this.progressPercent == null || this.progressPercent < 0)
&& this.workItem.progressMessage && this.workItem.progressMessage.indexOf('{{percent}}') >= 0) {
return false;
}
changed = true;
}
if (PowerShellNotification.hasData(item)) {
Logging.logDebug('Notification', '{0}/Data/{1}'.format(this.id, JSON.stringify(item.message.results)));
this.object = item.message.results && MsftSme.last(item.message.results);
changed = true;
}
if (PowerShellNotification.hasCompleted(item)) {
Logging.logDebug('Notification', '{0}/Completed'.format(this.id));
if (this.updateState(NotificationState.Success)) {
this.changedTimestampValue = now;
this.changedTimestamp = this.endTimestamp = this.getGlobalizedTimestamp(now);
}
changed = true;
}
this.updateMessageAndLinkAndTitle(this.workItem);
return !this.isDisabled && changed;
}
/**
* Update the notification by socket message from the gateway.
*
* @param state the state of notification.
* @param item the socket message.
* @return boolean the changed status.
*/
updateFromNotificationMessage(state, item) {
const now = Date.now();
this.title = item.title;
this.message = item.message;
this.state = state;
this.changedTimestampValue = now;
if (state === NotificationState.InProgress) {
this.changedTimestamp = this.getGlobalizedTimestamp(now);
}
else {
this.changedTimestamp = this.endTimestamp = this.getGlobalizedTimestamp(now);
}
return true;
}
/**
* Update the notification by instant notification message from the client.
*
* @param client the instant notification object.
* @param boolean the changed status.
*/
updateFromClient(client) {
let changed = false;
const title = client.title;
if (this.title !== title) {
changed = true;
this.title = title;
}
if (this.updateState(client.state)) {
const now = Date.now();
this.changedTimestampValue = now;
this.changedTimestamp = this.getGlobalizedTimestamp(now);
changed = true;
if (this.state !== NotificationState.InProgress) {
this.endTimestamp = this.changedTimestamp;
}
}
const link = this.formatLink(client.link, client.linkType);
if (this.link !== link) {
changed = true;
this.link = link;
this.linkType = client.linkType;
}
if (this.message !== client.message) {
changed = true;
this.message = client.message;
}
if (this.solutionMessage !== client.solutionMessage) {
changed = true;
this.solutionMessage = client.solutionMessage;
}
return changed;
}
/**
* Gets the module display name.
*/
getModuleDisplayName(iFrameService, sourceName, sourceSubName) {
const activeIFrame = iFrameService ? iFrameService.getActiveToolIFrameData() : null;
if (activeIFrame && activeIFrame.entryPoint) {
const entryPoint = activeIFrame.entryPoint;
this.entryPointName = entryPoint.name;
if (entryPoint.parentModule) {
this.moduleName = entryPoint.parentModule.name;
}
}
else {
// fallback see if we can get any info from RPC
this.moduleName = sourceName;
if (sourceSubName) {
this.entryPointName = MsftSme.first(sourceSubName.split('#'));
}
}
const environment = MsftSme.self().Environment;
// if moduleName === environment.name then the source is Shell. Return null to use generic module name
if (this.moduleName && this.moduleName !== environment.name) {
const module = MsftSme.find(environment.modules, value => value.name === this.moduleName);
if (module) {
if (this.entryPointName) {
const entryPoint = MsftSme.find(module.entryPoints, ep => ep.name === this.entryPointName);
if (entryPoint) {
return entryPoint.displayName;
}
}
return module.displayName;
}
return this.moduleName;
}
return null;
}
/**
* Update the state.
*
* @param state the new state.
* @return boolean the changed state.
*/
updateState(state) {
if (this.state !== state) {
this.state = state;
return true;
}
return false;
}
initializeFromWorkItem(item, iFrameService) {
this.nodeName = item.nodeName;
this.moduleDisplayName = this.getModuleDisplayName(iFrameService, item.sourceName, item.sourceSubName);
this.startTimestamp = this.getGlobalizedTimestamp(item.timestamp);
this.changedTimestamp = this.getGlobalizedTimestamp(item.timestamp);
this.changedTimestampValue = item.timestamp;
this.typeId = item.typeId;
this.updateMessageAndLinkAndTitle(item);
}
updateMessageAndLinkAndTitle(item) {
let template;
this.isDisabled = !this.isFromRecover && item.disableAllNotifications;
switch (this.state) {
case NotificationState.Started:
this.title = item.inProgressTitle;
template = item.startedMessage;
this.link = this.formatLink(null);
break;
case NotificationState.Error:
this.title = item.errorTitle;
this.isDisabled = !this.isFromRecover && item.disableErrorNotification;
this.endTimestamp = this.getGlobalizedTimestamp(item.timestamp);
template = item.errorMessage;
this.link = this.formatLink(item.errorLink, item.errorLinkType);
this.linkType = item.errorLinkType;
this.showLinkInAlert = true;
this.moduleDisplayName = item.errorLinkText || this.moduleDisplayName;
break;
case NotificationState.InProgress:
this.title = item.inProgressTitle;
template = item.progressMessage;
this.link = this.formatLink(null);
break;
case NotificationState.Success:
this.title = item.successTitle;
this.endTimestamp = this.getGlobalizedTimestamp(item.timestamp);
template = item.successMessage;
this.link = this.formatLink(item.successLink, item.successLinkType);
this.linkType = item.successLinkType;
this.moduleDisplayName = item.successLinkText || this.moduleDisplayName;
this.showLinkInAlert = true;
break;
default:
const message = MsftSme.getStrings().MsftSmeShell.Core.Error.NotificationUnsupportedState.message;
throw new Error(message);
}
this.message = this.formatMessage(template);
}
initializeFromInstant(client, iFrameService) {
this.nodeName = client.nodeName;
this.moduleDisplayName = client.linkText ?
client.linkText
: this.getModuleDisplayName(iFrameService, client.sourceName, client.sourceSubName);
this.startTimestamp = this.getGlobalizedTimestamp(client.timestamp);
this.changedTimestamp = this.getGlobalizedTimestamp(client.timestamp);
this.changedTimestampValue = client.timestamp;
this.title = client.title;
this.state = client.state;
this.link = this.formatLink(client.link, client.linkType);
this.linkType = client.linkType;
this.message = client.message;
this.solutionMessage = client.solutionMessage;
if (this.state !== NotificationState.Started && this.state !== NotificationState.InProgress) {
this.endTimestamp = this.getGlobalizedTimestamp(client.timestamp);
}
}
getGlobalizedTimestamp(timestamp) {
return Globalization.timeOnly(new Date(timestamp));
}
formatLink(link, linkType) {
// href example: "http://localhost:4400/apps/msft.sme.server-manager!servers/tools/msft.sme.module-seed!main?connection=sme-full1.redmond.corp.microsoft.com"
if (linkType === NotificationLinkType.Absolute) {
return link;
}
// null linkType behaves like NotificationLinkType.RelativeToTool
const pathname = linkType === NotificationLinkType.RelativeToRoot ?
'/'
: this.locationPathname || this.workItem && this.workItem.locationPathname;
if (link && pathname) {
this.showLinkInAlert = true;
return MsftSme.trimEnd(pathname, '/') + '/' + MsftSme.trimStart(link, '/');
}
this.showLinkInAlert = false;
return pathname;
}
formatMessage(template) {
const parameters = this.findParameters(template);
const message = this.replaceParameters(template, parameters);
return message;
}
findParameters(template) {
const results = [];
if (!template) {
return results;
}
const segments = template.split('{{');
for (const seg of segments) {
const index = seg.indexOf('}}');
if (index > 0) {
results.push(seg.substring(0, index));
}
}
return results;
}
replaceParameters(message, parameters) {
for (const param of parameters) {
if (param === 'percent') {
if (typeof this.progressPercent === 'number') {
message = message.replaceAll('{{percent}}', '' + this.progressPercent);
}
}
else if (param === 'objectName') {
if (this.workItem && this.workItem.objectName) {
message = message.replaceAll('{{objectName}}', this.workItem.objectName);
}
}
else {
if (this.object) {
const segments = param.split('.');
let target = this.object;
for (const seg of segments) {
if (target[seg]) {
target = target[seg];
}
else {
target = null;
break;
}
}
if (target) {
message = message.replaceAll('{{' + param + '}}', '' + target);
}
}
}
}
return message;
}
}
//# sourceMappingURL=notification.js.map