Skip to content

Commit 2cd46ce

Browse files
authored
feat: Add ObjectFetcher context and useObjectFetcher hook (#1753)
- Register a context that will fetch an object given the provided object metadata - Pass in a `VariableDescriptor` to fetch an object - On Enterprise, the `VariableDescriptor` object will be extended to include connection information (such as which query/session the variable is from) BREAKING CHANGE: - `useConnection` is moved from `jsapi-components` package to `app-utils` package - Should only be used at the app level, as there could be multiple connections - `WidgetDefinition` has been renamed to `WidgetDescriptor`
1 parent 9f1bff3 commit 2cd46ce

34 files changed

Lines changed: 459 additions & 227 deletions

packages/app-utils/src/components/ConnectionBootstrap.tsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1-
import React, { useEffect, useState } from 'react';
1+
import React, { useCallback, useEffect, useState } from 'react';
22
import { LoadingOverlay } from '@deephaven/components';
3-
import { useApi, useClient } from '@deephaven/jsapi-bootstrap';
4-
import type { IdeConnection } from '@deephaven/jsapi-types';
5-
import { ConnectionContext } from '@deephaven/jsapi-components';
3+
import {
4+
ObjectFetcherContext,
5+
sanitizeVariableDescriptor,
6+
useApi,
7+
useClient,
8+
} from '@deephaven/jsapi-bootstrap';
9+
import type { IdeConnection, VariableDescriptor } from '@deephaven/jsapi-types';
610
import Log from '@deephaven/log';
11+
import { assertNotNull } from '@deephaven/utils';
12+
import ConnectionContext from './ConnectionContext';
713

814
const log = Log.module('@deephaven/app-utils.ConnectionBootstrap');
915

@@ -69,6 +75,14 @@ export function ConnectionBootstrap({
6975
[api, connection]
7076
);
7177

78+
const objectFetcher = useCallback(
79+
async (descriptor: VariableDescriptor) => {
80+
assertNotNull(connection, 'No connection available to fetch object with');
81+
return connection.getObject(sanitizeVariableDescriptor(descriptor));
82+
},
83+
[connection]
84+
);
85+
7286
if (connection == null || error != null) {
7387
return (
7488
<LoadingOverlay
@@ -81,7 +95,9 @@ export function ConnectionBootstrap({
8195

8296
return (
8397
<ConnectionContext.Provider value={connection}>
84-
{children}
98+
<ObjectFetcherContext.Provider value={objectFetcher}>
99+
{children}
100+
</ObjectFetcherContext.Provider>
85101
</ConnectionContext.Provider>
86102
);
87103
}
File renamed without changes.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
export * from './AppBootstrap';
22
export * from './AuthBootstrap';
33
export * from './ConnectionBootstrap';
4+
export * from './ConnectionContext';
45
export * from './FontBootstrap';
56
export * from './FontsLoaded';
67
export * from './PluginsBootstrap';
78
export * from './ThemeBootstrap';
9+
export * from './useConnection';
810
export * from './useServerConfig';
911
export * from './useUser';

packages/jsapi-components/src/useConnection.ts renamed to packages/app-utils/src/components/useConnection.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import type { IdeConnection } from '@deephaven/jsapi-types';
22
import { useContextOrThrow } from '@deephaven/react-hooks';
33
import { ConnectionContext } from './ConnectionContext';
44

5+
/**
6+
* Retrieve the connection for the current context.
7+
*
8+
* @returns Connection for the current context
9+
*/
510
export function useConnection(): IdeConnection {
611
return useContextOrThrow(
712
ConnectionContext,

packages/code-studio/src/main/AppDashboards.tsx

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import {
66
DehydratedDashboardPanelProps,
77
LazyDashboard,
88
} from '@deephaven/dashboard';
9-
import { useConnection } from '@deephaven/jsapi-components';
10-
import { VariableDefinition } from '@deephaven/jsapi-types';
9+
import {
10+
sanitizeVariableDescriptor,
11+
useObjectFetcher,
12+
} from '@deephaven/jsapi-bootstrap';
1113
import LayoutManager, { ItemConfigType } from '@deephaven/golden-layout';
1214
import { LoadingOverlay } from '@deephaven/components';
1315
import EmptyDashboard from './EmptyDashboard';
@@ -30,36 +32,26 @@ export function AppDashboards({
3032
plugins,
3133
onAutoFillClick,
3234
}: AppDashboardsProps): JSX.Element {
33-
const connection = useConnection();
35+
const fetchObject = useObjectFetcher();
3436

3537
const hydratePanel = useCallback(
3638
(hydrateProps: DehydratedDashboardPanelProps, id: string) => {
3739
const { metadata } = hydrateProps;
38-
if (
39-
metadata?.type != null &&
40-
(metadata?.id != null || metadata?.name != null)
41-
) {
42-
// Looks like a widget, hydrate it as such
43-
const widget: VariableDefinition =
44-
metadata.id != null
45-
? {
46-
type: metadata.type,
47-
id: metadata.id,
48-
}
49-
: {
50-
type: metadata.type,
51-
name: metadata.name,
52-
title: metadata.name,
53-
};
54-
return {
55-
fetch: async () => connection?.getObject(widget),
56-
...hydrateProps,
57-
localDashboardId: id,
58-
};
40+
try {
41+
if (metadata != null) {
42+
const widget = sanitizeVariableDescriptor(metadata);
43+
return {
44+
fetch: async () => fetchObject(widget),
45+
...hydrateProps,
46+
localDashboardId: id,
47+
};
48+
}
49+
} catch (e: unknown) {
50+
// Ignore being unable to get the variable descriptor, do the default dashboard hydration
5951
}
6052
return DashboardUtils.hydrate(hydrateProps, id);
6153
},
62-
[connection]
54+
[fetchObject]
6355
);
6456

6557
return (

packages/code-studio/src/main/AppInit.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import {
1818
import { FileStorage } from '@deephaven/file-explorer';
1919
import { useApi, useClient } from '@deephaven/jsapi-bootstrap';
2020
import type { dh as DhType, IdeConnection } from '@deephaven/jsapi-types';
21-
import { useConnection } from '@deephaven/jsapi-components';
2221
import {
2322
getSessionDetails,
2423
loadSessionWrapper,
@@ -46,7 +45,7 @@ import {
4645
WorkspaceSettings,
4746
CustomizableWorkspace,
4847
} from '@deephaven/redux';
49-
import { useServerConfig, useUser } from '@deephaven/app-utils';
48+
import { useConnection, useServerConfig, useUser } from '@deephaven/app-utils';
5049
import { type PluginModuleMap, usePlugins } from '@deephaven/plugin';
5150
import { setLayoutStorage as setLayoutStorageAction } from '../redux/actions';
5251
import App from './App';

packages/code-studio/src/main/AppMainContainer.test.tsx

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22
import React from 'react';
33
import { Provider } from 'react-redux';
44
import { render, screen } from '@testing-library/react';
5+
import { ConnectionContext } from '@deephaven/app-utils';
56
import { ToolType } from '@deephaven/dashboard-core-plugins';
6-
import { ApiContext } from '@deephaven/jsapi-bootstrap';
7-
import { ConnectionContext } from '@deephaven/jsapi-components';
7+
import {
8+
ApiContext,
9+
ObjectFetcher,
10+
ObjectFetcherContext,
11+
} from '@deephaven/jsapi-bootstrap';
812
import dh from '@deephaven/jsapi-shim';
913
import type {
1014
IdeConnection,
@@ -70,36 +74,39 @@ function renderAppMainContainer({
7074
sessionConfig = makeSessionConfig(),
7175
match = makeMatch(),
7276
plugins = new Map(),
77+
objectFetcher = jest.fn() as ObjectFetcher,
7378
} = {}) {
7479
const store = createMockStore();
7580
return render(
7681
<Provider store={store}>
7782
<ApiContext.Provider value={dh}>
7883
<ConnectionContext.Provider value={connection}>
79-
<AppMainContainer
80-
dashboardData={dashboardData}
81-
allDashboardData={dashboardData}
82-
layoutStorage={layoutStorage as LayoutStorage}
83-
saveWorkspace={saveWorkspace}
84-
updateDashboardData={updateDashboardData}
85-
updateWorkspaceData={updateWorkspaceData}
86-
user={user}
87-
workspace={workspace as Workspace}
88-
workspaceStorage={workspaceStorage}
89-
activeTool={activeTool}
90-
setActiveTool={setActiveTool}
91-
setDashboardIsolatedLinkerPanelId={
92-
setDashboardIsolatedLinkerPanelId
93-
}
94-
client={client}
95-
serverConfigValues={serverConfigValues}
96-
dashboardOpenedPanelMaps={dashboardOpenedPanelMaps}
97-
connection={connection}
98-
session={session as unknown as IdeSession}
99-
sessionConfig={sessionConfig}
100-
match={match}
101-
plugins={plugins}
102-
/>
84+
<ObjectFetcherContext.Provider value={objectFetcher}>
85+
<AppMainContainer
86+
dashboardData={dashboardData}
87+
allDashboardData={dashboardData}
88+
layoutStorage={layoutStorage as LayoutStorage}
89+
saveWorkspace={saveWorkspace}
90+
updateDashboardData={updateDashboardData}
91+
updateWorkspaceData={updateWorkspaceData}
92+
user={user}
93+
workspace={workspace as Workspace}
94+
workspaceStorage={workspaceStorage}
95+
activeTool={activeTool}
96+
setActiveTool={setActiveTool}
97+
setDashboardIsolatedLinkerPanelId={
98+
setDashboardIsolatedLinkerPanelId
99+
}
100+
client={client}
101+
serverConfigValues={serverConfigValues}
102+
dashboardOpenedPanelMaps={dashboardOpenedPanelMaps}
103+
connection={connection}
104+
session={session as unknown as IdeSession}
105+
sessionConfig={sessionConfig}
106+
match={match}
107+
plugins={plugins}
108+
/>
109+
</ObjectFetcherContext.Provider>
103110
</ConnectionContext.Provider>
104111
</ApiContext.Provider>
105112
</Provider>
@@ -200,17 +207,17 @@ it('listens for widgets properly', async () => {
200207
describe('hydrates widgets correctly', () => {
201208
const localDashboardId = DEFAULT_DASHBOARD_ID;
202209
let connection: IdeConnection;
210+
let objectFetcher: ObjectFetcher;
203211
beforeEach(() => {
204212
connection = makeConnection();
213+
objectFetcher = jest.fn();
205214
});
206215

207216
it('hydrates empty props with defaults', () => {
208217
mockProp = {};
209218
mockId = localDashboardId;
210219
renderAppMainContainer({ connection });
211-
expect(
212-
screen.getByText('{"metadata":{},"localDashboardId":"default"}')
213-
).toBeTruthy();
220+
expect(screen.getByText('{"localDashboardId":"default"}')).toBeTruthy();
214221
});
215222
it('does not try and add fetch when metadata does not have widget metadata', () => {
216223
mockProp = { metadata: {} };
@@ -223,14 +230,14 @@ describe('hydrates widgets correctly', () => {
223230
it('hydrates a widget properly', () => {
224231
mockProp = { metadata: { type: 'TestType', name: 'TestName' } };
225232
mockId = localDashboardId;
226-
expect(connection.getObject).not.toHaveBeenCalled();
227-
renderAppMainContainer({ connection });
233+
expect(objectFetcher).not.toHaveBeenCalled();
234+
renderAppMainContainer({ objectFetcher });
228235

229236
expect(
230237
screen.getByText(
231238
'{"metadata":{"type":"TestType","name":"TestName"},"localDashboardId":"default"}'
232239
)
233240
).toBeTruthy();
234-
expect(connection.getObject).toHaveBeenCalled();
241+
expect(objectFetcher).toHaveBeenCalled();
235242
});
236243
});

packages/code-studio/src/main/AppMainContainer.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import {
6262
dhSquareFilled,
6363
vsHome,
6464
} from '@deephaven/icons';
65+
import { getVariableDescriptor } from '@deephaven/jsapi-bootstrap';
6566
import dh from '@deephaven/jsapi-shim';
6667
import type {
6768
IdeConnection,
@@ -723,7 +724,7 @@ export class AppMainContainer extends Component<
723724
this.emitLayoutEvent(PanelEvent.OPEN, {
724725
dragEvent,
725726
fetch: async () => connection?.getObject(widget),
726-
widget,
727+
widget: getVariableDescriptor(widget),
727728
});
728729
}
729730

0 commit comments

Comments
 (0)