11import { z } from "zod" ;
2- import { type ClickHouse } from "@internal/clickhouse" ;
3- import {
4- type PrismaClientOrTransaction ,
5- } from "@trigger.dev/database" ;
2+ import { type ClickHouse , type WhereCondition } from "@internal/clickhouse" ;
3+ import { type PrismaClientOrTransaction } from "@trigger.dev/database" ;
64import { EVENT_STORE_TYPES , getConfiguredEventRepository } from "~/v3/eventRepository/index.server" ;
75
86import parseDuration from "parse-duration" ;
97import { type Direction } from "~/components/ListPagination" ;
10- import { timeFilters } from "~/components/runs/v3/SharedFilters" ;
8+ import { timeFilterFromTo , timeFilters } from "~/components/runs/v3/SharedFilters" ;
119import { findDisplayableEnvironment } from "~/models/runtimeEnvironment.server" ;
1210import { getAllTaskIdentifiers } from "~/models/task.server" ;
1311import { ServiceValidationError } from "~/v3/services/baseService.server" ;
@@ -28,14 +26,9 @@ type ErrorAttributes = {
2826} ;
2927
3028function escapeClickHouseString ( val : string ) : string {
31- return val
32- . replace ( / \\ / g, "\\\\" )
33- . replace ( / \/ / g, "\\/" )
34- . replace ( / % / g, "\\%" )
35- . replace ( / _ / g, "\\_" ) ;
29+ return val . replace ( / \\ / g, "\\\\" ) . replace ( / \/ / g, "\\/" ) . replace ( / % / g, "\\%" ) . replace ( / _ / g, "\\_" ) ;
3630}
3731
38-
3932export type LogsListOptions = {
4033 userId ?: string ;
4134 projectId : string ;
@@ -153,24 +146,16 @@ export class LogsListPresenter extends BasePresenter {
153146 retentionLimitDays,
154147 } : LogsListOptions
155148 ) {
156- const time = timeFilters ( {
149+ const time = timeFilterFromTo ( {
157150 period,
158151 from,
159152 to,
160- defaultPeriod,
153+ defaultPeriod : defaultPeriod ?? "1h" ,
161154 } ) ;
162155
163156 let effectiveFrom = time . from ;
164157 let effectiveTo = time . to ;
165158
166- if ( ! effectiveFrom && ! effectiveTo && time . period ) {
167- const periodMs = parseDuration ( time . period ) ;
168- if ( periodMs ) {
169- effectiveFrom = new Date ( Date . now ( ) - periodMs ) ;
170- effectiveTo = new Date ( ) ;
171- }
172- }
173-
174159 // Apply retention limit if provided
175160 let wasClampedByRetention = false ;
176161 if ( retentionLimitDays !== undefined && effectiveFrom ) {
@@ -250,11 +235,10 @@ export class LogsListPresenter extends BasePresenter {
250235 } ) ;
251236 queryBuilder . where ( "project_id = {projectId: String}" , { projectId } ) ;
252237
253-
254238 if ( effectiveFrom ) {
255- queryBuilder . where ( "triggered_timestamp >= {triggeredAtStart: DateTime64(3)}" , {
256- triggeredAtStart : convertDateToClickhouseDateTime ( effectiveFrom ) ,
257- } ) ;
239+ queryBuilder . where ( "triggered_timestamp >= {triggeredAtStart: DateTime64(3)}" , {
240+ triggeredAtStart : convertDateToClickhouseDateTime ( effectiveFrom ) ,
241+ } ) ;
258242 }
259243
260244 if ( effectiveTo ) {
@@ -283,50 +267,43 @@ export class LogsListPresenter extends BasePresenter {
283267 queryBuilder . where (
284268 "(lower(message) like {searchPattern: String} OR lower(attributes_text) like {searchPattern: String})" ,
285269 {
286- searchPattern : `%${ searchTerm } %`
270+ searchPattern : `%${ searchTerm } %` ,
287271 }
288272 ) ;
289273 }
290274
291275 if ( levels && levels . length > 0 ) {
292- const conditions : string [ ] = [ ] ;
293- const params : Record < string , string [ ] > = { } ;
276+ const conditions : WhereCondition [ ] = [ ] ;
294277
295- for ( const level of levels ) {
296- const filter = levelToKindsAndStatuses ( level ) ;
297- const levelConditions : string [ ] = [ ] ;
278+ for ( let i = 0 ; i < levels . length ; i ++ ) {
279+ const filter = levelToKindsAndStatuses ( levels [ i ] ) ;
298280
299281 if ( filter . kinds && filter . kinds . length > 0 ) {
300- const kindsKey = `kinds_${ level } ` ;
301- let kindCondition = `kind IN {${ kindsKey } : Array(String)}` ;
302-
303-
304- kindCondition += ` AND status NOT IN {excluded_statuses: Array(String)}` ;
305- params [ "excluded_statuses" ] = [ "ERROR" , "CANCELLED" ] ;
306-
307-
308- levelConditions . push ( kindCondition ) ;
309- params [ kindsKey ] = filter . kinds ;
282+ conditions . push ( {
283+ clause : `kind IN {kinds_${ i } : Array(String)} AND status NOT IN {excluded_statuses: Array(String)}` ,
284+ params : {
285+ [ `kinds_${ i } ` ] : filter . kinds ,
286+ excluded_statuses : [ "ERROR" , "CANCELLED" ] ,
287+ } ,
288+ } ) ;
310289 }
311290
312291 if ( filter . statuses && filter . statuses . length > 0 ) {
313- const statusesKey = `statuses_${ level } ` ;
314- levelConditions . push ( `status IN {${ statusesKey } : Array(String)}` ) ;
315- params [ statusesKey ] = filter . statuses ;
316- }
317-
318- if ( levelConditions . length > 0 ) {
319- conditions . push ( `(${ levelConditions . join ( " OR " ) } )` ) ;
292+ conditions . push ( {
293+ clause : `status IN {statuses_${ i } : Array(String)}` ,
294+ params : { [ `statuses_${ i } ` ] : filter . statuses } ,
295+ } ) ;
320296 }
321297 }
322298
323- if ( conditions . length > 0 ) {
324- queryBuilder . where ( `(${ conditions . join ( " OR " ) } )` , params ) ;
325- }
299+ queryBuilder . whereOr ( conditions ) ;
326300 }
327301
328- // Cursor pagination using explicit lexicographic comparison
329- // Must mirror the ORDER BY columns: (organization_id, environment_id, triggered_timestamp, trace_id)
302+ // Cursor-based pagination using lexicographic comparison on (triggered_timestamp, trace_id).
303+ // Since ORDER BY is DESC, "next page" means rows that sort *after* the cursor, i.e. less-than.
304+ // The OR handles the tiebreaker: rows with an earlier timestamp always qualify, and rows
305+ // with the *same* timestamp only qualify if their trace_id is also smaller.
306+ // Equivalent to: WHERE (triggered_timestamp, trace_id) < (cursor.triggered_timestamp, cursor.trace_id)
330307 const decodedCursor = cursor ? decodeCursor ( cursor ) : null ;
331308 if ( decodedCursor ) {
332309 queryBuilder . where (
@@ -428,10 +405,13 @@ export class LogsListPresenter extends BasePresenter {
428405 hasFilters,
429406 hasAnyLogs : transformedLogs . length > 0 ,
430407 searchTerm : search ,
431- retention : retentionLimitDays !== undefined ? {
432- limitDays : retentionLimitDays ,
433- wasClamped : wasClampedByRetention ,
434- } : undefined ,
408+ retention :
409+ retentionLimitDays !== undefined
410+ ? {
411+ limitDays : retentionLimitDays ,
412+ wasClamped : wasClampedByRetention ,
413+ }
414+ : undefined ,
435415 } ;
436416 }
437417}
0 commit comments