Move query history into extension (#19794)

* initial

* more

* Remove connectionId

* cleanup

* cleanup

* Remove core contributions, add to panel by default

* Add enabled state

* Update config

* cleanup

* Move

* Remove newlines

* update README
This commit is contained in:
Charles Gagnon
2022-06-22 12:37:32 -07:00
committed by GitHub
parent 8ce19dca8c
commit c24305f9d8
27 changed files with 353 additions and 1138 deletions

View File

@@ -1,56 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
#workbench\.panel\.queryHistory .no-queries-message {
padding: 10px 22px 0 22px;
}
#workbench\.panel\.queryHistory .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .query-history-item {
line-height:22px;
display:flex;
}
/* query history label and description */
#workbench\.panel\.queryHistory .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .query-history-item > .label {
text-overflow: ellipsis;
overflow: hidden;
font-weight: bold;
}
/* style for server name | database name */
#workbench\.panel\.queryHistory .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .query-history-item > .connection-info {
text-overflow: ellipsis;
overflow: hidden;
padding-left: 12px
}
/* style for timing */
#workbench\.panel\.queryHistory .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .query-history-item > .time {
padding-left: 12px;
opacity: .6;
text-overflow: ellipsis;
font-style: italic
}
/* query history icon status */
#workbench\.panel\.queryHistory .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .query-history-item > .query-history-icon {
background-size: 16px;
background-position: left center;
background-repeat: no-repeat;
padding-right: 6px;
width: 22px;
height: 22px;
display: inline-block;
}
#workbench\.panel\.queryHistory .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .query-history-item > .query-history-icon.success {
background-image: url("status_success.svg");
}
#workbench\.panel\.queryHistory .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .query-history-item > .query-history-icon.error {
background-image: url("status_error.svg");
}

View File

@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#d02e00;}</style></defs><title>globalerror_red</title><path class="cls-1" d="M8,0a7.92,7.92,0,0,1,4,1.09A8.15,8.15,0,0,1,14.91,4a8,8,0,0,1,.81,1.91,8,8,0,0,1-.81,6.16A8.15,8.15,0,0,1,12,14.92a8,8,0,0,1-8.07,0,8.15,8.15,0,0,1-2.87-2.87A8,8,0,0,1,1.09,4,8.15,8.15,0,0,1,4,1.11,7.92,7.92,0,0,1,8,0ZM8,15a6.88,6.88,0,0,0,1.86-.25,7,7,0,0,0,4.89-4.89,7.07,7.07,0,0,0,0-3.73A7,7,0,0,0,9.86,1.27a7.07,7.07,0,0,0-3.73,0A7,7,0,0,0,1.25,6.15a7.07,7.07,0,0,0,0,3.73,7,7,0,0,0,4.89,4.89A6.88,6.88,0,0,0,8,15Zm3.46-9.76L8.71,8l2.75,2.76-.7.7L8,8.73,5.24,11.48l-.7-.7L7.29,8,4.54,5.26l.7-.7L8,7.31l2.76-2.75Z"/></svg>

Before

Width:  |  Height:  |  Size: 721 B

View File

@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#3bb44a;}</style></defs><title>success_16x16</title><path class="cls-1" d="M16,3.16,5.48,13.69,0,8.2l.89-.89,4.6,4.59,9.63-9.62Z"/></svg>

Before

Width:  |  Height:  |  Size: 255 B

View File

