Skip to content

Commit 8023370

Browse files
committed
Pagination implemeneted
1 parent 2174d5b commit 8023370

File tree

2 files changed

+45
-16
lines changed

2 files changed

+45
-16
lines changed

apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export const ErrorsListOptionsSchema = z.object({
5050
pageSize: z.number().int().positive().max(1000).optional(),
5151
});
5252

53-
const DEFAULT_PAGE_SIZE = 50;
53+
const DEFAULT_PAGE_SIZE = 25;
5454

5555
export type ErrorsList = Awaited<ReturnType<ErrorsListPresenter["call"]>>;
5656
export type ErrorGroup = ErrorsList["errorGroups"][0];
@@ -88,6 +88,14 @@ function decodeCursor(cursor: string): ErrorGroupCursor | null {
8888
}
8989
}
9090

91+
function cursorFromRow(row: { occurrence_count: number; error_fingerprint: string; task_identifier: string }): string {
92+
return encodeCursor({
93+
occurrenceCount: row.occurrence_count,
94+
fingerprint: row.error_fingerprint,
95+
taskIdentifier: row.task_identifier,
96+
});
97+
}
98+
9199
function parseClickHouseDateTime(value: string): Date {
92100
const asNum = Number(value);
93101
if (!isNaN(asNum) && asNum > 1e12) {
@@ -119,6 +127,7 @@ export class ErrorsListPresenter extends BasePresenter {
119127
search,
120128
from,
121129
to,
130+
direction,
122131
cursor,
123132
pageSize = DEFAULT_PAGE_SIZE,
124133
defaultPeriod,
@@ -193,13 +202,15 @@ export class ErrorsListPresenter extends BasePresenter {
193202
);
194203
}
195204

196-
// Cursor-based pagination (sorted by occurrence_count DESC, then fingerprint, then task)
205+
const isBackward = direction === "backward";
197206
const decodedCursor = cursor ? decodeCursor(cursor) : null;
207+
198208
if (decodedCursor) {
209+
const cmp = isBackward ? ">" : "<";
199210
queryBuilder.having(
200-
`(occurrence_count < {cursorOccurrenceCount: UInt64}
201-
OR (occurrence_count = {cursorOccurrenceCount: UInt64} AND error_fingerprint < {cursorFingerprint: String})
202-
OR (occurrence_count = {cursorOccurrenceCount: UInt64} AND error_fingerprint = {cursorFingerprint: String} AND task_identifier < {cursorTaskIdentifier: String}))`,
211+
`(occurrence_count ${cmp} {cursorOccurrenceCount: UInt64}
212+
OR (occurrence_count = {cursorOccurrenceCount: UInt64} AND error_fingerprint ${cmp} {cursorFingerprint: String})
213+
OR (occurrence_count = {cursorOccurrenceCount: UInt64} AND error_fingerprint = {cursorFingerprint: String} AND task_identifier ${cmp} {cursorTaskIdentifier: String}))`,
203214
{
204215
cursorOccurrenceCount: decodedCursor.occurrenceCount,
205216
cursorFingerprint: decodedCursor.fingerprint,
@@ -208,7 +219,10 @@ export class ErrorsListPresenter extends BasePresenter {
208219
);
209220
}
210221

211-
queryBuilder.orderBy("task_identifier DESC, error_fingerprint DESC, occurrence_count DESC");
222+
const sortDir = isBackward ? "ASC" : "DESC";
223+
queryBuilder.orderBy(
224+
`occurrence_count ${sortDir}, error_fingerprint ${sortDir}, task_identifier ${sortDir}`
225+
);
212226
queryBuilder.limit(pageSize + 1);
213227

214228
const [queryError, records] = await queryBuilder.execute();
@@ -219,18 +233,25 @@ export class ErrorsListPresenter extends BasePresenter {
219233

220234
const results = records || [];
221235
const hasMore = results.length > pageSize;
222-
const errorGroups = results.slice(0, pageSize);
236+
const page = results.slice(0, pageSize);
237+
238+
if (isBackward) {
239+
page.reverse();
240+
}
223241

224242
let nextCursor: string | undefined;
225-
if (hasMore && errorGroups.length > 0) {
226-
const lastError = errorGroups[errorGroups.length - 1];
227-
nextCursor = encodeCursor({
228-
occurrenceCount: lastError.occurrence_count,
229-
fingerprint: lastError.error_fingerprint,
230-
taskIdentifier: lastError.task_identifier,
231-
});
243+
let previousCursor: string | undefined;
244+
245+
if (isBackward) {
246+
previousCursor = hasMore && page.length > 0 ? cursorFromRow(page[0]) : undefined;
247+
nextCursor = page.length > 0 ? cursorFromRow(page[page.length - 1]) : undefined;
248+
} else {
249+
previousCursor = decodedCursor && page.length > 0 ? cursorFromRow(page[0]) : undefined;
250+
nextCursor = hasMore && page.length > 0 ? cursorFromRow(page[page.length - 1]) : undefined;
232251
}
233252

253+
const errorGroups = page;
254+
234255
// Fetch global first_seen / last_seen from the errors_v1 summary table
235256
const fingerprints = errorGroups.map((e) => e.error_fingerprint);
236257
const globalSummaryMap = await this.getGlobalSummary(
@@ -256,8 +277,8 @@ export class ErrorsListPresenter extends BasePresenter {
256277
return {
257278
errorGroups: transformedErrorGroups,
258279
pagination: {
259-
hasMore,
260-
nextCursor,
280+
next: nextCursor,
281+
previous: previousCursor,
261282
},
262283
filters: {
263284
tasks,

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import {
4949
import { logsClickhouseClient } from "~/services/clickhouseInstance.server";
5050
import { getCurrentPlan } from "~/services/platform.v3.server";
5151
import { requireUser } from "~/services/session.server";
52+
import { ListPagination } from "~/components/ListPagination";
5253
import { formatNumberCompact } from "~/utils/numberFormatter";
5354
import { EnvironmentParamSchema, v3ErrorPath } from "~/utils/pathBuilder";
5455
import { ServiceValidationError } from "~/v3/services/baseService.server";
@@ -85,6 +86,10 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
8586
const toStr = url.searchParams.get("to");
8687
const from = fromStr ? parseInt(fromStr, 10) : undefined;
8788
const to = toStr ? parseInt(toStr, 10) : undefined;
89+
const cursor = url.searchParams.get("cursor") ?? undefined;
90+
const directionRaw = url.searchParams.get("direction");
91+
const direction =
92+
directionRaw === "forward" || directionRaw === "backward" ? directionRaw : undefined;
8893

8994
const plan = await getCurrentPlan(project.organizationId);
9095
const retentionLimitDays = plan?.v3Subscription?.plan?.limits.logRetentionDays.number ?? 30;
@@ -100,6 +105,8 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
100105
period,
101106
from,
102107
to,
108+
cursor,
109+
direction,
103110
defaultPeriod: "1d",
104111
retentionLimitDays,
105112
})
@@ -276,6 +283,7 @@ function FiltersBar({
276283
</>
277284
)}
278285
</div>
286+
{list && <ListPagination list={list} />}
279287
</div>
280288
);
281289
}

0 commit comments

Comments
 (0)