mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-24 09:35:37 -05:00
Adding Execution Plan Editor to ADS (#18696)
* Pushing Execution Plan Editor * Renaming class Handling error * Awaiting for handlers to be registered * Addressing some PR comments * Fixing return type for provider * Fixing editor id and removing unnecessary overrides * Adding a namespace * adding execution plan namespace * Adding protocol comment * Fixing if logic * Fixing error message * Cleaning up code * cleanup code * Adding help comments * Fixing method call * Using path.ts to get the base file name * converting to lambda functions * Adding comment for run action * Fixing pr comments * Fixing editor label * Fixing doc comments * Adding some more comments * Fixign branding in comments
This commit is contained in:
@@ -39,17 +39,20 @@ import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
import { IExecutionPlanService } from 'sql/workbench/services/executionPlan/common/interfaces';
|
||||
import { LoadingSpinner } from 'sql/base/browser/ui/loadingSpinner/loadingSpinner';
|
||||
import { InfoBox } from 'sql/base/browser/ui/infoBox/infoBox';
|
||||
|
||||
let azdataGraph = azdataGraphModule();
|
||||
|
||||
export interface InternalExecutionPlanNode extends azdata.ExecutionPlanNode {
|
||||
export interface InternalExecutionPlanNode extends azdata.executionPlan.ExecutionPlanNode {
|
||||
/**
|
||||
* Unique internal id given to graph node by ADS.
|
||||
*/
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export interface InternalExecutionPlanEdge extends azdata.ExecutionPlanEdge {
|
||||
export interface InternalExecutionPlanEdge extends azdata.executionPlan.ExecutionPlanEdge {
|
||||
/**
|
||||
* Unique internal id given to graph edge by ADS.
|
||||
*/
|
||||
@@ -78,17 +81,22 @@ export class ExecutionPlanTab implements IPanelTab {
|
||||
}
|
||||
|
||||
export class ExecutionPlanView implements IPanelView {
|
||||
private _loadingSpinner: LoadingSpinner;
|
||||
private _loadingErrorInfoBox: InfoBox;
|
||||
private _eps?: ExecutionPlan[] = [];
|
||||
private _graphs?: azdata.ExecutionPlanGraph[] = [];
|
||||
private _graphs?: azdata.executionPlan.ExecutionPlanGraph[] = [];
|
||||
private _container = DOM.$('.eps-container');
|
||||
|
||||
private _planCache: Map<string, azdata.executionPlan.ExecutionPlanGraph[]> = new Map();
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IExecutionPlanService private executionPlanService: IExecutionPlanService
|
||||
) {
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
container.appendChild(this._container);
|
||||
public render(parent: HTMLElement): void {
|
||||
parent.appendChild(this._container);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
@@ -106,7 +114,11 @@ export class ExecutionPlanView implements IPanelView {
|
||||
DOM.clearNode(this._container);
|
||||
}
|
||||
|
||||
public addGraphs(newGraphs: azdata.ExecutionPlanGraph[] | undefined) {
|
||||
/**
|
||||
* Adds executionPlanGraph to the graph controller.
|
||||
* @param newGraphs ExecutionPlanGraphs to be added.
|
||||
*/
|
||||
public addGraphs(newGraphs: azdata.executionPlan.ExecutionPlanGraph[] | undefined) {
|
||||
if (newGraphs) {
|
||||
newGraphs.forEach(g => {
|
||||
const ep = this.instantiationService.createInstance(ExecutionPlan, this._container, this._eps.length + 1);
|
||||
@@ -118,6 +130,45 @@ export class ExecutionPlanView implements IPanelView {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the graph file by converting the file to generic executionPlan graphs.
|
||||
* This feature requires the right providers to be registered that can handle
|
||||
* the graphFileType in the graphFile
|
||||
* Please note: this method clears the existing graph in the graph control
|
||||
* @param graphFile graph file to be loaded.
|
||||
* @returns
|
||||
*/
|
||||
public async loadGraphFile(graphFile: azdata.executionPlan.ExecutionPlanGraphInfo) {
|
||||
this.clear();
|
||||
this._loadingSpinner = new LoadingSpinner(this._container, { showText: true, fullSize: true });
|
||||
this._loadingSpinner.loadingMessage = localize('loadingExecutionPlanFile', "Generating execution plans");
|
||||
try {
|
||||
this._loadingSpinner.loading = true;
|
||||
if (this._planCache.has(graphFile.graphFileContent)) {
|
||||
this.addGraphs(this._planCache.get(graphFile.graphFileContent));
|
||||
return;
|
||||
} else {
|
||||
const graphs = (await this.executionPlanService.getExecutionPlan({
|
||||
graphFileContent: graphFile.graphFileContent,
|
||||
graphFileType: graphFile.graphFileType
|
||||
})).graphs;
|
||||
this.addGraphs(graphs);
|
||||
this._planCache.set(graphFile.graphFileContent, graphs);
|
||||
}
|
||||
this._loadingSpinner.loadingCompletedMessage = localize('executionPlanFileLoadingComplete', "Execution plans are generated");
|
||||
} catch (e) {
|
||||
this._loadingErrorInfoBox = new InfoBox(this._container, {
|
||||
text: e.toString(),
|
||||
style: 'error',
|
||||
isClickable: false
|
||||
});
|
||||
this._loadingErrorInfoBox.isClickable = false;
|
||||
this._loadingSpinner.loadingCompletedMessage = localize('executionPlanFileLoadingFailed', "Failed to load execution plan");
|
||||
} finally {
|
||||
this._loadingSpinner.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private updateRelativeCosts() {
|
||||
const sum = this._graphs.reduce((prevCost: number, cg) => {
|
||||
return prevCost += cg.root.subTreeCost + cg.root.cost;
|
||||
@@ -132,7 +183,7 @@ export class ExecutionPlanView implements IPanelView {
|
||||
}
|
||||
|
||||
export class ExecutionPlan implements ISashLayoutProvider {
|
||||
private _graphModel?: azdata.ExecutionPlanGraph;
|
||||
private _graphModel?: azdata.executionPlan.ExecutionPlanGraph;
|
||||
|
||||
private _container: HTMLElement;
|
||||
|
||||
@@ -327,7 +378,7 @@ export class ExecutionPlan implements ISashLayoutProvider {
|
||||
return diagramEdge;
|
||||
}
|
||||
|
||||
private populateProperties(props: azdata.ExecutionPlanGraphElementProperty[]) {
|
||||
private populateProperties(props: azdata.executionPlan.ExecutionPlanGraphElementProperty[]) {
|
||||
return props.filter(e => isString(e.displayValue) && e.showInTooltip)
|
||||
.sort((a, b) => a.displayOrder - b.displayOrder)
|
||||
.map(e => {
|
||||
@@ -347,7 +398,7 @@ export class ExecutionPlan implements ISashLayoutProvider {
|
||||
|
||||
private createPlanDiagram(container: HTMLElement) {
|
||||
let diagramRoot: any = new Object();
|
||||
let graphRoot: azdata.ExecutionPlanNode = this._graphModel.root;
|
||||
let graphRoot: azdata.executionPlan.ExecutionPlanNode = this._graphModel.root;
|
||||
|
||||
this.populate(graphRoot, diagramRoot);
|
||||
this.azdataGraphDiagram = new azdataGraph.azdataQueryPlan(container, diagramRoot, executionPlanNodeIconPaths);
|
||||
@@ -385,7 +436,7 @@ export class ExecutionPlan implements ISashLayoutProvider {
|
||||
}
|
||||
|
||||
|
||||
public set graphModel(graph: azdata.ExecutionPlanGraph | undefined) {
|
||||
public set graphModel(graph: azdata.executionPlan.ExecutionPlanGraph | undefined) {
|
||||
this._graphModel = graph;
|
||||
if (this._graphModel) {
|
||||
this.planHeader.graphIndex = this._graphIndex;
|
||||
@@ -420,7 +471,7 @@ export class ExecutionPlan implements ISashLayoutProvider {
|
||||
}
|
||||
}
|
||||
|
||||
public get graphModel(): azdata.ExecutionPlanGraph | undefined {
|
||||
public get graphModel(): azdata.executionPlan.ExecutionPlanGraph | undefined {
|
||||
return this._graphModel;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor';
|
||||
import { EditorExtensions } from 'vs/workbench/common/editor';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExecutionPlanService } from 'sql/workbench/services/executionPlan/common/interfaces';
|
||||
import { ExecutionPlanInput } from 'sql/workbench/contrib/executionPlan/common/executionPlanInput';
|
||||
import { ExecutionPlanEditor } from 'sql/workbench/contrib/executionPlan/browser/executionPlanEditor';
|
||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||
|
||||
// Execution Plan editor registration
|
||||
|
||||
const executionPlanEditorDescriptor = EditorPaneDescriptor.create(
|
||||
ExecutionPlanEditor,
|
||||
ExecutionPlanEditor.ID,
|
||||
ExecutionPlanEditor.LABEL
|
||||
);
|
||||
|
||||
Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane)
|
||||
.registerEditorPane(executionPlanEditorDescriptor, [new SyncDescriptor(ExecutionPlanInput)]);
|
||||
|
||||
export class ExecutionPlanEditorOverrideContribution extends Disposable implements IWorkbenchContribution {
|
||||
constructor(
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IEditorResolverService private _editorResolverService: IEditorResolverService,
|
||||
@IExecutionPlanService private _executionPlanService: IExecutionPlanService,
|
||||
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService,
|
||||
) {
|
||||
super();
|
||||
this.registerEditorOverride();
|
||||
|
||||
this._capabilitiesService.onCapabilitiesRegistered(e => {
|
||||
const newFileFormats = this._executionPlanService.getSupportedExecutionPlanExtensionsForProvider(e.id);
|
||||
if (newFileFormats?.length > 0) {
|
||||
this._editorResolverService.updateUserAssociations(this.getGlobForFileExtensions(newFileFormats), ExecutionPlanEditor.ID); // Registering new file formats when new providers are registered.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public registerEditorOverride(): void {
|
||||
const supportedFileFormats: string[] = [];
|
||||
Object.keys(this._capabilitiesService.providers).forEach(e => {
|
||||
if (this._capabilitiesService.providers[e]?.connection?.supportedExecutionPlanFileExtensions) {
|
||||
supportedFileFormats.push(... this._capabilitiesService.providers[e].connection.supportedExecutionPlanFileExtensions);
|
||||
}
|
||||
});
|
||||
|
||||
this._editorResolverService.registerEditor(
|
||||
this.getGlobForFileExtensions(supportedFileFormats),
|
||||
{
|
||||
id: ExecutionPlanEditor.ID,
|
||||
label: ExecutionPlanEditor.LABEL,
|
||||
priority: RegisteredEditorPriority.builtin
|
||||
},
|
||||
{},
|
||||
(editorInput, group) => {
|
||||
const executionPlanInput = this._instantiationService.createInstance(ExecutionPlanInput, editorInput.resource);
|
||||
return { editor: executionPlanInput, options: editorInput.options, group: group };
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private getGlobForFileExtensions(extensions: string[]): string {
|
||||
return extensions?.length === 0 ? '' : `*.{${extensions.join()}}`;
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
|
||||
.registerWorkbenchContribution(ExecutionPlanEditorOverrideContribution, LifecyclePhase.Restored);
|
||||
@@ -0,0 +1,64 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IEditorOpenContext } from 'vs/workbench/common/editor';
|
||||
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ExecutionPlanInput } from 'sql/workbench/contrib/executionPlan/common/executionPlanInput';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { ExecutionPlanView } from 'sql/workbench/contrib/executionPlan/browser/executionPlan';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export class ExecutionPlanEditor extends EditorPane {
|
||||
|
||||
public static ID: string = 'workbench.editor.executionplan';
|
||||
public static LABEL: string = localize('executionPlanEditor', "Query Execution Plan Editor");
|
||||
|
||||
private view: ExecutionPlanView;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
) {
|
||||
super(ExecutionPlanEditor.ID, telemetryService, themeService, storageService);
|
||||
this.view = this._register(instantiationService.createInstance(ExecutionPlanView));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to create the editor in the parent element.
|
||||
*/
|
||||
public createEditor(parent: HTMLElement): void {
|
||||
//Enable scrollbars when drawing area is larger than viewport
|
||||
parent.style.overflow = 'auto';
|
||||
this.view.render(parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the internal variable keeping track of the editor's size, and re-calculates the sash position.
|
||||
* To be called when the container of this editor changes size.
|
||||
*/
|
||||
public layout(dimension: DOM.Dimension): void {
|
||||
this.view.layout(dimension);
|
||||
}
|
||||
|
||||
public override async setInput(input: ExecutionPlanInput, options: IEditorOptions, context: IEditorOpenContext): Promise<void> {
|
||||
if (this.input instanceof ExecutionPlanInput && this.input.matches(input)) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
await input.resolve();
|
||||
await super.setInput(input, options, context, CancellationToken.None);
|
||||
this.view.loadGraphFile({
|
||||
graphFileContent: input.content,
|
||||
graphFileType: input.getFileExtension().replace('.', '')
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -126,7 +126,7 @@ export class ExecutionPlanPropertiesView {
|
||||
attachTableStyler(this._table, this._themeService);
|
||||
}
|
||||
|
||||
public set graphElement(element: azdata.ExecutionPlanNode | azdata.ExecutionPlanEdge) {
|
||||
public set graphElement(element: azdata.executionPlan.ExecutionPlanNode | azdata.executionPlan.ExecutionPlanEdge) {
|
||||
this._model.graphElement = element;
|
||||
this.sortPropertiesByImportance();
|
||||
this.renderView();
|
||||
@@ -186,7 +186,7 @@ export class ExecutionPlanPropertiesView {
|
||||
|
||||
private renderView(): void {
|
||||
if (this._model.graphElement) {
|
||||
const nodeName = (<azdata.ExecutionPlanNode>this._model.graphElement).name;
|
||||
const nodeName = (<azdata.executionPlan.ExecutionPlanNode>this._model.graphElement).name;
|
||||
this._operationName.innerText = nodeName ? removeLineBreaks(nodeName) : localize('executionPlanPropertiesEdgeOperationName', "Edge"); //since edges do not have names like node, we set the operation name to 'Edge'
|
||||
}
|
||||
this._tableContainer.scrollTo(0, 0);
|
||||
@@ -201,7 +201,7 @@ export class ExecutionPlanPropertiesView {
|
||||
this._table.resizeCanvas();
|
||||
}
|
||||
|
||||
private convertPropertiesToTableRows(props: azdata.ExecutionPlanGraphElementProperty[], parentIndex: number, indent: number, rows: { [key: string]: string }[] = []): { [key: string]: string }[] {
|
||||
private convertPropertiesToTableRows(props: azdata.executionPlan.ExecutionPlanGraphElementProperty[], parentIndex: number, indent: number, rows: { [key: string]: string }[] = []): { [key: string]: string }[] {
|
||||
if (!props) {
|
||||
return rows;
|
||||
}
|
||||
@@ -228,7 +228,7 @@ export class ExecutionPlanPropertiesView {
|
||||
}
|
||||
|
||||
export interface GraphElementPropertyViewData {
|
||||
graphElement: azdata.ExecutionPlanNode | azdata.ExecutionPlanEdge;
|
||||
graphElement: azdata.executionPlan.ExecutionPlanNode | azdata.executionPlan.ExecutionPlanEdge;
|
||||
}
|
||||
|
||||
export class ClosePropertyViewAction extends Action {
|
||||
|
||||
@@ -22,7 +22,7 @@ export class PlanHeader {
|
||||
private _query: string;
|
||||
private _queryContainer: HTMLElement; // container that holds query text
|
||||
|
||||
private _recommendations: azdata.ExecutionPlanRecommendations[];
|
||||
private _recommendations: azdata.executionPlan.ExecutionPlanRecommendations[];
|
||||
private _recommendationsContainer: HTMLElement; // container that holds graph recommendations
|
||||
|
||||
public constructor(
|
||||
@@ -61,7 +61,7 @@ export class PlanHeader {
|
||||
this.renderQueryText();
|
||||
}
|
||||
|
||||
public set recommendations(recommendations: azdata.ExecutionPlanRecommendations[]) {
|
||||
public set recommendations(recommendations: azdata.executionPlan.ExecutionPlanRecommendations[]) {
|
||||
recommendations.forEach(r => {
|
||||
r.displayString = removeLineBreaks(r.displayString);
|
||||
});
|
||||
@@ -113,5 +113,5 @@ export interface PlanHeaderData {
|
||||
planIndex?: number;
|
||||
relativeCost?: number;
|
||||
query?: string;
|
||||
recommendations?: azdata.ExecutionPlanRecommendations[];
|
||||
recommendations?: azdata.executionPlan.ExecutionPlanRecommendations[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user