Skip to content

Commit

Permalink
feat: improvements to null and empty strings filters in grid (#1348)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
dsmmcken and mofojed authored Jun 14, 2023
1 parent 626de83 commit ed3a8c5
Show file tree
Hide file tree
Showing 5 changed files with 316 additions and 28 deletions.
4 changes: 2 additions & 2 deletions packages/components/src/SelectValueList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Checkbox from './Checkbox';

export interface SelectItem<T> {
value: T;
displayValue?: string;
displayValue?: string | JSX.Element;
isSelected: boolean;
}

Expand Down Expand Up @@ -72,7 +72,7 @@ class SelectValueList<T> extends PureComponent<SelectValueListProps<T>> {
itemIndex: number,
key: number,
value: T,
displayValue: string | undefined,
displayValue: string | JSX.Element | undefined,
rowHeight: number,
isSelected: boolean,
disabled: boolean
Expand Down
21 changes: 14 additions & 7 deletions packages/iris-grid/src/AdvancedFilterCreatorSelectValueList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint react/no-did-update-set-state: "off" */
import React, { PureComponent } from 'react';
import React, { PureComponent, ReactElement } from 'react';
import { CSSTransition } from 'react-transition-group';
import type {
dh as DhType,
Expand Down Expand Up @@ -182,11 +182,18 @@ class AdvancedFilterCreatorSelectValueList<T = unknown> extends PureComponent<
for (let r = 0; r < data.rows.length; r += 1) {
const row = data.rows[r];
const value = row.get(column);
const displayValue = formatter.getFormattedString(
value,
column.type,
column.name
);
let displayValue: string | JSX.Element;
if (value == null) {
displayValue = <i className="text-muted">null</i>;
} else if (value === '') {
displayValue = <i className="text-muted">empty</i>;
} else {
displayValue = formatter.getFormattedString(
value,
column.type,
column.name
);
}
const isSelected = this.isValueSelected(value);
items.push({
displayValue,
Expand Down Expand Up @@ -263,7 +270,7 @@ class AdvancedFilterCreatorSelectValueList<T = unknown> extends PureComponent<
table.setViewport(topRow, bottomRow);
}

render(): React.ReactElement {
render(): ReactElement {
const { offset, isLoading, items, itemCount } = this.state;

return (
Expand Down
141 changes: 130 additions & 11 deletions packages/iris-grid/src/mousehandlers/IrisGridContextMenuHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
};

if (value == null) {
// null gets a special menu
if (quickFilters.get(modelColumn)) {
filterMenu.actions.push({
title: 'And',
Expand All @@ -426,6 +427,22 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
});
}
filterMenu.actions.push(...this.nullFilterActions(column));
} else if (value === '') {
// empty string gets a special menu
if (quickFilters.get(modelColumn)) {
filterMenu.actions.push({
title: 'And',

actions: this.emptyStringFilterActions(
column,
quickFilters.get(modelColumn),
'&&'
),
order: 2,
group: ContextActions.groups.high,
});
}
filterMenu.actions.push(...this.emptyStringFilterActions(column));
} else if (TableUtils.isBooleanType(column.type)) {
// boolean should have OR condition, and handles it's own null menu options
if (quickFilters.get(modelColumn)) {
Expand Down Expand Up @@ -918,6 +935,11 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
const actions = [];
const { model } = this.irisGrid.props;
const columnIndex = model.getColumnIndexByName(column.name);

const quickFilterValueText:
| string
| null = TableUtils.escapeQuickTextFilter(valueText);

assertNotNull(columnIndex);

actions.push({
Expand Down Expand Up @@ -946,7 +968,7 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
),
IrisGridContextMenuHandler.getQuickFilterText(
filterText,
`=${valueText}`,
`${quickFilterValueText}`,
operator
)
);
Expand All @@ -967,7 +989,7 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
),
IrisGridContextMenuHandler.getQuickFilterText(
filterText,
`!=${valueText}`,
`!=${quickFilterValueText}`,
operator
)
);
Expand All @@ -983,12 +1005,16 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
columnIndex,
IrisGridContextMenuHandler.getQuickFilterCondition(
filter,
column.filter().contains(filterValue),
column
.filter()
.isNull()
.not()
.and(column.filter().contains(filterValue)),
operator
),
IrisGridContextMenuHandler.getQuickFilterText(
filterText,
`~${valueText}`,
`~${quickFilterValueText}`,
operator
)
);
Expand All @@ -1004,12 +1030,15 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
columnIndex,
IrisGridContextMenuHandler.getQuickFilterCondition(
filter,
column.filter().contains(filterValue).not(),
column
.filter()
.isNull()
.or(column.filter().contains(filterValue).not()),
operator
),
IrisGridContextMenuHandler.getQuickFilterText(
filterText,
`!~${valueText}`,
`!~${quickFilterValueText}`,
operator
)
);
Expand All @@ -1025,12 +1054,16 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
columnIndex,
IrisGridContextMenuHandler.getQuickFilterCondition(
filter,
column.filter().invoke('startsWith', filterValue),
column
.filter()
.isNull()
.not()
.and(column.filter().invoke('startsWith', filterValue)),
operator
),
IrisGridContextMenuHandler.getQuickFilterText(
filterText,
`${valueText}*`,
`${quickFilterValueText}*`,
operator
)
);
Expand All @@ -1046,12 +1079,16 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
columnIndex,
IrisGridContextMenuHandler.getQuickFilterCondition(
filter,
column.filter().invoke('endsWith', filterValue),
column
.filter()
.isNull()
.not()
.and(column.filter().invoke('endsWith', filterValue)),
operator
),
IrisGridContextMenuHandler.getQuickFilterText(
filterText,
`*${valueText}`,
`*${quickFilterValueText}`,
operator
)
);
Expand Down Expand Up @@ -1518,6 +1555,88 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
return actions;
}

emptyStringFilterActions(
column: Column,
quickFilter?: QuickFilter,
operator?: '&&' | '||' | null
): ContextAction[] {
const { dh } = this;
const filterValue = dh.FilterValue.ofString('');
let newQuickFilter:
| {
filter: null | FilterCondition | undefined;
text: string | null;
}
| undefined
| null = quickFilter;
if (!newQuickFilter) {
newQuickFilter = { filter: null, text: null };
}
const { filter, text: filterText } = newQuickFilter;
const actions = [];
const { model } = this.irisGrid.props;
const columnIndex = model.getColumnIndexByName(column.name);
assertNotNull(columnIndex);

actions.push({
menuElement: (
<div className="iris-grid-filter-menu-item-value">
{operator
? IrisGridContextMenuHandler.getOperatorAsText(operator)
: ''}{' '}
<i className="text-muted">empty</i>
</div>
),
order: 1,
group: ContextActions.groups.high,
});

actions.push({
title: 'is empty string',
description: `Show only rows where ${column.name} is empty`,
action: () => {
this.irisGrid.setQuickFilter(
columnIndex,
IrisGridContextMenuHandler.getQuickFilterCondition(
filter,
column.filter().eq(filterValue),
operator
),
IrisGridContextMenuHandler.getQuickFilterText(
filterText,
`=`,
operator
)
);
},
order: 10,
group: ContextActions.groups.low,
});
actions.push({
title: 'is not empty string',
description: `Show only rows where ${column.name} is not empty`,
action: () => {
this.irisGrid.setQuickFilter(
columnIndex,
IrisGridContextMenuHandler.getQuickFilterCondition(
filter,
column.filter().notEq(filterValue),
operator
),
IrisGridContextMenuHandler.getQuickFilterText(
filterText,
`!=`,
operator
)
);
},
order: 20,
group: ContextActions.groups.low,
});

return actions;
}

nullFilterActions(
column: Column,
quickFilter?: QuickFilter,
Expand All @@ -1540,7 +1659,7 @@ class IrisGridContextMenuHandler extends GridMouseHandler {
{operator
? IrisGridContextMenuHandler.getOperatorAsText(operator)
: ''}{' '}
&quot;null&quot;
<i className="text-muted">null</i>
</div>
),
order: 1,
Expand Down
Loading

0 comments on commit ed3a8c5

Please sign in to comment.