add save/load filter feature to profiler (#6170)

* save/load profiler filter

* add role for custom buttons
This commit is contained in:
Alan Ren
2019-06-26 23:55:03 -07:00
committed by GitHub
parent c2cec5d93f
commit f39647f243
5 changed files with 106 additions and 38 deletions

View File

@@ -6,7 +6,6 @@
.profiler-filter-dialog { .profiler-filter-dialog {
height: 300px; height: 300px;
padding: 10px; padding: 10px;
overflow-y: scroll;
} }
.profiler-filter-clause-table { .profiler-filter-clause-table {
@@ -14,13 +13,21 @@
margin-bottom: 10px; margin-bottom: 10px;
} }
.clause-table-container{
max-height: 270px;
overflow-y: scroll;
}
.profiler-filter-remove-condition { .profiler-filter-remove-condition {
width:20px; width:20px;
height:20px; height:20px;
cursor: pointer; cursor: pointer;
} }
.profiler-filter-add-clause-prompt { .profiler-filter-clause-table-action {
cursor: pointer; cursor: pointer;
margin: 0px 2px 0px 2px margin-right: 10px;
padding: 2px;
text-decoration: underline;
display: inline-block;
} }

View File

@@ -12,7 +12,7 @@ import * as nls from 'vs/nls';
import { ProfilerInput } from 'sql/workbench/parts/profiler/browser/profilerInput'; import { ProfilerInput } from 'sql/workbench/parts/profiler/browser/profilerInput';
import { ProfilerEditor } from 'sql/workbench/parts/profiler/browser/profilerEditor'; import { ProfilerEditor } from 'sql/workbench/parts/profiler/browser/profilerEditor';
import { PROFILER_VIEW_TEMPLATE_SETTINGS, PROFILER_SESSION_TEMPLATE_SETTINGS, IProfilerViewTemplate, IProfilerSessionTemplate, EngineType } from 'sql/workbench/services/profiler/common/interfaces'; import { PROFILER_VIEW_TEMPLATE_SETTINGS, PROFILER_SESSION_TEMPLATE_SETTINGS, IProfilerViewTemplate, IProfilerSessionTemplate, EngineType, PROFILER_FILTER_SETTINGS } from 'sql/workbench/services/profiler/common/interfaces';
const profilerDescriptor = new EditorDescriptor( const profilerDescriptor = new EditorDescriptor(
ProfilerEditor, ProfilerEditor,
@@ -346,14 +346,20 @@ const profilerSessionTemplateSchema: IJSONSchema = {
] ]
}; };
const profilerFiltersSchema: IJSONSchema = {
description: nls.localize('profiler.settings.Filters', "Profiler Filters"),
type: 'array'
};
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration); const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
const dashboardConfig: IConfigurationNode = { const profilerConfig: IConfigurationNode = {
id: 'Profiler', id: 'Profiler',
type: 'object', type: 'object',
properties: { properties: {
[PROFILER_VIEW_TEMPLATE_SETTINGS]: profilerViewTemplateSchema, [PROFILER_VIEW_TEMPLATE_SETTINGS]: profilerViewTemplateSchema,
[PROFILER_SESSION_TEMPLATE_SETTINGS]: profilerSessionTemplateSchema [PROFILER_SESSION_TEMPLATE_SETTINGS]: profilerSessionTemplateSchema,
[PROFILER_FILTER_SETTINGS]: profilerFiltersSchema
} }
}; };
configurationRegistry.registerConfiguration(dashboardConfig); configurationRegistry.registerConfiguration(profilerConfig);

View File

