mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Machine Learning Model Registry - Iteration1 (#9105)
* Machine learning services extension - model registration wizard
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ViewBase, LocalFileEventName, LocalFolderEventName } 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(LocalFileEventName, async () => {
|
||||
await this.executeAction(view, LocalFileEventName, this.getLocalFilePath, this._apiWrapper);
|
||||
});
|
||||
view.on(LocalFolderEventName, async () => {
|
||||
await this.executeAction(view, LocalFolderEventName, this.getLocalFolderPath, this._apiWrapper);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns local file path picked by the user
|
||||
* @param apiWrapper apiWrapper
|
||||
*/
|
||||
public async getLocalFilePath(apiWrapper: ApiWrapper): Promise<string> {
|
||||
let result = await apiWrapper.showOpenDialog({
|
||||
canSelectFiles: true,
|
||||
canSelectFolders: false,
|
||||
canSelectMany: false
|
||||
});
|
||||
return result && result.length > 0 ? result[0].fsPath : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns local folder path picked by the user
|
||||
* @param apiWrapper apiWrapper
|
||||
*/
|
||||
public async getLocalFolderPath(apiWrapper: ApiWrapper): Promise<string> {
|
||||
let result = await apiWrapper.showOpenDialog({
|
||||
canSelectFiles: false,
|
||||
canSelectFolders: true,
|
||||
canSelectMany: false
|
||||
});
|
||||
return result && result.length > 0 ? result[0].fsPath : '';
|
||||
}
|
||||
}
|
||||
41
extensions/machine-learning-services/src/views/dialogView.ts
Normal file
41
extensions/machine-learning-services/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;
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ export class CurrentLanguagesTab extends LanguageViewBase {
|
||||
|
||||
// TODO: only supporting single location for now. We should add a drop down for multi locations mode
|
||||
//
|
||||
let locationTitle = await this.getLocationTitle();
|
||||
let locationTitle = await this.getServerTitle();
|
||||
this._locationComponent = view.modelBuilder.text().withProperties({
|
||||
value: locationTitle
|
||||
}).component();
|
||||
|
||||
@@ -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/src/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);
|
||||
}
|
||||
}
|
||||
34
extensions/machine-learning-services/src/views/interfaces.ts
Normal file
34
extensions/machine-learning-services/src/views/interfaces.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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>;
|
||||
refresh: () => 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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 refresh(): Promise<void> {
|
||||
if (this._pages) {
|
||||
await Promise.all(this._pages.map(p => p.refresh()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as constants from '../../common/constants';
|
||||
import { IPageView, IDataComponent, AzureModelResource } from '../interfaces';
|
||||
|
||||
export class AzureModelsComponent extends ModelViewBase implements IPageView, IDataComponent<AzureModelResource> {
|
||||
|
||||
public azureModelsTable: AzureModelsTable | undefined;
|
||||
public azureFilterComponent: AzureResourceFilterComponent | undefined;
|
||||
|
||||
private _loader: azdata.LoadingComponent | undefined;
|
||||
private _form: azdata.FormContainer | undefined;
|
||||
|
||||
/**
|
||||
* Component to render a view to pick an azure model
|
||||
*/
|
||||
constructor(apiWrapper: ApiWrapper, parent: ModelViewBase) {
|
||||
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._loader = modelBuilder.loadingComponent()
|
||||
.withItem(this.azureModelsTable.component)
|
||||
.withProperties({
|
||||
loading: true
|
||||
}).component();
|
||||
|
||||
this.azureFilterComponent.onWorkspacesSelected(async () => {
|
||||
await this.onLoading();
|
||||
await this.azureModelsTable?.loadData(this.azureFilterComponent?.data);
|
||||
await this.onLoaded();
|
||||
});
|
||||
|
||||
this._form = modelBuilder.formContainer().withFormItems([{
|
||||
title: constants.azureModelFilter,
|
||||
component: this.azureFilterComponent.component
|
||||
}, {
|
||||
title: constants.azureModels,
|
||||
component: this._loader
|
||||
}]).component();
|
||||
return this._form;
|
||||
}
|
||||
|
||||
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 Object.assign({}, this.azureFilterComponent?.data, {
|
||||
model: this.azureModelsTable?.data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the view
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
await this.loadData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the title of the page
|
||||
*/
|
||||
public get title(): string {
|
||||
return constants.azureModelsTitle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { 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 _selectedModelId: any;
|
||||
private _models: WorkspaceModel[] | undefined;
|
||||
|
||||
/**
|
||||
* Creates a view to render azure models in a table
|
||||
*/
|
||||
constructor(apiWrapper: ApiWrapper, private _modelBuilder: azdata.ModelBuilder, parent: ModelViewBase) {
|
||||
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: [
|
||||
{ // Id
|
||||
displayName: constants.modeIld,
|
||||
ariaLabel: constants.modeIld,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: 100,
|
||||
headerCssStyles: {
|
||||
...constants.cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
...constants.cssStyles.tableRow
|
||||
},
|
||||
},
|
||||
{ // Name
|
||||
displayName: constants.modelName,
|
||||
ariaLabel: constants.modelName,
|
||||
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
|
||||
},
|
||||
}
|
||||
],
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private createTableRow(model: WorkspaceModel): any[] {
|
||||
if (this._modelBuilder) {
|
||||
let selectModelButton = this._modelBuilder.radioButton().withProperties({
|
||||
name: 'amlModel',
|
||||
value: model.id,
|
||||
width: 15,
|
||||
height: 15,
|
||||
checked: false
|
||||
}).component();
|
||||
selectModelButton.onDidClick(() => {
|
||||
this._selectedModelId = model.id;
|
||||
});
|
||||
return [model.id, model.name, selectModelButton];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected data
|
||||
*/
|
||||
public get data(): WorkspaceModel | undefined {
|
||||
if (this._models && this._selectedModelId) {
|
||||
return this._models.find(x => x.id === this._selectedModelId);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the view
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
await this.loadData();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 = 200;
|
||||
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 _onWorkspacesSelected: vscode.EventEmitter<void> = new vscode.EventEmitter<void>();
|
||||
public readonly onWorkspacesSelected: vscode.Event<void> = this._onWorkspacesSelected.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.onWorkspaceSelected();
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.onWorkspaceSelected();
|
||||
}
|
||||
|
||||
private onWorkspaceSelected(): void {
|
||||
this._onWorkspacesSelected.fire();
|
||||
}
|
||||
|
||||
private get workspace(): Workspace | undefined {
|
||||
return this._azureWorkspaces ? this._azureWorkspaces.find(a => a.id === (<azdata.CategoryValue>this._workspaces.value).name) : undefined;
|
||||
}
|
||||
|
||||
private get account(): azdata.Account | undefined {
|
||||
return this._azureAccounts ? this._azureAccounts.find(a => a.key.accountId === (<azdata.CategoryValue>this._accounts.value).name) : undefined;
|
||||
}
|
||||
|
||||
private get group(): azureResource.AzureResource | undefined {
|
||||
return this._azureGroups ? this._azureGroups.find(a => a.id === (<azdata.CategoryValue>this._groups.value).name) : undefined;
|
||||
}
|
||||
|
||||
private get subscription(): azureResource.AzureResourceSubscription | undefined {
|
||||
return this._azureSubscriptions ? this._azureSubscriptions.find(a => a.id === (<azdata.CategoryValue>this._subscriptions.value).name) : undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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, RegisterModelEventName } from './modelViewBase';
|
||||
import { CurrentModelsTable } from './currentModelsTable';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
import { IPageView } from '../interfaces';
|
||||
|
||||
/**
|
||||
* View to render current registered models
|
||||
*/
|
||||
export class CurrentModelsPage extends ModelViewBase implements IPageView {
|
||||
private _tableComponent: azdata.DeclarativeTableComponent | undefined;
|
||||
private _dataTable: CurrentModelsTable | undefined;
|
||||
private _loader: azdata.LoadingComponent | undefined;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param apiWrapper Creates new view
|
||||
* @param parent page parent
|
||||
*/
|
||||
constructor(apiWrapper: ApiWrapper, parent: ModelViewBase) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param modelBuilder register the components
|
||||
*/
|
||||
public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component {
|
||||
this._dataTable = new CurrentModelsTable(this._apiWrapper, modelBuilder, this);
|
||||
this._tableComponent = this._dataTable.component;
|
||||
|
||||
let registerButton = modelBuilder.button().withProperties({
|
||||
label: constants.registerModelButton,
|
||||
width: this.buttonMaxLength
|
||||
}).component();
|
||||
registerButton.onDidClick(async () => {
|
||||
await this.sendDataRequest(RegisterModelEventName);
|
||||
});
|
||||
|
||||
let formModel = modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
title: '',
|
||||
component: registerButton
|
||||
}, {
|
||||
component: this._tableComponent,
|
||||
title: ''
|
||||
}]).component();
|
||||
|
||||
this._loader = modelBuilder.loadingComponent()
|
||||
.withItem(formModel)
|
||||
.withProperties({
|
||||
loading: true
|
||||
}).component();
|
||||
return this._loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the component
|
||||
*/
|
||||
public get component(): azdata.Component | undefined {
|
||||
return this._loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the view
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
await this.onLoading();
|
||||
|
||||
try {
|
||||
await this._dataTable?.refresh();
|
||||
} catch (err) {
|
||||
this.showErrorMessage(constants.getErrorMessage(err));
|
||||
} finally {
|
||||
await this.onLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,131 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { RegisteredModel } from '../../modelManagement/interfaces';
|
||||
|
||||
/**
|
||||
* View to render registered models table
|
||||
*/
|
||||
export class CurrentModelsTable extends ModelViewBase {
|
||||
|
||||
private _table: azdata.DeclarativeTableComponent;
|
||||
|
||||
/**
|
||||
* Creates new view
|
||||
*/
|
||||
constructor(apiWrapper: ApiWrapper, private _modelBuilder: azdata.ModelBuilder, parent: ModelViewBase) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
this._table = this.registerComponent(this._modelBuilder);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param modelBuilder register the components
|
||||
*/
|
||||
public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.DeclarativeTableComponent {
|
||||
this._table = modelBuilder.declarativeTable()
|
||||
.withProperties<azdata.DeclarativeTableProperties>(
|
||||
{
|
||||
columns: [
|
||||
{ // Id
|
||||
displayName: constants.modeIld,
|
||||
ariaLabel: constants.modeIld,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: 100,
|
||||
headerCssStyles: {
|
||||
...constants.cssStyles.tableHeader
|
||||
},
|
||||
rowCssStyles: {
|
||||
...constants.cssStyles.tableRow
|
||||
},
|
||||
},
|
||||
{ // Name
|
||||
displayName: constants.modelName,
|
||||
ariaLabel: constants.modelName,
|
||||
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
|
||||
},
|
||||
}
|
||||
],
|
||||
data: [],
|
||||
ariaLabel: constants.mlsConfigTitle
|
||||
})
|
||||
.component();
|
||||
return this._table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the component
|
||||
*/
|
||||
public get component(): azdata.DeclarativeTableComponent {
|
||||
return this._table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the data in the component
|
||||
*/
|
||||
public async loadData(): Promise<void> {
|
||||
let models: RegisteredModel[] | undefined;
|
||||
|
||||
models = await this.listModels();
|
||||
let tableData: any[][] = [];
|
||||
|
||||
if (models) {
|
||||
tableData = tableData.concat(models.map(model => this.createTableRow(model)));
|
||||
}
|
||||
|
||||
this._table.data = tableData;
|
||||
}
|
||||
|
||||
private createTableRow(model: RegisteredModel): any[] {
|
||||
if (this._modelBuilder) {
|
||||
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(() => {
|
||||
});
|
||||
return [model.id, model.name, editLanguageButton];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the view
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
await this.loadData();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* View to pick local models file
|
||||
*/
|
||||
export class LocalModelsComponent extends ModelViewBase implements IPageView, IDataComponent<string> {
|
||||
|
||||
private _form: azdata.FormContainer | undefined;
|
||||
private _localPath: azdata.InputBoxComponent | undefined;
|
||||
private _localBrowse: azdata.ButtonComponent | undefined;
|
||||
|
||||
/**
|
||||
* Creates new view
|
||||
*/
|
||||
constructor(apiWrapper: ApiWrapper, parent: ModelViewBase) {
|
||||
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 () => {
|
||||
const filePath = await this.getLocalFilePath();
|
||||
if (this._localPath) {
|
||||
this._localPath.value = filePath;
|
||||
}
|
||||
});
|
||||
|
||||
let flexFilePathModel = modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
justifyContent: 'space-between'
|
||||
}).withItems([
|
||||
this._localPath, this._localBrowse]
|
||||
).component();
|
||||
|
||||
this._form = modelBuilder.formContainer().withFormItems([{
|
||||
title: '',
|
||||
component: flexFilePathModel
|
||||
}]).component();
|
||||
return this._form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected data
|
||||
*/
|
||||
public get data(): string {
|
||||
return this._localPath?.value || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,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 { azureResource } from '../../typings/azure-resource';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
import { AzureModelRegistryService } from '../../modelManagement/azureModelRegistryService';
|
||||
import { Workspace } from '@azure/arm-machinelearningservices/esm/models';
|
||||
import { RegisteredModel, WorkspaceModel } from '../../modelManagement/interfaces';
|
||||
import { RegisteredModelService } from '../../modelManagement/registeredModelService';
|
||||
import { RegisteredModelsDialog } from './registeredModelsDialog';
|
||||
import { AzureResourceEventArgs, ListAzureModelsEventName, ListSubscriptionsEventName, ListModelsEventName, ListWorkspacesEventName, ListGroupsEventName, ListAccountsEventName, RegisterLocalModelEventName, RegisterLocalModelEventArgs, RegisterAzureModelEventName, RegisterAzureModelEventArgs, ModelViewBase, SourceModelSelectedEventName, RegisterModelEventName } from './modelViewBase';
|
||||
import { ControllerBase } from '../controllerBase';
|
||||
import { RegisterModelWizard } from './registerModelWizard';
|
||||
import * as fs from 'fs';
|
||||
import * as constants from '../../common/constants';
|
||||
|
||||
/**
|
||||
* Model management UI controller
|
||||
*/
|
||||
export class ModelManagementController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* Creates new instance
|
||||
*/
|
||||
constructor(
|
||||
apiWrapper: ApiWrapper,
|
||||
private _root: string,
|
||||
private _amlService: AzureModelRegistryService,
|
||||
private _registeredModelService: RegisteredModelService) {
|
||||
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(parent?: ModelViewBase, controller?: ModelManagementController, apiWrapper?: ApiWrapper, root?: string): Promise<ModelViewBase> {
|
||||
controller = controller || this;
|
||||
apiWrapper = apiWrapper || this._apiWrapper;
|
||||
root = root || this._root;
|
||||
let view = new RegisterModelWizard(apiWrapper, root, parent);
|
||||
|
||||
controller.registerEvents(view);
|
||||
|
||||
// Open view
|
||||
//
|
||||
view.open();
|
||||
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 () => {
|
||||
await this.executeAction(view, ListModelsEventName, this.getRegisteredModels, this._registeredModelService);
|
||||
});
|
||||
view.on(RegisterLocalModelEventName, async (arg) => {
|
||||
let registerArgs = <RegisterLocalModelEventArgs>arg;
|
||||
await this.executeAction(view, RegisterLocalModelEventName, this.registerLocalModel, this._registeredModelService, registerArgs.filePath);
|
||||
view.refresh();
|
||||
});
|
||||
view.on(RegisterModelEventName, async () => {
|
||||
await this.executeAction(view, RegisterModelEventName, this.registerModel, view, this, this._apiWrapper, this._root);
|
||||
});
|
||||
view.on(RegisterAzureModelEventName, async (arg) => {
|
||||
let registerArgs = <RegisterAzureModelEventArgs>arg;
|
||||
await this.executeAction(view, RegisterAzureModelEventName, this.registerAzureModel, this._amlService, this._registeredModelService,
|
||||
registerArgs.account, registerArgs.subscription, registerArgs.group, registerArgs.workspace, registerArgs.model);
|
||||
});
|
||||
view.on(SourceModelSelectedEventName, () => {
|
||||
view.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the dialog for model management
|
||||
*/
|
||||
public async manageRegisteredModels(): Promise<ModelViewBase> {
|
||||
let view = new RegisteredModelsDialog(this._apiWrapper, this._root);
|
||||
|
||||
// Register events
|
||||
//
|
||||
this.registerEvents(view);
|
||||
|
||||
// Open view
|
||||
//
|
||||
view.open();
|
||||
return view;
|
||||
}
|
||||
|
||||
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: RegisteredModelService): Promise<RegisteredModel[]> {
|
||||
return registeredModelService.getRegisteredModels();
|
||||
}
|
||||
|
||||
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: RegisteredModelService, filePath?: string): Promise<void> {
|
||||
if (filePath) {
|
||||
await service.registerLocalModel(filePath);
|
||||
} else {
|
||||
throw Error(constants.invalidModelToRegisterError);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private async registerAzureModel(
|
||||
azureService: AzureModelRegistryService,
|
||||
service: RegisteredModelService,
|
||||
account: azdata.Account | undefined,
|
||||
subscription: azureResource.AzureResourceSubscription | undefined,
|
||||
resourceGroup: azureResource.AzureResource | undefined,
|
||||
workspace: Workspace | undefined,
|
||||
model: WorkspaceModel | undefined): Promise<void> {
|
||||
if (!account || !subscription || !resourceGroup || !workspace || !model) {
|
||||
throw Error(constants.invalidAzureResourceError);
|
||||
}
|
||||
const filePath = await azureService.downloadModel(account, subscription, resourceGroup, workspace, model);
|
||||
if (filePath) {
|
||||
await service.registerLocalModel(filePath);
|
||||
await fs.promises.unlink(filePath);
|
||||
} else {
|
||||
throw Error(constants.invalidModelToRegisterError);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 } from './modelViewBase';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
import * as constants from '../../common/constants';
|
||||
import { IPageView, IDataComponent } from '../interfaces';
|
||||
|
||||
export enum ModelSourceType {
|
||||
Local,
|
||||
Azure
|
||||
}
|
||||
/**
|
||||
* View tp pick model source
|
||||
*/
|
||||
export class ModelSourcesComponent extends ModelViewBase implements IPageView, IDataComponent<ModelSourceType> {
|
||||
|
||||
private _form: azdata.FormContainer | undefined;
|
||||
private _amlModel: azdata.RadioButtonComponent | undefined;
|
||||
private _localModel: azdata.RadioButtonComponent | undefined;
|
||||
private _isLocalModel: boolean = true;
|
||||
|
||||
constructor(apiWrapper: ApiWrapper, parent: ModelViewBase) {
|
||||
super(apiWrapper, parent.root, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param modelBuilder Register components
|
||||
*/
|
||||
public registerComponent(modelBuilder: azdata.ModelBuilder): azdata.Component {
|
||||
this._localModel = modelBuilder.radioButton()
|
||||
.withProperties({
|
||||
value: 'local',
|
||||
name: 'modelLocation',
|
||||
label: constants.localModelSource,
|
||||
checked: true
|
||||
}).component();
|
||||
|
||||
|
||||
this._amlModel = modelBuilder.radioButton()
|
||||
.withProperties({
|
||||
value: 'aml',
|
||||
name: 'modelLocation',
|
||||
label: constants.azureModelSource,
|
||||
}).component();
|
||||
|
||||
this._localModel.onDidClick(() => {
|
||||
this._isLocalModel = true;
|
||||
this.sendRequest(SourceModelSelectedEventName);
|
||||
|
||||
});
|
||||
this._amlModel.onDidClick(() => {
|
||||
this._isLocalModel = false;
|
||||
this.sendRequest(SourceModelSelectedEventName);
|
||||
});
|
||||
|
||||
let flex = modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'column',
|
||||
justifyContent: 'space-between'
|
||||
}).withItems([
|
||||
this._localModel, this._amlModel]
|
||||
).component();
|
||||
|
||||
this._form = modelBuilder.formContainer().withFormItems([{
|
||||
title: constants.modelSourcesTitle,
|
||||
component: flex
|
||||
}]).component();
|
||||
return this._form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected data
|
||||
*/
|
||||
public get data(): ModelSourceType {
|
||||
return this._isLocalModel ? ModelSourceType.Local : ModelSourceType.Azure;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.modelSourcesTitle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { RegisteredModel, WorkspaceModel } from '../../modelManagement/interfaces';
|
||||
import { Workspace } from '@azure/arm-machinelearningservices/esm/models';
|
||||
import { AzureWorkspaceResource, AzureModelResource } from '../interfaces';
|
||||
|
||||
export interface AzureResourceEventArgs extends AzureWorkspaceResource {
|
||||
}
|
||||
|
||||
export interface RegisterAzureModelEventArgs extends AzureModelResource {
|
||||
model?: WorkspaceModel;
|
||||
}
|
||||
|
||||
export interface RegisterLocalModelEventArgs extends AzureResourceEventArgs {
|
||||
filePath?: string;
|
||||
}
|
||||
|
||||
// Event names
|
||||
//
|
||||
export const ListModelsEventName = 'listModels';
|
||||
export const ListAzureModelsEventName = 'listAzureModels';
|
||||
export const ListAccountsEventName = 'listAccounts';
|
||||
export const ListSubscriptionsEventName = 'listSubscriptions';
|
||||
export const ListGroupsEventName = 'listGroups';
|
||||
export const ListWorkspacesEventName = 'listWorkspaces';
|
||||
export const RegisterLocalModelEventName = 'registerLocalModel';
|
||||
export const RegisterAzureModelEventName = 'registerAzureLocalModel';
|
||||
export const RegisterModelEventName = 'registerModel';
|
||||
export const SourceModelSelectedEventName = 'sourceModelSelected';
|
||||
|
||||
/**
|
||||
* Base class for all model management views
|
||||
*/
|
||||
export abstract class ModelViewBase extends ViewBase {
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(): Promise<RegisteredModel[]> {
|
||||
return await this.sendDataRequest(ListModelsEventName);
|
||||
}
|
||||
|
||||
/**
|
||||
* lists azure accounts
|
||||
*/
|
||||
public async listAzureAccounts(): Promise<azdata.Account[]> {
|
||||
return await this.sendDataRequest(ListAccountsEventName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 registerLocalModel(localFilePath: string | undefined): Promise<void> {
|
||||
const args: RegisterLocalModelEventArgs = {
|
||||
filePath: localFilePath
|
||||
};
|
||||
return await this.sendDataRequest(RegisterLocalModelEventName, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* registers azure model
|
||||
* @param args azure resource
|
||||
*/
|
||||
public async registerAzureModel(args: RegisterAzureModelEventArgs | undefined): Promise<void> {
|
||||
return await this.sendDataRequest(RegisterAzureModelEventName, 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,96 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ModelSourcesComponent, ModelSourceType } from './modelSourcesComponent';
|
||||
import { LocalModelsComponent } from './localModelsComponent';
|
||||
import { AzureModelsComponent } from './azureModelsComponent';
|
||||
import * as constants from '../../common/constants';
|
||||
import { WizardView } from '../wizardView';
|
||||
|
||||
/**
|
||||
* Wizard to register a model
|
||||
*/
|
||||
export class RegisterModelWizard extends ModelViewBase {
|
||||
|
||||
public modelResources: ModelSourcesComponent | undefined;
|
||||
public localModelsComponent: LocalModelsComponent | undefined;
|
||||
public azureModelsComponent: AzureModelsComponent | 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 open(): void {
|
||||
|
||||
this.modelResources = new ModelSourcesComponent(this._apiWrapper, this);
|
||||
this.localModelsComponent = new LocalModelsComponent(this._apiWrapper, this);
|
||||
this.azureModelsComponent = new AzureModelsComponent(this._apiWrapper, this);
|
||||
|
||||
this.wizardView = new WizardView(this._apiWrapper);
|
||||
|
||||
let wizard = this.wizardView.createWizard(constants.registerModelWizardTitle, [this.modelResources, this.localModelsComponent]);
|
||||
this.mainViewPanel = wizard;
|
||||
wizard.doneButton.label = constants.azureRegisterModel;
|
||||
wizard.generateScriptButton.hidden = true;
|
||||
|
||||
wizard.registerNavigationValidator(async (pageInfo: azdata.window.WizardPageChangeInfo) => {
|
||||
if (pageInfo.newPage === undefined) {
|
||||
await this.registerModel();
|
||||
if (this._parentView) {
|
||||
this._parentView?.refresh();
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
wizard.open();
|
||||
}
|
||||
|
||||
private async registerModel(): Promise<boolean> {
|
||||
try {
|
||||
if (this.modelResources && this.localModelsComponent && this.modelResources.data === ModelSourceType.Local) {
|
||||
await this.registerLocalModel(this.localModelsComponent.data);
|
||||
} else {
|
||||
await this.registerAzureModel(this.azureModelsComponent?.data);
|
||||
}
|
||||
this.showInfoMessage(constants.modelRegisteredSuccessfully);
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.showErrorMessage(`${constants.modelFailedToRegister} ${constants.getErrorMessage(error)}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private loadPages(): void {
|
||||
if (this.modelResources && this.localModelsComponent && this.modelResources.data === ModelSourceType.Local) {
|
||||
this.wizardView?.addWizardPage(this.localModelsComponent, 1);
|
||||
|
||||
} else if (this.azureModelsComponent) {
|
||||
this.wizardView?.addWizardPage(this.azureModelsComponent, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the pages
|
||||
*/
|
||||
public async refresh(): Promise<void> {
|
||||
this.loadPages();
|
||||
this.wizardView?.refresh();
|
||||
}
|
||||
}
|
||||
@@ -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 { CurrentModelsPage } from './currentModelsPage';
|
||||
|
||||
import { ModelViewBase } 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 RegisteredModelsDialog extends ModelViewBase {
|
||||
|
||||
constructor(
|
||||
apiWrapper: ApiWrapper,
|
||||
root: string) {
|
||||
super(apiWrapper, root);
|
||||
this.dialogView = new DialogView(this._apiWrapper);
|
||||
}
|
||||
public dialogView: DialogView;
|
||||
public currentLanguagesTab: CurrentModelsPage | undefined;
|
||||
|
||||
/**
|
||||
* Opens a dialog to manage packages used by notebooks.
|
||||
*/
|
||||
public open(): void {
|
||||
|
||||
this.currentLanguagesTab = new CurrentModelsPage(this._apiWrapper, this);
|
||||
|
||||
let dialog = this.dialogView.createDialog('', [this.currentLanguagesTab]);
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
198
extensions/machine-learning-services/src/views/viewBase.ts
Normal file
198
extensions/machine-learning-services/src/views/viewBase.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { 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 LocalFileEventName = 'localFile';
|
||||
export const LocalFolderEventName = 'localFolder';
|
||||
|
||||
/**
|
||||
* 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 [LocalFolderEventName, LocalFileEventName];
|
||||
}
|
||||
|
||||
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 getLocalFilePath(): Promise<string> {
|
||||
return await this.sendDataRequest(LocalFileEventName);
|
||||
}
|
||||
|
||||
public async getLocalFolderPath(): Promise<string> {
|
||||
return await this.sendDataRequest(LocalFolderEventName);
|
||||
}
|
||||
|
||||
public async getLocationTitle(): Promise<string> {
|
||||
let connection = await this.getCurrentConnection();
|
||||
if (connection) {
|
||||
return `${connection.serverName} ${connection.databaseName ? connection.databaseName : ''}`;
|
||||
}
|
||||
return constants.packageManagerNoConnection;
|
||||
}
|
||||
|
||||
public getServerTitle(): string {
|
||||
if (this.connection) {
|
||||
return this.connection.serverName;
|
||||
}
|
||||
return constants.packageManagerNoConnection;
|
||||
}
|
||||
|
||||
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} ${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>;
|
||||
}
|
||||
76
extensions/machine-learning-services/src/views/wizardView.ts
Normal file
76
extensions/machine-learning-services/src/views/wizardView.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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) {
|
||||
this.addPage(page, index);
|
||||
this._wizard.removePage(index);
|
||||
if (!page.viewPanel) {
|
||||
this.createWizardPage(page.title || '', page);
|
||||
}
|
||||
this._wizard.addPage(<azdata.window.WizardPage>page.viewPanel, index);
|
||||
this._wizard.setCurrentPage(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) => {
|
||||
this.onWizardPageChanged(info);
|
||||
});
|
||||
return this._wizard;
|
||||
}
|
||||
|
||||
private onWizardPageChanged(pageInfo: azdata.window.WizardPageChangeInfo) {
|
||||
let idxLast = pageInfo.lastPage;
|
||||
let lastPage = this._pages[idxLast];
|
||||
if (lastPage && lastPage.onLeave) {
|
||||
lastPage.onLeave();
|
||||
}
|
||||
|
||||
let idx = pageInfo.newPage;
|
||||
let page = this._pages[idx];
|
||||
if (page && page.onEnter) {
|
||||
page.onEnter();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user