Skip to content

Commit ed3a8c5

Browse files
dsmmckenmofojed
andauthored
feat: improvements to null and empty strings filters in grid (#1348)
Fixes #1243 - advanced styling for null and empty strings - context menu styling for null and empty strings - escape the null when using filter by menu - escape any operator when using filter by menu - New special empty string context menu - Fixed broken startswith/endwith - Allow empty string search as just "=" or "!=" Any filter containing a backslashes however is still broken in the jsapi deephaven/deephaven-core#3912 --------- Co-authored-by: Mike Bender <mikebender@deephaven.io>
1 parent 626de83 commit ed3a8c5

5 files changed

Lines changed: 316 additions & 28 deletions

File tree

packages/components/src/SelectValueList.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Checkbox from './Checkbox';
66

77
export interface SelectItem<T> {
88
value: T;
9-
displayValue?: string;
9+
displayValue?: string | JSX.Element;
1010
isSelected: boolean;
1111
}
1212

@@ -72,7 +72,7 @@ class SelectValueList<T> extends PureComponent<SelectValueListProps<T>> {
7272
itemIndex: number,
7373
key: number,
7474
value: T,
75-
displayValue: string | undefined,
75+
displayValue: string | JSX.Element | undefined,
7676
rowHeight: number,
7777
isSelected: boolean,
7878
disabled: boolean

packages/iris-grid/src/AdvancedFilterCreatorSelectValueList.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint react/no-did-update-set-state: "off" */
2-
import React, { PureComponent } from 'react';
2+
import React, { PureComponent, ReactElement } from 'react';
33
import { CSSTransition } from 'react-transition-group';
44
import type {
55
dh as DhType,
@@ -182,11 +182,18 @@ class AdvancedFilterCreatorSelectValueList<T = unknown> extends PureComponent<
182182
for (let r = 0; r < data.rows.length; r += 1) {
183183
const row = data.rows[r];
184184
const value = row.get(column);
185-
const displayValue = formatter.getFormattedString(
186-
value,
187-
column.type,
188-
column.name
189-
);
185+
let displayValue: string | JSX.Element;
186+
if (value == null) {
187+
displayValue = <i className="text-muted">null</i>;
188+
} else if (value === '') {
189+
displayValue = <i className="text-muted">empty</i>;
190+
} else {
191+
displayValue = formatter.getFormattedString(
192+
value,
193+
column.type,
194+
column.name
195+
);
196+
}
190197
const isSelected = this.isValueSelected(value);
191198
items.push({
192199
displayValue,
@@ -263,7 +270,7 @@ class AdvancedFilterCreatorSelectValueList<T = unknown> extends PureComponent<
263270
table.setViewport(topRow, bottomRow);
264271
}
265272

266-
render(): React.ReactElement {
273+
render(): ReactElement {
267274
const { offset, isLoading, items, itemCount } = this.state;
268275

269276
return (

packages/iris-grid/src/mousehandlers/IrisGridContextMenuHandler.tsx

Lines changed: 130 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
413413
};
414414

415415
if (value == null) {
416+
// null gets a special menu
416417
if (quickFilters.get(modelColumn)) {
417418
filterMenu.actions.push({
418419
title: 'And',
@@ -426,6 +427,22 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
426427
});
427428
}
428429
filterMenu.actions.push(...this.nullFilterActions(column));
430+
} else if (value === '') {
431+
// empty string gets a special menu
432+
if (quickFilters.get(modelColumn)) {
433+
filterMenu.actions.push({
434+
title: 'And',
435+
436+
actions: this.emptyStringFilterActions(
437+
column,
438+
quickFilters.get(modelColumn),
439+
'&&'
440+
),
441+
order: 2,
442+
group: ContextActions.groups.high,
443+
});
444+
}
445+
filterMenu.actions.push(...this.emptyStringFilterActions(column));
429446
} else if (TableUtils.isBooleanType(column.type)) {
430447
// boolean should have OR condition, and handles it's own null menu options
431448
if (quickFilters.get(modelColumn)) {
@@ -918,6 +935,11 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
918935
const actions = [];
919936
const { model } = this.irisGrid.props;
920937
const columnIndex = model.getColumnIndexByName(column.name);
938+
939+
const quickFilterValueText:
940+
| string
941+
| null = TableUtils.escapeQuickTextFilter(valueText);
942+
921943
assertNotNull(columnIndex);
922944

923945
actions.push({
@@ -946,7 +968,7 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
946968
),
947969
IrisGridContextMenuHandler.getQuickFilterText(
948970
filterText,
949-
`=${valueText}`,
971+
`${quickFilterValueText}`,
950972
operator
951973
)
952974
);
@@ -967,7 +989,7 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
967989
),
968990
IrisGridContextMenuHandler.getQuickFilterText(
969991
filterText,
970-
`!=${valueText}`,
992+
`!=${quickFilterValueText}`,
971993
operator
972994
)
973995
);
@@ -983,12 +1005,16 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
9831005
columnIndex,
9841006
IrisGridContextMenuHandler.getQuickFilterCondition(
9851007
filter,
986-
column.filter().contains(filterValue),
1008+
column
1009+
.filter()
1010+
.isNull()
1011+
.not()
1012+
.and(column.filter().contains(filterValue)),
9871013
operator
9881014
),
9891015
IrisGridContextMenuHandler.getQuickFilterText(
9901016
filterText,
991-
`~${valueText}`,
1017+
`~${quickFilterValueText}`,
9921018
operator
9931019
)
9941020
);
@@ -1004,12 +1030,15 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
10041030
columnIndex,
10051031
IrisGridContextMenuHandler.getQuickFilterCondition(
10061032
filter,
1007-
column.filter().contains(filterValue).not(),
1033+
column
1034+
.filter()
1035+
.isNull()
1036+
.or(column.filter().contains(filterValue).not()),
10081037
operator
10091038
),
10101039
IrisGridContextMenuHandler.getQuickFilterText(
10111040
filterText,
1012-
`!~${valueText}`,
1041+
`!~${quickFilterValueText}`,
10131042
operator
10141043
)
10151044
);
@@ -1025,12 +1054,16 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
10251054
columnIndex,
10261055
IrisGridContextMenuHandler.getQuickFilterCondition(
10271056
filter,
1028-
column.filter().invoke('startsWith', filterValue),
1057+
column
1058+
.filter()
1059+
.isNull()
1060+
.not()
1061+
.and(column.filter().invoke('startsWith', filterValue)),
10291062
operator
10301063
),
10311064
IrisGridContextMenuHandler.getQuickFilterText(
10321065
filterText,
1033-
`${valueText}*`,
1066+
`${quickFilterValueText}*`,
10341067
operator
10351068
)
10361069
);
@@ -1046,12 +1079,16 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
10461079
columnIndex,
10471080
IrisGridContextMenuHandler.getQuickFilterCondition(
10481081
filter,
1049-
column.filter().invoke('endsWith', filterValue),
1082+
column
1083+
.filter()
1084+
.isNull()
1085+
.not()
1086+
.and(column.filter().invoke('endsWith', filterValue)),
10501087
operator
10511088
),
10521089
IrisGridContextMenuHandler.getQuickFilterText(
10531090
filterText,
1054-
`*${valueText}`,
1091+
`*${quickFilterValueText}`,
10551092
operator
10561093
)
10571094
);
@@ -1518,6 +1555,88 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
15181555
return actions;
15191556
}
15201557

1558+
emptyStringFilterActions(
1559+
column: Column,
1560+
quickFilter?: QuickFilter,
1561+
operator?: '&&' | '||' | null
1562+
): ContextAction[] {
1563+
const { dh } = this;
1564+
const filterValue = dh.FilterValue.ofString('');
1565+
let newQuickFilter:
1566+
| {
1567+
filter: null | FilterCondition | undefined;
1568+
text: string | null;
1569+
}
1570+
| undefined
1571+
| null = quickFilter;
1572+
if (!newQuickFilter) {
1573+
newQuickFilter = { filter: null, text: null };
1574+
}
1575+
const { filter, text: filterText } = newQuickFilter;
1576+
const actions = [];
1577+
const { model } = this.irisGrid.props;
1578+
const columnIndex = model.getColumnIndexByName(column.name);
1579+
assertNotNull(columnIndex);
1580+
1581+
actions.push({
1582+
menuElement: (
1583+
<div className="iris-grid-filter-menu-item-value">
1584+
{operator
1585+
? IrisGridContextMenuHandler.getOperatorAsText(operator)
1586+
: ''}{' '}
1587+
<i className="text-muted">empty</i>
1588+
</div>
1589+
),
1590+
order: 1,
1591+
group: ContextActions.groups.high,
1592+
});
1593+
1594+
actions.push({
1595+
title: 'is empty string',
1596+
description: `Show only rows where ${column.name} is empty`,
1597+
action: () => {
1598+
this.irisGrid.setQuickFilter(
1599+
columnIndex,
1600+
IrisGridContextMenuHandler.getQuickFilterCondition(
1601+
filter,
1602+
column.filter().eq(filterValue),
1603+
operator
1604+
),
1605+
IrisGridContextMenuHandler.getQuickFilterText(
1606+
filterText,
1607+
`=`,
1608+
operator
1609+
)
1610+
);
1611+
},
1612+
order: 10,
1613+
group: ContextActions.groups.low,
1614+
});
1615+
actions.push({
1616+
title: 'is not empty string',
1617+
description: `Show only rows where ${column.name} is not empty`,
1618+
action: () => {
1619+
this.irisGrid.setQuickFilter(
1620+
columnIndex,
1621+
IrisGridContextMenuHandler.getQuickFilterCondition(
1622+
filter,
1623+
column.filter().notEq(filterValue),
1624+
operator
1625+
),
1626+
IrisGridContextMenuHandler.getQuickFilterText(
1627+
filterText,
1628+
`!=`,
1629+
operator
1630+
)
1631+
);
1632+
},
1633+
order: 20,
1634+
group: ContextActions.groups.low,
1635+
});
1636+
1637+
return actions;
1638+
}
1639+
15211640
nullFilterActions(
15221641
column: Column,
15231642
quickFilter?: QuickFilter,
@@ -1540,7 +1659,7 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
15401659
{operator
15411660
? IrisGridContextMenuHandler.getOperatorAsText(operator)
15421661
: ''}{' '}
1543-
&quot;null&quot;
1662+
<i className="text-muted">null</i>
15441663
</div>
15451664
),
15461665
order: 1,

0 commit comments

Comments
 (0)