ML - dashboard icons and links (#10153)

* ML - dashboard icons and links
This commit is contained in:
Leila Lali
2020-04-28 21:21:30 -07:00
committed by GitHub
parent 046995f2a5
commit 04af41c424
145 changed files with 387 additions and 134 deletions

View File

@@ -0,0 +1,101 @@
/*---------------------------------------------------------------------------------------------
* 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 { ModelViewBase } from '../modelViewBase';
import { ApiWrapper } from '../../../common/apiWrapper';
import * as constants from '../../../common/constants';
import { IPageView, IDataComponent } from '../../interfaces';
import { InputColumnsComponent } from './inputColumnsComponent';
import { OutputColumnsComponent } from './outputColumnsComponent';
import { PredictParameters } from '../../../prediction/interfaces';
/**
* View to pick model source
*/
export class ColumnsSelectionPage extends ModelViewBase implements IPageView, IDataComponent<PredictParameters> {
private _form: azdata.FormContainer | undefined;
private _formBuilder: azdata.FormBuilder | undefined;
public inputColumnsComponent: InputColumnsComponent | undefined;
public outputColumnsComponent: OutputColumnsComponent | undefined;
constructor(apiWrapper: ApiWrapper, parent: ModelViewBase) {
super(apiWrapper, parent.root, parent);
}
/**
*
* @param modelBuilder Register components
*/
public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component {
this._formBuilder = modelBuilder.formContainer();
this.inputColumnsComponent = new InputColumnsComponent(this._apiWrapper, this);
this.inputColumnsComponent.registerComponent(modelBuilder);
this.inputColumnsComponent.addComponents(this._formBuilder);
this.outputColumnsComponent = new OutputColumnsComponent(this._apiWrapper, this);
this.outputColumnsComponent.registerComponent(modelBuilder);
this.outputColumnsComponent.addComponents(this._formBuilder);
this._form = this._formBuilder.component();
return this._form;
}
/**
* Returns selected data
*/
public get data(): PredictParameters | undefined {
return this.inputColumnsComponent?.data && this.outputColumnsComponent?.data ?
Object.assign({}, this.inputColumnsComponent.data, { outputColumns: this.outputColumnsComponent.data }) :
undefined;
}
/**
* Returns the component
*/
public get component(): azdata.Component | undefined {
return this._form;
}
/**
* Refreshes the view
*/
public async refresh(): Promise<void> {
if (this._formBuilder) {
if (this.inputColumnsComponent) {
await this.inputColumnsComponent.refresh();
}
if (this.outputColumnsComponent) {
await this.outputColumnsComponent.refresh();
}
}
}
public async onEnter(): Promise<void> {
await this.inputColumnsComponent?.onLoading();
await this.outputColumnsComponent?.onLoading();
try {
const modelParameters = await this.loadModelParameters();
if (modelParameters && this.inputColumnsComponent && this.outputColumnsComponent) {
this.inputColumnsComponent.modelParameters = modelParameters;
this.outputColumnsComponent.modelParameters = modelParameters;
await this.inputColumnsComponent.refresh();
await this.outputColumnsComponent.refresh();
}
} catch (error) {
this.showErrorMessage(constants.loadModelParameterFailedError, error);
}
await this.inputColumnsComponent?.onLoaded();
await this.outputColumnsComponent?.onLoaded();
}
/**
* Returns page title
*/
public get title(): string {
return constants.columnSelectionPageTitle;
}
}

View File

@@ -0,0 +1,302 @@
/*---------------------------------------------------------------------------------------------
* 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 constants from '../../../common/constants';
import { ModelViewBase } from '../modelViewBase';
import { ApiWrapper } from '../../../common/apiWrapper';
import { IDataComponent } from '../../interfaces';
import { PredictColumn, DatabaseTable, TableColumn } from '../../../prediction/interfaces';
import { ModelParameter, ModelParameters } from '../../../modelManagement/interfaces';
/**
* View to render azure models in a table
*/
export class ColumnsTable extends ModelViewBase implements IDataComponent<PredictColumn[]> {
private _table: azdata.DeclarativeTableComponent | undefined;
private _parameters: PredictColumn[] = [];
private _loader: azdata.LoadingComponent;
private _dataTypes: string[] = [
'bigint',
'int',
'smallint',
'real',
'float',
'varchar(MAX)',
'bit'
];
/**
* Creates a view to render azure models in a table
*/
constructor(apiWrapper: ApiWrapper, private _modelBuilder: azdata.ModelBuilder, parent: ModelViewBase, private _forInput: boolean = true) {
super(apiWrapper, parent.root, parent);
this._loader = this.registerComponent(this._modelBuilder);
}
/**
* Register components
* @param modelBuilder model builder
*/
public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.LoadingComponent {
let columnHeader: azdata.DeclarativeTableColumn[];
if (this._forInput) {
columnHeader = [
{ // Action
displayName: constants.columnName,
ariaLabel: constants.columnName,
valueType: azdata.DeclarativeDataType.component,
isReadOnly: true,
width: 50,
headerCssStyles: {
...constants.cssStyles.tableHeader
},
rowCssStyles: {
...constants.cssStyles.tableRow
},
},
{ // Name
displayName: '',
ariaLabel: '',
valueType: azdata.DeclarativeDataType.component,
isReadOnly: true,
width: 50,
headerCssStyles: {
...constants.cssStyles.tableHeader
},
rowCssStyles: {
...constants.cssStyles.tableRow
},
},
{ // Name
displayName: constants.inputName,
ariaLabel: constants.inputName,
valueType: azdata.DeclarativeDataType.component,
isReadOnly: true,
width: 120,
headerCssStyles: {
...constants.cssStyles.tableHeader
},
rowCssStyles: {
...constants.cssStyles.tableRow
},
}
];
} else {
columnHeader = [
{ // Name
displayName: constants.outputName,
ariaLabel: constants.outputName,
valueType: azdata.DeclarativeDataType.string,
isReadOnly: true,
width: 200,
headerCssStyles: {
...constants.cssStyles.tableHeader
},
rowCssStyles: {
...constants.cssStyles.tableRow
},
},
{ // Action
displayName: constants.displayName,
ariaLabel: constants.displayName,
valueType: azdata.DeclarativeDataType.component,
isReadOnly: true,
width: 50,
headerCssStyles: {
...constants.cssStyles.tableHeader
},
rowCssStyles: {
...constants.cssStyles.tableRow
},
},
{ // Action
displayName: constants.dataTypeName,
ariaLabel: constants.dataTypeName,
valueType: azdata.DeclarativeDataType.component,
isReadOnly: true,
width: 50,
headerCssStyles: {
...constants.cssStyles.tableHeader
},
rowCssStyles: {
...constants.cssStyles.tableRow
},
}
];
}
this._table = modelBuilder.declarativeTable()
.withProperties<azdata.DeclarativeTableProperties>(
{
columns: columnHeader,
data: [],
ariaLabel: constants.mlsConfigTitle
})
.component();
this._loader = modelBuilder.loadingComponent()
.withItem(this._table)
.withProperties({
loading: true
}).component();
return this._loader;
}
public async onLoading(): Promise<void> {
if (this._loader) {
await this._loader.updateProperties({ loading: true });
}
}
public async onLoaded(): Promise<void> {
if (this._loader) {
await this._loader.updateProperties({ loading: false });
}
}
public get component(): azdata.Component {
return this._loader;
}
/**
* Load data in the component
* @param workspaceResource Azure workspace
*/
public async loadInputs(modelParameters: ModelParameters | undefined, table: DatabaseTable): Promise<void> {
await this.onLoading();
this._parameters = [];
let tableData: any[][] = [];
if (this._table) {
if (this._forInput) {
const columns = await this.listColumnNames(table);
if (modelParameters?.inputs && columns) {
tableData = tableData.concat(modelParameters.inputs.map(input => this.createInputTableRow(input, columns)));
}
}
this._table.data = tableData;
}
await this.onLoaded();
}
public async loadOutputs(modelParameters: ModelParameters | undefined): Promise<void> {
this.onLoading();
this._parameters = [];
let tableData: any[][] = [];
if (this._table) {
if (!this._forInput) {
if (modelParameters?.outputs && this._dataTypes) {
tableData = tableData.concat(modelParameters.outputs.map(output => this.createOutputTableRow(output, this._dataTypes)));
}
}
this._table.data = tableData;
}
this.onLoaded();
}
private createOutputTableRow(modelParameter: ModelParameter, dataTypes: string[]): any[] {
if (this._modelBuilder) {
let nameInput = this._modelBuilder.dropDown().withProperties({
values: dataTypes,
width: this.componentMaxLength
}).component();
const name = modelParameter.name;
const dataType = dataTypes.find(x => x === modelParameter.type);
if (dataType) {
nameInput.value = dataType;
}
this._parameters.push({ columnName: name, paramName: name, dataType: modelParameter.type });
nameInput.onValueChanged(() => {
const value = <string>nameInput.value;
if (value !== modelParameter.type) {
let selectedRow = this._parameters.find(x => x.paramName === name);
if (selectedRow) {
selectedRow.dataType = value;
}
}
});
let displayNameInput = this._modelBuilder.inputBox().withProperties({
value: name,
width: 200
}).component();
displayNameInput.onTextChanged(() => {
let selectedRow = this._parameters.find(x => x.paramName === name);
if (selectedRow) {
selectedRow.columnName = displayNameInput.value || name;
}
});
return [`${name}(${modelParameter.type ? modelParameter.type : constants.unsupportedModelParameterType})`, displayNameInput, nameInput];
}
return [];
}
private createInputTableRow(modelParameter: ModelParameter, columns: TableColumn[] | undefined): any[] {
if (this._modelBuilder && columns) {
const values = columns.map(c => { return { name: c.columnName, displayName: `${c.columnName}(${c.dataType})` }; });
let nameInput = this._modelBuilder.dropDown().withProperties({
values: values,
width: this.componentMaxLength
}).component();
const name = modelParameter.name;
let column = values.find(x => x.name === modelParameter.name);
if (!column) {
column = values[0];
}
nameInput.value = column;
this._parameters.push({ columnName: column.name, paramName: name });
nameInput.onValueChanged(() => {
const selectedColumn = nameInput.value;
const value = selectedColumn ? (<azdata.CategoryValue>selectedColumn).name : undefined;
let selectedRow = this._parameters.find(x => x.paramName === name);
if (selectedRow) {
selectedRow.columnName = value || '';
}
});
const label = this._modelBuilder.inputBox().withProperties({
value: `${name}(${modelParameter.type ? modelParameter.type : constants.unsupportedModelParameterType})`,
enabled: false,
width: this.componentMaxLength
}).component();
const image = this._modelBuilder.image().withProperties({
width: 50,
height: 50,
iconPath: {
dark: this.asAbsolutePath('images/arrow.svg'),
light: this.asAbsolutePath('images/arrow.svg')
},
iconWidth: 20,
iconHeight: 20,
title: 'maps'
}).component();
return [nameInput, image, label];
}
return [];
}
/**
* Returns selected data
*/
public get data(): PredictColumn[] | undefined {
return this._parameters;
}
/**
* Refreshes the view
*/
public async refresh(): Promise<void> {
}
}

View File

@@ -0,0 +1,142 @@
/*---------------------------------------------------------------------------------------------
* 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 { ModelViewBase } from '../modelViewBase';
import { ApiWrapper } from '../../../common/apiWrapper';
import * as constants from '../../../common/constants';
import { IDataComponent } from '../../interfaces';
import { PredictColumn, PredictInputParameters, DatabaseTable } from '../../../prediction/interfaces';
import { ModelParameters } from '../../../modelManagement/interfaces';
import { ColumnsTable } from './columnsTable';
import { TableSelectionComponent } from '../tableSelectionComponent';
/**
* View to render filters to pick an azure resource
*/
export class InputColumnsComponent extends ModelViewBase implements IDataComponent<PredictInputParameters> {
private _form: azdata.FormContainer | undefined;
private _tableSelectionComponent: TableSelectionComponent | undefined;
private _columns: ColumnsTable | undefined;
private _modelParameters: ModelParameters | undefined;
/**
* Creates a new view
*/
constructor(apiWrapper: ApiWrapper, parent: ModelViewBase) {
super(apiWrapper, parent.root, parent);
}
/**
* Register components
* @param modelBuilder model builder
*/
public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component {
this._tableSelectionComponent = new TableSelectionComponent(this._apiWrapper, this, false);
this._tableSelectionComponent.registerComponent(modelBuilder);
this._tableSelectionComponent.onSelectedChanged(async () => {
await this.onTableSelected();
});
this._columns = new ColumnsTable(this._apiWrapper, modelBuilder, this);
this._form = modelBuilder.formContainer().withFormItems([{
title: constants.inputColumns,
component: this._columns.component
}]).component();
return this._form;
}
public addComponents(formBuilder: azdata.FormBuilder) {
if (this._columns && this._tableSelectionComponent && this._tableSelectionComponent.component) {
formBuilder.addFormItems([{
title: '',
component: this._tableSelectionComponent.component
}, {
title: constants.inputColumns,
component: this._columns.component
}]);
}
}
public removeComponents(formBuilder: azdata.FormBuilder) {
if (this._columns && this._tableSelectionComponent && this._tableSelectionComponent.component) {
formBuilder.removeFormItem({
title: '',
component: this._tableSelectionComponent.component
});
formBuilder.removeFormItem({
title: constants.inputColumns,
component: this._columns.component
});
}
}
/**
* Returns the created component
*/
public get component(): azdata.Component | undefined {
return this._form;
}
/**
* Returns selected data
*/
public get data(): PredictInputParameters | undefined {
return Object.assign({}, this.databaseTable, {
inputColumns: this.columnNames
});
}
/**
* loads data in the components
*/
public async loadData(): Promise<void> {
if (this._tableSelectionComponent) {
this._tableSelectionComponent.refresh();
}
}
public set modelParameters(value: ModelParameters) {
this._modelParameters = value;
}
public async onLoading(): Promise<void> {
if (this._columns) {
await this._columns.onLoading();
}
}
public async onLoaded(): Promise<void> {
if (this._columns) {
await this._columns.onLoaded();
}
}
/**
* refreshes the view
*/
public async refresh(): Promise<void> {
await this.loadData();
}
private async onTableSelected(): Promise<void> {
this._columns?.loadInputs(this._modelParameters, this.databaseTable);
}
private get databaseTable(): DatabaseTable {
let selectedItem = this._tableSelectionComponent?.data;
return {
databaseName: selectedItem?.databaseName,
tableName: selectedItem?.tableName,
schema: selectedItem?.schema
};
}
private get columnNames(): PredictColumn[] | undefined {
return this._columns?.data;
}
}

View File

@@ -0,0 +1,35 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as utils from '../../../common/utils';
/**
* Wizard to register a model
*/
export class ModelArtifact {
/**
* Creates new model artifact
*/
constructor(private _filePath: string, private _deleteAtClose: boolean = true) {
}
public get filePath(): string {
return this._filePath;
}
/**
* Closes the artifact and disposes the resources
*/
public async close(): Promise<void> {
if (this._deleteAtClose) {
try {
await utils.deleteFile(this._filePath);
} catch {
}
}
}
}

View File

@@ -0,0 +1,109 @@
/*---------------------------------------------------------------------------------------------
* 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 { ModelViewBase } from '../modelViewBase';
import { ApiWrapper } from '../../../common/apiWrapper';
import * as constants from '../../../common/constants';
import { IDataComponent } from '../../interfaces';
import { PredictColumn } from '../../../prediction/interfaces';
import { ColumnsTable } from './columnsTable';
import { ModelParameters } from '../../../modelManagement/interfaces';
/**
* View to render filters to pick an azure resource
*/
export class OutputColumnsComponent extends ModelViewBase implements IDataComponent<PredictColumn[]> {
private _form: azdata.FormContainer | undefined;
private _columns: ColumnsTable | undefined;
private _modelParameters: ModelParameters | undefined;
/**
* Creates a new view
*/
constructor(apiWrapper: ApiWrapper, parent: ModelViewBase) {
super(apiWrapper, parent.root, parent);
}
/**
* Register components
* @param modelBuilder model builder
*/
public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component {
this._columns = new ColumnsTable(this._apiWrapper, modelBuilder, this, false);
this._form = modelBuilder.formContainer().withFormItems([{
title: constants.azureAccount,
component: this._columns.component
}]).component();
return this._form;
}
public addComponents(formBuilder: azdata.FormBuilder) {
if (this._columns) {
formBuilder.addFormItems([{
title: constants.outputColumns,
component: this._columns.component
}]);
}
}
public removeComponents(formBuilder: azdata.FormBuilder) {
if (this._columns) {
formBuilder.removeFormItem({
title: constants.outputColumns,
component: this._columns.component
});
}
}
/**
* Returns the created component
*/
public get component(): azdata.Component | undefined {
return this._form;
}
/**
* loads data in the components
*/
public async loadData(): Promise<void> {
if (this._modelParameters) {
this._columns?.loadOutputs(this._modelParameters);
}
}
public set modelParameters(value: ModelParameters) {
this._modelParameters = value;
}
public async onLoading(): Promise<void> {
if (this._columns) {
await this._columns.onLoading();
}
}
public async onLoaded(): Promise<void> {
if (this._columns) {
await this._columns.onLoaded();
}
}
/**
* refreshes the view
*/
public async refresh(): Promise<void> {
await this.loadData();
}
/**
* Returns selected data
*/
public get data(): PredictColumn[] | undefined {
return this._columns?.data;
}
}

View File

@@ -0,0 +1,159 @@
/*---------------------------------------------------------------------------------------------
* 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 { ModelViewBase, ModelSourceType } from '../modelViewBase';
import { ApiWrapper } from '../../../common/apiWrapper';
import { ModelSourcesComponent } from '../modelSourcesComponent';
import { LocalModelsComponent } from '../localModelsComponent';
import { AzureModelsComponent } from '../azureModelsComponent';
import * as constants from '../../../common/constants';
import { WizardView } from '../../wizardView';
import { ModelSourcePage } from '../modelSourcePage';
import { ColumnsSelectionPage } from './columnsSelectionPage';
import { ImportedModel } from '../../../modelManagement/interfaces';
import { ModelArtifact } from './modelArtifact';
import { ModelBrowsePage } from '../modelBrowsePage';
/**
* Wizard to register a model
*/
export class PredictWizard extends ModelViewBase {
public modelSourcePage: ModelSourcePage | undefined;
public columnsSelectionPage: ColumnsSelectionPage | undefined;
public modelBrowsePage: ModelBrowsePage | undefined;
public wizardView: WizardView | undefined;
private _parentView: ModelViewBase | undefined;
constructor(
apiWrapper: ApiWrapper,
root: string,
parent?: ModelViewBase) {
super(apiWrapper, root);
this._parentView = parent;
}
/**
* Opens a dialog to manage packages used by notebooks.
*/
public async open(): Promise<void> {
this.modelSourcePage = new ModelSourcePage(this._apiWrapper, this, [ModelSourceType.RegisteredModels, ModelSourceType.Local, ModelSourceType.Azure]);
this.columnsSelectionPage = new ColumnsSelectionPage(this._apiWrapper, this);
this.modelBrowsePage = new ModelBrowsePage(this._apiWrapper, this, false);
this.wizardView = new WizardView(this._apiWrapper);
let wizard = this.wizardView.createWizard(constants.makePredictionTitle,
[this.modelSourcePage,
this.modelBrowsePage,
this.columnsSelectionPage]);
this.mainViewPanel = wizard;
wizard.doneButton.label = constants.predictModel;
wizard.generateScriptButton.hidden = true;
wizard.displayPageTitles = true;
wizard.doneButton.onClick(async () => {
await this.onClose();
});
wizard.cancelButton.onClick(async () => {
await this.onClose();
});
wizard.registerNavigationValidator(async (pageInfo: azdata.window.WizardPageChangeInfo) => {
let validated: boolean = true;
if (pageInfo.newPage > pageInfo.lastPage) {
validated = this.wizardView ? await this.wizardView.validate(pageInfo) : false;
}
if (validated) {
if (pageInfo.newPage === undefined) {
this.onLoading();
await this.predict();
this.onLoaded();
if (this._parentView) {
this._parentView?.refresh();
}
}
return true;
}
return validated;
});
await wizard.open();
}
private onLoading(): void {
this.refreshButtons(true);
}
private onLoaded(): void {
this.refreshButtons(false);
}
private refreshButtons(loading: boolean): void {
if (this.wizardView && this.wizardView.wizard) {
this.wizardView.wizard.cancelButton.enabled = !loading;
this.wizardView.wizard.cancelButton.enabled = !loading;
}
}
public get modelResources(): ModelSourcesComponent | undefined {
return this.modelSourcePage?.modelResources;
}
public get localModelsComponent(): LocalModelsComponent | undefined {
return this.modelBrowsePage?.localModelsComponent;
}
public get azureModelsComponent(): AzureModelsComponent | undefined {
return this.modelBrowsePage?.azureModelsComponent;
}
public async getModelFileName(): Promise<ModelArtifact | undefined> {
if (this.modelResources && this.localModelsComponent && this.modelResources.data === ModelSourceType.Local) {
return new ModelArtifact(this.localModelsComponent.data[0], false);
} else if (this.modelResources && this.azureModelsComponent && this.modelResources.data === ModelSourceType.Azure) {
return await this.azureModelsComponent.getDownloadedModel();
} else if (this.modelBrowsePage && this.modelBrowsePage.registeredModelsComponent) {
return await this.modelBrowsePage.registeredModelsComponent.modelTable?.getDownloadedModel();
}
return undefined;
}
private async predict(): Promise<boolean> {
try {
let modelFilePath: string | undefined;
let registeredModel: ImportedModel | undefined = undefined;
if (this.modelResources && this.modelResources.data && this.modelResources.data === ModelSourceType.RegisteredModels
&& this.modelBrowsePage && this.modelBrowsePage.registeredModelsComponent) {
const data = this.modelBrowsePage?.registeredModelsComponent?.data;
registeredModel = data && data.length > 0 ? data[0] : undefined;
} else {
const artifact = await this.getModelFileName();
modelFilePath = artifact?.filePath;
}
await this.generatePredictScript(registeredModel, modelFilePath, this.columnsSelectionPage?.data);
return true;
} catch (error) {
this.showErrorMessage(`${constants.modelFailedToRegister} ${constants.getErrorMessage(error)}`);
return false;
}
}
private async onClose(): Promise<void> {
const artifact = await this.getModelFileName();
if (artifact) {
artifact.close();
}
await this.wizardView?.disposePages();
}
/**
* Refresh the pages
*/
public async refresh(): Promise<void> {
await this.wizardView?.refresh();
}
}