diff --git a/extensions/query-history/package.json b/extensions/query-history/package.json index eeb8e7ec20..0b01b22e5b 100644 --- a/extensions/query-history/package.json +++ b/extensions/query-history/package.json @@ -10,7 +10,7 @@ "aiKey": "AIF-444c3af9-8e69-4462-ab49-4191e6ad1916", "engines": { "vscode": "^1.30.1", - "azdata": ">=1.9.0" + "azdata": ">=1.12.0" }, "activationEvents": [ "*" @@ -28,12 +28,20 @@ "command": "queryHistory.clear", "title": "%queryHistory.clear%", "category": "%queryHistory.displayName%" + }, + { + "command": "queryHistory.toggleCapture", + "title": "%queryHistory.toggleCapture%", + "category": "%queryHistory.displayName%" } ], "menus": { "commandPalette": [ { "command": "queryHistory.clear" + }, + { + "command": "queryHistory.toggleCapture" } ] } diff --git a/extensions/query-history/package.nls.json b/extensions/query-history/package.nls.json index 19a373d209..05b12e3800 100644 --- a/extensions/query-history/package.nls.json +++ b/extensions/query-history/package.nls.json @@ -1,5 +1,6 @@ { "queryHistory.displayName": "Query History", "queryHistory.description": "View and run previously executed queries", - "queryHistory.clear": "Clear All History" + "queryHistory.clear": "Clear All History", + "queryHistory.toggleCapture": "Toggle Query History Capture" } diff --git a/src/sql/platform/queryHistory/common/queryHistoryService.ts b/src/sql/platform/queryHistory/common/queryHistoryService.ts index 0eae3982ba..dbb3559c1b 100644 --- a/src/sql/platform/queryHistory/common/queryHistoryService.ts +++ b/src/sql/platform/queryHistory/common/queryHistoryService.ts @@ -17,10 +17,39 @@ export const IQueryHistoryService = createDecorator(SERVIC export interface IQueryHistoryService { _serviceBrand: any; + /** + * Event fired whenever the collection of stored QueryHistoryInfo's is updated + */ onInfosUpdated: Event; + /** + * Event fired whenever the Query History capture state has changed + */ + onQueryHistoryCaptureChanged: Event; + /** + * Whether Query History capture is currently enabled + */ + readonly captureEnabled: boolean; + + /** + * Gets the current list of Query History Info objects that have been collected + */ getQueryHistoryInfos(): QueryHistoryInfo[]; + /** + * Deletes all QueryHistoryInfo's from the collection that have the same id as the specified one + * @param info The QueryHistoryInfo to delete + */ deleteQueryHistoryInfo(info: QueryHistoryInfo): void; + /** + * Clears all Query History - removing all collected items + */ clearQueryHistory(): void; + /** + * Toggles whether Query History capture is enabled + */ + toggleCaptureEnabled(): Promise; + /** + * Starts the Query History Service + */ start(): void; } diff --git a/src/sql/platform/queryHistory/common/queryHistoryServiceImpl.ts b/src/sql/platform/queryHistory/common/queryHistoryServiceImpl.ts index 9ffb73376f..985dec9cde 100644 --- a/src/sql/platform/queryHistory/common/queryHistoryServiceImpl.ts +++ b/src/sql/platform/queryHistory/common/queryHistoryServiceImpl.ts @@ -13,6 +13,8 @@ import { QueryHistoryInfo, QueryStatus } from 'sql/platform/queryHistory/common/ import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; +import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationChangedEvent } from 'sql/workbench/parts/profiler/browser/profilerFindWidget'; /** * Service that collects the results of executed queries @@ -23,20 +25,30 @@ export class QueryHistoryService extends Disposable implements IQueryHistoryServ // MEMBER VARIABLES //////////////////////////////////////////////////// private _infos: QueryHistoryInfo[] = []; private _onInfosUpdated: Emitter = new Emitter(); - + private _onQueryHistoryCaptureChanged: Emitter = new Emitter(); + private _captureEnabled; // EVENTS ////////////////////////////////////////////////////////////// public get onInfosUpdated(): Event { return this._onInfosUpdated.event; } - + public get onQueryHistoryCaptureChanged(): Event { return this._onQueryHistoryCaptureChanged.event; } // CONSTRUCTOR ///////////////////////////////////////////////////////// constructor( @IQueryModelService _queryModelService: IQueryModelService, @IModelService _modelService: IModelService, - @IConnectionManagementService _connectionManagementService: IConnectionManagementService + @IConnectionManagementService _connectionManagementService: IConnectionManagementService, + @IConfigurationService private _configurationService: IConfigurationService ) { super(); + this._captureEnabled = !!this._configurationService.getValue('queryHistory.captureEnabled'); + + this._register(this._configurationService.onDidChangeConfiguration((e: IConfigurationChangeEvent) => { + if (e.affectedKeys.includes('queryHistory.captureEnabled')) { + this.updateCaptureEnabled(); + } + })); + this._register(_queryModelService.onQueryEvent((e: IQueryEvent) => { - if (e.type === 'queryStop') { + if (this._captureEnabled && e.type === 'queryStop') { const uri: URI = URI.parse(e.uri); // VS Range is 1 based so offset values by 1. The endLine we get back from SqlToolsService is incremented // by 1 from the original input range sent in as well so take that into account and don't modify @@ -62,6 +74,13 @@ export class QueryHistoryService extends Disposable implements IQueryHistoryServ })); } + /** + * Whether Query History capture is currently enabled + */ + public get captureEnabled(): boolean { + return this._captureEnabled; + } + /** * Gets all the current query history infos */ @@ -71,9 +90,9 @@ export class QueryHistoryService extends Disposable implements IQueryHistoryServ /** * Deletes infos from the cache with the same ID as the given QueryHistoryInfo - * @param info TheQueryHistoryInfo to delete + * @param info The QueryHistoryInfo to delete */ - public deleteQueryHistoryInfo(info: QueryHistoryInfo) { + public deleteQueryHistoryInfo(info: QueryHistoryInfo): void { this._infos = this._infos.filter(i => i.id !== info.id); this._onInfosUpdated.fire(this._infos); } @@ -81,11 +100,24 @@ export class QueryHistoryService extends Disposable implements IQueryHistoryServ /** * Clears all infos from the cache */ - public clearQueryHistory() { + public clearQueryHistory(): void { this._infos = []; this._onInfosUpdated.fire(this._infos); } + public async toggleCaptureEnabled(): Promise { + const captureEnabled = !!this._configurationService.getValue('queryHistory.captureEnabled'); + await this._configurationService.updateValue('queryHistory.captureEnabled', !captureEnabled); + } + + private updateCaptureEnabled(): void { + const currentCaptureEnabled = this._captureEnabled; + this._captureEnabled = !!this._configurationService.getValue('queryHistory.captureEnabled'); + if (currentCaptureEnabled !== this._captureEnabled) { + this._onQueryHistoryCaptureChanged.fire(this._captureEnabled); + } + } + /** * Method to force initialization of the service so that it can start tracking query events */ diff --git a/src/sql/workbench/parts/queryHistory/browser/queryHistoryActionProvider.ts b/src/sql/workbench/parts/queryHistory/browser/queryHistoryActionProvider.ts index 3795fdc8b2..ff1c1ad753 100644 --- a/src/sql/workbench/parts/queryHistory/browser/queryHistoryActionProvider.ts +++ b/src/sql/workbench/parts/queryHistory/browser/queryHistoryActionProvider.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DeleteAction, OpenQueryAction, RunQueryAction, ClearHistoryAction } from 'sql/workbench/parts/queryHistory/browser/queryHistoryActions'; +import { DeleteAction, OpenQueryAction, RunQueryAction, ClearHistoryAction, ToggleQueryHistoryCaptureAction } from 'sql/workbench/parts/queryHistory/browser/queryHistoryActions'; import { ITree } from 'vs/base/parts/tree/browser/tree'; import { ContributableActionProvider } from 'vs/workbench/browser/actions'; import { IAction } from 'vs/base/common/actions'; @@ -19,7 +19,8 @@ export class QueryHistoryActionProvider extends ContributableActionProvider { openQueryAction: IAction, runQueryAction: IAction, deleteAction: IAction, - clearAction: IAction + clearAction: IAction, + toggleCaptureAction: IAction }; constructor( @@ -30,7 +31,8 @@ export class QueryHistoryActionProvider extends ContributableActionProvider { openQueryAction: instantiationService.createInstance(OpenQueryAction, OpenQueryAction.ID, OpenQueryAction.LABEL), runQueryAction: instantiationService.createInstance(RunQueryAction, RunQueryAction.ID, RunQueryAction.LABEL), deleteAction: instantiationService.createInstance(DeleteAction, DeleteAction.ID, DeleteAction.LABEL), - clearAction: instantiationService.createInstance(ClearHistoryAction, ClearHistoryAction.ID, ClearHistoryAction.LABEL) + clearAction: instantiationService.createInstance(ClearHistoryAction, ClearHistoryAction.ID, ClearHistoryAction.LABEL), + toggleCaptureAction: instantiationService.createInstance(ToggleQueryHistoryCaptureAction, ToggleQueryHistoryCaptureAction.ID, ToggleQueryHistoryCaptureAction.LABEL) }; } @@ -43,6 +45,7 @@ export class QueryHistoryActionProvider extends ContributableActionProvider { */ public getActions(element: any): IAction[] { const actions: IAction[] = []; + // Actions we only want to display if we're on a valid QueryHistoryNode if (element instanceof QueryHistoryNode && element.info) { if (element.info && element.info.queryText && element.info.queryText !== '') { actions.push(this._actions.openQueryAction); @@ -50,7 +53,8 @@ export class QueryHistoryActionProvider extends ContributableActionProvider { } actions.push(this._actions.deleteAction); } - actions.push(this._actions.clearAction); + // Common actions we want to always display + actions.push(this._actions.clearAction, this._actions.toggleCaptureAction); return actions; } diff --git a/src/sql/workbench/parts/queryHistory/browser/queryHistoryActions.ts b/src/sql/workbench/parts/queryHistory/browser/queryHistoryActions.ts index 6310ce7411..38d4c913f9 100644 --- a/src/sql/workbench/parts/queryHistory/browser/queryHistoryActions.ts +++ b/src/sql/workbench/parts/queryHistory/browser/queryHistoryActions.ts @@ -63,7 +63,7 @@ export class ClearHistoryAction extends Action { } public async run(): Promise { - this._commandService.executeCommand('queryHistory.clear'); + return this._commandService.executeCommand('queryHistory.clear'); } } @@ -106,3 +106,33 @@ export class RunQueryAction extends Action { } } +export class ToggleQueryHistoryCaptureAction extends Action { + public static ID = 'queryHistory.toggleCapture'; + public static LABEL = localize('queryHistory.toggleCaptureLabel', "Toggle Query History capture"); + + constructor( + id: string, + label: string, + @ICommandService private _commandService: ICommandService, + @IQueryHistoryService queryHistoryService: IQueryHistoryService + ) { + super(id, label); + this.setClassAndLabel(queryHistoryService.captureEnabled); + this._register(queryHistoryService.onQueryHistoryCaptureChanged((captureEnabled: boolean) => { this.setClassAndLabel(captureEnabled); })); + } + + public async run(): Promise { + return this._commandService.executeCommand('queryHistory.toggleCapture'); + } + + private setClassAndLabel(enabled: boolean) { + if (enabled) { + this.class = 'toggle-query-history-capture-action codicon-pause'; + this.label = localize('queryHistory.disableCapture', "Pause Query History capture"); + } else { + this.class = 'toggle-query-history-capture-action codicon-play'; + this.label = localize('queryHistory.enableCapture', "Start Query History capture"); + } + } +} + diff --git a/src/sql/workbench/parts/queryHistory/electron-browser/queryHistory.ts b/src/sql/workbench/parts/queryHistory/electron-browser/queryHistory.ts index 05f5cf6bab..21f7d58668 100644 --- a/src/sql/workbench/parts/queryHistory/electron-browser/queryHistory.ts +++ b/src/sql/workbench/parts/queryHistory/electron-browser/queryHistory.ts @@ -15,6 +15,7 @@ import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor } from 'v import { QUERY_HISTORY_PANEL_ID } from 'sql/workbench/parts/queryHistory/common/constants'; import { ToggleQueryHistoryAction } from 'sql/workbench/parts/queryHistory/browser/queryHistoryActions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { IConfigurationRegistry, ConfigurationScope, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; export class QueryHistoryWorkbenchContribution implements IWorkbenchContribution { @@ -34,6 +35,21 @@ export class QueryHistoryWorkbenchContribution implements IWorkbenchContribution if (!this.queryHistoryEnabled) { this.queryHistoryEnabled = true; + const configurationRegistry = Registry.as(Extensions.Configuration); + configurationRegistry.registerConfiguration({ + id: 'queryHistory', + title: localize('queryHistoryConfigurationTitle', "QueryHistory"), + type: 'object', + properties: { + 'queryHistory.captureEnabled': { + type: 'boolean', + default: true, + scope: ConfigurationScope.APPLICATION, + description: localize('queryHistoryCaptureEnabled', "Whether Query History capture is enabled. If false queries executed will not be captured.") + } + } + }); + // We need this to be running in the background even if the Panel (which is currently the only thing using it) // isn't shown yet. Otherwise the service won't be initialized until the Panel is which means we might miss out // on some events @@ -47,6 +63,14 @@ export class QueryHistoryWorkbenchContribution implements IWorkbenchContribution } }); + CommandsRegistry.registerCommand({ + id: 'queryHistory.toggleCapture', + handler: (accessor) => { + const queryHistoryService = accessor.get(IQueryHistoryService); + queryHistoryService.toggleCaptureEnabled(); + } + }); + const registry = Registry.as(ActionExtensions.WorkbenchActions); registry.registerWorkbenchAction( new SyncActionDescriptor(