Skip to content

Commit ac1aa39

Browse files
feat: disable the query bar controls while GenAI is running COMPASS-7839 (#5756)
* feat: disable the query bar controls while GenAI is running COMPASS-7839 * refactor: revert deleting of showExportToLanguageButton
1 parent 0ef5b8a commit ac1aa39

File tree

7 files changed

+218
-17
lines changed

7 files changed

+218
-17
lines changed

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
createSandboxFromDefaultPreferences,
1414
} from 'compass-preferences-model';
1515
import { PreferencesProvider } from 'compass-preferences-model/provider';
16+
import { AIPipelineActionTypes } from '../../../modules/pipeline-builder/pipeline-ai';
1617

1718
describe('PipelineActions', function () {
1819
afterEach(cleanup);
@@ -275,6 +276,36 @@ describe('PipelineActions', function () {
275276
).to.equal('true');
276277
});
277278

279+
it('should disable actions while ai is fetching', function () {
280+
const { store, rerender } = renderPipelineActions({
281+
pipeline: [{ $match: { _id: 1 } }],
282+
});
283+
284+
store.dispatch({
285+
type: AIPipelineActionTypes.AIPipelineStarted,
286+
requestId: 'pineapples',
287+
});
288+
rerender();
289+
290+
expect(
291+
screen
292+
.getByTestId('pipeline-toolbar-explain-aggregation-button')
293+
.getAttribute('aria-disabled')
294+
).to.equal('true');
295+
296+
expect(
297+
screen
298+
.getByTestId('pipeline-toolbar-export-aggregation-button')
299+
.getAttribute('aria-disabled')
300+
).to.equal('true');
301+
302+
expect(
303+
screen
304+
.getByTestId('pipeline-toolbar-run-button')
305+
.getAttribute('aria-disabled')
306+
).to.equal('true');
307+
});
308+
278309
it('should disable export button when pipeline is $out / $merge', function () {
279310
renderPipelineActions({
280311
pipeline: [{ $out: 'foo' }],

packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/pipeline-actions.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,17 +170,20 @@ const mapState = (state: RootState) => {
170170
const isMergeOrOutPipeline = isOutputStage(lastStage);
171171
const hasSyntaxErrors = getIsPipelineInvalidFromBuilderState(state, false);
172172
const isBuilderView = state.workspace === 'builder';
173+
const isAIFetching = state.pipelineBuilder.aiPipeline.status === 'fetching';
173174

174175
return {
175-
isRunButtonDisabled: hasSyntaxErrors,
176-
isExplainButtonDisabled: hasSyntaxErrors,
177-
isExportButtonDisabled: isMergeOrOutPipeline || hasSyntaxErrors,
176+
isRunButtonDisabled: hasSyntaxErrors || isAIFetching,
177+
isExplainButtonDisabled: hasSyntaxErrors || isAIFetching,
178+
isExportButtonDisabled:
179+
isMergeOrOutPipeline || hasSyntaxErrors || isAIFetching,
178180
showAIEntry:
179181
!state.pipelineBuilder.aiPipeline.isInputVisible &&
180182
resultPipeline.length > 0 &&
181183
isBuilderView,
182184
showUpdateViewButton: Boolean(state.editViewName),
183-
isUpdateViewButtonDisabled: !state.isModified || hasSyntaxErrors,
185+
isUpdateViewButtonDisabled:
186+
!state.isModified || hasSyntaxErrors || isAIFetching,
184187
showCollectionScanInsight: state.insights.isCollectionScan,
185188
};
186189
};

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ type OptionEditorProps = {
9393
value?: string;
9494
['data-testid']?: string;
9595
insights?: Signal | Signal[];
96+
disabled?: boolean;
9697
};
9798

9899
export const OptionEditor: React.FunctionComponent<OptionEditorProps> = ({
@@ -108,6 +109,7 @@ export const OptionEditor: React.FunctionComponent<OptionEditorProps> = ({
108109
value = '',
109110
['data-testid']: dataTestId,
110111
insights,
112+
disabled = false,
111113
}) => {
112114
const showInsights = usePreference('showInsights');
113115
const editorContainerRef = useRef<HTMLDivElement>(null);
@@ -199,6 +201,7 @@ export const OptionEditor: React.FunctionComponent<OptionEditorProps> = ({
199201
onFocus={onFocus}
200202
onPaste={onPaste}
201203
onBlur={onBlur}
204+
readOnly={disabled}
202205
/>
203206
{showInsights && insights && (
204207
<div className={queryBarEditorOptionInsightsStyles}>

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ type QueryBarRowProps = {
1919
queryOptionsLayout: QueryBarRowLayout;
2020
onApply?(): void;
2121
placeholders?: Record<QueryProperty, string>;
22+
disabled?: boolean;
2223
};
2324

2425
export const QueryBarRow: React.FunctionComponent<QueryBarRowProps> = ({
2526
queryOptionsLayout,
2627
onApply,
2728
placeholders,
29+
disabled,
2830
}) => {
2931
return (
3032
<div className={rowStyles}>
@@ -35,6 +37,7 @@ export const QueryBarRow: React.FunctionComponent<QueryBarRowProps> = ({
3537
id={`query-bar-option-input-${queryOptionsLayout}`}
3638
onApply={onApply}
3739
placeholder={placeholders?.[queryOptionsLayout]}
40+
disabled={disabled}
3841
/>
3942
) : (
4043
queryOptionsLayout.map((optionName: QueryOption) => (
@@ -44,6 +47,7 @@ export const QueryBarRow: React.FunctionComponent<QueryBarRowProps> = ({
4447
id={`query-bar-option-input-${optionName}`}
4548
onApply={onApply}
4649
placeholder={placeholders?.[optionName]}
50+
disabled={disabled}
4751
/>
4852
))
4953
)}

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

Lines changed: 155 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import type { ComponentProps } from 'react';
3-
import { cleanup, render, screen } from '@testing-library/react';
3+
import { cleanup, render, screen, within } from '@testing-library/react';
44
import userEvent from '@testing-library/user-event';
55
import { expect } from 'chai';
66
import sinon from 'sinon';
@@ -10,6 +10,7 @@ import { Provider } from '../stores/context';
1010
import { configureStore } from '../stores/query-bar-store';
1111
import type { QueryBarExtraArgs, RootState } from '../stores/query-bar-store';
1212
import { toggleQueryOptions } from '../stores/query-bar-reducer';
13+
import { AIQueryActionTypes } from '../stores/ai-query-reducer';
1314
import type { PreferencesAccess } from 'compass-preferences-model';
1415
import { createSandboxFromDefaultPreferences } from 'compass-preferences-model';
1516
import { mapQueryToFormFields } from '../utils/query';
@@ -51,7 +52,7 @@ describe('QueryBar Component', function () {
5152
} as QueryBarExtraArgs);
5253
store.dispatch(toggleQueryOptions(expanded));
5354

54-
render(
55+
const component = (
5556
<PreferencesProvider value={preferences}>
5657
<FavoriteQueryStorageProvider value={compassFavoriteQueryStorageAccess}>
5758
<RecentQueryStorageProvider value={compassRecentQueryStorageAccess}>
@@ -60,15 +61,25 @@ describe('QueryBar Component', function () {
6061
buttonLabel="Apply"
6162
onApply={noop}
6263
onReset={noop}
63-
showExportToLanguageButton
6464
resultId="123"
65+
showExportToLanguageButton
6566
{...props}
6667
/>
6768
</Provider>
6869
</RecentQueryStorageProvider>
6970
</FavoriteQueryStorageProvider>
7071
</PreferencesProvider>
7172
);
73+
74+
const result = render(component);
75+
76+
return {
77+
...result,
78+
store,
79+
rerender: () => {
80+
result.rerender(component);
81+
},
82+
};
7283
};
7384

7485
beforeEach(async function () {
@@ -126,23 +137,158 @@ describe('QueryBar Component', function () {
126137
});
127138
});
128139

129-
describe('with one query option', function () {
140+
describe('when rendered', function () {
130141
beforeEach(function () {
131142
renderQueryBar({
132-
queryOptionsLayout: ['project'],
133-
expanded: true,
134143
onApply: onApplySpy,
135144
onReset: onResetSpy,
136145
});
137146
});
138147

139-
it('renders the expanded inputs', function () {
148+
it('renders the filter input', function () {
149+
const filterInput = screen.getByTestId('query-bar-option-filter-input');
150+
expect(filterInput).to.exist;
151+
expect(filterInput).to.have.attribute(
152+
'id',
153+
'query-bar-option-input-filter'
154+
);
155+
140156
const queryInputs = screen.getAllByRole('textbox');
141-
expect(queryInputs.length).to.equal(2);
157+
expect(queryInputs.length).to.equal(1);
158+
});
159+
160+
it('renders the query history button', function () {
161+
const queryHistoryButton = screen.queryByTestId(queryHistoryButtonId);
162+
expect(queryHistoryButton).to.be.visible;
163+
});
164+
165+
it('does not render the query history popover', function () {
166+
const queryHistory = screen.queryByTestId(queryHistoryComponentTestId);
167+
expect(queryHistory).to.not.exist;
168+
});
169+
});
170+
171+
describe('when ai is ready', function () {
172+
beforeEach(function () {
173+
renderQueryBar(
174+
{
175+
queryOptionsLayout: ['project'],
176+
expanded: true,
177+
onApply: onApplySpy,
178+
onReset: onResetSpy,
179+
},
180+
{}
181+
);
182+
});
183+
184+
it('query controls are enabled', function () {
185+
expect(
186+
screen
187+
.getByTestId('query-bar-open-export-to-language-button')
188+
.getAttribute('aria-disabled')
189+
).to.equal('false');
190+
expect(
191+
screen
192+
.getByTestId('query-bar-apply-filter-button')
193+
.getAttribute('aria-disabled')
194+
).to.equal('false');
195+
expect(
196+
screen
197+
.getByTestId('query-bar-open-export-to-language-button')
198+
.getAttribute('aria-disabled')
199+
).to.equal('false');
200+
expect(
201+
screen
202+
.getByTestId('query-bar-open-export-to-language-button')
203+
.getAttribute('aria-disabled')
204+
).to.equal('false');
205+
});
206+
207+
it('editors are not readonly', function () {
208+
expect(
209+
within(screen.getByTestId('query-bar-option-filter-input'))
210+
.getByRole('textbox')
211+
.getAttribute('aria-readonly')
212+
).to.not.exist;
213+
expect(
214+
within(screen.getByTestId('query-bar-option-project-input'))
215+
.getByRole('textbox')
216+
.getAttribute('aria-readonly')
217+
).to.not.exist;
218+
});
219+
});
220+
221+
describe('while ai is fetching', function () {
222+
it('query controls are disabled', function () {
223+
const { store, rerender } = renderQueryBar(
224+
{
225+
queryOptionsLayout: ['project'],
226+
expanded: true,
227+
onApply: onApplySpy,
228+
onReset: onResetSpy,
229+
},
230+
{}
231+
);
232+
233+
store.dispatch({
234+
type: AIQueryActionTypes.AIQueryStarted,
235+
requestId: 'pineapples',
236+
});
237+
rerender();
238+
239+
expect(
240+
screen
241+
.getByTestId('query-bar-open-export-to-language-button')
242+
.getAttribute('aria-disabled')
243+
).to.equal('true');
244+
expect(
245+
screen
246+
.getByTestId('query-bar-apply-filter-button')
247+
.getAttribute('aria-disabled')
248+
).to.equal('true');
249+
expect(
250+
screen
251+
.getByTestId('query-bar-open-export-to-language-button')
252+
.getAttribute('aria-disabled')
253+
).to.equal('true');
254+
expect(
255+
screen
256+
.getByTestId('query-bar-open-export-to-language-button')
257+
.getAttribute('aria-disabled')
258+
).to.equal('true');
259+
});
260+
261+
it('editors are readonly', function () {
262+
const store = configureStore({}, {
263+
preferences,
264+
logger: createNoopLoggerAndTelemetry(),
265+
} as QueryBarExtraArgs);
266+
267+
store.dispatch({
268+
type: AIQueryActionTypes.AIQueryStarted,
269+
requestId: 'pineapples',
270+
});
271+
272+
render(
273+
<Provider store={store}>
274+
<QueryBar
275+
buttonLabel="Apply"
276+
onApply={noop}
277+
onReset={noop}
278+
resultId="123"
279+
/>
280+
</Provider>
281+
);
282+
283+
expect(
284+
within(screen.getByTestId('query-bar-option-filter-input'))
285+
.getByRole('textbox')
286+
.getAttribute('aria-readonly')
287+
).to.equal('true');
142288
});
143289
});
144290

145-
describe('with ai enabled', function () {
291+
describe('with ai is enabled', function () {
146292
beforeEach(async function () {
147293
await preferences.savePreferences({
148294
enableGenAIFeatures: true,

0 commit comments

Comments
 (0)