Skip to content
Draft
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
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';

import { deserializeFilters, quoteIfSpecialCharacters, serializeFilters, toFilter } from './helpers.svelte';
import { applyTimeFilter, deserializeFilters, quoteIfSpecialCharacters, serializeFilters, toFilter, toFilterFromSerializedFilters } from './helpers.svelte';
import {
BooleanFilter,
DateFilter,
Expand Down Expand Up @@ -63,6 +63,19 @@ describe('helpers.svelte', () => {
});
});

describe('applyTimeFilter', () => {
it('removes an existing date filter when time is explicitly empty', () => {
// Arrange
const filters = [new DateFilter('date', '[now-7d TO now]'), new StringFilter('stack', 'stack-1')];

// Act
const result = applyTimeFilter(filters, null);

// Assert
expect(result.map((filter) => filter.key)).toEqual(['string-stack']);
});
});

describe('serializeFilters', () => {
it('serializes an empty array', () => {
expect(serializeFilters([])).toBe('[]');
Expand Down Expand Up @@ -186,6 +199,30 @@ describe('serializeFilters', () => {
});
});

describe('toFilterFromSerializedFilters', () => {
it('derives the filter expression from serialized filter controls', () => {
// Arrange
const serialized = serializeFilters([new DateFilter('date', '[now-7d TO now]'), new StringFilter('stack', 'stack-1')]);

// Act
const result = toFilterFromSerializedFilters(serialized);

// Assert
expect(result).toBe('stack:"stack-1"');
});

it('returns null when serialized filters contain only date controls', () => {
// Arrange
const serialized = serializeFilters([new DateFilter('date', '[now-7d TO now]')]);

// Act
const result = toFilterFromSerializedFilters(serialized);

// Assert
expect(result).toBeNull();
});
});

describe('deserializeFilters', () => {
it('deserializes an empty array', () => {
expect(deserializeFilters('[]')).toEqual([]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export function applyTimeFilter(filters: IFilter[], time: null | string): IFilte
if (time) {
const dateFilter = filters[dateFilterIndex] as DateFilter;
dateFilter.value = time;
} else {
filters.splice(dateFilterIndex, 1);
}
} else if (time) {
filters.push(new DateFilter('date', time));
Expand Down Expand Up @@ -222,6 +224,11 @@ export function toFilter(filters: IFilter[]): string {
.trim();
}

export function toFilterFromSerializedFilters(json: string): null | string {
const filter = toFilter(deserializeFilters(json).filter((f) => f.type !== 'date'));
return filter || null;
}

export function updateFilterCache(cacheKey: string, filters: IFilter[]) {
// Prevent unbounded growth
if (filterCache.size >= 100) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { LogLevel } from '$features/events/models/event-data';
import type { StackStatus } from '$features/stacks/models';

import { resolve } from '$app/paths';

export interface EventErrorSummaryData {
Message?: string;
Method?: string;
Expand Down Expand Up @@ -146,3 +148,12 @@ export type SummaryTemplateKeys =
| 'stack-session-summary'
| 'stack-simple-summary'
| 'stack-summary';

export function buildStackEventsHref(stackId: string): string {
const queryParams = new URLSearchParams({
stack: stackId,
time: 'all'
});

return `${resolve('/(app)/event')}?${queryParams}`;
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
<script lang="ts">
import type { StackStatus } from '$features/stacks/models';

import { resolve } from '$app/paths';
import { A, Muted } from '$comp/typography';
import StackStatusBadge from '$features/stacks/components/stack-status-badge.svelte';
import ChevronRight from '@lucide/svelte/icons/chevron-right';

import type { StackSummaryModel, SummaryModel, SummaryTemplateKeys } from './index';
import { buildStackEventsHref, type StackSummaryModel, type SummaryModel, type SummaryTemplateKeys } from './index';

interface Props {
badgeStatus: StackStatus;
Expand Down Expand Up @@ -37,7 +36,7 @@
</strong>
{/if}

<A class="inline" href={`${resolve('/(app)/event')}?filter=stack:${source.id}`}>
<A class="inline" href={buildStackEventsHref(source.id)}>
{source.title}
</A>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
<script lang="ts">
import type { StackStatus } from '$features/stacks/models';

import { resolve } from '$app/paths';
import { A } from '$comp/typography';
import StackStatusBadge from '$features/stacks/components/stack-status-badge.svelte';

import type { StackSummaryModel, SummaryModel, SummaryTemplateKeys } from './index';
import { buildStackEventsHref, type StackSummaryModel, type SummaryModel, type SummaryTemplateKeys } from './index';

interface Props {
badgeStatus: StackStatus;
Expand All @@ -27,7 +26,7 @@
<strong>Feature</strong>:&nbsp;
{/if}

<A class="inline" href={`${resolve('/(app)/event')}?filter=stack:${source.id}`}>
<A class="inline" href={buildStackEventsHref(source.id)}>
{source.title}
</A>
</div>
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
<script lang="ts">
import type { StackStatus } from '$features/stacks/models';

import { resolve } from '$app/paths';
import { A } from '$comp/typography';
import StackStatusBadge from '$features/stacks/components/stack-status-badge.svelte';

import type { StackSummaryModel, SummaryModel, SummaryTemplateKeys } from './index';
import { buildStackEventsHref, type StackSummaryModel, type SummaryModel, type SummaryTemplateKeys } from './index';

interface Props {
badgeStatus: StackStatus;
Expand All @@ -27,7 +26,7 @@
<strong>Log source:</strong>&nbsp;
{/if}

<A class="inline" href={`${resolve('/(app)/event')}?filter=stack:${source.id}`}>
<A class="inline" href={buildStackEventsHref(source.id)}>
{#if source.data?.Source}
<abbr title={source.data.Source}>{source.data.SourceShortName}</abbr>
{:else}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
<script lang="ts">
import type { StackStatus } from '$features/stacks/models';

import { resolve } from '$app/paths';
import { A } from '$comp/typography';
import StackStatusBadge from '$features/stacks/components/stack-status-badge.svelte';

import type { StackSummaryModel, SummaryModel, SummaryTemplateKeys } from './index';
import { buildStackEventsHref, type StackSummaryModel, type SummaryModel, type SummaryTemplateKeys } from './index';

interface Props {
badgeStatus: StackStatus;
Expand All @@ -26,7 +25,7 @@
{#if showType}
<strong>404</strong>:&nbsp;
{/if}
<A class="inline" href={`${resolve('/(app)/event')}?filter=stack:${source.id}`}>
<A class="inline" href={buildStackEventsHref(source.id)}>
{source.title}
</A>
</div>
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
<script lang="ts">
import type { StackStatus } from '$features/stacks/models';

import { resolve } from '$app/paths';
import { A } from '$comp/typography';
import StackStatusBadge from '$features/stacks/components/stack-status-badge.svelte';

import type { StackSummaryModel, SummaryModel, SummaryTemplateKeys } from './index';
import { buildStackEventsHref, type StackSummaryModel, type SummaryModel, type SummaryTemplateKeys } from './index';

interface Props {
badgeStatus: StackStatus;
Expand All @@ -27,7 +26,7 @@
<strong>Session</strong>:&nbsp;
{/if}

<A class="inline" href={`${resolve('/(app)/event')}?filter=stack:${source.id}`}>
<A class="inline" href={buildStackEventsHref(source.id)}>
{source.title}
</A>
</div>
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
<script lang="ts">
import type { StackStatus } from '$features/stacks/models';

import { resolve } from '$app/paths';
import { A, Muted } from '$comp/typography';
import StackStatusBadge from '$features/stacks/components/stack-status-badge.svelte';
import ChevronRight from '@lucide/svelte/icons/chevron-right';

import type { StackSummaryModel, SummaryModel, SummaryTemplateKeys } from './index';
import { buildStackEventsHref, type StackSummaryModel, type SummaryModel, type SummaryTemplateKeys } from './index';

interface Props {
badgeStatus: StackStatus;
Expand All @@ -27,7 +26,7 @@
<abbr title={source.data.TypeFullName}>{source.data.Type}</abbr>:
</strong>

<A class="inline" href={`${resolve('/(app)/event')}?filter=stack:${source.id}`}>{source.title}</A>
<A class="inline" href={buildStackEventsHref(source.id)}>{source.title}</A>
</div>

{#if source.data.Path}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
<script lang="ts">
import type { StackStatus } from '$features/stacks/models';

import { resolve } from '$app/paths';
import { A } from '$comp/typography';
import StackStatusBadge from '$features/stacks/components/stack-status-badge.svelte';

import type { StackSummaryModel, SummaryModel, SummaryTemplateKeys } from './index';
import { buildStackEventsHref, type StackSummaryModel, type SummaryModel, type SummaryTemplateKeys } from './index';

interface Props {
badgeStatus: StackStatus;
Expand Down Expand Up @@ -39,7 +38,7 @@
:&nbsp;
{/if}

<A class="inline" href={`${resolve('/(app)/event')}?filter=stack:${source.id}`}>
<A class="inline" href={buildStackEventsHref(source.id)}>
{source.title}
</A>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { savedViewHref, savedViewResolvedSlug } from './slugs';

export interface SavedViewQueryParams {
filter: null | string | undefined;
filters?: null | string | undefined;
saved?: null | string | undefined;
sort?: null | string;
time?: null | string;
Expand Down Expand Up @@ -262,6 +263,7 @@ export function useSavedViews(options: UseSavedViewsOptions): UseSavedViewsRetur
}

options.queryParams.filter = null;
options.queryParams.filters = null;
setSortQueryParam(options.queryParams, null);
setTimeQueryParam(options.queryParams, null);
applyColumnState(view);
Expand All @@ -270,6 +272,7 @@ export function useSavedViews(options: UseSavedViewsOptions): UseSavedViewsRetur

function handleClearSavedView() {
options.queryParams.filter = null;
options.queryParams.filters = null;
setSortQueryParam(options.queryParams, null);
setTimeQueryParam(options.queryParams, null);
applyColumnState(undefined);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,15 @@ export function getSharedTableOptions<TData extends RowData, TPaginationStrategy
const [rowSelection, setRowSelection] = createTableState<RowSelectionState>({});

const onPaginationChange = (updaterOrValue: Updater<PaginationState>) => {
const previousPageIndex = pagination().pageIndex;
const previousPageInfo = pagination();

setPagination(updaterOrValue);
const currentPageInfo = pagination();
const paginationChange = getPaginationChange(previousPageInfo, pagination());
if (paginationChange.pageIndexChanged) {
setPagination(paginationChange.currentPageInfo);
}
Comment on lines +114 to +120
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels a bit hacky. this was working. We have three different table pagination strategies.


const currentPageInfo = paginationChange.currentPageInfo;
if (configuration.queryParameters.limit !== currentPageInfo.pageSize) {
configuration.queryParameters.limit = currentPageInfo.pageSize;
}
Expand All @@ -125,16 +129,9 @@ export function getSharedTableOptions<TData extends RowData, TPaginationStrategy
const start = currentPageInfo.pageIndex * currentPageInfo.pageSize;
setData(allData().slice(start, start + currentPageInfo.pageSize));
} else if (isCursorPaging) {
const queryMeta = meta();
const nextLink = queryMeta?.links?.next?.after;
const previousLink = queryMeta?.links?.previous?.before;

const parameters = configuration.queryParameters as TableCursorPagingParameters;
parameters.after = currentPageInfo.pageIndex > previousPageIndex ? nextLink : undefined;
// Ensure previousLink is only used when actually moving back and not on the first page
parameters.before = currentPageInfo.pageIndex < previousPageIndex && currentPageInfo.pageIndex > 0 ? previousLink : undefined;
updateCursorPagingParameters(configuration.queryParameters as TableCursorPagingParameters, meta(), paginationChange);
} else if (isOffsetPaging || isMemoryPaging) {
(configuration.queryParameters as TableMemoryPagingParameters | TableOffsetPagingParameters).page = currentPageInfo.pageIndex + 1; // API uses 1-based index
updatePageNumberPagingParameters(configuration.queryParameters as TableMemoryPagingParameters | TableOffsetPagingParameters, currentPageInfo);
}
};

Expand Down Expand Up @@ -214,6 +211,18 @@ export function getSharedTableOptions<TData extends RowData, TPaginationStrategy
// NOTE: Two different effects are used here to avoid circular dependency issues with in memory paging.
$effect(() => setDataImpl(configuration.queryData ?? []));
$effect(() => setMetaImpl(configuration.queryMeta));
$effect(() => {
const nextPageSize = configuration.queryParameters.limit ?? DEFAULT_LIMIT;
const nextPageIndex = getPageIndexFromParameters(configuration.paginationStrategy, configuration.queryParameters, pagination().pageIndex);
const currentPageInfo = pagination();

if (currentPageInfo.pageSize !== nextPageSize || currentPageInfo.pageIndex !== nextPageIndex) {
setPagination({
pageIndex: nextPageIndex,
pageSize: nextPageSize
});
}
});
$effect(() => {
if (!hasSortQueryParameter(configuration.queryParameters)) {
return;
Expand Down Expand Up @@ -375,6 +384,36 @@ function getColumnIds<TData extends RowData>(columns: ColumnDef<StockFeatures, T
});
}

function getPageIndexFromParameters(strategy: PaginationStrategy, parameters: TablePagingParameters, fallbackPageIndex: number): number {
if (strategy !== 'offset' && strategy !== 'memory') {
return fallbackPageIndex;
}

return Math.max(0, (((parameters as TableMemoryPagingParameters | TableOffsetPagingParameters).page ?? 1) as number) - 1);
}

function getPaginationChange(previousPageInfo: PaginationState, currentPageInfo: PaginationState) {
const pageSizeChanged = previousPageInfo.pageSize !== currentPageInfo.pageSize;
if (!pageSizeChanged || currentPageInfo.pageIndex === 0) {
return {
currentPageInfo,
pageIndexChanged: false,
pageSizeChanged,
previousPageInfo
};
}

return {
currentPageInfo: {
...currentPageInfo,
pageIndex: 0
},
pageIndexChanged: true,
pageSizeChanged,
previousPageInfo
};
}

function hasSortQueryParameter(parameters: TablePagingParameters): parameters is TableCursorPagingParameters | TableOffsetPagingParameters {
return Object.prototype.hasOwnProperty.call(parameters, 'sort');
}
Expand Down Expand Up @@ -410,3 +449,22 @@ function sanitizeColumnOrder<TData extends RowData>(columnOrder: ColumnOrderStat
function serializeSortState(sorting: ColumnSort[]): string | undefined {
return sorting.length > 0 ? sorting.map((sort) => `${sort.desc ? '-' : ''}${sort.id}`).join(',') : undefined;
}

function updateCursorPagingParameters(
parameters: TableCursorPagingParameters,
meta: QueryMeta | undefined,
paginationChange: ReturnType<typeof getPaginationChange>
): void {
const movingForward = paginationChange.currentPageInfo.pageIndex > paginationChange.previousPageInfo.pageIndex;
const movingBackward = paginationChange.currentPageInfo.pageIndex < paginationChange.previousPageInfo.pageIndex;

// Cursor tokens are only valid for the current page size and direction.
// When the page size changes, clear both tokens and let the first page reload.
parameters.after = !paginationChange.pageSizeChanged && movingForward ? meta?.links?.next?.after : undefined;
parameters.before =
!paginationChange.pageSizeChanged && movingBackward && paginationChange.currentPageInfo.pageIndex > 0 ? meta?.links?.previous?.before : undefined;
}

function updatePageNumberPagingParameters(parameters: TableMemoryPagingParameters | TableOffsetPagingParameters, currentPageInfo: PaginationState): void {
parameters.page = currentPageInfo.pageIndex + 1; // API uses 1-based indexes.
}
Loading
Loading