ML - dashboard icons and links (#10153)

* ML - dashboard icons and links
This commit is contained in:
Leila Lali
2020-04-28 21:21:30 -07:00
committed by GitHub
parent 046995f2a5
commit 04af41c424
145 changed files with 387 additions and 134 deletions

View File

@@ -0,0 +1,136 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as azdata from 'azdata';
/**
* Wrapper class to act as a facade over VSCode and Data APIs and allow us to test / mock callbacks into
* this API from our code
*/
export class ApiWrapper {
public createOutputChannel(name: string): vscode.OutputChannel {
return vscode.window.createOutputChannel(name);
}
public createTerminalWithOptions(options: vscode.TerminalOptions): vscode.Terminal {
return vscode.window.createTerminal(options);
}
public getCurrentConnection(): Thenable<azdata.connection.ConnectionProfile> {
return azdata.connection.getCurrentConnection();
}
public getCredentials(connectionId: string): Thenable<{ [name: string]: string }> {
return azdata.connection.getCredentials(connectionId);
}
public registerCommand(command: string, callback: (...args: any[]) => any, thisArg?: any): vscode.Disposable {
return vscode.commands.registerCommand(command, callback, thisArg);
}
public executeCommand<T>(command: string, ...rest: any[]): Thenable<T | undefined> {
return vscode.commands.executeCommand(command, ...rest);
}
public registerTaskHandler(taskId: string, handler: (profile: azdata.IConnectionProfile) => void): void {
azdata.tasks.registerTask(taskId, handler);
}
public getUriForConnection(connectionId: string): Thenable<string> {
return azdata.connection.getUriForConnection(connectionId);
}
public getProvider<T extends azdata.DataProvider>(providerId: string, providerType: azdata.DataProviderType): T {
return azdata.dataprotocol.getProvider<T>(providerId, providerType);
}
public showErrorMessage(message: string, ...items: string[]): Thenable<string | undefined> {
return vscode.window.showErrorMessage(message, ...items);
}
public showInfoMessage(message: string, ...items: string[]): Thenable<string | undefined> {
return vscode.window.showInformationMessage(message, ...items);
}
public showOpenDialog(options: vscode.OpenDialogOptions): Thenable<vscode.Uri[] | undefined> {
return vscode.window.showOpenDialog(options);
}
public startBackgroundOperation(operationInfo: azdata.BackgroundOperationInfo): void {
azdata.tasks.startBackgroundOperation(operationInfo);
}
public openExternal(target: vscode.Uri): Thenable<boolean> {
return vscode.env.openExternal(target);
}
public getExtension(extensionId: string): vscode.Extension<any> | undefined {
return vscode.extensions.getExtension(extensionId);
}
public getConfiguration(section?: string, resource?: vscode.Uri | null): vscode.WorkspaceConfiguration {
return vscode.workspace.getConfiguration(section, resource);
}
public createTab(title: string): azdata.window.DialogTab {
return azdata.window.createTab(title);
}
public createModelViewDialog(title: string, dialogName?: string, isWide?: boolean): azdata.window.Dialog {
return azdata.window.createModelViewDialog(title, dialogName, isWide);
}
public createWizard(title: string): azdata.window.Wizard {
return azdata.window.createWizard(title);
}
public createWizardPage(title: string): azdata.window.WizardPage {
return azdata.window.createWizardPage(title);
}
public openDialog(dialog: azdata.window.Dialog): void {
return azdata.window.openDialog(dialog);
}
public getAllAccounts(): Thenable<azdata.Account[]> {
return azdata.accounts.getAllAccounts();
}
public getSecurityToken(account: azdata.Account, resource: azdata.AzureResource): Thenable<{ [key: string]: any }> {
return azdata.accounts.getSecurityToken(account, resource);
}
public showQuickPick<T extends vscode.QuickPickItem>(items: T[] | Thenable<T[]>, options?: vscode.QuickPickOptions, token?: vscode.CancellationToken): Thenable<T | undefined> {
return vscode.window.showQuickPick(items, options, token);
}
public listDatabases(connectionId: string): Thenable<string[]> {
return azdata.connection.listDatabases(connectionId);
}
public openTextDocument(options?: { language?: string; content?: string; }): Thenable<vscode.TextDocument> {
return vscode.workspace.openTextDocument(options);
}
public connect(fileUri: string, connectionId: string): Thenable<void> {
return azdata.queryeditor.connect(fileUri, connectionId);
}
public runQuery(fileUri: string, options?: Map<string, string>, runCurrentQuery?: boolean): void {
azdata.queryeditor.runQuery(fileUri, options, runCurrentQuery);
}
public showTextDocument(uri: vscode.Uri, options?: vscode.TextDocumentShowOptions): Thenable<vscode.TextEditor> {
return vscode.window.showTextDocument(uri, options);
}
public createButton(label: string, position?: azdata.window.DialogButtonPosition): azdata.window.Button {
return azdata.window.createButton(label, position);
}
public registerWidget(widgetId: string, handler: (view: azdata.ModelView) => void): void {
azdata.ui.registerModelViewProvider(widgetId, handler);
}
}

View File

@@ -0,0 +1,235 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export const winPlatform = 'win32';
export const pythonBundleVersion = '0.0.1';
export const managePackagesCommand = 'jupyter.cmd.managePackages';
export const pythonLanguageName = 'Python';
export const rLanguageName = 'R';
export const rLPackagedFolderName = 'r_packages';
export const mlEnableMlsCommand = 'mls.command.enableMls';
export const mlDisableMlsCommand = 'mls.command.disableMls';
export const extensionOutputChannel = 'Machine Learning';
export const notebookExtensionName = 'Microsoft.notebook';
export const azureSubscriptionsCommand = 'azure.accounts.getSubscriptions';
export const azureResourceGroupsCommand = 'azure.accounts.getResourceGroups';
export const signInToAzureCommand = 'azure.resource.signin';
// Tasks, commands
//
export const mlManageLanguagesCommand = 'mls.command.manageLanguages';
export const mlsPredictModelCommand = 'mls.command.predictModel';
export const mlManageModelsCommand = 'mls.command.manageModels';
export const mlImportModelCommand = 'mls.command.importModel';
export const mlManagePackagesCommand = 'mls.command.managePackages';
export const mlsDependenciesCommand = 'mls.command.dependencies';
export const notebookCommandNew = 'notebook.command.new';
// Configurations
//
export const mlsConfigKey = 'machineLearningServices';
export const pythonPathConfigKey = 'pythonPath';
export const pythonEnabledConfigKey = 'enablePython';
export const rEnabledConfigKey = 'enableR';
export const registeredModelsTableName = 'registeredModelsTableName';
export const rPathConfigKey = 'rPath';
// Localized texts
//
export const msgYes = localize('msgYes', "Yes");
export const msgNo = localize('msgNo', "No");
export const managePackageCommandError = localize('mls.managePackages.error', "Either no connection is available or the server does not have external script enabled.");
export function taskFailedError(taskName: string, err: string): string { return localize('mls.taskFailedError.error', "Failed to complete task '{0}'. Error: {1}", taskName, err); }
export const installPackageMngDependenciesMsgTaskName = localize('mls.installPackageMngDependencies.msgTaskName', "Installing package management dependencies");
export const installModelMngDependenciesMsgTaskName = localize('mls.installModelMngDependencies.msgTaskName', "Installing model management dependencies");
export const noResultError = localize('mls.noResultError', "No Result returned");
export const requiredPackagesNotInstalled = localize('mls.requiredPackagesNotInstalled', "The required dependencies are not installed");
export const confirmEnableExternalScripts = localize('mls.confirmEnableExternalScripts', "External script is required for package management. Are you sure you want to enable that.");
export const enableExternalScriptsError = localize('mls.enableExternalScriptsError', "Failed to enable External script.");
export const externalScriptsIsRequiredError = localize('mls.externalScriptsIsRequiredError', "External script configuration is required for this action.");
export function confirmInstallPythonPackages(packages: string): string {
return localize('mls.installDependencies.confirmInstallPythonPackages'
, "The following Python packages are required to install: {0}. Are you sure you want to install?", packages);
}
export function confirmDeleteModel(modelName: string): string {
return localize('models.confirmDeleteModel'
, "Are you sure you want to delete model '{0}?", modelName);
}
export const installDependenciesPackages = localize('mls.installDependencies.packages', "Installing required packages ...");
export const installDependenciesPackagesAlreadyInstalled = localize('mls.installDependencies.packagesAlreadyInstalled', "Required packages are already installed.");
export function installDependenciesGetPackagesError(err: string): string { return localize('mls.installDependencies.getPackagesError', "Failed to get installed python packages. Error: {0}", err); }
export const noConnectionError = localize('mls.packageManager.NoConnection', "No connection selected");
export const notebookExtensionNotLoaded = localize('mls.notebookExtensionNotLoaded', "Notebook extension is not loaded");
export const mssqlExtensionNotLoaded = localize('mls.mssqlExtensionNotLoaded', "MSSQL extension is not loaded");
export const mlsEnabledMessage = localize('mls.enabledMessage', "Machine Learning Services Enabled");
export const mlsConfigUpdateFailed = localize('mls.configUpdateFailed', "Failed to modify Machine Learning Services configurations");
export const mlsEnableButtonTitle = localize('mls.enableButtonTitle', "Enable");
export const mlsDisableButtonTitle = localize('mls.disableButtonTitle', "Disable");
export const mlsConfigTitle = localize('mls.configTitle', "Config");
export const mlsConfigStatus = localize('mls.configStatus', "Enabled");
export const mlsConfigAction = localize('mls.configAction', "Action");
export const mlsExternalExecuteScriptTitle = localize('mls.externalExecuteScriptTitle', "External Execute Script");
export const mlsPythonLanguageTitle = localize('mls.pythonLanguageTitle', "Python");
export const mlsRLanguageTitle = localize('mls.rLanguageTitle', "R");
export const downloadError = localize('mls.downloadError', "Error while downloading");
export function invalidModelIdError(modelUrl: string | undefined): string { return localize('mls.invalidModelIdError', "Invalid model id. model url: {0}", modelUrl || ''); }
export function noArtifactError(modelUrl: string | undefined): string { return localize('mls.noArtifactError', "Model doesn't have any artifact. model url: {0}", modelUrl || ''); }
export const downloadingProgress = localize('mls.downloadingProgress', "Downloading");
export const pythonConfigError = localize('mls.pythonConfigError', "Python executable is not configured");
export const rConfigError = localize('mls.rConfigError', "R executable is not configured");
export const installingDependencies = localize('mls.installingDependencies', "Installing dependencies ...");
export const resourceNotFoundError = localize('mls.resourceNotFound', "Could not find the specified resource");
export const latestVersion = localize('mls.latestVersion', "Latest");
export const localhost = 'localhost';
export function httpGetRequestError(code: number, message: string): string {
return localize('mls.httpGetRequestError', "Package info request failed with error: {0} {1}",
code,
message);
}
export function getErrorMessage(error: Error): string { return localize('azure.resource.error', "Error: {0}", error?.message || error?.toString()); }
export const notSupportedEventArg = localize('notSupportedEventArg', "Not supported event args");
export const extLangInstallTabTitle = localize('extLang.installTabTitle', "Installed");
export const extLangLanguageCreatedDate = localize('extLang.languageCreatedDate', "Installed");
export const extLangLanguagePlatform = localize('extLang.languagePlatform', "Platform");
export const deleteTitle = localize('extLang.delete', "Delete");
export const extLangInstallButtonText = localize('extLang.installButtonText', "Install");
export const extLangCancelButtonText = localize('extLang.CancelButtonText', "Cancel");
export const extLangDoneButtonText = localize('extLang.DoneButtonText', "Close");
export const extLangOkButtonText = localize('extLang.OkButtonText', "OK");
export const extLangSaveButtonText = localize('extLang.SaveButtonText', "Save");
export const extLangLanguageName = localize('extLang.languageName', "Name");
export const extLangNewLanguageTabTitle = localize('extLang.newLanguageTabTitle', "Add new");
export const extLangFileBrowserTabTitle = localize('extLang.fileBrowserTabTitle', "File Browser");
export const extLangDialogTitle = localize('extLang.DialogTitle', "Languages");
export const extLangTarget = localize('extLang.Target', "Target");
export const extLangLocal = localize('extLang.Local', "localhost");
export const extLangExtensionFilePath = localize('extLang.extensionFilePath', "Language extension path");
export const extLangExtensionFileLocation = localize('extLang.extensionFileLocation', "Language extension location");
export const extLangExtensionFileName = localize('extLang.extensionFileName', "Extension file Name");
export const extLangEnvVariables = localize('extLang.envVariables', "Environment variables");
export const extLangParameters = localize('extLang.parameters', "Parameters");
export const extLangSelectedPath = localize('extLang.selectedPath', "Selected Path");
export const extLangInstallFailedError = localize('extLang.installFailedError', "Failed to install language");
export const extLangUpdateFailedError = localize('extLang.updateFailedError', "Failed to update language");
export const modelUpdateFailedError = localize('models.modelUpdateFailedError', "Failed to update the model");
export const databaseName = localize('databaseName', "Database name");
export const tableName = localize('tableName', "Table name");
export const modelName = localize('models.name', "Name");
export const modelFileName = localize('models.fileName', "File");
export const modelDescription = localize('models.description', "Description");
export const modelCreated = localize('models.created', "Date created");
export const modelDeployed = localize('models.deployed', "Date deployed");
export const modelFramework = localize('models.framework', "Framework");
export const modelFrameworkVersion = localize('models.frameworkVersion', "Framework version");
export const modelVersion = localize('models.version', "Version");
export const browseModels = localize('models.browseButton', "...");
export const azureAccount = localize('models.azureAccount', "Azure account");
export const azureSignIn = localize('models.azureSignIn', "Sign in to Azure");
export const columnDatabase = localize('predict.columnDatabase', "Target database");
export const columnTable = localize('predict.columnTable', "Target table");
export const inputColumns = localize('predict.inputColumns', "Model input mapping");
export const outputColumns = localize('predict.outputColumns', "Model output");
export const columnName = localize('predict.columnName', "Target columns");
export const dataTypeName = localize('predict.dataTypeName', "Type");
export const displayName = localize('predict.displayName', "Display name");
export const inputName = localize('predict.inputName', "Required model input features");
export const outputName = localize('predict.outputName', "Name");
export const azureSubscription = localize('models.azureSubscription', "Azure subscription");
export const azureGroup = localize('models.azureGroup', "Azure resource group");
export const azureModelWorkspace = localize('models.azureModelWorkspace', "Azure ML workspace");
export const azureModelFilter = localize('models.azureModelFilter', "Filter");
export const azureModels = localize('models.azureModels', "Models");
export const azureModelsTitle = localize('models.azureModelsTitle', "Azure models");
export const localModelsTitle = localize('models.localModelsTitle', "Local models");
export const modelSourcesTitle = localize('models.modelSourcesTitle', "Source location");
export const modelSourcePageTitle = localize('models.modelSourcePageTitle', "Where is your model located?");
export const modelImportTargetPageTitle = localize('models.modelImportTargetPageTitle', "Where do you want import models to?");
export const columnSelectionPageTitle = localize('models.columnSelectionPageTitle', "Map predictions target data to model input");
export const modelDetailsPageTitle = localize('models.modelDetailsPageTitle', "Enter model details");
export const modelLocalSourceTitle = localize('models.modelLocalSourceTitle', "Source file");
export const currentModelsTitle = localize('models.currentModelsTitle', "Models");
export const azureRegisterModel = localize('models.azureRegisterModel', "Deploy");
export const predictModel = localize('models.predictModel', "Predict");
export const registerModelTitle = localize('models.RegisterWizard', "Import models");
export const importModelTitle = localize('models.importModelTitle', "Import models");
export const editModelTitle = localize('models.editModelTitle', "Edit model");
export const importModelDesc = localize('models.importModelDesc', "Build, import and expose a machine learning model");
export const makePredictionTitle = localize('models.makePredictionTitle', "Make predictions");
export const makePredictionDesc = localize('models.makePredictionDesc', "Generates a predicted value or scores using a managed model");
export const createNotebookTitle = localize('models.createNotebookTitle', "Create notebook");
export const createNotebookDesc = localize('models.createNotebookDesc', "Run experiments and create models");
export const modelRegisteredSuccessfully = localize('models.modelRegisteredSuccessfully', "Model registered successfully");
export const modelUpdatedSuccessfully = localize('models.modelUpdatedSuccessfully', "Model updated successfully");
export const modelFailedToRegister = localize('models.modelFailedToRegistered', "Model failed to register");
export const localModelSource = localize('models.localModelSource', "File upload");
export const localModelPageTitle = localize('models.localModelPageTitle', "Upload model file");
export const azureModelSource = localize('models.azureModelSource', "Azure Machine Learning");
export const azureModelPageTitle = localize('models.azureModelPageTitle', "Import from Azure Machine Learning");
export const importedModelsPageTitle = localize('models.importedModelsPageTitle', "Select imported model");
export const registeredModelsSource = localize('models.registeredModelsSource', "Imported models");
export const downloadModelMsgTaskName = localize('models.downloadModelMsgTaskName', "Downloading Model from Azure");
export const invalidAzureResourceError = localize('models.invalidAzureResourceError', "Invalid Azure resource");
export const invalidModelToRegisterError = localize('models.invalidModelToRegisterError', "Invalid model to register");
export const invalidModelToPredictError = localize('models.invalidModelToPredictError', "Invalid model to predict");
export const invalidModelToSelectError = localize('models.invalidModelToSelectError', "Please select a valid model");
export const invalidModelImportTargetError = localize('models.invalidModelImportTargetError', "Please select a valid table");
export const modelNameRequiredError = localize('models.modelNameRequiredError', "Model name is required.");
export const updateModelFailedError = localize('models.updateModelFailedError', "Failed to update the model");
export function importModelFailedError(modelName: string | undefined, filePath: string | undefined): string { return localize('models.importModelFailedError', "Failed to register the model: {0} ,file: {1}", modelName || '', filePath || ''); }
export function invalidImportTableError(databaseName: string | undefined, tableName: string | undefined): string { return localize('models.invalidImportTableError', "Invalid table for importing models. database name: {0} ,table name: {1}", databaseName || '', tableName || ''); }
export function invalidImportTableSchemaError(databaseName: string | undefined, tableName: string | undefined): string { return localize('models.invalidImportTableSchemaError', "Table schema is not supported for model import. database name: {0} ,table name: {1}", databaseName || '', tableName || ''); }
export const loadModelParameterFailedError = localize('models.loadModelParameterFailedError', "Failed to load model parameters'");
export const unsupportedModelParameterType = localize('models.unsupportedModelParameterType', "unsupported");
export const dashboardTitle = localize('dashboardTitle', "Machine Learning");
export const dashboardDesc = localize('dashboardDesc', "Machine Learning for SQL Databases");
export const dashboardLinksTitle = localize('dashboardLinksTitle', "Useful links");
export const dashboardVideoLinksTitle = localize('dashboardVideoLinksTitle', "Video tutorials");
export const showMoreTitle = localize('showMoreTitle', "Show more");
export const showLessTitle = localize('showLessTitle', "Show less");
export const learnMoreTitle = localize('learnMoreTitle', "Learn more");
export const sqlMlDocTitle = localize('sqlMlDocTitle', "SQL machine learning documentation");
export const sqlMlDocDesc = localize('sqlMlDocDesc', "Learn how to use machine learning in SQL Server and SQL on Azure, to run Python and R scripts on relational data.");
export const sqlMlsDocTitle = localize('sqlMlsDocTitle', "SQL Server Machine Learning Services (Python and R)");
export const sqlMlsDocDesc = localize('sqlMlsDocDesc', "Get started with Machine Learning Services on SQL Server and how to install it on Windows and Linux.");
export const sqlMlsAzureDocTitle = localize('sqlMlsAzureDocTitle', "Machine Learning Services in Azure SQL Managed Instance (preview)");
export const sqlMlsAzureDocDesc = localize('sqlMlsAzureDocDesc', "Get started with Machine Learning Services in Azure SQL Managed Instances.");
export const mlsInstallOdbcDocTitle = localize('mlsInstallObdcDocTitle', "Install the Microsoft ODBC driver for SQL Server");
export const mlsInstallOdbcDocDesc = localize('mlsInstallOdbcDocDesc', "This document explains how to install the Microsoft ODBC Driver for SQL Server.");
// Links
//
export const mlsDocuments = 'https://docs.microsoft.com/sql/advanced-analytics/?view=sql-server-ver15';
export const odbcDriverWindowsDocuments = 'https://docs.microsoft.com/sql/connect/odbc/windows/microsoft-odbc-driver-for-sql-server-on-windows?view=sql-server-ver15';
export const odbcDriverLinuxDocuments = 'https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-ver15';
export const mlDocLink = 'https://docs.microsoft.com/sql/machine-learning/';
export const mlsDocLink = 'https://docs.microsoft.com/sql/machine-learning/what-is-sql-server-machine-learning';
export const mlsAzureDocLink = 'https://docs.microsoft.com/azure/sql-database/sql-database-managed-instance-machine-learning-services-overview';
export const installMlsWindowsDocs = 'https://docs.microsoft.com/sql/advanced-analytics/install/sql-machine-learning-services-windows-install?view=sql-server-ver15';
// CSS Styles
//
export namespace cssStyles {
export const title = { 'font-size': '14px', 'font-weight': '600' };
export const tableHeader = { 'text-align': 'left', 'font-weight': 'bold', 'text-transform': 'uppercase', 'font-size': '10px', 'user-select': 'text', 'border': 'none' };
export const tableRow = { 'border-top': 'solid 1px #ccc', 'border-bottom': 'solid 1px #ccc', 'border-left': 'none', 'border-right': 'none' };
export const hyperlink = { 'user-select': 'text', 'color': '#0078d4', 'text-decoration': 'underline', 'cursor': 'pointer' };
export const text = { 'margin-block-start': '0px', 'margin-block-end': '0px' };
export const overflowEllipsisText = { ...text, 'overflow': 'hidden', 'text-overflow': 'ellipsis' };
export const nonSelectableText = { ...cssStyles.text, 'user-select': 'none' };
export const tabHeaderText = { 'margin-block-start': '2px', 'margin-block-end': '0px', 'user-select': 'none' };
export const selectedResourceHeaderTab = { 'font-weight': 'bold', 'color': '' };
export const unselectedResourceHeaderTab = { 'font-weight': '', 'color': '#0078d4' };
export const selectedTabDiv = { 'border-bottom': '2px solid #000' };
export const unselectedTabDiv = { 'border-bottom': '1px solid #ccc' };
export const lastUpdatedText = { ...text, 'color': '#595959' };
export const errorText = { ...text, 'color': 'red' };
}

View File

@@ -0,0 +1,45 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
export class EventEmitterCollection extends vscode.Disposable {
private _events: Map<string, vscode.EventEmitter<any>[]> = new Map<string, vscode.EventEmitter<any>[]>();
/**
*
*/
constructor() {
super(() => this.dispose());
}
public on(evt: string, listener: (e: any) => any, thisArgs?: any) {
if (!this._events.has(evt)) {
this._events.set(evt, []);
}
let eventEmitter = new vscode.EventEmitter<any>();
eventEmitter.event(listener, thisArgs);
this._events.get(evt)?.push(eventEmitter);
return this;
}
public fire(evt: string, arg?: any) {
if (!this._events.has(evt)) {
this._events.set(evt, []);
}
this._events.get(evt)?.forEach(eventEmitter => {
eventEmitter.fire(arg);
});
}
public dispose(): any {
this._events.forEach(events => {
events.forEach(event => {
event.dispose();
});
});
}
}

View File

@@ -0,0 +1,80 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as request from 'request';
import * as constants from './constants';
const DownloadTimeout = 20000;
const GetTimeout = 10000;
export class HttpClient {
public async fetch(url: string): Promise<any> {
return new Promise<any>((resolve, reject) => {
request.get(url, { timeout: GetTimeout }, (error, response, body) => {
if (error) {
return reject(error);
}
if (response.statusCode === 404) {
return reject(constants.resourceNotFoundError);
}
if (response.statusCode !== 200) {
return reject(
constants.httpGetRequestError(
response.statusCode,
response.statusMessage));
}
resolve(body);
});
});
}
public download(downloadUrl: string, targetPath: string, outputChannel: vscode.OutputChannel): Promise<void> {
return new Promise((resolve, reject) => {
let totalMegaBytes: number | undefined = undefined;
let receivedBytes = 0;
let printThreshold = 0.1;
let downloadRequest = request.get(downloadUrl, { timeout: DownloadTimeout })
.on('error', downloadError => {
outputChannel.appendLine(constants.downloadError);
reject(downloadError);
})
.on('response', (response) => {
if (response.statusCode !== 200) {
outputChannel.appendLine(constants.downloadError);
return reject(response.statusMessage);
}
let contentLength = response.headers['content-length'];
let totalBytes = parseInt(contentLength || '0');
totalMegaBytes = totalBytes / (1024 * 1024);
outputChannel.appendLine(`'Downloading' (0 / ${totalMegaBytes.toFixed(2)} MB)`);
})
.on('data', (data) => {
receivedBytes += data.length;
if (totalMegaBytes) {
let receivedMegaBytes = receivedBytes / (1024 * 1024);
let percentage = receivedMegaBytes / totalMegaBytes;
if (percentage >= printThreshold) {
outputChannel.appendLine(`${constants.downloadingProgress} (${receivedMegaBytes.toFixed(2)} / ${totalMegaBytes.toFixed(2)} MB)`);
printThreshold += 0.1;
}
}
});
downloadRequest.pipe(fs.createWriteStream(targetPath))
.on('close', async () => {
resolve();
})
.on('error', (downloadError) => {
reject(downloadError);
downloadRequest.abort();
});
});
}
}

View File

@@ -0,0 +1,91 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as childProcess from 'child_process';
const ExecScriptsTimeoutInSeconds = 600000;
export class ProcessService {
public timeout = ExecScriptsTimeoutInSeconds;
public async execScripts(exeFilePath: string, scripts: string[], args?: string[], outputChannel?: vscode.OutputChannel): Promise<string> {
return new Promise<string>((resolve, reject) => {
const scriptExecution = childProcess.spawn(exeFilePath, args);
let timer: NodeJS.Timeout;
let output: string = '';
scripts.forEach(script => {
scriptExecution.stdin.write(`${script}\n`);
});
scriptExecution.stdin.end();
// Add listeners to print stdout and stderr if an output channel was provided
scriptExecution.stdout.on('data', data => {
if (outputChannel) {
this.outputDataChunk(data, outputChannel, ' stdout: ');
}
output = output + data.toString();
});
scriptExecution.stderr.on('data', data => {
if (outputChannel) {
this.outputDataChunk(data, outputChannel, ' stderr: ');
}
output = output + data.toString();
});
scriptExecution.on('exit', (code) => {
if (timer) {
clearTimeout(timer);
}
if (code === 0) {
resolve(output);
} else {
reject(`Process exited with code: ${code}. output: ${output}`);
}
});
timer = setTimeout(() => {
try {
scriptExecution.kill();
} catch (error) {
console.log(error);
}
}, this.timeout);
});
}
public async executeBufferedCommand(cmd: string, outputChannel?: vscode.OutputChannel): Promise<string> {
return new Promise<string>((resolve, reject) => {
if (outputChannel) {
outputChannel.appendLine(` > ${cmd}`);
}
let child = childProcess.exec(cmd, {
timeout: this.timeout
}, (err, stdout) => {
if (err) {
reject(err);
} else {
resolve(stdout);
}
});
// Add listeners to print stdout and stderr if an output channel was provided
if (outputChannel) {
child.stdout.on('data', data => { this.outputDataChunk(data, outputChannel, ' stdout: '); });
child.stderr.on('data', data => { this.outputDataChunk(data, outputChannel, ' stderr: '); });
}
});
}
private outputDataChunk(data: string | Buffer, outputChannel: vscode.OutputChannel, header: string): void {
data.toString().split(/\r?\n/)
.forEach(line => {
outputChannel.appendLine(header + line);
});
}
}

View File

@@ -0,0 +1,214 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as nbExtensionApis from '../typings/notebookServices';
import { ApiWrapper } from './apiWrapper';
import * as constants from '../common/constants';
import * as utils from '../common/utils';
const maxNumberOfRetries = 2;
const listPythonPackagesQuery = `
Declare @tablevar table(name NVARCHAR(MAX), version NVARCHAR(MAX))
insert into @tablevar(name, version)
EXEC sp_execute_external_script
@language=N'Python',
@script=N'import pkg_resources
import pandas
OutputDataSet = pandas.DataFrame([(d.project_name, d.version) for d in pkg_resources.working_set])'
select e.name, version from sys.external_libraries e join @tablevar t on e.name = t.name
where [language] = 'PYTHON'
`;
const listRPackagesQuery = `
Declare @tablevar table(name NVARCHAR(MAX), version NVARCHAR(MAX))
insert into @tablevar(name, version)
EXEC sp_execute_external_script
@language=N'R',
@script=N'
OutputDataSet <- as.data.frame(installed.packages()[,c(1,3)])'
select e.name, version from sys.external_libraries e join @tablevar t on e.name = t.name
where [language] = 'R'
`;
const checkMlInstalledQuery = `
Declare @tablevar table(name NVARCHAR(MAX), min INT, max INT, config_value bit, run_value bit)
insert into @tablevar(name, min, max, config_value, run_value) exec sp_configure
Declare @external_script_enabled bit
SELECT @external_script_enabled=config_value FROM @tablevar WHERE name = 'external scripts enabled'
SELECT @external_script_enabled`;
const checkLanguageInstalledQuery = `
SELECT is_installed
FROM sys.dm_db_external_language_stats s, sys.external_languages l
WHERE s.external_language_id = l.external_language_id AND language = '#LANGUAGE#'`;
const modifyExternalScriptConfigQuery = `
EXEC sp_configure 'external scripts enabled', #CONFIG_VALUE#;
RECONFIGURE WITH OVERRIDE;
Declare @tablevar table(name NVARCHAR(MAX), min INT, max INT, config_value bit, run_value bit)
insert into @tablevar(name, min, max, config_value, run_value) exec sp_configure
Declare @external_script_enabled bit
SELECT @external_script_enabled=config_value FROM @tablevar WHERE name = 'external scripts enabled'
SELECT @external_script_enabled`;
/**
* SQL Query runner
*/
export class QueryRunner {
constructor(private _apiWrapper: ApiWrapper) {
}
/**
* Returns python packages installed in SQL server instance
* @param connection SQL Connection
*/
public async getPythonPackages(connection: azdata.connection.ConnectionProfile, databaseName: string): Promise<nbExtensionApis.IPackageDetails[]> {
return this.getPackages(connection, databaseName, listPythonPackagesQuery);
}
/**
* Returns python packages installed in SQL server instance
* @param connection SQL Connection
*/
public async getRPackages(connection: azdata.connection.ConnectionProfile, databaseName: string): Promise<nbExtensionApis.IPackageDetails[]> {
return this.getPackages(connection, databaseName, listRPackagesQuery);
}
private async getPackages(connection: azdata.connection.ConnectionProfile, databaseName: string, script: string): Promise<nbExtensionApis.IPackageDetails[]> {
let packages: nbExtensionApis.IPackageDetails[] = [];
let result: azdata.SimpleExecuteResult | undefined = undefined;
for (let index = 0; index < maxNumberOfRetries; index++) {
result = await this.runQuery(connection, utils.getScriptWithDBChange(connection.databaseName, databaseName, script));
if (result && result.rowCount > 0) {
break;
}
}
if (result && result.rows.length > 0) {
packages = result.rows.map(row => {
return {
name: row[0].displayValue,
version: row[1].displayValue
};
});
}
return packages;
}
/**
* Updates External Script Config in a SQL server instance
* @param connection SQL Connection
* @param enable if true the config will be enabled otherwise it will be disabled
*/
public async updateExternalScriptConfig(connection: azdata.connection.ConnectionProfile, enable: boolean): Promise<void> {
let query = modifyExternalScriptConfigQuery;
let configValue = enable ? '1' : '0';
query = query.replace('#CONFIG_VALUE#', configValue);
await this.runQuery(connection, query);
}
/**
* Returns true if python installed in the give SQL server instance
*/
public async isPythonInstalled(connection: azdata.connection.ConnectionProfile): Promise<boolean> {
return this.isLanguageInstalled(connection, constants.pythonLanguageName);
}
/**
* Returns true if R installed in the give SQL server instance
*/
public async isRInstalled(connection: azdata.connection.ConnectionProfile): Promise<boolean> {
return this.isLanguageInstalled(connection, constants.rLanguageName);
}
/**
* Returns true if language installed in the give SQL server instance
*/
private async isLanguageInstalled(connection: azdata.connection.ConnectionProfile, language: string): Promise<boolean> {
let result = await this.runQuery(connection, checkLanguageInstalledQuery.replace('#LANGUAGE#', language));
let isInstalled = false;
if (result && result.rows && result.rows.length > 0) {
isInstalled = result.rows[0][0].displayValue === '1';
}
return isInstalled;
}
/**
* Returns true if mls is installed in the give SQL server instance
*/
public async isMachineLearningServiceEnabled(connection: azdata.connection.ConnectionProfile): Promise<boolean> {
let result = await this.runQuery(connection, checkMlInstalledQuery);
let isEnabled = false;
if (result && result.rows && result.rows.length > 0) {
isEnabled = result.rows[0][0].displayValue === '1';
}
return isEnabled;
}
public async runQuery(connection: azdata.connection.ConnectionProfile, query: string): Promise<azdata.SimpleExecuteResult | undefined> {
let result: azdata.SimpleExecuteResult | undefined = undefined;
try {
if (connection) {
let connectionUri = await this._apiWrapper.getUriForConnection(connection.connectionId);
let queryProvider = this._apiWrapper.getProvider<azdata.QueryProvider>(connection.providerId, azdata.DataProviderType.QueryProvider);
if (queryProvider) {
result = await queryProvider.runQueryAndReturn(connectionUri, query);
}
}
} catch (error) {
console.log(error);
}
return result;
}
/**
* Executes the query but doesn't fail it is fails
* @param connection SQL connection
* @param query query to run
*/
public async safeRunQuery(connection: azdata.connection.ConnectionProfile, query: string): Promise<azdata.SimpleExecuteResult | undefined> {
try {
return await this.runQuery(connection, query);
} catch (error) {
//console.log(error);
return undefined;
}
}
/**
* Executes the query but doesn't fail it is fails
* @param connection SQL connection
* @param query query to run
*/
public async runWithDatabaseChange(connection: azdata.connection.ConnectionProfile, query: string, queryDb: string): Promise<azdata.SimpleExecuteResult | undefined> {
if (connection) {
try {
return await this.runQuery(connection, `
USE [${utils.doubleEscapeSingleBrackets(queryDb)}]
${query}`);
} catch (error) {
console.log(error);
}
finally {
this.safeRunQuery(connection, `USE [${utils.doubleEscapeSingleBrackets(connection.databaseName || 'master')}]`);
}
}
return undefined;
}
}

View File

@@ -0,0 +1,261 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
import * as path from 'path';
import * as os from 'os';
import * as fs from 'fs';
import * as constants from './constants';
import { promisify } from 'util';
import { ApiWrapper } from './apiWrapper';
export async function execCommandOnTempFile<T>(content: string, command: (filePath: string) => Promise<T>): Promise<T> {
let tempFilePath: string = '';
try {
tempFilePath = path.join(os.tmpdir(), `ads_ml_temp_${UUID.generateUuid()}`);
await fs.promises.writeFile(tempFilePath, content);
let result = await command(tempFilePath);
return result;
}
finally {
await deleteFile(tempFilePath);
}
}
/**
* Deletes a file
* @param filePath file path
*/
export async function deleteFile(filePath: string) {
if (filePath) {
await fs.promises.unlink(filePath);
}
}
export async function readFileInHex(filePath: string): Promise<string> {
let buffer = await fs.promises.readFile(filePath);
return `0X${buffer.toString('hex')}`;
}
export async function exists(path: string): Promise<boolean> {
return promisify(fs.exists)(path);
}
export async function createFolder(dirPath: string): Promise<void> {
let folderExists = await exists(dirPath);
if (!folderExists) {
await fs.promises.mkdir(dirPath);
}
}
export function getPythonInstallationLocation(rootFolder: string) {
return path.join(rootFolder, 'python');
}
export function getPythonExePath(rootFolder: string): string {
return path.join(
getPythonInstallationLocation(rootFolder),
constants.pythonBundleVersion,
process.platform === constants.winPlatform ? 'python.exe' : 'bin/python3');
}
export function getPackageFilePath(rootFolder: string, packageName: string): string {
return path.join(
rootFolder,
constants.rLPackagedFolderName,
packageName);
}
export function getRPackagesFolderPath(rootFolder: string): string {
return path.join(
rootFolder,
constants.rLPackagedFolderName);
}
/**
* Compares two version strings to see which is greater.
* @param first First version string to compare.
* @param second Second version string to compare.
* @returns 1 if the first version is greater, -1 if it's less, and 0 otherwise.
*/
export function comparePackageVersions(first: string, second: string): number {
let firstVersion = first.split('.').map(numStr => Number.parseInt(numStr));
let secondVersion = second.split('.').map(numStr => Number.parseInt(numStr));
// If versions have different lengths, then append zeroes to the shorter one
if (firstVersion.length > secondVersion.length) {
let diff = firstVersion.length - secondVersion.length;
secondVersion = secondVersion.concat(new Array(diff).fill(0));
} else if (secondVersion.length > firstVersion.length) {
let diff = secondVersion.length - firstVersion.length;
firstVersion = firstVersion.concat(new Array(diff).fill(0));
}
for (let i = 0; i < firstVersion.length; ++i) {
if (firstVersion[i] > secondVersion[i]) {
return 1;
} else if (firstVersion[i] < secondVersion[i]) {
return -1;
}
}
return 0;
}
export function sortPackageVersions(versions: string[], ascending: boolean = true) {
return versions.sort((first, second) => {
let compareResult = comparePackageVersions(first, second);
if (ascending) {
return compareResult;
} else {
return compareResult * -1;
}
});
}
export function isWindows(): boolean {
return process.platform === 'win32';
}
/**
* Escapes all single-quotes (') by prefixing them with another single quote ('')
* ' => ''
* @param value The string to escape
*/
export function doubleEscapeSingleQuotes(value: string | undefined): string {
return value ? value.replace(/'/g, '\'\'') : '';
}
/**
* Escapes all single-bracket ([]) by replacing them with another bracket quote ([[]])
* ' => ''
* @param value The string to escape
*/
export function doubleEscapeSingleBrackets(value: string | undefined): string {
return value ? value.replace(/\[/g, '[[').replace(/\]/g, ']]') : '';
}
/**
* Installs dependencies for the extension
*/
export async function executeTasks<T>(apiWrapper: ApiWrapper, taskName: string, dependencies: PromiseLike<T>[], parallel: boolean): Promise<T[]> {
return new Promise<T[]>((resolve, reject) => {
let msgTaskName = taskName;
apiWrapper.startBackgroundOperation({
displayName: msgTaskName,
description: msgTaskName,
isCancelable: false,
operation: async op => {
try {
let result: T[] = [];
// Install required packages
//
if (parallel) {
result = await Promise.all(dependencies);
} else {
for (let index = 0; index < dependencies.length; index++) {
result.push(await dependencies[index]);
}
}
op.updateStatus(azdata.TaskStatus.Succeeded);
resolve(result);
} catch (error) {
let errorMsg = constants.taskFailedError(taskName, error ? error.message : '');
op.updateStatus(azdata.TaskStatus.Failed, errorMsg);
reject(errorMsg);
}
}
});
});
}
export async function promptConfirm(message: string, apiWrapper: ApiWrapper): Promise<boolean> {
let choices: { [id: string]: boolean } = {};
choices[constants.msgYes] = true;
choices[constants.msgNo] = false;
let options = {
placeHolder: message
};
let result = await apiWrapper.showQuickPick(Object.keys(choices).map(c => {
return {
label: c
};
}), options);
if (result === undefined) {
throw Error('invalid selection');
}
return choices[result.label] || false;
}
export function makeLinuxPath(filePath: string): string {
const parts = filePath.split('\\');
return parts.join('/');
}
/**
*
* @param currentDb Wraps the given script with database switch scripts
* @param databaseName
* @param script
*/
export function getScriptWithDBChange(currentDb: string, databaseName: string, script: string): string {
if (!currentDb) {
currentDb = 'master';
}
let escapedDbName = doubleEscapeSingleBrackets(databaseName);
let escapedCurrentDbName = doubleEscapeSingleBrackets(currentDb);
return `
USE [${escapedDbName}]
${script}
USE [${escapedCurrentDbName}]
`;
}
/**
* Returns full name of model registration table
* @param config config
*/
export function getRegisteredModelsThreePartsName(db: string, table: string, schema: string) {
const dbName = doubleEscapeSingleBrackets(db);
const schemaName = doubleEscapeSingleBrackets(schema);
const tableName = doubleEscapeSingleBrackets(table);
return `[${dbName}].[${schemaName}].[${tableName}]`;
}
/**
* Returns full name of model registration table
* @param config config object
*/
export function getRegisteredModelsTwoPartsName(table: string, schema: string) {
const schemaName = doubleEscapeSingleBrackets(schema);
const tableName = doubleEscapeSingleBrackets(table);
return `[${schemaName}].[${tableName}]`;
}
/**
* Write a file using a hex string
* @param content file content
*/
export async function writeFileFromHex(content: string): Promise<string> {
content = content.startsWith('0x') || content.startsWith('0X') ? content.substr(2) : content;
const tempFilePath = path.join(os.tmpdir(), `ads_ml_temp_${UUID.generateUuid()}`);
await fs.promises.writeFile(tempFilePath, Buffer.from(content, 'hex'));
return tempFilePath;
}
/**
*
* @param filePath Returns file name
*/
export function getFileName(filePath: string) {
if (filePath) {
return filePath.replace(/^.*[\\\/]/, '');
} else {
return '';
}
}