Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/compass-assistant/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
"@mongodb-js/atlas-service": "^0.55.0",
"@mongodb-js/compass-app-registry": "^9.4.19",
"@mongodb-js/compass-components": "^1.48.0",
"@mongodb-js/connection-info": "^0.17.1",
"mongodb-connection-string-url": "^3.0.1",
"ai": "^5.0.5",
"compass-preferences-model": "^2.50.0",
"react": "^17.0.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const CompassAssistantDrawer: React.FunctionComponent<{
'The current chat will be cleared, and chat history will not be retrievable.',
buttonText: 'Clear chat',
variant: 'danger',
'data-testid': 'confirm-clear-chat-modal',
});
if (confirmed) {
clearChat();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,19 +201,19 @@ describe('AssistantProvider', function () {
userEvent.click(clearButton);

await waitFor(() => {
expect(screen.getByTestId('confirmation-modal')).to.exist;
expect(screen.getByTestId('confirm-clear-chat-modal')).to.exist;
});

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

const modal = screen.getByTestId('confirmation-modal');
const modal = screen.getByTestId('confirm-clear-chat-modal');
const confirmButton = within(modal).getByText('Clear chat');
userEvent.click(confirmButton);

await waitForElementToBeRemoved(() =>
screen.getByTestId('confirmation-modal')
screen.getByTestId('confirm-clear-chat-modal')
);

expect(mockChat.messages).to.be.empty;
Expand All @@ -230,19 +230,19 @@ describe('AssistantProvider', function () {
userEvent.click(clearButton);

await waitFor(() => {
expect(screen.getByTestId('confirmation-modal')).to.exist;
expect(screen.getByTestId('confirm-clear-chat-modal')).to.exist;
});

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

const modal = screen.getByTestId('confirmation-modal');
const modal = screen.getByTestId('confirm-clear-chat-modal');
const cancelButton = within(modal).getByText('Cancel');
userEvent.click(cancelButton);

await waitForElementToBeRemoved(() =>
screen.getByTestId('confirmation-modal')
screen.getByTestId('confirm-clear-chat-modal')
);

expect(mockChat.messages).to.deep.equal(mockMessages);
Expand Down
54 changes: 52 additions & 2 deletions packages/compass-assistant/src/compass-assistant-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ import React, { type PropsWithChildren, useRef } from 'react';
import { type UIMessage } from './@ai-sdk/react/use-chat';
import { Chat } from './@ai-sdk/react/chat-react';
import { createContext, useContext } from 'react';
import { registerCompassPlugin } from '@mongodb-js/compass-app-registry';
import {
createServiceLocator,
registerCompassPlugin,
} from '@mongodb-js/compass-app-registry';
import { atlasServiceLocator } from '@mongodb-js/atlas-service/provider';
import { DocsProviderTransport } from './docs-provider-transport';
import { useDrawerActions } from '@mongodb-js/compass-components';
import { buildExplainPlanPrompt } from './prompts';
import { buildConnectionErrorPrompt, buildExplainPlanPrompt } from './prompts';
import { usePreference } from 'compass-preferences-model/provider';
import type { ConnectionInfo } from '@mongodb-js/connection-info';
import { redactConnectionString } from 'mongodb-connection-string-url';

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

Expand All @@ -32,11 +37,19 @@ type AssistantActionsContextType = {
namespace: string;
explainPlan: string;
}) => void;
interpretConnectionError: ({
connectionInfo,
error,
}: {
connectionInfo: ConnectionInfo;
error: Error;
}) => void;
clearChat: () => void;
};
export const AssistantActionsContext =
createContext<AssistantActionsContextType>({
interpretExplainPlan: () => {},
interpretConnectionError: () => {},
clearChat: () => {},
});

Expand All @@ -51,6 +64,24 @@ export function useAssistantActions(): AssistantActionsContextType & {
};
}

export const compassAssistantServiceLocator = createServiceLocator(function () {
const { isAssistantEnabled, ...actions } = useAssistantActions();

const assistantEnabledRef = useRef(isAssistantEnabled);
assistantEnabledRef.current = isAssistantEnabled;

return {
...actions,
getIsAssistantEnabled() {
return assistantEnabledRef.current;
},
};
}, 'compassAssistantLocator');

export type CompassAssistantService = ReturnType<
typeof compassAssistantServiceLocator
>;

export const AssistantProvider: React.FunctionComponent<
PropsWithChildren<{
chat: Chat<AssistantMessage>;
Expand All @@ -72,6 +103,25 @@ export const AssistantProvider: React.FunctionComponent<
{}
);
},
interpretConnectionError: ({ connectionInfo, error }) => {
openDrawer(ASSISTANT_DRAWER_ID);

const connectionString = redactConnectionString(
connectionInfo.connectionOptions.connectionString
);
const connectionError = error.toString();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want more than just the error as a string?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems fine to me


const { prompt } = buildConnectionErrorPrompt({
connectionString,
connectionError,
});
void chat.sendMessage(
{
text: prompt,
},
{}
);
},
clearChat: () => {
chat.messages = [];
},
Expand Down
6 changes: 5 additions & 1 deletion packages/compass-assistant/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export { CompassAssistantProvider } from './compass-assistant-provider';
export { CompassAssistantDrawer } from './compass-assistant-drawer';
export { useAssistantActions } from './compass-assistant-provider';
export {
useAssistantActions,
compassAssistantServiceLocator,
} from './compass-assistant-provider';
export type { CompassAssistantService } from './compass-assistant-provider';
18 changes: 18 additions & 0 deletions packages/compass-assistant/src/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,21 @@ ${explainPlan}`,
displayText: 'Provide an explanation of this explain plan.',
};
};

export const buildConnectionErrorPrompt = ({
connectionString,
connectionError,
}: {
connectionString: string;
connectionError: string;
}) => {
return {
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:

Connection string (password redacted):
${connectionString}

Error message:
${connectionError}`,
};
};
1 change: 1 addition & 0 deletions packages/compass-connections/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
},
"dependencies": {
"@mongodb-js/compass-app-registry": "^9.4.19",
"@mongodb-js/compass-assistant": "^1.1.0",
"@mongodb-js/compass-components": "^1.48.0",
"@mongodb-js/compass-logging": "^1.7.11",
"@mongodb-js/compass-telemetry": "^1.13.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,33 @@ function ConnectionErrorToastBody({
);
}

