Skip to content

Commit

Permalink
Adds toggle button to switch between estimated and actual execution p…
Browse files Browse the repository at this point in the history
…lans (#19629) (#19706)

* Creates toggle button to switch between estimate and actual query plans

* Renames ID for the toggleActualExecutionPlanModeAction class

* Renames button back to explain

* Creating actual execution plans resembles SSMS

* Adds CTRL/CMD + L shortcut to display estimated execution plans

* Alphabetically organizes telemetry actions

* Adds telemetry when the setting for actual execution plan toggle is used

* Resolves build errors

* Fixes broken unit tests.

* Code review changes

* Removes unnecessary null-coalescing operator.

* Creates placeholder icons for actual execution plans enabled

* Code review changes

* Shortens label names

* Telemetry moved to toggle button

* Telemetry review changes

* Clarifies misleading label
  • Loading branch information
lewis-sanchez authored Jun 10, 2022
1 parent 3c1b371 commit d904740
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 17 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions src/sql/base/browser/ui/taskbar/media/icons.css
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,31 @@
background-image: url('query-plan.svg');
}

.vs .codicon.disabledActualExecutionPlan {
background-image: url('disabled-actual-execution-plan.svg');
}

.vs .codicon.enabledActualExecutionPlan {
background-image: url('enabled-actual-execution-plan.svg');
}

.vs-dark .codicon.estimatedQueryPlan,
.hc-black .codicon.estimatedQueryPlan,
.vs-dark .codicon.actualQueryPlan,
.hc-black .codicon.actualQueryPlan {
background-image: url('query-plan-inverse.svg');
}

.vs-dark .codicon.codicon.disabledActualExecutionPlan,
.hc-black .codicon.codicon.disabledActualExecutionPlan {
background-image: url('disabled-actual-execution-plan-inverse.svg');
}

.vs-dark .codicon.codicon.enabledActualExecutionPlan,
.hc-black .codicon.codicon.enabledActualExecutionPlan {
background-image: url('enabled-actual-execution-plan-inverse.svg');
}

.vs .codicon.createInsight {
background-image: url('create_insight.svg');
}
Expand Down
17 changes: 9 additions & 8 deletions src/sql/platform/telemetry/common/telemetryKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,16 @@ export const enum TelemetryError {
}

