diff --git a/extensions/machine-learning/images/emptyTable.svg b/extensions/machine-learning/images/emptyTable.svg
new file mode 100644
index 0000000000..15ce8973bc
--- /dev/null
+++ b/extensions/machine-learning/images/emptyTable.svg
@@ -0,0 +1,30 @@
+
diff --git a/extensions/machine-learning/images/invalidItem.svg b/extensions/machine-learning/images/invalidItem.svg
new file mode 100644
index 0000000000..9f6ebdda6d
--- /dev/null
+++ b/extensions/machine-learning/images/invalidItem.svg
@@ -0,0 +1,3 @@
+
diff --git a/extensions/machine-learning/images/validItem.svg b/extensions/machine-learning/images/validItem.svg
new file mode 100644
index 0000000000..6187cb83d1
--- /dev/null
+++ b/extensions/machine-learning/images/validItem.svg
@@ -0,0 +1,4 @@
+
diff --git a/extensions/machine-learning/src/common/constants.ts b/extensions/machine-learning/src/common/constants.ts
index 08b0d84194..9df654e946 100644
--- a/extensions/machine-learning/src/common/constants.ts
+++ b/extensions/machine-learning/src/common/constants.ts
@@ -150,6 +150,8 @@ export const extLangUpdateFailedError = localize('extLang.updateFailedError', "F
export const modelUpdateFailedError = localize('models.modelUpdateFailedError', "Failed to update the model");
export const modelsListEmptyMessage = localize('models.modelsListEmptyMessage', "No models yet");
+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 databaseToStoreInfo = localize('databaseToStoreInfo', "Select a database to store the new model.");
@@ -234,7 +236,7 @@ export const modelsRequiredError = localize('models.modelsRequiredError', "Pleas
export const updateModelFailedError = localize('models.updateModelFailedError', "Failed to update the model");
export const modelSchemaIsAcceptedMessage = localize('models.modelSchemaIsAcceptedMessage', "Table meets requirements!");
export const selectModelsTableMessage = localize('models.selectModelsTableMessage', "Select models table");
-export const modelSchemaIsNotAcceptedMessage = localize('models.modelSchemaIsNotAcceptedMessage', "Invalid table structure");
+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 || ''); }
diff --git a/extensions/machine-learning/src/views/dataInfoComponent.ts b/extensions/machine-learning/src/views/dataInfoComponent.ts
new file mode 100644
index 0000000000..6cf26047c1
--- /dev/null
+++ b/extensions/machine-learning/src/views/dataInfoComponent.ts
@@ -0,0 +1,163 @@
+/*---------------------------------------------------------------------------------------------
+ * 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 vscode from 'vscode';
+import { ApiWrapper } from '../common/apiWrapper';
+import { ModelViewBase } from './models/modelViewBase';
+import { ViewBase } from './viewBase';
+
+
+export interface iconSettings {
+ width?: number,
+ height?: number,
+ css?: { [key: string]: string },
+ path?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri }
+}
+/**
+ * View to pick model source
+ */
+export class DataInfoComponent extends ViewBase {
+ private _labelContainer: azdata.FlexContainer | undefined;
+ private _labelComponent: azdata.TextComponent | undefined;
+ private _descriptionComponent: azdata.TextComponent | undefined;
+ private _loadingComponent: azdata.LoadingComponent | undefined;
+ private _width: number = 100;
+ private _height: number = 100;
+ private _title: string = '';
+ private _description: string = '';
+ private _iconComponent: azdata.ImageComponent | undefined;
+ private _iconSettings: iconSettings | undefined;
+
+
+ constructor(apiWrapper: ApiWrapper, parent: ModelViewBase) {
+ super(apiWrapper, parent.root, parent);
+ }
+
+ public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component {
+ this._descriptionComponent = modelBuilder.text().withProperties({
+ width: 200
+ }).component();
+ this._labelComponent = modelBuilder.text().withProperties({
+ width: 200
+ }).component();
+ this._labelContainer = modelBuilder.flexContainer().withLayout({
+ flexFlow: 'column',
+ width: this._width,
+ height: this._height,
+ justifyContent: 'center',
+ alignItems: 'center',
+ textAlign: 'center'
+ }).component();
+
+ if (!this._iconSettings) {
+ this._iconSettings = {
+ css: {},
+ height: 50,
+ width: 50,
+ path: ''
+ ,
+ };
+ }
+
+ this._iconComponent = modelBuilder.image().withProperties({
+ width: 100,
+ height: 100,
+ iconWidth: this._iconSettings.width,
+ iconHeight: this._iconSettings.height,
+ title: this._title
+ }).component();
+ let iconContainer = modelBuilder.flexContainer().withLayout({
+ width: 100,
+ }).component();
+
+ iconContainer.addItem(this._iconComponent, {
+ CSSStyles: this._iconSettings.css
+ });
+
+ this._labelContainer.addItem(iconContainer);
+ this._labelContainer.addItem(
+ this._labelComponent
+ , {
+ CSSStyles: {
+ 'font-size': '16px'
+ }
+ });
+ this._labelContainer.addItem(
+ this._descriptionComponent
+ , {
+ CSSStyles: {
+ 'font-size': '13px'
+ }
+ });
+
+ this._loadingComponent = modelBuilder.loadingComponent().withItem(
+ this._labelContainer
+ ).withProperties({
+ loading: false
+ }).component();
+
+ return this._loadingComponent;
+ }
+
+ public set width(value: number) {
+ this._width = value;
+ }
+
+ public set height(value: number) {
+ this._height = value;
+ }
+
+ public set title(value: string) {
+ this._title = value;
+ }
+
+ public set description(value: string) {
+ this._description = value;
+ }
+
+ public set iconSettings(value: iconSettings) {
+ this._iconSettings = value;
+ }
+
+ public get iconSettings(): iconSettings {
+ return this._iconSettings || {};
+ }
+
+ public get component(): azdata.Component | undefined {
+ return this._loadingComponent;
+ }
+
+ public loading(): void {
+ if (this._loadingComponent) {
+ this._loadingComponent.loading = true;
+ }
+ }
+
+ public loaded(): void {
+ if (this._loadingComponent) {
+ this._loadingComponent.loading = false;
+ }
+ }
+
+ public async refresh(): Promise {
+ this.loaded();
+ if (this._labelComponent) {
+ this._labelComponent.value = this._title;
+ }
+ if (this._descriptionComponent) {
+ this._descriptionComponent.value = this._description;
+ }
+
+ if (this._iconComponent) {
+ this._iconComponent.iconPath = this._iconSettings?.path;
+ }
+ if (this._labelContainer) {
+ this._labelContainer.height = this._height;
+ this._labelContainer.width = this._width;
+ }
+ return Promise.resolve();
+ }
+}
diff --git a/extensions/machine-learning/src/views/models/azureModelsComponent.ts b/extensions/machine-learning/src/views/models/azureModelsComponent.ts
index 557981c738..8ac45799a2 100644
--- a/extensions/machine-learning/src/views/models/azureModelsComponent.ts
+++ b/extensions/machine-learning/src/views/models/azureModelsComponent.ts
@@ -11,6 +11,8 @@ import { AzureModelsTable } from './azureModelsTable';
import { IDataComponent, AzureModelResource } from '../interfaces';
import { ModelArtifact } from './prediction/modelArtifact';
import { AzureSignInComponent } from './azureSignInComponent';
+import { DataInfoComponent } from '../dataInfoComponent';
+import * as constants from '../../common/constants';
export class AzureModelsComponent extends ModelViewBase implements IDataComponent {
@@ -21,6 +23,7 @@ export class AzureModelsComponent extends ModelViewBase implements IDataComponen
private _loader: azdata.LoadingComponent | undefined;
private _form: azdata.FormContainer | undefined;
private _downloadedFile: ModelArtifact | undefined;
+ private _emptyModelsComponent: DataInfoComponent | undefined;
/**
* Component to render a view to pick an azure model
@@ -49,9 +52,32 @@ export class AzureModelsComponent extends ModelViewBase implements IDataComponen
this._downloadedFile = undefined;
});
+ this._emptyModelsComponent = new DataInfoComponent(this._apiWrapper, this);
+ this._emptyModelsComponent.width = 300;
+ this._emptyModelsComponent.height = 250;
+ this._emptyModelsComponent.iconSettings = {
+ css: { 'padding-top': '20px' },
+ width: 100,
+ height: 100
+ };
+
+ this._emptyModelsComponent.registerComponent(modelBuilder);
+
this.azureFilterComponent.onWorkspacesSelectedChanged(async () => {
await this.onLoading();
await this.azureModelsTable?.loadData(this.azureFilterComponent?.data);
+ if (this._emptyModelsComponent) {
+ if (this.azureModelsTable?.isTableEmpty) {
+ this._emptyModelsComponent.title = constants.azureModelsListEmptyTitle;
+ this._emptyModelsComponent.description = constants.azureModelsListEmptyDescription;
+ this._emptyModelsComponent.iconSettings.path = this.asAbsolutePath('images/emptyTable.svg');
+ } else {
+ this._emptyModelsComponent.title = '';
+ this._emptyModelsComponent.description = '';
+ this._emptyModelsComponent.iconSettings.path = 'noicon';
+ }
+ await this._emptyModelsComponent.refresh();
+ }
await this.onLoaded();
});
@@ -80,23 +106,31 @@ export class AzureModelsComponent extends ModelViewBase implements IDataComponen
}
private addAzureComponents(formBuilder: azdata.FormBuilder) {
- if (this.azureFilterComponent && this._loader) {
+ if (this.azureFilterComponent && this._loader && this._emptyModelsComponent?.component) {
this.azureFilterComponent.addComponents(formBuilder);
formBuilder.addFormItems([{
title: '',
component: this._loader
- }]);
+ }], { horizontal: true });
+ formBuilder.addFormItems([{
+ title: '',
+ component: this._emptyModelsComponent.component
+ }], { horizontal: true });
}
}
private removeAzureComponents(formBuilder: azdata.FormBuilder) {
- if (this.azureFilterComponent && this._loader) {
+ if (this.azureFilterComponent && this._loader && this._emptyModelsComponent?.component) {
this.azureFilterComponent.removeComponents(formBuilder);
formBuilder.removeFormItem({
title: '',
component: this._loader
});
+ formBuilder.removeFormItem({
+ title: '',
+ component: this._emptyModelsComponent.component
+ });
}
}
diff --git a/extensions/machine-learning/src/views/models/azureModelsTable.ts b/extensions/machine-learning/src/views/models/azureModelsTable.ts
index eb2e8f7ef7..1dd41a47aa 100644
--- a/extensions/machine-learning/src/views/models/azureModelsTable.ts
+++ b/extensions/machine-learning/src/views/models/azureModelsTable.ts
@@ -39,6 +39,18 @@ export class AzureModelsTable extends ModelViewBase implements IDataComponent(
{
columns: [
+ { // Action
+ displayName: '',
+ valueType: azdata.DeclarativeDataType.component,
+ isReadOnly: true,
+ width: 50,
+ headerCssStyles: {
+ ...constants.cssStyles.tableHeader
+ },
+ rowCssStyles: {
+ ...constants.cssStyles.tableRow
+ },
+ },
{ // Name
displayName: constants.modelName,
ariaLabel: constants.modelName,
@@ -90,18 +102,6 @@ export class AzureModelsTable extends ModelViewBase implements IDataComponent {
- if (this._table && workspaceResource) {
- this._models = await this.listAzureModels(workspaceResource);
- let tableData: any[][] = [];
+ if (this._table) {
+ if (workspaceResource) {
+ this._models = await this.listAzureModels(workspaceResource);
+ let tableData: any[][] = [];
- if (this._models) {
- tableData = tableData.concat(this._models.map(model => this.createTableRow(model)));
+ if (this._models) {
+ tableData = tableData.concat(this._models.map(model => this.createTableRow(model)));
+ }
+
+ if (this.isTableEmpty) {
+ this._table.dataValues = [];
+ } else {
+ this._table.data = tableData;
+ }
+ } else {
+ this._table.dataValues = [];
}
-
- this._table.data = tableData;
}
this._onModelSelectionChanged.fire();
}
+ public get isTableEmpty(): boolean {
+ return !this._models || this._models.length === 0;
+ }
+
private createTableRow(model: WorkspaceModel): any[] {
if (this._modelBuilder) {
let selectModelButton: azdata.Component;
@@ -172,7 +184,7 @@ export class AzureModelsTable extends ModelViewBase implements IDataComponent {
await this.onTableSelected();
@@ -85,10 +59,12 @@ export class ModelImportLocationPage extends ModelViewBase implements IPageView,
this.tableSelectionComponent.registerComponent(modelBuilder);
this.tableSelectionComponent.addComponents(this._formBuilder);
- this._formBuilder.addFormItem({
- title: '',
- component: this._labelContainer
- });
+ if (this._dataInfoComponent.component) {
+ this._formBuilder.addFormItem({
+ title: '',
+ component: this._dataInfoComponent.component
+ });
+ }
this._form = this._formBuilder.component();
return this._form;
}
@@ -98,22 +74,28 @@ export class ModelImportLocationPage extends ModelViewBase implements IPageView,
this.importTable = this.tableSelectionComponent?.data;
}
- if (this.importTable && this._labelComponent) {
+ if (this.importTable && this._dataInfoComponent) {
+ this._dataInfoComponent.loading();
// Add table name to the models imported.
// 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()) {
- this._labelComponent.value = constants.selectModelsTableMessage;
+ this._dataInfoComponent.title = constants.selectModelsTableMessage;
+ this._dataInfoComponent.iconSettings.path = 'noicon';
} else {
const validated = await this.verifyImportConfigTable(this.importTable);
if (validated) {
- this._labelComponent.value = constants.modelSchemaIsAcceptedMessage;
+ this._dataInfoComponent.title = constants.modelSchemaIsAcceptedMessage;
+ this._dataInfoComponent.iconSettings.path = this.asAbsolutePath('images/validItem.svg');
} else {
- this._labelComponent.value = constants.modelSchemaIsNotAcceptedMessage;
+ this._dataInfoComponent.title = constants.modelSchemaIsNotAcceptedMessage;
+ this._dataInfoComponent.iconSettings.path = this.asAbsolutePath('images/invalidItem.svg');
}
}
+
+ await this._dataInfoComponent.refresh();
}
}
@@ -143,6 +125,10 @@ export class ModelImportLocationPage extends ModelViewBase implements IPageView,
if (this.tableSelectionComponent) {
await this.tableSelectionComponent.refresh();
}
+
+ if (this._dataInfoComponent) {
+ await this._dataInfoComponent.refresh();
+ }
}
/**