ML extension - View models styles / layout updates (#13091)

* Revised styles and added elements to view models stage of the View and import models.

* Implemented Leilas DataInfoComponent, replacing my use of icon and title, description fields. Corrected some styles.

* Fixed the issue with icon title and description

* Fixed severla issues

* Added method to output localized text for number of models shown. Added component to display models shown text.

* Fixed the issues with order of components

* Fixed showing number of models

Co-authored-by: llali <llali@microsoft.com>
This commit is contained in:
Hale Rankin
2020-11-04 12:07:54 -08:00
committed by GitHub
parent 06a4b0d1a2
commit 4af67c9734
13 changed files with 286 additions and 105 deletions

View File

@@ -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' };

View File

@@ -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);

View File

@@ -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);

View File

@@ -132,11 +132,13 @@ export class AzureModelsTable extends ModelViewBase implements IDataComponent<Wo
if (this.isTableEmpty) {
this._table.dataValues = [];
this._table.data = [];
} else {
this._table.data = tableData;
}
} else {
this._table.dataValues = [];
this._table.data = [];
}
}
this._onModelSelectionChanged.fire();

View File

@@ -6,7 +6,8 @@
import * as azdata from 'azdata';
import * as constants from '../../../common/constants';
import { ModelViewBase } from '../modelViewBase';
import { DataInfoComponent } from '../../dataInfoComponent';
import { ModelActionType, ModelViewBase } from '../modelViewBase';
import { CurrentModelsTable } from './currentModelsTable';
import { ApiWrapper } from '../../../common/apiWrapper';
import { IPageView, IComponentSettings } from '../../interfaces';
@@ -17,11 +18,15 @@ import { ImportedModel } from '../../../modelManagement/interfaces';
* View to render current registered models
*/
export class CurrentModelsComponent extends ModelViewBase implements IPageView {
private _emptyModelsComponent: DataInfoComponent | undefined;
private _dataTable: CurrentModelsTable | undefined;
private _loader: azdata.LoadingComponent | undefined;
private _tableSelectionComponent: TableSelectionComponent | undefined;
private _labelComponent: azdata.TextComponent | undefined;
private _descriptionComponent: azdata.TextComponent | undefined;
private _tableDataCountContainer: azdata.FlexContainer | undefined;
private _tableDataCountComponent: azdata.TextComponent | undefined;
private _subheadingContainer: azdata.FlexContainer | undefined;
private _subheadingTextComponent: azdata.TextComponent | undefined;
private _subheadingLinkComponent: azdata.HyperlinkComponent | undefined;
private _labelContainer: azdata.FlexContainer | undefined;
private _formBuilder: azdata.FormBuilder | undefined;
@@ -45,7 +50,11 @@ export class CurrentModelsComponent extends ModelViewBase implements IPageView {
databaseTitle: constants.databaseName,
tableTitle: constants.tableName,
databaseInfo: constants.modelDatabaseInfo,
tableInfo: constants.modelTableInfo
tableInfo: constants.modelTableInfo,
layout: 'vertical',
defaultDbName: constants.selectModelDatabaseTitle,
defaultTableName: constants.selectModelTableTitle,
useImportModelCache: true
});
this._tableSelectionComponent.registerComponent(modelBuilder);
this._tableSelectionComponent.onSelectedChanged(async () => {
@@ -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(<azdata.CheckBoxProperties>{
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<void> {
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();
}

View File

@@ -25,7 +25,7 @@ export class CurrentModelsTable extends ModelViewBase implements IDataComponent<
private _downloadedFile: ModelArtifact | undefined;
private _onModelSelectionChanged: vscode.EventEmitter<void> = new vscode.EventEmitter<void>();
public readonly onModelSelectionChanged: vscode.Event<void> = 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) {

View File

@@ -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;

View File

@@ -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<void> {
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<boolean> {
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) {

View File

@@ -77,7 +77,7 @@ export class ColumnsSelectionPage extends ModelViewBase implements IPageView, ID
public async validate(): Promise<boolean> {
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);

View File

@@ -162,7 +162,7 @@ export class ColumnsTable extends ModelViewBase implements IDataComponent<Predic
this._parameters = [];
let tableData: any[][] = [];
if (this._table && table && table.tableName !== constants.selectTableTitle) {
if (this._table && table) {
if (this._forInput) {
let columns: TableColumn[];
try {

View File

@@ -22,6 +22,7 @@ export class InputColumnsComponent extends ModelViewBase implements IDataCompone
private _tableSelectionComponent: TableSelectionComponent | undefined;
private _columns: ColumnsTable | undefined;
private _modelParameters: ModelParameters | undefined;
private _tableLoadingPromise: Promise<void> | 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<void> {
await this.loadWithTable(this.databaseTable);
if (this._tableSelectionComponent !== undefined && this._tableSelectionComponent.isDataValid) {
await this.loadWithTable(this.databaseTable);
}
}
public async loadWithTable(table: DatabaseTable): Promise<void> {
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 {

View File

@@ -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<void> {
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<void> {