Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,38 @@ 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('-events')).toBe('stack_frequent');
expect(getStackSortMode('-last')).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<StackSummaryModel<SummaryTemplateKeys>>('stack_frequent');
const columnsById = Object.fromEntries(result.map((column) => [column.id, column]));

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).toBe('First');
expect(columnsById.last?.header).toBeTypeOf('function');
});

it('keeps summary message column unsortable', () => {
const result = getColumns<EventSummaryModel<SummaryTemplateKeys>>('summary');
const columnsById = Object.fromEntries(result.map((column) => [column.id, column]));

expect(columnsById.message?.enableSorting).toBe(false);
});

it('offers project and tags as hidden optional columns', () => {
const columns = getColumns<EventSummaryModel<SummaryTemplateKeys>>();
const columnIds = columns.map((column) => column.id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import LogLevel from '../log-level.svelte';
import Summary from '../summary/summary.svelte';
import EventTagsSummaryCell from './event-tags-summary-cell.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';

Expand All @@ -33,9 +34,15 @@ export const defaultStackColumnVisibility: ColumnVisibilityState = {
tags: false
};

export type StackSortMode = Extract<GetEventsMode, 'stack_frequent' | 'stack_recent'>;

export function getColumns<TSummaryModel extends SummaryModel<SummaryTemplateKeys>>(
mode: GetEventsMode = 'summary',
options?: { onTagClick?: (tag: string) => Promise<void> | void; showType?: boolean }
options?: {
onStackSort?: (mode: StackSortMode) => void;
onTagClick?: (tag: string) => Promise<void> | void;
showType?: boolean;
}
): ColumnDef<StockFeatures, TSummaryModel, unknown>[] {
const showType = options?.showType ?? true;
const columns: ColumnDef<StockFeatures, TSummaryModel, unknown>[] = [
Expand Down Expand Up @@ -302,7 +309,12 @@ export function getColumns<TSummaryModel extends SummaryModel<SummaryTemplateKey
value: prop.getValue<number>()
}),
enableSorting: false,
header: 'Events',
header: () =>
renderComponent(StackSortHeader, {
active: mode === 'stack_frequent',
label: 'Events',
onclick: () => options?.onStackSort?.('stack_frequent')
}),
id: 'events',
maxSize: 320,
meta: {
Expand Down Expand Up @@ -334,7 +346,12 @@ export function getColumns<TSummaryModel extends SummaryModel<SummaryTemplateKey
value: prop.getValue<string>()
}),
enableSorting: false,
header: 'Last',
header: () =>
renderComponent(StackSortHeader, {
active: mode === 'stack_recent',
label: 'Last',
onclick: () => options?.onStackSort?.('stack_recent')
}),
id: 'last',
maxSize: 480,
meta: {
Expand All @@ -349,6 +366,18 @@ export function getColumns<TSummaryModel extends SummaryModel<SummaryTemplateKey
return columns;
}

export function getStackSortMode(value: null | string | undefined): StackSortMode | undefined {
if (value === 'stack_frequent' || value === '-events') {
return 'stack_frequent';
}

if (value === 'stack_recent' || value === '-last') {
return 'stack_recent';
}

return undefined;
}

function formatTextColumn(value: unknown): string {
return typeof value === 'string' && value.length > 0 ? value : '—';
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script lang="ts">
import { Button } from '$comp/ui/button';
import ArrowDown from '@lucide/svelte/icons/arrow-down';

interface Props {
active: boolean;
label: string;
onclick: () => void;
}

let { active, label, onclick }: Props = $props();
</script>

<Button aria-label={`Sort by ${label} descending`} aria-pressed={active} class="h-8" {onclick} variant="ghost">
{label}
{#if active}
<ArrowDown aria-hidden="true" />
{/if}
</Button>
Original file line number Diff line number Diff line change
@@ -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.getAttribute('aria-pressed')).toBe('true');

await fireEvent.click(button);

expect(onclick).toHaveBeenCalledOnce();
});

it('does not mark inactive sort modes as selected', () => {
render(StackSortHeader, { active: false, label: 'Last', onclick: vi.fn() });

expect(screen.getByRole('button', { name: 'Sort by Last descending' }).getAttribute('aria-pressed')).toBe('false');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -115,6 +115,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,
Expand Down Expand Up @@ -215,6 +216,24 @@
.filter((item) => item);
}

function getPersistedStackSort(): string | undefined {
if (queryParams.sort != null) {
return getStackSortMode(queryParams.sort) ?? 'stack_frequent';
}

return savedViewsState.activeSavedView?.sort ?? undefined;
}

function getEffectiveStackSort(): StackSortMode {
return getStackSortMode(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 = createQueryParameters({
defaults: DEFAULT_PARAMS,
Expand All @@ -229,6 +248,7 @@
project: 'string',
reference: 'string',
session: 'string',
sort: 'string',
stack: 'string',
status: 'string',
tag: 'string',
Expand All @@ -255,6 +275,7 @@
getFilterDefinitions: () => serializeFilters(filters ?? []),
getShowChart: () => showChart,
getShowStats: () => showStats,
getSort: getPersistedStackSort,
getTime: getQueryTime,
queryParams,
setColumnOrder: (v) => table.setColumnOrder(v),
Expand Down Expand Up @@ -626,7 +647,9 @@
set limit(value) {
setPageSize(value);
},
mode: 'stack_frequent',
get mode() {
return getEffectiveStackSort();
},
offset: DEFAULT_OFFSET,
get page() {
return queryParams.page ?? undefined;
Expand Down Expand Up @@ -667,6 +690,7 @@
columnPersistenceKey: 'stacks-column-visibility',
get columns() {
return getColumns<EventSummaryModel<SummaryTemplateKeys>>(eventsQueryParameters.mode, {
onStackSort: setStackSort,
onTagClick: (tag) => onFilterChanged(new TagFilter([tag])),
showType: !hasSingleTypeFilter(eventsQueryParameters.filter)
});
Expand Down Expand Up @@ -864,6 +888,7 @@
{showStats}
setShowChart={(v) => (showChart = v)}
setShowStats={(v) => (showStats = v)}
sort={getStackSortMode(getPersistedStackSort())}
{table}
time={getQueryTime() ?? undefined}
view={VIEW}
Expand Down
Loading