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,