mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-15 02:48:30 -05:00
Machine Learning Model Registry - Iteration1 (#9105)
* Machine learning services extension - model registration wizard
This commit is contained in:
@@ -9,5 +9,11 @@
|
||||
{ "name": "sqlmlutils", "fileName": "sqlmlutils_0.7.1.zip", "downloadUrl": "https://github.com/microsoft/sqlmlutils/blob/master/R/dist/sqlmlutils_0.7.1.zip?raw=true"}
|
||||
],
|
||||
|
||||
"rPackagesRepository": "https://cran.r-project.org"
|
||||
"rPackagesRepository": "https://cran.r-project.org",
|
||||
|
||||
"registeredModelsDatabaseName": "MlFlowDB",
|
||||
"registeredModelsTableName": "dbo.artifacts",
|
||||
"amlModelManagementUrl": "modelmanagement.azureml.net",
|
||||
"amlExperienceUrl": "experiments.azureml.net",
|
||||
"amlApiVersion": "2018-11-19"
|
||||
}
|
||||
|
||||
@@ -57,6 +57,14 @@
|
||||
"command": "mls.command.managePackages",
|
||||
"title": "%mls.command.managePackages%"
|
||||
},
|
||||
{
|
||||
"command": "mls.command.manageModels",
|
||||
"title": "%mls.command.manageModels%"
|
||||
},
|
||||
{
|
||||
"command": "mls.command.registerModel",
|
||||
"title": "%mls.command.registerModel%"
|
||||
},
|
||||
{
|
||||
"command": "mls.command.manageLanguages",
|
||||
"title": "%mls.command.manageLanguages%"
|
||||
@@ -101,6 +109,17 @@
|
||||
"tasks-widget": [
|
||||
"mls.command.managePackages",
|
||||
"mls.command.manageLanguages",
|
||||
"mls.command.manageModels",
|
||||
"mls.command.registerModel"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "%title.documents%",
|
||||
"row": 1,
|
||||
"col": 0,
|
||||
"widget": {
|
||||
"tasks-widget": [
|
||||
"mls.command.odbcdriver",
|
||||
"mls.command.mlsdocs"
|
||||
]
|
||||
@@ -114,7 +133,9 @@
|
||||
"dependencies": {
|
||||
"request": "^2.88.0",
|
||||
"vscode-nls": "^4.0.0",
|
||||
"vscode-languageclient": "^5.3.0-next.1"
|
||||
"vscode-languageclient": "^5.3.0-next.1",
|
||||
"@azure/arm-machinelearningservices" : "^3.0.0",
|
||||
"polly-js": "^1.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^5.2.5",
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
"displayName": "SQL Server Machine Learning Services",
|
||||
"description": "SQL Server Machine Learning Services",
|
||||
"title.tasks": "Tasks",
|
||||
"title.documents": "Documents",
|
||||
"title.configurations": "Configurations",
|
||||
"title.endpoints": "Endpoints",
|
||||
"mls.command.managePackages": "Manage Packages in SQL Server",
|
||||
"mls.command.manageLanguages": "Manage External Languages",
|
||||
"mls.command.manageModels": "Manage Models",
|
||||
"mls.command.registerModel": "Register Model",
|
||||
"mls.command.odbcdriver": "Install ODBC Driver for SQL Server",
|
||||
"mls.command.mlsdocs": "Machine Learning Services Documentation",
|
||||
"mls.configuration.title": "Machine Learning Services configurations",
|
||||
|
||||
@@ -82,7 +82,23 @@ export class ApiWrapper {
|
||||
return azdata.window.createModelViewDialog(title, dialogName, isWide);
|
||||
}
|
||||
|
||||
public createWizard(title: string): azdata.window.Wizard {
|
||||
return azdata.window.createWizard(title);
|
||||
}
|
||||
|
||||
public createWizardPage(title: string): azdata.window.WizardPage {
|
||||
return azdata.window.createWizardPage(title);
|
||||
}
|
||||
|
||||
public openDialog(dialog: azdata.window.Dialog): void {
|
||||
return azdata.window.openDialog(dialog);
|
||||
}
|
||||
|
||||
public getAllAccounts(): Thenable<azdata.Account[]> {
|
||||
return azdata.accounts.getAllAccounts();
|
||||
}
|
||||
|
||||
public getSecurityToken(account: azdata.Account, resource: azdata.AzureResource): Thenable<{ [key: string]: any }> {
|
||||
return azdata.accounts.getSecurityToken(account, resource);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,14 @@ export const mlEnableMlsCommand = 'mls.command.enableMls';
|
||||
export const mlDisableMlsCommand = 'mls.command.disableMls';
|
||||
export const extensionOutputChannel = 'Machine Learning Services';
|
||||
export const notebookExtensionName = 'Microsoft.notebook';
|
||||
export const azureSubscriptionsCommand = 'azure.accounts.getSubscriptions';
|
||||
export const azureResourceGroupsCommand = 'azure.accounts.getResourceGroups';
|
||||
|
||||
// Tasks, commands
|
||||
//
|
||||
export const mlManageLanguagesCommand = 'mls.command.manageLanguages';
|
||||
export const mlManageModelsCommand = 'mls.command.manageModels';
|
||||
export const mlRegisterModelCommand = 'mls.command.registerModel';
|
||||
export const mlManagePackagesCommand = 'mls.command.managePackages';
|
||||
export const mlOdbcDriverCommand = 'mls.command.odbcdriver';
|
||||
export const mlsDocumentsCommand = 'mls.command.mlsdocs';
|
||||
@@ -33,6 +37,7 @@ export const mlsConfigKey = 'machineLearningServices';
|
||||
export const pythonPathConfigKey = 'pythonPath';
|
||||
export const pythonEnabledConfigKey = 'enablePython';
|
||||
export const rEnabledConfigKey = 'enableR';
|
||||
export const registeredModelsTableName = 'registeredModelsTableName';
|
||||
export const rPathConfigKey = 'rPath';
|
||||
|
||||
// Localized texts
|
||||
@@ -70,7 +75,8 @@ export function httpGetRequestError(code: number, message: string): string {
|
||||
code,
|
||||
message);
|
||||
}
|
||||
export function getErrorMessage(error: Error): string { return localize('azure.resource.error', "Error: {0}", error?.message); }
|
||||
export function getErrorMessage(error: Error): string { return localize('azure.resource.error', "Error: {0}", error?.message || error?.toString()); }
|
||||
export const notSupportedEventArg = localize('notSupportedEventArg', "Not supported event args");
|
||||
export const extLangInstallTabTitle = localize('extLang.installTabTitle', "Installed");
|
||||
export const extLangLanguageCreatedDate = localize('extLang.languageCreatedDate', "Installed");
|
||||
export const extLangLanguagePlatform = localize('extLang.languagePlatform', "Platform");
|
||||
@@ -95,6 +101,33 @@ export const extLangSelectedPath = localize('extLang.selectedPath', "Selected Pa
|
||||
export const extLangInstallFailedError = localize('extLang.installFailedError', "Failed to install language");
|
||||
export const extLangUpdateFailedError = localize('extLang.updateFailedError', "Failed to update language");
|
||||
|
||||
export const modeIld = localize('models.id', "Id");
|
||||
export const modelName = localize('models.name', "Name");
|
||||
export const modelSize = localize('models.size', "Size");
|
||||
export const browseModels = localize('models.browseButton', "...");
|
||||
export const azureAccount = localize('models.azureAccount', "Account");
|
||||
export const azureSubscription = localize('models.azureSubscription', "Subscription");
|
||||
export const azureGroup = localize('models.azureGroup', "Resource Group");
|
||||
export const azureModelWorkspace = localize('models.azureModelWorkspace', "Workspace");
|
||||
export const azureModelFilter = localize('models.azureModelFilter', "Filter");
|
||||
export const azureModels = localize('models.azureModels', "Models");
|
||||
export const azureModelsTitle = localize('models.azureModelsTitle', "Azure models");
|
||||
export const localModelsTitle = localize('models.localModelsTitle', "Local models");
|
||||
export const modelSourcesTitle = localize('models.modelSourcesTitle', "Source location");
|
||||
export const currentModelsTitle = localize('models.currentModelsTitle', "Models");
|
||||
export const azureRegisterModel = localize('models.azureRegisterModel', "Register");
|
||||
export const registerModelWizardTitle = localize('models.RegisterWizard', "Register");
|
||||
export const registerModelButton = localize('models.RegisterModelButton', "Register model");
|
||||
export const modelRegisteredSuccessfully = localize('models.modelRegisteredSuccessfully', "Model registered successfully");
|
||||
export const modelFailedToRegister = localize('models.modelFailedToRegistered', "Model failed to register");
|
||||
export const localModelSource = localize('models.localModelSource', "Upload file");
|
||||
export const azureModelSource = localize('models.azureModelSource', "Import from AzureML registry");
|
||||
export const downloadModelMsgTaskName = localize('models.downloadModelMsgTaskName', "Downloading Model from Azure");
|
||||
export const invalidAzureResourceError = localize('models.invalidAzureResourceError', "Invalid Azure resource");
|
||||
export const invalidModelToRegisterError = localize('models.invalidModelToRegisterError', "Invalid model to register");
|
||||
|
||||
|
||||
|
||||
// Links
|
||||
//
|
||||
export const mlsDocuments = 'https://docs.microsoft.com/sql/advanced-analytics/?view=sql-server-ver15';
|
||||
|
||||
@@ -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 vscode from 'vscode';
|
||||
|
||||
export class EventEmitterCollection extends vscode.Disposable {
|
||||
private _events: Map<string, vscode.EventEmitter<any>[]> = new Map<string, vscode.EventEmitter<any>[]>();
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor() {
|
||||
super(() => this.dispose());
|
||||
|
||||
}
|
||||
|
||||
public on(evt: string, listener: (e: any) => any, thisArgs?: any) {
|
||||
if (!this._events.has(evt)) {
|
||||
this._events.set(evt, []);
|
||||
}
|
||||
let eventEmitter = new vscode.EventEmitter<any>();
|
||||
eventEmitter.event(listener, thisArgs);
|
||||
this._events.get(evt)?.push(eventEmitter);
|
||||
return this;
|
||||
}
|
||||
|
||||
public fire(evt: string, arg?: any) {
|
||||
if (!this._events.has(evt)) {
|
||||
this._events.set(evt, []);
|
||||
}
|
||||
this._events.get(evt)?.forEach(eventEmitter => {
|
||||
eventEmitter.fire(arg);
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): any {
|
||||
this._events.forEach(events => {
|
||||
events.forEach(event => {
|
||||
event.dispose();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -148,7 +148,7 @@ export class QueryRunner {
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
private async runQuery(connection: azdata.connection.ConnectionProfile, query: string): Promise<azdata.SimpleExecuteResult | undefined> {
|
||||
public async runQuery(connection: azdata.connection.ConnectionProfile, query: string): Promise<azdata.SimpleExecuteResult | undefined> {
|
||||
let result: azdata.SimpleExecuteResult | undefined = undefined;
|
||||
try {
|
||||
if (connection) {
|
||||
|
||||
@@ -75,6 +75,42 @@ export class Config {
|
||||
return this.config.get(constants.rEnabledConfigKey) || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns registered models table name
|
||||
*/
|
||||
public get registeredModelTableName(): string {
|
||||
return this._configValues.registeredModelsTableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns registered models table name
|
||||
*/
|
||||
public get registeredModelDatabaseName(): string {
|
||||
return this._configValues.registeredModelsDatabaseName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Azure ML API
|
||||
*/
|
||||
public get amlModelManagementUrl(): string {
|
||||
return this._configValues.amlModelManagementUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Azure ML API
|
||||
*/
|
||||
public get amlExperienceUrl(): string {
|
||||
return this._configValues.amlExperienceUrl;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns Azure ML API Version
|
||||
*/
|
||||
public get amlApiVersion(): string {
|
||||
return this._configValues.amlApiVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns r path from user settings
|
||||
*/
|
||||
|
||||
@@ -16,8 +16,12 @@ import { Config } from '../configurations/config';
|
||||
import { ServerConfigWidget } from '../widgets/serverConfigWidgets';
|
||||
import { ServerConfigManager } from '../serverConfig/serverConfigManager';
|
||||
import { HttpClient } from '../common/httpClient';
|
||||
import { LanguageController } from '../externalLanguage/languageController';
|
||||
import { LanguageController } from '../views/externalLanguages/languageController';
|
||||
import { LanguageService } from '../externalLanguage/languageService';
|
||||
import { ModelManagementController } from '../views/models/modelManagementController';
|
||||
import { RegisteredModelService } from '../modelManagement/registeredModelService';
|
||||
import { AzureModelRegistryService } from '../modelManagement/azureModelRegistryService';
|
||||
import { ModelImporter } from '../modelManagement/modelImporter';
|
||||
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
@@ -94,13 +98,28 @@ export default class MainController implements vscode.Disposable {
|
||||
await packageManager.managePackages();
|
||||
}));
|
||||
|
||||
// External Languages
|
||||
//
|
||||
let mssqlService = await this.getLanguageExtensionService();
|
||||
let languagesModel = new LanguageService(this._apiWrapper, mssqlService);
|
||||
let languageController = new LanguageController(this._apiWrapper, this._rootPath, languagesModel);
|
||||
let modelImporter = new ModelImporter(this._outputChannel, this._apiWrapper, this._processService, this._config);
|
||||
|
||||
// Model Management
|
||||
//
|
||||
let registeredModelService = new RegisteredModelService(this._apiWrapper, this._config, this._queryRunner, modelImporter);
|
||||
let azureModelsService = new AzureModelRegistryService(this._apiWrapper, this._config, this.httpClient, this._outputChannel);
|
||||
let modelManagementController = new ModelManagementController(this._apiWrapper, this._rootPath, azureModelsService, registeredModelService);
|
||||
|
||||
this._apiWrapper.registerCommand(constants.mlManageLanguagesCommand, (async () => {
|
||||
await languageController.manageLanguages();
|
||||
}));
|
||||
this._apiWrapper.registerCommand(constants.mlManageModelsCommand, (async () => {
|
||||
await modelManagementController.manageRegisteredModels();
|
||||
}));
|
||||
this._apiWrapper.registerCommand(constants.mlRegisterModelCommand, (async () => {
|
||||
await modelManagementController.registerModel();
|
||||
}));
|
||||
this._apiWrapper.registerCommand(constants.mlsDependenciesCommand, (async () => {
|
||||
await packageManager.installDependencies();
|
||||
}));
|
||||
@@ -110,6 +129,12 @@ export default class MainController implements vscode.Disposable {
|
||||
this._apiWrapper.registerTaskHandler(constants.mlManageLanguagesCommand, async () => {
|
||||
await languageController.manageLanguages();
|
||||
});
|
||||
this._apiWrapper.registerTaskHandler(constants.mlManageModelsCommand, async () => {
|
||||
await modelManagementController.manageRegisteredModels();
|
||||
});
|
||||
this._apiWrapper.registerTaskHandler(constants.mlRegisterModelCommand, async () => {
|
||||
await modelManagementController.registerModel();
|
||||
});
|
||||
this._apiWrapper.registerTaskHandler(constants.mlOdbcDriverCommand, async () => {
|
||||
await this.serverConfigManager.openOdbcDriverDocuments();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as msRest from '@azure/ms-rest-js';
|
||||
import * as Models from './interfaces';
|
||||
import * as Mappers from './mappers';
|
||||
import * as Parameters from './parameters';
|
||||
import { AzureMachineLearningWorkspacesContext } from '@azure/arm-machinelearningservices';
|
||||
|
||||
export class Artifacts {
|
||||
private readonly client: AzureMachineLearningWorkspacesContext;
|
||||
|
||||
constructor(client: AzureMachineLearningWorkspacesContext) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
getArtifactContentInformation2(subscriptionId: string, resourceGroupName: string, workspaceName: string, origin: string, container: string, options?: Models.ArtifactAPIGetArtifactContentInformation2OptionalParams): Promise<Models.GetArtifactContentInformation2Response>;
|
||||
getArtifactContentInformation2(subscriptionId: string, resourceGroupName: string, workspaceName: string, origin: string, container: string, callback: msRest.ServiceCallback<Models.ArtifactContentInformationDto>): void;
|
||||
getArtifactContentInformation2(subscriptionId: string, resourceGroupName: string, workspaceName: string, origin: string, container: string, options: Models.ArtifactAPIGetArtifactContentInformation2OptionalParams, callback: msRest.ServiceCallback<Models.ArtifactContentInformationDto>): void;
|
||||
getArtifactContentInformation2(subscriptionId: string, resourceGroupName: string, workspaceName: string, origin: string, container: string, options?: Models.ArtifactAPIGetArtifactContentInformation2OptionalParams | msRest.ServiceCallback<Models.ArtifactContentInformationDto>, callback?: msRest.ServiceCallback<Models.ArtifactContentInformationDto>): Promise<Models.GetArtifactContentInformation2Response> {
|
||||
return this.client.sendOperationRequest(
|
||||
{
|
||||
subscriptionId,
|
||||
resourceGroupName,
|
||||
workspaceName,
|
||||
origin,
|
||||
container,
|
||||
options
|
||||
},
|
||||
getArtifactContentInformation2OperationSpec,
|
||||
callback) as Promise<Models.GetArtifactContentInformation2Response>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const serializer = new msRest.Serializer(Mappers);
|
||||
const getArtifactContentInformation2OperationSpec: msRest.OperationSpec = {
|
||||
httpMethod: 'GET',
|
||||
path: 'artifact/v1.0/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.MachineLearningServices/workspaces/{workspaceName}/artifacts/contentinfo/{origin}/{container}',
|
||||
urlParameters: [
|
||||
Parameters.subscriptionId,
|
||||
Parameters.resourceGroupName,
|
||||
Parameters.workspaceName,
|
||||
Parameters.origin,
|
||||
Parameters.container,
|
||||
Parameters.apiVersion
|
||||
],
|
||||
queryParameters: [
|
||||
Parameters.projectName0,
|
||||
Parameters.path1,
|
||||
Parameters.accountName
|
||||
],
|
||||
responses: {
|
||||
200: {
|
||||
bodyMapper: Mappers.ArtifactContentInformationDto
|
||||
},
|
||||
default: {}
|
||||
},
|
||||
serializer
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as msRest from '@azure/ms-rest-js';
|
||||
import * as Models from './interfaces';
|
||||
import * as Mappers from './mappers';
|
||||
import * as Parameters from './parameters';
|
||||
import { AzureMachineLearningWorkspacesContext } from '@azure/arm-machinelearningservices';
|
||||
|
||||
export class Assets {
|
||||
private readonly client: AzureMachineLearningWorkspacesContext;
|
||||
|
||||
constructor(client: AzureMachineLearningWorkspacesContext) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
queryById(
|
||||
subscriptionId: string,
|
||||
resourceGroup: string,
|
||||
workspace: string,
|
||||
id: string,
|
||||
options?: msRest.RequestOptionsBase
|
||||
): Promise<Models.AssetsQueryByIdResponse>;
|
||||
queryById(
|
||||
subscriptionId: string,
|
||||
resourceGroup: string,
|
||||
workspace: string,
|
||||
id: string,
|
||||
callback: msRest.ServiceCallback<Models.Asset>
|
||||
): void;
|
||||
queryById(
|
||||
subscriptionId: string,
|
||||
resourceGroup: string,
|
||||
workspace: string,
|
||||
id: string,
|
||||
options: msRest.RequestOptionsBase,
|
||||
callback: msRest.ServiceCallback<Models.Asset>
|
||||
): void;
|
||||
queryById(
|
||||
subscriptionId: string,
|
||||
resourceGroup: string,
|
||||
workspace: string,
|
||||
id: string,
|
||||
options?: msRest.RequestOptionsBase | msRest.ServiceCallback<Models.Asset>,
|
||||
callback?: msRest.ServiceCallback<Models.Asset>
|
||||
): Promise<Models.AssetsQueryByIdResponse> {
|
||||
return this.client.sendOperationRequest(
|
||||
{
|
||||
subscriptionId,
|
||||
resourceGroup,
|
||||
workspace,
|
||||
id,
|
||||
options
|
||||
},
|
||||
queryByIdOperationSpec,
|
||||
callback
|
||||
) as Promise<Models.AssetsQueryByIdResponse>;
|
||||
}
|
||||
}
|
||||
|
||||
const serializer = new msRest.Serializer(Mappers);
|
||||
const queryByIdOperationSpec: msRest.OperationSpec = {
|
||||
httpMethod: 'GET',
|
||||
path:
|
||||
'modelmanagement/v1.0/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.MachineLearningServices/workspaces/{workspace}/assets/{id}',
|
||||
urlParameters: [Parameters.subscriptionId, Parameters.resourceGroup, Parameters.workspace, Parameters.id],
|
||||
responses: {
|
||||
200: {
|
||||
bodyMapper: Mappers.Asset
|
||||
},
|
||||
default: {
|
||||
bodyMapper: Mappers.ModelErrorResponse
|
||||
}
|
||||
},
|
||||
serializer
|
||||
};
|
||||
@@ -0,0 +1,296 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 constants from '../common/constants';
|
||||
import { azureResource } from '../typings/azure-resource';
|
||||
import { AzureMachineLearningWorkspaces } from '@azure/arm-machinelearningservices';
|
||||
import { TokenCredentials } from '@azure/ms-rest-js';
|
||||
import { WorkspaceModels } from './workspacesModels';
|
||||
import { AzureMachineLearningWorkspacesOptions, Workspace } from '@azure/arm-machinelearningservices/esm/models';
|
||||
import { WorkspaceModel, Asset, IArtifactParts } from './interfaces';
|
||||
import { Config } from '../configurations/config';
|
||||
import { Assets } from './assets';
|
||||
import * as polly from 'polly-js';
|
||||
import { Artifacts } from './artifacts';
|
||||
import { HttpClient } from '../common/httpClient';
|
||||
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
|
||||
/**
|
||||
* Azure Model Service
|
||||
*/
|
||||
export class AzureModelRegistryService {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor(private _apiWrapper: ApiWrapper, private _config: Config, private _httpClient: HttpClient, private _outputChannel: vscode.OutputChannel) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of azure accounts
|
||||
*/
|
||||
public async getAccounts(): Promise<azdata.Account[]> {
|
||||
return await this._apiWrapper.getAllAccounts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of azure subscriptions
|
||||
* @param account azure account
|
||||
*/
|
||||
public async getSubscriptions(account: azdata.Account | undefined): Promise<azureResource.AzureResourceSubscription[] | undefined> {
|
||||
const data = <azureResource.GetSubscriptionsResult>await this._apiWrapper.executeCommand(constants.azureSubscriptionsCommand, account, true);
|
||||
return data?.subscriptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of azure groups
|
||||
* @param account azure account
|
||||
* @param subscription azure subscription
|
||||
*/
|
||||
public async getGroups(
|
||||
account: azdata.Account | undefined,
|
||||
subscription: azureResource.AzureResourceSubscription | undefined): Promise<azureResource.AzureResource[] | undefined> {
|
||||
const data = <azureResource.GetResourceGroupsResult>await this._apiWrapper.executeCommand(constants.azureResourceGroupsCommand, account, subscription, true);
|
||||
return data?.resourceGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of workspaces
|
||||
* @param account azure account
|
||||
* @param subscription azure subscription
|
||||
* @param resourceGroup azure resource group
|
||||
*/
|
||||
public async getWorkspaces(
|
||||
account: azdata.Account,
|
||||
subscription: azureResource.AzureResourceSubscription,
|
||||
resourceGroup: azureResource.AzureResource | undefined): Promise<Workspace[]> {
|
||||
return await this.fetchWorkspaces(account, subscription, resourceGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of models
|
||||
* @param account azure account
|
||||
* @param subscription azure subscription
|
||||
* @param resourceGroup azure resource group
|
||||
* @param workspace azure workspace
|
||||
*/
|
||||
public async getModels(
|
||||
account: azdata.Account,
|
||||
subscription: azureResource.AzureResourceSubscription,
|
||||
resourceGroup: azureResource.AzureResource,
|
||||
workspace: Workspace): Promise<WorkspaceModel[] | undefined> {
|
||||
return await this.fetchModels(account, subscription, resourceGroup, workspace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download an azure model to a temporary location
|
||||
* @param account azure account
|
||||
* @param subscription azure subscription
|
||||
* @param resourceGroup azure resource group
|
||||
* @param workspace azure workspace
|
||||
* @param model azure model
|
||||
*/
|
||||
public async downloadModel(
|
||||
account: azdata.Account,
|
||||
subscription: azureResource.AzureResourceSubscription,
|
||||
resourceGroup: azureResource.AzureResource,
|
||||
workspace: Workspace,
|
||||
model: WorkspaceModel): Promise<string> {
|
||||
let downloadedFilePath: string = '';
|
||||
|
||||
for (const tenant of account.properties.tenants) {
|
||||
try {
|
||||
const downloadUrls = await this.getAssetArtifactsDownloadLinks(account, subscription, resourceGroup, workspace, model, tenant);
|
||||
if (downloadUrls && downloadUrls.length > 0) {
|
||||
downloadedFilePath = await this.downloadArtifact(downloadUrls[0]);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
return downloadedFilePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs dependencies for the extension
|
||||
*/
|
||||
public async downloadArtifact(downloadUrl: string): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
let msgTaskName = constants.downloadModelMsgTaskName;
|
||||
this._apiWrapper.startBackgroundOperation({
|
||||
displayName: msgTaskName,
|
||||
description: msgTaskName,
|
||||
isCancelable: false,
|
||||
operation: async op => {
|
||||
let tempFilePath: string = '';
|
||||
try {
|
||||
tempFilePath = path.join(os.tmpdir(), `ads_ml_temp_${UUID.generateUuid()}`);
|
||||
await this._httpClient.download(downloadUrl, tempFilePath, op, this._outputChannel);
|
||||
|
||||
op.updateStatus(azdata.TaskStatus.Succeeded);
|
||||
resolve(tempFilePath);
|
||||
} catch (error) {
|
||||
let errorMsg = constants.installDependenciesError(error ? error.message : '');
|
||||
op.updateStatus(azdata.TaskStatus.Failed, errorMsg);
|
||||
reject(errorMsg);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async fetchWorkspaces(account: azdata.Account, subscription: azureResource.AzureResourceSubscription, resourceGroup: azureResource.AzureResource | undefined): Promise<Workspace[]> {
|
||||
let resources: Workspace[] = [];
|
||||
|
||||
try {
|
||||
for (const tenant of account.properties.tenants) {
|
||||
const tokens = await this._apiWrapper.getSecurityToken(account, azdata.AzureResource.ResourceManagement);
|
||||
const token = tokens[tenant.id].token;
|
||||
const tokenType = tokens[tenant.id].tokenType;
|
||||
const client = new AzureMachineLearningWorkspaces(new TokenCredentials(token, tokenType), subscription.id);
|
||||
let result = resourceGroup ? await client.workspaces.listByResourceGroup(resourceGroup.name) : await client.workspaces.listBySubscription();
|
||||
resources.push(...result);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
|
||||
private async fetchModels(
|
||||
account: azdata.Account,
|
||||
subscription: azureResource.AzureResourceSubscription,
|
||||
resourceGroup: azureResource.AzureResource,
|
||||
workspace: Workspace): Promise<WorkspaceModel[]> {
|
||||
let resources: WorkspaceModel[] = [];
|
||||
|
||||
for (const tenant of account.properties.tenants) {
|
||||
try {
|
||||
let baseUri = this.getBaseUrl(workspace, this._config.amlModelManagementUrl);
|
||||
const client = await this.getClient(baseUri, account, subscription, tenant);
|
||||
let modelsClient = new WorkspaceModels(client);
|
||||
resources = resources.concat(await modelsClient.listModels(resourceGroup.name, workspace.name || ''));
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
private async fetchModelAsset(
|
||||
subscription: azureResource.AzureResourceSubscription,
|
||||
resourceGroup: azureResource.AzureResource,
|
||||
workspace: Workspace,
|
||||
model: WorkspaceModel,
|
||||
client: AzureMachineLearningWorkspaces): Promise<Asset> {
|
||||
|
||||
const modelId = this.getModelId(model);
|
||||
let modelsClient = new Assets(client);
|
||||
return await modelsClient.queryById(subscription.id, resourceGroup.name, workspace.name || '', modelId);
|
||||
}
|
||||
|
||||
public async getAssetArtifactsDownloadLinks(
|
||||
account: azdata.Account,
|
||||
subscription: azureResource.AzureResourceSubscription,
|
||||
resourceGroup: azureResource.AzureResource,
|
||||
workspace: Workspace,
|
||||
model: WorkspaceModel,
|
||||
tenant: any): Promise<string[]> {
|
||||
let baseUri = this.getBaseUrl(workspace, this._config.amlModelManagementUrl);
|
||||
const modelManagementClient = await this.getClient(baseUri, account, subscription, tenant);
|
||||
const asset = await this.fetchModelAsset(subscription, resourceGroup, workspace, model, modelManagementClient);
|
||||
baseUri = this.getBaseUrl(workspace, this._config.amlExperienceUrl);
|
||||
const experienceClient = await this.getClient(baseUri, account, subscription, tenant);
|
||||
const artifactClient = new Artifacts(experienceClient);
|
||||
let downloadLinks: string[] = [];
|
||||
if (asset && asset.artifacts) {
|
||||
const downloadLinkPromises: Array<Promise<string>> = [];
|
||||
for (const artifact of asset.artifacts) {
|
||||
const parts = artifact.id
|
||||
? this.getPartsFromAssetIdOrPrefix(artifact.id)
|
||||
: this.getPartsFromAssetIdOrPrefix(artifact.prefix);
|
||||
|
||||
if (parts) {
|
||||
const promise = polly()
|
||||
.waitAndRetry(3)
|
||||
.executeForPromise(
|
||||
async (): Promise<string> => {
|
||||
const artifact = await artifactClient.getArtifactContentInformation2(
|
||||
experienceClient.subscriptionId,
|
||||
resourceGroup.name,
|
||||
workspace.name || '',
|
||||
parts.origin,
|
||||
parts.container,
|
||||
{ path: parts.path }
|
||||
);
|
||||
if (artifact) {
|
||||
return artifact.contentUri || '';
|
||||
} else {
|
||||
return Promise.reject();
|
||||
}
|
||||
}
|
||||
);
|
||||
downloadLinkPromises.push(promise);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
downloadLinks = await Promise.all(downloadLinkPromises);
|
||||
} catch (rejectedPromiseError) {
|
||||
return rejectedPromiseError;
|
||||
}
|
||||
}
|
||||
return downloadLinks;
|
||||
}
|
||||
|
||||
public getPartsFromAssetIdOrPrefix(idOrPrefix: string | undefined): IArtifactParts | undefined {
|
||||
const artifactRegex = /^(.+?)\/(.+?)\/(.+?)$/;
|
||||
if (idOrPrefix) {
|
||||
const parts = artifactRegex.exec(idOrPrefix);
|
||||
if (parts && parts.length === 4) {
|
||||
return {
|
||||
origin: parts[1],
|
||||
container: parts[2],
|
||||
path: parts[3]
|
||||
};
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getBaseUrl(workspace: Workspace, server: string): string {
|
||||
let baseUri = `https://${workspace.location}.${server}`;
|
||||
if (workspace.location === 'chinaeast2') {
|
||||
baseUri = `https://${workspace.location}.${server}`;
|
||||
}
|
||||
return baseUri;
|
||||
}
|
||||
|
||||
private async getClient(baseUri: string, account: azdata.Account, subscription: azureResource.AzureResourceSubscription, tenant: any): Promise<AzureMachineLearningWorkspaces> {
|
||||
const tokens = await this._apiWrapper.getSecurityToken(account, azdata.AzureResource.ResourceManagement);
|
||||
const token = tokens[tenant.id].token;
|
||||
const tokenType = tokens[tenant.id].tokenType;
|
||||
const options: AzureMachineLearningWorkspacesOptions = {
|
||||
baseUri: baseUri
|
||||
};
|
||||
const client = new AzureMachineLearningWorkspaces(new TokenCredentials(token, tokenType), subscription.id, options);
|
||||
client.apiVersion = this._config.amlApiVersion;
|
||||
return client;
|
||||
}
|
||||
|
||||
private getModelId(model: WorkspaceModel): string {
|
||||
const amlAssetRegex = /^aml:\/\/asset\/(.+)$/;
|
||||
const id = model ? amlAssetRegex.exec(model.url || '') : undefined;
|
||||
return id && id.length === 2 ? id[1] : '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as msRest from '@azure/ms-rest-js';
|
||||
import { Resource } from '@azure/arm-machinelearningservices/esm/models';
|
||||
|
||||
/**
|
||||
* An interface representing ListWorkspaceModelResult.
|
||||
*/
|
||||
export interface ListWorkspaceModelsResult extends Array<WorkspaceModel> {
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface representing Workspace model
|
||||
*/
|
||||
export interface WorkspaceModel extends Resource {
|
||||
framework?: string;
|
||||
frameworkVersion?: string;
|
||||
createdBy?: string;
|
||||
createdTime?: string;
|
||||
experimentName?: string;
|
||||
outputsSchema?: Array<string>;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface representing Workspace model list response
|
||||
*/
|
||||
export type WorkspacesModelsResponse = ListWorkspaceModelsResult & {
|
||||
/**
|
||||
* The underlying HTTP response.
|
||||
*/
|
||||
_response: msRest.HttpResponse & {
|
||||
/**
|
||||
* The response body as text (string format)
|
||||
*/
|
||||
bodyAsText: string;
|
||||
|
||||
/**
|
||||
* The response body as parsed JSON or XML
|
||||
*/
|
||||
parsedBody: ListWorkspaceModelsResult;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* An interface representing registered model
|
||||
*/
|
||||
export interface RegisteredModel {
|
||||
id: number,
|
||||
name: string
|
||||
}
|
||||
|
||||
/**
|
||||
* The Artifact definition.
|
||||
*/
|
||||
export interface ArtifactDetails {
|
||||
/**
|
||||
* The Artifact Id.
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* The Artifact prefix.
|
||||
*/
|
||||
prefix?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
* An interface representing Asset.
|
||||
* The Asset definition.
|
||||
*
|
||||
*/
|
||||
export interface Asset {
|
||||
/**
|
||||
* @member {string} [id] The Asset Id.
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* @member {string} [name] The name of the Asset.
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* @member {string} [description] The Asset description.
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* @member {ArtifactDetails[]} [artifacts] A list of child artifacts.
|
||||
*/
|
||||
artifacts?: ArtifactDetails[];
|
||||
/**
|
||||
* @member {string[]} [tags] The Asset tag list.
|
||||
*/
|
||||
tags?: string[];
|
||||
/**
|
||||
* @member {{ [propertyName: string]: string }} [kvTags] The Asset tag
|
||||
* dictionary. Tags are mutable.
|
||||
*/
|
||||
kvTags?: { [propertyName: string]: string };
|
||||
/**
|
||||
* @member {{ [propertyName: string]: string }} [properties] The Asset
|
||||
* property dictionary. Properties are immutable.
|
||||
*/
|
||||
properties?: { [propertyName: string]: string };
|
||||
/**
|
||||
* @member {string} [runid] The RunId associated with this Asset.
|
||||
*/
|
||||
runid?: string;
|
||||
/**
|
||||
* @member {string} [projectid] The project Id.
|
||||
*/
|
||||
projectid?: string;
|
||||
/**
|
||||
* @member {{ [propertyName: string]: string }} [meta] A dictionary
|
||||
* containing metadata about the Asset.
|
||||
*/
|
||||
meta?: { [propertyName: string]: string };
|
||||
/**
|
||||
* @member {Date} [createdTime] The time the Asset was created in UTC.
|
||||
*/
|
||||
createdTime?: Date;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Contains response data for the queryById operation.
|
||||
*/
|
||||
export type AssetsQueryByIdResponse = Asset & {
|
||||
/**
|
||||
* The underlying HTTP response.
|
||||
*/
|
||||
_response: msRest.HttpResponse & {
|
||||
/**
|
||||
* The response body as text (string format)
|
||||
*/
|
||||
bodyAsText: string;
|
||||
/**
|
||||
* The response body as parsed JSON or XML
|
||||
*/
|
||||
parsedBody: Asset;
|
||||
};
|
||||
};
|
||||
|
||||
export interface IArtifactParts {
|
||||
origin: string;
|
||||
container: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
* An interface representing ArtifactContentInformationDto.
|
||||
*/
|
||||
export interface ArtifactContentInformationDto {
|
||||
/**
|
||||
* @member {string} [contentUri]
|
||||
*/
|
||||
contentUri?: string;
|
||||
/**
|
||||
* @member {string} [origin]
|
||||
*/
|
||||
origin?: string;
|
||||
/**
|
||||
* @member {string} [container]
|
||||
*/
|
||||
container?: string;
|
||||
/**
|
||||
* @member {string} [path]
|
||||
*/
|
||||
path?: string;
|
||||
}
|
||||
/**
|
||||
* Contains response data for the getArtifactContentInformation2 operation.
|
||||
*/
|
||||
export type GetArtifactContentInformation2Response = ArtifactContentInformationDto & {
|
||||
/**
|
||||
* The underlying HTTP response.
|
||||
*/
|
||||
_response: msRest.HttpResponse & {
|
||||
/**
|
||||
* The response body as text (string format)
|
||||
*/
|
||||
bodyAsText: string;
|
||||
/**
|
||||
* The response body as parsed JSON or XML
|
||||
*/
|
||||
parsedBody: ArtifactContentInformationDto;
|
||||
};
|
||||
};
|
||||
/**
|
||||
* @interface
|
||||
* An interface representing ArtifactAPIGetArtifactContentInformation2OptionalParams.
|
||||
* Optional Parameters.
|
||||
*
|
||||
* @extends RequestOptionsBase
|
||||
*/
|
||||
export interface ArtifactAPIGetArtifactContentInformation2OptionalParams extends msRest.RequestOptionsBase {
|
||||
/**
|
||||
* @member {string} [projectName]
|
||||
*/
|
||||
projectName?: string;
|
||||
/**
|
||||
* @member {string} [path]
|
||||
*/
|
||||
path?: string;
|
||||
/**
|
||||
* @member {string} [accountName]
|
||||
*/
|
||||
accountName?: string;
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as msRest from '@azure/ms-rest-js';
|
||||
|
||||
export const Resource: msRest.CompositeMapper = {
|
||||
serializedName: 'Resource',
|
||||
type: {
|
||||
name: 'Composite',
|
||||
className: 'Resource',
|
||||
modelProperties: {
|
||||
id: {
|
||||
readOnly: true,
|
||||
serializedName: 'id',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
},
|
||||
name: {
|
||||
readOnly: true,
|
||||
serializedName: 'name',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
},
|
||||
identity: {
|
||||
readOnly: true,
|
||||
serializedName: 'identity',
|
||||
type: {
|
||||
name: 'Composite',
|
||||
className: 'Identity'
|
||||
}
|
||||
},
|
||||
location: {
|
||||
serializedName: 'location',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
},
|
||||
type: {
|
||||
readOnly: true,
|
||||
serializedName: 'type',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
},
|
||||
tags: {
|
||||
serializedName: 'tags',
|
||||
type: {
|
||||
name: 'Dictionary',
|
||||
value: {
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const ListWorkspaceModelsResult: msRest.CompositeMapper = {
|
||||
serializedName: 'ListWorkspaceModelsResult',
|
||||
type: {
|
||||
name: 'Composite',
|
||||
className: 'ListWorkspaceModelsResult',
|
||||
modelProperties: {
|
||||
value: {
|
||||
serializedName: '',
|
||||
type: {
|
||||
name: 'Sequence',
|
||||
element: {
|
||||
type: {
|
||||
name: 'Composite',
|
||||
className: 'WorkspaceModel'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
nextLink: {
|
||||
serializedName: 'nextLink',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const WorkspaceModel: msRest.CompositeMapper = {
|
||||
serializedName: 'WorkspaceModel',
|
||||
type: {
|
||||
name: 'Composite',
|
||||
className: 'WorkspaceModel',
|
||||
modelProperties: {
|
||||
...Resource.type.modelProperties,
|
||||
framework: {
|
||||
readOnly: true,
|
||||
serializedName: 'framework',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const MachineLearningServiceError: msRest.CompositeMapper = {
|
||||
serializedName: 'MachineLearningServiceError',
|
||||
type: {
|
||||
name: 'Composite',
|
||||
className: 'MachineLearningServiceError',
|
||||
modelProperties: {
|
||||
error: {
|
||||
readOnly: true,
|
||||
serializedName: 'error',
|
||||
type: {
|
||||
name: 'Composite',
|
||||
className: 'ErrorResponse'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
export const ModelErrorResponse: msRest.CompositeMapper = {
|
||||
serializedName: 'ModelErrorResponse',
|
||||
type: {
|
||||
name: 'Composite',
|
||||
className: 'ModelErrorResponse',
|
||||
modelProperties: {
|
||||
code: {
|
||||
serializedName: 'code',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
},
|
||||
statusCode: {
|
||||
serializedName: 'statusCode',
|
||||
type: {
|
||||
name: 'Number'
|
||||
}
|
||||
},
|
||||
message: {
|
||||
serializedName: 'message',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
},
|
||||
details: {
|
||||
serializedName: 'details',
|
||||
type: {
|
||||
name: 'Sequence',
|
||||
element: {
|
||||
type: {
|
||||
name: 'Composite',
|
||||
className: 'ErrorDetails'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
export const ArtifactDetails: msRest.CompositeMapper = {
|
||||
serializedName: 'ArtifactDetails',
|
||||
type: {
|
||||
name: 'Composite',
|
||||
className: 'ArtifactDetails',
|
||||
modelProperties: {
|
||||
id: {
|
||||
serializedName: 'id',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
},
|
||||
prefix: {
|
||||
serializedName: 'prefix',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
export const Asset: msRest.CompositeMapper = {
|
||||
serializedName: 'Asset',
|
||||
type: {
|
||||
name: 'Composite',
|
||||
className: 'Asset',
|
||||
modelProperties: {
|
||||
id: {
|
||||
serializedName: 'id',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
},
|
||||
name: {
|
||||
serializedName: 'name',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
},
|
||||
description: {
|
||||
serializedName: 'description',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
},
|
||||
artifacts: {
|
||||
serializedName: 'artifacts',
|
||||
type: {
|
||||
name: 'Sequence',
|
||||
element: {
|
||||
type: {
|
||||
name: 'Composite',
|
||||
className: 'ArtifactDetails'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
tags: {
|
||||
serializedName: 'tags',
|
||||
type: {
|
||||
name: 'Sequence',
|
||||
element: {
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
kvTags: {
|
||||
serializedName: 'kvTags',
|
||||
type: {
|
||||
name: 'Dictionary',
|
||||
value: {
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
properties: {
|
||||
serializedName: 'properties',
|
||||
type: {
|
||||
name: 'Dictionary',
|
||||
value: {
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
runid: {
|
||||
serializedName: 'runid',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
},
|
||||
projectid: {
|
||||
serializedName: 'projectid',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
},
|
||||
meta: {
|
||||
serializedName: 'meta',
|
||||
type: {
|
||||
name: 'Dictionary',
|
||||
value: {
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
createdTime: {
|
||||
serializedName: 'createdTime',
|
||||
type: {
|
||||
name: 'DateTime'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
export const ArtifactContentInformationDto: msRest.CompositeMapper = {
|
||||
serializedName: 'ArtifactContentInformationDto',
|
||||
type: {
|
||||
name: 'Composite',
|
||||
className: 'ArtifactContentInformationDto',
|
||||
modelProperties: {
|
||||
contentUri: {
|
||||
serializedName: 'contentUri',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
},
|
||||
origin: {
|
||||
serializedName: 'origin',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
},
|
||||
container: {
|
||||
serializedName: 'container',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
},
|
||||
path: {
|
||||
serializedName: 'path',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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 { ProcessService } from '../common/processService';
|
||||
import { Config } from '../configurations/config';
|
||||
import { ApiWrapper } from '../common/apiWrapper';
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
||||
|
||||
/**
|
||||
* Service to import model to database
|
||||
*/
|
||||
export class ModelImporter {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor(private _outputChannel: vscode.OutputChannel, private _apiWrapper: ApiWrapper, private _processService: ProcessService, private _config: Config) {
|
||||
}
|
||||
|
||||
public async registerModel(connection: azdata.connection.ConnectionProfile, modelFolderPath: string): Promise<void> {
|
||||
await this.executeScripts(connection, modelFolderPath);
|
||||
}
|
||||
|
||||
protected async executeScripts(connection: azdata.connection.ConnectionProfile, modelFolderPath: string): Promise<void> {
|
||||
|
||||
const parts = modelFolderPath.split('\\');
|
||||
modelFolderPath = parts.join('/');
|
||||
|
||||
let credentials = await this._apiWrapper.getCredentials(connection.connectionId);
|
||||
|
||||
if (connection) {
|
||||
let server = connection.serverName;
|
||||
|
||||
const experimentId = `ads_ml_experiment_${UUID.generateUuid()}`;
|
||||
const credential = connection.userName ? `${connection.userName}:${credentials[azdata.ConnectionOptionSpecialType.password]}` : '';
|
||||
let scripts: string[] = [
|
||||
'import mlflow.onnx',
|
||||
'import onnx',
|
||||
'from mlflow.tracking.client import MlflowClient',
|
||||
`onx = onnx.load("${modelFolderPath}")`,
|
||||
'client = MlflowClient()',
|
||||
`exp_name = "${experimentId}"`,
|
||||
`db_uri_artifact = "mssql+pyodbc://${credential}@${server}/MlFlowDB?driver=ODBC+Driver+17+for+SQL+Server"`,
|
||||
'client.create_experiment(exp_name, artifact_location=db_uri_artifact)',
|
||||
'mlflow.set_experiment(exp_name)',
|
||||
'mlflow.onnx.log_model(onx, "pipeline_vectorize")'
|
||||
];
|
||||
let pythonExecutable = this._config.pythonExecutable;
|
||||
await this._processService.execScripts(pythonExecutable, scripts, [], this._outputChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 msRest from '@azure/ms-rest-js';
|
||||
|
||||
export const subscriptionId: msRest.OperationURLParameter = {
|
||||
parameterPath: 'subscriptionId',
|
||||
mapper: {
|
||||
required: true,
|
||||
serializedName: 'subscriptionId',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
}
|
||||
};
|
||||
export const resourceGroupName: msRest.OperationURLParameter = {
|
||||
parameterPath: 'resourceGroupName',
|
||||
mapper: {
|
||||
required: true,
|
||||
serializedName: 'resourceGroupName',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
}
|
||||
};
|
||||
export const workspaceName: msRest.OperationURLParameter = {
|
||||
parameterPath: 'workspaceName',
|
||||
mapper: {
|
||||
required: true,
|
||||
serializedName: 'workspaceName',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
}
|
||||
};
|
||||
export const workspace: msRest.OperationURLParameter = {
|
||||
parameterPath: 'workspace',
|
||||
mapper: {
|
||||
required: true,
|
||||
serializedName: 'workspace',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
}
|
||||
};
|
||||
export const resourceGroup: msRest.OperationURLParameter = {
|
||||
parameterPath: 'resourceGroup',
|
||||
mapper: {
|
||||
required: true,
|
||||
serializedName: 'resourceGroup',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
}
|
||||
};
|
||||
export const id: msRest.OperationURLParameter = {
|
||||
parameterPath: 'id',
|
||||
mapper: {
|
||||
required: true,
|
||||
serializedName: 'id',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
}
|
||||
};
|
||||
export const acceptLanguage: msRest.OperationParameter = {
|
||||
parameterPath: 'acceptLanguage',
|
||||
mapper: {
|
||||
serializedName: 'accept-language',
|
||||
defaultValue: 'en-US',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
}
|
||||
};
|
||||
export const apiVersion: msRest.OperationQueryParameter = {
|
||||
parameterPath: 'apiVersion',
|
||||
mapper: {
|
||||
required: true,
|
||||
serializedName: 'api-version',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
}
|
||||
};
|
||||
export const origin: msRest.OperationURLParameter = {
|
||||
parameterPath: 'origin',
|
||||
mapper: {
|
||||
required: true,
|
||||
serializedName: 'origin',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
}
|
||||
};
|
||||
export const container: msRest.OperationURLParameter = {
|
||||
parameterPath: 'container',
|
||||
mapper: {
|
||||
required: true,
|
||||
serializedName: 'container',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
}
|
||||
};
|
||||
export const projectName0: msRest.OperationQueryParameter = {
|
||||
parameterPath: [
|
||||
'options',
|
||||
'projectName'
|
||||
],
|
||||
mapper: {
|
||||
serializedName: 'projectName',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
}
|
||||
};
|
||||
export const path1: msRest.OperationQueryParameter = {
|
||||
parameterPath: [
|
||||
'options',
|
||||
'path'
|
||||
],
|
||||
mapper: {
|
||||
serializedName: 'path',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
}
|
||||
};
|
||||
export const accountName: msRest.OperationQueryParameter = {
|
||||
parameterPath: [
|
||||
'options',
|
||||
'accountName'
|
||||
],
|
||||
mapper: {
|
||||
serializedName: 'accountName',
|
||||
type: {
|
||||
name: 'String'
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Config } from '../configurations/config';
|
||||
import { QueryRunner } from '../common/queryRunner';
|
||||
import { RegisteredModel } from './interfaces';
|
||||
import { ModelImporter } from './modelImporter';
|
||||
|
||||
/**
|
||||
* Service to registered models
|
||||
*/
|
||||
export class RegisteredModelService {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor(
|
||||
private _apiWrapper: ApiWrapper,
|
||||
private _config: Config,
|
||||
private _queryRunner: QueryRunner,
|
||||
private _modelImporter: ModelImporter) {
|
||||
}
|
||||
|
||||
public async getRegisteredModels(): Promise<RegisteredModel[]> {
|
||||
let connection = await this.getCurrentConnection();
|
||||
let list: RegisteredModel[] = [];
|
||||
if (connection) {
|
||||
let result = await this.runRegisteredModelsListQuery(connection);
|
||||
if (result && result.rows && result.rows.length > 0) {
|
||||
result.rows.forEach(row => {
|
||||
list.push({
|
||||
id: +row[0].displayValue,
|
||||
name: row[1].displayValue
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public async registerLocalModel(filePath: string) {
|
||||
let connection = await this.getCurrentConnection();
|
||||
if (connection) {
|
||||
await this._modelImporter.registerModel(connection, filePath);
|
||||
}
|
||||
}
|
||||
|
||||
private async getCurrentConnection(): Promise<azdata.connection.ConnectionProfile> {
|
||||
return await this._apiWrapper.getCurrentConnection();
|
||||
}
|
||||
|
||||
private async runRegisteredModelsListQuery(connection: azdata.connection.ConnectionProfile): Promise<azdata.SimpleExecuteResult | undefined> {
|
||||
try {
|
||||
return await this._queryRunner.runQuery(connection, this.registeredModelsQuery(this._config.registeredModelDatabaseName, this._config.registeredModelTableName));
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private registeredModelsQuery(databaseName: string, tableName: string) {
|
||||
return `
|
||||
IF (EXISTS (SELECT name
|
||||
FROM master.dbo.sysdatabases
|
||||
WHERE ('[' + name + ']' = '${databaseName}'
|
||||
OR name = '${databaseName}')))
|
||||
BEGIN
|
||||
SELECT artifact_id, artifact_name, group_path, artifact_initial_size from ${databaseName}.${tableName}
|
||||
WHERE artifact_name like '%.onnx'
|
||||
END
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as msRest from '@azure/ms-rest-js';
|
||||
import { AzureMachineLearningWorkspacesContext } from '@azure/arm-machinelearningservices';
|
||||
import * as Models from './interfaces';
|
||||
import * as Mappers from './mappers';
|
||||
import * as Parameters from './parameters';
|
||||
|
||||
/**
|
||||
* Workspace models client
|
||||
*/
|
||||
export class WorkspaceModels {
|
||||
private readonly client: AzureMachineLearningWorkspacesContext;
|
||||
|
||||
constructor(client: AzureMachineLearningWorkspacesContext) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
listModels(resourceGroupName: string, workspaceName: string, options?: msRest.RequestOptionsBase): Promise<Models.ListWorkspaceModelsResult>;
|
||||
listModels(resourceGroupName: string, workspaceName: string, callback: msRest.ServiceCallback<Models.ListWorkspaceModelsResult>): void;
|
||||
listModels(resourceGroupName: string, workspaceName: string, options: msRest.RequestOptionsBase, callback: msRest.ServiceCallback<Models.ListWorkspaceModelsResult>): void;
|
||||
listModels(resourceGroupName: string, workspaceName: string, options?: msRest.RequestOptionsBase | msRest.ServiceCallback<Models.ListWorkspaceModelsResult>, callback?: msRest.ServiceCallback<Models.ListWorkspaceModelsResult>): Promise<Models.WorkspacesModelsResponse> {
|
||||
return this.client.sendOperationRequest(
|
||||
{
|
||||
resourceGroupName,
|
||||
workspaceName,
|
||||
options
|
||||
},
|
||||
listModelsOperationSpec,
|
||||
callback) as Promise<Models.WorkspacesModelsResponse>;
|
||||
}
|
||||
}
|
||||
|
||||
const serializer = new msRest.Serializer(Mappers);
|
||||
const listModelsOperationSpec: msRest.OperationSpec = {
|
||||
httpMethod: 'GET',
|
||||
path:
|
||||
'modelmanagement/v1.0/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.MachineLearningServices/workspaces/{workspaceName}/models',
|
||||
urlParameters: [
|
||||
Parameters.subscriptionId,
|
||||
Parameters.resourceGroupName,
|
||||
Parameters.workspaceName
|
||||
],
|
||||
queryParameters: [
|
||||
Parameters.apiVersion
|
||||
],
|
||||
headerParameters: [
|
||||
Parameters.acceptLanguage
|
||||
],
|
||||
responses: {
|
||||
200: {
|
||||
bodyMapper: Mappers.ListWorkspaceModelsResult
|
||||
},
|
||||
default: {
|
||||
bodyMapper: Mappers.MachineLearningServiceError
|
||||
}
|
||||
},
|
||||
serializer
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as should from 'should';
|
||||
import 'mocha';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import { createContext } from './utils';
|
||||
import { LanguageController } from '../../../externalLanguage/languageController';
|
||||
import { LanguageController } from '../../../views/externalLanguages/languageController';
|
||||
import * as mssql from '../../../../../mssql/src/mssql';
|
||||
|
||||
describe('External Languages Controller', () => {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ApiWrapper } from '../../../common/apiWrapper';
|
||||
import { LanguageViewBase } from '../../../views/externalLanguages/languageViewBase';
|
||||
import * as mssql from '../../../../../mssql/src/mssql';
|
||||
import { LanguageService } from '../../../externalLanguage/languageService';
|
||||
import { createViewContext } from '../utils';
|
||||
|
||||
export interface TestContext {
|
||||
apiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||
@@ -30,195 +31,11 @@ export class ParentDialog extends LanguageViewBase {
|
||||
}
|
||||
|
||||
export function createContext(): TestContext {
|
||||
let onClick: vscode.EventEmitter<any> = new vscode.EventEmitter<any>();
|
||||
|
||||
let apiWrapper = TypeMoq.Mock.ofType(ApiWrapper);
|
||||
let componentBase: azdata.Component = {
|
||||
id: '',
|
||||
updateProperties: () => Promise.resolve(),
|
||||
updateProperty: () => Promise.resolve(),
|
||||
updateCssStyles: undefined!,
|
||||
onValidityChanged: undefined!,
|
||||
valid: true,
|
||||
validate: undefined!,
|
||||
focus: undefined!
|
||||
};
|
||||
let button: azdata.ButtonComponent = Object.assign({}, componentBase, {
|
||||
onDidClick: onClick.event
|
||||
});
|
||||
let radioButton: azdata.RadioButtonComponent = Object.assign({}, componentBase, {
|
||||
onDidClick: onClick.event
|
||||
});
|
||||
let container = {
|
||||
clearItems: () => { },
|
||||
addItems: () => { },
|
||||
addItem: () => { },
|
||||
removeItem: () => true,
|
||||
insertItem: () => { },
|
||||
items: [],
|
||||
setLayout: () => { }
|
||||
};
|
||||
let form: azdata.FormContainer = Object.assign({}, componentBase, container, {
|
||||
});
|
||||
let flex: azdata.FlexContainer = Object.assign({}, componentBase, container, {
|
||||
});
|
||||
|
||||
let buttonBuilder: azdata.ComponentBuilder<azdata.ButtonComponent> = {
|
||||
component: () => button,
|
||||
withProperties: () => buttonBuilder,
|
||||
withValidation: () => buttonBuilder
|
||||
};
|
||||
let radioButtonBuilder: azdata.ComponentBuilder<azdata.ButtonComponent> = {
|
||||
component: () => radioButton,
|
||||
withProperties: () => radioButtonBuilder,
|
||||
withValidation: () => radioButtonBuilder
|
||||
};
|
||||
let inputBox: () => azdata.InputBoxComponent = () => Object.assign({}, componentBase, {
|
||||
onTextChanged: undefined!,
|
||||
onEnterKeyPressed: undefined!,
|
||||
value: ''
|
||||
});
|
||||
let declarativeTable: () => azdata.DeclarativeTableComponent = () => Object.assign({}, componentBase, {
|
||||
onDataChanged: undefined!,
|
||||
data: [],
|
||||
columns: []
|
||||
});
|
||||
|
||||
let loadingComponent: () => azdata.LoadingComponent = () => Object.assign({}, componentBase, {
|
||||
loading: false,
|
||||
component: undefined!
|
||||
});
|
||||
|
||||
let declarativeTableBuilder: azdata.ComponentBuilder<azdata.DeclarativeTableComponent> = {
|
||||
component: () => declarativeTable(),
|
||||
withProperties: () => declarativeTableBuilder,
|
||||
withValidation: () => declarativeTableBuilder
|
||||
};
|
||||
|
||||
let loadingBuilder: azdata.LoadingComponentBuilder = {
|
||||
component: () => loadingComponent(),
|
||||
withProperties: () => loadingBuilder,
|
||||
withValidation: () => loadingBuilder,
|
||||
withItem: () => loadingBuilder
|
||||
};
|
||||
|
||||
let formBuilder: azdata.FormBuilder = Object.assign({}, {
|
||||
component: () => form,
|
||||
addFormItem: () => { },
|
||||
insertFormItem: () => { },
|
||||
removeFormItem: () => true,
|
||||
addFormItems: () => { },
|
||||
withFormItems: () => formBuilder,
|
||||
withProperties: () => formBuilder,
|
||||
withValidation: () => formBuilder,
|
||||
withItems: () => formBuilder,
|
||||
withLayout: () => formBuilder
|
||||
});
|
||||
|
||||
let flexBuilder: azdata.FlexBuilder = Object.assign({}, {
|
||||
component: () => flex,
|
||||
withProperties: () => flexBuilder,
|
||||
withValidation: () => flexBuilder,
|
||||
withItems: () => flexBuilder,
|
||||
withLayout: () => flexBuilder
|
||||
});
|
||||
|
||||
let inputBoxBuilder: azdata.ComponentBuilder<azdata.InputBoxComponent> = {
|
||||
component: () => {
|
||||
let r = inputBox();
|
||||
return r;
|
||||
},
|
||||
withProperties: () => inputBoxBuilder,
|
||||
withValidation: () => inputBoxBuilder
|
||||
};
|
||||
|
||||
let view: azdata.ModelView = {
|
||||
onClosed: undefined!,
|
||||
connection: undefined!,
|
||||
serverInfo: undefined!,
|
||||
valid: true,
|
||||
onValidityChanged: undefined!,
|
||||
validate: undefined!,
|
||||
initializeModel: () => { return Promise.resolve(); },
|
||||
modelBuilder: {
|
||||
radioCardGroup: undefined!,
|
||||
navContainer: undefined!,
|
||||
divContainer: undefined!,
|
||||
flexContainer: () => flexBuilder,
|
||||
splitViewContainer: undefined!,
|
||||
dom: undefined!,
|
||||
card: undefined!,
|
||||
inputBox: () => inputBoxBuilder,
|
||||
checkBox: undefined!,
|
||||
radioButton: () => radioButtonBuilder,
|
||||
webView: undefined!,
|
||||
editor: undefined!,
|
||||
diffeditor: undefined!,
|
||||
text: () => inputBoxBuilder,
|
||||
image: undefined!,
|
||||
button: () => buttonBuilder,
|
||||
dropDown: undefined!,
|
||||
tree: undefined!,
|
||||
listBox: undefined!,
|
||||
table: undefined!,
|
||||
declarativeTable: () => declarativeTableBuilder,
|
||||
dashboardWidget: undefined!,
|
||||
dashboardWebview: undefined!,
|
||||
formContainer: () => formBuilder,
|
||||
groupContainer: undefined!,
|
||||
toolbarContainer: undefined!,
|
||||
loadingComponent: () => loadingBuilder,
|
||||
fileBrowserTree: undefined!,
|
||||
hyperlink: undefined!,
|
||||
separator: undefined!
|
||||
}
|
||||
};
|
||||
let tab: azdata.window.DialogTab = {
|
||||
title: '',
|
||||
content: '',
|
||||
registerContent: async (handler) => {
|
||||
try {
|
||||
await handler(view);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
},
|
||||
onValidityChanged: undefined!,
|
||||
valid: true,
|
||||
modelView: undefined!
|
||||
};
|
||||
|
||||
let dialogButton: azdata.window.Button = {
|
||||
label: '',
|
||||
enabled: true,
|
||||
hidden: false,
|
||||
onClick: onClick.event,
|
||||
|
||||
};
|
||||
let dialogMessage: azdata.window.DialogMessage = {
|
||||
text: '',
|
||||
};
|
||||
let dialog: azdata.window.Dialog = {
|
||||
title: '',
|
||||
isWide: false,
|
||||
content: [],
|
||||
okButton: dialogButton,
|
||||
cancelButton: dialogButton,
|
||||
customButtons: [],
|
||||
message: dialogMessage,
|
||||
registerCloseValidator: () => { },
|
||||
registerOperation: () => { },
|
||||
onValidityChanged: new vscode.EventEmitter<boolean>().event,
|
||||
registerContent: () => { },
|
||||
modelView: undefined!,
|
||||
valid: true
|
||||
};
|
||||
apiWrapper.setup(x => x.createTab(TypeMoq.It.isAny())).returns(() => tab);
|
||||
apiWrapper.setup(x => x.createModelViewDialog(TypeMoq.It.isAny())).returns(() => dialog);
|
||||
apiWrapper.setup(x => x.openDialog(TypeMoq.It.isAny())).returns(() => { });
|
||||
let viewTestContext = createViewContext();
|
||||
let connection = new azdata.connection.ConnectionProfile();
|
||||
apiWrapper.setup(x => x.getCurrentConnection()).returns(() => { return Promise.resolve(connection); });
|
||||
apiWrapper.setup(x => x.getUriForConnection(TypeMoq.It.isAny())).returns(() => { return Promise.resolve('connectionUrl'); });
|
||||
viewTestContext.apiWrapper.setup(x => x.getCurrentConnection()).returns(() => { return Promise.resolve(connection); });
|
||||
viewTestContext.apiWrapper.setup(x => x.getUriForConnection(TypeMoq.It.isAny())).returns(() => { return Promise.resolve('connectionUrl'); });
|
||||
|
||||
let languageExtensionService: mssql.ILanguageExtensionService = {
|
||||
listLanguages: () => { return Promise.resolve([]); },
|
||||
@@ -226,12 +43,11 @@ export function createContext(): TestContext {
|
||||
updateLanguage: () => { return Promise.resolve(); }
|
||||
};
|
||||
|
||||
|
||||
return {
|
||||
apiWrapper: apiWrapper,
|
||||
view: view,
|
||||
apiWrapper: viewTestContext.apiWrapper,
|
||||
view: viewTestContext.view,
|
||||
languageExtensionService: languageExtensionService,
|
||||
onClick: onClick,
|
||||
onClick: viewTestContext.onClick,
|
||||
dialogModel: TypeMoq.Mock.ofType(LanguageService)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 should from 'should';
|
||||
import 'mocha';
|
||||
import { createContext, ParentDialog } from './utils';
|
||||
import { AzureModelsComponent } from '../../../views/models/azureModelsComponent';
|
||||
import { ListAccountsEventName, ListSubscriptionsEventName, ListGroupsEventName, ListWorkspacesEventName, ListAzureModelsEventName } from '../../../views/models/modelViewBase';
|
||||
import { azureResource } from '../../../typings/azure-resource';
|
||||
import { Workspace } from '@azure/arm-machinelearningservices/esm/models';
|
||||
import { ViewBase } from '../../../views/viewBase';
|
||||
import { WorkspaceModel } from '../../../modelManagement/interfaces';
|
||||
|
||||
describe('Azure Models Component', () => {
|
||||
it('Should create view components successfully ', async function (): Promise<void> {
|
||||
let testContext = createContext();
|
||||
let parent = new ParentDialog(testContext.apiWrapper.object);
|
||||
|
||||
let view = new AzureModelsComponent(testContext.apiWrapper.object, parent);
|
||||
view.registerComponent(testContext.view.modelBuilder);
|
||||
should.notEqual(view.component, undefined);
|
||||
});
|
||||
|
||||
it('Should load data successfully ', async function (): Promise<void> {
|
||||
let testContext = createContext();
|
||||
let parent = new ParentDialog(testContext.apiWrapper.object);
|
||||
|
||||
let view = new AzureModelsComponent(testContext.apiWrapper.object, parent);
|
||||
view.registerComponent(testContext.view.modelBuilder);
|
||||
|
||||
let accounts: azdata.Account[] = [
|
||||
{
|
||||
key: {
|
||||
accountId: '1',
|
||||
providerId: ''
|
||||
},
|
||||
displayInfo: {
|
||||
displayName: 'account',
|
||||
userId: '',
|
||||
accountType: '',
|
||||
contextualDisplayName: ''
|
||||
},
|
||||
isStale: false,
|
||||
properties: []
|
||||
}
|
||||
];
|
||||
let subscriptions: azureResource.AzureResourceSubscription[] = [
|
||||
{
|
||||
name: 'subscription',
|
||||
id: '2'
|
||||
}
|
||||
];
|
||||
let groups: azureResource.AzureResourceResourceGroup[] = [
|
||||
{
|
||||
name: 'group',
|
||||
id: '3'
|
||||
}
|
||||
];
|
||||
let workspaces: Workspace[] = [
|
||||
{
|
||||
name: 'workspace',
|
||||
id: '4'
|
||||
}
|
||||
];
|
||||
let models: WorkspaceModel[] = [
|
||||
{
|
||||
id: '5',
|
||||
name: 'model'
|
||||
}
|
||||
];
|
||||
parent.on(ListAccountsEventName, () => {
|
||||
parent.sendCallbackRequest(ViewBase.getCallbackEventName(ListAccountsEventName), { data: accounts });
|
||||
});
|
||||
parent.on(ListSubscriptionsEventName, () => {
|
||||
|
||||
parent.sendCallbackRequest(ViewBase.getCallbackEventName(ListSubscriptionsEventName), { data: subscriptions });
|
||||
});
|
||||
parent.on(ListGroupsEventName, () => {
|
||||
parent.sendCallbackRequest(ViewBase.getCallbackEventName(ListGroupsEventName), { data: groups });
|
||||
});
|
||||
parent.on(ListWorkspacesEventName, () => {
|
||||
parent.sendCallbackRequest(ViewBase.getCallbackEventName(ListWorkspacesEventName), { data: workspaces });
|
||||
});
|
||||
parent.on(ListAzureModelsEventName, () => {
|
||||
parent.sendCallbackRequest(ViewBase.getCallbackEventName(ListAzureModelsEventName), { data: models });
|
||||
});
|
||||
await view.refresh();
|
||||
testContext.onClick.fire();
|
||||
should.notEqual(view.data, undefined);
|
||||
should.deepEqual(view.data?.account, accounts[0]);
|
||||
should.deepEqual(view.data?.subscription, subscriptions[0]);
|
||||
should.deepEqual(view.data?.group, groups[0]);
|
||||
should.deepEqual(view.data?.workspace, workspaces[0]);
|
||||
should.deepEqual(view.data?.model, models[0]);
|
||||
});
|
||||
});
|
||||
@@ -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 * as should from 'should';
|
||||
import 'mocha';
|
||||
import { createContext } from './utils';
|
||||
import { ListModelsEventName, ListAccountsEventName, ListSubscriptionsEventName, ListGroupsEventName, ListWorkspacesEventName, ListAzureModelsEventName } from '../../../views/models/modelViewBase';
|
||||
import { RegisteredModel } from '../../../modelManagement/interfaces';
|
||||
import { azureResource } from '../../../typings/azure-resource';
|
||||
import { Workspace } from '@azure/arm-machinelearningservices/esm/models';
|
||||
import { ViewBase } from '../../../views/viewBase';
|
||||
import { WorkspaceModel } from '../../../modelManagement/interfaces';
|
||||
import { RegisterModelWizard } from '../../../views/models/registerModelWizard';
|
||||
|
||||
describe('Register Model Wizard', () => {
|
||||
it('Should create view components successfully ', async function (): Promise<void> {
|
||||
let testContext = createContext();
|
||||
|
||||
let view = new RegisterModelWizard(testContext.apiWrapper.object, '');
|
||||
view.open();
|
||||
|
||||
should.notEqual(view.wizardView, undefined);
|
||||
should.notEqual(view.localModelsComponent, undefined);
|
||||
should.notEqual(view.azureModelsComponent, undefined);
|
||||
should.notEqual(view.modelResources, undefined);
|
||||
});
|
||||
|
||||
it('Should load data successfully ', async function (): Promise<void> {
|
||||
let testContext = createContext();
|
||||
|
||||
let view = new RegisterModelWizard(testContext.apiWrapper.object, '');
|
||||
view.open();
|
||||
let accounts: azdata.Account[] = [
|
||||
{
|
||||
key: {
|
||||
accountId: '1',
|
||||
providerId: ''
|
||||
},
|
||||
displayInfo: {
|
||||
displayName: 'account',
|
||||
userId: '',
|
||||
accountType: '',
|
||||
contextualDisplayName: ''
|
||||
},
|
||||
isStale: false,
|
||||
properties: []
|
||||
}
|
||||
];
|
||||
let subscriptions: azureResource.AzureResourceSubscription[] = [
|
||||
{
|
||||
name: 'subscription',
|
||||
id: '2'
|
||||
}
|
||||
];
|
||||
let groups: azureResource.AzureResourceResourceGroup[] = [
|
||||
{
|
||||
name: 'group',
|
||||
id: '3'
|
||||
}
|
||||
];
|
||||
let workspaces: Workspace[] = [
|
||||
{
|
||||
name: 'workspace',
|
||||
id: '4'
|
||||
}
|
||||
];
|
||||
let models: WorkspaceModel[] = [
|
||||
{
|
||||
id: '5',
|
||||
name: 'model'
|
||||
}
|
||||
];
|
||||
let localModels: RegisteredModel[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'model'
|
||||
}
|
||||
];
|
||||
view.on(ListModelsEventName, () => {
|
||||
view.sendCallbackRequest(ViewBase.getCallbackEventName(ListModelsEventName), { data: localModels });
|
||||
});
|
||||
view.on(ListAccountsEventName, () => {
|
||||
view.sendCallbackRequest(ViewBase.getCallbackEventName(ListAccountsEventName), { data: accounts });
|
||||
});
|
||||
view.on(ListSubscriptionsEventName, () => {
|
||||
|
||||
view.sendCallbackRequest(ViewBase.getCallbackEventName(ListSubscriptionsEventName), { data: subscriptions });
|
||||
});
|
||||
view.on(ListGroupsEventName, () => {
|
||||
view.sendCallbackRequest(ViewBase.getCallbackEventName(ListGroupsEventName), { data: groups });
|
||||
});
|
||||
view.on(ListWorkspacesEventName, () => {
|
||||
view.sendCallbackRequest(ViewBase.getCallbackEventName(ListWorkspacesEventName), { data: workspaces });
|
||||
});
|
||||
view.on(ListAzureModelsEventName, () => {
|
||||
view.sendCallbackRequest(ViewBase.getCallbackEventName(ListAzureModelsEventName), { data: models });
|
||||
});
|
||||
await view.refresh();
|
||||
});
|
||||
});
|
||||
@@ -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 should from 'should';
|
||||
import 'mocha';
|
||||
import { createContext } from './utils';
|
||||
import { RegisteredModelsDialog } from '../../../views/models/registeredModelsDialog';
|
||||
import { ListModelsEventName } from '../../../views/models/modelViewBase';
|
||||
import { RegisteredModel } from '../../../modelManagement/interfaces';
|
||||
import { ViewBase } from '../../../views/viewBase';
|
||||
|
||||
describe('Registered Models Dialog', () => {
|
||||
it('Should create view components successfully ', async function (): Promise<void> {
|
||||
let testContext = createContext();
|
||||
|
||||
let view = new RegisteredModelsDialog(testContext.apiWrapper.object, '');
|
||||
view.open();
|
||||
|
||||
should.notEqual(view.dialogView, undefined);
|
||||
should.notEqual(view.currentLanguagesTab, undefined);
|
||||
});
|
||||
|
||||
it('Should load data successfully ', async function (): Promise<void> {
|
||||
let testContext = createContext();
|
||||
|
||||
let view = new RegisteredModelsDialog(testContext.apiWrapper.object, '');
|
||||
view.open();
|
||||
let models: RegisteredModel[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'model'
|
||||
}
|
||||
];
|
||||
view.on(ListModelsEventName, () => {
|
||||
view.sendCallbackRequest(ViewBase.getCallbackEventName(ListModelsEventName), { data: models });
|
||||
});
|
||||
await view.refresh();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 TypeMoq from 'typemoq';
|
||||
import { ApiWrapper } from '../../../common/apiWrapper';
|
||||
import * as mssql from '../../../../../mssql/src/mssql';
|
||||
import { createViewContext } from '../utils';
|
||||
import { ModelViewBase } from '../../../views/models/modelViewBase';
|
||||
|
||||
export interface TestContext {
|
||||
apiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||
view: azdata.ModelView;
|
||||
languageExtensionService: mssql.ILanguageExtensionService;
|
||||
onClick: vscode.EventEmitter<any>;
|
||||
}
|
||||
|
||||
export class ParentDialog extends ModelViewBase {
|
||||
public refresh(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
public reset(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
constructor(
|
||||
apiWrapper: ApiWrapper) {
|
||||
super(apiWrapper, '');
|
||||
}
|
||||
}
|
||||
|
||||
export function createContext(): TestContext {
|
||||
|
||||
let viewTestContext = createViewContext();
|
||||
let languageExtensionService: mssql.ILanguageExtensionService = {
|
||||
listLanguages: () => { return Promise.resolve([]); },
|
||||
deleteLanguage: () => { return Promise.resolve(); },
|
||||
updateLanguage: () => { return Promise.resolve(); }
|
||||
};
|
||||
|
||||
return {
|
||||
apiWrapper: viewTestContext.apiWrapper,
|
||||
view: viewTestContext.view,
|
||||
languageExtensionService: languageExtensionService,
|
||||
onClick: viewTestContext.onClick
|
||||
};
|
||||
}
|
||||
261
extensions/machine-learning-services/src/test/views/utils.ts
Normal file
261
extensions/machine-learning-services/src/test/views/utils.ts
Normal file
@@ -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 TypeMoq from 'typemoq';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
|
||||
export interface ViewTestContext {
|
||||
apiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||
view: azdata.ModelView;
|
||||
onClick: vscode.EventEmitter<any>;
|
||||
}
|
||||
|
||||
export function createViewContext(): ViewTestContext {
|
||||
let onClick: vscode.EventEmitter<any> = new vscode.EventEmitter<any>();
|
||||
|
||||
let apiWrapper = TypeMoq.Mock.ofType(ApiWrapper);
|
||||
let componentBase: azdata.Component = {
|
||||
id: '',
|
||||
updateProperties: () => Promise.resolve(),
|
||||
updateProperty: () => Promise.resolve(),
|
||||
updateCssStyles: undefined!,
|
||||
onValidityChanged: undefined!,
|
||||
valid: true,
|
||||
validate: undefined!,
|
||||
focus: undefined!
|
||||
};
|
||||
let button: azdata.ButtonComponent = Object.assign({}, componentBase, {
|
||||
onDidClick: onClick.event
|
||||
});
|
||||
let radioButton: azdata.RadioButtonComponent = Object.assign({}, componentBase, {
|
||||
onDidClick: onClick.event
|
||||
});
|
||||
let container = {
|
||||
clearItems: () => { },
|
||||
addItems: () => { },
|
||||
addItem: () => { },
|
||||
removeItem: () => true,
|
||||
insertItem: () => { },
|
||||
items: [],
|
||||
setLayout: () => { }
|
||||
};
|
||||
let form: azdata.FormContainer = Object.assign({}, componentBase, container, {
|
||||
});
|
||||
let flex: azdata.FlexContainer = Object.assign({}, componentBase, container, {
|
||||
});
|
||||
|
||||
let buttonBuilder: azdata.ComponentBuilder<azdata.ButtonComponent> = {
|
||||
component: () => button,
|
||||
withProperties: () => buttonBuilder,
|
||||
withValidation: () => buttonBuilder
|
||||
};
|
||||
let radioButtonBuilder: azdata.ComponentBuilder<azdata.ButtonComponent> = {
|
||||
component: () => radioButton,
|
||||
withProperties: () => radioButtonBuilder,
|
||||
withValidation: () => radioButtonBuilder
|
||||
};
|
||||
let inputBox: () => azdata.InputBoxComponent = () => Object.assign({}, componentBase, {
|
||||
onTextChanged: undefined!,
|
||||
onEnterKeyPressed: undefined!,
|
||||
value: ''
|
||||
});
|
||||
let dropdown: () => azdata.DropDownComponent = () => Object.assign({}, componentBase, {
|
||||
onValueChanged: onClick.event,
|
||||
value: {
|
||||
name: '',
|
||||
displayName: ''
|
||||
},
|
||||
values: []
|
||||
});
|
||||
let declarativeTable: () => azdata.DeclarativeTableComponent = () => Object.assign({}, componentBase, {
|
||||
onDataChanged: undefined!,
|
||||
data: [],
|
||||
columns: []
|
||||
});
|
||||
|
||||
let loadingComponent: () => azdata.LoadingComponent = () => Object.assign({}, componentBase, {
|
||||
loading: false,
|
||||
component: undefined!
|
||||
});
|
||||
|
||||
let declarativeTableBuilder: azdata.ComponentBuilder<azdata.DeclarativeTableComponent> = {
|
||||
component: () => declarativeTable(),
|
||||
withProperties: () => declarativeTableBuilder,
|
||||
withValidation: () => declarativeTableBuilder
|
||||
};
|
||||
|
||||
let loadingBuilder: azdata.LoadingComponentBuilder = {
|
||||
component: () => loadingComponent(),
|
||||
withProperties: () => loadingBuilder,
|
||||
withValidation: () => loadingBuilder,
|
||||
withItem: () => loadingBuilder
|
||||
};
|
||||
|
||||
let formBuilder: azdata.FormBuilder = Object.assign({}, {
|
||||
component: () => form,
|
||||
addFormItem: () => { },
|
||||
insertFormItem: () => { },
|
||||
removeFormItem: () => true,
|
||||
addFormItems: () => { },
|
||||
withFormItems: () => formBuilder,
|
||||
withProperties: () => formBuilder,
|
||||
withValidation: () => formBuilder,
|
||||
withItems: () => formBuilder,
|
||||
withLayout: () => formBuilder
|
||||
});
|
||||
|
||||
let flexBuilder: azdata.FlexBuilder = Object.assign({}, {
|
||||
component: () => flex,
|
||||
withProperties: () => flexBuilder,
|
||||
withValidation: () => flexBuilder,
|
||||
withItems: () => flexBuilder,
|
||||
withLayout: () => flexBuilder
|
||||
});
|
||||
|
||||
let inputBoxBuilder: azdata.ComponentBuilder<azdata.InputBoxComponent> = {
|
||||
component: () => {
|
||||
let r = inputBox();
|
||||
return r;
|
||||
},
|
||||
withProperties: () => inputBoxBuilder,
|
||||
withValidation: () => inputBoxBuilder
|
||||
};
|
||||
let dropdownBuilder: azdata.ComponentBuilder<azdata.DropDownComponent> = {
|
||||
component: () => {
|
||||
let r = dropdown();
|
||||
return r;
|
||||
},
|
||||
withProperties: () => dropdownBuilder,
|
||||
withValidation: () => dropdownBuilder
|
||||
};
|
||||
|
||||
let view: azdata.ModelView = {
|
||||
onClosed: undefined!,
|
||||
connection: undefined!,
|
||||
serverInfo: undefined!,
|
||||
valid: true,
|
||||
onValidityChanged: undefined!,
|
||||
validate: undefined!,
|
||||
initializeModel: () => { return Promise.resolve(); },
|
||||
modelBuilder: {
|
||||
radioCardGroup: undefined!,
|
||||
navContainer: undefined!,
|
||||
divContainer: undefined!,
|
||||
flexContainer: () => flexBuilder,
|
||||
splitViewContainer: undefined!,
|
||||
dom: undefined!,
|
||||
card: undefined!,
|
||||
inputBox: () => inputBoxBuilder,
|
||||
checkBox: undefined!,
|
||||
radioButton: () => radioButtonBuilder,
|
||||
webView: undefined!,
|
||||
editor: undefined!,
|
||||
diffeditor: undefined!,
|
||||
text: () => inputBoxBuilder,
|
||||
image: undefined!,
|
||||
button: () => buttonBuilder,
|
||||
dropDown: () => dropdownBuilder,
|
||||
tree: undefined!,
|
||||
listBox: undefined!,
|
||||
table: undefined!,
|
||||
declarativeTable: () => declarativeTableBuilder,
|
||||
dashboardWidget: undefined!,
|
||||
dashboardWebview: undefined!,
|
||||
formContainer: () => formBuilder,
|
||||
groupContainer: undefined!,
|
||||
toolbarContainer: undefined!,
|
||||
loadingComponent: () => loadingBuilder,
|
||||
fileBrowserTree: undefined!,
|
||||
hyperlink: undefined!,
|
||||
separator: undefined!
|
||||
}
|
||||
};
|
||||
let tab: azdata.window.DialogTab = {
|
||||
title: '',
|
||||
content: '',
|
||||
registerContent: async (handler) => {
|
||||
try {
|
||||
await handler(view);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
},
|
||||
onValidityChanged: undefined!,
|
||||
valid: true,
|
||||
modelView: undefined!
|
||||
};
|
||||
|
||||
let dialogButton: azdata.window.Button = {
|
||||
label: '',
|
||||
enabled: true,
|
||||
hidden: false,
|
||||
onClick: onClick.event,
|
||||
|
||||
};
|
||||
let dialogMessage: azdata.window.DialogMessage = {
|
||||
text: '',
|
||||
};
|
||||
let dialog: azdata.window.Dialog = {
|
||||
title: '',
|
||||
isWide: false,
|
||||
content: [],
|
||||
okButton: dialogButton,
|
||||
cancelButton: dialogButton,
|
||||
customButtons: [],
|
||||
message: dialogMessage,
|
||||
registerCloseValidator: () => { },
|
||||
registerOperation: () => { },
|
||||
onValidityChanged: new vscode.EventEmitter<boolean>().event,
|
||||
registerContent: () => { },
|
||||
modelView: undefined!,
|
||||
valid: true
|
||||
};
|
||||
let wizard: azdata.window.Wizard = {
|
||||
title: '',
|
||||
pages: [],
|
||||
currentPage: 0,
|
||||
doneButton: dialogButton,
|
||||
cancelButton: dialogButton,
|
||||
generateScriptButton: dialogButton,
|
||||
nextButton: dialogButton,
|
||||
backButton: dialogButton,
|
||||
customButtons: [],
|
||||
displayPageTitles: true,
|
||||
onPageChanged: onClick.event,
|
||||
addPage: () => { return Promise.resolve(); },
|
||||
removePage: () => { return Promise.resolve(); },
|
||||
setCurrentPage: () => { return Promise.resolve(); },
|
||||
open: () => { return Promise.resolve(); },
|
||||
close: () => { return Promise.resolve(); },
|
||||
registerNavigationValidator: () => { },
|
||||
message: dialogMessage,
|
||||
registerOperation: () => { }
|
||||
};
|
||||
let wizardPage: azdata.window.WizardPage = {
|
||||
title: '',
|
||||
content: '',
|
||||
customButtons: [],
|
||||
enabled: true,
|
||||
description: '',
|
||||
onValidityChanged: onClick.event,
|
||||
registerContent: () => { },
|
||||
modelView: undefined!,
|
||||
valid: true
|
||||
};
|
||||
apiWrapper.setup(x => x.createTab(TypeMoq.It.isAny())).returns(() => tab);
|
||||
apiWrapper.setup(x => x.createWizard(TypeMoq.It.isAny())).returns(() => wizard);
|
||||
apiWrapper.setup(x => x.createWizardPage(TypeMoq.It.isAny())).returns(() => wizardPage);
|
||||
apiWrapper.setup(x => x.createModelViewDialog(TypeMoq.It.isAny())).returns(() => dialog);
|
||||
apiWrapper.setup(x => x.openDialog(TypeMoq.It.isAny())).returns(() => { });
|
||||
|
||||
return {
|
||||
apiWrapper: apiWrapper,
|
||||
view: view,
|
||||
onClick: onClick,
|
||||
};
|
||||
}
|
||||
|
||||
28
extensions/machine-learning-services/src/typings/azure-resource.d.ts
vendored
Normal file
28
extensions/machine-learning-services/src/typings/azure-resource.d.ts
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Account } from 'azdata';
|
||||
import * as msRest from '@azure/ms-rest-js';
|
||||
|
||||
export namespace azureResource {
|
||||
|
||||
export interface AzureResource {
|
||||
name: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface AzureResourceSubscription extends AzureResource {
|
||||
}
|
||||
|
||||
export interface AzureResourceResourceGroup extends AzureResource {
|
||||
}
|
||||
|
||||
export interface IAzureResourceService<T extends AzureResource> {
|
||||
getResources(subscription: AzureResourceSubscription, credential: msRest.ServiceClientCredentials): Promise<T[]>;
|
||||
}
|
||||
|
||||
export type GetSubscriptionsResult = { subscriptions: AzureResourceSubscription[], errors: Error[] };
|
||||
export type GetResourceGroupsResult = { resourceGroups: AzureResourceResourceGroup[], errors: Error[] };
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
* 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 './languageService';
|
||||
import { LanguagesDialog } from '../views/externalLanguages/languagesDialog';
|
||||
import { LanguageEditDialog } from '../views/externalLanguages/languageEditDialog';
|
||||
import { FileBrowserDialog } from '../views/externalLanguages/fileBrowserDialog';
|
||||
import { LanguageViewBase, LanguageUpdateModel } from '../views/externalLanguages/languageViewBase';
|
||||
import * as constants from '../common/constants';
|
||||
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 {
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,37 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@azure/arm-machinelearningservices@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@azure/arm-machinelearningservices/-/arm-machinelearningservices-3.0.0.tgz#02fd0f98bbc24e75aa3fd384fe5125af983d47af"
|
||||
integrity sha512-An0/K+fay1/fMmn/hW/GNHFiaaGuRs8KRUfJLiOxQkBaSzafYuH2N159lUY+okVvo0JKrhEaOQ1+iQ7G9apZQg==
|
||||
dependencies:
|
||||
"@azure/ms-rest-azure-js" "^1.3.2"
|
||||
"@azure/ms-rest-js" "^1.8.1"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@azure/ms-rest-azure-js@^1.3.2":
|
||||
version "1.3.8"
|
||||
resolved "https://registry.yarnpkg.com/@azure/ms-rest-azure-js/-/ms-rest-azure-js-1.3.8.tgz#96b518223d3baa2496b2981bc07288b3d887486e"
|
||||
integrity sha512-AHLfDTCyIH6wBK6+CpImI6sc9mLZ17ZgUrTx3Rhwv+3Mb3Z73BxormkarfR6Stb6scrBYitxJ27FXyndXlGAYg==
|
||||
dependencies:
|
||||
"@azure/ms-rest-js" "^1.8.10"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@azure/ms-rest-js@^1.8.1", "@azure/ms-rest-js@^1.8.10":
|
||||
version "1.8.14"
|
||||
resolved "https://registry.yarnpkg.com/@azure/ms-rest-js/-/ms-rest-js-1.8.14.tgz#657fc145db20b6eb3d58d1a2055473aa72eb609d"
|
||||
integrity sha512-IrCPN22c8RbKWA06ZXuFwwEb15cSnr0zZ6J8Fspp9ns1SSNTERf7hv+gWvTIis1FlwHy42Mfk8hVu0/r3a0AWA==
|
||||
dependencies:
|
||||
"@types/tunnel" "0.0.0"
|
||||
axios "^0.19.0"
|
||||
form-data "^2.3.2"
|
||||
tough-cookie "^2.4.3"
|
||||
tslib "^1.9.2"
|
||||
tunnel "0.0.6"
|
||||
uuid "^3.2.1"
|
||||
xml2js "^0.4.19"
|
||||
|
||||
"@babel/code-frame@^7.0.0":
|
||||
version "7.8.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e"
|
||||
@@ -117,6 +148,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.6.tgz#c880579e087d7a0db13777ff8af689f4ffc7b0d5"
|
||||
integrity sha512-wHNBMnkoEBiRAd3s8KTKwIuO9biFtTf0LehITzBhSco+HQI0xkXZbLOD55SW3Aqw3oUkHstkm5SPv58yaAdFPQ==
|
||||
|
||||
"@types/tunnel@0.0.0":
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/tunnel/-/tunnel-0.0.0.tgz#c2a42943ee63c90652a5557b8c4e56cda77f944e"
|
||||
integrity sha512-FGDp0iBRiBdPjOgjJmn1NH0KDLN+Z8fRmo+9J7XGBhubq1DPrGrbmG4UTlGzrpbCpesMqD0sWkzi27EYkOMHyg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
abbrev@1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
||||
@@ -410,6 +448,13 @@ aws4@^1.8.0:
|
||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
||||
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
|
||||
|
||||
axios@^0.19.0:
|
||||
version "0.19.2"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
|
||||
integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==
|
||||
dependencies:
|
||||
follow-redirects "1.5.10"
|
||||
|
||||
bach@^1.0.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/bach/-/bach-1.2.0.tgz#4b3ce96bf27134f79a1b414a51c14e34c3bd9880"
|
||||
@@ -820,7 +865,7 @@ debug-fabulous@1.X:
|
||||
memoizee "0.4.X"
|
||||
object-assign "4.X"
|
||||
|
||||
debug@3.1.0:
|
||||
debug@3.1.0, debug@=3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
||||
@@ -1311,6 +1356,13 @@ flush-write-stream@^1.0.2:
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^2.3.6"
|
||||
|
||||
follow-redirects@1.5.10:
|
||||
version "1.5.10"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
|
||||
integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
|
||||
dependencies:
|
||||
debug "=3.1.0"
|
||||
|
||||
for-in@^1.0.1, for-in@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||
@@ -1328,7 +1380,7 @@ forever-agent@~0.6.1:
|
||||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
|
||||
|
||||
form-data@^2.5.0:
|
||||
form-data@^2.3.2, form-data@^2.5.0:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
|
||||
integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==
|
||||
@@ -2866,6 +2918,11 @@ pm-mocha-jenkins-reporter@^0.2.6:
|
||||
mkdirp "0.5.0"
|
||||
mocha ">=2.0.0"
|
||||
|
||||
polly-js@^1.6.3:
|
||||
version "1.6.5"
|
||||
resolved "https://registry.yarnpkg.com/polly-js/-/polly-js-1.6.5.tgz#78ebd7e87516eddd5da51db34290b6b8e3332aac"
|
||||
integrity sha512-gGouufZpvrYBAeGkLJfty/89hAvFYia3lCxufMj066+aT9ZnR1Edfn/cAkY71Y22EcMPGfIKV4Z4iyi/+YauwQ==
|
||||
|
||||
posix-character-classes@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
|
||||
@@ -2901,6 +2958,11 @@ psl@^1.1.24:
|
||||
resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184"
|
||||
integrity sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==
|
||||
|
||||
psl@^1.1.28:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c"
|
||||
integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==
|
||||
|
||||
pump@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
|
||||
@@ -2923,7 +2985,7 @@ punycode@^1.4.1:
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
||||
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
|
||||
|
||||
punycode@^2.1.0:
|
||||
punycode@^2.1.0, punycode@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
@@ -3203,6 +3265,11 @@ safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
|
||||
sax@>=0.6.0:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
|
||||
|
||||
semver-greatest-satisfied-range@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b"
|
||||
@@ -3679,6 +3746,14 @@ to-through@^2.0.0:
|
||||
dependencies:
|
||||
through2 "^2.0.3"
|
||||
|
||||
tough-cookie@^2.4.3:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
|
||||
integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
|
||||
dependencies:
|
||||
psl "^1.1.28"
|
||||
punycode "^2.1.1"
|
||||
|
||||
tough-cookie@~2.4.3:
|
||||
version "2.4.3"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
|
||||
@@ -3687,7 +3762,7 @@ tough-cookie@~2.4.3:
|
||||
psl "^1.1.24"
|
||||
punycode "^1.4.1"
|
||||
|
||||
tslib@^1.8.0, tslib@^1.8.1:
|
||||
tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.2, tslib@^1.9.3:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
||||
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
|
||||
@@ -3739,6 +3814,11 @@ tunnel-agent@^0.6.0:
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
tunnel@0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
|
||||
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
|
||||
|
||||
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
||||
version "0.14.5"
|
||||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
||||
@@ -3861,6 +3941,11 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||
|
||||
uuid@^3.2.1:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
||||
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
||||
|
||||
uuid@^3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
||||
@@ -4054,11 +4139,24 @@ wrappy@1:
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||
|
||||
xml2js@^0.4.19:
|
||||
version "0.4.23"
|
||||
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
|
||||
integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==
|
||||
dependencies:
|
||||
sax ">=0.6.0"
|
||||
xmlbuilder "~11.0.0"
|
||||
|
||||
xml@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"
|
||||
integrity sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=
|
||||
|
||||
xmlbuilder@~11.0.0:
|
||||
version "11.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
|
||||
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
|
||||
|
||||
xtend@~4.0.0, xtend@~4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||
|
||||
Reference in New Issue
Block a user