Skip to content

Commit 7ac4dcb

Browse files
authored
chore(trace ai queries): Search with free text directly (#96722)
- Fix extra commas being added on free search - Fix "uncommitted" text from the search bar not showing up in the Search Bar when switching to that view - Automatically fires the search if the `Ask Seer` button is selected from the free text view https://github.com/user-attachments/assets/52afe1a7-2962-4a36-99e1-952249f537f3
1 parent 3205191 commit 7ac4dcb

File tree

6 files changed

+74
-13
lines changed

6 files changed

+74
-13
lines changed

static/app/components/searchQueryBuilder/context.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ import useOrganization from 'sentry/utils/useOrganization';
2929

3030
interface SearchQueryBuilderContextData {
3131
actionBarRef: React.RefObject<HTMLDivElement | null>;
32+
autoSubmitSeer: boolean;
3233
committedQuery: string;
34+
currentInputValue: string;
3335
disabled: boolean;
3436
disallowFreeText: boolean;
3537
disallowWildcard: boolean;
@@ -49,6 +51,8 @@ interface SearchQueryBuilderContextData {
4951
parsedQuery: ParseResult | null;
5052
query: string;
5153
searchSource: string;
54+
setAutoSubmitSeer: (enabled: boolean) => void;
55+
setCurrentInputValue: (value: string) => void;
5256
setDisplaySeerResults: (enabled: boolean) => void;
5357
size: 'small' | 'normal';
5458
wrapperRef: React.RefObject<HTMLDivElement | null>;
@@ -110,6 +114,8 @@ export function SearchQueryBuilderProvider({
110114
const {setupAcknowledgement} = useOrganizationSeerSetup({enabled: enableAISearch});
111115

112116
const [displaySeerResults, setDisplaySeerResults] = useState(false);
117+
const [autoSubmitSeer, setAutoSubmitSeer] = useState(false);
118+
const [currentInputValue, setCurrentInputValue] = useState('');
113119

114120
const {state, dispatch} = useQueryBuilderState({
115121
initialQuery,
@@ -193,17 +199,22 @@ export function SearchQueryBuilderProvider({
193199
portalTarget,
194200
displaySeerResults,
195201
setDisplaySeerResults,
202+
autoSubmitSeer,
203+
setAutoSubmitSeer,
196204
replaceRawSearchKeys,
197205
matchKeySuggestions,
198206
filterKeyAliases,
199207
gaveSeerConsent: setupAcknowledgement.orgHasAcknowledged,
208+
currentInputValue,
209+
setCurrentInputValue,
200210
};
201211
}, [
202212
disabled,
203213
disallowFreeText,
204214
disallowWildcard,
205215
dispatch,
206216
displaySeerResults,
217+
autoSubmitSeer,
207218
enableAISearch,
208219
filterKeyAliases,
209220
filterKeyMenuWidth,
@@ -224,6 +235,8 @@ export function SearchQueryBuilderProvider({
224235
stableFilterKeys,
225236
stableGetSuggestedFilterKey,
226237
state,
238+
currentInputValue,
239+
setCurrentInputValue,
227240
]);
228241

229242
return (

static/app/components/searchQueryBuilder/tokens/filterKeyListBox/useFilterKeyListBox.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,10 @@ export function useFilterKeyListBox({filterValue}: {filterValue: string}) {
168168
filterKeys,
169169
getFieldDefinition,
170170
setDisplaySeerResults,
171+
setAutoSubmitSeer,
171172
enableAISearch,
172173
gaveSeerConsent,
174+
currentInputValue,
173175
} = useSearchQueryBuilder();
174176
const {sectionedItems} = useFilterKeyItems();
175177
const recentFilters = useRecentSearchFilters();
@@ -391,6 +393,12 @@ export function useFilterKeyListBox({filterValue}: {filterValue: string}) {
391393
action: 'opened',
392394
});
393395
setDisplaySeerResults(true);
396+
397+
if (currentInputValue?.trim()) {
398+
setAutoSubmitSeer(true);
399+
} else {
400+
setAutoSubmitSeer(false);
401+
}
394402
return;
395403
}
396404

@@ -403,7 +411,13 @@ export function useFilterKeyListBox({filterValue}: {filterValue: string}) {
403411
return;
404412
}
405413
},
406-
[organization, seerAcknowledgeMutate, setDisplaySeerResults]
414+
[
415+
organization,
416+
seerAcknowledgeMutate,
417+
setDisplaySeerResults,
418+
setAutoSubmitSeer,
419+
currentInputValue,
420+
]
407421
);
408422

409423
return {

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,6 @@ function SearchQueryBuilderInputInternal({
253253
setSelectionIndex(inputRef.current?.selectionStart ?? 0);
254254
}, []);
255255

256-
const resetInputValue = useCallback(() => {
257-
setInputValue(trimmedTokenValue);
258-
updateSelectionIndex();
259-
}, [trimmedTokenValue, updateSelectionIndex]);
260-
261256
const filterValue = getWordAtCursorPosition(inputValue, selectionIndex);
262257

263258
const {
@@ -270,8 +265,15 @@ function SearchQueryBuilderInputInternal({
270265
placeholder,
271266
searchSource,
272267
recentSearches,
268+
setCurrentInputValue,
273269
} = useSearchQueryBuilder();
274270

271+
const resetInputValue = useCallback(() => {
272+
setInputValue(trimmedTokenValue);
273+
setCurrentInputValue(trimmedTokenValue);
274+
updateSelectionIndex();
275+
}, [trimmedTokenValue, updateSelectionIndex, setCurrentInputValue]);
276+
275277
const {customMenu, sectionItems, maxOptions, onKeyDownCapture, handleOptionSelected} =
276278
useFilterKeyListBox({
277279
filterValue,
@@ -617,6 +619,7 @@ function SearchQueryBuilderInputInternal({
617619
}
618620

619621
setInputValue(e.target.value);
622+
setCurrentInputValue(e.target.value);
620623
setSelectionIndex(e.target.selectionStart ?? 0);
621624
}}
622625
onKeyDown={onKeyDown}

static/app/views/explore/components/seerComboBox/seerComboBox.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ export function SeerComboBox({initialQuery, ...props}: SeerComboBoxProps) {
106106
);
107107

108108
const openForm = useFeedbackForm();
109-
const {setDisplaySeerResults} = useSearchQueryBuilder();
109+
const {setDisplaySeerResults, autoSubmitSeer, setAutoSubmitSeer} =
110+
useSearchQueryBuilder();
110111
const {rawResult, submitQuery, isPending} = useSeerSearch();
111112
const applySeerSearchQuery = useApplySeerSearchQuery();
112113
const organization = useOrganization();
@@ -321,6 +322,17 @@ export function SeerComboBox({initialQuery, ...props}: SeerComboBoxProps) {
321322
}
322323
}, [state]);
323324

325+
useLayoutEffect(() => {
326+
if (autoSubmitSeer && searchQuery.trim()) {
327+
trackAnalytics('trace.explorer.ai_query_submitted', {
328+
organization,
329+
natural_language_query: searchQuery.trim(),
330+
});
331+
submitQuery(searchQuery.trim());
332+
setAutoSubmitSeer(false);
333+
}
334+
}, [autoSubmitSeer, searchQuery, organization, submitQuery, setAutoSubmitSeer]);
335+
324336
return (
325337
<Wrapper ref={containerRef} isDropdownOpen={state.isOpen}>
326338
<PositionedSearchIconContainer>

static/app/views/explore/spans/spansTab.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,10 +173,21 @@ function SpansSearchBar({
173173
}: {
174174
eapSpanSearchQueryBuilderProps: EAPSpanSearchQueryBuilderProps;
175175
}) {
176-
const {displaySeerResults, query} = useSearchQueryBuilder();
176+
const {displaySeerResults, query, currentInputValue} = useSearchQueryBuilder();
177+
178+
const initialSeerQuery = (() => {
179+
const committedQuery = query.trim();
180+
const inputValue = currentInputValue.trim();
181+
182+
if (!inputValue) return committedQuery;
183+
184+
if (!committedQuery) return inputValue;
185+
186+
return `${committedQuery} ${inputValue}`;
187+
})();
177188

178189
return displaySeerResults ? (
179-
<SeerComboBox initialQuery={query} />
190+
<SeerComboBox initialQuery={initialSeerQuery} />
180191
) : (
181192
<EAPSpanSearchQueryBuilder autoFocus {...eapSpanSearchQueryBuilderProps} />
182193
);

static/app/views/explore/utils.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -618,18 +618,26 @@ export function formatQueryToNaturalLanguage(query: string): string {
618618
return formattedTokens.reduce((result, token, index) => {
619619
if (index === 0) return token;
620620

621-
const prevToken = formattedTokens[index - 1];
622-
if (!prevToken) return `${result}, ${token}`;
621+
const currentOriginalToken = tokens[index] || '';
622+
const prevOriginalToken = tokens[index - 1] || '';
623623

624624
const isLogicalOp = token.toUpperCase() === 'AND' || token.toUpperCase() === 'OR';
625625
const prevIsLogicalOp =
626-
prevToken.toUpperCase() === 'AND' || prevToken.toUpperCase() === 'OR';
626+
formattedTokens[index - 1]?.toUpperCase() === 'AND' ||
627+
formattedTokens[index - 1]?.toUpperCase() === 'OR';
627628

628629
if (isLogicalOp || prevIsLogicalOp) {
629630
return `${result} ${token}`;
630631
}
631632

632-
return `${result}, ${token}`;
633+
const isCurrentFilter = /[:>=<!]/.test(currentOriginalToken);
634+
const isPrevFilter = /[:>=<!]/.test(prevOriginalToken);
635+
636+
if (isCurrentFilter && isPrevFilter) {
637+
return `${result}, ${token}`;
638+
}
639+
640+
return `${result} ${token}`;
633641
}, '');
634642
}
635643

0 commit comments

Comments
 (0)