mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-14 18:46:34 -05:00
ML - dashboard icons and links (#10153)
* ML - dashboard icons and links
This commit is contained in:
54
extensions/machine-learning/src/views/controllerBase.ts
Normal file
54
extensions/machine-learning/src/views/controllerBase.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import { ViewBase, LocalPathsEventName } from './viewBase';
|
||||
import { ApiWrapper } from '../common/apiWrapper';
|
||||
|
||||
/**
|
||||
* Base classes for UI controllers
|
||||
*/
|
||||
export abstract class ControllerBase {
|
||||
|
||||
/**
|
||||
* creates new instance
|
||||
*/
|
||||
constructor(protected _apiWrapper: ApiWrapper) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an action and sends back callback event to the view
|
||||
*/
|
||||
public async executeAction<T extends ViewBase>(dialog: T, eventName: string, func: (...args: any[]) => Promise<any>, ...args: any[]): Promise<void> {
|
||||
const callbackEvent = ViewBase.getCallbackEventName(eventName);
|
||||
try {
|
||||
let result = await func(...args);
|
||||
dialog.sendCallbackRequest(callbackEvent, { data: result });
|
||||
|
||||
} catch (error) {
|
||||
dialog.sendCallbackRequest(callbackEvent, { error: error });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register common events for views
|
||||
* @param view view
|
||||
*/
|
||||
public registerEvents(view: ViewBase): void {
|
||||
view.on(LocalPathsEventName, async (args) => {
|
||||
await this.executeAction(view, LocalPathsEventName, this.getLocalPaths, this._apiWrapper, args);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns local file path picked by the user
|
||||
* @param apiWrapper apiWrapper
|
||||
*/
|
||||
public async getLocalPaths(apiWrapper: ApiWrapper, options: vscode.OpenDialogOptions): Promise<string[]> {
|
||||
let result = await apiWrapper.showOpenDialog(options);
|
||||
return result ? result?.map(x => x.fsPath) : [];
|
||||
}
|
||||
}
|
||||
41
extensions/machine-learning/src/views/dialogView.ts
Normal file
41
extensions/machine-learning/src/views/dialogView.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ApiWrapper } from '../common/apiWrapper';
|
||||
import { MainViewBase } from './mainViewBase';
|
||||
import { IPageView } from './interfaces';
|
||||
|
||||
/**
|
||||
* Dialog view to create and manage a dialog
|
||||
*/
|
||||
export class DialogView extends MainViewBase {
|
||||
|
||||
private _dialog: azdata.window.Dialog | undefined;
|
||||
|
||||
/**
|
||||
* Creates new instance
|
||||
*/
|
||||
constructor(apiWrapper: ApiWrapper) {
|
||||
super(apiWrapper);
|
||||
}
|
||||
|
||||
private createDialogPage(title: string, componentView: IPageView): azdata.window.DialogTab {
|
||||
let viewPanel = this._apiWrapper.createTab(title);
|
||||
this.addPage(componentView);
|
||||
this.registerContent(viewPanel, componentView);
|
||||
return viewPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new dialog
|
||||
* @param title title
|
||||
* @param pages pages
|
||||
*/
|
||||
public createDialog(title: string, pages: IPageView[]): azdata.window.Dialog {
|
||||
this._dialog = this._apiWrapper.createModelViewDialog(title);
|
||||
this._dialog.content = pages.map(x => this.createDialogPage(x.title || '', x));
|
||||
return this._dialog;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { LanguageViewBase, LanguageUpdateModel } from './languageViewBase';
|
||||
import { LanguageContentView } from './languageContentView';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
|
||||
export class AddEditLanguageTab extends LanguageViewBase {
|
||||
private _dialogTab: azdata.window.DialogTab;
|
||||
public languageName: azdata.TextComponent | undefined;
|
||||
private _editMode: boolean = false;
|
||||
public saveButton: azdata.ButtonComponent | undefined;
|
||||
public languageView: LanguageContentView | undefined;
|
||||
|
||||
constructor(
|
||||
apiWrapper: ApiWrapper,
|
||||
parent: LanguageViewBase,
|
||||
private _languageUpdateModel: LanguageUpdateModel) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
this._editMode = !this._languageUpdateModel.newLang;
|
||||
this._dialogTab = apiWrapper.createTab(constants.extLangNewLanguageTabTitle);
|
||||
this._dialogTab.registerContent(async view => {
|
||||
let language = this._languageUpdateModel.language;
|
||||
let content = this._languageUpdateModel.content;
|
||||
this.languageName = view.modelBuilder.inputBox().withProperties({
|
||||
value: language.name,
|
||||
width: '150px',
|
||||
enabled: !this._editMode
|
||||
}).withValidation(component => component.value !== '').component();
|
||||
|
||||
let formBuilder = view.modelBuilder.formContainer();
|
||||
formBuilder.addFormItem({
|
||||
component: this.languageName,
|
||||
title: constants.extLangLanguageName,
|
||||
required: true
|
||||
});
|
||||
|
||||
this.languageView = new LanguageContentView(this._apiWrapper, this, view.modelBuilder, formBuilder, content);
|
||||
|
||||
if (!this._editMode) {
|
||||
this.saveButton = view.modelBuilder.button().withProperties({
|
||||
label: constants.extLangInstallButtonText,
|
||||
width: '100px'
|
||||
}).component();
|
||||
this.saveButton.onDidClick(async () => {
|
||||
try {
|
||||
await this.updateLanguage(this.updatedData);
|
||||
} catch (err) {
|
||||
this.showErrorMessage(constants.extLangInstallFailedError, err);
|
||||
}
|
||||
});
|
||||
|
||||
formBuilder.addFormItem({
|
||||
component: this.saveButton,
|
||||
title: ''
|
||||
});
|
||||
}
|
||||
|
||||
await view.initializeModel(formBuilder.component());
|
||||
await this.reset();
|
||||
});
|
||||
}
|
||||
|
||||
public get updatedData(): LanguageUpdateModel {
|
||||
return {
|
||||
language: {
|
||||
name: this.languageName?.value || '',
|
||||
contents: this._languageUpdateModel.language.contents
|
||||
},
|
||||
content: this.languageView?.updatedContent || this._languageUpdateModel.content,
|
||||
newLang: this._languageUpdateModel.newLang
|
||||
};
|
||||
}
|
||||
|
||||
public get tab(): azdata.window.DialogTab {
|
||||
return this._dialogTab;
|
||||
}
|
||||
|
||||
public async reset(): Promise<void> {
|
||||
if (this.languageName) {
|
||||
this.languageName.value = this._languageUpdateModel.language.name;
|
||||
}
|
||||
this.languageView?.reset();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { LanguageViewBase } from './languageViewBase';
|
||||
import { LanguagesTable } from './languagesTable';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
|
||||
export class CurrentLanguagesTab extends LanguageViewBase {
|
||||
|
||||
private _installedLangsTab: azdata.window.DialogTab;
|
||||
|
||||
private _locationComponent: azdata.TextComponent | undefined;
|
||||
private _installLanguagesTable: azdata.DeclarativeTableComponent | undefined;
|
||||
private _languageTable: LanguagesTable | undefined;
|
||||
private _loader: azdata.LoadingComponent | undefined;
|
||||
|
||||
constructor(apiWrapper: ApiWrapper, parent: LanguageViewBase) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
this._installedLangsTab = this._apiWrapper.createTab(constants.extLangInstallTabTitle);
|
||||
|
||||
this._installedLangsTab.registerContent(async view => {
|
||||
|
||||
// TODO: only supporting single location for now. We should add a drop down for multi locations mode
|
||||
//
|
||||
let locationTitle = await this.getServerTitle();
|
||||
this._locationComponent = view.modelBuilder.text().withProperties({
|
||||
value: locationTitle
|
||||
}).component();
|
||||
|
||||
this._languageTable = new LanguagesTable(apiWrapper, view.modelBuilder, this);
|
||||
this._installLanguagesTable = this._languageTable.table;
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this._locationComponent,
|
||||
title: constants.extLangTarget
|
||||
}, {
|
||||
component: this._installLanguagesTable,
|
||||
title: ''
|
||||
}]).component();
|
||||
|
||||
this._loader = view.modelBuilder.loadingComponent()
|
||||
.withItem(formModel)
|
||||
.withProperties({
|
||||
loading: true
|
||||
}).component();
|
||||
|
||||
await view.initializeModel(this._loader);
|
||||
await this.reset();
|
||||
});
|
||||
}
|
||||
|
||||
public get tab(): azdata.window.DialogTab {
|
||||
return this._installedLangsTab;
|
||||
}
|
||||
|
||||
private async onLoading(): Promise<void> {
|
||||
if (this._loader) {
|
||||
await this._loader.updateProperties({ loading: true });
|
||||
}
|
||||
}
|
||||
|
||||
private async onLoaded(): Promise<void> {
|
||||
if (this._loader) {
|
||||
await this._loader.updateProperties({ loading: false });
|
||||
}
|
||||
}
|
||||
|
||||
public async reset(): Promise<void> {
|
||||
await this.onLoading();
|
||||
|
||||
try {
|
||||
await this._languageTable?.reset();
|
||||
} catch (err) {
|
||||
this.showErrorMessage(constants.getErrorMessage(err));
|
||||
} finally {
|
||||
await this.onLoaded();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as constants from '../../common/constants';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
|
||||
export class FileBrowserDialog {
|
||||
|
||||
private _selectedPathTextBox: azdata.InputBoxComponent | undefined;
|
||||
private _fileBrowserDialog: azdata.window.Dialog | undefined;
|
||||
private _fileBrowserTree: azdata.FileBrowserTreeComponent | undefined;
|
||||
|
||||
private _onPathSelected: vscode.EventEmitter<string> = new vscode.EventEmitter<string>();
|
||||
public readonly onPathSelected: vscode.Event<string> = this._onPathSelected.event;
|
||||
|
||||
constructor(private _apiWrapper: ApiWrapper, private ownerUri: string) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a dialog to browse server files and folders.
|
||||
*/
|
||||
public showDialog(): void {
|
||||
let fileBrowserTitle = '';
|
||||
this._fileBrowserDialog = this._apiWrapper.createModelViewDialog(fileBrowserTitle);
|
||||
let fileBrowserTab = this._apiWrapper.createTab(constants.extLangFileBrowserTabTitle);
|
||||
this._fileBrowserDialog.content = [fileBrowserTab];
|
||||
fileBrowserTab.registerContent(async (view) => {
|
||||
this._fileBrowserTree = view.modelBuilder.fileBrowserTree()
|
||||
.withProperties({ ownerUri: this.ownerUri, width: 420, height: 700 })
|
||||
.component();
|
||||
this._selectedPathTextBox = view.modelBuilder.inputBox()
|
||||
.withProperties({ inputType: 'text' })
|
||||
.component();
|
||||
this._fileBrowserTree.onDidChange((args) => {
|
||||
if (this._selectedPathTextBox) {
|
||||
this._selectedPathTextBox.value = args.fullPath;
|
||||
}
|
||||
});
|
||||
|
||||
let fileBrowserContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this._fileBrowserTree,
|
||||
title: ''
|
||||
}, {
|
||||
component: this._selectedPathTextBox,
|
||||
title: constants.extLangSelectedPath
|
||||
}
|
||||
]).component();
|
||||
view.initializeModel(fileBrowserContainer);
|
||||
});
|
||||
this._fileBrowserDialog.okButton.onClick(() => {
|
||||
if (this._selectedPathTextBox) {
|
||||
let selectedPath = this._selectedPathTextBox.value || '';
|
||||
this._onPathSelected.fire(selectedPath);
|
||||
}
|
||||
});
|
||||
|
||||
this._fileBrowserDialog.cancelButton.onClick(() => {
|
||||
this._onPathSelected.fire('');
|
||||
});
|
||||
this._fileBrowserDialog.okButton.label = constants.extLangOkButtonText;
|
||||
this._fileBrowserDialog.cancelButton.label = constants.extLangCancelButtonText;
|
||||
this._apiWrapper.openDialog(this._fileBrowserDialog);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 mssql from '../../../../mssql';
|
||||
import { LanguageViewBase } from './languageViewBase';
|
||||
import * as constants from '../../common/constants';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
|
||||
export class LanguageContentView extends LanguageViewBase {
|
||||
|
||||
private _serverPath: azdata.RadioButtonComponent;
|
||||
private _localPath: azdata.RadioButtonComponent;
|
||||
public extensionFile: azdata.TextComponent;
|
||||
public extensionFileName: azdata.TextComponent;
|
||||
public envVariables: azdata.TextComponent;
|
||||
public parameters: azdata.TextComponent;
|
||||
private _isLocalPath: boolean = true;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor(
|
||||
apiWrapper: ApiWrapper,
|
||||
parent: LanguageViewBase,
|
||||
private _modelBuilder: azdata.ModelBuilder,
|
||||
private _formBuilder: azdata.FormBuilder,
|
||||
private _languageContent: mssql.ExternalLanguageContent | undefined,
|
||||
) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
this._localPath = this._modelBuilder.radioButton()
|
||||
.withProperties({
|
||||
value: 'local',
|
||||
name: 'extensionLocation',
|
||||
label: constants.extLangLocal,
|
||||
checked: true
|
||||
}).component();
|
||||
|
||||
this._serverPath = this._modelBuilder.radioButton()
|
||||
.withProperties({
|
||||
value: 'server',
|
||||
name: 'extensionLocation',
|
||||
label: this.getServerTitle(),
|
||||
}).component();
|
||||
|
||||
this._localPath.onDidClick(() => {
|
||||
this._isLocalPath = true;
|
||||
});
|
||||
this._serverPath.onDidClick(() => {
|
||||
this._isLocalPath = false;
|
||||
});
|
||||
|
||||
|
||||
let flexRadioButtonsModel = this._modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
justifyContent: 'space-between'
|
||||
//width: parent.componentMaxLength
|
||||
}).withItems([
|
||||
this._localPath, this._serverPath]
|
||||
).component();
|
||||
|
||||
this.extensionFile = this._modelBuilder.inputBox().withProperties({
|
||||
value: '',
|
||||
width: parent.componentMaxLength - parent.browseButtonMaxLength - parent.spaceBetweenComponentsLength
|
||||
}).component();
|
||||
let fileBrowser = this._modelBuilder.button().withProperties({
|
||||
label: '...',
|
||||
width: parent.browseButtonMaxLength,
|
||||
CSSStyles: {
|
||||
'text-align': 'end'
|
||||
}
|
||||
}).component();
|
||||
|
||||
let flexFilePathModel = this._modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
justifyContent: 'space-between'
|
||||
}).withItems([
|
||||
this.extensionFile, fileBrowser]
|
||||
).component();
|
||||
this.filePathSelected(args => {
|
||||
this.extensionFile.value = args.filePath;
|
||||
});
|
||||
fileBrowser.onDidClick(async () => {
|
||||
this.onOpenFileBrowser({ filePath: '', target: this._isLocalPath ? constants.localhost : this.connectionUrl });
|
||||
});
|
||||
|
||||
this.extensionFileName = this._modelBuilder.inputBox().withProperties({
|
||||
value: '',
|
||||
width: parent.componentMaxLength
|
||||
}).component();
|
||||
|
||||
this.envVariables = this._modelBuilder.inputBox().withProperties({
|
||||
value: '',
|
||||
width: parent.componentMaxLength
|
||||
}).component();
|
||||
this.parameters = this._modelBuilder.inputBox().withProperties({
|
||||
value: '',
|
||||
width: parent.componentMaxLength
|
||||
}).component();
|
||||
|
||||
this.load();
|
||||
|
||||
this._formBuilder.addFormItems([{
|
||||
component: flexRadioButtonsModel,
|
||||
title: constants.extLangExtensionFileLocation
|
||||
}, {
|
||||
component: flexFilePathModel,
|
||||
title: constants.extLangExtensionFilePath,
|
||||
required: true
|
||||
}, {
|
||||
component: this.extensionFileName,
|
||||
title: constants.extLangExtensionFileName,
|
||||
required: true
|
||||
}, {
|
||||
component: this.envVariables,
|
||||
title: constants.extLangEnvVariables
|
||||
}, {
|
||||
component: this.parameters,
|
||||
title: constants.extLangParameters
|
||||
}]);
|
||||
}
|
||||
|
||||
private load() {
|
||||
if (this._languageContent) {
|
||||
this._isLocalPath = this._languageContent.isLocalFile;
|
||||
this._localPath.checked = this._isLocalPath;
|
||||
this._serverPath.checked = !this._isLocalPath;
|
||||
this.extensionFile.value = this._languageContent.pathToExtension;
|
||||
this.extensionFileName.value = this._languageContent.extensionFileName;
|
||||
this.envVariables.value = this._languageContent.environmentVariables;
|
||||
this.parameters.value = this._languageContent.parameters;
|
||||
}
|
||||
}
|
||||
|
||||
public async reset(): Promise<void> {
|
||||
this._isLocalPath = true;
|
||||
this._localPath.checked = this._isLocalPath;
|
||||
this._serverPath.checked = !this._isLocalPath;
|
||||
this.load();
|
||||
}
|
||||
|
||||
public get updatedContent(): mssql.ExternalLanguageContent {
|
||||
return {
|
||||
pathToExtension: this.extensionFile.value || '',
|
||||
extensionFileName: this.extensionFileName.value || '',
|
||||
parameters: this.parameters.value || '',
|
||||
environmentVariables: this.envVariables.value || '',
|
||||
isLocalFile: this._isLocalPath || false,
|
||||
platform: this._languageContent?.platform
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as mssql from '../../../../mssql';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
import { LanguageService } from '../../externalLanguage/languageService';
|
||||
import { LanguagesDialog } from './languagesDialog';
|
||||
import { LanguageEditDialog } from './languageEditDialog';
|
||||
import { FileBrowserDialog } from './fileBrowserDialog';
|
||||
import { LanguageViewBase, LanguageUpdateModel } from './languageViewBase';
|
||||
import * as constants from '../../common/constants';
|
||||
|
||||
export class LanguageController {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor(
|
||||
private _apiWrapper: ApiWrapper,
|
||||
private _root: string,
|
||||
private _service: LanguageService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the manage language dialog and connects events to the model
|
||||
*/
|
||||
public async manageLanguages(): Promise<LanguagesDialog> {
|
||||
|
||||
let dialog = new LanguagesDialog(this._apiWrapper, this._root);
|
||||
|
||||
// Load current connection
|
||||
//
|
||||
await this._service.load();
|
||||
dialog.connection = this._service.connection;
|
||||
dialog.connectionUrl = this._service.connectionUrl;
|
||||
|
||||
// Handle dialog events and connect to model
|
||||
//
|
||||
dialog.onEdit(model => {
|
||||
this.editLanguage(dialog, model);
|
||||
});
|
||||
dialog.onDelete(async deleteModel => {
|
||||
try {
|
||||
await this.executeAction(dialog, this.deleteLanguage, this._service, deleteModel);
|
||||
dialog.onUpdatedLanguage(deleteModel);
|
||||
} catch (err) {
|
||||
dialog.onActionFailed(err);
|
||||
}
|
||||
});
|
||||
|
||||
dialog.onUpdate(async updateModel => {
|
||||
try {
|
||||
await this.executeAction(dialog, this.updateLanguage, this._service, updateModel);
|
||||
dialog.onUpdatedLanguage(updateModel);
|
||||
} catch (err) {
|
||||
dialog.onActionFailed(err);
|
||||
}
|
||||
});
|
||||
|
||||
dialog.onList(async () => {
|
||||
try {
|
||||
let result = await this.listLanguages(this._service);
|
||||
dialog.onListLanguageLoaded(result);
|
||||
} catch (err) {
|
||||
dialog.onActionFailed(err);
|
||||
}
|
||||
});
|
||||
this.onSelectFile(dialog);
|
||||
|
||||
// Open dialog
|
||||
//
|
||||
dialog.showDialog();
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public async executeAction<T>(dialog: LanguageViewBase, func: (...args: any[]) => Promise<T>, ...args: any[]): Promise<T> {
|
||||
let result = await func(...args);
|
||||
await dialog.reset();
|
||||
return result;
|
||||
}
|
||||
|
||||
public editLanguage(parent: LanguageViewBase, languageUpdateModel: LanguageUpdateModel): void {
|
||||
let editDialog = new LanguageEditDialog(this._apiWrapper, parent, languageUpdateModel);
|
||||
editDialog.showDialog();
|
||||
}
|
||||
|
||||
private onSelectFile(dialog: LanguageViewBase): void {
|
||||
dialog.fileBrowser(async (args) => {
|
||||
let filePath = '';
|
||||
if (args.target === constants.localhost) {
|
||||
filePath = await this.getLocalFilePath();
|
||||
|
||||
} else {
|
||||
filePath = await this.getServerFilePath(args.target);
|
||||
}
|
||||
dialog.onFilePathSelected({ filePath: filePath, target: args.target });
|
||||
});
|
||||
}
|
||||
|
||||
public getServerFilePath(connectionUrl: string): Promise<string> {
|
||||
return new Promise<string>((resolve) => {
|
||||
let dialog = new FileBrowserDialog(this._apiWrapper, connectionUrl);
|
||||
dialog.onPathSelected((selectedPath) => {
|
||||
resolve(selectedPath);
|
||||
});
|
||||
|
||||
dialog.showDialog();
|
||||
});
|
||||
}
|
||||
|
||||
public async getLocalFilePath(): Promise<string> {
|
||||
let result = await this._apiWrapper.showOpenDialog({
|
||||
canSelectFiles: true,
|
||||
canSelectFolders: false,
|
||||
canSelectMany: false
|
||||
});
|
||||
return result && result.length > 0 ? result[0].fsPath : '';
|
||||
}
|
||||
|
||||
public async deleteLanguage(model: LanguageService, deleteModel: LanguageUpdateModel): Promise<void> {
|
||||
await model.deleteLanguage(deleteModel.language.name);
|
||||
}
|
||||
|
||||
public async listLanguages(model: LanguageService): Promise<mssql.ExternalLanguage[]> {
|
||||
return await model.getLanguageList();
|
||||
}
|
||||
|
||||
public async updateLanguage(model: LanguageService, updateModel: LanguageUpdateModel): Promise<void> {
|
||||
if (!updateModel.language) {
|
||||
return;
|
||||
}
|
||||
let contents: mssql.ExternalLanguageContent[] = [];
|
||||
if (updateModel.language.contents && updateModel.language.contents.length >= 0) {
|
||||
contents = updateModel.language.contents.filter(x => x.platform !== updateModel.content.platform);
|
||||
}
|
||||
contents.push(updateModel.content);
|
||||
|
||||
updateModel.language.contents = contents;
|
||||
await model.updateLanguage(updateModel.language);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as constants from '../../common/constants';
|
||||
import { AddEditLanguageTab } from './addEditLanguageTab';
|
||||
import { LanguageViewBase, LanguageUpdateModel } from './languageViewBase';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
|
||||
export class LanguageEditDialog extends LanguageViewBase {
|
||||
|
||||
public addNewLanguageTab: AddEditLanguageTab | undefined;
|
||||
|
||||
constructor(
|
||||
apiWrapper: ApiWrapper,
|
||||
parent: LanguageViewBase,
|
||||
private _languageUpdateModel: LanguageUpdateModel) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a dialog to edit a language or a content of a language
|
||||
*/
|
||||
public showDialog(): void {
|
||||
this._dialog = this._apiWrapper.createModelViewDialog(constants.extLangDialogTitle);
|
||||
|
||||
this.addNewLanguageTab = new AddEditLanguageTab(this._apiWrapper, this, this._languageUpdateModel);
|
||||
|
||||
this._dialog.cancelButton.label = constants.extLangCancelButtonText;
|
||||
this._dialog.okButton.label = constants.extLangSaveButtonText;
|
||||
|
||||
this.dialog?.registerCloseValidator(async (): Promise<boolean> => {
|
||||
return await this.onSave();
|
||||
});
|
||||
|
||||
this._dialog.content = [this.addNewLanguageTab.tab];
|
||||
this._apiWrapper.openDialog(this._dialog);
|
||||
}
|
||||
|
||||
public async onSave(): Promise<boolean> {
|
||||
if (this.addNewLanguageTab) {
|
||||
try {
|
||||
await this.updateLanguage(this.addNewLanguageTab.updatedData);
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.showErrorMessage(constants.extLangUpdateFailedError, err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Resets the tabs for given provider Id
|
||||
*/
|
||||
public async reset(): Promise<void> {
|
||||
await this.addNewLanguageTab?.reset();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as constants from '../../common/constants';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
import * as mssql from '../../../../mssql';
|
||||
import * as path from 'path';
|
||||
|
||||
export interface LanguageUpdateModel {
|
||||
language: mssql.ExternalLanguage,
|
||||
content: mssql.ExternalLanguageContent,
|
||||
newLang: boolean
|
||||
}
|
||||
|
||||
export interface FileBrowseEventArgs {
|
||||
filePath: string,
|
||||
target: string
|
||||
}
|
||||
|
||||
export abstract class LanguageViewBase {
|
||||
protected _dialog: azdata.window.Dialog | undefined;
|
||||
public connection: azdata.connection.ConnectionProfile | undefined;
|
||||
public connectionUrl: string = '';
|
||||
|
||||
// Events
|
||||
//
|
||||
protected _onEdit: vscode.EventEmitter<LanguageUpdateModel> = new vscode.EventEmitter<LanguageUpdateModel>();
|
||||
public readonly onEdit: vscode.Event<LanguageUpdateModel> = this._onEdit.event;
|
||||
|
||||
protected _onUpdate: vscode.EventEmitter<LanguageUpdateModel> = new vscode.EventEmitter<LanguageUpdateModel>();
|
||||
public readonly onUpdate: vscode.Event<LanguageUpdateModel> = this._onUpdate.event;
|
||||
|
||||
protected _onDelete: vscode.EventEmitter<LanguageUpdateModel> = new vscode.EventEmitter<LanguageUpdateModel>();
|
||||
public readonly onDelete: vscode.Event<LanguageUpdateModel> = this._onDelete.event;
|
||||
|
||||
protected _fileBrowser: vscode.EventEmitter<FileBrowseEventArgs> = new vscode.EventEmitter<FileBrowseEventArgs>();
|
||||
public readonly fileBrowser: vscode.Event<FileBrowseEventArgs> = this._fileBrowser.event;
|
||||
|
||||
protected _filePathSelected: vscode.EventEmitter<FileBrowseEventArgs> = new vscode.EventEmitter<FileBrowseEventArgs>();
|
||||
public readonly filePathSelected: vscode.Event<FileBrowseEventArgs> = this._filePathSelected.event;
|
||||
|
||||
protected _onUpdated: vscode.EventEmitter<LanguageUpdateModel> = new vscode.EventEmitter<LanguageUpdateModel>();
|
||||
public readonly onUpdated: vscode.Event<LanguageUpdateModel> = this._onUpdated.event;
|
||||
|
||||
protected _onList: vscode.EventEmitter<void> = new vscode.EventEmitter<void>();
|
||||
public readonly onList: vscode.Event<void> = this._onList.event;
|
||||
|
||||
protected _onListLoaded: vscode.EventEmitter<mssql.ExternalLanguage[]> = new vscode.EventEmitter<mssql.ExternalLanguage[]>();
|
||||
public readonly onListLoaded: vscode.Event<mssql.ExternalLanguage[]> = this._onListLoaded.event;
|
||||
|
||||
protected _onFailed: vscode.EventEmitter<any> = new vscode.EventEmitter<any>();
|
||||
public readonly onFailed: vscode.Event<any> = this._onFailed.event;
|
||||
|
||||
public componentMaxLength = 350;
|
||||
public browseButtonMaxLength = 20;
|
||||
public spaceBetweenComponentsLength = 10;
|
||||
|
||||
constructor(protected _apiWrapper: ApiWrapper, protected _root?: string, protected _parent?: LanguageViewBase,) {
|
||||
if (this._parent) {
|
||||
if (!this._root) {
|
||||
this._root = this._parent.root;
|
||||
}
|
||||
this.connection = this._parent.connection;
|
||||
this.connectionUrl = this._parent.connectionUrl;
|
||||
}
|
||||
this.registerEvents();
|
||||
}
|
||||
|
||||
private registerEvents() {
|
||||
if (this._parent) {
|
||||
this._dialog = this._parent.dialog;
|
||||
this.fileBrowser(url => {
|
||||
this._parent?.onOpenFileBrowser(url);
|
||||
});
|
||||
this.onUpdate(model => {
|
||||
this._parent?.onUpdateLanguage(model);
|
||||
});
|
||||
this.onEdit(model => {
|
||||
this._parent?.onEditLanguage(model);
|
||||
});
|
||||
this.onDelete(model => {
|
||||
this._parent?.onDeleteLanguage(model);
|
||||
});
|
||||
this.onList(() => {
|
||||
this._parent?.onListLanguages();
|
||||
});
|
||||
this._parent.filePathSelected(x => {
|
||||
this.onFilePathSelected(x);
|
||||
});
|
||||
this._parent.onUpdated(x => {
|
||||
this.onUpdatedLanguage(x);
|
||||
});
|
||||
this._parent.onFailed(x => {
|
||||
this.onActionFailed(x);
|
||||
});
|
||||
this._parent.onListLoaded(x => {
|
||||
this.onListLanguageLoaded(x);
|
||||
});
|
||||
}
|
||||
}
|
||||
public async getLocationTitle(): Promise<string> {
|
||||
let connection = await this.getCurrentConnection();
|
||||
if (connection) {
|
||||
return `${connection.serverName} ${connection.databaseName ? connection.databaseName : constants.extLangLocal}`;
|
||||
}
|
||||
return constants.noConnectionError;
|
||||
}
|
||||
|
||||
public getServerTitle(): string {
|
||||
if (this.connection) {
|
||||
return this.connection.serverName;
|
||||
}
|
||||
return constants.noConnectionError;
|
||||
}
|
||||
|
||||
private async getCurrentConnectionUrl(): Promise<string> {
|
||||
let connection = await this.getCurrentConnection();
|
||||
if (connection) {
|
||||
return await this._apiWrapper.getUriForConnection(connection.connectionId);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
private async getCurrentConnection(): Promise<azdata.connection.ConnectionProfile> {
|
||||
return await this._apiWrapper.getCurrentConnection();
|
||||
}
|
||||
|
||||
public async loadConnection(): Promise<void> {
|
||||
this.connection = await this.getCurrentConnection();
|
||||
this.connectionUrl = await this.getCurrentConnectionUrl();
|
||||
}
|
||||
|
||||
public updateLanguage(updateModel: LanguageUpdateModel): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.onUpdateLanguage(updateModel);
|
||||
this.onUpdated(() => {
|
||||
resolve();
|
||||
});
|
||||
this.onFailed(err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public deleteLanguage(model: LanguageUpdateModel): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.onDeleteLanguage(model);
|
||||
this.onUpdated(() => {
|
||||
resolve();
|
||||
});
|
||||
this.onFailed(err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public listLanguages(): Promise<mssql.ExternalLanguage[]> {
|
||||
return new Promise<mssql.ExternalLanguage[]>((resolve, reject) => {
|
||||
this.onListLanguages();
|
||||
this.onListLoaded(list => {
|
||||
resolve(list);
|
||||
});
|
||||
this.onFailed(err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Dialog model instance
|
||||
*/
|
||||
public get dialog(): azdata.window.Dialog | undefined {
|
||||
return this._dialog;
|
||||
}
|
||||
|
||||
public set dialog(value: azdata.window.Dialog | undefined) {
|
||||
this._dialog = value;
|
||||
}
|
||||
|
||||
public showInfoMessage(message: string): void {
|
||||
this.showMessage(message, azdata.window.MessageLevel.Information);
|
||||
}
|
||||
|
||||
public showErrorMessage(message: string, error?: any): void {
|
||||
this.showMessage(`${message} ${constants.getErrorMessage(error)}`, azdata.window.MessageLevel.Error);
|
||||
}
|
||||
|
||||
public onUpdateLanguage(model: LanguageUpdateModel): void {
|
||||
this._onUpdate.fire(model);
|
||||
}
|
||||
|
||||
public onUpdatedLanguage(model: LanguageUpdateModel): void {
|
||||
this._onUpdated.fire(model);
|
||||
}
|
||||
|
||||
public onActionFailed(error: any): void {
|
||||
this._onFailed.fire(error);
|
||||
}
|
||||
|
||||
public onListLanguageLoaded(list: mssql.ExternalLanguage[]): void {
|
||||
this._onListLoaded.fire(list);
|
||||
}
|
||||
|
||||
public onEditLanguage(model: LanguageUpdateModel): void {
|
||||
this._onEdit.fire(model);
|
||||
}
|
||||
|
||||
public onDeleteLanguage(model: LanguageUpdateModel): void {
|
||||
this._onDelete.fire(model);
|
||||
}
|
||||
|
||||
public onListLanguages(): void {
|
||||
this._onList.fire();
|
||||
}
|
||||
|
||||
public onOpenFileBrowser(fileBrowseArgs: FileBrowseEventArgs): void {
|
||||
this._fileBrowser.fire(fileBrowseArgs);
|
||||
}
|
||||
|
||||
public onFilePathSelected(fileBrowseArgs: FileBrowseEventArgs): void {
|
||||
this._filePathSelected.fire(fileBrowseArgs);
|
||||
}
|
||||
|
||||
private showMessage(message: string, level: azdata.window.MessageLevel): void {
|
||||
if (this._dialog) {
|
||||
this._dialog.message = {
|
||||
text: message,
|
||||
level: level
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public get root(): string {
|
||||
return this._root || '';
|
||||
}
|
||||
|
||||
public asAbsolutePath(filePath: string): string {
|
||||
return path.join(this._root || '', filePath);
|
||||
}
|
||||
|
||||
public abstract reset(): Promise<void>;
|
||||
|
||||
public createNewContent(): mssql.ExternalLanguageContent {
|
||||
return {
|
||||
extensionFileName: '',
|
||||
isLocalFile: true,
|
||||
pathToExtension: '',
|
||||
};
|
||||
}
|
||||
|
||||
public createNewLanguage(): mssql.ExternalLanguage {
|
||||
return {
|
||||
name: '',
|
||||
contents: []
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CurrentLanguagesTab } from './currentLanguagesTab';
|
||||
import { AddEditLanguageTab } from './addEditLanguageTab';
|
||||
import { LanguageViewBase } from './languageViewBase';
|
||||
import * as constants from '../../common/constants';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
|
||||
export class LanguagesDialog extends LanguageViewBase {
|
||||
|
||||
public currentLanguagesTab: CurrentLanguagesTab | undefined;
|
||||
public addNewLanguageTab: AddEditLanguageTab | undefined;
|
||||
|
||||
constructor(
|
||||
apiWrapper: ApiWrapper,
|
||||
root: string) {
|
||||
super(apiWrapper, root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a dialog to manage packages used by notebooks.
|
||||
*/
|
||||
public showDialog(): void {
|
||||
this.dialog = this._apiWrapper.createModelViewDialog(constants.extLangDialogTitle);
|
||||
|
||||
this.currentLanguagesTab = new CurrentLanguagesTab(this._apiWrapper, this);
|
||||
|
||||
let languageUpdateModel = {
|
||||
language: this.createNewLanguage(),
|
||||
content: this.createNewContent(),
|
||||
newLang: true
|
||||
};
|
||||
this.addNewLanguageTab = new AddEditLanguageTab(this._apiWrapper, this, languageUpdateModel);
|
||||
|
||||
this.dialog.okButton.hidden = true;
|
||||
this.dialog.cancelButton.label = constants.extLangDoneButtonText;
|
||||
this.dialog.content = [this.currentLanguagesTab.tab, this.addNewLanguageTab.tab];
|
||||
|
||||
this.dialog.registerCloseValidator(() => {
|
||||
return false; // Blocks Enter key from closing dialog.
|
||||
});
|
||||
|
||||
this._apiWrapper.openDialog(this.dialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the tabs for given provider Id
|
||||
*/
|
||||
public async reset(): Promise<void> {
|
||||
await this.currentLanguagesTab?.reset();
|
||||
await this.addNewLanguageTab?.reset();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as mssql from '../../../../mssql';
|
||||
import { LanguageViewBase } from './languageViewBase';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
|
||||
export class LanguagesTable extends LanguageViewBase {
|
||||
|
||||
private _table: azdata.DeclarativeTableComponent;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor(apiWrapper: ApiWrapper, private _modelBuilder: azdata.ModelBuilder, parent: LanguageViewBase) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
this._table = _modelBuilder.declarativeTable()
|
||||
.withProperties<azdata.DeclarativeTableProperties>(
|
||||
{
|
||||
columns: [
|
||||
{ // Name
|
||||
displayName: constants.extLangLanguageName,
|
||||
ariaLabel: constants.extLangLanguageName,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: 100,
|
||||
headerCssStyles: {
|
||||
...constants.cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
...constants.cssStyles.tableRow
|
||||
},
|
||||
},
|
||||
{ // Platform
|
||||
displayName: constants.extLangLanguagePlatform,
|
||||
ariaLabel: constants.extLangLanguagePlatform,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: 150,
|
||||
headerCssStyles: {
|
||||
...constants.cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
...constants.cssStyles.tableRow
|
||||
},
|
||||
},
|
||||
{ // Created Date
|
||||
displayName: constants.extLangLanguageCreatedDate,
|
||||
ariaLabel: constants.extLangLanguageCreatedDate,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: 150,
|
||||
headerCssStyles: {
|
||||
...constants.cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
...constants.cssStyles.tableRow
|
||||
},
|
||||
},
|
||||
{ // Action
|
||||
displayName: '',
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
isReadOnly: true,
|
||||
width: 50,
|
||||
headerCssStyles: {
|
||||
...constants.cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
...constants.cssStyles.tableRow
|
||||
},
|
||||
},
|
||||
{ // Action
|
||||
displayName: '',
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
isReadOnly: true,
|
||||
width: 50,
|
||||
headerCssStyles: {
|
||||
...constants.cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
...constants.cssStyles.tableRow
|
||||
},
|
||||
}
|
||||
],
|
||||
data: [],
|
||||
ariaLabel: constants.mlsConfigTitle
|
||||
})
|
||||
.component();
|
||||
}
|
||||
|
||||
public get table(): azdata.DeclarativeTableComponent {
|
||||
return this._table;
|
||||
}
|
||||
|
||||
public async loadData(): Promise<void> {
|
||||
let languages: mssql.ExternalLanguage[] | undefined;
|
||||
|
||||
languages = await this.listLanguages();
|
||||
let tableData: any[][] = [];
|
||||
|
||||
if (languages) {
|
||||
|
||||
languages.forEach(language => {
|
||||
if (!language.contents || language.contents.length === 0) {
|
||||
language.contents.push(this.createNewContent());
|
||||
}
|
||||
|
||||
tableData = tableData.concat(language.contents.map(content => this.createTableRow(language, content)));
|
||||
});
|
||||
}
|
||||
|
||||
this._table.data = tableData;
|
||||
}
|
||||
|
||||
private createTableRow(language: mssql.ExternalLanguage, content: mssql.ExternalLanguageContent): any[] {
|
||||
if (this._modelBuilder) {
|
||||
let dropLanguageButton = this._modelBuilder.button().withProperties({
|
||||
label: '',
|
||||
title: constants.deleteTitle,
|
||||
iconPath: {
|
||||
dark: this.asAbsolutePath('images/dark/delete_inverse.svg'),
|
||||
light: this.asAbsolutePath('images/light/delete.svg')
|
||||
},
|
||||
width: 15,
|
||||
height: 15
|
||||
}).component();
|
||||
dropLanguageButton.onDidClick(async () => {
|
||||
await this.deleteLanguage({
|
||||
language: language,
|
||||
content: content,
|
||||
newLang: false
|
||||
});
|
||||
});
|
||||
|
||||
let editLanguageButton = this._modelBuilder.button().withProperties({
|
||||
label: '',
|
||||
title: constants.deleteTitle,
|
||||
iconPath: {
|
||||
dark: this.asAbsolutePath('images/dark/edit_inverse.svg'),
|
||||
light: this.asAbsolutePath('images/light/edit.svg')
|
||||
},
|
||||
width: 15,
|
||||
height: 15
|
||||
}).component();
|
||||
editLanguageButton.onDidClick(() => {
|
||||
this.onEditLanguage({
|
||||
language: language,
|
||||
content: content,
|
||||
newLang: false
|
||||
});
|
||||
});
|
||||
return [language.name, content.platform, language.createdDate, dropLanguageButton, editLanguageButton];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public async reset(): Promise<void> {
|
||||
await this.loadData();
|
||||
}
|
||||
}
|
||||
43
extensions/machine-learning/src/views/interfaces.ts
Normal file
43
extensions/machine-learning/src/views/interfaces.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { azureResource } from '../typings/azure-resource';
|
||||
import { Workspace } from '@azure/arm-machinelearningservices/esm/models';
|
||||
import { WorkspaceModel } from '../modelManagement/interfaces';
|
||||
|
||||
export interface IDataComponent<T> {
|
||||
data: T | undefined;
|
||||
}
|
||||
|
||||
export interface IPageView {
|
||||
registerComponent: (modelBuilder: azdata.ModelBuilder) => azdata.Component;
|
||||
component: azdata.Component | undefined;
|
||||
onEnter?: () => Promise<void>;
|
||||
onLeave?: () => Promise<void>;
|
||||
validate?: () => Promise<boolean>;
|
||||
refresh: () => Promise<void>;
|
||||
disposePage?: () => Promise<void>;
|
||||
viewPanel: azdata.window.ModelViewPanel | undefined;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface AzureWorkspaceResource {
|
||||
account?: azdata.Account,
|
||||
subscription?: azureResource.AzureResourceSubscription,
|
||||
group?: azureResource.AzureResource,
|
||||
workspace?: Workspace
|
||||
}
|
||||
|
||||
export interface AzureModelResource extends AzureWorkspaceResource {
|
||||
model?: WorkspaceModel;
|
||||
}
|
||||
|
||||
export interface IComponentSettings {
|
||||
multiSelect?: boolean;
|
||||
editable?: boolean;
|
||||
selectable?: boolean;
|
||||
}
|
||||
|
||||
|
||||
55
extensions/machine-learning/src/views/mainViewBase.ts
Normal file
55
extensions/machine-learning/src/views/mainViewBase.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ApiWrapper } from '../common/apiWrapper';
|
||||
import { IPageView } from './interfaces';
|
||||
|
||||
/**
|
||||
* Base class for dialog and wizard
|
||||
*/
|
||||
export class MainViewBase {
|
||||
|
||||
protected _pages: IPageView[] = [];
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor(protected _apiWrapper: ApiWrapper) {
|
||||
}
|
||||
|
||||
protected registerContent(viewPanel: azdata.window.DialogTab | azdata.window.WizardPage, componentView: IPageView) {
|
||||
viewPanel.registerContent(async view => {
|
||||
if (componentView) {
|
||||
let component = componentView.registerComponent(view.modelBuilder);
|
||||
await view.initializeModel(component);
|
||||
await componentView.refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected addPage(page: IPageView, index?: number): void {
|
||||
if (index) {
|
||||
this._pages[index] = page;
|
||||
} else {
|
||||
this._pages.push(page);
|
||||
}
|
||||
}
|
||||
|
||||
public async disposePages(): Promise<void> {
|
||||
if (this._pages) {
|
||||
await Promise.all(this._pages.map(async (p) => {
|
||||
if (p.disposePage) {
|
||||
await p.disposePage();
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
public async refresh(): Promise<void> {
|
||||
if (this._pages) {
|
||||
await Promise.all(this._pages.map(async (p) => await p.refresh()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { AzureResourceFilterComponent } from './azureResourceFilterComponent';
|
||||
import { AzureModelsTable } from './azureModelsTable';
|
||||
import { IDataComponent, AzureModelResource } from '../interfaces';
|
||||
import { ModelArtifact } from './prediction/modelArtifact';
|
||||
import { AzureSignInComponent } from './azureSignInComponent';
|
||||
|
||||
export class AzureModelsComponent extends ModelViewBase implements IDataComponent<AzureModelResource[]> {
|
||||
|
||||
public azureModelsTable: AzureModelsTable | undefined;
|
||||
public azureFilterComponent: AzureResourceFilterComponent | undefined;
|
||||
public azureSignInComponent: AzureSignInComponent | undefined;
|
||||
|
||||
private _loader: azdata.LoadingComponent | undefined;
|
||||
private _form: azdata.FormContainer | undefined;
|
||||
private _downloadedFile: ModelArtifact | undefined;
|
||||
|
||||
/**
|
||||
* Component to render a view to pick an azure model
|
||||
*/
|
||||
constructor(apiWrapper: ApiWrapper, parent: ModelViewBase, private _multiSelect: boolean = true) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register components
|
||||
* @param modelBuilder model builder
|
||||
*/
|
||||
public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component {
|
||||
this.azureFilterComponent = new AzureResourceFilterComponent(this._apiWrapper, modelBuilder, this);
|
||||
this.azureModelsTable = new AzureModelsTable(this._apiWrapper, modelBuilder, this, this._multiSelect);
|
||||
this.azureSignInComponent = new AzureSignInComponent(this._apiWrapper, modelBuilder, this);
|
||||
this._loader = modelBuilder.loadingComponent()
|
||||
.withItem(this.azureModelsTable.component)
|
||||
.withProperties({
|
||||
loading: true
|
||||
}).component();
|
||||
this.azureModelsTable.onModelSelectionChanged(async () => {
|
||||
if (this._downloadedFile) {
|
||||
await this._downloadedFile.close();
|
||||
}
|
||||
this._downloadedFile = undefined;
|
||||
});
|
||||
|
||||
this.azureFilterComponent.onWorkspacesSelectedChanged(async () => {
|
||||
await this.onLoading();
|
||||
await this.azureModelsTable?.loadData(this.azureFilterComponent?.data);
|
||||
await this.onLoaded();
|
||||
});
|
||||
|
||||
this._form = modelBuilder.formContainer().withFormItems([{
|
||||
title: '',
|
||||
component: this.azureFilterComponent.component
|
||||
}, {
|
||||
title: '',
|
||||
component: this._loader
|
||||
}]).component();
|
||||
return this._form;
|
||||
}
|
||||
|
||||
public addComponents(formBuilder: azdata.FormBuilder) {
|
||||
this.removeComponents(formBuilder);
|
||||
if (this.azureFilterComponent?.data?.account) {
|
||||
this.addAzureComponents(formBuilder);
|
||||
} else {
|
||||
this.addAzureSignInComponents(formBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
public removeComponents(formBuilder: azdata.FormBuilder) {
|
||||
this.removeAzureComponents(formBuilder);
|
||||
this.removeAzureSignInComponents(formBuilder);
|
||||
}
|
||||
|
||||
private addAzureComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this.azureFilterComponent && this._loader) {
|
||||
this.azureFilterComponent.addComponents(formBuilder);
|
||||
|
||||
formBuilder.addFormItems([{
|
||||
title: '',
|
||||
component: this._loader
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
private removeAzureComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this.azureFilterComponent && this._loader) {
|
||||
this.azureFilterComponent.removeComponents(formBuilder);
|
||||
formBuilder.removeFormItem({
|
||||
title: '',
|
||||
component: this._loader
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private addAzureSignInComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this.azureSignInComponent) {
|
||||
this.azureSignInComponent.addComponents(formBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
private removeAzureSignInComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this.azureSignInComponent) {
|
||||
this.azureSignInComponent.removeComponents(formBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
private async onLoading(): Promise<void> {
|
||||
if (this._loader) {
|
||||
await this._loader.updateProperties({ loading: true });
|
||||
}
|
||||
}
|
||||
|
||||
private async onLoaded(): Promise<void> {
|
||||
if (this._loader) {
|
||||
await this._loader.updateProperties({ loading: false });
|
||||
}
|
||||
}
|
||||
|
||||
public get component(): azdata.Component | undefined {
|
||||
return this._form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the data in the components
|
||||
*/
|
||||
public async loadData(): Promise<void> {
|
||||
await this.azureFilterComponent?.loadData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected data
|
||||
*/
|
||||
public get data(): AzureModelResource[] | undefined {
|
||||
return this.azureModelsTable?.data ? this.azureModelsTable?.data.map(x => Object.assign({}, this.azureFilterComponent?.data, {
|
||||
model: x
|
||||
})) : undefined;
|
||||
}
|
||||
|
||||
public async getDownloadedModel(): Promise<ModelArtifact | undefined> {
|
||||
const data = this.data;
|
||||
if (!this._downloadedFile && data && data.length > 0) {
|
||||
this._downloadedFile = new ModelArtifact(await this.downloadAzureModel(data[0]));
|
||||
}
|
||||
return this._downloadedFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* disposes the view
|
||||
*/
|
||||
public async disposeComponent(): Promise<void> {
|
||||
if (this._downloadedFile) {
|
||||
await this._downloadedFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the view
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
await this.loadData();
|
||||
}
|
||||
}
|
||||
184
extensions/machine-learning/src/views/models/azureModelsTable.ts
Normal file
184
extensions/machine-learning/src/views/models/azureModelsTable.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as constants from '../../common/constants';
|
||||
import { ModelViewBase } from './modelViewBase';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
import { WorkspaceModel } from '../../modelManagement/interfaces';
|
||||
import { IDataComponent, AzureWorkspaceResource } from '../interfaces';
|
||||
|
||||
/**
|
||||
* View to render azure models in a table
|
||||
*/
|
||||
export class AzureModelsTable extends ModelViewBase implements IDataComponent<WorkspaceModel[]> {
|
||||
|
||||
private _table: azdata.DeclarativeTableComponent;
|
||||
private _selectedModel: WorkspaceModel[] = [];
|
||||
private _models: WorkspaceModel[] | undefined;
|
||||
private _onModelSelectionChanged: vscode.EventEmitter<void> = new vscode.EventEmitter<void>();
|
||||
public readonly onModelSelectionChanged: vscode.Event<void> = this._onModelSelectionChanged.event;
|
||||
|
||||
/**
|
||||
* Creates a view to render azure models in a table
|
||||
*/
|
||||
constructor(apiWrapper: ApiWrapper, private _modelBuilder: azdata.ModelBuilder, parent: ModelViewBase, private _multiSelect: boolean = true) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
this._table = this.registerComponent(this._modelBuilder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register components
|
||||
* @param modelBuilder model builder
|
||||
*/
|
||||
public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.DeclarativeTableComponent {
|
||||
this._table = modelBuilder.declarativeTable()
|
||||
.withProperties<azdata.DeclarativeTableProperties>(
|
||||
{
|
||||
columns: [
|
||||
{ // Name
|
||||
displayName: constants.modelName,
|
||||
ariaLabel: constants.modelName,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: 150,
|
||||
headerCssStyles: {
|
||||
...constants.cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
...constants.cssStyles.tableRow
|
||||
},
|
||||
},
|
||||
{ // Created
|
||||
displayName: constants.modelCreated,
|
||||
ariaLabel: constants.modelCreated,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: 100,
|
||||
headerCssStyles: {
|
||||
...constants.cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
...constants.cssStyles.tableRow
|
||||
},
|
||||
},
|
||||
{ // Version
|
||||
displayName: constants.modelVersion,
|
||||
ariaLabel: constants.modelVersion,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: 100,
|
||||
headerCssStyles: {
|
||||
...constants.cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
...constants.cssStyles.tableRow
|
||||
},
|
||||
},
|
||||
{ // Action
|
||||
displayName: '',
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
isReadOnly: true,
|
||||
width: 50,
|
||||
headerCssStyles: {
|
||||
...constants.cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
...constants.cssStyles.tableRow
|
||||
},
|
||||
}
|
||||
],
|
||||
data: [],
|
||||
ariaLabel: constants.mlsConfigTitle
|
||||
})
|
||||
.component();
|
||||
return this._table;
|
||||
}
|
||||
|
||||
public get component(): azdata.DeclarativeTableComponent {
|
||||
return this._table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load data in the component
|
||||
* @param workspaceResource Azure workspace
|
||||
*/
|
||||
public async loadData(workspaceResource?: AzureWorkspaceResource | undefined): Promise<void> {
|
||||
|
||||
if (this._table && workspaceResource) {
|
||||
this._models = await this.listAzureModels(workspaceResource);
|
||||
let tableData: any[][] = [];
|
||||
|
||||
if (this._models) {
|
||||
tableData = tableData.concat(this._models.map(model => this.createTableRow(model)));
|
||||
}
|
||||
|
||||
this._table.data = tableData;
|
||||
}
|
||||
this._onModelSelectionChanged.fire();
|
||||
}
|
||||
|
||||
private createTableRow(model: WorkspaceModel): any[] {
|
||||
if (this._modelBuilder) {
|
||||
let selectModelButton: azdata.Component;
|
||||
let onSelectItem = (checked: boolean) => {
|
||||
const foundItem = this._selectedModel.find(x => x === model);
|
||||
if (checked && !foundItem) {
|
||||
this._selectedModel.push(model);
|
||||
} else if (foundItem) {
|
||||
this._selectedModel = this._selectedModel.filter(x => x !== model);
|
||||
}
|
||||
this._onModelSelectionChanged.fire();
|
||||
};
|
||||
if (this._multiSelect) {
|
||||
const checkbox = this._modelBuilder.checkBox().withProperties({
|
||||
name: 'amlModel',
|
||||
value: model.id,
|
||||
width: 15,
|
||||
height: 15,
|
||||
checked: false
|
||||
}).component();
|
||||
checkbox.onChanged(() => {
|
||||
onSelectItem(checkbox.checked || false);
|
||||
});
|
||||
selectModelButton = checkbox;
|
||||
} else {
|
||||
const radioButton = this._modelBuilder.radioButton().withProperties({
|
||||
name: 'amlModel',
|
||||
value: model.id,
|
||||
width: 15,
|
||||
height: 15,
|
||||
checked: false
|
||||
}).component();
|
||||
radioButton.onDidClick(() => {
|
||||
onSelectItem(radioButton.checked || false);
|
||||
});
|
||||
selectModelButton = radioButton;
|
||||
}
|
||||
|
||||
return [model.name, model.createdTime, model.frameworkVersion, selectModelButton];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected data
|
||||
*/
|
||||
public get data(): WorkspaceModel[] | undefined {
|
||||
if (this._models && this._selectedModel) {
|
||||
return this._selectedModel;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the view
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
await this.loadData();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import { ModelViewBase } from './modelViewBase';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
import { azureResource } from '../../typings/azure-resource';
|
||||
import { Workspace } from '@azure/arm-machinelearningservices/esm/models';
|
||||
import * as constants from '../../common/constants';
|
||||
import { AzureWorkspaceResource, IDataComponent } from '../interfaces';
|
||||
|
||||
/**
|
||||
* View to render filters to pick an azure resource
|
||||
*/
|
||||
const componentWidth = 300;
|
||||
export class AzureResourceFilterComponent extends ModelViewBase implements IDataComponent<AzureWorkspaceResource> {
|
||||
|
||||
private _form: azdata.FormContainer;
|
||||
private _accounts: azdata.DropDownComponent;
|
||||
private _subscriptions: azdata.DropDownComponent;
|
||||
private _groups: azdata.DropDownComponent;
|
||||
private _workspaces: azdata.DropDownComponent;
|
||||
private _azureAccounts: azdata.Account[] = [];
|
||||
private _azureSubscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||
private _azureGroups: azureResource.AzureResource[] = [];
|
||||
private _azureWorkspaces: Workspace[] = [];
|
||||
private _onWorkspacesSelectedChanged: vscode.EventEmitter<void> = new vscode.EventEmitter<void>();
|
||||
public readonly onWorkspacesSelectedChanged: vscode.Event<void> = this._onWorkspacesSelectedChanged.event;
|
||||
|
||||
/**
|
||||
* Creates a new view
|
||||
*/
|
||||
constructor(apiWrapper: ApiWrapper, private _modelBuilder: azdata.ModelBuilder, parent: ModelViewBase) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
this._accounts = this._modelBuilder.dropDown().withProperties({
|
||||
width: componentWidth
|
||||
}).component();
|
||||
this._subscriptions = this._modelBuilder.dropDown().withProperties({
|
||||
width: componentWidth
|
||||
}).component();
|
||||
this._groups = this._modelBuilder.dropDown().withProperties({
|
||||
width: componentWidth
|
||||
}).component();
|
||||
this._workspaces = this._modelBuilder.dropDown().withProperties({
|
||||
width: componentWidth
|
||||
}).component();
|
||||
|
||||
this._accounts.onValueChanged(async () => {
|
||||
await this.onAccountSelected();
|
||||
});
|
||||
|
||||
this._subscriptions.onValueChanged(async () => {
|
||||
await this.onSubscriptionSelected();
|
||||
});
|
||||
this._groups.onValueChanged(async () => {
|
||||
await this.onGroupSelected();
|
||||
});
|
||||
this._workspaces.onValueChanged(async () => {
|
||||
await this.onWorkspaceSelectedChanged();
|
||||
});
|
||||
|
||||
this._form = this._modelBuilder.formContainer().withFormItems([{
|
||||
title: constants.azureAccount,
|
||||
component: this._accounts
|
||||
}, {
|
||||
title: constants.azureSubscription,
|
||||
component: this._subscriptions
|
||||
}, {
|
||||
title: constants.azureGroup,
|
||||
component: this._groups
|
||||
}, {
|
||||
title: constants.azureModelWorkspace,
|
||||
component: this._workspaces
|
||||
}]).component();
|
||||
}
|
||||
|
||||
public addComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this._accounts && this._subscriptions && this._groups && this._workspaces) {
|
||||
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
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
public removeComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this._accounts && this._subscriptions && this._groups && this._workspaces) {
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the created component
|
||||
*/
|
||||
public get component(): azdata.Component {
|
||||
return this._form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected data
|
||||
*/
|
||||
public get data(): AzureWorkspaceResource | undefined {
|
||||
return {
|
||||
account: this.account,
|
||||
subscription: this.subscription,
|
||||
group: this.group,
|
||||
workspace: this.workspace
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* loads data in the components
|
||||
*/
|
||||
public async loadData(): Promise<void> {
|
||||
this._azureAccounts = await this.listAzureAccounts();
|
||||
if (this._azureAccounts && this._azureAccounts.length > 0) {
|
||||
let values = this._azureAccounts.map(a => { return { displayName: a.displayInfo.displayName, name: a.key.accountId }; });
|
||||
this._accounts.values = values;
|
||||
this._accounts.value = values[0];
|
||||
}
|
||||
await this.onAccountSelected();
|
||||
}
|
||||
|
||||
/**
|
||||
* refreshes the view
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
await this.loadData();
|
||||
}
|
||||
|
||||
private async onAccountSelected(): Promise<void> {
|
||||
this._azureSubscriptions = await this.listAzureSubscriptions(this.account);
|
||||
if (this._azureSubscriptions && this._azureSubscriptions.length > 0) {
|
||||
let values = this._azureSubscriptions.map(s => { return { displayName: s.name, name: s.id }; });
|
||||
this._subscriptions.values = values;
|
||||
this._subscriptions.value = values[0];
|
||||
}
|
||||
await this.onSubscriptionSelected();
|
||||
}
|
||||
|
||||
private async onSubscriptionSelected(): Promise<void> {
|
||||
this._azureGroups = await this.listAzureGroups(this.account, this.subscription);
|
||||
if (this._azureGroups && this._azureGroups.length > 0) {
|
||||
let values = this._azureGroups.map(s => { return { displayName: s.name, name: s.id }; });
|
||||
this._groups.values = values;
|
||||
this._groups.value = values[0];
|
||||
}
|
||||
await this.onGroupSelected();
|
||||
}
|
||||
|
||||
private async onGroupSelected(): Promise<void> {
|
||||
this._azureWorkspaces = await this.listWorkspaces(this.account, this.subscription, this.group);
|
||||
if (this._azureWorkspaces && this._azureWorkspaces.length > 0) {
|
||||
let values = this._azureWorkspaces.map(s => { return { displayName: s.name || '', name: s.id || '' }; });
|
||||
this._workspaces.values = values;
|
||||
this._workspaces.value = values[0];
|
||||
}
|
||||
this.onWorkspaceSelectedChanged();
|
||||
}
|
||||
|
||||
private onWorkspaceSelectedChanged(): void {
|
||||
this._onWorkspacesSelectedChanged.fire();
|
||||
}
|
||||
|
||||
private get workspace(): Workspace | undefined {
|
||||
return this._azureWorkspaces && this._workspaces.value ? this._azureWorkspaces.find(a => a.id === (<azdata.CategoryValue>this._workspaces.value).name) : undefined;
|
||||
}
|
||||
|
||||
private get account(): azdata.Account | undefined {
|
||||
return this._azureAccounts && this._accounts.value ? this._azureAccounts.find(a => a.key.accountId === (<azdata.CategoryValue>this._accounts.value).name) : undefined;
|
||||
}
|
||||
|
||||
private get group(): azureResource.AzureResource | undefined {
|
||||
return this._azureGroups && this._groups.value ? this._azureGroups.find(a => a.id === (<azdata.CategoryValue>this._groups.value).name) : undefined;
|
||||
}
|
||||
|
||||
private get subscription(): azureResource.AzureResourceSubscription | undefined {
|
||||
return this._azureSubscriptions && this._subscriptions.value ? this._azureSubscriptions.find(a => a.id === (<azdata.CategoryValue>this._subscriptions.value).name) : undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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, SignInToAzureEventName } from './modelViewBase';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
import * as constants from '../../common/constants';
|
||||
|
||||
/**
|
||||
* View to render filters to pick an azure resource
|
||||
*/
|
||||
const componentWidth = 300;
|
||||
export class AzureSignInComponent extends ModelViewBase {
|
||||
|
||||
private _form: azdata.FormContainer;
|
||||
private _signInButton: azdata.ButtonComponent;
|
||||
|
||||
/**
|
||||
* Creates a new view
|
||||
*/
|
||||
constructor(apiWrapper: ApiWrapper, private _modelBuilder: azdata.ModelBuilder, parent: ModelViewBase) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
this._signInButton = this._modelBuilder.button().withProperties({
|
||||
width: componentWidth,
|
||||
label: constants.azureSignIn,
|
||||
}).component();
|
||||
this._signInButton.onDidClick(() => {
|
||||
this.sendRequest(SignInToAzureEventName);
|
||||
});
|
||||
|
||||
this._form = this._modelBuilder.formContainer().withFormItems([{
|
||||
title: constants.azureAccount,
|
||||
component: this._signInButton
|
||||
}]).component();
|
||||
}
|
||||
|
||||
public addComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this._signInButton) {
|
||||
formBuilder.addFormItems([{
|
||||
title: constants.azureAccount,
|
||||
component: this._signInButton
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
public removeComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this._signInButton) {
|
||||
formBuilder.removeFormItem({
|
||||
title: constants.azureAccount,
|
||||
component: this._signInButton
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the created component
|
||||
*/
|
||||
public get component(): azdata.Component {
|
||||
return this._form;
|
||||
}
|
||||
|
||||
/**
|
||||
* refreshes the view
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ModelViewBase } from './modelViewBase';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
import * as constants from '../../common/constants';
|
||||
import { IDataComponent } from '../interfaces';
|
||||
|
||||
/**
|
||||
* View to pick local models file
|
||||
*/
|
||||
export class LocalModelsComponent extends ModelViewBase implements IDataComponent<string[]> {
|
||||
|
||||
private _form: azdata.FormContainer | undefined;
|
||||
private _flex: azdata.FlexContainer | undefined;
|
||||
private _localPath: azdata.InputBoxComponent | undefined;
|
||||
private _localBrowse: azdata.ButtonComponent | undefined;
|
||||
|
||||
/**
|
||||
* Creates new view
|
||||
*/
|
||||
constructor(apiWrapper: ApiWrapper, parent: ModelViewBase, private _multiSelect: boolean = true) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param modelBuilder Register the components
|
||||
*/
|
||||
public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component {
|
||||
this._localPath = modelBuilder.inputBox().withProperties({
|
||||
value: '',
|
||||
width: this.componentMaxLength - this.browseButtonMaxLength - this.spaceBetweenComponentsLength
|
||||
}).component();
|
||||
this._localBrowse = modelBuilder.button().withProperties({
|
||||
label: constants.browseModels,
|
||||
width: this.browseButtonMaxLength,
|
||||
CSSStyles: {
|
||||
'text-align': 'end'
|
||||
}
|
||||
}).component();
|
||||
this._localBrowse.onDidClick(async () => {
|
||||
|
||||
let options: vscode.OpenDialogOptions = {
|
||||
canSelectFiles: true,
|
||||
canSelectFolders: false,
|
||||
canSelectMany: this._multiSelect,
|
||||
filters: { 'ONNX File': ['onnx'] }
|
||||
};
|
||||
|
||||
const filePaths = await this.getLocalPaths(options);
|
||||
if (this._localPath && filePaths && filePaths.length > 0) {
|
||||
this._localPath.value = this._multiSelect ? filePaths.join(';') : filePaths[0];
|
||||
} else if (this._localPath) {
|
||||
this._localPath.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
this._flex = modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
justifyContent: 'space-between',
|
||||
width: this.componentMaxLength
|
||||
}).withItems([
|
||||
this._localPath, this._localBrowse]
|
||||
).component();
|
||||
|
||||
this._form = modelBuilder.formContainer().withFormItems([{
|
||||
title: '',
|
||||
component: this._flex
|
||||
}]).component();
|
||||
return this._form;
|
||||
}
|
||||
|
||||
public addComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this._flex) {
|
||||
formBuilder.addFormItem({
|
||||
title: '',
|
||||
component: this._flex
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public removeComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this._flex) {
|
||||
formBuilder.removeFormItem({
|
||||
title: '',
|
||||
component: this._flex
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected data
|
||||
*/
|
||||
public get data(): string[] {
|
||||
if (this._localPath?.value) {
|
||||
return this._localPath?.value.split(';');
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the component
|
||||
*/
|
||||
public get component(): azdata.Component | undefined {
|
||||
return this._form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the view
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the page title
|
||||
*/
|
||||
public get title(): string {
|
||||
return constants.localModelsTitle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { CurrentModelsTable } from './currentModelsTable';
|
||||
import { ApiWrapper } from '../../../common/apiWrapper';
|
||||
import { IPageView, IComponentSettings } from '../../interfaces';
|
||||
import { TableSelectionComponent } from '../tableSelectionComponent';
|
||||
import { ImportedModel } from '../../../modelManagement/interfaces';
|
||||
|
||||
/**
|
||||
* View to render current registered models
|
||||
*/
|
||||
export class CurrentModelsComponent extends ModelViewBase implements IPageView {
|
||||
private _tableComponent: azdata.Component | undefined;
|
||||
private _dataTable: CurrentModelsTable | undefined;
|
||||
private _loader: azdata.LoadingComponent | undefined;
|
||||
private _tableSelectionComponent: TableSelectionComponent | undefined;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param apiWrapper Creates new view
|
||||
* @param parent page parent
|
||||
*/
|
||||
constructor(apiWrapper: ApiWrapper, parent: ModelViewBase, private _settings: IComponentSettings) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param modelBuilder register the components
|
||||
*/
|
||||
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._dataTable = new CurrentModelsTable(this._apiWrapper, this, this._settings);
|
||||
this._dataTable.registerComponent(modelBuilder);
|
||||
this._tableComponent = this._dataTable.component;
|
||||
|
||||
let formModelBuilder = modelBuilder.formContainer();
|
||||
this._tableSelectionComponent.addComponents(formModelBuilder);
|
||||
|
||||
if (this._tableComponent) {
|
||||
formModelBuilder.addFormItem({
|
||||
component: this._tableComponent,
|
||||
title: ''
|
||||
});
|
||||
}
|
||||
|
||||
this._loader = modelBuilder.loadingComponent()
|
||||
.withItem(formModelBuilder.component())
|
||||
.withProperties({
|
||||
loading: true
|
||||
}).component();
|
||||
return this._loader;
|
||||
}
|
||||
|
||||
public addComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this._tableSelectionComponent && this._dataTable) {
|
||||
this._tableSelectionComponent.addComponents(formBuilder);
|
||||
this._dataTable.addComponents(formBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
public removeComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this._tableSelectionComponent && this._dataTable) {
|
||||
this._tableSelectionComponent.removeComponents(formBuilder);
|
||||
this._dataTable.removeComponents(formBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the component
|
||||
*/
|
||||
public get component(): azdata.Component | undefined {
|
||||
return this._loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the view
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
await this.onLoading();
|
||||
|
||||
try {
|
||||
if (this._tableSelectionComponent) {
|
||||
this._tableSelectionComponent.refresh();
|
||||
}
|
||||
await this._dataTable?.refresh();
|
||||
} catch (err) {
|
||||
this.showErrorMessage(constants.getErrorMessage(err));
|
||||
} finally {
|
||||
await this.onLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
public get data(): ImportedModel[] | undefined {
|
||||
return this._dataTable?.data;
|
||||
}
|
||||
|
||||
private async onTableSelected(): Promise<void> {
|
||||
if (this._tableSelectionComponent?.data) {
|
||||
this.importTable = this._tableSelectionComponent?.data;
|
||||
await this.storeImportConfigTable();
|
||||
await this._dataTable?.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public get modelTable(): CurrentModelsTable | undefined {
|
||||
return this._dataTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* disposes the view
|
||||
*/
|
||||
public async disposeComponent(): Promise<void> {
|
||||
if (this._dataTable) {
|
||||
await this._dataTable.disposeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the title of the page
|
||||
*/
|
||||
public get title(): string {
|
||||
return constants.currentModelsTitle;
|
||||
}
|
||||
|
||||
private async onLoading(): Promise<void> {
|
||||
if (this._loader) {
|
||||
await this._loader.updateProperties({ loading: true });
|
||||
}
|
||||
}
|
||||
|
||||
private async onLoaded(): Promise<void> {
|
||||
if (this._loader) {
|
||||
await this._loader.updateProperties({ loading: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as constants from '../../../common/constants';
|
||||
import { ModelViewBase, DeleteModelEventName, EditModelEventName } from '../modelViewBase';
|
||||
import { ApiWrapper } from '../../../common/apiWrapper';
|
||||
import { ImportedModel } from '../../../modelManagement/interfaces';
|
||||
import { IDataComponent, IComponentSettings } from '../../interfaces';
|
||||
import { ModelArtifact } from '../prediction/modelArtifact';
|
||||
import * as utils from '../../../common/utils';
|
||||
|
||||
/**
|
||||
* View to render registered models table
|
||||
*/
|
||||
export class CurrentModelsTable extends ModelViewBase implements IDataComponent<ImportedModel[]> {
|
||||
|
||||
private _table: azdata.DeclarativeTableComponent | undefined;
|
||||
private _modelBuilder: azdata.ModelBuilder | undefined;
|
||||
private _selectedModel: ImportedModel[] = [];
|
||||
private _loader: azdata.LoadingComponent | undefined;
|
||||
private _downloadedFile: ModelArtifact | undefined;
|
||||
private _onModelSelectionChanged: vscode.EventEmitter<void> = new vscode.EventEmitter<void>();
|
||||
public readonly onModelSelectionChanged: vscode.Event<void> = this._onModelSelectionChanged.event;
|
||||
|
||||
/**
|
||||
* Creates new view
|
||||
*/
|
||||
constructor(apiWrapper: ApiWrapper, parent: ModelViewBase, private _settings: IComponentSettings) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param modelBuilder register the components
|
||||
*/
|
||||
public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component {
|
||||
this._modelBuilder = modelBuilder;
|
||||
let columns = [
|
||||
{ // Name
|
||||
displayName: constants.modelName,
|
||||
ariaLabel: constants.modelName,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: 150,
|
||||
headerCssStyles: {
|
||||
...constants.cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
...constants.cssStyles.tableRow
|
||||
},
|
||||
},
|
||||
{ // Created
|
||||
displayName: constants.modelCreated,
|
||||
ariaLabel: constants.modelCreated,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: 150,
|
||||
headerCssStyles: {
|
||||
...constants.cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
...constants.cssStyles.tableRow
|
||||
},
|
||||
},
|
||||
{ // Action
|
||||
displayName: '',
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
isReadOnly: true,
|
||||
width: 50,
|
||||
headerCssStyles: {
|
||||
...constants.cssStyles.tableHeader
|
||||
},
|
||||
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
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
this._table = modelBuilder.declarativeTable()
|
||||
.withProperties<azdata.DeclarativeTableProperties>(
|
||||
{
|
||||
columns: columns,
|
||||
data: [],
|
||||
ariaLabel: constants.mlsConfigTitle
|
||||
})
|
||||
.component();
|
||||
this._loader = modelBuilder.loadingComponent()
|
||||
.withItem(this._table)
|
||||
.withProperties({
|
||||
loading: true
|
||||
}).component();
|
||||
return this._loader;
|
||||
}
|
||||
|
||||
public addComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this.component) {
|
||||
formBuilder.addFormItem({ title: constants.modelSourcesTitle, component: this.component });
|
||||
}
|
||||
}
|
||||
|
||||
public removeComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this.component) {
|
||||
formBuilder.removeFormItem({ title: constants.modelSourcesTitle, component: this.component });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the component
|
||||
*/
|
||||
public get component(): azdata.Component | undefined {
|
||||
return this._loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the data in the component
|
||||
*/
|
||||
public async loadData(): Promise<void> {
|
||||
await this.onLoading();
|
||||
if (this._table) {
|
||||
let models: ImportedModel[] | undefined;
|
||||
|
||||
if (this.importTable) {
|
||||
models = await this.listModels(this.importTable);
|
||||
} else {
|
||||
this.showErrorMessage('No import table');
|
||||
}
|
||||
let tableData: any[][] = [];
|
||||
|
||||
if (models) {
|
||||
tableData = tableData.concat(models.map(model => this.createTableRow(model)));
|
||||
}
|
||||
|
||||
this._table.data = tableData;
|
||||
}
|
||||
this.onModelSelected();
|
||||
await this.onLoaded();
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
private createTableRow(model: ImportedModel): any[] {
|
||||
let row: any[] = [model.modelName, model.created];
|
||||
if (this._modelBuilder) {
|
||||
const selectButton = this.createSelectButton(model);
|
||||
if (selectButton) {
|
||||
row.push(selectButton);
|
||||
}
|
||||
const editButtons = this.createEditButtons(model);
|
||||
if (editButtons && editButtons.length > 0) {
|
||||
row = row.concat(editButtons);
|
||||
}
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
private createSelectButton(model: ImportedModel): azdata.Component | undefined {
|
||||
let selectModelButton: azdata.Component | undefined = undefined;
|
||||
if (this._modelBuilder && this._settings.selectable) {
|
||||
|
||||
let onSelectItem = (checked: boolean) => {
|
||||
if (!this._settings.multiSelect) {
|
||||
this._selectedModel = [];
|
||||
}
|
||||
const foundItem = this._selectedModel.find(x => x === model);
|
||||
if (checked && !foundItem) {
|
||||
this._selectedModel.push(model);
|
||||
} else if (foundItem) {
|
||||
this._selectedModel = this._selectedModel.filter(x => x !== model);
|
||||
}
|
||||
this.onModelSelected();
|
||||
};
|
||||
if (this._settings.multiSelect) {
|
||||
const checkbox = this._modelBuilder.checkBox().withProperties({
|
||||
name: 'amlModel',
|
||||
value: model.id,
|
||||
width: 15,
|
||||
height: 15,
|
||||
checked: false
|
||||
}).component();
|
||||
checkbox.onChanged(() => {
|
||||
onSelectItem(checkbox.checked || false);
|
||||
});
|
||||
selectModelButton = checkbox;
|
||||
} else {
|
||||
const radioButton = this._modelBuilder.radioButton().withProperties({
|
||||
name: 'amlModel',
|
||||
value: model.id,
|
||||
width: 15,
|
||||
height: 15,
|
||||
checked: false
|
||||
}).component();
|
||||
radioButton.onDidClick(() => {
|
||||
onSelectItem(radioButton.checked || false);
|
||||
});
|
||||
selectModelButton = radioButton;
|
||||
}
|
||||
}
|
||||
return selectModelButton;
|
||||
}
|
||||
|
||||
private createEditButtons(model: ImportedModel): azdata.Component[] | undefined {
|
||||
let dropButton: azdata.ButtonComponent | undefined = undefined;
|
||||
let editButton: azdata.ButtonComponent | undefined = undefined;
|
||||
if (this._modelBuilder && this._settings.editable) {
|
||||
dropButton = this._modelBuilder.button().withProperties({
|
||||
label: '',
|
||||
title: constants.deleteTitle,
|
||||
iconPath: {
|
||||
dark: this.asAbsolutePath('images/dark/delete_inverse.svg'),
|
||||
light: this.asAbsolutePath('images/light/delete.svg')
|
||||
},
|
||||
width: 15,
|
||||
height: 15
|
||||
}).component();
|
||||
dropButton.onDidClick(async () => {
|
||||
try {
|
||||
const confirm = await utils.promptConfirm(constants.confirmDeleteModel(model.modelName), this._apiWrapper);
|
||||
if (confirm) {
|
||||
await this.sendDataRequest(DeleteModelEventName, model);
|
||||
if (this.parent) {
|
||||
await this.parent?.refresh();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.showErrorMessage(`${constants.updateModelFailedError} ${constants.getErrorMessage(error)}`);
|
||||
}
|
||||
});
|
||||
|
||||
editButton = this._modelBuilder.button().withProperties({
|
||||
label: '',
|
||||
title: constants.deleteTitle,
|
||||
iconPath: {
|
||||
dark: this.asAbsolutePath('images/dark/edit_inverse.svg'),
|
||||
light: this.asAbsolutePath('images/light/edit.svg')
|
||||
},
|
||||
width: 15,
|
||||
height: 15
|
||||
}).component();
|
||||
editButton.onDidClick(async () => {
|
||||
await this.sendDataRequest(EditModelEventName, model);
|
||||
});
|
||||
}
|
||||
return editButton && dropButton ? [editButton, dropButton] : undefined;
|
||||
}
|
||||
|
||||
private async onModelSelected(): Promise<void> {
|
||||
this._onModelSelectionChanged.fire();
|
||||
if (this._downloadedFile) {
|
||||
await this._downloadedFile.close();
|
||||
}
|
||||
this._downloadedFile = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected data
|
||||
*/
|
||||
public get data(): ImportedModel[] | undefined {
|
||||
return this._selectedModel;
|
||||
}
|
||||
|
||||
public async getDownloadedModel(): Promise<ModelArtifact | undefined> {
|
||||
if (!this._downloadedFile && this.data && this.data.length > 0) {
|
||||
this._downloadedFile = new ModelArtifact(await this.downloadRegisteredModel(this.data[0]));
|
||||
}
|
||||
return this._downloadedFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* disposes the view
|
||||
*/
|
||||
public async disposeComponent(): Promise<void> {
|
||||
if (this._downloadedFile) {
|
||||
await this._downloadedFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the view
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
await this.loadData();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ModelViewBase, UpdateModelEventName } from '../modelViewBase';
|
||||
import * as constants from '../../../common/constants';
|
||||
import { ApiWrapper } from '../../../common/apiWrapper';
|
||||
import { DialogView } from '../../dialogView';
|
||||
import { ModelDetailsEditPage } from './modelDetailsEditPage';
|
||||
import { ImportedModel } from '../../../modelManagement/interfaces';
|
||||
|
||||
/**
|
||||
* Dialog to render registered model views
|
||||
*/
|
||||
export class EditModelDialog extends ModelViewBase {
|
||||
|
||||
constructor(
|
||||
apiWrapper: ApiWrapper,
|
||||
root: string,
|
||||
private _parentView: ModelViewBase | undefined,
|
||||
private _model: ImportedModel) {
|
||||
super(apiWrapper, root);
|
||||
this.dialogView = new DialogView(this._apiWrapper);
|
||||
}
|
||||
public dialogView: DialogView;
|
||||
public editModelPage: ModelDetailsEditPage | undefined;
|
||||
|
||||
/**
|
||||
* Opens a dialog to edit models.
|
||||
*/
|
||||
public open(): void {
|
||||
|
||||
this.editModelPage = new ModelDetailsEditPage(this._apiWrapper, this, this._model);
|
||||
|
||||
let registerModelButton = this._apiWrapper.createButton(constants.extLangSaveButtonText);
|
||||
registerModelButton.onClick(async () => {
|
||||
if (this.editModelPage) {
|
||||
const valid = await this.editModelPage.validate();
|
||||
if (valid) {
|
||||
try {
|
||||
await this.sendDataRequest(UpdateModelEventName, this.editModelPage?.data);
|
||||
this.showInfoMessage(constants.modelUpdatedSuccessfully);
|
||||
if (this._parentView) {
|
||||
await this._parentView.refresh();
|
||||
}
|
||||
} catch (error) {
|
||||
this.showInfoMessage(`${constants.modelUpdateFailedError} ${constants.getErrorMessage(error)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let dialog = this.dialogView.createDialog(constants.editModelTitle, [this.editModelPage]);
|
||||
dialog.customButtons = [registerModelButton];
|
||||
this.mainViewPanel = dialog;
|
||||
dialog.okButton.hidden = true;
|
||||
dialog.cancelButton.label = constants.extLangDoneButtonText;
|
||||
|
||||
dialog.registerCloseValidator(() => {
|
||||
return false; // Blocks Enter key from closing dialog.
|
||||
});
|
||||
|
||||
this._apiWrapper.openDialog(dialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the tabs for given provider Id
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
if (this.dialogView) {
|
||||
this.dialogView.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ModelDetailsPage } from '../modelDetailsPage';
|
||||
import { ModelBrowsePage } from '../modelBrowsePage';
|
||||
import { ModelImportLocationPage } from './modelImportLocationPage';
|
||||
|
||||
/**
|
||||
* Wizard to register a model
|
||||
*/
|
||||
export class ImportModelWizard extends ModelViewBase {
|
||||
|
||||
public modelSourcePage: ModelSourcePage | undefined;
|
||||
public modelBrowsePage: ModelBrowsePage | undefined;
|
||||
public modelDetailsPage: ModelDetailsPage | undefined;
|
||||
public modelImportTargetPage: ModelImportLocationPage | 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);
|
||||
this.modelDetailsPage = new ModelDetailsPage(this._apiWrapper, this);
|
||||
this.modelBrowsePage = new ModelBrowsePage(this._apiWrapper, this);
|
||||
this.modelImportTargetPage = new ModelImportLocationPage(this._apiWrapper, this);
|
||||
this.wizardView = new WizardView(this._apiWrapper);
|
||||
|
||||
let wizard = this.wizardView.createWizard(constants.registerModelTitle, [this.modelImportTargetPage, this.modelSourcePage, this.modelBrowsePage, this.modelDetailsPage]);
|
||||
|
||||
this.mainViewPanel = wizard;
|
||||
wizard.doneButton.label = constants.azureRegisterModel;
|
||||
wizard.generateScriptButton.hidden = true;
|
||||
wizard.displayPageTitles = true;
|
||||
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 && pageInfo.newPage === undefined) {
|
||||
wizard.cancelButton.enabled = false;
|
||||
wizard.backButton.enabled = false;
|
||||
let result = await this.registerModel();
|
||||
wizard.cancelButton.enabled = true;
|
||||
wizard.backButton.enabled = true;
|
||||
if (this._parentView) {
|
||||
this._parentView.importTable = this.importTable;
|
||||
await this._parentView.refresh();
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
return validated;
|
||||
});
|
||||
|
||||
await wizard.open();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private async registerModel(): Promise<boolean> {
|
||||
try {
|
||||
if (this.modelResources && this.localModelsComponent && this.modelResources.data === ModelSourceType.Local) {
|
||||
await this.importLocalModel(this.modelsViewData);
|
||||
} else {
|
||||
await this.importAzureModel(this.modelsViewData);
|
||||
}
|
||||
await this.storeImportConfigTable();
|
||||
this.showInfoMessage(constants.modelRegisteredSuccessfully);
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.showErrorMessage(`${constants.modelFailedToRegister} ${constants.getErrorMessage(error)}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the pages
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
await this.wizardView?.refresh();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CurrentModelsComponent } from './currentModelsComponent';
|
||||
|
||||
import { ModelViewBase, RegisterModelEventName } from '../modelViewBase';
|
||||
import * as constants from '../../../common/constants';
|
||||
import { ApiWrapper } from '../../../common/apiWrapper';
|
||||
import { DialogView } from '../../dialogView';
|
||||
|
||||
/**
|
||||
* Dialog to render registered model views
|
||||
*/
|
||||
export class ManageModelsDialog extends ModelViewBase {
|
||||
|
||||
constructor(
|
||||
apiWrapper: ApiWrapper,
|
||||
root: string) {
|
||||
super(apiWrapper, root);
|
||||
this.dialogView = new DialogView(this._apiWrapper);
|
||||
}
|
||||
public dialogView: DialogView;
|
||||
public currentLanguagesTab: CurrentModelsComponent | undefined;
|
||||
|
||||
/**
|
||||
* Opens a dialog to manage packages used by notebooks.
|
||||
*/
|
||||
public open(): void {
|
||||
|
||||
this.currentLanguagesTab = new CurrentModelsComponent(this._apiWrapper, this, {
|
||||
editable: true,
|
||||
selectable: false
|
||||
});
|
||||
|
||||
let registerModelButton = this._apiWrapper.createButton(constants.importModelTitle);
|
||||
registerModelButton.onClick(async () => {
|
||||
await this.sendDataRequest(RegisterModelEventName, this.currentLanguagesTab?.modelTable?.importTable);
|
||||
});
|
||||
|
||||
let dialog = this.dialogView.createDialog(constants.registerModelTitle, [this.currentLanguagesTab]);
|
||||
dialog.customButtons = [registerModelButton];
|
||||
this.mainViewPanel = dialog;
|
||||
dialog.okButton.hidden = true;
|
||||
dialog.cancelButton.label = constants.extLangDoneButtonText;
|
||||
|
||||
dialog.registerCloseValidator(() => {
|
||||
return false; // Blocks Enter key from closing dialog.
|
||||
});
|
||||
|
||||
this._apiWrapper.openDialog(dialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the tabs for given provider Id
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
if (this.dialogView) {
|
||||
this.dialogView.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ImportedModel } from '../../../modelManagement/interfaces';
|
||||
|
||||
/**
|
||||
* View to render filters to pick an azure resource
|
||||
*/
|
||||
export class ModelDetailsComponent extends ModelViewBase implements IDataComponent<ImportedModel> {
|
||||
|
||||
private _form: azdata.FormContainer | undefined;
|
||||
private _nameComponent: azdata.InputBoxComponent | undefined;
|
||||
private _descriptionComponent: azdata.InputBoxComponent | undefined;
|
||||
private _createdComponent: azdata.Component | undefined;
|
||||
private _deployedComponent: azdata.Component | undefined;
|
||||
private _frameworkComponent: azdata.Component | undefined;
|
||||
private _frameworkVersionComponent: azdata.Component | undefined;
|
||||
/**
|
||||
* Creates a new view
|
||||
*/
|
||||
constructor(apiWrapper: ApiWrapper, parent: ModelViewBase, private _model: ImportedModel) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register components
|
||||
* @param modelBuilder model builder
|
||||
*/
|
||||
public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component {
|
||||
this._createdComponent = modelBuilder.text().withProperties({
|
||||
value: this._model.created
|
||||
}).component();
|
||||
this._deployedComponent = modelBuilder.text().withProperties({
|
||||
value: this._model.deploymentTime
|
||||
}).component();
|
||||
this._frameworkComponent = modelBuilder.text().withProperties({
|
||||
value: this._model.framework
|
||||
}).component();
|
||||
this._frameworkVersionComponent = modelBuilder.text().withProperties({
|
||||
value: this._model.frameworkVersion
|
||||
}).component();
|
||||
this._nameComponent = modelBuilder.inputBox().withProperties({
|
||||
width: this.componentMaxLength,
|
||||
value: this._model.modelName
|
||||
}).component();
|
||||
this._descriptionComponent = modelBuilder.inputBox().withProperties({
|
||||
width: this.componentMaxLength,
|
||||
value: this._model.description,
|
||||
multiline: true,
|
||||
height: 50
|
||||
}).component();
|
||||
|
||||
this._form = modelBuilder.formContainer().withFormItems([{
|
||||
title: '',
|
||||
component: this._nameComponent
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
component: this._descriptionComponent
|
||||
}]).component();
|
||||
return this._form;
|
||||
}
|
||||
|
||||
public addComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this._nameComponent && this._descriptionComponent && this._createdComponent && this._deployedComponent && this._frameworkComponent && this._frameworkVersionComponent) {
|
||||
formBuilder.addFormItems([{
|
||||
title: constants.modelName,
|
||||
component: this._nameComponent
|
||||
}, {
|
||||
title: constants.modelCreated,
|
||||
component: this._createdComponent
|
||||
},
|
||||
{
|
||||
title: constants.modelDeployed,
|
||||
component: this._deployedComponent
|
||||
}, {
|
||||
title: constants.modelFramework,
|
||||
component: this._frameworkComponent
|
||||
}, {
|
||||
title: constants.modelFrameworkVersion,
|
||||
component: this._frameworkVersionComponent
|
||||
}, {
|
||||
title: constants.modelDescription,
|
||||
component: this._descriptionComponent
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
public removeComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this._nameComponent && this._descriptionComponent && this._createdComponent && this._deployedComponent && this._frameworkComponent && this._frameworkVersionComponent) {
|
||||
formBuilder.removeFormItem({
|
||||
title: constants.modelCreated,
|
||||
component: this._createdComponent
|
||||
});
|
||||
formBuilder.removeFormItem({
|
||||
title: constants.modelCreated,
|
||||
component: this._frameworkComponent
|
||||
});
|
||||
formBuilder.removeFormItem({
|
||||
title: constants.modelCreated,
|
||||
component: this._frameworkVersionComponent
|
||||
});
|
||||
formBuilder.removeFormItem({
|
||||
title: constants.modelCreated,
|
||||
component: this._deployedComponent
|
||||
});
|
||||
formBuilder.removeFormItem({
|
||||
title: constants.modelName,
|
||||
component: this._nameComponent
|
||||
});
|
||||
formBuilder.removeFormItem({
|
||||
title: constants.modelDescription,
|
||||
component: this._descriptionComponent
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the created component
|
||||
*/
|
||||
public get component(): azdata.Component | undefined {
|
||||
return this._form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected data
|
||||
*/
|
||||
public get data(): ImportedModel | undefined {
|
||||
let model = Object.assign({}, this._model);
|
||||
model.modelName = this._nameComponent?.value || '';
|
||||
model.description = this._descriptionComponent?.value || '';
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* loads data in the components
|
||||
*/
|
||||
public async loadData(): Promise<void> {
|
||||
}
|
||||
|
||||
/**
|
||||
* refreshes the view
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
await this.loadData();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ImportedModel } from '../../../modelManagement/interfaces';
|
||||
import { ModelDetailsComponent } from './modelDetailsComponent';
|
||||
|
||||
/**
|
||||
* View to pick model source
|
||||
*/
|
||||
export class ModelDetailsEditPage extends ModelViewBase implements IPageView, IDataComponent<ImportedModel> {
|
||||
|
||||
private _form: azdata.FormContainer | undefined;
|
||||
private _formBuilder: azdata.FormBuilder | undefined;
|
||||
public modelDetailsComponent: ModelDetailsComponent | undefined;
|
||||
|
||||
constructor(apiWrapper: ApiWrapper, parent: ModelViewBase, private _model: ImportedModel) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param modelBuilder Register components
|
||||
*/
|
||||
public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component {
|
||||
|
||||
this._formBuilder = modelBuilder.formContainer();
|
||||
this.modelDetailsComponent = new ModelDetailsComponent(this._apiWrapper, this, this._model);
|
||||
|
||||
this.modelDetailsComponent.registerComponent(modelBuilder);
|
||||
this.modelDetailsComponent.addComponents(this._formBuilder);
|
||||
this._form = this._formBuilder.component();
|
||||
return this._form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected data
|
||||
*/
|
||||
public get data(): ImportedModel | undefined {
|
||||
return this.modelDetailsComponent?.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the component
|
||||
*/
|
||||
public get component(): azdata.Component | undefined {
|
||||
return this._form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the view
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
if (this.modelDetailsComponent) {
|
||||
await this.modelDetailsComponent.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns page title
|
||||
*/
|
||||
public get title(): string {
|
||||
return constants.modelImportTargetPageTitle;
|
||||
}
|
||||
|
||||
public async disposePage(): Promise<void> {
|
||||
}
|
||||
|
||||
public async validate(): Promise<boolean> {
|
||||
let validated = false;
|
||||
|
||||
if (this.data?.modelName) {
|
||||
validated = true;
|
||||
} else {
|
||||
this.showErrorMessage(constants.modelNameRequiredError);
|
||||
}
|
||||
return validated;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { TableSelectionComponent } from '../tableSelectionComponent';
|
||||
import { DatabaseTable } from '../../../prediction/interfaces';
|
||||
|
||||
/**
|
||||
* View to pick model source
|
||||
*/
|
||||
export class ModelImportLocationPage extends ModelViewBase implements IPageView, IDataComponent<DatabaseTable> {
|
||||
|
||||
private _form: azdata.FormContainer | undefined;
|
||||
private _formBuilder: azdata.FormBuilder | undefined;
|
||||
public tableSelectionComponent: TableSelectionComponent | 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.tableSelectionComponent = new TableSelectionComponent(this._apiWrapper, this, true);
|
||||
this.tableSelectionComponent.onSelectedChanged(async () => {
|
||||
await this.onTableSelected();
|
||||
});
|
||||
this.tableSelectionComponent.registerComponent(modelBuilder);
|
||||
this.tableSelectionComponent.addComponents(this._formBuilder);
|
||||
this._form = this._formBuilder.component();
|
||||
return this._form;
|
||||
}
|
||||
|
||||
private async onTableSelected(): Promise<void> {
|
||||
if (this.tableSelectionComponent?.data) {
|
||||
this.importTable = this.tableSelectionComponent?.data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected data
|
||||
*/
|
||||
public get data(): DatabaseTable | undefined {
|
||||
return this.tableSelectionComponent?.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the component
|
||||
*/
|
||||
public get component(): azdata.Component | undefined {
|
||||
return this._form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the view
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
if (this.tableSelectionComponent) {
|
||||
await this.tableSelectionComponent.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns page title
|
||||
*/
|
||||
public get title(): string {
|
||||
return constants.modelImportTargetPageTitle;
|
||||
}
|
||||
|
||||
public async disposePage(): Promise<void> {
|
||||
}
|
||||
|
||||
public async validate(): Promise<boolean> {
|
||||
let validated = false;
|
||||
|
||||
if (this.data?.databaseName && this.data?.tableName) {
|
||||
validated = true;
|
||||
validated = await this.verifyImportConfigTable(this.data);
|
||||
if (!validated) {
|
||||
this.showErrorMessage(constants.invalidImportTableSchemaError(this.data?.databaseName, this.data?.tableName));
|
||||
}
|
||||
} else {
|
||||
this.showErrorMessage(constants.invalidImportTableError(this.data?.databaseName, this.data?.tableName));
|
||||
}
|
||||
return validated;
|
||||
}
|
||||
}
|
||||
207
extensions/machine-learning/src/views/models/modelBrowsePage.ts
Normal file
207
extensions/machine-learning/src/views/models/modelBrowsePage.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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, ModelViewData } from './modelViewBase';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
import * as constants from '../../common/constants';
|
||||
import { IPageView, IDataComponent } from '../interfaces';
|
||||
import { LocalModelsComponent } from './localModelsComponent';
|
||||
import { AzureModelsComponent } from './azureModelsComponent';
|
||||
import * as utils from '../../common/utils';
|
||||
import { CurrentModelsComponent } from './manageModels/currentModelsComponent';
|
||||
|
||||
/**
|
||||
* View to pick model source
|
||||
*/
|
||||
export class ModelBrowsePage extends ModelViewBase implements IPageView, IDataComponent<ModelViewData[]> {
|
||||
|
||||
private _form: azdata.FormContainer | undefined;
|
||||
private _title: string = constants.localModelPageTitle;
|
||||
private _formBuilder: azdata.FormBuilder | undefined;
|
||||
public localModelsComponent: LocalModelsComponent | undefined;
|
||||
public azureModelsComponent: AzureModelsComponent | undefined;
|
||||
public registeredModelsComponent: CurrentModelsComponent | undefined;
|
||||
|
||||
constructor(apiWrapper: ApiWrapper, parent: ModelViewBase, private _multiSelect: boolean = true) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param modelBuilder Register components
|
||||
*/
|
||||
public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component {
|
||||
|
||||
this._formBuilder = modelBuilder.formContainer();
|
||||
this.localModelsComponent = new LocalModelsComponent(this._apiWrapper, this, this._multiSelect);
|
||||
this.localModelsComponent.registerComponent(modelBuilder);
|
||||
this.azureModelsComponent = new AzureModelsComponent(this._apiWrapper, this, this._multiSelect);
|
||||
this.azureModelsComponent.registerComponent(modelBuilder);
|
||||
this.registeredModelsComponent = new CurrentModelsComponent(this._apiWrapper, this, {
|
||||
selectable: true,
|
||||
multiSelect: this._multiSelect,
|
||||
editable: false
|
||||
});
|
||||
this.registeredModelsComponent.registerComponent(modelBuilder);
|
||||
this.refresh();
|
||||
this._form = this._formBuilder.component();
|
||||
return this._form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected data
|
||||
*/
|
||||
public get data(): ModelViewData[] {
|
||||
return this.modelsViewData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.modelSourceType === ModelSourceType.Local) {
|
||||
if (this.localModelsComponent && this.azureModelsComponent && this.registeredModelsComponent) {
|
||||
this.azureModelsComponent.removeComponents(this._formBuilder);
|
||||
this.registeredModelsComponent.removeComponents(this._formBuilder);
|
||||
this.localModelsComponent.addComponents(this._formBuilder);
|
||||
await this.localModelsComponent.refresh();
|
||||
}
|
||||
|
||||
} else if (this.modelSourceType === ModelSourceType.Azure) {
|
||||
if (this.localModelsComponent && this.azureModelsComponent && this.registeredModelsComponent) {
|
||||
this.localModelsComponent.removeComponents(this._formBuilder);
|
||||
this.azureModelsComponent.addComponents(this._formBuilder);
|
||||
this.registeredModelsComponent.removeComponents(this._formBuilder);
|
||||
await this.azureModelsComponent.refresh();
|
||||
}
|
||||
|
||||
} else if (this.modelSourceType === ModelSourceType.RegisteredModels) {
|
||||
if (this.localModelsComponent && this.azureModelsComponent && this.registeredModelsComponent) {
|
||||
this.localModelsComponent.removeComponents(this._formBuilder);
|
||||
this.azureModelsComponent.removeComponents(this._formBuilder);
|
||||
this.registeredModelsComponent.addComponents(this._formBuilder);
|
||||
await this.registeredModelsComponent.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
this.loadTitle();
|
||||
}
|
||||
|
||||
private loadTitle(): void {
|
||||
if (this.modelSourceType === ModelSourceType.Local) {
|
||||
this._title = constants.localModelPageTitle;
|
||||
} else if (this.modelSourceType === ModelSourceType.Azure) {
|
||||
this._title = constants.azureModelPageTitle;
|
||||
|
||||
} else if (this.modelSourceType === ModelSourceType.RegisteredModels) {
|
||||
this._title = constants.importedModelsPageTitle;
|
||||
} else {
|
||||
this._title = constants.modelSourcePageTitle;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns page title
|
||||
*/
|
||||
public get title(): string {
|
||||
this.loadTitle();
|
||||
return this._title;
|
||||
}
|
||||
|
||||
public validate(): Promise<boolean> {
|
||||
let validated = false;
|
||||
if (this.modelSourceType === ModelSourceType.Local && this.localModelsComponent) {
|
||||
validated = this.localModelsComponent.data !== undefined && this.localModelsComponent.data.length > 0;
|
||||
|
||||
} else if (this.modelSourceType === ModelSourceType.Azure && this.azureModelsComponent) {
|
||||
validated = this.azureModelsComponent.data !== undefined && this.azureModelsComponent.data.length > 0;
|
||||
|
||||
} else if (this.modelSourceType === ModelSourceType.RegisteredModels && this.registeredModelsComponent) {
|
||||
validated = this.registeredModelsComponent.data !== undefined && this.registeredModelsComponent.data.length > 0;
|
||||
}
|
||||
if (!validated) {
|
||||
this.showErrorMessage(constants.invalidModelToSelectError);
|
||||
}
|
||||
return Promise.resolve(validated);
|
||||
}
|
||||
|
||||
public onEnter(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public async onLeave(): Promise<void> {
|
||||
this.modelsViewData = [];
|
||||
if (this.modelSourceType === ModelSourceType.Local && this.localModelsComponent) {
|
||||
if (this.localModelsComponent.data !== undefined && this.localModelsComponent.data.length > 0) {
|
||||
this.modelsViewData = this.localModelsComponent.data.map(x => {
|
||||
const fileName = utils.getFileName(x);
|
||||
return {
|
||||
modelData: x,
|
||||
modelDetails: {
|
||||
modelName: fileName,
|
||||
fileName: fileName
|
||||
},
|
||||
targetImportTable: this.importTable
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
} else if (this.modelSourceType === ModelSourceType.Azure && this.azureModelsComponent) {
|
||||
if (this.azureModelsComponent.data !== undefined && this.azureModelsComponent.data.length > 0) {
|
||||
this.modelsViewData = this.azureModelsComponent.data.map(x => {
|
||||
return {
|
||||
modelData: {
|
||||
account: x.account,
|
||||
subscription: x.subscription,
|
||||
group: x.group,
|
||||
workspace: x.workspace,
|
||||
model: x.model
|
||||
},
|
||||
modelDetails: {
|
||||
modelName: x.model?.name || '',
|
||||
fileName: x.model?.name,
|
||||
framework: x.model?.framework,
|
||||
frameworkVersion: x.model?.frameworkVersion,
|
||||
created: x.model?.createdTime
|
||||
},
|
||||
targetImportTable: this.importTable
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
} else if (this.modelSourceType === ModelSourceType.RegisteredModels && this.registeredModelsComponent) {
|
||||
if (this.registeredModelsComponent.data !== undefined) {
|
||||
this.modelsViewData = this.registeredModelsComponent.data.map(x => {
|
||||
return {
|
||||
modelData: x,
|
||||
modelDetails: {
|
||||
modelName: ''
|
||||
},
|
||||
targetImportTable: this.importTable
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async disposePage(): Promise<void> {
|
||||
if (this.azureModelsComponent) {
|
||||
await this.azureModelsComponent.disposeComponent();
|
||||
|
||||
}
|
||||
if (this.registeredModelsComponent) {
|
||||
await this.registeredModelsComponent.disposeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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, ModelViewData } from './modelViewBase';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
import * as constants from '../../common/constants';
|
||||
import { IPageView, IDataComponent } from '../interfaces';
|
||||
import { ModelsDetailsTableComponent } from './modelsDetailsTableComponent';
|
||||
|
||||
/**
|
||||
* View to pick model details
|
||||
*/
|
||||
export class ModelDetailsPage extends ModelViewBase implements IPageView, IDataComponent<ModelViewData[]> {
|
||||
|
||||
private _form: azdata.FormContainer | undefined;
|
||||
private _formBuilder: azdata.FormBuilder | undefined;
|
||||
public modelDetails: ModelsDetailsTableComponent | 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.modelDetails = new ModelsDetailsTableComponent(this._apiWrapper, modelBuilder, this);
|
||||
this.modelDetails.registerComponent(modelBuilder);
|
||||
this.modelDetails.addComponents(this._formBuilder);
|
||||
this.refresh();
|
||||
this._form = this._formBuilder.component();
|
||||
return this._form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected data
|
||||
*/
|
||||
public get data(): ModelViewData[] | undefined {
|
||||
return this.modelDetails?.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the component
|
||||
*/
|
||||
public get component(): azdata.Component | undefined {
|
||||
return this._form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the view
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
if (this.modelDetails) {
|
||||
await this.modelDetails.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public async onEnter(): Promise<void> {
|
||||
await this.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns page title
|
||||
*/
|
||||
public get title(): string {
|
||||
return constants.modelDetailsPageTitle;
|
||||
}
|
||||
|
||||
public validate(): Promise<boolean> {
|
||||
if (this.data && this.data.length > 0 && !this.data.find(x => !x.modelDetails?.modelName)) {
|
||||
return Promise.resolve(true);
|
||||
} else {
|
||||
this.showErrorMessage(constants.modelNameRequiredError);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { azureResource } from '../../typings/azure-resource';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
import { AzureModelRegistryService } from '../../modelManagement/azureModelRegistryService';
|
||||
import { Workspace } from '@azure/arm-machinelearningservices/esm/models';
|
||||
import { ImportedModel, WorkspaceModel, ModelParameters } from '../../modelManagement/interfaces';
|
||||
import { PredictParameters, DatabaseTable, TableColumn } from '../../prediction/interfaces';
|
||||
import { DeployedModelService } from '../../modelManagement/deployedModelService';
|
||||
import { ManageModelsDialog } from './manageModels/manageModelsDialog';
|
||||
import {
|
||||
AzureResourceEventArgs, ListAzureModelsEventName, ListSubscriptionsEventName, ListModelsEventName, ListWorkspacesEventName,
|
||||
ListGroupsEventName, ListAccountsEventName, RegisterLocalModelEventName, RegisterAzureModelEventName,
|
||||
ModelViewBase, SourceModelSelectedEventName, RegisterModelEventName, DownloadAzureModelEventName,
|
||||
ListDatabaseNamesEventName, ListTableNamesEventName, ListColumnNamesEventName, PredictModelEventName, PredictModelEventArgs, DownloadRegisteredModelEventName, LoadModelParametersEventName, ModelSourceType, ModelViewData, StoreImportTableEventName, VerifyImportTableEventName, EditModelEventName, UpdateModelEventName, DeleteModelEventName, SignInToAzureEventName
|
||||
} from './modelViewBase';
|
||||
import { ControllerBase } from '../controllerBase';
|
||||
import { ImportModelWizard } from './manageModels/importModelWizard';
|
||||
import * as fs from 'fs';
|
||||
import * as constants from '../../common/constants';
|
||||
import { PredictWizard } from './prediction/predictWizard';
|
||||
import { AzureModelResource } from '../interfaces';
|
||||
import { PredictService } from '../../prediction/predictService';
|
||||
import { EditModelDialog } from './manageModels/editModelDialog';
|
||||
|
||||
/**
|
||||
* Model management UI controller
|
||||
*/
|
||||
export class ModelManagementController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* Creates new instance
|
||||
*/
|
||||
constructor(
|
||||
apiWrapper: ApiWrapper,
|
||||
private _root: string,
|
||||
private _amlService: AzureModelRegistryService,
|
||||
private _registeredModelService: DeployedModelService,
|
||||
private _predictService: PredictService) {
|
||||
super(apiWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the dialog for model registration
|
||||
* @param parent parent if the view is opened from another view
|
||||
* @param controller controller
|
||||
* @param apiWrapper apiWrapper
|
||||
* @param root root folder path
|
||||
*/
|
||||
public async registerModel(importTable: DatabaseTable | undefined, parent?: ModelViewBase, controller?: ModelManagementController, apiWrapper?: ApiWrapper, root?: string): Promise<ModelViewBase> {
|
||||
controller = controller || this;
|
||||
apiWrapper = apiWrapper || this._apiWrapper;
|
||||
root = root || this._root;
|
||||
let view = new ImportModelWizard(apiWrapper, root, parent);
|
||||
if (importTable) {
|
||||
view.importTable = importTable;
|
||||
} else {
|
||||
view.importTable = await controller._registeredModelService.getRecentImportTable();
|
||||
}
|
||||
|
||||
controller.registerEvents(view);
|
||||
|
||||
// Open view
|
||||
//
|
||||
await view.open();
|
||||
await view.refresh();
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the dialog to edit model
|
||||
*/
|
||||
public async editModel(model: ImportedModel, parent?: ModelViewBase, controller?: ModelManagementController, apiWrapper?: ApiWrapper, root?: string): Promise<ModelViewBase> {
|
||||
controller = controller || this;
|
||||
apiWrapper = apiWrapper || this._apiWrapper;
|
||||
root = root || this._root;
|
||||
let view = new EditModelDialog(apiWrapper, root, parent, model);
|
||||
|
||||
controller.registerEvents(view);
|
||||
|
||||
// Open view
|
||||
//
|
||||
await view.open();
|
||||
await view.refresh();
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the wizard for prediction
|
||||
*/
|
||||
public async predictModel(): Promise<ModelViewBase> {
|
||||
|
||||
let view = new PredictWizard(this._apiWrapper, this._root);
|
||||
view.importTable = await this._registeredModelService.getRecentImportTable();
|
||||
|
||||
this.registerEvents(view);
|
||||
view.on(LoadModelParametersEventName, async () => {
|
||||
const modelArtifact = await view.getModelFileName();
|
||||
await this.executeAction(view, LoadModelParametersEventName, this.loadModelParameters, this._registeredModelService,
|
||||
modelArtifact?.filePath);
|
||||
});
|
||||
|
||||
// Open view
|
||||
//
|
||||
await view.open();
|
||||
await view.refresh();
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register events in the main view
|
||||
* @param view main view
|
||||
*/
|
||||
public registerEvents(view: ModelViewBase): void {
|
||||
|
||||
// Register events
|
||||
//
|
||||
super.registerEvents(view);
|
||||
view.on(ListAccountsEventName, async () => {
|
||||
await this.executeAction(view, ListAccountsEventName, this.getAzureAccounts, this._amlService);
|
||||
});
|
||||
view.on(ListSubscriptionsEventName, async (arg) => {
|
||||
let azureArgs = <AzureResourceEventArgs>arg;
|
||||
await this.executeAction(view, ListSubscriptionsEventName, this.getAzureSubscriptions, this._amlService, azureArgs.account);
|
||||
});
|
||||
view.on(ListWorkspacesEventName, async (arg) => {
|
||||
let azureArgs = <AzureResourceEventArgs>arg;
|
||||
await this.executeAction(view, ListWorkspacesEventName, this.getWorkspaces, this._amlService, azureArgs.account, azureArgs.subscription, azureArgs.group);
|
||||
});
|
||||
view.on(ListGroupsEventName, async (arg) => {
|
||||
let azureArgs = <AzureResourceEventArgs>arg;
|
||||
await this.executeAction(view, ListGroupsEventName, this.getAzureGroups, this._amlService, azureArgs.account, azureArgs.subscription);
|
||||
});
|
||||
view.on(ListAzureModelsEventName, async (arg) => {
|
||||
let azureArgs = <AzureResourceEventArgs>arg;
|
||||
await this.executeAction(view, ListAzureModelsEventName, this.getAzureModels, this._amlService
|
||||
, azureArgs.account, azureArgs.subscription, azureArgs.group, azureArgs.workspace);
|
||||
});
|
||||
view.on(ListModelsEventName, async (args) => {
|
||||
const table = <DatabaseTable>args;
|
||||
await this.executeAction(view, ListModelsEventName, this.getRegisteredModels, this._registeredModelService, table);
|
||||
});
|
||||
view.on(RegisterLocalModelEventName, async (arg) => {
|
||||
let models = <ModelViewData[]>arg;
|
||||
await this.executeAction(view, RegisterLocalModelEventName, this.registerLocalModel, this._registeredModelService, models);
|
||||
view.refresh();
|
||||
});
|
||||
view.on(RegisterModelEventName, async (args) => {
|
||||
const importTable = <DatabaseTable>args;
|
||||
await this.executeAction(view, RegisterModelEventName, this.registerModel, importTable, view, this, this._apiWrapper, this._root);
|
||||
});
|
||||
view.on(EditModelEventName, async (args) => {
|
||||
const model = <ImportedModel>args;
|
||||
await this.executeAction(view, EditModelEventName, this.editModel, model, view, this, this._apiWrapper, this._root);
|
||||
});
|
||||
view.on(UpdateModelEventName, async (args) => {
|
||||
const model = <ImportedModel>args;
|
||||
await this.executeAction(view, UpdateModelEventName, this.updateModel, this._registeredModelService, model);
|
||||
});
|
||||
view.on(DeleteModelEventName, async (args) => {
|
||||
const model = <ImportedModel>args;
|
||||
await this.executeAction(view, DeleteModelEventName, this.deleteModel, this._registeredModelService, model);
|
||||
});
|
||||
view.on(RegisterAzureModelEventName, async (arg) => {
|
||||
let models = <ModelViewData[]>arg;
|
||||
await this.executeAction(view, RegisterAzureModelEventName, this.registerAzureModel, this._amlService, this._registeredModelService,
|
||||
models);
|
||||
});
|
||||
view.on(DownloadAzureModelEventName, async (arg) => {
|
||||
let registerArgs = <AzureModelResource>arg;
|
||||
await this.executeAction(view, DownloadAzureModelEventName, this.downloadAzureModel, this._amlService,
|
||||
registerArgs.account, registerArgs.subscription, registerArgs.group, registerArgs.workspace, registerArgs.model);
|
||||
});
|
||||
view.on(ListDatabaseNamesEventName, async () => {
|
||||
await this.executeAction(view, ListDatabaseNamesEventName, this.getDatabaseList, this._predictService);
|
||||
});
|
||||
view.on(ListTableNamesEventName, async (arg) => {
|
||||
let dbName = <string>arg;
|
||||
await this.executeAction(view, ListTableNamesEventName, this.getTableList, this._predictService, dbName);
|
||||
});
|
||||
view.on(ListColumnNamesEventName, async (arg) => {
|
||||
let tableColumnsArgs = <DatabaseTable>arg;
|
||||
await this.executeAction(view, ListColumnNamesEventName, this.getTableColumnsList, this._predictService,
|
||||
tableColumnsArgs);
|
||||
});
|
||||
view.on(PredictModelEventName, async (arg) => {
|
||||
let predictArgs = <PredictModelEventArgs>arg;
|
||||
await this.executeAction(view, PredictModelEventName, this.generatePredictScript, this._predictService,
|
||||
predictArgs, predictArgs.model, predictArgs.filePath);
|
||||
});
|
||||
view.on(DownloadRegisteredModelEventName, async (arg) => {
|
||||
let model = <ImportedModel>arg;
|
||||
await this.executeAction(view, DownloadRegisteredModelEventName, this.downloadRegisteredModel, this._registeredModelService,
|
||||
model);
|
||||
});
|
||||
view.on(StoreImportTableEventName, async (arg) => {
|
||||
let importTable = <DatabaseTable>arg;
|
||||
await this.executeAction(view, StoreImportTableEventName, this.storeImportTable, this._registeredModelService,
|
||||
importTable);
|
||||
});
|
||||
view.on(VerifyImportTableEventName, async (arg) => {
|
||||
let importTable = <DatabaseTable>arg;
|
||||
await this.executeAction(view, VerifyImportTableEventName, this.verifyImportTable, this._registeredModelService,
|
||||
importTable);
|
||||
});
|
||||
view.on(SourceModelSelectedEventName, async (arg) => {
|
||||
view.modelSourceType = <ModelSourceType>arg;
|
||||
await view.refresh();
|
||||
});
|
||||
view.on(SignInToAzureEventName, async () => {
|
||||
await this.executeAction(view, SignInToAzureEventName, this.signInToAzure, this._amlService);
|
||||
await view.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the dialog for model management
|
||||
*/
|
||||
public async manageRegisteredModels(importTable?: DatabaseTable): Promise<ModelViewBase> {
|
||||
let view = new ManageModelsDialog(this._apiWrapper, this._root);
|
||||
|
||||
if (importTable) {
|
||||
view.importTable = importTable;
|
||||
} else {
|
||||
view.importTable = await this._registeredModelService.getRecentImportTable();
|
||||
}
|
||||
|
||||
// Register events
|
||||
//
|
||||
this.registerEvents(view);
|
||||
|
||||
// Open view
|
||||
//
|
||||
view.open();
|
||||
return view;
|
||||
}
|
||||
|
||||
private async signInToAzure(service: AzureModelRegistryService): Promise<void> {
|
||||
return await service.signInToAzure();
|
||||
}
|
||||
|
||||
private async getAzureAccounts(service: AzureModelRegistryService): Promise<azdata.Account[]> {
|
||||
return await service.getAccounts();
|
||||
}
|
||||
|
||||
private async getAzureSubscriptions(service: AzureModelRegistryService, account: azdata.Account | undefined): Promise<azureResource.AzureResourceSubscription[] | undefined> {
|
||||
return await service.getSubscriptions(account);
|
||||
}
|
||||
|
||||
private async getAzureGroups(service: AzureModelRegistryService, account: azdata.Account | undefined, subscription: azureResource.AzureResourceSubscription | undefined): Promise<azureResource.AzureResource[] | undefined> {
|
||||
return await service.getGroups(account, subscription);
|
||||
}
|
||||
|
||||
private async getWorkspaces(service: AzureModelRegistryService, account: azdata.Account | undefined, subscription: azureResource.AzureResourceSubscription | undefined, group: azureResource.AzureResource | undefined): Promise<Workspace[] | undefined> {
|
||||
if (!account || !subscription) {
|
||||
return [];
|
||||
}
|
||||
return await service.getWorkspaces(account, subscription, group);
|
||||
}
|
||||
|
||||
private async getRegisteredModels(registeredModelService: DeployedModelService, table: DatabaseTable): Promise<ImportedModel[]> {
|
||||
return registeredModelService.getDeployedModels(table);
|
||||
}
|
||||
|
||||
private async getAzureModels(
|
||||
service: AzureModelRegistryService,
|
||||
account: azdata.Account | undefined,
|
||||
subscription: azureResource.AzureResourceSubscription | undefined,
|
||||
resourceGroup: azureResource.AzureResource | undefined,
|
||||
workspace: Workspace | undefined): Promise<WorkspaceModel[]> {
|
||||
if (!account || !subscription || !resourceGroup || !workspace) {
|
||||
return [];
|
||||
}
|
||||
return await service.getModels(account, subscription, resourceGroup, workspace) || [];
|
||||
}
|
||||
|
||||
private async registerLocalModel(service: DeployedModelService, models: ModelViewData[] | undefined): Promise<void> {
|
||||
if (models) {
|
||||
await Promise.all(models.map(async (model) => {
|
||||
if (model && model.targetImportTable) {
|
||||
const localModel = <string>model.modelData;
|
||||
if (localModel) {
|
||||
await service.deployLocalModel(localModel, model.modelDetails, model.targetImportTable);
|
||||
}
|
||||
} else {
|
||||
throw Error(constants.invalidModelToRegisterError);
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
throw Error(constants.invalidModelToRegisterError);
|
||||
}
|
||||
}
|
||||
|
||||
private async updateModel(service: DeployedModelService, model: ImportedModel | undefined): Promise<void> {
|
||||
if (model) {
|
||||
await service.updateModel(model);
|
||||
} else {
|
||||
throw Error(constants.invalidModelToRegisterError);
|
||||
}
|
||||
}
|
||||
|
||||
private async deleteModel(service: DeployedModelService, model: ImportedModel | undefined): Promise<void> {
|
||||
if (model) {
|
||||
await service.deleteModel(model);
|
||||
} else {
|
||||
throw Error(constants.invalidModelToRegisterError);
|
||||
}
|
||||
}
|
||||
|
||||
private async registerAzureModel(
|
||||
azureService: AzureModelRegistryService,
|
||||
service: DeployedModelService,
|
||||
models: ModelViewData[] | undefined): Promise<void> {
|
||||
if (!models) {
|
||||
throw Error(constants.invalidAzureResourceError);
|
||||
}
|
||||
|
||||
await Promise.all(models.map(async (model) => {
|
||||
if (model && model.targetImportTable) {
|
||||
const azureModel = <AzureModelResource>model.modelData;
|
||||
if (azureModel && azureModel.account && azureModel.subscription && azureModel.group && azureModel.workspace && azureModel.model) {
|
||||
let filePath: string | undefined;
|
||||
try {
|
||||
const filePath = await azureService.downloadModel(azureModel.account, azureModel.subscription, azureModel.group,
|
||||
azureModel.workspace, azureModel.model);
|
||||
if (filePath) {
|
||||
await service.deployLocalModel(filePath, model.modelDetails, model.targetImportTable);
|
||||
} else {
|
||||
throw Error(constants.invalidModelToRegisterError);
|
||||
}
|
||||
} finally {
|
||||
if (filePath) {
|
||||
await fs.promises.unlink(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw Error(constants.invalidModelToRegisterError);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private async getDatabaseList(predictService: PredictService): Promise<string[]> {
|
||||
return await predictService.getDatabaseList();
|
||||
}
|
||||
|
||||
private async getTableList(predictService: PredictService, databaseName: string): Promise<DatabaseTable[]> {
|
||||
return await predictService.getTableList(databaseName);
|
||||
}
|
||||
|
||||
private async getTableColumnsList(predictService: PredictService, databaseTable: DatabaseTable): Promise<TableColumn[]> {
|
||||
return await predictService.getTableColumnsList(databaseTable);
|
||||
}
|
||||
|
||||
private async generatePredictScript(
|
||||
predictService: PredictService,
|
||||
predictParams: PredictParameters,
|
||||
registeredModel: ImportedModel | undefined,
|
||||
filePath: string | undefined
|
||||
): Promise<string> {
|
||||
if (!predictParams) {
|
||||
throw Error(constants.invalidModelToPredictError);
|
||||
}
|
||||
const result = await predictService.generatePredictScript(predictParams, registeredModel, filePath);
|
||||
return result;
|
||||
}
|
||||
|
||||
private async storeImportTable(registeredModelService: DeployedModelService, table: DatabaseTable | undefined): Promise<void> {
|
||||
if (table) {
|
||||
await registeredModelService.storeRecentImportTable(table);
|
||||
} else {
|
||||
throw Error(constants.invalidImportTableError(undefined, undefined));
|
||||
}
|
||||
}
|
||||
|
||||
private async verifyImportTable(registeredModelService: DeployedModelService, table: DatabaseTable | undefined): Promise<boolean> {
|
||||
if (table) {
|
||||
return await registeredModelService.verifyConfigTable(table);
|
||||
} else {
|
||||
throw Error(constants.invalidImportTableError(undefined, undefined));
|
||||
}
|
||||
}
|
||||
|
||||
private async downloadRegisteredModel(
|
||||
registeredModelService: DeployedModelService,
|
||||
model: ImportedModel | undefined): Promise<string> {
|
||||
if (!model) {
|
||||
throw Error(constants.invalidModelToPredictError);
|
||||
}
|
||||
return await registeredModelService.downloadModel(model);
|
||||
}
|
||||
|
||||
private async loadModelParameters(
|
||||
registeredModelService: DeployedModelService,
|
||||
model: string | undefined): Promise<ModelParameters | undefined> {
|
||||
if (!model) {
|
||||
return undefined;
|
||||
}
|
||||
return await registeredModelService.loadModelParameters(model);
|
||||
}
|
||||
|
||||
private async downloadAzureModel(
|
||||
azureService: AzureModelRegistryService,
|
||||
account: azdata.Account | undefined,
|
||||
subscription: azureResource.AzureResourceSubscription | undefined,
|
||||
resourceGroup: azureResource.AzureResource | undefined,
|
||||
workspace: Workspace | undefined,
|
||||
model: WorkspaceModel | undefined): Promise<string> {
|
||||
if (!account || !subscription || !resourceGroup || !workspace || !model) {
|
||||
throw Error(constants.invalidAzureResourceError);
|
||||
}
|
||||
const filePath = await azureService.downloadModel(account, subscription, resourceGroup, workspace, model);
|
||||
if (filePath) {
|
||||
return filePath;
|
||||
} else {
|
||||
throw Error(constants.invalidModelToRegisterError);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as constants from '../../common/constants';
|
||||
import { IPageView, IDataComponent } from '../interfaces';
|
||||
import { ModelSourcesComponent } from './modelSourcesComponent';
|
||||
|
||||
/**
|
||||
* View to pick model source
|
||||
*/
|
||||
export class ModelSourcePage extends ModelViewBase implements IPageView, IDataComponent<ModelSourceType> {
|
||||
|
||||
private _form: azdata.FormContainer | undefined;
|
||||
private _formBuilder: azdata.FormBuilder | undefined;
|
||||
public modelResources: ModelSourcesComponent | undefined;
|
||||
|
||||
constructor(apiWrapper: ApiWrapper, parent: ModelViewBase, private _options: ModelSourceType[] = [ModelSourceType.Local, ModelSourceType.Azure]) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param modelBuilder Register components
|
||||
*/
|
||||
public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component {
|
||||
|
||||
this._formBuilder = modelBuilder.formContainer();
|
||||
this.modelResources = new ModelSourcesComponent(this._apiWrapper, this, this._options);
|
||||
this.modelResources.registerComponent(modelBuilder);
|
||||
this.modelResources.addComponents(this._formBuilder);
|
||||
this._form = this._formBuilder.component();
|
||||
return this._form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected data
|
||||
*/
|
||||
public get data(): ModelSourceType {
|
||||
return this.modelResources?.data || ModelSourceType.Local;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the component
|
||||
*/
|
||||
public get component(): azdata.Component | undefined {
|
||||
return this._form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the view
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns page title
|
||||
*/
|
||||
public get title(): string {
|
||||
return constants.modelSourcePageTitle;
|
||||
}
|
||||
|
||||
public async disposePage(): Promise<void> {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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, SourceModelSelectedEventName, ModelSourceType } from './modelViewBase';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
import * as constants from '../../common/constants';
|
||||
import { IDataComponent } from '../interfaces';
|
||||
|
||||
/**
|
||||
* View to pick model source
|
||||
*/
|
||||
export class ModelSourcesComponent extends ModelViewBase implements IDataComponent<ModelSourceType> {
|
||||
|
||||
private _form: azdata.FormContainer | undefined;
|
||||
private _flexContainer: azdata.FlexContainer | undefined;
|
||||
private _amlModel: azdata.CardComponent | undefined;
|
||||
private _localModel: azdata.CardComponent | undefined;
|
||||
private _registeredModels: azdata.CardComponent | undefined;
|
||||
private _sourceType: ModelSourceType = ModelSourceType.Local;
|
||||
|
||||
constructor(apiWrapper: ApiWrapper, parent: ModelViewBase, private _options: ModelSourceType[] = [ModelSourceType.Local, ModelSourceType.Azure]) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param modelBuilder Register components
|
||||
*/
|
||||
public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component {
|
||||
|
||||
this._localModel = modelBuilder.card()
|
||||
.withProperties({
|
||||
value: 'local',
|
||||
name: 'modelLocation',
|
||||
label: constants.localModelSource,
|
||||
selected: this._options[0] === ModelSourceType.Local,
|
||||
cardType: azdata.CardType.VerticalButton,
|
||||
width: 50
|
||||
}).component();
|
||||
this._amlModel = modelBuilder.card()
|
||||
.withProperties({
|
||||
value: 'aml',
|
||||
name: 'modelLocation',
|
||||
label: constants.azureModelSource,
|
||||
selected: this._options[0] === ModelSourceType.Azure,
|
||||
cardType: azdata.CardType.VerticalButton,
|
||||
width: 50
|
||||
}).component();
|
||||
|
||||
this._registeredModels = modelBuilder.card()
|
||||
.withProperties({
|
||||
value: 'registered',
|
||||
name: 'modelLocation',
|
||||
label: constants.registeredModelsSource,
|
||||
selected: this._options[0] === ModelSourceType.RegisteredModels,
|
||||
cardType: azdata.CardType.VerticalButton,
|
||||
width: 50
|
||||
}).component();
|
||||
|
||||
this._localModel.onCardSelectedChanged(() => {
|
||||
this._sourceType = ModelSourceType.Local;
|
||||
this.sendRequest(SourceModelSelectedEventName, this._sourceType);
|
||||
if (this._amlModel && this._registeredModels) {
|
||||
this._amlModel.selected = false;
|
||||
this._registeredModels.selected = false;
|
||||
}
|
||||
});
|
||||
this._amlModel.onCardSelectedChanged(() => {
|
||||
this._sourceType = ModelSourceType.Azure;
|
||||
this.sendRequest(SourceModelSelectedEventName, this._sourceType);
|
||||
if (this._localModel && this._registeredModels) {
|
||||
this._localModel.selected = false;
|
||||
this._registeredModels.selected = false;
|
||||
}
|
||||
});
|
||||
this._registeredModels.onCardSelectedChanged(() => {
|
||||
this._sourceType = ModelSourceType.RegisteredModels;
|
||||
this.sendRequest(SourceModelSelectedEventName, this._sourceType);
|
||||
if (this._localModel && this._amlModel) {
|
||||
this._localModel.selected = false;
|
||||
this._amlModel.selected = false;
|
||||
}
|
||||
});
|
||||
let components: azdata.Component[] = [];
|
||||
|
||||
this._options.forEach(option => {
|
||||
switch (option) {
|
||||
case ModelSourceType.Local:
|
||||
if (this._localModel) {
|
||||
components.push(this._localModel);
|
||||
}
|
||||
break;
|
||||
case ModelSourceType.Azure:
|
||||
if (this._amlModel) {
|
||||
components.push(this._amlModel);
|
||||
}
|
||||
break;
|
||||
case ModelSourceType.RegisteredModels:
|
||||
if (this._registeredModels) {
|
||||
components.push(this._registeredModels);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
this._sourceType = this._options[0];
|
||||
this.sendRequest(SourceModelSelectedEventName, this._sourceType);
|
||||
|
||||
this._flexContainer = modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
justifyContent: 'space-between'
|
||||
}).withItems(components).component();
|
||||
|
||||
this._form = modelBuilder.formContainer().withFormItems([{
|
||||
title: '',
|
||||
component: this._flexContainer
|
||||
}]).component();
|
||||
|
||||
return this._form;
|
||||
}
|
||||
|
||||
public addComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this._flexContainer) {
|
||||
formBuilder.addFormItem({ title: '', component: this._flexContainer });
|
||||
}
|
||||
}
|
||||
|
||||
public removeComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this._flexContainer) {
|
||||
formBuilder.removeFormItem({ title: '', component: this._flexContainer });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected data
|
||||
*/
|
||||
public get data(): ModelSourceType {
|
||||
return this._sourceType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the component
|
||||
*/
|
||||
public get component(): azdata.Component | undefined {
|
||||
return this._form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the view
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
}
|
||||
}
|
||||
328
extensions/machine-learning/src/views/models/modelViewBase.ts
Normal file
328
extensions/machine-learning/src/views/models/modelViewBase.ts
Normal file
@@ -0,0 +1,328 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { azureResource } from '../../typings/azure-resource';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
import { ViewBase } from '../viewBase';
|
||||
import { ImportedModel, WorkspaceModel, ImportedModelDetails, ModelParameters } from '../../modelManagement/interfaces';
|
||||
import { PredictParameters, DatabaseTable, TableColumn } from '../../prediction/interfaces';
|
||||
import { Workspace } from '@azure/arm-machinelearningservices/esm/models';
|
||||
import { AzureWorkspaceResource, AzureModelResource } from '../interfaces';
|
||||
|
||||
|
||||
export interface AzureResourceEventArgs extends AzureWorkspaceResource {
|
||||
}
|
||||
|
||||
export interface RegisterModelEventArgs extends AzureWorkspaceResource {
|
||||
details?: ImportedModelDetails
|
||||
}
|
||||
|
||||
export interface PredictModelEventArgs extends PredictParameters {
|
||||
model?: ImportedModel;
|
||||
filePath?: string;
|
||||
}
|
||||
|
||||
|
||||
export enum ModelSourceType {
|
||||
Local,
|
||||
Azure,
|
||||
RegisteredModels
|
||||
}
|
||||
|
||||
export interface ModelViewData {
|
||||
modelFile?: string;
|
||||
modelData: AzureModelResource | string | ImportedModel;
|
||||
modelDetails?: ImportedModelDetails;
|
||||
targetImportTable?: DatabaseTable;
|
||||
}
|
||||
|
||||
// Event names
|
||||
//
|
||||
export const ListModelsEventName = 'listModels';
|
||||
export const ListAzureModelsEventName = 'listAzureModels';
|
||||
export const ListAccountsEventName = 'listAccounts';
|
||||
export const ListDatabaseNamesEventName = 'listDatabaseNames';
|
||||
export const ListTableNamesEventName = 'listTableNames';
|
||||
export const ListColumnNamesEventName = 'listColumnNames';
|
||||
export const ListSubscriptionsEventName = 'listSubscriptions';
|
||||
export const ListGroupsEventName = 'listGroups';
|
||||
export const ListWorkspacesEventName = 'listWorkspaces';
|
||||
export const RegisterLocalModelEventName = 'registerLocalModel';
|
||||
export const RegisterAzureModelEventName = 'registerAzureLocalModel';
|
||||
export const DownloadAzureModelEventName = 'downloadAzureLocalModel';
|
||||
export const DownloadRegisteredModelEventName = 'downloadRegisteredModel';
|
||||
export const PredictModelEventName = 'predictModel';
|
||||
export const RegisterModelEventName = 'registerModel';
|
||||
export const EditModelEventName = 'editModel';
|
||||
export const UpdateModelEventName = 'updateModel';
|
||||
export const DeleteModelEventName = 'deleteModel';
|
||||
export const SourceModelSelectedEventName = 'sourceModelSelected';
|
||||
export const LoadModelParametersEventName = 'loadModelParameters';
|
||||
export const StoreImportTableEventName = 'storeImportTable';
|
||||
export const VerifyImportTableEventName = 'verifyImportTable';
|
||||
export const SignInToAzureEventName = 'signInToAzure';
|
||||
|
||||
/**
|
||||
* Base class for all model management views
|
||||
*/
|
||||
export abstract class ModelViewBase extends ViewBase {
|
||||
|
||||
private _modelSourceType: ModelSourceType = ModelSourceType.Local;
|
||||
private _modelsViewData: ModelViewData[] = [];
|
||||
private _importTable: DatabaseTable | undefined;
|
||||
|
||||
constructor(apiWrapper: ApiWrapper, root?: string, parent?: ModelViewBase) {
|
||||
super(apiWrapper, root, parent);
|
||||
}
|
||||
|
||||
protected getEventNames(): string[] {
|
||||
return super.getEventNames().concat([ListModelsEventName,
|
||||
ListAzureModelsEventName,
|
||||
ListAccountsEventName,
|
||||
ListSubscriptionsEventName,
|
||||
ListGroupsEventName,
|
||||
ListWorkspacesEventName,
|
||||
RegisterLocalModelEventName,
|
||||
RegisterAzureModelEventName,
|
||||
RegisterModelEventName,
|
||||
SourceModelSelectedEventName,
|
||||
ListDatabaseNamesEventName,
|
||||
ListTableNamesEventName,
|
||||
ListColumnNamesEventName,
|
||||
PredictModelEventName,
|
||||
DownloadAzureModelEventName,
|
||||
DownloadRegisteredModelEventName,
|
||||
LoadModelParametersEventName,
|
||||
StoreImportTableEventName,
|
||||
VerifyImportTableEventName,
|
||||
EditModelEventName,
|
||||
UpdateModelEventName,
|
||||
DeleteModelEventName,
|
||||
SignInToAzureEventName]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parent view
|
||||
*/
|
||||
public get parent(): ModelViewBase | undefined {
|
||||
return this._parent ? <ModelViewBase>this._parent : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* list azure models
|
||||
*/
|
||||
public async listAzureModels(workspaceResource: AzureWorkspaceResource): Promise<WorkspaceModel[]> {
|
||||
const args: AzureResourceEventArgs = workspaceResource;
|
||||
return await this.sendDataRequest(ListAzureModelsEventName, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* list registered models
|
||||
*/
|
||||
public async listModels(table: DatabaseTable): Promise<ImportedModel[]> {
|
||||
return await this.sendDataRequest(ListModelsEventName, table);
|
||||
}
|
||||
|
||||
/**
|
||||
* lists azure accounts
|
||||
*/
|
||||
public async listAzureAccounts(): Promise<azdata.Account[]> {
|
||||
return await this.sendDataRequest(ListAccountsEventName);
|
||||
}
|
||||
|
||||
/**
|
||||
* lists database names
|
||||
*/
|
||||
public async listDatabaseNames(): Promise<string[]> {
|
||||
return await this.sendDataRequest(ListDatabaseNamesEventName);
|
||||
}
|
||||
|
||||
/**
|
||||
* lists table names
|
||||
*/
|
||||
public async listTableNames(dbName: string): Promise<DatabaseTable[]> {
|
||||
return await this.sendDataRequest(ListTableNamesEventName, dbName);
|
||||
}
|
||||
|
||||
/**
|
||||
* lists column names
|
||||
*/
|
||||
public async listColumnNames(table: DatabaseTable): Promise<TableColumn[]> {
|
||||
return await this.sendDataRequest(ListColumnNamesEventName, table);
|
||||
}
|
||||
|
||||
/**
|
||||
* lists azure subscriptions
|
||||
* @param account azure account
|
||||
*/
|
||||
public async listAzureSubscriptions(account: azdata.Account | undefined): Promise<azureResource.AzureResourceSubscription[]> {
|
||||
const args: AzureResourceEventArgs = {
|
||||
account: account
|
||||
};
|
||||
return await this.sendDataRequest(ListSubscriptionsEventName, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* registers local model
|
||||
* @param localFilePath local file path
|
||||
*/
|
||||
public async importLocalModel(models: ModelViewData[]): Promise<void> {
|
||||
return await this.sendDataRequest(RegisterLocalModelEventName, models);
|
||||
}
|
||||
|
||||
/**
|
||||
* downloads registered model
|
||||
* @param model model to download
|
||||
*/
|
||||
public async downloadRegisteredModel(model: ImportedModel | undefined): Promise<string> {
|
||||
return await this.sendDataRequest(DownloadRegisteredModelEventName, model);
|
||||
}
|
||||
|
||||
/**
|
||||
* download azure model
|
||||
* @param args azure resource
|
||||
*/
|
||||
public async downloadAzureModel(resource: AzureModelResource | undefined): Promise<string> {
|
||||
return await this.sendDataRequest(DownloadAzureModelEventName, resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads model parameters
|
||||
*/
|
||||
public async loadModelParameters(): Promise<ModelParameters | undefined> {
|
||||
return await this.sendDataRequest(LoadModelParametersEventName);
|
||||
}
|
||||
|
||||
/**
|
||||
* registers azure model
|
||||
* @param args azure resource
|
||||
*/
|
||||
public async importAzureModel(models: ModelViewData[]): Promise<void> {
|
||||
return await this.sendDataRequest(RegisterAzureModelEventName, models);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the name of the table as recent config table for importing models
|
||||
*/
|
||||
public async storeImportConfigTable(): Promise<void> {
|
||||
await this.sendRequest(StoreImportTableEventName, this.importTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if table is valid to import models to
|
||||
*/
|
||||
public async verifyImportConfigTable(table: DatabaseTable): Promise<boolean> {
|
||||
return await this.sendDataRequest(VerifyImportTableEventName, table);
|
||||
}
|
||||
|
||||
/**
|
||||
* registers azure model
|
||||
* @param args azure resource
|
||||
*/
|
||||
public async generatePredictScript(model: ImportedModel | undefined, filePath: string | undefined, params: PredictParameters | undefined): Promise<void> {
|
||||
const args: PredictModelEventArgs = Object.assign({}, params, {
|
||||
model: model,
|
||||
filePath: filePath,
|
||||
loadFromRegisteredModel: !filePath
|
||||
});
|
||||
return await this.sendDataRequest(PredictModelEventName, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* list resource groups
|
||||
* @param account azure account
|
||||
* @param subscription azure subscription
|
||||
*/
|
||||
public async listAzureGroups(account: azdata.Account | undefined, subscription: azureResource.AzureResourceSubscription | undefined): Promise<azureResource.AzureResource[]> {
|
||||
const args: AzureResourceEventArgs = {
|
||||
account: account,
|
||||
subscription: subscription
|
||||
};
|
||||
return await this.sendDataRequest(ListGroupsEventName, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets model source type
|
||||
*/
|
||||
public set modelSourceType(value: ModelSourceType) {
|
||||
if (this.parent) {
|
||||
this.parent.modelSourceType = value;
|
||||
} else {
|
||||
this._modelSourceType = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns model source type
|
||||
*/
|
||||
public get modelSourceType(): ModelSourceType {
|
||||
if (this.parent) {
|
||||
return this.parent.modelSourceType;
|
||||
} else {
|
||||
return this._modelSourceType;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets model data
|
||||
*/
|
||||
public set modelsViewData(value: ModelViewData[]) {
|
||||
if (this.parent) {
|
||||
this.parent.modelsViewData = value;
|
||||
} else {
|
||||
this._modelsViewData = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns model data
|
||||
*/
|
||||
public get modelsViewData(): ModelViewData[] {
|
||||
if (this.parent) {
|
||||
return this.parent.modelsViewData;
|
||||
} else {
|
||||
return this._modelsViewData;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets import table
|
||||
*/
|
||||
public set importTable(value: DatabaseTable | undefined) {
|
||||
if (this.parent) {
|
||||
this.parent.importTable = value;
|
||||
} else {
|
||||
this._importTable = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns import table
|
||||
*/
|
||||
public get importTable(): DatabaseTable | undefined {
|
||||
if (this.parent) {
|
||||
return this.parent.importTable;
|
||||
} else {
|
||||
return this._importTable;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* lists azure workspaces
|
||||
* @param account azure account
|
||||
* @param subscription azure subscription
|
||||
* @param group azure resource group
|
||||
*/
|
||||
public async listWorkspaces(account: azdata.Account | undefined, subscription: azureResource.AzureResourceSubscription | undefined, group: azureResource.AzureResource | undefined): Promise<Workspace[]> {
|
||||
const args: AzureResourceEventArgs = {
|
||||
account: account,
|
||||
subscription: subscription,
|
||||
group: group
|
||||
};
|
||||
return await this.sendDataRequest(ListWorkspacesEventName, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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, ModelViewData } from './modelViewBase';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
import * as constants from '../../common/constants';
|
||||
import { IDataComponent } from '../interfaces';
|
||||
|
||||
/**
|
||||
* View to pick local models file
|
||||
*/
|
||||
export class ModelsDetailsTableComponent extends ModelViewBase implements IDataComponent<ModelViewData[]> {
|
||||
private _table: azdata.DeclarativeTableComponent | undefined;
|
||||
|
||||
/**
|
||||
* Creates new view
|
||||
*/
|
||||
constructor(apiWrapper: ApiWrapper, private _modelBuilder: azdata.ModelBuilder, parent: ModelViewBase) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param modelBuilder Register the components
|
||||
*/
|
||||
public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component {
|
||||
this._table = modelBuilder.declarativeTable()
|
||||
.withProperties<azdata.DeclarativeTableProperties>(
|
||||
{
|
||||
columns: [
|
||||
{ // Name
|
||||
displayName: constants.modelFileName,
|
||||
ariaLabel: constants.modelFileName,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: 150,
|
||||
headerCssStyles: {
|
||||
...constants.cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
...constants.cssStyles.tableRow
|
||||
},
|
||||
},
|
||||
{ // Name
|
||||
displayName: constants.modelName,
|
||||
ariaLabel: constants.modelName,
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
isReadOnly: true,
|
||||
width: 150,
|
||||
headerCssStyles: {
|
||||
...constants.cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
...constants.cssStyles.tableRow
|
||||
},
|
||||
},
|
||||
{ // Created
|
||||
displayName: constants.modelDescription,
|
||||
ariaLabel: constants.modelDescription,
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
isReadOnly: true,
|
||||
width: 100,
|
||||
headerCssStyles: {
|
||||
...constants.cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
...constants.cssStyles.tableRow
|
||||
},
|
||||
},
|
||||
{ // Action
|
||||
displayName: '',
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
isReadOnly: true,
|
||||
width: 50,
|
||||
headerCssStyles: {
|
||||
...constants.cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
...constants.cssStyles.tableRow
|
||||
},
|
||||
}
|
||||
],
|
||||
data: [],
|
||||
ariaLabel: constants.mlsConfigTitle
|
||||
})
|
||||
.component();
|
||||
|
||||
return this._table;
|
||||
}
|
||||
|
||||
public addComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this._table) {
|
||||
formBuilder.addFormItems([{
|
||||
title: '',
|
||||
component: this._table
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
public removeComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this._table) {
|
||||
formBuilder.removeFormItem({
|
||||
title: '',
|
||||
component: this._table
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load data in the component
|
||||
* @param workspaceResource Azure workspace
|
||||
*/
|
||||
public async loadData(): Promise<void> {
|
||||
|
||||
const models = this.modelsViewData;
|
||||
if (this._table && models) {
|
||||
|
||||
let tableData: any[][] = [];
|
||||
tableData = tableData.concat(models.map(model => this.createTableRow(model)));
|
||||
this._table.data = tableData;
|
||||
}
|
||||
}
|
||||
|
||||
private createTableRow(model: ModelViewData | undefined): any[] {
|
||||
if (this._modelBuilder && model && model.modelDetails) {
|
||||
const nameComponent = this._modelBuilder.inputBox().withProperties({
|
||||
value: model.modelDetails.modelName,
|
||||
width: this.componentMaxLength - 100,
|
||||
required: true
|
||||
}).component();
|
||||
const descriptionComponent = this._modelBuilder.inputBox().withProperties({
|
||||
value: model.modelDetails.description,
|
||||
width: this.componentMaxLength
|
||||
}).component();
|
||||
descriptionComponent.onTextChanged(() => {
|
||||
if (model.modelDetails) {
|
||||
model.modelDetails.description = descriptionComponent.value;
|
||||
}
|
||||
});
|
||||
nameComponent.onTextChanged(() => {
|
||||
if (model.modelDetails) {
|
||||
model.modelDetails.modelName = nameComponent.value || '';
|
||||
}
|
||||
});
|
||||
let deleteButton = this._modelBuilder.button().withProperties({
|
||||
label: '',
|
||||
title: constants.deleteTitle,
|
||||
width: 15,
|
||||
height: 15,
|
||||
iconPath: {
|
||||
dark: this.asAbsolutePath('images/dark/delete_inverse.svg'),
|
||||
light: this.asAbsolutePath('images/light/delete.svg')
|
||||
},
|
||||
}).component();
|
||||
deleteButton.onDidClick(async () => {
|
||||
this.modelsViewData = this.modelsViewData.filter(x => x !== model);
|
||||
await this.refresh();
|
||||
});
|
||||
return [model.modelDetails.fileName, nameComponent, descriptionComponent, deleteButton];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected data
|
||||
*/
|
||||
public get data(): ModelViewData[] {
|
||||
return this.modelsViewData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the component
|
||||
*/
|
||||
public get component(): azdata.Component | undefined {
|
||||
return this._table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the view
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
await this.loadData();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ModelViewBase } from './modelViewBase';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
import * as constants from '../../common/constants';
|
||||
import { IDataComponent } from '../interfaces';
|
||||
import { DatabaseTable } from '../../prediction/interfaces';
|
||||
|
||||
/**
|
||||
* View to render filters to pick an azure resource
|
||||
*/
|
||||
export class TableSelectionComponent extends ModelViewBase implements IDataComponent<DatabaseTable> {
|
||||
|
||||
private _form: azdata.FormContainer | undefined;
|
||||
private _databases: azdata.DropDownComponent | undefined;
|
||||
private _selectedTableName: string = '';
|
||||
private _tables: azdata.DropDownComponent | undefined;
|
||||
private _dbNames: string[] = [];
|
||||
private _tableNames: DatabaseTable[] = [];
|
||||
private _dbTableComponent: azdata.FlexContainer | undefined;
|
||||
private tableMaxLength = this.componentMaxLength * 2 + 70;
|
||||
private _onSelectedChanged: vscode.EventEmitter<void> = new vscode.EventEmitter<void>();
|
||||
public readonly onSelectedChanged: vscode.Event<void> = this._onSelectedChanged.event;
|
||||
|
||||
/**
|
||||
* Creates a new view
|
||||
*/
|
||||
constructor(apiWrapper: ApiWrapper, parent: ModelViewBase, private _editable: boolean) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register components
|
||||
* @param modelBuilder model builder
|
||||
*/
|
||||
public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component {
|
||||
this._databases = modelBuilder.dropDown().withProperties({
|
||||
width: this.componentMaxLength,
|
||||
editable: this._editable,
|
||||
fireOnTextChange: this._editable
|
||||
}).component();
|
||||
this._tables = modelBuilder.dropDown().withProperties({
|
||||
width: this.componentMaxLength,
|
||||
editable: this._editable,
|
||||
fireOnTextChange: this._editable
|
||||
}).component();
|
||||
|
||||
this._databases.onValueChanged(async () => {
|
||||
await this.onDatabaseSelected();
|
||||
});
|
||||
|
||||
this._tables.onValueChanged(async (value) => {
|
||||
// There's an issue with dropdown doesn't set the value in editable mode. this is the workaround
|
||||
|
||||
if (this._tables && value) {
|
||||
this._selectedTableName = this._editable ? value : value.selected;
|
||||
}
|
||||
await this.onTableSelected();
|
||||
});
|
||||
|
||||
const databaseForm = modelBuilder.formContainer().withFormItems([{
|
||||
title: constants.columnDatabase,
|
||||
component: this._databases,
|
||||
}]).withLayout({
|
||||
padding: '0px'
|
||||
}).component();
|
||||
const tableForm = modelBuilder.formContainer().withFormItems([{
|
||||
title: constants.columnTable,
|
||||
component: this._tables
|
||||
}]).withLayout({
|
||||
padding: '0px'
|
||||
}).component();
|
||||
this._dbTableComponent = modelBuilder.flexContainer().withItems([
|
||||
databaseForm,
|
||||
tableForm
|
||||
], {
|
||||
flex: '0 0 auto',
|
||||
CSSStyles: {
|
||||
'align-items': 'flex-start'
|
||||
}
|
||||
}).withLayout({
|
||||
flexFlow: 'row',
|
||||
justifyContent: 'space-between',
|
||||
width: this.tableMaxLength
|
||||
}).component();
|
||||
|
||||
this._form = modelBuilder.formContainer().withFormItems([{
|
||||
title: '',
|
||||
component: this._dbTableComponent
|
||||
}]).component();
|
||||
return this._form;
|
||||
}
|
||||
|
||||
public addComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this._databases && this._tables) {
|
||||
formBuilder.addFormItems([{
|
||||
title: constants.databaseName,
|
||||
component: this._databases
|
||||
}, {
|
||||
title: constants.tableName,
|
||||
component: this._tables
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
public removeComponents(formBuilder: azdata.FormBuilder) {
|
||||
if (this._databases && this._tables) {
|
||||
formBuilder.removeFormItem({
|
||||
title: constants.databaseName,
|
||||
component: this._databases
|
||||
});
|
||||
formBuilder.removeFormItem({
|
||||
title: constants.tableName,
|
||||
component: this._tables
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the created component
|
||||
*/
|
||||
public get component(): azdata.Component | undefined {
|
||||
return this._dbTableComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected data
|
||||
*/
|
||||
public get data(): DatabaseTable | undefined {
|
||||
return this.databaseTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* loads data in the components
|
||||
*/
|
||||
public async loadData(): Promise<void> {
|
||||
this._dbNames = await this.listDatabaseNames();
|
||||
if (this._databases && this._dbNames && this._dbNames.length > 0) {
|
||||
this._databases.values = this._dbNames;
|
||||
if (this.importTable) {
|
||||
this._databases.value = this.importTable.databaseName;
|
||||
} else {
|
||||
this._databases.value = this._dbNames[0];
|
||||
}
|
||||
}
|
||||
await this.onDatabaseSelected();
|
||||
}
|
||||
|
||||
/**
|
||||
* refreshes the view
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
await this.loadData();
|
||||
}
|
||||
|
||||
private async onDatabaseSelected(): Promise<void> {
|
||||
this._tableNames = await this.listTableNames(this.databaseName || '');
|
||||
if (this._tables && this._tableNames && this._tableNames.length > 0) {
|
||||
this._tables.values = this._tableNames.map(t => this.getTableFullName(t));
|
||||
if (this.importTable) {
|
||||
const selectedTable = this._tableNames.find(t => t.tableName === this.importTable?.tableName && t.schema === this.importTable?.schema);
|
||||
if (selectedTable) {
|
||||
this._selectedTableName = this.getTableFullName(selectedTable);
|
||||
this._tables.value = this.getTableFullName(selectedTable);
|
||||
} else {
|
||||
this._selectedTableName = this._editable ? this.getTableFullName(this.importTable) : this.getTableFullName(this._tableNames[0]);
|
||||
}
|
||||
} else {
|
||||
this._selectedTableName = this.getTableFullName(this._tableNames[0]);
|
||||
}
|
||||
this._tables.value = this._selectedTableName;
|
||||
} else if (this._tables) {
|
||||
this._tables.values = [];
|
||||
this._tables.value = '';
|
||||
}
|
||||
await this.onTableSelected();
|
||||
}
|
||||
|
||||
private getTableFullName(table: DatabaseTable): string {
|
||||
return `${table.schema}.${table.tableName}`;
|
||||
}
|
||||
|
||||
private async onTableSelected(): Promise<void> {
|
||||
this._onSelectedChanged.fire();
|
||||
}
|
||||
|
||||
private get databaseName(): string | undefined {
|
||||
return <string>this._databases?.value;
|
||||
}
|
||||
|
||||
private get databaseTable(): DatabaseTable {
|
||||
let selectedItem = this._tableNames.find(x => this.getTableFullName(x) === this._selectedTableName);
|
||||
if (!selectedItem) {
|
||||
const value = this._selectedTableName;
|
||||
const parts = value ? value.split('.') : undefined;
|
||||
selectedItem = {
|
||||
databaseName: this.databaseName,
|
||||
tableName: parts && parts.length > 1 ? parts[1] : value,
|
||||
schema: parts && parts.length > 1 ? parts[0] : 'dbo',
|
||||
};
|
||||
}
|
||||
return {
|
||||
databaseName: this.databaseName,
|
||||
tableName: selectedItem?.tableName,
|
||||
schema: selectedItem?.schema
|
||||
};
|
||||
}
|
||||
}
|
||||
195
extensions/machine-learning/src/views/viewBase.ts
Normal file
195
extensions/machine-learning/src/views/viewBase.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as constants from '../common/constants';
|
||||
import { ApiWrapper } from '../common/apiWrapper';
|
||||
import * as path from 'path';
|
||||
import { EventEmitterCollection } from '../common/eventEmitter';
|
||||
|
||||
export interface CallbackEventArgs {
|
||||
data?: any;
|
||||
error?: (reason?: any) => void;
|
||||
}
|
||||
|
||||
|
||||
export interface CallbackEventArgs {
|
||||
data?: any;
|
||||
error?: (reason?: any) => void;
|
||||
}
|
||||
|
||||
export const CallEventNamePostfix = 'Callback';
|
||||
export const LocalPathsEventName = 'localPaths';
|
||||
|
||||
/**
|
||||
* Base class for views
|
||||
*/
|
||||
export abstract class ViewBase extends EventEmitterCollection {
|
||||
protected _mainViewPanel: azdata.window.Dialog | azdata.window.Wizard | undefined;
|
||||
public viewPanel: azdata.window.ModelViewPanel | undefined;
|
||||
public connection: azdata.connection.ConnectionProfile | undefined;
|
||||
public connectionUrl: string = '';
|
||||
|
||||
public componentMaxLength = 350;
|
||||
public buttonMaxLength = 150;
|
||||
public browseButtonMaxLength = 20;
|
||||
public spaceBetweenComponentsLength = 10;
|
||||
|
||||
constructor(protected _apiWrapper: ApiWrapper, protected _root?: string, protected _parent?: ViewBase) {
|
||||
super();
|
||||
if (this._parent) {
|
||||
if (!this._root) {
|
||||
this._root = this._parent.root;
|
||||
}
|
||||
this.connection = this._parent.connection;
|
||||
this.connectionUrl = this._parent.connectionUrl;
|
||||
}
|
||||
this.registerEvents();
|
||||
}
|
||||
|
||||
protected getEventNames(): string[] {
|
||||
return [LocalPathsEventName];
|
||||
}
|
||||
|
||||
protected getCallbackEventNames(): string[] {
|
||||
return this.getEventNames().map(eventName => {
|
||||
return ViewBase.getCallbackEventName(eventName);
|
||||
});
|
||||
}
|
||||
|
||||
public static getCallbackEventName(eventName: string) {
|
||||
return `${eventName}${CallEventNamePostfix}`;
|
||||
}
|
||||
|
||||
protected registerEvents() {
|
||||
if (this._parent) {
|
||||
const events = this.getEventNames();
|
||||
if (events) {
|
||||
events.forEach(eventName => {
|
||||
this.on(eventName, (arg) => {
|
||||
this._parent?.sendRequest(eventName, arg);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
const callbackEvents = this.getCallbackEventNames();
|
||||
if (callbackEvents) {
|
||||
callbackEvents.forEach(eventName => {
|
||||
this._parent?.on(eventName, (arg) => {
|
||||
this.sendRequest(eventName, arg);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sendRequest(requestType: string, arg?: any) {
|
||||
this.fire(requestType, arg);
|
||||
}
|
||||
|
||||
public sendCallbackRequest(requestType: string, arg: CallbackEventArgs) {
|
||||
this.fire(requestType, arg);
|
||||
}
|
||||
|
||||
public sendDataRequest<T>(
|
||||
eventName: string,
|
||||
arg?: any,
|
||||
callbackEventName?: string): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
if (!callbackEventName) {
|
||||
callbackEventName = ViewBase.getCallbackEventName(eventName);
|
||||
}
|
||||
this.on(callbackEventName, result => {
|
||||
let callbackArgs = <CallbackEventArgs>result;
|
||||
if (callbackArgs) {
|
||||
if (callbackArgs.error) {
|
||||
reject(callbackArgs.error);
|
||||
} else {
|
||||
resolve(<T>callbackArgs.data);
|
||||
}
|
||||
} else {
|
||||
reject(constants.notSupportedEventArg);
|
||||
}
|
||||
});
|
||||
this.fire(eventName, arg);
|
||||
});
|
||||
}
|
||||
|
||||
public async getLocalPaths(options: vscode.OpenDialogOptions): Promise<string[]> {
|
||||
return await this.sendDataRequest(LocalPathsEventName, options);
|
||||
}
|
||||
|
||||
public async getLocationTitle(): Promise<string> {
|
||||
let connection = await this.getCurrentConnection();
|
||||
if (connection) {
|
||||
return `${connection.serverName} ${connection.databaseName ? connection.databaseName : ''}`;
|
||||
}
|
||||
return constants.noConnectionError;
|
||||
}
|
||||
|
||||
public getServerTitle(): string {
|
||||
if (this.connection) {
|
||||
return this.connection.serverName;
|
||||
}
|
||||
return constants.noConnectionError;
|
||||
}
|
||||
|
||||
private async getCurrentConnectionUrl(): Promise<string> {
|
||||
let connection = await this.getCurrentConnection();
|
||||
if (connection) {
|
||||
return await this._apiWrapper.getUriForConnection(connection.connectionId);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
private async getCurrentConnection(): Promise<azdata.connection.ConnectionProfile> {
|
||||
return await this._apiWrapper.getCurrentConnection();
|
||||
}
|
||||
|
||||
public async loadConnection(): Promise<void> {
|
||||
this.connection = await this.getCurrentConnection();
|
||||
this.connectionUrl = await this.getCurrentConnectionUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dialog model instance
|
||||
*/
|
||||
public get mainViewPanel(): azdata.window.Dialog | azdata.window.Wizard | undefined {
|
||||
return this._mainViewPanel || this._parent?.mainViewPanel;
|
||||
}
|
||||
|
||||
public set mainViewPanel(value: azdata.window.Dialog | azdata.window.Wizard | undefined) {
|
||||
this._mainViewPanel = value;
|
||||
}
|
||||
|
||||
public showInfoMessage(message: string): void {
|
||||
this.showMessage(message, azdata.window.MessageLevel.Information);
|
||||
}
|
||||
|
||||
public showErrorMessage(message: string, error?: any): void {
|
||||
this.showMessage(`${message} ${error ? constants.getErrorMessage(error) : ''}`, azdata.window.MessageLevel.Error);
|
||||
}
|
||||
|
||||
private showMessage(message: string, level: azdata.window.MessageLevel): void {
|
||||
if (this.mainViewPanel) {
|
||||
this.mainViewPanel.message = {
|
||||
text: message,
|
||||
level: level
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public get root(): string {
|
||||
return this._root || '';
|
||||
}
|
||||
|
||||
public asAbsolutePath(filePath: string): string {
|
||||
return path.join(this._root || '', filePath);
|
||||
}
|
||||
|
||||
public abstract refresh(): Promise<void>;
|
||||
}
|
||||
580
extensions/machine-learning/src/views/widgets/dashboardWidget.ts
Normal file
580
extensions/machine-learning/src/views/widgets/dashboardWidget.ts
Normal file
@@ -0,0 +1,580 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as path from 'path';
|
||||
import * as constants from '../../common/constants';
|
||||
import * as utils from '../../common/utils';
|
||||
|
||||
interface IActionMetadata {
|
||||
title?: string,
|
||||
description?: string,
|
||||
link?: string,
|
||||
iconPath?: { light: string | vscode.Uri; dark: string | vscode.Uri },
|
||||
command?: string;
|
||||
}
|
||||
|
||||
const maxWidth = 800;
|
||||
const headerMaxHeight = 300;
|
||||
export class DashboardWidget {
|
||||
|
||||
/**
|
||||
* Creates new instance of dashboard
|
||||
*/
|
||||
constructor(private _apiWrapper: ApiWrapper, private _root: string) {
|
||||
}
|
||||
|
||||
public register(): void {
|
||||
this._apiWrapper.registerWidget('mls.dashboard', async (view) => {
|
||||
const container = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}).component();
|
||||
const header = this.createHeader(view);
|
||||
const tasksContainer = this.createTasks(view);
|
||||
const footerContainer = this.createFooter(view);
|
||||
container.addItem(header, {
|
||||
CSSStyles: {
|
||||
'background-image': `url(${vscode.Uri.file(this.asAbsolutePath('images/background.svg'))})`,
|
||||
'background-repeat': 'no-repeat',
|
||||
'background-position': 'bottom',
|
||||
'width': `${maxWidth}px`,
|
||||
'height': '330px',
|
||||
'background-size': `${maxWidth}px ${headerMaxHeight}px`,
|
||||
'margin-bottom': '-60px'
|
||||
}
|
||||
});
|
||||
container.addItem(tasksContainer, {
|
||||
CSSStyles: {
|
||||
'width': `${maxWidth}px`,
|
||||
'height': '150px',
|
||||
}
|
||||
});
|
||||
container.addItem(footerContainer, {
|
||||
CSSStyles: {
|
||||
'width': `${maxWidth}px`,
|
||||
'height': '500px',
|
||||
}
|
||||
});
|
||||
const mainContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'column',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute'
|
||||
}).component();
|
||||
mainContainer.addItem(container, {
|
||||
CSSStyles: { 'padding-top': '25px', 'padding-left': '5px' }
|
||||
});
|
||||
await view.initializeModel(mainContainer);
|
||||
});
|
||||
}
|
||||
|
||||
private createHeader(view: azdata.ModelView): azdata.Component {
|
||||
const header = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: maxWidth,
|
||||
height: headerMaxHeight
|
||||
}).component();
|
||||
const titleComponent = view.modelBuilder.text().withProperties({
|
||||
value: constants.dashboardTitle,
|
||||
CSSStyles: {
|
||||
'font-size': '36px',
|
||||
'font-weight': 'bold',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
const descComponent = view.modelBuilder.text().withProperties({
|
||||
value: constants.dashboardDesc,
|
||||
CSSStyles: {
|
||||
'font-size': '14px',
|
||||
'font-weight': 'bold',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
header.addItems([titleComponent, descComponent], {
|
||||
CSSStyles: {
|
||||
'width': `${maxWidth}px`,
|
||||
'padding': '5px'
|
||||
}
|
||||
});
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
private createFooter(view: azdata.ModelView): azdata.Component {
|
||||
const footerContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
width: maxWidth,
|
||||
height: '500px',
|
||||
justifyContent: 'flex-start'
|
||||
}).component();
|
||||
const linksContainer = this.createLinks(view);
|
||||
const videoLinksContainer = this.createVideoLinks(view);
|
||||
footerContainer.addItem(linksContainer);
|
||||
footerContainer.addItem(videoLinksContainer, {
|
||||
CSSStyles: {
|
||||
'padding-left': '45px',
|
||||
}
|
||||
});
|
||||
|
||||
return footerContainer;
|
||||
}
|
||||
|
||||
private createVideoLinkContainers(view: azdata.ModelView, links: IActionMetadata[]): azdata.Component {
|
||||
const maxWidth = 400;
|
||||
const videosContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
width: maxWidth,
|
||||
height: '300px',
|
||||
}).component();
|
||||
|
||||
links.forEach(link => {
|
||||
const videoContainer = this.createVideoLink(view, link);
|
||||
|
||||
videosContainer.addItem(videoContainer);
|
||||
});
|
||||
|
||||
return videosContainer;
|
||||
}
|
||||
|
||||
private createVideoLinks(view: azdata.ModelView): azdata.Component {
|
||||
const maxWidth = 400;
|
||||
const linksContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: maxWidth,
|
||||
height: '500px',
|
||||
justifyContent: 'flex-start'
|
||||
}).component();
|
||||
const titleComponent = view.modelBuilder.text().withProperties({
|
||||
value: constants.dashboardVideoLinksTitle,
|
||||
CSSStyles: {
|
||||
'font-size': '18px',
|
||||
'font-weight': 'bold',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
const viewPanelStyle = {
|
||||
'padding': '0px',
|
||||
'padding-right': '5px',
|
||||
'padding-top': '20px',
|
||||
'height': '200px',
|
||||
'margin': '0px'
|
||||
};
|
||||
|
||||
linksContainer.addItems([titleComponent], {
|
||||
CSSStyles: {
|
||||
'padding': '0px',
|
||||
'padding-right': '5px',
|
||||
'padding-top': '10px',
|
||||
'height': '10px',
|
||||
'margin': '0px'
|
||||
}
|
||||
});
|
||||
const videosContainer = this.createVideoLinkContainers(view, [
|
||||
{
|
||||
iconPath: { light: 'images/video1.svg', dark: 'images/video1.svg' },
|
||||
description: 'Artificial intelligence and machine learning with SQL Server 2019',
|
||||
link: 'https://www.youtube.com/watch?v=sE99cSoFOHs'
|
||||
},
|
||||
{
|
||||
iconPath: { light: 'images/video2.svg', dark: 'images/video2.svg' },
|
||||
description: 'SQL Server Machine Learning Services',
|
||||
link: 'https://www.youtube.com/watch?v=R4GCBoxADyQ'
|
||||
}
|
||||
]);
|
||||
|
||||
linksContainer.addItem(videosContainer, {
|
||||
CSSStyles: viewPanelStyle
|
||||
});
|
||||
|
||||
const moreVideosContainer = this.createVideoLinkContainers(view, [
|
||||
{
|
||||
iconPath: { light: 'images/video2.svg', dark: 'images/video2.svg' },
|
||||
description: 'Introduction to Azure Data Studio Notebooks',
|
||||
link: 'https://www.youtube.com/watch?v=Nt4kIHQ0IOc'
|
||||
}
|
||||
]);
|
||||
|
||||
this.addShowMorePanel(view, linksContainer, moreVideosContainer, { 'padding-left': '5px' }, viewPanelStyle);
|
||||
return linksContainer;
|
||||
}
|
||||
|
||||
private addShowMorePanel(view: azdata.ModelView, parentPanel: azdata.FlexContainer, morePanel: azdata.Component, moreButtonStyle: { [key: string]: string }, morePanelStyle: { [key: string]: string }): azdata.Component {
|
||||
const maxWidth = 100;
|
||||
const linkContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
width: maxWidth + 10,
|
||||
justifyContent: 'flex-start'
|
||||
}).component();
|
||||
const showMoreComponent = view.modelBuilder.hyperlink().withProperties({
|
||||
label: constants.showMoreTitle
|
||||
}).component();
|
||||
const image = view.modelBuilder.image().withProperties({
|
||||
width: '10px',
|
||||
height: '10px',
|
||||
iconPath: {
|
||||
dark: this.asAbsolutePath('images/dark/showMore_inverse.svg'),
|
||||
light: this.asAbsolutePath('images/light/showMore.svg'),
|
||||
},
|
||||
iconHeight: '10px',
|
||||
iconWidth: '10px'
|
||||
}).component();
|
||||
showMoreComponent.onDidClick(() => {
|
||||
let showMore = showMoreComponent.label === constants.showMoreTitle;
|
||||
if (showMore) {
|
||||
showMoreComponent.label = constants.showLessTitle;
|
||||
image.iconPath = {
|
||||
dark: this.asAbsolutePath('images/dark/showLess_inverse.svg'),
|
||||
light: this.asAbsolutePath('images/light/showLess.svg'),
|
||||
};
|
||||
parentPanel.addItem(morePanel, {
|
||||
CSSStyles: morePanelStyle
|
||||
});
|
||||
} else {
|
||||
showMoreComponent.label = constants.showMoreTitle;
|
||||
parentPanel.removeItem(morePanel);
|
||||
image.iconPath = {
|
||||
dark: this.asAbsolutePath('images/dark/showMore_inverse.svg'),
|
||||
light: this.asAbsolutePath('images/light/showMore.svg'),
|
||||
};
|
||||
}
|
||||
showMore = !showMore;
|
||||
});
|
||||
linkContainer.addItem(showMoreComponent, {
|
||||
CSSStyles: Object.assign({}, moreButtonStyle, {
|
||||
'font-size': '12px',
|
||||
'margin': '0px',
|
||||
'color': '#006ab1',
|
||||
'padding-right': '5px'
|
||||
}
|
||||
)
|
||||
});
|
||||
linkContainer.addItem(image, {
|
||||
CSSStyles: {
|
||||
'padding': '0px',
|
||||
'padding-right': '5px',
|
||||
'padding-top': '5px',
|
||||
'height': '10px',
|
||||
'margin': '0px'
|
||||
}
|
||||
});
|
||||
|
||||
parentPanel.addItem(linkContainer, {
|
||||
CSSStyles: {
|
||||
'padding': '0px',
|
||||
'padding-right': '5px',
|
||||
'padding-top': '10px',
|
||||
'height': '10px',
|
||||
'margin': '0px'
|
||||
}
|
||||
});
|
||||
|
||||
return showMoreComponent;
|
||||
}
|
||||
|
||||
private createVideoLink(view: azdata.ModelView, linkMetaData: IActionMetadata): azdata.Component {
|
||||
const maxWidth = 150;
|
||||
const videosContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: maxWidth,
|
||||
height: maxWidth,
|
||||
justifyContent: 'flex-start'
|
||||
}).component();
|
||||
const video1Container = view.modelBuilder.divContainer().withProperties({
|
||||
clickable: true,
|
||||
width: maxWidth,
|
||||
height: '100px'
|
||||
}).component();
|
||||
const descriptionComponent = view.modelBuilder.text().withProperties({
|
||||
value: linkMetaData.description,
|
||||
width: maxWidth,
|
||||
height: '50px',
|
||||
CSSStyles: {
|
||||
'font-size': '12px',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
video1Container.onDidClick(async () => {
|
||||
if (linkMetaData.link) {
|
||||
await this._apiWrapper.openExternal(vscode.Uri.parse(linkMetaData.link));
|
||||
}
|
||||
});
|
||||
videosContainer.addItem(video1Container, {
|
||||
CSSStyles: {
|
||||
'background-image': `url(${vscode.Uri.file(this.asAbsolutePath(<string>linkMetaData.iconPath?.light || ''))})`,
|
||||
'background-repeat': 'no-repeat',
|
||||
'background-position': 'top',
|
||||
'width': `${maxWidth}px`,
|
||||
'height': '110px',
|
||||
'background-size': `{maxWidth}px 120px`
|
||||
}
|
||||
});
|
||||
videosContainer.addItem(descriptionComponent);
|
||||
return videosContainer;
|
||||
}
|
||||
|
||||
private createLinks(view: azdata.ModelView): azdata.Component {
|
||||
const maxWidth = 400;
|
||||
const linksContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: maxWidth,
|
||||
height: '500px',
|
||||
}).component();
|
||||
const titleComponent = view.modelBuilder.text().withProperties({
|
||||
value: constants.dashboardLinksTitle,
|
||||
CSSStyles: {
|
||||
'font-size': '18px',
|
||||
'font-weight': 'bold',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
|
||||
const links = [{
|
||||
title: constants.sqlMlDocTitle,
|
||||
description: constants.sqlMlDocDesc,
|
||||
link: constants.mlDocLink
|
||||
}, {
|
||||
title: constants.sqlMlsDocTitle,
|
||||
description: constants.sqlMlsDocDesc,
|
||||
link: constants.mlsDocLink
|
||||
},
|
||||
{
|
||||
title: constants.sqlMlsAzureDocTitle,
|
||||
description: constants.sqlMlsAzureDocDesc,
|
||||
link: constants.mlsAzureDocLink
|
||||
}];
|
||||
|
||||
const moreLink = {
|
||||
title: constants.mlsInstallOdbcDocTitle,
|
||||
description: constants.mlsInstallOdbcDocDesc,
|
||||
link: utils.isWindows() ? constants.odbcDriverWindowsDocuments : constants.odbcDriverLinuxDocuments
|
||||
};
|
||||
const styles = {
|
||||
'padding': '10px'
|
||||
};
|
||||
|
||||
linksContainer.addItem(titleComponent, {
|
||||
CSSStyles: {
|
||||
'padding': '10px'
|
||||
}
|
||||
});
|
||||
|
||||
linksContainer.addItems(links.map(l => this.createLink(view, l)), {
|
||||
CSSStyles: styles
|
||||
});
|
||||
|
||||
this.addShowMorePanel(view, linksContainer, this.createLink(view, moreLink), { 'padding-left': '10px' }, styles);
|
||||
return linksContainer;
|
||||
}
|
||||
|
||||
private createLink(view: azdata.ModelView, linkMetaData: IActionMetadata): azdata.Component {
|
||||
const maxHeight = 80;
|
||||
const maxWidth = 400;
|
||||
const labelsContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
justifyContent: 'flex-start'
|
||||
}).component();
|
||||
const descriptionComponent = view.modelBuilder.text().withProperties({
|
||||
value: linkMetaData.description,
|
||||
width: maxWidth,
|
||||
CSSStyles: {
|
||||
'font-size': '12px',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
const linkContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
width: maxWidth + 10,
|
||||
justifyContent: 'flex-start'
|
||||
}).component();
|
||||
const linkComponent = view.modelBuilder.hyperlink().withProperties({
|
||||
label: linkMetaData.title,
|
||||
url: linkMetaData.link,
|
||||
CSSStyles: {
|
||||
'font-size': '14px',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
const image = view.modelBuilder.image().withProperties({
|
||||
width: '10px',
|
||||
height: '10px',
|
||||
iconPath: {
|
||||
dark: this.asAbsolutePath('images/linkIcon.svg'),
|
||||
light: this.asAbsolutePath('images/linkIcon.svg'),
|
||||
},
|
||||
iconHeight: '10px',
|
||||
iconWidth: '10px'
|
||||
}).component();
|
||||
linkContainer.addItem(linkComponent, {
|
||||
CSSStyles: {
|
||||
'padding': '0px',
|
||||
'padding-right': '5px',
|
||||
'margin': '0px',
|
||||
'color': '#006ab1'
|
||||
}
|
||||
});
|
||||
linkContainer.addItem(image, {
|
||||
CSSStyles: {
|
||||
'padding': '0px',
|
||||
'padding-right': '5px',
|
||||
'padding-top': '5px',
|
||||
'height': '10px',
|
||||
'margin': '0px'
|
||||
}
|
||||
});
|
||||
labelsContainer.addItems([linkContainer, descriptionComponent], {
|
||||
CSSStyles: {
|
||||
'padding': '0px',
|
||||
'padding-top': '5px',
|
||||
'margin': '0px'
|
||||
}
|
||||
});
|
||||
|
||||
return labelsContainer;
|
||||
}
|
||||
|
||||
private asAbsolutePath(filePath: string): string {
|
||||
return path.join(this._root || '', filePath);
|
||||
}
|
||||
|
||||
private createTasks(view: azdata.ModelView): azdata.Component {
|
||||
const tasksContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
width: '100%',
|
||||
height: '50px',
|
||||
}).component();
|
||||
const predictionMetadata: IActionMetadata = {
|
||||
title: constants.makePredictionTitle,
|
||||
description: constants.makePredictionDesc,
|
||||
iconPath: {
|
||||
dark: this.asAbsolutePath('images/makePredictions.svg'),
|
||||
light: this.asAbsolutePath('images/makePredictions.svg'),
|
||||
},
|
||||
link: '',
|
||||
command: constants.mlsPredictModelCommand
|
||||
};
|
||||
const predictionButton = this.createTaskButton(view, predictionMetadata);
|
||||
const importMetadata: IActionMetadata = {
|
||||
title: constants.importModelTitle,
|
||||
description: constants.importModelDesc,
|
||||
iconPath: {
|
||||
dark: this.asAbsolutePath('images/manageModels.svg'),
|
||||
light: this.asAbsolutePath('images/manageModels.svg'),
|
||||
},
|
||||
link: '',
|
||||
command: constants.mlManageModelsCommand
|
||||
};
|
||||
const importModelsButton = this.createTaskButton(view, importMetadata);
|
||||
const notebookMetadata: IActionMetadata = {
|
||||
title: constants.createNotebookTitle,
|
||||
description: constants.createNotebookDesc,
|
||||
iconPath: {
|
||||
dark: this.asAbsolutePath('images/createNotebook.svg'),
|
||||
light: this.asAbsolutePath('images/createNotebook.svg'),
|
||||
},
|
||||
link: '',
|
||||
command: constants.notebookCommandNew
|
||||
};
|
||||
const notebookModelsButton = this.createTaskButton(view, notebookMetadata);
|
||||
tasksContainer.addItems([predictionButton, importModelsButton, notebookModelsButton], {
|
||||
CSSStyles: {
|
||||
'padding': '10px'
|
||||
}
|
||||
});
|
||||
|
||||
return tasksContainer;
|
||||
}
|
||||
|
||||
private createTaskButton(view: azdata.ModelView, taskMetaData: IActionMetadata): azdata.Component {
|
||||
const maxHeight = 106;
|
||||
const maxWidth = 250;
|
||||
const mainContainer = view.modelBuilder.divContainer().withLayout({
|
||||
width: maxWidth,
|
||||
height: maxHeight
|
||||
}).withProperties({
|
||||
clickable: true,
|
||||
ariaRole: taskMetaData.title
|
||||
}).component();
|
||||
const iconContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
width: maxWidth,
|
||||
height: maxHeight - 20,
|
||||
alignItems: 'flex-start'
|
||||
}).component();
|
||||
const labelsContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: maxWidth - 50,
|
||||
height: maxHeight - 20,
|
||||
justifyContent: 'space-between'
|
||||
}).component();
|
||||
const titleComponent = view.modelBuilder.text().withProperties({
|
||||
value: taskMetaData.title,
|
||||
CSSStyles: {
|
||||
'font-size': '14px',
|
||||
'font-weight': 'bold',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
const descriptionComponent = view.modelBuilder.text().withProperties({
|
||||
value: taskMetaData.description,
|
||||
CSSStyles: {
|
||||
'font-size': '13px',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
const linkComponent = view.modelBuilder.hyperlink().withProperties({
|
||||
label: constants.learnMoreTitle,
|
||||
url: taskMetaData.link
|
||||
}).component();
|
||||
const image = view.modelBuilder.image().withProperties({
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
iconPath: taskMetaData.iconPath,
|
||||
iconHeight: '20px',
|
||||
iconWidth: '20px'
|
||||
}).component();
|
||||
labelsContainer.addItems([titleComponent, descriptionComponent, linkComponent], {
|
||||
CSSStyles: {
|
||||
'padding': '0px',
|
||||
'padding-bottom': '5px',
|
||||
'width': '180px',
|
||||
'margin': '0px',
|
||||
'color': '#006ab1'
|
||||
}
|
||||
});
|
||||
iconContainer.addItem(image, {
|
||||
CSSStyles: {
|
||||
'padding-top': '10px',
|
||||
'padding-right': '10px'
|
||||
}
|
||||
});
|
||||
iconContainer.addItem(labelsContainer, {
|
||||
CSSStyles: {
|
||||
'padding-top': '5px',
|
||||
'padding-right': '10px'
|
||||
}
|
||||
});
|
||||
mainContainer.addItems([iconContainer], {
|
||||
CSSStyles: {
|
||||
'padding': '10px',
|
||||
'border-radius': '5px',
|
||||
'border-color': '#f2f2f2',
|
||||
'border': '1px solid'
|
||||
}
|
||||
});
|
||||
mainContainer.onDidClick(async () => {
|
||||
if (taskMetaData.command) {
|
||||
await this._apiWrapper.executeCommand(taskMetaData.command);
|
||||
}
|
||||
});
|
||||
return mainContainer;
|
||||
}
|
||||
}
|
||||
123
extensions/machine-learning/src/views/wizardView.ts
Normal file
123
extensions/machine-learning/src/views/wizardView.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ApiWrapper } from '../common/apiWrapper';
|
||||
import { MainViewBase } from './mainViewBase';
|
||||
import { IPageView } from './interfaces';
|
||||
|
||||
/**
|
||||
* Wizard view to creates wizard and pages
|
||||
*/
|
||||
export class WizardView extends MainViewBase {
|
||||
|
||||
private _wizard: azdata.window.Wizard | undefined;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor(apiWrapper: ApiWrapper) {
|
||||
super(apiWrapper);
|
||||
}
|
||||
|
||||
private createWizardPage(title: string, componentView: IPageView): azdata.window.WizardPage {
|
||||
let viewPanel = this._apiWrapper.createWizardPage(title);
|
||||
this.registerContent(viewPanel, componentView);
|
||||
componentView.viewPanel = viewPanel;
|
||||
return viewPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds wizard page
|
||||
* @param page page
|
||||
* @param index page index
|
||||
*/
|
||||
public addWizardPage(page: IPageView, index: number): void {
|
||||
if (this._wizard) {
|
||||
const currentPage = this._wizard.currentPage;
|
||||
if (page && currentPage < index) {
|
||||
this.addPage(page, index);
|
||||
this._wizard.removePage(index);
|
||||
this.createWizardPage(page.title || '', page);
|
||||
this._wizard.addPage(<azdata.window.WizardPage>page.viewPanel, index);
|
||||
this._wizard.setCurrentPage(currentPage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds wizard page
|
||||
* @param page page
|
||||
* @param index page index
|
||||
*/
|
||||
public removeWizardPage(page: IPageView, index: number): void {
|
||||
if (this._wizard && this._pages[index] === page) {
|
||||
this._pages = this._pages.splice(index);
|
||||
this._wizard.removePage(index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param title Creates anew wizard
|
||||
* @param pages wizard pages
|
||||
*/
|
||||
public createWizard(title: string, pages: IPageView[]): azdata.window.Wizard {
|
||||
this._wizard = this._apiWrapper.createWizard(title);
|
||||
this._pages = pages;
|
||||
this._wizard.pages = pages.map(x => this.createWizardPage(x.title || '', x));
|
||||
this._wizard.onPageChanged(async (info) => {
|
||||
await this.onWizardPageChanged(info);
|
||||
});
|
||||
|
||||
return this._wizard;
|
||||
}
|
||||
|
||||
public async validate(pageInfo: azdata.window.WizardPageChangeInfo): Promise<boolean> {
|
||||
if (pageInfo?.lastPage !== undefined) {
|
||||
let idxLast = pageInfo.lastPage;
|
||||
let lastPage = this._pages[idxLast];
|
||||
if (lastPage && lastPage.validate) {
|
||||
return await lastPage.validate();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private async onWizardPageChanged(pageInfo: azdata.window.WizardPageChangeInfo) {
|
||||
if (pageInfo?.lastPage !== undefined) {
|
||||
let idxLast = pageInfo.lastPage;
|
||||
let lastPage = this._pages[idxLast];
|
||||
if (lastPage && lastPage.onLeave) {
|
||||
await lastPage.onLeave();
|
||||
}
|
||||
}
|
||||
|
||||
if (pageInfo?.newPage !== undefined) {
|
||||
let idx = pageInfo.newPage;
|
||||
let page = this._pages[idx];
|
||||
if (page && page.onEnter) {
|
||||
if (this._wizard && this._wizard.pages.length > idx) {
|
||||
this._wizard.pages[idx].title = page.title;
|
||||
}
|
||||
await page.onEnter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public get wizard(): azdata.window.Wizard | undefined {
|
||||
return this._wizard;
|
||||
}
|
||||
|
||||
public async refresh(): Promise<void> {
|
||||
for (let index = 0; index < this._pages.length; index++) {
|
||||
const page = this._pages[index];
|
||||
if (this._wizard?.pages[index]?.title !== page.title) {
|
||||
this.addWizardPage(page, index);
|
||||
}
|
||||
}
|
||||
await super.refresh();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user