Skip to content

Commit bb7cdac

Browse files
authored
feat(compass-assistant): connection error entrypoint COMPASS-9605 (#7224)
* connection error entrypoint * console.logs, todos * deps, unused code * add a test * add missing dep * remove display text to match the figma design * commented test for when the toast order is swapped * connection, not connetion * nit * depcheck
1 parent 21d2590 commit bb7cdac

File tree

15 files changed

+264
-49
lines changed

15 files changed

+264
-49
lines changed

package-lock.json

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/compass-assistant/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353
"@mongodb-js/atlas-service": "^0.56.0",
5454
"@mongodb-js/compass-app-registry": "^9.4.20",
5555
"@mongodb-js/compass-components": "^1.49.0",
56+
"@mongodb-js/connection-info": "^0.17.1",
57+
"mongodb-connection-string-url": "^3.0.1",
5658
"ai": "^5.0.5",
5759
"compass-preferences-model": "^2.51.0",
5860
"react": "^17.0.2",

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const CompassAssistantDrawer: React.FunctionComponent<{
3333
'The current chat will be cleared, and chat history will not be retrievable.',
3434
buttonText: 'Clear chat',
3535
variant: 'danger',
36+
'data-testid': 'assistant-confirm-clear-chat-modal',
3637
});
3738
if (confirmed) {
3839
clearChat();

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -202,19 +202,20 @@ describe('AssistantProvider', function () {
202202
userEvent.click(clearButton);
203203

204204
await waitFor(() => {
205-
expect(screen.getByTestId('confirmation-modal')).to.exist;
205+
expect(screen.getByTestId('assistant-confirm-clear-chat-modal')).to
206+
.exist;
206207
});
207208

208209
// There should be messages in the chat
209210
expect(screen.getByTestId('assistant-message-1')).to.exist;
210211
expect(screen.getByTestId('assistant-message-2')).to.exist;
211212

212-
const modal = screen.getByTestId('confirmation-modal');
213+
const modal = screen.getByTestId('assistant-confirm-clear-chat-modal');
213214
const confirmButton = within(modal).getByText('Clear chat');
214215
userEvent.click(confirmButton);
215216

216217
await waitForElementToBeRemoved(() =>
217-
screen.getByTestId('confirmation-modal')
218+
screen.getByTestId('assistant-confirm-clear-chat-modal')
218219
);
219220

220221
expect(mockChat.messages).to.be.empty;
@@ -231,19 +232,20 @@ describe('AssistantProvider', function () {
231232
userEvent.click(clearButton);
232233

233234
await waitFor(() => {
234-
expect(screen.getByTestId('confirmation-modal')).to.exist;
235+
expect(screen.getByTestId('assistant-confirm-clear-chat-modal')).to
236+
.exist;
235237
});
236238

237239
// There should be messages in the chat
238240
expect(screen.getByTestId('assistant-message-1')).to.exist;
239241
expect(screen.getByTestId('assistant-message-2')).to.exist;
240242

241-
const modal = screen.getByTestId('confirmation-modal');
243+
const modal = screen.getByTestId('assistant-confirm-clear-chat-modal');
242244
const cancelButton = within(modal).getByText('Cancel');
243245
userEvent.click(cancelButton);
244246

245247
await waitForElementToBeRemoved(() =>
246-
screen.getByTestId('confirmation-modal')
248+
screen.getByTestId('assistant-confirm-clear-chat-modal')
247249
);
248250

249251
expect(mockChat.messages).to.deep.equal(mockMessages);

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

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@ 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';
15+
import { redactConnectionString } from 'mongodb-connection-string-url';
1116

1217
export const ASSISTANT_DRAWER_ID = 'compass-assistant-drawer';
1318

@@ -32,11 +37,19 @@ type AssistantActionsContextType = {
3237
namespace: string;
3338
explainPlan: string;
3439
}) => void;
40+
interpretConnectionError: ({
41+
connectionInfo,
42+
error,
43+
}: {
44+
connectionInfo: ConnectionInfo;
45+
error: Error;
46+
}) => void;
3547
clearChat: () => void;
3648
};
3749
export const AssistantActionsContext =
3850
createContext<AssistantActionsContextType>({
3951
interpretExplainPlan: () => {},
52+
interpretConnectionError: () => {},
4053
clearChat: () => {},
4154
});
4255

@@ -51,6 +64,24 @@ export function useAssistantActions(): AssistantActionsContextType & {
5164
};
5265
}
5366

67+
export const compassAssistantServiceLocator = createServiceLocator(function () {
68+
const { isAssistantEnabled, ...actions } = useAssistantActions();
69+
70+
const assistantEnabledRef = useRef(isAssistantEnabled);
71+
assistantEnabledRef.current = isAssistantEnabled;
72+
73+
return {
74+
...actions,
75+
getIsAssistantEnabled() {
76+
return assistantEnabledRef.current;
77+
},
78+
};
79+
}, 'compassAssistantLocator');
80+
81+
export type CompassAssistantService = ReturnType<
82+
typeof compassAssistantServiceLocator
83+
>;
84+
5485
export const AssistantProvider: React.FunctionComponent<
5586
PropsWithChildren<{
5687
chat: Chat<AssistantMessage>;
@@ -72,6 +103,25 @@ export const AssistantProvider: React.FunctionComponent<
72103
{}
73104
);
74105
},
106+
interpretConnectionError: ({ connectionInfo, error }) => {
107+
openDrawer(ASSISTANT_DRAWER_ID);
108+
109+
const connectionString = redactConnectionString(
110+
connectionInfo.connectionOptions.connectionString
111+
);
112+
const connectionError = error.toString();
113+
114+
const { prompt } = buildConnectionErrorPrompt({
115+
connectionString,
116+
connectionError,
117+
});
118+
void chat.sendMessage(
119+
{
120+
text: prompt,
121+
},
122+
{}
123+
);
124+
},
75125
clearChat: () => {
76126
chat.messages = [];
77127
},
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: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,21 @@ ${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+
};
31+
};