@@ -22,19 +22,20 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView
import { generateUuid } from 'vs/base/common/uuid'; import { generateUuid } from 'vs/base/common/uuid';
import * as DOM from 'vs/base/browser/dom'; import * as DOM from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ProfilerFilter, ProfilerFilterClause, ProfilerFilterClauseOperator } from 'sql/workbench/services/profiler/common/interfaces'; import { ProfilerFilter, ProfilerFilterClause, ProfilerFilterClauseOperator, IProfilerService } from 'sql/workbench/services/profiler/common/interfaces';
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
const ClearText: string = localize('profilerFilterDialog.clear', "Clear All"); const ClearText: string = localize('profilerFilterDialog.clear', "Clear all");
const ApplyText: string = localize('profilerFilterDialog.apply', "Apply"); const ApplyText: string = localize('profilerFilterDialog.apply', "Apply");
const OkText: string = localize('profilerFilterDialog.ok', "OK"); const OkText: string = localize('profilerFilterDialog.ok', "OK");
const CancelText: string = localize('profilerFilterDialog.cancel', "Cancel"); const CancelText: string = localize('profilerFilterDialog.cancel', "Cancel");
const DialogTitle: string = localize('profilerFilterDialog.title', "Filters"); const DialogTitle: string = localize('profilerFilterDialog.title', "Filters");
const RemoveText: string = localize('profilerFilterDialog.remove', "Remove"); const RemoveText: string = localize('profilerFilterDialog.remove', "Remove this clause");
const AddText: string = localize('profilerFilterDialog.add', "Add"); const SaveFilterText: string = localize('profilerFilterDialog.saveFilter', "Save Filter");
const AddClausePromptText: string = localize('profilerFilterDialog.addClauseText', "Click here to add a clause"); const LoadFilterText: string = localize('profilerFilterDialog.loadFilter', "Load Filter");
const AddClauseText: string = localize('profilerFilterDialog.addClauseText', "Add a clause");
const TitleIconClass: string = 'icon filterLabel'; const TitleIconClass: string = 'icon filterLabel';
const FieldText: string = localize('profilerFilterDialog.fieldColumn', "Field"); const FieldText: string = localize('profilerFilterDialog.fieldColumn', "Field");
@@ -61,9 +62,9 @@ export class ProfilerFilterDialog extends Modal {
private _clauseBuilder: HTMLElement; private _clauseBuilder: HTMLElement;
private _okButton: Button; private _okButton: Button;
private _cancelButton: Button; private _cancelButton: Button;
private _clearButton: Button;
private _applyButton: Button; private _applyButton: Button;
private _addClauseButton: Button; private _loadFilterButton: Button;
private _saveFilterButton: Button;
private _input: ProfilerInput; private _input: ProfilerInput;
private _clauseRows: ClauseRowUI[] = []; private _clauseRows: ClauseRowUI[] = [];
@@ -75,7 +76,8 @@ export class ProfilerFilterDialog extends Modal {
@ITelemetryService telemetryService: ITelemetryService, @ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService contextKeyService: IContextKeyService, @IContextKeyService contextKeyService: IContextKeyService,
@ILogService logService: ILogService, @ILogService logService: ILogService,
@IContextViewService private contextViewService: IContextViewService @IContextViewService private contextViewService: IContextViewService,
@IProfilerService private profilerService: IProfilerService
) { ) {
super('', TelemetryKeys.ProfilerFilter, telemetryService, layoutService, clipboardService, themeService, logService, contextKeyService, { isFlyout: false, hasTitleIcon: true }); super('', TelemetryKeys.ProfilerFilter, telemetryService, layoutService, clipboardService, themeService, logService, contextKeyService, { isFlyout: false, hasTitleIcon: true });
} }
@@ -96,21 +98,22 @@ export class ProfilerFilterDialog extends Modal {
this.title = DialogTitle; this.title = DialogTitle;
this.titleIconClassName = TitleIconClass; this.titleIconClassName = TitleIconClass;
this._register(attachModalDialogStyler(this, this._themeService)); this._register(attachModalDialogStyler(this, this._themeService));
this._addClauseButton = this.addFooterButton(AddText, () => this.addClauseRow(false), 'left'); this._saveFilterButton = this.addFooterButton(SaveFilterText, () => this.saveFilter(), 'left');
this._clearButton = this.addFooterButton(ClearText, () => this.handleClearButtonClick(), 'left'); this._loadFilterButton = this.addFooterButton(LoadFilterText, () => this.loadSavedFilter(), 'left');
this._applyButton = this.addFooterButton(ApplyText, () => this.filterSession()); this._applyButton = this.addFooterButton(ApplyText, () => this.filterSession());
this._okButton = this.addFooterButton(OkText, () => this.handleOkButtonClick()); this._okButton = this.addFooterButton(OkText, () => this.handleOkButtonClick());
this._cancelButton = this.addFooterButton(CancelText, () => this.hide()); this._cancelButton = this.addFooterButton(CancelText, () => this.hide());
this._register(attachButtonStyler(this._okButton, this._themeService)); this._register(attachButtonStyler(this._okButton, this._themeService));
this._register(attachButtonStyler(this._cancelButton, 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._applyButton, this._themeService));
this._register(attachButtonStyler(this._addClauseButton, this._themeService)); this._register(attachButtonStyler(this._saveFilterButton, this._themeService));
this._register(attachButtonStyler(this._loadFilterButton, this._themeService));
} }
protected renderBody(container: HTMLElement) { protected renderBody(container: HTMLElement) {
const body = DOM.append(container, DOM.$('.profiler-filter-dialog')); const body = DOM.append(container, DOM.$('.profiler-filter-dialog'));
this._clauseBuilder = DOM.append(body, DOM.$('table.profiler-filter-clause-table')); const clauseTableContainer = DOM.append(body, DOM.$('.clause-table-container'));
this._clauseBuilder = DOM.append(clauseTableContainer, DOM.$('table.profiler-filter-clause-table'));
const headerRow = DOM.append(this._clauseBuilder, DOM.$('tr')); const headerRow = DOM.append(this._clauseBuilder, DOM.$('tr'));
DOM.append(headerRow, DOM.$('td')).innerText = FieldText; DOM.append(headerRow, DOM.$('td')).innerText = FieldText;
DOM.append(headerRow, DOM.$('td')).innerText = OperatorText; DOM.append(headerRow, DOM.$('td')).innerText = OperatorText;
@@ -121,15 +124,8 @@ export class ProfilerFilterDialog extends Modal {
this.addClauseRow(true, clause.field, this.convertToOperatorString(clause.operator), clause.value); this.addClauseRow(true, clause.field, this.convertToOperatorString(clause.operator), clause.value);
}); });
const prompt = DOM.append(body, DOM.$('.profiler-filter-add-clause-prompt', { tabIndex: '0' })); this.createClauseTableActionLink(AddClauseText, body, () => { this.addClauseRow(false); });
prompt.innerText = AddClausePromptText; this.createClauseTableActionLink(ClearText, body, () => { this.handleClearButtonClick(); });
DOM.addDisposableListener(prompt, DOM.EventType.CLICK, () => this.addClauseRow(false));
DOM.addStandardDisposableListener(prompt, DOM.EventType.KEY_DOWN, (e: StandardKeyboardEvent) => {
if (e.equals(KeyCode.Space) || e.equals(KeyCode.Enter)) {
this.addClauseRow(false);
e.stopPropagation();
}
});
} }
protected layout(height?: number): void { protected layout(height?: number): void {
@@ -158,6 +154,21 @@ export class ProfilerFilterDialog extends Modal {
this._clauseRows = []; this._clauseRows = [];
} }
private createClauseTableActionLink(text: string, parent: HTMLElement, handler: () => void): void {
const actionLink = DOM.append(parent, DOM.$('.profiler-filter-clause-table-action', {
'tabIndex': '0',
'role': 'button'
}));
actionLink.innerText = text;
DOM.addDisposableListener(actionLink, DOM.EventType.CLICK, handler);
DOM.addStandardDisposableListener(actionLink, DOM.EventType.KEY_DOWN, (e: StandardKeyboardEvent) => {
if (e.equals(KeyCode.Space) || e.equals(KeyCode.Enter)) {
handler();
e.stopPropagation();
}
});
}
private createSelectBox(container: HTMLElement, options: string[], selectedOption: string, ariaLabel: string): SelectBox { private createSelectBox(container: HTMLElement, options: string[], selectedOption: string, ariaLabel: string): SelectBox {
const dropdown = new SelectBox(options, selectedOption, this.contextViewService, undefined, { ariaLabel: ariaLabel }); const dropdown = new SelectBox(options, selectedOption, this.contextViewService, undefined, { ariaLabel: ariaLabel });
dropdown.render(container); dropdown.render(container);
@@ -165,10 +176,29 @@ export class ProfilerFilterDialog extends Modal {
return dropdown; return dropdown;
} }
private filterSession() { private filterSession(): void {
this._input.filterSession(this.getFilter()); this._input.filterSession(this.getFilter());
} }
private saveFilter(): void {
this.profilerService.saveFilter(this.getFilter());
}
private loadSavedFilter(): void {
// for now we only have one saved filter, this is enough for what user asked for so far.
const savedFilters = this.profilerService.getFilters();
if (savedFilters && savedFilters.length > 0) {
const savedFilter = savedFilters[0];
this._clauseRows.forEach(clause => {
clause.row.remove();
});
this._clauseRows = [];
savedFilter.clauses.forEach(clause => {
this.addClauseRow(true, clause.field, this.convertToOperatorString(clause.operator), clause.value);
});
}
}
private getFilter(): ProfilerFilter { private getFilter(): ProfilerFilter {
const clauses: ProfilerFilterClause[] = []; const clauses: ProfilerFilterClause[] = [];
@@ -181,15 +211,20 @@ export class ProfilerFilterDialog extends Modal {
}); });
return { return {
name: 'default',
clauses: clauses clauses: clauses
}; };
} }
private addClauseRow(setInitialValue: boolean, field?: string, operator?: string, value?: string): any { private addClauseRow(setInitialValue: boolean, field?: string, operator?: string, value?: string): void {
const columns = this._input.columns.map(column => column.name);
if (field && !columns.includes(field)) {
return;
}
const row = DOM.append(this._clauseBuilder, DOM.$('tr')); const row = DOM.append(this._clauseBuilder, DOM.$('tr'));
const clauseId = generateUuid(); const clauseId = generateUuid();
const columns = this._input.columns.map(column => column.name);
const fieldDropDown = this.createSelectBox(DOM.append(row, DOM.$('td')), columns, columns[0], FieldText); const fieldDropDown = this.createSelectBox(DOM.append(row, DOM.$('td')), columns, columns[0], FieldText);
const operatorDropDown = this.createSelectBox(DOM.append(row, DOM.$('td')), Operators, Operators[0], OperatorText); const operatorDropDown = this.createSelectBox(DOM.append(row, DOM.$('td')), Operators, Operators[0], OperatorText);
@@ -201,7 +236,8 @@ export class ProfilerFilterDialog extends Modal {
const removeClauseButton = DOM.append(removeCell, DOM.$('.profiler-filter-remove-condition.icon.remove', { const removeClauseButton = DOM.append(removeCell, DOM.$('.profiler-filter-remove-condition.icon.remove', {
'tabIndex': '0', 'tabIndex': '0',
'aria-label': RemoveText, 'aria-label': RemoveText,
'title': RemoveText 'title': RemoveText,
'role': 'button'
})); }));
DOM.addStandardDisposableListener(removeClauseButton, DOM.EventType.KEY_DOWN, (e: StandardKeyboardEvent) => { DOM.addStandardDisposableListener(removeClauseButton, DOM.EventType.KEY_DOWN, (e: StandardKeyboardEvent) => {

View File

@@ -17,6 +17,7 @@ export type ProfilerSessionID = string;
export const PROFILER_VIEW_TEMPLATE_SETTINGS = 'profiler.viewTemplates'; export const PROFILER_VIEW_TEMPLATE_SETTINGS = 'profiler.viewTemplates';
export const PROFILER_SESSION_TEMPLATE_SETTINGS = 'profiler.sessionTemplates'; export const PROFILER_SESSION_TEMPLATE_SETTINGS = 'profiler.sessionTemplates';
export const PROFILER_FILTER_SETTINGS = 'profiler.filters';
export const PROFILER_SETTINGS = 'profiler'; export const PROFILER_SETTINGS = 'profiler';
/** /**
@@ -130,11 +131,21 @@ export interface IProfilerService {
* @param input input object * @param input input object
*/ */
launchFilterSessionDialog(input: ProfilerInput): void; launchFilterSessionDialog(input: ProfilerInput): void;
/**
* Gets the filters
*/
getFilters(): Array<ProfilerFilter>;
/**
* Saves the filter
* @param filter filter object
*/
saveFilter(filter: ProfilerFilter): void;
} }
export interface IProfilerSettings { export interface IProfilerSettings {
viewTemplates: Array<IProfilerViewTemplate>; viewTemplates: Array<IProfilerViewTemplate>;
sessionTemplates: Array<IProfilerSessionTemplate>; sessionTemplates: Array<IProfilerSessionTemplate>;
filters: Array<ProfilerFilter>;
} }
export interface IColumnViewTemplate { export interface IColumnViewTemplate {
@@ -160,6 +171,7 @@ export interface IProfilerSessionTemplate {
} }
export interface ProfilerFilter { export interface ProfilerFilter {
name?: string;
clauses: ProfilerFilterClause[]; clauses: ProfilerFilterClause[];
} }

View File

@@ -4,17 +4,14 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { IConnectionManagementService, IConnectionCompletionOptions, ConnectionType, RunQueryOnConnectionMode } from 'sql/platform/connection/common/connectionManagement'; import { IConnectionManagementService, IConnectionCompletionOptions, ConnectionType, RunQueryOnConnectionMode } from 'sql/platform/connection/common/connectionManagement';
import { import { ProfilerSessionID, IProfilerSession, IProfilerService, IProfilerViewTemplate, IProfilerSessionTemplate, PROFILER_SETTINGS, IProfilerSettings, EngineType, ProfilerFilter, PROFILER_FILTER_SETTINGS } from './interfaces';
ProfilerSessionID, IProfilerSession, IProfilerService, IProfilerViewTemplate, IProfilerSessionTemplate,
PROFILER_SETTINGS, IProfilerSettings, EngineType
} from './interfaces';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { ProfilerInput } from 'sql/workbench/parts/profiler/browser/profilerInput'; import { ProfilerInput } from 'sql/workbench/parts/profiler/browser/profilerInput';
import { ProfilerColumnEditorDialog } from 'sql/workbench/parts/profiler/browser/profilerColumnEditorDialog'; import { ProfilerColumnEditorDialog } from 'sql/workbench/parts/profiler/browser/profilerColumnEditorDialog';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotificationService } from 'vs/platform/notification/common/notification';
import { ICommandService } from 'vs/platform/commands/common/commands'; import { ICommandService } from 'vs/platform/commands/common/commands';
@@ -242,4 +239,14 @@ export class ProfilerService implements IProfilerService {
let dialog = this._instantiationService.createInstance(ProfilerFilterDialog); let dialog = this._instantiationService.createInstance(ProfilerFilterDialog);
dialog.open(input); dialog.open(input);
} }
public getFilters(): ProfilerFilter[] {
const config = <ProfilerFilter[]>this._configurationService.getValue(PROFILER_FILTER_SETTINGS);
return config;
}
public saveFilter(filter: ProfilerFilter): void {
const config = [filter];
this._configurationService.updateValue(PROFILER_FILTER_SETTINGS, config, ConfigurationTarget.USER);
}
} }