mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Query Execution Plan Expensive Operator Highlighting (#20579)
* Boilerplate for new context menu options * Enables checkmarks for expensive operator context menu actions * Updates azdataGraph version to 0.0.44 * Adds clearing logic to actions and retrieves additional info from nodes * Removes unnecessary actions that aren't supported by other providers * Finishes setting up the rest of the context menu actions * Corrects context menu action label * Defines new widget type for finding expensive operations * Adds TODO for class icon name * Creates action to open the expensive operation widget * Adds escape and enter key shortcuts to findExpensiveOperation widget * Styles find expensive operation widget * Corrects class name for finding expensive operator action * Corrects import statement. * Code clean up * Bumps azdataGraph version to 0.0.45 * Adds an info box for when a metric doesn't find any nodes * Adds label to find expensive operator widget * Invokes dispose when widget controller removes widget * Implements disposable in all execution plan widgets. * Adds off action to clear highlighting * Adds a default setting and default prompt for highlighting metric * Fixes not all code paths return error * Removes shortcut key from text for widget actions * Adds enum description * Removes added dictionary type, and renames class name * Removes unnecessary null checks * Removes cost metric dictionary and adds corresponding properties * Code review changes * Removes incorrectly implemented key down event for widget. * Renames widget and action class names for highlighting expensive ops * File rename * Cleans up labels to better reflect highlight action * Removes hardcoded button width style and sets it to auto * More clean up * Clean up import statement * Code review changes * Drop down list only shows available metrics * Updates highlight expensive operation icon * Update STS version
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||||
"version": "4.3.0.30",
|
"version": "4.3.0.32",
|
||||||
"downloadFileNames": {
|
"downloadFileNames": {
|
||||||
"Windows_86": "win-x86-net6.0.zip",
|
"Windows_86": "win-x86-net6.0.zip",
|
||||||
"Windows_64": "win-x64-net6.0.zip",
|
"Windows_64": "win-x64-net6.0.zip",
|
||||||
|
|||||||
@@ -261,6 +261,29 @@
|
|||||||
"default": false,
|
"default": false,
|
||||||
"description": "%mssql.intelliSense.lowerCaseSuggestions%"
|
"description": "%mssql.intelliSense.lowerCaseSuggestions%"
|
||||||
},
|
},
|
||||||
|
"mssql.executionPlan.expensiveOperationMetric": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "%mssql.executionPlan.expensiveOperationMetric%",
|
||||||
|
"default": "cost",
|
||||||
|
"enum": [
|
||||||
|
"off",
|
||||||
|
"actualElapsedTime",
|
||||||
|
"actualElapsedCpuTime",
|
||||||
|
"cost",
|
||||||
|
"subtreeCost",
|
||||||
|
"actualNumberOfRowsForAllExecutions",
|
||||||
|
"numberOfRowsRead"
|
||||||
|
],
|
||||||
|
"enumDescriptions": [
|
||||||
|
"%mssql.executionPlan.expensiveOperationMetric.off%",
|
||||||
|
"%mssql.executionPlan.expensiveOperationMetric.actualElapsedTime%",
|
||||||
|
"%mssql.executionPlan.expensiveOperationMetric.actualElapsedCpuTime%",
|
||||||
|
"%mssql.executionPlan.cost%",
|
||||||
|
"%mssql.executionPlan.subtreeCost%",
|
||||||
|
"%mssql.executionPlan.actualNumberOfRowsForAllExecutions%",
|
||||||
|
"%mssql.executionPlan.numberOfRowsRead%"
|
||||||
|
]
|
||||||
|
},
|
||||||
"mssql.query.rowCount": {
|
"mssql.query.rowCount": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"default": 0,
|
"default": 0,
|
||||||
|
|||||||
@@ -39,6 +39,14 @@
|
|||||||
"mssql.disabled": "Disabled",
|
"mssql.disabled": "Disabled",
|
||||||
"mssql.enabled": "Enabled",
|
"mssql.enabled": "Enabled",
|
||||||
|
|
||||||
|
"mssql.executionPlan.expensiveOperationMetric": "The default metric to use to highlight an expensive operation in query execution plans",
|
||||||
|
"mssql.executionPlan.expensiveOperationMetric.off": "Expensive operation highlighting will be turned off for execution plans.",
|
||||||
|
"mssql.executionPlan.expensiveOperationMetric.actualElapsedTime": "Highlights the execution plan operation that took the most time.",
|
||||||
|
"mssql.executionPlan.expensiveOperationMetric.actualElapsedCpuTime": "Highlights the execution plan operation that used the most CPU time.",
|
||||||
|
"mssql.executionPlan.cost": "Highlights the execution plan operation with the highest cost.",
|
||||||
|
"mssql.executionPlan.subtreeCost": "Highlights the execution plan operation with the highest subtree cost.",
|
||||||
|
"mssql.executionPlan.actualNumberOfRowsForAllExecutions": "Highlights the execution plan operation with the greatest actual number of rows for all executions.",
|
||||||
|
"mssql.executionPlan.numberOfRowsRead": "Highlights the execution plan operation with the greatest number of rows read.",
|
||||||
"mssql.exportNotebookToSql": "Export Notebook as SQL",
|
"mssql.exportNotebookToSql": "Export Notebook as SQL",
|
||||||
"mssql.exportSqlAsNotebook": "Export SQL as Notebook",
|
"mssql.exportSqlAsNotebook": "Export SQL as Notebook",
|
||||||
"mssql.configuration.title": "MSSQL configuration",
|
"mssql.configuration.title": "MSSQL configuration",
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
"angular2-grid": "2.0.6",
|
"angular2-grid": "2.0.6",
|
||||||
"ansi_up": "^5.1.0",
|
"ansi_up": "^5.1.0",
|
||||||
"applicationinsights": "1.0.8",
|
"applicationinsights": "1.0.8",
|
||||||
"azdataGraph": "github:Microsoft/azdataGraph#0.0.42",
|
"azdataGraph": "github:Microsoft/azdataGraph#0.0.45",
|
||||||
"chart.js": "^2.9.4",
|
"chart.js": "^2.9.4",
|
||||||
"chokidar": "3.5.1",
|
"chokidar": "3.5.1",
|
||||||
"graceful-fs": "4.2.8",
|
"graceful-fs": "4.2.8",
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
"applicationinsights": "1.0.8",
|
"applicationinsights": "1.0.8",
|
||||||
"angular2-grid": "2.0.6",
|
"angular2-grid": "2.0.6",
|
||||||
"ansi_up": "^5.1.0",
|
"ansi_up": "^5.1.0",
|
||||||
"azdataGraph": "github:Microsoft/azdataGraph#0.0.42",
|
"azdataGraph": "github:Microsoft/azdataGraph#0.0.45",
|
||||||
"chart.js": "^2.9.4",
|
"chart.js": "^2.9.4",
|
||||||
"cookie": "^0.4.0",
|
"cookie": "^0.4.0",
|
||||||
"graceful-fs": "4.2.8",
|
"graceful-fs": "4.2.8",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
"@vscode/vscode-languagedetection": "1.0.21",
|
"@vscode/vscode-languagedetection": "1.0.21",
|
||||||
"angular2-grid": "2.0.6",
|
"angular2-grid": "2.0.6",
|
||||||
"ansi_up": "^5.1.0",
|
"ansi_up": "^5.1.0",
|
||||||
"azdataGraph": "github:Microsoft/azdataGraph#0.0.42",
|
"azdataGraph": "github:Microsoft/azdataGraph#0.0.45",
|
||||||
"chart.js": "^2.9.4",
|
"chart.js": "^2.9.4",
|
||||||
"gridstack": "^3.1.3",
|
"gridstack": "^3.1.3",
|
||||||
"kburtram-query-plan": "2.6.1",
|
"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"
|
resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
|
||||||
integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=
|
integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=
|
||||||
|
|
||||||
"azdataGraph@github:Microsoft/azdataGraph#0.0.42":
|
"azdataGraph@github:Microsoft/azdataGraph#0.0.45":
|
||||||
version "0.0.42"
|
version "0.0.45"
|
||||||
resolved "https://codeload.github.com/Microsoft/azdataGraph/tar.gz/86f72ee9c9ea78a31c9ad2f402fb24d40e50c75b"
|
resolved "https://codeload.github.com/Microsoft/azdataGraph/tar.gz/8675ac6dfc60aad331ccbf7e4145b9eaa25e7304"
|
||||||
|
|
||||||
chalk@^2.3.0, chalk@^2.4.1:
|
chalk@^2.3.0, chalk@^2.4.1:
|
||||||
version "2.4.2"
|
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"
|
resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
|
||||||
integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=
|
integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=
|
||||||
|
|
||||||
"azdataGraph@github:Microsoft/azdataGraph#0.0.42":
|
"azdataGraph@github:Microsoft/azdataGraph#0.0.45":
|
||||||
version "0.0.42"
|
version "0.0.45"
|
||||||
resolved "https://codeload.github.com/Microsoft/azdataGraph/tar.gz/86f72ee9c9ea78a31c9ad2f402fb24d40e50c75b"
|
resolved "https://codeload.github.com/Microsoft/azdataGraph/tar.gz/8675ac6dfc60aad331ccbf7e4145b9eaa25e7304"
|
||||||
|
|
||||||
bindings@^1.5.0:
|
bindings@^1.5.0:
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
|
|||||||
19
src/sql/azdata.proposed.d.ts
vendored
19
src/sql/azdata.proposed.d.ts
vendored
@@ -1311,6 +1311,10 @@ declare module 'azdata' {
|
|||||||
* Time take by the node operation in milliseconds
|
* Time take by the node operation in milliseconds
|
||||||
*/
|
*/
|
||||||
elapsedTimeInMs: number;
|
elapsedTimeInMs: number;
|
||||||
|
/**
|
||||||
|
* CPU time taken by the node operation in milliseconds
|
||||||
|
*/
|
||||||
|
elapsedCpuTimeInMs: number;
|
||||||
/**
|
/**
|
||||||
* Node properties to be shown in the tooltip
|
* Node properties to be shown in the tooltip
|
||||||
*/
|
*/
|
||||||
@@ -1351,6 +1355,21 @@ declare module 'azdata' {
|
|||||||
* Cost string for the node
|
* Cost string for the node
|
||||||
*/
|
*/
|
||||||
costDisplayString: string;
|
costDisplayString: string;
|
||||||
|
/**
|
||||||
|
* Cost metrics for the node
|
||||||
|
*/
|
||||||
|
costMetrics: CostMetric[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CostMetric {
|
||||||
|
/**
|
||||||
|
* Name of the cost metric.
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* The value of the cost metric
|
||||||
|
*/
|
||||||
|
value: number | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExecutionPlanBadge {
|
export interface ExecutionPlanBadge {
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ export const enum TelemetryAction {
|
|||||||
GeneratePreviewReport = 'GeneratePreviewReport',
|
GeneratePreviewReport = 'GeneratePreviewReport',
|
||||||
GetDataGridItems = 'GetDataGridItems',
|
GetDataGridItems = 'GetDataGridItems',
|
||||||
GetDataGridColumns = 'GetDataGridColumns',
|
GetDataGridColumns = 'GetDataGridColumns',
|
||||||
|
HighlightExpensiveOperation = 'HighlightExpensiveOperation',
|
||||||
ModelViewDashboardOpened = 'ModelViewDashboardOpened',
|
ModelViewDashboardOpened = 'ModelViewDashboardOpened',
|
||||||
ModalDialogClosed = 'ModalDialogClosed',
|
ModalDialogClosed = 'ModalDialogClosed',
|
||||||
ModalDialogOpened = 'ModalDialogOpened',
|
ModalDialogOpened = 'ModalDialogOpened',
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export class AzdataGraphView {
|
|||||||
private _diagram: any;
|
private _diagram: any;
|
||||||
private _diagramModel: AzDataGraphCell;
|
private _diagramModel: AzDataGraphCell;
|
||||||
private _cellInFocus: AzDataGraphCell;
|
private _cellInFocus: AzDataGraphCell;
|
||||||
|
public expensiveMetricTypes: Set<ExpensiveMetricType> = new Set();
|
||||||
|
|
||||||
private _graphElementPropertiesSet: Set<string> = new Set();
|
private _graphElementPropertiesSet: Set<string> = new Set();
|
||||||
|
|
||||||
@@ -230,6 +231,13 @@ export class AzdataGraphView {
|
|||||||
return resultNodes;
|
return resultNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public clearExpensiveOperatorHighlighting(): void {
|
||||||
|
this._diagram.clearExpensiveOperatorHighlighting();
|
||||||
|
}
|
||||||
|
|
||||||
|
public highlightExpensiveOperator(predicate: (cell: AzDataGraphCell) => number): boolean {
|
||||||
|
return this._diagram.highlightExpensiveOperator(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Brings a graph element to the center of the parent view.
|
* Brings a graph element to the center of the parent view.
|
||||||
@@ -295,40 +303,65 @@ export class AzdataGraphView {
|
|||||||
if (!node.id.toString().startsWith(`element-`)) {
|
if (!node.id.toString().startsWith(`element-`)) {
|
||||||
node.id = `element-${node.id}`;
|
node.id = `element-${node.id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.expensiveMetricTypes.add(ExpensiveMetricType.Off);
|
||||||
|
|
||||||
diagramNode.id = node.id;
|
diagramNode.id = node.id;
|
||||||
|
|
||||||
if (node.type) {
|
|
||||||
diagramNode.icon = node.type;
|
diagramNode.icon = node.type;
|
||||||
}
|
|
||||||
|
|
||||||
if (node.properties) {
|
|
||||||
diagramNode.metrics = this.populateProperties(node.properties);
|
diagramNode.metrics = this.populateProperties(node.properties);
|
||||||
}
|
|
||||||
|
|
||||||
if (node.badges) {
|
|
||||||
diagramNode.badges = [];
|
diagramNode.badges = [];
|
||||||
for (let i = 0; i < node.badges.length; i++) {
|
for (let i = 0; node.badges && i < node.badges.length; i++) {
|
||||||
diagramNode.badges.push(this.getBadgeTypeString(node.badges[i].type));
|
diagramNode.badges.push(this.getBadgeTypeString(node.badges[i].type));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (node.edges) {
|
|
||||||
diagramNode.edges = this.populateEdges(node.edges);
|
diagramNode.edges = this.populateEdges(node.edges);
|
||||||
}
|
|
||||||
|
|
||||||
if (node.children) {
|
|
||||||
diagramNode.children = [];
|
diagramNode.children = [];
|
||||||
for (let i = 0; i < node.children.length; ++i) {
|
for (let i = 0; node.children && i < node.children.length; ++i) {
|
||||||
diagramNode.children.push(this.populate(node.children[i]));
|
diagramNode.children.push(this.populate(node.children[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diagramNode.description = node.description;
|
||||||
|
diagramNode.cost = node.cost;
|
||||||
|
if (node.cost) {
|
||||||
|
this.expensiveMetricTypes.add(ExpensiveMetricType.Cost);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.description) {
|
diagramNode.subTreeCost = node.subTreeCost;
|
||||||
diagramNode.description = node.description;
|
if (node.subTreeCost) {
|
||||||
|
this.expensiveMetricTypes.add(ExpensiveMetricType.SubtreeCost);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diagramNode.relativeCost = node.relativeCost;
|
||||||
|
diagramNode.elapsedTimeInMs = node.elapsedTimeInMs;
|
||||||
|
if (node.elapsedTimeInMs) {
|
||||||
|
this.expensiveMetricTypes.add(ExpensiveMetricType.ActualElapsedTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
let costMetrics = [];
|
||||||
|
for (let i = 0; node.costMetrics && i < node.costMetrics.length; ++i) {
|
||||||
|
costMetrics.push(node.costMetrics[i]);
|
||||||
|
|
||||||
|
this.loadMetricTypesFromCostMetrics(node.costMetrics[i].name);
|
||||||
|
}
|
||||||
|
diagramNode.costMetrics = costMetrics;
|
||||||
|
|
||||||
return diagramNode;
|
return diagramNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private loadMetricTypesFromCostMetrics(costMetricName: string): void {
|
||||||
|
if (costMetricName === 'ElapsedCpuTime') {
|
||||||
|
this.expensiveMetricTypes.add(ExpensiveMetricType.ActualElapsedCpuTime);
|
||||||
|
}
|
||||||
|
else if (costMetricName === 'EstimateRowsAllExecs' || costMetricName === 'ActualRows') {
|
||||||
|
this.expensiveMetricTypes.add(ExpensiveMetricType.ActualNumberOfRowsForAllExecutions);
|
||||||
|
}
|
||||||
|
else if (costMetricName === 'EstimatedRowsRead' || costMetricName === 'ActualRowsRead') {
|
||||||
|
this.expensiveMetricTypes.add(ExpensiveMetricType.NumberOfRowsRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private getBadgeTypeString(badgeType: sqlExtHostType.executionPlan.BadgeType): {
|
private getBadgeTypeString(badgeType: sqlExtHostType.executionPlan.BadgeType): {
|
||||||
type: string,
|
type: string,
|
||||||
tooltip: string
|
tooltip: string
|
||||||
@@ -357,7 +390,11 @@ export class AzdataGraphView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private populateProperties(props: azdata.executionPlan.ExecutionPlanGraphElementProperty[]): AzDataGraphCellMetric[] {
|
private populateProperties(props: azdata.executionPlan.ExecutionPlanGraphElementProperty[] | undefined): AzDataGraphCellMetric[] {
|
||||||
|
if (!props) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
props.forEach(p => {
|
props.forEach(p => {
|
||||||
this._graphElementPropertiesSet.add(p.name);
|
this._graphElementPropertiesSet.add(p.name);
|
||||||
});
|
});
|
||||||
@@ -372,7 +409,11 @@ export class AzdataGraphView {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private populateEdges(edges: InternalExecutionPlanEdge[]): AzDataGraphCellEdge[] {
|
private populateEdges(edges: InternalExecutionPlanEdge[] | undefined): AzDataGraphCellEdge[] {
|
||||||
|
if (!edges) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
return edges.map(e => {
|
return edges.map(e => {
|
||||||
e.id = this.createGraphElementId();
|
e.id = this.createGraphElementId();
|
||||||
return {
|
return {
|
||||||
@@ -473,6 +514,37 @@ export interface AzDataGraphCell {
|
|||||||
*/
|
*/
|
||||||
description: string;
|
description: string;
|
||||||
badges: AzDataGraphNodeBadge[];
|
badges: AzDataGraphNodeBadge[];
|
||||||
|
/**
|
||||||
|
* Cost associated with the node
|
||||||
|
*/
|
||||||
|
cost: number;
|
||||||
|
/**
|
||||||
|
* Cost of the node subtree
|
||||||
|
*/
|
||||||
|
subTreeCost: number;
|
||||||
|
/**
|
||||||
|
* Relative cost of the node compared to its siblings.
|
||||||
|
*/
|
||||||
|
relativeCost: number;
|
||||||
|
/**
|
||||||
|
* Time taken by the node operation in milliseconds
|
||||||
|
*/
|
||||||
|
elapsedTimeInMs: number;
|
||||||
|
/**
|
||||||
|
* cost metrics for the node
|
||||||
|
*/
|
||||||
|
costMetrics: CostMetric[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CostMetric {
|
||||||
|
/**
|
||||||
|
* Name of the cost metric.
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* The value of the cost metric
|
||||||
|
*/
|
||||||
|
value: number | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AzDataGraphNodeBadge {
|
export interface AzDataGraphNodeBadge {
|
||||||
@@ -529,6 +601,17 @@ export enum SearchType {
|
|||||||
LesserThanEqualTo,
|
LesserThanEqualTo,
|
||||||
LesserAndGreaterThan
|
LesserAndGreaterThan
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ExpensiveMetricType {
|
||||||
|
Off = 'off',
|
||||||
|
ActualElapsedTime = 'actualElapsedTime',
|
||||||
|
ActualElapsedCpuTime = 'actualElapsedCpuTime',
|
||||||
|
Cost = 'cost',
|
||||||
|
SubtreeCost = 'subtreeCost',
|
||||||
|
ActualNumberOfRowsForAllExecutions = 'actualNumberOfRowsForAllExecutions',
|
||||||
|
NumberOfRowsRead = 'numberOfRowsRead'
|
||||||
|
}
|
||||||
|
|
||||||
export interface SearchQuery {
|
export interface SearchQuery {
|
||||||
/**
|
/**
|
||||||
* property name to be searched
|
* property name to be searched
|
||||||
|
|||||||
@@ -265,6 +265,7 @@ export const collapseExpandNodeIconPaths = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const savePlanIconClassNames = 'ep-save-plan-icon';
|
export const savePlanIconClassNames = 'ep-save-plan-icon';
|
||||||
|
export const highlightExpensiveOperationClassNames = 'ep-highlight-expensive-operation-icon';
|
||||||
export const openPropertiesIconClassNames = 'ep-open-properties-icon';
|
export const openPropertiesIconClassNames = 'ep-open-properties-icon';
|
||||||
export const openQueryIconClassNames = 'ep-open-query-icon';
|
export const openQueryIconClassNames = 'ep-open-query-icon';
|
||||||
export const openPlanFileIconClassNames = 'ep-open-plan-file-icon';
|
export const openPlanFileIconClassNames = 'ep-open-plan-file-icon';
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { Progress } from 'vs/platform/progress/common/progress';
|
|||||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||||
import { Action, Separator } from 'vs/base/common/actions';
|
import { Action, Separator } from 'vs/base/common/actions';
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
import { customZoomIconClassNames, disableTooltipIconClassName, enableTooltipIconClassName, executionPlanCompareIconClassName, executionPlanTopOperations, openPlanFileIconClassNames, openPropertiesIconClassNames, openQueryIconClassNames, savePlanIconClassNames, searchIconClassNames, zoomInIconClassNames, zoomOutIconClassNames, zoomToFitIconClassNames } from 'sql/workbench/contrib/executionPlan/browser/constants';
|
import * as constants from 'sql/workbench/contrib/executionPlan/browser/constants';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import { VSBuffer } from 'vs/base/common/buffer';
|
import { VSBuffer } from 'vs/base/common/buffer';
|
||||||
import { CustomZoomWidget } from 'sql/workbench/contrib/executionPlan/browser/widgets/customZoomWidget';
|
import { CustomZoomWidget } from 'sql/workbench/contrib/executionPlan/browser/widgets/customZoomWidget';
|
||||||
@@ -37,6 +37,7 @@ import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
|
|||||||
import { ExecutionPlanComparisonInput } from 'sql/workbench/contrib/executionPlan/browser/compareExecutionPlanInput';
|
import { ExecutionPlanComparisonInput } from 'sql/workbench/contrib/executionPlan/browser/compareExecutionPlanInput';
|
||||||
import { ExecutionPlanFileView } from 'sql/workbench/contrib/executionPlan/browser/executionPlanFileView';
|
import { ExecutionPlanFileView } from 'sql/workbench/contrib/executionPlan/browser/executionPlanFileView';
|
||||||
import { QueryResultsView } from 'sql/workbench/contrib/query/browser/queryResultsView';
|
import { QueryResultsView } from 'sql/workbench/contrib/query/browser/queryResultsView';
|
||||||
|
import { HighlightExpensiveOperationWidget } from 'sql/workbench/contrib/executionPlan/browser/widgets/highlightExpensiveNodeWidget';
|
||||||
|
|
||||||
export class ExecutionPlanView implements ISashLayoutProvider {
|
export class ExecutionPlanView implements ISashLayoutProvider {
|
||||||
|
|
||||||
@@ -66,6 +67,9 @@ export class ExecutionPlanView implements ISashLayoutProvider {
|
|||||||
// plan diagram
|
// plan diagram
|
||||||
public executionPlanDiagram: AzdataGraphView;
|
public executionPlanDiagram: AzdataGraphView;
|
||||||
|
|
||||||
|
// previous expensive operator action selected
|
||||||
|
public previousExpensiveOperatorAction: Action;
|
||||||
|
|
||||||
public actionBarToggleTopTip: Action;
|
public actionBarToggleTopTip: Action;
|
||||||
public contextMenuToggleTooltipAction: Action;
|
public contextMenuToggleTooltipAction: Action;
|
||||||
constructor(
|
constructor(
|
||||||
@@ -169,6 +173,7 @@ export class ExecutionPlanView implements ISashLayoutProvider {
|
|||||||
this._instantiationService.createInstance(SearchNodeAction, 'ActionBar'),
|
this._instantiationService.createInstance(SearchNodeAction, 'ActionBar'),
|
||||||
this._instantiationService.createInstance(PropertiesAction, 'ActionBar'),
|
this._instantiationService.createInstance(PropertiesAction, 'ActionBar'),
|
||||||
this._instantiationService.createInstance(CompareExecutionPlanAction, 'ActionBar'),
|
this._instantiationService.createInstance(CompareExecutionPlanAction, 'ActionBar'),
|
||||||
|
this._instantiationService.createInstance(HighlightExpensiveOperationAction, 'ActionBar'),
|
||||||
this.actionBarToggleTopTip
|
this.actionBarToggleTopTip
|
||||||
];
|
];
|
||||||
// Setting up context menu
|
// Setting up context menu
|
||||||
@@ -186,7 +191,9 @@ export class ExecutionPlanView implements ISashLayoutProvider {
|
|||||||
this._instantiationService.createInstance(SearchNodeAction, 'ContextMenu'),
|
this._instantiationService.createInstance(SearchNodeAction, 'ContextMenu'),
|
||||||
this._instantiationService.createInstance(PropertiesAction, 'ContextMenu'),
|
this._instantiationService.createInstance(PropertiesAction, 'ContextMenu'),
|
||||||
this._instantiationService.createInstance(CompareExecutionPlanAction, 'ContextMenu'),
|
this._instantiationService.createInstance(CompareExecutionPlanAction, 'ContextMenu'),
|
||||||
this.contextMenuToggleTooltipAction
|
this._instantiationService.createInstance(HighlightExpensiveOperationAction, 'ContextMenu'),
|
||||||
|
this.contextMenuToggleTooltipAction,
|
||||||
|
new Separator(),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (this._queryResultsView) {
|
if (this._queryResultsView) {
|
||||||
@@ -320,7 +327,7 @@ export class OpenQueryAction extends Action {
|
|||||||
constructor(private source: ExecutionPlanActionSource,
|
constructor(private source: ExecutionPlanActionSource,
|
||||||
@IAdsTelemetryService private readonly telemetryService: IAdsTelemetryService
|
@IAdsTelemetryService private readonly telemetryService: IAdsTelemetryService
|
||||||
) {
|
) {
|
||||||
super(OpenQueryAction.ID, OpenQueryAction.LABEL, openQueryIconClassNames);
|
super(OpenQueryAction.ID, OpenQueryAction.LABEL, constants.openQueryIconClassNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async run(context: ExecutionPlanView): Promise<void> {
|
public override async run(context: ExecutionPlanView): Promise<void> {
|
||||||
@@ -340,7 +347,7 @@ export class PropertiesAction extends Action {
|
|||||||
constructor(private source: ExecutionPlanActionSource,
|
constructor(private source: ExecutionPlanActionSource,
|
||||||
@IAdsTelemetryService private readonly telemetryService: IAdsTelemetryService
|
@IAdsTelemetryService private readonly telemetryService: IAdsTelemetryService
|
||||||
) {
|
) {
|
||||||
super(PropertiesAction.ID, PropertiesAction.LABEL, openPropertiesIconClassNames);
|
super(PropertiesAction.ID, PropertiesAction.LABEL, constants.openPropertiesIconClassNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async run(context: ExecutionPlanView): Promise<void> {
|
public override async run(context: ExecutionPlanView): Promise<void> {
|
||||||
@@ -360,7 +367,7 @@ export class ZoomInAction extends Action {
|
|||||||
constructor(private source: ExecutionPlanActionSource,
|
constructor(private source: ExecutionPlanActionSource,
|
||||||
@IAdsTelemetryService private readonly telemetryService: IAdsTelemetryService
|
@IAdsTelemetryService private readonly telemetryService: IAdsTelemetryService
|
||||||
) {
|
) {
|
||||||
super(ZoomInAction.ID, ZoomInAction.LABEL, zoomInIconClassNames);
|
super(ZoomInAction.ID, ZoomInAction.LABEL, constants.zoomInIconClassNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async run(context: ExecutionPlanView): Promise<void> {
|
public override async run(context: ExecutionPlanView): Promise<void> {
|
||||||
@@ -380,7 +387,7 @@ export class ZoomOutAction extends Action {
|
|||||||
constructor(private source: ExecutionPlanActionSource,
|
constructor(private source: ExecutionPlanActionSource,
|
||||||
@IAdsTelemetryService private readonly telemetryService: IAdsTelemetryService
|
@IAdsTelemetryService private readonly telemetryService: IAdsTelemetryService
|
||||||
) {
|
) {
|
||||||
super(ZoomOutAction.ID, ZoomOutAction.LABEL, zoomOutIconClassNames);
|
super(ZoomOutAction.ID, ZoomOutAction.LABEL, constants.zoomOutIconClassNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async run(context: ExecutionPlanView): Promise<void> {
|
public override async run(context: ExecutionPlanView): Promise<void> {
|
||||||
@@ -400,7 +407,7 @@ export class ZoomToFitAction extends Action {
|
|||||||
constructor(private source: ExecutionPlanActionSource,
|
constructor(private source: ExecutionPlanActionSource,
|
||||||
@IAdsTelemetryService private readonly telemetryService: IAdsTelemetryService
|
@IAdsTelemetryService private readonly telemetryService: IAdsTelemetryService
|
||||||
) {
|
) {
|
||||||
super(ZoomToFitAction.ID, ZoomToFitAction.LABEL, zoomToFitIconClassNames);
|
super(ZoomToFitAction.ID, ZoomToFitAction.LABEL, constants.zoomToFitIconClassNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async run(context: ExecutionPlanView): Promise<void> {
|
public override async run(context: ExecutionPlanView): Promise<void> {
|
||||||
@@ -418,7 +425,7 @@ export class SavePlanFile extends Action {
|
|||||||
public static LABEL = localize('executionPlanSavePlanXML', "Save Plan File");
|
public static LABEL = localize('executionPlanSavePlanXML', "Save Plan File");
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(SavePlanFile.ID, SavePlanFile.LABEL, savePlanIconClassNames);
|
super(SavePlanFile.ID, SavePlanFile.LABEL, constants.savePlanIconClassNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async run(context: ExecutionPlanView): Promise<void> {
|
public override async run(context: ExecutionPlanView): Promise<void> {
|
||||||
@@ -453,7 +460,7 @@ export class CustomZoomAction extends Action {
|
|||||||
constructor(private source: ExecutionPlanActionSource,
|
constructor(private source: ExecutionPlanActionSource,
|
||||||
@IAdsTelemetryService private readonly telemetryService: IAdsTelemetryService
|
@IAdsTelemetryService private readonly telemetryService: IAdsTelemetryService
|
||||||
) {
|
) {
|
||||||
super(CustomZoomAction.ID, CustomZoomAction.LABEL, customZoomIconClassNames);
|
super(CustomZoomAction.ID, CustomZoomAction.LABEL, constants.customZoomIconClassNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async run(context: ExecutionPlanView): Promise<void> {
|
public override async run(context: ExecutionPlanView): Promise<void> {
|
||||||
@@ -473,7 +480,7 @@ export class SearchNodeAction extends Action {
|
|||||||
constructor(private source: ExecutionPlanActionSource,
|
constructor(private source: ExecutionPlanActionSource,
|
||||||
@IAdsTelemetryService private readonly telemetryService: IAdsTelemetryService
|
@IAdsTelemetryService private readonly telemetryService: IAdsTelemetryService
|
||||||
) {
|
) {
|
||||||
super(SearchNodeAction.ID, SearchNodeAction.LABEL, searchIconClassNames);
|
super(SearchNodeAction.ID, SearchNodeAction.LABEL, constants.searchIconClassNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async run(context: ExecutionPlanView): Promise<void> {
|
public override async run(context: ExecutionPlanView): Promise<void> {
|
||||||
@@ -491,7 +498,7 @@ export class OpenPlanFile extends Action {
|
|||||||
public static Label = localize('executionPlanOpenGraphFile', "Show Query Plan XML"); //TODO: add a contribution point for providers to set this text
|
public static Label = localize('executionPlanOpenGraphFile', "Show Query Plan XML"); //TODO: add a contribution point for providers to set this text
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(OpenPlanFile.ID, OpenPlanFile.Label, openPlanFileIconClassNames);
|
super(OpenPlanFile.ID, OpenPlanFile.Label, constants.openPlanFileIconClassNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async run(context: ExecutionPlanView): Promise<void> {
|
public override async run(context: ExecutionPlanView): Promise<void> {
|
||||||
@@ -505,17 +512,17 @@ export class ActionBarToggleTooltip extends Action {
|
|||||||
public static WHEN_TOOLTIPS_DISABLED_LABEL = localize('executionPlanDisableTooltip', "Tooltips disabled");
|
public static WHEN_TOOLTIPS_DISABLED_LABEL = localize('executionPlanDisableTooltip', "Tooltips disabled");
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(ActionBarToggleTooltip.ID, ActionBarToggleTooltip.WHEN_TOOLTIPS_ENABLED_LABEL, enableTooltipIconClassName);
|
super(ActionBarToggleTooltip.ID, ActionBarToggleTooltip.WHEN_TOOLTIPS_ENABLED_LABEL, constants.enableTooltipIconClassName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async run(context: ExecutionPlanView): Promise<void> {
|
public override async run(context: ExecutionPlanView): Promise<void> {
|
||||||
const state = context.executionPlanDiagram.toggleTooltip();
|
const state = context.executionPlanDiagram.toggleTooltip();
|
||||||
if (!state) {
|
if (!state) {
|
||||||
this.class = disableTooltipIconClassName;
|
this.class = constants.disableTooltipIconClassName;
|
||||||
this.label = ActionBarToggleTooltip.WHEN_TOOLTIPS_DISABLED_LABEL;
|
this.label = ActionBarToggleTooltip.WHEN_TOOLTIPS_DISABLED_LABEL;
|
||||||
context.contextMenuToggleTooltipAction.label = ContextMenuTooltipToggle.WHEN_TOOLTIPS_DISABLED_LABEL;
|
context.contextMenuToggleTooltipAction.label = ContextMenuTooltipToggle.WHEN_TOOLTIPS_DISABLED_LABEL;
|
||||||
} else {
|
} else {
|
||||||
this.class = enableTooltipIconClassName;
|
this.class = constants.enableTooltipIconClassName;
|
||||||
this.label = ActionBarToggleTooltip.WHEN_TOOLTIPS_ENABLED_LABEL;
|
this.label = ActionBarToggleTooltip.WHEN_TOOLTIPS_ENABLED_LABEL;
|
||||||
context.contextMenuToggleTooltipAction.label = ContextMenuTooltipToggle.WHEN_TOOLTIPS_ENABLED_LABEL;
|
context.contextMenuToggleTooltipAction.label = ContextMenuTooltipToggle.WHEN_TOOLTIPS_ENABLED_LABEL;
|
||||||
}
|
}
|
||||||
@@ -528,18 +535,18 @@ export class ContextMenuTooltipToggle extends Action {
|
|||||||
public static WHEN_TOOLTIPS_DISABLED_LABEL = localize('executionPlanContextMenuEnableTooltip', "Enable Tooltips");
|
public static WHEN_TOOLTIPS_DISABLED_LABEL = localize('executionPlanContextMenuEnableTooltip', "Enable Tooltips");
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(ContextMenuTooltipToggle.ID, ContextMenuTooltipToggle.WHEN_TOOLTIPS_ENABLED_LABEL, enableTooltipIconClassName);
|
super(ContextMenuTooltipToggle.ID, ContextMenuTooltipToggle.WHEN_TOOLTIPS_ENABLED_LABEL, constants.enableTooltipIconClassName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async run(context: ExecutionPlanView): Promise<void> {
|
public override async run(context: ExecutionPlanView): Promise<void> {
|
||||||
const state = context.executionPlanDiagram.toggleTooltip();
|
const state = context.executionPlanDiagram.toggleTooltip();
|
||||||
if (!state) {
|
if (!state) {
|
||||||
this.label = ContextMenuTooltipToggle.WHEN_TOOLTIPS_DISABLED_LABEL;
|
this.label = ContextMenuTooltipToggle.WHEN_TOOLTIPS_DISABLED_LABEL;
|
||||||
context.actionBarToggleTopTip.class = disableTooltipIconClassName;
|
context.actionBarToggleTopTip.class = constants.disableTooltipIconClassName;
|
||||||
context.actionBarToggleTopTip.label = ActionBarToggleTooltip.WHEN_TOOLTIPS_DISABLED_LABEL;
|
context.actionBarToggleTopTip.label = ActionBarToggleTooltip.WHEN_TOOLTIPS_DISABLED_LABEL;
|
||||||
} else {
|
} else {
|
||||||
this.label = ContextMenuTooltipToggle.WHEN_TOOLTIPS_ENABLED_LABEL;
|
this.label = ContextMenuTooltipToggle.WHEN_TOOLTIPS_ENABLED_LABEL;
|
||||||
context.actionBarToggleTopTip.class = enableTooltipIconClassName;
|
context.actionBarToggleTopTip.class = constants.enableTooltipIconClassName;
|
||||||
context.actionBarToggleTopTip.label = ActionBarToggleTooltip.WHEN_TOOLTIPS_ENABLED_LABEL;
|
context.actionBarToggleTopTip.label = ActionBarToggleTooltip.WHEN_TOOLTIPS_ENABLED_LABEL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -552,7 +559,7 @@ export class CompareExecutionPlanAction extends Action {
|
|||||||
constructor(private source: ExecutionPlanActionSource,
|
constructor(private source: ExecutionPlanActionSource,
|
||||||
@IAdsTelemetryService private readonly telemetryService: IAdsTelemetryService
|
@IAdsTelemetryService private readonly telemetryService: IAdsTelemetryService
|
||||||
) {
|
) {
|
||||||
super(CompareExecutionPlanAction.COMPARE_PLAN, CompareExecutionPlanAction.COMPARE_PLAN, executionPlanCompareIconClassName);
|
super(CompareExecutionPlanAction.COMPARE_PLAN, CompareExecutionPlanAction.COMPARE_PLAN, constants.executionPlanCompareIconClassName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async run(context: ExecutionPlanView): Promise<void> {
|
public override async run(context: ExecutionPlanView): Promise<void> {
|
||||||
@@ -572,10 +579,30 @@ export class TopOperationsAction extends Action {
|
|||||||
|
|
||||||
constructor
|
constructor
|
||||||
() {
|
() {
|
||||||
super(TopOperationsAction.ID, TopOperationsAction.LABEL, executionPlanTopOperations);
|
super(TopOperationsAction.ID, TopOperationsAction.LABEL, constants.executionPlanTopOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async run(context: ExecutionPlanView): Promise<void> {
|
public override async run(context: ExecutionPlanView): Promise<void> {
|
||||||
context.openTopOperations();
|
context.openTopOperations();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class HighlightExpensiveOperationAction extends Action {
|
||||||
|
public static ID = 'ep.highlightExpensiveOperation';
|
||||||
|
public static LABEL = localize('executionPlanHighlightExpensiveOperationAction', 'Highlight Expensive Operation');
|
||||||
|
|
||||||
|
constructor(private source: ExecutionPlanActionSource,
|
||||||
|
@IAdsTelemetryService private readonly telemetryService: IAdsTelemetryService
|
||||||
|
) {
|
||||||
|
super(HighlightExpensiveOperationAction.ID, HighlightExpensiveOperationAction.LABEL, constants.highlightExpensiveOperationClassNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async run(context: ExecutionPlanView): Promise<void> {
|
||||||
|
this.telemetryService
|
||||||
|
.createActionEvent(TelemetryKeys.TelemetryView.ExecutionPlan, TelemetryKeys.TelemetryAction.HighlightExpensiveOperation)
|
||||||
|
.withAdditionalProperties({ source: this.source })
|
||||||
|
.send();
|
||||||
|
|
||||||
|
context.widgetController.toggleWidget(context._instantiationService.createInstance(HighlightExpensiveOperationWidget, context.widgetController, context.executionPlanDiagram));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,13 +3,17 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
export abstract class ExecutionPlanWidgetBase {
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
|
|
||||||
|
export abstract class ExecutionPlanWidgetBase extends Disposable {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param container HTML Element that contains the UI for the plan action view.
|
* @param container HTML Element that contains the UI for the plan action view.
|
||||||
* @param identifier Uniquely identify the view to be added or removed. Note: Only 1 view with the same id can be added to the controller
|
* @param identifier Uniquely identify the view to be added or removed. Note: Only 1 view with the same id can be added to the controller
|
||||||
*/
|
*/
|
||||||
constructor(public container: HTMLElement, public identifier: string) {
|
constructor(public container: HTMLElement, public identifier: string) {
|
||||||
|
super();
|
||||||
|
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this.identifier = identifier;
|
this.identifier = identifier;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export class ExecutionPlanWidgetController {
|
|||||||
public removeWidget(widget: ExecutionPlanWidgetBase) {
|
public removeWidget(widget: ExecutionPlanWidgetBase) {
|
||||||
if (widget.identifier) {
|
if (widget.identifier) {
|
||||||
if (this._executionPlanWidgetMap.has(widget.identifier)) {
|
if (this._executionPlanWidgetMap.has(widget.identifier)) {
|
||||||
|
widget.dispose();
|
||||||
this._parentContainer.removeChild(this._executionPlanWidgetMap.get(widget.identifier).container);
|
this._parentContainer.removeChild(this._executionPlanWidgetMap.get(widget.identifier).container);
|
||||||
this._executionPlanWidgetMap.delete(widget.identifier);
|
this._executionPlanWidgetMap.delete(widget.identifier);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<g id="f4eb8f61-ee01-4fe9-a111-7a076ae7c7be" data-name="Icons">
|
||||||
|
<path d="M5,5H0V0H5Z"/>
|
||||||
|
<path d="M16,5H11V0h5ZM12,4h3V1H12Z"/>
|
||||||
|
<path d="M7.975.525l.75.75-.684.692H11V3.033H8.041l.684.692-.75.75L6,2.5Z"/>
|
||||||
|
<path d="M16,14H11V9h5Zm-4-1h3V10H12Z"/>
|
||||||
|
<rect x="13" y="5" width="1" height="5"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 407 B |
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#FFFFFF;}
|
||||||
|
</style>
|
||||||
|
<g id="f4eb8f61-ee01-4fe9-a111-7a076ae7c7be">
|
||||||
|
<path class="st0" d="M5,5H0V0h5V5z"/>
|
||||||
|
<path class="st0" d="M16,5h-5V0h5V5z M12,4h3V1h-3V4z"/>
|
||||||
|
<path class="st0" d="M8,0.5l0.8,0.8L8,2h3V3H8l0.7,0.7L8,4.5l-2-2L8,0.5z"/>
|
||||||
|
<path class="st0" d="M16,14h-5V9h5V14z M12,13h3v-3h-3V13z"/>
|
||||||
|
<rect x="13" y="5" class="st0" width="1" height="4"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 746 B |
@@ -92,11 +92,42 @@ However we always want it to be the width of the container it is resizing.
|
|||||||
width: 240px;
|
width: 240px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Find expensive operation view */
|
||||||
|
.eps-container .execution-plan .plan .plan-action-container .find-expensive-operation-widget {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 5px;
|
||||||
|
height: auto;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eps-container .execution-plan .plan .plan-action-container .find-expensive-operation-widget .select-container {
|
||||||
|
margin-left: 5px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eps-container .execution-plan .plan .plan-action-container .find-expensive-operation-widget .select-container expensive-operation-name-select-box-label {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eps-container .execution-plan .plan .plan-action-container .find-expensive-operation-widget .select-container>select {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eps-container .execution-plan .plan .plan-action-container .find-expensive-operation-widget .monaco-button.monaco-text-button {
|
||||||
|
width: auto;
|
||||||
|
padding-left: 15px;
|
||||||
|
padding-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
/* execution plan header that contains the relative query cost, query statement and recommendations */
|
/* execution plan header that contains the relative query cost, query statement and recommendations */
|
||||||
.eps-container .execution-plan .plan .header,
|
.eps-container .execution-plan .plan .header,
|
||||||
.top-operations-tab .top-operations-container .query-row {
|
.top-operations-tab .top-operations-container .query-row {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
font-weight: bolder;
|
font-weight: bolder;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* execution plan header that contains the relative query cost, query statement and recommendations */
|
/* execution plan header that contains the relative query cost, query statement and recommendations */
|
||||||
@@ -478,6 +509,21 @@ However we always want it to be the width of the container it is resizing.
|
|||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.eps-container .ep-highlight-expensive-operation-icon {
|
||||||
|
background-image: url(../images/actionIcons/highlightExpensiveOperation.svg);
|
||||||
|
background-size: 16px 16px;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vs-dark .eps-container .ep-highlight-expensive-operation-icon,
|
||||||
|
.hc-black .eps-container .ep-highlight-expensive-operation-icon {
|
||||||
|
background-image: url(../images/actionIcons/highlightExpensiveOperationDark.svg);
|
||||||
|
background-size: 16px 16px;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
.eps-container .ep-enable-tooltip-icon {
|
.eps-container .ep-enable-tooltip-icon {
|
||||||
background-image: url(../images/actionIcons/enableTooltip.svg);
|
background-image: url(../images/actionIcons/enableTooltip.svg);
|
||||||
background-size: 16px 16px;
|
background-size: 16px 16px;
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export class CustomZoomWidget extends ExecutionPlanWidgetBase {
|
|||||||
ariaLabel: zoomValueLabel,
|
ariaLabel: zoomValueLabel,
|
||||||
flexibleWidth: false
|
flexibleWidth: false
|
||||||
});
|
});
|
||||||
attachInputBoxStyler(this.customZoomInputBox, this.themeService);
|
this._register(attachInputBoxStyler(this.customZoomInputBox, this.themeService));
|
||||||
|
|
||||||
const currentZoom = this.executionPlanDiagram.getZoomLevel();
|
const currentZoom = this.executionPlanDiagram.getZoomLevel();
|
||||||
|
|
||||||
@@ -57,14 +57,14 @@ export class CustomZoomWidget extends ExecutionPlanWidgetBase {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const applyButton = new Button(this.container, {
|
const applyButton = new Button(this.container, {
|
||||||
title: localize('customZoomApplyButtonTitle', "Apply Zoom (Enter)")
|
title: localize('customZoomApplyButtonTitle', "Apply Zoom")
|
||||||
});
|
});
|
||||||
applyButton.setWidth('60px');
|
applyButton.setWidth('60px');
|
||||||
applyButton.label = localize('customZoomApplyButton', "Apply");
|
applyButton.label = localize('customZoomApplyButton', "Apply");
|
||||||
|
|
||||||
applyButton.onDidClick(async e => {
|
this._register(applyButton.onDidClick(async e => {
|
||||||
await new CustomZoomAction().run(self);
|
await new CustomZoomAction().run(self);
|
||||||
});
|
}));
|
||||||
|
|
||||||
// Adding action bar
|
// Adding action bar
|
||||||
this._actionBar = new ActionBar(this.container);
|
this._actionBar = new ActionBar(this.container);
|
||||||
@@ -80,7 +80,7 @@ export class CustomZoomWidget extends ExecutionPlanWidgetBase {
|
|||||||
|
|
||||||
export class CustomZoomAction extends Action {
|
export class CustomZoomAction extends Action {
|
||||||
public static ID = 'qp.customZoomAction';
|
public static ID = 'qp.customZoomAction';
|
||||||
public static LABEL = localize('zoomAction', "Zoom (Enter)");
|
public static LABEL = localize('zoomAction', "Zoom");
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(CustomZoomAction.ID, CustomZoomAction.LABEL, zoomIconClassNames);
|
super(CustomZoomAction.ID, CustomZoomAction.LABEL, zoomIconClassNames);
|
||||||
@@ -101,7 +101,7 @@ export class CustomZoomAction extends Action {
|
|||||||
|
|
||||||
export class CancelZoom extends Action {
|
export class CancelZoom extends Action {
|
||||||
public static ID = 'qp.cancelCustomZoomAction';
|
public static ID = 'qp.cancelCustomZoomAction';
|
||||||
public static LABEL = localize('cancelCustomZoomAction', "Close (Escape)");
|
public static LABEL = localize('cancelCustomZoomAction', "Close");
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(CancelZoom.ID, CancelZoom.LABEL, Codicon.chromeClose.classNames);
|
super(CancelZoom.ID, CancelZoom.LABEL, Codicon.chromeClose.classNames);
|
||||||
|
|||||||
@@ -0,0 +1,337 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
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';
|
||||||
|
import { localize } from 'vs/nls';
|
||||||
|
import * as errors from 'vs/base/common/errors';
|
||||||
|
import { Codicon } from 'vs/base/common/codicons';
|
||||||
|
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||||
|
import { attachSelectBoxStyler } from 'sql/platform/theme/common/styler';
|
||||||
|
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 { AzDataGraphCell, AzdataGraphView, ExpensiveMetricType } from 'sql/workbench/contrib/executionPlan/browser/azdataGraphView';
|
||||||
|
import { ExecutionPlanWidgetController } from 'sql/workbench/contrib/executionPlan/browser/executionPlanWidgetController';
|
||||||
|
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||||
|
import { Button } from 'sql/base/browser/ui/button/button';
|
||||||
|
import { searchIconClassNames } from 'sql/workbench/contrib/executionPlan/browser/constants';
|
||||||
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
|
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||||
|
|
||||||
|
const OFF_STRING = localize('executionPlanOff', 'Off');
|
||||||
|
const ACTUAL_ELAPSED_TIME_STRING = localize('executionPlanActualElapsedTime', 'Actual Elapsed Time');
|
||||||
|
const ACTUAL_ELAPSED_CPU_TIME_STRING = localize('executionPlanActualElapsedCpuTime', 'Actual Elapsed CPU Time');
|
||||||
|
const COST_STRING = localize('executionPlanCost', 'Cost');
|
||||||
|
const SUBTREE_COST_STRING = localize('executionPlanSubtreeCost', 'Subtree Cost');
|
||||||
|
const ACTUAL_NUMBER_OF_ROWS_FOR_ALL_EXECUTIONS_STRING = localize('actualNumberOfRowsForAllExecutionsAction', 'Actual Number of Rows For All Executions');
|
||||||
|
const NUMBER_OF_ROWS_READ_STRING = localize('executionPlanNumberOfRowsRead', 'Number of Rows Read');
|
||||||
|
|
||||||
|
export class HighlightExpensiveOperationWidget extends ExecutionPlanWidgetBase {
|
||||||
|
private _actionBar: ActionBar;
|
||||||
|
|
||||||
|
public expenseMetricSelectBox: SelectBox;
|
||||||
|
private _expenseMetricSelectBoxContainer: HTMLElement;
|
||||||
|
private _selectedExpensiveOperationType: ExpensiveMetricType = ExpensiveMetricType.Cost;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public readonly widgetController: ExecutionPlanWidgetController,
|
||||||
|
public readonly executionPlanDiagram: AzdataGraphView,
|
||||||
|
@IContextViewService public readonly contextViewService: IContextViewService,
|
||||||
|
@IThemeService public readonly themeService: IThemeService,
|
||||||
|
@INotificationService public readonly notificationService: INotificationService,
|
||||||
|
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||||
|
@IStorageService private readonly _storageService: IStorageService
|
||||||
|
) {
|
||||||
|
super(DOM.$('.find-expensive-operation-widget'), 'findExpensiveOperation');
|
||||||
|
|
||||||
|
this.renderAndStyleWidget();
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDefaultExpensiveOperationMetric(): ExpensiveMetricType {
|
||||||
|
const defaultMetricConfiguration = this._configurationService.getValue<string>('mssql.executionPlan.expensiveOperationMetric');
|
||||||
|
|
||||||
|
switch (defaultMetricConfiguration) {
|
||||||
|
case 'actualElapsedTime':
|
||||||
|
return ExpensiveMetricType.ActualElapsedTime;
|
||||||
|
case 'actualElapsedCpuTime':
|
||||||
|
return ExpensiveMetricType.ActualElapsedCpuTime;
|
||||||
|
case 'cost':
|
||||||
|
return ExpensiveMetricType.Cost;
|
||||||
|
case 'subtreeCost':
|
||||||
|
return ExpensiveMetricType.SubtreeCost;
|
||||||
|
case 'actualNumberOfRowsForAllExecutions':
|
||||||
|
return ExpensiveMetricType.ActualNumberOfRowsForAllExecutions;
|
||||||
|
case 'numberOfRowsRead':
|
||||||
|
return ExpensiveMetricType.NumberOfRowsRead;
|
||||||
|
default:
|
||||||
|
return ExpensiveMetricType.Off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderAndStyleWidget(): void {
|
||||||
|
// Expensive Operation Dropdown
|
||||||
|
this._expenseMetricSelectBoxContainer = DOM.$('expensive-operation-name-select-box .dropdown-container');
|
||||||
|
const operationLabel = DOM.$('expensive-operation-name-select-box-label');
|
||||||
|
operationLabel.innerText = localize('expensiveOperationLabel', 'Metric:');
|
||||||
|
|
||||||
|
this._expenseMetricSelectBoxContainer.appendChild(operationLabel);
|
||||||
|
this.container.appendChild(this._expenseMetricSelectBoxContainer);
|
||||||
|
|
||||||
|
const selectBoxOptions = this.getSelectBoxOptionsFromExecutionPlanDiagram();
|
||||||
|
this.expenseMetricSelectBox = new SelectBox(selectBoxOptions, COST_STRING, this.contextViewService, this._expenseMetricSelectBoxContainer);
|
||||||
|
|
||||||
|
this.expenseMetricSelectBox.render(this._expenseMetricSelectBoxContainer);
|
||||||
|
this._register(attachSelectBoxStyler(this.expenseMetricSelectBox, this.themeService));
|
||||||
|
|
||||||
|
this._expenseMetricSelectBoxContainer.style.width = '200px';
|
||||||
|
this._expenseMetricSelectBoxContainer.style.marginRight = '5px';
|
||||||
|
|
||||||
|
this._register(this.expenseMetricSelectBox.onDidSelect(e => {
|
||||||
|
switch (e.selected) {
|
||||||
|
case ACTUAL_ELAPSED_TIME_STRING:
|
||||||
|
this._selectedExpensiveOperationType = ExpensiveMetricType.ActualElapsedTime;
|
||||||
|
break;
|
||||||
|
case ACTUAL_ELAPSED_CPU_TIME_STRING:
|
||||||
|
this._selectedExpensiveOperationType = ExpensiveMetricType.ActualElapsedCpuTime;
|
||||||
|
break;
|
||||||
|
case COST_STRING:
|
||||||
|
this._selectedExpensiveOperationType = ExpensiveMetricType.Cost;
|
||||||
|
break;
|
||||||
|
case SUBTREE_COST_STRING:
|
||||||
|
this._selectedExpensiveOperationType = ExpensiveMetricType.SubtreeCost;
|
||||||
|
break;
|
||||||
|
case ACTUAL_NUMBER_OF_ROWS_FOR_ALL_EXECUTIONS_STRING:
|
||||||
|
this._selectedExpensiveOperationType = ExpensiveMetricType.ActualNumberOfRowsForAllExecutions;
|
||||||
|
break;
|
||||||
|
case NUMBER_OF_ROWS_READ_STRING:
|
||||||
|
this._selectedExpensiveOperationType = ExpensiveMetricType.NumberOfRowsRead;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this._selectedExpensiveOperationType = ExpensiveMetricType.Off;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Apply Button
|
||||||
|
const highlightExpensiveOperationAction = new HighlightExpensiveOperationAction();
|
||||||
|
this._register(highlightExpensiveOperationAction);
|
||||||
|
|
||||||
|
const clearHighlightExpensiveOperationAction = new TurnOffExpensiveHighlightingOperationAction();
|
||||||
|
this._register(clearHighlightExpensiveOperationAction);
|
||||||
|
|
||||||
|
const cancelHighlightExpensiveOperationAction = new CancelHIghlightExpensiveOperationAction();
|
||||||
|
this._register(cancelHighlightExpensiveOperationAction);
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
const applyButton = new Button(this.container, {
|
||||||
|
title: localize('highlightExpensiveOperationButtonTitle', 'Highlight Expensive Operation')
|
||||||
|
});
|
||||||
|
applyButton.label = localize('highlightExpensiveOperationApplyButton', 'Apply');
|
||||||
|
|
||||||
|
this._register(applyButton.onDidClick(async e => {
|
||||||
|
if (this._selectedExpensiveOperationType === ExpensiveMetricType.Off) {
|
||||||
|
await clearHighlightExpensiveOperationAction.run(self);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await highlightExpensiveOperationAction.run(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showStoreDefaultMetricPrompt();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Adds Action bar
|
||||||
|
this._actionBar = new ActionBar(this.container);
|
||||||
|
this._actionBar.context = this;
|
||||||
|
this._actionBar.pushAction(cancelHighlightExpensiveOperationAction, { label: false, icon: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSelectBoxOptionsFromExecutionPlanDiagram(): string[] {
|
||||||
|
const selectBoxOptions: string[] = [];
|
||||||
|
|
||||||
|
for (let expenseMetricType of this.executionPlanDiagram.expensiveMetricTypes) {
|
||||||
|
switch (expenseMetricType) {
|
||||||
|
case ExpensiveMetricType.Off:
|
||||||
|
selectBoxOptions.push(OFF_STRING);
|
||||||
|
break;
|
||||||
|
case ExpensiveMetricType.ActualElapsedTime:
|
||||||
|
selectBoxOptions.push(ACTUAL_ELAPSED_TIME_STRING);
|
||||||
|
break;
|
||||||
|
case ExpensiveMetricType.ActualElapsedCpuTime:
|
||||||
|
selectBoxOptions.push(ACTUAL_ELAPSED_CPU_TIME_STRING);
|
||||||
|
break;
|
||||||
|
case ExpensiveMetricType.Cost:
|
||||||
|
selectBoxOptions.push(COST_STRING);
|
||||||
|
break;
|
||||||
|
case ExpensiveMetricType.SubtreeCost:
|
||||||
|
selectBoxOptions.push(SUBTREE_COST_STRING);
|
||||||
|
break;
|
||||||
|
case ExpensiveMetricType.ActualNumberOfRowsForAllExecutions:
|
||||||
|
selectBoxOptions.push(ACTUAL_NUMBER_OF_ROWS_FOR_ALL_EXECUTIONS_STRING);
|
||||||
|
break;
|
||||||
|
case ExpensiveMetricType.NumberOfRowsRead:
|
||||||
|
selectBoxOptions.push(NUMBER_OF_ROWS_READ_STRING);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectBoxOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public showStoreDefaultMetricPrompt(): void {
|
||||||
|
const currentDefaultExpensiveOperationMetric = this.getDefaultExpensiveOperationMetric();
|
||||||
|
if (this._selectedExpensiveOperationType === currentDefaultExpensiveOperationMetric || !this._storageService.getBoolean('qp.expensiveOperationMetric.showChangeDefaultExpensiveMetricPrompt', StorageScope.GLOBAL, true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const infoMessage = localize('queryExecutionPlan.showUpdateDefaultMetricInfo', 'Set chosen metric as the default for query execution plans?');
|
||||||
|
const promptChoices = [
|
||||||
|
{
|
||||||
|
label: localize('qp.expensiveOperationMetric.yes', 'Yes'),
|
||||||
|
run: () => this._configurationService.updateValue('mssql.executionPlan.expensiveOperationMetric', this._selectedExpensiveOperationType.toString()).catch(e => errors.onUnexpectedError(e))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: localize('qp.expensiveOperationMetric.no', 'No'),
|
||||||
|
run: () => { }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: localize('qp.expensiveOperationMetric.dontShowAgain', "Don't Show Again"),
|
||||||
|
run: () => this._storageService.store('qp.expensiveOperationMetric.showChangeDefaultExpensiveMetricPrompt', false, StorageScope.GLOBAL, StorageTarget.USER)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
this.notificationService.prompt(Severity.Info, infoMessage, promptChoices, { sticky: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
public focus() {
|
||||||
|
this.expenseMetricSelectBox.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getExpensiveOperationDelegate(): (cell: AzDataGraphCell) => number | undefined {
|
||||||
|
const getElapsedTimeInMs = (cell: AzDataGraphCell): number | undefined => cell.elapsedTimeInMs;
|
||||||
|
|
||||||
|
const getElapsedCpuTimeInMs = (cell: AzDataGraphCell): number | undefined => {
|
||||||
|
const elapsedCpuMetric = cell.costMetrics.find(m => m.name === 'ElapsedCpuTime');
|
||||||
|
|
||||||
|
if (elapsedCpuMetric === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Number(elapsedCpuMetric.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCost = (cell: AzDataGraphCell): number | undefined => cell.cost;
|
||||||
|
const getSubtreeCost = (cell: AzDataGraphCell): number | undefined => cell.subTreeCost;
|
||||||
|
|
||||||
|
const getRowsForAllExecutions = (cell: AzDataGraphCell): number | undefined => {
|
||||||
|
const actualRowsMetric = cell.costMetrics.find(m => m.name === 'ActualRows');
|
||||||
|
const estimateRowsForAllExecutionsMetric = cell.costMetrics.find(m => m.name === 'EstimateRowsAllExecs');
|
||||||
|
|
||||||
|
if (actualRowsMetric === undefined && estimateRowsForAllExecutionsMetric === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = Number(actualRowsMetric?.value);
|
||||||
|
if (!result) {
|
||||||
|
result = Number(estimateRowsForAllExecutionsMetric?.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(result)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNumberOfRowsRead = (cell: AzDataGraphCell): number | undefined => {
|
||||||
|
const actualRowsReadMetric = cell.costMetrics.find(m => m.name === 'ActualRowsRead');
|
||||||
|
const estimatedRowsReadMetric = cell.costMetrics.find(m => m.name === 'EstimatedRowsRead');
|
||||||
|
|
||||||
|
if (actualRowsReadMetric === undefined && estimatedRowsReadMetric === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = Number(actualRowsReadMetric?.value);
|
||||||
|
if (!result) {
|
||||||
|
result = Number(estimatedRowsReadMetric?.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(result)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
let expensiveOperationDelegate = getCost;
|
||||||
|
switch (this._selectedExpensiveOperationType) {
|
||||||
|
case ExpensiveMetricType.ActualElapsedTime:
|
||||||
|
expensiveOperationDelegate = getElapsedTimeInMs;
|
||||||
|
break;
|
||||||
|
case ExpensiveMetricType.ActualElapsedCpuTime:
|
||||||
|
expensiveOperationDelegate = getElapsedCpuTimeInMs;
|
||||||
|
break;
|
||||||
|
case ExpensiveMetricType.SubtreeCost:
|
||||||
|
expensiveOperationDelegate = getSubtreeCost;
|
||||||
|
break;
|
||||||
|
case ExpensiveMetricType.ActualNumberOfRowsForAllExecutions:
|
||||||
|
expensiveOperationDelegate = getRowsForAllExecutions;
|
||||||
|
break;
|
||||||
|
case ExpensiveMetricType.NumberOfRowsRead:
|
||||||
|
expensiveOperationDelegate = getNumberOfRowsRead;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return expensiveOperationDelegate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HighlightExpensiveOperationAction extends Action {
|
||||||
|
public static ID = 'qp.highlightExpensiveOperationAction';
|
||||||
|
public static LABEL = localize('highlightExpensiveOperationAction', 'Apply');
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(HighlightExpensiveOperationAction.ID, HighlightExpensiveOperationAction.LABEL, searchIconClassNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async run(context: HighlightExpensiveOperationWidget): Promise<void> {
|
||||||
|
const expensiveOperationDelegate: (cell: AzDataGraphCell) => number | undefined = context.getExpensiveOperationDelegate();
|
||||||
|
|
||||||
|
context.executionPlanDiagram.clearExpensiveOperatorHighlighting();
|
||||||
|
let result = context.executionPlanDiagram.highlightExpensiveOperator(expensiveOperationDelegate);
|
||||||
|
if (!result) {
|
||||||
|
const metric = context.expenseMetricSelectBox.value;
|
||||||
|
context.notificationService.warn(localize('invalidPropertyExecutionPlanMetric', 'No nodes found with the {0} metric.', metric));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TurnOffExpensiveHighlightingOperationAction extends Action {
|
||||||
|
public static ID = 'qp.turnOffExpensiveHighlightingOperationAction';
|
||||||
|
public static LABEL = localize('turnOffExpensiveHighlightingOperationAction', 'Off');
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(TurnOffExpensiveHighlightingOperationAction.ID, TurnOffExpensiveHighlightingOperationAction.LABEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async run(context: HighlightExpensiveOperationWidget): Promise<void> {
|
||||||
|
context.executionPlanDiagram.clearExpensiveOperatorHighlighting();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CancelHIghlightExpensiveOperationAction extends Action {
|
||||||
|
public static ID = 'qp.cancelExpensiveOperationAction';
|
||||||
|
public static LABEL = localize('cancelExpensiveOperationAction', 'Close');
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(CancelHIghlightExpensiveOperationAction.ID, CancelHIghlightExpensiveOperationAction.LABEL, Codicon.chromeClose.classNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async run(context: HighlightExpensiveOperationWidget): Promise<void> {
|
||||||
|
context.widgetController.removeWidget(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -55,12 +55,12 @@ export class NodeSearchWidget extends ExecutionPlanWidgetBase {
|
|||||||
this.container.appendChild(this._propertyNameSelectBoxContainer);
|
this.container.appendChild(this._propertyNameSelectBoxContainer);
|
||||||
const propDropdownOptions = this._executionPlanDiagram.getUniqueElementProperties();
|
const propDropdownOptions = this._executionPlanDiagram.getUniqueElementProperties();
|
||||||
this._propertyNameSelectBox = new SelectBox(propDropdownOptions, propDropdownOptions[0], this.contextViewService, this._propertyNameSelectBoxContainer);
|
this._propertyNameSelectBox = new SelectBox(propDropdownOptions, propDropdownOptions[0], this.contextViewService, this._propertyNameSelectBoxContainer);
|
||||||
attachSelectBoxStyler(this._propertyNameSelectBox, this.themeService);
|
this._register(attachSelectBoxStyler(this._propertyNameSelectBox, this.themeService));
|
||||||
this._propertyNameSelectBoxContainer.style.width = '150px';
|
this._propertyNameSelectBoxContainer.style.width = '150px';
|
||||||
this._propertyNameSelectBox.render(this._propertyNameSelectBoxContainer);
|
this._propertyNameSelectBox.render(this._propertyNameSelectBoxContainer);
|
||||||
this._propertyNameSelectBox.onDidSelect(e => {
|
this._register(this._propertyNameSelectBox.onDidSelect(e => {
|
||||||
this._usePreviousSearchResult = false;
|
this._usePreviousSearchResult = false;
|
||||||
});
|
}));
|
||||||
|
|
||||||
// search type dropdown
|
// search type dropdown
|
||||||
this._searchTypeSelectBoxContainer = DOM.$('.search-widget-search-type-select-box .dropdown-container');
|
this._searchTypeSelectBoxContainer = DOM.$('.search-widget-search-type-select-box .dropdown-container');
|
||||||
@@ -75,9 +75,9 @@ export class NodeSearchWidget extends ExecutionPlanWidgetBase {
|
|||||||
LESSER_AND_GREATER_DISPLAY_STRING
|
LESSER_AND_GREATER_DISPLAY_STRING
|
||||||
], EQUALS_DISPLAY_STRING, this.contextViewService, this._searchTypeSelectBoxContainer);
|
], EQUALS_DISPLAY_STRING, this.contextViewService, this._searchTypeSelectBoxContainer);
|
||||||
this._searchTypeSelectBox.render(this._searchTypeSelectBoxContainer);
|
this._searchTypeSelectBox.render(this._searchTypeSelectBoxContainer);
|
||||||
attachSelectBoxStyler(this._searchTypeSelectBox, this.themeService);
|
this._register(attachSelectBoxStyler(this._searchTypeSelectBox, this.themeService));
|
||||||
this._searchTypeSelectBoxContainer.style.width = '100px';
|
this._searchTypeSelectBoxContainer.style.width = '100px';
|
||||||
this._searchTypeSelectBox.onDidSelect(e => {
|
this._register(this._searchTypeSelectBox.onDidSelect(e => {
|
||||||
this._usePreviousSearchResult = false;
|
this._usePreviousSearchResult = false;
|
||||||
switch (e.selected) {
|
switch (e.selected) {
|
||||||
case EQUALS_DISPLAY_STRING:
|
case EQUALS_DISPLAY_STRING:
|
||||||
@@ -101,35 +101,44 @@ export class NodeSearchWidget extends ExecutionPlanWidgetBase {
|
|||||||
case LESSER_AND_GREATER_DISPLAY_STRING:
|
case LESSER_AND_GREATER_DISPLAY_STRING:
|
||||||
this._selectedSearchType = SearchType.LesserAndGreaterThan;
|
this._selectedSearchType = SearchType.LesserAndGreaterThan;
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
|
|
||||||
// search text input box
|
// search text input box
|
||||||
this._searchTextInputBox = new InputBox(this.container, this.contextViewService, {});
|
this._searchTextInputBox = new InputBox(this.container, this.contextViewService, {});
|
||||||
attachInputBoxStyler(this._searchTextInputBox, this.themeService);
|
this._register(attachInputBoxStyler(this._searchTextInputBox, this.themeService));
|
||||||
this._searchTextInputBox.element.style.marginLeft = '5px';
|
this._searchTextInputBox.element.style.marginLeft = '5px';
|
||||||
this._searchTextInputBox.onDidChange(e => {
|
this._register(this._searchTextInputBox.onDidChange(e => {
|
||||||
this._usePreviousSearchResult = false;
|
this._usePreviousSearchResult = false;
|
||||||
});
|
}));
|
||||||
|
|
||||||
|
|
||||||
// setting up key board shortcuts
|
// setting up key board shortcuts
|
||||||
|
const goToPreviousMatchAction = new GoToPreviousMatchAction();
|
||||||
|
this._register(goToPreviousMatchAction);
|
||||||
|
|
||||||
|
const goToNextMatchAction = new GoToNextMatchAction();
|
||||||
|
this._register(goToNextMatchAction);
|
||||||
|
|
||||||
|
const cancelSearchAction = new CancelSearch();
|
||||||
|
this._register(cancelSearchAction);
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
this._searchTextInputBox.element.onkeydown = async e => {
|
this._searchTextInputBox.element.onkeydown = async e => {
|
||||||
if (e.key === 'Enter' && e.shiftKey) {
|
if (e.key === 'Enter' && e.shiftKey) {
|
||||||
await new GoToPreviousMatchAction().run(self);
|
await goToPreviousMatchAction.run(self);
|
||||||
} else if (e.key === 'Enter') {
|
} else if (e.key === 'Enter') {
|
||||||
await new GoToNextMatchAction().run(self);
|
await goToNextMatchAction.run(self);
|
||||||
} else if (e.key === 'Escape') {
|
} else if (e.key === 'Escape') {
|
||||||
await new CancelSearch().run(self);
|
await cancelSearchAction.run(self);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Adding action bar
|
// Adding action bar
|
||||||
this._actionBar = new ActionBar(this.container);
|
this._actionBar = new ActionBar(this.container);
|
||||||
this._actionBar.context = this;
|
this._actionBar.context = this;
|
||||||
this._actionBar.pushAction(new GoToPreviousMatchAction(), { label: false, icon: true });
|
this._actionBar.pushAction(goToPreviousMatchAction, { label: false, icon: true });
|
||||||
this._actionBar.pushAction(new GoToNextMatchAction(), { label: false, icon: true });
|
this._actionBar.pushAction(goToNextMatchAction, { label: false, icon: true });
|
||||||
this._actionBar.pushAction(new CancelSearch(), { label: false, icon: true });
|
this._actionBar.pushAction(cancelSearchAction, { label: false, icon: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initial focus is set to the search text input box
|
// Initial focus is set to the search text input box
|
||||||
@@ -175,7 +184,7 @@ export class NodeSearchWidget extends ExecutionPlanWidgetBase {
|
|||||||
|
|
||||||
export class GoToNextMatchAction extends Action {
|
export class GoToNextMatchAction extends Action {
|
||||||
public static ID = 'qp.NextSearchAction';
|
public static ID = 'qp.NextSearchAction';
|
||||||
public static LABEL = localize('nextSearchItemAction', "Next Match (Enter)");
|
public static LABEL = localize('nextSearchItemAction', "Next Match");
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(GoToNextMatchAction.ID, GoToNextMatchAction.LABEL, Codicon.arrowDown.classNames);
|
super(GoToNextMatchAction.ID, GoToNextMatchAction.LABEL, Codicon.arrowDown.classNames);
|
||||||
@@ -188,7 +197,7 @@ export class GoToNextMatchAction extends Action {
|
|||||||
|
|
||||||
export class GoToPreviousMatchAction extends Action {
|
export class GoToPreviousMatchAction extends Action {
|
||||||
public static ID = 'qp.PreviousSearchAction';
|
public static ID = 'qp.PreviousSearchAction';
|
||||||
public static LABEL = localize('previousSearchItemAction', "Previous Match (Shift+Enter)");
|
public static LABEL = localize('previousSearchItemAction', "Previous Match");
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(GoToPreviousMatchAction.ID, GoToPreviousMatchAction.LABEL, Codicon.arrowUp.classNames);
|
super(GoToPreviousMatchAction.ID, GoToPreviousMatchAction.LABEL, Codicon.arrowUp.classNames);
|
||||||
@@ -201,7 +210,7 @@ export class GoToPreviousMatchAction extends Action {
|
|||||||
|
|
||||||
export class CancelSearch extends Action {
|
export class CancelSearch extends Action {
|
||||||
public static ID = 'qp.cancelSearchAction';
|
public static ID = 'qp.cancelSearchAction';
|
||||||
public static LABEL = localize('cancelSearchAction', "Close (Escape)");
|
public static LABEL = localize('cancelSearchAction', "Close");
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(CancelSearch.ID, CancelSearch.LABEL, Codicon.chromeClose.classNames);
|
super(CancelSearch.ID, CancelSearch.LABEL, Codicon.chromeClose.classNames);
|
||||||
|
|||||||
@@ -1891,9 +1891,9 @@ aws4@^1.8.0:
|
|||||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
||||||
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
|
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
|
||||||
|
|
||||||
"azdataGraph@github:Microsoft/azdataGraph#0.0.42":
|
"azdataGraph@github:Microsoft/azdataGraph#0.0.45":
|
||||||
version "0.0.42"
|
version "0.0.45"
|
||||||
resolved "https://codeload.github.com/Microsoft/azdataGraph/tar.gz/86f72ee9c9ea78a31c9ad2f402fb24d40e50c75b"
|
resolved "https://codeload.github.com/Microsoft/azdataGraph/tar.gz/8675ac6dfc60aad331ccbf7e4145b9eaa25e7304"
|
||||||
|
|
||||||
bach@^1.0.0:
|
bach@^1.0.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user