Skip to content

Commit 5a9083a

Browse files
committed
connection error entrypoint
1 parent 7b4ed4b commit 5a9083a

File tree

7 files changed

+197
-43
lines changed

7 files changed

+197
-43
lines changed

packages/compass-assistant/src/compass-assistant-provider.tsx

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ import React, { type PropsWithChildren, useRef } from 'react';
22
import { type UIMessage } from './@ai-sdk/react/use-chat';
33
import { Chat } from './@ai-sdk/react/chat-react';
44
import { createContext, useContext } from 'react';
5-
import { registerCompassPlugin } from '@mongodb-js/compass-app-registry';
5+
import {
6+
createServiceLocator,
7+
registerCompassPlugin,
8+
} from '@mongodb-js/compass-app-registry';
69
import { atlasServiceLocator } from '@mongodb-js/atlas-service/provider';
710
import { DocsProviderTransport } from './docs-provider-transport';
811
import { useDrawerActions } from '@mongodb-js/compass-components';
9-
import { buildExplainPlanPrompt } from './prompts';
12+
import { buildConnectionErrorPrompt, buildExplainPlanPrompt } from './prompts';
1013
import { usePreference } from 'compass-preferences-model/provider';
14+
import type { ConnectionInfo } from '@mongodb-js/connection-info';
1115

1216
export const ASSISTANT_DRAWER_ID = 'compass-assistant-drawer';
1317

@@ -32,10 +36,18 @@ type AssistantActionsContextType = {
3236
namespace: string;
3337
explainPlan: string;
3438
}) => void;
39+
interpretConnectionError: ({
40+
connectionInfo,
41+
error,
42+
}: {
43+
connectionInfo: ConnectionInfo;
44+
error: Error;
45+
}) => void;
3546
};
3647
export const AssistantActionsContext =
3748
createContext<AssistantActionsContextType>({
3849
interpretExplainPlan: () => {},
50+
interpretConnectionError: () => {},
3951
});
4052

4153
export function useAssistantActions(): AssistantActionsContextType & {
@@ -49,6 +61,24 @@ export function useAssistantActions(): AssistantActionsContextType & {
4961
};
5062
}
5163

64+
export const compassAssistantServiceLocator = createServiceLocator(function () {
65+
const { isAssistantEnabled, ...actions } = useAssistantActions();
66+
67+
const assistantEnabledRef = useRef(isAssistantEnabled);
68+
assistantEnabledRef.current = isAssistantEnabled;
69+
70+
return {
71+
...actions,
72+
getIsAssistantEnabled() {
73+
return assistantEnabledRef.current;
74+
},
75+
};
76+
}, 'compassAssistantLocator');
77+
78+
export type CompassAssistantService = ReturnType<
79+
typeof compassAssistantServiceLocator
80+
>;
81+
5282
export const AssistantProvider: React.FunctionComponent<
5383
PropsWithChildren<{
5484
chat: Chat<AssistantMessage>;
@@ -70,6 +100,29 @@ export const AssistantProvider: React.FunctionComponent<
70100
{}
71101
);
72102
},
103+
interpretConnectionError: ({ connectionInfo, error }) => {
104+
openDrawer(ASSISTANT_DRAWER_ID);
105+
106+
// TODO: redact the connection string
107+
const connectionString =
108+
connectionInfo.connectionOptions.connectionString;
109+
const connectionError = error.toString(); // TODO
110+
console.log({ connectionString, connectionError });
111+
112+
const { prompt, displayText } = buildConnectionErrorPrompt({
113+
connectionString,
114+
connectionError,
115+
});
116+
void chat.sendMessage(
117+
{
118+
text: prompt,
119+
metadata: {
120+
displayText,
121+
},
122+
},
123+
{}
124+
);
125+
},
73126
});
74127
const { openDrawer } = useDrawerActions();
75128

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
export { CompassAssistantProvider } from './compass-assistant-provider';
22
export { CompassAssistantDrawer } from './compass-assistant-drawer';
3-
export { useAssistantActions } from './compass-assistant-provider';
3+
export {
4+
useAssistantActions,
5+
compassAssistantServiceLocator,
6+
} from './compass-assistant-provider';
7+
export type { CompassAssistantService } from './compass-assistant-provider';

