Initial changes for reusable QDS report (#23527)

* initial changes for making a QDS report with placeholders

* Add icon for configure button

* Add another example report to show layout

* move files

* add placeholder names to components and cleanup toolbar

* cleanup

* switch to createViews() instead of createTop and BottomSections()

* add QueryStoreView class for the different components in a report

* cleanup

* add more comments

* fix yarn not running for query store extension folder

* add missing break

* change one more view to container
This commit is contained in:
Kim Santiago
2023-06-29 13:24:22 -10:00
committed by GitHub
parent cf607e98a1
commit 220b2b4ac4
12 changed files with 379 additions and 3 deletions

View File

@@ -0,0 +1,146 @@
/*---------------------------------------------------------------------------------------------
* 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 path from 'path';
import * as utils from '../common/utils';
import * as constants from '../common/constants';
export abstract class BaseQueryStoreReport {
protected editor: azdata.workspace.ModelViewEditor;
protected flexModel?: azdata.FlexContainer;
constructor(reportName: string, private reportTitle: string, protected resizeable: boolean, private extensionContext: vscode.ExtensionContext) {
this.editor = azdata.workspace.createModelViewEditor(reportName, { retainContextWhenHidden: true, supportsSave: false }, reportName);
}
/**
* Creates and opens the report
*/
public async open(): Promise<void> {
this.editor.registerContent(async (view) => {
this.flexModel = <azdata.FlexContainer>view.modelBuilder.flexContainer().component();
const toolbar = await this.createToolbar(view);
this.flexModel.addItem(toolbar, { flex: 'none' });
const views = await this.createViews(view);
const mainContainer = await this.createMainContainer(view, views);
this.flexModel.addItem(mainContainer, { CSSStyles: { 'width': '100%', 'height': '100%' } });
this.flexModel.setLayout({
flexFlow: 'column',
height: '100%'
});
await view.initializeModel(this.flexModel);
});
await this.editor.openEditor();
}
/**
* Creates the main container containing the different components of the report
* @param view
* @param containers Array of containers to add to the main container
* @returns FlexContainer or SplitViewContainer containing the containers
*/
private async createMainContainer(view: azdata.ModelView, containers: azdata.FlexContainer[]): Promise<azdata.FlexContainer | azdata.SplitViewContainer> {
let mainContainer;
switch (containers.length) {
case 1: {
mainContainer = containers[0];
break;
}
case 2: {
// TODO: replace 800 to have the number be based on how big the window is
// one container on top, one on the bottom
mainContainer = this.resizeable ? utils.createVerticalSplitView(view, containers[0], containers[1], 800) : await utils.createTwoComponentFlexContainer(view, containers[0], containers[1], 'column');
break;
} case 3: {
// 2 containers on top, one on the bottom
// TODO: support portrait and landscape view. Right now it's landscape view only
mainContainer = this.resizeable ? utils.createVerticalSplitView(view, await utils.createTwoComponentFlexContainer(view, containers[0], containers[1], 'row'), containers[2], 800)
: await utils.createTwoComponentFlexContainer(view, await utils.createTwoComponentFlexContainer(view, containers[0], containers[1], 'row'), containers[2], 'column');
break;
} case 4: {
// 2 containers on top, 2 on the bottom
mainContainer = this.resizeable ? utils.createVerticalSplitView(view, await utils.createTwoComponentFlexContainer(view, containers[0], containers[1], 'row'), await utils.createTwoComponentFlexContainer(view, containers[2], containers[3], 'row'), 800)
: await utils.createTwoComponentFlexContainer(view, await utils.createTwoComponentFlexContainer(view, containers[0], containers[1], 'row'), await utils.createTwoComponentFlexContainer(view, containers[2], containers[3], 'row'), 'column');
break;
} default: {
throw new Error(`{views.length} number of views in a QDS report is not supported`);
}
}
return mainContainer
}
/**
* Creates the toolbar for the overall report with the report title, time range, and configure button
* @param view
*/
protected async createToolbar(view: azdata.ModelView): Promise<azdata.ToolbarContainer> {
const toolBar = <azdata.ToolbarBuilder>view.modelBuilder.toolbarContainer().withProps({
CSSStyles: { 'padding': '5px' }
});
const reportTitle = view.modelBuilder.text().withProps({
value: this.reportTitle,
title: this.reportTitle,
CSSStyles: { 'margin-top': '5px', 'margin-bottom': '5px', 'margin-right': '15px' }
}).component();
// TODO: get time from configuration dialog
const timePeriod = view.modelBuilder.text().withProps({
// placeholder times
value: 'Time period: 5/15/2023 11:58 AM - 5/23/2023 11:58 AM',
title: 'Time period: 5/15/2023 11:58 AM - 5/23/2023 11:58 AM',
CSSStyles: { 'margin-top': '5px', 'margin-bottom': '5px', 'margin-right': '15px' }
}).component();
const configureButton = view.modelBuilder.button().withProps({
label: constants.configure,
title: constants.configure,
iconPath: {
light: path.join(this.extensionContext.extensionPath, 'images', 'light', 'gear.svg'),
dark: path.join(this.extensionContext.extensionPath, 'images', 'dark', 'gear.svg')
}
}).component();
// TODO: enable after the configuration dialog is implemented
configureButton.enabled = false;
configureButton.onDidClick(() => {
// TODO: implement configuration dialog
console.error('configuration dialog not implemented')
});
await configureButton.updateCssStyles({ 'margin-top': '5px' });
toolBar.addToolbarItems([
{
component: reportTitle,
toolbarSeparatorAfter: true
},
{
component: timePeriod,
toolbarSeparatorAfter: true
},
{
component: configureButton
}
]);
return toolBar.component();
}
protected abstract createViews(_view: azdata.ModelView): Promise<azdata.FlexContainer[]>;
}