@@ -1,62 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DeleteAction, OpenQueryAction, RunQueryAction, ClearHistoryAction, ToggleQueryHistoryCaptureAction } from 'sql/workbench/contrib/queryHistory/browser/queryHistoryActions';
import { ITree } from 'vs/base/parts/tree/browser/tree';
import { IAction } from 'vs/base/common/actions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { QueryHistoryNode } from 'sql/workbench/contrib/queryHistory/browser/queryHistoryNode';
/**
* Provides query history actions
*/
export class QueryHistoryActionProvider {
private _actions: {
openQueryAction: IAction,
runQueryAction: IAction,
deleteAction: IAction,
clearAction: IAction,
toggleCaptureAction: IAction
};
constructor(
@IInstantiationService instantiationService: IInstantiationService
) {
this._actions = {
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),
toggleCaptureAction: instantiationService.createInstance(ToggleQueryHistoryCaptureAction, ToggleQueryHistoryCaptureAction.ID, ToggleQueryHistoryCaptureAction.LABEL)
};
}
public hasActions(element: any): boolean {
return element instanceof QueryHistoryNode;
}
/**
* Return actions for a selected node - or the default actions if no node is selected
*/
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);
actions.push(this._actions.runQueryAction);
}
actions.push(this._actions.deleteAction);
}
// Common actions we want to always display
actions.push(this._actions.clearAction, this._actions.toggleCaptureAction);
return actions;
}
public hasSecondaryActions(tree: ITree, element: any): boolean {
return false;
}
}

View File

@@ -1,141 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { QUERY_HISTORY_VIEW_ID } from 'sql/workbench/contrib/queryHistory/common/constants';
import { RunQueryOnConnectionMode } from 'sql/platform/connection/common/connectionManagement';
import { Action } from 'vs/base/common/actions';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { localize } from 'vs/nls';
import { IQueryHistoryService } from 'sql/workbench/services/queryHistory/common/queryHistoryService';
import { QueryHistoryNode } from 'sql/workbench/contrib/queryHistory/browser/queryHistoryNode';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { openNewQuery } from 'sql/workbench/contrib/query/browser/queryActions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IViewsService, IViewDescriptorService } from 'vs/workbench/common/views';
import { ToggleViewAction } from 'sql/workbench/browser/actions/layoutActions';
export class ToggleQueryHistoryAction extends ToggleViewAction {
public static readonly ID = 'workbench.action.tasks.toggleQueryHistory';
public static readonly LABEL = localize('toggleQueryHistory', "Toggle Query History");
constructor(
id: string, label: string,
@IViewsService viewsService: IViewsService,
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
@IContextKeyService contextKeyService: IContextKeyService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService
) {
super(id, label, QUERY_HISTORY_VIEW_ID, viewsService, viewDescriptorService, contextKeyService, layoutService);
}
}
export class DeleteAction extends Action {
public static ID = 'queryHistory.delete';
public static LABEL = localize('queryHistory.delete', "Delete");
constructor(
id: string,
label: string,
@IQueryHistoryService private _queryHistoryService: IQueryHistoryService
) {
super(id, label);
}
public override async run(element: QueryHistoryNode): Promise<void> {
if (element instanceof QueryHistoryNode && element.info) {
this._queryHistoryService.deleteQueryHistoryInfo(element.info);
}
}
}
export class ClearHistoryAction extends Action {
public static ID = 'queryHistory.clear';
public static LABEL = localize('queryHistory.clearLabel', "Clear All History");
constructor(
id: string,
label: string,
@ICommandService private _commandService: ICommandService
) {
super(id, label, 'clear-query-history-action codicon-clear-all');
}
public override async run(): Promise<void> {
return this._commandService.executeCommand('queryHistory.clear');
}
}
export class OpenQueryAction extends Action {
public static ID = 'queryHistory.openQuery';
public static LABEL = localize('queryHistory.openQuery', "Open Query");
constructor(
id: string,
label: string,
@IInstantiationService private _instantiationService: IInstantiationService
) {
super(id, label);
}
public override async run(element: QueryHistoryNode): Promise<void> {
if (element instanceof QueryHistoryNode && element.info) {
return this._instantiationService.invokeFunction(openNewQuery, element.info.connectionProfile, element.info.queryText, RunQueryOnConnectionMode.none).then();
}
}
}
export class RunQueryAction extends Action {
public static ID = 'queryHistory.runQuery';
public static LABEL = localize('queryHistory.runQuery', "Run Query");
constructor(
id: string,
label: string,
@IInstantiationService private _instantiationService: IInstantiationService
) {
super(id, label);
}
public override async run(element: QueryHistoryNode): Promise<void> {
if (element instanceof QueryHistoryNode && element.info) {
return this._instantiationService.invokeFunction(openNewQuery, element.info.connectionProfile, element.info.queryText, RunQueryOnConnectionMode.executeQuery).then();
}
}
}
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 override async run(): Promise<void> {
return this._commandService.executeCommand('queryHistory.toggleCapture');
}
private setClassAndLabel(enabled: boolean) {
if (enabled) {
this.class = 'toggle-query-history-capture-action codicon-debug-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");
}
}
}

