mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Alanren/profiler filter (#3760)
* profiler filter * add test cases * perf improvement with bulk insert * update dependency version and address comments
This commit is contained in:
@@ -67,7 +67,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.11",
|
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.15",
|
||||||
"opener": "^1.4.3",
|
"opener": "^1.4.3",
|
||||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
||||||
"vscode-extension-telemetry": "0.0.18",
|
"vscode-extension-telemetry": "0.0.18",
|
||||||
|
|||||||
@@ -64,9 +64,9 @@ core-util-is@~1.0.0:
|
|||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||||
|
|
||||||
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.11":
|
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.15":
|
||||||
version "0.2.11"
|
version "0.2.15"
|
||||||
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/bc80d2226699d23f45a2ec26129cbcdee4781ca9"
|
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/a2cd2db109de882f0959f7b6421c86afa585f460"
|
||||||
dependencies:
|
dependencies:
|
||||||
vscode-languageclient "3.5.1"
|
vscode-languageclient "3.5.1"
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
"update-grammar": "node ../../build/npm/update-grammar.js Microsoft/vscode-mssql syntaxes/SQL.plist ./syntaxes/sql.tmLanguage.json"
|
"update-grammar": "node ../../build/npm/update-grammar.js Microsoft/vscode-mssql syntaxes/SQL.plist ./syntaxes/sql.tmLanguage.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.11",
|
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.15",
|
||||||
"opener": "^1.4.3",
|
"opener": "^1.4.3",
|
||||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
||||||
"vscode-extension-telemetry": "^0.0.15"
|
"vscode-extension-telemetry": "^0.0.15"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||||
"version": "1.5.0-alpha.65",
|
"version": "1.5.0-alpha.66",
|
||||||
"downloadFileNames": {
|
"downloadFileNames": {
|
||||||
"Windows_86": "win-x86-netcoreapp2.2.zip",
|
"Windows_86": "win-x86-netcoreapp2.2.zip",
|
||||||
"Windows_64": "win-x64-netcoreapp2.2.zip",
|
"Windows_64": "win-x64-netcoreapp2.2.zip",
|
||||||
|
|||||||
@@ -64,9 +64,9 @@ core-util-is@~1.0.0:
|
|||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||||
|
|
||||||
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.11":
|
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.15":
|
||||||
version "0.2.11"
|
version "0.2.15"
|
||||||
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/bc80d2226699d23f45a2ec26129cbcdee4781ca9"
|
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/a2cd2db109de882f0959f7b6421c86afa585f460"
|
||||||
dependencies:
|
dependencies:
|
||||||
vscode-languageclient "3.5.1"
|
vscode-languageclient "3.5.1"
|
||||||
|
|
||||||
|
|||||||
@@ -40,10 +40,14 @@ function defaultSort<T>(args: Slick.OnSortEventArgs<T>, data: Array<T>): Array<T
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class TableDataView<T extends Slick.SlickData> implements IDisposableDataProvider<T> {
|
export class TableDataView<T extends Slick.SlickData> implements IDisposableDataProvider<T> {
|
||||||
|
//The data exposed publicly, when filter is enabled, _data holds the filtered data.
|
||||||
private _data: Array<T>;
|
private _data: Array<T>;
|
||||||
|
//Used when filtering is enabled, _allData holds the complete set of data.
|
||||||
|
private _allData: Array<T>;
|
||||||
private _findArray: Array<IFindPosition>;
|
private _findArray: Array<IFindPosition>;
|
||||||
private _findObs: Observable<IFindPosition>;
|
private _findObs: Observable<IFindPosition>;
|
||||||
private _findIndex: number;
|
private _findIndex: number;
|
||||||
|
private _filterEnabled: boolean;
|
||||||
|
|
||||||
private _onRowCountChange = new Emitter<number>();
|
private _onRowCountChange = new Emitter<number>();
|
||||||
get onRowCountChange(): Event<number> { return this._onRowCountChange.event; }
|
get onRowCountChange(): Event<number> { return this._onRowCountChange.event; }
|
||||||
@@ -51,10 +55,14 @@ export class TableDataView<T extends Slick.SlickData> implements IDisposableData
|
|||||||
private _onFindCountChange = new Emitter<number>();
|
private _onFindCountChange = new Emitter<number>();
|
||||||
get onFindCountChange(): Event<number> { return this._onFindCountChange.event; }
|
get onFindCountChange(): Event<number> { return this._onFindCountChange.event; }
|
||||||
|
|
||||||
|
private _onFilterStateChange = new Emitter<void>();
|
||||||
|
get onFilterStateChange(): Event<void> { return this._onFilterStateChange.event; }
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
data?: Array<T>,
|
data?: Array<T>,
|
||||||
private _findFn?: (val: T, exp: string) => Array<number>,
|
private _findFn?: (val: T, exp: string) => Array<number>,
|
||||||
private _sortFn?: (args: Slick.OnSortEventArgs<T>, data: Array<T>) => Array<T>
|
private _sortFn?: (args: Slick.OnSortEventArgs<T>, data: Array<T>) => Array<T>,
|
||||||
|
private _filterFn?: (data: Array<T>) => Array<T>
|
||||||
) {
|
) {
|
||||||
if (data) {
|
if (data) {
|
||||||
this._data = data;
|
this._data = data;
|
||||||
@@ -65,6 +73,35 @@ export class TableDataView<T extends Slick.SlickData> implements IDisposableData
|
|||||||
if (!_sortFn) {
|
if (!_sortFn) {
|
||||||
this._sortFn = defaultSort;
|
this._sortFn = defaultSort;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_filterFn) {
|
||||||
|
this._filterFn = (dataToFilter) => dataToFilter;
|
||||||
|
}
|
||||||
|
this._filterEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get filterEnabled(): boolean {
|
||||||
|
return this._filterEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public filter() {
|
||||||
|
if (!this.filterEnabled) {
|
||||||
|
this._allData = new Array(...this._data);
|
||||||
|
this._data = this._filterFn(this._allData);
|
||||||
|
this._filterEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._data = this._filterFn(this._allData);
|
||||||
|
this._onFilterStateChange.fire();
|
||||||
|
}
|
||||||
|
|
||||||
|
public clearFilter() {
|
||||||
|
if (this._filterEnabled) {
|
||||||
|
this._data = this._allData;
|
||||||
|
this._allData = [];
|
||||||
|
this._filterEnabled = false;
|
||||||
|
this._onFilterStateChange.fire();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort(args: Slick.OnSortEventArgs<T>) {
|
sort(args: Slick.OnSortEventArgs<T>) {
|
||||||
@@ -79,20 +116,39 @@ export class TableDataView<T extends Slick.SlickData> implements IDisposableData
|
|||||||
return this._data[index];
|
return this._data[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getLengthNonFiltered(): number {
|
||||||
|
return this.filterEnabled ? this._allData.length : this._data.length;
|
||||||
|
}
|
||||||
|
|
||||||
push(items: Array<T>);
|
push(items: Array<T>);
|
||||||
push(item: T);
|
push(item: T);
|
||||||
push(input: T | Array<T>) {
|
push(input: T | Array<T>) {
|
||||||
|
let inputArray = new Array();
|
||||||
if (Array.isArray(input)) {
|
if (Array.isArray(input)) {
|
||||||
this._data.push(...input);
|
inputArray.push(...input);
|
||||||
} else {
|
} else {
|
||||||
this._data.push(input);
|
inputArray.push(input);
|
||||||
}
|
}
|
||||||
this._onRowCountChange.fire();
|
|
||||||
|
if (this._filterEnabled) {
|
||||||
|
this._allData.push(...inputArray);
|
||||||
|
let filteredArray = this._filterFn(inputArray);
|
||||||
|
if (filteredArray.length !== 0) {
|
||||||
|
this._data.push(...filteredArray);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._data.push(...inputArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._onRowCountChange.fire(this.getLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this._data = new Array<T>();
|
this._data = new Array<T>();
|
||||||
this._onRowCountChange.fire();
|
if (this._filterEnabled) {
|
||||||
|
this._allData = new Array<T>();
|
||||||
|
}
|
||||||
|
this._onRowCountChange.fire(this.getLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
find(exp: string, maxMatches: number = 0): Thenable<IFindPosition> {
|
find(exp: string, maxMatches: number = 0): Thenable<IFindPosition> {
|
||||||
@@ -180,6 +236,7 @@ export class TableDataView<T extends Slick.SlickData> implements IDisposableData
|
|||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
this._data = undefined;
|
this._data = undefined;
|
||||||
|
this._allData = undefined;
|
||||||
this._findArray = undefined;
|
this._findArray = undefined;
|
||||||
this._findObs = undefined;
|
this._findObs = undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export const Accounts = 'Accounts';
|
|||||||
export const FireWallRule = 'FirewallRule';
|
export const FireWallRule = 'FirewallRule';
|
||||||
export const AutoOAuth = 'AutoOAuth';
|
export const AutoOAuth = 'AutoOAuth';
|
||||||
export const AddNewDashboardTab = 'AddNewDashboardTab';
|
export const AddNewDashboardTab = 'AddNewDashboardTab';
|
||||||
|
export const ProfilerFilter = 'ProfilerFilter';
|
||||||
|
|
||||||
// SQL Agent Events:
|
// SQL Agent Events:
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
import 'vs/css!sql/parts/profiler/media/profiler';
|
import 'vs/css!sql/parts/profiler/media/profiler';
|
||||||
import { IProfilerService } from 'sql/parts/profiler/service/interfaces';
|
import { IProfilerService, ProfilerFilter } from 'sql/parts/profiler/service/interfaces';
|
||||||
import { IProfilerController } from 'sql/parts/profiler/editor/controller/interfaces';
|
import { IProfilerController } from 'sql/parts/profiler/editor/controller/interfaces';
|
||||||
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
|
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
|
||||||
import { BaseActionContext } from 'sql/workbench/common/actions';
|
import { BaseActionContext } from 'sql/workbench/common/actions';
|
||||||
@@ -298,3 +298,36 @@ export class NewProfilerAction extends Task {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ProfilerFilterSession extends Action {
|
||||||
|
public static ID = 'profiler.filter';
|
||||||
|
public static LABEL = nls.localize('profiler.filter', "Filter…");
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
id: string, label: string,
|
||||||
|
@IProfilerService private _profilerService: IProfilerService
|
||||||
|
) {
|
||||||
|
super(id, label, 'filterLabel');
|
||||||
|
}
|
||||||
|
|
||||||
|
public run(input: ProfilerInput): TPromise<boolean> {
|
||||||
|
this._profilerService.launchFilterSessionDialog(input);
|
||||||
|
return TPromise.wrap(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProfilerClearSessionFilter extends Action {
|
||||||
|
public static ID = 'profiler.clearFilter';
|
||||||
|
public static LABEL = nls.localize('profiler.clearFilter', "Clear Filter");
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
id: string, label: string
|
||||||
|
) {
|
||||||
|
super(id, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
public run(input: ProfilerInput): TPromise<boolean> {
|
||||||
|
input.clearFilter();
|
||||||
|
return TPromise.wrap(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
25
src/sql/parts/profiler/dialog/media/profilerFilterDialog.css
Normal file
25
src/sql/parts/profiler/dialog/media/profilerFilterDialog.css
Normal 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.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
.profiler-filter-dialog {
|
||||||
|
height: 300px;
|
||||||
|
padding: 10px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profiler-filter-clause-table {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profiler-filter-remove-condition {
|
||||||
|
width:20px;
|
||||||
|
height:20px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profiler-filter-add-clause-prompt {
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0px 2px 0px 2px
|
||||||
|
}
|
||||||
329
src/sql/parts/profiler/dialog/profilerFilterDialog.ts
Normal file
329
src/sql/parts/profiler/dialog/profilerFilterDialog.ts
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
import 'vs/css!sql/media/icons/common-icons';
|
||||||
|
import 'vs/css!./media/profilerFilterDialog';
|
||||||
|
import { Button } from 'sql/base/browser/ui/button/button';
|
||||||
|
import { Modal } from 'sql/base/browser/ui/modal/modal';
|
||||||
|
import * as TelemetryKeys from 'sql/common/telemetryKeys';
|
||||||
|
import { attachButtonStyler, attachModalDialogStyler, attachInputBoxStyler } from 'sql/common/theme/styler';
|
||||||
|
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||||
|
import { Builder } from 'vs/base/browser/builder';
|
||||||
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
|
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||||
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
|
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||||
|
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||||
|
import { localize } from 'vs/nls';
|
||||||
|
import { ProfilerFilter, ProfilerFilterClauseOperator, ProfilerFilterClause } from 'sql/parts/profiler/service/interfaces';
|
||||||
|
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
|
||||||
|
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
|
||||||
|
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||||
|
import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
|
||||||
|
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||||
|
import { generateUuid } from 'vs/base/common/uuid';
|
||||||
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
|
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||||
|
|
||||||
|
|
||||||
|
const ClearText: string = localize('profilerFilterDialog.clear', 'Clear All');
|
||||||
|
const ApplyText: string = localize('profilerFilterDialog.apply', 'Apply');
|
||||||
|
const OkText: string = localize('profilerFilterDialog.ok', 'OK');
|
||||||
|
const CancelText: string = localize('profilerFilterDialog.cancel', 'Cancel');
|
||||||
|
const DialogTitle: string = localize('profilerFilterDialog.title', 'Filters');
|
||||||
|
const RemoveText: string = localize('profilerFilterDialog.remove', 'Remove');
|
||||||
|
const AddText: string = localize('profilerFilterDialog.add', 'Add');
|
||||||
|
const AddClausePromptText: string = localize('profilerFilterDialog.addClauseText', 'Click here to add a clause');
|
||||||
|
const TitleIconClass: string = 'icon filterLabel';
|
||||||
|
|
||||||
|
const FieldText: string = localize('profilerFilterDialog.fieldColumn', 'Field');
|
||||||
|
const OperatorText: string = localize('profilerFilterDialog.operatorColumn', 'Operator');
|
||||||
|
const ValueText: string = localize('profilerFilterDialog.valueColumn', 'Value');
|
||||||
|
|
||||||
|
const Equals: string = '=';
|
||||||
|
const NotEquals: string = '<>';
|
||||||
|
const LessThan: string = '<';
|
||||||
|
const LessThanOrEquals: string = '<=';
|
||||||
|
const GreaterThan: string = '>';
|
||||||
|
const GreaterThanOrEquals: string = '>=';
|
||||||
|
const IsNull: string = localize('profilerFilterDialog.isNullOperator', 'Is Null');
|
||||||
|
const IsNotNull: string = localize('profilerFilterDialog.isNotNullOperator', 'Is Not Null');
|
||||||
|
const Contains: string = localize('profilerFilterDialog.containsOperator', 'Contains');
|
||||||
|
const NotContains: string = localize('profilerFilterDialog.notContainsOperator', 'Not Contains');
|
||||||
|
const StartsWith: string = localize('profilerFilterDialog.startsWithOperator', 'Starts With');
|
||||||
|
const NotStartsWith: string = localize('profilerFilterDialog.notStartsWithOperator', 'Not Starts With');
|
||||||
|
|
||||||
|
const Operators = [Equals, NotEquals, LessThan, LessThanOrEquals, GreaterThan, GreaterThanOrEquals, GreaterThan, GreaterThanOrEquals, IsNull, IsNotNull, Contains, NotContains, StartsWith, NotStartsWith];
|
||||||
|
|
||||||
|
export class ProfilerFilterDialog extends Modal {
|
||||||
|
|
||||||
|
private _clauseBuilder: Builder;
|
||||||
|
private _okButton: Button;
|
||||||
|
private _cancelButton: Button;
|
||||||
|
private _clearButton: Button;
|
||||||
|
private _applyButton: Button;
|
||||||
|
private _addClauseButton: Button;
|
||||||
|
private _input: ProfilerInput;
|
||||||
|
private _clauseRows: ClauseRowUI[] = [];
|
||||||
|
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@IThemeService themeService: IThemeService,
|
||||||
|
@IClipboardService clipboardService: IClipboardService,
|
||||||
|
@IPartService partService: IPartService,
|
||||||
|
@ITelemetryService telemetryService: ITelemetryService,
|
||||||
|
@IContextKeyService contextKeyService: IContextKeyService,
|
||||||
|
@IContextViewService private contextViewService: IContextViewService
|
||||||
|
) {
|
||||||
|
super('', TelemetryKeys.ProfilerFilter, partService, telemetryService, clipboardService, themeService, contextKeyService, { isFlyout: false, hasTitleIcon: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
public open(input: ProfilerInput) {
|
||||||
|
this._input = input;
|
||||||
|
this.render();
|
||||||
|
this.show();
|
||||||
|
this._okButton.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
super.render();
|
||||||
|
this.title = DialogTitle;
|
||||||
|
this.titleIconClassName = TitleIconClass;
|
||||||
|
this._register(attachModalDialogStyler(this, this._themeService));
|
||||||
|
this._addClauseButton = this.addFooterButton(AddText, () => this.addClauseRow(false), 'left');
|
||||||
|
this._clearButton = this.addFooterButton(ClearText, () => this.handleClearButtonClick(), 'left');
|
||||||
|
this._applyButton = this.addFooterButton(ApplyText, () => this.filterSession());
|
||||||
|
this._okButton = this.addFooterButton(OkText, () => this.handleOkButtonClick());
|
||||||
|
this._cancelButton = this.addFooterButton(CancelText, () => this.hide());
|
||||||
|
this._register(attachButtonStyler(this._okButton, this._themeService));
|
||||||
|
this._register(attachButtonStyler(this._cancelButton, this._themeService));
|
||||||
|
this._register(attachButtonStyler(this._clearButton, this._themeService));
|
||||||
|
this._register(attachButtonStyler(this._applyButton, this._themeService));
|
||||||
|
this._register(attachButtonStyler(this._addClauseButton, this._themeService));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderBody(container: HTMLElement) {
|
||||||
|
new Builder(container).div({ 'class': 'profiler-filter-dialog' }, (bodyBuilder) => {
|
||||||
|
bodyBuilder.element('table', { 'class': 'profiler-filter-clause-table' }, (builder) => {
|
||||||
|
this._clauseBuilder = builder;
|
||||||
|
});
|
||||||
|
this._clauseBuilder.element('tr', {}, (headerBuilder) => {
|
||||||
|
headerBuilder.element('td').text(FieldText);
|
||||||
|
headerBuilder.element('td').text(OperatorText);
|
||||||
|
headerBuilder.element('td').text(ValueText);
|
||||||
|
headerBuilder.element('td').text('');
|
||||||
|
});
|
||||||
|
|
||||||
|
this._input.filter.clauses.forEach(clause => {
|
||||||
|
this.addClauseRow(true, clause.field, this.convertToOperatorString(clause.operator), clause.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
bodyBuilder.div({
|
||||||
|
'class': 'profiler-filter-add-clause-prompt',
|
||||||
|
'tabIndex': '0'
|
||||||
|
}).text(AddClausePromptText).on(DOM.EventType.CLICK, () => {
|
||||||
|
this.addClauseRow(false);
|
||||||
|
}).on(DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||||
|
let event = new StandardKeyboardEvent(e);
|
||||||
|
if (event.equals(KeyCode.Space) || event.equals(KeyCode.Enter)) {
|
||||||
|
this.addClauseRow(false);
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected layout(height?: number): void {
|
||||||
|
// Nothing to re-layout
|
||||||
|
}
|
||||||
|
|
||||||
|
/* espace key */
|
||||||
|
protected onClose() {
|
||||||
|
this.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* enter key */
|
||||||
|
protected onAccept() {
|
||||||
|
this.handleOkButtonClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleOkButtonClick(): void {
|
||||||
|
this.filterSession();
|
||||||
|
this.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleClearButtonClick() {
|
||||||
|
this._clauseRows.forEach(clause => {
|
||||||
|
clause.row.remove();
|
||||||
|
});
|
||||||
|
this._clauseRows = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private createSelectBox(container: HTMLElement, options: string[], selectedOption: string, ariaLabel: string): SelectBox {
|
||||||
|
let dropdown = new SelectBox(options, selectedOption, this.contextViewService, undefined, { ariaLabel: ariaLabel });
|
||||||
|
dropdown.render(container);
|
||||||
|
this._register(attachSelectBoxStyler(dropdown, this._themeService));
|
||||||
|
return dropdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
private filterSession() {
|
||||||
|
this._input.filterSession(this.getFilter());
|
||||||
|
}
|
||||||
|
|
||||||
|
private getFilter(): ProfilerFilter {
|
||||||
|
let clauses: ProfilerFilterClause[] = [];
|
||||||
|
|
||||||
|
this._clauseRows.forEach(row => {
|
||||||
|
clauses.push({
|
||||||
|
field: row.field.value,
|
||||||
|
operator: this.convertToOperatorEnum(row.operator.value),
|
||||||
|
value: row.value.value
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
clauses: clauses
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private addClauseRow(setInitialValue: boolean, field?: string, operator?: string, value?: string): any {
|
||||||
|
this._clauseBuilder.element('tr', {}, (rowBuilder) => {
|
||||||
|
let rowElement = rowBuilder.getHTMLElement();
|
||||||
|
let clauseId = generateUuid();
|
||||||
|
let fieldDropDown: SelectBox;
|
||||||
|
let operatorDropDown: SelectBox;
|
||||||
|
let valueText: InputBox;
|
||||||
|
|
||||||
|
rowBuilder.element('td', {}, (fieldCell) => {
|
||||||
|
let columns = this._input.columns.map(column => column.name);
|
||||||
|
fieldDropDown = this.createSelectBox(fieldCell.getHTMLElement(), columns, columns[0], FieldText);
|
||||||
|
});
|
||||||
|
rowBuilder.element('td', {}, (operatorCell) => {
|
||||||
|
operatorDropDown = this.createSelectBox(operatorCell.getHTMLElement(), Operators, Operators[0], OperatorText);
|
||||||
|
});
|
||||||
|
rowBuilder.element('td', {}, (textCell) => {
|
||||||
|
valueText = new InputBox(textCell.getHTMLElement(), undefined, {});
|
||||||
|
this._register(attachInputBoxStyler(valueText, this._themeService));
|
||||||
|
});
|
||||||
|
rowBuilder.element('td', {}, (removeImageCell) => {
|
||||||
|
let removeClauseButton = removeImageCell.div({
|
||||||
|
'class': 'profiler-filter-remove-condition icon remove',
|
||||||
|
'tabIndex': '0',
|
||||||
|
'aria-label': RemoveText,
|
||||||
|
'title': RemoveText
|
||||||
|
});
|
||||||
|
|
||||||
|
removeClauseButton.on(DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||||
|
let event = new StandardKeyboardEvent(e);
|
||||||
|
if (event.equals(KeyCode.Space) || event.equals(KeyCode.Enter)) {
|
||||||
|
this.removeRow(clauseId);
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
removeClauseButton.on(DOM.EventType.CLICK, () => {
|
||||||
|
this.removeRow(clauseId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (setInitialValue) {
|
||||||
|
fieldDropDown.selectWithOptionName(field);
|
||||||
|
operatorDropDown.selectWithOptionName(operator);
|
||||||
|
valueText.value = value;
|
||||||
|
}
|
||||||
|
this._clauseRows.push({
|
||||||
|
id: clauseId,
|
||||||
|
row: rowElement,
|
||||||
|
field: fieldDropDown,
|
||||||
|
operator: operatorDropDown,
|
||||||
|
value: valueText
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeRow(clauseId: string) {
|
||||||
|
let idx = this._clauseRows.findIndex((entry) => { return entry.id === clauseId; });
|
||||||
|
if (idx !== -1) {
|
||||||
|
this._clauseRows[idx].row.remove();
|
||||||
|
this._clauseRows.splice(idx, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private convertToOperatorEnum(operator: string): ProfilerFilterClauseOperator {
|
||||||
|
switch (operator) {
|
||||||
|
case Equals:
|
||||||
|
return ProfilerFilterClauseOperator.Equals;
|
||||||
|
case NotEquals:
|
||||||
|
return ProfilerFilterClauseOperator.NotEquals;
|
||||||
|
case LessThan:
|
||||||
|
return ProfilerFilterClauseOperator.LessThan;
|
||||||
|
case LessThanOrEquals:
|
||||||
|
return ProfilerFilterClauseOperator.LessThanOrEquals;
|
||||||
|
case GreaterThan:
|
||||||
|
return ProfilerFilterClauseOperator.GreaterThan;
|
||||||
|
case GreaterThanOrEquals:
|
||||||
|
return ProfilerFilterClauseOperator.GreaterThanOrEquals;
|
||||||
|
case IsNull:
|
||||||
|
return ProfilerFilterClauseOperator.IsNull;
|
||||||
|
case IsNotNull:
|
||||||
|
return ProfilerFilterClauseOperator.IsNotNull;
|
||||||
|
case Contains:
|
||||||
|
return ProfilerFilterClauseOperator.Contains;
|
||||||
|
case NotContains:
|
||||||
|
return ProfilerFilterClauseOperator.NotContains;
|
||||||
|
case StartsWith:
|
||||||
|
return ProfilerFilterClauseOperator.StartsWith;
|
||||||
|
case NotStartsWith:
|
||||||
|
return ProfilerFilterClauseOperator.NotStartsWith;
|
||||||
|
default:
|
||||||
|
throw `Not a valid operator: ${operator}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private convertToOperatorString(operator: ProfilerFilterClauseOperator): string {
|
||||||
|
switch (operator) {
|
||||||
|
case ProfilerFilterClauseOperator.Equals:
|
||||||
|
return Equals;
|
||||||
|
case ProfilerFilterClauseOperator.NotEquals:
|
||||||
|
return NotEquals;
|
||||||
|
case ProfilerFilterClauseOperator.LessThan:
|
||||||
|
return LessThan;
|
||||||
|
case ProfilerFilterClauseOperator.LessThanOrEquals:
|
||||||
|
return LessThanOrEquals;
|
||||||
|
case ProfilerFilterClauseOperator.GreaterThan:
|
||||||
|
return GreaterThan;
|
||||||
|
case ProfilerFilterClauseOperator.GreaterThanOrEquals:
|
||||||
|
return GreaterThanOrEquals;
|
||||||
|
case ProfilerFilterClauseOperator.IsNull:
|
||||||
|
return IsNull;
|
||||||
|
case ProfilerFilterClauseOperator.IsNotNull:
|
||||||
|
return IsNotNull;
|
||||||
|
case ProfilerFilterClauseOperator.Contains:
|
||||||
|
return Contains;
|
||||||
|
case ProfilerFilterClauseOperator.NotContains:
|
||||||
|
return NotContains;
|
||||||
|
case ProfilerFilterClauseOperator.StartsWith:
|
||||||
|
return StartsWith;
|
||||||
|
case ProfilerFilterClauseOperator.NotStartsWith:
|
||||||
|
return NotStartsWith;
|
||||||
|
default:
|
||||||
|
throw `Not a valid operator: ${operator}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ClauseRowUI {
|
||||||
|
id: string;
|
||||||
|
row: HTMLElement;
|
||||||
|
field: SelectBox;
|
||||||
|
operator: SelectBox;
|
||||||
|
value: InputBox;
|
||||||
|
}
|
||||||
@@ -28,6 +28,8 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
|||||||
import { Dimension } from 'vs/base/browser/dom';
|
import { Dimension } from 'vs/base/browser/dom';
|
||||||
import { textFormatter } from 'sql/parts/grid/services/sharedServices';
|
import { textFormatter } from 'sql/parts/grid/services/sharedServices';
|
||||||
import { PROFILER_MAX_MATCHES } from 'sql/parts/profiler/editor/controller/profilerFindWidget';
|
import { PROFILER_MAX_MATCHES } from 'sql/parts/profiler/editor/controller/profilerFindWidget';
|
||||||
|
import { IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar';
|
||||||
|
import { localize } from 'vs/nls';
|
||||||
|
|
||||||
export interface ProfilerTableViewState {
|
export interface ProfilerTableViewState {
|
||||||
scrollTop: number;
|
scrollTop: number;
|
||||||
@@ -47,6 +49,8 @@ export class ProfilerTableEditor extends BaseEditor implements IProfilerControll
|
|||||||
private _overlay: HTMLElement;
|
private _overlay: HTMLElement;
|
||||||
private _currentDimensions: Dimension;
|
private _currentDimensions: Dimension;
|
||||||
private _actionMap: { [x: string]: IEditorAction } = {};
|
private _actionMap: { [x: string]: IEditorAction } = {};
|
||||||
|
private _statusbarItem: IDisposable;
|
||||||
|
private _showStatusBarItem: boolean;
|
||||||
|
|
||||||
private _onDidChangeConfiguration = new Emitter<IConfigurationChangedEvent>();
|
private _onDidChangeConfiguration = new Emitter<IConfigurationChangedEvent>();
|
||||||
public onDidChangeConfiguration: Event<IConfigurationChangedEvent> = this._onDidChangeConfiguration.event;
|
public onDidChangeConfiguration: Event<IConfigurationChangedEvent> = this._onDidChangeConfiguration.event;
|
||||||
@@ -57,11 +61,13 @@ export class ProfilerTableEditor extends BaseEditor implements IProfilerControll
|
|||||||
@IContextViewService private _contextViewService: IContextViewService,
|
@IContextViewService private _contextViewService: IContextViewService,
|
||||||
@IKeybindingService private _keybindingService: IKeybindingService,
|
@IKeybindingService private _keybindingService: IKeybindingService,
|
||||||
@IContextKeyService private _contextKeyService: IContextKeyService,
|
@IContextKeyService private _contextKeyService: IContextKeyService,
|
||||||
@IInstantiationService private _instantiationService: IInstantiationService
|
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||||
|
@IStatusbarService private _statusbarService: IStatusbarService
|
||||||
) {
|
) {
|
||||||
super(ProfilerTableEditor.ID, telemetryService, _themeService);
|
super(ProfilerTableEditor.ID, telemetryService, _themeService);
|
||||||
this._actionMap[ACTION_IDS.FIND_NEXT] = this._instantiationService.createInstance(ProfilerFindNext, this);
|
this._actionMap[ACTION_IDS.FIND_NEXT] = this._instantiationService.createInstance(ProfilerFindNext, this);
|
||||||
this._actionMap[ACTION_IDS.FIND_PREVIOUS] = this._instantiationService.createInstance(ProfilerFindPrevious, this);
|
this._actionMap[ACTION_IDS.FIND_PREVIOUS] = this._instantiationService.createInstance(ProfilerFindPrevious, this);
|
||||||
|
this._showStatusBarItem = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public createEditor(parent: HTMLElement): void {
|
public createEditor(parent: HTMLElement): void {
|
||||||
@@ -99,7 +105,11 @@ export class ProfilerTableEditor extends BaseEditor implements IProfilerControll
|
|||||||
}
|
}
|
||||||
|
|
||||||
public setInput(input: ProfilerInput): TPromise<void> {
|
public setInput(input: ProfilerInput): TPromise<void> {
|
||||||
|
this._showStatusBarItem = true;
|
||||||
this._input = input;
|
this._input = input;
|
||||||
|
|
||||||
|
this._updateRowCountStatus();
|
||||||
|
|
||||||
if (this._columnListener) {
|
if (this._columnListener) {
|
||||||
this._columnListener.dispose();
|
this._columnListener.dispose();
|
||||||
}
|
}
|
||||||
@@ -114,7 +124,16 @@ export class ProfilerTableEditor extends BaseEditor implements IProfilerControll
|
|||||||
this._stateListener.dispose();
|
this._stateListener.dispose();
|
||||||
}
|
}
|
||||||
this._stateListener = input.state.addChangeListener(e => this._onStateChange(e));
|
this._stateListener = input.state.addChangeListener(e => this._onStateChange(e));
|
||||||
input.data.onRowCountChange(() => { this._profilerTable.updateRowCount(); });
|
input.data.onRowCountChange(() => {
|
||||||
|
this._profilerTable.updateRowCount();
|
||||||
|
this._updateRowCountStatus();
|
||||||
|
});
|
||||||
|
|
||||||
|
input.data.onFilterStateChange(() => {
|
||||||
|
this._profilerTable.grid.invalidateAllRows();
|
||||||
|
this._profilerTable.updateRowCount();
|
||||||
|
this._updateRowCountStatus();
|
||||||
|
});
|
||||||
|
|
||||||
if (this._findCountChangeListener) {
|
if (this._findCountChangeListener) {
|
||||||
this._findCountChangeListener.dispose();
|
this._findCountChangeListener.dispose();
|
||||||
@@ -128,6 +147,10 @@ export class ProfilerTableEditor extends BaseEditor implements IProfilerControll
|
|||||||
this._profilerTable.setActiveCell(val.row, val.col);
|
this._profilerTable.setActiveCell(val.row, val.col);
|
||||||
this._updateFinderMatchState();
|
this._updateFinderMatchState();
|
||||||
}, er => { });
|
}, er => { });
|
||||||
|
|
||||||
|
this._input.onDispose(() => {
|
||||||
|
this._disposeStatusbarItem();
|
||||||
|
});
|
||||||
return TPromise.as(null);
|
return TPromise.as(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,7 +260,26 @@ export class ProfilerTableEditor extends BaseEditor implements IProfilerControll
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _updateRowCountStatus(): void {
|
||||||
|
if (this._showStatusBarItem) {
|
||||||
|
let message = this._input.data.filterEnabled ?
|
||||||
|
localize('ProfilerTableEditor.eventCountFiltered', 'Events (Filtered): {0}/{1}', this._input.data.getLength(), this._input.data.getLengthNonFiltered())
|
||||||
|
: localize('ProfilerTableEditor.eventCount', 'Events: {0}', this._input.data.getLength());
|
||||||
|
|
||||||
|
this._disposeStatusbarItem();
|
||||||
|
this._statusbarItem = this._statusbarService.addEntry({ text: message }, StatusbarAlignment.RIGHT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _disposeStatusbarItem() {
|
||||||
|
if (this._statusbarItem) {
|
||||||
|
this._statusbarItem.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public saveViewState(): ProfilerTableViewState {
|
public saveViewState(): ProfilerTableViewState {
|
||||||
|
this._disposeStatusbarItem();
|
||||||
|
this._showStatusBarItem = false;
|
||||||
let viewElement = this._profilerTable.grid.getCanvasNode().parentElement;
|
let viewElement = this._profilerTable.grid.getCanvasNode().parentElement;
|
||||||
return {
|
return {
|
||||||
scrollTop: viewElement.scrollTop,
|
scrollTop: viewElement.scrollTop,
|
||||||
@@ -246,6 +288,8 @@ export class ProfilerTableEditor extends BaseEditor implements IProfilerControll
|
|||||||
}
|
}
|
||||||
|
|
||||||
public restoreViewState(state: ProfilerTableViewState): void {
|
public restoreViewState(state: ProfilerTableViewState): void {
|
||||||
|
this._showStatusBarItem = true;
|
||||||
|
this._updateRowCountStatus();
|
||||||
let viewElement = this._profilerTable.grid.getCanvasNode().parentElement;
|
let viewElement = this._profilerTable.grid.getCanvasNode().parentElement;
|
||||||
viewElement.scrollTop = state.scrollTop;
|
viewElement.scrollTop = state.scrollTop;
|
||||||
viewElement.scrollLeft = state.scrollLeft;
|
viewElement.scrollLeft = state.scrollLeft;
|
||||||
|
|||||||
@@ -144,6 +144,8 @@ export class ProfilerEditor extends BaseEditor {
|
|||||||
private _autoscrollAction: Actions.ProfilerAutoScroll;
|
private _autoscrollAction: Actions.ProfilerAutoScroll;
|
||||||
private _createAction: Actions.ProfilerCreate;
|
private _createAction: Actions.ProfilerCreate;
|
||||||
private _collapsedPanelAction: Actions.ProfilerCollapsablePanelAction;
|
private _collapsedPanelAction: Actions.ProfilerCollapsablePanelAction;
|
||||||
|
private _filterAction: Actions.ProfilerFilterSession;
|
||||||
|
private _clearFilterAction: Actions.ProfilerClearSessionFilter;
|
||||||
|
|
||||||
private _savedTableViewStates = new Map<ProfilerInput, ProfilerTableViewState>();
|
private _savedTableViewStates = new Map<ProfilerInput, ProfilerTableViewState>();
|
||||||
|
|
||||||
@@ -217,7 +219,10 @@ export class ProfilerEditor extends BaseEditor {
|
|||||||
this._pauseAction.enabled = false;
|
this._pauseAction.enabled = false;
|
||||||
this._connectAction = this._instantiationService.createInstance(Actions.ProfilerConnect, Actions.ProfilerConnect.ID, Actions.ProfilerConnect.LABEL);
|
this._connectAction = this._instantiationService.createInstance(Actions.ProfilerConnect, Actions.ProfilerConnect.ID, Actions.ProfilerConnect.LABEL);
|
||||||
this._autoscrollAction = this._instantiationService.createInstance(Actions.ProfilerAutoScroll, Actions.ProfilerAutoScroll.ID, Actions.ProfilerAutoScroll.LABEL);
|
this._autoscrollAction = this._instantiationService.createInstance(Actions.ProfilerAutoScroll, Actions.ProfilerAutoScroll.ID, Actions.ProfilerAutoScroll.LABEL);
|
||||||
|
this._filterAction = this._instantiationService.createInstance(Actions.ProfilerFilterSession, Actions.ProfilerFilterSession.ID, Actions.ProfilerFilterSession.LABEL);
|
||||||
|
this._filterAction.enabled = true;
|
||||||
|
this._clearFilterAction = this._instantiationService.createInstance(Actions.ProfilerClearSessionFilter, Actions.ProfilerClearSessionFilter.ID, Actions.ProfilerClearSessionFilter.LABEL);
|
||||||
|
this._clearFilterAction.enabled = true;
|
||||||
this._viewTemplates = this._profilerService.getViewTemplates();
|
this._viewTemplates = this._profilerService.getViewTemplates();
|
||||||
this._viewTemplateSelector = new SelectBox(this._viewTemplates.map(i => i.name), 'Standard View', this._contextViewService);
|
this._viewTemplateSelector = new SelectBox(this._viewTemplates.map(i => i.name), 'Standard View', this._contextViewService);
|
||||||
this._viewTemplateSelector.setAriaLabel(nls.localize('profiler.viewSelectAccessibleName', 'Select View'));
|
this._viewTemplateSelector.setAriaLabel(nls.localize('profiler.viewSelectAccessibleName', 'Select View'));
|
||||||
@@ -257,6 +262,9 @@ export class ProfilerEditor extends BaseEditor {
|
|||||||
{ action: this._stopAction },
|
{ action: this._stopAction },
|
||||||
{ action: this._pauseAction },
|
{ action: this._pauseAction },
|
||||||
{ element: Taskbar.createTaskbarSeparator() },
|
{ element: Taskbar.createTaskbarSeparator() },
|
||||||
|
{ action: this._filterAction },
|
||||||
|
{ action: this._clearFilterAction },
|
||||||
|
{ element: Taskbar.createTaskbarSeparator() },
|
||||||
{ element: this._createTextElement(nls.localize('profiler.viewSelectLabel', 'Select View:')) },
|
{ element: this._createTextElement(nls.localize('profiler.viewSelectLabel', 'Select View:')) },
|
||||||
{ element: viewTemplateContainer },
|
{ element: viewTemplateContainer },
|
||||||
{ action: this._autoscrollAction },
|
{ action: this._autoscrollAction },
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
|
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
|
||||||
import { IProfilerSession, IProfilerService, ProfilerSessionID, IProfilerViewTemplate } from 'sql/parts/profiler/service/interfaces';
|
import { IProfilerSession, IProfilerService, ProfilerSessionID, IProfilerViewTemplate, ProfilerFilter } from 'sql/parts/profiler/service/interfaces';
|
||||||
import { ProfilerState } from './profilerState';
|
import { ProfilerState } from './profilerState';
|
||||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@ import { escape } from 'sql/base/common/strings';
|
|||||||
import * as types from 'vs/base/common/types';
|
import * as types from 'vs/base/common/types';
|
||||||
import URI from 'vs/base/common/uri';
|
import URI from 'vs/base/common/uri';
|
||||||
import Severity from 'vs/base/common/severity';
|
import Severity from 'vs/base/common/severity';
|
||||||
|
import { FilterData } from 'sql/parts/profiler/service/profilerFilter';
|
||||||
|
|
||||||
export class ProfilerInput extends EditorInput implements IProfilerSession {
|
export class ProfilerInput extends EditorInput implements IProfilerSession {
|
||||||
|
|
||||||
@@ -41,6 +42,8 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
|
|||||||
private _onColumnsChanged = new Emitter<Slick.Column<Slick.SlickData>[]>();
|
private _onColumnsChanged = new Emitter<Slick.Column<Slick.SlickData>[]>();
|
||||||
public onColumnsChanged: Event<Slick.Column<Slick.SlickData>[]> = this._onColumnsChanged.event;
|
public onColumnsChanged: Event<Slick.Column<Slick.SlickData>[]> = this._onColumnsChanged.event;
|
||||||
|
|
||||||
|
private _filter: ProfilerFilter = { clauses: [] };
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public connection: IConnectionProfile,
|
public connection: IConnectionProfile,
|
||||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||||
@@ -73,7 +76,12 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
|
|||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
this._data = new TableDataView<Slick.SlickData>(undefined, searchFn);
|
|
||||||
|
let filterFn = (data: Array<Slick.SlickData>): Array<Slick.SlickData> => {
|
||||||
|
return FilterData(this._filter, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
this._data = new TableDataView<Slick.SlickData>(undefined, searchFn, undefined, filterFn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get providerType(): string {
|
public get providerType(): string {
|
||||||
@@ -187,6 +195,10 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
|
|||||||
return this._state;
|
return this._state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get filter(): ProfilerFilter {
|
||||||
|
return this._filter;
|
||||||
|
}
|
||||||
|
|
||||||
public onSessionStopped(notification: sqlops.ProfilerSessionStoppedParams) {
|
public onSessionStopped(notification: sqlops.ProfilerSessionStoppedParams) {
|
||||||
this._notificationService.error(nls.localize("profiler.sessionStopped", "XEvent Profiler Session stopped unexpectedly on the server {0}.", this.connection.serverName));
|
this._notificationService.error(nls.localize("profiler.sessionStopped", "XEvent Profiler Session stopped unexpectedly on the server {0}.", this.connection.serverName));
|
||||||
|
|
||||||
@@ -232,6 +244,7 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
|
|||||||
this._notificationService.warn(nls.localize("profiler.eventsLost", "The XEvent Profiler session for {0} has lost events.", this.connection.serverName));
|
this._notificationService.warn(nls.localize("profiler.eventsLost", "The XEvent Profiler session for {0} has lost events.", this.connection.serverName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let newEvents = [];
|
||||||
for (let i: number = 0; i < eventMessage.events.length && i < 500; ++i) {
|
for (let i: number = 0; i < eventMessage.events.length && i < 500; ++i) {
|
||||||
let e: sqlops.ProfilerEvent = eventMessage.events[i];
|
let e: sqlops.ProfilerEvent = eventMessage.events[i];
|
||||||
let data = {};
|
let data = {};
|
||||||
@@ -249,9 +262,26 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
|
|||||||
data[columnName] = escape(value);
|
data[columnName] = escape(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._data.push(data);
|
newEvents.push(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (newEvents.length > 0) {
|
||||||
|
this._data.push(newEvents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filterSession(filter: ProfilerFilter) {
|
||||||
|
this._filter = filter;
|
||||||
|
if (this._filter.clauses.length !== 0) {
|
||||||
|
this.data.filter();
|
||||||
|
} else {
|
||||||
|
this.data.clearFilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearFilter() {
|
||||||
|
this._filter = { clauses: [] };
|
||||||
|
this.data.clearFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
confirmSave(): TPromise<ConfirmResult> {
|
confirmSave(): TPromise<ConfirmResult> {
|
||||||
@@ -280,4 +310,9 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
|
|||||||
isDirty(): boolean {
|
isDirty(): boolean {
|
||||||
return this.state.isRunning || this.state.isPaused;
|
return this.state.isRunning || this.state.isPaused;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
super.dispose();
|
||||||
|
this._profilerService.disconnectSession(this.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,6 +125,11 @@ export interface IProfilerService {
|
|||||||
* @param input input object that contains the necessary information which will be modified based on used input
|
* @param input input object that contains the necessary information which will be modified based on used input
|
||||||
*/
|
*/
|
||||||
launchCreateSessionDialog(input: ProfilerInput): Thenable<void>;
|
launchCreateSessionDialog(input: ProfilerInput): Thenable<void>;
|
||||||
|
/**
|
||||||
|
* Launches the dialog for collecting the filter object
|
||||||
|
* @param input input object
|
||||||
|
*/
|
||||||
|
launchFilterSessionDialog(input: ProfilerInput): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IProfilerSettings {
|
export interface IProfilerSettings {
|
||||||
@@ -147,3 +152,28 @@ export interface IProfilerSessionTemplate {
|
|||||||
defaultView: string;
|
defaultView: string;
|
||||||
createStatement: string;
|
createStatement: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProfilerFilter {
|
||||||
|
clauses: ProfilerFilterClause[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProfilerFilterClause {
|
||||||
|
field: string;
|
||||||
|
operator: ProfilerFilterClauseOperator;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ProfilerFilterClauseOperator {
|
||||||
|
Equals,
|
||||||
|
NotEquals,
|
||||||
|
LessThan,
|
||||||
|
LessThanOrEquals,
|
||||||
|
GreaterThan,
|
||||||
|
GreaterThanOrEquals,
|
||||||
|
IsNull,
|
||||||
|
IsNotNull,
|
||||||
|
Contains,
|
||||||
|
NotContains,
|
||||||
|
StartsWith,
|
||||||
|
NotStartsWith
|
||||||
|
}
|
||||||
102
src/sql/parts/profiler/service/profilerFilter.ts
Normal file
102
src/sql/parts/profiler/service/profilerFilter.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { ProfilerFilter, ProfilerFilterClause, ProfilerFilterClauseOperator } from 'sql/parts/profiler/service/interfaces';
|
||||||
|
|
||||||
|
export function FilterData(filter: ProfilerFilter, data: any[]): any[] {
|
||||||
|
if (!data || !filter) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return data.filter(item => matches(item, filter.clauses));
|
||||||
|
}
|
||||||
|
|
||||||
|
function matches(item: any, clauses: ProfilerFilterClause[]): boolean {
|
||||||
|
let match = true;
|
||||||
|
if (!item) {
|
||||||
|
match = false;
|
||||||
|
} else if (clauses) {
|
||||||
|
for (let i = 0; i < clauses.length; i++) {
|
||||||
|
let clause = clauses[i];
|
||||||
|
if (!!clause && !!clause.field) {
|
||||||
|
let actualValue: any = item[clause.field];
|
||||||
|
let expectedValue: any = clause.value;
|
||||||
|
let actualValueString: string = actualValue === undefined ? undefined : actualValue.toLocaleLowerCase();
|
||||||
|
let expectedValueString: string = expectedValue === undefined ? undefined : expectedValue.toLocaleLowerCase();
|
||||||
|
let actualValueDate = new Date(actualValue).valueOf();
|
||||||
|
let expectedValueDate = new Date(expectedValue).valueOf();
|
||||||
|
let actualValueNumber = new Number(actualValue).valueOf();
|
||||||
|
let expectedValueNumber = new Number(expectedValue).valueOf();
|
||||||
|
|
||||||
|
if (isValidNumber(actualValue) && isValidNumber(expectedValue)) {
|
||||||
|
actualValue = actualValueNumber;
|
||||||
|
expectedValue = expectedValueNumber;
|
||||||
|
} else if (isValidDate(actualValue) && isValidDate(expectedValue)) {
|
||||||
|
actualValue = actualValueDate;
|
||||||
|
expectedValue = expectedValueDate;
|
||||||
|
} else {
|
||||||
|
actualValue = actualValueString;
|
||||||
|
expectedValue = expectedValueString;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (clause.operator) {
|
||||||
|
case ProfilerFilterClauseOperator.Equals:
|
||||||
|
match = actualValue === expectedValue;
|
||||||
|
break;
|
||||||
|
case ProfilerFilterClauseOperator.NotEquals:
|
||||||
|
match = actualValue !== expectedValue;
|
||||||
|
break;
|
||||||
|
case ProfilerFilterClauseOperator.LessThan:
|
||||||
|
match = actualValue < expectedValue;
|
||||||
|
break;
|
||||||
|
case ProfilerFilterClauseOperator.LessThanOrEquals:
|
||||||
|
match = actualValue <= expectedValue;
|
||||||
|
break;
|
||||||
|
case ProfilerFilterClauseOperator.GreaterThan:
|
||||||
|
match = actualValue > expectedValue;
|
||||||
|
break;
|
||||||
|
case ProfilerFilterClauseOperator.GreaterThanOrEquals:
|
||||||
|
match = actualValue >= expectedValue;
|
||||||
|
break;
|
||||||
|
case ProfilerFilterClauseOperator.IsNull:
|
||||||
|
match = actualValue === undefined || actualValue === null || actualValue === '';
|
||||||
|
break;
|
||||||
|
case ProfilerFilterClauseOperator.IsNotNull:
|
||||||
|
match = actualValue !== undefined && actualValue !== null && actualValue !== '';
|
||||||
|
break;
|
||||||
|
case ProfilerFilterClauseOperator.Contains:
|
||||||
|
match = actualValueString && actualValueString.includes(expectedValueString);
|
||||||
|
break;
|
||||||
|
case ProfilerFilterClauseOperator.NotContains:
|
||||||
|
match = !actualValueString || !actualValueString.includes(expectedValueString);
|
||||||
|
break;
|
||||||
|
case ProfilerFilterClauseOperator.StartsWith:
|
||||||
|
match = actualValueString.startsWith(expectedValueString);
|
||||||
|
break;
|
||||||
|
case ProfilerFilterClauseOperator.NotStartsWith:
|
||||||
|
match = !actualValueString || !actualValueString.startsWith(expectedValueString);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw `Not a valid operator: ${clause.operator}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidNumber(value: string): boolean {
|
||||||
|
let num = new Number(value);
|
||||||
|
return value !== undefined && !isNaN(num.valueOf()) && value.replace(' ', '') !== '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidDate(value: string): boolean {
|
||||||
|
let date = new Date(value);
|
||||||
|
return value !== undefined && !isNaN(date.valueOf());
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
|
|||||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||||
import { Scope as MementoScope, Memento } from 'vs/workbench/common/memento';
|
import { Scope as MementoScope, Memento } from 'vs/workbench/common/memento';
|
||||||
|
import { ProfilerFilterDialog } from 'sql/parts/profiler/dialog/profilerFilterDialog';
|
||||||
|
|
||||||
class TwoWayMap<T, K> {
|
class TwoWayMap<T, K> {
|
||||||
private forwardMap: Map<T, K>;
|
private forwardMap: Map<T, K>;
|
||||||
@@ -233,4 +234,9 @@ export class ProfilerService implements IProfilerService {
|
|||||||
public launchCreateSessionDialog(input?: ProfilerInput): Thenable<void> {
|
public launchCreateSessionDialog(input?: ProfilerInput): Thenable<void> {
|
||||||
return this._commandService.executeCommand('profiler.openCreateSessionDialog', input.id, input.providerType, this.getSessionTemplates());
|
return this._commandService.executeCommand('profiler.openCreateSessionDialog', input.id, input.providerType, this.getSessionTemplates());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public launchFilterSessionDialog(input: ProfilerInput): void {
|
||||||
|
let dialog = this._instantiationService.createInstance(ProfilerFilterDialog);
|
||||||
|
dialog.open(input);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -548,6 +548,13 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
|
|||||||
return this._resolveProvider<sqlops.ProfilerProvider>(handle).pauseSession(sessionId);
|
return this._resolveProvider<sqlops.ProfilerProvider>(handle).pauseSession(sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect a profiler session
|
||||||
|
*/
|
||||||
|
public $disconnectSession(handle: number, sessionId: string): Thenable<boolean> {
|
||||||
|
return this._resolveProvider<sqlops.ProfilerProvider>(handle).disconnectSession(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get list of running XEvent sessions on the session's target server
|
* Get list of running XEvent sessions on the session's target server
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ export class MainThreadDataProtocol implements MainThreadDataProtocolShape {
|
|||||||
return TPromise.as(true);
|
return TPromise.as(true);
|
||||||
},
|
},
|
||||||
disconnectSession(sessionId: string): Thenable<boolean> {
|
disconnectSession(sessionId: string): Thenable<boolean> {
|
||||||
return TPromise.as(true);
|
return self._proxy.$disconnectSession(handle, sessionId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -356,6 +356,11 @@ export abstract class ExtHostDataProtocolShape {
|
|||||||
*/
|
*/
|
||||||
$getXEventSessions(handle: number, sessionId: string): Thenable<string[]> { throw ni(); }
|
$getXEventSessions(handle: number, sessionId: string): Thenable<string[]> { throw ni(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect a profiler session
|
||||||
|
*/
|
||||||
|
$disconnectSession(handle: number, sessionId: string): Thenable<boolean> { throw ni(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Agent Job list
|
* Get Agent Job list
|
||||||
*/
|
*/
|
||||||
|
|||||||
100
src/sqltest/base/browser/ui/table/tableDataView.test.ts
Normal file
100
src/sqltest/base/browser/ui/table/tableDataView.test.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as assert from 'assert';
|
||||||
|
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
|
||||||
|
|
||||||
|
suite('TableDataView Tests', () => {
|
||||||
|
test('Data can be filtered and filter can be cleared', () => {
|
||||||
|
const rowCount = 10;
|
||||||
|
const columnCount = 5;
|
||||||
|
const originalData = populateData(rowCount, columnCount);
|
||||||
|
|
||||||
|
let filteredRowCount = 5;
|
||||||
|
const obj = new TableDataView(originalData, undefined, undefined, (data: any[]) => {
|
||||||
|
return populateData(filteredRowCount, columnCount);
|
||||||
|
});
|
||||||
|
|
||||||
|
let rowCountEventInvokeCount = 0;
|
||||||
|
let filterStateChangeEventInvokeCount = 0;
|
||||||
|
let rowCountEventParameter;
|
||||||
|
obj.onRowCountChange((count) => {
|
||||||
|
rowCountEventInvokeCount++;
|
||||||
|
rowCountEventParameter = count;
|
||||||
|
});
|
||||||
|
|
||||||
|
obj.onFilterStateChange(() => {
|
||||||
|
filterStateChangeEventInvokeCount++;
|
||||||
|
});
|
||||||
|
|
||||||
|
let verify = (expectedRowCountChangeInvokeCount: number,
|
||||||
|
expectedDataLength: number,
|
||||||
|
expectedNonFilteredDataLength: number,
|
||||||
|
expectedFilterStateChangeInvokeCount: number,
|
||||||
|
stepName: string,
|
||||||
|
verifyRowCountEventParameter: boolean = true) => {
|
||||||
|
assert.equal(rowCountEventInvokeCount, expectedRowCountChangeInvokeCount, 'RowCountChange event count - ' + stepName);
|
||||||
|
if (verifyRowCountEventParameter) {
|
||||||
|
assert.equal(rowCountEventParameter, expectedDataLength, 'Row count passed by RowCountChange event - ' + stepName);
|
||||||
|
}
|
||||||
|
assert.equal(obj.getLength(), expectedDataLength, 'Data length - ' + stepName);
|
||||||
|
assert.equal(obj.getLengthNonFiltered(), expectedNonFilteredDataLength, 'Length for all data - ' + stepName);
|
||||||
|
assert.equal(filterStateChangeEventInvokeCount, expectedFilterStateChangeInvokeCount, 'FilterStateChange event count - ' + stepName);
|
||||||
|
};
|
||||||
|
|
||||||
|
verify(0, rowCount, rowCount, 0, 'after initialization', false);
|
||||||
|
|
||||||
|
obj.filter();
|
||||||
|
|
||||||
|
verify(0, filteredRowCount, rowCount, 1, 'after filtering', false);
|
||||||
|
|
||||||
|
const additionalRowCount = 20;
|
||||||
|
const additionalData = populateData(additionalRowCount, columnCount);
|
||||||
|
obj.push(additionalData);
|
||||||
|
|
||||||
|
verify(1, filteredRowCount * 2, rowCount + additionalRowCount, 1, 'after adding more data');
|
||||||
|
|
||||||
|
obj.clearFilter();
|
||||||
|
|
||||||
|
verify(1, rowCount + additionalRowCount, rowCount + additionalRowCount, 2, 'after clearing filter', false);
|
||||||
|
|
||||||
|
//From this point on, nothing matches the filter criteria
|
||||||
|
filteredRowCount = 0;
|
||||||
|
|
||||||
|
obj.filter();
|
||||||
|
verify(1, 0, rowCount + additionalRowCount, 3, 'after 2nd filtering', false);
|
||||||
|
|
||||||
|
obj.push(additionalData);
|
||||||
|
verify(2, 0, rowCount + additionalRowCount + additionalRowCount, 3, 'after 2nd adding more data');
|
||||||
|
|
||||||
|
obj.clearFilter();
|
||||||
|
verify(2, rowCount + additionalRowCount + additionalRowCount, rowCount + additionalRowCount + additionalRowCount, 4, 'after 2nd clearing filter', false);
|
||||||
|
|
||||||
|
obj.clearFilter();
|
||||||
|
verify(2, rowCount + additionalRowCount + additionalRowCount, rowCount + additionalRowCount + additionalRowCount, 4, 'calling clearFilter() multiple times', false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function populateData(row: number, column: number): any[] {
|
||||||
|
let data = [];
|
||||||
|
for (let i: number = 0; i < row; i++) {
|
||||||
|
let row = {};
|
||||||
|
for (let j: number = 0; j < column; j++) {
|
||||||
|
row[getColumnName(j)] = getCellValue(i, j);
|
||||||
|
}
|
||||||
|
data.push(row);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColumnName(index: number): string {
|
||||||
|
return `column${index}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCellValue(row: number, column: number): string {
|
||||||
|
return `row ${row} column ${column}`;
|
||||||
|
}
|
||||||
122
src/sqltest/parts/profiler/service/profilerFilter.test.ts
Normal file
122
src/sqltest/parts/profiler/service/profilerFilter.test.ts
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as assert from 'assert';
|
||||||
|
import { FilterData } from 'sql/parts/profiler/service/profilerFilter';
|
||||||
|
import { ProfilerFilterClauseOperator, ProfilerFilter } from 'sql/parts/profiler/service/interfaces';
|
||||||
|
|
||||||
|
const property1 = 'property1';
|
||||||
|
const property2 = 'property2';
|
||||||
|
|
||||||
|
suite('Profiler filter data tests', () => {
|
||||||
|
test('number type filter data test', () => {
|
||||||
|
let filter: ProfilerFilter = { clauses: [] };
|
||||||
|
let entry1: TestData = { property1: '-1', property2: '0' };
|
||||||
|
let entry2: TestData = { property1: '0', property2: '10' };
|
||||||
|
let entry3: TestData = { property1: '10.0', property2: '-1' };
|
||||||
|
|
||||||
|
let data: TestData[] = [entry1, entry2, entry3];
|
||||||
|
|
||||||
|
filter.clauses = [{ field: property1, operator: ProfilerFilterClauseOperator.Equals, value: '10' }];
|
||||||
|
filterAndVerify(filter, data, [entry3], 'Equals operator');
|
||||||
|
|
||||||
|
filter.clauses = [{ field: property1, operator: ProfilerFilterClauseOperator.NotEquals, value: '-1' }];
|
||||||
|
filterAndVerify(filter, data, [entry2, entry3], 'NotEquals operator');
|
||||||
|
|
||||||
|
filter.clauses = [{ field: property1, operator: ProfilerFilterClauseOperator.GreaterThan, value: '2' }];
|
||||||
|
filterAndVerify(filter, data, [entry3], 'GreaterThan operator');
|
||||||
|
|
||||||
|
filter.clauses = [{ field: property1, operator: ProfilerFilterClauseOperator.GreaterThanOrEquals, value: '0' }];
|
||||||
|
filterAndVerify(filter, data, [entry2, entry3], 'GreaterThanOrEquals operator');
|
||||||
|
|
||||||
|
filter.clauses = [{ field: property1, operator: ProfilerFilterClauseOperator.LessThan, value: '0' }];
|
||||||
|
filterAndVerify(filter, data, [entry1], 'LessThan operator');
|
||||||
|
|
||||||
|
filter.clauses = [{ field: property1, operator: ProfilerFilterClauseOperator.LessThanOrEquals, value: '0' }];
|
||||||
|
filterAndVerify(filter, data, [entry1, entry2], 'LessThanOrEquals operator');
|
||||||
|
|
||||||
|
filter.clauses = [{ field: property1, operator: ProfilerFilterClauseOperator.LessThanOrEquals, value: '-2' }];
|
||||||
|
filterAndVerify(filter, data, [], 'Empty result set');
|
||||||
|
|
||||||
|
filter.clauses = [{ field: property1, operator: ProfilerFilterClauseOperator.LessThanOrEquals, value: '10' }];
|
||||||
|
filterAndVerify(filter, data, data, 'All matches');
|
||||||
|
|
||||||
|
filter.clauses = [{ field: property1, operator: ProfilerFilterClauseOperator.LessThanOrEquals, value: '0' },
|
||||||
|
{ field: property2, operator: ProfilerFilterClauseOperator.LessThan, value: '10' }];
|
||||||
|
filterAndVerify(filter, data, [entry1], 'Multiple clauses');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('date type filter data test', () => {
|
||||||
|
let filter: ProfilerFilter = { clauses: [] };
|
||||||
|
let entry1: TestData = { property1: '2019-01-02T19:00:00.000Z', property2: '' };
|
||||||
|
let entry2: TestData = { property1: '2019-01-03T10:00:00.000Z', property2: '' };
|
||||||
|
let entry3: TestData = { property1: '2019-01-04T10:00:00.000Z', property2: '' };
|
||||||
|
|
||||||
|
let data: TestData[] = [entry1, entry2, entry3];
|
||||||
|
|
||||||
|
filter.clauses = [{ field: property1, operator: ProfilerFilterClauseOperator.Equals, value: '2019-01-02T19:00:00Z' }];
|
||||||
|
filterAndVerify(filter, data, [entry1], 'Equals operator');
|
||||||
|
|
||||||
|
filter.clauses = [{ field: property1, operator: ProfilerFilterClauseOperator.NotEquals, value: '2019-01-03T10:00:00Z' }];
|
||||||
|
filterAndVerify(filter, data, [entry1, entry3], 'NotEquals operator');
|
||||||
|
|
||||||
|
filter.clauses = [{ field: property1, operator: ProfilerFilterClauseOperator.GreaterThan, value: '2019-01-01T00:00:00Z' }];
|
||||||
|
filterAndVerify(filter, data, [entry1, entry2, entry3], 'GreaterThan operator');
|
||||||
|
|
||||||
|
filter.clauses = [{ field: property1, operator: ProfilerFilterClauseOperator.GreaterThanOrEquals, value: '2019-01-03T10:00:00.000Z' }];
|
||||||
|
filterAndVerify(filter, data, [entry2, entry3], 'GreaterThanOrEquals operator');
|
||||||
|
|
||||||
|
filter.clauses = [{ field: property1, operator: ProfilerFilterClauseOperator.LessThan, value: '2019-01-03T10:00:00.000Z' }];
|
||||||
|
filterAndVerify(filter, data, [entry1], 'LessThan operator');
|
||||||
|
|
||||||
|
filter.clauses = [{ field: property1, operator: ProfilerFilterClauseOperator.LessThanOrEquals, value: '2019-01-03T10:00:00Z' }];
|
||||||
|
filterAndVerify(filter, data, [entry1, entry2], 'LessThanOrEquals operator');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('string type filter data test', () => {
|
||||||
|
let filter: ProfilerFilter = { clauses: [] };
|
||||||
|
let entry1: TestData = { property1: '', property2: '' };
|
||||||
|
let entry2: TestData = { property1: 'test string', property2: '' };
|
||||||
|
let entry3: TestData = { property1: 'new string', property2: '' };
|
||||||
|
|
||||||
|
let data: TestData[] = [entry1, entry2, entry3];
|
||||||
|
|
||||||
|
filter.clauses = [{ field: property1, operator: ProfilerFilterClauseOperator.IsNull, value: '' }];
|
||||||
|
filterAndVerify(filter, data, [entry1], 'IsNull operator');
|
||||||
|
|
||||||
|
filter.clauses = [{ field: property1, operator: ProfilerFilterClauseOperator.IsNotNull, value: '' }];
|
||||||
|
filterAndVerify(filter, data, [entry2, entry3], 'IsNotNull operator');
|
||||||
|
|
||||||
|
filter.clauses = [{ field: property1, operator: ProfilerFilterClauseOperator.Contains, value: 'sTRing' }];
|
||||||
|
filterAndVerify(filter, data, [entry2, entry3], 'Contains operator');
|
||||||
|
|
||||||
|
filter.clauses = [{ field: property1, operator: ProfilerFilterClauseOperator.NotContains, value: 'string' }];
|
||||||
|
filterAndVerify(filter, data, [entry1], 'NotContains operator');
|
||||||
|
|
||||||
|
filter.clauses = [{ field: property1, operator: ProfilerFilterClauseOperator.StartsWith, value: 'tEst' }];
|
||||||
|
filterAndVerify(filter, data, [entry2], 'StartsWith operator');
|
||||||
|
|
||||||
|
filter.clauses = [{ field: property1, operator: ProfilerFilterClauseOperator.NotStartsWith, value: 'Test' }];
|
||||||
|
filterAndVerify(filter, data, [entry1, entry3], 'NotStartsWith operator');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function filterAndVerify(filter: ProfilerFilter, data: TestData[], expectedResult: TestData[], stepName: string) {
|
||||||
|
let actualResult = FilterData(filter, data);
|
||||||
|
assert.equal(actualResult.length, expectedResult.length, `length check for ${stepName}`);
|
||||||
|
for (let i = 0; i < actualResult.length; i++) {
|
||||||
|
let actual = actualResult[i];
|
||||||
|
let expected = expectedResult[i];
|
||||||
|
assert(actual.property1 === expected.property1 && actual.property2 === expected.property2, `array content check for ${stepName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TestData {
|
||||||
|
property1: string;
|
||||||
|
property2: string;
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user