Skip to content

Commit 016cdd8

Browse files
nsdeschenesandrewshie-sentry
authored andcommitted
fix(search-bar): Set initial key input value and render dropdown with one or more items (#97480)
This PR makes a couple modifications to the combobox's: - When editing key, we set the value to the input value, this enables the user to adjust their key slightly, rather than having to fully re-type it. - Render dropdowns if there is one or more items present. Ticket: EXP-439, EXP-440
1 parent f694628 commit 016cdd8

File tree

4 files changed

+103
-40
lines changed

4 files changed

+103
-40
lines changed

static/app/components/searchQueryBuilder/index.spec.tsx

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1918,11 +1918,12 @@ describe('SearchQueryBuilder', function () {
19181918
await userEvent.click(
19191919
screen.getByRole('button', {name: 'Edit key for filter: browser.name'})
19201920
);
1921-
// Should start with an empty input
1922-
await waitFor(() => {
1923-
expect(screen.getByRole('combobox', {name: 'Edit filter key'})).toHaveValue('');
1924-
});
19251921

1922+
// Should start with an input with the previous value
1923+
const combobox = screen.getByRole('combobox', {name: 'Edit filter key'});
1924+
await waitFor(() => expect(combobox).toHaveValue('browser.name'));
1925+
1926+
await userEvent.clear(combobox);
19261927
await userEvent.click(screen.getByRole('option', {name: 'custom_tag_name'}));
19271928

19281929
await waitFor(() => {
@@ -1955,11 +1956,11 @@ describe('SearchQueryBuilder', function () {
19551956
await userEvent.click(
19561957
screen.getByRole('button', {name: 'Edit key for filter: browser.name'})
19571958
);
1958-
// Should start with an empty input
1959-
await waitFor(() => {
1960-
expect(screen.getByRole('combobox', {name: 'Edit filter key'})).toHaveValue('');
1961-
});
1959+
// Should start with an input with the previous value
1960+
const combobox = screen.getByRole('combobox', {name: 'Edit filter key'});
1961+
await waitFor(() => expect(combobox).toHaveValue('browser.name'));
19621962

1963+
await userEvent.clear(combobox);
19631964
await userEvent.click(screen.getByRole('option', {name: 'age'}));
19641965

19651966
await waitFor(() => {
@@ -3626,18 +3627,18 @@ describe('SearchQueryBuilder', function () {
36263627
<SearchQueryBuilder {...builderProps} initialQuery="tags[foo,string]:foo" />
36273628
);
36283629

3629-
expect(
3630-
screen.getByRole('button', {name: 'Edit key for filter: tags[foo,string]'})
3631-
).toHaveTextContent('foo');
3632-
3633-
await userEvent.click(
3634-
screen.getByRole('button', {name: 'Edit key for filter: tags[foo,string]'})
3635-
);
3630+
const editKeyButton = screen.getByRole('button', {
3631+
name: 'Edit key for filter: tags[foo,string]',
3632+
});
3633+
expect(editKeyButton).toHaveTextContent('foo');
3634+
await userEvent.click(editKeyButton);
36363635

36373636
const input = screen.getByPlaceholderText('foo');
36383637
expect(input).toBeInTheDocument();
36393638
expect(input).toHaveFocus();
3639+
await userEvent.clear(input);
36403640
await userEvent.keyboard('foo');
3641+
36413642
expect(screen.getByRole('option', {name: 'foo'})).toBeInTheDocument();
36423643
});
36433644

@@ -3646,18 +3647,18 @@ describe('SearchQueryBuilder', function () {
36463647
<SearchQueryBuilder {...builderProps} initialQuery="tags[bar,number]:<=100" />
36473648
);
36483649

3649-
expect(
3650-
screen.getByRole('button', {name: 'Edit key for filter: tags[bar,number]'})
3651-
).toHaveTextContent('bar');
3652-
3653-
await userEvent.click(
3654-
screen.getByRole('button', {name: 'Edit key for filter: tags[bar,number]'})
3655-
);
3650+
const editKeyButton = screen.getByRole('button', {
3651+
name: 'Edit key for filter: tags[bar,number]',
3652+
});
3653+
expect(editKeyButton).toHaveTextContent('bar');
3654+
await userEvent.click(editKeyButton);
36563655

3657-
const input = screen.getByPlaceholderText('bar');
3656+
const input = screen.getByRole('combobox', {name: 'Edit filter key'});
36583657
expect(input).toBeInTheDocument();
36593658
expect(input).toHaveFocus();
3659+
await userEvent.clear(input);
36603660
await userEvent.keyboard('bar');
3661+
36613662
expect(screen.getByRole('option', {name: 'bar'})).toBeInTheDocument();
36623663
});
36633664

@@ -3793,6 +3794,7 @@ describe('SearchQueryBuilder', function () {
37933794
await userEvent.click(
37943795
screen.getByRole('button', {name: 'Edit key for filter: browser.name'})
37953796
);
3797+
await userEvent.clear(screen.getByRole('combobox', {name: 'Edit filter key'}));
37963798
await userEvent.keyboard('foo{Enter}{Escape}');
37973799

37983800
expect(
@@ -3808,6 +3810,7 @@ describe('SearchQueryBuilder', function () {
38083810
await userEvent.click(
38093811
screen.getByRole('button', {name: 'Edit key for filter: browser.name'})
38103812
);
3813+
await userEvent.clear(screen.getByRole('combobox', {name: 'Edit filter key'}));
38113814
await userEvent.keyboard('bar{Enter}{Escape}');
38123815

38133816
expect(

static/app/components/searchQueryBuilder/tokens/filter/filterKeyCombobox.tsx

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import styled from '@emotion/styled';
33
import {Item} from '@react-stately/collections';
44
import type {Node} from '@react-types/shared';
55

6+
import {useSeerAcknowledgeMutation} from 'sentry/components/events/autofix/useSeerAcknowledgeMutation';
7+
import {ASK_SEER_CONSENT_ITEM_KEY} from 'sentry/components/searchQueryBuilder/askSeer/askSeerConsentOption';
8+
import {ASK_SEER_ITEM_KEY} from 'sentry/components/searchQueryBuilder/askSeer/askSeerOption';
69
import {useSearchQueryBuilder} from 'sentry/components/searchQueryBuilder/context';
710
import {SearchQueryBuilderCombobox} from 'sentry/components/searchQueryBuilder/tokens/combobox';
811
import {getFilterValueType} from 'sentry/components/searchQueryBuilder/tokens/filter/utils';
@@ -16,8 +19,9 @@ import type {
1619
} from 'sentry/components/searchSyntax/parser';
1720
import {getKeyLabel, getKeyName} from 'sentry/components/searchSyntax/utils';
1821
import {t} from 'sentry/locale';
19-
import {space} from 'sentry/styles/space';
22+
import {trackAnalytics} from 'sentry/utils/analytics';
2023
import {FieldKey} from 'sentry/utils/fields';
24+
import useOrganization from 'sentry/utils/useOrganization';
2125

2226
type KeyComboboxProps = {
2327
item: Node<ParseResultToken>;
@@ -27,13 +31,23 @@ type KeyComboboxProps = {
2731

2832
export function FilterKeyCombobox({token, onCommit, item}: KeyComboboxProps) {
2933
const inputRef = useRef<HTMLInputElement>(null);
30-
const [inputValue, setInputValue] = useState('');
34+
const [inputValue, setInputValue] = useState(getKeyLabel(token.key) ?? '');
35+
36+
const organization = useOrganization();
37+
const {mutate: seerAcknowledgeMutate} = useSeerAcknowledgeMutation();
3138
const sortedFilterKeys = useSortedFilterKeyItems({
3239
filterValue: inputValue,
3340
inputValue,
3441
includeSuggestions: false,
3542
});
36-
const {dispatch, getFieldDefinition, getSuggestedFilterKey} = useSearchQueryBuilder();
43+
const {
44+
dispatch,
45+
getFieldDefinition,
46+
getSuggestedFilterKey,
47+
setDisplayAskSeer,
48+
currentInputValue,
49+
setAutoSubmitSeer,
50+
} = useSearchQueryBuilder();
3751

3852
const currentFilterValueType = getFilterValueType(
3953
token,
@@ -45,6 +59,31 @@ export function FilterKeyCombobox({token, onCommit, item}: KeyComboboxProps) {
4559
const newFieldDef = getFieldDefinition(keyName);
4660
const newFilterValueType = getFilterValueType(token, newFieldDef);
4761

62+
if (keyName === ASK_SEER_ITEM_KEY) {
63+
trackAnalytics('trace.explorer.ai_query_interface', {
64+
organization,
65+
action: 'opened',
66+
});
67+
setDisplayAskSeer(true);
68+
69+
if (currentInputValue?.trim()) {
70+
setAutoSubmitSeer(true);
71+
} else {
72+
setAutoSubmitSeer(false);
73+
}
74+
75+
return;
76+
}
77+
78+
if (keyName === ASK_SEER_CONSENT_ITEM_KEY) {
79+
trackAnalytics('trace.explorer.ai_query_interface', {
80+
organization,
81+
action: 'consent_accepted',
82+
});
83+
seerAcknowledgeMutate();
84+
return;
85+
}
86+
4887
if (keyName === getKeyName(token.key)) {
4988
onCommit();
5089
return;
@@ -53,7 +92,7 @@ export function FilterKeyCombobox({token, onCommit, item}: KeyComboboxProps) {
5392
if (
5493
newFilterValueType === currentFilterValueType &&
5594
// IS and HAS filters are strings, but treated differently and will break
56-
// if we prevserve the value.
95+
// if we preserve the value.
5796
keyName !== FieldKey.IS &&
5897
keyName !== FieldKey.HAS
5998
) {
@@ -78,7 +117,19 @@ export function FilterKeyCombobox({token, onCommit, item}: KeyComboboxProps) {
78117

79118
onCommit();
80119
},
81-
[currentFilterValueType, dispatch, getFieldDefinition, item.key, onCommit, token]
120+
[
121+
currentFilterValueType,
122+
currentInputValue,
123+
dispatch,
124+
getFieldDefinition,
125+
item.key,
126+
onCommit,
127+
organization,
128+
seerAcknowledgeMutate,
129+
setAutoSubmitSeer,
130+
setDisplayAskSeer,
131+
token,
132+
]
82133
);
83134

84135
const onOptionSelected = useCallback(
@@ -88,7 +139,7 @@ export function FilterKeyCombobox({token, onCommit, item}: KeyComboboxProps) {
88139
[handleSelectKey]
89140
);
90141

91-
const onValueCommited = useCallback(
142+
const onValueCommitted = useCallback(
92143
(keyName: string) => {
93144
const trimmedKeyName = keyName.trim();
94145

@@ -115,12 +166,12 @@ export function FilterKeyCombobox({token, onCommit, item}: KeyComboboxProps) {
115166
<SearchQueryBuilderCombobox
116167
ref={inputRef}
117168
items={sortedFilterKeys}
118-
placeholder={getKeyLabel(token.key)}
119169
onOptionSelected={onOptionSelected}
120-
onCustomValueCommitted={onValueCommited}
170+
onCustomValueCommitted={onValueCommitted}
121171
onCustomValueBlurred={onCustomValueBlurred}
122172
onExit={onExit}
123173
inputValue={inputValue}
174+
placeholder={getKeyLabel(token.key)}
124175
token={token}
125176
inputLabel={t('Edit filter key')}
126177
onInputChange={e => setInputValue(e.target.value)}
@@ -144,5 +195,5 @@ const EditingWrapper = styled('div')`
144195
height: 100%;
145196
align-items: center;
146197
max-width: 400px;
147-
padding-left: ${space(0.25)};
198+
padding-left: ${p => p.theme.space['2xs']};
148199
`;

static/app/components/searchQueryBuilder/tokens/filter/valueCombobox.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
type SearchItem,
1414
} from 'sentry/components/deprecatedSmartSearchBar/types';
1515
import {DeviceName} from 'sentry/components/deviceName';
16+
import {ASK_SEER_CONSENT_ITEM_KEY} from 'sentry/components/searchQueryBuilder/askSeer/askSeerConsentOption';
17+
import {ASK_SEER_ITEM_KEY} from 'sentry/components/searchQueryBuilder/askSeer/askSeerOption';
1618
import {useSearchQueryBuilder} from 'sentry/components/searchQueryBuilder/context';
1719
import {
1820
SearchQueryBuilderCombobox,
@@ -842,9 +844,16 @@ export function SearchQueryBuilderValueCombobox({
842844
useMemo(() => {
843845
if (!showDatePicker) {
844846
return function (props) {
847+
// Removing the ask seer options from the value list box props as we don't
848+
// display and ask seer option in this list box.
849+
const hiddenOptions = new Set(props.hiddenOptions);
850+
hiddenOptions.delete(ASK_SEER_ITEM_KEY);
851+
hiddenOptions.delete(ASK_SEER_CONSENT_ITEM_KEY);
852+
845853
return (
846854
<ValueListBox
847855
{...props}
856+
hiddenOptions={hiddenOptions}
848857
wrapperRef={topLevelWrapperRef}
849858
isMultiSelect={canSelectMultipleValues}
850859
items={items}

static/app/components/searchQueryBuilder/tokens/useSortedFilterKeyItems.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,13 @@ export function useSortedFilterKeyItems({
202202
return createItem(filterKeys[item.key]!, getFieldDefinition(item.key));
203203
});
204204

205+
const askSeerItem = [];
206+
if (enableAISearch) {
207+
askSeerItem.push(
208+
gaveSeerConsent ? createAskSeerItem() : createAskSeerConsentItem()
209+
);
210+
}
211+
205212
if (includeSuggestions) {
206213
const rawSearchSection: KeySectionItem = {
207214
key: 'raw-search',
@@ -281,13 +288,6 @@ export function useSortedFilterKeyItems({
281288
type: 'section',
282289
};
283290

284-
const askSeerItem = [];
285-
if (enableAISearch) {
286-
askSeerItem.push(
287-
gaveSeerConsent ? createAskSeerItem() : createAskSeerConsentItem()
288-
);
289-
}
290-
291291
const {shouldShowAtTop, suggestedFiltersSection} =
292292
getValueSuggestionsFromSearchResult(searched);
293293

@@ -302,7 +302,7 @@ export function useSortedFilterKeyItems({
302302
];
303303
}
304304

305-
return keyItems;
305+
return [...keyItems, ...askSeerItem];
306306
}, [
307307
disallowFreeText,
308308
enableAISearch,

0 commit comments

Comments
 (0)