Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add ObjectFetcher context and useObjectFetcher hook #1753

Merged
merged 13 commits into from
Feb 2, 2024
26 changes: 21 additions & 5 deletions packages/app-utils/src/components/ConnectionBootstrap.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { LoadingOverlay } from '@deephaven/components';
import { useApi, useClient } from '@deephaven/jsapi-bootstrap';
import type { IdeConnection } from '@deephaven/jsapi-types';
import { ConnectionContext } from '@deephaven/jsapi-components';
import {
ObjectFetcherContext,
sanitizeVariableDescriptor,
useApi,
useClient,
} from '@deephaven/jsapi-bootstrap';
import type { IdeConnection, VariableDescriptor } from '@deephaven/jsapi-types';
import Log from '@deephaven/log';
import { assertNotNull } from '@deephaven/utils';
import ConnectionContext from './ConnectionContext';

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

Expand Down Expand Up @@ -69,6 +75,14 @@ export function ConnectionBootstrap({
[api, connection]
);

const objectFetcher = useCallback(
async (descriptor: VariableDescriptor) => {
assertNotNull(connection, 'No connection available to fetch object with');
return connection.getObject(sanitizeVariableDescriptor(descriptor));
},
[connection]
);

if (connection == null || error != null) {
return (
<LoadingOverlay
Expand All @@ -81,7 +95,9 @@ export function ConnectionBootstrap({

return (
<ConnectionContext.Provider value={connection}>
{children}
<ObjectFetcherContext.Provider value={objectFetcher}>
{children}
</ObjectFetcherContext.Provider>
</ConnectionContext.Provider>
);
}
Expand Down
2 changes: 2 additions & 0 deletions packages/app-utils/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
export * from './AppBootstrap';
export * from './AuthBootstrap';
export * from './ConnectionBootstrap';
export * from './ConnectionContext';
export * from './FontBootstrap';
export * from './FontsLoaded';
export * from './PluginsBootstrap';
export * from './ThemeBootstrap';
export * from './useConnection';
export * from './useServerConfig';
export * from './useUser';
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import type { IdeConnection } from '@deephaven/jsapi-types';
import { useContextOrThrow } from '@deephaven/react-hooks';
import { ConnectionContext } from './ConnectionContext';

/**
* Retrieve the connection for the current context.
*
* @returns Connection for the current context
*/
export function useConnection(): IdeConnection {
return useContextOrThrow(
ConnectionContext,
Expand Down
42 changes: 17 additions & 25 deletions packages/code-studio/src/main/AppDashboards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import {
DehydratedDashboardPanelProps,
LazyDashboard,
} from '@deephaven/dashboard';
import { useConnection } from '@deephaven/jsapi-components';
import { VariableDefinition } from '@deephaven/jsapi-types';
import {
sanitizeVariableDescriptor,
useObjectFetcher,
} from '@deephaven/jsapi-bootstrap';
import LayoutManager, { ItemConfigType } from '@deephaven/golden-layout';
import { LoadingOverlay } from '@deephaven/components';
import EmptyDashboard from './EmptyDashboard';
Expand All @@ -30,36 +32,26 @@ export function AppDashboards({
plugins,
onAutoFillClick,
}: AppDashboardsProps): JSX.Element {
const connection = useConnection();
const fetchObject = useObjectFetcher();

const hydratePanel = useCallback(
(hydrateProps: DehydratedDashboardPanelProps, id: string) => {
const { metadata } = hydrateProps;
if (
metadata?.type != null &&
(metadata?.id != null || metadata?.name != null)
) {
// Looks like a widget, hydrate it as such
const widget: VariableDefinition =
metadata.id != null
? {
type: metadata.type,
id: metadata.id,
}
: {
type: metadata.type,
name: metadata.name,
title: metadata.name,
};
return {
fetch: async () => connection?.getObject(widget),
...hydrateProps,
localDashboardId: id,
};
try {
if (metadata != null) {
const widget = sanitizeVariableDescriptor(metadata);
return {
fetch: async () => fetchObject(widget),
...hydrateProps,
localDashboardId: id,
};
}
} catch (e: unknown) {
// Ignore being unable to get the variable descriptor, do the default dashboard hydration
}
return DashboardUtils.hydrate(hydrateProps, id);
},
[connection]
[fetchObject]
);

return (
Expand Down
3 changes: 1 addition & 2 deletions packages/code-studio/src/main/AppInit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
import { FileStorage } from '@deephaven/file-explorer';
import { useApi, useClient } from '@deephaven/jsapi-bootstrap';
import type { dh as DhType, IdeConnection } from '@deephaven/jsapi-types';
import { useConnection } from '@deephaven/jsapi-components';
import {
getSessionDetails,
loadSessionWrapper,
Expand Down Expand Up @@ -46,7 +45,7 @@ import {
WorkspaceSettings,
CustomizableWorkspace,
} from '@deephaven/redux';
import { useServerConfig, useUser } from '@deephaven/app-utils';
import { useConnection, useServerConfig, useUser } from '@deephaven/app-utils';
import { type PluginModuleMap, usePlugins } from '@deephaven/plugin';
import { setLayoutStorage as setLayoutStorageAction } from '../redux/actions';
import App from './App';
Expand Down
71 changes: 39 additions & 32 deletions packages/code-studio/src/main/AppMainContainer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
import React from 'react';
import { Provider } from 'react-redux';
import { render, screen } from '@testing-library/react';
import { ConnectionContext } from '@deephaven/app-utils';
import { ToolType } from '@deephaven/dashboard-core-plugins';
import { ApiContext } from '@deephaven/jsapi-bootstrap';
import { ConnectionContext } from '@deephaven/jsapi-components';
import {
ApiContext,
ObjectFetcher,
ObjectFetcherContext,
} from '@deephaven/jsapi-bootstrap';
import dh from '@deephaven/jsapi-shim';
import type {
IdeConnection,
Expand Down Expand Up @@ -70,36 +74,39 @@ function renderAppMainContainer({
sessionConfig = makeSessionConfig(),
match = makeMatch(),
plugins = new Map(),
objectFetcher = jest.fn() as ObjectFetcher,
} = {}) {
const store = createMockStore();
return render(
<Provider store={store}>
<ApiContext.Provider value={dh}>
<ConnectionContext.Provider value={connection}>
<AppMainContainer
dashboardData={dashboardData}
allDashboardData={dashboardData}
layoutStorage={layoutStorage as LayoutStorage}
saveWorkspace={saveWorkspace}
updateDashboardData={updateDashboardData}
updateWorkspaceData={updateWorkspaceData}
user={user}
workspace={workspace as Workspace}
workspaceStorage={workspaceStorage}
activeTool={activeTool}
setActiveTool={setActiveTool}
setDashboardIsolatedLinkerPanelId={
setDashboardIsolatedLinkerPanelId
}
client={client}
serverConfigValues={serverConfigValues}
dashboardOpenedPanelMaps={dashboardOpenedPanelMaps}
connection={connection}
session={session as unknown as IdeSession}
sessionConfig={sessionConfig}
match={match}
plugins={plugins}
/>
<ObjectFetcherContext.Provider value={objectFetcher}>
<AppMainContainer
dashboardData={dashboardData}
allDashboardData={dashboardData}
layoutStorage={layoutStorage as LayoutStorage}
saveWorkspace={saveWorkspace}
updateDashboardData={updateDashboardData}
updateWorkspaceData={updateWorkspaceData}
user={user}
workspace={workspace as Workspace}
workspaceStorage={workspaceStorage}
activeTool={activeTool}
setActiveTool={setActiveTool}
setDashboardIsolatedLinkerPanelId={
setDashboardIsolatedLinkerPanelId
}
client={client}
serverConfigValues={serverConfigValues}
dashboardOpenedPanelMaps={dashboardOpenedPanelMaps}
connection={connection}
session={session as unknown as IdeSession}
sessionConfig={sessionConfig}
match={match}
plugins={plugins}
/>
</ObjectFetcherContext.Provider>
</ConnectionContext.Provider>
</ApiContext.Provider>
</Provider>
Expand Down Expand Up @@ -200,17 +207,17 @@ it('listens for widgets properly', async () => {
describe('hydrates widgets correctly', () => {
const localDashboardId = DEFAULT_DASHBOARD_ID;
let connection: IdeConnection;
let objectFetcher: ObjectFetcher;
beforeEach(() => {
connection = makeConnection();
objectFetcher = jest.fn();
});

it('hydrates empty props with defaults', () => {
mockProp = {};
mockId = localDashboardId;
renderAppMainContainer({ connection });
expect(
screen.getByText('{"metadata":{},"localDashboardId":"default"}')
).toBeTruthy();
expect(screen.getByText('{"localDashboardId":"default"}')).toBeTruthy();
});
it('does not try and add fetch when metadata does not have widget metadata', () => {
mockProp = { metadata: {} };
Expand All @@ -223,14 +230,14 @@ describe('hydrates widgets correctly', () => {
it('hydrates a widget properly', () => {
mockProp = { metadata: { type: 'TestType', name: 'TestName' } };
mockId = localDashboardId;
expect(connection.getObject).not.toHaveBeenCalled();
renderAppMainContainer({ connection });
expect(objectFetcher).not.toHaveBeenCalled();
renderAppMainContainer({ objectFetcher });

expect(
screen.getByText(
'{"metadata":{"type":"TestType","name":"TestName"},"localDashboardId":"default"}'
)
).toBeTruthy();
expect(connection.getObject).toHaveBeenCalled();
expect(objectFetcher).toHaveBeenCalled();
});
});
3 changes: 2 additions & 1 deletion packages/code-studio/src/main/AppMainContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import {
dhSquareFilled,
vsHome,
} from '@deephaven/icons';
import { getVariableDescriptor } from '@deephaven/jsapi-bootstrap';
import dh from '@deephaven/jsapi-shim';
import type {
IdeConnection,
Expand Down Expand Up @@ -723,7 +724,7 @@ export class AppMainContainer extends Component<
this.emitLayoutEvent(PanelEvent.OPEN, {
dragEvent,
fetch: async () => connection?.getObject(widget),
widget,
widget: getVariableDescriptor(widget),
});
}

Expand Down
Loading
Loading