packages/compass-assistant/src/prompts.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,22 @@ ${explainPlan}`,
1111
displayText: 'Provide an explanation of this explain plan.',
1212
};
1313
};
14+
15+
export const buildConnectionErrorPrompt = ({
16+
connectionString,
17+
connectionError,
18+
}: {
19+
connectionString: string;
20+
connectionError: string;
21+
}) => {
22+
return {
23+
prompt: `Given the error message below, please provide clear instructions to guide the user to debug their connection attempt from MongoDB Compass. If no auth mechanism is specified in the connection string, the default (username/password) is being used:
24+
25+
Connection string (password redacted):
26+
${connectionString}
27+
28+
Error message:
29+
${connectionError}`,
30+
displayText: 'Provide an explanation of this connection error.',
31+
};
32+
};

packages/compass-connections/src/components/connection-status-notifications.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,37 @@ function ConnectionErrorToastBody({
8383
);
8484
}
8585

86+
type ConnectionDebugToastBodyProps = {
87+
info?: ConnectionInfo | null;
88+
onDebug: () => void;
89+
};
90+
91+
function ConnectionDebugToastBody({
92+
info,
93+
onDebug,
94+
}: ConnectionDebugToastBodyProps): React.ReactElement {
95+
return (
96+
<span className={connectionErrorToastBodyStyles}>
97+
<span
98+
data-testid="connection-debug-text"
99+
className={connectionErrorTextStyles}
100+
>
101+
Diagnose the issue and explore solutions with the assistant
102+
</span>
103+
{info && (
104+
<Link
105+
className={connectionErrorToastActionMessageStyles}
106+
hideExternalIcon={true}
107+
onClick={onDebug}
108+
data-testid="connection-error-debug"
109+
>
110+
DEBUG FOR ME
111+
</Link>
112+
)}
113+
</span>
114+
);
115+
}
116+
86117
const deviceAuthModalContentStyles = css({
87118
textAlign: 'center',
88119
'& > *:not(:last-child)': {
@@ -134,6 +165,8 @@ const openConnectionFailedToast = (
134165
) => {
135166
const failedToastId = connectionInfo?.id ?? 'failed';
136167

168+
// TODO: close the existing connection toast and make a new one for the
169+
// failure so that the debug toast will appear below the failure one
137170
openToast(`connection-status--${failedToastId}`, {
138171
title: error.message,
139172
description: (
@@ -150,6 +183,26 @@ const openConnectionFailedToast = (
150183
});
151184
};
152185

186+
const openDebugConnectionErrorToast = (
187+
connectionInfo: ConnectionInfo,
188+
error: Error,
189+
onDebugClick: () => void
190+
) => {
191+
openToast(`debug-connetion-error--${connectionInfo.id}`, {
192+
title: 'Need help debugging your connection error?',
193+
description: (
194+
<ConnectionDebugToastBody
195+
info={connectionInfo}
196+
onDebug={() => {
197+
closeToast(`debug-connetion-error--${connectionInfo.id}`);
198+
onDebugClick();
199+
}}
200+
/>
201+
),
202+
variant: 'note',
203+
});
204+
};
205+
153206
const openMaximumConnectionsReachedToast = (
154207
maxConcurrentConnections: number
155208
) => {
@@ -214,6 +267,7 @@ export function getNotificationTriggers() {
214267
openConnectionStartedToast,
215268
openConnectionSucceededToast,
216269
openConnectionFailedToast,
270+
openDebugConnectionErrorToast,
217271
openMaximumConnectionsReachedToast,
218272
closeConnectionStatusToast: (connectionId: string) => {
219273
return closeToast(`connection-status--${connectionId}`);

packages/compass-connections/src/index.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
} from './stores/store-context';
2525
export type { ConnectionFeature } from './utils/connection-supports';
2626
export { connectionSupports, connectable } from './utils/connection-supports';
27+
import { compassAssistantServiceLocator } from '@mongodb-js/compass-assistant';
2728

2829
const ConnectionsComponent: React.FunctionComponent<{
2930
/**
@@ -82,7 +83,14 @@ const CompassConnectionsPlugin = registerCompassPlugin(
8283
component: ConnectionsComponent,
8384
activate(
8485
initialProps,
85-
{ logger, preferences, connectionStorage, track, globalAppRegistry },
86+
{
87+
logger,
88+
preferences,
89+
connectionStorage,
90+
track,
91+
globalAppRegistry,
92+
compassAssistant,
93+
},
8694
{ addCleanup, cleanup }
8795
) {
8896
const store = configureStore(initialProps.preloadStorageConnectionInfos, {
@@ -95,6 +103,7 @@ const CompassConnectionsPlugin = registerCompassPlugin(
95103
connectFn: initialProps.connectFn,
96104
globalAppRegistry,
97105
onFailToLoadConnections: initialProps.onFailToLoadConnections,
106+
compassAssistant,
98107
});
99108

100109
setTimeout(() => {
@@ -128,6 +137,7 @@ const CompassConnectionsPlugin = registerCompassPlugin(
128137
preferences: preferencesLocator,
129138
connectionStorage: connectionStorageLocator,
130139
track: telemetryLocator,
140+
compassAssistant: compassAssistantServiceLocator,
131141
}
132142
);
133143

packages/compass-connections/src/stores/connections-store-redux.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
} from '../utils/end-of-life-server';
4242
import type { ImportConnectionOptions } from '@mongodb-js/connection-storage/provider';
4343
import { getErrorCodeCauseChain } from '../utils/telemetry';
44+
import type { CompassAssistantService } from '@mongodb-js/compass-assistant';
4445

4546
export type ConnectionsEventMap = {
4647
connected: (
@@ -212,6 +213,7 @@ type ThunkExtraArg = {
212213
connectFn?: typeof devtoolsConnect;
213214
globalAppRegistry: Pick<AppRegistry, 'on' | 'emit' | 'removeListener'>;
214215
onFailToLoadConnections: (error: Error) => void;
216+
compassAssistant: CompassAssistantService;
215217
};
216218

217219
export type ConnectionsThunkAction<
@@ -1263,11 +1265,26 @@ const connectionAttemptError = (
12631265
connectionInfo: ConnectionInfo | null,
12641266
err: any
12651267
): ConnectionsThunkAction<void, ConnectionAttemptErrorAction> => {
1266-
return (dispatch, _getState, { track, getExtraConnectionData }) => {
1267-
const { openConnectionFailedToast } = getNotificationTriggers();
1268+
return (
1269+
dispatch,
1270+
_getState,
1271+
{ track, getExtraConnectionData, compassAssistant }
1272+
) => {
1273+
const { openConnectionFailedToast, openDebugConnectionErrorToast } =
1274+
getNotificationTriggers();
1275+
1276+
const isAssistanceEnabled = compassAssistant.getIsAssistantEnabled();
1277+
if (isAssistanceEnabled && connectionInfo) {
1278+
openDebugConnectionErrorToast(connectionInfo, err, () => {
1279+
console.log('compassAssistant.interpretConnectionError()');
1280+
compassAssistant.interpretConnectionError({
1281+
connectionInfo,
1282+
error: err,
1283+
});
1284+
});
1285+
}
12681286

12691287
const showReviewButton = !!connectionInfo && !connectionInfo.atlasMetadata;
1270-
12711288
openConnectionFailedToast(connectionInfo, err, showReviewButton, () => {
12721289
if (connectionInfo) {
12731290
dispatch(editConnection(connectionInfo.id));

packages/compass/src/app/components/home.tsx

Lines changed: 33 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -108,27 +108,22 @@ function Home({
108108
return (
109109
<ConnectionImportExportProvider>
110110
<CompassInstanceStorePlugin>
111-
<CompassAssistantProvider>
112-
<FieldStorePlugin>
113-
<div data-testid="home" className={verticalSplitStyles}>
114-
<AppRegistryProvider scopeName="Connections">
115-
<Workspace
116-
appName={appName}
117-
onActiveWorkspaceTabChange={onWorkspaceChange}
118-
/>
119-
</AppRegistryProvider>
120-
</div>
121-
<WelcomeModal
122-
isOpen={isWelcomeOpen}
123-
closeModal={closeWelcomeModal}
124-
/>
125-
<CompassSettingsPlugin></CompassSettingsPlugin>
126-
<CompassFindInPagePlugin></CompassFindInPagePlugin>
127-
<AtlasAuthPlugin></AtlasAuthPlugin>
128-
<CompassGenerativeAIPlugin></CompassGenerativeAIPlugin>
129-
<LegacyConnectionsModal />
130-
</FieldStorePlugin>
131-
</CompassAssistantProvider>
111+
<FieldStorePlugin>
112+
<div data-testid="home" className={verticalSplitStyles}>
113+
<AppRegistryProvider scopeName="Connections">
114+
<Workspace
115+
appName={appName}
116+
onActiveWorkspaceTabChange={onWorkspaceChange}
117+
/>
118+
</AppRegistryProvider>
119+
</div>
120+
<WelcomeModal isOpen={isWelcomeOpen} closeModal={closeWelcomeModal} />
121+
<CompassSettingsPlugin></CompassSettingsPlugin>
122+
<CompassFindInPagePlugin></CompassFindInPagePlugin>
123+
<AtlasAuthPlugin></AtlasAuthPlugin>
124+
<CompassGenerativeAIPlugin></CompassGenerativeAIPlugin>
125+
<LegacyConnectionsModal />
126+
</FieldStorePlugin>
132127
</CompassInstanceStorePlugin>
133128
</ConnectionImportExportProvider>
134129
);
@@ -152,21 +147,23 @@ function HomeWithConnections({
152147
return (
153148
<ConnectionStorageProvider value={connectionStorage}>
154149
<FileInputBackendProvider createFileInputBackend={createFileInputBackend}>
155-
<CompassConnections
156-
appName={props.appName}
157-
onExtraConnectionDataRequest={getExtraConnectionData}
158-
onAutoconnectInfoRequest={onAutoconnectInfoRequest}
159-
doNotReconnectDisconnectedAutoconnectInfo
160-
onFailToLoadConnections={(error) => {
161-
openToast('failed-to-load-connections', {
162-
title: 'Failed to load connections',
163-
description: error.message,
164-
variant: 'warning',
165-
});
166-
}}
167-
>
168-
<Home {...props}></Home>
169-
</CompassConnections>
150+
<CompassAssistantProvider>
151+
<CompassConnections
152+
appName={props.appName}
153+
onExtraConnectionDataRequest={getExtraConnectionData}
154+
onAutoconnectInfoRequest={onAutoconnectInfoRequest}
155+
doNotReconnectDisconnectedAutoconnectInfo
156+
onFailToLoadConnections={(error) => {
157+
openToast('failed-to-load-connections', {
158+
title: 'Failed to load connections',
159+
description: error.message,
160+
variant: 'warning',
161+
});
162+
}}
163+
>
164+
<Home {...props}></Home>
165+
</CompassConnections>
166+
</CompassAssistantProvider>
170167
</FileInputBackendProvider>
171168
</ConnectionStorageProvider>
172169
);

0 commit comments

Comments
 (0)