packages/compass-connections/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
},
5353
"dependencies": {
5454
"@mongodb-js/compass-app-registry": "^9.4.20",
55+
"@mongodb-js/compass-assistant": "^1.2.0",
5556
"@mongodb-js/compass-components": "^1.49.0",
5657
"@mongodb-js/compass-logging": "^1.7.12",
5758
"@mongodb-js/compass-telemetry": "^1.14.0",

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

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

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

164+
// TODO(COMPASS-9746): close the existing connection toast and make a new one
165+
// for the failure so that the debug toast will appear below the failure one
137166
openToast(`connection-status--${failedToastId}`, {
138167
title: error.message,
139168
description: (
@@ -150,6 +179,25 @@ const openConnectionFailedToast = (
150179
});
151180
};
152181

182+
const openDebugConnectionErrorToast = (
183+
connectionInfo: ConnectionInfo,
184+
error: Error,
185+
onDebugClick: () => void
186+
) => {
187+
openToast(`debug-connection-error--${connectionInfo.id}`, {
188+
title: 'Need help debugging your connection error?',
189+
description: (
190+
<ConnectionDebugToastBody
191+
onDebug={() => {
192+
closeToast(`debug-connection-error--${connectionInfo.id}`);
193+
onDebugClick();
194+
}}
195+
/>
196+
),
197+
variant: 'note',
198+
});
199+
};
200+
153201
const openMaximumConnectionsReachedToast = (
154202
maxConcurrentConnections: number
155203
) => {
@@ -214,6 +262,7 @@ export function getNotificationTriggers() {
214262
openConnectionStartedToast,
215263
openConnectionSucceededToast,
216264
openConnectionFailedToast,
265+
openDebugConnectionErrorToast,
217266
openMaximumConnectionsReachedToast,
218267
closeConnectionStatusToast: (connectionId: string) => {
219268
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

0 commit comments

Comments
 (0)