mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
resource deployment ext implementation -wip (#5508)
* resource types * implement the dialog * remove unused method * fix issues * formatting * 5-17 * address comments and more tests
This commit is contained in:
@@ -3,3 +3,77 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export interface ResourceType {
|
||||
name: string;
|
||||
displayName: string;
|
||||
description: string;
|
||||
platforms: string[];
|
||||
icon: { light: string; dark: string };
|
||||
options: ResourceTypeOption[];
|
||||
providers: DeploymentProvider[];
|
||||
getProvider(selectedOptions: { option: string, value: string }[]): DeploymentProvider | undefined;
|
||||
}
|
||||
|
||||
export interface ResourceTypeOption {
|
||||
name: string;
|
||||
displayName: string;
|
||||
values: ResourceTypeOptionValue[];
|
||||
}
|
||||
|
||||
export interface ResourceTypeOptionValue {
|
||||
name: string;
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
export interface DeploymentProvider {
|
||||
notebook: string | NotebookInfo;
|
||||
requiredTools: ToolRequirementInfo[];
|
||||
when: string;
|
||||
}
|
||||
|
||||
export interface NotebookInfo {
|
||||
win32: string;
|
||||
darwin: string;
|
||||
linux: string;
|
||||
}
|
||||
|
||||
export interface ToolRequirementInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export enum ToolType {
|
||||
Unknown,
|
||||
AzCli,
|
||||
KubeCtl,
|
||||
Docker,
|
||||
Python,
|
||||
MSSQLCtl
|
||||
}
|
||||
|
||||
export interface ToolStatusInfo {
|
||||
type: ToolType;
|
||||
name: string;
|
||||
description: string;
|
||||
version: string;
|
||||
status: ToolInstallationStatus;
|
||||
}
|
||||
|
||||
export interface ITool {
|
||||
readonly name: string;
|
||||
readonly displayName: string;
|
||||
readonly description: string;
|
||||
readonly type: ToolType;
|
||||
readonly supportAutoInstall: boolean;
|
||||
|
||||
getInstallationStatus(versionExpression: string): Thenable<ToolInstallationStatus>;
|
||||
install(version: string): Thenable<void>;
|
||||
}
|
||||
|
||||
export enum ToolInstallationStatus {
|
||||
NotInstalled,
|
||||
Installed,
|
||||
Installing,
|
||||
FailedToInstall
|
||||
}
|
||||
@@ -6,15 +6,45 @@
|
||||
|
||||
import vscode = require('vscode');
|
||||
import { ResourceDeploymentDialog } from './ui/resourceDeploymentDialog';
|
||||
import { ToolsService } from './services/toolsService';
|
||||
import { NotebookService } from './services/notebookService';
|
||||
import { ResourceTypeService } from './services/resourceTypeService';
|
||||
import { PlatformService } from './services/platformService';
|
||||
import * as nls from 'vscode-nls';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
const platformService = new PlatformService();
|
||||
const toolsService = new ToolsService();
|
||||
const notebookService = new NotebookService(platformService);
|
||||
const resourceTypeService = new ResourceTypeService(platformService, toolsService);
|
||||
|
||||
const resourceTypes = resourceTypeService.getResourceTypes();
|
||||
const validationFailures = resourceTypeService.validateResourceTypes(resourceTypes);
|
||||
if (validationFailures.length !== 0) {
|
||||
const errorMessage = localize('resourceDeployment.FailedToLoadExtension', 'Failed to load extension: {0}, Error detected in the resource type definition in package.json, check debug console for details.', context.extensionPath);
|
||||
vscode.window.showErrorMessage(errorMessage);
|
||||
validationFailures.forEach(message => console.error(message));
|
||||
return;
|
||||
}
|
||||
|
||||
const openDialog = (resourceTypeName: string) => {
|
||||
const filtered = resourceTypes.filter(resourceType => resourceType.name === resourceTypeName);
|
||||
if (filtered.length !== 1) {
|
||||
vscode.window.showErrorMessage(localize('resourceDeployment.UnknownResourceType', 'The resource type: {0} is not defined', resourceTypeName));
|
||||
}
|
||||
else {
|
||||
let dialog = new ResourceDeploymentDialog(context, notebookService, toolsService, resourceTypeService, filtered[0]);
|
||||
dialog.open();
|
||||
}
|
||||
};
|
||||
|
||||
vscode.commands.registerCommand('azdata.resource.sql-image.deploy', () => {
|
||||
let dialog = new ResourceDeploymentDialog();
|
||||
dialog.open();
|
||||
openDialog('sql-image');
|
||||
});
|
||||
vscode.commands.registerCommand('azdata.resource.sql-bdc.deploy', () => {
|
||||
let dialog = new ResourceDeploymentDialog();
|
||||
dialog.open();
|
||||
openDialog('sql-bdc');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { NotebookInfo } from '../interfaces';
|
||||
import { isString } from 'util';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { IPlatformService } from './platformService';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export interface INotebookService {
|
||||
launchNotebook(notebook: string | NotebookInfo): void;
|
||||
}
|
||||
|
||||
export class NotebookService implements INotebookService {
|
||||
|
||||
constructor(private platformService: IPlatformService) { }
|
||||
|
||||
/**
|
||||
* Copy the notebook to the user's home directory and launch the notebook from there.
|
||||
* @param notebook the path of the notebook
|
||||
*/
|
||||
launchNotebook(notebook: string | NotebookInfo): void {
|
||||
const notebookRelativePath = this.getNotebook(notebook);
|
||||
const notebookFullPath = path.join(__dirname, '../../', notebookRelativePath);
|
||||
if (notebookRelativePath && this.platformService.fileExists(notebookFullPath)) {
|
||||
const targetFileName = this.getTargetNotebookFileName(notebookFullPath, os.homedir());
|
||||
this.platformService.copyFile(notebookFullPath, targetFileName);
|
||||
this.platformService.openFile(targetFileName);
|
||||
}
|
||||
else {
|
||||
this.platformService.showErrorMessage(localize('resourceDeployment.notebookNotFound', 'The notebook {0} does not exist', notebookFullPath));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get the notebook path for current platform
|
||||
* @param notebook the notebook path
|
||||
*/
|
||||
getNotebook(notebook: string | NotebookInfo): string {
|
||||
let notebookPath;
|
||||
if (notebook && !isString(notebook)) {
|
||||
const platform = this.platformService.platform();
|
||||
if (platform === 'win32') {
|
||||
notebookPath = notebook.win32;
|
||||
} else if (platform === 'darwin') {
|
||||
notebookPath = notebook.darwin;
|
||||
} else {
|
||||
notebookPath = notebook.linux;
|
||||
}
|
||||
} else {
|
||||
notebookPath = notebook;
|
||||
}
|
||||
return notebookPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a file name that is not already used in the target directory
|
||||
* @param notebook source notebook file name
|
||||
* @param targetDirectory target directory
|
||||
*/
|
||||
getTargetNotebookFileName(notebook: string, targetDirectory: string): string {
|
||||
const notebookFileExtension = '.ipynb';
|
||||
const baseName = path.basename(notebook, notebookFileExtension);
|
||||
let targetFileName;
|
||||
let idx = 0;
|
||||
do {
|
||||
const suffix = idx === 0 ? '' : `-${idx}`;
|
||||
targetFileName = path.join(targetDirectory, `${baseName}${suffix}${notebookFileExtension}`);
|
||||
idx++;
|
||||
} while (this.platformService.fileExists(targetFileName));
|
||||
|
||||
return targetFileName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
/**
|
||||
* Abstract of platform dependencies
|
||||
*/
|
||||
export interface IPlatformService {
|
||||
platform(): string;
|
||||
copyFile(source: string, target: string): void;
|
||||
fileExists(file: string): boolean;
|
||||
openFile(filePath: string): void;
|
||||
showErrorMessage(message: string): void;
|
||||
}
|
||||
|
||||
export class PlatformService implements IPlatformService {
|
||||
platform(): string {
|
||||
return process.platform;
|
||||
}
|
||||
|
||||
copyFile(source: string, target: string): void {
|
||||
fs.copyFileSync(source, target);
|
||||
}
|
||||
|
||||
fileExists(file: string): boolean {
|
||||
return fs.existsSync(file);
|
||||
}
|
||||
|
||||
openFile(filePath: string): void {
|
||||
vscode.commands.executeCommand('vscode.open', vscode.Uri.file(filePath));
|
||||
}
|
||||
|
||||
showErrorMessage(message: string): void {
|
||||
vscode.window.showErrorMessage(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { ResourceType, ResourceTypeOption, DeploymentProvider } from '../interfaces';
|
||||
import { IToolsService } from './toolsService';
|
||||
import * as vscode from 'vscode';
|
||||
import { IPlatformService } from './platformService';
|
||||
|
||||
export interface IResourceTypeService {
|
||||
getResourceTypes(filterByPlatform?: boolean): ResourceType[];
|
||||
validateResourceTypes(resourceTypes: ResourceType[]): string[];
|
||||
}
|
||||
|
||||
export class ResourceTypeService implements IResourceTypeService {
|
||||
private _resourceTypes: ResourceType[] = [];
|
||||
|
||||
constructor(private platformService: IPlatformService, private toolsService: IToolsService) { }
|
||||
|
||||
/**
|
||||
* Get the supported resource types
|
||||
* @param filterByPlatform indicates whether to return the resource types supported on current platform.
|
||||
*/
|
||||
getResourceTypes(filterByPlatform: boolean = true): ResourceType[] {
|
||||
if (this._resourceTypes.length === 0) {
|
||||
// If we load package.json directly using require(path) the contents won't be localized
|
||||
this._resourceTypes = vscode.extensions.getExtension('microsoft.resource-deployment')!.packageJSON.resourceTypes as ResourceType[];
|
||||
this._resourceTypes.forEach(resourceType => {
|
||||
resourceType.getProvider = (selectedOptions) => { return this.getProvider(resourceType, selectedOptions); };
|
||||
});
|
||||
}
|
||||
|
||||
let resourceTypes = this._resourceTypes;
|
||||
if (filterByPlatform) {
|
||||
resourceTypes = resourceTypes.filter(resourceType => resourceType.platforms.includes(this.platformService.platform()));
|
||||
}
|
||||
|
||||
return resourceTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the resource types and returns validation error messages if any.
|
||||
* @param resourceTypes resource types to be validated
|
||||
*/
|
||||
validateResourceTypes(resourceTypes: ResourceType[]): string[] {
|
||||
// NOTE: The validation error messages do not need to be localized as it is only meant for the developer's use.
|
||||
const errorMessages: string[] = [];
|
||||
if (!resourceTypes || resourceTypes.length === 0) {
|
||||
errorMessages.push('Resource type list is empty');
|
||||
} else {
|
||||
let resourceTypeIndex = 1;
|
||||
resourceTypes.forEach(resourceType => {
|
||||
this.validateResourceType(resourceType, `resource type index: ${resourceTypeIndex}`, errorMessages);
|
||||
resourceTypeIndex++;
|
||||
});
|
||||
}
|
||||
|
||||
return errorMessages;
|
||||
}
|
||||
|
||||
private validateResourceType(resourceType: ResourceType, positionInfo: string, errorMessages: string[]): void {
|
||||
this.validateNameDisplayName(resourceType, 'resource type', positionInfo, errorMessages);
|
||||
if (!resourceType.icon || !resourceType.icon.dark || !resourceType.icon.light) {
|
||||
errorMessages.push(`Icon for resource type is not specified properly. ${positionInfo} `);
|
||||
}
|
||||
|
||||
if (resourceType.options && resourceType.options.length > 0) {
|
||||
let optionIndex = 1;
|
||||
resourceType.options.forEach(option => {
|
||||
const optionInfo = `${positionInfo}, option index: ${optionIndex} `;
|
||||
this.validateResourceTypeOption(option, optionInfo, errorMessages);
|
||||
optionIndex++;
|
||||
});
|
||||
}
|
||||
|
||||
this.validateProviders(resourceType, positionInfo, errorMessages);
|
||||
}
|
||||
|
||||
private validateResourceTypeOption(option: ResourceTypeOption, positionInfo: string, errorMessages: string[]): void {
|
||||
this.validateNameDisplayName(option, 'option', positionInfo, errorMessages);
|
||||
if (!option.values || option.values.length === 0) {
|
||||
errorMessages.push(`Option contains no values.${positionInfo} `);
|
||||
} else {
|
||||
let optionValueIndex = 1;
|
||||
option.values.forEach(optionValue => {
|
||||
const optionValueInfo = `${positionInfo}, option value index: ${optionValueIndex} `;
|
||||
this.validateNameDisplayName(optionValue, 'option value', optionValueInfo, errorMessages);
|
||||
optionValueIndex++;
|
||||
});
|
||||
|
||||
// Make sure the values are unique
|
||||
for (let i = 0; i < option.values.length; i++) {
|
||||
if (option.values[i].name && option.values[i].displayName) {
|
||||
let dupePositions = [];
|
||||
for (let j = i + 1; j < option.values.length; j++) {
|
||||
if (option.values[i].name === option.values[j].name
|
||||
|| option.values[i].displayName === option.values[j].displayName) {
|
||||
// +1 to make the position 1 based.
|
||||
dupePositions.push(j + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (dupePositions.length !== 0) {
|
||||
errorMessages.push(`Option values with same name or display name are found at the following positions: ${i + 1}, ${dupePositions.join(',')}.${positionInfo} `);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private validateProviders(resourceType: ResourceType, positionInfo: string, errorMessages: string[]): void {
|
||||
if (!resourceType.providers || resourceType.providers.length === 0) {
|
||||
errorMessages.push(`No providers defined for resource type, ${positionInfo}`);
|
||||
} else {
|
||||
let providerIndex = 1;
|
||||
resourceType.providers.forEach(provider => {
|
||||
const providerPositionInfo = `${positionInfo}, provider index: ${providerIndex} `;
|
||||
if (!provider.notebook) {
|
||||
errorMessages.push(`Notebook is not specified for the provider, ${providerPositionInfo}`);
|
||||
}
|
||||
|
||||
if (provider.requiredTools && provider.requiredTools.length > 0) {
|
||||
provider.requiredTools.forEach(tool => {
|
||||
if (!this.toolsService.getToolByName(tool.name)) {
|
||||
errorMessages.push(`The tool is not supported: ${tool.name}, ${providerPositionInfo} `);
|
||||
}
|
||||
});
|
||||
}
|
||||
providerIndex++;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private validateNameDisplayName(obj: { name: string; displayName: string }, type: string, positionInfo: string, errorMessages: string[]): void {
|
||||
if (!obj.name) {
|
||||
errorMessages.push(`Name of the ${type} is empty.${positionInfo} `);
|
||||
}
|
||||
if (!obj.displayName) {
|
||||
errorMessages.push(`Display name of the ${type} is empty.${positionInfo} `);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the provider based on the selected options
|
||||
*/
|
||||
private getProvider(resourceType: ResourceType, selectedOptions: { option: string, value: string }[]): DeploymentProvider | undefined {
|
||||
for (let i = 0; i < resourceType.providers.length; i++) {
|
||||
const provider = resourceType.providers[i];
|
||||
|
||||
const expected = provider.when.replace(' ', '').split('&&').sort();
|
||||
let actual: string[] = [];
|
||||
selectedOptions.forEach(option => {
|
||||
actual.push(`${option.option}=${option.value}`);
|
||||
});
|
||||
actual = actual.sort();
|
||||
|
||||
if (actual.length === expected.length) {
|
||||
let matches = true;
|
||||
for (let j = 0; j < actual.length; j++) {
|
||||
if (actual[j] !== expected[j]) {
|
||||
matches = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matches) {
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
import { ToolType, ITool, ToolInstallationStatus } from '../../interfaces';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class AzCliTool implements ITool {
|
||||
get name(): string {
|
||||
return 'azcli';
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return localize('resourceDeployment.AzCLIDescription', 'Tool used for managing Azure services');
|
||||
}
|
||||
|
||||
get type(): ToolType {
|
||||
return ToolType.AzCli;
|
||||
}
|
||||
|
||||
get displayName(): string {
|
||||
return localize('resourceDeployment.AzCLIDisplayName', 'Azure CLI');
|
||||
}
|
||||
|
||||
get supportAutoInstall(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
install(version: string): Thenable<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getInstallationStatus(versionExpression: string): Thenable<ToolInstallationStatus> {
|
||||
let promise = new Promise<ToolInstallationStatus>(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve(ToolInstallationStatus.Installed);
|
||||
}, 500);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
import { ToolType, ITool, ToolInstallationStatus } from '../../interfaces';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
||||
export class DockerTool implements ITool {
|
||||
get name(): string {
|
||||
return 'docker';
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return localize('resourceDeployment.DockerDescription', 'Manages the containers');
|
||||
}
|
||||
|
||||
get type(): ToolType {
|
||||
return ToolType.Docker;
|
||||
}
|
||||
|
||||
get displayName(): string {
|
||||
return localize('resourceDeployment.DockerDisplayName', 'Docker');
|
||||
}
|
||||
|
||||
get supportAutoInstall(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
install(version: string): Thenable<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getInstallationStatus(versionExpression: string): Thenable<ToolInstallationStatus> {
|
||||
let promise = new Promise<ToolInstallationStatus>(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve(ToolInstallationStatus.Installed);
|
||||
}, 500);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
import { ToolType, ITool, ToolInstallationStatus } from '../../interfaces';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class KubeCtlTool implements ITool {
|
||||
get name(): string {
|
||||
return 'kubectl';
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return localize('resourceDeployment.KUBECTLDescription', 'Tool used for managing the Kubernetes cluster');
|
||||
}
|
||||
|
||||
get type(): ToolType {
|
||||
return ToolType.KubeCtl;
|
||||
}
|
||||
|
||||
get displayName(): string {
|
||||
return localize('resourceDeployment.KUBECTLDisplayName', 'kubectl');
|
||||
}
|
||||
|
||||
get supportAutoInstall(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
install(version: string): Thenable<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getInstallationStatus(versionExpression: string): Thenable<ToolInstallationStatus> {
|
||||
let promise = new Promise<ToolInstallationStatus>(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve(ToolInstallationStatus.Installed);
|
||||
}, 500);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
import { ToolType, ITool, ToolInstallationStatus } from '../../interfaces';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class MSSQLCtlTool implements ITool {
|
||||
get name(): string {
|
||||
return 'mssqlctl';
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return localize('resourceDeployment.MSSQLCTLDescription', 'Command-line tool for installing and managing the SQL Server big data cluster');
|
||||
}
|
||||
|
||||
get type(): ToolType {
|
||||
return ToolType.MSSQLCtl;
|
||||
}
|
||||
|
||||
get displayName(): string {
|
||||
return localize('resourceDeployment.MSSQLCTLDisplayName', 'mssqlctl');
|
||||
}
|
||||
|
||||
isInstalled(versionExpression: string): Thenable<boolean> {
|
||||
let promise = new Promise<boolean>(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve(true);
|
||||
}, 500);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
get supportAutoInstall(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
install(version: string): Thenable<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getInstallationStatus(versionExpression: string): Thenable<ToolInstallationStatus> {
|
||||
let promise = new Promise<ToolInstallationStatus>(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve(ToolInstallationStatus.Installed);
|
||||
}, 500);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
import { ToolType, ITool, ToolInstallationStatus } from '../../interfaces';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
||||
export class PythonTool implements ITool {
|
||||
get name(): string {
|
||||
return 'python';
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return localize('resourceDeployment.PythonDescription', 'Required by notebook feature');
|
||||
}
|
||||
|
||||
get type(): ToolType {
|
||||
return ToolType.Python;
|
||||
}
|
||||
|
||||
get displayName(): string {
|
||||
return localize('resourceDeployment.PythonDisplayName', 'Python');
|
||||
}
|
||||
|
||||
get supportAutoInstall(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
install(version: string): Thenable<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getInstallationStatus(versionExpression: string): Thenable<ToolInstallationStatus> {
|
||||
let promise = new Promise<ToolInstallationStatus>(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve(ToolInstallationStatus.Installed);
|
||||
}, 500);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
52
extensions/resource-deployment/src/services/toolsService.ts
Normal file
52
extensions/resource-deployment/src/services/toolsService.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
import { ToolRequirementInfo, ToolStatusInfo, ITool } from '../interfaces';
|
||||
import { PythonTool } from './tools/pythonTool';
|
||||
import { DockerTool } from './tools/dockerTool';
|
||||
import { AzCliTool } from './tools/azCliTool';
|
||||
import { MSSQLCtlTool } from './tools/mssqlCtlTool';
|
||||
import { KubeCtlTool } from './tools/kubeCtlTool';
|
||||
|
||||
export interface IToolsService {
|
||||
getToolStatus(toolRequirements: ToolRequirementInfo[]): Thenable<ToolStatusInfo[]>;
|
||||
getToolByName(toolName: string): ITool | undefined;
|
||||
}
|
||||
|
||||
export class ToolsService implements IToolsService {
|
||||
private static readonly SupportedTools: ITool[] = [new PythonTool(), new DockerTool(), new AzCliTool(), new MSSQLCtlTool(), new KubeCtlTool()];
|
||||
|
||||
getToolStatus(toolRequirements: ToolRequirementInfo[]): Thenable<ToolStatusInfo[]> {
|
||||
const toolStatusList: ToolStatusInfo[] = [];
|
||||
let promises = [];
|
||||
for (let i = 0; i < toolRequirements.length; i++) {
|
||||
const toolRequirement = toolRequirements[i];
|
||||
const tool = this.getToolByName(toolRequirement.name);
|
||||
if (tool !== undefined) {
|
||||
promises.push(tool.getInstallationStatus(toolRequirement.version).then(installStatus => {
|
||||
toolStatusList.push(<ToolStatusInfo>{
|
||||
name: tool.displayName,
|
||||
description: tool.description,
|
||||
status: installStatus,
|
||||
version: toolRequirement.version
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
return Promise.all(promises).then(() => { return toolStatusList; });
|
||||
}
|
||||
|
||||
getToolByName(toolName: string): ITool | undefined {
|
||||
if (toolName) {
|
||||
for (let i = 0; i < ToolsService.SupportedTools.length; i++) {
|
||||
if (toolName === ToolsService.SupportedTools[i].name) {
|
||||
return ToolsService.SupportedTools[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
}
|
||||
30
extensions/resource-deployment/src/test/index.ts
Normal file
30
extensions/resource-deployment/src/test/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
const path = require('path');
|
||||
const testRunner = require('vscode/lib/testrunner');
|
||||
|
||||
const suite = 'Resource Deployment Unit Tests';
|
||||
|
||||
const testOptions: any = {
|
||||
ui: 'tdd',
|
||||
useColors: true,
|
||||
timeout: 60000
|
||||
};
|
||||
|
||||
if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
|
||||
testOptions.reporter = 'mocha-multi-reporters';
|
||||
testOptions.reporterOptions = {
|
||||
reporterEnabled: 'spec, mocha-junit-reporter',
|
||||
mochaJunitReporterReporterOptions: {
|
||||
testsuitesTitle: `${suite} ${process.platform}`,
|
||||
mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
testRunner.configure(testOptions);
|
||||
|
||||
export = testRunner;
|
||||
130
extensions/resource-deployment/src/test/notebookService.test.ts
Normal file
130
extensions/resource-deployment/src/test/notebookService.test.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import 'mocha';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { NotebookService } from '../services/NotebookService';
|
||||
import assert = require('assert');
|
||||
import { NotebookInfo } from '../interfaces';
|
||||
import { IPlatformService } from '../services/platformService';
|
||||
|
||||
suite('Notebook Service Tests', function (): void {
|
||||
|
||||
test('getNotebook with string parameter', () => {
|
||||
const mockPlatformService = TypeMoq.Mock.ofType<IPlatformService>();
|
||||
const notebookService = new NotebookService(mockPlatformService.object);
|
||||
const notebookInput = 'test-notebook.ipynb';
|
||||
mockPlatformService.setup((service) => service.platform()).returns(() => { return 'win32'; });
|
||||
let returnValue = notebookService.getNotebook(notebookInput);
|
||||
assert.equal(returnValue, notebookInput, 'returned notebook name does not match expected value');
|
||||
mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.never());
|
||||
|
||||
mockPlatformService.reset();
|
||||
mockPlatformService.setup((service) => service.platform()).returns(() => { return 'win32'; });
|
||||
returnValue = notebookService.getNotebook('');
|
||||
assert.equal(returnValue, '', 'returned notebook name does not match expected value is not an empty string');
|
||||
mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.never());
|
||||
});
|
||||
|
||||
test('getNotebook with NotebookInfo parameter', () => {
|
||||
const mockPlatformService = TypeMoq.Mock.ofType<IPlatformService>();
|
||||
const notebookService = new NotebookService(mockPlatformService.object);
|
||||
const notebookWin32 = 'test-notebook-win32.ipynb';
|
||||
const notebookDarwin = 'test-notebook-darwin.ipynb';
|
||||
const notebookLinux = 'test-notebook-linux.ipynb';
|
||||
|
||||
const notebookInput: NotebookInfo = {
|
||||
darwin: notebookDarwin,
|
||||
win32: notebookWin32,
|
||||
linux: notebookLinux
|
||||
};
|
||||
mockPlatformService.setup((service) => service.platform()).returns(() => { return 'win32'; });
|
||||
let returnValue = notebookService.getNotebook(notebookInput);
|
||||
assert.equal(returnValue, notebookWin32, 'returned notebook name does not match expected value for win32 platform');
|
||||
mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.once());
|
||||
|
||||
mockPlatformService.reset();
|
||||
mockPlatformService.setup((service) => service.platform()).returns(() => { return 'darwin'; });
|
||||
returnValue = notebookService.getNotebook(notebookInput);
|
||||
assert.equal(returnValue, notebookDarwin, 'returned notebook name does not match expected value for darwin platform');
|
||||
mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.once());
|
||||
|
||||
mockPlatformService.reset();
|
||||
mockPlatformService.setup((service) => service.platform()).returns(() => { return 'linux'; });
|
||||
returnValue = notebookService.getNotebook(notebookInput);
|
||||
assert.equal(returnValue, notebookLinux, 'returned notebook name does not match expected value for linux platform');
|
||||
mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.once());
|
||||
});
|
||||
|
||||
test('launchNotebook', () => {
|
||||
const mockPlatformService = TypeMoq.Mock.ofType<IPlatformService>();
|
||||
const notebookService = new NotebookService(mockPlatformService.object);
|
||||
const notebookFileName = 'mynotebook.ipynb';
|
||||
const notebookPath = `./notebooks/${notebookFileName}`;
|
||||
|
||||
let actualSourceFile;
|
||||
const expectedSourceFile = path.join(__dirname, '../../', notebookPath);
|
||||
let actualTargetFile;
|
||||
const expectedTargetFile = path.join(os.homedir(), notebookFileName);
|
||||
mockPlatformService.setup((service) => service.platform()).returns(() => { return 'win32'; });
|
||||
mockPlatformService.setup((service) => service.openFile(TypeMoq.It.isAnyString()));
|
||||
mockPlatformService.setup((service) => service.fileExists(TypeMoq.It.isAnyString()))
|
||||
.returns((path) => {
|
||||
if (path === expectedSourceFile) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
mockPlatformService.setup((service) => service.copyFile(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString()))
|
||||
.returns((source, target) => { actualSourceFile = source; actualTargetFile = target; });
|
||||
notebookService.launchNotebook(notebookPath);
|
||||
mockPlatformService.verify((service) => service.copyFile(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString()), TypeMoq.Times.once());
|
||||
mockPlatformService.verify((service) => service.openFile(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
|
||||
assert.equal(actualSourceFile, expectedSourceFile, 'source file is not correct');
|
||||
assert.equal(actualTargetFile, expectedTargetFile, 'target file is not correct');
|
||||
});
|
||||
|
||||
test('getTargetNotebookFileName with no name conflict', () => {
|
||||
const mockPlatformService = TypeMoq.Mock.ofType<IPlatformService>();
|
||||
const notebookService = new NotebookService(mockPlatformService.object);
|
||||
const notebookFileName = 'mynotebook.ipynb';
|
||||
const sourceNotebookPath = `./notebooks/${notebookFileName}`;
|
||||
|
||||
const expectedTargetFile = path.join(os.homedir(), notebookFileName);
|
||||
mockPlatformService.setup((service) => service.fileExists(TypeMoq.It.isAnyString()))
|
||||
.returns((path) => { return false; });
|
||||
const actualFileName = notebookService.getTargetNotebookFileName(sourceNotebookPath, os.homedir());
|
||||
mockPlatformService.verify((service) => service.fileExists(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
|
||||
assert.equal(actualFileName, expectedTargetFile, 'target file name is not correct');
|
||||
});
|
||||
|
||||
test('getTargetNotebookFileName with name conflicts', () => {
|
||||
const mockPlatformService = TypeMoq.Mock.ofType<IPlatformService>();
|
||||
const notebookService = new NotebookService(mockPlatformService.object);
|
||||
const notebookFileName = 'mynotebook.ipynb';
|
||||
const sourceNotebookPath = `./notebooks/${notebookFileName}`;
|
||||
const expectedFileName = 'mynotebook-2.ipynb';
|
||||
|
||||
const expected1stAttemptTargetFile = path.join(os.homedir(), notebookFileName);
|
||||
const expected2ndAttemptTargetFile = path.join(os.homedir(), 'mynotebook-1.ipynb');
|
||||
const expectedTargetFile = path.join(os.homedir(), expectedFileName);
|
||||
mockPlatformService.setup((service) => service.fileExists(TypeMoq.It.isAnyString()))
|
||||
.returns((path) => {
|
||||
// list all the possible values here and handle them
|
||||
// if we only handle the expected value and return true for anything else, the test might run forever until times out
|
||||
if (path === expected1stAttemptTargetFile || path === expected2ndAttemptTargetFile) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
const actualFileName = notebookService.getTargetNotebookFileName(sourceNotebookPath, os.homedir());
|
||||
mockPlatformService.verify((service) => service.fileExists(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(3));
|
||||
assert.equal(actualFileName, expectedTargetFile, 'target file name is not correct');
|
||||
});
|
||||
});
|
||||
@@ -6,26 +6,210 @@
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { IResourceTypeService } from '../services/resourceTypeService';
|
||||
import * as vscode from 'vscode';
|
||||
import { ResourceType, DeploymentProvider, ToolInstallationStatus } from '../interfaces';
|
||||
import { IToolsService } from '../services/toolsService';
|
||||
import { INotebookService } from '../services/notebookService';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class ResourceDeploymentDialog {
|
||||
private dialogObject: azdata.window.Dialog;
|
||||
private _selectedResourceType: ResourceType;
|
||||
private _toDispose: vscode.Disposable[] = [];
|
||||
private _dialogObject: azdata.window.Dialog;
|
||||
private _resourceTypeCards: azdata.CardComponent[] = [];
|
||||
private _view!: azdata.ModelView;
|
||||
private _resourceDescriptionLabel!: azdata.TextComponent;
|
||||
private _optionsContainer!: azdata.FlexContainer;
|
||||
private _toolsTable!: azdata.TableComponent;
|
||||
private _cardResourceTypeMap: Map<string, azdata.CardComponent> = new Map();
|
||||
private _optionDropDownMap: Map<string, azdata.DropDownComponent> = new Map();
|
||||
|
||||
constructor() {
|
||||
this.dialogObject = azdata.window.createModelViewDialog(localize('deploymentDialog.title', 'Install SQL Server'), 'resourceDeploymentDialog', true);
|
||||
constructor(private context: vscode.ExtensionContext,
|
||||
private notebookService: INotebookService,
|
||||
private toolsService: IToolsService,
|
||||
private resourceTypeService: IResourceTypeService,
|
||||
resourceType: ResourceType) {
|
||||
this._selectedResourceType = resourceType;
|
||||
this._dialogObject = azdata.window.createModelViewDialog(localize('deploymentDialog.title', 'Select a configuration'), 'resourceDeploymentDialog', true);
|
||||
this._dialogObject.cancelButton.onClick(() => this.onCancel());
|
||||
this._dialogObject.okButton.label = localize('deploymentDialog.OKButtonText', 'Select');
|
||||
this._dialogObject.okButton.onClick(() => this.onComplete());
|
||||
}
|
||||
|
||||
private initializeDialog() {
|
||||
let tab = azdata.window.createTab('');
|
||||
tab.registerContent((view: azdata.ModelView) => {
|
||||
let text = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: 'place holder' }).component();
|
||||
return view.initializeModel(text);
|
||||
this._view = view;
|
||||
this.resourceTypeService.getResourceTypes().forEach(resourceType => this.addCard(resourceType));
|
||||
const cardsContainer = view.modelBuilder.flexContainer().withItems(this._resourceTypeCards, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '10px' } }).withLayout({ flexFlow: 'row', alignItems: 'left' }).component();
|
||||
this._resourceDescriptionLabel = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: this._selectedResourceType ? this._selectedResourceType.description : undefined }).component();
|
||||
this._optionsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
||||
|
||||
const toolColumn: azdata.TableColumn = {
|
||||
value: localize('deploymentDialog.toolNameColumnHeader', 'Tool'),
|
||||
width: 100
|
||||
};
|
||||
const descriptionColumn: azdata.TableColumn = {
|
||||
value: localize('deploymentDialog.toolDescriptionColumnHeader', 'Description'),
|
||||
width: 500
|
||||
};
|
||||
const versionColumn: azdata.TableColumn = {
|
||||
value: localize('deploymentDialog.toolVersionColumnHeader', 'Version'),
|
||||
width: 200
|
||||
};
|
||||
const statusColumn: azdata.TableColumn = {
|
||||
value: localize('deploymentDialog.toolStatusColumnHeader', 'Status'),
|
||||
width: 200
|
||||
};
|
||||
|
||||
this._toolsTable = view.modelBuilder.table().withProperties<azdata.TableComponentProperties>({
|
||||
height: 150,
|
||||
data: [],
|
||||
columns: [toolColumn, descriptionColumn, versionColumn, statusColumn],
|
||||
width: 1000
|
||||
}).component();
|
||||
|
||||
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
[
|
||||
{
|
||||
component: cardsContainer,
|
||||
title: ''
|
||||
}, {
|
||||
component: this._resourceDescriptionLabel,
|
||||
title: ''
|
||||
}, {
|
||||
component: this._optionsContainer,
|
||||
title: localize('deploymentDialog.OptionsTitle', 'Options')
|
||||
}, {
|
||||
component: this._toolsTable,
|
||||
title: localize('deploymentDialog.RequiredToolsTitle', 'Required tools')
|
||||
}
|
||||
],
|
||||
{
|
||||
horizontal: false
|
||||
}
|
||||
);
|
||||
|
||||
const form = formBuilder.withLayout({ width: '100%' }).component();
|
||||
|
||||
if (this._selectedResourceType) {
|
||||
this.selectResourceType(this._selectedResourceType);
|
||||
}
|
||||
|
||||
return view.initializeModel(form);
|
||||
});
|
||||
this.dialogObject.content = [tab];
|
||||
this._dialogObject.content = [tab];
|
||||
}
|
||||
|
||||
public open(): void {
|
||||
this.initializeDialog();
|
||||
azdata.window.openDialog(this.dialogObject);
|
||||
azdata.window.openDialog(this._dialogObject);
|
||||
}
|
||||
|
||||
private addCard(resourceType: ResourceType): void {
|
||||
const card = this._view.modelBuilder.card().withProperties<azdata.CardProperties>({
|
||||
cardType: azdata.CardType.VerticalButton,
|
||||
iconPath: {
|
||||
dark: this.context.asAbsolutePath(resourceType.icon.dark),
|
||||
light: this.context.asAbsolutePath(resourceType.icon.light)
|
||||
},
|
||||
label: resourceType.displayName,
|
||||
selected: (this._selectedResourceType && this._selectedResourceType.name === resourceType.name)
|
||||
}).component();
|
||||
|
||||
this._resourceTypeCards.push(card);
|
||||
this._cardResourceTypeMap.set(resourceType.name, card);
|
||||
this._toDispose.push(card.onCardSelectedChanged(() => this.selectResourceType(resourceType)));
|
||||
}
|
||||
|
||||
private selectResourceType(resourceType: ResourceType): void {
|
||||
this._selectedResourceType = resourceType;
|
||||
const card = this._cardResourceTypeMap.get(this._selectedResourceType.name)!;
|
||||
if (card.selected) {
|
||||
// clear the selected state of the previously selected card
|
||||
this._resourceTypeCards.forEach(c => {
|
||||
if (c !== card) {
|
||||
c.selected = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// keep the selected state if no other card is selected
|
||||
if (this._resourceTypeCards.filter(c => { return c !== card && c.selected; }).length === 0) {
|
||||
card.selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
this._resourceDescriptionLabel.value = resourceType.description;
|
||||
this._optionsContainer.clearItems();
|
||||
this._optionDropDownMap.clear();
|
||||
resourceType.options.forEach(option => {
|
||||
const optionLabel = this._view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: option.displayName
|
||||
}).component();
|
||||
optionLabel.width = '150px';
|
||||
|
||||
const optionSelectBox = this._view.modelBuilder.dropDown().withProperties<azdata.DropDownProperties>({
|
||||
values: option.values,
|
||||
value: option.values[0],
|
||||
width: '300px'
|
||||
}).component();
|
||||
|
||||
this._toDispose.push(optionSelectBox.onValueChanged(() => { this.updateTools(); }));
|
||||
this._optionDropDownMap.set(option.name, optionSelectBox);
|
||||
const row = this._view.modelBuilder.flexContainer().withItems([optionLabel, optionSelectBox], { flex: '0 0 auto', CSSStyles: { 'margin-right': '20px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
|
||||
this._optionsContainer.addItem(row);
|
||||
});
|
||||
this.updateTools();
|
||||
}
|
||||
|
||||
private updateTools(): void {
|
||||
this.toolsService.getToolStatus(this.getCurrentProvider().requiredTools).then(toolStatus => {
|
||||
let tableData = toolStatus.map(tool => {
|
||||
return [tool.name, tool.description, tool.version, this.getToolStatusText(tool.status)];
|
||||
});
|
||||
this._toolsTable.data = tableData;
|
||||
});
|
||||
}
|
||||
|
||||
private getToolStatusText(status: ToolInstallationStatus): string {
|
||||
switch (status) {
|
||||
case ToolInstallationStatus.Installed:
|
||||
return '✔️ ' + localize('deploymentDialog.InstalledText', 'Installed');
|
||||
case ToolInstallationStatus.NotInstalled:
|
||||
return '❌ ' + localize('deploymentDialog.NotInstalledText', 'Not Installed');
|
||||
case ToolInstallationStatus.Installing:
|
||||
return '⌛ ' + localize('deploymentDialog.InstallingText', 'Installing…');
|
||||
case ToolInstallationStatus.FailedToInstall:
|
||||
return '❌ ' + localize('deploymentDialog.FailedToInstallText', 'Install Failed');
|
||||
default:
|
||||
return 'unknown status';
|
||||
}
|
||||
}
|
||||
|
||||
private getCurrentProvider(): DeploymentProvider {
|
||||
const options: { option: string, value: string }[] = [];
|
||||
|
||||
this._optionDropDownMap.forEach((selectBox, option) => {
|
||||
let selectedValue: azdata.CategoryValue = selectBox.value as azdata.CategoryValue;
|
||||
options.push({ option: option, value: selectedValue.name });
|
||||
});
|
||||
|
||||
return this._selectedResourceType.getProvider(options)!;
|
||||
}
|
||||
|
||||
private onCancel(): void {
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
private onComplete(): void {
|
||||
const provider = this.getCurrentProvider();
|
||||
this.notebookService.launchNotebook(provider.notebook);
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
private dispose(): void {
|
||||
this._toDispose.forEach(disposable => disposable.dispose());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user