View File

@@ -0,0 +1,35 @@
/*---------------------------------------------------------------------------------------------
* 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 constants from '../common/constants';
import { BaseQueryStoreReport } from './baseQueryStoreReport';
import { QueryStoreView } from './queryStoreView';
export class OverallResourceConsumption extends BaseQueryStoreReport {
private duration: QueryStoreView;
private executionCount: QueryStoreView;
private cpuTime: QueryStoreView;
private logicalReads: QueryStoreView;
constructor(extensionContext: vscode.ExtensionContext, databaseName: string) {
super(constants.overallResourceConsumption, 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');
this.logicalReads = new QueryStoreView(constants.logicalReads, 'forestgreen');
}
public override async createViews(view: azdata.ModelView): Promise<azdata.FlexContainer[]> {
const durationContainer = await this.duration.createViewContainer(view);
const executionCountContainer = await this.executionCount.createViewContainer(view);
const cpuTimeContainer = await this.cpuTime.createViewContainer(view);
const logicalReadsContainer = await this.logicalReads.createViewContainer(view);
return [durationContainer, executionCountContainer, cpuTimeContainer, logicalReadsContainer];
}
}

View File

@@ -0,0 +1,37 @@
/*---------------------------------------------------------------------------------------------
* 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 { createOneComponentFlexContainer } from '../common/utils';
/**
* Defines a view in a query store report
*/
export class QueryStoreView {
// TODO: add toolbar support
// TODO: add support for toggling between chart and table components (could potentially add a child class to support this).
public component?: azdata.Component; // chart, query plan, text (component to display whole query text)
/**
*
* @param title Title of view to display
* @param backgroundColor TODO: remove this after chart components are supported
*/
constructor(private title: string, private backgroundColor: string) { }
/**
* Creates component in a container with the background color. Eventually will create the component with a toolbar in a flex container
* @param view
* @returns
*/
public async createViewContainer(view: azdata.ModelView): Promise<azdata.FlexContainer> {
// TODO: replace these text components with the actual chart/table/query plan components
this.component = view.modelBuilder.text().withProps({
value: this.title
}).component();
return await createOneComponentFlexContainer(view, this.component, this.backgroundColor);
}
}

View File

@@ -0,0 +1,31 @@
/*---------------------------------------------------------------------------------------------
* 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 constants from '../common/constants';
import { BaseQueryStoreReport } from './baseQueryStoreReport';
import { QueryStoreView } from './queryStoreView';
export class TopResourceConsumingQueries extends BaseQueryStoreReport {
private queries: QueryStoreView;
private planSummary: QueryStoreView;
private plan: QueryStoreView;
constructor(extensionContext: vscode.ExtensionContext, databaseName: string) {
super(constants.topResourceConsumingQueries, 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');
}
public override async createViews(view: azdata.ModelView): Promise<azdata.FlexContainer[]> {
const queriesContainer = await this.queries.createViewContainer(view);
const planSummaryContainer = await this.planSummary.createViewContainer(view);
const planContainer = await this.plan.createViewContainer(view);
return [queriesContainer, planSummaryContainer, planContainer];
}
}