mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-10 18:22:34 -05:00
ML - dashboard icons and links (#10153)
* ML - dashboard icons and links
This commit is contained in:
580
extensions/machine-learning/src/views/widgets/dashboardWidget.ts
Normal file
580
extensions/machine-learning/src/views/widgets/dashboardWidget.ts
Normal file
@@ -0,0 +1,580 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
import * as path from 'path';
|
||||
import * as constants from '../../common/constants';
|
||||
import * as utils from '../../common/utils';
|
||||
|
||||
interface IActionMetadata {
|
||||
title?: string,
|
||||
description?: string,
|
||||
link?: string,
|
||||
iconPath?: { light: string | vscode.Uri; dark: string | vscode.Uri },
|
||||
command?: string;
|
||||
}
|
||||
|
||||
const maxWidth = 800;
|
||||
const headerMaxHeight = 300;
|
||||
export class DashboardWidget {
|
||||
|
||||
/**
|
||||
* Creates new instance of dashboard
|
||||
*/
|
||||
constructor(private _apiWrapper: ApiWrapper, private _root: string) {
|
||||
}
|
||||
|
||||
public register(): void {
|
||||
this._apiWrapper.registerWidget('mls.dashboard', async (view) => {
|
||||
const container = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}).component();
|
||||
const header = this.createHeader(view);
|
||||
const tasksContainer = this.createTasks(view);
|
||||
const footerContainer = this.createFooter(view);
|
||||
container.addItem(header, {
|
||||
CSSStyles: {
|
||||
'background-image': `url(${vscode.Uri.file(this.asAbsolutePath('images/background.svg'))})`,
|
||||
'background-repeat': 'no-repeat',
|
||||
'background-position': 'bottom',
|
||||
'width': `${maxWidth}px`,
|
||||
'height': '330px',
|
||||
'background-size': `${maxWidth}px ${headerMaxHeight}px`,
|
||||
'margin-bottom': '-60px'
|
||||
}
|
||||
});
|
||||
container.addItem(tasksContainer, {
|
||||
CSSStyles: {
|
||||
'width': `${maxWidth}px`,
|
||||
'height': '150px',
|
||||
}
|
||||
});
|
||||
container.addItem(footerContainer, {
|
||||
CSSStyles: {
|
||||
'width': `${maxWidth}px`,
|
||||
'height': '500px',
|
||||
}
|
||||
});
|
||||
const mainContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'column',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute'
|
||||
}).component();
|
||||
mainContainer.addItem(container, {
|
||||
CSSStyles: { 'padding-top': '25px', 'padding-left': '5px' }
|
||||
});
|
||||
await view.initializeModel(mainContainer);
|
||||
});
|
||||
}
|
||||
|
||||
private createHeader(view: azdata.ModelView): azdata.Component {
|
||||
const header = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: maxWidth,
|
||||
height: headerMaxHeight
|
||||
}).component();
|
||||
const titleComponent = view.modelBuilder.text().withProperties({
|
||||
value: constants.dashboardTitle,
|
||||
CSSStyles: {
|
||||
'font-size': '36px',
|
||||
'font-weight': 'bold',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
const descComponent = view.modelBuilder.text().withProperties({
|
||||
value: constants.dashboardDesc,
|
||||
CSSStyles: {
|
||||
'font-size': '14px',
|
||||
'font-weight': 'bold',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
header.addItems([titleComponent, descComponent], {
|
||||
CSSStyles: {
|
||||
'width': `${maxWidth}px`,
|
||||
'padding': '5px'
|
||||
}
|
||||
});
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
private createFooter(view: azdata.ModelView): azdata.Component {
|
||||
const footerContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
width: maxWidth,
|
||||
height: '500px',
|
||||
justifyContent: 'flex-start'
|
||||
}).component();
|
||||
const linksContainer = this.createLinks(view);
|
||||
const videoLinksContainer = this.createVideoLinks(view);
|
||||
footerContainer.addItem(linksContainer);
|
||||
footerContainer.addItem(videoLinksContainer, {
|
||||
CSSStyles: {
|
||||
'padding-left': '45px',
|
||||
}
|
||||
});
|
||||
|
||||
return footerContainer;
|
||||
}
|
||||
|
||||
private createVideoLinkContainers(view: azdata.ModelView, links: IActionMetadata[]): azdata.Component {
|
||||
const maxWidth = 400;
|
||||
const videosContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
width: maxWidth,
|
||||
height: '300px',
|
||||
}).component();
|
||||
|
||||
links.forEach(link => {
|
||||
const videoContainer = this.createVideoLink(view, link);
|
||||
|
||||
videosContainer.addItem(videoContainer);
|
||||
});
|
||||
|
||||
return videosContainer;
|
||||
}
|
||||
|
||||
private createVideoLinks(view: azdata.ModelView): azdata.Component {
|
||||
const maxWidth = 400;
|
||||
const linksContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: maxWidth,
|
||||
height: '500px',
|
||||
justifyContent: 'flex-start'
|
||||
}).component();
|
||||
const titleComponent = view.modelBuilder.text().withProperties({
|
||||
value: constants.dashboardVideoLinksTitle,
|
||||
CSSStyles: {
|
||||
'font-size': '18px',
|
||||
'font-weight': 'bold',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
const viewPanelStyle = {
|
||||
'padding': '0px',
|
||||
'padding-right': '5px',
|
||||
'padding-top': '20px',
|
||||
'height': '200px',
|
||||
'margin': '0px'
|
||||
};
|
||||
|
||||
linksContainer.addItems([titleComponent], {
|
||||
CSSStyles: {
|
||||
'padding': '0px',
|
||||
'padding-right': '5px',
|
||||
'padding-top': '10px',
|
||||
'height': '10px',
|
||||
'margin': '0px'
|
||||
}
|
||||
});
|
||||
const videosContainer = this.createVideoLinkContainers(view, [
|
||||
{
|
||||
iconPath: { light: 'images/video1.svg', dark: 'images/video1.svg' },
|
||||
description: 'Artificial intelligence and machine learning with SQL Server 2019',
|
||||
link: 'https://www.youtube.com/watch?v=sE99cSoFOHs'
|
||||
},
|
||||
{
|
||||
iconPath: { light: 'images/video2.svg', dark: 'images/video2.svg' },
|
||||
description: 'SQL Server Machine Learning Services',
|
||||
link: 'https://www.youtube.com/watch?v=R4GCBoxADyQ'
|
||||
}
|
||||
]);
|
||||
|
||||
linksContainer.addItem(videosContainer, {
|
||||
CSSStyles: viewPanelStyle
|
||||
});
|
||||
|
||||
const moreVideosContainer = this.createVideoLinkContainers(view, [
|
||||
{
|
||||
iconPath: { light: 'images/video2.svg', dark: 'images/video2.svg' },
|
||||
description: 'Introduction to Azure Data Studio Notebooks',
|
||||
link: 'https://www.youtube.com/watch?v=Nt4kIHQ0IOc'
|
||||
}
|
||||
]);
|
||||
|
||||
this.addShowMorePanel(view, linksContainer, moreVideosContainer, { 'padding-left': '5px' }, viewPanelStyle);
|
||||
return linksContainer;
|
||||
}
|
||||
|
||||
private addShowMorePanel(view: azdata.ModelView, parentPanel: azdata.FlexContainer, morePanel: azdata.Component, moreButtonStyle: { [key: string]: string }, morePanelStyle: { [key: string]: string }): azdata.Component {
|
||||
const maxWidth = 100;
|
||||
const linkContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
width: maxWidth + 10,
|
||||
justifyContent: 'flex-start'
|
||||
}).component();
|
||||
const showMoreComponent = view.modelBuilder.hyperlink().withProperties({
|
||||
label: constants.showMoreTitle
|
||||
}).component();
|
||||
const image = view.modelBuilder.image().withProperties({
|
||||
width: '10px',
|
||||
height: '10px',
|
||||
iconPath: {
|
||||
dark: this.asAbsolutePath('images/dark/showMore_inverse.svg'),
|
||||
light: this.asAbsolutePath('images/light/showMore.svg'),
|
||||
},
|
||||
iconHeight: '10px',
|
||||
iconWidth: '10px'
|
||||
}).component();
|
||||
showMoreComponent.onDidClick(() => {
|
||||
let showMore = showMoreComponent.label === constants.showMoreTitle;
|
||||
if (showMore) {
|
||||
showMoreComponent.label = constants.showLessTitle;
|
||||
image.iconPath = {
|
||||
dark: this.asAbsolutePath('images/dark/showLess_inverse.svg'),
|
||||
light: this.asAbsolutePath('images/light/showLess.svg'),
|
||||
};
|
||||
parentPanel.addItem(morePanel, {
|
||||
CSSStyles: morePanelStyle
|
||||
});
|
||||
} else {
|
||||
showMoreComponent.label = constants.showMoreTitle;
|
||||
parentPanel.removeItem(morePanel);
|
||||
image.iconPath = {
|
||||
dark: this.asAbsolutePath('images/dark/showMore_inverse.svg'),
|
||||
light: this.asAbsolutePath('images/light/showMore.svg'),
|
||||
};
|
||||
}
|
||||
showMore = !showMore;
|
||||
});
|
||||
linkContainer.addItem(showMoreComponent, {
|
||||
CSSStyles: Object.assign({}, moreButtonStyle, {
|
||||
'font-size': '12px',
|
||||
'margin': '0px',
|
||||
'color': '#006ab1',
|
||||
'padding-right': '5px'
|
||||
}
|
||||
)
|
||||
});
|
||||
linkContainer.addItem(image, {
|
||||
CSSStyles: {
|
||||
'padding': '0px',
|
||||
'padding-right': '5px',
|
||||
'padding-top': '5px',
|
||||
'height': '10px',
|
||||
'margin': '0px'
|
||||
}
|
||||
});
|
||||
|
||||
parentPanel.addItem(linkContainer, {
|
||||
CSSStyles: {
|
||||
'padding': '0px',
|
||||
'padding-right': '5px',
|
||||
'padding-top': '10px',
|
||||
'height': '10px',
|
||||
'margin': '0px'
|
||||
}
|
||||
});
|
||||
|
||||
return showMoreComponent;
|
||||
}
|
||||
|
||||
private createVideoLink(view: azdata.ModelView, linkMetaData: IActionMetadata): azdata.Component {
|
||||
const maxWidth = 150;
|
||||
const videosContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: maxWidth,
|
||||
height: maxWidth,
|
||||
justifyContent: 'flex-start'
|
||||
}).component();
|
||||
const video1Container = view.modelBuilder.divContainer().withProperties({
|
||||
clickable: true,
|
||||
width: maxWidth,
|
||||
height: '100px'
|
||||
}).component();
|
||||
const descriptionComponent = view.modelBuilder.text().withProperties({
|
||||
value: linkMetaData.description,
|
||||
width: maxWidth,
|
||||
height: '50px',
|
||||
CSSStyles: {
|
||||
'font-size': '12px',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
video1Container.onDidClick(async () => {
|
||||
if (linkMetaData.link) {
|
||||
await this._apiWrapper.openExternal(vscode.Uri.parse(linkMetaData.link));
|
||||
}
|
||||
});
|
||||
videosContainer.addItem(video1Container, {
|
||||
CSSStyles: {
|
||||
'background-image': `url(${vscode.Uri.file(this.asAbsolutePath(<string>linkMetaData.iconPath?.light || ''))})`,
|
||||
'background-repeat': 'no-repeat',
|
||||
'background-position': 'top',
|
||||
'width': `${maxWidth}px`,
|
||||
'height': '110px',
|
||||
'background-size': `{maxWidth}px 120px`
|
||||
}
|
||||
});
|
||||
videosContainer.addItem(descriptionComponent);
|
||||
return videosContainer;
|
||||
}
|
||||
|
||||
private createLinks(view: azdata.ModelView): azdata.Component {
|
||||
const maxWidth = 400;
|
||||
const linksContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: maxWidth,
|
||||
height: '500px',
|
||||
}).component();
|
||||
const titleComponent = view.modelBuilder.text().withProperties({
|
||||
value: constants.dashboardLinksTitle,
|
||||
CSSStyles: {
|
||||
'font-size': '18px',
|
||||
'font-weight': 'bold',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
|
||||
const links = [{
|
||||
title: constants.sqlMlDocTitle,
|
||||
description: constants.sqlMlDocDesc,
|
||||
link: constants.mlDocLink
|
||||
}, {
|
||||
title: constants.sqlMlsDocTitle,
|
||||
description: constants.sqlMlsDocDesc,
|
||||
link: constants.mlsDocLink
|
||||
},
|
||||
{
|
||||
title: constants.sqlMlsAzureDocTitle,
|
||||
description: constants.sqlMlsAzureDocDesc,
|
||||
link: constants.mlsAzureDocLink
|
||||
}];
|
||||
|
||||
const moreLink = {
|
||||
title: constants.mlsInstallOdbcDocTitle,
|
||||
description: constants.mlsInstallOdbcDocDesc,
|
||||
link: utils.isWindows() ? constants.odbcDriverWindowsDocuments : constants.odbcDriverLinuxDocuments
|
||||
};
|
||||
const styles = {
|
||||
'padding': '10px'
|
||||
};
|
||||
|
||||
linksContainer.addItem(titleComponent, {
|
||||
CSSStyles: {
|
||||
'padding': '10px'
|
||||
}
|
||||
});
|
||||
|
||||
linksContainer.addItems(links.map(l => this.createLink(view, l)), {
|
||||
CSSStyles: styles
|
||||
});
|
||||
|
||||
this.addShowMorePanel(view, linksContainer, this.createLink(view, moreLink), { 'padding-left': '10px' }, styles);
|
||||
return linksContainer;
|
||||
}
|
||||
|
||||
private createLink(view: azdata.ModelView, linkMetaData: IActionMetadata): azdata.Component {
|
||||
const maxHeight = 80;
|
||||
const maxWidth = 400;
|
||||
const labelsContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
justifyContent: 'flex-start'
|
||||
}).component();
|
||||
const descriptionComponent = view.modelBuilder.text().withProperties({
|
||||
value: linkMetaData.description,
|
||||
width: maxWidth,
|
||||
CSSStyles: {
|
||||
'font-size': '12px',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
const linkContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
width: maxWidth + 10,
|
||||
justifyContent: 'flex-start'
|
||||
}).component();
|
||||
const linkComponent = view.modelBuilder.hyperlink().withProperties({
|
||||
label: linkMetaData.title,
|
||||
url: linkMetaData.link,
|
||||
CSSStyles: {
|
||||
'font-size': '14px',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
const image = view.modelBuilder.image().withProperties({
|
||||
width: '10px',
|
||||
height: '10px',
|
||||
iconPath: {
|
||||
dark: this.asAbsolutePath('images/linkIcon.svg'),
|
||||
light: this.asAbsolutePath('images/linkIcon.svg'),
|
||||
},
|
||||
iconHeight: '10px',
|
||||
iconWidth: '10px'
|
||||
}).component();
|
||||
linkContainer.addItem(linkComponent, {
|
||||
CSSStyles: {
|
||||
'padding': '0px',
|
||||
'padding-right': '5px',
|
||||
'margin': '0px',
|
||||
'color': '#006ab1'
|
||||
}
|
||||
});
|
||||
linkContainer.addItem(image, {
|
||||
CSSStyles: {
|
||||
'padding': '0px',
|
||||
'padding-right': '5px',
|
||||
'padding-top': '5px',
|
||||
'height': '10px',
|
||||
'margin': '0px'
|
||||
}
|
||||
});
|
||||
labelsContainer.addItems([linkContainer, descriptionComponent], {
|
||||
CSSStyles: {
|
||||
'padding': '0px',
|
||||
'padding-top': '5px',
|
||||
'margin': '0px'
|
||||
}
|
||||
});
|
||||
|
||||
return labelsContainer;
|
||||
}
|
||||
|
||||
private asAbsolutePath(filePath: string): string {
|
||||
return path.join(this._root || '', filePath);
|
||||
}
|
||||
|
||||
private createTasks(view: azdata.ModelView): azdata.Component {
|
||||
const tasksContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
width: '100%',
|
||||
height: '50px',
|
||||
}).component();
|
||||
const predictionMetadata: IActionMetadata = {
|
||||
title: constants.makePredictionTitle,
|
||||
description: constants.makePredictionDesc,
|
||||
iconPath: {
|
||||
dark: this.asAbsolutePath('images/makePredictions.svg'),
|
||||
light: this.asAbsolutePath('images/makePredictions.svg'),
|
||||
},
|
||||
link: '',
|
||||
command: constants.mlsPredictModelCommand
|
||||
};
|
||||
const predictionButton = this.createTaskButton(view, predictionMetadata);
|
||||
const importMetadata: IActionMetadata = {
|
||||
title: constants.importModelTitle,
|
||||
description: constants.importModelDesc,
|
||||
iconPath: {
|
||||
dark: this.asAbsolutePath('images/manageModels.svg'),
|
||||
light: this.asAbsolutePath('images/manageModels.svg'),
|
||||
},
|
||||
link: '',
|
||||
command: constants.mlManageModelsCommand
|
||||
};
|
||||
const importModelsButton = this.createTaskButton(view, importMetadata);
|
||||
const notebookMetadata: IActionMetadata = {
|
||||
title: constants.createNotebookTitle,
|
||||
description: constants.createNotebookDesc,
|
||||
iconPath: {
|
||||
dark: this.asAbsolutePath('images/createNotebook.svg'),
|
||||
light: this.asAbsolutePath('images/createNotebook.svg'),
|
||||
},
|
||||
link: '',
|
||||
command: constants.notebookCommandNew
|
||||
};
|
||||
const notebookModelsButton = this.createTaskButton(view, notebookMetadata);
|
||||
tasksContainer.addItems([predictionButton, importModelsButton, notebookModelsButton], {
|
||||
CSSStyles: {
|
||||
'padding': '10px'
|
||||
}
|
||||
});
|
||||
|
||||
return tasksContainer;
|
||||
}
|
||||
|
||||
private createTaskButton(view: azdata.ModelView, taskMetaData: IActionMetadata): azdata.Component {
|
||||
const maxHeight = 106;
|
||||
const maxWidth = 250;
|
||||
const mainContainer = view.modelBuilder.divContainer().withLayout({
|
||||
width: maxWidth,
|
||||
height: maxHeight
|
||||
}).withProperties({
|
||||
clickable: true,
|
||||
ariaRole: taskMetaData.title
|
||||
}).component();
|
||||
const iconContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
width: maxWidth,
|
||||
height: maxHeight - 20,
|
||||
alignItems: 'flex-start'
|
||||
}).component();
|
||||
const labelsContainer = view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
width: maxWidth - 50,
|
||||
height: maxHeight - 20,
|
||||
justifyContent: 'space-between'
|
||||
}).component();
|
||||
const titleComponent = view.modelBuilder.text().withProperties({
|
||||
value: taskMetaData.title,
|
||||
CSSStyles: {
|
||||
'font-size': '14px',
|
||||
'font-weight': 'bold',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
const descriptionComponent = view.modelBuilder.text().withProperties({
|
||||
value: taskMetaData.description,
|
||||
CSSStyles: {
|
||||
'font-size': '13px',
|
||||
'margin': '0px'
|
||||
}
|
||||
}).component();
|
||||
const linkComponent = view.modelBuilder.hyperlink().withProperties({
|
||||
label: constants.learnMoreTitle,
|
||||
url: taskMetaData.link
|
||||
}).component();
|
||||
const image = view.modelBuilder.image().withProperties({
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
iconPath: taskMetaData.iconPath,
|
||||
iconHeight: '20px',
|
||||
iconWidth: '20px'
|
||||
}).component();
|
||||
labelsContainer.addItems([titleComponent, descriptionComponent, linkComponent], {
|
||||
CSSStyles: {
|
||||
'padding': '0px',
|
||||
'padding-bottom': '5px',
|
||||
'width': '180px',
|
||||
'margin': '0px',
|
||||
'color': '#006ab1'
|
||||
}
|
||||
});
|
||||
iconContainer.addItem(image, {
|
||||
CSSStyles: {
|
||||
'padding-top': '10px',
|
||||
'padding-right': '10px'
|
||||
}
|
||||
});
|
||||
iconContainer.addItem(labelsContainer, {
|
||||
CSSStyles: {
|
||||
'padding-top': '5px',
|
||||
'padding-right': '10px'
|
||||
}
|
||||
});
|
||||
mainContainer.addItems([iconContainer], {
|
||||
CSSStyles: {
|
||||
'padding': '10px',
|
||||
'border-radius': '5px',
|
||||
'border-color': '#f2f2f2',
|
||||
'border': '1px solid'
|
||||
}
|
||||
});
|
||||
mainContainer.onDidClick(async () => {
|
||||
if (taskMetaData.command) {
|
||||
await this._apiWrapper.executeCommand(taskMetaData.command);
|
||||
}
|
||||
});
|
||||
return mainContainer;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user