From 5629356c66ccc56a6e0d0f3b8f15df9d02993366 Mon Sep 17 00:00:00 2001 From: Alan Ren Date: Tue, 29 Oct 2019 11:53:50 -0700 Subject: [PATCH] notebook background execution (#8079) * notebook background execution * code review comments * comments 2 * more logging --- .../resource-deployment/src/interfaces.ts | 3 + extensions/resource-deployment/src/main.ts | 2 +- .../src/services/notebookService.ts | 4 +- .../src/services/resourceTypeService.ts | 2 +- .../src/services/tools/toolBase.ts | 6 +- .../src/ui/deploymentInputDialog.ts | 63 +++++++++++++++++-- extensions/resource-deployment/src/utils.ts | 23 ++++--- 7 files changed, 83 insertions(+), 20 deletions(-) diff --git a/extensions/resource-deployment/src/interfaces.ts b/extensions/resource-deployment/src/interfaces.ts index 9ea3471a21..7775e341a0 100644 --- a/extensions/resource-deployment/src/interfaces.ts +++ b/extensions/resource-deployment/src/interfaces.ts @@ -98,6 +98,8 @@ export interface WizardInfo { export interface NotebookBasedDialogInfo extends DialogInfoBase { notebook: string | NotebookInfo; + runNotebook?: boolean; + taskName?: string; } export interface CommandBasedDialogInfo extends DialogInfoBase { @@ -118,6 +120,7 @@ export interface DialogInfoBase { title: string; name: string; tabs: DialogTabInfo[]; + actionText?: string; } export interface DialogTabInfo { diff --git a/extensions/resource-deployment/src/main.ts b/extensions/resource-deployment/src/main.ts index e340b30129..c6bf8704cf 100644 --- a/extensions/resource-deployment/src/main.ts +++ b/extensions/resource-deployment/src/main.ts @@ -52,7 +52,7 @@ export function activate(context: vscode.ExtensionContext) { } }); vscode.commands.registerCommand('azdata.openNotebookInputDialog', (dialogInfo: NotebookBasedDialogInfo) => { - const dialog = new DeploymentInputDialog(notebookService, dialogInfo); + const dialog = new DeploymentInputDialog(notebookService, platformService, dialogInfo); dialog.open(); }); } diff --git a/extensions/resource-deployment/src/services/notebookService.ts b/extensions/resource-deployment/src/services/notebookService.ts index 622c3ce810..614e405bce 100644 --- a/extensions/resource-deployment/src/services/notebookService.ts +++ b/extensions/resource-deployment/src/services/notebookService.ts @@ -35,7 +35,7 @@ export interface INotebookService { launchNotebook(notebook: string | NotebookInfo): Thenable; launchNotebookWithContent(title: string, content: string): Thenable; getNotebook(notebook: string | NotebookInfo): Promise; - executeNotebook(notebook: any, env: NodeJS.ProcessEnv): Promise; + executeNotebook(notebook: any, env?: NodeJS.ProcessEnv): Promise; } export class NotebookService implements INotebookService { @@ -73,7 +73,7 @@ export class NotebookService implements INotebookService { return JSON.parse(await this.platformService.readTextFile(notebookPath)); } - async executeNotebook(notebook: Notebook, env: NodeJS.ProcessEnv): Promise { + async executeNotebook(notebook: Notebook, env?: NodeJS.ProcessEnv): Promise { const content = JSON.stringify(notebook, undefined, 4); const fileName = `nb-${getDateTimeString()}.ipynb`; const workingDirectory = this.platformService.storagePath(); diff --git a/extensions/resource-deployment/src/services/resourceTypeService.ts b/extensions/resource-deployment/src/services/resourceTypeService.ts index d2751ba8cd..9fca6ab564 100644 --- a/extensions/resource-deployment/src/services/resourceTypeService.ts +++ b/extensions/resource-deployment/src/services/resourceTypeService.ts @@ -234,7 +234,7 @@ export class ResourceTypeService implements IResourceTypeService { const wizard = new DeployClusterWizard(provider.wizard, new KubeService(), new AzdataService(this.platformService), this.notebookService); wizard.open(); } else if (instanceOfDialogDeploymentProvider(provider)) { - const dialog = new DeploymentInputDialog(this.notebookService, provider.dialog); + const dialog = new DeploymentInputDialog(this.notebookService, this.platformService, provider.dialog); dialog.open(); } else if (instanceOfNotebookDeploymentProvider(provider)) { this.notebookService.launchNotebook(provider.notebook); diff --git a/extensions/resource-deployment/src/services/tools/toolBase.ts b/extensions/resource-deployment/src/services/tools/toolBase.ts index 103161835f..6aef929e1c 100644 --- a/extensions/resource-deployment/src/services/tools/toolBase.ts +++ b/extensions/resource-deployment/src/services/tools/toolBase.ts @@ -263,8 +263,10 @@ export abstract class ToolBase implements ITool { ); this.version = this.getVersionFromOutput(commandOutput); if (this.version) { - // discover and set the installationPath - await this.setInstallationPath(); + if (this.autoInstallSupported) { + // discover and set the installationPath + await this.setInstallationPath(); + } return ToolStatus.Installed; } else { diff --git a/extensions/resource-deployment/src/ui/deploymentInputDialog.ts b/extensions/resource-deployment/src/ui/deploymentInputDialog.ts index dc1733a065..7c206669fa 100644 --- a/extensions/resource-deployment/src/ui/deploymentInputDialog.ts +++ b/extensions/resource-deployment/src/ui/deploymentInputDialog.ts @@ -8,10 +8,12 @@ import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import { DialogBase } from './dialogBase'; import { INotebookService } from '../services/notebookService'; -import { DialogInfo, instanceOfNotebookBasedDialogInfo } from '../interfaces'; +import { DialogInfo, instanceOfNotebookBasedDialogInfo, NotebookBasedDialogInfo } from '../interfaces'; import { Validator, initializeDialog, InputComponents, setModelValues } from './modelViewUtils'; import { Model } from './model'; import { EOL } from 'os'; +import { getDateTimeString, getErrorMessage } from '../utils'; +import { IPlatformService } from '../services/platformService'; const localize = nls.loadMessageBundle(); @@ -20,9 +22,19 @@ export class DeploymentInputDialog extends DialogBase { private inputComponents: InputComponents = {}; constructor(private notebookService: INotebookService, + private platformService: IPlatformService, private dialogInfo: DialogInfo) { super(dialogInfo.title, dialogInfo.name, false); - this._dialogObject.okButton.label = localize('deploymentDialog.OKButtonText', "Open Notebook"); + let okButtonText: string; + if (dialogInfo.actionText) { + okButtonText = dialogInfo.actionText; + } else if (instanceOfNotebookBasedDialogInfo(dialogInfo) && !dialogInfo.runNotebook) { + okButtonText = localize('deploymentDialog.OpenNotebook', "Open Notebook"); + } else { + okButtonText = localize('deploymentDialog.OkButtonText', "OK"); + } + + this._dialogObject.okButton.label = okButtonText; } protected initialize() { @@ -63,11 +75,52 @@ export class DeploymentInputDialog extends DialogBase { setModelValues(this.inputComponents, model); if (instanceOfNotebookBasedDialogInfo(this.dialogInfo)) { model.setEnvironmentVariables(); - this.notebookService.launchNotebook(this.dialogInfo.notebook).then(() => { }, (error) => { - vscode.window.showErrorMessage(error); - }); + if (this.dialogInfo.runNotebook) { + this.executeNotebook(this.dialogInfo); + } else { + this.notebookService.launchNotebook(this.dialogInfo.notebook).then(() => { }, (error) => { + vscode.window.showErrorMessage(error); + }); + } } else { vscode.commands.executeCommand(this.dialogInfo.command, model); } } + + private executeNotebook(notebookDialogInfo: NotebookBasedDialogInfo): void { + azdata.tasks.startBackgroundOperation({ + displayName: notebookDialogInfo.taskName!, + description: notebookDialogInfo.taskName!, + isCancelable: false, + operation: async op => { + op.updateStatus(azdata.TaskStatus.InProgress); + const notebook = await this.notebookService.getNotebook(notebookDialogInfo.notebook); + const result = await this.notebookService.executeNotebook(notebook); + if (result.succeeded) { + op.updateStatus(azdata.TaskStatus.Succeeded); + } else { + op.updateStatus(azdata.TaskStatus.Failed, result.errorMessage); + if (result.outputNotebook) { + const viewErrorDetail = localize('resourceDeployment.ViewErrorDetail', "View error detail"); + const taskFailedMessage = localize('resourceDeployment.DeployFailed', "The task \"{0}\" has failed.", notebookDialogInfo.taskName); + const selectedOption = await vscode.window.showErrorMessage(taskFailedMessage, viewErrorDetail); + this.platformService.logToOutputChannel(taskFailedMessage); + if (selectedOption === viewErrorDetail) { + try { + this.notebookService.launchNotebookWithContent(`deploy-${getDateTimeString()}`, result.outputNotebook); + } catch (error) { + const launchNotebookError = localize('resourceDeployment.FailedToOpenNotebook', "An error occurred launching the output notebook. {1}{2}.", EOL, getErrorMessage(error)); + this.platformService.logToOutputChannel(launchNotebookError); + vscode.window.showErrorMessage(launchNotebookError); + } + } + } else { + const errorMessage = localize('resourceDeployment.TaskFailedWithNoOutputNotebook', "The task \"{0}\" failed and no output Notebook was generated.", notebookDialogInfo.taskName); + this.platformService.logToOutputChannel(errorMessage); + vscode.window.showErrorMessage(errorMessage); + } + } + } + }); + } } diff --git a/extensions/resource-deployment/src/utils.ts b/extensions/resource-deployment/src/utils.ts index 8c24a7f747..9a407a5f53 100644 --- a/extensions/resource-deployment/src/utils.ts +++ b/extensions/resource-deployment/src/utils.ts @@ -17,16 +17,21 @@ export function getDateTimeString(): string { } export function setEnvironmentVariablesForInstallPaths(tools: ITool[]): void { + // Use Set class to make sure the collection only contains unique values. let installationPaths: Set = new Set(); tools.forEach(t => { - // construct an env variable name with NoteBookEnvironmentVariablePrefix prefix - // and tool.name as suffix, making sure of using all uppercase characters and only _ as separator - const envVarName: string = `${NoteBookEnvironmentVariablePrefix}${t.name.toUpperCase().replace(/ |-/, '_')}`; - process.env[envVarName] = t.installationPath; - installationPaths.add(path.resolve(path.dirname(t.installationPath))); - console.log(`setting env var:'${envVarName}' to: '${t.installationPath}'`); + if (t.installationPath) { + // construct an env variable name with NoteBookEnvironmentVariablePrefix prefix + // and tool.name as suffix, making sure of using all uppercase characters and only _ as separator + const envVarName: string = `${NoteBookEnvironmentVariablePrefix}${t.name.toUpperCase().replace(/ |-/g, '_')}`; + process.env[envVarName] = t.installationPath; + installationPaths.add(path.resolve(path.dirname(t.installationPath))); + console.log(`setting env var:'${envVarName}' to: '${t.installationPath}'`); + } }); - const envVarToolsInstallationPath: string = [...installationPaths.values()].join(path.delimiter); - process.env[ToolsInstallPath] = envVarToolsInstallationPath; - console.log(`setting env var:'${ToolsInstallPath}' to: '${envVarToolsInstallationPath}'`); + if (installationPaths.size > 0) { + const envVarToolsInstallationPath: string = [...installationPaths.values()].join(path.delimiter); + process.env[ToolsInstallPath] = envVarToolsInstallationPath; + console.log(`setting env var:'${ToolsInstallPath}' to: '${envVarToolsInstallationPath}'`); + } }