mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-13 17:22:15 -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#}",
|
||||
"version": "4.7.0.29",
|
||||
"version": "4.7.0.31",
|
||||
"downloadFileNames": {
|
||||
"Windows_86": "win-x86-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
|
||||
*/
|
||||
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 {
|
||||
/**
|
||||
* The name of the filter property
|
||||
*/
|
||||
name: string;
|
||||
displayName: string;
|
||||
/**
|
||||
* The operator of the filter property
|
||||
*/
|
||||
@@ -1833,7 +1846,7 @@ declare module 'azdata' {
|
||||
/**
|
||||
* The applied values of the filter property
|
||||
*/
|
||||
value: string | string[] | number | boolean | undefined;
|
||||
value: string | string[] | number | number[] | boolean | undefined;
|
||||
}
|
||||
|
||||
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;
|
||||
class TextEditor extends Disposable {
|
||||
private _originalValue: string;
|
||||
@@ -76,6 +76,8 @@ export class TableCellEditorFactory {
|
||||
this._register(self._options.onStyleChange(() => {
|
||||
self._options.editorStyler(this._input);
|
||||
}));
|
||||
|
||||
this._input.value = presetValue ?? '';
|
||||
}
|
||||
|
||||
private async commitEdit(): Promise<void> {
|
||||
@@ -96,11 +98,21 @@ export class TableCellEditorFactory {
|
||||
|
||||
public loadValue(item: Slick.SlickData): void {
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ import { ActionRunner } from 'vs/base/common/actions';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { USE_ASYNC_SERVER_TREE_CONFIG } from 'sql/workbench/contrib/objectExplorer/common/serverGroup.contribution';
|
||||
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_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)
|
||||
*/
|
||||
|
||||
@@ -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>;
|
||||
layout(size: number): void;
|
||||
showFilteredTree(view: ServerTreeViewView): void;
|
||||
filterElementChildren(node: TreeNode): Promise<void>;
|
||||
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(', ')}`);
|
||||
|
||||
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
|
||||
if (newRequest) {
|
||||
delete self._sessions[session.sessionId!].nodes[node.nodePath];
|
||||
@@ -531,12 +539,15 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
||||
});
|
||||
if (newRequest) {
|
||||
allProviders.forEach(provider => {
|
||||
self.callExpandOrRefreshFromProvider(provider, {
|
||||
let expandRequest: azdata.ExpandNodeInfo = {
|
||||
sessionId: session.sessionId!,
|
||||
nodePath: node.nodePath,
|
||||
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) {
|
||||
// The provider stated it's not going to expand the node, therefore do not need to track when merging results
|
||||
let emptyResult: azdata.ObjectExplorerExpandInfo = {
|
||||
@@ -613,6 +624,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
||||
});
|
||||
}
|
||||
finalResult.nodes = allNodes;
|
||||
finalResult.errorMessage = errorMessages.join('\n');
|
||||
}
|
||||
return finalResult;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
import {
|
||||
DisconnectConnectionAction, EditConnectionAction,
|
||||
DeleteConnectionAction, RefreshAction, EditServerGroupAction, AddServerAction
|
||||
DeleteConnectionAction, RefreshAction, EditServerGroupAction, AddServerAction, FilterChildrenAction, RemoveFilterAction
|
||||
} from 'sql/workbench/services/objectExplorer/browser/connectionTreeAction';
|
||||
import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode';
|
||||
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 { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||
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
|
||||
@@ -42,7 +44,8 @@ export class ServerTreeActionProvider {
|
||||
@IMenuService private menuService: IMenuService,
|
||||
@IContextKeyService private _contextKeyService: IContextKeyService,
|
||||
@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
|
||||
if (!this.isScriptableObject(context)) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,8 @@ export class TestObjectExplorerService implements IObjectExplorerService {
|
||||
|
||||
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 async updateObjectExplorerNodes(connection: IConnectionProfile): Promise<void> { }
|
||||
|
||||
Reference in New Issue
Block a user