Redoing Execution Plan Comparison Editor (#19375)
* Adding code for execution plan comparison editor * Handling floating promises and fixing a loc string * Fixing some polygon stuff * Fixing azdatagraph null check bug * Adding progress notification for similar areas * Removing some floating promises * Fixing button enabled state
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>BranchCompare_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline"><path class="icon-vs-out" d="M14,9.619V5.5a3.507,3.507,0,0,0-2.717-3.412l.235-.234L9.664,0H9.129L6.656,2.473A3.25,3.25,0,1,0,2,6.131V10.5a3.506,3.506,0,0,0,2.717,3.412l-.235.234L6.336,16h.535L9.4,13.473A3.25,3.25,0,1,0,14,9.619Zm-3,0a3.224,3.224,0,0,0-1.6,1.907L6.6,8.732,5,10.336v-4.2A3.222,3.222,0,0,0,6.538,4.41L9.4,7.268l1.6-1.6Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M13,10.311V5.5A2.5,2.5,0,0,0,10.5,3H8.957L10.1,1.854,9.4,1.146,7.043,3.5,9.4,5.854l.708-.708L8.957,4H10.5A1.5,1.5,0,0,1,12,5.5v4.811a2.25,2.25,0,1,0,1,0Zm-.5,3.439a1.25,1.25,0,1,1,1.25-1.25A1.252,1.252,0,0,1,12.5,13.75Zm-6.6-2.9L7.043,12H5.5A1.5,1.5,0,0,1,4,10.5V5.44a2.25,2.25,0,1,0-1,0V10.5A2.5,2.5,0,0,0,5.5,13H7.043L5.9,14.146l.708.708L8.957,12.5,6.6,10.146ZM2.25,3.25A1.25,1.25,0,1,1,3.5,4.5,1.252,1.252,0,0,1,2.25,3.25Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
extensions/theme-seti/icons/images/execution-plan.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.st0{opacity:0}.st0,.st1{fill:#f6f6f6}.st2{fill:#424242}.st3{fill:#f0eff1}</style><g id="outline"><path class="st0" d="M0 0h16v16H0z"/><path class="st1" d="M9 5v2H8V0H1v7h2v8h6v1h6V5H9zm0 7H6v-2h3v2z"/></g><path class="st2" d="M14 10V6h-4v2H5V6h2V1H2v5h2v8h6v1h4v-4h-4v2H5V9h5v1h4zm-3-3h2v2h-2V7zm0 5h2v2h-2v-2zM3 5V2h3v3H3z" id="icon_x5F_bg"/><g id="icon_x5F_fg"><path class="st3" d="M11 7h2v2h-2zM11 12h2v2h-2zM3 2h3v3H3z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 503 B |
@@ -1529,6 +1529,12 @@
|
||||
},
|
||||
"table-graphedge": {
|
||||
"iconPath": "./images/table-graphedge.svg"
|
||||
},
|
||||
"execution-plan": {
|
||||
"iconPath": "./images/execution-plan.svg"
|
||||
},
|
||||
"execution-plan-compare": {
|
||||
"iconPath": "./images/execution-plan-compare.svg"
|
||||
}
|
||||
},
|
||||
"file": "_default",
|
||||
@@ -1830,7 +1836,9 @@
|
||||
"table-basic": "table-basic",
|
||||
"table-temporal": "table-temporal",
|
||||
"table-graphnode": "table-graphnode",
|
||||
"table-graphedge": "table-graphedge"
|
||||
"table-graphedge": "table-graphedge",
|
||||
"execution-plan": "execution-plan",
|
||||
"execution-plan-compare": "execution-plan-compare"
|
||||
},
|
||||
"languageIds": {
|
||||
"bat": "_windows",
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
"angular2-grid": "2.0.6",
|
||||
"ansi_up": "^5.1.0",
|
||||
"applicationinsights": "1.0.8",
|
||||
"azdataGraph": "github:Microsoft/azdataGraph#0.0.21",
|
||||
"azdataGraph": "github:Microsoft/azdataGraph#0.0.26",
|
||||
"chart.js": "^2.9.4",
|
||||
"chokidar": "3.5.2",
|
||||
"graceful-fs": "4.2.6",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"applicationinsights": "1.0.8",
|
||||
"angular2-grid": "2.0.6",
|
||||
"ansi_up": "^5.1.0",
|
||||
"azdataGraph": "github:Microsoft/azdataGraph#0.0.21",
|
||||
"azdataGraph": "github:Microsoft/azdataGraph#0.0.26",
|
||||
"chart.js": "^2.9.4",
|
||||
"chokidar": "3.5.2",
|
||||
"cookie": "^0.4.0",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"@vscode/vscode-languagedetection": "1.0.18",
|
||||
"angular2-grid": "2.0.6",
|
||||
"ansi_up": "^5.1.0",
|
||||
"azdataGraph": "github:Microsoft/azdataGraph#0.0.21",
|
||||
"azdataGraph": "github:Microsoft/azdataGraph#0.0.26",
|
||||
"chart.js": "^2.9.4",
|
||||
"gridstack": "^3.1.3",
|
||||
"kburtram-query-plan": "2.6.1",
|
||||
|
||||
@@ -150,9 +150,9 @@ array-uniq@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
|
||||
integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=
|
||||
|
||||
"azdataGraph@github:Microsoft/azdataGraph#0.0.21":
|
||||
version "0.0.21"
|
||||
resolved "https://codeload.github.com/Microsoft/azdataGraph/tar.gz/59f38fc96ab8beadb5ed2b6986ae3f39d662d040"
|
||||
"azdataGraph@github:Microsoft/azdataGraph#0.0.26":
|
||||
version "0.0.26"
|
||||
resolved "https://codeload.github.com/Microsoft/azdataGraph/tar.gz/a5f94e53cb655bc44f1a2727653bb403942e1cf9"
|
||||
|
||||
chalk@^2.3.0, chalk@^2.4.1:
|
||||
version "2.4.2"
|
||||
|
||||
@@ -198,9 +198,9 @@ array-uniq@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
|
||||
integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=
|
||||
|
||||
"azdataGraph@github:Microsoft/azdataGraph#0.0.21":
|
||||
version "0.0.21"
|
||||
resolved "https://codeload.github.com/Microsoft/azdataGraph/tar.gz/59f38fc96ab8beadb5ed2b6986ae3f39d662d040"
|
||||
"azdataGraph@github:Microsoft/azdataGraph#0.0.26":
|
||||
version "0.0.26"
|
||||
resolved "https://codeload.github.com/Microsoft/azdataGraph/tar.gz/a5f94e53cb655bc44f1a2727653bb403942e1cf9"
|
||||
|
||||
binary-extensions@^2.0.0:
|
||||
version "2.0.0"
|
||||
|
||||
10
src/sql/azdata.proposed.d.ts
vendored
@@ -1232,6 +1232,10 @@ declare module 'azdata' {
|
||||
}
|
||||
|
||||
export interface ExecutionPlanNode {
|
||||
/**
|
||||
* Unique id given to node by the provider
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Type of the node. This property determines the icon that is displayed for it
|
||||
*/
|
||||
@@ -1365,6 +1369,10 @@ declare module 'azdata' {
|
||||
* File type for execution plan. This will be the file type of the editor when the user opens the graph file
|
||||
*/
|
||||
graphFileType: string;
|
||||
/**
|
||||
* Index of the execution plan in the file content
|
||||
*/
|
||||
planIndexInFile?: number;
|
||||
}
|
||||
|
||||
export interface GetExecutionPlanResult extends ResultStatus {
|
||||
@@ -1391,7 +1399,7 @@ declare module 'azdata' {
|
||||
/**
|
||||
* List of matching nodes for the ExecutionGraphComparisonResult.
|
||||
*/
|
||||
matchingNodes: ExecutionGraphComparisonResult[];
|
||||
matchingNodesId: number[];
|
||||
/**
|
||||
* The parent of the ExecutionGraphComparisonResult.
|
||||
*/
|
||||
|
||||
@@ -553,7 +553,8 @@ export class MainThreadDataProtocol extends Disposable implements MainThreadData
|
||||
|
||||
public $registerExecutionPlanProvider(providerId: string, handle: number): void {
|
||||
this._executionPlanService.registerProvider(providerId, <azdata.executionPlan.ExecutionPlanProvider>{
|
||||
getExecutionPlan: (planFile: azdata.executionPlan.ExecutionPlanGraphInfo) => this._proxy.$getExecutionPlan(handle, planFile)
|
||||
getExecutionPlan: (planFile: azdata.executionPlan.ExecutionPlanGraphInfo) => this._proxy.$getExecutionPlan(handle, planFile),
|
||||
compareExecutionPlanGraph: (firstPlanFile: azdata.executionPlan.ExecutionPlanGraphInfo, secondPlanFile: azdata.executionPlan.ExecutionPlanGraphInfo) => this._proxy.$compareExecutionPlanGraph(handle, firstPlanFile, secondPlanFile)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdataGraphModule from 'azdataGraph';
|
||||
import type * as azdata from 'azdata';
|
||||
import * as azdata from 'azdata';
|
||||
import * as sqlExtHostType from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
@@ -12,7 +12,8 @@ import { badgeIconPaths, executionPlanNodeIconPaths } from 'sql/workbench/contri
|
||||
import { localize } from 'vs/nls';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IColorTheme, ICssStyleCollector, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { editorBackground, foreground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { foreground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
const azdataGraph = azdataGraphModule();
|
||||
|
||||
/**
|
||||
@@ -23,7 +24,6 @@ export class AzdataGraphView {
|
||||
|
||||
private _diagram: any;
|
||||
private _diagramModel: AzDataGraphCell;
|
||||
private _uniqueElementId: number = -1;
|
||||
private _cellInFocus: AzDataGraphCell;
|
||||
|
||||
private _graphElementPropertiesSet: Set<string> = new Set();
|
||||
@@ -51,11 +51,6 @@ export class AzdataGraphView {
|
||||
this._diagram.graph.tooltipHandler.delay = 700; // increasing delay for tooltips
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
const iconBackground = theme.getColor(editorBackground);
|
||||
if (iconBackground) {
|
||||
this._diagram.setIconBackgroundColor(iconBackground);
|
||||
}
|
||||
|
||||
const iconLabelColor = theme.getColor(foreground);
|
||||
if (iconLabelColor) {
|
||||
this._diagram.setTextFontColor(iconLabelColor);
|
||||
@@ -99,7 +94,7 @@ export class AzdataGraphView {
|
||||
if (element) {
|
||||
cell = this._diagram.graph.model.getCell(element.id);
|
||||
} else {
|
||||
cell = this._diagram.graph.model.getCell((<InternalExecutionPlanNode>this._executionPlan.root).id);
|
||||
cell = this._diagram.graph.model.getCell((<azdata.executionPlan.ExecutionPlanNode>this._executionPlan.root).id);
|
||||
}
|
||||
this._diagram.graph.getSelectionModel().setCell(cell);
|
||||
if (bringToCenter) {
|
||||
@@ -125,7 +120,6 @@ export class AzdataGraphView {
|
||||
this._diagram.zoomIn();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Zooms out of the diagram
|
||||
*/
|
||||
@@ -163,7 +157,7 @@ export class AzdataGraphView {
|
||||
* @param id id of the diagram element
|
||||
*/
|
||||
public getElementById(id: string): InternalExecutionPlanElement | undefined {
|
||||
const nodeStack: InternalExecutionPlanNode[] = [];
|
||||
const nodeStack: azdata.executionPlan.ExecutionPlanNode[] = [];
|
||||
nodeStack.push(this._executionPlan.root);
|
||||
while (nodeStack.length !== 0) {
|
||||
const currentNode = nodeStack.pop();
|
||||
@@ -185,10 +179,10 @@ export class AzdataGraphView {
|
||||
/**
|
||||
* Searches the diagram nodes based on the search query provided.
|
||||
*/
|
||||
public searchNodes(searchQuery: SearchQuery): InternalExecutionPlanNode[] {
|
||||
const resultNodes: InternalExecutionPlanNode[] = [];
|
||||
public searchNodes(searchQuery: SearchQuery): azdata.executionPlan.ExecutionPlanNode[] {
|
||||
const resultNodes: azdata.executionPlan.ExecutionPlanNode[] = [];
|
||||
|
||||
const nodeStack: InternalExecutionPlanNode[] = [];
|
||||
const nodeStack: azdata.executionPlan.ExecutionPlanNode[] = [];
|
||||
nodeStack.push(this._executionPlan.root);
|
||||
|
||||
while (nodeStack.length !== 0) {
|
||||
@@ -287,13 +281,14 @@ export class AzdataGraphView {
|
||||
});
|
||||
}
|
||||
|
||||
private populate(node: InternalExecutionPlanNode): AzDataGraphCell {
|
||||
private populate(node: azdata.executionPlan.ExecutionPlanNode): AzDataGraphCell {
|
||||
let diagramNode: AzDataGraphCell = <AzDataGraphCell>{};
|
||||
diagramNode.label = node.subtext.join(this.textResourcePropertiesService.getEOL(undefined));
|
||||
diagramNode.tooltipTitle = node.name;
|
||||
const nodeId = this.createGraphElementId();
|
||||
diagramNode.id = nodeId;
|
||||
node.id = nodeId;
|
||||
if (!node.id.toString().startsWith(`element-`)) {
|
||||
node.id = `element-${node.id}`;
|
||||
}
|
||||
diagramNode.id = node.id;
|
||||
|
||||
if (node.type) {
|
||||
diagramNode.icon = node.type;
|
||||
@@ -383,8 +378,7 @@ export class AzdataGraphView {
|
||||
}
|
||||
|
||||
private createGraphElementId(): string {
|
||||
this._uniqueElementId += 1;
|
||||
return `element-${this._uniqueElementId}`;
|
||||
return `element-${generateUuid()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -406,13 +400,15 @@ export class AzdataGraphView {
|
||||
}
|
||||
return this._diagram.graph.tooltipHandler.enabled;
|
||||
}
|
||||
}
|
||||
|
||||
export interface InternalExecutionPlanNode extends azdata.executionPlan.ExecutionPlanNode {
|
||||
/**
|
||||
* Unique internal id given to graph node by ADS.
|
||||
*/
|
||||
id?: string;
|
||||
public drawSubtreePolygon(subtreeRoot: string, fillColor: string, borderColor: string): void {
|
||||
const drawPolygon = this._diagram.graph.model.getCell(`element-${subtreeRoot}`);
|
||||
this._diagram.drawPolygon(drawPolygon, fillColor, borderColor);
|
||||
}
|
||||
|
||||
public clearSubtreePolygon(): void {
|
||||
this._diagram.removeDrawnPolygons();
|
||||
}
|
||||
}
|
||||
|
||||
export interface InternalExecutionPlanEdge extends azdata.executionPlan.ExecutionPlanEdge {
|
||||
@@ -422,7 +418,7 @@ export interface InternalExecutionPlanEdge extends azdata.executionPlan.Executio
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export type InternalExecutionPlanElement = InternalExecutionPlanEdge | InternalExecutionPlanNode;
|
||||
export type InternalExecutionPlanElement = InternalExecutionPlanEdge | azdata.executionPlan.ExecutionPlanNode;
|
||||
|
||||
export interface AzDataGraphCell {
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { localize } from 'vs/nls';
|
||||
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ExecutionPlanComparisonEditorView } from 'sql/workbench/contrib/executionPlan/browser/executionPlanComparisonEditorView';
|
||||
|
||||
export class ExecutionPlanComparisonInput extends EditorInput {
|
||||
public static ID: string = 'workbench.editorinputs.compareExecutionPlanInput';
|
||||
public static SCHEME: string = 'compareExecutionPlanInput';
|
||||
private readonly editorNamePrefix = localize('epCompare.editorName', "Compare Execution Plans");
|
||||
private _editorName: string;
|
||||
|
||||
// Caching the views for faster tab switching
|
||||
public _executionPlanComparisonView: ExecutionPlanComparisonEditorView;
|
||||
|
||||
constructor(
|
||||
public preloadModel: ExecutionPlanComparisonEditorModel | undefined,
|
||||
@IEditorService private readonly _editorService: IEditorService
|
||||
) {
|
||||
super();
|
||||
|
||||
// Getting name for the editor
|
||||
const existingNames = this._editorService.editors.map(editor => editor.getName());
|
||||
let i = 0;
|
||||
this._editorName = `${this.editorNamePrefix} ${i}`;
|
||||
while (existingNames.includes(this._editorName)) {
|
||||
i++;
|
||||
this._editorName = `${this.editorNamePrefix} ${i}`;
|
||||
}
|
||||
}
|
||||
|
||||
get typeId(): string {
|
||||
return ExecutionPlanComparisonInput.ID;
|
||||
}
|
||||
|
||||
get resource(): URI {
|
||||
return URI.from({
|
||||
scheme: ExecutionPlanComparisonInput.SCHEME,
|
||||
path: 'execution-plan-compare'
|
||||
});
|
||||
}
|
||||
|
||||
public override getName(): string {
|
||||
return this._editorName;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ExecutionPlanComparisonEditorModel {
|
||||
topExecutionPlan?: azdata.executionPlan.ExecutionPlanGraph[];
|
||||
bottomExecutionPlan?: azdata.executionPlan.ExecutionPlanGraph[];
|
||||
}
|
||||
@@ -273,3 +273,87 @@ export const zoomToFitIconClassNames = 'ep-zoom-to-fit-icon';
|
||||
export const zoomIconClassNames = 'ep-zoom-icon';
|
||||
export const enableTooltipIconClassName = 'ep-enable-tooltip-icon';
|
||||
export const disableTooltipIconClassName = 'ep-disable-tooltip-icon';
|
||||
export const addIconClassName = 'ep-add-icon';
|
||||
export const settingsIconClassName = 'ep-settings-icon';
|
||||
export const splitScreenHorizontallyIconClassName = 'ep-split-screen-horizontally-icon';
|
||||
export const splitScreenVerticallyIconClassName = 'ep-split-screen-vertically-icon';
|
||||
export const resetZoomIconClassName = 'ep-reset-zoom-icon';
|
||||
export const executionPlanCompareIconClassName = 'ep-plan-compare-icon';
|
||||
|
||||
/**
|
||||
* Plan comparison polygon border colors
|
||||
*/
|
||||
export const polygonBorderColor: string[] = [
|
||||
`rgba(0, 188, 242)`, // "themeMain blue"
|
||||
`rgba(236, 0, 140)`, // "themeError pink"
|
||||
`rgba(0, 216, 204)`, // "h2 blue"
|
||||
`rgba(236, 0, 140)`, // "b0 orange"
|
||||
`rgba(255, 140, 0)`, // "themeWarning orange"
|
||||
`rgba(127, 186, 0)`, // "themeSuccess green"
|
||||
`rgba(252, 214, 241)`, // "paletteDiffDel light pink"
|
||||
`rgba(252, 209, 22)`, // "a1 gold"
|
||||
`rgba(68,35,89)`, // "e1 dark purple"
|
||||
`rgba(0, 114, 198)`, // "g1 blue"
|
||||
`rgba(160, 165, 168)`, // "i1 green"
|
||||
`rgba(255, 140, 0)`, // "k1 grey"
|
||||
`rgba(199, 241, 199)`, // "paletteDiffAdd light green"
|
||||
`rgba(0, 24, 143)`, // "d0 pink",
|
||||
`rgba(186, 216, 10)`, // "f0 royal blue"
|
||||
`rgba(255, 252, 158)`, // "h0 seafoam green"
|
||||
`rgba(221, 89, 0)`, // "j0 yellow green"
|
||||
`rgba(155, 79, 150)`, // "a2 light yellow"
|
||||
`rgba(109, 194, 233)`, // "c2 burnt orange"
|
||||
`rgba(85, 212, 85)`, // "e2 purple"
|
||||
`rgba(180, 0, 158)`, // "d1 purple"
|
||||
`rgba(0, 32, 80)`, // "f1 navy blue"
|
||||
`rgba(0, 130, 114)`, // "h1 blue green"
|
||||
`rgba(127, 186, 0)`, // "j1 yellow green"
|
||||
`rgba(255, 241, 0)`, // "a0 bright yellow"
|
||||
`rgba(104, 33, 122)`, // "e0 purple"
|
||||
`rgba(0, 188, 242)`, // "g0 sky blue"
|
||||
`rgba(0, 158, 73)`, // "i0 green"
|
||||
`rgba(187, 194, 202)`, // "k0 grey"
|
||||
`rgba(255, 185, 0)`, // "b2 gold"
|
||||
`rgba(244, 114, 208)`, // "d2 pink"
|
||||
`rgba(70, 104, 197)`, // "f2 blue purple"
|
||||
`rgba(226, 229, 132)`, // "j2 khaki"
|
||||
];
|
||||
|
||||
/**
|
||||
* Plan comparison polygon fill colors
|
||||
*/
|
||||
export const polygonFillColor: string[] = [
|
||||
`rgba(0, 188, 242, 0.1)`, // "themeMain blue"
|
||||
`rgba(236, 0, 140, 0.1)`, // "themeError pink"
|
||||
`rgba(0, 216, 204, 0.1)`, // "h2 blue"
|
||||
`rgba(236, 0, 140, 0.1)`, // "b0 orange"
|
||||
`rgba(255, 140, 0, 0.1)`, // "themeWarning orange"
|
||||
`rgba(127, 186, 0, 0.1)`, // "themeSuccess green"
|
||||
`rgba(252, 214, 241, 0.1)`, // "paletteDiffDel light pink"
|
||||
`rgba(252, 209, 22, 0.1)`, // "a1 gold"
|
||||
`rgba(68,35,89, 0.1)`, // "e1 dark purple"
|
||||
`rgba(0, 114, 198, 0.1)`, // "g1 blue"
|
||||
`rgba(160, 165, 168, 0.1)`, // "i1 green"
|
||||
`rgba(255, 140, 0, 0.1)`, // "k1 grey"
|
||||
`rgba(199, 241, 199, 0.1)`, // "paletteDiffAdd light green"
|
||||
`rgba(0, 24, 143, 0.1)`, // "d0 pink",
|
||||
`rgba(186, 216, 10, 0.1)`, // "f0 royal blue"
|
||||
`rgba(255, 252, 158, 0.1)`, // "h0 seafoam green"
|
||||
`rgba(221, 89, 0, 0.1)`, // "j0 yellow green"
|
||||
`rgba(155, 79, 150, 0.1)`, // "a2 light yellow"
|
||||
`rgba(109, 194, 233, 0.1)`, // "c2 burnt orange"
|
||||
`rgba(85, 212, 85, 0.1)`, // "e2 purple"
|
||||
`rgba(180, 0, 158, 0.1)`, // "d1 purple"
|
||||
`rgba(0, 32, 80, 0.1)`, // "f1 navy blue"
|
||||
`rgba(0, 130, 114, 0.1)`, // "h1 blue green"
|
||||
`rgba(127, 186, 0, 0.1)`, // "j1 yellow green"
|
||||
`rgba(255, 241, 0, 0.1)`, // "a0 bright yellow"
|
||||
`rgba(104, 33, 122, 0.1)`, // "e0 purple"
|
||||
`rgba(0, 188, 242, 0.1)`, // "g0 sky blue"
|
||||
`rgba(0, 158, 73, 0.1)`, // "i0 green"
|
||||
`rgba(187, 194, 202, 0.1)`, // "k0 grey"
|
||||
`rgba(255, 185, 0, 0.1)`, // "b2 gold"
|
||||
`rgba(244, 114, 208, 0.1)`, // "d2 pink"
|
||||
`rgba(70, 104, 197, 0.1)`, // "f2 blue purple"
|
||||
`rgba(226, 229, 132, 0.1)`, // "j2 khaki"
|
||||
];
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/executionPlan';
|
||||
import { localize } from 'vs/nls';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
|
||||
import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { IEditorOpenContext } from 'vs/workbench/common/editor';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ExecutionPlanComparisonInput } from 'sql/workbench/contrib/executionPlan/browser/compareExecutionPlanInput';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ExecutionPlanComparisonEditorView } from 'sql/workbench/contrib/executionPlan/browser/executionPlanComparisonEditorView';
|
||||
|
||||
|
||||
export class ExecutionPlanComparisonEditor extends EditorPane {
|
||||
public static ID: string = 'workbench.editor.compareExecutionPlan';
|
||||
public static LABEL: string = localize('compareExecutionPlanEditor', "Compare Execution Plan Editor");
|
||||
|
||||
private _editorContainer: HTMLElement;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IContextViewService readonly contextViewService: IContextViewService
|
||||
) {
|
||||
super(ExecutionPlanComparisonEditor.ID, telemetryService, themeService, storageService);
|
||||
}
|
||||
|
||||
protected createEditor(parent: HTMLElement): void {
|
||||
this._editorContainer = DOM.$('.eps-container');
|
||||
parent.appendChild(this._editorContainer);
|
||||
}
|
||||
|
||||
public override get input(): ExecutionPlanComparisonInput {
|
||||
return <ExecutionPlanComparisonInput>super.input;
|
||||
}
|
||||
|
||||
layout(dimension: DOM.Dimension): void {
|
||||
this._editorContainer.style.width = dimension.width + 'px';
|
||||
this._editorContainer.style.height = dimension.height + 'px';
|
||||
}
|
||||
|
||||
public override async setInput(input: ExecutionPlanComparisonInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
|
||||
const oldInput = this.input as ExecutionPlanComparisonInput;
|
||||
|
||||
// returning when new input is the same as current input
|
||||
if (oldInput && input.matches(oldInput)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
super.setInput(input, options, context, token);
|
||||
|
||||
// removing existing comparison containers
|
||||
while (this._editorContainer.firstChild) {
|
||||
this._editorContainer.removeChild(this._editorContainer.firstChild);
|
||||
}
|
||||
|
||||
// creating a new comparison view if the new input does not already have a cached one.
|
||||
if (!input._executionPlanComparisonView) {
|
||||
input._executionPlanComparisonView = this._instantiationService.createInstance(ExecutionPlanComparisonEditorView, this._editorContainer);
|
||||
if (this.input.preloadModel) {
|
||||
if (this.input.preloadModel.topExecutionPlan) {
|
||||
input._executionPlanComparisonView.addExecutionPlanGraph(this.input.preloadModel.topExecutionPlan);
|
||||
}
|
||||
if (this.input.preloadModel.bottomExecutionPlan) {
|
||||
input._executionPlanComparisonView.addExecutionPlanGraph(this.input.preloadModel.bottomExecutionPlan);
|
||||
}
|
||||
}
|
||||
} else { // Getting the cached comparison view from the input and adding it to the base editor node.
|
||||
this._editorContainer.appendChild(input._executionPlanComparisonView.container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,675 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||
import { ITaskbarContent, Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||
import { AzdataGraphView } from 'sql/workbench/contrib/executionPlan/browser/azdataGraphView';
|
||||
import { ExecutionPlanComparisonPropertiesView } from 'sql/workbench/contrib/executionPlan/browser/executionPlanComparisonPropertiesView';
|
||||
import { IExecutionPlanService } from 'sql/workbench/services/executionPlan/common/interfaces';
|
||||
import { IHorizontalSashLayoutProvider, ISashEvent, IVerticalSashLayoutProvider, Orientation, Sash } from 'vs/base/browser/ui/sash/sash';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { localize } from 'vs/nls';
|
||||
import { addIconClassName, openPropertiesIconClassNames, polygonBorderColor, polygonFillColor, resetZoomIconClassName, splitScreenHorizontallyIconClassName, splitScreenVerticallyIconClassName, zoomInIconClassNames, zoomOutIconClassNames, zoomToFitIconClassNames } from 'sql/workbench/contrib/executionPlan/browser/constants';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { extname } from 'vs/base/common/path';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { InfoBox } from 'sql/workbench/browser/ui/infoBox/infoBox';
|
||||
import { LoadingSpinner } from 'sql/base/browser/ui/loadingSpinner/loadingSpinner';
|
||||
import { errorForeground, listHoverBackground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ExecutionPlanViewHeader } from 'sql/workbench/contrib/executionPlan/browser/executionPlanViewHeader';
|
||||
import { attachSelectBoxStyler } from 'sql/platform/theme/common/styler';
|
||||
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
|
||||
|
||||
|
||||
export class ExecutionPlanComparisonEditorView {
|
||||
|
||||
public container: HTMLElement;
|
||||
|
||||
private _taskbarContainer: HTMLElement;
|
||||
private _taskbar: Taskbar;
|
||||
private _addExecutionPlanAction: Action;
|
||||
private _zoomInAction: Action;
|
||||
private _zoomOutAction: Action;
|
||||
private _zoomToFitAction: Action;
|
||||
private _resetZoomAction: Action;
|
||||
private _propertiesAction: Action;
|
||||
private _toggleOrientationAction: Action;
|
||||
|
||||
private _planComparisonContainer: HTMLElement;
|
||||
|
||||
private _propertiesContainer: HTMLElement;
|
||||
private _propertiesView: ExecutionPlanComparisonPropertiesView;
|
||||
|
||||
public planSplitViewContainer: HTMLElement;
|
||||
|
||||
private _sashContainer: HTMLElement;
|
||||
private _horizontalSash: Sash;
|
||||
private _verticalSash: Sash;
|
||||
private _orientation: 'horizontal' | 'vertical' = 'horizontal';
|
||||
|
||||
private _placeholderContainer: HTMLElement;
|
||||
private _placeholderInfoboxContainer: HTMLElement;
|
||||
private _placeholderInfobox: InfoBox;
|
||||
private _placeholderLoading: LoadingSpinner;
|
||||
|
||||
private _topPlanContainer: HTMLElement;
|
||||
private _topPlanDropdown: SelectBox;
|
||||
private _topPlanDropdownContainer: HTMLElement;
|
||||
private _topPlanDiagramContainers: HTMLElement[] = [];
|
||||
public topPlanDiagrams: AzdataGraphView[] = [];
|
||||
private _topPlanDiagramModels: azdata.executionPlan.ExecutionPlanGraph[];
|
||||
private _activeTopPlanIndex: number = 0;
|
||||
private _topPlanRecommendations: ExecutionPlanViewHeader;
|
||||
private _topSimilarNode: Map<string, azdata.executionPlan.ExecutionGraphComparisonResult> = new Map();
|
||||
private _polygonRootsMap: Map<number, {
|
||||
topPolygon: azdata.executionPlan.ExecutionGraphComparisonResult,
|
||||
bottomPolygon: azdata.executionPlan.ExecutionGraphComparisonResult
|
||||
}> = new Map();
|
||||
|
||||
private get _activeTopPlanDiagram(): AzdataGraphView {
|
||||
if (this.topPlanDiagrams.length > 0) {
|
||||
return this.topPlanDiagrams[this._activeTopPlanIndex];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _bottomPlanContainer: HTMLElement;
|
||||
private _bottomPlanDropdown: SelectBox;
|
||||
private _bottomPlanDropdownContainer: HTMLElement;
|
||||
private _bottomPlanDiagramContainers: HTMLElement[] = [];
|
||||
public bottomPlanDiagrams: AzdataGraphView[] = [];
|
||||
private _bottomPlanDiagramModels: azdata.executionPlan.ExecutionPlanGraph[];
|
||||
private _activeBottomPlanIndex: number = 0;
|
||||
private _bottomPlanRecommendations: ExecutionPlanViewHeader;
|
||||
private _bottomSimilarNode: Map<string, azdata.executionPlan.ExecutionGraphComparisonResult> = new Map();
|
||||
|
||||
|
||||
private get _activeBottomPlanDiagram(): AzdataGraphView {
|
||||
if (this.bottomPlanDiagrams.length > 0) {
|
||||
return this.bottomPlanDiagrams[this._activeBottomPlanIndex];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
constructor(
|
||||
parentContainer: HTMLElement,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IThemeService private themeService: IThemeService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IExecutionPlanService private _executionPlanService: IExecutionPlanService,
|
||||
@IFileDialogService private _fileDialogService: IFileDialogService,
|
||||
@IContextViewService readonly contextViewService: IContextViewService,
|
||||
@ITextFileService private readonly _textFileService: ITextFileService,
|
||||
@INotificationService private _notificationService: INotificationService,
|
||||
@IProgressService private _progressService: IProgressService
|
||||
) {
|
||||
|
||||
this.container = DOM.$('.comparison-editor');
|
||||
parentContainer.appendChild(this.container);
|
||||
this.initializeToolbar();
|
||||
this.initializePlanComparison();
|
||||
this.refreshSplitView();
|
||||
}
|
||||
|
||||
// creating and adding editor toolbar actions
|
||||
private initializeToolbar(): void {
|
||||
this._taskbarContainer = DOM.$('.editor-toolbar');
|
||||
this._taskbar = new Taskbar(this._taskbarContainer, {
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
|
||||
});
|
||||
this._taskbar.context = this;
|
||||
this._addExecutionPlanAction = new AddExecutionPlanAction();
|
||||
this._zoomOutAction = new ZoomOutAction();
|
||||
this._zoomInAction = new ZoomInAction();
|
||||
this._zoomToFitAction = new ZoomToFitAction();
|
||||
this._propertiesAction = new PropertiesAction();
|
||||
this._toggleOrientationAction = new ToggleOrientation();
|
||||
this._resetZoomAction = new ZoomReset();
|
||||
const content: ITaskbarContent[] = [
|
||||
{ action: this._addExecutionPlanAction },
|
||||
{ action: this._zoomInAction },
|
||||
{ action: this._zoomOutAction },
|
||||
{ action: this._zoomToFitAction },
|
||||
{ action: this._resetZoomAction },
|
||||
{ action: this._toggleOrientationAction },
|
||||
{ action: this._propertiesAction }
|
||||
];
|
||||
this._taskbar.setContent(content);
|
||||
this.container.appendChild(this._taskbarContainer);
|
||||
}
|
||||
|
||||
private initializePlanComparison(): void {
|
||||
this._planComparisonContainer = DOM.$('.plan-comparison-container');
|
||||
this.container.appendChild(this._planComparisonContainer);
|
||||
this.initializeSplitView();
|
||||
this.initializeProperties();
|
||||
}
|
||||
|
||||
private initializeSplitView(): void {
|
||||
this.planSplitViewContainer = DOM.$('.split-view-container');
|
||||
this._planComparisonContainer.appendChild(this.planSplitViewContainer);
|
||||
|
||||
this._placeholderContainer = DOM.$('.placeholder');
|
||||
this._placeholderInfoboxContainer = DOM.$('.placeholder-infobox');
|
||||
this._placeholderLoading = new LoadingSpinner(this._placeholderContainer, {
|
||||
fullSize: true,
|
||||
showText: true
|
||||
});
|
||||
this._placeholderContainer.appendChild(this._placeholderInfoboxContainer);
|
||||
this._placeholderLoading.loadingMessage = localize('epComapre.LoadingPlanMessage', "Loading execution plan");
|
||||
this._placeholderLoading.loadingCompletedMessage = localize('epComapre.LoadingPlanCompleteMessage', "Execution plan successfully loaded");
|
||||
this._placeholderInfobox = this._instantiationService.createInstance(InfoBox, this._placeholderInfoboxContainer, {
|
||||
style: 'information',
|
||||
text: ''
|
||||
});
|
||||
this._placeholderInfobox.text = localize('epComapre.placeholderInfoboxText', "Add execution plans to compare");
|
||||
|
||||
this._topPlanContainer = DOM.$('.plan-container');
|
||||
this.planSplitViewContainer.appendChild(this._topPlanContainer);
|
||||
this._topPlanDropdownContainer = DOM.$('.dropdown-container');
|
||||
this._topPlanDropdown = new SelectBox(['option 1', 'option2'], 'option1', this.contextViewService, this._topPlanDropdownContainer);
|
||||
this._topPlanDropdown.render(this._topPlanDropdownContainer);
|
||||
this._topPlanDropdown.onDidSelect(async (e) => {
|
||||
this._activeBottomPlanDiagram.clearSubtreePolygon();
|
||||
this._activeTopPlanDiagram.clearSubtreePolygon();
|
||||
this._topPlanDiagramContainers.forEach(c => {
|
||||
c.style.display = 'none';
|
||||
});
|
||||
this._topPlanDiagramContainers[e.index].style.display = '';
|
||||
this.topPlanDiagrams[e.index].selectElement(undefined);
|
||||
this._propertiesView.setTopElement(this._topPlanDiagramModels[e.index].root);
|
||||
this._topPlanRecommendations.recommendations = this._topPlanDiagramModels[e.index].recommendations;
|
||||
this._activeTopPlanIndex = e.index;
|
||||
await this.getSkeletonNodes();
|
||||
});
|
||||
attachSelectBoxStyler(this._topPlanDropdown, this.themeService);
|
||||
this._topPlanContainer.appendChild(this._topPlanDropdownContainer);
|
||||
this._topPlanRecommendations = this._instantiationService.createInstance(ExecutionPlanViewHeader, this._topPlanContainer, undefined);
|
||||
|
||||
this.initializeSash();
|
||||
|
||||
this._bottomPlanContainer = DOM.$('.plan-container');
|
||||
this.planSplitViewContainer.appendChild(this._bottomPlanContainer);
|
||||
this._bottomPlanDropdownContainer = DOM.$('.dropdown-container');
|
||||
this._bottomPlanDropdown = new SelectBox(['option 1', 'option2'], 'option1', this.contextViewService, this._bottomPlanDropdownContainer);
|
||||
this._bottomPlanDropdown.render(this._bottomPlanDropdownContainer);
|
||||
this._bottomPlanDropdown.onDidSelect(async (e) => {
|
||||
this._activeBottomPlanDiagram.clearSubtreePolygon();
|
||||
this._activeTopPlanDiagram.clearSubtreePolygon();
|
||||
this._bottomPlanDiagramContainers.forEach(c => {
|
||||
c.style.display = 'none';
|
||||
});
|
||||
this._bottomPlanDiagramContainers[e.index].style.display = '';
|
||||
this.bottomPlanDiagrams[e.index].selectElement(undefined);
|
||||
this._propertiesView.setTopElement(this._bottomPlanDiagramModels[e.index].root);
|
||||
this._bottomPlanRecommendations.recommendations = this._bottomPlanDiagramModels[e.index].recommendations;
|
||||
this._activeBottomPlanIndex = e.index;
|
||||
await this.getSkeletonNodes();
|
||||
});
|
||||
attachSelectBoxStyler(this._bottomPlanDropdown, this.themeService);
|
||||
|
||||
this._bottomPlanContainer.appendChild(this._bottomPlanDropdownContainer);
|
||||
this._bottomPlanRecommendations = this._instantiationService.createInstance(ExecutionPlanViewHeader, this._bottomPlanContainer, undefined);
|
||||
}
|
||||
|
||||
private initializeSash(): void {
|
||||
this._sashContainer = DOM.$('.sash-container');
|
||||
this.planSplitViewContainer.appendChild(this._sashContainer);
|
||||
this._verticalSash = new Sash(this._sashContainer, new VerticalSash(this), { orientation: Orientation.VERTICAL, size: 3 });
|
||||
|
||||
let originalWidth;
|
||||
let change = 0;
|
||||
this._verticalSash.onDidStart((e: ISashEvent) => {
|
||||
originalWidth = this._topPlanContainer.offsetWidth;
|
||||
});
|
||||
this._verticalSash.onDidChange((evt: ISashEvent) => {
|
||||
change = evt.startX - evt.currentX;
|
||||
const newWidth = originalWidth - change;
|
||||
if (newWidth < 200) {
|
||||
return;
|
||||
}
|
||||
this._topPlanContainer.style.minWidth = '200px';
|
||||
this._topPlanContainer.style.flex = `0 0 ${newWidth}px`;
|
||||
});
|
||||
|
||||
this._horizontalSash = new Sash(this._sashContainer, new HorizontalSash(this), { orientation: Orientation.HORIZONTAL, size: 3 });
|
||||
let startHeight;
|
||||
this._horizontalSash.onDidStart((e: ISashEvent) => {
|
||||
startHeight = this._topPlanContainer.offsetHeight;
|
||||
});
|
||||
this._horizontalSash.onDidChange((evt: ISashEvent) => {
|
||||
change = evt.startY - evt.currentY;
|
||||
const newHeight = startHeight - change;
|
||||
if (newHeight < 200) {
|
||||
return;
|
||||
}
|
||||
this._topPlanContainer.style.minHeight = '200px';
|
||||
this._topPlanContainer.style.flex = `0 0 ${newHeight}px`;
|
||||
});
|
||||
}
|
||||
|
||||
private initializeProperties(): void {
|
||||
this._propertiesContainer = DOM.$('.properties');
|
||||
this._propertiesView = this._instantiationService.createInstance(ExecutionPlanComparisonPropertiesView, this._propertiesContainer);
|
||||
this._planComparisonContainer.appendChild(this._propertiesContainer);
|
||||
}
|
||||
|
||||
public async openAndAddExecutionPlanFile(): Promise<void> {
|
||||
try {
|
||||
const openedFileUris = await this._fileDialogService.showOpenDialog({
|
||||
filters: [
|
||||
{
|
||||
extensions: await this._executionPlanService.getSupportedExecutionPlanExtensions(),
|
||||
name: localize('epCompare.FileFilterDescription', "Execution Plan Files")
|
||||
}
|
||||
],
|
||||
canSelectMany: false,
|
||||
canSelectFiles: true
|
||||
});
|
||||
if (openedFileUris?.length === 1) {
|
||||
this._placeholderInfoboxContainer.style.display = 'none';
|
||||
this._placeholderLoading.loading = true;
|
||||
const fileURI = openedFileUris[0];
|
||||
const fileContent = (await this._textFileService.read(fileURI, { acceptTextOnly: true })).value;
|
||||
let executionPlanGraphs = await this._executionPlanService.getExecutionPlan({
|
||||
graphFileContent: fileContent,
|
||||
graphFileType: extname(fileURI.fsPath).replace('.', '')
|
||||
});
|
||||
await this.addExecutionPlanGraph(executionPlanGraphs.graphs);
|
||||
}
|
||||
this._placeholderInfoboxContainer.style.display = '';
|
||||
this._placeholderLoading.loading = false;
|
||||
this._placeholderInfoboxContainer.style.display = '';
|
||||
} catch (e) {
|
||||
this._placeholderLoading.loading = false;
|
||||
this._notificationService.error(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async addExecutionPlanGraph(executionPlanGraphs: azdata.executionPlan.ExecutionPlanGraph[]): Promise<void> {
|
||||
if (!this._topPlanDiagramModels) {
|
||||
this._topPlanDiagramModels = executionPlanGraphs;
|
||||
this._topPlanDropdown.setOptions(executionPlanGraphs.map(e => {
|
||||
return {
|
||||
text: e.query
|
||||
};
|
||||
}), 0);
|
||||
|
||||
executionPlanGraphs.forEach((e, i) => {
|
||||
const graphContainer = DOM.$('.plan-diagram');
|
||||
this._topPlanDiagramContainers.push(graphContainer);
|
||||
this._topPlanContainer.appendChild(graphContainer);
|
||||
const diagram = this._instantiationService.createInstance(AzdataGraphView, graphContainer, e);
|
||||
diagram.onElementSelected(e => {
|
||||
this._propertiesView.setTopElement(e);
|
||||
const id = e.id.replace(`element-`, '');
|
||||
if (this._topSimilarNode.has(id)) {
|
||||
const similarNode = this._topSimilarNode.get(id);
|
||||
const element = this._activeBottomPlanDiagram.getElementById(`element-` + similarNode.matchingNodesId[0]);
|
||||
if (similarNode.matchingNodesId.find(m => this._activeBottomPlanDiagram.getSelectedElement().id === `element-` + m) !== undefined) {
|
||||
return;
|
||||
}
|
||||
this._activeBottomPlanDiagram.selectElement(element);
|
||||
}
|
||||
});
|
||||
this.topPlanDiagrams.push(diagram);
|
||||
graphContainer.style.display = 'none';
|
||||
});
|
||||
|
||||
this._topPlanDiagramContainers[0].style.display = '';
|
||||
this._topPlanRecommendations.recommendations = executionPlanGraphs[0].recommendations;
|
||||
this.topPlanDiagrams[0].selectElement(undefined);
|
||||
this._propertiesView.setTopElement(executionPlanGraphs[0].root);
|
||||
this._propertiesAction.enabled = true;
|
||||
this._zoomInAction.enabled = true;
|
||||
this._zoomOutAction.enabled = true;
|
||||
this._resetZoomAction.enabled = true;
|
||||
this._zoomToFitAction.enabled = true;
|
||||
this._toggleOrientationAction.enabled = true;
|
||||
} else {
|
||||
this._bottomPlanDiagramModels = executionPlanGraphs;
|
||||
this._bottomPlanDropdown.setOptions(executionPlanGraphs.map(e => {
|
||||
return {
|
||||
text: e.query
|
||||
};
|
||||
}), 0);
|
||||
executionPlanGraphs.forEach((e, i) => {
|
||||
const graphContainer = DOM.$('.plan-diagram');
|
||||
this._bottomPlanDiagramContainers.push(graphContainer);
|
||||
this._bottomPlanContainer.appendChild(graphContainer);
|
||||
const diagram = this._instantiationService.createInstance(AzdataGraphView, graphContainer, e);
|
||||
diagram.onElementSelected(e => {
|
||||
this._propertiesView.setBottomElement(e);
|
||||
const id = e.id.replace(`element-`, '');
|
||||
if (this._bottomSimilarNode.has(id)) {
|
||||
const similarNode = this._bottomSimilarNode.get(id);
|
||||
const element = this._activeTopPlanDiagram.getElementById(`element-` + similarNode.matchingNodesId[0]);
|
||||
if (similarNode.matchingNodesId.find(m => this._activeTopPlanDiagram.getSelectedElement().id === `element-` + m) !== undefined) {
|
||||
return;
|
||||
}
|
||||
this._activeTopPlanDiagram.selectElement(element);
|
||||
}
|
||||
});
|
||||
this.bottomPlanDiagrams.push(diagram);
|
||||
graphContainer.style.display = 'none';
|
||||
});
|
||||
|
||||
this._bottomPlanDiagramContainers[0].style.display = '';
|
||||
this._bottomPlanRecommendations.recommendations = executionPlanGraphs[0].recommendations;
|
||||
this.bottomPlanDiagrams[0].selectElement(undefined);
|
||||
this._propertiesView.setBottomElement(executionPlanGraphs[0].root);
|
||||
this._addExecutionPlanAction.enabled = false;
|
||||
await this.getSkeletonNodes();
|
||||
}
|
||||
this.refreshSplitView();
|
||||
}
|
||||
|
||||
private async getSkeletonNodes(): Promise<void> {
|
||||
this._progressService.withProgress(
|
||||
{
|
||||
location: ProgressLocation.Notification,
|
||||
title: localize('epCompare.comparisonProgess', "Loading similar areas in compared plans"),
|
||||
cancellable: false
|
||||
},
|
||||
async (progress) => {
|
||||
this._polygonRootsMap = new Map();
|
||||
this._topSimilarNode = new Map();
|
||||
this._bottomSimilarNode = new Map();
|
||||
if (this._topPlanDiagramModels && this._bottomPlanDiagramModels) {
|
||||
this._topPlanDiagramModels[this._activeTopPlanIndex].graphFile.graphFileType = 'sqlplan';
|
||||
this._bottomPlanDiagramModels[this._activeBottomPlanIndex].graphFile.graphFileType = 'sqlplan';
|
||||
const result = await this._executionPlanService.compareExecutionPlanGraph(this._topPlanDiagramModels[this._activeTopPlanIndex].graphFile,
|
||||
this._bottomPlanDiagramModels[this._activeBottomPlanIndex].graphFile);
|
||||
this.getSimilarSubtrees(result.firstComparisonResult);
|
||||
this.getSimilarSubtrees(result.secondComparisonResult, true);
|
||||
let colorIndex = 0;
|
||||
this._polygonRootsMap.forEach((v, k) => {
|
||||
this._activeTopPlanDiagram.drawSubtreePolygon(v.topPolygon.baseNode.id, polygonFillColor[colorIndex], polygonBorderColor[colorIndex]);
|
||||
this._activeBottomPlanDiagram.drawSubtreePolygon(v.bottomPolygon.baseNode.id, polygonFillColor[colorIndex], polygonBorderColor[colorIndex]);
|
||||
colorIndex += 1;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private getSimilarSubtrees(comparedNode: azdata.executionPlan.ExecutionGraphComparisonResult, isBottomPlan: boolean = false): void {
|
||||
if (comparedNode.hasMatch) {
|
||||
if (!isBottomPlan) {
|
||||
this._topSimilarNode.set(`${comparedNode.baseNode.id}`, comparedNode);
|
||||
if (!this._polygonRootsMap.has(comparedNode.groupIndex)) {
|
||||
this._polygonRootsMap.set(comparedNode.groupIndex, {
|
||||
topPolygon: comparedNode,
|
||||
bottomPolygon: undefined
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this._bottomSimilarNode.set(`${comparedNode.baseNode.id}`, comparedNode);
|
||||
if (this._polygonRootsMap.get(comparedNode.groupIndex).bottomPolygon === undefined) {
|
||||
const polygonMapEntry = this._polygonRootsMap.get(comparedNode.groupIndex);
|
||||
polygonMapEntry.bottomPolygon = comparedNode;
|
||||
this._polygonRootsMap.set(comparedNode.groupIndex, polygonMapEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
comparedNode.children.forEach(c => {
|
||||
this.getSimilarSubtrees(c, isBottomPlan);
|
||||
});
|
||||
}
|
||||
|
||||
public togglePropertiesView(): void {
|
||||
this._propertiesContainer.style.display = this._propertiesContainer.style.display === 'none' ? '' : 'none';
|
||||
}
|
||||
|
||||
public toggleOrientation(): void {
|
||||
if (this._orientation === 'vertical') {
|
||||
this._sashContainer.style.width = '100%';
|
||||
this._sashContainer.style.height = '3px';
|
||||
this.planSplitViewContainer.style.flexDirection = 'column';
|
||||
this._topPlanContainer.style.minHeight = '200px';
|
||||
this._topPlanContainer.style.minWidth = '';
|
||||
this._topPlanContainer.style.flex = '1';
|
||||
this._orientation = 'horizontal';
|
||||
this._toggleOrientationAction.class = splitScreenHorizontallyIconClassName;
|
||||
} else {
|
||||
this._sashContainer.style.width = '3px';
|
||||
this._sashContainer.style.height = '100%';
|
||||
this.planSplitViewContainer.style.flexDirection = 'row';
|
||||
this._topPlanContainer.style.minHeight = '';
|
||||
this._topPlanContainer.style.minWidth = '200px';
|
||||
this._orientation = 'vertical';
|
||||
this._toggleOrientationAction.class = splitScreenVerticallyIconClassName;
|
||||
}
|
||||
this._topPlanContainer.style.flex = '1';
|
||||
this._bottomPlanContainer.style.flex = '1';
|
||||
}
|
||||
|
||||
public refreshSplitView(): void {
|
||||
if (this.planSplitViewContainer.contains(this._topPlanContainer)) {
|
||||
this.planSplitViewContainer.removeChild(this._topPlanContainer);
|
||||
}
|
||||
|
||||
if (this.planSplitViewContainer.contains(this._bottomPlanContainer)) {
|
||||
this.planSplitViewContainer.removeChild(this._bottomPlanContainer);
|
||||
}
|
||||
|
||||
if (this.planSplitViewContainer.contains(this._sashContainer)) {
|
||||
this.planSplitViewContainer.removeChild(this._sashContainer);
|
||||
}
|
||||
|
||||
if (this.planSplitViewContainer.contains(this._placeholderContainer)) {
|
||||
this.planSplitViewContainer.removeChild(this._placeholderContainer);
|
||||
}
|
||||
|
||||
if (!this._topPlanDiagramModels && !this._bottomPlanDiagramModels) {
|
||||
this.planSplitViewContainer.appendChild(this._placeholderContainer);
|
||||
} else if (this._topPlanDiagramModels && !this._bottomPlanDiagramModels) {
|
||||
this.planSplitViewContainer.appendChild(this._topPlanContainer);
|
||||
this.planSplitViewContainer.appendChild(this._sashContainer);
|
||||
this.planSplitViewContainer.appendChild(this._placeholderContainer);
|
||||
} else {
|
||||
this.planSplitViewContainer.appendChild(this._topPlanContainer);
|
||||
this.planSplitViewContainer.appendChild(this._sashContainer);
|
||||
this.planSplitViewContainer.appendChild(this._bottomPlanContainer);
|
||||
}
|
||||
}
|
||||
|
||||
public zoomIn(): void {
|
||||
this._activeTopPlanDiagram.zoomIn();
|
||||
this._activeBottomPlanDiagram.zoomIn();
|
||||
this.syncZoom();
|
||||
}
|
||||
|
||||
public zoomOut(): void {
|
||||
this._activeTopPlanDiagram.zoomOut();
|
||||
this._activeBottomPlanDiagram.zoomOut();
|
||||
this.syncZoom();
|
||||
}
|
||||
|
||||
public zoomToFit(): void {
|
||||
this._activeTopPlanDiagram.zoomToFit();
|
||||
this._activeBottomPlanDiagram.zoomToFit();
|
||||
this.syncZoom();
|
||||
}
|
||||
|
||||
public resetZoom(): void {
|
||||
if (this._activeTopPlanDiagram) {
|
||||
this._activeTopPlanDiagram.setZoomLevel(100);
|
||||
}
|
||||
if (this._activeBottomPlanDiagram) {
|
||||
this._activeBottomPlanDiagram.setZoomLevel(100);
|
||||
}
|
||||
}
|
||||
|
||||
private syncZoom(): void {
|
||||
if (this._activeTopPlanDiagram.getZoomLevel() < this._activeBottomPlanDiagram.getZoomLevel()) {
|
||||
this._activeBottomPlanDiagram.setZoomLevel(this._activeTopPlanDiagram.getZoomLevel());
|
||||
} else {
|
||||
this._activeTopPlanDiagram.setZoomLevel(this._activeBottomPlanDiagram.getZoomLevel());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AddExecutionPlanAction extends Action {
|
||||
public static ID = 'ep.AddExecutionPlan';
|
||||
public static LABEL = localize('addExecutionPlanLabel', "Add execution plan");
|
||||
constructor() {
|
||||
super(AddExecutionPlanAction.ID, AddExecutionPlanAction.LABEL, addIconClassName);
|
||||
}
|
||||
|
||||
public override async run(context: ExecutionPlanComparisonEditorView): Promise<void> {
|
||||
await context.openAndAddExecutionPlanFile();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ZoomInAction extends Action {
|
||||
public static ID = 'ep.zoomIn';
|
||||
public static LABEL = localize('epCompare.zoomInAction', "Zoom In");
|
||||
constructor() {
|
||||
super(ZoomInAction.ID, ZoomInAction.LABEL, zoomInIconClassNames);
|
||||
this.enabled = false;
|
||||
}
|
||||
public override async run(context: ExecutionPlanComparisonEditorView): Promise<void> {
|
||||
context.zoomIn();
|
||||
}
|
||||
}
|
||||
|
||||
class ZoomOutAction extends Action {
|
||||
public static ID = 'ep.zoomOut';
|
||||
public static LABEL = localize('epCompare.zoomOutAction', "Zoom Out");
|
||||
constructor() {
|
||||
super(ZoomOutAction.ID, ZoomOutAction.LABEL, zoomOutIconClassNames);
|
||||
this.enabled = false;
|
||||
}
|
||||
public override async run(context: ExecutionPlanComparisonEditorView): Promise<void> {
|
||||
context.zoomOut();
|
||||
}
|
||||
}
|
||||
|
||||
class ZoomToFitAction extends Action {
|
||||
public static ID = 'ep.zoomToFit';
|
||||
public static LABEL = localize('epCompare.zoomToFit', "Zoom to fit");
|
||||
|
||||
constructor() {
|
||||
super(ZoomToFitAction.ID, ZoomToFitAction.LABEL, zoomToFitIconClassNames);
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
public override async run(context: ExecutionPlanComparisonEditorView): Promise<void> {
|
||||
context.zoomToFit();
|
||||
}
|
||||
}
|
||||
|
||||
class ZoomReset extends Action {
|
||||
public static ID = 'ep.resetZoom';
|
||||
public static LABEL = localize('epCompare.zoomReset', "Reset Zoom");
|
||||
|
||||
constructor() {
|
||||
super(ZoomReset.ID, ZoomReset.LABEL, resetZoomIconClassName);
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
public override async run(context: ExecutionPlanComparisonEditorView): Promise<void> {
|
||||
context.resetZoom();
|
||||
}
|
||||
}
|
||||
|
||||
class ToggleOrientation extends Action {
|
||||
public static ID = 'ep.toggleOrientation';
|
||||
public static LABEL = localize('epCompare.toggleOrientation', "Toggle Orientation");
|
||||
|
||||
constructor() {
|
||||
super(ToggleOrientation.ID, ToggleOrientation.LABEL, splitScreenHorizontallyIconClassName);
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
public override async run(context: ExecutionPlanComparisonEditorView): Promise<void> {
|
||||
context.toggleOrientation();
|
||||
}
|
||||
}
|
||||
|
||||
class PropertiesAction extends Action {
|
||||
public static ID = 'epCompare.comparePropertiesAction';
|
||||
public static LABEL = localize('epCompare.comparePropertiesAction', "Properties");
|
||||
constructor() {
|
||||
super(PropertiesAction.ID, PropertiesAction.LABEL, openPropertiesIconClassNames);
|
||||
this.enabled = false;
|
||||
}
|
||||
public override async run(context: ExecutionPlanComparisonEditorView): Promise<void> {
|
||||
context.togglePropertiesView();
|
||||
}
|
||||
}
|
||||
|
||||
class HorizontalSash implements IHorizontalSashLayoutProvider {
|
||||
constructor(private _context: ExecutionPlanComparisonEditorView) {
|
||||
}
|
||||
getHorizontalSashTop(sash: Sash): number {
|
||||
return 0;
|
||||
}
|
||||
getHorizontalSashLeft?(sash: Sash): number {
|
||||
return 0;
|
||||
}
|
||||
getHorizontalSashWidth?(sash: Sash): number {
|
||||
return this._context.planSplitViewContainer.clientWidth;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class VerticalSash implements IVerticalSashLayoutProvider {
|
||||
constructor(private _context: ExecutionPlanComparisonEditorView) {
|
||||
|
||||
}
|
||||
getVerticalSashLeft(sash: Sash): number {
|
||||
return 0;
|
||||
}
|
||||
getVerticalSashTop?(sash: Sash): number {
|
||||
return 0;
|
||||
}
|
||||
getVerticalSashHeight?(sash: Sash): number {
|
||||
return this._context.planSplitViewContainer.clientHeight;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
const separatorColor = theme.getColor(errorForeground);
|
||||
if (separatorColor) {
|
||||
collector.addRule(`
|
||||
.designer-component .issues-container .issue-item .issue-icon.codicon-error {
|
||||
color: ${separatorColor};
|
||||
}`);
|
||||
}
|
||||
const recommendationsColor = theme.getColor(textLinkForeground);
|
||||
if (recommendationsColor) {
|
||||
collector.addRule(`
|
||||
.eps-container .comparison-editor .plan-comparison-container .split-view-container .plan-container .recommendations {
|
||||
color: ${recommendationsColor};
|
||||
}
|
||||
`);
|
||||
}
|
||||
const menuBackgroundColor = theme.getColor(listHoverBackground);
|
||||
if (menuBackgroundColor) {
|
||||
collector.addRule(`
|
||||
.eps-container .comparison-editor .plan-comparison-container .split-view-container .plan-container .recommendations {
|
||||
background-color: ${menuBackgroundColor};
|
||||
}
|
||||
`);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,268 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ExecutionPlanPropertiesViewBase, PropertiesSortType } from 'sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesViewBase';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import type * as azdata from 'azdata';
|
||||
import { localize } from 'vs/nls';
|
||||
import { textFormatter } from 'sql/base/browser/ui/table/formatters';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
import { removeLineBreaks } from 'sql/base/common/strings';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { InternalExecutionPlanElement } from 'sql/workbench/contrib/executionPlan/browser/azdataGraphView';
|
||||
|
||||
export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanPropertiesViewBase {
|
||||
private _model: ExecutionPlanComparisonPropertiesViewModel;
|
||||
private _topOperationNameContainer: HTMLElement;
|
||||
private _bottomOperationNameContainer: HTMLElement;
|
||||
|
||||
public constructor(
|
||||
parentContainer: HTMLElement,
|
||||
@IThemeService themeService: IThemeService,
|
||||
) {
|
||||
super(parentContainer, themeService);
|
||||
this._model = <ExecutionPlanComparisonPropertiesViewModel>{};
|
||||
this._parentContainer.style.display = 'none';
|
||||
const header = DOM.$('.compare-operation-name');
|
||||
this._topOperationNameContainer = DOM.$('.compare-operation-name-text');
|
||||
header.appendChild(this._topOperationNameContainer);
|
||||
this._bottomOperationNameContainer = DOM.$('.compare-operation-name-text');
|
||||
header.appendChild(this._bottomOperationNameContainer);
|
||||
this.setHeader(header);
|
||||
}
|
||||
|
||||
|
||||
public setTopElement(e: InternalExecutionPlanElement): void {
|
||||
this._model.topElement = e;
|
||||
let target;
|
||||
if ((<azdata.executionPlan.ExecutionPlanNode>e).name) {
|
||||
target = removeLineBreaks((<azdata.executionPlan.ExecutionPlanNode>e).name);
|
||||
} else {
|
||||
target = localize('executionPlanPropertiesEdgeOperationName', "Edge");
|
||||
}
|
||||
const titleText = localize('executionPlanComparisonPropertiesTopOperation', "Top operation: {0}", target);
|
||||
this._topOperationNameContainer.innerText = titleText;
|
||||
this._bottomOperationNameContainer.title = titleText;
|
||||
this.addDataToTable();
|
||||
}
|
||||
|
||||
public setBottomElement(e: InternalExecutionPlanElement): void {
|
||||
this._model.bottomElement = e;
|
||||
let target;
|
||||
if ((<azdata.executionPlan.ExecutionPlanNode>e)?.name) {
|
||||
target = removeLineBreaks((<azdata.executionPlan.ExecutionPlanNode>e).name);
|
||||
} else {
|
||||
target = localize('executionPlanPropertiesEdgeOperationName', "Edge");
|
||||
}
|
||||
|
||||
const titleText = localize('executionPlanComparisonPropertiesBottomOperation', "Bottom operation: {0}", target);
|
||||
this._bottomOperationNameContainer.innerText = titleText;
|
||||
this._bottomOperationNameContainer.title = titleText;
|
||||
this.addDataToTable();
|
||||
}
|
||||
|
||||
|
||||
public addDataToTable() {
|
||||
const columns: Slick.Column<Slick.SlickData>[] = [
|
||||
];
|
||||
if (this._model.topElement) {
|
||||
columns.push({
|
||||
id: 'name',
|
||||
name: localize('nodePropertyViewNameNameColumnHeader', "Name"),
|
||||
field: 'name',
|
||||
width: 200,
|
||||
editor: Slick.Editors.Text,
|
||||
headerCssClass: 'prop-table-header',
|
||||
formatter: textFormatter
|
||||
});
|
||||
columns.push({
|
||||
id: 'value',
|
||||
name: localize('nodePropertyViewNameValueColumnTopHeader', "Value (Top Plan)"),
|
||||
field: 'value1',
|
||||
width: 150,
|
||||
editor: Slick.Editors.Text,
|
||||
headerCssClass: 'prop-table-header',
|
||||
formatter: textFormatter
|
||||
});
|
||||
}
|
||||
if (this._model.bottomElement) {
|
||||
columns.push({
|
||||
id: 'value',
|
||||
name: localize('nodePropertyViewNameValueColumnBottomHeader', "Value (Bottom Plan)"),
|
||||
field: 'value2',
|
||||
width: 150,
|
||||
editor: Slick.Editors.Text,
|
||||
headerCssClass: 'prop-table-header',
|
||||
formatter: textFormatter
|
||||
});
|
||||
}
|
||||
|
||||
let topProps = [];
|
||||
let bottomProps = [];
|
||||
if (this._model.topElement?.properties) {
|
||||
topProps = this._model.topElement.properties;
|
||||
}
|
||||
if (this._model.bottomElement?.properties) {
|
||||
bottomProps = this._model.bottomElement.properties;
|
||||
}
|
||||
|
||||
this.populateTable(columns, this.convertPropertiesToTableRows(topProps, bottomProps, -1, 0));
|
||||
}
|
||||
|
||||
public sortPropertiesAlphabetically(props: Map<string, TablePropertiesMapEntry>): Map<string, TablePropertiesMapEntry> {
|
||||
return new Map([...props.entries()].sort((a, b) => {
|
||||
if (!a[1]?.name && !b[1]?.name) {
|
||||
return 0;
|
||||
} else if (!a[1]?.name) {
|
||||
return -1;
|
||||
} else if (!b[1]?.name) {
|
||||
return 1;
|
||||
} else {
|
||||
return a[1].name.localeCompare(b[1].name);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public sortPropertiesByImportance(props: Map<string, TablePropertiesMapEntry>): Map<string, TablePropertiesMapEntry> {
|
||||
return new Map([...props.entries()].sort((a, b) => {
|
||||
if (!a[1]?.displayOrder && !b[1]?.displayOrder) {
|
||||
return 0;
|
||||
} else if (!a[1]?.displayOrder) {
|
||||
return -1;
|
||||
} else if (!b[1]?.displayOrder) {
|
||||
return 1;
|
||||
} else {
|
||||
return a[1].displayOrder - b[1].displayOrder;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public sortPropertiesReverseAlphabetically(props: Map<string, TablePropertiesMapEntry>): Map<string, TablePropertiesMapEntry> {
|
||||
return new Map([...props.entries()].sort((a, b) => {
|
||||
if (!a[1]?.displayOrder && !b[1]?.displayOrder) {
|
||||
return 0;
|
||||
} else if (!a[1]?.displayOrder) {
|
||||
return -1;
|
||||
} else if (!b[1]?.displayOrder) {
|
||||
return 1;
|
||||
} else {
|
||||
return b[1].displayOrder - a[1].displayOrder;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private convertPropertiesToTableRows(topNode: azdata.executionPlan.ExecutionPlanGraphElementProperty[], bottomNode: azdata.executionPlan.ExecutionPlanGraphElementProperty[], parentIndex: number, indent: number, rows: { [key: string]: string }[] = []): { [key: string]: string }[] {
|
||||
let propertiesMap: Map<string, TablePropertiesMapEntry> = new Map();
|
||||
|
||||
if (topNode) {
|
||||
topNode.forEach(p => {
|
||||
propertiesMap.set(p.name, {
|
||||
topProp: p,
|
||||
bottomProp: undefined,
|
||||
displayOrder: p.displayOrder,
|
||||
name: p.name
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (bottomNode) {
|
||||
bottomNode.forEach(p => {
|
||||
if (propertiesMap.has(p.name)) {
|
||||
propertiesMap.get(p.name).bottomProp = p;
|
||||
} else {
|
||||
propertiesMap.set(p.name, {
|
||||
topProp: undefined,
|
||||
bottomProp: p,
|
||||
displayOrder: p.displayOrder,
|
||||
name: p.name
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
switch (this.sortType) {
|
||||
case PropertiesSortType.DisplayOrder:
|
||||
propertiesMap = this.sortPropertiesByImportance(propertiesMap);
|
||||
break;
|
||||
case PropertiesSortType.Alphabetical:
|
||||
propertiesMap = this.sortPropertiesAlphabetically(propertiesMap);
|
||||
break;
|
||||
case PropertiesSortType.ReverseAlphabetical:
|
||||
propertiesMap = this.sortPropertiesReverseAlphabetically(propertiesMap);
|
||||
break;
|
||||
}
|
||||
|
||||
propertiesMap.forEach((v, k) => {
|
||||
let row = {};
|
||||
row['name'] = {
|
||||
text: k
|
||||
};
|
||||
row['parent'] = parentIndex;
|
||||
|
||||
const topProp = v.topProp;
|
||||
const bottomProp = v.bottomProp;
|
||||
const parentRowCellStyling = 'font-weight: bold';
|
||||
|
||||
if (topProp && bottomProp) {
|
||||
row['displayOrder'] = v.topProp.displayOrder;
|
||||
row['value1'] = {
|
||||
text: removeLineBreaks(v.topProp.displayValue, ' ')
|
||||
};
|
||||
row['value2'] = {
|
||||
text: removeLineBreaks(v.bottomProp.displayValue, ' ')
|
||||
};
|
||||
if ((topProp && !isString(topProp.value)) || (bottomProp && !isString(bottomProp.value))) {
|
||||
row['name'].style = parentRowCellStyling;
|
||||
row['value1'].style = parentRowCellStyling;
|
||||
row['value2'].style = parentRowCellStyling;
|
||||
}
|
||||
rows.push(row);
|
||||
if (!isString(topProp.value) && !isString(bottomProp.value)) {
|
||||
this.convertPropertiesToTableRows(topProp.value, bottomProp.value, rows.length - 1, indent + 2, rows);
|
||||
} else if (isString(topProp?.value) && !isString(bottomProp.value)) {
|
||||
this.convertPropertiesToTableRows(undefined, bottomProp.value, rows.length - 1, indent + 2, rows);
|
||||
} else if (!isString(topProp.value) && !isString(bottomProp.value)) {
|
||||
this.convertPropertiesToTableRows(topProp.value, undefined, rows.length - 1, indent + 2, rows);
|
||||
}
|
||||
} else if (topProp && !bottomProp) {
|
||||
row['displayOrder'] = v.topProp.displayOrder;
|
||||
row['value1'] = {
|
||||
text: v.topProp.displayValue
|
||||
};
|
||||
rows.push(row);
|
||||
if (!isString(topProp.value)) {
|
||||
row['name'].style = parentRowCellStyling;
|
||||
row['value1'].style = parentRowCellStyling;
|
||||
this.convertPropertiesToTableRows(topProp.value, undefined, rows.length - 1, indent + 2, rows);
|
||||
}
|
||||
} else if (!topProp && bottomProp) {
|
||||
row['displayOrder'] = v.bottomProp.displayOrder;
|
||||
row['value2'] = {
|
||||
text: v.bottomProp.displayValue
|
||||
};
|
||||
rows.push(row);
|
||||
if (!isString(bottomProp.value)) {
|
||||
row['name'].style = parentRowCellStyling;
|
||||
row['value2'].style = parentRowCellStyling;
|
||||
this.convertPropertiesToTableRows(undefined, bottomProp.value, rows.length - 1, indent + 2, rows);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ExecutionPlanComparisonPropertiesViewModel {
|
||||
topElement: InternalExecutionPlanElement,
|
||||
bottomElement: InternalExecutionPlanElement
|
||||
}
|
||||
|
||||
interface TablePropertiesMapEntry {
|
||||
topProp: azdata.executionPlan.ExecutionPlanGraphElementProperty,
|
||||
bottomProp: azdata.executionPlan.ExecutionPlanGraphElementProperty,
|
||||
displayOrder: number,
|
||||
name: string
|
||||
}
|
||||
@@ -11,11 +11,17 @@ 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 { IInstantiationService, ServicesAccessor } 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';
|
||||
import { ExecutionPlanComparisonEditor } from 'sql/workbench/contrib/executionPlan/browser/executionPlanComparisonEditor';
|
||||
import { ExecutionPlanComparisonInput } from 'sql/workbench/contrib/executionPlan/browser/compareExecutionPlanInput';
|
||||
import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
import { localize } from 'vs/nls';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
// Execution Plan editor registration
|
||||
|
||||
@@ -39,7 +45,7 @@ export class ExecutionPlanEditorOverrideContribution extends Disposable implemen
|
||||
this.registerEditorOverride();
|
||||
|
||||
this._capabilitiesService.onCapabilitiesRegistered(e => {
|
||||
const newFileFormats = this._executionPlanService.getSupportedExecutionPlanExtensionsForProvider(e.id);
|
||||
const newFileFormats = this._executionPlanService.getSupportedExecutionPlanExtensions(e.id);
|
||||
if (newFileFormats?.length > 0) {
|
||||
this._editorResolverService.updateUserAssociations(this.getGlobForFileExtensions(newFileFormats), ExecutionPlanEditor.ID); // Registering new file formats when new providers are registered.
|
||||
}
|
||||
@@ -76,3 +82,33 @@ export class ExecutionPlanEditorOverrideContribution extends Disposable implemen
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
|
||||
.registerWorkbenchContribution(ExecutionPlanEditorOverrideContribution, LifecyclePhase.Restored);
|
||||
|
||||
const comparisonExecutionPlanEditor = EditorPaneDescriptor.create(
|
||||
ExecutionPlanComparisonEditor,
|
||||
ExecutionPlanComparisonEditor.ID,
|
||||
ExecutionPlanComparisonEditor.LABEL
|
||||
);
|
||||
|
||||
const COMPARE_EXECUTION_PLAN_COMMAND_ID = 'compareExecutionPlan';
|
||||
|
||||
Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane)
|
||||
.registerEditorPane(comparisonExecutionPlanEditor, [new SyncDescriptor(ExecutionPlanComparisonInput)]);
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
command: {
|
||||
id: COMPARE_EXECUTION_PLAN_COMMAND_ID,
|
||||
title: {
|
||||
value: localize('executionPlanCompareCommandValue', "Compare execution plans"),
|
||||
original: localize('executionPlanCompareCommandOriginalValue', "Compare execution plans")
|
||||
},
|
||||
category: 'Execution Plan'
|
||||
}
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand(COMPARE_EXECUTION_PLAN_COMMAND_ID, (accessors: ServicesAccessor) => {
|
||||
const editorService = accessors.get(IEditorService);
|
||||
const instantiationService = accessors.get(IInstantiationService);
|
||||
editorService.openEditor(instantiationService.createInstance(ExecutionPlanComparisonInput, undefined), {
|
||||
pinned: true
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,7 +33,7 @@ export class ExecutionPlanPropertiesView extends ExecutionPlanPropertiesViewBase
|
||||
|
||||
public set graphElement(element: azdata.executionPlan.ExecutionPlanNode | azdata.executionPlan.ExecutionPlanEdge) {
|
||||
this._model.graphElement = element;
|
||||
this.renderView();
|
||||
this.addDataToTable();
|
||||
}
|
||||
|
||||
public sortPropertiesAlphabetically(props: azdata.executionPlan.ExecutionPlanGraphElementProperty[]): azdata.executionPlan.ExecutionPlanGraphElementProperty[] {
|
||||
@@ -79,7 +79,7 @@ export class ExecutionPlanPropertiesView extends ExecutionPlanPropertiesViewBase
|
||||
});
|
||||
}
|
||||
|
||||
public renderView(): void {
|
||||
public addDataToTable(): void {
|
||||
if (this._model.graphElement) {
|
||||
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'
|
||||
|
||||
@@ -15,8 +15,9 @@ import { attachTableStyler } from 'sql/platform/theme/common/styler';
|
||||
import { RESULTS_GRID_DEFAULTS } from 'sql/workbench/common/constants';
|
||||
import { contrastBorder, listHoverBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { TreeGrid } from 'sql/base/browser/ui/table/treeGrid';
|
||||
import { ISashEvent, IVerticalSashLayoutProvider, Orientation, Sash } from 'vs/base/browser/ui/sash/sash';
|
||||
|
||||
export abstract class ExecutionPlanPropertiesViewBase {
|
||||
export abstract class ExecutionPlanPropertiesViewBase implements IVerticalSashLayoutProvider {
|
||||
// Title bar with close button action
|
||||
private _titleBarContainer!: HTMLElement;
|
||||
private _titleBarTextContainer!: HTMLElement;
|
||||
@@ -39,6 +40,8 @@ export abstract class ExecutionPlanPropertiesViewBase {
|
||||
|
||||
public sortType: PropertiesSortType = PropertiesSortType.DisplayOrder;
|
||||
|
||||
public resizeSash: Sash;
|
||||
|
||||
constructor(
|
||||
public _parentContainer: HTMLElement,
|
||||
private _themeService: IThemeService
|
||||
@@ -47,8 +50,28 @@ export abstract class ExecutionPlanPropertiesViewBase {
|
||||
const sashContainer = DOM.$('.properties-sash');
|
||||
this._parentContainer.appendChild(sashContainer);
|
||||
|
||||
this.resizeSash = new Sash(sashContainer, this, { orientation: Orientation.VERTICAL, size: 3 });
|
||||
let originalWidth = 0;
|
||||
this.resizeSash.onDidStart((e: ISashEvent) => {
|
||||
originalWidth = this._parentContainer.clientWidth;
|
||||
});
|
||||
this.resizeSash.onDidChange((evt: ISashEvent) => {
|
||||
const change = evt.startX - evt.currentX;
|
||||
const newWidth = originalWidth + change;
|
||||
if (newWidth < 200) {
|
||||
return;
|
||||
}
|
||||
this._parentContainer.style.flex = `0 0 ${newWidth}px`;
|
||||
propertiesContent.style.width = `${newWidth}px`;
|
||||
});
|
||||
this.resizeSash.onDidEnd(() => {
|
||||
});
|
||||
|
||||
const propertiesContent = DOM.$('.properties-content');
|
||||
this._parentContainer.appendChild(propertiesContent);
|
||||
|
||||
this._titleBarContainer = DOM.$('.title');
|
||||
this._parentContainer.appendChild(this._titleBarContainer);
|
||||
propertiesContent.appendChild(this._titleBarContainer);
|
||||
|
||||
this._titleBarTextContainer = DOM.$('h3');
|
||||
this._titleBarTextContainer.classList.add('text');
|
||||
@@ -64,10 +87,10 @@ export abstract class ExecutionPlanPropertiesViewBase {
|
||||
this._titleActions.pushAction([new ClosePropertyViewAction()], { icon: true, label: false });
|
||||
|
||||
this._headerContainer = DOM.$('.header');
|
||||
this._parentContainer.appendChild(this._headerContainer);
|
||||
propertiesContent.appendChild(this._headerContainer);
|
||||
|
||||
this._headerActionsContainer = DOM.$('.table-action-bar');
|
||||
this._parentContainer.appendChild(this._headerActionsContainer);
|
||||
propertiesContent.appendChild(this._headerActionsContainer);
|
||||
this._headerActions = new ActionBar(this._headerActionsContainer, {
|
||||
orientation: ActionsOrientation.HORIZONTAL, context: this
|
||||
});
|
||||
@@ -75,7 +98,7 @@ export abstract class ExecutionPlanPropertiesViewBase {
|
||||
|
||||
|
||||
this._tableContainer = DOM.$('.table-container');
|
||||
this._parentContainer.appendChild(this._tableContainer);
|
||||
propertiesContent.appendChild(this._tableContainer);
|
||||
|
||||
const table = DOM.$('.table');
|
||||
this._tableContainer.appendChild(table);
|
||||
@@ -90,9 +113,19 @@ export abstract class ExecutionPlanPropertiesViewBase {
|
||||
attachTableStyler(this._tableComponent, this._themeService);
|
||||
|
||||
new ResizeObserver((e) => {
|
||||
this.resizeSash.layout();
|
||||
this.resizeTable();
|
||||
}).observe(this._parentContainer);
|
||||
}).observe(_parentContainer);
|
||||
}
|
||||
|
||||
getVerticalSashLeft(sash: Sash): number {
|
||||
return 0;
|
||||
}
|
||||
getVerticalSashTop?(sash: Sash): number {
|
||||
return 0;
|
||||
}
|
||||
getVerticalSashHeight?(sash: Sash): number {
|
||||
return this._parentContainer.clientHeight;
|
||||
}
|
||||
|
||||
public setTitle(v: string): void {
|
||||
@@ -106,14 +139,12 @@ export abstract class ExecutionPlanPropertiesViewBase {
|
||||
public set tableHeight(value: number) {
|
||||
if (this.tableHeight !== value) {
|
||||
this._tableHeight = value;
|
||||
this.renderView();
|
||||
}
|
||||
}
|
||||
|
||||
public set tableWidth(value: number) {
|
||||
if (this._tableWidth !== value) {
|
||||
this._tableWidth = value;
|
||||
this.renderView();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,11 +156,10 @@ export abstract class ExecutionPlanPropertiesViewBase {
|
||||
return this._tableHeight;
|
||||
}
|
||||
|
||||
public abstract renderView();
|
||||
public abstract addDataToTable();
|
||||
|
||||
public toggleVisibility(): void {
|
||||
this._parentContainer.style.display = this._parentContainer.style.display === 'none' ? 'block' : 'none';
|
||||
this.renderView();
|
||||
this._parentContainer.style.display = this._parentContainer.style.display === 'none' ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
public populateTable(columns: Slick.Column<Slick.SlickData>[], data: { [key: string]: string }[]) {
|
||||
@@ -177,7 +207,7 @@ export class SortPropertiesAlphabeticallyAction extends Action {
|
||||
|
||||
public override async run(context: ExecutionPlanPropertiesViewBase): Promise<void> {
|
||||
context.sortType = PropertiesSortType.Alphabetical;
|
||||
context.renderView();
|
||||
context.addDataToTable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +221,7 @@ export class SortPropertiesReverseAlphabeticallyAction extends Action {
|
||||
|
||||
public override async run(context: ExecutionPlanPropertiesViewBase): Promise<void> {
|
||||
context.sortType = PropertiesSortType.ReverseAlphabetical;
|
||||
context.renderView();
|
||||
context.addDataToTable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +235,7 @@ export class SortPropertiesByDisplayOrderAction extends Action {
|
||||
|
||||
public override async run(context: ExecutionPlanPropertiesViewBase): Promise<void> {
|
||||
context.sortType = PropertiesSortType.DisplayOrder;
|
||||
context.renderView();
|
||||
context.addDataToTable();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import { Progress } from 'vs/platform/progress/common/progress';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { localize } from 'vs/nls';
|
||||
import { customZoomIconClassNames, disableTooltipIconClassName, enableTooltipIconClassName, openPlanFileIconClassNames, openPropertiesIconClassNames, openQueryIconClassNames, savePlanIconClassNames, searchIconClassNames, zoomInIconClassNames, zoomOutIconClassNames, zoomToFitIconClassNames } from 'sql/workbench/contrib/executionPlan/browser/constants';
|
||||
import { customZoomIconClassNames, disableTooltipIconClassName, enableTooltipIconClassName, executionPlanCompareIconClassName, openPlanFileIconClassNames, openPropertiesIconClassNames, openQueryIconClassNames, savePlanIconClassNames, searchIconClassNames, zoomInIconClassNames, zoomOutIconClassNames, zoomToFitIconClassNames } from 'sql/workbench/contrib/executionPlan/browser/constants';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { CustomZoomWidget } from 'sql/workbench/contrib/executionPlan/browser/widgets/customZoomWidget';
|
||||
@@ -35,6 +35,7 @@ import { NodeSearchWidget } from 'sql/workbench/contrib/executionPlan/browser/wi
|
||||
import { AzdataGraphView } from 'sql/workbench/contrib/executionPlan/browser/azdataGraphView';
|
||||
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
|
||||
import { ExecutionPlanComparisonInput } from 'sql/workbench/contrib/executionPlan/browser/compareExecutionPlanInput';
|
||||
|
||||
export class ExecutionPlanView implements ISashLayoutProvider {
|
||||
|
||||
@@ -78,6 +79,7 @@ export class ExecutionPlanView implements ISashLayoutProvider {
|
||||
@IFileDialogService public fileDialogService: IFileDialogService,
|
||||
@IFileService public fileService: IFileService,
|
||||
@IWorkspaceContextService public workspaceContextService: IWorkspaceContextService,
|
||||
@IEditorService private _editorService: IEditorService
|
||||
) {
|
||||
// parent container for query plan.
|
||||
this._container = DOM.$('.execution-plan');
|
||||
@@ -162,6 +164,7 @@ export class ExecutionPlanView implements ISashLayoutProvider {
|
||||
this._instantiationService.createInstance(ZoomToFitAction, 'ActionBar'),
|
||||
this._instantiationService.createInstance(CustomZoomAction, 'ActionBar'),
|
||||
this._instantiationService.createInstance(PropertiesAction, 'ActionBar'),
|
||||
new CompareExecutionPlanAction(),
|
||||
this.actionBarToggleTopTip
|
||||
];
|
||||
this._actionBar.pushAction(actionBarActions, { icon: true, label: false });
|
||||
@@ -178,6 +181,7 @@ export class ExecutionPlanView implements ISashLayoutProvider {
|
||||
this._instantiationService.createInstance(ZoomToFitAction, 'ContextMenu'),
|
||||
this._instantiationService.createInstance(CustomZoomAction, 'ContextMenu'),
|
||||
this._instantiationService.createInstance(PropertiesAction, 'ContextMenu'),
|
||||
new CompareExecutionPlanAction(),
|
||||
this.contextMenuToggleTooltipAction
|
||||
];
|
||||
const self = this;
|
||||
@@ -278,6 +282,14 @@ export class ExecutionPlanView implements ISashLayoutProvider {
|
||||
public hideActionBar() {
|
||||
this._actionBarContainer.style.display = 'none';
|
||||
}
|
||||
|
||||
public compareCurrentExecutionPlan() {
|
||||
this._editorService.openEditor(this._instantiationService.createInstance(ExecutionPlanComparisonInput, {
|
||||
topExecutionPlan: [this._model]
|
||||
}), {
|
||||
pinned: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
type ExecutionPlanActionSource = 'ContextMenu' | 'ActionBar' | 'HotKey';
|
||||
@@ -513,3 +525,16 @@ export class ContextMenuTooltipToggle extends Action {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class CompareExecutionPlanAction extends Action {
|
||||
public static ID = 'ep.tooltipToggleContextMenu';
|
||||
public static COMPARE_PLAN = localize('executionPlanCompareExecutionPlanAction', "Compare execution plan");
|
||||
|
||||
constructor() {
|
||||
super(CompareExecutionPlanAction.COMPARE_PLAN, CompareExecutionPlanAction.COMPARE_PLAN, executionPlanCompareIconClassName);
|
||||
}
|
||||
|
||||
public override async run(context: ExecutionPlanView): Promise<void> {
|
||||
context.compareCurrentExecutionPlan();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,13 +27,15 @@ export class ExecutionPlanViewHeader {
|
||||
|
||||
public constructor(
|
||||
private _parentContainer: HTMLElement,
|
||||
headerData: PlanHeaderData,
|
||||
headerData: PlanHeaderData | undefined,
|
||||
@IInstantiationService public readonly _instantiationService: IInstantiationService) {
|
||||
|
||||
this._graphIndex = headerData.planIndex;
|
||||
this._relativeCost = headerData.relativeCost;
|
||||
this._query = headerData.query;
|
||||
this._recommendations = headerData.recommendations ?? [];
|
||||
if (headerData) {
|
||||
this._graphIndex = headerData.planIndex;
|
||||
this._relativeCost = headerData.relativeCost;
|
||||
this._query = headerData.query;
|
||||
this._recommendations = headerData.recommendations ?? [];
|
||||
}
|
||||
|
||||
this._graphIndexAndCostContainer = DOM.$('.index-row');
|
||||
this._queryContainer = DOM.$('.query-row');
|
||||
@@ -84,28 +86,31 @@ export class ExecutionPlanViewHeader {
|
||||
}
|
||||
|
||||
private renderQueryText(): void {
|
||||
this._queryContainer.innerText = this._query;
|
||||
if (this._query) {
|
||||
this._queryContainer.innerText = this._query;
|
||||
}
|
||||
}
|
||||
|
||||
private renderRecommendations(): void {
|
||||
while (this._recommendationsContainer.firstChild) {
|
||||
this._recommendationsContainer.removeChild(this._recommendationsContainer.firstChild);
|
||||
if (this._recommendations) {
|
||||
while (this._recommendationsContainer.firstChild) {
|
||||
this._recommendationsContainer.removeChild(this._recommendationsContainer.firstChild);
|
||||
}
|
||||
this._recommendations.forEach(r => {
|
||||
|
||||
const link = new Button(this._recommendationsContainer, {
|
||||
title: r.displayString,
|
||||
secondary: true,
|
||||
});
|
||||
|
||||
link.label = r.displayString;
|
||||
|
||||
//Enabling on click action for recommendations. It will open the recommendation File
|
||||
link.onDidClick(e => {
|
||||
this._instantiationService.invokeFunction(openNewQuery, undefined, r.queryWithDescription, RunQueryOnConnectionMode.none);
|
||||
});
|
||||
});
|
||||
}
|
||||
this._recommendations.forEach(r => {
|
||||
|
||||
const link = new Button(this._recommendationsContainer, {
|
||||
title: r.displayString,
|
||||
secondary: true,
|
||||
});
|
||||
|
||||
link.label = r.displayString;
|
||||
|
||||
//Enabling on click action for recommendations. It will open the recommendation File
|
||||
link.onDidClick(e => {
|
||||
this._instantiationService.invokeFunction(openNewQuery, undefined, r.queryWithDescription, RunQueryOnConnectionMode.none);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-action-green{fill:#388a34;}</style></defs><title>Add_16x</title><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/><path class="icon-vs-out" d="M6,16V10H0V6H6V0h4V6h6v4H10v6Z"/><path class="icon-vs-action-green" d="M15,9H9v6H7V9H1V7H7V1H9V7h6Z"/></svg>
|
||||
|
After Width: | Height: | Size: 429 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>BranchCompare_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline"><path class="icon-vs-out" d="M14,9.619V5.5a3.507,3.507,0,0,0-2.717-3.412l.235-.234L9.664,0H9.129L6.656,2.473A3.25,3.25,0,1,0,2,6.131V10.5a3.506,3.506,0,0,0,2.717,3.412l-.235.234L6.336,16h.535L9.4,13.473A3.25,3.25,0,1,0,14,9.619Zm-3,0a3.224,3.224,0,0,0-1.6,1.907L6.6,8.732,5,10.336v-4.2A3.222,3.222,0,0,0,6.538,4.41L9.4,7.268l1.6-1.6Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M13,10.311V5.5A2.5,2.5,0,0,0,10.5,3H8.957L10.1,1.854,9.4,1.146,7.043,3.5,9.4,5.854l.708-.708L8.957,4H10.5A1.5,1.5,0,0,1,12,5.5v4.811a2.25,2.25,0,1,0,1,0Zm-.5,3.439a1.25,1.25,0,1,1,1.25-1.25A1.252,1.252,0,0,1,12.5,13.75Zm-6.6-2.9L7.043,12H5.5A1.5,1.5,0,0,1,4,10.5V5.44a2.25,2.25,0,1,0-1,0V10.5A2.5,2.5,0,0,0,5.5,13H7.043L5.9,14.146l.708.708L8.957,12.5,6.6,10.146ZM2.25,3.25A1.25,1.25,0,1,1,3.5,4.5,1.252,1.252,0,0,1,2.25,3.25Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.st0{opacity:0}.st0,.st1{fill:#f6f6f6}.st2{fill:#424242}.st3{fill:#f0eff1}</style><g id="outline"><path class="st0" d="M0 0h16v16H0z"/><path class="st1" d="M9 5v2H8V0H1v7h2v8h6v1h6V5H9zm0 7H6v-2h3v2z"/></g><path class="st2" d="M14 10V6h-4v2H5V6h2V1H2v5h2v8h6v1h4v-4h-4v2H5V9h5v1h4zm-3-3h2v2h-2V7zm0 5h2v2h-2v-2zM3 5V2h3v3H3z" id="icon_x5F_bg"/><g id="icon_x5F_fg"><path class="st3" d="M11 7h2v2h-2zM11 12h2v2h-2zM3 2h3v3H3z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 503 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.canvas,.icon-canvas-transparent{opacity:0;}.canvas{fill:none;}.icon-vs-bg{fill:#424242;}.icon-vs-action-purple{fill:#652d90;}</style></defs><title>ResetView_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline"><path class="icon-vs-out" d="M16,0V16H0V0Z"/></g><g id="iconBg"><path class="canvas" d="M16,16H0V0H16Z"/><path class="icon-vs-bg" d="M1,1H5V2H2V5H1ZM11,1V2h3V5h1V1Zm3,13H11v1h4V11H14ZM2,11H1v4H5V14H2Z"/></g><g id="colorImportance"><path class="icon-vs-action-purple" d="M8,3.567,4,5.876V10.5l4,2.31,4-2.31V5.876Zm2.536,2.619L8,7.65,5.464,6.186,8,4.722ZM5,7.072,7.5,8.516v2.846L5,9.917Zm3.5,4.29V8.516L11,7.072V9.917Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 816 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{fill:#F6F6F6;} .icon-vs-bg{fill:#424242;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M5.986 16l-.373-2.237-1.846 1.317-2.848-2.847 1.319-1.847-2.238-.373v-4.027l2.238-.373-1.319-1.846 2.849-2.848 1.846 1.319.372-2.238h4.028l.372 2.238 1.847-1.319 2.847 2.848-1.318 1.846 2.238.373v4.028l-2.238.372 1.318 1.847-2.847 2.847-1.847-1.318-.373 2.238h-4.027z" id="outline"/><path class="icon-vs-bg" d="M9.964 3.257l-.443-.133-.354-2.124h-2.334l-.353 2.121c-.296.093-.58.21-.855.354l-1.75-1.25-1.65 1.65 1.252 1.752-.22.409-.133.443-2.124.354v2.333l2.121.354c.092.296.21.58.354.855l-1.25 1.75 1.65 1.65 1.752-1.252.408.219.444.134.354 2.124h2.333l.354-2.121c.296-.092.58-.21.854-.354l1.75 1.25 1.65-1.65-1.252-1.752.219-.408.134-.444 2.125-.354v-2.334l-2.121-.353c-.092-.296-.21-.58-.354-.854l1.25-1.75-1.65-1.65-1.752 1.252-.409-.221zm.248 4.743c0 1.222-.991 2.212-2.212 2.212-1.222 0-2.212-.991-2.212-2.212s.99-2.212 2.212-2.212c1.222 0 2.212.99 2.212 2.212z" id="iconBg"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>SplitScreenHorizontal_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline"><path class="icon-vs-out" d="M16,1V15H0V1Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M1,2V14H15V2ZM14,13H2V10H14Zm0-5H2V5H14Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 479 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>SplitScreenVertical_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline"><path class="icon-vs-out" d="M16,1V15H0V1Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M1,2V14H15V2ZM7,13H2V5H7Zm7,0H9V5h5Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 473 B |
@@ -109,24 +109,34 @@ However we always want it to be the width of the container it is resizing.
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Properties view in execution plan */
|
||||
.eps-container .execution-plan .properties {
|
||||
.eps-container .properties {
|
||||
flex: 0 0 500px;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.eps-container .properties-sash {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
/* Properties view in execution plan */
|
||||
.properties-content {
|
||||
overflow: hidden;
|
||||
width: 500px;
|
||||
height: calc( 100% - 2px );
|
||||
height: calc(100% - 2px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-left: 1px solid;
|
||||
border-left: 3px solid;
|
||||
border-color: var(--separator-border)
|
||||
}
|
||||
|
||||
/* Title container of the properties view */
|
||||
.eps-container .execution-plan .properties .title {
|
||||
.eps-container .properties .title {
|
||||
line-height: 30px;
|
||||
height: 22px;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -137,7 +147,7 @@ However we always want it to be the width of the container it is resizing.
|
||||
}
|
||||
|
||||
/* text in title container of properties view */
|
||||
.eps-container .execution-plan .properties .title .text {
|
||||
.eps-container .properties .title .text {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
@@ -148,7 +158,7 @@ However we always want it to be the width of the container it is resizing.
|
||||
}
|
||||
|
||||
/* action bar in the title container for the properties view. This contains the close icon */
|
||||
.eps-container .execution-plan .properties .title .action-bar {
|
||||
.eps-container .properties .title .action-bar {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
@@ -160,7 +170,7 @@ However we always want it to be the width of the container it is resizing.
|
||||
}
|
||||
|
||||
/* Operation name styling in the properties view. */
|
||||
.eps-container .execution-plan .properties .operation-name {
|
||||
.eps-container .properties .operation-name {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
@@ -174,8 +184,8 @@ However we always want it to be the width of the container it is resizing.
|
||||
}
|
||||
|
||||
/* Properties table container in properties view */
|
||||
.eps-container .execution-plan .properties .table-container {
|
||||
overflow-y: scroll;
|
||||
.eps-container .properties .table-container {
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
flex-grow: 1;
|
||||
}
|
||||
@@ -183,6 +193,7 @@ However we always want it to be the width of the container it is resizing.
|
||||
/* Action bar for the execution plan */
|
||||
.eps-container .execution-plan .action-bar-container {
|
||||
flex: 0 0 25px;
|
||||
border-left: 1px solid var(--separator-border);
|
||||
}
|
||||
|
||||
/* styling for the column headers in the properties table */
|
||||
@@ -195,7 +206,6 @@ However we always want it to be the width of the container it is resizing.
|
||||
-webkit-margin-before: 0;
|
||||
-webkit-margin-after: 0;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.eps-container .properties-header {
|
||||
@@ -331,3 +341,184 @@ However we always want it to be the width of the container it is resizing.
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.ep-add-icon {
|
||||
background-image: url(../images/actionIcons/add.svg);
|
||||
background-size: 16px 16px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.ep-settings-icon {
|
||||
background-image: url(../images/actionIcons/settings.svg);
|
||||
background-size: 16px 16px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.ep-split-screen-horizontally-icon {
|
||||
background-image: url(../images/actionIcons/splitScreenHorizontally.svg);
|
||||
background-size: 16px 16px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.ep-split-screen-vertically-icon {
|
||||
background-image: url(../images/actionIcons/splitScreenVertically.svg);
|
||||
background-size: 16px 16px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.ep-reset-zoom-icon {
|
||||
background-image: url(../images/actionIcons/resetZoom.svg);
|
||||
background-size: 16px 16px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.ep-plan-compare-icon {
|
||||
background-image: url(../images/actionIcons/execution-plan-compare.svg);
|
||||
background-size: 16px 16px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.eps-container .comparison-editor {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.eps-container .comparison-editor .editor-toolbar {
|
||||
width: 100%;
|
||||
flex: 0 0 auto;
|
||||
border-bottom: 2px solid;
|
||||
border-color: var(--separator-border);
|
||||
}
|
||||
|
||||
.eps-container .comparison-editor .plan-comparison-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.eps-container .comparison-editor .plan-comparison-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.eps-container .comparison-editor .plan-comparison-container {
|
||||
width: 100%;
|
||||
height: calc(100% - 25px);
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.eps-container .comparison-editor .plan-comparison-container .split-view-container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.eps-container .comparison-editor .plan-comparison-container .split-view-container .plan-container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.eps-container .comparison-editor .plan-comparison-container .split-view-container .plan-container .dropdown-container {
|
||||
flex: 0;
|
||||
padding: 3px;
|
||||
border-bottom: 1px solid var(--separator-border);
|
||||
}
|
||||
|
||||
/* each link in execution plan recommendations */
|
||||
.eps-container .comparison-editor .plan-comparison-container .split-view-container .plan-container .recommendations > a {
|
||||
width: fit-content;
|
||||
align-items: left;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.eps-container .comparison-editor .plan-comparison-container .split-view-container .sash-container {
|
||||
flex: 0 0 2px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
background-color: var(--separator-border);
|
||||
}
|
||||
|
||||
/*
|
||||
The actual sash element constructed by code. Important is used here because the width of the sash is fixed.
|
||||
However we always want it to be the width of the container it is resizing.
|
||||
*/
|
||||
.eps-container .comparison-editor .plan-comparison-container .split-view-container .sash-container .horizontal {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.eps-container .comparison-editor .plan-comparison-container .split-view-container .sash-container .vertical {
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.eps-container .comparison-editor .plan-comparison-container .properties {
|
||||
display: flex;
|
||||
flex: 0 0 500px;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.eps-container .comparison-editor .plan-comparison-container .split-view-container .placeholder {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.eps-container .comparison-editor .plan-comparison-container .split-view-container .placeholder .infobox-container {
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
|
||||
.eps-container .comparison-editor .plan-comparison-container .split-view-container .plan-container .plan-diagram {
|
||||
flex: 1;
|
||||
overflow: scroll;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
.eps-container .comparison-editor .plan-comparison-container .properties .compare-operation-name {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.eps-container .comparison-editor .plan-comparison-container .properties .compare-operation-name-text {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
font-size: 13px;
|
||||
-webkit-margin-before: 0;
|
||||
-webkit-margin-after: 0;
|
||||
margin-top: 3px;
|
||||
margin-bottom: 5px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.eps-container .comparison-editor .properties .table-container {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { ExecutionPlanWidgetBase } from 'sql/workbench/contrib/executionPlan/browser/executionPlanWidgetBase';
|
||||
import { ActionBar } from 'sql/base/browser/ui/taskbar/actionbar';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
@@ -14,7 +15,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
|
||||
import { AzdataGraphView, InternalExecutionPlanNode, SearchType } from 'sql/workbench/contrib/executionPlan/browser/azdataGraphView';
|
||||
import { AzdataGraphView, SearchType } from 'sql/workbench/contrib/executionPlan/browser/azdataGraphView';
|
||||
import { ExecutionPlanWidgetController } from 'sql/workbench/contrib/executionPlan/browser/executionPlanWidgetController';
|
||||
|
||||
const CONTAINS_DISPLAY_STRING = localize("executionPlanSearchTypeContains", 'Contains');
|
||||
@@ -35,7 +36,7 @@ export class NodeSearchWidget extends ExecutionPlanWidgetBase {
|
||||
private _selectedSearchType: SearchType = SearchType.Equals;
|
||||
|
||||
private _searchTextInputBox: InputBox;
|
||||
private _searchResults: InternalExecutionPlanNode[] = [];
|
||||
private _searchResults: azdata.executionPlan.ExecutionPlanNode[] = [];
|
||||
private _currentSearchResultIndex = 0;
|
||||
private _usePreviousSearchResult: boolean = false;
|
||||
|
||||
|
||||
@@ -67,6 +67,9 @@ export class ExecutionPlanInput extends EditorInput {
|
||||
}
|
||||
|
||||
get resource(): URI | undefined {
|
||||
return undefined;
|
||||
return URI.from({
|
||||
scheme: ExecutionPlanInput.SCHEMA,
|
||||
path: 'execution-plan'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,5 +133,23 @@ export class ExecutionPlanService implements IExecutionPlanService {
|
||||
return this._capabilitiesService.getCapabilities(providerId).connection.supportedExecutionPlanFileExtensions;
|
||||
}
|
||||
|
||||
getSupportedExecutionPlanExtensions(providerId: string): string[] | undefined {
|
||||
if (providerId) {
|
||||
return this._capabilitiesService.getCapabilities(providerId).connection.supportedExecutionPlanFileExtensions;
|
||||
} else {
|
||||
const supportedFileExtensionsSet: Set<string> = new Set();
|
||||
|
||||
Object.keys(this._capabilitiesService.providers).forEach(v => {
|
||||
const extensions = this._capabilitiesService.getCapabilities(v).connection.supportedExecutionPlanFileExtensions;
|
||||
if (extensions) {
|
||||
extensions.forEach(ext => {
|
||||
supportedFileExtensionsSet.add(ext);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return [...supportedFileExtensionsSet];
|
||||
}
|
||||
}
|
||||
_serviceBrand: undefined;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,8 @@ export interface IExecutionPlanService {
|
||||
compareExecutionPlanGraph(firstPlanFile: azdata.executionPlan.ExecutionPlanGraphInfo, secondPlanFile: azdata.executionPlan.ExecutionPlanGraphInfo): Promise<azdata.executionPlan.ExecutionPlanComparisonResult>;
|
||||
|
||||
/**
|
||||
* Get execution plan file extensions supported by the provider.
|
||||
* Get execution plan file extensions supported by all registered providers.
|
||||
* @param providerId optional parameter to get extensions only supported by a particular provider.
|
||||
*/
|
||||
getSupportedExecutionPlanExtensionsForProvider(providerId: string): string[];
|
||||
getSupportedExecutionPlanExtensions(providerId?: string): string[];
|
||||
}
|
||||
|
||||
@@ -1831,9 +1831,9 @@ aws4@^1.8.0:
|
||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
||||
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
|
||||
|
||||
"azdataGraph@github:Microsoft/azdataGraph#0.0.21":
|
||||
version "0.0.21"
|
||||
resolved "https://codeload.github.com/Microsoft/azdataGraph/tar.gz/59f38fc96ab8beadb5ed2b6986ae3f39d662d040"
|
||||
"azdataGraph@github:Microsoft/azdataGraph#0.0.26":
|
||||
version "0.0.26"
|
||||
resolved "https://codeload.github.com/Microsoft/azdataGraph/tar.gz/a5f94e53cb655bc44f1a2727653bb403942e1cf9"
|
||||
|
||||
azure-storage@^2.10.2:
|
||||
version "2.10.2"
|
||||
|
||||