diff --git a/static/app/components/searchQueryBuilder/index.spec.tsx b/static/app/components/searchQueryBuilder/index.spec.tsx index 00a595d7999368..b4a7c658906e1a 100644 --- a/static/app/components/searchQueryBuilder/index.spec.tsx +++ b/static/app/components/searchQueryBuilder/index.spec.tsx @@ -4045,6 +4045,54 @@ describe('SearchQueryBuilder', () => { screen.getByRole('row', {name: 'span.description:"random value"'}) ).toBeInTheDocument(); }); + + describe('with wildcard operators enabled', () => { + it('should replace raw search keys with defined key:contains:value', async () => { + render( + , + {organization: {features: ['search-query-builder-wildcard-operators']}} + ); + + await userEvent.type(screen.getByRole('textbox'), 'randomValue'); + + await userEvent.click( + within(screen.getByRole('listbox')).getAllByText('span.description')[0]! + ); + + expect( + screen.getByRole('row', { + name: `span.description:${WildcardOperators.CONTAINS}randomValue`, + }) + ).toBeInTheDocument(); + }); + + it('should replace raw search keys with defined key:contains:"value space', async () => { + render( + , + {organization: {features: ['search-query-builder-wildcard-operators']}} + ); + + await userEvent.type(screen.getByRole('textbox'), 'random value'); + + await userEvent.click( + within(screen.getByRole('listbox')).getAllByText('span.description')[0]! + ); + + expect( + screen.getByRole('row', { + name: `span.description:${WildcardOperators.CONTAINS}"random value"`, + }) + ).toBeInTheDocument(); + }); + }); }); describe('matchKeySuggestions', () => { diff --git a/static/app/components/searchQueryBuilder/tokens/filterKeyListBox/utils.tsx b/static/app/components/searchQueryBuilder/tokens/filterKeyListBox/utils.tsx index e1e360f9054947..33c6b2603905b0 100644 --- a/static/app/components/searchQueryBuilder/tokens/filterKeyListBox/utils.tsx +++ b/static/app/components/searchQueryBuilder/tokens/filterKeyListBox/utils.tsx @@ -17,7 +17,11 @@ import type { FieldDefinitionGetter, FilterKeySection, } from 'sentry/components/searchQueryBuilder/types'; -import type {Token, TokenResult} from 'sentry/components/searchSyntax/parser'; +import { + WildcardOperators, + type Token, + type TokenResult, +} from 'sentry/components/searchSyntax/parser'; import { getKeyLabel as getFilterKeyLabel, getKeyName, @@ -164,6 +168,24 @@ export function createRawSearchFilterIsValueItem( }; } +export function createRawSearchFilterContainsValueItem( + key: string, + value: string +): RawSearchFilterIsValueItem { + const filter = `${key}:${WildcardOperators.CONTAINS}${escapeFilterValue(value)}`; + + return { + key: getEscapedKey(`${key}:${WildcardOperators.CONTAINS}${value}`), + label: , + value: filter, + textValue: filter, + hideCheck: true, + showDetailsInOverlay: true, + details: null, + type: 'raw-search-filter-is-value', + }; +} + export function createRecentFilterItem({filter}: {filter: TokenResult}) { const key = getKeyName(filter.key); return { diff --git a/static/app/components/searchQueryBuilder/tokens/useSortedFilterKeyItems.tsx b/static/app/components/searchQueryBuilder/tokens/useSortedFilterKeyItems.tsx index ff1c5254fd07fe..8d12b333164554 100644 --- a/static/app/components/searchQueryBuilder/tokens/useSortedFilterKeyItems.tsx +++ b/static/app/components/searchQueryBuilder/tokens/useSortedFilterKeyItems.tsx @@ -10,6 +10,7 @@ import { createAskSeerItem, createFilterValueItem, createItem, + createRawSearchFilterContainsValueItem, createRawSearchFilterIsValueItem, createRawSearchItem, } from 'sentry/components/searchQueryBuilder/tokens/filterKeyListBox/utils'; @@ -18,6 +19,7 @@ import type {Tag} from 'sentry/types/group'; import {defined} from 'sentry/utils'; import {FieldKey} from 'sentry/utils/fields'; import {useFuzzySearch} from 'sentry/utils/fuzzySearch'; +import useOrganization from 'sentry/utils/useOrganization'; type FilterKeySearchItem = { description: string; @@ -139,6 +141,10 @@ export function useSortedFilterKeyItems({ enableAISearch, } = useSearchQueryBuilder(); + const hasWildcardOperators = useOrganization().features.includes( + 'search-query-builder-wildcard-operators' + ); + const flatKeys = useMemo(() => Object.values(filterKeys), [filterKeys]); const searchableItems = useMemo(() => { @@ -215,12 +221,17 @@ export function useSortedFilterKeyItems({ !replaceRawSearchKeys?.length; const rawSearchFilterIsValueItems = - replaceRawSearchKeys?.map(key => { + replaceRawSearchKeys?.flatMap(key => { const value = inputValue?.includes(' ') ? `"${inputValue.replace(/"/g, '')}"` : inputValue; - return createRawSearchFilterIsValueItem(key, value); + return [ + ...(hasWildcardOperators + ? [createRawSearchFilterContainsValueItem(key, value)] + : []), + createRawSearchFilterIsValueItem(key, value), + ]; }) ?? []; const rawSearchReplacements: KeySectionItem = { @@ -292,6 +303,7 @@ export function useSortedFilterKeyItems({ filterValue, flatKeys, getFieldDefinition, + hasWildcardOperators, includeSuggestions, inputValue, matchKeySuggestions,