mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-24 17:23:05 -05:00
310 lines
12 KiB
TypeScript
310 lines
12 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* 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 nls from 'vscode-nls';
|
|
import { SqlAssessmentTab } from './sqlAssessmentTab';
|
|
import { AssessmentEngine, AssessmentType } from '../engine';
|
|
import { promises as fs } from 'fs';
|
|
import { suggestReportFile, limitLongName } from '../utils';
|
|
import { HTMLReportBuilder } from '../htmlReportGenerator';
|
|
import { AssessmentResultGrid } from '../assessmentResultGrid';
|
|
import { LocalizedStrings } from '../localized';
|
|
import { TelemetryReporter, SqlAssessmentTelemetryView, SqlTelemetryActions } from '../telemetry';
|
|
import { EOL } from 'os';
|
|
|
|
const localize = nls.loadMessageBundle();
|
|
|
|
export class SqlAssessmentMainTab extends SqlAssessmentTab {
|
|
private apiVersionPropItem: azdata.PropertiesContainerItem;
|
|
private defaultRulesetPropItem: azdata.PropertiesContainerItem;
|
|
private serverProps: azdata.PropertiesContainerItem[] = [];
|
|
|
|
private invokeAssessmentLabel: string = localize('invokeAssessmentLabelServer', "Invoke assessment");
|
|
private getItemsLabel: string = localize('getAssessmentItemsServer', "View applicable rules");
|
|
private btnExportAsScript!: azdata.ButtonComponent;
|
|
private btnHTMLExport!: azdata.ButtonComponent;
|
|
|
|
private engine: AssessmentEngine;
|
|
private toDispose: vscode.Disposable[] = [];
|
|
private resultGrid!: AssessmentResultGrid;
|
|
|
|
|
|
public constructor(extensionContext: vscode.ExtensionContext, engine: AssessmentEngine) {
|
|
super(extensionContext, LocalizedStrings.ASSESSMENT_TAB_NAME, 'MainTab', {
|
|
dark: extensionContext.asAbsolutePath('resources/dark/server.svg'),
|
|
light: extensionContext.asAbsolutePath('resources/light/server.svg')
|
|
});
|
|
this.apiVersionPropItem = { displayName: LocalizedStrings.API_VERSION, value: '' };
|
|
this.defaultRulesetPropItem = { displayName: LocalizedStrings.DEFAULT_RULESET_VERSION, value: '' };
|
|
this.engine = engine;
|
|
}
|
|
|
|
public dispose() {
|
|
this.toDispose.forEach(disposable => disposable.dispose());
|
|
}
|
|
|
|
async tabContent(view: azdata.ModelView): Promise<azdata.Component> {
|
|
|
|
if (!this.engine.isServerConnection) {
|
|
this.invokeAssessmentLabel = localize('invokeAssessmentLabelDatabase', "Invoke assessment for {0}", this.engine.databaseName);
|
|
this.getItemsLabel = localize('getAssessmentItemsDatabase', "View applicable rules for {0}", this.engine.databaseName);
|
|
}
|
|
else {
|
|
this.invokeAssessmentLabel = localize('invokeAssessmentLabelServer', "Invoke assessment");
|
|
this.getItemsLabel = localize('getAssessmentItemsServer', "View applicable rules");
|
|
}
|
|
|
|
let rootContainer = view.modelBuilder.flexContainer().withLayout(
|
|
{
|
|
flexFlow: 'column',
|
|
width: '100%',
|
|
height: '100%',
|
|
|
|
}).component();
|
|
await this.createServerProperties(view);
|
|
|
|
rootContainer.addItem(await this.createToolbar(view), {
|
|
flex: '0 0 auto', CSSStyles: {
|
|
'border-top': '3px solid rgb(221, 221, 221)'
|
|
}
|
|
});
|
|
|
|
this.resultGrid = new AssessmentResultGrid(view, this.extensionContext);
|
|
rootContainer.addItem(this.resultGrid.component, {
|
|
flex: '1 1 auto',
|
|
CSSStyles: {
|
|
'padding-bottom': '10px'
|
|
}
|
|
});
|
|
|
|
return rootContainer;
|
|
}
|
|
|
|
private async createServerProperties(view: azdata.ModelView): Promise<void> {
|
|
const serverInfo = await azdata.connection.getServerInfo(view.connection.connectionId);
|
|
const connectionProfile = await azdata.connection.getCurrentConnection();
|
|
this.serverProps = [
|
|
{ displayName: LocalizedStrings.SERVER_VERSION, value: serverInfo.serverVersion },
|
|
{ displayName: LocalizedStrings.SERVER_INSTANCENAME, value: connectionProfile.serverName },
|
|
{ displayName: LocalizedStrings.SERVER_EDITION, value: serverInfo.serverEdition },
|
|
{ displayName: LocalizedStrings.SERVER_OSVERSION, value: serverInfo.osVersion },
|
|
];
|
|
}
|
|
|
|
|
|
|
|
private async createToolbar(view: azdata.ModelView): Promise<azdata.ToolbarContainer> {
|
|
const targetIconPath = this.engine.isServerConnection
|
|
? {
|
|
dark: this.extensionContext.asAbsolutePath('resources/dark/server.svg'),
|
|
light: this.extensionContext.asAbsolutePath('resources/light/server.svg')
|
|
} : {
|
|
dark: this.extensionContext.asAbsolutePath('resources/dark/database.svg'),
|
|
light: this.extensionContext.asAbsolutePath('resources/light/database.svg')
|
|
};
|
|
const iconSize: number = 16;
|
|
const btnHeight: string = '26px';
|
|
const maxNameLength: number = 40;
|
|
|
|
const btnInvokeAssessment = view.modelBuilder.button()
|
|
.withProperties<azdata.ButtonProperties>({
|
|
label: limitLongName(this.invokeAssessmentLabel, maxNameLength),
|
|
iconPath: targetIconPath,
|
|
iconHeight: iconSize,
|
|
iconWidth: iconSize,
|
|
height: btnHeight
|
|
|
|
}).component();
|
|
const btnInvokeAssessmentLoading = view.modelBuilder.loadingComponent()
|
|
.withItem(btnInvokeAssessment)
|
|
.withProperties<azdata.LoadingComponentProperties>({
|
|
loadingText: limitLongName(this.invokeAssessmentLabel, maxNameLength),
|
|
showText: true,
|
|
loading: false
|
|
}).component();
|
|
|
|
this.toDispose.push(btnInvokeAssessment.onDidClick(async () => {
|
|
btnInvokeAssessmentLoading.loading = true;
|
|
try {
|
|
await this.engine.performAssessment(AssessmentType.InvokeAssessment,
|
|
async (result: azdata.SqlAssessmentResult, assessmentType: AssessmentType, append: boolean) => {
|
|
if (append) {
|
|
await this.resultGrid.appendResult(result);
|
|
} else {
|
|
this.displayResults(result, assessmentType);
|
|
}
|
|
});
|
|
}
|
|
finally {
|
|
btnInvokeAssessmentLoading.loading = false;
|
|
}
|
|
}));
|
|
|
|
const btnGetAssessmentItems = view.modelBuilder.button()
|
|
.withProperties<azdata.ButtonProperties>({
|
|
label: limitLongName(this.getItemsLabel, maxNameLength),
|
|
iconPath: targetIconPath,
|
|
iconHeight: iconSize,
|
|
iconWidth: iconSize,
|
|
height: btnHeight
|
|
}).component();
|
|
const btnGetAssessmentItemsLoading = view.modelBuilder.loadingComponent()
|
|
.withItem(btnGetAssessmentItems)
|
|
.withProperties<azdata.LoadingComponentProperties>({
|
|
loadingText: limitLongName(this.getItemsLabel, maxNameLength),
|
|
showText: true,
|
|
loading: false
|
|
}).component();
|
|
|
|
this.toDispose.push(btnGetAssessmentItems.onDidClick(async () => {
|
|
btnGetAssessmentItemsLoading.loading = true;
|
|
try {
|
|
await this.engine.performAssessment(AssessmentType.AvailableRules,
|
|
async (result: azdata.SqlAssessmentResult, assessmentType: AssessmentType, append: boolean) => {
|
|
if (append) {
|
|
await this.resultGrid.appendResult(result);
|
|
} else {
|
|
this.displayResults(result, assessmentType);
|
|
}
|
|
});
|
|
}
|
|
finally {
|
|
btnGetAssessmentItemsLoading.loading = false;
|
|
}
|
|
}));
|
|
|
|
this.btnExportAsScript = view.modelBuilder.button()
|
|
.withProperties<azdata.ButtonProperties>({
|
|
label: localize('btnExportAsScript', "Export as script"),
|
|
enabled: false,
|
|
iconPath: {
|
|
dark: this.extensionContext.asAbsolutePath('resources/dark/newquery_inverse.svg'),
|
|
light: this.extensionContext.asAbsolutePath('resources/light/newquery.svg')
|
|
},
|
|
iconHeight: iconSize,
|
|
iconWidth: iconSize,
|
|
height: btnHeight
|
|
}).component();
|
|
this.toDispose.push(this.btnExportAsScript.onDidClick(async () => {
|
|
this.engine.generateAssessmentScript();
|
|
}));
|
|
|
|
this.btnHTMLExport = view.modelBuilder.button()
|
|
.withProperties<azdata.ButtonProperties>({
|
|
label: localize('btnGeneratehtmlreport', "Create HTML Report"),
|
|
enabled: false,
|
|
iconPath: {
|
|
dark: this.extensionContext.asAbsolutePath('resources/dark/book_inverse.svg'),
|
|
light: this.extensionContext.asAbsolutePath('resources/light/book.svg')
|
|
},
|
|
iconHeight: iconSize,
|
|
iconWidth: iconSize,
|
|
height: btnHeight
|
|
}).component();
|
|
|
|
this.toDispose.push(this.btnHTMLExport.onDidClick(async () => {
|
|
TelemetryReporter.sendActionEvent(SqlAssessmentTelemetryView, SqlTelemetryActions.CreateHTMLReport);
|
|
const options: vscode.SaveDialogOptions = {
|
|
defaultUri: vscode.Uri.file(suggestReportFile(Date.now())),
|
|
filters: { 'HTML File': ['html'] }
|
|
};
|
|
|
|
const choosenPath = await vscode.window.showSaveDialog(options);
|
|
if (choosenPath !== undefined) {
|
|
const reportContent = await new HTMLReportBuilder(this.engine.recentResult.result,
|
|
this.engine.recentResult.dateUpdated,
|
|
this.engine.recentResult.connectionInfo).build();
|
|
await fs.writeFile(choosenPath.fsPath, reportContent);
|
|
if (await vscode.window.showInformationMessage(
|
|
localize('asmtaction.openReport', "Report has been saved. Do you want to open it?"),
|
|
localize('asmtaction.label.open', "Open"), localize('asmtaction.label.cancel', "Cancel")
|
|
) === localize('asmtaction.label.open', "Open")) {
|
|
vscode.env.openExternal(choosenPath);
|
|
}
|
|
}
|
|
}));
|
|
|
|
|
|
let btnViewSamples = view.modelBuilder.button()
|
|
.withProperties<azdata.ButtonProperties>({
|
|
label: localize('btnViewSamplesShort', "View all on GitHub"),
|
|
iconPath: {
|
|
dark: this.extensionContext.asAbsolutePath('resources/dark/configuredashboard_inverse.svg'),
|
|
light: this.extensionContext.asAbsolutePath('resources/light/configuredashboard.svg')
|
|
},
|
|
iconHeight: iconSize,
|
|
iconWidth: iconSize,
|
|
height: btnHeight,
|
|
title: localize('btnViewSamples', "View all rules and learn more on GitHub"),
|
|
}).component();
|
|
|
|
this.toDispose.push(btnViewSamples.onDidClick(() => {
|
|
TelemetryReporter.sendActionEvent(SqlAssessmentTelemetryView, SqlTelemetryActions.LearnMoreAssessmentLink);
|
|
vscode.env.openExternal(vscode.Uri.parse('https://aka.ms/sql-assessment-api'));
|
|
}));
|
|
|
|
let btnAPIDetails = view.modelBuilder.button()
|
|
.withProperties<azdata.ButtonProperties>({
|
|
label: LocalizedStrings.SECTION_TITLE_API,
|
|
iconPath: {
|
|
dark: this.extensionContext.asAbsolutePath('resources/dark/status_info.svg'),
|
|
light: this.extensionContext.asAbsolutePath('resources/light/status_info.svg')
|
|
},
|
|
iconHeight: iconSize,
|
|
iconWidth: iconSize,
|
|
height: btnHeight
|
|
}).component();
|
|
this.toDispose.push(btnAPIDetails.onDidClick(async () => {
|
|
let infoArray: azdata.PropertiesContainerItem[] = [];
|
|
|
|
if (this.apiVersionPropItem.value) {
|
|
infoArray.push(this.apiVersionPropItem);
|
|
}
|
|
|
|
if (this.defaultRulesetPropItem.value) {
|
|
infoArray.push(this.defaultRulesetPropItem);
|
|
}
|
|
|
|
infoArray.push(...this.serverProps);
|
|
const message = localize('msgBoxAsmtInfo', "SQL Assessment Information") + EOL + EOL +
|
|
infoArray.map(v => `${v.displayName}: ${v.value}`).join(EOL);
|
|
|
|
const copy: vscode.MessageItem = { title: localize('msgBoxCopyBtn', "Copy") };
|
|
const ok: vscode.MessageItem = { isCloseAffordance: true, title: localize('ok', "OK") };
|
|
|
|
const response = await vscode.window.showInformationMessage(message, { modal: true }, copy, ok);
|
|
if (response === copy) {
|
|
await vscode.env.clipboard.writeText(message);
|
|
vscode.window.showInformationMessage(localize('msgBoxCopied', 'SQL Assessment Information copied'));
|
|
}
|
|
}));
|
|
|
|
btnGetAssessmentItemsLoading.loading = false;
|
|
btnInvokeAssessmentLoading.loading = false;
|
|
|
|
return view.modelBuilder.toolbarContainer()
|
|
.withToolbarItems(
|
|
[
|
|
{ component: btnInvokeAssessmentLoading },
|
|
{ component: btnGetAssessmentItemsLoading },
|
|
{ component: this.btnExportAsScript },
|
|
{ component: this.btnHTMLExport },
|
|
{ component: btnViewSamples },
|
|
{ component: btnAPIDetails }
|
|
]
|
|
).component();
|
|
}
|
|
|
|
private displayResults(result: azdata.SqlAssessmentResult, assessmentType: AssessmentType): void {
|
|
this.apiVersionPropItem.value = result.apiVersion;
|
|
this.defaultRulesetPropItem.value = result.items?.length > 0 ? result.items[0].rulesetVersion : '';
|
|
|
|
this.resultGrid.displayResult(result, assessmentType);
|
|
this.btnExportAsScript.enabled = this.btnHTMLExport.enabled = assessmentType === AssessmentType.InvokeAssessment;
|
|
}
|
|
}
|