From 43cd92bbc3fa263ad3835ef3627ec1cfca588d60 Mon Sep 17 00:00:00 2001 From: nsdeschenes Date: Thu, 2 Apr 2026 11:20:26 -0300 Subject: [PATCH 1/2] revert(searchQueryBuilder): Roll back input flow changes to old focus behavior Remove the search-query-builder-input-flow-changes feature flag and revert to the previous behavior where adding a filter focuses directly on the value instead of the operator step. Co-Authored-By: Claude Sonnet 4 --- .../searchQueryBuilder/tokens/freeText.tsx | 36 +++---------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/static/app/components/searchQueryBuilder/tokens/freeText.tsx b/static/app/components/searchQueryBuilder/tokens/freeText.tsx index 8878dfb77020..b222cdc53f22 100644 --- a/static/app/components/searchQueryBuilder/tokens/freeText.tsx +++ b/static/app/components/searchQueryBuilder/tokens/freeText.tsx @@ -8,7 +8,6 @@ import type {KeyboardEvent, Node} from '@react-types/shared'; import {useSearchQueryBuilder} from 'sentry/components/searchQueryBuilder/context'; import {useQueryBuilderGridItem} from 'sentry/components/searchQueryBuilder/hooks/useQueryBuilderGridItem'; import {SearchQueryBuilderCombobox} from 'sentry/components/searchQueryBuilder/tokens/combobox'; -import {areWildcardOperatorsAllowed} from 'sentry/components/searchQueryBuilder/tokens/filter/utils'; import {useFilterKeyListBox} from 'sentry/components/searchQueryBuilder/tokens/filterKeyListBox/useFilterKeyListBox'; import {InvalidTokenTooltip} from 'sentry/components/searchQueryBuilder/tokens/invalidTokenTooltip'; import {useSortedFilterKeyItems} from 'sentry/components/searchQueryBuilder/tokens/useSortedFilterKeyItems'; @@ -27,7 +26,6 @@ import { recentSearchTypeToLabel, } from 'sentry/components/searchQueryBuilder/utils'; import { - FilterType, InvalidReason, parseSearch, Token, @@ -134,25 +132,13 @@ function countPreviousItemsOfType({ function calculateNextFocusForFilter( state: ListState, - definition: FieldDefinition | null, - key: string | null, - hasInputChangeFlows: boolean + definition: FieldDefinition | null ): FocusOverride { const numPreviousFilterItems = countPreviousItemsOfType({state, type: Token.FILTER}); - const isNumericValueType = - definition?.valueType === FieldValueType.NUMBER || - definition?.valueType === FieldValueType.INTEGER; - - let part: FocusOverride['part'] = - hasInputChangeFlows && (isNumericValueType || areWildcardOperatorsAllowed(definition)) - ? 'op' - : 'value'; - + let part: FocusOverride['part'] = 'value'; if (definition?.kind === FieldKind.FUNCTION && definition.parameters?.length) { part = 'key'; - } else if (key === FilterType.IS || key === FilterType.HAS) { - part = 'value'; } return { @@ -265,9 +251,6 @@ function SearchQueryBuilderInputInternal({ const [selectionIndex, setSelectionIndex] = useState(0); const organization = useOrganization(); - const hasInputChangeFlows = organization.features.includes( - 'search-query-builder-input-flow-changes' - ); const updateSelectionIndex = useCallback(() => { setSelectionIndex(inputRef.current?.selectionStart ?? 0); @@ -512,12 +495,7 @@ function SearchQueryBuilderInputInternal({ value, getFieldDefinition ), - focusOverride: calculateNextFocusForFilter( - state, - getFieldDefinition(value), - value, - hasInputChangeFlows - ), + focusOverride: calculateNextFocusForFilter(state, getFieldDefinition(value)), shouldCommitQuery: false, }); resetInputValue(); @@ -617,9 +595,7 @@ function SearchQueryBuilderInputInternal({ ), focusOverride: calculateNextFocusForFilter( state, - getFieldDefinition(filterValue), - null, - hasInputChangeFlows + getFieldDefinition(filterValue) ), shouldCommitQuery: false, }); @@ -660,9 +636,7 @@ function SearchQueryBuilderInputInternal({ ), focusOverride: calculateNextFocusForFilter( state, - getFieldDefinition(filterKey), - filterKey, - hasInputChangeFlows + getFieldDefinition(filterKey) ), shouldCommitQuery: false, }); From 28907b59a14f05d44ae7b8a79167fc3997811096 Mon Sep 17 00:00:00 2001 From: nsdeschenes Date: Thu, 2 Apr 2026 11:21:15 -0300 Subject: [PATCH 2/2] test: Update tests to remove search-query-builder-input-flow-changes flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the feature flag from test fixtures and update test flows to match the reverted behavior — no longer clicking through the operator step when adding a filter. Co-Authored-By: Claude Sonnet 4 --- .../searchQueryBuilder/index.spec.tsx | 50 ++++--------------- static/app/views/automations/list.spec.tsx | 4 +- .../filterResultsStep/spansSearchBar.spec.tsx | 3 +- .../streamline/eventDetailsHeader.spec.tsx | 7 +-- .../streamline/eventSearch.spec.tsx | 8 ++- static/app/views/releases/list/index.spec.tsx | 2 +- 6 files changed, 17 insertions(+), 57 deletions(-) diff --git a/static/app/components/searchQueryBuilder/index.spec.tsx b/static/app/components/searchQueryBuilder/index.spec.tsx index 4e82b8c027c7..50da5a97daea 100644 --- a/static/app/components/searchQueryBuilder/index.spec.tsx +++ b/static/app/components/searchQueryBuilder/index.spec.tsx @@ -1068,9 +1068,7 @@ describe('SearchQueryBuilder', () => { it('can add a new token by clicking a key suggestion', async () => { const mockOnChange = jest.fn(); - render(, { - organization: {features: ['search-query-builder-input-flow-changes']}, - }); + render(); await userEvent.click(screen.getByRole('combobox', {name: 'Add a search term'})); await userEvent.click(screen.getByRole('option', {name: 'browser.name'})); @@ -1082,11 +1080,6 @@ describe('SearchQueryBuilder', () => { // onChange should not be called until exiting edit mode expect(mockOnChange).not.toHaveBeenCalled(); - // Should have focus on the operator option - const operatorOption = await screen.findByRole('option', {name: 'contains'}); - expect(operatorOption).toHaveFocus(); - await userEvent.click(operatorOption); - await userEvent.click(await screen.findByRole('option', {name: 'Firefox'})); // New token should have a value, and selecting from dropdown switches operator to "is" @@ -1109,8 +1102,7 @@ describe('SearchQueryBuilder', () => { , - {organization: {features: ['search-query-builder-input-flow-changes']}} + /> ); await userEvent.click( @@ -1142,9 +1134,7 @@ describe('SearchQueryBuilder', () => { }); it('can add a filter after some free text', async () => { - render(, { - organization: {features: ['search-query-builder-input-flow-changes']}, - }); + render(); await userEvent.click(getLastInput()); @@ -1156,11 +1146,6 @@ describe('SearchQueryBuilder', () => { await userEvent.click(screen.getByRole('option', {name: 'browser.name'})); jest.restoreAllMocks(); - // Should have focus on the operator option - const operatorOption = await screen.findByRole('option', {name: 'contains'}); - expect(operatorOption).toHaveFocus(); - await userEvent.click(operatorOption); - // Filter value should have focus expect(await screen.findByLabelText('Edit filter value')).toHaveFocus(); await userEvent.keyboard('foo{enter}'); @@ -1244,9 +1229,7 @@ describe('SearchQueryBuilder', () => { }); it('converts text to filter when typing :', async () => { - render(, { - organization: {features: ['search-query-builder-input-flow-changes']}, - }); + render(); await userEvent.click(getLastInput()); await userEvent.type( @@ -1276,16 +1259,13 @@ describe('SearchQueryBuilder', () => { }); it('selects [Filtered] from dropdown', async () => { - render(, { - organization: {features: ['search-query-builder-input-flow-changes']}, - }); + render(); await userEvent.click(getLastInput()); await userEvent.type( screen.getByRole('combobox', {name: 'Add a search term'}), 'message:' ); - await userEvent.keyboard('{enter}'); await userEvent.click(screen.getByRole('option', {name: '[Filtered]'})); // Selecting from dropdown switches operator from contains to "is" @@ -4197,36 +4177,24 @@ describe('SearchQueryBuilder', () => { }); it('focuses on the filter value when user selects an aggregate filter with no arguments', async () => { - render(, { - organization: {features: ['search-query-builder-input-flow-changes']}, - }); + render(); await userEvent.click(getLastInput()); await userEvent.keyboard('count'); await userEvent.click(screen.getByRole('option', {name: 'count()'})); expect(screen.getByLabelText('count():>100')).toBeInTheDocument(); - const gtOption = screen.getByRole('option', {name: '>'}); - expect(gtOption).toHaveFocus(); - await userEvent.click(gtOption); - - expect(screen.getByLabelText('Edit filter value')).toHaveFocus(); + expect(await screen.findByLabelText('Edit filter value')).toHaveFocus(); }); it('focuses on the filter value when user input looks like an aggregate filter with no arguments', async () => { - render(, { - organization: {features: ['search-query-builder-input-flow-changes']}, - }); + render(); await userEvent.click(getLastInput()); await userEvent.keyboard('count('); expect(screen.getByLabelText('count():>100')).toBeInTheDocument(); - const gtOption = screen.getByRole('option', {name: '>'}); - expect(gtOption).toHaveFocus(); - await userEvent.click(gtOption); - - expect(screen.getByLabelText('Edit filter value')).toHaveFocus(); + expect(await screen.findByLabelText('Edit filter value')).toHaveFocus(); }); it('focuses on the filter value after only argument is specified', async () => { diff --git a/static/app/views/automations/list.spec.tsx b/static/app/views/automations/list.spec.tsx index 8210c982d969..576ed27a9b61 100644 --- a/static/app/views/automations/list.spec.tsx +++ b/static/app/views/automations/list.spec.tsx @@ -25,7 +25,7 @@ import AutomationsList from 'sentry/views/automations/list'; describe('AutomationsList', () => { const organization = OrganizationFixture({ - features: ['workflow-engine-ui', 'search-query-builder-input-flow-changes'], + features: ['workflow-engine-ui'], }); const project = ProjectFixture({id: '1', slug: 'project-1'}); const detector = MetricDetectorFixture({ @@ -180,7 +180,6 @@ describe('AutomationsList', () => { // Click through menus to select action:slack await userEvent.click(screen.getByRole('combobox', {name: 'Add a search term'})); await userEvent.click(await screen.findByRole('option', {name: 'action'})); - await userEvent.click(await screen.findByRole('option', {name: 'is'})); await userEvent.click(await screen.findByRole('option', {name: 'slack'})); await screen.findByText('Slack Automation'); @@ -433,7 +432,6 @@ describe('AutomationsList', () => { // Click through menus to select action:slack await userEvent.click(screen.getByRole('combobox', {name: 'Add a search term'})); await userEvent.click(await screen.findByRole('option', {name: 'action'})); - await userEvent.click(await screen.findByRole('option', {name: 'is'})); await userEvent.click(await screen.findByRole('option', {name: 'slack'})); // Wait for filtered results to load diff --git a/static/app/views/dashboards/widgetBuilder/buildSteps/filterResultsStep/spansSearchBar.spec.tsx b/static/app/views/dashboards/widgetBuilder/buildSteps/filterResultsStep/spansSearchBar.spec.tsx index 0ccb3c9dadf7..976a90cfdd51 100644 --- a/static/app/views/dashboards/widgetBuilder/buildSteps/filterResultsStep/spansSearchBar.spec.tsx +++ b/static/app/views/dashboards/widgetBuilder/buildSteps/filterResultsStep/spansSearchBar.spec.tsx @@ -21,7 +21,7 @@ function renderWithProvider({ }: ComponentProps) { return render( , - {organization: {features: ['search-query-builder-input-flow-changes']}} + {} ); } @@ -135,7 +135,6 @@ describe('SpansSearchBar', () => { name: 'Add a search term', }); await userEvent.type(searchInput, 'span.op:', {delay: null}); - await userEvent.keyboard('{enter}'); await userEvent.keyboard('function', {delay: null}); await userEvent.keyboard('{enter}'); diff --git a/static/app/views/issueDetails/streamline/eventDetailsHeader.spec.tsx b/static/app/views/issueDetails/streamline/eventDetailsHeader.spec.tsx index 058f2d114de9..f2e6743c7b98 100644 --- a/static/app/views/issueDetails/streamline/eventDetailsHeader.spec.tsx +++ b/static/app/views/issueDetails/streamline/eventDetailsHeader.spec.tsx @@ -25,9 +25,7 @@ jest.mock('sentry/views/issueDetails/utils', () => ({ })); describe('EventDetailsHeader', () => { - const organization = OrganizationFixture({ - features: ['search-query-builder-input-flow-changes'], - }); + const organization = OrganizationFixture(); const project = ProjectFixture({ environments: ['production', 'staging', 'development'], }); @@ -139,8 +137,7 @@ describe('EventDetailsHeader', () => { const search = await screen.findByPlaceholderText('Filter events\u2026'); await userEvent.type(search, `${tagKey}:`, {delay: null}); - await userEvent.click(screen.getByRole('option', {name: 'is'})); - await userEvent.keyboard(`${tagValue}{enter}`, {delay: null}); + await userEvent.click(await screen.findByRole('option', {name: tagValue})); await waitFor(() => { expect(mockUseNavigate).toHaveBeenCalledWith( expect.objectContaining(locationQuery), diff --git a/static/app/views/issueDetails/streamline/eventSearch.spec.tsx b/static/app/views/issueDetails/streamline/eventSearch.spec.tsx index 43afcd1e020c..1784fa73f791 100644 --- a/static/app/views/issueDetails/streamline/eventSearch.spec.tsx +++ b/static/app/views/issueDetails/streamline/eventSearch.spec.tsx @@ -11,9 +11,7 @@ import {EventSearch} from 'sentry/views/issueDetails/streamline/eventSearch'; const mockHandleSearch = jest.fn(); describe('EventSearch', () => { - const organization = OrganizationFixture({ - features: ['search-query-builder-input-flow-changes'], - }); + const organization = OrganizationFixture(); const project = ProjectFixture({ environments: ['production', 'staging', 'developement'], }); @@ -53,8 +51,8 @@ describe('EventSearch', () => { const search = screen.getByRole('combobox', {name: 'Add a search term'}); expect(search).toBeInTheDocument(); await userEvent.type(search, `${tagKey}:`); - await userEvent.click(screen.getByRole('option', {name: 'is'})); - await userEvent.keyboard(`${tagValue}{enter}{enter}`); + await userEvent.click(await screen.findByRole('option', {name: tagValue})); + await userEvent.keyboard('{enter}'); await waitFor(() => { expect(mockTagKeyQuery).toHaveBeenCalled(); diff --git a/static/app/views/releases/list/index.spec.tsx b/static/app/views/releases/list/index.spec.tsx index 621f38cf59c2..61d597152be6 100644 --- a/static/app/views/releases/list/index.spec.tsx +++ b/static/app/views/releases/list/index.spec.tsx @@ -21,7 +21,7 @@ import {ReleasesStatusOption} from 'sentry/views/releases/list/releasesStatusOpt describe('ReleasesList', () => { const organization = OrganizationFixture({ - features: ['search-query-builder-input-flow-changes', 'preprod-frontend-routes'], + features: ['preprod-frontend-routes'], }); const projects = [ProjectFixture({features: ['releases']})]; const semverVersionInfo = {