View File

@@ -1,80 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITree, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree';
import * as treeDefaults from 'vs/base/parts/tree/browser/treeDefaults';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { QueryHistoryActionProvider } from 'sql/workbench/contrib/queryHistory/browser/queryHistoryActionProvider';
/**
* Extends the tree controller to handle clicks on the tree elements for a Query History node
*/
export class QueryHistoryController extends treeDefaults.DefaultController {
constructor(
private actionProvider: QueryHistoryActionProvider,
@IContextMenuService private contextMenuService: IContextMenuService,
@IKeybindingService private keybindingService: IKeybindingService
) {
super({ clickBehavior: treeDefaults.ClickBehavior.ON_MOUSE_DOWN });
}
public override onClick(tree: ITree, element: any, event: IMouseEvent): boolean {
return super.onClick(tree, element, event);
}
protected override onLeftClick(tree: ITree, element: any, event: IMouseEvent, origin: string = 'mouse'): boolean {
return super.onLeftClick(tree, element, event, origin);
}
// Do not allow left / right to expand and collapse groups #7848
protected override onLeft(tree: ITree, event: IKeyboardEvent): boolean {
return true;
}
protected override onRight(tree: ITree, event: IKeyboardEvent): boolean {
return true;
}
protected override onEnter(tree: ITree, event: IKeyboardEvent): boolean {
return super.onEnter(tree, event);
}
/**
* Return actions in the context menu
*/
public override onContextMenu(tree: ITree, element: any, event: ContextMenuEvent): boolean {
if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') {
return false;
}
// Check if clicked on some element
if (element === tree.getInput()) {
return false;
}
event.preventDefault();
event.stopPropagation();
tree.setFocus(element);
let anchor = { x: event.posx + 1, y: event.posy };
this.contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActions: () => this.actionProvider.getActions(element),
getKeyBinding: (action) => this.keybindingService.lookupKeybinding(action.id),
onHide: (wasCancelled?: boolean) => {
if (wasCancelled) {
tree.domFocus();
}
},
getActionsContext: () => (element)
});
return true;
}
}

View File

@@ -1,51 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITree, IDataSource } from 'vs/base/parts/tree/browser/tree';
import { QueryHistoryNode } from 'sql/workbench/contrib/queryHistory/browser/queryHistoryNode';
/**
* Implements the DataSource (that returns a parent/children of an element) for the query history
*/
export class QueryHistoryDataSource implements IDataSource {
/**
* Returns the unique identifier of the given element.
* No more than one element may use a given identifier.
*/
public getId(tree: ITree, element: any): string {
if (element instanceof QueryHistoryNode && element.info) {
return element.info.id;
}
return '';
}
/**
* Returns a boolean value indicating whether the element has children.
*/
public hasChildren(tree: ITree, element: any): boolean {
if (element instanceof QueryHistoryNode) {
return element.hasChildren;
}
return false;
}
/**
* Returns the element's children as an array
*/
public async getChildren(tree: ITree, element: any): Promise<any> {
if (element instanceof QueryHistoryNode) {
return element.children;
}
return undefined;
}
/**
* Returns the element's parent
*/
public async getParent(tree: ITree, element: any): Promise<any> {
return undefined;
}
}

View File

@@ -1,19 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { QueryHistoryInfo } from 'sql/workbench/services/queryHistory/common/queryHistoryInfo';
/**
* Wrapper around a QueryHistoryInfo for displaying in the panel TreeView
*/
export class QueryHistoryNode {
public hasChildren: boolean = false;
public children: QueryHistoryNode[] = [];
constructor(
public info: QueryHistoryInfo | undefined) { }
}