export const enum TelemetryAction {
adsCommandExecuted = 'adsCommandExecuted',
AddExecutionPlan = 'AddExecutionPlan',
AddServerGroup = 'AddServerGroup',
adsCommandExecuted = 'adsCommandExecuted',
BackupCreated = 'BackupCreated',
ConnectToServer = 'ConnectToServer',
CustomZoom = 'CustomZoom',
BackupCreated = 'BackupCreated',
CancelQuery = 'CancelQuery',
ChartCreated = 'ChartCreated',
Click = 'Click',
CompareExecutionPlan = 'CompareExecutionPlan',
DashboardNavigated = 'DashboardNavigated',
DatabaseConnected = 'DatabaseConnected',
DatabaseDisconnected = 'DatabaseDisconnected',
Expand All @@ -71,10 +75,6 @@ export const enum TelemetryAction {
DeleteAgentProxy = 'DeleteAgentProxy',
DeleteConnection = 'DeleteConnection',
DeleteServerGroup = 'DeleteServerGroup',
CancelQuery = 'CancelQuery',
ChartCreated = 'ChartCreated',
Click = 'Click',
CompareExecutionPlan = 'CompareExecutionPlan',
FindNode = 'FindNode',
FirewallRuleRequested = 'FirewallRuleCreated',
GenerateScript = 'GenerateScript',
Expand All @@ -97,13 +97,14 @@ export const enum TelemetryAction {
RunQuery = 'RunQuery',
RunQueryStatement = 'RunQueryStatement',
RunQueryString = 'RunQueryString',
SearchCompleted = 'SearchCompleted',
SearchStarted = 'SearchStarted',
ShowChart = 'ShowChart',
StopAgentJob = 'StopAgentJob',
ToggleActualExecutionPlan = 'ToggleActualExecutionPlan',
ViewExecutionPlanComparisonProperties = 'ViewExecutionPlanComparisonProperties',
ViewTopOperations = 'ViewTopOperations',
WizardPagesNavigation = 'WizardPagesNavigation',
SearchStarted = 'SearchStarted',
SearchCompleted = 'SearchCompleted',
ZoomIn = 'ZoomIn',
ZoomOut = 'ZoomOut',
ZoomToFit = 'ZoomToFIt'
Expand Down
14 changes: 14 additions & 0 deletions src/sql/workbench/common/editor/query/queryEditorInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface IQueryEditorStateChange {
executingChange?: boolean;
connectingChange?: boolean;
sqlCmdModeChanged?: boolean;
actualExecutionPlanModeChanged?: boolean;
}

export class QueryEditorState extends Disposable {
Expand All @@ -49,6 +50,7 @@ export class QueryEditorState extends Disposable {
private _resultsVisible = false;
private _executing = false;
private _connecting = false;
private _isActualExecutionPlanMode = false;

private _onChange = this._register(new Emitter<IQueryEditorStateChange>());
public onChange = this._onChange.event;
Expand Down Expand Up @@ -108,12 +110,24 @@ export class QueryEditorState extends Disposable {
return this._isSqlCmdMode;
}

public set isActualExecutionPlanMode(val: boolean) {
if (val !== this._isActualExecutionPlanMode) {
this._isActualExecutionPlanMode = val;
this._onChange.fire({ actualExecutionPlanModeChanged: true });
}
}

public get isActualExecutionPlanMode() {
return this._isActualExecutionPlanMode;
}

public setState(newState: QueryEditorState): void {
this.connected = newState.connected;
this.connecting = newState.connecting;
this.resultsVisible = newState.resultsVisible;
this.executing = newState.executing;
this.isSqlCmdMode = newState.isSqlCmdMode;
this.isActualExecutionPlanMode = newState.isActualExecutionPlanMode;
}
}

Expand Down
21 changes: 21 additions & 0 deletions src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,27 @@ export class CopyQueryWithResultsKeyboardAction extends Action {
}
}

export class EstimatedExecutionPlanKeyboardAction extends Action {
public static ID = 'estimatedExecutionPlanKeyboardAction';
public static LABEL = nls.localize('estimatedExecutionPlanKeyboardAction', "Display Estimated Execution Plan");

constructor(
id: string,
label: string,
@IEditorService private _editorService: IEditorService
) {
super(id, label);
this.enabled = true;
}

public override async run(): Promise<void> {
const editor = this._editorService.activeEditorPane;
if (editor instanceof QueryEditor) {
editor.input.runQuery(editor.getSelection(), { displayEstimatedQueryPlan: true });
}
}
}

export class RunCurrentQueryWithActualPlanKeyboardAction extends Action {
public static ID = 'runCurrentQueryWithActualPlanKeyboardAction';
public static LABEL = nls.localize('runCurrentQueryWithActualPlanKeyboardAction', "Run Current Query with Actual Plan");
Expand Down
12 changes: 11 additions & 1 deletion src/sql/workbench/contrib/query/browser/query.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { QueryResultsInput } from 'sql/workbench/common/editor/query/queryResult
import * as queryContext from 'sql/workbench/contrib/query/common/queryContext';
import {
RunQueryKeyboardAction, RunCurrentQueryKeyboardAction, CancelQueryKeyboardAction, RefreshIntellisenseKeyboardAction, ToggleQueryResultsKeyboardAction,
RunQueryShortcutAction, RunCurrentQueryWithActualPlanKeyboardAction, CopyQueryWithResultsKeyboardAction, FocusOnCurrentQueryKeyboardAction, ParseSyntaxAction, ToggleFocusBetweenQueryEditorAndResultsAction
RunQueryShortcutAction, RunCurrentQueryWithActualPlanKeyboardAction, CopyQueryWithResultsKeyboardAction, FocusOnCurrentQueryKeyboardAction, ParseSyntaxAction, ToggleFocusBetweenQueryEditorAndResultsAction, EstimatedExecutionPlanKeyboardAction
} from 'sql/workbench/contrib/query/browser/keyboardQueryActions';
import * as gridActions from 'sql/workbench/contrib/editData/browser/gridActions';
import * as gridCommands from 'sql/workbench/contrib/editData/browser/gridCommands';
Expand Down Expand Up @@ -135,6 +135,16 @@ actionRegistry.registerWorkbenchAction(
RunCurrentQueryKeyboardAction.LABEL
);

actionRegistry.registerWorkbenchAction(
SyncActionDescriptor.create(
EstimatedExecutionPlanKeyboardAction,
EstimatedExecutionPlanKeyboardAction.ID,
EstimatedExecutionPlanKeyboardAction.LABEL,
{ primary: KeyMod.CtrlCmd | KeyCode.KEY_L }
),
EstimatedExecutionPlanKeyboardAction.LABEL
);

actionRegistry.registerWorkbenchAction(
SyncActionDescriptor.create(
RunCurrentQueryWithActualPlanKeyboardAction,
Expand Down
67 changes: 60 additions & 7 deletions src/sql/workbench/contrib/query/browser/queryActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import 'vs/css!./media/queryActions';
import * as nls from 'vs/nls';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { Action, IAction, IActionRunner } from 'vs/base/common/actions';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
Expand Down Expand Up @@ -46,6 +47,7 @@ import { getErrorMessage, onUnexpectedError } from 'vs/base/common/errors';
import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { gen3Version, sqlDataWarehouse } from 'sql/platform/connection/common/constants';
import { Dropdown } from 'sql/base/browser/ui/editableDropdown/browser/dropdown';
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';

/**
* Action class that query-based Actions will extend. This base class automatically handles activating and
Expand Down Expand Up @@ -242,9 +244,15 @@ export class RunQueryAction extends QueryTaskbarAction {
if (runCurrentStatement && selection && this.isCursorPosition(selection)) {
editor.input.runQueryStatement(selection);
} else {
// get the selection again this time with trimming
selection = editor.getSelection();
editor.input.runQuery(selection);
if (editor.input.state.isActualExecutionPlanMode) {
selection = editor.getSelection();
editor.input.runQuery(selection, { displayActualQueryPlan: true });
}
else {
// get the selection again this time with trimming
selection = editor.getSelection();
editor.input.runQuery(selection);
}
}
return true;
}
Expand Down Expand Up @@ -300,7 +308,7 @@ export class EstimatedQueryPlanAction extends QueryTaskbarAction {
@IConnectionManagementService connectionManagementService: IConnectionManagementService
) {
super(connectionManagementService, editor, EstimatedQueryPlanAction.ID, EstimatedQueryPlanAction.EnabledClass);
this.label = nls.localize('estimatedQueryPlan', "Explain");
this.label = nls.localize('estimatedQueryPlan', "Estimated Plan");
}

public override async run(): Promise<void> {
Expand All @@ -323,13 +331,57 @@ export class EstimatedQueryPlanAction extends QueryTaskbarAction {
}

if (this.isConnected(editor)) {
editor.input.runQuery(editor.getSelection(), {
displayEstimatedQueryPlan: true
});
editor.input.runQuery(editor.getSelection(), { displayEstimatedQueryPlan: true });
}
}
}

/**
* Action class that toggles the actual execution plan mode for the editor
*/
export class ToggleActualExecutionPlanModeAction extends QueryTaskbarAction {
public static EnabledClass = 'enabledActualExecutionPlan';
public static ID = 'toggleActualExecutionPlanModeAction';

private _enableActualPlanLabel = nls.localize('enableActualPlanLabel', "Include Actual Plan");
private _disableActualPlanLabel = nls.localize('disableActualPlanLabel', "Exclude Actual Plan");

constructor(
editor: QueryEditor,
private _isActualPlanMode: boolean,
@IQueryManagementService protected readonly queryManagementService: IQueryManagementService,
@IConfigurationService protected readonly configurationService: IConfigurationService,
@IConnectionManagementService connectionManagementService: IConnectionManagementService,
@IAdsTelemetryService private readonly telemetryService: IAdsTelemetryService
) {
super(connectionManagementService, editor, ToggleActualExecutionPlanModeAction.ID, ToggleActualExecutionPlanModeAction.EnabledClass);
this.updateLabel();
}

public get isActualExecutionPlanMode(): boolean {
return this._isActualPlanMode;
}

public set isActualExecutionPlanMode(value: boolean) {
this._isActualPlanMode = value;
this.updateLabel();
}

private updateLabel(): void {
// show option to disable actual plan mode if already enabled
this.label = this.isActualExecutionPlanMode ? this._disableActualPlanLabel : this._enableActualPlanLabel;
}

public override async run(): Promise<void> {
const toActualPlanState = !this.isActualExecutionPlanMode;
this.editor.input.state.isActualExecutionPlanMode = toActualPlanState;

this.telemetryService.createActionEvent(TelemetryKeys.TelemetryView.ExecutionPlan, TelemetryKeys.TelemetryAction.Click, 'ToggleActualExecutionPlan')
.withAdditionalProperties({ actualExecutionPlanMode: this.isActualExecutionPlanMode })
.send();
}
}

export class ActualQueryPlanAction extends QueryTaskbarAction {
public static EnabledClass = 'actualQueryPlan';
public static ID = 'actualQueryPlanAction';
Expand Down Expand Up @@ -522,6 +574,7 @@ export class ToggleSqlCmdModeAction extends QueryTaskbarAction {

private _enablesqlcmdLabel = nls.localize('enablesqlcmdLabel', "Enable SQLCMD");
private _disablesqlcmdLabel = nls.localize('disablesqlcmdLabel', "Disable SQLCMD");

constructor(
editor: QueryEditor,
private _isSqlCmdMode: boolean,
Expand Down
9 changes: 8 additions & 1 deletion src/sql/workbench/contrib/query/browser/queryEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export class QueryEditor extends EditorPane {
private _actualQueryPlanAction: actions.ActualQueryPlanAction;
private _listDatabasesActionItem: actions.ListDatabasesActionItem;
private _toggleSqlcmdMode: actions.ToggleSqlCmdModeAction;
private _toggleActualExecutionPlanMode: actions.ToggleActualExecutionPlanModeAction;
private _exportAsNotebookAction: actions.ExportAsNotebookAction;

constructor(
Expand Down Expand Up @@ -205,6 +206,7 @@ export class QueryEditor extends EditorPane {
this._estimatedQueryPlanAction = this.instantiationService.createInstance(actions.EstimatedQueryPlanAction, this);
this._actualQueryPlanAction = this.instantiationService.createInstance(actions.ActualQueryPlanAction, this);
this._toggleSqlcmdMode = this.instantiationService.createInstance(actions.ToggleSqlCmdModeAction, this, false);
this._toggleActualExecutionPlanMode = this.instantiationService.createInstance(actions.ToggleActualExecutionPlanModeAction, this, false);
this._exportAsNotebookAction = this.instantiationService.createInstance(actions.ExportAsNotebookAction, this);
this.setTaskbarContent();
this._register(this.configurationService.onDidChangeConfiguration(e => {
Expand Down Expand Up @@ -241,6 +243,10 @@ export class QueryEditor extends EditorPane {
this._toggleSqlcmdMode.isSqlCmdMode = this.input.state.isSqlCmdMode;
}

if (stateChangeEvent.actualExecutionPlanModeChanged) {
this._toggleActualExecutionPlanMode.isActualExecutionPlanMode = this.input.state.isActualExecutionPlanMode;
}

if (stateChangeEvent.connectingChange) {
this._runQueryAction.enabled = !this.input.state.connecting;
this._estimatedQueryPlanAction.enabled = !this.input.state.connecting;
Expand Down Expand Up @@ -322,6 +328,7 @@ export class QueryEditor extends EditorPane {
content.push(
{ element: Taskbar.createTaskbarSeparator() },
{ action: this._estimatedQueryPlanAction },
{ action: this._toggleActualExecutionPlanMode },
{ action: this._toggleSqlcmdMode },
{ action: this._exportAsNotebookAction }
);
Expand Down Expand Up @@ -367,7 +374,7 @@ export class QueryEditor extends EditorPane {

this.inputDisposables.clear();
this.inputDisposables.add(this.input.state.onChange(c => this.updateState(c)));
this.updateState({ connectingChange: true, connectedChange: true, executingChange: true, resultsVisibleChange: true, sqlCmdModeChanged: true });
this.updateState({ connectingChange: true, connectedChange: true, executingChange: true, resultsVisibleChange: true, sqlCmdModeChanged: true, actualExecutionPlanModeChanged: true });

const editorViewState = this.loadTextEditorViewState(this.input.resource);

Expand Down
Loading

0 comments on commit d904740

Please sign in to comment.