From 1bcda64a1b6ab8a167836d768d23d9670cb2887a Mon Sep 17 00:00:00 2001 From: Leila Lali Date: Fri, 15 May 2020 11:07:31 -0700 Subject: [PATCH] Fixed ML urls in dashboard (#10411) * Fixed ML urls in dashboard --- extensions/machine-learning/package.json | 8 +++--- .../machine-learning/src/common/constants.ts | 25 +++++++++++++------ .../src/packageManagement/packageManager.ts | 5 +++- .../packageManagement/packageManager.test.ts | 8 +++--- .../manageModels/currentModelsComponent.ts | 11 ++++++-- .../models/manageModels/manageModelsDialog.ts | 4 +-- .../manageModels/modelImportLocationPage.ts | 12 +++++++-- .../prediction/inputColumnsComponent.ts | 12 +++++++-- .../views/models/tableSelectionComponent.ts | 20 +++++++++------ .../src/views/widgets/dashboardWidget.ts | 12 ++++++--- 10 files changed, 81 insertions(+), 36 deletions(-) diff --git a/extensions/machine-learning/package.json b/extensions/machine-learning/package.json index 544241b7a1..e3238bfad0 100644 --- a/extensions/machine-learning/package.json +++ b/extensions/machine-learning/package.json @@ -30,22 +30,22 @@ "type": "object", "title": "%mls.configuration.title%", "properties": { - "machineLearningServices.enablePython": { + "machineLearning.enablePython": { "type": "boolean", "default": "true", "description": "%mls.enablePython.description%" }, - "machineLearningServices.enableR": { + "machineLearning.enableR": { "type": "boolean", "default": "false", "description": "%mls.enableR.description%" }, - "machineLearningServices.pythonPath": { + "machineLearning.pythonPath": { "type": "string", "default": "", "description": "%mls.pythonPath.description%" }, - "machineLearningServices.rPath": { + "machineLearning.rPath": { "type": "string", "default": "", "description": "%mls.rPath.description%" diff --git a/extensions/machine-learning/src/common/constants.ts b/extensions/machine-learning/src/common/constants.ts index 3c6d1ba32a..71565acfb2 100644 --- a/extensions/machine-learning/src/common/constants.ts +++ b/extensions/machine-learning/src/common/constants.ts @@ -35,7 +35,7 @@ export const notebookCommandNew = 'notebook.command.new'; // Configurations // -export const mlsConfigKey = 'machineLearningServices'; +export const mlsConfigKey = 'machineLearning'; export const pythonPathConfigKey = 'pythonPath'; export const pythonEnabledConfigKey = 'enablePython'; export const rEnabledConfigKey = 'enableR'; @@ -125,10 +125,14 @@ export const extLangInstallFailedError = localize('extLang.installFailedError', export const extLangUpdateFailedError = localize('extLang.updateFailedError', "Failed to update language"); export const modelUpdateFailedError = localize('models.modelUpdateFailedError', "Failed to update the model"); -export const modelsListEmptyMessage = localize('models.modelsListEmptyMessage', "No Models Yet"); +export const modelsListEmptyMessage = localize('models.modelsListEmptyMessage', "No models yet"); export const modelsListEmptyDescription = localize('models.modelsListEmptyDescription', "Use import wizard to add models to this table"); export const databaseName = localize('databaseName', "Models database"); +export const databaseToStoreInfo = localize('databaseToStoreInfo', "Select a database to store the new model."); +export const tableToStoreInfo = localize('tableToStoreInfo', "Select an existing table that conforms the model schema or create a new one to store the new model."); export const tableName = localize('tableName', "Models table"); +export const modelTableInfo = localize('modelTableInfo', "Select a model table to view the list of existing / imported models."); +export const modelDatabaseInfo = localize('modelDatabaseInfo', "Select a database where existing / imported models are stored."); export const existingTableName = localize('existingTableName', "Existing table"); export const newTableName = localize('newTableName', "New table"); export const modelName = localize('models.name', "Name"); @@ -143,7 +147,9 @@ 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', "Source database"); +export const columnDatabaseInfo = localize('predict.columnDatabaseInfo', "Select the database containing the dataset to apply the prediction."); export const columnTable = localize('predict.columnTable', "Source table"); +export const columnTableInfo = localize('predict.columnTableInfo', "Select the table containing the dataset to apply the prediction."); export const inputColumns = localize('predict.inputColumns', "Model Input mapping"); export const outputColumns = localize('predict.outputColumns', "Model output"); export const columnName = localize('predict.columnName', "Source columns"); @@ -173,13 +179,14 @@ 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 importedModelTitle = localize('models.importedModelTitle', "Imported models"); +export const importModelTitle = localize('models.importModelTitle', "Import or view 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 importModelDesc = localize('models.importModelDesc', "Import or view machine learning models stored in database"); 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 makePredictionDesc = localize('models.makePredictionDesc', "Generate 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 createNotebookDesc = localize('models.createNotebookDesc', "Run experiments and create models in a notebook"); 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"); @@ -204,7 +211,7 @@ export const selectModelsTableMessage = localize('models.selectModelsTableMessag export const modelSchemaIsNotAcceptedMessage = localize('models.modelSchemaIsNotAcceptedMessage', "Invalid table structure"); 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 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"); @@ -216,6 +223,8 @@ 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 sqlMlExtDocTitle = localize('sqlMlExtDocTitle', "Machine Learning extension in Azure Data Studio"); +export const sqlMlExtDocDesc = localize('sqlMlExtDocDesc', "Learn how to use Machine Learning extension in Azure Data Studio, to manage packages, make predictions, and import models."); 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."); @@ -230,9 +239,11 @@ export const onnxOnEdgeOdbcDocDesc = localize('onnxOnEdgeOdbcDocDesc', "Get star // export const odbcDriverDocuments = 'https://go.microsoft.com/fwlink/?linkid=2129818'; export const mlDocLink = 'https://go.microsoft.com/fwlink/?linkid=2128671'; +export const mlExtDocLink = 'https://go.microsoft.com/fwlink/?linkid=2129918'; export const mlsDocLink = 'https://go.microsoft.com/fwlink/?linkid=2128672'; export const mlsAzureDocLink = 'https://go.microsoft.com/fwlink/?linkid=2128673'; export const onnxOnEdgeDocs = 'https://go.microsoft.com/fwlink/?linkid=2128882'; +export const managePackagesDocs = 'https://go.microsoft.com/fwlink/?linkid=2129919'; // CSS Styles // diff --git a/extensions/machine-learning/src/packageManagement/packageManager.ts b/extensions/machine-learning/src/packageManagement/packageManager.ts index 98e98979fd..10ef6713c8 100644 --- a/extensions/machine-learning/src/packageManagement/packageManager.ts +++ b/extensions/machine-learning/src/packageManagement/packageManager.ts @@ -96,7 +96,10 @@ export class PackageManager { defaultProviderId: defaultProvider.providerId }); } else { - this._apiWrapper.showInfoMessage(constants.managePackageCommandError); + const result = await this._apiWrapper.showInfoMessage(constants.managePackageCommandError, constants.learnMoreTitle); + if (result === constants.learnMoreTitle) { + await this._apiWrapper.openExternal(vscode.Uri.parse(constants.managePackagesDocs)); + } } } catch (err) { this._apiWrapper.showErrorMessage(err); diff --git a/extensions/machine-learning/src/test/packageManagement/packageManager.test.ts b/extensions/machine-learning/src/test/packageManagement/packageManager.test.ts index 66353dd73a..2cfd1dedaf 100644 --- a/extensions/machine-learning/src/test/packageManagement/packageManager.test.ts +++ b/extensions/machine-learning/src/test/packageManagement/packageManager.test.ts @@ -48,14 +48,14 @@ describe('Package Manager', () => { let connection = new azdata.connection.ConnectionProfile(); testContext.apiWrapper.setup(x => x.getCurrentConnection()).returns(() => {return Promise.resolve(connection);}); testContext.apiWrapper.setup(x => x.executeCommand(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => {return Promise.resolve();}); - testContext.apiWrapper.setup(x => x.showInfoMessage(TypeMoq.It.isAny())); + testContext.apiWrapper.setup(x => x.showInfoMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny())); testContext.serverConfigManager.setup(x => x.isPythonInstalled(connection)).returns(() => {return Promise.resolve(false);}); testContext.serverConfigManager.setup(x => x.isRInstalled(connection)).returns(() => {return Promise.resolve(false);}); testContext.serverConfigManager.setup(x => x.isPythonInstalled(connection)).returns(() => {return Promise.resolve(true);}); testContext.serverConfigManager.setup(x => x.enableExternalScriptConfig(connection)).returns(() => {return Promise.resolve(true);}); let packageManager = createPackageManager(testContext); await packageManager.managePackages(); - testContext.apiWrapper.verify(x => x.showInfoMessage(TypeMoq.It.isAny()), TypeMoq.Times.once()); + testContext.apiWrapper.verify(x => x.showInfoMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once()); }); it('Manage Package command Should show an error for no connection', async function (): Promise { @@ -63,12 +63,12 @@ describe('Package Manager', () => { let connection: azdata.connection.ConnectionProfile; testContext.apiWrapper.setup(x => x.getCurrentConnection()).returns(() => {return Promise.resolve(connection);}); testContext.apiWrapper.setup(x => x.executeCommand(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => {return Promise.resolve();}); - testContext.apiWrapper.setup(x => x.showInfoMessage(TypeMoq.It.isAny())); + testContext.apiWrapper.setup(x => x.showInfoMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny())); testContext.serverConfigManager.setup(x => x.enableExternalScriptConfig(connection)).returns(() => {return Promise.resolve(true);}); let packageManager = createPackageManager(testContext); await packageManager.managePackages(); - testContext.apiWrapper.verify(x => x.showInfoMessage(TypeMoq.It.isAny()), TypeMoq.Times.once()); + testContext.apiWrapper.verify(x => x.showInfoMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once()); }); it('installDependencies Should download sqlmlutils if does not exist', async function (): Promise { diff --git a/extensions/machine-learning/src/views/models/manageModels/currentModelsComponent.ts b/extensions/machine-learning/src/views/models/manageModels/currentModelsComponent.ts index 4b736638c5..325c263930 100644 --- a/extensions/machine-learning/src/views/models/manageModels/currentModelsComponent.ts +++ b/extensions/machine-learning/src/views/models/manageModels/currentModelsComponent.ts @@ -39,8 +39,15 @@ export class CurrentModelsComponent extends ModelViewBase implements IPageView { * @param modelBuilder register the components */ public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component { - this._tableSelectionComponent = new TableSelectionComponent(this._apiWrapper, this, { editable: false, preSelected: true }); - this._tableSelectionComponent.registerComponent(modelBuilder, constants.databaseName, constants.tableName); + this._tableSelectionComponent = new TableSelectionComponent(this._apiWrapper, this, { + editable: false, + preSelected: true, + databaseTitle: constants.databaseName, + tableTitle: constants.tableName, + databaseInfo: constants.modelDatabaseInfo, + tableInfo: constants.modelTableInfo + }); + this._tableSelectionComponent.registerComponent(modelBuilder); this._tableSelectionComponent.onSelectedChanged(async () => { await this.onTableSelected(); }); diff --git a/extensions/machine-learning/src/views/models/manageModels/manageModelsDialog.ts b/extensions/machine-learning/src/views/models/manageModels/manageModelsDialog.ts index cd4d49b88e..02616a7ea6 100644 --- a/extensions/machine-learning/src/views/models/manageModels/manageModelsDialog.ts +++ b/extensions/machine-learning/src/views/models/manageModels/manageModelsDialog.ts @@ -34,12 +34,12 @@ export class ManageModelsDialog extends ModelViewBase { selectable: false }); - let registerModelButton = this._apiWrapper.createButton(constants.importModelTitle); + let registerModelButton = this._apiWrapper.createButton(constants.registerModelTitle); registerModelButton.onClick(async () => { await this.sendDataRequest(RegisterModelEventName, this.currentLanguagesTab?.modelTable?.importTable); }); - let dialog = this.dialogView.createDialog(constants.registerModelTitle, [this.currentLanguagesTab]); + let dialog = this.dialogView.createDialog(constants.importedModelTitle, [this.currentLanguagesTab]); dialog.isWide = true; dialog.customButtons = [registerModelButton]; this.mainViewPanel = dialog; diff --git a/extensions/machine-learning/src/views/models/manageModels/modelImportLocationPage.ts b/extensions/machine-learning/src/views/models/manageModels/modelImportLocationPage.ts index f39f68746a..7880cccdb2 100644 --- a/extensions/machine-learning/src/views/models/manageModels/modelImportLocationPage.ts +++ b/extensions/machine-learning/src/views/models/manageModels/modelImportLocationPage.ts @@ -35,7 +35,15 @@ export class ModelImportLocationPage extends ModelViewBase implements IPageView, public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component { this._formBuilder = modelBuilder.formContainer(); - this.tableSelectionComponent = new TableSelectionComponent(this._apiWrapper, this, { editable: true, preSelected: true }); + this.tableSelectionComponent = new TableSelectionComponent(this._apiWrapper, this, + { + editable: true, + preSelected: true, + databaseTitle: constants.databaseName, + tableTitle: constants.tableName, + databaseInfo: constants.databaseToStoreInfo, + tableInfo: constants.tableToStoreInfo + }); this._descriptionComponent = modelBuilder.text().withProperties({ width: 200 }).component(); @@ -74,7 +82,7 @@ export class ModelImportLocationPage extends ModelViewBase implements IPageView, this.tableSelectionComponent.onSelectedChanged(async () => { await this.onTableSelected(); }); - this.tableSelectionComponent.registerComponent(modelBuilder, constants.databaseName, constants.tableName); + this.tableSelectionComponent.registerComponent(modelBuilder); this.tableSelectionComponent.addComponents(this._formBuilder); this._formBuilder.addFormItem({ diff --git a/extensions/machine-learning/src/views/models/prediction/inputColumnsComponent.ts b/extensions/machine-learning/src/views/models/prediction/inputColumnsComponent.ts index 94c1e9bfda..c34506508f 100644 --- a/extensions/machine-learning/src/views/models/prediction/inputColumnsComponent.ts +++ b/extensions/machine-learning/src/views/models/prediction/inputColumnsComponent.ts @@ -35,8 +35,16 @@ export class InputColumnsComponent extends ModelViewBase implements IDataCompone * @param modelBuilder model builder */ public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component { - this._tableSelectionComponent = new TableSelectionComponent(this._apiWrapper, this, { editable: false, preSelected: false }); - this._tableSelectionComponent.registerComponent(modelBuilder, constants.columnDatabase, constants.columnTable); + this._tableSelectionComponent = new TableSelectionComponent(this._apiWrapper, this, + { + editable: false, + preSelected: false, + databaseTitle: constants.columnDatabase, + tableTitle: constants.columnTable, + databaseInfo: constants.columnDatabaseInfo, + tableInfo: constants.columnTableInfo + }); + this._tableSelectionComponent.registerComponent(modelBuilder); this._tableSelectionComponent.onSelectedChanged(async () => { await this.onTableSelected(); }); diff --git a/extensions/machine-learning/src/views/models/tableSelectionComponent.ts b/extensions/machine-learning/src/views/models/tableSelectionComponent.ts index be7a9a0a61..a95dd66c25 100644 --- a/extensions/machine-learning/src/views/models/tableSelectionComponent.ts +++ b/extensions/machine-learning/src/views/models/tableSelectionComponent.ts @@ -13,7 +13,11 @@ import * as constants from '../../common/constants'; export interface ITableSelectionSettings { editable: boolean, - preSelected: boolean + preSelected: boolean, + databaseTitle: string, + tableTitle: string, + databaseInfo: string, + tableInfo: string } /** * View to render filters to pick an azure resource @@ -47,7 +51,7 @@ export class TableSelectionComponent extends ModelViewBase implements IDataCompo * Register components * @param modelBuilder model builder */ - public registerComponent(modelBuilder: azdata.ModelBuilder, databaseTitle: string, tableTitle: string): azdata.Component { + public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component { this._databases = modelBuilder.dropDown().withProperties({ width: this.componentMaxLength, }).component(); @@ -111,23 +115,23 @@ export class TableSelectionComponent extends ModelViewBase implements IDataCompo }); const databaseForm = modelBuilder.formContainer().withFormItems([{ - title: databaseTitle, + title: this._settings.databaseTitle, component: this._databases, - }], { info: databaseTitle }).withLayout({ + }], { info: this._settings.databaseInfo }).withLayout({ padding: '0px' }).component(); const tableForm = modelBuilder.formContainer(); if (this._settings.editable) { tableForm.addFormItem({ - title: tableTitle, + title: this._settings.tableTitle, component: group - }, { info: tableTitle }); + }, { info: this._settings.tableInfo }); } else { tableForm.addFormItem({ - title: tableTitle, + title: this._settings.tableTitle, component: this._tables - }, { info: tableTitle }); + }, { info: this._settings.tableInfo }); } this._dbTableComponent = modelBuilder.flexContainer().withItems([ diff --git a/extensions/machine-learning/src/views/widgets/dashboardWidget.ts b/extensions/machine-learning/src/views/widgets/dashboardWidget.ts index f8732441ab..33a00b6f6e 100644 --- a/extensions/machine-learning/src/views/widgets/dashboardWidget.ts +++ b/extensions/machine-learning/src/views/widgets/dashboardWidget.ts @@ -339,6 +339,10 @@ export class DashboardWidget { }).component(); const links = [{ + title: constants.sqlMlExtDocTitle, + description: constants.sqlMlExtDocDesc, + link: constants.mlExtDocLink + }, { title: constants.sqlMlDocTitle, description: constants.sqlMlDocDesc, link: constants.mlDocLink @@ -461,7 +465,7 @@ export class DashboardWidget { dark: this.asAbsolutePath('images/makePredictions.svg'), light: this.asAbsolutePath('images/makePredictions.svg'), }, - link: '', + link: 'https://go.microsoft.com/fwlink/?linkid=2129795', command: constants.mlsPredictModelCommand }; const predictionButton = this.createTaskButton(view, predictionMetadata); @@ -472,7 +476,7 @@ export class DashboardWidget { dark: this.asAbsolutePath('images/manageModels.svg'), light: this.asAbsolutePath('images/manageModels.svg'), }, - link: '', + link: 'https://go.microsoft.com/fwlink/?linkid=2129796', command: constants.mlManageModelsCommand }; const importModelsButton = this.createTaskButton(view, importMetadata); @@ -483,7 +487,7 @@ export class DashboardWidget { dark: this.asAbsolutePath('images/createNotebook.svg'), light: this.asAbsolutePath('images/createNotebook.svg'), }, - link: '', + link: 'https://go.microsoft.com/fwlink/?linkid=2129920', command: constants.notebookCommandNew }; const notebookModelsButton = this.createTaskButton(view, notebookMetadata); @@ -551,7 +555,7 @@ export class DashboardWidget { CSSStyles: { 'padding': '0px', 'padding-bottom': '5px', - 'width': '180px', + 'width': '200px', 'margin': '0px', 'color': '#006ab1' }