diff --git a/extensions/query-store/package.json b/extensions/query-store/package.json index 9455e7a053..07c0772a37 100644 --- a/extensions/query-store/package.json +++ b/extensions/query-store/package.json @@ -33,13 +33,8 @@ "contributes": { "commands": [ { - "command": "queryStore.topResourceConsumingQueriesOpen", - "title": "%queryStore.topResourceConsumingQueriesOpen%", - "category": "%queryStore.category%" - }, - { - "command": "queryStore.overallResourceConsumptionOpen", - "title": "%queryStore.overallResourceConsumptionOpen%", + "command": "queryStore.openQueryStoreDashboard", + "title": "%queryStore.openQueryStoreDashboard%", "category": "%queryStore.category%" } ], diff --git a/extensions/query-store/package.nls.json b/extensions/query-store/package.nls.json index c51a47d357..6d6a2c2fa1 100644 --- a/extensions/query-store/package.nls.json +++ b/extensions/query-store/package.nls.json @@ -2,6 +2,5 @@ "queryStore.displayName": "Query Store", "queryStore.description": "Query Store extension for Azure Data Studio.", "queryStore.category": "Query Store", - "queryStore.topResourceConsumingQueriesOpen": "Open Top Resource Consuming Queries", - "queryStore.overallResourceConsumptionOpen": "Open Overall Resource Consumption" + "queryStore.openQueryStoreDashboard": "Query Store" } diff --git a/extensions/query-store/src/common/constants.ts b/extensions/query-store/src/common/constants.ts index d974ebf0a7..0b1a9db6a9 100644 --- a/extensions/query-store/src/common/constants.ts +++ b/extensions/query-store/src/common/constants.ts @@ -7,6 +7,8 @@ import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); +export function queryStoreDashboardTitle(databaseName: string): string { return localize('queryStoreDashboardTitle', "Query Store - {0}", databaseName); } + export const overallResourceConsumption = localize('overallResourceConsumption', "Overall Resource Consumption"); export const duration = localize('duration', "Duration"); export const executionCount = localize('executionCount', "Execution Count"); diff --git a/extensions/query-store/src/extension.ts b/extensions/query-store/src/extension.ts index 7d54b63a08..77c6e16835 100644 --- a/extensions/query-store/src/extension.ts +++ b/extensions/query-store/src/extension.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { TopResourceConsumingQueries } from './reports/topResourceConsumingQueries'; -import { OverallResourceConsumption } from './reports/overallResourceConsumption'; +import { QueryStoreDashboard } from './reports/queryStoreDashboard'; export async function activate(context: vscode.ExtensionContext): Promise { // TODO: get db name - context.subscriptions.push(vscode.commands.registerCommand('queryStore.topResourceConsumingQueriesOpen', async () => { await new TopResourceConsumingQueries(context, 'WideWorldImporters').open() })); - context.subscriptions.push(vscode.commands.registerCommand('queryStore.overallResourceConsumptionOpen', async () => { await new OverallResourceConsumption(context, 'WideWorldImporters').open() })); + // TODO: add OE entry point with condition for command to only be visible for db's with Query Store enabled (or consider always showing and having a way to enable when dashboard is opened?) + // TODO: remove entry point from command palette - keeping for now to speed up testing so a connection doesn't need to be made to launch the dashboard + context.subscriptions.push(vscode.commands.registerCommand('queryStore.openQueryStoreDashboard', async () => { await new QueryStoreDashboard('AdventureWorks', context).open() })); } export function deactivate(): void { diff --git a/extensions/query-store/src/reports/baseQueryStoreReport.ts b/extensions/query-store/src/reports/baseQueryStoreReport.ts index be109a769e..69d7b3cfe5 100644 --- a/extensions/query-store/src/reports/baseQueryStoreReport.ts +++ b/extensions/query-store/src/reports/baseQueryStoreReport.ts @@ -11,40 +11,35 @@ import * as constants from '../common/constants'; import { ConfigureDialog } from '../settings/configureDialog'; export abstract class BaseQueryStoreReport { - protected editor: azdata.workspace.ModelViewEditor; protected flexModel?: azdata.FlexContainer; protected configureDialog?: ConfigureDialog; protected configureButton?: azdata.ButtonComponent; - constructor(reportName: string, private reportTitle: string, protected resizeable: boolean, private extensionContext: vscode.ExtensionContext) { - this.editor = azdata.workspace.createModelViewEditor(reportName, { retainContextWhenHidden: true, supportsSave: false }, reportName); + constructor(private reportTitle: string, protected resizeable: boolean, private extensionContext: vscode.ExtensionContext) { } + + public get ReportContent(): azdata.FlexContainer | undefined { + return this.flexModel; } /** * Creates and opens the report */ - public async open(): Promise { - this.editor.registerContent(async (view) => { - this.flexModel = view.modelBuilder.flexContainer().component(); + public async createReport(view: azdata.ModelView): Promise { + this.flexModel = view.modelBuilder.flexContainer().component(); - const toolbar = await this.createToolbar(view); - this.flexModel.addItem(toolbar, { flex: 'none' }); + const toolbar = await this.createToolbar(view); + this.flexModel.addItem(toolbar, { flex: 'none' }); - const views = await this.createViews(view); + const views = await this.createViews(view); - const mainContainer = await this.createMainContainer(view, views); + const mainContainer = await this.createMainContainer(view, views); - this.flexModel.addItem(mainContainer, { CSSStyles: { 'width': '100%', 'height': '100%' } }); + this.flexModel.addItem(mainContainer, { CSSStyles: { 'width': '100%', 'height': '100%' } }); - this.flexModel.setLayout({ - flexFlow: 'column', - height: '100%' - }); - - await view.initializeModel(this.flexModel); + this.flexModel.setLayout({ + flexFlow: 'column', + height: '100%' }); - - await this.editor.openEditor(); } /** diff --git a/extensions/query-store/src/reports/overallResourceConsumption.ts b/extensions/query-store/src/reports/overallResourceConsumption.ts index 3454bbe3f0..f32446b989 100644 --- a/extensions/query-store/src/reports/overallResourceConsumption.ts +++ b/extensions/query-store/src/reports/overallResourceConsumption.ts @@ -19,7 +19,7 @@ export class OverallResourceConsumption extends BaseQueryStoreReport { private logicalReads: QueryStoreView; constructor(extensionContext: vscode.ExtensionContext, databaseName: string) { - super(constants.overallResourceConsumption, constants.overallResourceConsumptionToolbarLabel(databaseName), /*resizeable*/ false, extensionContext); + super(constants.overallResourceConsumptionToolbarLabel(databaseName), /*resizeable*/ false, extensionContext); this.duration = new QueryStoreView(constants.duration, 'chartreuse'); this.executionCount = new QueryStoreView(constants.executionCount, 'coral'); this.cpuTime = new QueryStoreView(constants.cpuTime, 'darkturquoise'); diff --git a/extensions/query-store/src/reports/queryStoreDashboard.ts b/extensions/query-store/src/reports/queryStoreDashboard.ts new file mode 100644 index 0000000000..9e66044af2 --- /dev/null +++ b/extensions/query-store/src/reports/queryStoreDashboard.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import * as azdata from 'azdata'; +import * as constants from '../common/constants'; +import { TopResourceConsumingQueries } from './topResourceConsumingQueries'; +import { OverallResourceConsumption } from './overallResourceConsumption'; + +export class QueryStoreDashboard { + constructor(private dbName: string, private extensionContext: vscode.ExtensionContext) { } + + /** + * Creates and opens the report + */ + public async open(): Promise { + // TODO: update title based on selected tab to have the current selected report in editor tab title + const dashboard = azdata.window.createModelViewDashboard(constants.queryStoreDashboardTitle(this.dbName)); + dashboard.registerTabs(async (view: azdata.ModelView) => { + const topResourceConsumingQueriesReport = new TopResourceConsumingQueries(this.extensionContext, this.dbName); + const overallResourceConsumptionReport = new OverallResourceConsumption(this.extensionContext, this.dbName); + + await Promise.all([topResourceConsumingQueriesReport.createReport(view), overallResourceConsumptionReport.createReport(view)]); + + const topResourceConsumingQueriesTab: azdata.DashboardTab = { + id: 'TopResourceConsumingQueriesTab', + content: topResourceConsumingQueriesReport.ReportContent!, + title: constants.topResourceConsumingQueries + }; + + const overallResourceConsumptionTab: azdata.DashboardTab = { + id: 'OverallResourceConsumptionTab', + content: overallResourceConsumptionReport.ReportContent!, + title: constants.overallResourceConsumption + }; + + return [ + overallResourceConsumptionTab, + topResourceConsumingQueriesTab + ]; + }); + + await dashboard.open(); + } +} + diff --git a/extensions/query-store/src/reports/topResourceConsumingQueries.ts b/extensions/query-store/src/reports/topResourceConsumingQueries.ts index 31d20ed7a9..95c2479f3c 100644 --- a/extensions/query-store/src/reports/topResourceConsumingQueries.ts +++ b/extensions/query-store/src/reports/topResourceConsumingQueries.ts @@ -17,7 +17,7 @@ export class TopResourceConsumingQueries extends BaseQueryStoreReport { private plan: QueryStoreView; constructor(extensionContext: vscode.ExtensionContext, databaseName: string) { - super(constants.topResourceConsumingQueries, constants.topResourceConsumingQueriesToolbarLabel(databaseName), /*resizeable*/ true, extensionContext); + super(constants.topResourceConsumingQueriesToolbarLabel(databaseName), /*resizeable*/ true, extensionContext); this.queries = new QueryStoreView(constants.queries, 'chartreuse'); this.planSummary = new QueryStoreView(constants.planSummary('x'), 'coral'); // TODO: replace 'x' with actual query id this.plan = new QueryStoreView(constants.plan('x'), 'darkturquoise');