From 640a9cfb3e13c45426dd85370a17e9746edb2a54 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 31 May 2026 23:57:34 +0000 Subject: [PATCH 1/7] Initial plan From d05b347aa564aae53aec5e64904cec8a5dd66e8e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 00:25:36 +0000 Subject: [PATCH 2/7] Enable stack and session duration column sorting Co-authored-by: niemyjski <1020579+niemyjski@users.noreply.github.com> --- .../components/table/options.svelte.test.ts | 23 +++++++++++++++++++ .../events/components/table/options.svelte.ts | 3 --- .../components/session-table-columns.test.ts | 12 ++++++++++ .../components/session-table-columns.ts | 1 - 4 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.test.ts create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/session-table-columns.test.ts diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.test.ts new file mode 100644 index 0000000000..4012c1e266 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.test.ts @@ -0,0 +1,23 @@ +import type { EventSummaryModel, StackSummaryModel, SummaryTemplateKeys } from '$features/events/components/summary'; + +import { describe, expect, it } from 'vitest'; + +import { getColumns } from './options.svelte'; + +describe('getColumns', () => { + it('keeps stack events, first, and last columns sortable', () => { + const result = getColumns>('stack_frequent'); + const columnsById = Object.fromEntries(result.map((column) => [column.id, column])); + + expect(columnsById.events?.enableSorting).toBeUndefined(); + expect(columnsById.first?.enableSorting).toBeUndefined(); + expect(columnsById.last?.enableSorting).toBeUndefined(); + }); + + it('keeps summary message column unsortable', () => { + const result = getColumns>('summary'); + const columnsById = Object.fromEntries(result.map((column) => [column.id, column])); + + expect(columnsById.message?.enableSorting).toBe(false); + }); +}); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts index ef0726d78d..ba0641f5c8 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts @@ -154,7 +154,6 @@ export function getColumns>('total'), cell: (prop) => renderComponent(NumberFormatter, { value: prop.getValue() }), - enableSorting: false, header: 'Events', id: 'events', meta: { @@ -164,7 +163,6 @@ export function getColumns>('first_occurrence'), cell: (prop) => renderComponent(TimeAgo, { value: prop.getValue() }), - enableSorting: false, header: 'First', id: 'first', meta: { @@ -174,7 +172,6 @@ export function getColumns>('last_occurrence'), cell: (prop) => renderComponent(TimeAgo, { value: prop.getValue() }), - enableSorting: false, header: 'Last', id: 'last', meta: { diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/session-table-columns.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/session-table-columns.test.ts new file mode 100644 index 0000000000..a30e4dbbea --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/session-table-columns.test.ts @@ -0,0 +1,12 @@ +import { describe, expect, it } from 'vitest'; + +import { getSessionColumns } from './session-table-columns'; + +describe('getSessionColumns', () => { + it('keeps duration column sortable', () => { + const result = getSessionColumns(); + const columnsById = Object.fromEntries(result.map((column) => [column.id, column])); + + expect(columnsById.duration?.enableSorting).toBeUndefined(); + }); +}); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/session-table-columns.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/session-table-columns.ts index cc3e1cd8d9..a5129eaf1a 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/session-table-columns.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/session-table-columns.ts @@ -40,7 +40,6 @@ export function getSessionColumns(): ColumnDef renderComponent(SessionDurationCell, { summary: prop.row.original }), - enableSorting: false, header: 'Duration', id: 'duration', meta: { From b6d3929e96d6964398d428348cde758bbbab1b85 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Fri, 10 Jul 2026 14:15:35 -0500 Subject: [PATCH 3/7] Fix stack sorting UI contract --- .../components/table/options.svelte.test.ts | 11 ++++--- .../events/components/table/options.svelte.ts | 29 ++++++++++++++++--- .../components/table/stack-sort-header.svelte | 19 ++++++++++++ .../table/stack-sort-header.svelte.test.ts | 24 +++++++++++++++ .../components/session-table-columns.test.ts | 12 -------- .../components/session-table-columns.ts | 1 + .../src/routes/(app)/stack/+page.svelte | 4 +++ 7 files changed, 80 insertions(+), 20 deletions(-) create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/stack-sort-header.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/stack-sort-header.svelte.test.ts delete mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/session-table-columns.test.ts diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.test.ts index 4012c1e266..ba27356aa4 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.test.ts @@ -5,13 +5,16 @@ import { describe, expect, it } from 'vitest'; import { getColumns } from './options.svelte'; describe('getColumns', () => { - it('keeps stack events, first, and last columns sortable', () => { + it('uses dedicated stack-mode controls instead of API sort parameters', () => { const result = getColumns>('stack_frequent'); const columnsById = Object.fromEntries(result.map((column) => [column.id, column])); - expect(columnsById.events?.enableSorting).toBeUndefined(); - expect(columnsById.first?.enableSorting).toBeUndefined(); - expect(columnsById.last?.enableSorting).toBeUndefined(); + expect(columnsById.events?.enableSorting).toBe(false); + expect(columnsById.first?.enableSorting).toBe(false); + expect(columnsById.last?.enableSorting).toBe(false); + expect(columnsById.events?.header).toBeTypeOf('function'); + expect(columnsById.first?.header).toBeTypeOf('function'); + expect(columnsById.last?.header).toBeTypeOf('function'); }); it('keeps summary message column unsortable', () => { diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts index 87523fdf11..ef16cebf1b 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts @@ -12,6 +12,7 @@ import type { EventSummaryModel, StackSummaryModel, SummaryModel, SummaryTemplat import LogLevel from '../log-level.svelte'; import Summary from '../summary/summary.svelte'; import EventsUserIdentitySummaryCell from './events-user-identity-summary-cell.svelte'; +import StackSortHeader from './stack-sort-header.svelte'; import StackStatusCell from './stack-status-cell.svelte'; import StackUsersSummaryCell from './stack-users-summary-cell.svelte'; @@ -25,9 +26,11 @@ export const defaultEventColumnVisibility: ColumnVisibilityState = { version: false }; +export type StackSortMode = Extract; + export function getColumns>( mode: GetEventsMode = 'summary', - options?: { showType?: boolean } + options?: { onStackSort?: (mode: StackSortMode) => void; showType?: boolean } ): ColumnDef[] { const showType = options?.showType ?? true; const columns: ColumnDef[] = [ @@ -175,7 +178,13 @@ export function getColumns>('total'), cell: (prop) => renderComponent(NumberFormatter, { value: prop.getValue() }), - header: 'Events', + enableSorting: false, + header: () => + renderComponent(StackSortHeader, { + active: mode === 'stack_frequent', + label: 'Events', + onclick: () => options?.onStackSort?.('stack_frequent') + }), id: 'events', meta: { class: 'w-24' @@ -184,7 +193,13 @@ export function getColumns>('first_occurrence'), cell: (prop) => renderComponent(TimeAgo, { value: prop.getValue() }), - header: 'First', + enableSorting: false, + header: () => + renderComponent(StackSortHeader, { + active: mode === 'stack_new', + label: 'First', + onclick: () => options?.onStackSort?.('stack_new') + }), id: 'first', meta: { class: 'w-36' @@ -193,7 +208,13 @@ export function getColumns>('last_occurrence'), cell: (prop) => renderComponent(TimeAgo, { value: prop.getValue() }), - header: 'Last', + enableSorting: false, + header: () => + renderComponent(StackSortHeader, { + active: mode === 'stack_recent', + label: 'Last', + onclick: () => options?.onStackSort?.('stack_recent') + }), id: 'last', meta: { class: 'w-36' diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/stack-sort-header.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/stack-sort-header.svelte new file mode 100644 index 0000000000..0c5413885b --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/stack-sort-header.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/stack-sort-header.svelte.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/stack-sort-header.svelte.test.ts new file mode 100644 index 0000000000..56ab03aaa0 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/stack-sort-header.svelte.test.ts @@ -0,0 +1,24 @@ +import { fireEvent, render, screen } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import StackSortHeader from './stack-sort-header.svelte'; + +describe('StackSortHeader', () => { + it('exposes the active descending sort and handles selection', async () => { + const onclick = vi.fn(); + render(StackSortHeader, { active: true, label: 'Events', onclick }); + + const button = screen.getByRole('button', { name: 'Sort by Events descending' }); + expect(button).toHaveAttribute('aria-pressed', 'true'); + + await fireEvent.click(button); + + expect(onclick).toHaveBeenCalledOnce(); + }); + + it('does not mark inactive sort modes as selected', () => { + render(StackSortHeader, { active: false, label: 'First', onclick: vi.fn() }); + + expect(screen.getByRole('button', { name: 'Sort by First descending' })).toHaveAttribute('aria-pressed', 'false'); + }); +}); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/session-table-columns.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/session-table-columns.test.ts deleted file mode 100644 index a30e4dbbea..0000000000 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/session-table-columns.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { describe, expect, it } from 'vitest'; - -import { getSessionColumns } from './session-table-columns'; - -describe('getSessionColumns', () => { - it('keeps duration column sortable', () => { - const result = getSessionColumns(); - const columnsById = Object.fromEntries(result.map((column) => [column.id, column])); - - expect(columnsById.duration?.enableSorting).toBeUndefined(); - }); -}); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/session-table-columns.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/session-table-columns.ts index a5129eaf1a..cc3e1cd8d9 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/session-table-columns.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/session-table-columns.ts @@ -40,6 +40,7 @@ export function getSessionColumns(): ColumnDef renderComponent(SessionDurationCell, { summary: prop.row.original }), + enableSorting: false, header: 'Duration', id: 'duration', meta: { diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/stack/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/stack/+page.svelte index 4900d209a5..851a09bc46 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/stack/+page.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/stack/+page.svelte @@ -619,6 +619,10 @@ columnPersistenceKey: 'stacks-column-visibility', get columns() { return getColumns>(eventsQueryParameters.mode, { + onStackSort: (mode) => { + eventsQueryParameters.mode = mode; + table.setPageIndex(0); + }, showType: !hasSingleTypeFilter(eventsQueryParameters.filter) }); }, From 6c7a4ebe637352feba0d7b91d60ca41d33ed7ee8 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Fri, 10 Jul 2026 14:18:56 -0500 Subject: [PATCH 4/7] Fix stack sort test portability --- .../events/components/table/stack-sort-header.svelte.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/stack-sort-header.svelte.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/stack-sort-header.svelte.test.ts index 56ab03aaa0..0e29592915 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/stack-sort-header.svelte.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/stack-sort-header.svelte.test.ts @@ -9,7 +9,7 @@ describe('StackSortHeader', () => { render(StackSortHeader, { active: true, label: 'Events', onclick }); const button = screen.getByRole('button', { name: 'Sort by Events descending' }); - expect(button).toHaveAttribute('aria-pressed', 'true'); + expect(button.getAttribute('aria-pressed')).toBe('true'); await fireEvent.click(button); @@ -19,6 +19,6 @@ describe('StackSortHeader', () => { it('does not mark inactive sort modes as selected', () => { render(StackSortHeader, { active: false, label: 'First', onclick: vi.fn() }); - expect(screen.getByRole('button', { name: 'Sort by First descending' })).toHaveAttribute('aria-pressed', 'false'); + expect(screen.getByRole('button', { name: 'Sort by First descending' }).getAttribute('aria-pressed')).toBe('false'); }); }); From 6c70da1a9a8e90faf085f91093ae3c496edaf30c Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Wed, 15 Jul 2026 21:50:10 -0500 Subject: [PATCH 5/7] Remove unsupported first occurrence stack sort --- .../events/components/table/options.svelte.test.ts | 2 +- .../features/events/components/table/options.svelte.ts | 9 ++------- .../components/table/stack-sort-header.svelte.test.ts | 4 ++-- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.test.ts index ba27356aa4..c85c34104e 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.test.ts @@ -13,7 +13,7 @@ describe('getColumns', () => { expect(columnsById.first?.enableSorting).toBe(false); expect(columnsById.last?.enableSorting).toBe(false); expect(columnsById.events?.header).toBeTypeOf('function'); - expect(columnsById.first?.header).toBeTypeOf('function'); + expect(columnsById.first?.header).toBe('First'); expect(columnsById.last?.header).toBeTypeOf('function'); }); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts index ef16cebf1b..7a078c4e61 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts @@ -26,7 +26,7 @@ export const defaultEventColumnVisibility: ColumnVisibilityState = { version: false }; -export type StackSortMode = Extract; +export type StackSortMode = Extract; export function getColumns>( mode: GetEventsMode = 'summary', @@ -194,12 +194,7 @@ export function getColumns>('first_occurrence'), cell: (prop) => renderComponent(TimeAgo, { value: prop.getValue() }), enableSorting: false, - header: () => - renderComponent(StackSortHeader, { - active: mode === 'stack_new', - label: 'First', - onclick: () => options?.onStackSort?.('stack_new') - }), + header: 'First', id: 'first', meta: { class: 'w-36' diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/stack-sort-header.svelte.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/stack-sort-header.svelte.test.ts index 0e29592915..d86bd454e7 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/stack-sort-header.svelte.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/stack-sort-header.svelte.test.ts @@ -17,8 +17,8 @@ describe('StackSortHeader', () => { }); it('does not mark inactive sort modes as selected', () => { - render(StackSortHeader, { active: false, label: 'First', onclick: vi.fn() }); + render(StackSortHeader, { active: false, label: 'Last', onclick: vi.fn() }); - expect(screen.getByRole('button', { name: 'Sort by First descending' }).getAttribute('aria-pressed')).toBe('false'); + expect(screen.getByRole('button', { name: 'Sort by Last descending' }).getAttribute('aria-pressed')).toBe('false'); }); }); From 97ebf34593a408c6ce01ebd676c6d4d896c8d3e7 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Wed, 12 Aug 2026 12:36:20 -0500 Subject: [PATCH 6/7] Persist stack sort selection --- .../components/table/options.svelte.test.ts | 10 +++++- .../events/components/table/options.svelte.ts | 4 +++ .../src/routes/(app)/stack/+page.svelte | 33 +++++++++++++++---- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.test.ts index e50ce76fd4..462b71cd74 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.test.ts @@ -2,9 +2,17 @@ import { describe, expect, it } from 'vitest'; import type { EventSummaryModel, StackSummaryModel, SummaryTemplateKeys } from '../summary'; -import { defaultEventColumnVisibility, defaultStackColumnVisibility, getColumns } from './options.svelte'; +import { defaultEventColumnVisibility, defaultStackColumnVisibility, getColumns, getStackSortMode } from './options.svelte'; describe('event table columns', () => { + it('accepts only supported stack sort modes', () => { + expect(getStackSortMode('stack_frequent')).toBe('stack_frequent'); + expect(getStackSortMode('stack_recent')).toBe('stack_recent'); + expect(getStackSortMode('stack_new')).toBeUndefined(); + expect(getStackSortMode('-last_occurrence')).toBeUndefined(); + expect(getStackSortMode(undefined)).toBeUndefined(); + }); + it('uses dedicated stack-mode controls instead of API sort parameters', () => { const result = getColumns>('stack_frequent'); const columnsById = Object.fromEntries(result.map((column) => [column.id, column])); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts index be56b6c26a..6a966657ae 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts @@ -319,6 +319,10 @@ export function getColumns 0 ? value : '—'; } diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/stack/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/stack/+page.svelte index 5f161574fc..623bb99644 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/stack/+page.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/stack/+page.svelte @@ -44,7 +44,7 @@ } from '$features/events/components/filters/helpers.svelte'; import OrganizationDefaultsFacetedFilterBuilder from '$features/events/components/filters/organization-defaults-faceted-filter-builder.svelte'; import EventsDataTable from '$features/events/components/table/events-data-table.svelte'; - import { defaultStackColumnVisibility, getColumns } from '$features/events/components/table/options.svelte'; + import { defaultStackColumnVisibility, getColumns, getStackSortMode, type StackSortMode } from '$features/events/components/table/options.svelte'; import { filterUsesPremiumFeatures } from '$features/events/premium-filter'; import { organization } from '$features/organizations/context.svelte'; import { premiumPage } from '$features/organizations/premium-page.svelte'; @@ -113,6 +113,7 @@ project: undefined as string | undefined, reference: undefined as string | undefined, session: undefined as string | undefined, + sort: undefined as string | undefined, stack: undefined as string | undefined, status: undefined as string | undefined, tag: undefined as string | undefined, @@ -213,6 +214,24 @@ .filter((item) => item); } + function getPersistedStackSort(): StackSortMode | undefined { + if (queryParams.sort != null) { + return getStackSortMode(queryParams.sort) ?? 'stack_frequent'; + } + + return getStackSortMode(savedViewsState.activeSavedView?.sort); + } + + function getEffectiveStackSort(): StackSortMode { + return getPersistedStackSort() ?? 'stack_frequent'; + } + + function setStackSort(mode: StackSortMode): void { + const savedViewSort = getStackSortMode(savedViewsState.activeSavedView?.sort) ?? 'stack_frequent'; + queryParams.sort = mode === savedViewSort ? null : mode; + table.setPageIndex(0); + } + updateFilterCache(filterCacheKey(DEFAULT_FILTER), DEFAULT_FILTERS); const queryParams = queryParamsState({ default: DEFAULT_PARAMS, @@ -227,6 +246,7 @@ project: 'string', reference: 'string', session: 'string', + sort: 'string', stack: 'string', status: 'string', tag: 'string', @@ -252,6 +272,7 @@ getFilterDefinitions: () => serializeFilters(filters ?? []), getShowChart: () => showChart, getShowStats: () => showStats, + getSort: getPersistedStackSort, getTime: getQueryTime, queryParams, setColumnOrder: (v) => table.setColumnOrder(v), @@ -616,7 +637,9 @@ set limit(value) { setPageSize(value); }, - mode: 'stack_frequent', + get mode() { + return getEffectiveStackSort(); + }, offset: DEFAULT_OFFSET, get page() { return queryParams.page ?? undefined; @@ -657,10 +680,7 @@ columnPersistenceKey: 'stacks-column-visibility', get columns() { return getColumns>(eventsQueryParameters.mode, { - onStackSort: (mode) => { - eventsQueryParameters.mode = mode; - table.setPageIndex(0); - }, + onStackSort: setStackSort, onTagClick: (tag) => onFilterChanged(new TagFilter([tag])), showType: !hasSingleTypeFilter(eventsQueryParameters.filter) }); @@ -863,6 +883,7 @@ {showStats} setShowChart={(v) => (showChart = v)} setShowStats={(v) => (showStats = v)} + sort={getPersistedStackSort()} {table} time={getQueryTime() ?? undefined} view={VIEW} From 2e9eef6e5a11c6f2d104e8b4a51f1c7ae6f7c040 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Wed, 12 Aug 2026 12:56:04 -0500 Subject: [PATCH 7/7] Preserve legacy stack view sorting --- .../events/components/table/options.svelte.test.ts | 2 ++ .../features/events/components/table/options.svelte.ts | 10 +++++++++- .../ClientApp/src/routes/(app)/stack/+page.svelte | 8 ++++---- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.test.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.test.ts index 462b71cd74..d8a669549a 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.test.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.test.ts @@ -8,6 +8,8 @@ describe('event table columns', () => { it('accepts only supported stack sort modes', () => { expect(getStackSortMode('stack_frequent')).toBe('stack_frequent'); expect(getStackSortMode('stack_recent')).toBe('stack_recent'); + expect(getStackSortMode('-events')).toBe('stack_frequent'); + expect(getStackSortMode('-last')).toBe('stack_recent'); expect(getStackSortMode('stack_new')).toBeUndefined(); expect(getStackSortMode('-last_occurrence')).toBeUndefined(); expect(getStackSortMode(undefined)).toBeUndefined(); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts index 6a966657ae..cdcfb5f7a4 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts @@ -320,7 +320,15 @@ export function getColumns item); } - function getPersistedStackSort(): StackSortMode | undefined { + function getPersistedStackSort(): string | undefined { if (queryParams.sort != null) { return getStackSortMode(queryParams.sort) ?? 'stack_frequent'; } - return getStackSortMode(savedViewsState.activeSavedView?.sort); + return savedViewsState.activeSavedView?.sort ?? undefined; } function getEffectiveStackSort(): StackSortMode { - return getPersistedStackSort() ?? 'stack_frequent'; + return getStackSortMode(getPersistedStackSort()) ?? 'stack_frequent'; } function setStackSort(mode: StackSortMode): void { @@ -883,7 +883,7 @@ {showStats} setShowChart={(v) => (showChart = v)} setShowStats={(v) => (showStats = v)} - sort={getPersistedStackSort()} + sort={getStackSortMode(getPersistedStackSort())} {table} time={getQueryTime() ?? undefined} view={VIEW}