View File

@@ -1,96 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/queryHistoryPanel';
import { ITree, IRenderer } from 'vs/base/parts/tree/browser/tree';
import { QueryStatus } from 'sql/workbench/services/queryHistory/common/queryHistoryInfo';
import * as dom from 'vs/base/browser/dom';
import { localize } from 'vs/nls';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { QueryHistoryNode } from 'sql/workbench/contrib/queryHistory/browser/queryHistoryNode';
const $ = dom.$;
export interface IQueryHistoryItemTemplateData {
root: HTMLElement;
icon: HTMLElement;
label: HTMLSpanElement;
connectionInfo: HTMLSpanElement;
time: HTMLSpanElement;
disposables: Array<IDisposable>;
}
/**
* Renders the tree items.
* Uses the dom template to render task history.
*/
export class QueryHistoryRenderer implements IRenderer {
public static readonly QUERYHISTORYOBJECT_HEIGHT = 22;
private static readonly FAIL_CLASS = 'error';
private static readonly SUCCESS_CLASS = 'success';
/**
* Returns the element's height in the tree, in pixels.
*/
public getHeight(tree: ITree, element: QueryHistoryNode): number {
return QueryHistoryRenderer.QUERYHISTORYOBJECT_HEIGHT;
}
/**
* Returns a template ID for a given element.
*/
public getTemplateId(tree: ITree, element: QueryHistoryNode): string {
return element.info?.id || '';
}
/**
* Render template in a dom element based on template id
*/
public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
const taskTemplate: IQueryHistoryItemTemplateData = Object.create(null);
taskTemplate.root = dom.append(container, $('.query-history-item'));
taskTemplate.icon = dom.append(taskTemplate.root, $('.codicon.query-history-icon'));
taskTemplate.label = dom.append(taskTemplate.root, $('.label'));
taskTemplate.connectionInfo = dom.append(taskTemplate.root, $('.connection-info'));
taskTemplate.time = dom.append(taskTemplate.root, $('.time'));
taskTemplate.disposables = [];
return taskTemplate;
}
/**
* Render a element, given an object bag returned by the template
*/
public renderElement(tree: ITree, element: QueryHistoryNode, templateId: string, templateData: IQueryHistoryItemTemplateData): void {
let taskStatus = '';
if (element && element.info) {
templateData.icon.className = 'query-history-icon';
if (element.info.status === QueryStatus.Succeeded) {
templateData.icon.classList.add(QueryHistoryRenderer.SUCCESS_CLASS);
taskStatus = localize('succeeded', "succeeded");
}
else if (element.info.status === QueryStatus.Failed) {
templateData.icon.classList.add(QueryHistoryRenderer.FAIL_CLASS);
taskStatus = localize('failed', "failed");
}
templateData.icon.title = taskStatus;
templateData.label.textContent = element.info.queryText;
templateData.label.title = templateData.label.textContent;
// Determine the target name and set hover text equal to that
const connectionInfo = `${element.info.connectionProfile.serverName}|${element.info.database}`;
templateData.connectionInfo.textContent = connectionInfo;
templateData.connectionInfo.title = templateData.connectionInfo.textContent;
templateData.time.textContent = element.info.startTime.toLocaleString();
templateData.time.title = templateData.time.textContent;
}
}
public disposeTemplate(tree: ITree, templateId: string, templateData: IQueryHistoryItemTemplateData): void {
dispose(templateData.disposables);
}
}

View File

