mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Adding filtering dialog and action to OE (#22937)
* Adding init change * Adding filter cache in OE * Adding more filtering changes * Fixed stuff with dialog * Fixing filter * Adding support for connecitons * Fixed stuff * filtering * Fixing date * Filters * Removing is filtering supported * Removing contracts * Fixing filters * Fixing cache * Adding some accessibility changes * Reverting some more changes to pull in changes from the main * Adding comments * Fixing boolean operators * Fixing stuff * Fixing stuff * Fixing error handling and making dialog generic * Fixing more stuff * Making filter a generic dialog * adding erase icon * removing floating promises * Fixing compile issue * Adding support for choice filter with different and actual value. * Adding null checks * Adding durability type fix * Fixing filtering for providers that do not play well with empty filter properties
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||||
"version": "4.7.0.29",
|
"version": "4.7.0.31",
|
||||||
"downloadFileNames": {
|
"downloadFileNames": {
|
||||||
"Windows_86": "win-x86-net7.0.zip",
|
"Windows_86": "win-x86-net7.0.zip",
|
||||||
"Windows_64": "win-x64-net7.0.zip",
|
"Windows_64": "win-x64-net7.0.zip",
|
||||||
|
|||||||
19
src/sql/azdata.proposed.d.ts
vendored
19
src/sql/azdata.proposed.d.ts
vendored
@@ -1818,14 +1818,27 @@ declare module 'azdata' {
|
|||||||
/**
|
/**
|
||||||
* The list of choices for the filter property if the type is choice
|
* The list of choices for the filter property if the type is choice
|
||||||
*/
|
*/
|
||||||
choices: string[];
|
choices: NodeFilterChoicePropertyValue[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NodeFilterChoicePropertyValue {
|
||||||
|
/**
|
||||||
|
* The value of the choice
|
||||||
|
*/
|
||||||
|
value: string;
|
||||||
|
/**
|
||||||
|
* The display name of the choice
|
||||||
|
* If not specified, the value will be used as the display name
|
||||||
|
* If specified, the display name will be used in the dropdown
|
||||||
|
*/
|
||||||
|
displayName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NodeFilter {
|
export interface NodeFilter {
|
||||||
/**
|
/**
|
||||||
* The name of the filter property
|
* The name of the filter property
|
||||||
*/
|
*/
|
||||||
name: string;
|
displayName: string;
|
||||||
/**
|
/**
|
||||||
* The operator of the filter property
|
* The operator of the filter property
|
||||||
*/
|
*/
|
||||||
@@ -1833,7 +1846,7 @@ declare module 'azdata' {
|
|||||||
/**
|
/**
|
||||||
* The applied values of the filter property
|
* The applied values of the filter property
|
||||||
*/
|
*/
|
||||||
value: string | string[] | number | boolean | undefined;
|
value: string | string[] | number | number[] | boolean | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum NodeFilterPropertyDataType {
|
export enum NodeFilterPropertyDataType {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export class TableCellEditorFactory {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTextEditorClass(context: any, inputType: 'text' | 'number' = 'text'): any {
|
public getTextEditorClass(context: any, inputType: 'text' | 'number' | 'date' = 'text', presetValue?: string): any {
|
||||||
const self = this;
|
const self = this;
|
||||||
class TextEditor extends Disposable {
|
class TextEditor extends Disposable {
|
||||||
private _originalValue: string;
|
private _originalValue: string;
|
||||||
@@ -76,6 +76,8 @@ export class TableCellEditorFactory {
|
|||||||
this._register(self._options.onStyleChange(() => {
|
this._register(self._options.onStyleChange(() => {
|
||||||
self._options.editorStyler(this._input);
|
self._options.editorStyler(this._input);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
this._input.value = presetValue ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
private async commitEdit(): Promise<void> {
|
private async commitEdit(): Promise<void> {
|
||||||
@@ -96,11 +98,21 @@ export class TableCellEditorFactory {
|
|||||||
|
|
||||||
public loadValue(item: Slick.SlickData): void {
|
public loadValue(item: Slick.SlickData): void {
|
||||||
this._originalValue = self._options.valueGetter(item, this._args.column) ?? '';
|
this._originalValue = self._options.valueGetter(item, this._args.column) ?? '';
|
||||||
this._input.value = this._originalValue;
|
if (inputType === 'date') {
|
||||||
|
this._input.inputElement.valueAsDate = new Date(this._originalValue);
|
||||||
|
} else {
|
||||||
|
this._input.value = this._originalValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public applyValue(item: Slick.SlickData, state: string): void {
|
public applyValue(item: Slick.SlickData, state: string): void {
|
||||||
const activeCell = this._args.grid.getActiveCell();
|
const activeCell = this._args.grid.getActiveCell();
|
||||||
|
if (inputType === 'date') {
|
||||||
|
// Usually, the date picker will return the date in the local time zone and change the date to the previous day.
|
||||||
|
// We need to convert the date to UTC time zone to avoid this behavior so that the date will be the same as the
|
||||||
|
// date picked in the date picker.
|
||||||
|
state = new Date(state).toLocaleDateString(window.navigator.language, { timeZone: 'UTC' });
|
||||||
|
}
|
||||||
self._options.valueSetter(context, activeCell.row, item, this._args.column, state);
|
self._options.valueSetter(context, activeCell.row, item, this._args.column, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import { ActionRunner } from 'vs/base/common/actions';
|
|||||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||||
import { USE_ASYNC_SERVER_TREE_CONFIG } from 'sql/workbench/contrib/objectExplorer/common/serverGroup.contribution';
|
import { USE_ASYNC_SERVER_TREE_CONFIG } from 'sql/workbench/contrib/objectExplorer/common/serverGroup.contribution';
|
||||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||||
|
import { FilterDialog } from 'sql/workbench/services/objectExplorer/browser/filterDialog/filterDialog';
|
||||||
|
|
||||||
export const CONTEXT_SERVER_TREE_VIEW = new RawContextKey<ServerTreeViewView>('serverTreeView.view', ServerTreeViewView.all);
|
export const CONTEXT_SERVER_TREE_VIEW = new RawContextKey<ServerTreeViewView>('serverTreeView.view', ServerTreeViewView.all);
|
||||||
export const CONTEXT_SERVER_TREE_HAS_CONNECTIONS = new RawContextKey<boolean>('serverTreeView.hasConnections', false);
|
export const CONTEXT_SERVER_TREE_HAS_CONNECTIONS = new RawContextKey<boolean>('serverTreeView.hasConnections', false);
|
||||||
@@ -588,6 +589,43 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async filterElementChildren(node: TreeNode): Promise<void> {
|
||||||
|
await FilterDialog.getFiltersForProperties(
|
||||||
|
node.filterProperties,
|
||||||
|
localize('objectExplorer.filterDialogTitle', "(Preview) Filter Settings: {0}", node.getConnectionProfile().title),
|
||||||
|
localize('objectExplorer.nodePath', "Node Path: {0}", node.nodePath),
|
||||||
|
node.filters,
|
||||||
|
async (filters) => {
|
||||||
|
let errorListener;
|
||||||
|
try {
|
||||||
|
let expansionError = undefined;
|
||||||
|
errorListener = this._objectExplorerService.onUpdateObjectExplorerNodes(e => {
|
||||||
|
if (e.errorMessage) {
|
||||||
|
expansionError = e.errorMessage;
|
||||||
|
}
|
||||||
|
errorListener.dispose();
|
||||||
|
});
|
||||||
|
node.forceRefresh = true;
|
||||||
|
node.filters = filters || [];
|
||||||
|
if (this._tree instanceof AsyncServerTree) {
|
||||||
|
await this._tree.rerender(node);
|
||||||
|
}
|
||||||
|
await this.refreshElement(node);
|
||||||
|
await this._tree.expand(node);
|
||||||
|
if (expansionError) {
|
||||||
|
throw new Error(expansionError);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (errorListener) {
|
||||||
|
errorListener.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
this._instantiationService
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter connections based on view (recent/active)
|
* Filter connections based on view (recent/active)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -310,3 +310,57 @@ export class DeleteConnectionAction extends Action {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class FilterChildrenAction extends Action {
|
||||||
|
public static ID = 'objectExplorer.filterChildren';
|
||||||
|
public static LABEL = localize('objectExplorer.filterChildren', "Filter (Preview)");
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
id: string,
|
||||||
|
label: string,
|
||||||
|
private _node: TreeNode,
|
||||||
|
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService) {
|
||||||
|
super(id, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async run(): Promise<void> {
|
||||||
|
await this._objectExplorerService.getServerTreeView().filterElementChildren(this._node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RemoveFilterAction extends Action {
|
||||||
|
public static ID = 'objectExplorer.removeFilter';
|
||||||
|
public static LABEL = localize('objectExplorer.removeFilter', "Remove Filter");
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
id: string,
|
||||||
|
label: string,
|
||||||
|
private _node: TreeNode,
|
||||||
|
private _tree: AsyncServerTree | ITree,
|
||||||
|
private _profile: ConnectionProfile | undefined,
|
||||||
|
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService
|
||||||
|
) {
|
||||||
|
super(id, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async run(): Promise<void> {
|
||||||
|
let node = this._node;
|
||||||
|
let nodeToRefresh: ServerTreeElement = this._node;
|
||||||
|
if (this._profile) {
|
||||||
|
node = this._objectExplorerService.getObjectExplorerNode(this._profile);
|
||||||
|
nodeToRefresh = this._profile;
|
||||||
|
}
|
||||||
|
node.filters = [];
|
||||||
|
if (nodeToRefresh instanceof TreeNode) {
|
||||||
|
nodeToRefresh.forceRefresh = true;
|
||||||
|
}
|
||||||
|
if (this._tree instanceof AsyncServerTree) {
|
||||||
|
await this._tree.rerender(nodeToRefresh);
|
||||||
|
await this._tree.updateChildren(nodeToRefresh);
|
||||||
|
await this._tree.expand(nodeToRefresh);
|
||||||
|
} else {
|
||||||
|
await this._tree.refresh(nodeToRefresh);
|
||||||
|
await this._tree.expand(nodeToRefresh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,726 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import 'vs/css!./../media/filterDialog';
|
||||||
|
import { Button } from 'sql/base/browser/ui/button/button';
|
||||||
|
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
|
||||||
|
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||||
|
import { Modal } from 'sql/workbench/browser/modal/modal'
|
||||||
|
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration';
|
||||||
|
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||||
|
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
|
||||||
|
import { ILogService } from 'vs/platform/log/common/log';
|
||||||
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
|
import { localize } from 'vs/nls';
|
||||||
|
import { attachModalDialogStyler } from 'sql/workbench/common/styler';
|
||||||
|
import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
|
||||||
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
|
import * as azdata from 'azdata';
|
||||||
|
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
|
||||||
|
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||||
|
import { NodeFilterPropertyDataType, NodeFilterOperator } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
|
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||||
|
import { Table } from 'sql/base/browser/ui/table/table';
|
||||||
|
import { TableCellEditorFactory } from 'sql/base/browser/ui/table/tableCellEditorFactory';
|
||||||
|
import { Emitter } from 'vs/base/common/event';
|
||||||
|
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
|
||||||
|
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||||
|
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
|
||||||
|
import { TableHeaderRowHeight, TableRowHeight } from 'sql/workbench/browser/designer/designerTableUtil';
|
||||||
|
import { textFormatter } from 'sql/base/browser/ui/table/formatters';
|
||||||
|
import { Dropdown } from 'sql/base/browser/ui/editableDropdown/browser/dropdown';
|
||||||
|
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox';
|
||||||
|
import { TabbedPanel } from 'sql/base/browser/ui/panel/panel';
|
||||||
|
import { attachTableStyler } from 'sql/platform/theme/common/styler';
|
||||||
|
import { ButtonColumn } from 'sql/base/browser/ui/table/plugins/buttonColumn.plugin';
|
||||||
|
import Severity from 'vs/base/common/severity';
|
||||||
|
import { status } from 'vs/base/browser/ui/aria/aria';
|
||||||
|
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
|
||||||
|
|
||||||
|
// strings for filter dialog
|
||||||
|
const OkButtonText = localize('objectExplorer.okButtonText', "OK");
|
||||||
|
const CancelButtonText = localize('objectExplorer.cancelButtonText', "Cancel");
|
||||||
|
const ClearAllButtonText = localize('objectExplorer.clearAllButtonText', "Clear All");
|
||||||
|
const TitleIconClass: string = 'icon filterLabel';
|
||||||
|
|
||||||
|
// strings for filter operator select box
|
||||||
|
const EQUALS_SELECT_BOX = localize('objectExplorer.equalsSelectBox', "Equals");
|
||||||
|
const NOT_EQUALS_SELECT_BOX = localize('objectExplorer.notEqualsSelectBox', "Not Equals");
|
||||||
|
const LESS_THAN_SELECT_BOX = localize('objectExplorer.lessThanSelectBox', "Less Than");
|
||||||
|
const LESS_THAN_OR_EQUALS_SELECT_BOX = localize('objectExplorer.lessThanOrEqualsSelectBox', "Less Than Or Equals");
|
||||||
|
const GREATER_THAN_SELECT_BOX = localize('objectExplorer.greaterThanSelectBox', "Greater Than");
|
||||||
|
const GREATER_THAN_OR_EQUALS_SELECT_BOX = localize('objectExplorer.greaterThanOrEqualsSelectBox', "Greater Than Or Equals");
|
||||||
|
const BETWEEN_SELECT_BOX = localize('objectExplorer.betweenSelectBox', "Between");
|
||||||
|
const NOT_BETWEEN_SELECT_BOX = localize('objectExplorer.notBetweenSelectBox', "Not Between");
|
||||||
|
const CONTAINS_SELECT_BOX = localize('objectExplorer.containsSelectBox', "Contains");
|
||||||
|
const NOT_CONTAINS_SELECT_BOX = localize('objectExplorer.notContainsSelectBox', "Not Contains");
|
||||||
|
const AND_SELECT_BOX = localize('objectExplorer.andSelectBox', "And");
|
||||||
|
const IS_NULL_SELECT_BOX = localize('objectExplorer.isNullSelectBox', "Is Null");
|
||||||
|
const IS_NOT_NULL_SELECT_BOX = localize('objectExplorer.isNotNullSelectBox', "Is Not Null");
|
||||||
|
|
||||||
|
// strings for filter table column headers
|
||||||
|
const PROPERTY_NAME_COLUMN_HEADER = localize('objectExplorer.propertyNameColumnHeader', "Property");
|
||||||
|
const OPERATOR_COLUMN_HEADER = localize('objectExplorer.operatorColumnHeader', "Operator");
|
||||||
|
const VALUE_COLUMN_HEADER = localize('objectExplorer.valueColumnHeader', "Value");
|
||||||
|
const CLEAR_COLUMN_HEADER = localize('objectExplorer.clearColumnHeader', "Clear");
|
||||||
|
|
||||||
|
|
||||||
|
// strings for value select box for boolean type filters
|
||||||
|
const TRUE_SELECT_BOX = localize('objectExplorer.trueSelectBox', "True");
|
||||||
|
const FALSE_SELECT_BOX = localize('objectExplorer.falseSelectBox', "False");
|
||||||
|
|
||||||
|
function nodePathDisplayString(nodepath: string): string { return localize('objectExplorer.nodePath', "Node Path: {0}", nodepath) }
|
||||||
|
|
||||||
|
const PROPERTY_COLUMN_ID = 'property';
|
||||||
|
const OPERATOR_COLUMN_ID = 'operator';
|
||||||
|
const VALUE_COLUMN_ID = 'value';
|
||||||
|
const CLEAR_COLUMN_ID = 'clear';
|
||||||
|
|
||||||
|
export class FilterDialog extends Modal {
|
||||||
|
|
||||||
|
private _okButton?: Button;
|
||||||
|
private _cancelButton?: Button;
|
||||||
|
private _clearAllButton?: Button;
|
||||||
|
|
||||||
|
private filterTable: Table<Slick.SlickData>;
|
||||||
|
private _tableCellEditorFactory: TableCellEditorFactory;
|
||||||
|
private _onStyleChangeEventEmitter = new Emitter<void>();
|
||||||
|
private _description: HTMLElement;
|
||||||
|
private _onFilterApplied = new Emitter<azdata.NodeFilter[]>();
|
||||||
|
public readonly onFilterApplied = this._onFilterApplied.event;
|
||||||
|
private _onCloseEvent = new Emitter<void>();
|
||||||
|
public readonly onDialogClose = this._onCloseEvent.event;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private _properties: azdata.NodeFilterProperty[],
|
||||||
|
private _filterDialogTitle: string,
|
||||||
|
private _filterDialogSubtitle: string,
|
||||||
|
private _appliedFilters: azdata.NodeFilter[],
|
||||||
|
private applyFilterAction: (filters: azdata.NodeFilter[]) => Promise<void> | undefined,
|
||||||
|
@IThemeService themeService: IThemeService,
|
||||||
|
@IAdsTelemetryService telemetryService: IAdsTelemetryService,
|
||||||
|
@ILayoutService layoutService: ILayoutService,
|
||||||
|
@IClipboardService clipboardService: IClipboardService,
|
||||||
|
@ILogService logService: ILogService,
|
||||||
|
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService,
|
||||||
|
@IContextKeyService contextKeyService: IContextKeyService,
|
||||||
|
@IContextViewService private readonly _contextViewProvider: IContextViewService,
|
||||||
|
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,
|
||||||
|
@IQuickInputService private readonly _quickInputService: IQuickInputService,
|
||||||
|
@IErrorMessageService private _errorMessageService: IErrorMessageService,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
'ObjectExplorerServiceDialog',
|
||||||
|
'Object Explorer Service Dialog',
|
||||||
|
telemetryService,
|
||||||
|
layoutService,
|
||||||
|
clipboardService,
|
||||||
|
themeService,
|
||||||
|
logService,
|
||||||
|
textResourcePropertiesService,
|
||||||
|
contextKeyService,
|
||||||
|
{
|
||||||
|
dialogStyle: 'normal',
|
||||||
|
hasTitleIcon: true,
|
||||||
|
hasSpinner: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public open(): void {
|
||||||
|
this.render();
|
||||||
|
this.show();
|
||||||
|
this._okButton.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override render() {
|
||||||
|
super.render();
|
||||||
|
this.title = this._filterDialogTitle;
|
||||||
|
this.titleIconClassName = TitleIconClass;
|
||||||
|
this._register(attachModalDialogStyler(this, this._themeService));
|
||||||
|
this._okButton = this.addFooterButton(OkButtonText, async () => { await this.onApply() });
|
||||||
|
this._cancelButton = this.addFooterButton(CancelButtonText, () => { this.onClose() });
|
||||||
|
this._clearAllButton = this.addFooterButton(ClearAllButtonText, () => { this.onClearAll() }, 'left', true);
|
||||||
|
this._register(attachButtonStyler(this._okButton, this._themeService));
|
||||||
|
this._register(attachButtonStyler(this._cancelButton, this._themeService));
|
||||||
|
this._register(attachButtonStyler(this._clearAllButton, this._themeService));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderBody(container: HTMLElement): void {
|
||||||
|
const body = DOM.append(container, DOM.$('.filter-dialog-body'));
|
||||||
|
const subtitle = DOM.append(body, DOM.$('.filter-dialog-node-path'));
|
||||||
|
subtitle.innerText = nodePathDisplayString(this._filterDialogSubtitle);
|
||||||
|
const clauseTableContainer = DOM.append(body, DOM.$('.filter-table-container'));
|
||||||
|
const filter = DOM.append(clauseTableContainer, DOM.$('.filter-table'));
|
||||||
|
this._tableCellEditorFactory = new TableCellEditorFactory(
|
||||||
|
{
|
||||||
|
valueGetter: (item, column): string => {
|
||||||
|
|
||||||
|
// if the operator is And and the operator is date, we need to get the date from the previous
|
||||||
|
// row to make it more user friendly for the user to enter the next value.
|
||||||
|
if (column.field === VALUE_COLUMN_ID && item[OPERATOR_COLUMN_ID].value === AND_SELECT_BOX) {
|
||||||
|
const index = item.filterPropertyIndex;
|
||||||
|
const tableData = this.filterTable.getData().getItems();
|
||||||
|
if (this._properties[index].type === NodeFilterPropertyDataType.Date) {
|
||||||
|
let value1 = '';
|
||||||
|
for (let i = 0; i < tableData.length; i++) {
|
||||||
|
if (tableData[i].filterPropertyIndex === index) {
|
||||||
|
value1 = tableData[i].value.value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const value2 = item[column.field].value;
|
||||||
|
return value2 === '' ? value1 : value2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item[column.field].value;
|
||||||
|
},
|
||||||
|
valueSetter: (context: any, row: number, item: any, column: Slick.Column<Slick.SlickData>, value: string): void => {
|
||||||
|
item[column.field].value = value;
|
||||||
|
if (column.field === 'operator') {
|
||||||
|
const index = item.filterPropertyIndex;
|
||||||
|
const nodeOperator = this._properties[index].type;
|
||||||
|
if (nodeOperator === NodeFilterPropertyDataType.Date || nodeOperator === NodeFilterPropertyDataType.Number) {
|
||||||
|
if (value === BETWEEN_SELECT_BOX || value === NOT_BETWEEN_SELECT_BOX) {
|
||||||
|
|
||||||
|
const tableData = this.filterTable.getData().getItems();
|
||||||
|
if (tableData.length > row + 1) {
|
||||||
|
if (tableData[row + 1].operator.value === AND_SELECT_BOX) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const newRow: Slick.SlickData = {
|
||||||
|
property: {
|
||||||
|
value: ''
|
||||||
|
},
|
||||||
|
operator: {
|
||||||
|
value: AND_SELECT_BOX,
|
||||||
|
values: [AND_SELECT_BOX]
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
value: '',
|
||||||
|
values: []
|
||||||
|
},
|
||||||
|
filterPropertyIndex: tableData[row].filterPropertyIndex
|
||||||
|
};
|
||||||
|
const activeElement = this.filterTable.activeCell;
|
||||||
|
tableData.splice(row + 1, 0, newRow);
|
||||||
|
dataProvider.clear();
|
||||||
|
dataProvider.push(tableData);
|
||||||
|
this.filterTable.rerenderGrid();
|
||||||
|
this.filterTable.layout(new DOM.Dimension(600, (dataProvider.getItems().length + 2) * TableRowHeight));
|
||||||
|
this.filterTable.setActiveCell(activeElement.row, activeElement.cell);
|
||||||
|
} else {
|
||||||
|
const tableData = this.filterTable.getData().getItems();
|
||||||
|
if (tableData.length > row + 1) {
|
||||||
|
if (tableData[row + 1].operator.value === AND_SELECT_BOX) {
|
||||||
|
const activeElement = this.filterTable.activeCell;
|
||||||
|
tableData.splice(row + 1, 1);
|
||||||
|
dataProvider.clear();
|
||||||
|
dataProvider.push(tableData);
|
||||||
|
this.filterTable.rerenderGrid();
|
||||||
|
this.filterTable.layout(new DOM.Dimension(600, (dataProvider.getItems().length + 2) * TableRowHeight));
|
||||||
|
this.filterTable.setActiveCell(activeElement.row, activeElement.cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optionsGetter: (item, column): string[] => {
|
||||||
|
return item[column.field].values;
|
||||||
|
},
|
||||||
|
editorStyler: (component) => {
|
||||||
|
this.styleComponent(component);
|
||||||
|
},
|
||||||
|
onStyleChange: this._onStyleChangeEventEmitter.event
|
||||||
|
}, this._contextViewProvider
|
||||||
|
);
|
||||||
|
const columns: Slick.Column<Slick.SlickData>[] = [
|
||||||
|
{
|
||||||
|
id: PROPERTY_COLUMN_ID,
|
||||||
|
name: PROPERTY_NAME_COLUMN_HEADER,
|
||||||
|
field: PROPERTY_COLUMN_ID,
|
||||||
|
formatter: textFormatter,
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: OPERATOR_COLUMN_ID,
|
||||||
|
name: OPERATOR_COLUMN_HEADER,
|
||||||
|
editor: this._tableCellEditorFactory.getDropdownEditorClass(this, [], false),
|
||||||
|
field: OPERATOR_COLUMN_ID,
|
||||||
|
formatter: textFormatter,
|
||||||
|
width: 180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: VALUE_COLUMN_ID,
|
||||||
|
name: VALUE_COLUMN_HEADER,
|
||||||
|
width: 180,
|
||||||
|
formatter: textFormatter,
|
||||||
|
field: VALUE_COLUMN_ID
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const clearValueColumn = new ButtonColumn({
|
||||||
|
id: CLEAR_COLUMN_ID,
|
||||||
|
iconCssClass: 'icon erase',
|
||||||
|
name: CLEAR_COLUMN_HEADER,
|
||||||
|
title: CLEAR_COLUMN_HEADER,
|
||||||
|
width: 60,
|
||||||
|
resizable: true,
|
||||||
|
isFontIcon: true
|
||||||
|
});
|
||||||
|
this._register(clearValueColumn.onClick(e => {
|
||||||
|
const row = e.row;
|
||||||
|
const data = this.filterTable.getData().getItems();
|
||||||
|
data[row][VALUE_COLUMN_ID].value = '';
|
||||||
|
dataProvider.clear();
|
||||||
|
dataProvider.push(data);
|
||||||
|
this.filterTable.rerenderGrid();
|
||||||
|
}));
|
||||||
|
columns.push(clearValueColumn.definition);
|
||||||
|
|
||||||
|
|
||||||
|
const tableData: Slick.SlickData[] = [];
|
||||||
|
if (!this._appliedFilters) {
|
||||||
|
this._appliedFilters = [];
|
||||||
|
}
|
||||||
|
this._properties.forEach((f, i) => {
|
||||||
|
const appliedFilter = this._appliedFilters.find(filter => filter.displayName === f.displayName);
|
||||||
|
const filterOperators = this.getOperatorsForType(f.type);
|
||||||
|
const row: Slick.SlickData = {
|
||||||
|
property: {
|
||||||
|
value: f.displayName
|
||||||
|
},
|
||||||
|
operator: {
|
||||||
|
value: appliedFilter ? this.getFilterOperatorString(appliedFilter.operator) : filterOperators[0],
|
||||||
|
values: filterOperators
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
value: appliedFilter ? this.getStringValueForFilter(f, appliedFilter.value) : '',
|
||||||
|
values: this.getChoiceValuesForFilterProperties(f)
|
||||||
|
},
|
||||||
|
filterPropertyIndex: i
|
||||||
|
};
|
||||||
|
tableData.push(row);
|
||||||
|
|
||||||
|
if (appliedFilter?.operator === NodeFilterOperator.Between || appliedFilter?.operator === NodeFilterOperator.NotBetween) {
|
||||||
|
row.value.value = this.getStringValueForFilter(f, appliedFilter.value[0]);
|
||||||
|
const andRow: Slick.SlickData = {
|
||||||
|
property: {
|
||||||
|
value: ''
|
||||||
|
},
|
||||||
|
operator: {
|
||||||
|
value: AND_SELECT_BOX,
|
||||||
|
values: [AND_SELECT_BOX]
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
value: this.getStringValueForFilter(f, appliedFilter.value[1]),
|
||||||
|
values: []
|
||||||
|
},
|
||||||
|
datatype: f.type,
|
||||||
|
filterPropertyIndex: i
|
||||||
|
};
|
||||||
|
tableData.push(andRow);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const dataProvider = new TableDataView<Slick.SlickData>();
|
||||||
|
dataProvider.push(tableData);
|
||||||
|
|
||||||
|
|
||||||
|
// Sets up the editor for the value column
|
||||||
|
(<any>dataProvider).getItemMetadata = (row: number) => {
|
||||||
|
const rowData = dataProvider.getItem(row);
|
||||||
|
|
||||||
|
const filterProperty = this._properties[rowData.filterPropertyIndex];
|
||||||
|
let editor;
|
||||||
|
if (rowData.operator.value === AND_SELECT_BOX) {
|
||||||
|
if (filterProperty.type === NodeFilterPropertyDataType.Number) {
|
||||||
|
editor = this._tableCellEditorFactory.getTextEditorClass(this, 'number');
|
||||||
|
} else if (filterProperty.type === NodeFilterPropertyDataType.Date) {
|
||||||
|
editor = this._tableCellEditorFactory.getTextEditorClass(this, 'date');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (filterProperty.type === NodeFilterPropertyDataType.String) {
|
||||||
|
editor = this._tableCellEditorFactory.getTextEditorClass(this, 'text');
|
||||||
|
} else if (filterProperty.type === NodeFilterPropertyDataType.Date) {
|
||||||
|
editor = this._tableCellEditorFactory.getTextEditorClass(this, 'date');
|
||||||
|
} else if (filterProperty.type === NodeFilterPropertyDataType.Boolean) {
|
||||||
|
editor = this._tableCellEditorFactory.getDropdownEditorClass(this, [TRUE_SELECT_BOX, FALSE_SELECT_BOX], false);
|
||||||
|
} else if (filterProperty.type === NodeFilterPropertyDataType.Number) {
|
||||||
|
editor = this._tableCellEditorFactory.getTextEditorClass(this, 'number');
|
||||||
|
} else if (filterProperty.type === NodeFilterPropertyDataType.Choice) {
|
||||||
|
editor = this._tableCellEditorFactory.getDropdownEditorClass(this, this.getDropdownOptionsForChoiceProperty(<azdata.NodeFilterChoiceProperty>filterProperty), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
columns: {
|
||||||
|
value: {
|
||||||
|
editor: editor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.filterTable = new Table(filter, this._accessibilityService, this._quickInputService, {
|
||||||
|
dataProvider: dataProvider!,
|
||||||
|
columns: columns,
|
||||||
|
}, {
|
||||||
|
editable: true,
|
||||||
|
autoEdit: true,
|
||||||
|
dataItemColumnValueExtractor: (data: any, column: Slick.Column<Slick.SlickData>): string => {
|
||||||
|
if (column.field) {
|
||||||
|
return data[column.field]?.value;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rowHeight: TableRowHeight,
|
||||||
|
headerRowHeight: TableHeaderRowHeight,
|
||||||
|
editorLock: new Slick.EditorLock(),
|
||||||
|
autoHeight: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.filterTable.grid.onActiveCellChanged.subscribe((e, any) => {
|
||||||
|
if (this.filterTable.grid.getActiveCell()) {
|
||||||
|
const row = this.filterTable.grid.getActiveCell().row;
|
||||||
|
const data = this.filterTable.getData().getItems()[row];
|
||||||
|
let index = data.filterPropertyIndex;
|
||||||
|
const filterPropertyDescription = this._properties[index].description;
|
||||||
|
this._description.innerText = filterPropertyDescription;
|
||||||
|
// Announcing the filter property description for screen reader users
|
||||||
|
status(filterPropertyDescription);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.filterTable.registerPlugin(clearValueColumn);
|
||||||
|
this.filterTable.layout(new DOM.Dimension(600, (tableData.length + 2) * TableRowHeight));
|
||||||
|
this._register(attachTableStyler(this.filterTable, this._themeService));
|
||||||
|
|
||||||
|
this._description = DOM.append(body, DOM.$('.filter-dialog-description'));
|
||||||
|
this._description.innerHTML = this._properties[0].description;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected layout(height?: number): void {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override onClose() {
|
||||||
|
this.hide('close');
|
||||||
|
this._onCloseEvent.fire();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onClearAll() {
|
||||||
|
const tableAllData = this.filterTable.getData().getItems();
|
||||||
|
tableAllData.forEach((row) => {
|
||||||
|
row.value.value = '';
|
||||||
|
|
||||||
|
});
|
||||||
|
this.filterTable.rerenderGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method is called when the ok button is pressed
|
||||||
|
private async onApply(): Promise<void> {
|
||||||
|
const tableData = this.filterTable.getData().getItems();
|
||||||
|
|
||||||
|
this._appliedFilters = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < tableData.length; i++) {
|
||||||
|
const row = tableData[i];
|
||||||
|
let filterProperty = this._properties[row.filterPropertyIndex]
|
||||||
|
let filter: azdata.NodeFilter = {
|
||||||
|
displayName: row.property.value,
|
||||||
|
operator: this.getFilterOperatorEnum(row.operator.value),
|
||||||
|
value: this.getFilterValue(filterProperty.type, row.value.value, filterProperty),
|
||||||
|
};
|
||||||
|
|
||||||
|
const isMultipleValueFilter = filter.operator === NodeFilterOperator.Between || filter.operator === NodeFilterOperator.NotBetween;
|
||||||
|
if (isMultipleValueFilter) {
|
||||||
|
i++;
|
||||||
|
const row2 = tableData[i];
|
||||||
|
var value1 = this.getFilterValue(filterProperty.type, row.value.value, filterProperty);
|
||||||
|
var value2 = this.getFilterValue(filterProperty.type, row2.value.value, filterProperty);
|
||||||
|
filter.value = <string[] | number[]>[value1, value2];
|
||||||
|
if (filterProperty.type === NodeFilterPropertyDataType.Date) {
|
||||||
|
if (filter.value[0] === '' && filter.value[1] !== '') {
|
||||||
|
// start date not specified.
|
||||||
|
this._errorMessageService.showDialog(Severity.Error, '', localize('filterDialog.errorStartDate', "Start date is not specified."));
|
||||||
|
return;
|
||||||
|
} else if (filter.value[0] !== '' && filter.value[1] === '') {
|
||||||
|
// end date not specified.
|
||||||
|
this._errorMessageService.showDialog(Severity.Error, '', localize('filterDialog.errorEndDate', "End date is not specified."));
|
||||||
|
return;
|
||||||
|
} else if (new Date(filter.value[0]) > new Date(filter.value[1])) {
|
||||||
|
// start date is greater than end date.
|
||||||
|
this._errorMessageService.showDialog(Severity.Error, '', localize('filterDialog.errorDateRange', "Start date cannot be greater than end date."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (filterProperty.type === NodeFilterPropertyDataType.Number) {
|
||||||
|
if (filter.value[0] === '' && filter.value[1] !== '') {
|
||||||
|
// start number not specified.
|
||||||
|
this._errorMessageService.showDialog(Severity.Error, '', localize('filterDialog.errorStartNumber', "Start number is not specified."));
|
||||||
|
return;
|
||||||
|
} else if (filter.value[0] !== '' && filter.value[1] === '') {
|
||||||
|
// end number not specified.
|
||||||
|
this._errorMessageService.showDialog(Severity.Error, '', localize('filterDialog.errorEndNumber', "End number is not specified."));
|
||||||
|
return;
|
||||||
|
} else if (Number(filter.value[0]) > Number(filter.value[1])) {
|
||||||
|
// start number is greater than end number.
|
||||||
|
this._errorMessageService.showDialog(Severity.Error, '', localize('filterDialog.errorNumberRange', "Start number cannot be greater than end number."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (value1 !== '' && value2 !== '') {
|
||||||
|
this._appliedFilters.push(filter);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (filter.value !== '') {
|
||||||
|
this._appliedFilters.push(filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.spinner = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (this.applyFilterAction) {
|
||||||
|
await this.applyFilterAction(this._appliedFilters);
|
||||||
|
}
|
||||||
|
this._onFilterApplied.fire(this._appliedFilters);
|
||||||
|
this.hide('ok');
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
this.spinner = false;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method is called by modal when the enter button is pressed
|
||||||
|
// We override it to do nothing so that the enter button doesn't close the dialog
|
||||||
|
protected override async onAccept() {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
private getFilterValue(
|
||||||
|
filterType: NodeFilterPropertyDataType,
|
||||||
|
value: string,
|
||||||
|
filterProperty: azdata.NodeFilterProperty
|
||||||
|
): string | number | boolean {
|
||||||
|
if (value === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
switch (filterType) {
|
||||||
|
case NodeFilterPropertyDataType.Boolean:
|
||||||
|
if (value === TRUE_SELECT_BOX) {
|
||||||
|
return true;
|
||||||
|
} else if (value === FALSE_SELECT_BOX) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case NodeFilterPropertyDataType.Number:
|
||||||
|
return Number(value);
|
||||||
|
case NodeFilterPropertyDataType.Choice:
|
||||||
|
const choice = ((<azdata.NodeFilterChoiceProperty>filterProperty).choices.find(c => c.displayName === value));
|
||||||
|
if (choice) {
|
||||||
|
return choice.value;
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
case NodeFilterPropertyDataType.Date:
|
||||||
|
case NodeFilterPropertyDataType.String:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getStringValueForFilter(filter: azdata.NodeFilterProperty, value: string | number | boolean | number[] | string[]): string {
|
||||||
|
switch (filter.type) {
|
||||||
|
case NodeFilterPropertyDataType.Boolean:
|
||||||
|
if (value === true) {
|
||||||
|
return TRUE_SELECT_BOX;
|
||||||
|
} else if (value === false) {
|
||||||
|
return FALSE_SELECT_BOX;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NodeFilterPropertyDataType.Number:
|
||||||
|
return value.toString();
|
||||||
|
case NodeFilterPropertyDataType.Choice:
|
||||||
|
return (<azdata.NodeFilterChoiceProperty>filter).choices.find(c => c.value === value).displayName;
|
||||||
|
case NodeFilterPropertyDataType.Date:
|
||||||
|
case NodeFilterPropertyDataType.String:
|
||||||
|
return value as string;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private getOperatorsForType(type: NodeFilterPropertyDataType): string[] {
|
||||||
|
switch (type) {
|
||||||
|
case NodeFilterPropertyDataType.String:
|
||||||
|
return [
|
||||||
|
CONTAINS_SELECT_BOX,
|
||||||
|
NOT_CONTAINS_SELECT_BOX,
|
||||||
|
EQUALS_SELECT_BOX,
|
||||||
|
NOT_EQUALS_SELECT_BOX
|
||||||
|
];
|
||||||
|
case NodeFilterPropertyDataType.Number:
|
||||||
|
return [
|
||||||
|
EQUALS_SELECT_BOX,
|
||||||
|
NOT_EQUALS_SELECT_BOX,
|
||||||
|
GREATER_THAN_SELECT_BOX,
|
||||||
|
GREATER_THAN_OR_EQUALS_SELECT_BOX,
|
||||||
|
LESS_THAN_SELECT_BOX,
|
||||||
|
LESS_THAN_OR_EQUALS_SELECT_BOX,
|
||||||
|
BETWEEN_SELECT_BOX,
|
||||||
|
NOT_BETWEEN_SELECT_BOX
|
||||||
|
];
|
||||||
|
case NodeFilterPropertyDataType.Boolean:
|
||||||
|
return [
|
||||||
|
EQUALS_SELECT_BOX,
|
||||||
|
NOT_EQUALS_SELECT_BOX
|
||||||
|
];
|
||||||
|
case NodeFilterPropertyDataType.Choice:
|
||||||
|
return [
|
||||||
|
EQUALS_SELECT_BOX,
|
||||||
|
NOT_EQUALS_SELECT_BOX
|
||||||
|
];
|
||||||
|
case NodeFilterPropertyDataType.Date:
|
||||||
|
return [
|
||||||
|
EQUALS_SELECT_BOX,
|
||||||
|
NOT_EQUALS_SELECT_BOX,
|
||||||
|
GREATER_THAN_SELECT_BOX,
|
||||||
|
GREATER_THAN_OR_EQUALS_SELECT_BOX,
|
||||||
|
LESS_THAN_SELECT_BOX,
|
||||||
|
LESS_THAN_OR_EQUALS_SELECT_BOX,
|
||||||
|
BETWEEN_SELECT_BOX,
|
||||||
|
NOT_BETWEEN_SELECT_BOX
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getFilterOperatorString(operator: NodeFilterOperator): string {
|
||||||
|
switch (operator) {
|
||||||
|
case NodeFilterOperator.Contains:
|
||||||
|
return CONTAINS_SELECT_BOX;
|
||||||
|
case NodeFilterOperator.NotContains:
|
||||||
|
return NOT_CONTAINS_SELECT_BOX;
|
||||||
|
case NodeFilterOperator.Equals:
|
||||||
|
return EQUALS_SELECT_BOX;
|
||||||
|
case NodeFilterOperator.NotEquals:
|
||||||
|
return NOT_EQUALS_SELECT_BOX;
|
||||||
|
case NodeFilterOperator.GreaterThan:
|
||||||
|
return GREATER_THAN_SELECT_BOX;
|
||||||
|
case NodeFilterOperator.GreaterThanOrEquals:
|
||||||
|
return GREATER_THAN_OR_EQUALS_SELECT_BOX;
|
||||||
|
case NodeFilterOperator.LessThan:
|
||||||
|
return LESS_THAN_SELECT_BOX;
|
||||||
|
case NodeFilterOperator.LessThanOrEquals:
|
||||||
|
return LESS_THAN_OR_EQUALS_SELECT_BOX;
|
||||||
|
case NodeFilterOperator.Between:
|
||||||
|
return BETWEEN_SELECT_BOX;
|
||||||
|
case NodeFilterOperator.NotBetween:
|
||||||
|
return NOT_BETWEEN_SELECT_BOX;
|
||||||
|
case NodeFilterOperator.IsNull:
|
||||||
|
return IS_NULL_SELECT_BOX;
|
||||||
|
case NodeFilterOperator.IsNotNull:
|
||||||
|
return IS_NOT_NULL_SELECT_BOX;
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getFilterOperatorEnum(operator: string): NodeFilterOperator {
|
||||||
|
switch (operator) {
|
||||||
|
case CONTAINS_SELECT_BOX:
|
||||||
|
return NodeFilterOperator.Contains;
|
||||||
|
case NOT_CONTAINS_SELECT_BOX:
|
||||||
|
return NodeFilterOperator.NotContains;
|
||||||
|
case EQUALS_SELECT_BOX:
|
||||||
|
return NodeFilterOperator.Equals;
|
||||||
|
case NOT_EQUALS_SELECT_BOX:
|
||||||
|
return NodeFilterOperator.NotEquals;
|
||||||
|
case GREATER_THAN_SELECT_BOX:
|
||||||
|
return NodeFilterOperator.GreaterThan;
|
||||||
|
case GREATER_THAN_OR_EQUALS_SELECT_BOX:
|
||||||
|
return NodeFilterOperator.GreaterThanOrEquals;
|
||||||
|
case LESS_THAN_SELECT_BOX:
|
||||||
|
return NodeFilterOperator.LessThan;
|
||||||
|
case LESS_THAN_OR_EQUALS_SELECT_BOX:
|
||||||
|
return NodeFilterOperator.LessThanOrEquals;
|
||||||
|
case BETWEEN_SELECT_BOX:
|
||||||
|
return NodeFilterOperator.Between;
|
||||||
|
case NOT_BETWEEN_SELECT_BOX:
|
||||||
|
return NodeFilterOperator.NotBetween;
|
||||||
|
case TRUE_SELECT_BOX:
|
||||||
|
return NodeFilterOperator.Equals;
|
||||||
|
case FALSE_SELECT_BOX:
|
||||||
|
return NodeFilterOperator.NotEquals;
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getChoiceValuesForFilterProperties(f: azdata.NodeFilterProperty): string[] {
|
||||||
|
switch (f.type) {
|
||||||
|
case NodeFilterPropertyDataType.Boolean:
|
||||||
|
return ['', TRUE_SELECT_BOX, FALSE_SELECT_BOX];
|
||||||
|
case NodeFilterPropertyDataType.Choice:
|
||||||
|
return ['', ...this.getDropdownOptionsForChoiceProperty(<azdata.NodeFilterChoiceProperty>f)];
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDropdownOptionsForChoiceProperty(f: azdata.NodeFilterChoiceProperty): string[] {
|
||||||
|
return f.choices.map(choice => {
|
||||||
|
return choice.displayName ?? choice.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private styleComponent(component: TabbedPanel | InputBox | Checkbox | Table<Slick.SlickData> | SelectBox | Button | Dropdown): void {
|
||||||
|
if (component instanceof InputBox) {
|
||||||
|
this._register(attachInputBoxStyler(component, this._themeService));
|
||||||
|
} else if (component instanceof SelectBox) {
|
||||||
|
this._register(attachSelectBoxStyler(component, this._themeService));
|
||||||
|
} else if (component instanceof Table) {
|
||||||
|
this._register(attachTableStyler(component, this._themeService));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is used to let user apply filters on the given filters properties.
|
||||||
|
* @param properties Properties on which user can apply filters.
|
||||||
|
* @param filterDialogTitle Title of the filter dialog.
|
||||||
|
* @param filterDialogSubtile Subtitle of the filter dialog.
|
||||||
|
* @param appliedFilters Filters that are already applied so that we can prepopulate the filter dialog values.
|
||||||
|
* @param applyFilterAction Action to be performed when user clicks on apply button. We should pass this so that we can handle the spinner and error message within the dialog.
|
||||||
|
* @param instantiationService Instantiation service to create the filter dialog.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public static async getFiltersForProperties(
|
||||||
|
properties: azdata.NodeFilterProperty[],
|
||||||
|
filterDialogTitle: string,
|
||||||
|
filterDialogSubtile: string,
|
||||||
|
appliedFilters: azdata.NodeFilter[] | undefined,
|
||||||
|
applyFilterAction: (filters: azdata.NodeFilter[]) => Promise<void> | undefined,
|
||||||
|
instantiationService: IInstantiationService,
|
||||||
|
): Promise<azdata.NodeFilter[]> {
|
||||||
|
|
||||||
|
const dialog = instantiationService.createInstance(FilterDialog, properties, filterDialogTitle, filterDialogSubtile, appliedFilters, applyFilterAction);
|
||||||
|
dialog.open();
|
||||||
|
return new Promise<azdata.NodeFilter[]>((resolve, reject) => {
|
||||||
|
dialog.onFilterApplied(filters => {
|
||||||
|
resolve(filters);
|
||||||
|
});
|
||||||
|
dialog.onDialogClose(() => {
|
||||||
|
reject();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048">
|
||||||
|
<path fill="#fff" d="M1115 1792h421v128H453L50 1516q-24-24-37-56t-13-68q0-35 13-67t38-58L1248 69l794 795-927 928zm133-1542L538 960l614 613 709-709-613-614zM933 1792l128-128-613-614-306 307q-14 14-14 35t14 35l364 365h427z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 299 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048">
|
||||||
|
<path d="M1115 1792h421v128H453L50 1516q-24-24-37-56t-13-68q0-35 13-67t38-58L1248 69l794 795-927 928zm133-1542L538 960l614 613 709-709-613-614zM933 1792l128-128-613-614-306 307q-14 14-14 35t14 35l364 365h427z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 287 B |
@@ -0,0 +1,77 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
.filter-dialog-body {
|
||||||
|
height: 400px;
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-table-container .filter-table {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-dialog-body .filter-dialog-node-path {
|
||||||
|
padding: 10px 0px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-table-container {
|
||||||
|
max-height: 270px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-table-container table.filter-table-element {
|
||||||
|
width: 97.2%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-table-container table.filter-table-element td {
|
||||||
|
width: calc((100% - 36px) / (4 - 1));
|
||||||
|
/* equal width for all but last column */
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-table-container table.filter-table-element td.delete-cell {
|
||||||
|
width: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-action-button-container div {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-table-element .monaco-inputbox {
|
||||||
|
height: 25px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.filter-dialog-body .filter-dialog-description {
|
||||||
|
height: 50px;
|
||||||
|
border: 1px solid;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vs .icon.erase,
|
||||||
|
.hc-light .icon.erase {
|
||||||
|
background-image: url("eraseLight.svg");
|
||||||
|
width: 16px;
|
||||||
|
height: 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vs-dark .icon.erase,
|
||||||
|
.hc-black .icon.erase {
|
||||||
|
background-image: url("eraseDark.svg");
|
||||||
|
width: 16px;
|
||||||
|
height: 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
@@ -55,6 +55,7 @@ export interface IServerTreeView {
|
|||||||
renderBody(container: HTMLElement): Promise<void>;
|
renderBody(container: HTMLElement): Promise<void>;
|
||||||
layout(size: number): void;
|
layout(size: number): void;
|
||||||
showFilteredTree(view: ServerTreeViewView): void;
|
showFilteredTree(view: ServerTreeViewView): void;
|
||||||
|
filterElementChildren(node: TreeNode): Promise<void>;
|
||||||
view: ServerTreeViewView;
|
view: ServerTreeViewView;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,7 +485,14 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
this.logService.trace(`${session.sessionId}: got providers for node expansion: ${allProviders.map(p => p.providerId).join(', ')}`);
|
this.logService.trace(`${session.sessionId}: got providers for node expansion: ${allProviders.map(p => p.providerId).join(', ')}`);
|
||||||
|
|
||||||
const resolveExpansion = () => {
|
const resolveExpansion = () => {
|
||||||
resolve(self.mergeResults(allProviders, resultMap, node.nodePath));
|
const expansionResult = self.mergeResults(allProviders, resultMap, node.nodePath);
|
||||||
|
if (expansionResult.errorMessage || expansionResult.nodes.some(n => n.errorMessage)) {
|
||||||
|
this._onUpdateObjectExplorerNodes.fire({
|
||||||
|
connection: node.getConnectionProfile(),
|
||||||
|
errorMessage: expansionResult.errorMessage
|
||||||
|
});
|
||||||
|
}
|
||||||
|
resolve(expansionResult);
|
||||||
// Have to delete it after get all responses otherwise couldn't find session for not the first response
|
// Have to delete it after get all responses otherwise couldn't find session for not the first response
|
||||||
if (newRequest) {
|
if (newRequest) {
|
||||||
delete self._sessions[session.sessionId!].nodes[node.nodePath];
|
delete self._sessions[session.sessionId!].nodes[node.nodePath];
|
||||||
@@ -531,12 +539,15 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
});
|
});
|
||||||
if (newRequest) {
|
if (newRequest) {
|
||||||
allProviders.forEach(provider => {
|
allProviders.forEach(provider => {
|
||||||
self.callExpandOrRefreshFromProvider(provider, {
|
let expandRequest: azdata.ExpandNodeInfo = {
|
||||||
sessionId: session.sessionId!,
|
sessionId: session.sessionId!,
|
||||||
nodePath: node.nodePath,
|
nodePath: node.nodePath,
|
||||||
securityToken: session.securityToken,
|
securityToken: session.securityToken,
|
||||||
filters: node.filters
|
};
|
||||||
}, refresh).then(isExpanding => {
|
if (node?.filters?.length > 0) {
|
||||||
|
expandRequest.filters = node.filters;
|
||||||
|
}
|
||||||
|
self.callExpandOrRefreshFromProvider(provider, expandRequest, refresh).then(isExpanding => {
|
||||||
if (!isExpanding) {
|
if (!isExpanding) {
|
||||||
// The provider stated it's not going to expand the node, therefore do not need to track when merging results
|
// The provider stated it's not going to expand the node, therefore do not need to track when merging results
|
||||||
let emptyResult: azdata.ObjectExplorerExpandInfo = {
|
let emptyResult: azdata.ObjectExplorerExpandInfo = {
|
||||||
@@ -613,6 +624,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
finalResult.nodes = allNodes;
|
finalResult.nodes = allNodes;
|
||||||
|
finalResult.errorMessage = errorMessages.join('\n');
|
||||||
}
|
}
|
||||||
return finalResult;
|
return finalResult;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
DisconnectConnectionAction, EditConnectionAction,
|
DisconnectConnectionAction, EditConnectionAction,
|
||||||
DeleteConnectionAction, RefreshAction, EditServerGroupAction, AddServerAction
|
DeleteConnectionAction, RefreshAction, EditServerGroupAction, AddServerAction, FilterChildrenAction, RemoveFilterAction
|
||||||
} from 'sql/workbench/services/objectExplorer/browser/connectionTreeAction';
|
} from 'sql/workbench/services/objectExplorer/browser/connectionTreeAction';
|
||||||
import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode';
|
import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode';
|
||||||
import { NodeType } from 'sql/workbench/services/objectExplorer/common/nodeType';
|
import { NodeType } from 'sql/workbench/services/objectExplorer/common/nodeType';
|
||||||
@@ -27,6 +27,8 @@ import { fillInActions } from 'vs/platform/actions/browser/menuEntryActionViewIt
|
|||||||
import { AsyncServerTree, ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
|
import { AsyncServerTree, ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
|
||||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||||
import { ILogService } from 'vs/platform/log/common/log';
|
import { ILogService } from 'vs/platform/log/common/log';
|
||||||
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
|
import { CONFIG_WORKBENCH_ENABLEPREVIEWFEATURES } from 'sql/workbench/common/constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides actions for the server tree elements
|
* Provides actions for the server tree elements
|
||||||
@@ -42,7 +44,8 @@ export class ServerTreeActionProvider {
|
|||||||
@IMenuService private menuService: IMenuService,
|
@IMenuService private menuService: IMenuService,
|
||||||
@IContextKeyService private _contextKeyService: IContextKeyService,
|
@IContextKeyService private _contextKeyService: IContextKeyService,
|
||||||
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService,
|
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService,
|
||||||
@ILogService private _logService: ILogService
|
@ILogService private _logService: ILogService,
|
||||||
|
@IConfigurationService private _configurationService: IConfigurationService
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,8 +227,16 @@ export class ServerTreeActionProvider {
|
|||||||
// Contribute refresh action for scriptable objects via contribution
|
// Contribute refresh action for scriptable objects via contribution
|
||||||
if (!this.isScriptableObject(context)) {
|
if (!this.isScriptableObject(context)) {
|
||||||
actions.push(this._instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL, context.tree, context.treeNode || context.profile));
|
actions.push(this._instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL, context.tree, context.treeNode || context.profile));
|
||||||
}
|
|
||||||
|
|
||||||
|
// Adding filter action if the node has filter properties
|
||||||
|
if (treeNode?.filterProperties?.length > 0 && this._configurationService.getValue<boolean>(CONFIG_WORKBENCH_ENABLEPREVIEWFEATURES)) {
|
||||||
|
actions.push(this._instantiationService.createInstance(FilterChildrenAction, FilterChildrenAction.ID, FilterChildrenAction.LABEL, context.treeNode));
|
||||||
|
}
|
||||||
|
// Adding remove filter action if the node has filters applied to it.
|
||||||
|
if (treeNode?.filters?.length > 0) {
|
||||||
|
actions.push(this._instantiationService.createInstance(RemoveFilterAction, RemoveFilterAction.ID, RemoveFilterAction.LABEL, context.treeNode, context.tree, undefined));
|
||||||
|
}
|
||||||
|
}
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ export class TestObjectExplorerService implements IObjectExplorerService {
|
|||||||
|
|
||||||
public get onUpdateObjectExplorerNodes(): Event<ObjectExplorerNodeEventArgs> { throw new Error('Method not implemented'); }
|
public get onUpdateObjectExplorerNodes(): Event<ObjectExplorerNodeEventArgs> { throw new Error('Method not implemented'); }
|
||||||
|
|
||||||
|
public get onNodeExpandedError(): Event<NodeExpandInfoWithProviderId> { throw new Error('Method not implemented'); }
|
||||||
|
|
||||||
public get onSelectionOrFocusChange(): Event<void> { throw new Error('Method not implemented'); }
|
public get onSelectionOrFocusChange(): Event<void> { throw new Error('Method not implemented'); }
|
||||||
|
|
||||||
public async updateObjectExplorerNodes(connection: IConnectionProfile): Promise<void> { }
|
public async updateObjectExplorerNodes(connection: IConnectionProfile): Promise<void> { }
|
||||||
|
|||||||
Reference in New Issue
Block a user