-
-
Notifications
You must be signed in to change notification settings - Fork 73
Issue 545 - Case (in)sensitive filtering #893
Conversation
const match = query.match(next.regexp) ?? []; | ||
const value = match[next.regexpMatch || 0]; | ||
const flags = match[next.regexpFlags || -1]; | ||
result.push({lexeme: next, flags, value}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From a lexicon perspective treat i|s
prefix to applicable relational operators as "flags" that can be used to determine the exact processing to do during evaluation. value
is the "base" operator + flags (e.g. icontains
)
<FilterOptions | ||
filterOptions={filterOptions} | ||
toggleFilterOptions={toggleFilterOptions} | ||
/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New component displayed in the filter cell to toggle filtering options
@@ -193,6 +198,7 @@ export interface IBaseColumn { | |||
clearable?: boolean | boolean[] | 'first' | 'last'; | |||
deletable?: boolean | boolean[] | 'first' | 'last'; | |||
editable: boolean; | |||
filter_options: FilterCase; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FilterCase vs. filter_options
may look inconsistent but is intended to be that way. In the future you may also want to add locale-aware comparisons and leaving some space to do so at a future time.
) => { | ||
const newColumn = toggleFilterOptions(column); | ||
|
||
updateColumnFilter(map, newColumn, operator, value, setFilter); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trigger a setProps
and return the new column configuration, use the new column configuration to update the column's query (and trigger a refresh)
} | ||
|
||
export default (propsFn: () => ControlledTableProps) => { | ||
const setFilter = memoizeOne((setProps: SetProps, setState: SetState) => | ||
handleSetFilter.bind(undefined, setProps, setState) | ||
); | ||
|
||
const toggleFilterOptions = memoizeOne( | ||
(setProps: SetProps, columns: IColumn[]) => (column: IColumn) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Called by https://github.com/plotly/dash-table/pull/893/files#r622661715
Defined at the factory level so that the individual components remain unaware of columns
for memoization/cache busting / performance reasons and setProps
so they remain unaware of Dash's idiosyncracies.
|
||
setProps({columns: newColumns}); | ||
|
||
return newColumn; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
update the table's columns in the shallowest way possible (minimize cache busting)
): boolean => | ||
relOp[0] == 'i' | ||
? fn(lhs.toString().toUpperCase(), rhs.toString().toUpperCase()) | ||
: fn(lhs, rhs); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Conditional evaluation of contains, =, !=, >=, >, <=, < based on the flags
), | ||
subType: RelationalOperator.Contains, | ||
regexp: /^((contains)(?=\s|$))/i, | ||
regexp: /^((i|s)?contains)(?=\s|$)/i, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
allow for optional flags
), | ||
subType: RelationalOperator.Contains, | ||
regexp: /^((contains)(?=\s|$))/i, | ||
regexp: /^((i|s)?contains)(?=\s|$)/i, | ||
regexpFlags: 2, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
regex match group for usage in https://github.com/plotly/dash-table/pull/893/files#r622660202
), | ||
subType: RelationalOperator.Equal, | ||
regexp: /^(=|(eq)(?=\s|$))/i, | ||
regexp: /^((i|s)?(=|(eq)(?=\s|$)))/i, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same thing for all applicable relational operators
query: `${c.hideOperand ? '' : '{a} '}seq ABC`, | ||
target: {a: 'abc'}, | ||
valid: true, | ||
evaluate: false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Multiple new tests for the (in)sensitive versions of the operators -- as used by the table itself
expect(tree.isValid).to.equal(true); | ||
expect(tree.evaluate({a: 'abc'})).to.equal(true); | ||
expect(tree.evaluate({a: 'ABC'})).to.equal(false); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test all new operators with the various query flavors
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking nice so far! I'll probably do another more fine-grained pass of the code later. The functionality looks good.
If you have time @alexcjohnson I'd welcome a review from you as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Marc-Andre-Rivet Let's say I set a column to be case-insensitive using the toggle, or with column-level 'filter_options': 'insensitive'
. I know I can force case-insensitive comparison using e.g. ilt
, but I might also expect lt
to behave the same as ilt
, considering the case-insensitive toggle is on, and right now it doesn't on my end. Does that make sense?
@wbrgss Good point. Yes it makes total sense to have |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice @Marc-Andre-Rivet
💃
src/dash-table/dash/DataTable.js
Outdated
* If the column-level `filter_options` prop is set it overrides | ||
* the table-level `filter_options` prop for that column. | ||
*/ | ||
filter_options: PropTypes.oneOf(['sensitive', 'insensitive']), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After a bit of a slack convo with @Marc-Andre-Rivet I think we concluded we should change this to filter_options: {case: ('sensitive'|'insensitive')}
. The reason he wrote it as filter_options
instead of something more specific like filter_case
was to allow extending to ignoring other distinctions, like accents and punctuation. But that means the current syntax is confusing as it doesn't tell you what you're controlling the sensitivity to, and to extend later we'll have to support both these strings and objects like {case: 'sensitive', accents: 'insensitive', punctuation: 'sensitive'}
The alternative would be to make a single specific prop now, like filter_case: 'sensitive'
, and then add more props later filter_accents: 'sensitive'
, filter_punctuation: 'sensitive'
. That's a little easier for the most common use of enabling case-insensitive filters, but will ultimately create more props than it would need to, at both the table and column levels.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing this to an object works for me. I knew @Marc-Andre-Rivet wrote this with extensibility in mind and I agree this more cleanly allows for various filter-option combinations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
…tring to improve forward compatibility and consistency
- invert column and table filter_options priority (column takes precedence)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💃
This is so awesome! 🎊 |
Closes #545. Uses as as a starting point and supersedes #609.
@wbrgss What follows is not necessary for the review itself but, if you want to dig a little bit into the table's query parser / AST implementation, here's the key things to look at:
dash-table/src/core/syntax-tree/lexicon.ts
Line 13 in 8172683
dash-table/src/core/syntax-tree/lexicon.ts
Line 27 in 8172683
dash-table/src/core/syntax-tree/lexer.ts
Line 16 in 8172683
dash-table/src/core/syntax-tree/syntaxer.ts
Line 76 in 8172683