Skip to content

Commit 45fa929

Browse files
authored
fix: Handle undefined DashboardData props (#1726)
- Handle undefined props on DashboardData. Don't render console panel if no session - Fixed a set state on unmounted HeapUsage bug - Removed chatty async interval debug logging fixes #1684 **Testing** - Run server with PSK enabled - Login and open any file - Logout should no longer show "ConsolePanel.tsx:372 Uncaught TypeError: Cannot destructure property 'config' of 'sessionWrapper' as it is undefined." error in debug console (Note that login will still fail until #1685 is done)
1 parent e79297b commit 45fa929

10 files changed

Lines changed: 149 additions & 76 deletions

File tree

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ interface AppMainContainerProps {
123123
match: {
124124
params: { notebookPath: string };
125125
};
126-
connection: IdeConnection;
126+
connection?: IdeConnection;
127127
session?: IdeSession;
128128
sessionConfig?: SessionConfig;
129129
setActiveTool: (tool: string) => void;
@@ -255,6 +255,10 @@ export class AppMainContainer extends Component<
255255

256256
initWidgets(): void {
257257
const { connection } = this.props;
258+
if (connection == null) {
259+
return;
260+
}
261+
258262
if (connection.subscribeToFieldUpdates == null) {
259263
log.warn(
260264
'subscribeToFieldUpdates not supported, not initializing widgets'
@@ -614,6 +618,10 @@ export class AppMainContainer extends Component<
614618

615619
startListeningForDisconnect(): void {
616620
const { connection } = this.props;
621+
if (connection == null) {
622+
return;
623+
}
624+
617625
connection.addEventListener(
618626
dh.IdeConnection.EVENT_DISCONNECT,
619627
this.handleDisconnect
@@ -630,6 +638,10 @@ export class AppMainContainer extends Component<
630638

631639
stopListeningForDisconnect(): void {
632640
const { connection } = this.props;
641+
if (connection == null) {
642+
return;
643+
}
644+
633645
connection.removeEventListener(
634646
dh.IdeConnection.EVENT_DISCONNECT,
635647
this.handleDisconnect
@@ -650,6 +662,7 @@ export class AppMainContainer extends Component<
650662
): DehydratedDashboardPanelProps & { fetch?: () => Promise<unknown> } {
651663
const { connection } = this.props;
652664
const { metadata } = props;
665+
653666
if (
654667
metadata?.type != null &&
655668
(metadata?.id != null || metadata?.name != null)
@@ -666,12 +679,14 @@ export class AppMainContainer extends Component<
666679
name: metadata.name,
667680
title: metadata.name,
668681
};
682+
669683
return {
670-
fetch: () => connection.getObject(widget),
684+
fetch: async () => connection?.getObject(widget),
671685
...props,
672686
localDashboardId: id,
673687
};
674688
}
689+
675690
return DashboardUtils.hydrate(props, id);
676691
}
677692

@@ -684,7 +699,7 @@ export class AppMainContainer extends Component<
684699
const { connection } = this.props;
685700
this.emitLayoutEvent(PanelEvent.OPEN, {
686701
dragEvent,
687-
fetch: () => connection.getObject(widget),
702+
fetch: async () => connection?.getObject(widget),
688703
widget,
689704
});
690705
}

packages/console/src/HeapUsage.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Tooltip } from '@deephaven/components';
44
import type { QueryConnectable } from '@deephaven/jsapi-types';
55
import { Plot, useChartTheme } from '@deephaven/chart';
66
import Log from '@deephaven/log';
7-
import { useAsyncInterval } from '@deephaven/react-hooks';
7+
import { useAsyncInterval, useIsMountedRef } from '@deephaven/react-hooks';
88
import './HeapUsage.scss';
99

1010
const log = Log.module('HeapUsage');
@@ -41,9 +41,16 @@ function HeapUsage({
4141
usages: [],
4242
});
4343

44+
const isMountedRef = useIsMountedRef();
45+
4446
const setUsageUpdateInterval = useCallback(async () => {
4547
try {
4648
const newUsage = await connection.getWorkerHeapInfo();
49+
50+
if (!isMountedRef.current) {
51+
return;
52+
}
53+
4754
setMemoryUsage(newUsage);
4855

4956
if (bgMonitoring || isOpen) {
@@ -69,7 +76,7 @@ function HeapUsage({
6976
} catch (e) {
7077
log.warn('Unable to get heap usage', e);
7178
}
72-
}, [isOpen, connection, monitorDuration, bgMonitoring]);
79+
}, [connection, isMountedRef, bgMonitoring, isOpen, monitorDuration]);
7380

7481
useAsyncInterval(
7582
setUsageUpdateInterval,

packages/dashboard-core-plugins/src/panels/ConsolePanel.test.tsx

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
import React from 'react';
22
import { render } from '@testing-library/react';
33
import { CommandHistoryStorage } from '@deephaven/console';
4-
import type { Container } from '@deephaven/golden-layout';
4+
import type { Container, EventEmitter } from '@deephaven/golden-layout';
55
import type { IdeConnection, IdeSession } from '@deephaven/jsapi-types';
6+
import { dh } from '@deephaven/jsapi-shim';
67
import { SessionConfig, SessionWrapper } from '@deephaven/jsapi-utils';
8+
import { TestUtils } from '@deephaven/utils';
79
import { ConsolePanel } from './ConsolePanel';
810

9-
const mockConsole = jest.fn(() => null);
11+
type IdeSessionConstructor = new (language: string) => IdeSession;
12+
13+
const mockConsole = jest.fn((_props: unknown) => null);
1014
jest.mock('@deephaven/console', () => ({
1115
...(jest.requireActual('@deephaven/console') as Record<string, unknown>),
1216
Console: props => mockConsole(props),
1317
default: props => mockConsole(props),
1418
}));
1519

1620
function makeSession(language = 'TEST_LANG'): IdeSession {
17-
return new dh.IdeSession(language) as unknown as IdeSession;
21+
return new (dh.IdeSession as unknown as IdeSessionConstructor)(language);
1822
}
1923

2024
function makeConnection({
@@ -42,31 +46,15 @@ function makeSessionWrapper({
4246
return { session, connection, config, dh };
4347
}
4448

45-
function makeEventHub() {
46-
return {
47-
emit: jest.fn(),
48-
on: jest.fn(),
49-
off: jest.fn(),
50-
trigger: jest.fn(),
51-
unbind: jest.fn(),
52-
};
53-
}
54-
55-
function makeGlContainer(): Container {
56-
return {
57-
emit: jest.fn(),
58-
on: jest.fn(),
59-
off: jest.fn(),
60-
} as unknown as Container;
61-
}
62-
6349
function makeCommandHistoryStorage(): CommandHistoryStorage {
6450
return {} as CommandHistoryStorage;
6551
}
6652

6753
function renderConsolePanel({
68-
eventHub = makeEventHub(),
69-
container = makeGlContainer(),
54+
eventHub = TestUtils.createMockProxy<EventEmitter>(),
55+
container = TestUtils.createMockProxy<Container>({
56+
tab: undefined,
57+
}),
7058
commandHistoryStorage = makeCommandHistoryStorage(),
7159
timeZone = 'MockTimeZone',
7260
sessionWrapper = makeSessionWrapper(),
@@ -78,11 +66,18 @@ function renderConsolePanel({
7866
commandHistoryStorage={commandHistoryStorage}
7967
timeZone={timeZone}
8068
sessionWrapper={sessionWrapper}
69+
localDashboardId="mock-localDashboardId"
70+
plugins={new Map()}
8171
/>
8272
);
8373
}
8474

8575
beforeEach(() => {
76+
// Mocking the Console component causes it to be treated as a functional
77+
// component which causes React to log an error about passing refs. Disable
78+
// logging to supress this
79+
TestUtils.disableConsoleOutput('error');
80+
8681
mockConsole.mockClear();
8782
});
8883

packages/dashboard-core-plugins/src/panels/ConsolePanel.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React, { PureComponent, ReactElement, RefObject } from 'react';
44
import shortid from 'shortid';
55
import debounce from 'lodash.debounce';
66
import { connect } from 'react-redux';
7+
import { LoadingOverlay } from '@deephaven/components';
78
import {
89
CommandHistoryStorage,
910
Console,
@@ -63,7 +64,7 @@ interface ConsolePanelProps extends DashboardPanelProps {
6364

6465
panelState?: PanelState;
6566

66-
sessionWrapper: SessionWrapper;
67+
sessionWrapper?: SessionWrapper;
6768

6869
timeZone: string;
6970
unzip?: (file: File) => Promise<JSZipObject[]>;
@@ -159,6 +160,10 @@ export class ConsolePanel extends PureComponent<
159160

160161
subscribeToFieldUpdates(): void {
161162
const { sessionWrapper } = this.props;
163+
if (sessionWrapper == null) {
164+
return;
165+
}
166+
162167
const { session } = sessionWrapper;
163168

164169
this.objectSubscriptionCleanup = session.subscribeToFieldUpdates(
@@ -244,6 +249,9 @@ export class ConsolePanel extends PureComponent<
244249

245250
handleOpenObject(object: VariableDefinition, forceOpen = true): void {
246251
const { sessionWrapper } = this.props;
252+
if (sessionWrapper == null) {
253+
return;
254+
}
247255
const { session } = sessionWrapper;
248256
const { root } = this.context;
249257
const oldPanelId =
@@ -359,7 +367,7 @@ export class ConsolePanel extends PureComponent<
359367
return <ObjectIcon type={type} />;
360368
}
361369

362-
render(): ReactElement {
370+
render(): ReactElement | null {
363371
const {
364372
commandHistoryStorage,
365373
glContainer,
@@ -368,6 +376,13 @@ export class ConsolePanel extends PureComponent<
368376
timeZone,
369377
unzip,
370378
} = this.props;
379+
380+
if (sessionWrapper == null) {
381+
return (
382+
<LoadingOverlay isLoading={false} errorMessage="Console is disabled." />
383+
);
384+
}
385+
371386
const { consoleSettings, error, objectMap } = this.state;
372387
const { config, session, connection, details = {}, dh } = sessionWrapper;
373388
const { workerName, processInfoId } = details;

packages/dashboard-core-plugins/src/panels/FileExplorerPanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const log = Log.module('FileExplorerPanel');
2121

2222
type StateProps = {
2323
fileStorage: FileStorage;
24-
language: string;
24+
language?: string;
2525
session?: IdeSession;
2626
};
2727

packages/dashboard-core-plugins/src/panels/LogPanel.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ import { getDashboardSessionWrapper } from '../redux';
1414
const log = Log.module('LogPanel');
1515

1616
interface LogPanelProps extends DashboardPanelProps {
17-
session: IdeSession;
17+
session?: IdeSession;
1818
}
1919

2020
interface LogPanelState {
21-
session: IdeSession;
21+
session?: IdeSession;
2222
}
2323

2424
class LogPanel extends PureComponent<LogPanelProps, LogPanelState> {

packages/dashboard-core-plugins/src/panels/NotebookPanel.tsx

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ interface Metadata extends PanelMetadata {
6464
id: string;
6565
}
6666
interface NotebookSetting {
67-
isMinimapEnabled: boolean;
67+
isMinimapEnabled?: boolean;
6868
}
6969

7070
interface FileMetadata {
@@ -78,16 +78,21 @@ interface PanelState {
7878
fileMetadata: FileMetadata | null;
7979
}
8080

81-
interface NotebookPanelProps extends DashboardPanelProps {
81+
interface NotebookPanelMappedProps {
82+
defaultNotebookSettings: NotebookSetting;
8283
fileStorage: FileStorage;
84+
session?: IdeSession;
85+
sessionLanguage?: string;
86+
}
87+
88+
interface NotebookPanelProps
89+
extends DashboardPanelProps,
90+
NotebookPanelMappedProps {
8391
isDashboardActive: boolean;
8492
isPreview: boolean;
8593
metadata: Metadata;
86-
session: IdeSession;
87-
sessionLanguage: string;
8894
panelState: PanelState;
8995
notebooksUrl: string;
90-
defaultNotebookSettings: NotebookSetting;
9196
updateSettings: (settings: Partial<WorkspaceSettings>) => void;
9297
}
9398

@@ -790,7 +795,7 @@ class NotebookPanel extends Component<NotebookPanelProps, NotebookPanelState> {
790795
const { defaultNotebookSettings, updateSettings } = this.props;
791796
const newSettings = {
792797
defaultNotebookSettings: {
793-
isMinimapEnabled: !defaultNotebookSettings.isMinimapEnabled,
798+
isMinimapEnabled: !(defaultNotebookSettings.isMinimapEnabled ?? false),
794799
},
795800
};
796801
updateSettings(newSettings);
@@ -1176,10 +1181,10 @@ class NotebookPanel extends Component<NotebookPanelProps, NotebookPanelState> {
11761181
const { defaultNotebookSettings } = this.props;
11771182
const { settings: initialSettings } = this.state;
11781183
return this.getOverflowActions(
1179-
defaultNotebookSettings.isMinimapEnabled,
1184+
defaultNotebookSettings.isMinimapEnabled ?? false,
11801185
this.getSettings(
11811186
initialSettings,
1182-
defaultNotebookSettings.isMinimapEnabled
1187+
defaultNotebookSettings.isMinimapEnabled ?? false
11831188
).wordWrap === 'on'
11841189
);
11851190
}
@@ -1216,7 +1221,7 @@ class NotebookPanel extends Component<NotebookPanelProps, NotebookPanelState> {
12161221
const isExistingItem = fileMetadata?.id != null;
12171222
const settings = this.getSettings(
12181223
initialSettings,
1219-
defaultNotebookSettings.isMinimapEnabled
1224+
defaultNotebookSettings.isMinimapEnabled ?? false
12201225
);
12211226
const isSessionConnected = session != null;
12221227
const isLanguageMatching = sessionLanguage === settings.language;
@@ -1428,10 +1433,7 @@ class NotebookPanel extends Component<NotebookPanelProps, NotebookPanelState> {
14281433
const mapStateToProps = (
14291434
state: RootState,
14301435
ownProps: { localDashboardId: string }
1431-
): Pick<
1432-
NotebookPanelProps,
1433-
'defaultNotebookSettings' | 'fileStorage' | 'session' | 'sessionLanguage'
1434-
> => {
1436+
): NotebookPanelMappedProps => {
14351437
const fileStorage = getFileStorage(state);
14361438
const defaultNotebookSettings = getDefaultNotebookSettings(state);
14371439
const sessionWrapper = getDashboardSessionWrapper(
@@ -1443,7 +1445,7 @@ const mapStateToProps = (
14431445
const { type: sessionLanguage } = sessionConfig ?? {};
14441446
return {
14451447
fileStorage,
1446-
defaultNotebookSettings: defaultNotebookSettings as NotebookSetting,
1448+
defaultNotebookSettings,
14471449
session,
14481450
sessionLanguage,
14491451
};

0 commit comments

Comments
 (0)