Skip to content

Commit 545ec79

Browse files
fix(logs-page): optimize logs retrieval queries, consolidate useEffects to prevent dup calls (#845)
* fix(logs-page): optimize loading times by improving query, removing unused index, adding new index * add migration files * remove fake min loading time
1 parent 3bd7a6c commit 545ec79

File tree

6 files changed

+5790
-147
lines changed

6 files changed

+5790
-147
lines changed

apps/sim/app/api/logs/route.ts

Lines changed: 70 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -75,33 +75,50 @@ export async function GET(request: NextRequest) {
7575
const { searchParams } = new URL(request.url)
7676
const params = QueryParamsSchema.parse(Object.fromEntries(searchParams.entries()))
7777

78-
const workflowConditions = and(
79-
eq(workflow.workspaceId, params.workspaceId),
80-
eq(permissions.userId, userId),
81-
eq(permissions.entityType, 'workspace')
82-
)
83-
84-
const userWorkflows = await db
85-
.select({ id: workflow.id, folderId: workflow.folderId })
86-
.from(workflow)
87-
.leftJoin(
78+
const baseQuery = db
79+
.select({
80+
id: workflowExecutionLogs.id,
81+
workflowId: workflowExecutionLogs.workflowId,
82+
executionId: workflowExecutionLogs.executionId,
83+
stateSnapshotId: workflowExecutionLogs.stateSnapshotId,
84+
level: workflowExecutionLogs.level,
85+
message: workflowExecutionLogs.message,
86+
trigger: workflowExecutionLogs.trigger,
87+
startedAt: workflowExecutionLogs.startedAt,
88+
endedAt: workflowExecutionLogs.endedAt,
89+
totalDurationMs: workflowExecutionLogs.totalDurationMs,
90+
blockCount: workflowExecutionLogs.blockCount,
91+
successCount: workflowExecutionLogs.successCount,
92+
errorCount: workflowExecutionLogs.errorCount,
93+
skippedCount: workflowExecutionLogs.skippedCount,
94+
totalCost: workflowExecutionLogs.totalCost,
95+
totalInputCost: workflowExecutionLogs.totalInputCost,
96+
totalOutputCost: workflowExecutionLogs.totalOutputCost,
97+
totalTokens: workflowExecutionLogs.totalTokens,
98+
metadata: workflowExecutionLogs.metadata,
99+
createdAt: workflowExecutionLogs.createdAt,
100+
workflowName: workflow.name,
101+
workflowDescription: workflow.description,
102+
workflowColor: workflow.color,
103+
workflowFolderId: workflow.folderId,
104+
workflowUserId: workflow.userId,
105+
workflowWorkspaceId: workflow.workspaceId,
106+
workflowCreatedAt: workflow.createdAt,
107+
workflowUpdatedAt: workflow.updatedAt,
108+
})
109+
.from(workflowExecutionLogs)
110+
.innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
111+
.innerJoin(
88112
permissions,
89113
and(
90114
eq(permissions.entityType, 'workspace'),
91115
eq(permissions.entityId, workflow.workspaceId),
92116
eq(permissions.userId, userId)
93117
)
94118
)
95-
.where(workflowConditions)
96119

97-
const userWorkflowIds = userWorkflows.map((w) => w.id)
98-
99-
if (userWorkflowIds.length === 0) {
100-
return NextResponse.json({ data: [], total: 0 }, { status: 200 })
101-
}
102-
103-
// Build conditions for logs
104-
let conditions: SQL | undefined = inArray(workflowExecutionLogs.workflowId, userWorkflowIds)
120+
// Build conditions for the joined query
121+
let conditions: SQL | undefined = eq(workflow.workspaceId, params.workspaceId)
105122

106123
// Filter by level
107124
if (params.level && params.level !== 'all') {
@@ -111,27 +128,16 @@ export async function GET(request: NextRequest) {
111128
// Filter by specific workflow IDs
112129
if (params.workflowIds) {
113130
const workflowIds = params.workflowIds.split(',').filter(Boolean)
114-
const filteredWorkflowIds = workflowIds.filter((id) => userWorkflowIds.includes(id))
115-
if (filteredWorkflowIds.length > 0) {
116-
conditions = and(
117-
conditions,
118-
inArray(workflowExecutionLogs.workflowId, filteredWorkflowIds)
119-
)
131+
if (workflowIds.length > 0) {
132+
conditions = and(conditions, inArray(workflow.id, workflowIds))
120133
}
121134
}
122135

123136
// Filter by folder IDs
124137
if (params.folderIds) {
125138
const folderIds = params.folderIds.split(',').filter(Boolean)
126-
const workflowsInFolders = userWorkflows
127-
.filter((w) => w.folderId && folderIds.includes(w.folderId))
128-
.map((w) => w.id)
129-
130-
if (workflowsInFolders.length > 0) {
131-
conditions = and(
132-
conditions,
133-
inArray(workflowExecutionLogs.workflowId, workflowsInFolders)
134-
)
139+
if (folderIds.length > 0) {
140+
conditions = and(conditions, inArray(workflow.folderId, folderIds))
135141
}
136142
}
137143

@@ -166,21 +172,30 @@ export async function GET(request: NextRequest) {
166172
)
167173
}
168174

169-
// Execute the query
170-
const logs = await db
171-
.select()
172-
.from(workflowExecutionLogs)
175+
// Execute the query using the optimized join
176+
const logs = await baseQuery
173177
.where(conditions)
174178
.orderBy(desc(workflowExecutionLogs.startedAt))
175179
.limit(params.limit)
176180
.offset(params.offset)
177181

178-
// Get total count for pagination
179-
const countResult = await db
182+
// Get total count for pagination using the same join structure
183+
const countQuery = db
180184
.select({ count: sql<number>`count(*)` })
181185
.from(workflowExecutionLogs)
186+
.innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
187+
.innerJoin(
188+
permissions,
189+
and(
190+
eq(permissions.entityType, 'workspace'),
191+
eq(permissions.entityId, workflow.workspaceId),
192+
eq(permissions.userId, userId)
193+
)
194+
)
182195
.where(conditions)
183196

197+
const countResult = await countQuery
198+
184199
const count = countResult[0]?.count || 0
185200

186201
// Block executions are now extracted from trace spans instead of separate table
@@ -271,7 +286,7 @@ export async function GET(request: NextRequest) {
271286
}
272287
}
273288

274-
// Transform to clean log format
289+
// Transform to clean log format with workflow data included
275290
const enhancedLogs = logs.map((log) => {
276291
const blockExecutions = blockExecutionsByExecution[log.executionId] || []
277292

@@ -298,6 +313,19 @@ export async function GET(request: NextRequest) {
298313
models: (log.metadata as any)?.models || {},
299314
}
300315

316+
// Build workflow object from joined data
317+
const workflow = {
318+
id: log.workflowId,
319+
name: log.workflowName,
320+
description: log.workflowDescription,
321+
color: log.workflowColor,
322+
folderId: log.workflowFolderId,
323+
userId: log.workflowUserId,
324+
workspaceId: log.workflowWorkspaceId,
325+
createdAt: log.workflowCreatedAt,
326+
updatedAt: log.workflowUpdatedAt,
327+
}
328+
301329
return {
302330
id: log.id,
303331
workflowId: log.workflowId,
@@ -307,6 +335,7 @@ export async function GET(request: NextRequest) {
307335
duration: log.totalDurationMs ? `${log.totalDurationMs}ms` : null,
308336
trigger: log.trigger,
309337
createdAt: log.startedAt.toISOString(),
338+
workflow: params.includeWorkflow ? workflow : undefined,
310339
metadata: {
311340
totalDuration: log.totalDurationMs,
312341
cost: costSummary,
@@ -323,30 +352,6 @@ export async function GET(request: NextRequest) {
323352
}
324353
})
325354

326-
if (params.includeWorkflow) {
327-
const workflowIds = [...new Set(logs.map((log) => log.workflowId))]
328-
const workflowConditions = inArray(workflow.id, workflowIds)
329-
330-
const workflowData = await db.select().from(workflow).where(workflowConditions)
331-
const workflowMap = new Map(workflowData.map((w) => [w.id, w]))
332-
333-
const logsWithWorkflow = enhancedLogs.map((log) => ({
334-
...log,
335-
workflow: workflowMap.get(log.workflowId) || null,
336-
}))
337-
338-
return NextResponse.json(
339-
{
340-
data: logsWithWorkflow,
341-
total: Number(count),
342-
page: Math.floor(params.offset / params.limit) + 1,
343-
pageSize: params.limit,
344-
totalPages: Math.ceil(Number(count) / params.limit),
345-
},
346-
{ status: 200 }
347-
)
348-
}
349-
350355
// Include block execution data if requested
351356
if (params.includeBlocks) {
352357
// Block executions are now extracted from stored trace spans in metadata

0 commit comments

Comments
 (0)