MLS - Changed the dashboard to match the design (#9905)
* Machine Learning Extension - Changed the dashboard to match the design
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32"><defs><style>.cls-1{fill:#fff;}.cls-2{mask:url(#mask);}.cls-3{fill:url(#linear-gradient);}.cls-4{fill:url(#linear-gradient-2);}.cls-5{fill:url(#linear-gradient-3);}.cls-6{fill:#9cebff;}.cls-7{fill:url(#linear-gradient-4);}.cls-8{fill:url(#linear-gradient-5);}.cls-9{fill:none;}</style><mask id="mask" x="3.556" y="-6.4" width="37.926" height="38.4" maskUnits="userSpaceOnUse"><g id="mask0"><path id="Union" class="cls-1" d="M39.9-6.4a1.59,1.59,0,0,1,1.58,1.6V10.4A1.59,1.59,0,0,1,39.9,12H31.21V30.4A1.59,1.59,0,0,1,29.63,32H5.136a1.59,1.59,0,0,1-1.58-1.6V1.6A1.59,1.59,0,0,1,5.136,0H18.568V-4.8a1.59,1.59,0,0,1,1.58-1.6Z"/></g></mask><linearGradient id="linear-gradient" x1="17.383" y1="32" x2="17.383" gradientUnits="userSpaceOnUse"><stop offset="0.243" stop-color="#0078d7"/><stop offset="1" stop-color="#5ea0ef"/></linearGradient><linearGradient id="linear-gradient-2" x1="3.556" y1="20.543" x2="3.556" y2="18.173" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#a3a3a3"/><stop offset="1" stop-color="#e6e6e6"/></linearGradient><linearGradient id="linear-gradient-3" x1="3.556" y1="27.259" x2="3.556" y2="24.889" xlink:href="#linear-gradient-2"/><linearGradient id="linear-gradient-4" x1="3.556" y1="7.111" x2="3.556" y2="4.741" xlink:href="#linear-gradient-2"/><linearGradient id="linear-gradient-5" x1="3.556" y1="13.827" x2="3.556" y2="11.457" xlink:href="#linear-gradient-2"/></defs><title>j_</title><g class="cls-2"><path id="Rectangle" class="cls-3" d="M3.556,1.6A1.59,1.59,0,0,1,5.136,0H29.63a1.59,1.59,0,0,1,1.58,1.6V30.4A1.59,1.59,0,0,1,29.63,32H5.136a1.59,1.59,0,0,1-1.58-1.6Z"/></g><g id="Union_2" data-name="Union 2"><path class="cls-4" d="M5.315,18.173H1.8a.918.918,0,0,0-1.006.79v.79a.918.918,0,0,0,1.006.79H5.315a.918.918,0,0,0,1.006-.79v-.79A.918.918,0,0,0,5.315,18.173Z"/></g><g id="Union_2-2" data-name="Union 2"><path class="cls-5" d="M5.315,24.889H1.8a.918.918,0,0,0-1.006.79v.79a.917.917,0,0,0,1.006.79H5.315a.917.917,0,0,0,1.006-.79v-.79A.918.918,0,0,0,5.315,24.889Z"/></g><g id="Union_2-3" data-name="Union 2"><path class="cls-6" d="M24.873,4.608H11.661A.736.736,0,0,0,11,5.4v4.667a.736.736,0,0,0,.666.79H24.873a.736.736,0,0,0,.666-.79V5.4A.736.736,0,0,0,24.873,4.608Z"/></g><g id="Union_2-4" data-name="Union 2"><path class="cls-7" d="M5.315,4.741H1.8a.917.917,0,0,0-1.006.79v.79a.918.918,0,0,0,1.006.79H5.315a.918.918,0,0,0,1.006-.79v-.79A.917.917,0,0,0,5.315,4.741Z"/></g><g id="Union_2-5" data-name="Union 2"><path class="cls-8" d="M5.315,11.457H1.8a.918.918,0,0,0-1.006.79v.79a.918.918,0,0,0,1.006.79H5.315a.918.918,0,0,0,1.006-.79v-.79A.918.918,0,0,0,5.315,11.457Z"/></g><rect class="cls-9" width="32" height="32"/></svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16 8H15V16H5V8H4V5H10V3H2V10H5V11H1V3H0V0H12V3H11V5H16V8ZM1 2H11V1H1V2ZM14 8H6V15H14V8ZM15 6H5V7H15V6ZM11 12H7V11H11V12ZM7 14V13H10V14H7Z" fill="#0078D4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 269 B |
3
extensions/machine-learning-services/images/linkIcon.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 6H11V11H0V0H5V1H1V10H10V6ZM11 0V5H10V1.71094L5.35156 6.35156L4.64844 5.64844L9.28906 1H6V0H11Z" fill="#0078D4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 230 B |
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:none;}</style></defs><title>v_</title><path d="M14,12.885h2v.994H14v1.988H13V13.879H11v-.994h2V10.9h1Z"/><path d="M3.648,3.587l.7.7L1.711,6.919,4.352,9.552l-.7.7L.289,6.919Zm8.7,6.664-.7-.7,2.641-2.633L11.648,4.286l.7-.7,3.359,3.332ZM9.477.954h1.046l-4,11.931H5.477Z"/><rect class="cls-1" width="16" height="16"/></svg>
|
||||
|
After Width: | Height: | Size: 438 B |
9
extensions/machine-learning-services/images/video1.svg
Normal file
|
After Width: | Height: | Size: 101 KiB |
9
extensions/machine-learning-services/images/video2.svg
Normal file
|
After Width: | Height: | Size: 107 KiB |
@@ -55,7 +55,11 @@
|
||||
"commands": [
|
||||
{
|
||||
"command": "mls.command.managePackages",
|
||||
"title": "%mls.command.managePackages%"
|
||||
"title": "%mls.command.managePackages%",
|
||||
"icon": {
|
||||
"light": "./images/installPackages.svg",
|
||||
"dark": "./images/installPackages.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "mls.command.predictModel",
|
||||
@@ -70,8 +74,8 @@
|
||||
"title": "%mls.command.manageModels%"
|
||||
},
|
||||
{
|
||||
"command": "mls.command.registerModel",
|
||||
"title": "%mls.command.registerModel%",
|
||||
"command": "mls.command.importModel",
|
||||
"title": "%mls.command.importModel%",
|
||||
"icon": {
|
||||
"light": "./images/registerModel.svg",
|
||||
"dark": "./images/registerModel.svg"
|
||||
@@ -79,15 +83,11 @@
|
||||
},
|
||||
{
|
||||
"command": "mls.command.manageLanguages",
|
||||
"title": "%mls.command.manageLanguages%"
|
||||
},
|
||||
{
|
||||
"command": "mls.command.odbcdriver",
|
||||
"title": "%mls.command.odbcdriver%"
|
||||
},
|
||||
{
|
||||
"command": "mls.command.mlsdocs",
|
||||
"title": "%mls.command.mlsdocs%"
|
||||
"title": "%mls.command.manageLanguages%",
|
||||
"icon": {
|
||||
"light": "./images/registerRuntime.svg",
|
||||
"dark": "./images/registerRuntime.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "mls.command.dependencies",
|
||||
@@ -110,21 +110,20 @@
|
||||
"widget": {
|
||||
"tasks-widget": [
|
||||
"mls.command.managePackages",
|
||||
"mls.command.manageLanguages",
|
||||
"mls.command.manageModels",
|
||||
"mls.command.predictModel"
|
||||
"mls.command.manageLanguages"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "%title.documents%",
|
||||
"name": "",
|
||||
"row": 0,
|
||||
"col": 1,
|
||||
"rowspan": 3,
|
||||
"colspan": 4,
|
||||
"widget": {
|
||||
"tasks-widget": [
|
||||
"mls.command.odbcdriver",
|
||||
"mls.command.mlsdocs"
|
||||
]
|
||||
"modelview": {
|
||||
"id":"mls.dashboard"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"displayName": "SQL Server Machine Learning Services",
|
||||
"description": "SQL Server Machine Learning Services",
|
||||
"displayName": "SQL Machine Learning",
|
||||
"description": "SQL Machine Learning",
|
||||
"title.tasks": "Tasks",
|
||||
"title.documents": "Documents",
|
||||
"title.configurations": "Configurations",
|
||||
@@ -9,13 +9,11 @@
|
||||
"mls.command.manageLanguages": "Manage external languages",
|
||||
"mls.command.predictModel": "Make prediction",
|
||||
"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",
|
||||
"mls.pythonPath.description": "Local path to a preexisting Python installation used by Machine Learning Services.",
|
||||
"mls.command.importModel": "Import model",
|
||||
"mls.configuration.title": "SQL Machine Learning Configurations",
|
||||
"mls.pythonPath.description": "Local path to a preexisting Python installation used by SQL Machine Learning.",
|
||||
"mls.enablePython.description": "Enable Python package management.",
|
||||
"mls.enableR.description": "Enable R package management.",
|
||||
"mls.rPath.description": "Local path to a preexisting R installation used by Machine Learning Services.",
|
||||
"mls.command.dependencies": "Install Machine Learning Services Dependencies"
|
||||
"mls.rPath.description": "Local path to a preexisting R installation used by SQL Machine Learning.",
|
||||
"mls.command.dependencies": "Install SQL Machine Learning Dependencies"
|
||||
}
|
||||
|
||||
@@ -129,4 +129,8 @@ export class ApiWrapper {
|
||||
public createButton(label: string, position?: azdata.window.DialogButtonPosition): azdata.window.Button {
|
||||
return azdata.window.createButton(label, position);
|
||||
}
|
||||
|
||||
public registerWidget(widgetId: string, handler: (view: azdata.ModelView) => void): void {
|
||||
azdata.ui.registerModelViewProvider(widgetId, handler);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export const rLPackagedFolderName = 'r_packages';
|
||||
|
||||
export const mlEnableMlsCommand = 'mls.command.enableMls';
|
||||
export const mlDisableMlsCommand = 'mls.command.disableMls';
|
||||
export const extensionOutputChannel = 'Machine Learning Services';
|
||||
export const extensionOutputChannel = 'SQL Machine Learning';
|
||||
export const notebookExtensionName = 'Microsoft.notebook';
|
||||
export const azureSubscriptionsCommand = 'azure.accounts.getSubscriptions';
|
||||
export const azureResourceGroupsCommand = 'azure.accounts.getResourceGroups';
|
||||
@@ -26,11 +26,10 @@ export const azureResourceGroupsCommand = 'azure.accounts.getResourceGroups';
|
||||
export const mlManageLanguagesCommand = 'mls.command.manageLanguages';
|
||||
export const mlsPredictModelCommand = 'mls.command.predictModel';
|
||||
export const mlManageModelsCommand = 'mls.command.manageModels';
|
||||
export const mlRegisterModelCommand = 'mls.command.registerModel';
|
||||
export const mlImportModelCommand = 'mls.command.importModel';
|
||||
export const mlManagePackagesCommand = 'mls.command.managePackages';
|
||||
export const mlOdbcDriverCommand = 'mls.command.odbcdriver';
|
||||
export const mlsDocumentsCommand = 'mls.command.mlsdocs';
|
||||
export const mlsDependenciesCommand = 'mls.command.dependencies';
|
||||
export const notebookCommandNew = 'notebook.command.new';
|
||||
|
||||
// Configurations
|
||||
//
|
||||
@@ -148,8 +147,12 @@ export const currentModelsTitle = localize('models.currentModelsTitle', "Models"
|
||||
export const azureRegisterModel = localize('models.azureRegisterModel', "Deploy");
|
||||
export const predictModel = localize('models.predictModel', "Predict");
|
||||
export const registerModelTitle = localize('models.RegisterWizard', "Deployed models");
|
||||
export const deployModelTitle = localize('models.deployModelTitle', "Deploy models");
|
||||
export const makePredictionTitle = localize('models.makePredictionTitle', "Make prediction");
|
||||
export const importModelTitle = localize('models.importModelTitle', "Import models");
|
||||
export const importModelDesc = localize('models.importModelDesc', "Build, import and expose a machine learning model");
|
||||
export const makePredictionTitle = localize('models.makePredictionTitle', "Make predictions");
|
||||
export const makePredictionDesc = localize('models.makePredictionDesc', "Generates a predicted value or scores using a managed model");
|
||||
export const createNotebookTitle = localize('models.createNotebookTitle', "Create notebook");
|
||||
export const createNotebookDesc = localize('models.createNotebookDesc', "Run experiments and create models");
|
||||
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', "File upload");
|
||||
@@ -166,8 +169,15 @@ export function importModelFailedError(modelName: string | undefined, filePath:
|
||||
|
||||
export const loadModelParameterFailedError = localize('models.loadModelParameterFailedError', "Failed to load model parameters'");
|
||||
export const unsupportedModelParameterType = localize('models.unsupportedModelParameterType', "unsupported");
|
||||
|
||||
|
||||
export const dashboardTitle = localize('dashboardTitle', "SQL ML");
|
||||
export const dashboardDesc = localize('dashboardDesc', "Machine learning for SQL databases");
|
||||
export const dashboardLinksTitle = localize('dashboardLinksTitle', "Useful links");
|
||||
export const dashboardVideoLinksTitle = localize('dashboardVideoLinksTitle', "Video tutorials");
|
||||
export const learnMoreTitle = localize('learnMoreTitle', "Learn more");
|
||||
export const mlsInstallMlsDocTitle = localize('mlsInstallMlsDocTitle', "Install SQL Server Machine Learning Services");
|
||||
export const mlsInstallMlsDocDesc = localize('mlsInstallMlsDocDesc', "This document guides you in the installation of SQL Server Machine Learning Services. Python and R scripts can be executed in-database using Machine Learning Services.");
|
||||
export const mlsInstallOdbcDocTitle = localize('mlsInstallObdcDocTitle', "Install the Microsoft ODBC driver for SQL Server");
|
||||
export const mlsInstallOdbcDocDesc = localize('mlsInstallOdbcDocDesc', "This document explains how to install the Microsoft ODBC Driver for SQL Server.");
|
||||
|
||||
// Links
|
||||
//
|
||||
|
||||
@@ -22,6 +22,7 @@ import { DeployedModelService } from '../modelManagement/deployedModelService';
|
||||
import { AzureModelRegistryService } from '../modelManagement/azureModelRegistryService';
|
||||
import { ModelPythonClient } from '../modelManagement/modelPythonClient';
|
||||
import { PredictService } from '../prediction/predictService';
|
||||
import { DashboardWidget } from '../views/widgets/dashboardWidget';
|
||||
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
@@ -110,13 +111,16 @@ export default class MainController implements vscode.Disposable {
|
||||
let modelManagementController = new ModelManagementController(this._apiWrapper, this._rootPath,
|
||||
azureModelsService, registeredModelService, predictService);
|
||||
|
||||
let dashboardWidget = new DashboardWidget(this._apiWrapper, this._rootPath);
|
||||
dashboardWidget.register();
|
||||
|
||||
this._apiWrapper.registerCommand(constants.mlManageLanguagesCommand, (async () => {
|
||||
await languageController.manageLanguages();
|
||||
}));
|
||||
this._apiWrapper.registerCommand(constants.mlManageModelsCommand, (async () => {
|
||||
await modelManagementController.manageRegisteredModels();
|
||||
}));
|
||||
this._apiWrapper.registerCommand(constants.mlRegisterModelCommand, (async () => {
|
||||
this._apiWrapper.registerCommand(constants.mlImportModelCommand, (async () => {
|
||||
await modelManagementController.registerModel();
|
||||
}));
|
||||
this._apiWrapper.registerCommand(constants.mlsPredictModelCommand, (async () => {
|
||||
@@ -134,18 +138,12 @@ export default class MainController implements vscode.Disposable {
|
||||
this._apiWrapper.registerTaskHandler(constants.mlManageModelsCommand, async () => {
|
||||
await modelManagementController.manageRegisteredModels();
|
||||
});
|
||||
this._apiWrapper.registerTaskHandler(constants.mlRegisterModelCommand, async () => {
|
||||
this._apiWrapper.registerTaskHandler(constants.mlImportModelCommand, async () => {
|
||||
await modelManagementController.registerModel();
|
||||
});
|
||||
this._apiWrapper.registerTaskHandler(constants.mlsPredictModelCommand, async () => {
|
||||
await modelManagementController.predictModel();
|
||||
});
|
||||
this._apiWrapper.registerTaskHandler(constants.mlOdbcDriverCommand, async () => {
|
||||
await this.packageManagementService.openOdbcDriverDocuments();
|
||||
});
|
||||
this._apiWrapper.registerTaskHandler(constants.mlsDocumentsCommand, async () => {
|
||||
await this.packageManagementService.openDocuments();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,28 +29,6 @@ export class PackageManagementService {
|
||||
return await this._apiWrapper.openExternal(vscode.Uri.parse(constants.mlsDocuments));
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens ODBC driver documents
|
||||
*/
|
||||
public async openOdbcDriverDocuments(): Promise<boolean> {
|
||||
if (utils.isWindows()) {
|
||||
return await this._apiWrapper.openExternal(vscode.Uri.parse(constants.odbcDriverWindowsDocuments));
|
||||
} else {
|
||||
return await this._apiWrapper.openExternal(vscode.Uri.parse(constants.odbcDriverLinuxDocuments));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens install MLS documents
|
||||
*/
|
||||
public async openInstallDocuments(): Promise<boolean> {
|
||||
if (utils.isWindows()) {
|
||||
return await this._apiWrapper.openExternal(vscode.Uri.parse(constants.installMlsWindowsDocs));
|
||||
} else {
|
||||
return await this._apiWrapper.openExternal(vscode.Uri.parse(constants.installMlsLinuxDocs));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if mls is installed in the give SQL server instance
|
||||
*/
|
||||
|
||||
@@ -31,20 +31,6 @@ describe('Package Management Service', () => {
|
||||
should.equal(await serverConfigManager.openDocuments(), true);
|
||||
});
|
||||
|
||||
it('openOdbcDriverDocuments should open document in browser successfully', async function (): Promise<void> {
|
||||
const context = createContext();
|
||||
context.apiWrapper.setup(x => x.openExternal(TypeMoq.It.isAny())).returns(() => Promise.resolve(true));
|
||||
let serverConfigManager = new PackageManagementService(context.apiWrapper.object, context.queryRunner.object);
|
||||
should.equal(await serverConfigManager.openOdbcDriverDocuments(), true);
|
||||
});
|
||||
|
||||
it('openInstallDocuments should open document in browser successfully', async function (): Promise<void> {
|
||||
const context = createContext();
|
||||
context.apiWrapper.setup(x => x.openExternal(TypeMoq.It.isAny())).returns(() => Promise.resolve(true));
|
||||
let serverConfigManager = new PackageManagementService(context.apiWrapper.object, context.queryRunner.object);
|
||||
should.equal(await serverConfigManager.openInstallDocuments(), true);
|
||||
});
|
||||
|
||||
it('isMachineLearningServiceEnabled should return true if external script is enabled', async function (): Promise<void> {
|
||||
const context = createContext();
|
||||
context.queryRunner.setup(x => x.isMachineLearningServiceEnabled(TypeMoq.It.isAny())).returns(() => Promise.resolve(true));
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { createViewContext } from './utils';
|
||||
import { DashboardWidget } from '../../views/widgets/dashboardWidget';
|
||||
|
||||
interface TestContext {
|
||||
apiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||
view: azdata.ModelView;
|
||||
onClick: vscode.EventEmitter<any>;
|
||||
}
|
||||
|
||||
|
||||
function createContext(): TestContext {
|
||||
|
||||
let viewTestContext = createViewContext();
|
||||
|
||||
return {
|
||||
apiWrapper: viewTestContext.apiWrapper,
|
||||
view: viewTestContext.view,
|
||||
onClick: viewTestContext.onClick
|
||||
};
|
||||
}
|
||||
|
||||
describe('Dashboard widget', () => {
|
||||
it('Should create view components successfully ', async function (): Promise<void> {
|
||||
let testContext = createContext();
|
||||
const dashboard = new DashboardWidget(testContext.apiWrapper.object, '');
|
||||
dashboard.register();
|
||||
testContext.onClick.fire();
|
||||
testContext.apiWrapper.verify(x => x.executeCommand(TypeMoq.It.isAny()), TypeMoq.Times.atMostOnce());
|
||||
});
|
||||
});
|
||||
@@ -52,6 +52,9 @@ export function createViewContext(): ViewTestContext {
|
||||
});
|
||||
let flex: azdata.FlexContainer = Object.assign({}, componentBase, container, {
|
||||
});
|
||||
let div: azdata.DivContainer = Object.assign({}, componentBase, container, {
|
||||
onDidClick: onClick.event
|
||||
});
|
||||
|
||||
let buttonBuilder: azdata.ComponentBuilder<azdata.ButtonComponent> = {
|
||||
component: () => button,
|
||||
@@ -134,6 +137,13 @@ export function createViewContext(): ViewTestContext {
|
||||
withItems: () => flexBuilder,
|
||||
withLayout: () => flexBuilder
|
||||
});
|
||||
let divBuilder: azdata.DivBuilder = Object.assign({}, {
|
||||
component: () => div,
|
||||
withProperties: () => divBuilder,
|
||||
withValidation: () => divBuilder,
|
||||
withItems: () => divBuilder,
|
||||
withLayout: () => divBuilder
|
||||
});
|
||||
|
||||
let inputBoxBuilder: azdata.ComponentBuilder<azdata.InputBoxComponent> = {
|
||||
component: () => {
|
||||
@@ -180,7 +190,7 @@ export function createViewContext(): ViewTestContext {
|
||||
modelBuilder: {
|
||||
radioCardGroup: undefined!,
|
||||
navContainer: undefined!,
|
||||
divContainer: undefined!,
|
||||
divContainer: () => divBuilder,
|
||||
flexContainer: () => flexBuilder,
|
||||
splitViewContainer: undefined!,
|
||||
dom: undefined!,
|
||||
@@ -295,6 +305,13 @@ export function createViewContext(): ViewTestContext {
|
||||
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(() => { });
|
||||
apiWrapper.setup(x => x.registerWidget(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(async (id, handler) => {
|
||||
if (id) {
|
||||
return await handler(view);
|
||||
} else {
|
||||
Promise.reject();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
apiWrapper: apiWrapper,
|
||||
|
||||
@@ -31,7 +31,7 @@ export class RegisteredModelsDialog extends ModelViewBase {
|
||||
|
||||
this.currentLanguagesTab = new CurrentModelsPage(this._apiWrapper, this);
|
||||
|
||||
let registerModelButton = this._apiWrapper.createButton(constants.deployModelTitle);
|
||||
let registerModelButton = this._apiWrapper.createButton(constants.importModelTitle);
|
||||
registerModelButton.onClick(async () => {
|
||||
await this.sendDataRequest(RegisterModelEventName);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,486 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
import * as path from 'path';
|
||||
import * as constants from '../../common/constants';
|
||||
import * as utils from '../../common/utils';
|
||||
|
||||
interface IActionMetadata {
|
||||
title?: string,
|
||||
description?: string,
|
||||
link?: string,
|
||||
iconPath?: { light: string | vscode.Uri; dark: string | vscode.Uri },
|
||||
command?: string;
|
||||
}
|
||||
|
||||
const maxWidth = 800;
|
||||
const headerMaxHeight = 200;
|
||||
export class DashboardWidget {
|
||||
|
||||
/**
|
||||
* Creates new instance of dashboard
|
||||
*/
|
||||
constructor(private _apiWrapper: ApiWrapper, private _root: string) {
|
||||
}
|
||||
|
||||
public register(): void {
|
||||
this._apiWrapper.registerWidget('mls.dashboard', async (view) => {
|
||||
const container = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}).component();
|
||||
const header = this.createHeader(view);
|
||||
const tasksContainer = this.createTasks(view);
|
||||
const footerContainer = this.createFooter(view);
|
||||
container.addItem(header, {
|
||||
CSSStyles: {
|
||||
'background-image': `url(${vscode.Uri.file(this.asAbsolutePath('images/background.svg'))})`,
|
||||
'background-repeat': 'no-repeat',
|
||||
'background-position': 'top',
|
||||
'width': `${maxWidth}px`,
|
||||
'height': '130px',
|
||||
'background-size': `${maxWidth}px ${headerMaxHeight}px`
|
||||
}
|
||||
});
|
||||
container.addItem(tasksContainer, {
|
||||
CSSStyles: {
|
||||
'width': `${maxWidth}px`,
|
||||
'height': '150px',
|
||||
}
|
||||
});
|
||||
container.addItem(footerContainer, {
|
||||
CSSStyles: {
|
||||
'width': `${maxWidth}px`,
|
||||
'height': '500px',
|
||||
}
|
||||
});
|
||||
const mainContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'column',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute'
|
||||
}).component();
|
||||
mainContainer.addItem(container, {
|
||||
CSSStyles: { 'padding-top': '25px', 'padding-left': '5px' }
|
||||
});
|
||||
await view.initializeModel(mainContainer);
|
||||
});
|
||||
}
|
||||
|
||||
private createHeader(view: azdata.ModelView): azdata.Component {
|
||||
const header = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: maxWidth,
|
||||
height: headerMaxHeight,
|
||||
}).component();
|
||||
const titleComponent = view.modelBuilder.text().withProperties({
|
||||
value: constants.dashboardTitle,
|
||||
CSSStyles: {
|
||||
'font-size': '36px',
|
||||
//'color': '#333333',
|
||||
'font-weight': 'bold',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
const descComponent = view.modelBuilder.text().withProperties({
|
||||
value: constants.dashboardDesc,
|
||||
CSSStyles: {
|
||||
'font-size': '14px',
|
||||
//'color': '#888888',
|
||||
'font-weight': 'bold',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
header.addItems([titleComponent, descComponent], {
|
||||
CSSStyles: {
|
||||
'width': `${maxWidth}px`,
|
||||
'padding': '10px'
|
||||
}
|
||||
});
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
private createFooter(view: azdata.ModelView): azdata.Component {
|
||||
const footerContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
width: maxWidth,
|
||||
height: '500px',
|
||||
justifyContent: 'flex-start'
|
||||
}).component();
|
||||
const linksContainer = this.createLinks(view);
|
||||
const videoLinksContainer = this.createVideoLinks(view);
|
||||
footerContainer.addItem(linksContainer);
|
||||
footerContainer.addItem(videoLinksContainer, {
|
||||
CSSStyles: {
|
||||
'padding-left': '50px',
|
||||
}
|
||||
});
|
||||
|
||||
return footerContainer;
|
||||
}
|
||||
|
||||
private createVideoLinks(view: azdata.ModelView): azdata.Component {
|
||||
const maxWidth = 400;
|
||||
const linksContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: maxWidth,
|
||||
height: '500px',
|
||||
justifyContent: 'flex-start'
|
||||
}).component();
|
||||
const titleComponent = view.modelBuilder.text().withProperties({
|
||||
value: constants.dashboardVideoLinksTitle,
|
||||
CSSStyles: {
|
||||
'font-size': '18px',
|
||||
'font-weight': 'bold',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
const videosContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
width: maxWidth,
|
||||
height: '500px',
|
||||
}).component();
|
||||
const video1Container = this.createVideoLink(view, {
|
||||
iconPath: { light: 'images/video1.svg', dark: 'images/video1.svg' },
|
||||
description: 'Visualize data using SandDance',
|
||||
link: 'https://www.youtube.com/watch?v=e305wTAoLZs'
|
||||
});
|
||||
videosContainer.addItem(video1Container);
|
||||
const video2Container = this.createVideoLink(view, {
|
||||
iconPath: { light: 'images/video2.svg', dark: 'images/video2.svg' },
|
||||
description: 'How to make the best out of Microsoft Azure'
|
||||
});
|
||||
videosContainer.addItem(video2Container);
|
||||
|
||||
linksContainer.addItems([titleComponent], {
|
||||
CSSStyles: {
|
||||
'padding': '0px',
|
||||
'padding-right': '5px',
|
||||
'padding-top': '10px',
|
||||
'height': '10px',
|
||||
'margin': '0px'
|
||||
}
|
||||
});
|
||||
linksContainer.addItems([videosContainer], {
|
||||
CSSStyles: {
|
||||
'padding': '0px',
|
||||
'padding-right': '5px',
|
||||
'padding-top': '10px',
|
||||
'height': '10px',
|
||||
'margin': '0px'
|
||||
}
|
||||
});
|
||||
return linksContainer;
|
||||
}
|
||||
|
||||
private createVideoLink(view: azdata.ModelView, linkMetaData: IActionMetadata): azdata.Component {
|
||||
const maxWidth = 200;
|
||||
const videosContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: maxWidth,
|
||||
height: '200px',
|
||||
justifyContent: 'flex-start'
|
||||
}).component();
|
||||
const video1Container = view.modelBuilder.divContainer().withProperties({
|
||||
clickable: true,
|
||||
width: maxWidth,
|
||||
height: '100px'
|
||||
}).component();
|
||||
const descriptionComponent = view.modelBuilder.text().withProperties({
|
||||
value: linkMetaData.description,
|
||||
width: '200px',
|
||||
height: '50px',
|
||||
CSSStyles: {
|
||||
//'color': '#605E5C',
|
||||
'font-size': '12px',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
video1Container.onDidClick(async () => {
|
||||
if (linkMetaData.link) {
|
||||
await this._apiWrapper.openExternal(vscode.Uri.parse(linkMetaData.link));
|
||||
}
|
||||
});
|
||||
videosContainer.addItem(video1Container, {
|
||||
CSSStyles: {
|
||||
'background-image': `url(${vscode.Uri.file(this.asAbsolutePath(<string>linkMetaData.iconPath?.light || ''))})`,
|
||||
'background-repeat': 'no-repeat',
|
||||
'background-position': 'top',
|
||||
'width': `150px`,
|
||||
'height': '110px',
|
||||
'background-size': `150px 120px`
|
||||
}
|
||||
});
|
||||
videosContainer.addItem(descriptionComponent);
|
||||
return videosContainer;
|
||||
}
|
||||
|
||||
private createLinks(view: azdata.ModelView): azdata.Component {
|
||||
const maxWidth = 400;
|
||||
const linksContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: maxWidth,
|
||||
height: '500px',
|
||||
}).component();
|
||||
const titleComponent = view.modelBuilder.text().withProperties({
|
||||
value: constants.dashboardLinksTitle,
|
||||
CSSStyles: {
|
||||
'font-size': '18px',
|
||||
//'color': '#323130',
|
||||
'font-weight': 'bold',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
let mlsLink: string;
|
||||
if (utils.isWindows()) {
|
||||
mlsLink = constants.installMlsWindowsDocs;
|
||||
} else {
|
||||
mlsLink = constants.installMlsLinuxDocs;
|
||||
}
|
||||
const mlsDocs = this.createLink(view, {
|
||||
title: constants.mlsInstallMlsDocTitle,
|
||||
description: constants.mlsInstallMlsDocDesc,
|
||||
link: mlsLink
|
||||
});
|
||||
let odbcLink: string;
|
||||
if (utils.isWindows()) {
|
||||
odbcLink = constants.odbcDriverWindowsDocuments;
|
||||
} else {
|
||||
odbcLink = constants.odbcDriverLinuxDocuments;
|
||||
}
|
||||
const rdbcDocs = this.createLink(view, {
|
||||
title: constants.mlsInstallOdbcDocTitle,
|
||||
description: constants.mlsInstallOdbcDocDesc,
|
||||
link: odbcLink
|
||||
});
|
||||
|
||||
linksContainer.addItems([titleComponent, mlsDocs, rdbcDocs], {
|
||||
CSSStyles: {
|
||||
'padding': '10px'
|
||||
}
|
||||
});
|
||||
return linksContainer;
|
||||
}
|
||||
|
||||
private createLink(view: azdata.ModelView, linkMetaData: IActionMetadata): azdata.Component {
|
||||
const maxHeight = 80;
|
||||
const maxWidth = 400;
|
||||
const labelsContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
justifyContent: 'flex-start'
|
||||
}).component();
|
||||
const descriptionComponent = view.modelBuilder.text().withProperties({
|
||||
value: linkMetaData.description,
|
||||
width: maxWidth,
|
||||
CSSStyles: {
|
||||
//'color': '#605E5C',
|
||||
'font-size': '12px',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
const linkContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
width: maxWidth,
|
||||
justifyContent: 'flex-start'
|
||||
}).component();
|
||||
const linkComponent = view.modelBuilder.hyperlink().withProperties({
|
||||
label: linkMetaData.title,
|
||||
url: linkMetaData.link,
|
||||
CSSStyles: {
|
||||
'color': '#3794ff',
|
||||
'font-size': '14px',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
const image = view.modelBuilder.image().withProperties({
|
||||
width: '10px',
|
||||
height: '10px',
|
||||
iconPath: {
|
||||
dark: this.asAbsolutePath('images/linkIcon.svg'),
|
||||
light: this.asAbsolutePath('images/linkIcon.svg'),
|
||||
},
|
||||
iconHeight: '10px',
|
||||
iconWidth: '10px'
|
||||
}).component();
|
||||
linkContainer.addItem(linkComponent, {
|
||||
CSSStyles: {
|
||||
'padding': '0px',
|
||||
'padding-right': '5px',
|
||||
'height': '10px',
|
||||
'margin': '0px'
|
||||
}
|
||||
});
|
||||
linkContainer.addItem(image, {
|
||||
CSSStyles: {
|
||||
'padding': '0px',
|
||||
'padding-right': '5px',
|
||||
'padding-top': '5px',
|
||||
'height': '10px',
|
||||
'margin': '0px'
|
||||
}
|
||||
});
|
||||
labelsContainer.addItems([linkContainer, descriptionComponent], {
|
||||
CSSStyles: {
|
||||
'padding': '0px',
|
||||
'padding-top': '5px',
|
||||
'margin': '0px'
|
||||
}
|
||||
});
|
||||
|
||||
return labelsContainer;
|
||||
}
|
||||
|
||||
private asAbsolutePath(filePath: string): string {
|
||||
return path.join(this._root || '', filePath);
|
||||
}
|
||||
|
||||
private createTasks(view: azdata.ModelView): azdata.Component {
|
||||
const tasksContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
width: '100%',
|
||||
height: '50px',
|
||||
}).component();
|
||||
const predictionMetadata: IActionMetadata = {
|
||||
title: constants.makePredictionTitle,
|
||||
description: constants.makePredictionDesc,
|
||||
iconPath: {
|
||||
dark: this.asAbsolutePath('images/makePredictions.svg'),
|
||||
light: this.asAbsolutePath('images/makePredictions.svg'),
|
||||
},
|
||||
link: '',
|
||||
command: constants.mlsPredictModelCommand
|
||||
};
|
||||
const predictionButton = this.createTaskButton(view, predictionMetadata);
|
||||
const importMetadata: IActionMetadata = {
|
||||
title: constants.importModelTitle,
|
||||
description: constants.importModelDesc,
|
||||
iconPath: {
|
||||
dark: this.asAbsolutePath('images/makePredictions.svg'),
|
||||
light: this.asAbsolutePath('images/makePredictions.svg'),
|
||||
},
|
||||
link: '',
|
||||
command: constants.mlImportModelCommand
|
||||
};
|
||||
const importModelsButton = this.createTaskButton(view, importMetadata);
|
||||
const notebookMetadata: IActionMetadata = {
|
||||
title: constants.createNotebookTitle,
|
||||
description: constants.createNotebookDesc,
|
||||
iconPath: {
|
||||
dark: this.asAbsolutePath('images/createNotebook.svg'),
|
||||
light: this.asAbsolutePath('images/createNotebook.svg'),
|
||||
},
|
||||
link: '',
|
||||
command: constants.notebookCommandNew
|
||||
};
|
||||
const notebookModelsButton = this.createTaskButton(view, notebookMetadata);
|
||||
tasksContainer.addItems([predictionButton, importModelsButton, notebookModelsButton], {
|
||||
CSSStyles: {
|
||||
'padding': '10px'
|
||||
}
|
||||
});
|
||||
|
||||
return tasksContainer;
|
||||
}
|
||||
|
||||
private createTaskButton(view: azdata.ModelView, taskMetaData: IActionMetadata): azdata.Component {
|
||||
const maxHeight = 106;
|
||||
const maxWidth = 250;
|
||||
const mainContainer = view.modelBuilder.divContainer().withLayout({
|
||||
width: maxWidth,
|
||||
height: maxHeight
|
||||
}).withProperties({
|
||||
clickable: true,
|
||||
ariaRole: taskMetaData.title
|
||||
}).component();
|
||||
const iconContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
width: maxWidth,
|
||||
height: maxHeight - 20,
|
||||
alignItems: 'flex-start'
|
||||
}).component();
|
||||
const labelsContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: maxWidth - 50,
|
||||
height: maxHeight - 20,
|
||||
justifyContent: 'space-between'
|
||||
}).component();
|
||||
const titleComponent = view.modelBuilder.text().withProperties({
|
||||
value: taskMetaData.title,
|
||||
CSSStyles: {
|
||||
'font-size': '14px',
|
||||
//'color': '#323130',
|
||||
'font-weight': 'bold',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
const descriptionComponent = view.modelBuilder.text().withProperties({
|
||||
value: taskMetaData.description,
|
||||
CSSStyles: {
|
||||
//'color': '#605E5C',
|
||||
'font-size': '13px',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
const linkComponent = view.modelBuilder.hyperlink().withProperties({
|
||||
label: constants.learnMoreTitle,
|
||||
url: taskMetaData.link,
|
||||
CSSStyles: {
|
||||
//'background-color': '#F2F2F2',
|
||||
'color': '#3794ff',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
const image = view.modelBuilder.image().withProperties({
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
iconPath: taskMetaData.iconPath,
|
||||
iconHeight: '20px',
|
||||
iconWidth: '20px'
|
||||
}).component();
|
||||
labelsContainer.addItems([titleComponent, descriptionComponent, linkComponent], {
|
||||
CSSStyles: {
|
||||
'padding': '0px',
|
||||
'padding-bottom': '5px',
|
||||
'width': '180px',
|
||||
'margin': '0px'
|
||||
}
|
||||
});
|
||||
iconContainer.addItem(image, {
|
||||
CSSStyles: {
|
||||
'padding-top': '10px',
|
||||
'padding-right': '10px'
|
||||
}
|
||||
});
|
||||
iconContainer.addItem(labelsContainer, {
|
||||
CSSStyles: {
|
||||
'padding-top': '5px',
|
||||
'padding-right': '10px'
|
||||
}
|
||||
});
|
||||
mainContainer.addItems([iconContainer], {
|
||||
CSSStyles: {
|
||||
//'background-color': '#f4f4f4',
|
||||
'padding': '10px',
|
||||
'border-radius': '5px',
|
||||
'border-color': '#f2f2f2',
|
||||
'border': '1px solid'
|
||||
}
|
||||
});
|
||||
mainContainer.onDidClick(async () => {
|
||||
if (taskMetaData.command) {
|
||||
await this._apiWrapper.executeCommand(taskMetaData.command);
|
||||
}
|
||||
});
|
||||
return mainContainer;
|
||||
}
|
||||
}
|
||||