Skip to content

Commit acc3eb9

Browse files
vbabichmofojed
andauthored
fix: DH-20654: Fix pivot still showing after worker is dead (#1274)
- Use `useObjectFetch` to track the parent query state and display error message when the query isn't running - Simplify `DashboardPlugin`, use `useDashboardPanel` hook - Extract duplicate pivot hydration hooks into `usePivotTableUtils` --------- Co-authored-by: Mike Bender <[email protected]>
1 parent a0698b9 commit acc3eb9

File tree

7 files changed

+209
-181
lines changed

7 files changed

+209
-181
lines changed

plugins/pivot/src/js/src/DashboardPlugin.tsx

Lines changed: 13 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,24 @@
1-
import React, { type DragEvent, useCallback, useEffect } from 'react';
2-
import { nanoid } from 'nanoid';
1+
import React from 'react';
32
import {
43
type DashboardPluginComponentProps,
5-
LayoutUtils,
6-
useListener,
4+
useDashboardPanel,
75
} from '@deephaven/dashboard';
8-
import type { dh } from '@deephaven/jsapi-types';
9-
import Log from '@deephaven/log';
106
import { assertNotNull } from '@deephaven/utils';
117
import PivotPanel from './PivotPanel';
128

139
const VARIABLE_TYPE = 'PivotTable';
1410

15-
const log = Log.module('@deephaven/js-plugin-pivot/DashboardPlugin');
16-
17-
export function DashboardPlugin({
18-
id,
19-
layout,
20-
registerComponent,
21-
}: DashboardPluginComponentProps): React.ReactNode {
22-
const handlePanelOpen = useCallback(
23-
({
24-
dragEvent,
25-
fetch,
26-
metadata = {},
27-
panelId = nanoid(),
28-
widget,
29-
}: {
30-
dragEvent?: DragEvent;
31-
fetch: () => Promise<dh.Widget>;
32-
metadata?: Record<string, unknown>;
33-
panelId?: string;
34-
widget: dh.ide.VariableDescriptor;
35-
}) => {
36-
const { name, type } = widget;
37-
if (type !== VARIABLE_TYPE) {
38-
// Ignore unsupported panel types
39-
return;
40-
}
41-
log.info('Panel opened of type', type);
42-
const config = {
43-
type: 'react-component' as const,
44-
component: PivotPanel.displayName,
45-
props: {
46-
localDashboardId: id,
47-
id: panelId,
48-
metadata: {
49-
...metadata,
50-
...widget,
51-
},
52-
fetch,
53-
},
54-
title: name ?? undefined,
55-
id: panelId,
56-
};
57-
58-
const { root } = layout;
59-
LayoutUtils.openComponent({ root, config, dragEvent });
60-
},
61-
[id, layout]
62-
);
63-
64-
useEffect(() => {
65-
assertNotNull(PivotPanel.displayName);
66-
const cleanups = [registerComponent(PivotPanel.displayName, PivotPanel)];
67-
return () => {
68-
cleanups.forEach(cleanup => cleanup());
69-
};
70-
}, [registerComponent]);
71-
72-
useListener(layout.eventHub, 'PanelEvent.OPEN', handlePanelOpen);
11+
export function DashboardPlugin(
12+
dashboardProps: DashboardPluginComponentProps
13+
): React.ReactNode {
14+
assertNotNull(PivotPanel.displayName);
15+
16+
useDashboardPanel({
17+
dashboardProps,
18+
componentName: PivotPanel.displayName,
19+
supportedTypes: VARIABLE_TYPE,
20+
component: PivotPanel,
21+
});
7322

7423
return null;
7524
}

plugins/pivot/src/js/src/PivotPanel.tsx

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,41 @@
11
import { forwardRef } from 'react';
2-
import { WidgetPanelProps } from '@deephaven/plugin';
32
import { type dh } from '@deephaven/jsapi-types';
4-
import { IrisGridPanel } from '@deephaven/dashboard-core-plugins';
3+
import { LoadingOverlay } from '@deephaven/components';
4+
import {
5+
IrisGridPanel,
6+
type IrisGridPanelProps,
7+
} from '@deephaven/dashboard-core-plugins';
8+
import { getErrorMessage } from '@deephaven/utils';
9+
import type { DashboardPanelProps } from '@deephaven/dashboard';
10+
import type { WidgetPanelProps } from '@deephaven/plugin';
511
import useHydratePivotGrid from './useHydratePivotGrid';
612

7-
// Unconnected IrisGridPanel type is not exported from dashboard-core-plugins
8-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
9-
export const PivotPanel = forwardRef<any, WidgetPanelProps<dh.Widget>>(
13+
export const PivotPanel = forwardRef(
14+
// Unconnected IrisGridPanel type is not exported from dashboard-core-plugins
1015
// eslint-disable-next-line @typescript-eslint/no-explicit-any
11-
(props: WidgetPanelProps<dh.Widget>, ref: React.Ref<any>): JSX.Element => {
12-
const { localDashboardId, fetch, metadata } = props;
16+
(panelProps: WidgetPanelProps<dh.Widget>, ref: React.Ref<any>) => {
17+
const { localDashboardId, metadata, panelState, ...props } =
18+
panelProps as DashboardPanelProps & {
19+
metadata?: dh.ide.VariableDescriptor;
20+
panelState?: IrisGridPanelProps['panelState'];
21+
};
1322

14-
const hydratedProps = useHydratePivotGrid(
15-
fetch,
16-
localDashboardId,
17-
metadata
18-
);
23+
const hydrateResult = useHydratePivotGrid(localDashboardId, metadata);
24+
25+
if (hydrateResult.status === 'loading') {
26+
return <LoadingOverlay isLoading />;
27+
}
28+
29+
if (hydrateResult.status === 'error') {
30+
return (
31+
<LoadingOverlay
32+
errorMessage={getErrorMessage(hydrateResult.error)}
33+
isLoading={false}
34+
/>
35+
);
36+
}
37+
38+
const { props: hydratedProps } = hydrateResult;
1939

2040
return (
2141
<IrisGridPanel
@@ -24,6 +44,7 @@ export const PivotPanel = forwardRef<any, WidgetPanelProps<dh.Widget>>(
2444
{...props}
2545
// eslint-disable-next-line react/jsx-props-no-spreading
2646
{...hydratedProps}
47+
panelState={panelState}
2748
/>
2849
);
2950
}

plugins/pivot/src/js/src/PivotWidget.tsx

Lines changed: 13 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,25 @@
1-
import { useCallback, useMemo } from 'react';
21
import { type WidgetComponentProps } from '@deephaven/plugin';
32
import { type dh as DhType } from '@deephaven/jsapi-types';
4-
import { IrisGrid, type MouseHandlersProp } from '@deephaven/iris-grid';
5-
import { useApi } from '@deephaven/jsapi-bootstrap';
6-
import { LoadingOverlay, useTheme } from '@deephaven/components';
3+
import { IrisGrid } from '@deephaven/iris-grid';
4+
import { LoadingOverlay } from '@deephaven/components';
75
import { getErrorMessage } from '@deephaven/utils';
8-
import Log from '@deephaven/log';
96
import { useIrisGridPivotModel } from './useIrisGridPivotModel';
10-
import PivotColumnGroupMouseHandler from './PivotColumnGroupMouseHandler';
11-
import { isCorePlusDh } from './PivotUtils';
12-
import IrisGridPivotRenderer from './IrisGridPivotRenderer';
13-
import { getIrisGridPivotTheme } from './IrisGridPivotTheme';
14-
15-
const log = Log.module('@deephaven/js-plugin-pivot/PivotWidget');
7+
import {
8+
usePivotTableFetch,
9+
usePivotMouseHandlers,
10+
usePivotRenderer,
11+
usePivotTheme,
12+
} from './usePivotTableUtils';
1613

1714
export function PivotWidget({
1815
fetch,
1916
}: WidgetComponentProps<DhType.Widget>): JSX.Element | null {
20-
const dh = useApi();
21-
22-
const mouseHandlers: MouseHandlersProp = useMemo(
23-
() => [irisGrid => new PivotColumnGroupMouseHandler(irisGrid)],
24-
[]
25-
);
26-
27-
const renderer = useMemo(() => new IrisGridPivotRenderer(), []);
28-
29-
const theme = useTheme();
30-
31-
const pivotTheme = useMemo(() => {
32-
log.debug('Theme changed, updating pivot theme', theme);
33-
return getIrisGridPivotTheme();
34-
}, [theme]);
35-
36-
const pivotTableFetch = useCallback(
37-
() =>
38-
fetch().then(result => {
39-
log.debug('pivotWidget fetch result:', result);
40-
if (!isCorePlusDh(dh)) {
41-
throw new Error('CorePlus is not available');
42-
}
43-
const pivot = new dh.coreplus.pivot.PivotTable(result);
44-
log.debug('Created pivot table:', pivot);
45-
return pivot;
46-
}),
47-
[dh, fetch]
48-
);
17+
const pivotFetch = usePivotTableFetch(fetch);
18+
const mouseHandlers = usePivotMouseHandlers();
19+
const renderer = usePivotRenderer();
20+
const pivotTheme = usePivotTheme();
4921

50-
const fetchResult = useIrisGridPivotModel(pivotTableFetch);
22+
const fetchResult = useIrisGridPivotModel(pivotFetch);
5123

5224
if (fetchResult.status === 'loading') {
5325
return <LoadingOverlay isLoading />;

plugins/pivot/src/js/src/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import { IrisGridPanel } from '@deephaven/dashboard-core-plugins';
1212
import useHydratePivotGrid from './useHydratePivotGrid';
1313

1414
function PivotPanel(props: WidgetPanelProps<dh.Widget>): JSX.Element {
15-
const { localDashboardId, fetch, metadata } = props;
15+
const { localDashboardId, metadata } = props;
1616

1717
// Provides makeModel, custom renderer, theme, and other props needed to render a Pivot in IrisGridPanel
18-
const hydratedProps = useHydratePivotGrid(fetch, localDashboardId, metadata);
18+
const hydratedProps = useHydratePivotGrid(localDashboardId, metadata);
1919

2020
return (
2121
<IrisGridPanel
Lines changed: 71 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,107 @@
1-
import { useCallback, useMemo } from 'react';
2-
import { useApi } from '@deephaven/jsapi-bootstrap';
1+
import { useApi, useObjectFetch } from '@deephaven/jsapi-bootstrap';
32
import type { dh } from '@deephaven-enterprise/jsapi-coreplus-types';
43
import {
54
useLoadTablePlugin,
65
type IrisGridPanelProps,
76
} from '@deephaven/dashboard-core-plugins';
8-
import type { MouseHandlersProp } from '@deephaven/iris-grid';
9-
import { useTheme } from '@deephaven/components';
7+
import { assertNotNull } from '@deephaven/utils';
108
import Log from '@deephaven/log';
119
import IrisGridPivotModel from './IrisGridPivotModel';
1210
import { isCorePlusDh } from './PivotUtils';
13-
import PivotColumnGroupMouseHandler from './PivotColumnGroupMouseHandler';
14-
import { IrisGridPivotRenderer } from './IrisGridPivotRenderer';
15-
import { getIrisGridPivotTheme } from './IrisGridPivotTheme';
16-
import PivotSortMouseHandler from './PivotSortMouseHandler';
11+
import {
12+
usePivotMouseHandlers,
13+
usePivotRenderer,
14+
usePivotTheme,
15+
} from './usePivotTableUtils';
1716

1817
const log = Log.module('@deephaven/js-plugin-pivot/useHydratePivotGrid');
1918

19+
export interface HydratePivotGridResultLoading {
20+
status: 'loading';
21+
}
22+
23+
export interface HydratePivotGridResultError {
24+
status: 'error';
25+
error: NonNullable<unknown>;
26+
}
27+
28+
export interface HydratePivotGridResultSuccess {
29+
props: { localDashboardId: string } & Pick<
30+
IrisGridPanelProps,
31+
| 'loadPlugin'
32+
| 'makeModel'
33+
| 'metadata'
34+
| 'mouseHandlers'
35+
| 'renderer'
36+
| 'theme'
37+
>;
38+
status: 'success';
39+
}
40+
41+
export type HydratePivotGridResult =
42+
| HydratePivotGridResultLoading
43+
| HydratePivotGridResultError
44+
| HydratePivotGridResultSuccess;
45+
2046
/**
2147
* Hydrate the props for a Pivot grid panel
22-
* @param fetch Function to fetch the Widget
2348
* @param id ID of the dashboard
2449
* @param metadata Optional serializable metadata for re-fetching the table later
2550
* @returns Props hydrated for a Pivot grid panel
2651
*/
2752
export function useHydratePivotGrid(
28-
fetch: () => Promise<dh.Widget>,
2953
id: string,
3054
metadata: dh.ide.VariableDescriptor | undefined
31-
): { localDashboardId: string } & Pick<
32-
IrisGridPanelProps,
33-
'loadPlugin' | 'makeModel'
34-
> {
55+
): HydratePivotGridResult {
56+
assertNotNull(metadata, 'Missing Pivot metadata');
57+
58+
// Manage loading and error states
59+
const objectFetch = useObjectFetch<dh.Widget>(metadata);
3560
const api = useApi();
3661
const loadPlugin = useLoadTablePlugin();
62+
const mouseHandlers = usePivotMouseHandlers();
63+
const renderer = usePivotRenderer();
64+
const theme = usePivotTheme();
3765

38-
const fetchTable = useCallback(
39-
() =>
40-
fetch().then(result => {
41-
log.debug('Pivot fetch result:', result);
42-
if (!isCorePlusDh(api)) {
43-
throw new Error('CorePlus is not available');
44-
}
45-
const pivot = new api.coreplus.pivot.PivotTable(result);
46-
log.debug('Created pivot table:', pivot);
47-
return pivot;
48-
}),
49-
[api, fetch]
50-
);
66+
const { status } = objectFetch;
5167

52-
const mouseHandlers: MouseHandlersProp = useMemo(
53-
() => [
54-
irisGrid => new PivotColumnGroupMouseHandler(irisGrid),
55-
irisGrid => new PivotSortMouseHandler(irisGrid),
56-
],
57-
[]
58-
);
68+
if (status === 'loading') {
69+
log.debug('Widget is loading');
70+
return { status: 'loading' };
71+
}
5972

60-
const renderer = useMemo(() => new IrisGridPivotRenderer(), []);
73+
if (status === 'error') {
74+
log.debug('Error fetching widget:', objectFetch.error);
75+
return {
76+
status: 'error',
77+
error: objectFetch.error,
78+
};
79+
}
6180

62-
const theme = useTheme();
81+
const { fetch } = objectFetch;
6382

64-
const pivotTheme = useMemo(() => {
65-
log.debug('Theme changed, updating pivot theme', theme);
66-
return getIrisGridPivotTheme();
67-
}, [theme]);
68-
69-
const hydratedProps = useMemo(
70-
() => ({
83+
return {
84+
status: 'success',
85+
props: {
7186
loadPlugin,
7287
localDashboardId: id,
73-
makeModel: async () => {
74-
const pivotWidget = await fetchTable();
75-
return new IrisGridPivotModel(api, pivotWidget);
88+
makeModel: async function makeModel() {
89+
log.debug('Fetching pivot widget');
90+
const widget = await fetch();
91+
log.debug('Pivot fetch result:', widget);
92+
if (!isCorePlusDh(api)) {
93+
throw new Error('CorePlus is not available');
94+
}
95+
const pivotTable = new api.coreplus.pivot.PivotTable(widget);
96+
log.debug('Created pivot table:', pivotTable);
97+
return new IrisGridPivotModel(api, pivotTable);
7698
},
7799
metadata,
78100
mouseHandlers,
79101
renderer,
80-
}),
81-
[api, fetchTable, id, loadPlugin, metadata, mouseHandlers, renderer]
82-
);
83-
84-
// Memoize the theme separately from the rest of the hydrated props
85-
// so that the the theme changes don't cause the model to be recreated
86-
const hydratedPropsWithTheme = useMemo(
87-
() => ({ ...hydratedProps, theme: pivotTheme }),
88-
[hydratedProps, pivotTheme]
89-
);
90-
91-
return hydratedPropsWithTheme;
102+
theme,
103+
},
104+
};
92105
}
93106

94107
export default useHydratePivotGrid;

0 commit comments

Comments
 (0)