Skip to content

Commit 901ce97

Browse files
committed
chore(compass-assistant): add feedback submission and telemetry COMPASS-9608
1 parent 1d36e9c commit 901ce97

File tree

7 files changed

+265
-17
lines changed

7 files changed

+265
-17
lines changed

package-lock.json

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

packages/compass-aggregations/src/modules/pipeline-builder/pipeline-ai.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ export const runAIPipelineGeneration = (
247247
track(
248248
'AI Prompt Submitted',
249249
() => ({
250+
source: 'natural language query' as const,
250251
editor_view_type,
251252
user_input_length: userInput.length,
252253
request_id: requestId,

packages/compass-assistant/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
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/compass-telemetry": "^1.14.0",
5657
"@mongodb-js/connection-info": "^0.17.1",
5758
"@mongodb-js/compass-logging": "^1.7.12",
5859
"mongodb-connection-string-url": "^3.0.1",

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

Lines changed: 159 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,6 @@ import { createMockChat } from '../test/utils';
1111
import type { AssistantMessage } from './compass-assistant-provider';
1212

1313
describe('AssistantChat', function () {
14-
let originalScrollTo: typeof Element.prototype.scrollTo;
15-
// Mock scrollTo method for DOM elements to prevent test failures
16-
before(function () {
17-
originalScrollTo = Element.prototype.scrollTo.bind(Element.prototype);
18-
Element.prototype.scrollTo = () => {};
19-
});
20-
after(function () {
21-
Element.prototype.scrollTo = originalScrollTo;
22-
});
23-
2414
const mockMessages: AssistantMessage[] = [
2515
{
2616
id: 'user',
@@ -41,8 +31,9 @@ describe('AssistantChat', function () {
4131

4232
function renderWithChat(messages: AssistantMessage[]) {
4333
const chat = createMockChat({ messages });
34+
const result = render(<AssistantChat chat={chat} />);
4435
return {
45-
result: render(<AssistantChat chat={chat} />),
36+
result,
4637
chat,
4738
};
4839
}
@@ -131,8 +122,9 @@ describe('AssistantChat', function () {
131122
);
132123
});
133124

134-
it('calls sendMessage when form is submitted', function () {
135-
const { chat } = renderWithChat([]);
125+
it('calls sendMessage when form is submitted', async function () {
126+
const { chat, result } = renderWithChat([]);
127+
const { track } = result;
136128
const inputField = screen.getByPlaceholderText(
137129
'Ask MongoDB Assistant a question'
138130
);
@@ -143,6 +135,12 @@ describe('AssistantChat', function () {
143135

144136
expect(chat.sendMessage.calledWith({ text: 'What is aggregation?' })).to.be
145137
.true;
138+
139+
await waitFor(() => {
140+
expect(track).to.have.been.calledWith('Assistant Prompt Submitted', {
141+
user_input_length: 'What is aggregation?'.length,
142+
});
143+
});
146144
});
147145

148146
it('clears input field after successful submission', function () {
@@ -160,8 +158,9 @@ describe('AssistantChat', function () {
160158
expect(inputField.value).to.equal('');
161159
});
162160

163-
it('trims whitespace from input before sending', function () {
164-
const { chat } = renderWithChat([]);
161+
it('trims whitespace from input before sending', async function () {
162+
const { chat, result } = renderWithChat([]);
163+
const { track } = result;
165164

166165
const inputField = screen.getByPlaceholderText(
167166
'Ask MongoDB Assistant a question'
@@ -172,6 +171,12 @@ describe('AssistantChat', function () {
172171

173172
expect(chat.sendMessage.calledWith({ text: 'What is sharding?' })).to.be
174173
.true;
174+
175+
await waitFor(() => {
176+
expect(track).to.have.been.calledWith('Assistant Prompt Submitted', {
177+
user_input_length: 'What is sharding?'.length,
178+
});
179+
});
175180
});
176181

177182
it('does not call sendMessage when input is empty or whitespace-only', function () {
@@ -272,4 +277,143 @@ describe('AssistantChat', function () {
272277
expect(screen.queryByText('Another part that should not display.')).to.not
273278
.exist;
274279
});
280+
281+
describe('feedback buttons', function () {
282+
it('shows feedback buttons only for assistant messages', function () {
283+
renderWithChat(mockMessages);
284+
285+
const userMessage = screen.getByTestId('assistant-message-user');
286+
const assistantMessage = screen.getByTestId(
287+
'assistant-message-assistant'
288+
);
289+
290+
// User messages should not have feedback buttons
291+
expect(userMessage.querySelector('[aria-label="Thumbs Up Icon"]')).to.not
292+
.exist;
293+
expect(userMessage.querySelector('[aria-label="Thumbs Down Icon"]')).to
294+
.not.exist;
295+
296+
// Assistant messages should have feedback buttons
297+
expect(assistantMessage.querySelector('[aria-label="Thumbs Up Icon"]')).to
298+
.exist;
299+
expect(assistantMessage.querySelector('[aria-label="Thumbs Down Icon"]'))
300+
.to.exist;
301+
});
302+
303+
it('tracks positive feedback when thumbs up is clicked', async function () {
304+
const { result } = renderWithChat(mockMessages);
305+
const { track } = result;
306+
307+
const assistantMessage = screen.getByTestId(
308+
'assistant-message-assistant'
309+
);
310+
311+
// Find and click the thumbs up button
312+
const thumbsUpButton = assistantMessage.querySelector(
313+
'[aria-label="Thumbs Up Icon"]'
314+
) as HTMLElement;
315+
316+
userEvent.click(thumbsUpButton);
317+
318+
await waitFor(() => {
319+
expect(track).to.have.callCount(1);
320+
expect(track).to.have.been.calledWith('Assistant Feedback Submitted', {
321+
feedback: 'positive',
322+
text: undefined,
323+
request_id: null,
324+
});
325+
});
326+
});
327+
328+
it('tracks negative feedback when thumbs down is clicked', async function () {
329+
const { result } = renderWithChat(mockMessages);
330+
const { track } = result;
331+
332+
const assistantMessage = screen.getByTestId(
333+
'assistant-message-assistant'
334+
);
335+
336+
// Find and click the thumbs down button
337+
const thumbsDownButton = assistantMessage.querySelector(
338+
'[aria-label="Thumbs Down Icon"]'
339+
) as HTMLElement;
340+
341+
userEvent.click(thumbsDownButton);
342+
343+
await waitFor(() => {
344+
expect(track).to.have.callCount(1);
345+
346+
expect(track).to.have.been.calledWith('Assistant Feedback Submitted', {
347+
feedback: 'negative',
348+
text: undefined,
349+
request_id: null,
350+
});
351+
});
352+
});
353+
354+
it('tracks detailed feedback when feedback text is submitted', async function () {
355+
const { result } = renderWithChat(mockMessages);
356+
const { track } = result;
357+
358+
const assistantMessage = screen.getByTestId(
359+
'assistant-message-assistant'
360+
);
361+
362+
// First click thumbs down to potentially open feedback form
363+
const thumbsDownButton = assistantMessage.querySelector(
364+
'[aria-label="Thumbs Down Icon"]'
365+
) as HTMLElement;
366+
367+
userEvent.click(thumbsDownButton);
368+
369+
// Look for feedback text area (the exact implementation depends on LeafyGreen)
370+
const feedbackTextArea = screen.getByTestId(
371+
'lg-chat-message_actions-feedback_textarea'
372+
);
373+
374+
userEvent.type(feedbackTextArea, 'This response was not helpful');
375+
376+
// Look for submit button
377+
const submitButton = screen.getByText('Submit');
378+
379+
userEvent.click(submitButton);
380+
381+
await waitFor(() => {
382+
expect(track).to.have.callCount(2);
383+
384+
expect(track).to.have.been.calledWith('Assistant Feedback Submitted', {
385+
feedback: 'negative',
386+
text: undefined,
387+
request_id: null,
388+
});
389+
390+
expect(track).to.have.been.calledWith('Assistant Feedback Submitted', {
391+
feedback: 'negative',
392+
text: 'This response was not helpful',
393+
request_id: null,
394+
});
395+
});
396+
});
397+
398+
it('does not show feedback buttons when there are no assistant messages', function () {
399+
const userOnlyMessages: AssistantMessage[] = [
400+
{
401+
id: 'user1',
402+
role: 'user',
403+
parts: [{ type: 'text', text: 'Hello!' }],
404+
},
405+
{
406+
id: 'user2',
407+
role: 'user',
408+
parts: [{ type: 'text', text: 'How are you?' }],
409+
},
410+
];
411+
412+
renderWithChat(userOnlyMessages);
413+
414+
// Should not find any feedback buttons in the entire component
415+
expect(screen.queryByLabelText('Thumbs Up Icon')).to.not.exist;
416+
expect(screen.queryByLabelText('Thumbs Down Icon')).to.not.exist;
417+
});
418+
});
275419
});

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

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,19 @@ import {
77
LgChatLeafygreenChatProvider,
88
LgChatMessage,
99
LgChatMessageFeed,
10+
LgChatMessageActions,
1011
LgChatInputBar,
1112
spacing,
1213
css,
1314
Banner,
1415
} from '@mongodb-js/compass-components';
16+
import { useTelemetry } from '@mongodb-js/compass-telemetry/provider';
1517

1618
const { ChatWindow } = LgChatChatWindow;
1719
const { LeafyGreenChatProvider, Variant } = LgChatLeafygreenChatProvider;
1820
const { Message } = LgChatMessage;
1921
const { MessageFeed } = LgChatMessageFeed;
22+
const { MessageActions } = LgChatMessageActions;
2023
const { InputBar } = LgChatInputBar;
2124

2225
interface AssistantChatProps {
@@ -52,6 +55,7 @@ export const AssistantChat: React.FunctionComponent<AssistantChatProps> = ({
5255
const { messages, sendMessage, status, error, clearError } = useChat({
5356
chat,
5457
});
58+
const track = useTelemetry();
5559

5660
// Transform AI SDK messages to LeafyGreen chat format
5761
const lgMessages = messages.map((message) => ({
@@ -70,10 +74,43 @@ export const AssistantChat: React.FunctionComponent<AssistantChatProps> = ({
7074
(messageBody: string) => {
7175
const trimmedMessageBody = messageBody.trim();
7276
if (trimmedMessageBody) {
77+
track('Assistant Prompt Submitted', {
78+
user_input_length: trimmedMessageBody.length,
79+
});
7380
void sendMessage({ text: trimmedMessageBody });
7481
}
7582
},
76-
[sendMessage]
83+
[sendMessage, track]
84+
);
85+
86+
const handleFeedback = useCallback(
87+
(
88+
event,
89+
state:
90+
| {
91+
feedback: string;
92+
rating: string;
93+
}
94+
| {
95+
rating: string;
96+
}
97+
| undefined
98+
) => {
99+
if (!state) {
100+
return;
101+
}
102+
const { rating } = state;
103+
const textFeedback = 'feedback' in state ? state.feedback : undefined;
104+
const feedback: 'positive' | 'negative' =
105+
rating === 'liked' ? 'positive' : 'negative';
106+
107+
track('Assistant Feedback Submitted', () => ({
108+
feedback,
109+
text: textFeedback,
110+
request_id: null,
111+
}));
112+
},
113+
[track]
77114
);
78115

79116
return (
@@ -94,7 +131,14 @@ export const AssistantChat: React.FunctionComponent<AssistantChatProps> = ({
94131
sourceType="markdown"
95132
{...messageFields}
96133
data-testid={`assistant-message-${messageFields.id}`}
97-
/>
134+
>
135+
{messageFields.isSender === false && (
136+
<MessageActions
137+
onRatingChange={handleFeedback}
138+
onSubmitFeedback={handleFeedback}
139+
/>
140+
)}
141+
</Message>
98142
))}
99143
{status === 'submitted' && (
100144
<Message

packages/compass-query-bar/src/stores/ai-query-reducer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ export const runAIQuery = (
178178
track(
179179
'AI Prompt Submitted',
180180
() => ({
181+
source: 'natural language query' as const,
181182
editor_view_type: 'find' as const,
182183
user_input_length: userInput.length,
183184
has_sample_documents: provideSampleDocuments,

0 commit comments

Comments
 (0)