diff --git a/extensions/machine-learning/images/emptyState.svg b/extensions/machine-learning/images/emptyState.svg new file mode 100644 index 0000000000..15ce8973bc --- /dev/null +++ b/extensions/machine-learning/images/emptyState.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extensions/machine-learning/src/common/constants.ts b/extensions/machine-learning/src/common/constants.ts index 018c340dab..dea4aecaac 100644 --- a/extensions/machine-learning/src/common/constants.ts +++ b/extensions/machine-learning/src/common/constants.ts @@ -149,14 +149,16 @@ 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 found"); +export const selectModelTableMessage = localize('models.selectModelTableMessage', "Select table"); +export const selectModelDatabaseMessage = localize('models.selectModelDatabaseMessage', "Select Database"); export const azureModelsListEmptyTitle = localize('models.azureModelsListEmptyTitle', "No models found"); export const azureModelsListEmptyDescription = localize('models.azureModelsListEmptyDescription', "Select another Azure ML workspace"); -export const modelsListEmptyDescription = localize('models.modelsListEmptyDescription', "Use import wizard to add models to this table"); -export const databaseName = localize('databaseName', "Models database"); +export const modelsListEmptyDescription = localize('models.modelsListEmptyDescription', "Select another database or table"); +export const databaseName = localize('databaseName', "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 imported model."); -export const tableName = localize('tableName', "Models table"); +export const tableName = localize('tableName', "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"); @@ -183,6 +185,8 @@ export const dataTypeName = localize('predict.dataTypeName', "Type"); export const displayName = localize('predict.displayName', "Display name"); export const inputName = localize('predict.inputName', "Model input"); export const selectColumnTitle = localize('predict.selectColumnTitle', "Select column..."); +export const selectModelDatabaseTitle = localize('models.selectModelDatabaseTitle', "Select database with models"); +export const selectModelTableTitle = localize('models.selectModelTableTitle', "Select tables with models"); export const selectDatabaseTitle = localize('predict.selectDatabaseTitle', "Select database"); export const selectTableTitle = localize('predict.selectTableTitle', "Select table"); export const outputName = localize('predict.outputName', "Name"); @@ -210,7 +214,13 @@ export const currentModelsTitle = localize('models.currentModelsTitle', "Models" export const importModelDoneButton = localize('models.importModelDoneButton', "Import"); export const predictModel = localize('models.predictModel', "Predict"); export const registerModelTitle = localize('models.RegisterWizard', "Import models"); -export const importedModelTitle = localize('models.importedModelTitle', "Imported models"); +export const viewImportModelsTitle = localize('models.viewImportModelsTitle', "View and import models"); +export const viewImportModelsDesc = localize('models.viewImportModelsDesc', + "Machine Learning models can be stored in one or more databases and tables. Select the model database and table to view the models within them."); +export const viewImportModeledForPredictDesc = localize('models.viewImportModeledForPredictDesc', + "The models are stored in one or more databases and tables. Select the model database and table to view models in them."); +export const learnMoreLink = localize('models.learnMoreLink', "Learn more."); + export const importModelTitle = localize('models.importModelTitle', "Import or view models"); export const editModelTitle = localize('models.editModelTitle', "Edit model"); export const importModelDesc = localize('models.importModelDesc', "Import or view machine learning models stored in database"); @@ -268,6 +278,8 @@ export const mlsInstallOdbcDocDesc = localize('mlsInstallOdbcDocDesc', "This doc export const onnxOnEdgeOdbcDocTitle = localize('onnxOnEdgeOdbcDocTitle', "Machine learning and AI with ONNX in SQL Database Edge Preview"); export const onnxOnEdgeOdbcDocDesc = localize('onnxOnEdgeOdbcDocDesc', "Get started with machine learning in Azure SQL Database Edge"); +export function getDataCount(dataCount: number): string { return localize('ml.dataCount', "Showing {0} model(s)", dataCount); } + // Links // export const odbcDriverDocuments = 'https://go.microsoft.com/fwlink/?linkid=2129818'; @@ -277,12 +289,13 @@ export const mlsDocLink = 'https://go.microsoft.com/fwlink/?linkid=2128672'; export const mlsMIDocLink = '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'; +export const importModelsDoc = 'https://go.microsoft.com/fwlink/?linkid=2129796'; // 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 tableHeader = { 'text-align': 'left', 'font-weight': 'bold', 'text-transform': 'capitalize', 'font-size': '10px', 'user-select': 'text', 'border': 'none', 'border-bottom': '1px solid #ccc' }; 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' }; diff --git a/extensions/machine-learning/src/views/dataInfoComponent.ts b/extensions/machine-learning/src/views/dataInfoComponent.ts index 6cf26047c1..5d4329072c 100644 --- a/extensions/machine-learning/src/views/dataInfoComponent.ts +++ b/extensions/machine-learning/src/views/dataInfoComponent.ts @@ -13,6 +13,8 @@ import { ViewBase } from './viewBase'; export interface iconSettings { width?: number, height?: number, + containerWidth?: number, + containerHeight?: number, css?: { [key: string]: string }, path?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri } } @@ -24,12 +26,13 @@ export class DataInfoComponent extends ViewBase { private _labelComponent: azdata.TextComponent | undefined; private _descriptionComponent: azdata.TextComponent | undefined; private _loadingComponent: azdata.LoadingComponent | undefined; - private _width: number = 100; - private _height: number = 100; + private _width: number = 200; + private _height: number = 200; private _title: string = ''; private _description: string = ''; private _iconComponent: azdata.ImageComponent | undefined; private _iconSettings: iconSettings | undefined; + private _defaultIconSize = 128; constructor(apiWrapper: ApiWrapper, parent: ModelViewBase) { @@ -38,9 +41,11 @@ export class DataInfoComponent extends ViewBase { public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component { this._descriptionComponent = modelBuilder.text().withProperties({ + value: this._description, width: 200 }).component(); this._labelComponent = modelBuilder.text().withProperties({ + value: this._title, width: 200 }).component(); this._labelContainer = modelBuilder.flexContainer().withLayout({ @@ -55,26 +60,26 @@ export class DataInfoComponent extends ViewBase { if (!this._iconSettings) { this._iconSettings = { css: {}, - height: 50, - width: 50, + height: this._defaultIconSize, + width: this._defaultIconSize, path: '' , }; } this._iconComponent = modelBuilder.image().withProperties({ - width: 100, - height: 100, - iconWidth: this._iconSettings.width, - iconHeight: this._iconSettings.height, + width: this._iconSettings?.containerWidth ?? this._defaultIconSize, + height: this._iconSettings?.containerHeight ?? this._defaultIconSize, + iconWidth: this._iconSettings?.width ?? this._defaultIconSize, + iconHeight: this._iconSettings?.height ?? this._defaultIconSize, title: this._title }).component(); let iconContainer = modelBuilder.flexContainer().withLayout({ - width: 100, + width: this._iconSettings?.containerWidth ?? this._defaultIconSize, }).component(); iconContainer.addItem(this._iconComponent, { - CSSStyles: this._iconSettings.css + CSSStyles: this._iconSettings?.css ?? {} }); this._labelContainer.addItem(iconContainer); diff --git a/extensions/machine-learning/src/views/models/azureModelsComponent.ts b/extensions/machine-learning/src/views/models/azureModelsComponent.ts index 8ac45799a2..b34375e308 100644 --- a/extensions/machine-learning/src/views/models/azureModelsComponent.ts +++ b/extensions/machine-learning/src/views/models/azureModelsComponent.ts @@ -57,8 +57,8 @@ export class AzureModelsComponent extends ModelViewBase implements IDataComponen this._emptyModelsComponent.height = 250; this._emptyModelsComponent.iconSettings = { css: { 'padding-top': '20px' }, - width: 100, - height: 100 + width: 128, + height: 128 }; this._emptyModelsComponent.registerComponent(modelBuilder); diff --git a/extensions/machine-learning/src/views/models/azureModelsTable.ts b/extensions/machine-learning/src/views/models/azureModelsTable.ts index 1dd41a47aa..b9cb25d031 100644 --- a/extensions/machine-learning/src/views/models/azureModelsTable.ts +++ b/extensions/machine-learning/src/views/models/azureModelsTable.ts @@ -132,11 +132,13 @@ export class AzureModelsTable extends ModelViewBase implements IDataComponent { @@ -54,47 +63,76 @@ export class CurrentModelsComponent extends ModelViewBase implements IPageView { this._dataTable = new CurrentModelsTable(this._apiWrapper, this, this._settings); this._dataTable.registerComponent(modelBuilder); + let dataCountString: string = constants.getDataCount(0); + + this._tableDataCountContainer = modelBuilder.flexContainer().component(); + this._tableDataCountComponent = modelBuilder.text().withProperties({ + value: dataCountString, + margin: '0' + }).component(); + this._tableDataCountContainer.addItem(this._tableDataCountComponent, + { + CSSStyles: { + 'font-size': '13px', + 'margin': '0' + } + }); + + let formModelBuilder = modelBuilder.formContainer(); this._loader = modelBuilder.loadingComponent() .withItem(formModelBuilder.component()) .withProperties({ loading: true }).component(); - this._labelComponent = modelBuilder.text().withProperties({ - width: 200, - value: constants.modelsListEmptyMessage - }).component(); - this._descriptionComponent = modelBuilder.text().withProperties({ - width: 200, - value: constants.modelsListEmptyDescription - }).component(); + this._emptyModelsComponent = new DataInfoComponent(this._apiWrapper, this); + this._emptyModelsComponent.width = 200; + this._emptyModelsComponent.height = 250; + this._emptyModelsComponent.title = constants.modelsListEmptyMessage; + this._emptyModelsComponent.description = constants.modelsListEmptyDescription; + this._emptyModelsComponent.iconSettings = { + css: { 'padding-top': '30px' }, + path: this.asAbsolutePath('images/emptyTable.svg'), + width: 128, + height: 128 + }; + this._emptyModelsComponent.registerComponent(modelBuilder); this._labelContainer = modelBuilder.flexContainer().withLayout({ flexFlow: 'column', - width: 800, + width: '750px', height: '400px', - justifyContent: 'center' + justifyContent: 'flex-start', + textAlign: 'center' }).component(); + this._subheadingContainer = modelBuilder.flexContainer().withLayout({ + flexFlow: 'column', + width: '452px' + }).component(); + this._subheadingTextComponent = modelBuilder.text().withProperties({ + value: this.modelActionType === ModelActionType.Import ? constants.viewImportModelsDesc : constants.viewImportModeledForPredictDesc, + CSSStyles: { + 'font-size': '13px' + } + }).component(); + this._subheadingLinkComponent = modelBuilder.hyperlink().withProperties({ + label: constants.learnMoreLink, + url: constants.importModelsDoc, + CSSStyles: { + 'font-size': '13px' + } + }).component(); + if (this._emptyModelsComponent.component) { + this._labelContainer.addItem(this._emptyModelsComponent.component + , { + CSSStyles: { + 'margin': '0 auto' + } + }); - this._labelContainer.addItem( - this._labelComponent - , { - CSSStyles: { - 'align-items': 'center', - 'padding-top': '30px', - 'padding-left': `${this.componentMaxLength}px`, - 'font-size': '16px' - } - }); - this._labelContainer.addItem( - this._descriptionComponent - , { - CSSStyles: { - 'align-items': 'center', - 'padding-top': '10px', - 'padding-left': `${this.componentMaxLength - 50}px`, - 'font-size': '13px' - } - }); + } + this._subheadingContainer.addItems( + [this._subheadingTextComponent, this._subheadingLinkComponent] + ); this.addComponents(formModelBuilder); return this._loader; @@ -102,20 +140,28 @@ export class CurrentModelsComponent extends ModelViewBase implements IPageView { public addComponents(formBuilder: azdata.FormBuilder) { this._formBuilder = formBuilder; - if (this._tableSelectionComponent && this._dataTable && this._labelContainer) { + if (this._tableSelectionComponent && this._dataTable && this._tableDataCountContainer && this._labelContainer && this._subheadingContainer) { + formBuilder.addFormItem({ title: '', component: this._subheadingContainer }); this._tableSelectionComponent.addComponents(formBuilder); + formBuilder.addFormItem({ title: '', component: this._tableDataCountContainer }); this._dataTable.addComponents(formBuilder); + if (this._dataTable.isEmpty) { formBuilder.addFormItem({ title: '', component: this._labelContainer }); } + if (this._tableDataCountComponent) { + this._tableDataCountComponent.value = constants.getDataCount(this._dataTable.modelCounts); + } } } public removeComponents(formBuilder: azdata.FormBuilder) { - if (this._tableSelectionComponent && this._dataTable && this._labelContainer) { + if (this._tableSelectionComponent && this._dataTable && this._labelContainer && this._tableDataCountContainer && this._subheadingContainer) { this._tableSelectionComponent.removeComponents(formBuilder); this._dataTable.removeComponents(formBuilder); formBuilder.removeFormItem({ title: '', component: this._labelContainer }); + formBuilder.removeFormItem({ title: '', component: this._tableDataCountContainer }); + formBuilder.removeFormItem({ title: '', component: this._subheadingContainer }); } } @@ -130,6 +176,9 @@ export class CurrentModelsComponent extends ModelViewBase implements IPageView { * Refreshes the view */ public async refresh(): Promise { + if (this._emptyModelsComponent) { + await this._emptyModelsComponent.refresh(); + } await this.onLoading(); try { @@ -161,6 +210,24 @@ export class CurrentModelsComponent extends ModelViewBase implements IPageView { await this.storeImportConfigTable(); if (this._dataTable) { await this._dataTable.refresh(); + if (this._emptyModelsComponent) { + if (this._tableSelectionComponent?.defaultDbNameIsSelected) { + this._emptyModelsComponent.title = constants.selectModelDatabaseMessage; + this._emptyModelsComponent.description = ''; + } else if (this._tableSelectionComponent?.defaultTableNameIsSelected) { + this._emptyModelsComponent.title = constants.selectModelTableMessage; + this._emptyModelsComponent.description = ''; + + } else { + this._emptyModelsComponent.title = constants.modelsListEmptyMessage; + this._emptyModelsComponent.description = constants.modelsListEmptyDescription; + } + await this._emptyModelsComponent.refresh(); + } + + if (this._tableDataCountComponent) { + this._tableDataCountComponent.value = constants.getDataCount(this._dataTable.modelCounts); + } } this.refreshComponents(); } diff --git a/extensions/machine-learning/src/views/models/manageModels/currentModelsTable.ts b/extensions/machine-learning/src/views/models/manageModels/currentModelsTable.ts index ea831d3b4d..203ab6316c 100644 --- a/extensions/machine-learning/src/views/models/manageModels/currentModelsTable.ts +++ b/extensions/machine-learning/src/views/models/manageModels/currentModelsTable.ts @@ -25,7 +25,7 @@ export class CurrentModelsTable extends ModelViewBase implements IDataComponent< private _downloadedFile: ModelArtifact | undefined; private _onModelSelectionChanged: vscode.EventEmitter = new vscode.EventEmitter(); public readonly onModelSelectionChanged: vscode.Event = this._onModelSelectionChanged.event; - public isEmpty: boolean = false; + public modelCounts: number = 0; /** * Creates new view @@ -40,7 +40,23 @@ export class CurrentModelsTable extends ModelViewBase implements IDataComponent< */ public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component { this._modelBuilder = modelBuilder; - let columns = [ + let columns: azdata.DeclarativeTableColumn[] = []; + if (this._settings.selectable) { + columns.push( + { // Action + displayName: '', + valueType: azdata.DeclarativeDataType.component, + isReadOnly: true, + width: 50, + headerCssStyles: { + ...constants.cssStyles.tableHeader + }, + rowCssStyles: { + ...constants.cssStyles.tableRow + }, + }); + } + columns.push(...[ { // Name displayName: constants.modelName, ariaLabel: constants.modelName, @@ -92,6 +108,20 @@ export class CurrentModelsTable extends ModelViewBase implements IDataComponent< rowCssStyles: { ...constants.cssStyles.tableRow }, + } + ]); + if (this._settings.editable) { + columns.push(...[{ // Action + displayName: '', + valueType: azdata.DeclarativeDataType.component, + isReadOnly: true, + width: 50, + headerCssStyles: { + ...constants.cssStyles.tableHeader + }, + rowCssStyles: { + ...constants.cssStyles.tableRow + }, }, { // Action displayName: '', @@ -105,22 +135,7 @@ export class CurrentModelsTable extends ModelViewBase implements IDataComponent< ...constants.cssStyles.tableRow }, } - ]; - if (this._settings.editable) { - columns.push( - { // Action - displayName: '', - valueType: azdata.DeclarativeDataType.component, - isReadOnly: true, - width: 50, - headerCssStyles: { - ...constants.cssStyles.tableHeader - }, - rowCssStyles: { - ...constants.cssStyles.tableRow - }, - } - ); + ]); columns.push( { // Action displayName: '', @@ -192,9 +207,10 @@ export class CurrentModelsTable extends ModelViewBase implements IDataComponent< tableData = tableData.concat(models.map(model => this.createTableRow(model))); } - this.isEmpty = models === undefined || models.length === 0; - + this.modelCounts = models === undefined || models.length === 0 ? 0 : models.length; this._table.data = tableData; + } else { + this.modelCounts = 0; } this.onModelSelected(); await this.onLoaded(); @@ -212,12 +228,16 @@ export class CurrentModelsTable extends ModelViewBase implements IDataComponent< } } + public get isEmpty(): boolean { + return this.modelCounts === 0; + } + private createTableRow(model: ImportedModel): any[] { let row: any[] = [model.modelName, model.created, model.version, model.framework]; if (this._modelBuilder) { const selectButton = this.createSelectButton(model); if (selectButton) { - row.push(selectButton); + row = [selectButton, model.modelName, model.created, model.version, model.framework]; } const editButtons = this.createEditButtons(model); if (editButtons && editButtons.length > 0) { diff --git a/extensions/machine-learning/src/views/models/manageModels/manageModelsDialog.ts b/extensions/machine-learning/src/views/models/manageModels/manageModelsDialog.ts index 02616a7ea6..704524783a 100644 --- a/extensions/machine-learning/src/views/models/manageModels/manageModelsDialog.ts +++ b/extensions/machine-learning/src/views/models/manageModels/manageModelsDialog.ts @@ -39,7 +39,7 @@ export class ManageModelsDialog extends ModelViewBase { await this.sendDataRequest(RegisterModelEventName, this.currentLanguagesTab?.modelTable?.importTable); }); - let dialog = this.dialogView.createDialog(constants.importedModelTitle, [this.currentLanguagesTab]); + let dialog = this.dialogView.createDialog(constants.viewImportModelsTitle, [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 e1eb10a6a9..3288db96c8 100644 --- a/extensions/machine-learning/src/views/models/manageModels/modelImportLocationPage.ts +++ b/extensions/machine-learning/src/views/models/manageModels/modelImportLocationPage.ts @@ -39,17 +39,23 @@ export class ModelImportLocationPage extends ModelViewBase implements IPageView, databaseTitle: constants.databaseName, tableTitle: constants.tableName, databaseInfo: constants.databaseToStoreInfo, - tableInfo: constants.tableToStoreInfo + tableInfo: constants.tableToStoreInfo, + defaultDbName: constants.selectModelDatabaseTitle, + defaultTableName: constants.selectModelTableTitle, + useImportModelCache: true }); this._dataInfoComponent = new DataInfoComponent(this._apiWrapper, this); - this._dataInfoComponent.width = 300; - this._dataInfoComponent.height = 300; + this._dataInfoComponent.width = 350; this._dataInfoComponent.iconSettings = { css: { 'border': 'solid', - 'margin': '5px' - } + 'margin': '5px', + }, + width: 50, + height: 50, + containerHeight: 100, + containerWidth: 100 }; this._dataInfoComponent.registerComponent(modelBuilder); @@ -70,8 +76,12 @@ export class ModelImportLocationPage extends ModelViewBase implements IPageView, } private async onTableSelected(): Promise { + let importTableIsValid = false; if (this.tableSelectionComponent?.data) { this.importTable = this.tableSelectionComponent?.data; + if (this.tableSelectionComponent !== undefined && this.tableSelectionComponent.isDataValid) { + importTableIsValid = true; + } } if (this.importTable && this._dataInfoComponent) { @@ -81,7 +91,7 @@ export class ModelImportLocationPage extends ModelViewBase implements IPageView, // Since Table name is picked last as per new flow this hasn't been set yet. this.modelsViewData?.forEach(x => x.targetImportTable = this.importTable); - if (!this.validateImportTableName()) { + if (!importTableIsValid) { this._dataInfoComponent.title = constants.selectModelsTableMessage; this._dataInfoComponent.iconSettings.path = 'noicon'; } else { @@ -99,11 +109,6 @@ export class ModelImportLocationPage extends ModelViewBase implements IPageView, } } - private validateImportTableName(): boolean { - return this.importTable?.databaseName !== undefined && this.importTable?.databaseName !== constants.selectDatabaseTitle - && this.importTable?.tableName !== undefined && this.importTable?.tableName !== constants.selectTableTitle; - } - /** * Returns selected data */ @@ -144,7 +149,7 @@ export class ModelImportLocationPage extends ModelViewBase implements IPageView, public async validate(): Promise { let validated = false; - if (this.data && this.validateImportTableName()) { + if (this.data && this.tableSelectionComponent !== undefined && this.tableSelectionComponent.isDataValid) { validated = true; validated = await this.verifyImportConfigTable(this.data); if (!validated) { diff --git a/extensions/machine-learning/src/views/models/prediction/columnsSelectionPage.ts b/extensions/machine-learning/src/views/models/prediction/columnsSelectionPage.ts index abb2c899e7..7226536213 100644 --- a/extensions/machine-learning/src/views/models/prediction/columnsSelectionPage.ts +++ b/extensions/machine-learning/src/views/models/prediction/columnsSelectionPage.ts @@ -77,7 +77,7 @@ export class ColumnsSelectionPage extends ModelViewBase implements IPageView, ID public async validate(): Promise { const data = this.data; const validated = data !== undefined && data.databaseName !== undefined && data.inputColumns !== undefined && data.outputColumns !== undefined - && data.tableName !== undefined && data.databaseName !== constants.selectDatabaseTitle && data.tableName !== constants.selectTableTitle + && data.tableName !== undefined && this.inputColumnsComponent !== undefined && this.inputColumnsComponent?.isDataValue && !data.inputColumns.find(x => (x.columnName === constants.selectColumnTitle) || !x.columnName); if (!validated) { this.showErrorMessage(constants.invalidModelParametersError); diff --git a/extensions/machine-learning/src/views/models/prediction/columnsTable.ts b/extensions/machine-learning/src/views/models/prediction/columnsTable.ts index bf359f9970..f49215b22f 100644 --- a/extensions/machine-learning/src/views/models/prediction/columnsTable.ts +++ b/extensions/machine-learning/src/views/models/prediction/columnsTable.ts @@ -162,7 +162,7 @@ export class ColumnsTable extends ModelViewBase implements IDataComponent | undefined; /** * Creates a new view @@ -42,7 +43,10 @@ export class InputColumnsComponent extends ModelViewBase implements IDataCompone databaseTitle: constants.columnDatabase, tableTitle: constants.columnTable, databaseInfo: constants.columnDatabaseInfo, - tableInfo: constants.columnTableInfo + tableInfo: constants.columnTableInfo, + defaultDbName: constants.selectDatabaseTitle, + defaultTableName: constants.selectTableTitle, + useImportModelCache: false }); this._tableSelectionComponent.registerComponent(modelBuilder); this._tableSelectionComponent.onSelectedChanged(async () => { @@ -99,6 +103,10 @@ export class InputColumnsComponent extends ModelViewBase implements IDataCompone }); } + public get isDataValue(): boolean { + return this._tableSelectionComponent !== undefined && this._tableSelectionComponent?.isDataValid; + } + /** * loads data in the components */ @@ -132,11 +140,21 @@ export class InputColumnsComponent extends ModelViewBase implements IDataCompone } private async onTableSelected(): Promise { - await this.loadWithTable(this.databaseTable); + if (this._tableSelectionComponent !== undefined && this._tableSelectionComponent.isDataValid) { + await this.loadWithTable(this.databaseTable); + } } public async loadWithTable(table: DatabaseTable): Promise { - await this._columns?.loadInputs(this._modelParameters, table); + if (this._tableLoadingPromise) { + try { + await this._tableLoadingPromise; + } catch { + } + } + this._tableLoadingPromise = this._columns?.loadInputs(this._modelParameters, table); + await this._tableLoadingPromise; + this._tableLoadingPromise = undefined; } private get databaseTable(): DatabaseTable { diff --git a/extensions/machine-learning/src/views/models/tableSelectionComponent.ts b/extensions/machine-learning/src/views/models/tableSelectionComponent.ts index a95dd66c25..e8532d7a58 100644 --- a/extensions/machine-learning/src/views/models/tableSelectionComponent.ts +++ b/extensions/machine-learning/src/views/models/tableSelectionComponent.ts @@ -9,7 +9,6 @@ import { ModelViewBase } from './modelViewBase'; import { ApiWrapper } from '../../common/apiWrapper'; import { IDataComponent } from '../interfaces'; import { DatabaseTable } from '../../prediction/interfaces'; -import * as constants from '../../common/constants'; export interface ITableSelectionSettings { editable: boolean, @@ -17,7 +16,11 @@ export interface ITableSelectionSettings { databaseTitle: string, tableTitle: string, databaseInfo: string, - tableInfo: string + tableInfo: string, + layout?: string, + defaultTableName: string; + defaultDbName: string; + useImportModelCache: boolean } /** * View to render filters to pick an azure resource @@ -53,10 +56,10 @@ export class TableSelectionComponent extends ModelViewBase implements IDataCompo */ public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component { this._databases = modelBuilder.dropDown().withProperties({ - width: this.componentMaxLength, + width: '221px' }).component(); this._tables = modelBuilder.dropDown().withProperties({ - width: this.componentMaxLength - 10, + width: '221px' }).component(); this._databases.onValueChanged(async () => { @@ -142,10 +145,10 @@ export class TableSelectionComponent extends ModelViewBase implements IDataCompo ], { flex: '0 0 auto', CSSStyles: { - 'align-items': 'flex-start' + 'align-items': 'flex-start', } }).withLayout({ - flexFlow: this._settings.editable ? 'column' : 'row', + flexFlow: this._settings.layout === 'horizontal' ? 'row' : 'column', justifyContent: 'space-between', width: this.tableMaxLength }).component(); @@ -190,14 +193,32 @@ export class TableSelectionComponent extends ModelViewBase implements IDataCompo return this.databaseTable; } + /** + * Returns selected data + */ + public get defaultDbNameIsSelected(): boolean { + return this.data === undefined || this.data.databaseName === this._settings.defaultDbName; + } + + /** + * Returns selected data + */ + public get defaultTableNameIsSelected(): boolean { + return this.data === undefined || this.data.tableName === this._settings.defaultTableName; + } + + public get isDataValid(): boolean { + return this.data !== undefined && this.data.databaseName !== this._settings.defaultDbName && this.data.tableName !== this._settings.defaultTableName; + } + /** * loads data in the components */ public async loadData(): Promise { this._dbNames = await this.listDatabaseNames(); let dbNames = this._dbNames; - if (!this._dbNames.find(x => x === constants.selectDatabaseTitle)) { - dbNames = [constants.selectDatabaseTitle].concat(this._dbNames); + if (!this._dbNames.find(x => x === this._settings.defaultDbName)) { + dbNames = [this._settings.defaultDbName].concat(this._dbNames); } if (this._databases && dbNames && dbNames.length > 0) { this._databases.values = dbNames; @@ -230,14 +251,14 @@ export class TableSelectionComponent extends ModelViewBase implements IDataCompo this.refreshTableComponent(); - if (this._tableNames && !this._tableNames.find(x => x.tableName === constants.selectTableTitle)) { - const firstRow: DatabaseTable = { tableName: constants.selectTableTitle, databaseName: '', schema: '' }; + if (this._tableNames && !this._tableNames.find(x => x.tableName === this._settings.defaultTableName)) { + const firstRow: DatabaseTable = { tableName: this._settings.defaultTableName, databaseName: '', schema: '' }; tableNames = [firstRow].concat(this._tableNames); } if (this._tables && tableNames && tableNames.length > 0) { this._tables.values = tableNames.map(t => this.getTableFullName(t)); - if (this.importTable && this.importTable.databaseName === this._databases?.value) { + if (this._settings.useImportModelCache && this.importTable && this.importTable.databaseName === this._databases?.value) { const selectedTable = tableNames.find(t => t.tableName === this.importTable?.tableName && t.schema === this.importTable?.schema); if (selectedTable) { this._selectedTableName = this.getTableFullName(selectedTable); @@ -266,7 +287,7 @@ export class TableSelectionComponent extends ModelViewBase implements IDataCompo } private getTableFullName(table: DatabaseTable): string { - return table.tableName === constants.selectTableTitle ? table.tableName : `${table.schema}.${table.tableName}`; + return table.tableName === this._settings.defaultTableName ? table.tableName : `${table.schema}.${table.tableName}`; } private async onTableSelected(): Promise {