From 683a36ecf003805a5f5168bc86d8a0829ab8712a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Mon, 18 May 2026 16:36:43 -0400 Subject: [PATCH 1/3] fix(ourlogs): Reset column sort to default on third click Column sort previously cycled between desc and asc with no way to return to the default sort. Add a third state: clicking an already-asc column resets the infinite table to timestamp-desc (the default) and the aggregate table to its first-field-desc default. Fixes LOGS-808 Co-Authored-By: Claude Sonnet 4 --- .../logs/tables/logsAggregateTable.tsx | 17 ++++--- .../logs/tables/logsInfiniteTable.spec.tsx | 47 +++++++++++++++++++ .../explore/logs/tables/logsInfiniteTable.tsx | 13 ++++- 3 files changed, 69 insertions(+), 8 deletions(-) diff --git a/static/app/views/explore/logs/tables/logsAggregateTable.tsx b/static/app/views/explore/logs/tables/logsAggregateTable.tsx index 6919823193eae0..3ee53de266794c 100644 --- a/static/app/views/explore/logs/tables/logsAggregateTable.tsx +++ b/static/app/views/explore/logs/tables/logsAggregateTable.tsx @@ -127,13 +127,18 @@ export function LogsAggregateTable({ canSort direction={direction} generateSortLink={() => { + const nextSort = (() => { + switch (direction) { + case 'asc': + return {field: allFields[0]!, kind: 'desc' as const}; + case 'desc': + return {field: column.key, kind: 'asc' as const}; + default: + return {field: column.key, kind: 'desc' as const}; + } + })(); return getTargetWithReadableQueryParams(location, { - aggregateSortBys: [ - { - field: column.key, - kind: direction === 'desc' ? 'asc' : 'desc', - }, - ], + aggregateSortBys: [nextSort], }); }} title={title} diff --git a/static/app/views/explore/logs/tables/logsInfiniteTable.spec.tsx b/static/app/views/explore/logs/tables/logsInfiniteTable.spec.tsx index dafe7e7e871b1c..5485436a725d6b 100644 --- a/static/app/views/explore/logs/tables/logsInfiniteTable.spec.tsx +++ b/static/app/views/explore/logs/tables/logsInfiniteTable.spec.tsx @@ -472,4 +472,51 @@ describe('LogsInfiniteTable', () => { await screen.findByText('abc123de'); await screen.findByText('abc123ee'); }); + + it('cycles column sort: unsorted → desc → asc → reset to default timestamp desc', async () => { + // Start with severity sorted ascending (second click has already happened) + mockUseLocation.mockReturnValue( + LocationFixture({ + pathname: `/organizations/${organization.slug}/explore/logs/`, + query: { + [LOGS_FIELDS_KEY]: visibleColumnFields, + [LOGS_SORT_BYS_KEY]: 'severity', + }, + }) + ); + + const {router} = render( + + + + + + + , + { + initialRouterConfig: { + location: { + pathname: `/organizations/${organization.slug}/explore/logs/`, + query: { + [LOGS_FIELDS_KEY]: visibleColumnFields, + [LOGS_SORT_BYS_KEY]: 'severity', + }, + }, + }, + organization, + } + ); + + // Wait for table headers to be rendered (empty while pending) + const severityHeader = await screen.findByText('Severity'); + + // Third click (asc → reset): should navigate to default timestamp desc sort + await userEvent.click(severityHeader); + expect(router.location.query[LOGS_SORT_BYS_KEY]).toBe('-timestamp'); + }); }); diff --git a/static/app/views/explore/logs/tables/logsInfiniteTable.tsx b/static/app/views/explore/logs/tables/logsInfiniteTable.tsx index 2a01f2662e5814..104b2f46992223 100644 --- a/static/app/views/explore/logs/tables/logsInfiniteTable.tsx +++ b/static/app/views/explore/logs/tables/logsInfiniteTable.tsx @@ -41,6 +41,7 @@ import { } from 'sentry/views/explore/components/table'; import {useLogsAutoRefreshEnabled} from 'sentry/views/explore/contexts/logs/logsAutoRefreshContext'; import {useLogsPageDataQueryResult} from 'sentry/views/explore/contexts/logs/logsPageData'; +import {logsTimestampDescendingSortBy} from 'sentry/views/explore/contexts/logs/sortBys'; import { MINIMUM_INFINITE_SCROLL_FETCH_COOLDOWN_MS, QUANTIZE_MINUTES, @@ -658,8 +659,16 @@ function LogsTableHeader({ isFrozen ? undefined : () => { - const kind = direction === 'desc' ? 'asc' : 'desc'; - setSortBys([{field, kind}]); + switch (direction) { + case 'asc': + setSortBys([logsTimestampDescendingSortBy]); + break; + case 'desc': + setSortBys([{field, kind: 'asc'}]); + break; + default: + setSortBys([{field, kind: 'desc'}]); + } } } isFrozen={isFrozen} From f1204a64737130a76f3724cbb345514ce47d10df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Mon, 18 May 2026 16:57:11 -0400 Subject: [PATCH 2/3] fix: prefer the first visualizes yAxis, if possible --- static/app/views/explore/logs/tables/logsAggregateTable.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/static/app/views/explore/logs/tables/logsAggregateTable.tsx b/static/app/views/explore/logs/tables/logsAggregateTable.tsx index 3ee53de266794c..75b80c9b52f797 100644 --- a/static/app/views/explore/logs/tables/logsAggregateTable.tsx +++ b/static/app/views/explore/logs/tables/logsAggregateTable.tsx @@ -130,7 +130,10 @@ export function LogsAggregateTable({ const nextSort = (() => { switch (direction) { case 'asc': - return {field: allFields[0]!, kind: 'desc' as const}; + return { + field: visualizes[0]?.yAxis ?? allFields[0]!, + kind: 'desc' as const, + }; case 'desc': return {field: column.key, kind: 'asc' as const}; default: From ed92f44c9ebdf5c03c92731af717a0043689eb3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Tue, 19 May 2026 08:29:07 -0400 Subject: [PATCH 3/3] Unified into renderWithProviders() --- .../logs/tables/logsInfiniteTable.spec.tsx | 24 +++++++------------ tests/js/sentry-test/reactTestingLibrary.tsx | 2 +- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/static/app/views/explore/logs/tables/logsInfiniteTable.spec.tsx b/static/app/views/explore/logs/tables/logsInfiniteTable.spec.tsx index 5485436a725d6b..cd451ac2de2af5 100644 --- a/static/app/views/explore/logs/tables/logsInfiniteTable.spec.tsx +++ b/static/app/views/explore/logs/tables/logsInfiniteTable.spec.tsx @@ -11,6 +11,7 @@ import { userEvent, waitFor, within, + type RenderOptions, } from 'sentry-test/reactTestingLibrary'; import {PageFiltersStore} from 'sentry/components/pageFilters/store'; @@ -194,8 +195,8 @@ describe('LogsInfiniteTable', () => { }); }); - const renderWithProviders = (children: React.ReactNode) => { - return render( + function Wrapper({children}: {children: React.ReactNode}) { + return ( { ); + } + + const renderWithProviders = (children: React.ReactElement, options?: RenderOptions) => { + return render(children, {additionalWrapper: Wrapper, ...options}); }; it('should render the table component', async () => { @@ -485,19 +490,8 @@ describe('LogsInfiniteTable', () => { }) ); - const {router} = render( - - - - - - - , + const {router} = renderWithProviders( + , { initialRouterConfig: { location: { diff --git a/tests/js/sentry-test/reactTestingLibrary.tsx b/tests/js/sentry-test/reactTestingLibrary.tsx index 1934f11baf088d..b83cc8adf5490a 100644 --- a/tests/js/sentry-test/reactTestingLibrary.tsx +++ b/tests/js/sentry-test/reactTestingLibrary.tsx @@ -91,7 +91,7 @@ export interface RouterConfig { routes?: string[]; } -interface RenderOptions extends rtl.RenderOptions, ProviderOptions { +export interface RenderOptions extends rtl.RenderOptions, ProviderOptions { initialRouterConfig?: RouterConfig; outletContext?: Record; }