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:
Lewis Sanchez
2022-10-03 12:32:39 -07:00
committed by GitHub
parent d0cab10cc9
commit f521d7cc63
22 changed files with 660 additions and 77 deletions

View File

@@ -26,7 +26,7 @@ import { Progress } from 'vs/platform/progress/common/progress';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Action, Separator } from 'vs/base/common/actions';
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 { VSBuffer } from 'vs/base/common/buffer';
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 { ExecutionPlanFileView } from 'sql/workbench/contrib/executionPlan/browser/executionPlanFileView';
import { QueryResultsView } from 'sql/workbench/contrib/query/browser/queryResultsView';
import { HighlightExpensiveOperationWidget } from 'sql/workbench/contrib/executionPlan/browser/widgets/highlightExpensiveNodeWidget';
export class ExecutionPlanView implements ISashLayoutProvider {
@@ -66,6 +67,9 @@ export class ExecutionPlanView implements ISashLayoutProvider {
// plan diagram
public executionPlanDiagram: AzdataGraphView;
// previous expensive operator action selected
public previousExpensiveOperatorAction: Action;
public actionBarToggleTopTip: Action;
public contextMenuToggleTooltipAction: Action;
constructor(
@@ -169,6 +173,7 @@ export class ExecutionPlanView implements ISashLayoutProvider {
this._instantiationService.createInstance(SearchNodeAction, 'ActionBar'),
this._instantiationService.createInstance(PropertiesAction, 'ActionBar'),
this._instantiationService.createInstance(CompareExecutionPlanAction, 'ActionBar'),
this._instantiationService.createInstance(HighlightExpensiveOperationAction, 'ActionBar'),
this.actionBarToggleTopTip
];
// Setting up context menu
@@ -186,7 +191,9 @@ export class ExecutionPlanView implements ISashLayoutProvider {
this._instantiationService.createInstance(SearchNodeAction, 'ContextMenu'),
this._instantiationService.createInstance(PropertiesAction, 'ContextMenu'),
this._instantiationService.createInstance(CompareExecutionPlanAction, 'ContextMenu'),
this.contextMenuToggleTooltipAction
this._instantiationService.createInstance(HighlightExpensiveOperationAction, 'ContextMenu'),
this.contextMenuToggleTooltipAction,
new Separator(),
];
if (this._queryResultsView) {
@@ -320,7 +327,7 @@ export class OpenQueryAction extends Action {
constructor(private source: ExecutionPlanActionSource,
@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> {
@@ -340,7 +347,7 @@ export class PropertiesAction extends Action {
constructor(private source: ExecutionPlanActionSource,
@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> {
@@ -360,7 +367,7 @@ export class ZoomInAction extends Action {
constructor(private source: ExecutionPlanActionSource,
@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> {
@@ -380,7 +387,7 @@ export class ZoomOutAction extends Action {
constructor(private source: ExecutionPlanActionSource,
@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> {
@@ -400,7 +407,7 @@ export class ZoomToFitAction extends Action {
constructor(private source: ExecutionPlanActionSource,
@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> {
@@ -418,7 +425,7 @@ export class SavePlanFile extends Action {
public static LABEL = localize('executionPlanSavePlanXML', "Save Plan File");
constructor() {
super(SavePlanFile.ID, SavePlanFile.LABEL, savePlanIconClassNames);
super(SavePlanFile.ID, SavePlanFile.LABEL, constants.savePlanIconClassNames);
}
public override async run(context: ExecutionPlanView): Promise<void> {
@@ -453,7 +460,7 @@ export class CustomZoomAction extends Action {
constructor(private source: ExecutionPlanActionSource,
@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> {
@@ -473,7 +480,7 @@ export class SearchNodeAction extends Action {
constructor(private source: ExecutionPlanActionSource,
@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> {
@@ -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
constructor() {
super(OpenPlanFile.ID, OpenPlanFile.Label, openPlanFileIconClassNames);
super(OpenPlanFile.ID, OpenPlanFile.Label, constants.openPlanFileIconClassNames);
}
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");
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> {
const state = context.executionPlanDiagram.toggleTooltip();
if (!state) {
this.class = disableTooltipIconClassName;
this.class = constants.disableTooltipIconClassName;
this.label = ActionBarToggleTooltip.WHEN_TOOLTIPS_DISABLED_LABEL;
context.contextMenuToggleTooltipAction.label = ContextMenuTooltipToggle.WHEN_TOOLTIPS_DISABLED_LABEL;
} else {
this.class = enableTooltipIconClassName;
this.class = constants.enableTooltipIconClassName;
this.label = ActionBarToggleTooltip.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");
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> {
const state = context.executionPlanDiagram.toggleTooltip();
if (!state) {
this.label = ContextMenuTooltipToggle.WHEN_TOOLTIPS_DISABLED_LABEL;
context.actionBarToggleTopTip.class = disableTooltipIconClassName;
context.actionBarToggleTopTip.class = constants.disableTooltipIconClassName;
context.actionBarToggleTopTip.label = ActionBarToggleTooltip.WHEN_TOOLTIPS_DISABLED_LABEL;
} else {
this.label = ContextMenuTooltipToggle.WHEN_TOOLTIPS_ENABLED_LABEL;
context.actionBarToggleTopTip.class = enableTooltipIconClassName;
context.actionBarToggleTopTip.class = constants.enableTooltipIconClassName;
context.actionBarToggleTopTip.label = ActionBarToggleTooltip.WHEN_TOOLTIPS_ENABLED_LABEL;
}
}
@@ -552,7 +559,7 @@ export class CompareExecutionPlanAction extends Action {
constructor(private source: ExecutionPlanActionSource,
@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> {
@@ -572,10 +579,30 @@ export class TopOperationsAction extends Action {
constructor
() {
super(TopOperationsAction.ID, TopOperationsAction.LABEL, executionPlanTopOperations);
super(TopOperationsAction.ID, TopOperationsAction.LABEL, constants.executionPlanTopOperations);
}
public override async run(context: ExecutionPlanView): Promise<void> {
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));
}
}