Skip to content
This repository was archived by the owner on Jun 4, 2024. It is now read-only.

Issue 564 - Customizable column filtering logical operator #751

Merged
merged 10 commits into from
Apr 21, 2020
2 changes: 1 addition & 1 deletion src/dash-table/components/ControlledTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,7 @@ export default class ControlledTable extends PureComponent<ControlledTableProps>
...(empty[1][1] ? ['dash-empty-11'] : []),
...(visibleColumns.length ? [] : ['dash-no-columns']),
...(virtualized.data.length ? [] : ['dash-no-data']),
...(filter_action !== TableAction.None ? [] : ['dash-no-filter']),
...(filter_action.type !== TableAction.None ? [] : ['dash-no-filter']),
...(fill_width ? ['dash-fill-width'] : []),
...(loading_state ? ['dash-loading'] : [])
];
Expand Down
2 changes: 1 addition & 1 deletion src/dash-table/components/EdgeFactory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export default class EdgeFactory {
columns,
visibleColumns,
(row_deletable ? 1 : 0) + (row_selectable ? 1 : 0),
filter_action !== TableAction.None,
filter_action.type !== TableAction.None,
workFilter.map,
fixed_columns,
fixed_rows,
Expand Down
17 changes: 12 additions & 5 deletions src/dash-table/components/FilterFactory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import memoizerCache from 'core/cache/memoizer';
import { memoizeOne } from 'core/memoizer';

import ColumnFilter from 'dash-table/components/Filter/Column';
import { ColumnId, IColumn, TableAction, IFilterFactoryProps, SetFilter } from 'dash-table/components/Table/props';
import { ColumnId, IColumn, TableAction, IFilterFactoryProps, SetFilter, FilterLogicalOperator } from 'dash-table/components/Table/props';
import derivedFilterStyles, { derivedFilterOpStyles } from 'dash-table/derived/filter/wrapperStyles';
import derivedHeaderOperations from 'dash-table/derived/header/operations';
import { derivedRelevantFilterStyles } from 'dash-table/derived/style';
Expand All @@ -32,18 +32,24 @@ export default class FilterFactory {

}

private onChange = (column: IColumn, map: Map<string, SingleColumnSyntaxTree>, setFilter: SetFilter, ev: any) => {
private onChange = (
column: IColumn,
map: Map<string, SingleColumnSyntaxTree>,
operator: FilterLogicalOperator,
setFilter: SetFilter, ev: any
) => {
Logger.debug('Filter -- onChange', column.id, ev.target.value && ev.target.value.trim());

const value = ev.target.value.trim();

updateColumnFilter(map, column, value, setFilter);
updateColumnFilter(map, column, operator, value, setFilter);
}

private filter = memoizerCache<[ColumnId, number]>()((
column: IColumn,
index: number,
map: Map<string, SingleColumnSyntaxTree>,
operator: FilterLogicalOperator,
setFilter: SetFilter
) => {
const ast = map.get(column.id.toString());
Expand All @@ -53,7 +59,7 @@ export default class FilterFactory {
className={`dash-filter column-${index}`}
columnId={column.id}
isValid={!ast || ast.isValid}
setFilter={this.onChange.bind(this, column, map, setFilter)}
setFilter={this.onChange.bind(this, column, map, operator, setFilter)}
value={ast && ast.query}
/>);
});
Expand Down Expand Up @@ -86,7 +92,7 @@ export default class FilterFactory {
visibleColumns
} = this.props;

if (filter_action === TableAction.None) {
if (filter_action.type === TableAction.None) {
return NO_FILTERS;
}

Expand All @@ -113,6 +119,7 @@ export default class FilterFactory {
column,
index,
map,
filter_action.operator,
setFilter
);
}, visibleColumns);
Expand Down
2 changes: 2 additions & 0 deletions src/dash-table/components/HeaderFactory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export default class HeaderFactory {
column_selectable,
columns,
data,
filter_action,
hidden_columns,
id,
map,
Expand Down Expand Up @@ -106,6 +107,7 @@ export default class HeaderFactory {
sort_action,
sort_mode,
sort_by,
filter_action.operator,
page_action,
setFilter,
setProps,
Expand Down
2 changes: 1 addition & 1 deletion src/dash-table/components/Table/derivedPropsHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export default () => {
const invalidatedSort = sortCache(sort_by);

const invalidateSelection =
(!invalidatedFilter.cached && !invalidatedFilter.first && filter_action === TableAction.Custom) ||
(!invalidatedFilter.cached && !invalidatedFilter.first && filter_action.type === TableAction.Custom) ||
(!invalidatedPagination.cached && !invalidatedPagination.first && page_action === TableAction.Custom) ||
(!invalidatedSort.cached && !invalidatedSort.first && sort_action === TableAction.Custom);

Expand Down
2 changes: 2 additions & 0 deletions src/dash-table/components/Table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export default class Table extends Component<SanitizedAndDerivedProps, Standalon
value: props.filter_query,
map: this.filterMap(
new Map<string, SingleColumnSyntaxTree>(),
props.filter_action.operator,
props.filter_query,
props.visibleColumns
)
Expand All @@ -60,6 +61,7 @@ export default class Table extends Component<SanitizedAndDerivedProps, Standalon
if (value !== nextProps.filter_query) {
const map = this.filterMap(
currentMap,
nextProps.filter_action.operator,
nextProps.filter_query,
nextProps.visibleColumns
);
Expand Down
15 changes: 13 additions & 2 deletions src/dash-table/components/Table/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ export interface IDerivedData {
indices: Indices;
}

export enum FilterLogicalOperator {
And = 'and',
Or = 'or'
}

export interface IFilterAction {
type: TableAction;
operator: FilterLogicalOperator;
}

export interface IViewportOffset {
rows: number;
columns: number;
Expand Down Expand Up @@ -299,7 +309,7 @@ export interface IProps {
editable?: boolean;
fill_width?: boolean;
filter_query?: string;
filter_action?: TableAction;
filter_action?: TableAction | IFilterAction;
hidden_columns?: string[];
include_headers_on_copy_paste?: boolean;
locale_format: INumberLocale;
Expand Down Expand Up @@ -410,6 +420,7 @@ export type SanitizedProps = Omit<Omit<
Merge<PropsWithDefaults, {
columns: Columns;
data: Data;
filter_action: IFilterAction;
fixed_columns: number;
fixed_rows: number;
loading_state: boolean;
Expand Down Expand Up @@ -442,7 +453,7 @@ export type SetFilter = (

export interface IFilterFactoryProps {
filter_query: string;
filter_action: TableAction;
filter_action: IFilterAction;
id: string;
map: Map<string, SingleColumnSyntaxTree>;
rawFilterQuery: string;
Expand Down
18 changes: 17 additions & 1 deletion src/dash-table/dash/DataTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -964,7 +964,23 @@ export const propTypes = {
* through a callback (where `filter_query` or `derived_filter_query_structure` would be the input
* and `data` would be the output).
*/
filter_action: PropTypes.oneOf(['custom', 'native', 'none']),
filter_action: PropTypes.oneOfType([
PropTypes.oneOf([
'custom',
'native',
'none'
]),
PropTypes.shape({
type: PropTypes.oneOf([
'custom',
'native'
]).isRequired,
operator: PropTypes.oneOf([
'and',
'or'
])
})
]),

/**
* The `sort_action` property enables data to be
Expand Down
12 changes: 11 additions & 1 deletion src/dash-table/dash/Sanitizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import {
SortAsNull,
TableAction,
ExportFormat,
ExportHeaders
ExportHeaders,
IFilterAction,
FilterLogicalOperator
} from 'dash-table/components/Table/props';
import headerRows from 'dash-table/derived/header/headerRows';
import resolveFlag from 'dash-table/derived/cell/resolveFlag';
Expand Down Expand Up @@ -70,6 +72,12 @@ const applyDefaultsToColumns = (

const applyDefaultToLocale = (locale: INumberLocale) => getLocale(locale);

const getFilterAction = (
action: TableAction | IFilterAction
): IFilterAction => typeof action === 'object' ?
{ type: action.type ?? TableAction.None, operator: action.operator ?? FilterLogicalOperator.And } :
{ type: action, operator: FilterLogicalOperator.And };

const getVisibleColumns = (
columns: Columns,
hiddenColumns: string[] | undefined
Expand All @@ -95,6 +103,7 @@ export default class Sanitizer {
columns,
data,
export_headers: headerFormat,
filter_action: this.getFilterAction(props.filter_action),
fixed_columns: getFixedColumns(props.fixed_columns, props.row_deletable, props.row_selectable),
fixed_rows: getFixedRows(props.fixed_rows, columns, props.filter_action),
loading_state: dataLoading(props.loading_state),
Expand All @@ -107,6 +116,7 @@ export default class Sanitizer {

private readonly applyDefaultsToColumns = memoizeOne(applyDefaultsToColumns);

private readonly getFilterAction = memoizeOne(getFilterAction);
private readonly getVisibleColumns = memoizeOne(getVisibleColumns);
}

Expand Down
7 changes: 4 additions & 3 deletions src/dash-table/derived/data/virtual.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import {
IDerivedData,
SortAsNull,
Columns,
TableAction
TableAction,
IFilterAction
} from 'dash-table/components/Table/props';
import { QuerySyntaxTree } from 'dash-table/syntax-tree';

const getter = (
columns: Columns,
data: Data,
filter_action: TableAction,
filter_action: IFilterAction,
filter_query: string,
sort_action: TableAction,
sort_by: SortBy = []
Expand All @@ -26,7 +27,7 @@ const getter = (
map.set(datum, index);
}, data);

if (filter_action === TableAction.Native) {
if (filter_action.type === TableAction.Native) {
const tree = new QuerySyntaxTree(filter_query);

data = tree.isValid ?
Expand Down
24 changes: 15 additions & 9 deletions src/dash-table/derived/filter/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as R from 'ramda';

import { memoizeOneFactory } from 'core/memoizer';

import { Columns, IColumn, SetFilter } from 'dash-table/components/Table/props';
import { Columns, IColumn, SetFilter, FilterLogicalOperator } from 'dash-table/components/Table/props';
import { SingleColumnSyntaxTree, MultiColumnsSyntaxTree, getMultiColumnQueryString, getSingleColumnMap } from 'dash-table/syntax-tree';

const cloneIf = (
Expand All @@ -12,12 +12,12 @@ const cloneIf = (

export default memoizeOneFactory((
map: Map<string, SingleColumnSyntaxTree>,
operator: FilterLogicalOperator,
query: string,
columns: Columns
): Map<string, SingleColumnSyntaxTree> => {
const multiQuery = new MultiColumnsSyntaxTree(query);
const multiQuery = new MultiColumnsSyntaxTree(query, operator);
const reversedMap = getSingleColumnMap(multiQuery, columns);

/*
* Couldn't process the query, just use the previous value.
*/
Expand Down Expand Up @@ -74,36 +74,42 @@ function updateMap(map: Map<string, SingleColumnSyntaxTree>, column: IColumn, va
return newMap;
}

function updateState(map: Map<string, SingleColumnSyntaxTree>, setFilter: SetFilter) {
function updateState(
map: Map<string, SingleColumnSyntaxTree>,
operator: FilterLogicalOperator,
setFilter: SetFilter
) {
const asts = Array.from(map.values());
const globalFilter = getMultiColumnQueryString(asts);
const globalFilter = getMultiColumnQueryString(asts, operator);

const rawGlobalFilter = R.map(
ast => ast.query || '',
R.filter<SingleColumnSyntaxTree>(ast => Boolean(ast), asts)
).join(' && ');
).join(operator === FilterLogicalOperator.And ? ' && ' : ' || ');

setFilter(globalFilter, rawGlobalFilter, map);
}

export const updateColumnFilter = (
map: Map<string, SingleColumnSyntaxTree>,
column: IColumn,
operator: FilterLogicalOperator,
value: any,
setFilter: SetFilter
) => {
map = updateMap(map, column, value);
updateState(map, setFilter);
updateState(map, operator, setFilter);
};

export const clearColumnsFilter = (
map: Map<string, SingleColumnSyntaxTree>,
columns: Columns,
operator: FilterLogicalOperator,
setFilter: SetFilter
) => {
R.forEach(column => {
map = updateMap(map, column, '');
}, columns);

updateState(map, setFilter);
};
updateState(map, operator, setFilter);
};
11 changes: 7 additions & 4 deletions src/dash-table/derived/header/content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {
SetFilter,
SetProps,
SortMode,
TableAction
TableAction,
FilterLogicalOperator
} from 'dash-table/components/Table/props';
import getColumnFlag from 'dash-table/derived/header/columnFlag';
import * as actions from 'dash-table/utils/actions';
Expand All @@ -34,6 +35,7 @@ const doAction = (
selected_columns: string[],
column: IColumn,
columns: Columns,
operator: FilterLogicalOperator,
visibleColumns: Columns,
columnRowIndex: any,
mergeDuplicateHeaders: boolean,
Expand Down Expand Up @@ -64,7 +66,7 @@ const doAction = (
}
}, affectedColumIds);

clearColumnsFilter(map, affectedColumns, setFilter);
clearColumnsFilter(map, affectedColumns, operator, setFilter);
};

function doSort(columnId: ColumnId, sortBy: SortBy, mode: SortMode, setProps: SetProps) {
Expand Down Expand Up @@ -162,6 +164,7 @@ function getter(
sort_action: TableAction,
mode: SortMode,
sortBy: SortBy,
filterOperator: FilterLogicalOperator,
paginationMode: TableAction,
setFilter: SetFilter,
setProps: SetProps,
Expand Down Expand Up @@ -263,7 +266,7 @@ function getter(
null :
(<span
className='column-header--clear'
onClick={doAction(actions.clearColumn, selected_columns, column, columns, visibleColumns, headerRowIndex, mergeDuplicateHeaders, setFilter, setProps, map, data)}
onClick={doAction(actions.clearColumn, selected_columns, column, columns, filterOperator, visibleColumns, headerRowIndex, mergeDuplicateHeaders, setFilter, setProps, map, data)}
>
<FontAwesomeIcon icon='eraser' />
</span>)
Expand All @@ -275,7 +278,7 @@ function getter(
className={'column-header--delete' + (spansAllColumns ? ' disabled' : '')}
onClick={spansAllColumns ?
undefined :
doAction(actions.deleteColumn, selected_columns, column, columns, visibleColumns, headerRowIndex, mergeDuplicateHeaders, setFilter, setProps, map, data)
doAction(actions.deleteColumn, selected_columns, column, columns, filterOperator, visibleColumns, headerRowIndex, mergeDuplicateHeaders, setFilter, setProps, map, data)
}
>
<FontAwesomeIcon icon={['far', 'trash-alt']} />
Expand Down
5 changes: 3 additions & 2 deletions src/dash-table/syntax-tree/MultiColumnsSyntaxTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { ISyntaxTree } from 'core/syntax-tree/syntaxer';

import columnMultiLexicon from './lexicon/columnMulti';
import { ILexemeResult } from 'core/syntax-tree/lexer';
import { FilterLogicalOperator } from 'dash-table/components/Table/props';

export default class MultiColumnsSyntaxTree extends SyntaxTree {
constructor(query: string) {
super(columnMultiLexicon, query);
constructor(query: string, operator: FilterLogicalOperator) {
super(columnMultiLexicon(operator), query);
}
get isValid() {
return super.isValid &&
Expand Down
Loading