diff --git a/package-lock.json b/package-lock.json
index cfe9f31f248..bbcde5668d5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -33772,6 +33772,7 @@
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/mongodb-build-info/-/mongodb-build-info-1.7.2.tgz",
"integrity": "sha512-eoLFZvCIjcwijYJdxvYupj1c+55VAVm0o4gBJjrcDxxmmpm+bC4Ix9ayZbyhQdVXDZAGDi03NA0GghXjBVXnxg==",
+ "license": "Apache-2.0",
"dependencies": {
"mongodb-connection-string-url": "^3.0.0"
}
@@ -51318,6 +51319,7 @@
"lodash": "^4.17.21",
"mocha": "^10.2.0",
"mongodb": "^6.19.0",
+ "mongodb-build-info": "^1.7.2",
"mongodb-data-service": "^22.30.2",
"mongodb-log-writer": "^2.3.4",
"mongodb-ns": "^2.4.2",
@@ -63803,6 +63805,7 @@
"lodash": "^4.17.21",
"mocha": "^10.2.0",
"mongodb": "^6.19.0",
+ "mongodb-build-info": "^1.7.2",
"mongodb-data-service": "^22.30.2",
"mongodb-log-writer": "^2.3.4",
"mongodb-ns": "^2.4.2",
diff --git a/packages/compass-assistant/package.json b/packages/compass-assistant/package.json
index 39b5b420f2d..8cb59c19a67 100644
--- a/packages/compass-assistant/package.json
+++ b/packages/compass-assistant/package.json
@@ -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",
@@ -53,13 +55,13 @@
"@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-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-connection-string-url": "^3.0.1",
"react": "^17.0.2",
"throttleit": "^2.1.0",
"use-sync-external-store": "^1.5.0"
diff --git a/packages/compass-assistant/src/assistant-chat.spec.tsx b/packages/compass-assistant/src/assistant-chat.spec.tsx
index 768f338c799..c4111c8882a 100644
--- a/packages/compass-assistant/src/assistant-chat.spec.tsx
+++ b/packages/compass-assistant/src/assistant-chat.spec.tsx
@@ -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,
@@ -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
@@ -59,8 +61,11 @@ describe('AssistantChat', function () {
};
const result = render(
-
-
+
+ ,
+ {
+ connections,
+ }
);
return {
result,
@@ -180,8 +185,58 @@ describe('AssistantChat', function () {
);
});
- it('calls ensureOptInAndSend when form is submitted', async function () {
- const { ensureOptInAndSendStub, result } = renderWithChat([]);
+ describe('non-genuine MongoDB host handling', function () {
+ it('shows warning message in chat when connected to non-genuine MongoDB', function () {
+ const chat = createMockChat({ messages: [] });
+ render();
+
+ 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', function () {
+ const chat = createMockChat({ messages: [] });
+ render(, {
+ connections: [],
+ });
+
+ const warningMessage = screen.queryByText(
+ /MongoDB Assistant will not provide accurate guidance for non-genuine hosts/
+ );
+ expect(warningMessage).to.not.exist;
+ });
+
+ it('warning message is removed when all active connections are changed to genuine', async function () {
+ const chat = createMockChat({ messages: [] });
+ const { rerender } = render(
+ ,
+ {}
+ );
+
+ expect(
+ screen.getByText(
+ /MongoDB Assistant will not provide accurate guidance for non-genuine hosts/
+ )
+ ).to.exist;
+
+ rerender();
+
+ 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'
diff --git a/packages/compass-assistant/src/assistant-chat.tsx b/packages/compass-assistant/src/assistant-chat.tsx
index ab33c111d21..3f41eb287b0 100644
--- a/packages/compass-assistant/src/assistant-chat.tsx
+++ b/packages/compass-assistant/src/assistant-chat.tsx
@@ -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';
@@ -20,6 +20,7 @@ import {
Link,
} from '@mongodb-js/compass-components';
import { useTelemetry } from '@mongodb-js/compass-telemetry/provider';
+import { NON_GENUINE_WARNING_MESSAGE } from './preset-messages';
const { DisclaimerText } = LgChatChatDisclaimer;
const { ChatWindow } = LgChatChatWindow;
@@ -32,6 +33,7 @@ const GEN_AI_FAQ_LINK = 'https://www.mongodb.com/docs/generative-ai-faq/';
interface AssistantChatProps {
chat: Chat;
+ hasNonGenuineConnections: boolean;
}
const assistantChatStyles = css({
@@ -127,11 +129,13 @@ const errorBannerWrapperStyles = css({
export const AssistantChat: React.FunctionComponent = ({
chat,
+ hasNonGenuineConnections,
}) => {
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', () => ({
@@ -140,6 +144,23 @@ export const AssistantChat: React.FunctionComponent = ({
},
});
+ useEffect(() => {
+ 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'
+ );
+ });
+ }
+ }, [hasNonGenuineConnections, 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
diff --git a/packages/compass-assistant/src/compass-assistant-drawer.tsx b/packages/compass-assistant/src/compass-assistant-drawer.tsx
index 5201f1ba5d3..618de0d6569 100644
--- a/packages/compass-assistant/src/compass-assistant-drawer.tsx
+++ b/packages/compass-assistant/src/compass-assistant-drawer.tsx
@@ -33,7 +33,8 @@ const assistantTitleTextStyles = css({
*/
export const CompassAssistantDrawer: React.FunctionComponent<{
autoOpen?: boolean;
-}> = ({ autoOpen }) => {
+ hasNonGenuineConnections?: boolean;
+}> = ({ autoOpen, hasNonGenuineConnections = false }) => {
const chat = useContext(AssistantContext);
const { clearChat } = useContext(AssistantActionsContext);
@@ -88,7 +89,10 @@ export const CompassAssistantDrawer: React.FunctionComponent<{
glyph="Sparkle"
autoOpen={autoOpen}
>
-
+
);
};
diff --git a/packages/compass-assistant/src/compass-assistant-provider.spec.tsx b/packages/compass-assistant/src/compass-assistant-provider.spec.tsx
index 16c9abb9145..9514e191998 100644
--- a/packages/compass-assistant/src/compass-assistant-provider.spec.tsx
+++ b/packages/compass-assistant/src/compass-assistant-provider.spec.tsx
@@ -25,7 +25,7 @@ import type { AtlasAuthService } from '@mongodb-js/atlas-service/provider';
import type { AtlasService } from '@mongodb-js/atlas-service/provider';
import { CompassAssistantDrawer } from './compass-assistant-drawer';
import { createMockChat } from '../test/utils';
-import { type AtlasAiService } from '@mongodb-js/compass-generative-ai/provider';
+import type { AtlasAiService } from '@mongodb-js/compass-generative-ai/provider';
function createMockProvider({
mockAtlasService,
@@ -68,12 +68,14 @@ const TestComponent: React.FunctionComponent<{
mockAtlasService?: any;
mockAtlasAiService?: any;
mockAtlasAuthService?: any;
+ hasNonGenuineConnections?: boolean;
}> = ({
chat,
autoOpen,
mockAtlasService,
mockAtlasAiService,
mockAtlasAuthService,
+ hasNonGenuineConnections,
}) => {
const MockedProvider = createMockProvider({
mockAtlasService: mockAtlasService as unknown as AtlasService,
@@ -86,7 +88,10 @@ const TestComponent: React.FunctionComponent<{
Provider children
-
+
@@ -248,15 +253,21 @@ describe('CompassAssistantProvider', function () {
}
});
- async function renderOpenAssistantDrawer(
- mockChat: Chat,
- mockAtlasAiService?: any
- ): Promise> {
+ async function renderOpenAssistantDrawer({
+ chat,
+ atlastAiService,
+ hasNonGenuineConnections,
+ }: {
+ chat: Chat;
+ atlastAiService?: Partial;
+ hasNonGenuineConnections?: boolean;
+ }): Promise> {
const result = render(
,
{
preferences: {
@@ -278,7 +289,7 @@ describe('CompassAssistantProvider', function () {
it('displays messages in the chat feed', async function () {
const mockChat = createMockChat({ messages: mockMessages });
- await renderOpenAssistantDrawer(mockChat);
+ await renderOpenAssistantDrawer({ chat: mockChat });
// ensureAiFeatureAccess is async
await waitFor(() => {
@@ -309,7 +320,7 @@ describe('CompassAssistantProvider', function () {
const sendMessageSpy = sinon.spy(mockChat, 'sendMessage');
- await renderOpenAssistantDrawer(mockChat);
+ await renderOpenAssistantDrawer({ chat: mockChat });
const input = screen.getByPlaceholderText(
'Ask MongoDB Assistant a question'
@@ -348,7 +359,7 @@ describe('CompassAssistantProvider', function () {
const sendMessageSpy = sinon.spy(mockChat, 'sendMessage');
- await renderOpenAssistantDrawer(mockChat);
+ await renderOpenAssistantDrawer({ chat: mockChat });
userEvent.type(
screen.getByPlaceholderText('Ask MongoDB Assistant a question'),
@@ -367,7 +378,7 @@ describe('CompassAssistantProvider', function () {
});
it('will not send new messages if the user does not opt in', async function () {
- const mockChat = new Chat({
+ const chat = new Chat({
messages: [
{
id: 'assistant',
@@ -385,13 +396,13 @@ describe('CompassAssistantProvider', function () {
},
});
- const mockAtlasAiService = {
+ const atlastAiService = {
ensureAiFeatureAccess: sinon.stub().rejects(),
};
- const sendMessageSpy = sinon.spy(mockChat, 'sendMessage');
+ const sendMessageSpy = sinon.spy(chat, 'sendMessage');
- await renderOpenAssistantDrawer(mockChat, mockAtlasAiService);
+ await renderOpenAssistantDrawer({ chat, atlastAiService });
userEvent.type(
screen.getByPlaceholderText('Ask MongoDB Assistant a question'),
@@ -400,7 +411,7 @@ describe('CompassAssistantProvider', function () {
userEvent.click(screen.getByLabelText('Send message'));
await waitFor(() => {
- expect(mockAtlasAiService.ensureAiFeatureAccess.calledOnce).to.be.true;
+ expect(atlastAiService.ensureAiFeatureAccess.calledOnce).to.be.true;
expect(sendMessageSpy.called).to.be.false;
});
expect(screen.queryByText('Hello assistant!')).to.not.exist;
@@ -410,7 +421,7 @@ describe('CompassAssistantProvider', function () {
it('clears the chat when the user clicks and confirms', async function () {
const mockChat = createMockChat({ messages: mockMessages });
- await renderOpenAssistantDrawer(mockChat);
+ await renderOpenAssistantDrawer({ chat: mockChat });
const clearButton = screen.getByTestId('assistant-clear-chat');
userEvent.click(clearButton);
@@ -440,7 +451,7 @@ describe('CompassAssistantProvider', function () {
it('does not clear the chat when the user clicks the button and cancels', async function () {
const mockChat = createMockChat({ messages: mockMessages });
- await renderOpenAssistantDrawer(mockChat);
+ await renderOpenAssistantDrawer({ chat: mockChat });
const clearButton = screen.getByTestId('assistant-clear-chat');
userEvent.click(clearButton);
@@ -466,6 +477,43 @@ describe('CompassAssistantProvider', function () {
expect(screen.getByTestId('assistant-message-1')).to.exist;
expect(screen.getByTestId('assistant-message-2')).to.exist;
});
+
+ it('should persist permanent warning messages when clearing chat', async function () {
+ const mockChat = createMockChat({ messages: mockMessages });
+ await renderOpenAssistantDrawer({
+ chat: mockChat,
+ hasNonGenuineConnections: true,
+ });
+
+ const clearButton = screen.getByTestId('assistant-clear-chat');
+ userEvent.click(clearButton);
+
+ await waitFor(() => {
+ expect(screen.getByTestId('assistant-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;
+ expect(screen.getByTestId('assistant-message-non-genuine-warning')).to
+ .exist;
+
+ const modal = screen.getByTestId('assistant-confirm-clear-chat-modal');
+ const confirmButton = within(modal).getByText('Clear chat');
+ userEvent.click(confirmButton);
+
+ await waitForElementToBeRemoved(() =>
+ screen.getByTestId('assistant-confirm-clear-chat-modal')
+ );
+
+ // The non-genuine warning message should still be in the chat
+ expect(screen.getByTestId('assistant-message-non-genuine-warning')).to
+ .exist;
+ // The user messages should be gone
+ expect(screen.queryByTestId('assistant-message-1')).to.not.exist;
+ expect(screen.queryByTestId('assistant-message-2')).to.not.exist;
+ });
});
});
diff --git a/packages/compass-assistant/src/compass-assistant-provider.tsx b/packages/compass-assistant/src/compass-assistant-provider.tsx
index bd9a7058346..c99073534a2 100644
--- a/packages/compass-assistant/src/compass-assistant-provider.tsx
+++ b/packages/compass-assistant/src/compass-assistant-provider.tsx
@@ -35,6 +35,10 @@ export type AssistantMessage = UIMessage & {
metadata?: {
/** The text to display instead of the message text. */
displayText?: string;
+ /** Whether to persist the message after chat clearing.
+ * Used for warning messages in cases like using non-genuine MongoDB.
+ */
+ isPermanent?: boolean;
};
};
@@ -188,7 +192,9 @@ export const AssistantProvider: React.FunctionComponent<
buildProactiveInsightsPrompt
),
clearChat: () => {
- chat.messages = [];
+ chat.messages = chat.messages.filter(
+ (message) => message.metadata?.isPermanent
+ );
},
ensureOptInAndSend: async (
message: SendMessage,
diff --git a/packages/compass-assistant/src/preset-messages.ts b/packages/compass-assistant/src/preset-messages.ts
new file mode 100644
index 00000000000..06b8bdf0642
--- /dev/null
+++ b/packages/compass-assistant/src/preset-messages.ts
@@ -0,0 +1,17 @@
+import type { AssistantMessage } from './compass-assistant-provider';
+
+export const NON_GENUINE_WARNING_MESSAGE: AssistantMessage = {
+ id: 'non-genuine-warning',
+ parts: [
+ {
+ type: 'text',
+ text: 'The user is connected to a non-genuine MongoDB server. This causes many features to work differently or not work at all, make sure to always warn the user about this.',
+ },
+ ],
+ metadata: {
+ displayText:
+ 'You are connected to **a non-genuine MongoDB server**. MongoDB Assistant will not provide accurate guidance for non-genuine hosts, and we encourage users to use real MongoDB deployments to take full advantage of our developer tools.',
+ isPermanent: true,
+ },
+ role: 'assistant',
+};
diff --git a/packages/compass-web/package.json b/packages/compass-web/package.json
index dc4f6211d2c..2aab43139fc 100644
--- a/packages/compass-web/package.json
+++ b/packages/compass-web/package.json
@@ -128,6 +128,7 @@
"lodash": "^4.17.21",
"mocha": "^10.2.0",
"mongodb": "^6.19.0",
+ "mongodb-build-info": "^1.7.2",
"mongodb-data-service": "^22.30.2",
"mongodb-log-writer": "^2.3.4",
"mongodb-ns": "^2.4.2",
diff --git a/packages/compass-web/src/compass-assistant-drawer.tsx b/packages/compass-web/src/compass-assistant-drawer.tsx
new file mode 100644
index 00000000000..b09ac7683c0
--- /dev/null
+++ b/packages/compass-web/src/compass-assistant-drawer.tsx
@@ -0,0 +1,21 @@
+import { useConnectionIds } from '@mongodb-js/compass-connections/provider';
+import { getGenuineMongoDB } from 'mongodb-build-info';
+import React from 'react';
+import { CompassAssistantDrawer } from '@mongodb-js/compass-assistant';
+
+// TODO(COMPASS-7830): This is a temporary solution to pass the
+// hasNonGenuineConnections prop to the CompassAssistantDrawer as otherwise
+// we end up with a circular dependency.
+export function CompassAssistantDrawerWithConnections() {
+ // Check for non-genuine connections
+ const activeConnectionIds = useConnectionIds(
+ (conn) =>
+ getGenuineMongoDB(conn.info.connectionOptions.connectionString)
+ .isGenuine === false && conn.status === 'connected'
+ );
+ return (
+ 0}
+ />
+ );
+}
diff --git a/packages/compass-web/src/entrypoint.tsx b/packages/compass-web/src/entrypoint.tsx
index 8198dd81337..4845a208fb3 100644
--- a/packages/compass-web/src/entrypoint.tsx
+++ b/packages/compass-web/src/entrypoint.tsx
@@ -62,10 +62,8 @@ import { WebWorkspaceTab as WelcomeWorkspaceTab } from '@mongodb-js/compass-welc
import { useCompassWebPreferences } from './preferences';
import { DataModelingWorkspaceTab as DataModelingWorkspace } from '@mongodb-js/compass-data-modeling';
import { DataModelStorageServiceProviderInMemory } from '@mongodb-js/compass-data-modeling/web';
-import {
- CompassAssistantDrawer,
- CompassAssistantProvider,
-} from '@mongodb-js/compass-assistant';
+import { CompassAssistantProvider } from '@mongodb-js/compass-assistant';
+import { CompassAssistantDrawerWithConnections } from './compass-assistant-drawer';
/** @public */
export type TrackFunction = (
@@ -229,7 +227,7 @@ function CompassWorkspace({
-
+
>
);
}}
diff --git a/packages/compass/src/app/components/compass-assistant-drawer.tsx b/packages/compass/src/app/components/compass-assistant-drawer.tsx
new file mode 100644
index 00000000000..b09ac7683c0
--- /dev/null
+++ b/packages/compass/src/app/components/compass-assistant-drawer.tsx
@@ -0,0 +1,21 @@
+import { useConnectionIds } from '@mongodb-js/compass-connections/provider';
+import { getGenuineMongoDB } from 'mongodb-build-info';
+import React from 'react';
+import { CompassAssistantDrawer } from '@mongodb-js/compass-assistant';
+
+// TODO(COMPASS-7830): This is a temporary solution to pass the
+// hasNonGenuineConnections prop to the CompassAssistantDrawer as otherwise
+// we end up with a circular dependency.
+export function CompassAssistantDrawerWithConnections() {
+ // Check for non-genuine connections
+ const activeConnectionIds = useConnectionIds(
+ (conn) =>
+ getGenuineMongoDB(conn.info.connectionOptions.connectionString)
+ .isGenuine === false && conn.status === 'connected'
+ );
+ return (
+ 0}
+ />
+ );
+}
diff --git a/packages/compass/src/app/components/workspace.tsx b/packages/compass/src/app/components/workspace.tsx
index fc152693c52..e53a87fe1ae 100644
--- a/packages/compass/src/app/components/workspace.tsx
+++ b/packages/compass/src/app/components/workspace.tsx
@@ -40,7 +40,7 @@ import updateTitle from '../utils/update-title';
import { getConnectionTitle } from '@mongodb-js/connection-info';
import { useConnectionsListRef } from '@mongodb-js/compass-connections/provider';
import { DataModelingWorkspaceTab } from '@mongodb-js/compass-data-modeling';
-import { CompassAssistantDrawer } from '@mongodb-js/compass-assistant';
+import { CompassAssistantDrawerWithConnections } from './compass-assistant-drawer';
export default function Workspace({
appName,
@@ -112,7 +112,7 @@ export default function Workspace({
-
+
>
)}
>