{"version":3,"sources":["../../../packages/core/notification/work-item-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,UAAU,EAAgC,MAAM,MAAM,CAAC;AAEhF,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAE/D,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAQzD,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAEnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAMnE,OAAO,EAAuB,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1E;;GAEG;AACH,qBAAa,eAAe;IA0BpB,OAAO,CAAC,GAAG;IACX,OAAO,CAAC,iBAAiB;IACzB,OAAO,CAAC,cAAc;IACf,sBAAsB,EAAE,sBAAsB;IA5BzD,OAAO,CAAC,MAAM,CAAC,mBAAmB,CAAiC;IACnE,OAAO,CAAC,MAAM,CAAC,4BAA4B,CAAiC;IAC5E,OAAO,CAAC,MAAM,CAAC,oCAAoC,CAA6B;IAEzE,MAAM,UAAS;IAEtB,OAAO,CAAC,iBAAiB,CAAe;IACxC,OAAO,CAAC,sBAAsB,CAAyB;IACvD,OAAO,CAAC,wBAAwB,CAAe;IAC/C,OAAO,CAAC,uBAAuB,CAAe;IAC9C,OAAO,CAAC,mBAAmB,CAAsB;IACjD,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,eAAe,CAA6E;IACpG,OAAO,CAAC,wBAAwB,CAA2B;IAC3D,OAAO,CAAC,kBAAkB,CAAS;IAEnC;;;;;;;OAOG;gBAES,GAAG,EAAE,GAAG,EACR,iBAAiB,EAAE,iBAAiB,EACpC,cAAc,EAAE,cAAc,EAC/B,sBAAsB,EAAE,sBAAsB;IAKzD;;OAEG;IACI,KAAK,IAAI,IAAI;IAiKpB;;OAEG;IACI,IAAI,IAAI,IAAI;IAuBnB;;;;;OAKG;IACI,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,CAAC,cAAc,CAAC;IAqDvE;;;;;OAKG;IACI,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,CAAC,cAAc,CAAC;IAwBtE,OAAO,CAAC,aAAa;CAqCxB","file":"work-item-manager.d.ts","sourcesContent":["import { forkJoin, from, Observable, of, Subscription, throwError } from 'rxjs';\r\nimport { catchError, switchMap } from 'rxjs/operators';\r\nimport { GatewayConnection } from '../data/gateway-connection';\r\nimport { Net } from '../data/net';\r\nimport { NodeConnection } from '../data/node-connection';\r\nimport { PowerShell, PowerShellCommand } from '../data/powershell';\r\nimport { LogLevel } from '../diagnostics/log-level';\r\nimport { Logging } from '../diagnostics/logging';\r\nimport { SmeWebTelemetry } from '../diagnostics/sme-web-telemetry';\r\nimport { TelemetryEventStates } from '../diagnostics/sme-web-telemetry-models';\r\nimport { Strings } from '../generated/strings';\r\nimport { EnvironmentModule } from '../manifest/environment-modules';\r\nimport { Rpc } from '../rpc/rpc';\r\nimport { RpcWorkItem } from '../rpc/work-item/rpc-work-item-model';\r\nimport { RpcWorkItemSubjectServer } from '../rpc/work-item/rpc-work-item-subject-server';\r\nimport { NotificationConnection } from './notification-connection';\r\nimport { NotificationManager } 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 { WorkItemRequestType, WorkItemResult } from './work-item-request';\r\n\r\n/**\r\n * Work item manager class.\r\n */\r\nexport class WorkItemManager {\r\n    private static apiWorkItems24hours = '/workitems?lastMinutes=1440';\r\n    private static apiNotificationMessageStored = '/NotificationMessage/stored';\r\n    private static apiNotificationMessageSubscriptionId = '/NotificationMessage/id';\r\n\r\n    public active = false;\r\n\r\n    private startSubscription: Subscription;\r\n    private powerShellNotification: PowerShellNotification;\r\n    private notificationSubscription: Subscription;\r\n    private rpcWorkItemSubscription: Subscription;\r\n    private notificationManager: NotificationManager;\r\n    private sequenceCounter = 1;\r\n    private sequencePackets: { [index: number]: { success: boolean, data: WorkItemResult | any } } = {};\r\n    private rpcWorkItemSubjectServer: RpcWorkItemSubjectServer;\r\n    private rootSubscriptionId: string;\r\n\r\n    /**\r\n     * Initializes a new instance of the WorkItemManager class.\r\n     *\r\n     * @param rpc the RPC object.\r\n     * @param gatewayConnection the gateway connection service.\r\n     * @param nodeConnection the node connection service.\r\n     * @param notificationManager the notification manager.\r\n     */\r\n    constructor(\r\n        private rpc: Rpc,\r\n        private gatewayConnection: GatewayConnection,\r\n        private nodeConnection: NodeConnection,\r\n        public notificationConnection: NotificationConnection) {\r\n        this.notificationManager = this.notificationConnection.notificationManager;\r\n        this.start();\r\n    }\r\n\r\n    /**\r\n     * Start the work item management.\r\n     */\r\n    public start(): void {\r\n        this.stop();\r\n        this.rpcWorkItemSubjectServer = new RpcWorkItemSubjectServer(this.rpc);\r\n        this.active = true;\r\n\r\n        // pickup active work items for last 24hours\r\n        this.startSubscription = this.gatewayConnection.get(WorkItemManager.apiWorkItems24hours)\r\n            .pipe(\r\n                catchError((error) => {\r\n                    const message = MsftSme.getStrings<Strings>().MsftSmeShell.Core.Error.NotificationNoWorkItemFound.message;\r\n                    Logging.logError('Notification', message.format(Net.getErrorMessage(error)));\r\n                    return of({ response: { value: null } });\r\n                }),\r\n                switchMap(response => {\r\n                    if (response.value) {\r\n                        response.value.forEach(element => this.notificationManager.addFromRecover(element));\r\n                    }\r\n\r\n                    // start websocket status query.\r\n                    this.powerShellNotification = new PowerShellNotification(this.gatewayConnection.gatewayUrl);\r\n                    return from(this.powerShellNotification.initialize());\r\n                }))\r\n            .subscribe({\r\n                next: () => {\r\n                    // notification from the gateway...\r\n                    this.notificationSubscription = this.powerShellNotification.subject\r\n                        .subscribe(item => {\r\n                            const message: any = item.message;\r\n                            if (message && message.sessionId) {\r\n                                // PowerShell Work Item message.\r\n                                const workItem: PowerShellWorkItemMessage = message;\r\n                                const psSessionId = workItem.sessionId;\r\n\r\n                                // Pass in psSessionId so telemetry can search up the PSCommand for this\r\n                                if (PowerShellNotification.hasError(item)) {\r\n                                    SmeWebTelemetry.tracePowershellEvent(\r\n                                        null, TelemetryEventStates.Error, { response: workItem, id: psSessionId});\r\n                                } else if (PowerShellNotification.hasCompleted(item)) {\r\n                                    SmeWebTelemetry.removePowershellId(psSessionId);\r\n                                }\r\n\r\n                                if (!this.notificationManager.updateFromMessage(\r\n                                    psSessionId, <SocketMessage<PowerShellWorkItemMessage>>item)) {\r\n                                    const unexpectedMessage =\r\n                                        MsftSme.getStrings<Strings>().MsftSmeShell.Core.Error.NotificationUnexpectedReceived.message;\r\n                                    Logging.log({\r\n                                        source: 'Notification', level: LogLevel.Warning, message: unexpectedMessage.format(psSessionId)\r\n                                    });\r\n                                }\r\n                            } else if (message && message.id) {\r\n                                // PowerShell Work Item message.\r\n                                const notification: NotificationMessage = message;\r\n                                const state = this.getErrorLevel(notification);\r\n\r\n                                // Pass in psSessionId so telemetry can search up the PSCommand for this\r\n                                if (state === NotificationState.Error) {\r\n                                    SmeWebTelemetry.tracePowershellEvent(\r\n                                        null, TelemetryEventStates.Error, { response: message, id: message.id});\r\n                                } else if (state === NotificationState.Success) {\r\n                                    SmeWebTelemetry.removePowershellId(message.id);\r\n                                }\r\n\r\n                                if (this.notificationManager.addForNotificationMessage(state, notification)) {\r\n                                    this.powerShellNotification.subscribeSession(notification.id);\r\n                                }\r\n\r\n                                if (!this.notificationManager.updateFromNotificationMessage(state, message)) {\r\n                                    const unexpectedMessage =\r\n                                        MsftSme.getStrings<Strings>().MsftSmeShell.Core.Error.NotificationUnexpectedReceived.message;\r\n                                    Logging.logWarning('Notification', unexpectedMessage.format(message.id));\r\n                                }\r\n                            }\r\n                        });\r\n\r\n                    if (EnvironmentModule.isGatewayV200) {\r\n                        this.notificationSubscription.add(\r\n                            forkJoin([\r\n                                this.gatewayConnection.get(WorkItemManager.apiNotificationMessageSubscriptionId),\r\n                                this.gatewayConnection.get(WorkItemManager.apiNotificationMessageStored)\r\n                            ]).subscribe(([idData, storedData]) => {\r\n                                this.rootSubscriptionId = idData.id;\r\n                                this.powerShellNotification.subscribeSession(this.rootSubscriptionId);\r\n                                storedData.value.forEach(element => {\r\n                                    const state = this.getErrorLevel(element);\r\n                                    this.notificationManager.addForNotificationMessage(state, element);\r\n                                    this.powerShellNotification.subscribeSession(element.id);\r\n                                });\r\n                            }));\r\n                    }\r\n\r\n                    // workItem request from rpc...\r\n                    this.rpcWorkItemSubscription = this.rpcWorkItemSubjectServer.subject\r\n                        .subscribe(item => {\r\n                            switch (item.data.type) {\r\n                                // legacy support for quick submission. it was expected 3 seconds response.\r\n                                case WorkItemRequestType.PowerShellSubmit:\r\n                                    this.submitWorkItem(item.data).toPromise().then(item.deferred.resolve, item.deferred.reject);\r\n                                    break;\r\n\r\n                                // new support for longer submission case like manage-as dialog displays.\r\n                                case WorkItemRequestType.WorkItemSubmit:\r\n                                    const submitSequenceId = this.sequenceCounter++;\r\n                                    item.data.sequenceId = submitSequenceId;\r\n                                    this.submitWorkItem(item.data)\r\n                                        .subscribe({\r\n                                            next: data => this.sequencePackets[submitSequenceId] = { success: true, data: data },\r\n                                            error: error => this.sequencePackets[submitSequenceId] = { success: false, data: error }\r\n                                        });\r\n\r\n                                    // using sequence id, submission return immediately.\r\n                                    item.deferred.resolve(<WorkItemResult>{ sequenceId: submitSequenceId, id: null });\r\n                                    break;\r\n\r\n                                // query to notification by 'id' or query to submission result by 'sequenceId'.\r\n                                case WorkItemRequestType.StateQuery:\r\n                                default:\r\n                                    if (item.data.id) {\r\n                                        // query to 'id'\r\n                                        this.queryWorkItem(item.data).toPromise().then(item.deferred.resolve, item.deferred.reject);\r\n                                    } else {\r\n                                        // query to 'sequenceId'\r\n                                        const querySequenceId = item.data.sequenceId;\r\n                                        const result = this.sequencePackets[querySequenceId];\r\n                                        if (result) {\r\n                                            // have a result, delete it and pass back as response.\r\n                                            delete this.sequencePackets[querySequenceId];\r\n                                            if (result.success) {\r\n                                                item.deferred.resolve(result.data);\r\n                                            } else {\r\n                                                item.deferred.reject(result.data);\r\n                                            }\r\n                                        } else {\r\n                                            // no result yet.\r\n                                            item.deferred.resolve(<WorkItemResult>{ sequenceId: querySequenceId, id: null });\r\n                                        }\r\n                                    }\r\n\r\n                                    break;\r\n\r\n                            }\r\n                        });\r\n\r\n                    // subscribe the session notification to the gateway...\r\n                    this.notificationManager.items.forEach((value) => {\r\n                        // only if it's not finalized.\r\n                        if (value.state === NotificationState.Started || value.state === NotificationState.InProgress) {\r\n                            this.powerShellNotification.subscribeSession(value.id);\r\n                        }\r\n                    });\r\n                },\r\n                error: error => {\r\n                    const message = MsftSme.getStrings<Strings>().MsftSmeShell.Core.Error.NotificationWebsocketInitialize.message;\r\n                    Logging.log({\r\n                        source: 'Notification',\r\n                        level: LogLevel.Error,\r\n                        message: message.format(Net.getErrorMessage(error))\r\n                    });\r\n                }\r\n            });\r\n    }\r\n\r\n    /**\r\n     * Stop the work item management.\r\n     */\r\n    public stop(): void {\r\n        this.active = false;\r\n        if (this.startSubscription) {\r\n            this.startSubscription.unsubscribe();\r\n            this.startSubscription = null;\r\n        }\r\n\r\n        if (this.notificationSubscription) {\r\n            this.notificationSubscription.unsubscribe();\r\n            this.notificationSubscription = null;\r\n        }\r\n\r\n        if (this.powerShellNotification) {\r\n            this.powerShellNotification.uninitialize();\r\n            this.powerShellNotification = null;\r\n        }\r\n\r\n        if (this.rpcWorkItemSubscription) {\r\n            this.rpcWorkItemSubscription.unsubscribe();\r\n            this.rpcWorkItemSubscription = null;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Create and submit a workItem.\r\n     *\r\n     * @param request the work item request.\r\n     * @return Observable the WorkItemResult observable.\r\n     */\r\n    public submitWorkItem(request: RpcWorkItem): Observable<WorkItemResult> {\r\n        let command: PowerShellCommand = null;\r\n        const notificationId = request.id || MsftSme.newGuid();\r\n\r\n        if (request.powerShellCommand || request.powerShellScript) {\r\n            // For a non-powershell long running task (eg: azure site recovery setup), command is null.\r\n            command = PowerShell.getPowerShellCommand(request.powerShellCommand || request.powerShellScript);\r\n        }\r\n\r\n        if (request.powerShellScript) {\r\n            delete request['powerShellScript'];\r\n        }\r\n\r\n        if (request.powerShellCommand) {\r\n            delete request['powerShellCommand'];\r\n        }\r\n\r\n        const nodeRequestOptions = PowerShell.newEndpointOptions(request.nodeRequestOptions);\r\n        if (request.nodeRequestOptions) {\r\n            delete request['nodeRequestOptions'];\r\n        }\r\n\r\n        // remember current URL where original request generated.\r\n        request.locationPathname = window.location.pathname;\r\n        request.locationSearch = window.location.search;\r\n\r\n        // send submit notification\r\n        this.notificationManager.addFromWorkItem(\r\n            notificationId,\r\n            request,\r\n            request.startedMessage ? NotificationState.Started : NotificationState.InProgress\r\n        );\r\n\r\n        return this.powerShellNotification.submit(\r\n            this.nodeConnection,\r\n            request.nodeName,\r\n            command,\r\n            request,\r\n            nodeRequestOptions,\r\n            result => {\r\n                if (!this.notificationManager.updateWorkItemWithPsSession(\r\n                    notificationId, result.id, request, result.state, result.error)) {\r\n                    const message = MsftSme.getStrings<Strings>()\r\n                        .MsftSmeShell.Core.Error.NotificationUnexpectedReceived.message;\r\n                    Logging.log({\r\n                        source: 'Notification',\r\n                        level: LogLevel.Warning,\r\n                        message: message.format(notificationId)\r\n                    });\r\n                }\r\n            });\r\n    }\r\n\r\n    /**\r\n     * Query a workItem.\r\n     *\r\n     * @param request the work item request.\r\n     * @return Observable the WorkItemResult observable.\r\n     */\r\n    public queryWorkItem(request: RpcWorkItem): Observable<WorkItemResult> {\r\n        const notification = this.notificationManager.find(request.id);\r\n        if (notification) {\r\n            if (notification.state === NotificationState.Error) {\r\n                return of(<WorkItemResult>{\r\n                    id: notification.id,\r\n                    state: notification.state,\r\n                    percent: notification.progressPercent,\r\n                    error: notification.object\r\n                });\r\n            }\r\n\r\n            return of(<WorkItemResult>{\r\n                id: notification.id,\r\n                state: notification.state,\r\n                percent: notification.progressPercent,\r\n                object: notification.object\r\n            });\r\n        }\r\n\r\n        const message = MsftSme.getStrings<Strings>().MsftSmeShell.Core.Error.NotificationNoIdFound.message;\r\n        return throwError(() => message.format(request.id));\r\n    }\r\n\r\n    private getErrorLevel(notification: NotificationMessage): NotificationState {\r\n        switch (notification.errorLevel) {\r\n            // Notification is unknown.\r\n            case 0:\r\n            case 'None':\r\n                return NotificationState.Informational;\r\n\r\n            // Notification is in progress. (non-terminated message.)\r\n            case 1:\r\n            case 'InProgress':\r\n                return NotificationState.InProgress;\r\n\r\n            // Notification is success. (terminated message.)\r\n            case 2:\r\n            case 'Success':\r\n                return NotificationState.Success;\r\n\r\n            // Notification is informational. (terminated message.)\r\n            case 3:\r\n            case 'Informational':\r\n                return NotificationState.Informational;\r\n\r\n            // Notification is warning. (terminated message.)\r\n            case 4:\r\n            case 'Warning':\r\n                return NotificationState.Warning;\r\n\r\n            // Notification is error. (terminated message.)\r\n            case 5:\r\n            case 'Error':\r\n                return NotificationState.Error;\r\n\r\n            // Notification is unknown.\r\n            default:\r\n                throw new Error('Couldn\\'t get expected error level from a message notification.');\r\n        }\r\n    }\r\n}\r\n"]}