Skip to content

Commit 8f853c2

Browse files
authored
chore(compass-query-bar, compass-aggregations): hide ai feedback options when telemetry is disabled COMPASS-7120 (#4747)
1 parent 42914f4 commit 8f853c2

File tree

9 files changed

+258
-149
lines changed

9 files changed

+258
-149
lines changed

packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-ai.spec.tsx

Lines changed: 90 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
import React from 'react';
22
import type { ComponentProps } from 'react';
3-
import {
4-
cleanup,
5-
fireEvent,
6-
render,
7-
screen,
8-
waitFor,
9-
} from '@testing-library/react';
3+
import { cleanup, render, screen, waitFor } from '@testing-library/react';
104
import { expect } from 'chai';
115
import sinon from 'sinon';
126
import type { SinonSpy } from 'sinon';
137
import { Provider } from 'react-redux';
148
import preferencesAccess from 'compass-preferences-model';
9+
import userEvent from '@testing-library/user-event';
1510

1611
import { PipelineAI } from './pipeline-ai';
1712
import configureStore from '../../../test/configure-store';
@@ -38,6 +33,7 @@ const renderPipelineAI = ({
3833
};
3934

4035
const feedbackPopoverTextAreaId = 'feedback-popover-textarea';
36+
const thumbsUpId = 'ai-feedback-thumbs-up';
4137

4238
describe('PipelineAI Component', function () {
4339
let store: ReturnType<typeof configureStore>;
@@ -84,63 +80,104 @@ describe('PipelineAI Component', function () {
8480

8581
describe('Pipeline AI Feedback', function () {
8682
let trackUsageStatistics: boolean | undefined;
83+
let sandbox: sinon.SinonSandbox;
8784

88-
beforeEach(async function () {
89-
store = renderPipelineAI();
90-
trackUsageStatistics =
91-
preferencesAccess.getPreferences().trackUsageStatistics;
92-
// 'compass:track' will only emit if tracking is enabled.
93-
await preferencesAccess.savePreferences({ trackUsageStatistics: true });
85+
beforeEach(function () {
86+
sandbox = sinon.createSandbox();
9487
});
95-
96-
afterEach(async function () {
97-
await preferencesAccess.savePreferences({ trackUsageStatistics });
88+
afterEach(function () {
89+
sandbox.restore();
9890
});
9991

100-
it('should log a telemetry event with the entered text on submit', async function () {
101-
// Note: This is coupling this test with internals of the logger and telemetry.
102-
// We're doing this as this is a unique case where we're using telemetry
103-
// for feedback. Avoid repeating this elsewhere.
104-
const trackingLogs: any[] = [];
105-
process.on('compass:track', (event) => trackingLogs.push(event));
92+
describe('usage statistics enabled', function () {
93+
beforeEach(async function () {
94+
trackUsageStatistics =
95+
preferencesAccess.getPreferences().trackUsageStatistics;
96+
sandbox
97+
.stub(preferencesAccess, 'getPreferences')
98+
.returns({ trackUsageStatistics: true } as any);
99+
// 'compass:track' will only emit if tracking is enabled.
100+
await preferencesAccess.savePreferences({ trackUsageStatistics: true });
101+
store = renderPipelineAI();
102+
});
106103

107-
// No feedback popover is shown yet.
108-
expect(screen.queryByTestId(feedbackPopoverTextAreaId)).to.not.exist;
109-
expect(screen.queryByTestId('ai-feedback-thumbs-up')).to.not.exist;
104+
afterEach(async function () {
105+
await preferencesAccess.savePreferences({ trackUsageStatistics });
106+
});
110107

111-
store.dispatch({
112-
type: AIPipelineActionTypes.AIPipelineSucceeded,
108+
it('should log a telemetry event with the entered text on submit', async function () {
109+
// Note: This is coupling this test with internals of the logger and telemetry.
110+
// We're doing this as this is a unique case where we're using telemetry
111+
// for feedback. Avoid repeating this elsewhere.
112+
const trackingLogs: any[] = [];
113+
process.on('compass:track', (event) => trackingLogs.push(event));
114+
115+
// No feedback popover is shown yet.
116+
expect(screen.queryByTestId(feedbackPopoverTextAreaId)).to.not.exist;
117+
expect(screen.queryByTestId(thumbsUpId)).to.not.exist;
118+
119+
store.dispatch({
120+
type: AIPipelineActionTypes.AIPipelineSucceeded,
121+
});
122+
123+
expect(screen.queryByTestId(feedbackPopoverTextAreaId)).to.not.exist;
124+
const thumbsUpButton = screen.getByTestId(thumbsUpId);
125+
expect(thumbsUpButton).to.be.visible;
126+
thumbsUpButton.click();
127+
128+
const textArea = screen.getByTestId(feedbackPopoverTextAreaId);
129+
expect(textArea).to.be.visible;
130+
userEvent.type(textArea, 'this is the pipeline I was looking for');
131+
132+
screen.getByText('Submit').click();
133+
134+
await waitFor(
135+
() => {
136+
// No feedback popover is shown.
137+
expect(screen.queryByTestId(feedbackPopoverTextAreaId)).to.not
138+
.exist;
139+
expect(trackingLogs).to.deep.equal([
140+
{
141+
event: 'PipelineAI Feedback',
142+
properties: {
143+
feedback: 'positive',
144+
text: 'this is the pipeline I was looking for',
145+
},
146+
},
147+
]);
148+
},
149+
{ interval: 10 }
150+
);
113151
});
152+
});
114153

115-
expect(screen.queryByTestId(feedbackPopoverTextAreaId)).to.not.exist;
116-
const thumbsUpButton = screen.getByTestId('ai-feedback-thumbs-up');
117-
expect(thumbsUpButton).to.be.visible;
118-
thumbsUpButton.click();
154+
describe('usage statistics disabled', function () {
155+
beforeEach(async function () {
156+
trackUsageStatistics =
157+
preferencesAccess.getPreferences().trackUsageStatistics;
158+
sandbox
159+
.stub(preferencesAccess, 'getPreferences')
160+
.returns({ trackUsageStatistics: false } as any);
161+
await preferencesAccess.savePreferences({
162+
trackUsageStatistics: false,
163+
});
164+
store = renderPipelineAI();
165+
});
119166

120-
const textArea = screen.getByTestId(feedbackPopoverTextAreaId);
121-
expect(textArea).to.be.visible;
122-
fireEvent.change(textArea, {
123-
target: { value: 'this is the pipeline I was looking for' },
167+
afterEach(async function () {
168+
await preferencesAccess.savePreferences({ trackUsageStatistics });
124169
});
125170

126-
screen.getByText('Submit').click();
127-
128-
await waitFor(
129-
() => {
130-
// No feedback popover is shown.
131-
expect(screen.queryByTestId(feedbackPopoverTextAreaId)).to.not.exist;
132-
expect(trackingLogs).to.deep.equal([
133-
{
134-
event: 'PipelineAI Feedback',
135-
properties: {
136-
feedback: 'positive',
137-
text: 'this is the pipeline I was looking for',
138-
},
139-
},
140-
]);
141-
},
142-
{ interval: 10 }
143-
);
171+
it('should not show the feedback items', function () {
172+
expect(screen.queryByTestId(thumbsUpId)).to.not.exist;
173+
174+
store.dispatch({
175+
type: AIPipelineActionTypes.AIPipelineSucceeded,
176+
});
177+
178+
// No feedback popover is shown.
179+
expect(screen.queryByTestId(thumbsUpId)).to.not.exist;
180+
});
144181
});
145182
});
146183
});

packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-ai.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react';
22
import { GenerativeAIInput } from '@mongodb-js/compass-components';
33
import { connect } from 'react-redux';
44
import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
5+
import { usePreference } from 'compass-preferences-model';
56

67
import {
78
changeAIPromptText,
@@ -30,9 +31,12 @@ type PipelineAIProps = Omit<
3031
>;
3132

3233
function PipelineAI(props: PipelineAIProps) {
34+
// Don't show the feedback options if telemetry is disabled.
35+
const enableTelemetry = usePreference('trackUsageStatistics', React);
36+
3337
return (
3438
<GenerativeAIInput
35-
onSubmitFeedback={onSubmitFeedback}
39+
onSubmitFeedback={enableTelemetry ? onSubmitFeedback : undefined}
3640
placeholder="Tell Compass what aggregation to build (e.g. how many movies were made each year)"
3741
{...props}
3842
/>

packages/compass-components/src/components/feedback-popover.spec.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useState } from 'react';
22
import { expect } from 'chai';
3-
import { render, screen, cleanup, fireEvent } from '@testing-library/react';
3+
import { render, screen, cleanup } from '@testing-library/react';
4+
import userEvent from '@testing-library/user-event';
45

56
import { FeedbackPopover } from './feedback-popover';
67

@@ -59,9 +60,7 @@ describe('FeedbackPopover', function () {
5960

6061
const textArea = screen.getByTestId('feedback-popover-textarea');
6162
expect(textArea).to.be.visible;
62-
fireEvent.change(textArea, {
63-
target: { value: 'pineapple' },
64-
});
63+
userEvent.type(textArea, 'pineapple');
6564

6665
screen.getByText('Submit').click();
6766
// Wait for the event to go through.

packages/compass-components/src/components/feedback-popover.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export const FeedbackPopover = ({
6161
return () => {
6262
document.removeEventListener('mousedown', listener);
6363
};
64-
}, [feedbackPopoverId, open, setOpen]);
64+
}, [feedbackPopoverId, open, setOpen, refEl]);
6565

6666
const onTextAreaKeyDown = useCallback(
6767
(evt: React.KeyboardEvent<HTMLTextAreaElement>) => {

packages/compass-components/src/components/generative-ai/ai-feedback.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ const buttonActiveNegativeStyles = css({
6868
fill: palette.red.dark2,
6969
});
7070

71-
export type AIFeedbackProps = {
71+
type AIFeedbackProps = {
7272
onSubmitFeedback: (feedback: 'positive' | 'negative', text: string) => void;
7373
};
7474

packages/compass-components/src/components/generative-ai/generative-ai-input.spec.tsx

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
import React from 'react';
22
import type { ComponentProps } from 'react';
3-
import {
4-
cleanup,
5-
fireEvent,
6-
render,
7-
screen,
8-
waitFor,
9-
} from '@testing-library/react';
3+
import { cleanup, render, screen, waitFor } from '@testing-library/react';
104
import { expect } from 'chai';
115
import sinon from 'sinon';
126
import type { SinonSpy } from 'sinon';
7+
import userEvent from '@testing-library/user-event';
138

149
import { GenerativeAIInput } from './generative-ai-input';
1510

@@ -36,6 +31,7 @@ const renderGenerativeAIInput = ({
3631
};
3732

3833
const feedbackPopoverTextAreaId = 'feedback-popover-textarea';
34+
const thumbsUpId = 'ai-feedback-thumbs-up';
3935

4036
describe('GenerativeAIInput Component', function () {
4137
afterEach(cleanup);
@@ -80,39 +76,60 @@ describe('GenerativeAIInput Component', function () {
8076
});
8177

8278
describe('AIFeedback', function () {
83-
it('should call the feedback handler on submit', async function () {
84-
let feedbackChoice;
85-
let feedbackText;
79+
describe('when onSubmitFeedback is passed', function () {
80+
let feedbackChoice: 'negative' | 'positive' | 'dismissed' | undefined;
81+
let feedbackText: string | undefined;
82+
83+
beforeEach(function () {
84+
feedbackChoice = undefined;
85+
feedbackText = undefined;
86+
87+
renderGenerativeAIInput({
88+
onSubmitFeedback: (_feedbackChoice, _feedbackText) => {
89+
feedbackChoice = _feedbackChoice;
90+
91+
feedbackText = _feedbackText;
92+
},
93+
didSucceed: true,
94+
});
95+
});
8696

87-
renderGenerativeAIInput({
88-
onSubmitFeedback: (_feedbackChoice, _feedbackText) => {
89-
feedbackChoice = _feedbackChoice;
97+
it('should have feedback options and call the feedback handler on submit', async function () {
98+
// No feedback popover is shown yet.
99+
expect(screen.queryByTestId(feedbackPopoverTextAreaId)).to.not.exist;
90100

91-
feedbackText = _feedbackText;
92-
},
93-
didSucceed: true,
94-
});
101+
const thumbsUpButton = screen.getByTestId(thumbsUpId);
102+
expect(thumbsUpButton).to.be.visible;
103+
thumbsUpButton.click();
104+
105+
const textArea = screen.getByTestId(feedbackPopoverTextAreaId);
106+
expect(textArea).to.be.visible;
107+
userEvent.type(textArea, 'this is the query I was looking for');
95108

96-
// No feedback popover is shown yet.
97-
expect(screen.queryByTestId(feedbackPopoverTextAreaId)).to.not.exist;
109+
await waitFor(() => screen.getByText('Submit').click());
98110

99-
const thumbsUpButton = screen.getByTestId('ai-feedback-thumbs-up');
100-
expect(thumbsUpButton).to.be.visible;
101-
thumbsUpButton.click();
111+
// No feedback popover is shown.
112+
expect(screen.queryByTestId(feedbackPopoverTextAreaId)).to.not.exist;
102113

103-
const textArea = screen.getByTestId(feedbackPopoverTextAreaId);
104-
expect(textArea).to.be.visible;
105-
fireEvent.change(textArea, {
106-
target: { value: 'this is the query I was looking for' },
114+
expect(feedbackChoice).to.equal('positive');
115+
expect(feedbackText).to.equal('this is the query I was looking for');
107116
});
117+
});
108118

109-
await waitFor(() => fireEvent.click(screen.getByText('Submit')));
119+
describe('when onSubmitFeedback is not passed', function () {
120+
beforeEach(function () {
121+
renderGenerativeAIInput({
122+
onSubmitFeedback: undefined,
123+
didSucceed: true,
124+
});
125+
});
110126

111-
// No feedback popover is shown.
112-
expect(screen.queryByTestId(feedbackPopoverTextAreaId)).to.not.exist;
127+
it('should not have feedback options', function () {
128+
expect(screen.queryByTestId(feedbackPopoverTextAreaId)).to.not.exist;
113129

114-
expect(feedbackChoice).to.equal('positive');
115-
expect(feedbackText).to.equal('this is the query I was looking for');
130+
const thumbsUpButton = screen.queryByTestId(thumbsUpId);
131+
expect(thumbsUpButton).to.not.exist;
132+
});
116133
});
117134
});
118135
});

packages/compass-components/src/components/generative-ai/generative-ai-input.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { ErrorSummary } from '../error-warning-summary';
99
import { SpinLoader } from '../loader';
1010
import { DEFAULT_ROBOT_SIZE, RobotSVG } from './robot-svg';
1111
import { AIFeedback } from './ai-feedback';
12-
import type { AIFeedbackProps } from './ai-feedback';
1312
import { focusRing } from '../../hooks/use-focus-ring';
1413

1514
const containerStyles = css({
@@ -155,7 +154,11 @@ type GenerativeAIInputProps = {
155154
onChangeAIPromptText: (text: string) => void;
156155
onClose: () => void;
157156
onSubmitText: (text: string) => void;
158-
} & AIFeedbackProps;
157+
onSubmitFeedback?: (
158+
feedback: 'positive' | 'negative',
159+
feedbackText: string
160+
) => void;
161+
};
159162

160163
function GenerativeAIInput({
161164
aiPromptText,
@@ -309,7 +312,9 @@ function GenerativeAIInput({
309312
</Button>
310313
</div>
311314
</div>
312-
{didSucceed && <AIFeedback onSubmitFeedback={onSubmitFeedback} />}
315+
{didSucceed && onSubmitFeedback && (
316+
<AIFeedback onSubmitFeedback={onSubmitFeedback} />
317+
)}
313318
</div>
314319
{errorMessage && (
315320
<div className={errorSummaryContainer}>

0 commit comments

Comments
 (0)