mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
ML - New UI component for icon, title and description of an item (#13109)
* initial checkin * addressed PR comment
This commit is contained in:
@@ -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 || ''); }
|
||||
|
||||
163
extensions/machine-learning/src/views/dataInfoComponent.ts
Normal file
163
extensions/machine-learning/src/views/dataInfoComponent.ts
Normal file
@@ -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<void> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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<AzureModelResource[]> {
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,18 @@ export class AzureModelsTable extends ModelViewBase implements IDataComponent<Wo
|
||||
.withProperties<azdata.DeclarativeTableProperties>(
|
||||
{
|
||||
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<Wo
|
||||
rowCssStyles: {
|
||||
...constants.cssStyles.tableRow
|
||||
},
|
||||
},
|
||||
{ // Action
|
||||
displayName: '',
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
isReadOnly: true,
|
||||
width: 50,
|
||||
headerCssStyles: {
|
||||
...constants.cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
...constants.cssStyles.tableRow
|
||||
},
|
||||
}
|
||||
],
|
||||
data: [],
|
||||
@@ -121,19 +121,31 @@ export class AzureModelsTable extends ModelViewBase implements IDataComponent<Wo
|
||||
*/
|
||||
public async loadData(workspaceResource?: AzureWorkspaceResource | undefined): Promise<void> {
|
||||
|
||||
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<Wo
|
||||
selectModelButton = radioButton;
|
||||
}
|
||||
|
||||
return [model.name, model.createdTime, model.framework, model.frameworkVersion, selectModelButton];
|
||||
return [selectModelButton, model.name, model.createdTime, model.framework, model.frameworkVersion];
|
||||
}
|
||||
|
||||
return [];
|
||||
|
||||
@@ -74,44 +74,25 @@ export class AzureResourceFilterComponent extends ModelViewBase implements IData
|
||||
}, {
|
||||
title: constants.azureModelWorkspace,
|
||||
component: this._workspaces
|
||||
}]).component();
|
||||
}], {
|
||||
horizontal: true
|
||||
}).component();
|
||||
}
|
||||
|
||||
public addComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this._accounts && this._subscriptions && this._groups && this._workspaces) {
|
||||
if (this._form) {
|
||||
formBuilder.addFormItems([{
|
||||
title: constants.azureAccount,
|
||||
component: this._accounts
|
||||
}, {
|
||||
title: constants.azureSubscription,
|
||||
component: this._subscriptions
|
||||
}, {
|
||||
title: constants.azureGroup,
|
||||
component: this._groups
|
||||
}, {
|
||||
title: constants.azureModelWorkspace,
|
||||
component: this._workspaces
|
||||
title: '',
|
||||
component: this._form
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
public removeComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this._accounts && this._subscriptions && this._groups && this._workspaces) {
|
||||
if (this._form) {
|
||||
formBuilder.removeFormItem({
|
||||
title: constants.azureAccount,
|
||||
component: this._accounts
|
||||
});
|
||||
formBuilder.removeFormItem({
|
||||
title: constants.azureSubscription,
|
||||
component: this._subscriptions
|
||||
});
|
||||
formBuilder.removeFormItem({
|
||||
title: constants.azureGroup,
|
||||
component: this._groups
|
||||
});
|
||||
formBuilder.removeFormItem({
|
||||
title: constants.azureModelWorkspace,
|
||||
component: this._workspaces
|
||||
title: '',
|
||||
component: this._form
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import * as constants from '../../../common/constants';
|
||||
import { IPageView, IDataComponent } from '../../interfaces';
|
||||
import { TableSelectionComponent } from '../tableSelectionComponent';
|
||||
import { DatabaseTable } from '../../../prediction/interfaces';
|
||||
import { DataInfoComponent } from '../../dataInfoComponent';
|
||||
|
||||
/**
|
||||
* View to pick model source
|
||||
@@ -19,10 +20,7 @@ export class ModelImportLocationPage extends ModelViewBase implements IPageView,
|
||||
private _form: azdata.FormContainer | undefined;
|
||||
private _formBuilder: azdata.FormBuilder | undefined;
|
||||
public tableSelectionComponent: TableSelectionComponent | undefined;
|
||||
private _labelComponent: azdata.TextComponent | undefined;
|
||||
private _descriptionComponent: azdata.TextComponent | undefined;
|
||||
private _labelContainer: azdata.FlexContainer | undefined;
|
||||
|
||||
private _dataInfoComponent: DataInfoComponent | undefined;
|
||||
|
||||
constructor(apiWrapper: ApiWrapper, parent: ModelViewBase) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
@@ -33,7 +31,6 @@ export class ModelImportLocationPage extends ModelViewBase implements IPageView,
|
||||
* @param modelBuilder Register components
|
||||
*/
|
||||
public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component {
|
||||
|
||||
this._formBuilder = modelBuilder.formContainer();
|
||||
this.tableSelectionComponent = new TableSelectionComponent(this._apiWrapper, this,
|
||||
{
|
||||
@@ -44,40 +41,17 @@ export class ModelImportLocationPage extends ModelViewBase implements IPageView,
|
||||
databaseInfo: constants.databaseToStoreInfo,
|
||||
tableInfo: constants.tableToStoreInfo
|
||||
});
|
||||
this._descriptionComponent = modelBuilder.text().withProperties({
|
||||
width: 200
|
||||
}).component();
|
||||
this._labelComponent = modelBuilder.text().withProperties({
|
||||
width: 200
|
||||
}).component();
|
||||
this._labelContainer = modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: 800,
|
||||
height: '300px',
|
||||
justifyContent: 'center'
|
||||
}).component();
|
||||
|
||||
this._labelContainer.addItem(
|
||||
this._labelComponent
|
||||
, {
|
||||
CSSStyles: {
|
||||
'align-items': 'center',
|
||||
'padding-top': '10px',
|
||||
'padding-left': `${this.componentMaxLength}px`,
|
||||
'font-size': '16px'
|
||||
}
|
||||
});
|
||||
this._labelContainer.addItem(
|
||||
this._descriptionComponent
|
||||
, {
|
||||
CSSStyles: {
|
||||
'align-items': 'center',
|
||||
'padding-top': '10px',
|
||||
'padding-left': `${this.componentMaxLength - 80}px`,
|
||||
'font-size': '13px'
|
||||
}
|
||||
});
|
||||
this._dataInfoComponent = new DataInfoComponent(this._apiWrapper, this);
|
||||
|
||||
this._dataInfoComponent.width = 300;
|
||||
this._dataInfoComponent.height = 300;
|
||||
this._dataInfoComponent.iconSettings = {
|
||||
css: {
|
||||
'border': 'solid',
|
||||
'margin': '5px'
|
||||
}
|
||||
};
|
||||
this._dataInfoComponent.registerComponent(modelBuilder);
|
||||
|
||||
this.tableSelectionComponent.onSelectedChanged(async () => {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user