Skip to content

Commit edd9397

Browse files
authored
feat: DH-18354 Add input filter support to UITable (#1180)
Fixes #1080 I tested with this snippet. Then added an input filter and checked that all columns showed up and worked. Also tested clear all filters button and shortcut ```py from deephaven import ui, empty_table from deephaven.plot import express as dx _stocks = dx.data.stocks() my_tabs_basic = ui.flex( ui.table(_stocks), empty_table(10).update("I=i"), ) ```
1 parent 41d59de commit edd9397

13 files changed

Lines changed: 574 additions & 308 deletions

package-lock.json

Lines changed: 188 additions & 186 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/ui/src/js/package.json

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,25 +40,25 @@
4040
"react-dom": "^17.0.2"
4141
},
4242
"dependencies": {
43-
"@deephaven/chart": "^0.109.0",
44-
"@deephaven/components": "^0.109.0",
45-
"@deephaven/console": "^0.109.0",
46-
"@deephaven/dashboard": "^0.109.0",
47-
"@deephaven/dashboard-core-plugins": "^0.109.0",
48-
"@deephaven/golden-layout": "^0.109.0",
49-
"@deephaven/grid": "^0.109.0",
50-
"@deephaven/icons": "^0.109.0",
51-
"@deephaven/iris-grid": "^0.109.0",
52-
"@deephaven/jsapi-bootstrap": "^0.109.0",
53-
"@deephaven/jsapi-components": "^0.109.0",
43+
"@deephaven/chart": "^1.0.0",
44+
"@deephaven/components": "^1.0.0",
45+
"@deephaven/console": "^1.0.0",
46+
"@deephaven/dashboard": "^1.0.0",
47+
"@deephaven/dashboard-core-plugins": "^1.0.0",
48+
"@deephaven/golden-layout": "^1.0.0",
49+
"@deephaven/grid": "^1.0.0",
50+
"@deephaven/icons": "^1.0.0",
51+
"@deephaven/iris-grid": "^1.0.0",
52+
"@deephaven/jsapi-bootstrap": "^1.0.0",
53+
"@deephaven/jsapi-components": "^1.0.0",
5454
"@deephaven/jsapi-types": "^1.0.0-dev0.35.0",
55-
"@deephaven/jsapi-utils": "^0.109.0",
56-
"@deephaven/log": "^0.109.0",
57-
"@deephaven/plugin": "^0.109.0",
58-
"@deephaven/react-hooks": "^0.109.0",
59-
"@deephaven/redux": "^0.109.0",
60-
"@deephaven/test-utils": "^0.109.0",
61-
"@deephaven/utils": "^0.109.0",
55+
"@deephaven/jsapi-utils": "^1.0.0",
56+
"@deephaven/log": "^1.0.0",
57+
"@deephaven/plugin": "^1.0.0",
58+
"@deephaven/react-hooks": "^1.0.0",
59+
"@deephaven/redux": "^1.0.0",
60+
"@deephaven/test-utils": "^1.0.0",
61+
"@deephaven/utils": "^1.0.0",
6262
"@fortawesome/react-fontawesome": "^0.2.0",
6363
"@internationalized/date": "^3.5.5",
6464
"classnames": "^2.5.1",

plugins/ui/src/js/src/elements/ObjectView.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ import type { dh } from '@deephaven/jsapi-types';
55

66
const log = Log.module('@deephaven/js-plugin-ui/ObjectView');
77

8-
export type ObjectViewProps = { object: dh.WidgetExportedObject };
8+
export type ObjectViewProps = {
9+
object: dh.WidgetExportedObject;
10+
__dhId?: string;
11+
};
12+
913
function ObjectView(props: ObjectViewProps): JSX.Element {
10-
const { object } = props;
14+
const { object, __dhId } = props;
1115
log.info('Object is', object);
1216
const { type } = object;
1317

@@ -32,7 +36,7 @@ function ObjectView(props: ObjectViewProps): JSX.Element {
3236
if (plugin != null) {
3337
const Component = plugin.component;
3438
// eslint-disable-next-line react/jsx-props-no-spreading
35-
return <Component {...props} fetch={fetch} />;
39+
return <Component {...props} fetch={fetch} __dhId={__dhId} />;
3640
}
3741

3842
log.warn('Unknown object type', object.type);

plugins/ui/src/js/src/elements/UITable/UITable.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ import {
2727
useTheme,
2828
viewStyleProps,
2929
} from '@deephaven/components';
30+
import {
31+
InputFilterEvent,
32+
useDashboardColumnFilters,
33+
} from '@deephaven/dashboard-core-plugins';
34+
import { useLayoutManager, useListener } from '@deephaven/dashboard';
3035
import { useApi } from '@deephaven/jsapi-bootstrap';
3136
import type { dh as DhType } from '@deephaven/jsapi-types';
3237
import Log from '@deephaven/log';
@@ -213,6 +218,7 @@ export function UITable({
213218
);
214219

215220
const dh = useApi();
221+
const { eventHub } = useLayoutManager();
216222
const theme = useTheme();
217223
const [irisGrid, setIrisGrid] = useState<IrisGridType | null>(null);
218224
const utils = useMemo(() => new IrisGridUtils(dh), [dh]);
@@ -527,6 +533,24 @@ export function UITable({
527533
};
528534
}, [irisGridServerProps, initialHydratedState]);
529535

536+
const inputFilters = useDashboardColumnFilters(
537+
model?.columns ?? EMPTY_ARRAY,
538+
model?.table
539+
);
540+
541+
const handleClearAllFilters = useCallback(() => {
542+
if (irisGrid == null) {
543+
return;
544+
}
545+
irisGrid.clearAllFilters();
546+
}, [irisGrid]);
547+
548+
useListener(
549+
eventHub,
550+
InputFilterEvent.CLEAR_ALL_FILTERS,
551+
handleClearAllFilters
552+
);
553+
530554
return model ? (
531555
<div
532556
// eslint-disable-next-line react/jsx-props-no-spreading
@@ -539,6 +563,7 @@ export function UITable({
539563
onStateChange={onStateChange}
540564
// eslint-disable-next-line react/jsx-props-no-spreading
541565
{...mergedIrisGridProps}
566+
inputFilters={inputFilters}
542567
/>
543568
</div>
544569
) : null;

plugins/ui/src/js/src/elements/utils/ElementUtils.test.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,32 +68,41 @@ describe('wrapElementChildren', () => {
6868

6969
it.each([
7070
[
71+
'single',
7172
mock.exportedA1,
72-
<ObjectView key={`${mock.exportedA1.type}-0`} object={mock.exportedA1} />,
73+
<ObjectView
74+
key={`${mock.exportedA1.type}-0`}
75+
object={mock.exportedA1}
76+
__dhId={`test-root/${mock.exportedA1.type}-0`}
77+
/>,
7378
],
7479
[
80+
'multiple',
7581
[mock.exportedA1, mock.exportedA2, mock.exportedB1],
7682
[
7783
<ObjectView
7884
key={`${mock.exportedA1.type}-0`}
7985
object={mock.exportedA1}
86+
__dhId={`test-root/${mock.exportedA1.type}-0`}
8087
/>,
8188
<ObjectView
8289
key={`${mock.exportedA1.type}-1`}
8390
object={mock.exportedA1}
91+
__dhId={`test-root/${mock.exportedA1.type}-1`}
8492
/>,
8593
<ObjectView
8694
key={`${mock.exportedB1.type}-0`}
8795
object={mock.exportedB1}
96+
__dhId={`test-root/${mock.exportedB1.type}-0`}
8897
/>,
8998
],
9099
],
91100
])(
92-
'should wrap exported object children in ObjectView: %s, %s',
93-
(children, expectedChildren) => {
101+
'should wrap exported object children in ObjectView: %s',
102+
(testName, children, expectedChildren) => {
94103
const actual = wrapElementChildren({
95104
[ELEMENT_KEY]: 'mock.element',
96-
props: { children },
105+
props: { children, __dhId: 'test-root' },
97106
});
98107

99108
expect(actual.props?.children).toEqual(expectedChildren);
@@ -119,7 +128,7 @@ describe('wrapElementChildren', () => {
119128
],
120129
],
121130
])(
122-
'should wrap primitive item element children in Text elements: %s, %s',
131+
'should wrap primitive item element children in Text elements: %s',
123132
(children, expectedChildren) => {
124133
const givenProps: Record<string, unknown> = { children };
125134
if (textValue != null) {

plugins/ui/src/js/src/elements/utils/ElementUtils.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,15 @@ export function wrapElementChildren(element: ElementNode): ElementNode {
191191
const wrappedChildren = children.map(child => {
192192
// Exported objects need to be converted to `ObjectView` to be rendered
193193
if (isExportedObject(child)) {
194-
return <ObjectView key={getChildKey(child.type)} object={child} />;
194+
const key = getChildKey(child.type);
195+
return (
196+
<ObjectView
197+
key={key}
198+
object={child}
199+
// eslint-disable-next-line no-underscore-dangle
200+
__dhId={`${element.props?.__dhId}/${key}`}
201+
/>
202+
);
195203
}
196204

197205
// Auto wrap primitive children of `Item` elements in `Text` elements

plugins/ui/src/js/src/layout/ReactPanel.tsx

Lines changed: 46 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { nanoid } from 'nanoid';
1010
import {
1111
LayoutUtils,
1212
PanelEvent,
13+
PanelIdContext,
1314
useLayoutManager,
1415
useListener,
1516
} from '@deephaven/dashboard';
@@ -218,52 +219,52 @@ function ReactPanel({
218219
return portal
219220
? ReactDOM.createPortal(
220221
<ReactPanelContext.Provider value={panelId}>
221-
<View
222-
height="100%"
223-
width="100%"
224-
backgroundColor={backgroundColor}
225-
padding={padding}
226-
paddingTop={paddingTop}
227-
paddingBottom={paddingBottom}
228-
paddingStart={paddingStart}
229-
paddingEnd={paddingEnd}
230-
paddingX={paddingX}
231-
paddingY={paddingY}
232-
overflow={overflow}
233-
UNSAFE_style={UNSAFE_style}
234-
UNSAFE_className={
235-
UNSAFE_className == null
236-
? 'dh-react-panel'
237-
: `${UNSAFE_className} dh-react-panel`
238-
}
239-
>
240-
<Flex
241-
UNSAFE_className="dh-inner-react-panel"
242-
wrap={wrap}
243-
direction={direction}
244-
justifyContent={justifyContent}
245-
alignContent={alignContent}
246-
alignItems={alignItems}
247-
gap={gap}
248-
rowGap={rowGap}
249-
columnGap={columnGap}
222+
<PanelIdContext.Provider value={panelId}>
223+
<View
224+
height="100%"
225+
width="100%"
226+
backgroundColor={backgroundColor}
227+
padding={padding}
228+
paddingTop={paddingTop}
229+
paddingBottom={paddingBottom}
230+
paddingStart={paddingStart}
231+
paddingEnd={paddingEnd}
232+
paddingX={paddingX}
233+
paddingY={paddingY}
234+
overflow={overflow}
235+
UNSAFE_style={UNSAFE_style}
236+
UNSAFE_className={
237+
UNSAFE_className == null
238+
? 'dh-react-panel'
239+
: `${UNSAFE_className} dh-react-panel`
240+
}
250241
>
251-
<ReactPanelErrorBoundary onReset={onErrorReset}>
252-
{/**
253-
* Don't render the children if there's an error with the widget. If there's an error with the widget, we can assume the children won't render properly,
254-
* but we still want the panels to appear so things don't disappear/jump around.
255-
*/}
256-
<PersistentStateProvider
257-
initialState={initialData}
258-
onChange={onDataChange}
259-
>
260-
{React.Children.map(renderedChildren, child =>
261-
React.cloneElement(child as React.ReactElement)
262-
)}
263-
</PersistentStateProvider>
264-
</ReactPanelErrorBoundary>
265-
</Flex>
266-
</View>
242+
<Flex
243+
UNSAFE_className="dh-inner-react-panel"
244+
wrap={wrap}
245+
direction={direction}
246+
justifyContent={justifyContent}
247+
alignContent={alignContent}
248+
alignItems={alignItems}
249+
gap={gap}
250+
rowGap={rowGap}
251+
columnGap={columnGap}
252+
>
253+
<ReactPanelErrorBoundary onReset={onErrorReset}>
254+
{/**
255+
* Don't render the children if there's an error with the widget. If there's an error with the widget, we can assume the children won't render properly,
256+
* but we still want the panels to appear so things don't disappear/jump around.
257+
*/}
258+
<PersistentStateProvider
259+
initialState={initialData}
260+
onChange={onDataChange}
261+
>
262+
{renderedChildren ?? null}
263+
</PersistentStateProvider>
264+
</ReactPanelErrorBoundary>
265+
</Flex>
266+
</View>
267+
</PanelIdContext.Provider>
267268
</ReactPanelContext.Provider>,
268269
portal,
269270
contentKey

plugins/ui/src/js/src/widget/DashboardWidgetHandler.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ function DashboardWidgetHandler({
4141

4242
return (
4343
<WidgetHandler
44+
id={id}
4445
onDataChange={handleDataChange}
4546
onClose={handleClose}
4647
// eslint-disable-next-line react/jsx-props-no-spreading

plugins/ui/src/js/src/widget/WidgetHandler.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ function makeWidgetHandler({
4545
}: Partial<WidgetHandlerProps> = {}) {
4646
return (
4747
<WidgetHandler
48+
id="test-widget-handler"
4849
widgetDescriptor={widget}
4950
onClose={onClose}
5051
initialData={initialData}

0 commit comments

Comments
 (0)