@@ -1,153 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as errors from 'vs/base/common/errors';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { attachListStyler } from 'vs/platform/theme/common/styler';
import { ITree } from 'vs/base/parts/tree/browser/tree';
import { DefaultFilter, DefaultDragAndDrop, DefaultAccessibilityProvider } from 'vs/base/parts/tree/browser/treeDefaults';
import { localize } from 'vs/nls';
import { hide, $, append, show } from 'vs/base/browser/dom';
import { QueryHistoryRenderer } from 'sql/workbench/contrib/queryHistory/browser/queryHistoryRenderer';
import { QueryHistoryDataSource } from 'sql/workbench/contrib/queryHistory/browser/queryHistoryDataSource';
import { QueryHistoryController } from 'sql/workbench/contrib/queryHistory/browser/queryHistoryController';
import { QueryHistoryActionProvider } from 'sql/workbench/contrib/queryHistory/browser/queryHistoryActionProvider';
import { IExpandableTree } from 'sql/workbench/services/objectExplorer/browser/treeUpdateUtils';
import { IQueryHistoryService } from 'sql/workbench/services/queryHistory/common/queryHistoryService';
import { QueryHistoryNode } from 'sql/workbench/contrib/queryHistory/browser/queryHistoryNode';
import { QueryHistoryInfo } from 'sql/workbench/services/queryHistory/common/queryHistoryInfo';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
import { IViewDescriptorService } from 'vs/workbench/common/views';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IOpenerService } from 'vs/platform/opener/common/opener';
/**
* QueryHistoryView implements the dynamic tree view for displaying Query History
*/
export class QueryHistoryView extends ViewPane {
private _messages!: HTMLElement;
private _tree!: ITree;
private _actionProvider: QueryHistoryActionProvider;
constructor(
options: IViewPaneOptions,
@IInstantiationService instantiationService: IInstantiationService,
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
@IConfigurationService configurationService: IConfigurationService,
@ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService contextKeyService: IContextKeyService,
@IContextMenuService contextMenuService: IContextMenuService,
@IKeybindingService keybindingService: IKeybindingService,
@IOpenerService openerService: IOpenerService,
@IThemeService themeService: IThemeService,
@IQueryHistoryService private readonly queryHistoryService: IQueryHistoryService
) {
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
this._actionProvider = this.instantiationService.createInstance(QueryHistoryActionProvider);
}
/**
* Render the view body
*/
public override renderBody(container: HTMLElement): void {
// Add div to display no task executed message
this._messages = append(container, $('div.no-queries-message'));
const noQueriesMessage = localize('noQueriesMessage', "No queries to display.");
append(this._messages, $('span')).innerText = noQueriesMessage;
this._tree = this._register(this.createQueryHistoryTree(container, this.instantiationService));
// Theme styler
this._register(attachListStyler(this._tree, this.themeService));
this.queryHistoryService.onInfosUpdated((nodes: QueryHistoryInfo[]) => {
this.refreshTree();
});
// Refresh the tree so we correctly update if there were already existing history items
this.refreshTree();
}
/**
* Create a task history tree
*/
public createQueryHistoryTree(treeContainer: HTMLElement, instantiationService: IInstantiationService): Tree {
const dataSource = instantiationService.createInstance(QueryHistoryDataSource);
const renderer = instantiationService.createInstance(QueryHistoryRenderer);
const controller = instantiationService.createInstance(QueryHistoryController, this._actionProvider);
const dnd = new DefaultDragAndDrop();
const filter = new DefaultFilter();
const accessibilityProvider = new DefaultAccessibilityProvider();
return new Tree(treeContainer, {
dataSource, renderer, controller, dnd, filter, sorter: undefined, accessibilityProvider
}, {
indentPixels: 10,
twistiePixels: 20,
ariaLabel: localize({ key: 'queryHistory.regTreeAriaLabel', comment: ['QueryHistory'] }, "Query History")
});
}
public refreshTree(): void {
let selectedElement: any;
let targetsToExpand: any[];
const selection = this._tree.getSelection();
if (selection && selection.length === 1) {
selectedElement = <any>selection[0];
}
// convert to old VS Code tree interface with expandable methods
const expandableTree: IExpandableTree = <IExpandableTree>this._tree;
targetsToExpand = expandableTree.getExpandedElements();
const nodes: QueryHistoryNode[] = this.queryHistoryService.getQueryHistoryInfos().map(i => new QueryHistoryNode(i));
if (nodes.length > 0) {
hide(this._messages);
} else {
show(this._messages);
}
// Set the tree input - root node is just an empty container node
const rootNode = new QueryHistoryNode(undefined);
rootNode.children = nodes;
rootNode.hasChildren = true;
this._tree.setInput(rootNode).then(() => {
// Make sure to expand all folders that were expanded in the previous session
if (targetsToExpand) {
this._tree.expandAll(targetsToExpand);
}
if (selectedElement) {
this._tree.select(selectedElement);
}
}, errors.onUnexpectedError);
}
/**
* set the layout of the view
*/
public override layout(height: number): void {
this._tree.layout(height);
}
/**
* set the visibility of the view
*/
public override setVisible(visible: boolean): void {
if (visible) {
this._tree.onVisible();
} else {
this._tree.onHidden();
}
}
}