type ConnectionDebugToastBodyProps = {
onDebug: () => void;
};

function ConnectionDebugToastBody({
onDebug,
}: ConnectionDebugToastBodyProps): React.ReactElement {
return (
<span className={connectionErrorToastBodyStyles}>
<span
data-testid="connection-debug-text"
className={connectionErrorTextStyles}
>
Diagnose the issue and explore solutions with the assistant
</span>
<Link
className={connectionErrorToastActionMessageStyles}
hideExternalIcon={true}
onClick={onDebug}
data-testid="connection-error-debug"
>
DEBUG FOR ME
</Link>
</span>
);
}

const deviceAuthModalContentStyles = css({
textAlign: 'center',
'& > *:not(:last-child)': {
Expand Down Expand Up @@ -134,6 +161,8 @@ const openConnectionFailedToast = (
) => {
const failedToastId = connectionInfo?.id ?? 'failed';

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

const openDebugConnectionErrorToast = (
connectionInfo: ConnectionInfo,
error: Error,
onDebugClick: () => void
) => {
openToast(`debug-connection-error--${connectionInfo.id}`, {
title: 'Need help debugging your connection error?',
description: (
<ConnectionDebugToastBody
onDebug={() => {
closeToast(`debug-connection-error--${connectionInfo.id}`);
onDebugClick();
}}
/>
),
variant: 'note',
});
};

const openMaximumConnectionsReachedToast = (
maxConcurrentConnections: number
) => {
Expand Down Expand Up @@ -214,6 +262,7 @@ export function getNotificationTriggers() {
openConnectionStartedToast,
openConnectionSucceededToast,
openConnectionFailedToast,
openDebugConnectionErrorToast,
openMaximumConnectionsReachedToast,
closeConnectionStatusToast: (connectionId: string) => {
return closeToast(`connection-status--${connectionId}`);
Expand Down
12 changes: 11 additions & 1 deletion packages/compass-connections/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from './stores/store-context';
export type { ConnectionFeature } from './utils/connection-supports';
export { connectionSupports, connectable } from './utils/connection-supports';
import { compassAssistantServiceLocator } from '@mongodb-js/compass-assistant';

const ConnectionsComponent: React.FunctionComponent<{
/**
Expand Down Expand Up @@ -82,7 +83,14 @@ const CompassConnectionsPlugin = registerCompassPlugin(
component: ConnectionsComponent,
activate(
initialProps,
{ logger, preferences, connectionStorage, track, globalAppRegistry },
{
logger,
preferences,
connectionStorage,
track,
globalAppRegistry,
compassAssistant,
},
{ addCleanup, cleanup }
) {
const store = configureStore(initialProps.preloadStorageConnectionInfos, {
Expand All @@ -95,6 +103,7 @@ const CompassConnectionsPlugin = registerCompassPlugin(
connectFn: initialProps.connectFn,
globalAppRegistry,
onFailToLoadConnections: initialProps.onFailToLoadConnections,
compassAssistant,
});

setTimeout(() => {
Expand Down Expand Up @@ -128,6 +137,7 @@ const CompassConnectionsPlugin = registerCompassPlugin(
preferences: preferencesLocator,
connectionStorage: connectionStorageLocator,
track: telemetryLocator,
compassAssistant: compassAssistantServiceLocator,
}
);

Expand Down
Loading
Loading