Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
5 changes: 5 additions & 0 deletions package-lock.json

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

10 changes: 7 additions & 3 deletions packages/compass-assistant/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
"types": "./dist/index.d.ts",
"scripts": {
"bootstrap": "npm run compile",
"clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\" || true",
"precompile": "npm run clean",
"compile": "tsc -p tsconfig.json",
"typecheck": "tsc -p tsconfig-lint.json --noEmit",
"eslint": "eslint-compass",
Expand All @@ -53,13 +55,15 @@
"@mongodb-js/atlas-service": "^0.56.0",
"@mongodb-js/compass-app-registry": "^9.4.20",
"@mongodb-js/compass-components": "^1.49.0",
"@mongodb-js/compass-connections": "^1.71.0",
"@mongodb-js/compass-generative-ai": "^0.51.0",
"@mongodb-js/compass-logging": "^1.7.12",
"@mongodb-js/compass-telemetry": "^1.14.0",
"@mongodb-js/connection-info": "^0.17.1",
"@mongodb-js/compass-logging": "^1.7.12",
"@mongodb-js/compass-generative-ai": "^0.51.0",
"mongodb-connection-string-url": "^3.0.1",
"ai": "^5.0.26",
"compass-preferences-model": "^2.51.0",
"mongodb-build-info": "^1.7.2",
"mongodb-connection-string-url": "^3.0.1",
"react": "^17.0.2",
"throttleit": "^2.1.0",
"use-sync-external-store": "^1.5.0"
Expand Down
137 changes: 133 additions & 4 deletions packages/compass-assistant/src/assistant-chat.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { AssistantChat } from './assistant-chat';
import { expect } from 'chai';
import { createMockChat } from '../test/utils';
import type { ConnectionInfo } from '@mongodb-js/connection-info';
import {
AssistantActionsContext,
type AssistantMessage,
Expand Down Expand Up @@ -36,13 +37,14 @@ describe('AssistantChat', function () {
function renderWithChat(
messages: AssistantMessage[],
{
connections,
status,
}: {
connections?: ConnectionInfo[];
status?: 'submitted' | 'streaming';
} = {}
) {
const chat = createMockChat({ messages, status });

// The chat component does not use chat.sendMessage() directly, it uses
// ensureOptInAndSend() via the AssistantActionsContext.
const ensureOptInAndSendStub = sinon
Expand All @@ -60,7 +62,10 @@ describe('AssistantChat', function () {
const result = render(
<AssistantActionsContext.Provider value={assistantActionsContext as any}>
<AssistantChat chat={chat} />
</AssistantActionsContext.Provider>
</AssistantActionsContext.Provider>,
{
connections,
}
);
return {
result,
Expand Down Expand Up @@ -180,8 +185,132 @@ describe('AssistantChat', function () {
);
});

it('calls ensureOptInAndSend when form is submitted', async function () {
const { ensureOptInAndSendStub, result } = renderWithChat([]);
describe('non-genuine MongoDB host handling', function () {
const nonGenuineConnectionInfo: ConnectionInfo = {
id: 'test-connection-non-genuine',
connectionOptions: {
connectionString: 'mongodb://docdb-elastic.amazonaws.com:27017',
},
favorite: { name: 'Test Connection Non-Genuine' },
savedConnectionType: 'recent',
};
const genuineConnectionInfo: ConnectionInfo = {
id: 'test-connection-genuine',
connectionOptions: {
connectionString: 'mongodb://localhost:27017', // localhost is detected as genuine
},
favorite: { name: 'Test Connection Genuine' },
savedConnectionType: 'recent',
};

it('shows warning message in chat when connected to non-genuine MongoDB', async function () {
const chat = createMockChat({ messages: [] });
const { connectionsStore } = render(<AssistantChat chat={chat} />, {
connections: [nonGenuineConnectionInfo],
});

// Not connected yet, so no warning message
expect(chat.messages).to.have.length(0);
expect(
screen.queryByText(
/MongoDB Assistant will not provide accurate guidance for non-genuine hosts/
)
).to.not.exist;

// Connect to the non-genuine connection
await connectionsStore.actions.connect(nonGenuineConnectionInfo);

await waitFor(() => {
expect(chat.messages).to.have.length(1);
expect(chat.messages[0].id).to.equal('non-genuine-warning');
});

const warningMessage = screen.getByText(
/MongoDB Assistant will not provide accurate guidance for non-genuine hosts/
);
expect(warningMessage).to.exist;
});

it('does not show warning message when all connections are genuine', async function () {
const chat = createMockChat({ messages: [] });
const { connectionsStore } = render(<AssistantChat chat={chat} />, {
connections: [genuineConnectionInfo],
});

await connectionsStore.actions.connect(genuineConnectionInfo);

const warningMessage = screen.queryByText(
/MongoDB Assistant will not provide accurate guidance for non-genuine hosts/
);
expect(warningMessage).to.not.exist;
});

it('does not show warning message when no connections are active', function () {
render(<AssistantChat chat={createMockChat({ messages: [] })} />, {
connections: [],
});

const warningMessage = screen.queryByText(
/MongoDB Assistant will not provide accurate guidance for non-genuine hosts/
);
expect(warningMessage).to.not.exist;
});

it('shows warning message when connections are changed to non-genuine', async function () {
const chat = createMockChat({ messages: [] });
const { connectionsStore } = render(<AssistantChat chat={chat} />, {
connections: [genuineConnectionInfo],
});

expect(
screen.queryByText(
/MongoDB Assistant will not provide accurate guidance for non-genuine hosts/
)
).to.not.exist;

await connectionsStore.actions.saveAndConnect(nonGenuineConnectionInfo);

await waitFor(() => {
expect(chat.messages).to.have.length(1);
expect(chat.messages[0].id).to.equal('non-genuine-warning');
expect(
screen.queryByText(
/MongoDB Assistant will not provide accurate guidance for non-genuine hosts/
)
).to.exist;
});
});

it('warning message is removed when all active connections are changed to genuine', async function () {
const chat = createMockChat({ messages: [] });
const { connectionsStore } = render(<AssistantChat chat={chat} />, {
connections: [genuineConnectionInfo, nonGenuineConnectionInfo],
});

// Connect to the genuine and non-genuine connections
await connectionsStore.actions.connect(genuineConnectionInfo);
await connectionsStore.actions.connect(nonGenuineConnectionInfo);

expect(
screen.getByText(
/MongoDB Assistant will not provide accurate guidance for non-genuine hosts/
)
).to.exist;

// Remove the non-genuine connection
connectionsStore.actions.disconnect(nonGenuineConnectionInfo.id);

await waitFor(() => {
const warningMessage = screen.queryByText(
/MongoDB Assistant will not provide accurate guidance for non-genuine hosts/
);
expect(warningMessage).to.not.exist;
});
});
});

it('calls sendMessage when form is submitted', async function () {
const { result, ensureOptInAndSendStub } = renderWithChat([]);
const { track } = result;
const inputField = screen.getByPlaceholderText(
'Ask MongoDB Assistant a question'
Expand Down
33 changes: 31 additions & 2 deletions packages/compass-assistant/src/assistant-chat.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useContext } from 'react';
import React, { useCallback, useEffect, useContext } from 'react';
import type { AssistantMessage } from './compass-assistant-provider';
import { AssistantActionsContext } from './compass-assistant-provider';
import type { Chat } from './@ai-sdk/react/chat-react';
Expand All @@ -20,6 +20,9 @@ import {
Link,
} from '@mongodb-js/compass-components';
import { useTelemetry } from '@mongodb-js/compass-telemetry/provider';
import { useConnectionIds } from '@mongodb-js/compass-connections/provider';
import { getGenuineMongoDB } from 'mongodb-build-info';
import { NON_GENUINE_WARNING_MESSAGE } from './preset-messages';

const { DisclaimerText } = LgChatChatDisclaimer;
const { ChatWindow } = LgChatChatWindow;
Expand Down Expand Up @@ -130,8 +133,9 @@ export const AssistantChat: React.FunctionComponent<AssistantChatProps> = ({
}) => {
const track = useTelemetry();
const darkMode = useDarkMode();

const { ensureOptInAndSend } = useContext(AssistantActionsContext);
const { messages, status, error, clearError } = useChat({
const { messages, status, error, clearError, setMessages } = useChat({
chat,
onError: (error) => {
track('Assistant Response Failed', () => ({
Expand All @@ -140,6 +144,31 @@ export const AssistantChat: React.FunctionComponent<AssistantChatProps> = ({
},
});

// Check for non-genuine connections
const activeConnectionIds = useConnectionIds(
(conn) =>
getGenuineMongoDB(conn.info.connectionOptions.connectionString)
.isGenuine === false && conn.status === 'connected'
);

useEffect(() => {
const hasNonGenuineConnections = activeConnectionIds.length > 0;
const hasExistingNonGenuineWarning = chat.messages.some(
(message) => message.id === 'non-genuine-warning'
);
if (hasNonGenuineConnections && !hasExistingNonGenuineWarning) {
setMessages((messages) => {
return [NON_GENUINE_WARNING_MESSAGE, ...messages];
});
} else if (hasExistingNonGenuineWarning && !hasNonGenuineConnections) {
setMessages((messages) => {
return messages.filter(
(message) => message.id !== 'non-genuine-warning'
);
});
}
}, [activeConnectionIds, chat, setMessages]);

// Transform AI SDK messages to LeafyGreen chat format and reverse the order of the messages
// for displaying it correctly with flex-direction: column-reverse.
const lgMessages = messages
Expand Down
Loading