View File

@@ -1,10 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Query History panel id
*/
export const QUERY_HISTORY_CONTAINER_ID = 'workbench.panel.queryHistory';
export const QUERY_HISTORY_VIEW_ID = 'workbench.panel.queryHistory.view';

View File

@@ -1,11 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { QueryHistoryWorkbenchContribution } from 'sql/workbench/contrib/queryHistory/electron-browser/queryHistory';
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(QueryHistoryWorkbenchContribution, LifecyclePhase.Restored);

View File

@@ -1,155 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IQueryHistoryService } from 'sql/workbench/services/queryHistory/common/queryHistoryService';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { localize } from 'vs/nls';
import { SyncActionDescriptor, MenuRegistry, MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
import { Registry } from 'vs/platform/registry/common/platform';
import { ToggleQueryHistoryAction } from 'sql/workbench/contrib/queryHistory/browser/queryHistoryActions';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IConfigurationRegistry, ConfigurationScope, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsRegistry, ViewContainerLocation } from 'vs/workbench/common/views';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { QUERY_HISTORY_CONTAINER_ID, QUERY_HISTORY_VIEW_ID } from 'sql/workbench/contrib/queryHistory/common/constants';
import { QueryHistoryView } from 'sql/workbench/contrib/queryHistory/browser/queryHistoryView';
import { ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { Codicon } from 'vs/base/common/codicons';
export class QueryHistoryWorkbenchContribution implements IWorkbenchContribution {
private queryHistoryEnabled: boolean = false;
constructor(
@IQueryHistoryService _queryHistoryService: IQueryHistoryService
) {
// This feature is in preview so for now hide it behind a flag. We expose this as a command
// so that the query-history extension can call it. We eventually want to move all this into
// the extension itself so this should be a temporary workaround
CommandsRegistry.registerCommand({
id: 'queryHistory.enableQueryHistory',
handler: () => {
// This should never be called more than once, but just in case
// we don't want to try and register multiple times
if (!this.queryHistoryEnabled) {
this.queryHistoryEnabled = true;
const configurationRegistry = Registry.as<IConfigurationRegistry>(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
_queryHistoryService.start();
registerAction2(class extends Action2 {
constructor() {
super({
id: `queryHistory.clear`,
title: { value: localize('queryHistory.clearLabel', "Clear All History"), original: 'Clear All History' },
icon: Codicon.clearAll,
menu: {
id: MenuId.ViewTitle,
group: 'navigation',
when: ContextKeyEqualsExpr.create('view', QUERY_HISTORY_VIEW_ID),
order: 10
}
});
}
run(accessor: ServicesAccessor) {
const queryHistoryService = accessor.get(IQueryHistoryService);
queryHistoryService.clearQueryHistory();
}
});
const PAUSE_QUERY_HISTORY_CAPTURE = localize('queryHistory.disableCapture', "Pause Query History Capture");
const START_QUERY_HISTORY_CAPTURE = localize('queryHistory.enableCapture', "Start Query History Capture");
registerAction2(class extends Action2 {
constructor() {
super({
id: `queryHistory.toggleCapture`,
title: { value: START_QUERY_HISTORY_CAPTURE, original: 'Start Query History Capture' },
tooltip: START_QUERY_HISTORY_CAPTURE,
icon: Codicon.play,
menu: {
id: MenuId.ViewTitle,
group: 'navigation',
when: ContextKeyEqualsExpr.create('view', QUERY_HISTORY_VIEW_ID),
order: 20
},
toggled: {
condition: ContextKeyEqualsExpr.create('config.queryHistory.captureEnabled', true),
title: { value: PAUSE_QUERY_HISTORY_CAPTURE, original: 'Pause Query History Capture' },
tooltip: PAUSE_QUERY_HISTORY_CAPTURE,
icon: { id: 'debug-pause' }
}
});
}
run(accessor: ServicesAccessor) {
const queryHistoryService = accessor.get(IQueryHistoryService);
queryHistoryService.toggleCaptureEnabled();
}
});
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(
SyncActionDescriptor.create(
ToggleQueryHistoryAction,
ToggleQueryHistoryAction.ID,
ToggleQueryHistoryAction.LABEL,
{ primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_T }),
'View: Toggle Query history',
localize('viewCategory', "View")
);
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
group: '4_panels',
command: {
id: ToggleQueryHistoryAction.ID,
title: localize({ key: 'miViewQueryHistory', comment: ['&& denotes a mnemonic'] }, "&&Query History")
},
order: 2
});
const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
id: QUERY_HISTORY_CONTAINER_ID,
title: localize('queryHistory', "Query History"),
hideIfEmpty: true,
order: 20,
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [QUERY_HISTORY_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]),
storageId: `${QUERY_HISTORY_CONTAINER_ID}.storage`,
}, ViewContainerLocation.Panel);
Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry).registerViews([{
id: QUERY_HISTORY_VIEW_ID,
name: localize('queryHistory', "Query History"),
canToggleVisibility: false,
canMoveView: false,
ctorDescriptor: new SyncDescriptor(QueryHistoryView),
}], VIEW_CONTAINER);
}
}
});
}
}

