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