@@ -6,7 +6,22 @@ import {
66 workflowDeploymentVersion ,
77 workflowExecutionLogs ,
88} from '@sim/db/schema'
9- import { and , desc , eq , gte , inArray , isNotNull , isNull , lte , or , type SQL , sql } from 'drizzle-orm'
9+ import {
10+ and ,
11+ desc ,
12+ eq ,
13+ gt ,
14+ gte ,
15+ inArray ,
16+ isNotNull ,
17+ isNull ,
18+ lt ,
19+ lte ,
20+ ne ,
21+ or ,
22+ type SQL ,
23+ sql ,
24+ } from 'drizzle-orm'
1025import { type NextRequest , NextResponse } from 'next/server'
1126import { z } from 'zod'
1227import { getSession } from '@/lib/auth'
@@ -22,14 +37,19 @@ const QueryParamsSchema = z.object({
2237 limit : z . coerce . number ( ) . optional ( ) . default ( 100 ) ,
2338 offset : z . coerce . number ( ) . optional ( ) . default ( 0 ) ,
2439 level : z . string ( ) . optional ( ) ,
25- workflowIds : z . string ( ) . optional ( ) , // Comma-separated list of workflow IDs
26- folderIds : z . string ( ) . optional ( ) , // Comma-separated list of folder IDs
27- triggers : z . string ( ) . optional ( ) , // Comma-separated list of trigger types
40+ workflowIds : z . string ( ) . optional ( ) ,
41+ folderIds : z . string ( ) . optional ( ) ,
42+ triggers : z . string ( ) . optional ( ) ,
2843 startDate : z . string ( ) . optional ( ) ,
2944 endDate : z . string ( ) . optional ( ) ,
3045 search : z . string ( ) . optional ( ) ,
3146 workflowName : z . string ( ) . optional ( ) ,
3247 folderName : z . string ( ) . optional ( ) ,
48+ executionId : z . string ( ) . optional ( ) ,
49+ costOperator : z . enum ( [ '=' , '>' , '<' , '>=' , '<=' , '!=' ] ) . optional ( ) ,
50+ costValue : z . coerce . number ( ) . optional ( ) ,
51+ durationOperator : z . enum ( [ '=' , '>' , '<' , '>=' , '<=' , '!=' ] ) . optional ( ) ,
52+ durationValue : z . coerce . number ( ) . optional ( ) ,
3353 workspaceId : z . string ( ) ,
3454} )
3555
@@ -49,7 +69,6 @@ export async function GET(request: NextRequest) {
4969 const { searchParams } = new URL ( request . url )
5070 const params = QueryParamsSchema . parse ( Object . fromEntries ( searchParams . entries ( ) ) )
5171
52- // Conditionally select columns based on detail level to optimize performance
5372 const selectColumns =
5473 params . details === 'full'
5574 ? {
@@ -63,9 +82,9 @@ export async function GET(request: NextRequest) {
6382 startedAt : workflowExecutionLogs . startedAt ,
6483 endedAt : workflowExecutionLogs . endedAt ,
6584 totalDurationMs : workflowExecutionLogs . totalDurationMs ,
66- executionData : workflowExecutionLogs . executionData , // Large field - only in full mode
85+ executionData : workflowExecutionLogs . executionData ,
6786 cost : workflowExecutionLogs . cost ,
68- files : workflowExecutionLogs . files , // Large field - only in full mode
87+ files : workflowExecutionLogs . files ,
6988 createdAt : workflowExecutionLogs . createdAt ,
7089 workflowName : workflow . name ,
7190 workflowDescription : workflow . description ,
@@ -82,7 +101,6 @@ export async function GET(request: NextRequest) {
82101 deploymentVersionName : workflowDeploymentVersion . name ,
83102 }
84103 : {
85- // Basic mode - exclude large fields for better performance
86104 id : workflowExecutionLogs . id ,
87105 workflowId : workflowExecutionLogs . workflowId ,
88106 executionId : workflowExecutionLogs . executionId ,
@@ -93,9 +111,9 @@ export async function GET(request: NextRequest) {
93111 startedAt : workflowExecutionLogs . startedAt ,
94112 endedAt : workflowExecutionLogs . endedAt ,
95113 totalDurationMs : workflowExecutionLogs . totalDurationMs ,
96- executionData : sql < null > `NULL` , // Exclude large execution data in basic mode
114+ executionData : sql < null > `NULL` ,
97115 cost : workflowExecutionLogs . cost ,
98- files : sql < null > `NULL` , // Exclude files in basic mode
116+ files : sql < null > `NULL` ,
99117 createdAt : workflowExecutionLogs . createdAt ,
100118 workflowName : workflow . name ,
101119 workflowDescription : workflow . description ,
@@ -109,7 +127,7 @@ export async function GET(request: NextRequest) {
109127 pausedTotalPauseCount : pausedExecutions . totalPauseCount ,
110128 pausedResumedCount : pausedExecutions . resumedCount ,
111129 deploymentVersion : workflowDeploymentVersion . version ,
112- deploymentVersionName : sql < null > `NULL` , // Only needed in full mode for details panel
130+ deploymentVersionName : sql < null > `NULL` ,
113131 }
114132
115133 const baseQuery = db
@@ -139,34 +157,28 @@ export async function GET(request: NextRequest) {
139157 )
140158 )
141159
142- // Build additional conditions for the query
143160 let conditions : SQL | undefined
144161
145- // Filter by level with support for derived statuses (running, pending)
146162 if ( params . level && params . level !== 'all' ) {
147163 const levels = params . level . split ( ',' ) . filter ( Boolean )
148164 const levelConditions : SQL [ ] = [ ]
149165
150166 for ( const level of levels ) {
151167 if ( level === 'error' ) {
152- // Direct database field
153168 levelConditions . push ( eq ( workflowExecutionLogs . level , 'error' ) )
154169 } else if ( level === 'info' ) {
155- // Completed info logs only (not running, not pending)
156170 const condition = and (
157171 eq ( workflowExecutionLogs . level , 'info' ) ,
158172 isNotNull ( workflowExecutionLogs . endedAt )
159173 )
160174 if ( condition ) levelConditions . push ( condition )
161175 } else if ( level === 'running' ) {
162- // Running logs: info level with no endedAt
163176 const condition = and (
164177 eq ( workflowExecutionLogs . level , 'info' ) ,
165178 isNull ( workflowExecutionLogs . endedAt )
166179 )
167180 if ( condition ) levelConditions . push ( condition )
168181 } else if ( level === 'pending' ) {
169- // Pending logs: info level with pause status indicators
170182 const condition = and (
171183 eq ( workflowExecutionLogs . level , 'info' ) ,
172184 or (
@@ -189,31 +201,27 @@ export async function GET(request: NextRequest) {
189201 }
190202 }
191203
192- // Filter by specific workflow IDs
193204 if ( params . workflowIds ) {
194205 const workflowIds = params . workflowIds . split ( ',' ) . filter ( Boolean )
195206 if ( workflowIds . length > 0 ) {
196207 conditions = and ( conditions , inArray ( workflow . id , workflowIds ) )
197208 }
198209 }
199210
200- // Filter by folder IDs
201211 if ( params . folderIds ) {
202212 const folderIds = params . folderIds . split ( ',' ) . filter ( Boolean )
203213 if ( folderIds . length > 0 ) {
204214 conditions = and ( conditions , inArray ( workflow . folderId , folderIds ) )
205215 }
206216 }
207217
208- // Filter by triggers
209218 if ( params . triggers ) {
210219 const triggers = params . triggers . split ( ',' ) . filter ( Boolean )
211220 if ( triggers . length > 0 && ! triggers . includes ( 'all' ) ) {
212221 conditions = and ( conditions , inArray ( workflowExecutionLogs . trigger , triggers ) )
213222 }
214223 }
215224
216- // Filter by date range
217225 if ( params . startDate ) {
218226 conditions = and (
219227 conditions ,
@@ -224,33 +232,79 @@ export async function GET(request: NextRequest) {
224232 conditions = and ( conditions , lte ( workflowExecutionLogs . startedAt , new Date ( params . endDate ) ) )
225233 }
226234
227- // Filter by search query
228235 if ( params . search ) {
229236 const searchTerm = `%${ params . search } %`
230- // With message removed, restrict search to executionId only
231237 conditions = and ( conditions , sql `${ workflowExecutionLogs . executionId } ILIKE ${ searchTerm } ` )
232238 }
233239
234- // Filter by workflow name (from advanced search input)
235240 if ( params . workflowName ) {
236241 const nameTerm = `%${ params . workflowName } %`
237242 conditions = and ( conditions , sql `${ workflow . name } ILIKE ${ nameTerm } ` )
238243 }
239244
240- // Filter by folder name (best-effort text match when present on workflows)
241245 if ( params . folderName ) {
242246 const folderTerm = `%${ params . folderName } %`
243247 conditions = and ( conditions , sql `${ workflow . name } ILIKE ${ folderTerm } ` )
244248 }
245249
246- // Execute the query using the optimized join
250+ if ( params . executionId ) {
251+ conditions = and ( conditions , eq ( workflowExecutionLogs . executionId , params . executionId ) )
252+ }
253+
254+ if ( params . costOperator && params . costValue !== undefined ) {
255+ const costField = sql `(${ workflowExecutionLogs . cost } ->>'total')::numeric`
256+ switch ( params . costOperator ) {
257+ case '=' :
258+ conditions = and ( conditions , sql `${ costField } = ${ params . costValue } ` )
259+ break
260+ case '>' :
261+ conditions = and ( conditions , sql `${ costField } > ${ params . costValue } ` )
262+ break
263+ case '<' :
264+ conditions = and ( conditions , sql `${ costField } < ${ params . costValue } ` )
265+ break
266+ case '>=' :
267+ conditions = and ( conditions , sql `${ costField } >= ${ params . costValue } ` )
268+ break
269+ case '<=' :
270+ conditions = and ( conditions , sql `${ costField } <= ${ params . costValue } ` )
271+ break
272+ case '!=' :
273+ conditions = and ( conditions , sql `${ costField } != ${ params . costValue } ` )
274+ break
275+ }
276+ }
277+
278+ if ( params . durationOperator && params . durationValue !== undefined ) {
279+ const durationField = workflowExecutionLogs . totalDurationMs
280+ switch ( params . durationOperator ) {
281+ case '=' :
282+ conditions = and ( conditions , eq ( durationField , params . durationValue ) )
283+ break
284+ case '>' :
285+ conditions = and ( conditions , gt ( durationField , params . durationValue ) )
286+ break
287+ case '<' :
288+ conditions = and ( conditions , lt ( durationField , params . durationValue ) )
289+ break
290+ case '>=' :
291+ conditions = and ( conditions , gte ( durationField , params . durationValue ) )
292+ break
293+ case '<=' :
294+ conditions = and ( conditions , lte ( durationField , params . durationValue ) )
295+ break
296+ case '!=' :
297+ conditions = and ( conditions , ne ( durationField , params . durationValue ) )
298+ break
299+ }
300+ }
301+
247302 const logs = await baseQuery
248303 . where ( conditions )
249304 . orderBy ( desc ( workflowExecutionLogs . startedAt ) )
250305 . limit ( params . limit )
251306 . offset ( params . offset )
252307
253- // Get total count for pagination using the same join structure
254308 const countQuery = db
255309 . select ( { count : sql < number > `count(*)` } )
256310 . from ( workflowExecutionLogs )
@@ -279,13 +333,10 @@ export async function GET(request: NextRequest) {
279333
280334 const count = countResult [ 0 ] ?. count || 0
281335
282- // Block executions are now extracted from trace spans instead of separate table
283336 const blockExecutionsByExecution : Record < string , any [ ] > = { }
284337
285- // Create clean trace spans from block executions
286338 const createTraceSpans = ( blockExecutions : any [ ] ) => {
287339 return blockExecutions . map ( ( block , index ) => {
288- // For error blocks, include error information in the output
289340 let output = block . outputData
290341 if ( block . status === 'error' && block . errorMessage ) {
291342 output = {
@@ -314,7 +365,6 @@ export async function GET(request: NextRequest) {
314365 } )
315366 }
316367
317- // Extract cost information from block executions
318368 const extractCostSummary = ( blockExecutions : any [ ] ) => {
319369 let totalCost = 0
320370 let totalInputCost = 0
@@ -333,7 +383,6 @@ export async function GET(request: NextRequest) {
333383 totalPromptTokens += block . cost . tokens ?. prompt || 0
334384 totalCompletionTokens += block . cost . tokens ?. completion || 0
335385
336- // Track per-model costs
337386 if ( block . cost . model ) {
338387 if ( ! models . has ( block . cost . model ) ) {
339388 models . set ( block . cost . model , {
@@ -363,34 +412,29 @@ export async function GET(request: NextRequest) {
363412 prompt : totalPromptTokens ,
364413 completion : totalCompletionTokens ,
365414 } ,
366- models : Object . fromEntries ( models ) , // Convert Map to object for JSON serialization
415+ models : Object . fromEntries ( models ) ,
367416 }
368417 }
369418
370- // Transform to clean log format with workflow data included
371419 const enhancedLogs = logs . map ( ( log ) => {
372420 const blockExecutions = blockExecutionsByExecution [ log . executionId ] || [ ]
373421
374- // Only process trace spans and detailed cost in full mode
375422 let traceSpans = [ ]
376423 let finalOutput : any
377424 let costSummary = ( log . cost as any ) || { total : 0 }
378425
379426 if ( params . details === 'full' && log . executionData ) {
380- // Use stored trace spans if available, otherwise create from block executions
381427 const storedTraceSpans = ( log . executionData as any ) ?. traceSpans
382428 traceSpans =
383429 storedTraceSpans && Array . isArray ( storedTraceSpans ) && storedTraceSpans . length > 0
384430 ? storedTraceSpans
385431 : createTraceSpans ( blockExecutions )
386432
387- // Prefer stored cost JSON; otherwise synthesize from blocks
388433 costSummary =
389434 log . cost && Object . keys ( log . cost as any ) . length > 0
390435 ? ( log . cost as any )
391436 : extractCostSummary ( blockExecutions )
392437
393- // Include finalOutput if present on executionData
394438 try {
395439 const fo = ( log . executionData as any ) ?. finalOutput
396440 if ( fo !== undefined ) finalOutput = fo
0 commit comments