Alanren/sql on windows (#6785)

* download file

* add entry

* fix issues

* add 2019

* string updates

* make dialog self contained

* expose notebook input dialog

* refactoring

* add log and correct the url

* comments
This commit is contained in:
Alan Ren
2019-08-20 14:45:27 -07:00
committed by GitHub
parent c540e81108
commit 7bd8a6f2b1
10 changed files with 206 additions and 65 deletions

View File

@@ -30,11 +30,14 @@ export interface DeploymentProvider {
title: string;
dialog: DialogInfo;
notebook: string | NotebookInfo;
downloadUrl: string;
webPageUrl: string;
requiredTools: ToolRequirementInfo[];
when: string;
}
export interface DialogInfo {
notebook: string | NotebookInfo;
title: string;
name: string;
tabs: DialogTabInfo[];

View File

@@ -4,13 +4,15 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import vscode = require('vscode');
import { ResourceTypePickerDialog } 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 vscode from 'vscode';
import * as nls from 'vscode-nls';
import { DialogInfo } from './interfaces';
import { NotebookService } from './services/notebookService';
import { PlatformService } from './services/platformService';
import { ResourceTypeService } from './services/resourceTypeService';
import { ToolsService } from './services/toolsService';
import { NotebookInputDialog } from './ui/notebookInputDialog';
import { ResourceTypePickerDialog } from './ui/resourceTypePickerDialog';
const localize = nls.loadMessageBundle();
@@ -18,8 +20,7 @@ export function activate(context: vscode.ExtensionContext) {
const platformService = new PlatformService();
const toolsService = new ToolsService();
const notebookService = new NotebookService(platformService, context.extensionPath);
const resourceTypeService = new ResourceTypeService(platformService, toolsService);
const resourceTypeService = new ResourceTypeService(platformService, toolsService, notebookService);
const resourceTypes = resourceTypeService.getResourceTypes();
const validationFailures = resourceTypeService.validateResourceTypes(resourceTypes);
if (validationFailures.length !== 0) {
@@ -33,7 +34,7 @@ export function activate(context: vscode.ExtensionContext) {
if (filtered.length !== 1) {
vscode.window.showErrorMessage(localize('resourceDeployment.UnknownResourceType', 'The resource type: {0} is not defined', resourceTypeName));
} else {
const dialog = new ResourceTypePickerDialog(context, notebookService, toolsService, resourceTypeService, filtered[0]);
const dialog = new ResourceTypePickerDialog(context, toolsService, resourceTypeService, filtered[0]);
dialog.open();
}
};
@@ -47,6 +48,10 @@ export function activate(context: vscode.ExtensionContext) {
vscode.commands.registerCommand('azdata.resource.deploy', () => {
openDialog('sql-bdc');
});
vscode.commands.registerCommand('azdata.openNotebookInputDialog', (dialogInfo: DialogInfo) => {
const dialog = new NotebookInputDialog(notebookService, dialogInfo);
dialog.open();
});
}
// this method is called when your extension is deactivated

View File

@@ -4,13 +4,13 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import { NotebookInfo } from '../interfaces';
import { isString } from 'util';
import * as path from 'path';
import { isString } from 'util';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { IPlatformService } from './platformService';
import { NotebookInfo } from '../interfaces';
const localize = nls.loadMessageBundle();
export interface INotebookService {
@@ -26,13 +26,16 @@ export class NotebookService implements INotebookService {
* @param notebook the path of the notebook
*/
launchNotebook(notebook: string | NotebookInfo): void {
const notebookRelativePath = this.getNotebook(notebook);
const notebookFullPath = path.join(this.extensionPath, notebookRelativePath);
if (notebookRelativePath && this.platformService.fileExists(notebookFullPath)) {
const notebookPath = this.getNotebook(notebook);
const notebookFullPath = path.join(this.extensionPath, notebookPath);
if (notebookPath && this.platformService.fileExists(notebookPath)) {
this.showNotebookAsUntitled(notebookPath);
}
else if (notebookPath && this.platformService.fileExists(notebookFullPath)) {
this.showNotebookAsUntitled(notebookFullPath);
}
else {
this.platformService.showErrorMessage(localize('resourceDeployment.notebookNotFound', 'The notebook {0} does not exist', notebookFullPath));
this.platformService.showErrorMessage(localize('resourceDeployment.notebookNotFound', "The notebook {0} does not exist", notebookPath));
}
}

View File

@@ -4,22 +4,31 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ResourceType, ResourceTypeOption, DeploymentProvider } from '../interfaces';
import { IToolsService } from './toolsService';
import * as azdata from 'azdata';
import * as cp from 'child_process';
import * as fs from 'fs';
import * as https from 'https';
import * as os from 'os';
import * as path from 'path';
import * as vscode from 'vscode';
import { IPlatformService } from './platformService';
import * as nls from 'vscode-nls';
import { INotebookService } from './notebookService';
import { IPlatformService } from './platformService';
import { IToolsService } from './toolsService';
import { ResourceType, ResourceTypeOption, DeploymentProvider } from '../interfaces';
import { NotebookInputDialog } from '../ui/notebookInputDialog';
const localize = nls.loadMessageBundle();
export interface IResourceTypeService {
getResourceTypes(filterByPlatform?: boolean): ResourceType[];
validateResourceTypes(resourceTypes: ResourceType[]): string[];
startDeployment(provider: DeploymentProvider): void;
}
export class ResourceTypeService implements IResourceTypeService {
private _resourceTypes: ResourceType[] = [];
constructor(private platformService: IPlatformService, private toolsService: IToolsService) { }
constructor(private platformService: IPlatformService, private toolsService: IToolsService, private notebookService: INotebookService) { }
/**
* Get the supported resource types
@@ -129,8 +138,8 @@ export class ResourceTypeService implements IResourceTypeService {
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.dialog && !provider.notebook && !provider.downloadUrl && !provider.webPageUrl) {
errorMessages.push(`No deployment method defined for the provider, ${providerPositionInfo}`);
}
if (provider.requiredTools && provider.requiredTools.length > 0) {
@@ -183,4 +192,79 @@ export class ResourceTypeService implements IResourceTypeService {
}
return undefined;
}
public startDeployment(provider: DeploymentProvider): void {
const self = this;
if (provider.dialog) {
const dialog = new NotebookInputDialog(this.notebookService, provider.dialog);
dialog.open();
} else if (provider.notebook) {
this.notebookService.launchNotebook(provider.notebook);
} else if (provider.downloadUrl) {
const taskName = localize('resourceDeployment.DownloadAndLaunchTaskName', "Download and launch installer, URL: {0}", provider.downloadUrl);
azdata.tasks.startBackgroundOperation({
displayName: taskName,
description: taskName,
isCancelable: false,
operation: op => {
op.updateStatus(azdata.TaskStatus.InProgress, localize('resourceDeployment.DownloadingText', "Downloading from: {0}", provider.downloadUrl));
self.download(provider.downloadUrl).then((downloadedFile) => {
op.updateStatus(azdata.TaskStatus.InProgress, localize('resourceDeployment.DownloadCompleteText', "Successfully downloaded: {0}", downloadedFile));
op.updateStatus(azdata.TaskStatus.InProgress, localize('resourceDeployment.LaunchingProgramText', "Launching: {0}", downloadedFile));
cp.exec(downloadedFile);
op.updateStatus(azdata.TaskStatus.Succeeded, localize('resourceDeployment.ProgramLaunchedText', "Successfully launched: {0}", downloadedFile));
}, (error) => {
op.updateStatus(azdata.TaskStatus.Failed, error);
});
}
});
} else if (provider.webPageUrl) {
vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(provider.webPageUrl));
}
}
private download(url: string): Promise<string> {
const self = this;
const promise = new Promise<string>((resolve, reject) => {
https.get(url, function (response) {
console.log('Download installer from: ' + url);
if (response.statusCode === 301 || response.statusCode === 302) {
// Redirect and download from new location
console.log('Redirecting the download to: ' + response.headers.location);
self.download(response.headers.location!).then((result) => {
resolve(result);
}, (err) => {
reject(err);
});
return;
}
if (response.statusCode !== 200) {
reject(localize('downloadError', "Download failed, status code: {0}, message: {1}", response.statusCode, response.statusMessage));
return;
}
const extension = path.extname(url);
const originalFileName = path.basename(url, extension);
let fileName = originalFileName;
const downloadFolder = os.homedir();
let cnt = 1;
while (fs.existsSync(path.join(downloadFolder, fileName + extension))) {
fileName = `${originalFileName}-${cnt}`;
cnt++;
}
fileName = path.join(downloadFolder, fileName + extension);
const file = fs.createWriteStream(fileName);
response.pipe(file);
file.on('finish', () => {
file.close();
resolve(fileName);
});
file.on('error', (err) => {
fs.unlink(fileName, () => { });
reject(err.message);
});
});
});
return promise;
}
}

View File

@@ -12,13 +12,15 @@ import { EOL } from 'os';
import { ResourceTypeService } from '../services/resourceTypeService';
import { IPlatformService } from '../services/platformService';
import { ToolsService } from '../services/toolsService';
import { NotebookService } from '../services/notebookService';
suite('Resource Type Service Tests', function (): void {
test('test resource types', () => {
const mockPlatformService = TypeMoq.Mock.ofType<IPlatformService>();
const toolsService = new ToolsService();
const resourceTypeService = new ResourceTypeService(mockPlatformService.object, toolsService);
const notebookService = new NotebookService(mockPlatformService.object, '');
const resourceTypeService = new ResourceTypeService(mockPlatformService.object, toolsService, notebookService);
// index 0: platform name, index 1: number of expected resource types
const platforms: { platform: string; resourceTypeCount: number }[] = [
{ platform: 'win32', resourceTypeCount: 2 },
@@ -39,4 +41,4 @@ suite('Resource Type Service Tests', function (): void {
const validationErrors = resourceTypeService.validateResourceTypes(allResourceTypes);
assert(validationErrors.length === 0, `Validation errors detected in the package.json: ${validationErrors.join(EOL)}.`);
});
});
});

View File

@@ -11,7 +11,7 @@ export abstract class DialogBase {
protected _toDispose: vscode.Disposable[] = [];
protected _dialogObject: azdata.window.Dialog;
constructor(protected extensionContext: vscode.ExtensionContext, dialogTitle: string, dialogName: string, isWide: boolean = false) {
constructor(dialogTitle: string, dialogName: string, isWide: boolean = false) {
this._dialogObject = azdata.window.createModelViewDialog(dialogTitle, dialogName, isWide);
this._dialogObject.cancelButton.onClick(() => this.onCancel());
}

View File

@@ -6,29 +6,27 @@
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import * as vscode from 'vscode';
import { DialogBase } from './dialogBase';
import { INotebookService } from '../services/notebookService';
import { DeploymentProvider, DialogFieldInfo, FieldType } from '../interfaces';
import { DialogFieldInfo, FieldType, DialogInfo } from '../interfaces';
const localize = nls.loadMessageBundle();
export class DeploymentDialog extends DialogBase {
export class NotebookInputDialog extends DialogBase {
private variables: { [s: string]: string | undefined; } = {};
private validators: (() => { valid: boolean, message: string })[] = [];
constructor(context: vscode.ExtensionContext,
private notebookService: INotebookService,
private deploymentProvider: DeploymentProvider) {
super(context, deploymentProvider.dialog.title, deploymentProvider.dialog.name, false);
constructor(private notebookService: INotebookService,
private dialogInfo: DialogInfo) {
super(dialogInfo.title, dialogInfo.name, false);
this._dialogObject.okButton.label = localize('deploymentDialog.OKButtonText', 'Open Notebook');
this._dialogObject.okButton.onClick(() => this.onComplete());
}
protected initializeDialog() {
const tabs: azdata.window.DialogTab[] = [];
this.deploymentProvider.dialog.tabs.forEach(tabInfo => {
this.dialogInfo.tabs.forEach(tabInfo => {
const tab = azdata.window.createTab(tabInfo.title);
tab.registerContent((view: azdata.ModelView) => {
const sections: azdata.FormComponentGroup[] = [];
@@ -191,7 +189,7 @@ export class DeploymentDialog extends DialogBase {
Object.keys(this.variables).forEach(key => {
process.env[key] = this.variables[key];
});
this.notebookService.launchNotebook(this.deploymentProvider.notebook);
this.notebookService.launchNotebook(this.dialogInfo.notebook);
this.dispose();
}

View File

@@ -5,14 +5,12 @@
'use strict';
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import { IResourceTypeService } from '../services/resourceTypeService';
import * as vscode from 'vscode';
import { ResourceType, DeploymentProvider } from '../interfaces';
import { IToolsService } from '../services/toolsService';
import { INotebookService } from '../services/notebookService';
import * as nls from 'vscode-nls';
import { DialogBase } from './dialogBase';
import { DeploymentDialog } from './deploymentDialog';
import { ResourceType, DeploymentProvider } from '../interfaces';
import { IResourceTypeService } from '../services/resourceTypeService';
import { IToolsService } from '../services/toolsService';
const localize = nls.loadMessageBundle();
@@ -26,12 +24,11 @@ export class ResourceTypePickerDialog extends DialogBase {
private _cardResourceTypeMap: Map<string, azdata.CardComponent> = new Map();
private _optionDropDownMap: Map<string, azdata.DropDownComponent> = new Map();
constructor(context: vscode.ExtensionContext,
private notebookService: INotebookService,
constructor(private extensionContext: vscode.ExtensionContext,
private toolsService: IToolsService,
private resourceTypeService: IResourceTypeService,
resourceType: ResourceType) {
super(context, localize('resourceTypePickerDialog.title', "Select the deployment options"), 'ResourceTypePickerDialog', true);
super(localize('resourceTypePickerDialog.title', "Select the deployment options"), 'ResourceTypePickerDialog', true);
this._selectedResourceType = resourceType;
this._dialogObject.okButton.label = localize('deploymentDialog.OKButtonText', 'Select');
this._dialogObject.okButton.onClick(() => this.onComplete());
@@ -156,11 +153,15 @@ export class ResourceTypePickerDialog extends DialogBase {
private updateTools(): void {
const tools = this.getCurrentProvider().requiredTools;
const headerRowHeight = 28;
this._toolsTable.height = 25 * tools.length + headerRowHeight;
this._toolsTable.data = tools.map(toolRef => {
const tool = this.toolsService.getToolByName(toolRef.name)!;
return [tool.displayName, tool.description];
});
this._toolsTable.height = 25 * Math.max(tools.length, 1) + headerRowHeight;
if (tools.length === 0) {
this._toolsTable.data = [[localize('deploymentDialog.NoRequiredTool', "No tools required"), '']];
} else {
this._toolsTable.data = tools.map(toolRef => {
const tool = this.toolsService.getToolByName(toolRef.name)!;
return [tool.displayName, tool.description];
});
}
}
private getCurrentProvider(): DeploymentProvider {
@@ -175,13 +176,7 @@ export class ResourceTypePickerDialog extends DialogBase {
}
private onComplete(): void {
const provider = this.getCurrentProvider();
if (provider.dialog) {
const dialog = new DeploymentDialog(this.extensionContext, this.notebookService, provider);
dialog.open();
} else {
this.notebookService.launchNotebook(provider.notebook);
}
this.resourceTypeService.startDeployment(this.getCurrentProvider());
this.dispose();
}
}