Skip to content

Commit 42914f4

Browse files
authored
feat(compass-query-bar): add ask ai button to query bar when filter has content COMPASS-7039 (#4746)
1 parent 6614e11 commit 42914f4

File tree

3 files changed

+114
-9
lines changed

3 files changed

+114
-9
lines changed

packages/compass-components/src/components/generative-ai/ai-experience-entry.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,13 @@ const aiEntryLightModeStyles = css(
7474
robotSVGLightModeStyles
7575
);
7676

77-
function AIExperienceEntry({ onClick }: { onClick: () => void }) {
77+
function AIExperienceEntry({
78+
'data-testid': dataTestId,
79+
onClick,
80+
}: {
81+
['data-testid']?: string;
82+
onClick: () => void;
83+
}) {
7884
const darkMode = useDarkMode();
7985

8086
return (
@@ -84,6 +90,7 @@ function AIExperienceEntry({ onClick }: { onClick: () => void }) {
8490
darkMode ? aiEntryDarkModeStyles : aiEntryLightModeStyles
8591
)}
8692
onClick={onClick}
93+
data-testid={dataTestId}
8794
>
8895
Ask AI
8996
<RobotSVG />

packages/compass-query-bar/src/components/query-bar.spec.tsx

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ import type { SinonSpy } from 'sinon';
88
import QueryBar from './query-bar';
99
import { Provider } from 'react-redux';
1010
import { configureStore } from '../stores/query-bar-store';
11+
import type { QueryBarStoreOptions } from '../stores/query-bar-store';
1112
import { toggleQueryOptions } from '../stores/query-bar-reducer';
1213
import {
1314
setCodemirrorEditorValue,
1415
getCodemirrorEditorValue,
1516
clickOnCodemirrorHandler,
1617
} from '@mongodb-js/compass-editor';
18+
import preferencesAccess from 'compass-preferences-model';
1719

1820
const skipIfElectron = function (message: string) {
1921
beforeEach(function () {
@@ -33,11 +35,14 @@ const exportToLanguageButtonId = 'query-bar-open-export-to-language-button';
3335
const queryHistoryButtonId = 'query-history-button';
3436
const queryHistoryComponentTestId = 'query-history';
3537

36-
const renderQueryBar = ({
37-
expanded = false,
38-
...props
39-
}: Partial<ComponentProps<typeof QueryBar>> & { expanded?: boolean } = {}) => {
40-
const store = configureStore();
38+
const renderQueryBar = (
39+
{
40+
expanded = false,
41+
...props
42+
}: Partial<ComponentProps<typeof QueryBar>> & { expanded?: boolean } = {},
43+
storeOptions: Partial<QueryBarStoreOptions> = {}
44+
) => {
45+
const store = configureStore(storeOptions);
4146
store.dispatch(toggleQueryOptions(expanded));
4247

4348
render(
@@ -161,6 +166,77 @@ describe('QueryBar Component', function () {
161166
});
162167
});
163168

169+
describe('with ai enabled', function () {
170+
let sandbox: sinon.SinonSandbox;
171+
172+
beforeEach(function () {
173+
sandbox = sinon.createSandbox();
174+
sandbox
175+
.stub(preferencesAccess, 'getPreferences')
176+
.returns({ enableAIExperience: true } as any);
177+
});
178+
179+
afterEach(function () {
180+
return sandbox.restore();
181+
});
182+
183+
describe('with filter content supplied', function () {
184+
beforeEach(function () {
185+
renderQueryBar(
186+
{
187+
queryOptionsLayout: ['filter'],
188+
},
189+
{
190+
query: {
191+
filter: { a: 2 },
192+
},
193+
}
194+
);
195+
});
196+
197+
it('renders the ask ai button', function () {
198+
expect(screen.getByText('Ask AI')).to.exist;
199+
expect(screen.getByTestId('ai-experience-ask-ai-button')).to.exist;
200+
});
201+
});
202+
203+
describe('without filter content supplied', function () {
204+
beforeEach(function () {
205+
renderQueryBar({
206+
queryOptionsLayout: ['filter'],
207+
});
208+
});
209+
210+
it('does not render the ask ai button, but renders the placeholder', function () {
211+
expect(screen.getByText('Ask AI')).to.exist;
212+
expect(screen.queryByTestId('ai-experience-ask-ai-button')).to.not
213+
.exist;
214+
});
215+
});
216+
});
217+
218+
describe('with ai disabled', function () {
219+
let sandbox: sinon.SinonSandbox;
220+
221+
beforeEach(function () {
222+
sandbox = sinon.createSandbox();
223+
sandbox
224+
.stub(preferencesAccess, 'getPreferences')
225+
.returns({ enableAIExperience: false } as any);
226+
renderQueryBar({
227+
queryOptionsLayout: ['filter'],
228+
});
229+
});
230+
231+
afterEach(function () {
232+
return sandbox.restore();
233+
});
234+
235+
it('does not render the ask ai button', function () {
236+
expect(screen.queryByText('Ask AI')).to.not.exist;
237+
});
238+
});
239+
164240
describe('with two query options', function () {
165241
beforeEach(function () {
166242
renderQueryBar({

packages/compass-query-bar/src/components/query-bar.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useCallback, useMemo } from 'react';
22
import {
3+
AIExperienceEntry,
34
Button,
45
Icon,
56
MoreOptionsToggle,
@@ -76,8 +77,11 @@ const moreOptionsContainerStyles = css({
7677
});
7778

7879
const filterContainerStyles = css({
80+
display: 'flex',
7981
position: 'relative',
8082
flexGrow: 1,
83+
alignItems: 'center',
84+
gap: spacing[2],
8185
});
8286

8387
const filterLabelStyles = css({
@@ -128,6 +132,7 @@ type QueryBarProps = {
128132
* clicked or not
129133
*/
130134
applyId: number;
135+
filterHasContent: boolean;
131136
showExplainButton?: boolean;
132137
showExportToLanguageButton?: boolean;
133138
showQueryHistoryButton?: boolean;
@@ -137,8 +142,8 @@ type QueryBarProps = {
137142
onExplain?: () => void;
138143
insights?: Signal | Signal[];
139144
isAIInputVisible?: boolean;
140-
onShowAIInputClick?: () => void;
141-
onHideAIInputClick?: () => void;
145+
onShowAIInputClick: () => void;
146+
onHideAIInputClick: () => void;
142147
};
143148

144149
export const QueryBar: React.FunctionComponent<QueryBarProps> = ({
@@ -155,6 +160,7 @@ export const QueryBar: React.FunctionComponent<QueryBarProps> = ({
155160
queryChanged,
156161
resultId,
157162
applyId,
163+
filterHasContent,
158164
showExplainButton = false,
159165
showExportToLanguageButton = true,
160166
showQueryHistoryButton = true,
@@ -185,7 +191,7 @@ export const QueryBar: React.FunctionComponent<QueryBarProps> = ({
185191
return enableAIQuery && !isAIInputVisible
186192
? createAIPlaceholderHTMLPlaceholder({
187193
onClickAI: () => {
188-
onShowAIInputClick?.();
194+
onShowAIInputClick();
189195
},
190196
darkMode,
191197
placeholderText: OPTION_DEFINITION.filter.placeholder,
@@ -199,6 +205,15 @@ export const QueryBar: React.FunctionComponent<QueryBarProps> = ({
199205
onShowAIInputClick,
200206
]);
201207

208+
const showAskAIButton = useMemo(() => {
209+
if (!enableAIQuery || isAIInputVisible) {
210+
return false;
211+
}
212+
213+
// See if there is content in the filter.
214+
return filterHasContent;
215+
}, [enableAIQuery, isAIInputVisible, filterHasContent]);
216+
202217
return (
203218
<form
204219
className={cx(queryBarFormStyles, darkMode && queryBarFormDarkStyles)}
@@ -229,6 +244,12 @@ export const QueryBar: React.FunctionComponent<QueryBarProps> = ({
229244
placeholder={filterPlaceholder}
230245
insights={insights}
231246
/>
247+
{showAskAIButton && (
248+
<AIExperienceEntry
249+
data-testid="ai-experience-ask-ai-button"
250+
onClick={onShowAIInputClick}
251+
/>
252+
)}
232253
</div>
233254
{showExplainButton && newExplainPlan && (
234255
<GuideCue
@@ -335,6 +356,7 @@ export default connect(
335356
return {
336357
expanded: expanded,
337358
queryChanged: !isEqualDefaultQuery(fields),
359+
filterHasContent: fields.filter.string !== '',
338360
valid: isQueryValid(fields),
339361
applyId: applyId,
340362
isAIInputVisible: aiQuery.isInputVisible,

0 commit comments

Comments
 (0)