mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-02 17:23:40 -05:00
Query History feature (#6579)
* Initial commit * Fix up QueryEventType * Making query history visible in view and open query command (#6479) * Add QueryInfo to query event events * Pull actual query text/connection info for displaying * cons and expand (#6489) * Making query history visible in view and open query command * expand and icons * Failure icon enabled (#6491) * Making query history visible in view and open query command * expand and icons * failure icon enabled * Minor cleanup * Open query with connection and add run query (#6496) * Add initial query-history extension * Fix issues caused by master merge, cleanup and add query-history extension (#6567) * Open query with connection and add run query * Fix issues caused by latest master merges, cleanup and add query-history extension * Remove child nodes (#6568) * Open query with connection and add run query * Fix issues caused by latest master merges, cleanup and add query-history extension * Remove child node expansion * Layering movement and add delete action (#6574) * Open query with connection and add run query * Fix issues caused by latest master merges, cleanup and add query-history extension * Remove child node expansion * Some layering movement and add delete action * Move query tracking into service (#6578) * Open query with connection and add run query * Fix issues caused by latest master merges, cleanup and add query-history extension * Remove child node expansion * Some layering movement and add delete action * Move query history tracking into service * Add comment * Fix actions * Remove unnecessary type * cleanup * Remove unused section of README * Fix merge issues and address PR comments * Fix compile and tslint errors * Change startup function name
This commit is contained in:
@@ -328,8 +328,8 @@ export enum DataProviderType {
|
||||
AgentServicesProvider = 'AgentServicesProvider',
|
||||
CapabilitiesProvider = 'CapabilitiesProvider',
|
||||
ObjectExplorerNodeProvider = 'ObjectExplorerNodeProvider',
|
||||
IconProvider = 'IconProvider',
|
||||
SerializationProvider = 'SerializationProvider'
|
||||
SerializationProvider = 'SerializationProvider',
|
||||
IconProvider = 'IconProvider'
|
||||
}
|
||||
|
||||
export enum DeclarativeDataType {
|
||||
@@ -581,10 +581,96 @@ export class ConnectionProfile {
|
||||
options: { [name: string]: any };
|
||||
|
||||
static createFrom(options: any[]): ConnectionProfile {
|
||||
throw new Error('Method not implemented');
|
||||
// create from options
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export enum SchemaUpdateAction {
|
||||
Delete = 0,
|
||||
Change = 1,
|
||||
Add = 2
|
||||
}
|
||||
|
||||
export enum SchemaDifferenceType {
|
||||
Object = 0,
|
||||
Property = 1
|
||||
}
|
||||
|
||||
export enum SchemaCompareEndpointType {
|
||||
Database = 0,
|
||||
Dacpac = 1
|
||||
}
|
||||
|
||||
export enum SchemaObjectType {
|
||||
Aggregates = 0,
|
||||
ApplicationRoles = 1,
|
||||
Assemblies = 2,
|
||||
AssemblyFiles = 3,
|
||||
AsymmetricKeys = 4,
|
||||
BrokerPriorities = 5,
|
||||
Certificates = 6,
|
||||
ColumnEncryptionKeys = 7,
|
||||
ColumnMasterKeys = 8,
|
||||
Contracts = 9,
|
||||
DatabaseOptions = 10,
|
||||
DatabaseRoles = 11,
|
||||
DatabaseTriggers = 12,
|
||||
Defaults = 13,
|
||||
ExtendedProperties = 14,
|
||||
ExternalDataSources = 15,
|
||||
ExternalFileFormats = 16,
|
||||
ExternalTables = 17,
|
||||
Filegroups = 18,
|
||||
FileTables = 19,
|
||||
FullTextCatalogs = 20,
|
||||
FullTextStoplists = 21,
|
||||
MessageTypes = 22,
|
||||
PartitionFunctions = 23,
|
||||
PartitionSchemes = 24,
|
||||
Permissions = 25,
|
||||
Queues = 26,
|
||||
RemoteServiceBindings = 27,
|
||||
RoleMembership = 28,
|
||||
Rules = 29,
|
||||
ScalarValuedFunctions = 30,
|
||||
SearchPropertyLists = 31,
|
||||
SecurityPolicies = 32,
|
||||
Sequences = 33,
|
||||
Services = 34,
|
||||
Signatures = 35,
|
||||
StoredProcedures = 36,
|
||||
SymmetricKeys = 37,
|
||||
Synonyms = 38,
|
||||
Tables = 39,
|
||||
TableValuedFunctions = 40,
|
||||
UserDefinedDataTypes = 41,
|
||||
UserDefinedTableTypes = 42,
|
||||
ClrUserDefinedTypes = 43,
|
||||
Users = 44,
|
||||
Views = 45,
|
||||
XmlSchemaCollections = 46,
|
||||
Audits = 47,
|
||||
Credentials = 48,
|
||||
CryptographicProviders = 49,
|
||||
DatabaseAuditSpecifications = 50,
|
||||
DatabaseEncryptionKeys = 51,
|
||||
DatabaseScopedCredentials = 52,
|
||||
Endpoints = 53,
|
||||
ErrorMessages = 54,
|
||||
EventNotifications = 55,
|
||||
EventSessions = 56,
|
||||
LinkedServerLogins = 57,
|
||||
LinkedServers = 58,
|
||||
Logins = 59,
|
||||
MasterKeys = 60,
|
||||
Routes = 61,
|
||||
ServerAuditSpecifications = 62,
|
||||
ServerRoleMembership = 63,
|
||||
ServerRoles = 64,
|
||||
ServerTriggers = 65
|
||||
}
|
||||
|
||||
export enum ColumnType {
|
||||
text = 0,
|
||||
checkBox = 1,
|
||||
@@ -602,3 +688,9 @@ export enum NotebookChangeKind {
|
||||
Save = 2,
|
||||
CellExecuted = 3
|
||||
}
|
||||
|
||||
export type QueryEventType =
|
||||
| 'queryStart'
|
||||
| 'queryStop'
|
||||
| 'executionPlan'
|
||||
| 'visualize';
|
||||
|
||||
@@ -10,13 +10,11 @@ import {
|
||||
RunQueryOnConnectionMode, IConnectionResult
|
||||
} from 'sql/platform/connection/common/connectionManagement';
|
||||
import { EditDataInput } from 'sql/workbench/parts/editData/browser/editDataInput';
|
||||
import { IRestoreDialogController } from 'sql/platform/restore/common/restoreService';
|
||||
import { IInsightsDialogService } from 'sql/workbench/services/insights/browser/insightsDialogService';
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService';
|
||||
import { QueryInput } from 'sql/workbench/parts/query/common/queryInput';
|
||||
import { DashboardInput } from 'sql/workbench/parts/dashboard/browser/dashboardInput';
|
||||
import { ProfilerInput } from 'sql/workbench/parts/profiler/browser/profilerInput';
|
||||
import { IBackupUiService } from 'sql/workbench/services/backup/common/backupUiService';
|
||||
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IInsightsConfig } from 'sql/platform/dashboard/browser/insightRegistry';
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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");
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 721 B |
@@ -0,0 +1 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 255 B |
@@ -0,0 +1,54 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 } 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';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { QueryHistoryNode } from 'sql/workbench/parts/queryHistory/browser/queryHistoryNode';
|
||||
|
||||
/**
|
||||
* Provides query history actions
|
||||
*/
|
||||
export class QueryHistoryActionProvider extends ContributableActionProvider {
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private _instantiationService: IInstantiationService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public hasActions(tree: ITree, element: any): boolean {
|
||||
return element instanceof QueryHistoryNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return actions given an element in the tree
|
||||
*/
|
||||
public getActions(tree: ITree, element: any): IAction[] {
|
||||
if (element instanceof QueryHistoryNode && element.info) {
|
||||
return this.getQueryHistoryActions(tree, element);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public hasSecondaryActions(tree: ITree, element: any): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return actions for query history task
|
||||
*/
|
||||
public getQueryHistoryActions(tree: ITree, element: QueryHistoryNode): IAction[] {
|
||||
const actions: IAction[] = [];
|
||||
if (element.info && element.info.queryText && element.info.queryText !== '') {
|
||||
actions.push(this._instantiationService.createInstance(OpenQueryAction, OpenQueryAction.ID, OpenQueryAction.LABEL));
|
||||
actions.push(this._instantiationService.createInstance(RunQueryAction, RunQueryAction.ID, RunQueryAction.LABEL));
|
||||
}
|
||||
actions.push(this._instantiationService.createInstance(DeleteAction, DeleteAction.ID, DeleteAction.LABEL));
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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_PANEL_ID } from 'sql/workbench/parts/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 { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { TogglePanelAction } from 'vs/workbench/browser/panel';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IQueryHistoryService } from 'sql/platform/queryHistory/common/queryHistoryService';
|
||||
import { QueryHistoryNode } from 'sql/workbench/parts/queryHistory/browser/queryHistoryNode';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { openNewQuery } from 'sql/workbench/parts/query/browser/queryActions';
|
||||
|
||||
export class ToggleQueryHistoryAction extends TogglePanelAction {
|
||||
|
||||
public static readonly ID = 'workbench.action.tasks.toggleQueryHistory';
|
||||
public static readonly LABEL = localize('toggleQueryHistory', "Toggle Query History");
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@IPanelService panelService: IPanelService,
|
||||
) {
|
||||
super(id, label, QUERY_HISTORY_PANEL_ID, panelService, 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 async run(element: QueryHistoryNode): Promise<boolean> {
|
||||
if (element instanceof QueryHistoryNode && element.info) {
|
||||
this._queryHistoryService.deleteQueryHistoryInfo(element.info);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public async run(element: QueryHistoryNode): Promise<boolean> {
|
||||
if (element instanceof QueryHistoryNode && element.info) {
|
||||
return this._instantiationService.invokeFunction(openNewQuery, element.info.connectionProfile, element.info.queryText, RunQueryOnConnectionMode.none).then(() => true, () => false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public async run(element: QueryHistoryNode): Promise<boolean> {
|
||||
if (element instanceof QueryHistoryNode && element.info) {
|
||||
return this._instantiationService.invokeFunction(openNewQuery, element.info.connectionProfile, element.info.queryText, RunQueryOnConnectionMode.executeQuery).catch(() => true, () => false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/parts/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 onClick(tree: ITree, element: any, event: IMouseEvent): boolean {
|
||||
return super.onClick(tree, element, event);
|
||||
}
|
||||
|
||||
protected 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 onLeft(tree: ITree, event: IKeyboardEvent): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected onRight(tree: ITree, event: IKeyboardEvent): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected onEnter(tree: ITree, event: IKeyboardEvent): boolean {
|
||||
return super.onEnter(tree, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return actions in the context menu
|
||||
*/
|
||||
public 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(tree, element),
|
||||
getKeyBinding: (action) => this.keybindingService.lookupKeybinding(action.id),
|
||||
onHide: (wasCancelled?: boolean) => {
|
||||
if (wasCancelled) {
|
||||
tree.domFocus();
|
||||
}
|
||||
},
|
||||
getActionsContext: () => (element)
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/parts/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 undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/platform/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) { }
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Dimension } from 'vs/base/browser/dom';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { QueryHistoryView } from 'sql/workbench/parts/queryHistory/browser/queryHistoryView';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { Panel } from 'vs/workbench/browser/panel';
|
||||
import { QUERY_HISTORY_PANEL_ID } from 'sql/workbench/parts/queryHistory/common/constants';
|
||||
|
||||
export class QueryHistoryPanel extends Panel {
|
||||
|
||||
private _queryHistoryView: QueryHistoryView;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
super(QUERY_HISTORY_PANEL_ID, telemetryService, themeService, storageService);
|
||||
}
|
||||
|
||||
public create(parent: HTMLElement): void {
|
||||
super.create(parent);
|
||||
this._queryHistoryView = this.instantiationService.createInstance(QueryHistoryView);
|
||||
this._queryHistoryView.renderBody(parent);
|
||||
}
|
||||
|
||||
public setVisible(visible: boolean): void {
|
||||
super.setVisible(visible);
|
||||
this._queryHistoryView.setVisible(visible);
|
||||
}
|
||||
|
||||
public layout({ height }: Dimension): void {
|
||||
this._queryHistoryView.layout(height);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ITree, IRenderer } from 'vs/base/parts/tree/browser/tree';
|
||||
import { QueryStatus } from 'sql/platform/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/parts/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, $('.icon.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) {
|
||||
dom.addClass(templateData.icon, QueryHistoryRenderer.SUCCESS_CLASS);
|
||||
taskStatus = localize('succeeded', "succeeded");
|
||||
}
|
||||
else if (element.info.status === QueryStatus.Failed) {
|
||||
dom.addClass(templateData.icon, 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);
|
||||
}
|
||||
}
|
||||
141
src/sql/workbench/parts/queryHistory/browser/queryHistoryView.ts
Normal file
141
src/sql/workbench/parts/queryHistory/browser/queryHistoryView.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { DefaultFilter, DefaultDragAndDrop, DefaultAccessibilityProvider } from 'vs/base/parts/tree/browser/treeDefaults';
|
||||
import { localize } from 'vs/nls';
|
||||
import { hide, $, append } from 'vs/base/browser/dom';
|
||||
import { QueryHistoryRenderer } from 'sql/workbench/parts/queryHistory/browser/queryHistoryRenderer';
|
||||
import { QueryHistoryDataSource } from 'sql/workbench/parts/queryHistory/browser/queryHistoryDataSource';
|
||||
import { QueryHistoryController } from 'sql/workbench/parts/queryHistory/browser/queryHistoryController';
|
||||
import { QueryHistoryActionProvider } from 'sql/workbench/parts/queryHistory/browser/queryHistoryActionProvider';
|
||||
import { IExpandableTree } from 'sql/workbench/parts/objectExplorer/browser/treeUpdateUtils';
|
||||
import { IQueryHistoryService } from 'sql/platform/queryHistory/common/queryHistoryService';
|
||||
import { QueryHistoryNode } from 'sql/workbench/parts/queryHistory/browser/queryHistoryNode';
|
||||
import { QueryHistoryInfo } from 'sql/platform/queryHistory/common/queryHistoryInfo';
|
||||
/**
|
||||
* QueryHistoryView implements the dynamic tree view for displaying Query History
|
||||
*/
|
||||
export class QueryHistoryView extends Disposable {
|
||||
private _messages: HTMLElement;
|
||||
private _tree: ITree;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IThemeService private _themeService: IThemeService,
|
||||
@IQueryHistoryService private readonly _queryHistoryService: IQueryHistoryService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the view body
|
||||
*/
|
||||
public 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 actionProvider = instantiationService.createInstance(QueryHistoryActionProvider);
|
||||
const renderer = instantiationService.createInstance(QueryHistoryRenderer);
|
||||
const controller = instantiationService.createInstance(QueryHistoryController, actionProvider);
|
||||
const dnd = new DefaultDragAndDrop();
|
||||
const filter = new DefaultFilter();
|
||||
const sorter = null;
|
||||
const accessibilityProvider = new DefaultAccessibilityProvider();
|
||||
|
||||
return new Tree(treeContainer, {
|
||||
dataSource, renderer, controller, dnd, filter, sorter, accessibilityProvider
|
||||
}, {
|
||||
indentPixels: 10,
|
||||
twistiePixels: 20,
|
||||
ariaLabel: localize({ key: 'queryHistory.regTreeAriaLabel', comment: ['QueryHistory'] }, "Query History")
|
||||
});
|
||||
}
|
||||
|
||||
public refreshTree(): void {
|
||||
let selectedElement: any;
|
||||
let targetsToExpand: any[];
|
||||
|
||||
// Focus
|
||||
this._tree.domFocus();
|
||||
|
||||
if (this._tree) {
|
||||
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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
this._tree.getFocus();
|
||||
}, errors.onUnexpectedError);
|
||||
}
|
||||
|
||||
/**
|
||||
* set the layout of the view
|
||||
*/
|
||||
public layout(height: number): void {
|
||||
this._tree.layout(height);
|
||||
}
|
||||
|
||||
/**
|
||||
* set the visibility of the view
|
||||
*/
|
||||
public setVisible(visible: boolean): void {
|
||||
if (visible) {
|
||||
this._tree.onVisible();
|
||||
} else {
|
||||
this._tree.onHidden();
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/sql/workbench/parts/queryHistory/common/constants.ts
Normal file
9
src/sql/workbench/parts/queryHistory/common/constants.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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_PANEL_ID = 'workbench.panel.queryHistory';
|
||||
@@ -0,0 +1,11 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/platform/lifecycle/common/lifecycle';
|
||||
import { QueryHistoryWorkbenchContribution } from 'sql/workbench/parts/queryHistory/electron-browser/queryHistory';
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(QueryHistoryWorkbenchContribution, LifecyclePhase.Restored);
|
||||
@@ -0,0 +1,74 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/platform/queryHistory/common/queryHistoryService';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { localize } from 'vs/nls';
|
||||
import { SyncActionDescriptor, MenuRegistry, MenuId } 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 { QueryHistoryPanel } from 'sql/workbench/parts/queryHistory/browser/queryHistoryPanel';
|
||||
import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor } from 'vs/workbench/browser/panel';
|
||||
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';
|
||||
|
||||
export class QueryHistoryWorkbenchContribution implements IWorkbenchContribution {
|
||||
|
||||
private queryHistoryEnabled: boolean = false;
|
||||
|
||||
constructor(
|
||||
@IQueryHistoryService _queryHistoryService: IQueryHistoryService
|
||||
) {
|
||||
// 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();
|
||||
|
||||
// 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 registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(
|
||||
new SyncActionDescriptor(
|
||||
ToggleQueryHistoryAction,
|
||||
ToggleQueryHistoryAction.ID,
|
||||
ToggleQueryHistoryAction.LABEL,
|
||||
{ primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_T }),
|
||||
'View: Toggle Query history',
|
||||
localize('viewCategory', "View")
|
||||
);
|
||||
|
||||
// Register Output Panel
|
||||
Registry.as<PanelRegistry>(PanelExtensions.Panels).registerPanel(new PanelDescriptor(
|
||||
QueryHistoryPanel,
|
||||
QUERY_HISTORY_PANEL_ID,
|
||||
localize('queryHistory', "Query History"),
|
||||
'output',
|
||||
20,
|
||||
ToggleQueryHistoryAction.ID
|
||||
));
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
|
||||
group: '4_panels',
|
||||
command: {
|
||||
id: ToggleQueryHistoryAction.ID,
|
||||
title: localize({ key: 'miViewQueryHistory', comment: ['&& denotes a mnemonic'] }, "&&Query History")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user