(params: ListLogsParams, userId: string)
| 66 | * workspace permission via the `permissions` join. |
| 67 | */ |
| 68 | export async function listLogs(params: ListLogsParams, userId: string): Promise<ListLogsResponse> { |
| 69 | const access = await checkWorkspaceAccess(params.workspaceId, userId) |
| 70 | if (!access.hasAccess) { |
| 71 | return { data: [], nextCursor: null } |
| 72 | } |
| 73 | |
| 74 | const sortBy = params.sortBy as SortBy |
| 75 | const sortOrder = params.sortOrder as SortOrder |
| 76 | const cursor = params.cursor ? decodeCursor(params.cursor) : null |
| 77 | |
| 78 | // Expand selected folders to include descendants (matches the route behavior), |
| 79 | // without mutating the caller's params object. |
| 80 | const folderIds = params.folderIds |
| 81 | ? await expandFolderIdsWithDescendants(params.workspaceId, params.folderIds) |
| 82 | : params.folderIds |
| 83 | const p: ListLogsParams = { ...params, folderIds } |
| 84 | |
| 85 | const workflowSortExpr: SQL<unknown> = (() => { |
| 86 | switch (sortBy) { |
| 87 | case 'duration': |
| 88 | return sql`${workflowExecutionLogs.totalDurationMs}` |
| 89 | case 'cost': |
| 90 | // Indexed projection of the usage_log ledger (dollars); no live aggregation. |
| 91 | return sql`${workflowExecutionLogs.costTotal}` |
| 92 | case 'status': |
| 93 | return sql`${workflowExecutionLogs.status}` |
| 94 | default: |
| 95 | return sql`${workflowExecutionLogs.startedAt}` |
| 96 | } |
| 97 | })() |
| 98 | |
| 99 | const jobSortExpr: SQL<unknown> = (() => { |
| 100 | switch (sortBy) { |
| 101 | case 'duration': |
| 102 | return sql`${jobExecutionLogs.totalDurationMs}` |
| 103 | case 'cost': |
| 104 | return sql`(${jobExecutionLogs.cost}->>'total')::numeric` |
| 105 | case 'status': |
| 106 | return sql`${jobExecutionLogs.status}` |
| 107 | default: |
| 108 | return sql`${jobExecutionLogs.startedAt}` |
| 109 | } |
| 110 | })() |
| 111 | |
| 112 | const dir = sortOrder === 'asc' ? asc : desc |
| 113 | const nullsLast = sql`NULLS LAST` |
| 114 | const orderByClause = (expr: SQL): SQL => sql`${dir(expr)} ${nullsLast}` |
| 115 | |
| 116 | const buildCursorCondition = (sortExpr: unknown, idCol: unknown): SQL | undefined => { |
| 117 | if (!cursor) return undefined |
| 118 | const v = cursor.v |
| 119 | const id = cursor.id |
| 120 | const cmp = sortOrder === 'asc' ? sql`>` : sql`<` |
| 121 | if (v === null) { |
| 122 | return sql`(${sortExpr} IS NULL AND ${idCol} ${cmp} ${id})` |
| 123 | } |
| 124 | return sql`((${sortExpr} IS NOT NULL AND ${sortExpr} ${cmp} ${v}) OR (${sortExpr} = ${v} AND ${idCol} ${cmp} ${id}) OR ${sortExpr} IS NULL)` |
| 125 | } |
no test coverage detected