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:
Charles Gagnon
2019-09-11 08:23:59 -07:00
committed by GitHub
parent 7d49e75e46
commit c4b90360a5
36 changed files with 3446 additions and 19 deletions

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import QueryRunner from 'sql/platform/query/common/queryRunner';
import QueryRunner, { IQueryMessage } from 'sql/platform/query/common/queryRunner';
import { DataService } from 'sql/workbench/parts/grid/common/dataService';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event } from 'vs/base/common/event';
@@ -31,9 +31,15 @@ export interface IQueryPlanInfo {
planXml: string;
}
export interface IQueryInfo {
selection: ISelectionData[];
messages: IQueryMessage[];
}
export interface IQueryEvent {
type: queryeditor.QueryEvent;
type: queryeditor.QueryEventType;
uri: string;
queryInfo: IQueryInfo;
params?: any;
}

View File

@@ -283,7 +283,12 @@ export class QueryModelService implements IQueryModelService {
// fire extensibility API event
let event: IQueryEvent = {
type: 'queryStop',
uri: uri
uri: uri,
queryInfo:
{
selection: info.selection,
messages: info.queryRunner.messages
}
};
this._onQueryEvent.fire(event);
@@ -296,7 +301,12 @@ export class QueryModelService implements IQueryModelService {
// fire extensibility API event
let event: IQueryEvent = {
type: 'queryStart',
uri: uri
uri: uri,
queryInfo:
{
selection: info.selection,
messages: info.queryRunner.messages
}
};
this._onQueryEvent.fire(event);
@@ -307,7 +317,12 @@ export class QueryModelService implements IQueryModelService {
let event: IQueryEvent = {
type: 'queryUpdate',
uri: uri
uri: uri,
queryInfo:
{
selection: info.selection,
messages: info.queryRunner.messages
}
};
this._onQueryEvent.fire(event);
@@ -319,6 +334,11 @@ export class QueryModelService implements IQueryModelService {
let event: IQueryEvent = {
type: 'executionPlan',
uri: planInfo.fileUri,
queryInfo:
{
selection: info.selection,
messages: info.queryRunner.messages
},
params: planInfo
};
this._onQueryEvent.fire(event);
@@ -328,6 +348,11 @@ export class QueryModelService implements IQueryModelService {
let event: IQueryEvent = {
type: 'visualize',
uri: uri,
queryInfo:
{
selection: info.selection,
messages: info.queryRunner.messages
},
params: resultSetInfo
};
this._onQueryEvent.fire(event);
@@ -443,7 +468,12 @@ export class QueryModelService implements IQueryModelService {
// fire extensibility API event
let event: IQueryEvent = {
type: 'queryStop',
uri: ownerUri
uri: ownerUri,
queryInfo:
{
selection: info.selection,
messages: info.queryRunner.messages
},
};
this._onQueryEvent.fire(event);
@@ -455,7 +485,12 @@ export class QueryModelService implements IQueryModelService {
// fire extensibility API event
let event: IQueryEvent = {
type: 'queryStart',
uri: ownerUri
uri: ownerUri,
queryInfo:
{
selection: info.selection,
messages: info.queryRunner.messages
},
};
this._onQueryEvent.fire(event);

View File

@@ -44,6 +44,7 @@ export interface IQueryMessage extends azdata.IResultMessage {
export default class QueryRunner extends Disposable {
// MEMBER VARIABLES ////////////////////////////////////////////////////
private _resultLineOffset: number;
private _resultColumnOffset: number;
private _totalElapsedMilliseconds: number = 0;
private _isExecuting: boolean = false;
private _hasCompleted: boolean = false;
@@ -179,6 +180,7 @@ export default class QueryRunner extends Disposable {
if (types.isObject(input) || types.isUndefinedOrNull(input)) {
// Update internal state to show that we're executing the query
this._resultLineOffset = input ? input.startLine : 0;
this._resultColumnOffset = input ? input.startColumn : 0;
this._isExecuting = true;
this._totalElapsedMilliseconds = 0;
// TODO issue #228 add statusview callbacks here
@@ -243,8 +245,10 @@ export default class QueryRunner extends Disposable {
this._batchSets.map(batch => {
if (batch.selection) {
batch.selection.startLine = batch.selection.startLine + this._resultLineOffset;
batch.selection.endLine = batch.selection.endLine + this._resultLineOffset;
batch.selection.startLine += this._resultLineOffset;
batch.selection.startColumn += this._resultColumnOffset;
batch.selection.endLine += this._resultLineOffset;
batch.selection.endColumn += this._resultColumnOffset;
}
});
@@ -271,7 +275,9 @@ export default class QueryRunner extends Disposable {
// Recalculate the start and end lines, relative to the result line offset
if (batch.selection) {
batch.selection.startLine += this._resultLineOffset;
batch.selection.startColumn += this._resultColumnOffset;
batch.selection.endLine += this._resultLineOffset;
batch.selection.endColumn += this._resultColumnOffset;
}
// Set the result sets as an empty array so that as result sets complete we can add to the list

View File

@@ -0,0 +1,34 @@
/*---------------------------------------------------------------------------------------------
* 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 ? connectionProfile.databaseName : '';
this.status = status;
}
}

View File

@@ -0,0 +1,25 @@
/*---------------------------------------------------------------------------------------------
* 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/platform/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;
onInfosUpdated: Event<QueryHistoryInfo[]>;
getQueryHistoryInfos(): QueryHistoryInfo[];
deleteQueryHistoryInfo(info: QueryHistoryInfo): void;
start(): void;
}

View File

@@ -0,0 +1,87 @@
/*---------------------------------------------------------------------------------------------
* 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/platform/queryHistory/common/queryHistoryService.ts';
import { IQueryModelService, IQueryEvent } from 'sql/platform/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/platform/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';
/**
* 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[]>();
// EVENTS //////////////////////////////////////////////////////////////
public get onInfosUpdated(): Event<QueryHistoryInfo[]> { return this._onInfosUpdated.event; }
// CONSTRUCTOR /////////////////////////////////////////////////////////
constructor(
@IQueryModelService _queryModelService: IQueryModelService,
@IModelService _modelService: IModelService,
@IConnectionManagementService _connectionManagementService: IConnectionManagementService
) {
super();
this._register(_queryModelService.onQueryEvent((e: IQueryEvent) => {
if (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
const text: string = _modelService.getModel(uri).getValueInRange(new Range(
e.queryInfo.selection[0].startLine + 1,
e.queryInfo.selection[0].startColumn + 1,
e.queryInfo.selection[0].endLine,
e.queryInfo.selection[0].endColumn + 1));
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);
}
}));
}
/**
* 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 TheQueryHistoryInfo to delete
*/
public deleteQueryHistoryInfo(info: QueryHistoryInfo) {
this._infos = this._infos.filter(i => i.id !== info.id);
this._onInfosUpdated.fire(this._infos);
}
/**
* Method to force initialization of the service so that it can start tracking query events
*/
public start(): void {
}
}