From 20d2256709ddc0920147e748252b5fbf74bda9dc Mon Sep 17 00:00:00 2001 From: Lewis Sanchez <87730006+lewis-sanchez@users.noreply.github.com> Date: Thu, 9 Jun 2022 16:07:12 -0700 Subject: [PATCH] Adds toggle button to switch between estimated and actual execution plans (#19629) * 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 --- ...disabled-actual-execution-plan-inverse.svg | 1 + .../media/disabled-actual-execution-plan.svg | 1 + .../enabled-actual-execution-plan-inverse.svg | 1 + .../media/enabled-actual-execution-plan.svg | 1 + .../base/browser/ui/taskbar/media/icons.css | 18 +++++ .../telemetry/common/telemetryKeys.ts | 17 ++--- .../common/editor/query/queryEditorInput.ts | 14 ++++ .../query/browser/keyboardQueryActions.ts | 21 ++++++ .../query/browser/query.contribution.ts | 12 +++- .../contrib/query/browser/queryActions.ts | 67 +++++++++++++++++-- .../contrib/query/browser/queryEditor.ts | 9 ++- .../query/test/browser/queryActions.test.ts | 14 ++++ 12 files changed, 159 insertions(+), 17 deletions(-) create mode 100644 src/sql/base/browser/ui/taskbar/media/disabled-actual-execution-plan-inverse.svg create mode 100644 src/sql/base/browser/ui/taskbar/media/disabled-actual-execution-plan.svg create mode 100644 src/sql/base/browser/ui/taskbar/media/enabled-actual-execution-plan-inverse.svg create mode 100644 src/sql/base/browser/ui/taskbar/media/enabled-actual-execution-plan.svg diff --git a/src/sql/base/browser/ui/taskbar/media/disabled-actual-execution-plan-inverse.svg b/src/sql/base/browser/ui/taskbar/media/disabled-actual-execution-plan-inverse.svg new file mode 100644 index 0000000000..1c7c0724d8 --- /dev/null +++ b/src/sql/base/browser/ui/taskbar/media/disabled-actual-execution-plan-inverse.svg @@ -0,0 +1 @@ +query_plan_inverse_16x16 \ No newline at end of file diff --git a/src/sql/base/browser/ui/taskbar/media/disabled-actual-execution-plan.svg b/src/sql/base/browser/ui/taskbar/media/disabled-actual-execution-plan.svg new file mode 100644 index 0000000000..d1c246bf81 --- /dev/null +++ b/src/sql/base/browser/ui/taskbar/media/disabled-actual-execution-plan.svg @@ -0,0 +1 @@ +query_plan_16x16 \ No newline at end of file diff --git a/src/sql/base/browser/ui/taskbar/media/enabled-actual-execution-plan-inverse.svg b/src/sql/base/browser/ui/taskbar/media/enabled-actual-execution-plan-inverse.svg new file mode 100644 index 0000000000..1c7c0724d8 --- /dev/null +++ b/src/sql/base/browser/ui/taskbar/media/enabled-actual-execution-plan-inverse.svg @@ -0,0 +1 @@ +query_plan_inverse_16x16 \ No newline at end of file diff --git a/src/sql/base/browser/ui/taskbar/media/enabled-actual-execution-plan.svg b/src/sql/base/browser/ui/taskbar/media/enabled-actual-execution-plan.svg new file mode 100644 index 0000000000..d1c246bf81 --- /dev/null +++ b/src/sql/base/browser/ui/taskbar/media/enabled-actual-execution-plan.svg @@ -0,0 +1 @@ +query_plan_16x16 \ No newline at end of file diff --git a/src/sql/base/browser/ui/taskbar/media/icons.css b/src/sql/base/browser/ui/taskbar/media/icons.css index 9ebe9573ef..14e2bf69e0 100644 --- a/src/sql/base/browser/ui/taskbar/media/icons.css +++ b/src/sql/base/browser/ui/taskbar/media/icons.css @@ -65,6 +65,14 @@ 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, @@ -72,6 +80,16 @@ 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'); } diff --git a/src/sql/platform/telemetry/common/telemetryKeys.ts b/src/sql/platform/telemetry/common/telemetryKeys.ts index 7033e02acb..404afa50f6 100644 --- a/src/sql/platform/telemetry/common/telemetryKeys.ts +++ b/src/sql/platform/telemetry/common/telemetryKeys.ts @@ -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', @@ -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', @@ -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' diff --git a/src/sql/workbench/common/editor/query/queryEditorInput.ts b/src/sql/workbench/common/editor/query/queryEditorInput.ts index 496ef9d0fd..faffa79868 100644 --- a/src/sql/workbench/common/editor/query/queryEditorInput.ts +++ b/src/sql/workbench/common/editor/query/queryEditorInput.ts @@ -41,6 +41,7 @@ export interface IQueryEditorStateChange { executingChange?: boolean; connectingChange?: boolean; sqlCmdModeChanged?: boolean; + actualExecutionPlanModeChanged?: boolean; } export class QueryEditorState extends Disposable { @@ -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()); public onChange = this._onChange.event; @@ -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; } } diff --git a/src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts b/src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts index 0ee40e0a26..62a7391952 100644 --- a/src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts +++ b/src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts @@ -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 { + 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"); diff --git a/src/sql/workbench/contrib/query/browser/query.contribution.ts b/src/sql/workbench/contrib/query/browser/query.contribution.ts index e55ddd3716..65631ee974 100644 --- a/src/sql/workbench/contrib/query/browser/query.contribution.ts +++ b/src/sql/workbench/contrib/query/browser/query.contribution.ts @@ -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'; @@ -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, diff --git a/src/sql/workbench/contrib/query/browser/queryActions.ts b/src/sql/workbench/contrib/query/browser/queryActions.ts index b718e3f519..9dfe4a3757 100644 --- a/src/sql/workbench/contrib/query/browser/queryActions.ts +++ b/src/sql/workbench/contrib/query/browser/queryActions.ts @@ -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'; @@ -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 @@ -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; } @@ -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 { @@ -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 { + 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'; @@ -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, diff --git a/src/sql/workbench/contrib/query/browser/queryEditor.ts b/src/sql/workbench/contrib/query/browser/queryEditor.ts index 2a65df6a0e..84770398cc 100644 --- a/src/sql/workbench/contrib/query/browser/queryEditor.ts +++ b/src/sql/workbench/contrib/query/browser/queryEditor.ts @@ -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( @@ -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 => { @@ -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; @@ -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 } ); @@ -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); diff --git a/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts b/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts index 5c1cddf354..31778eb65b 100644 --- a/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts +++ b/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts @@ -34,6 +34,7 @@ import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/commo import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; import { IRange } from 'vs/editor/common/core/range'; import { ServerInfo } from 'azdata'; +import { QueryEditorState } from 'sql/workbench/common/editor/query/queryEditorInput'; suite('SQL QueryAction Tests', () => { @@ -41,6 +42,7 @@ suite('SQL QueryAction Tests', () => { let editor: TypeMoq.Mock; let calledRunQueryOnInput: boolean = undefined; let testQueryInput: TypeMoq.Mock; + let testQueryInputState: TypeMoq.Mock; let configurationService: TypeMoq.Mock; let queryModelService: TypeMoq.Mock; let connectionManagementService: TypeMoq.Mock; @@ -76,9 +78,13 @@ suite('SQL QueryAction Tests', () => { const service = accessor.untitledTextEditorService; let fileInput = workbenchinstantiationService.createInstance(UntitledTextEditorInput, service.create({ associatedResource: URI.parse('file://testUri') })); // Setup a reusable mock QueryInput + testQueryInputState = TypeMoq.Mock.ofType(QueryEditorState, TypeMoq.MockBehavior.Strict); + testQueryInputState.setup(x => x.isActualExecutionPlanMode).returns(() => false); + testQueryInput = TypeMoq.Mock.ofType(UntitledQueryEditorInput, TypeMoq.MockBehavior.Strict, undefined, fileInput, undefined, connectionManagementService.object, queryModelService.object, configurationService.object); testQueryInput.setup(x => x.uri).returns(() => testUri); testQueryInput.setup(x => x.runQuery(undefined)).callback(() => { calledRunQueryOnInput = true; }); + testQueryInput.setup(x => x.state).returns(() => testQueryInputState.object); }); test('setClass sets child CSS class correctly', () => { @@ -184,11 +190,15 @@ suite('SQL QueryAction Tests', () => { let fileInput = workbenchinstantiationService.createInstance(UntitledTextEditorInput, service.create({ associatedResource: URI.parse('file://testUri') })); // ... Mock "isSelectionEmpty" in QueryEditor + let queryInputState = TypeMoq.Mock.ofType(QueryEditorState, TypeMoq.MockBehavior.Loose); + queryInputState.setup(x => x.isActualExecutionPlanMode).returns(() => false); + let queryInput = TypeMoq.Mock.ofType(UntitledQueryEditorInput, TypeMoq.MockBehavior.Strict, undefined, fileInput, undefined, connectionManagementService.object, queryModelService.object, configurationService.object); queryInput.setup(x => x.uri).returns(() => testUri); queryInput.setup(x => x.runQuery(undefined)).callback(() => { countCalledRunQuery++; }); + queryInput.setup(x => x.state).returns(() => queryInputState.object); const contextkeyservice = new MockContextKeyService(); // Setup a reusable mock QueryEditor @@ -234,12 +244,16 @@ suite('SQL QueryAction Tests', () => { const service = accessor.untitledTextEditorService; let fileInput = workbenchinstantiationService.createInstance(UntitledTextEditorInput, service.create({ associatedResource: URI.parse('file://testUri') })); + let queryInputState = TypeMoq.Mock.ofType(QueryEditorState, TypeMoq.MockBehavior.Loose); + queryInputState.setup(x => x.isActualExecutionPlanMode).returns(() => false); + let queryInput = TypeMoq.Mock.ofType(UntitledQueryEditorInput, TypeMoq.MockBehavior.Loose, undefined, fileInput, undefined, connectionManagementService.object, queryModelService.object, configurationService.object); queryInput.setup(x => x.uri).returns(() => testUri); queryInput.setup(x => x.runQuery(TypeMoq.It.isAny())).callback((selection: IRange) => { runQuerySelection = selection; countCalledRunQuery++; }); + queryInput.setup(x => x.state).returns(() => queryInputState.object); queryInput.setup(x => x.runQuery(undefined)).callback((selection: IRange) => { runQuerySelection = selection; countCalledRunQuery++;