View File

@@ -287,7 +287,6 @@ export class QueryModelService implements IQueryModelService {
});
queryRunner.onQueryEnd(totalMilliseconds => {
this._onRunQueryComplete.fire(queryRunner.uri);
// fire extensibility API event
let event: IQueryEvent = {
type: 'queryStop',

View File

@@ -1,34 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { generateUuid } from 'vs/base/common/uuid';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
export enum QueryStatus {
Succeeded = 0,
Failed = 1,
Nothing = 2
}
/**
* Contains information about a query that was ran
*/
export class QueryHistoryInfo {
public database: string;
public status?: QueryStatus;
public readonly id = generateUuid();
constructor(
public queryText: string,
public connectionProfile: IConnectionProfile,
public startTime: Date,
status?: QueryStatus) {
this.database = connectionProfile?.databaseName ?? '';
this.status = status;
}
}

View File

@@ -1,55 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { QueryHistoryInfo } from 'sql/workbench/services/queryHistory/common/queryHistoryInfo';
import { Event } from 'vs/base/common/event';
export const SERVICE_ID = 'queryHistoryService';
export const IQueryHistoryService = createDecorator<IQueryHistoryService>(SERVICE_ID);
/**
* Service that collects the results of executed queries
*/
export interface IQueryHistoryService {
_serviceBrand: any;
/**
* Event fired whenever the collection of stored QueryHistoryInfo's is updated
*/
onInfosUpdated: Event<QueryHistoryInfo[]>;
/**
* Event fired whenever the Query History capture state has changed
*/
onQueryHistoryCaptureChanged: Event<boolean>;
/**
* 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<void>;
/**
* Starts the Query History Service
*/
start(): void;
}

View File

@@ -1,135 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IQueryHistoryService } from 'sql/workbench/services/queryHistory/common/queryHistoryService';
import { IQueryModelService, IQueryEvent } from 'sql/workbench/services/query/common/queryModel';
import { IModelService } from 'vs/editor/common/services/modelService';
import { URI } from 'vs/base/common/uri';
import { Range } from 'vs/editor/common/core/range';
import { QueryHistoryInfo, QueryStatus } from 'sql/workbench/services/queryHistory/common/queryHistoryInfo';
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';
/**
* Service that collects the results of executed queries
*/
export class QueryHistoryService extends Disposable implements IQueryHistoryService {
_serviceBrand: any;
// MEMBER VARIABLES ////////////////////////////////////////////////////
private _infos: QueryHistoryInfo[] = [];
private _onInfosUpdated: Emitter<QueryHistoryInfo[]> = new Emitter<QueryHistoryInfo[]>();
private _onQueryHistoryCaptureChanged: Emitter<boolean> = new Emitter<boolean>();
private _captureEnabled: boolean;
private _started: boolean = false;
// EVENTS //////////////////////////////////////////////////////////////
public get onInfosUpdated(): Event<QueryHistoryInfo[]> { return this._onInfosUpdated.event; }
public get onQueryHistoryCaptureChanged(): Event<boolean> { return this._onQueryHistoryCaptureChanged.event; }
// CONSTRUCTOR /////////////////////////////////////////////////////////
constructor(
@IQueryModelService _queryModelService: IQueryModelService,
@IModelService _modelService: IModelService,
@IConnectionManagementService _connectionManagementService: IConnectionManagementService,
@IConfigurationService private _configurationService: IConfigurationService
) {
super();
this.updateCaptureEnabled();
this._register(this._configurationService.onDidChangeConfiguration((e: IConfigurationChangeEvent) => {
if (e.affectedKeys.find(x => x === 'queryHistory.captureEnabled')) {
this.updateCaptureEnabled();
}
}));
this._register(_queryModelService.onQueryEvent((e: IQueryEvent) => {
if (this._captureEnabled && e.type === 'queryStop') {
const uri: URI = URI.parse(e.uri);
const model = _modelService.getModel(uri);
if (model) {
// 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
const text: string = e.queryInfo.range && e.queryInfo.range.length > 0 ?
model.getValueInRange(new Range(
e.queryInfo.range[0].startLineNumber,
e.queryInfo.range[0].startColumn,
e.queryInfo.range[0].endLineNumber,
e.queryInfo.range[0].endColumn)) :
// If no specific selection get the entire text
model.getValue();
const newInfo = new QueryHistoryInfo(text, _connectionManagementService.getConnectionProfile(e.uri), new Date(), QueryStatus.Succeeded);
// icon as required (for now logic is if any message has error query has error)
let error: boolean = false;
e.queryInfo.messages.forEach(x => error = error || x.isError);
if (error) {
newInfo.status = QueryStatus.Failed;
}
// Append new node to beginning of array so the newest ones are at the top
this._infos.unshift(newInfo);
this._onInfosUpdated.fire(this._infos);
}
}
}));
}
/**
* Whether Query History capture is currently enabled
*/
public get captureEnabled(): boolean {
return this._captureEnabled;
}
/**
* Gets all the current query history infos
*/
public getQueryHistoryInfos(): QueryHistoryInfo[] {
return this._infos;
}
/**
* Deletes infos from the cache with the same ID as the given QueryHistoryInfo
* @param info The QueryHistoryInfo to delete
*/
public deleteQueryHistoryInfo(info: QueryHistoryInfo): void {
this._infos = this._infos.filter(i => i.id !== info.id);
this._onInfosUpdated.fire(this._infos);
}
/**
* Clears all infos from the cache
*/
public clearQueryHistory(): void {
this._infos = [];
this._onInfosUpdated.fire(this._infos);
}
public async toggleCaptureEnabled(): Promise<void> {
const captureEnabled = !!this._configurationService.getValue<boolean>('queryHistory.captureEnabled');
await this._configurationService.updateValue('queryHistory.captureEnabled', !captureEnabled);
}
private updateCaptureEnabled(): void {
const currentCaptureEnabled = this._captureEnabled;
this._captureEnabled = !!this._configurationService.getValue<boolean>('queryHistory.captureEnabled') && this._started;
if (currentCaptureEnabled !== this._captureEnabled) {
this._onQueryHistoryCaptureChanged.fire(this._captureEnabled);
}
}
/**
* Method to force initialization of the service so that it can start tracking query events
*/
public start(): void {
this._started = true;
this.updateCaptureEnabled();
}
}