diff --git a/configs/testing-library-compass/src/index.tsx b/configs/testing-library-compass/src/index.tsx index 0e4a90e18ee..c0b332f7016 100644 --- a/configs/testing-library-compass/src/index.tsx +++ b/configs/testing-library-compass/src/index.tsx @@ -93,6 +93,10 @@ type TestConnectionsOptions = { connectFn?: ( connectionOptions: ConnectionInfo['connectionOptions'] ) => Partial | Promise>; + /** + * Connection storage mock + */ + connectionStorage?: ConnectionStorage; } & Partial< Omit< React.ComponentProps, @@ -258,9 +262,9 @@ function createWrapper( preferences: new InMemoryPreferencesAccess(options.preferences), track: Sinon.stub(), logger: createNoopLogger(), - connectionStorage: new InMemoryConnectionStorage( - options.connections - ) as ConnectionStorage, + connectionStorage: + options.connectionStorage ?? + (new InMemoryConnectionStorage(options.connections) as ConnectionStorage), connectionsStore: { getState: undefined as unknown as () => State, actions: {} as ReturnType, @@ -330,6 +334,12 @@ function createWrapper( { + // noop + }) + } onExtraConnectionDataRequest={ options.onExtraConnectionDataRequest ?? (() => { diff --git a/packages/compass-connections/src/index.tsx b/packages/compass-connections/src/index.tsx index b1a6073d68a..e6a20fabea6 100644 --- a/packages/compass-connections/src/index.tsx +++ b/packages/compass-connections/src/index.tsx @@ -63,6 +63,10 @@ const ConnectionsComponent: React.FunctionComponent<{ * connections on plugin activate */ preloadStorageConnectionInfos?: ConnectionInfo[]; + /** + * When connections fail to load, this callback will be called + */ + onFailToLoadConnections: (error: Error) => void; }> = ({ children }) => { return ( @@ -90,6 +94,7 @@ const CompassConnectionsPlugin = registerHadronPlugin( appName: initialProps.appName, connectFn: initialProps.connectFn, globalAppRegistry, + onFailToLoadConnections: initialProps.onFailToLoadConnections, }); setTimeout(() => { diff --git a/packages/compass-connections/src/stores/connections-store-redux.spec.tsx b/packages/compass-connections/src/stores/connections-store-redux.spec.tsx index d6f6cc36492..a238f5c7ff6 100644 --- a/packages/compass-connections/src/stores/connections-store-redux.spec.tsx +++ b/packages/compass-connections/src/stores/connections-store-redux.spec.tsx @@ -9,6 +9,7 @@ import { render, } from '@mongodb-js/testing-library-compass'; import React from 'react'; +import { InMemoryConnectionStorage } from '@mongodb-js/connection-storage/provider'; const mockConnections = [ { @@ -48,6 +49,30 @@ describe('CompassConnections store', function () { sinon.restore(); }); + describe('#loadAll', function () { + it('calls onFailToLoadConnections when it fails to loadAll connections', async function () { + const onFailToLoadConnectionsSpy = sinon.spy(); + const connectionStorage = new InMemoryConnectionStorage(); + connectionStorage.loadAll = sinon + .stub() + .rejects(new Error('loadAll failed')); + + renderCompassConnections({ + connectionStorage, + onFailToLoadConnections: onFailToLoadConnectionsSpy, + }); + + await waitFor(() => { + expect(onFailToLoadConnectionsSpy).to.have.been.calledOnce; + }); + + expect(onFailToLoadConnectionsSpy.firstCall.firstArg).to.have.property( + 'message', + 'loadAll failed' + ); + }); + }); + describe('#connect', function () { it('should show notifications throughout connection flow and save connection to persistent store', async function () { const { connectionsStore, connectionStorage, track } = diff --git a/packages/compass-connections/src/stores/connections-store-redux.ts b/packages/compass-connections/src/stores/connections-store-redux.ts index 2e29261282f..057c3a2762f 100644 --- a/packages/compass-connections/src/stores/connections-store-redux.ts +++ b/packages/compass-connections/src/stores/connections-store-redux.ts @@ -216,6 +216,7 @@ type ThunkExtraArg = { ) => Promise<[ExtraConnectionDataForTelemetry, string | null]>; connectFn?: typeof devtoolsConnect; globalAppRegistry: Pick; + onFailToLoadConnections: (error: Error) => void; }; export type ConnectionsThunkAction< @@ -1266,7 +1267,11 @@ export const loadConnections = (): ConnectionsThunkAction< | ConnectionsLoadSuccessAction | ConnectionsLoadErrorAction > => { - return async (dispatch, getState, { connectionStorage }) => { + return async ( + dispatch, + getState, + { connectionStorage, onFailToLoadConnections } + ) => { if (getState().connections.status !== 'initial') { return; } @@ -1276,6 +1281,7 @@ export const loadConnections = (): ConnectionsThunkAction< dispatch({ type: ActionTypes.ConnectionsLoadSuccess, connections }); } catch (err) { dispatch({ type: ActionTypes.ConnectionsLoadError, error: err as any }); + onFailToLoadConnections(err as Error); } }; }; diff --git a/packages/compass-web/sandbox/index.tsx b/packages/compass-web/sandbox/index.tsx index 848a941ffbe..7f07b889617 100644 --- a/packages/compass-web/sandbox/index.tsx +++ b/packages/compass-web/sandbox/index.tsx @@ -1,6 +1,11 @@ -import React, { useLayoutEffect, useRef } from 'react'; +import React, { useCallback, useLayoutEffect, useRef } from 'react'; import ReactDOM from 'react-dom'; -import { resetGlobalCSS, css, Body } from '@mongodb-js/compass-components'; +import { + resetGlobalCSS, + css, + Body, + openToast, +} from '@mongodb-js/compass-components'; import type { AllPreferences } from 'compass-preferences-model'; import { CompassWeb } from '../src/index'; import { SandboxConnectionStorageProvider } from '../src/connection-storage'; @@ -86,6 +91,14 @@ const App = () => { getMetaEl('csrf-time').setAttribute('content', csrfTime ?? ''); }, [csrfToken, csrfTime]); + const onFailToLoadConnections = useCallback((error: Error) => { + openToast('failed-to-load-connections', { + title: 'Failed to load connections', + description: error.message, + variant: 'warning', + }); + }, []); + if (status === 'checking') { return null; } @@ -132,6 +145,7 @@ const App = () => { onTrack={sandboxTelemetry.track} onDebug={sandboxLogger.debug} onLog={sandboxLogger.log} + onFailToLoadConnections={onFailToLoadConnections} > diff --git a/packages/compass-web/src/entrypoint.spec.tsx b/packages/compass-web/src/entrypoint.spec.tsx index e920b5189c2..e574f38003f 100644 --- a/packages/compass-web/src/entrypoint.spec.tsx +++ b/packages/compass-web/src/entrypoint.spec.tsx @@ -74,6 +74,7 @@ describe('CompassWeb', function () { enableCreatingNewConnections: true, ...props.initialPreferences, }} + onFailToLoadConnections={() => {}} > ); diff --git a/packages/compass-web/src/entrypoint.tsx b/packages/compass-web/src/entrypoint.tsx index 6b47914adf5..482038fba6c 100644 --- a/packages/compass-web/src/entrypoint.tsx +++ b/packages/compass-web/src/entrypoint.tsx @@ -150,6 +150,11 @@ type CompassWebProps = { onOpenConnectViaModal?: ( atlasMetadata: ConnectionInfo['atlasMetadata'] ) => void; + + /** + * Callback prop called when connections fail to load + */ + onFailToLoadConnections: (err: Error) => void; }; function CompassWorkspace({ @@ -253,6 +258,7 @@ const CompassWeb = ({ onDebug, onTrack, onOpenConnectViaModal, + onFailToLoadConnections, }: CompassWebProps) => { const appRegistry = useRef(new AppRegistry()); const logger = useCompassWebLoggerAndTelemetry({ @@ -335,6 +341,7 @@ const CompassWeb = ({ > { return Promise.resolve([{}, null] as [ Record, diff --git a/packages/compass/src/app/components/home.tsx b/packages/compass/src/app/components/home.tsx index 5cf88e8b73e..e018b09abd7 100644 --- a/packages/compass/src/app/components/home.tsx +++ b/packages/compass/src/app/components/home.tsx @@ -6,6 +6,7 @@ import { css, cx, getScrollbarStyles, + openToast, palette, resetGlobalCSS, } from '@mongodb-js/compass-components'; @@ -149,6 +150,13 @@ function HomeWithConnections({ onExtraConnectionDataRequest={getExtraConnectionData} onAutoconnectInfoRequest={onAutoconnectInfoRequest} doNotReconnectDisconnectedAutoconnectInfo + onFailToLoadConnections={(error) => { + openToast('failed-to-load-connections', { + title: 'Failed to load connections', + description: error.message, + variant: 'warning', + }); + }} >