From 3f08d5d7145155c0b1ad8ebd7d22418aa2627daa Mon Sep 17 00:00:00 2001 From: Leila Lali Date: Mon, 13 Apr 2020 14:06:29 -0700 Subject: [PATCH] MLS - Changed the dashboard to match the design (#9905) * Machine Learning Extension - Changed the dashboard to match the design --- .../images/background.svg | 1 + .../images/createNotebook.svg | 1 + .../images/installPackages.svg | 3 + .../images/linkIcon.svg | 3 + .../images/registerRuntime.svg | 1 + .../images/video1.svg | 9 + .../images/video2.svg | 9 + .../machine-learning-services/package.json | 39 +- .../package.nls.json | 16 +- .../src/common/apiWrapper.ts | 4 + .../src/common/constants.ts | 26 +- .../src/controllers/mainController.ts | 14 +- .../packageManagementService.ts | 22 - .../packageManagementService.test.ts | 14 - .../src/test/views/dashboardWidget.test.ts | 39 ++ .../src/test/views/utils.ts | 19 +- .../registerModels/registeredModelsDialog.ts | 2 +- .../src/views/widgets/dashboardWidget.ts | 486 ++++++++++++++++++ 18 files changed, 625 insertions(+), 83 deletions(-) create mode 100644 extensions/machine-learning-services/images/background.svg create mode 100644 extensions/machine-learning-services/images/createNotebook.svg create mode 100644 extensions/machine-learning-services/images/installPackages.svg create mode 100644 extensions/machine-learning-services/images/linkIcon.svg create mode 100644 extensions/machine-learning-services/images/registerRuntime.svg create mode 100644 extensions/machine-learning-services/images/video1.svg create mode 100644 extensions/machine-learning-services/images/video2.svg create mode 100644 extensions/machine-learning-services/src/test/views/dashboardWidget.test.ts create mode 100644 extensions/machine-learning-services/src/views/widgets/dashboardWidget.ts diff --git a/extensions/machine-learning-services/images/background.svg b/extensions/machine-learning-services/images/background.svg new file mode 100644 index 0000000000..50fd10a899 --- /dev/null +++ b/extensions/machine-learning-services/images/background.svg @@ -0,0 +1 @@ +ADS ML Extension_background_image \ No newline at end of file diff --git a/extensions/machine-learning-services/images/createNotebook.svg b/extensions/machine-learning-services/images/createNotebook.svg new file mode 100644 index 0000000000..1f0a03b9b4 --- /dev/null +++ b/extensions/machine-learning-services/images/createNotebook.svg @@ -0,0 +1 @@ +j_ \ No newline at end of file diff --git a/extensions/machine-learning-services/images/installPackages.svg b/extensions/machine-learning-services/images/installPackages.svg new file mode 100644 index 0000000000..5b75cda4c0 --- /dev/null +++ b/extensions/machine-learning-services/images/installPackages.svg @@ -0,0 +1,3 @@ + + + diff --git a/extensions/machine-learning-services/images/linkIcon.svg b/extensions/machine-learning-services/images/linkIcon.svg new file mode 100644 index 0000000000..63f69fc22e --- /dev/null +++ b/extensions/machine-learning-services/images/linkIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/extensions/machine-learning-services/images/registerRuntime.svg b/extensions/machine-learning-services/images/registerRuntime.svg new file mode 100644 index 0000000000..f0b395a639 --- /dev/null +++ b/extensions/machine-learning-services/images/registerRuntime.svg @@ -0,0 +1 @@ +v_ diff --git a/extensions/machine-learning-services/images/video1.svg b/extensions/machine-learning-services/images/video1.svg new file mode 100644 index 0000000000..01f4180c8b --- /dev/null +++ b/extensions/machine-learning-services/images/video1.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/extensions/machine-learning-services/images/video2.svg b/extensions/machine-learning-services/images/video2.svg new file mode 100644 index 0000000000..618673834d --- /dev/null +++ b/extensions/machine-learning-services/images/video2.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/extensions/machine-learning-services/package.json b/extensions/machine-learning-services/package.json index abe3a7f0a8..9e649f2616 100644 --- a/extensions/machine-learning-services/package.json +++ b/extensions/machine-learning-services/package.json @@ -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" + } } } ] diff --git a/extensions/machine-learning-services/package.nls.json b/extensions/machine-learning-services/package.nls.json index 509f2e87cb..5ad08fabf3 100644 --- a/extensions/machine-learning-services/package.nls.json +++ b/extensions/machine-learning-services/package.nls.json @@ -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" } diff --git a/extensions/machine-learning-services/src/common/apiWrapper.ts b/extensions/machine-learning-services/src/common/apiWrapper.ts index bf2cb4867c..9beea070aa 100644 --- a/extensions/machine-learning-services/src/common/apiWrapper.ts +++ b/extensions/machine-learning-services/src/common/apiWrapper.ts @@ -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); + } } diff --git a/extensions/machine-learning-services/src/common/constants.ts b/extensions/machine-learning-services/src/common/constants.ts index 175eaa80d0..6a580d6559 100644 --- a/extensions/machine-learning-services/src/common/constants.ts +++ b/extensions/machine-learning-services/src/common/constants.ts @@ -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 // diff --git a/extensions/machine-learning-services/src/controllers/mainController.ts b/extensions/machine-learning-services/src/controllers/mainController.ts index a9cc6babf6..005c600582 100644 --- a/extensions/machine-learning-services/src/controllers/mainController.ts +++ b/extensions/machine-learning-services/src/controllers/mainController.ts @@ -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(); - }); } /** diff --git a/extensions/machine-learning-services/src/packageManagement/packageManagementService.ts b/extensions/machine-learning-services/src/packageManagement/packageManagementService.ts index fcb73a6555..093cbb817f 100644 --- a/extensions/machine-learning-services/src/packageManagement/packageManagementService.ts +++ b/extensions/machine-learning-services/src/packageManagement/packageManagementService.ts @@ -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 { - 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 { - 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 */ diff --git a/extensions/machine-learning-services/src/test/packageManagement/packageManagementService.test.ts b/extensions/machine-learning-services/src/test/packageManagement/packageManagementService.test.ts index 8a6315b094..86adf320a7 100644 --- a/extensions/machine-learning-services/src/test/packageManagement/packageManagementService.test.ts +++ b/extensions/machine-learning-services/src/test/packageManagement/packageManagementService.test.ts @@ -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 { - 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 { - 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 { const context = createContext(); context.queryRunner.setup(x => x.isMachineLearningServiceEnabled(TypeMoq.It.isAny())).returns(() => Promise.resolve(true)); diff --git a/extensions/machine-learning-services/src/test/views/dashboardWidget.test.ts b/extensions/machine-learning-services/src/test/views/dashboardWidget.test.ts new file mode 100644 index 0000000000..de1fe94b28 --- /dev/null +++ b/extensions/machine-learning-services/src/test/views/dashboardWidget.test.ts @@ -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; + view: azdata.ModelView; + onClick: vscode.EventEmitter; +} + + +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 { + 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()); + }); +}); diff --git a/extensions/machine-learning-services/src/test/views/utils.ts b/extensions/machine-learning-services/src/test/views/utils.ts index e55c545cba..ede30eca63 100644 --- a/extensions/machine-learning-services/src/test/views/utils.ts +++ b/extensions/machine-learning-services/src/test/views/utils.ts @@ -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 = { 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 = { 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, diff --git a/extensions/machine-learning-services/src/views/models/registerModels/registeredModelsDialog.ts b/extensions/machine-learning-services/src/views/models/registerModels/registeredModelsDialog.ts index 9ec3665aba..af45cec44b 100644 --- a/extensions/machine-learning-services/src/views/models/registerModels/registeredModelsDialog.ts +++ b/extensions/machine-learning-services/src/views/models/registerModels/registeredModelsDialog.ts @@ -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); }); diff --git a/extensions/machine-learning-services/src/views/widgets/dashboardWidget.ts b/extensions/machine-learning-services/src/views/widgets/dashboardWidget.ts new file mode 100644 index 0000000000..4570f8bf96 --- /dev/null +++ b/extensions/machine-learning-services/src/views/widgets/dashboardWidget.ts @@ -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(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; + } +}