Skip to content

Commit

Permalink
fix: Use ObjectFetcher to retrieve objects (#234)
Browse files Browse the repository at this point in the history
- In Enterprise environment, there may be multiple connections. We
should not be accessing the connection directly from the plugin for
fetching objects, instead use the ObjectFetcher registered by the app
- Pass in the object metadata to fetch the object
- Needs deephaven/web-client-ui#1753
  • Loading branch information
mofojed authored Feb 5, 2024
1 parent 39cf7db commit 728be7b
Show file tree
Hide file tree
Showing 10 changed files with 437 additions and 467 deletions.
724 changes: 362 additions & 362 deletions package-lock.json

Large diffs are not rendered by default.

26 changes: 13 additions & 13 deletions plugins/ui/src/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,19 @@
},
"dependencies": {
"@adobe/react-spectrum": "^3.29.0",
"@deephaven/chart": "^0.60.0",
"@deephaven/components": "^0.60.0",
"@deephaven/dashboard": "^0.60.0",
"@deephaven/dashboard-core-plugins": "^0.60.0",
"@deephaven/icons": "^0.60.0",
"@deephaven/iris-grid": "^0.60.0",
"@deephaven/jsapi-bootstrap": "^0.60.0",
"@deephaven/jsapi-components": "^0.60.0",
"@deephaven/jsapi-types": "^0.60.0",
"@deephaven/log": "^0.60.0",
"@deephaven/plugin": "^0.60.0",
"@deephaven/react-hooks": "^0.60.0",
"@deephaven/utils": "^0.60.0",
"@deephaven/chart": "0.62.0",
"@deephaven/components": "0.62.0",
"@deephaven/dashboard": "0.62.0",
"@deephaven/dashboard-core-plugins": "0.62.0",
"@deephaven/icons": "0.62.0",
"@deephaven/iris-grid": "0.62.0",
"@deephaven/jsapi-bootstrap": "0.62.0",
"@deephaven/jsapi-components": "0.62.0",
"@deephaven/jsapi-types": "0.62.0",
"@deephaven/log": "0.62.0",
"@deephaven/plugin": "0.62.0",
"@deephaven/react-hooks": "0.62.0",
"@deephaven/utils": "0.62.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"@react-types/shared": "^3.22.0",
"json-rpc-2.0": "^1.6.0",
Expand Down
73 changes: 27 additions & 46 deletions plugins/ui/src/js/src/DashboardPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ import {
useListener,
useDashboardPluginData,
emitCreateDashboard,
WidgetDescriptor,
PanelOpenEventDetail,
} from '@deephaven/dashboard';
import Log from '@deephaven/log';
import { useConnection } from '@deephaven/jsapi-components';
import { DeferredApiBootstrap } from '@deephaven/jsapi-bootstrap';
import {
DeferredApiBootstrap,
useObjectFetcher,
} from '@deephaven/jsapi-bootstrap';
import { Widget } from '@deephaven/jsapi-types';
import type { VariableDefinition } from '@deephaven/jsapi-types';
import { ErrorBoundary } from '@deephaven/components';
import styles from './styles.scss?inline';
import { WidgetWrapper } from './WidgetTypes';
Expand All @@ -31,20 +34,21 @@ interface DashboardPluginData {
type: string;
title: string;
id: string;
metadata: Record<string, unknown>;
widget: WidgetDescriptor;
}

export function DashboardPlugin({
id,
layout,
registerComponent,
}: DashboardPluginComponentProps): JSX.Element | null {
const connection = useConnection();
const [pluginData] = useDashboardPluginData(
id,
DASHBOARD_ELEMENT
) as unknown as [DashboardPluginData];

const objectFetcher = useObjectFetcher();

// Keep track of the widgets we've got opened.
const [widgetMap, setWidgetMap] = useState<
ReadonlyMap<string, WidgetWrapper>
Expand All @@ -53,31 +57,20 @@ export function DashboardPlugin({
const handleWidgetOpen = useCallback(
({
fetch,
metadata,
panelId: widgetId = shortid.generate(),
widgetId = shortid.generate(),
widget,
}: {
fetch: () => Promise<Widget>;
metadata: Record<string, unknown>;
panelId?: string;
widget: VariableDefinition;
widgetId: string;
widget: WidgetDescriptor;
}) => {
log.info('Opening widget with ID', widgetId, metadata);
log.info('Opening widget with ID', widgetId, widget);
setWidgetMap(prevWidgetMap => {
const newWidgetMap = new Map<string, WidgetWrapper>(prevWidgetMap);
// We need to create a new definition object, otherwise the layout will think it's already open
// Can't use a spread operator because the widget definition uses property accessors
const definition = {
type: widget.type,
title: widget.title,
id: widget.id,
name: widget.name,
};
newWidgetMap.set(widgetId, {
definition,
fetch,
id: widgetId,
metadata,
widget,
});
return newWidgetMap;
});
Expand All @@ -86,14 +79,8 @@ export function DashboardPlugin({
);

const handleDashboardOpen = useCallback(
({
widget,
metadata,
}: {
widget: VariableDefinition;
metadata: Record<string, unknown>;
}) => {
const { id: dashboardId, type, title = 'Untitled' } = widget;
({ widget }: { widget: WidgetDescriptor }) => {
const { id: dashboardId, type, name: title = 'Untitled' } = widget;
if (dashboardId == null) {
log.error("Can't open dashboard without an ID", widget);
return;
Expand All @@ -106,7 +93,7 @@ export function DashboardPlugin({
type,
title,
id: dashboardId,
metadata,
widget,
} satisfies DashboardPluginData,
});
},
Expand All @@ -118,30 +105,25 @@ export function DashboardPlugin({
fetch,
panelId: widgetId = shortid.generate(),
widget,
metadata = {},
}: {
fetch: () => Promise<Widget>;
panelId?: string;
widget: VariableDefinition;
metadata: Record<string, unknown>;
}) => {
}: PanelOpenEventDetail<Widget>) => {
const { type } = widget;

switch (type) {
case NAME_ELEMENT: {
handleWidgetOpen({ fetch, panelId: widgetId, widget, metadata });
const widgetFetch = fetch ?? (() => objectFetcher(widget));
handleWidgetOpen({ fetch: widgetFetch, widgetId, widget });
break;
}
case DASHBOARD_ELEMENT: {
handleDashboardOpen({ widget, metadata });
handleDashboardOpen({ widget });
break;
}
default: {
log.error('Unknown widget type', type);
}
}
},
[handleDashboardOpen, handleWidgetOpen]
[handleDashboardOpen, handleWidgetOpen, objectFetcher]
);

useEffect(
Expand All @@ -157,17 +139,16 @@ export function DashboardPlugin({
// We need to create a new definition object, otherwise the layout will think it's already open
// Can't use a spread operator because the widget definition uses property accessors

const { widget } = pluginData;
newWidgetMap.set(id, {
definition: pluginData,
fetch: () =>
connection.getObject(pluginData) as unknown as Promise<Widget>,
fetch: () => objectFetcher(widget),
id,
metadata: {},
widget,
});
return newWidgetMap;
});
},
[connection, pluginData, id]
[objectFetcher, pluginData, id]
);

const handlePanelClose = useCallback((panelId: string) => {
Expand Down Expand Up @@ -206,7 +187,7 @@ export function DashboardPlugin({
() =>
[...widgetMap.entries()].map(([widgetId, widget]) => (
<ErrorBoundary key={widgetId}>
<DeferredApiBootstrap options={widget.metadata}>
<DeferredApiBootstrap widget={widget.widget}>
<WidgetHandler widget={widget} onClose={handleWidgetClose} />
</DeferredApiBootstrap>
</ErrorBoundary>
Expand Down
6 changes: 3 additions & 3 deletions plugins/ui/src/js/src/DocumentHandler.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { WidgetDefinition } from '@deephaven/dashboard';
import { WidgetDescriptor } from '@deephaven/dashboard';
import { TestUtils } from '@deephaven/utils';
import { render } from '@testing-library/react';
import DocumentHandler, { DocumentHandlerProps } from './DocumentHandler';
Expand Down Expand Up @@ -40,11 +40,11 @@ function makeDocument(children: React.ReactNode = []): React.ReactNode {

function makeDocumentHandler({
children = makeDocument(),
definition = TestUtils.createMockProxy<WidgetDefinition>({}),
widget = TestUtils.createMockProxy<WidgetDescriptor>({}),
onClose = jest.fn(),
}: Partial<DocumentHandlerProps> = {}) {
return (
<DocumentHandler definition={definition} onClose={onClose}>
<DocumentHandler widget={widget} onClose={onClose}>
{children}
</DocumentHandler>
);
Expand Down
20 changes: 8 additions & 12 deletions plugins/ui/src/js/src/DocumentHandler.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useCallback, useMemo, useRef } from 'react';
import { WidgetDefinition } from '@deephaven/dashboard';
import { WidgetDescriptor } from '@deephaven/dashboard';
import Log from '@deephaven/log';
import { ReactPanelManagerContext } from './ReactPanelManager';
import { getRootChildren } from './DocumentUtils';
Expand All @@ -8,7 +8,7 @@ const log = Log.module('@deephaven/js-plugin-ui/DocumentHandler');

export type DocumentHandlerProps = React.PropsWithChildren<{
/** Definition of the widget used to create this document. Used for titling panels if necessary. */
definition: WidgetDefinition;
widget: WidgetDescriptor;

/** Triggered when all panels opened from this document have closed */
onClose?: () => void;
Expand All @@ -20,20 +20,16 @@ export type DocumentHandlerProps = React.PropsWithChildren<{
* or all non-panels, which will automatically be wrapped in one panel.
* Responsible for opening any panels or dashboards specified in the document.
*/
function DocumentHandler({
children,
definition,
onClose,
}: DocumentHandlerProps) {
log.debug('Rendering document', definition);
function DocumentHandler({ children, widget, onClose }: DocumentHandlerProps) {
log.debug('Rendering document', widget);
const panelOpenCountRef = useRef(0);

const metadata = useMemo(
() => ({
name: definition.title ?? definition.name ?? 'Unknown',
type: definition.type,
name: widget.name ?? 'Unknown',
type: widget.type,
}),
[definition]
[widget]
);

const handleOpen = useCallback(() => {
Expand Down Expand Up @@ -63,7 +59,7 @@ function DocumentHandler({

return (
<ReactPanelManagerContext.Provider value={panelManager}>
{getRootChildren(children, definition)}
{getRootChildren(children, widget)}
</ReactPanelManagerContext.Provider>
);
}
Expand Down
11 changes: 4 additions & 7 deletions plugins/ui/src/js/src/DocumentUtils.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { WidgetDefinition } from '@deephaven/dashboard';
import { WidgetDescriptor } from '@deephaven/dashboard';
import ReactPanel from './ReactPanel';
import { MixedPanelsError, NoChildrenError } from './errors';
import Dashboard from './layout/Dashboard';
Expand All @@ -11,12 +11,12 @@ import Dashboard from './layout/Dashboard';
*
*
* @param children Root children of the document.
* @param definition Definition of the widget used to create this document. Used for titling panels if necessary.
* @param widget Descriptor of the widget used to create this document. Used for titling panels if necessary.
* @returns The children, wrapped in a panel if necessary.
*/
export function getRootChildren(
children: React.ReactNode,
definition: WidgetDefinition
widget: WidgetDescriptor
): React.ReactNode {
if (children == null) {
return null;
Expand Down Expand Up @@ -48,10 +48,7 @@ export function getRootChildren(
if (nonLayoutCount === childrenArray.length) {
// Just wrap it in a panel
return (
<ReactPanel
key="root"
title={definition.title ?? definition.id ?? definition.type}
>
<ReactPanel key="root" title={widget.name ?? widget.id ?? widget.type}>
{children}
</ReactPanel>
);
Expand Down
14 changes: 7 additions & 7 deletions plugins/ui/src/js/src/WidgetHandler.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { DocumentHandlerProps } from './DocumentHandler';
import {
makeDocumentUpdatedJsonRpcString,
makeWidget,
makeWidgetDefinition,
makeWidgetDescriptor,
makeWidgetWrapper,
} from './WidgetTestUtils';

Expand Down Expand Up @@ -45,30 +45,30 @@ it('updates the document when event is received', async () => {
fetchResolve = resolve;
});
const fetch = jest.fn(() => fetchPromise);
const definition = makeWidgetDefinition();
const widget = makeWidgetDescriptor();
const cleanup = jest.fn();
const mockAddEventListener = jest.fn(() => cleanup);
const initialDocument = { foo: 'bar' };
const widget = makeWidget({
const widgetObject = makeWidget({
addEventListener: mockAddEventListener,
getDataAsString: jest.fn(() =>
makeDocumentUpdatedJsonRpcString(initialDocument)
),
});
const wrapper = makeWidgetWrapper({ definition, fetch });
const wrapper = makeWidgetWrapper({ widget, fetch });
const { unmount } = render(makeWidgetHandler({ widget: wrapper }));
expect(fetch).toHaveBeenCalledTimes(1);
expect(mockAddEventListener).not.toHaveBeenCalled();
expect(mockDocumentHandler).not.toHaveBeenCalled();
await act(async () => {
fetchResolve!(widget);
fetchResolve!(widgetObject);
await fetchPromise;
});

expect(mockAddEventListener).toHaveBeenCalledTimes(1);
expect(mockDocumentHandler).toHaveBeenCalledWith(
expect.objectContaining({
definition,
widget,
children: initialDocument,
})
);
Expand All @@ -90,7 +90,7 @@ it('updates the document when event is received', async () => {
});
expect(mockDocumentHandler).toHaveBeenCalledWith(
expect.objectContaining({
definition,
widget,
children: updatedDocument,
})
);
Expand Down
9 changes: 3 additions & 6 deletions plugins/ui/src/js/src/WidgetHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ function WidgetHandler({ onClose, widget: wrapper }: WidgetHandlerProps) {

useEffect(
function loadWidget() {
log.debug('loadWidget', wrapper.id, wrapper.definition);
log.debug('loadWidget', wrapper.id, wrapper.widget);
let isCancelled = false;
async function loadWidgetInternal() {
const newWidget = await wrapper.fetch();
Expand All @@ -224,7 +224,7 @@ function WidgetHandler({ onClose, widget: wrapper }: WidgetHandlerProps) {
});
return;
}
log.debug('newWidget', wrapper.id, wrapper.definition, newWidget);
log.debug('newWidget', wrapper.id, wrapper.widget, newWidget);
setWidget(newWidget);
}
loadWidgetInternal();
Expand All @@ -243,10 +243,7 @@ function WidgetHandler({ onClose, widget: wrapper }: WidgetHandlerProps) {
return useMemo(
() =>
document != null ? (
<DocumentHandler
definition={wrapper.definition}
onClose={handleDocumentClose}
>
<DocumentHandler widget={wrapper.widget} onClose={handleDocumentClose}>
{document}
</DocumentHandler>
) : null,
Expand Down
Loading

0 comments on commit 728be7b

Please sign in to comment.