Skip to content

Commit 8afd5c7

Browse files
emir-karabegwaleedlatif1
authored andcommitted
improvement(logs): dashboard/logs optimizations and improvements
1 parent e01d4cb commit 8afd5c7

File tree

18 files changed

+1237
-924
lines changed

18 files changed

+1237
-924
lines changed

apps/sim/app/api/workspaces/[id]/metrics/executions/route.ts

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,18 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
2929
}
3030
const userId = session.user.id
3131

32-
const end = qp.endTime ? new Date(qp.endTime) : new Date()
33-
const start = qp.startTime
32+
let end = qp.endTime ? new Date(qp.endTime) : new Date()
33+
let start = qp.startTime
3434
? new Date(qp.startTime)
3535
: new Date(end.getTime() - 24 * 60 * 60 * 1000)
36-
if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime()) || start >= end) {
36+
37+
const isAllTime = start.getTime() < 86400000
38+
39+
if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) {
3740
return NextResponse.json({ error: 'Invalid time range' }, { status: 400 })
3841
}
3942

4043
const segments = qp.segments
41-
const totalMs = Math.max(1, end.getTime() - start.getTime())
42-
const segmentMs = Math.max(1, Math.floor(totalMs / Math.max(1, segments)))
4344

4445
const [permission] = await db
4546
.select()
@@ -75,23 +76,18 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
7576
workflows: [],
7677
startTime: start.toISOString(),
7778
endTime: end.toISOString(),
78-
segmentMs,
79+
segmentMs: 0,
7980
})
8081
}
8182

8283
const workflowIdList = workflows.map((w) => w.id)
8384

84-
const logWhere = [
85-
inArray(workflowExecutionLogs.workflowId, workflowIdList),
86-
gte(workflowExecutionLogs.startedAt, start),
87-
lte(workflowExecutionLogs.startedAt, end),
88-
] as SQL[]
85+
const baseLogWhere = [inArray(workflowExecutionLogs.workflowId, workflowIdList)] as SQL[]
8986
if (qp.triggers) {
9087
const t = qp.triggers.split(',').filter(Boolean)
91-
logWhere.push(inArray(workflowExecutionLogs.trigger, t))
88+
baseLogWhere.push(inArray(workflowExecutionLogs.trigger, t))
9289
}
9390

94-
// Handle level filtering with support for derived statuses and multiple selections
9591
if (qp.level && qp.level !== 'all') {
9692
const levels = qp.level.split(',').filter(Boolean)
9793
const levelConditions: SQL[] = []
@@ -100,21 +96,18 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
10096
if (level === 'error') {
10197
levelConditions.push(eq(workflowExecutionLogs.level, 'error'))
10298
} else if (level === 'info') {
103-
// Completed info logs only
10499
const condition = and(
105100
eq(workflowExecutionLogs.level, 'info'),
106101
isNotNull(workflowExecutionLogs.endedAt)
107102
)
108103
if (condition) levelConditions.push(condition)
109104
} else if (level === 'running') {
110-
// Running logs: info level with no endedAt
111105
const condition = and(
112106
eq(workflowExecutionLogs.level, 'info'),
113107
isNull(workflowExecutionLogs.endedAt)
114108
)
115109
if (condition) levelConditions.push(condition)
116110
} else if (level === 'pending') {
117-
// Pending logs: info level with pause status indicators
118111
const condition = and(
119112
eq(workflowExecutionLogs.level, 'info'),
120113
or(
@@ -132,10 +125,55 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
132125
if (levelConditions.length > 0) {
133126
const combinedCondition =
134127
levelConditions.length === 1 ? levelConditions[0] : or(...levelConditions)
135-
if (combinedCondition) logWhere.push(combinedCondition)
128+
if (combinedCondition) baseLogWhere.push(combinedCondition)
136129
}
137130
}
138131

132+
if (isAllTime) {
133+
const boundsQuery = db
134+
.select({
135+
minDate: sql<Date>`MIN(${workflowExecutionLogs.startedAt})`,
136+
maxDate: sql<Date>`MAX(${workflowExecutionLogs.startedAt})`,
137+
})
138+
.from(workflowExecutionLogs)
139+
.leftJoin(
140+
pausedExecutions,
141+
eq(pausedExecutions.executionId, workflowExecutionLogs.executionId)
142+
)
143+
.where(and(...baseLogWhere))
144+
145+
const [bounds] = await boundsQuery
146+
147+
if (bounds?.minDate && bounds?.maxDate) {
148+
start = new Date(bounds.minDate)
149+
end = new Date(Math.max(new Date(bounds.maxDate).getTime(), Date.now()))
150+
} else {
151+
return NextResponse.json({
152+
workflows: workflows.map((wf) => ({
153+
workflowId: wf.id,
154+
workflowName: wf.name,
155+
segments: [],
156+
})),
157+
startTime: new Date().toISOString(),
158+
endTime: new Date().toISOString(),
159+
segmentMs: 0,
160+
})
161+
}
162+
}
163+
164+
if (start >= end) {
165+
return NextResponse.json({ error: 'Invalid time range' }, { status: 400 })
166+
}
167+
168+
const totalMs = Math.max(1, end.getTime() - start.getTime())
169+
const segmentMs = Math.max(1, Math.floor(totalMs / Math.max(1, segments)))
170+
171+
const logWhere = [
172+
...baseLogWhere,
173+
gte(workflowExecutionLogs.startedAt, start),
174+
lte(workflowExecutionLogs.startedAt, end),
175+
]
176+
139177
const logs = await db
140178
.select({
141179
workflowId: workflowExecutionLogs.workflowId,
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
export type { LineChartMultiSeries, LineChartPoint } from './line-chart'
2-
export { default, LineChart } from './line-chart'
1+
export { default, LineChart, type LineChartMultiSeries, type LineChartPoint } from './line-chart'

apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/components/line-chart/line-chart.tsx

Lines changed: 110 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useRef, useState } from 'react'
1+
import { memo, useEffect, useMemo, useRef, useState } from 'react'
22
import { cn } from '@/lib/core/utils/cn'
33
import { formatDate, formatLatency } from '@/app/workspace/[workspaceId]/logs/utils'
44

@@ -15,7 +15,7 @@ export interface LineChartMultiSeries {
1515
dashed?: boolean
1616
}
1717

18-
export function LineChart({
18+
function LineChartComponent({
1919
data,
2020
label,
2121
color,
@@ -95,92 +95,92 @@ export function LineChart({
9595

9696
const hasExternalWrapper = !label || label === ''
9797

98-
if (containerWidth === null) {
99-
return (
100-
<div
101-
ref={containerRef}
102-
className={cn('w-full', !hasExternalWrapper && 'rounded-lg border bg-card p-4')}
103-
style={{ height }}
104-
/>
105-
)
106-
}
107-
108-
if (data.length === 0) {
109-
return (
110-
<div
111-
className={cn(
112-
'flex items-center justify-center',
113-
!hasExternalWrapper && 'rounded-lg border bg-card p-4'
114-
)}
115-
style={{ width, height }}
116-
>
117-
<p className='text-muted-foreground text-sm'>No data</p>
118-
</div>
119-
)
120-
}
98+
const allSeries = useMemo(
99+
() =>
100+
(Array.isArray(series) && series.length > 0
101+
? [{ id: 'base', label, color, data }, ...series]
102+
: [{ id: 'base', label, color, data }]
103+
).map((s, idx) => ({ ...s, id: s.id || s.label || String(idx) })),
104+
[series, label, color, data]
105+
)
121106

122-
const allSeries = (
123-
Array.isArray(series) && series.length > 0
124-
? [{ id: 'base', label, color, data }, ...series]
125-
: [{ id: 'base', label, color, data }]
126-
).map((s, idx) => ({ ...s, id: s.id || s.label || String(idx) }))
127-
128-
const flatValues = allSeries.flatMap((s) => s.data.map((d) => d.value))
129-
const rawMax = Math.max(...flatValues, 1)
130-
const rawMin = Math.min(...flatValues, 0)
131-
const paddedMax = rawMax === 0 ? 1 : rawMax * 1.1
132-
const paddedMin = Math.min(0, rawMin)
133-
const unitSuffixPre = (unit || '').trim().toLowerCase()
134-
let maxValue = Math.ceil(paddedMax)
135-
let minValue = Math.floor(paddedMin)
136-
if (unitSuffixPre === 'ms' || unitSuffixPre === 'latency') {
137-
minValue = 0
138-
if (paddedMax < 10) {
139-
maxValue = Math.ceil(paddedMax)
140-
} else if (paddedMax < 100) {
141-
maxValue = Math.ceil(paddedMax / 10) * 10
142-
} else if (paddedMax < 1000) {
143-
maxValue = Math.ceil(paddedMax / 50) * 50
144-
} else if (paddedMax < 10000) {
145-
maxValue = Math.ceil(paddedMax / 500) * 500
146-
} else {
147-
maxValue = Math.ceil(paddedMax / 1000) * 1000
107+
const { maxValue, minValue, valueRange } = useMemo(() => {
108+
const flatValues = allSeries.flatMap((s) => s.data.map((d) => d.value))
109+
const rawMax = Math.max(...flatValues, 1)
110+
const rawMin = Math.min(...flatValues, 0)
111+
const paddedMax = rawMax === 0 ? 1 : rawMax * 1.1
112+
const paddedMin = Math.min(0, rawMin)
113+
const unitSuffixPre = (unit || '').trim().toLowerCase()
114+
let maxVal = Math.ceil(paddedMax)
115+
let minVal = Math.floor(paddedMin)
116+
if (unitSuffixPre === 'ms' || unitSuffixPre === 'latency') {
117+
minVal = 0
118+
if (paddedMax < 10) {
119+
maxVal = Math.ceil(paddedMax)
120+
} else if (paddedMax < 100) {
121+
maxVal = Math.ceil(paddedMax / 10) * 10
122+
} else if (paddedMax < 1000) {
123+
maxVal = Math.ceil(paddedMax / 50) * 50
124+
} else if (paddedMax < 10000) {
125+
maxVal = Math.ceil(paddedMax / 500) * 500
126+
} else {
127+
maxVal = Math.ceil(paddedMax / 1000) * 1000
128+
}
148129
}
149-
}
150-
const valueRange = maxValue - minValue || 1
130+
return {
131+
maxValue: maxVal,
132+
minValue: minVal,
133+
valueRange: maxVal - minVal || 1,
134+
}
135+
}, [allSeries, unit])
151136

152137
const yMin = padding.top + 3
153138
const yMax = padding.top + chartHeight - 3
154139

155-
const scaledPoints = data.map((d, i) => {
156-
const usableW = Math.max(1, chartWidth)
157-
const x = padding.left + (i / (data.length - 1 || 1)) * usableW
158-
const rawY = padding.top + chartHeight - ((d.value - minValue) / valueRange) * chartHeight
159-
const y = Math.max(yMin, Math.min(yMax, rawY))
160-
return { x, y }
161-
})
162-
163-
const scaledSeries = allSeries.map((s) => {
164-
const pts = s.data.map((d, i) => {
165-
const usableW = Math.max(1, chartWidth)
166-
const x = padding.left + (i / (s.data.length - 1 || 1)) * usableW
167-
const rawY = padding.top + chartHeight - ((d.value - minValue) / valueRange) * chartHeight
168-
const y = Math.max(yMin, Math.min(yMax, rawY))
169-
return { x, y }
170-
})
171-
return { ...s, pts }
172-
})
140+
const scaledPoints = useMemo(
141+
() =>
142+
data.map((d, i) => {
143+
const usableW = Math.max(1, chartWidth)
144+
const x = padding.left + (i / (data.length - 1 || 1)) * usableW
145+
const rawY = padding.top + chartHeight - ((d.value - minValue) / valueRange) * chartHeight
146+
const y = Math.max(yMin, Math.min(yMax, rawY))
147+
return { x, y }
148+
}),
149+
[data, chartWidth, chartHeight, minValue, valueRange, yMin, yMax, padding.left, padding.top]
150+
)
151+
152+
const scaledSeries = useMemo(
153+
() =>
154+
allSeries.map((s) => {
155+
const pts = s.data.map((d, i) => {
156+
const usableW = Math.max(1, chartWidth)
157+
const x = padding.left + (i / (s.data.length - 1 || 1)) * usableW
158+
const rawY = padding.top + chartHeight - ((d.value - minValue) / valueRange) * chartHeight
159+
const y = Math.max(yMin, Math.min(yMax, rawY))
160+
return { x, y }
161+
})
162+
return { ...s, pts }
163+
}),
164+
[
165+
allSeries,
166+
chartWidth,
167+
chartHeight,
168+
minValue,
169+
valueRange,
170+
yMin,
171+
yMax,
172+
padding.left,
173+
padding.top,
174+
]
175+
)
173176

174177
const getSeriesById = (id?: string | null) => scaledSeries.find((s) => s.id === id)
175-
const visibleSeries = activeSeriesId
176-
? scaledSeries.filter((s) => s.id === activeSeriesId)
177-
: scaledSeries
178-
const orderedSeries = (() => {
179-
if (!activeSeriesId) return visibleSeries
180-
return visibleSeries
181-
})()
182-
183-
const pathD = (() => {
178+
const visibleSeries = useMemo(
179+
() => (activeSeriesId ? scaledSeries.filter((s) => s.id === activeSeriesId) : scaledSeries),
180+
[activeSeriesId, scaledSeries]
181+
)
182+
183+
const pathD = useMemo(() => {
184184
if (scaledPoints.length <= 1) return ''
185185
const p = scaledPoints
186186
const tension = 0.2
@@ -199,7 +199,7 @@ export function LineChart({
199199
d += ` C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${p2.x} ${p2.y}`
200200
}
201201
return d
202-
})()
202+
}, [scaledPoints, yMin, yMax])
203203

204204
const getCompactDateLabel = (timestamp?: string) => {
205205
if (!timestamp) return ''
@@ -222,6 +222,30 @@ export function LineChart({
222222
const currentHoverDate =
223223
hoverIndex !== null && data[hoverIndex] ? getCompactDateLabel(data[hoverIndex].timestamp) : ''
224224

225+
if (containerWidth === null) {
226+
return (
227+
<div
228+
ref={containerRef}
229+
className={cn('w-full', !hasExternalWrapper && 'rounded-lg border bg-card p-4')}
230+
style={{ height }}
231+
/>
232+
)
233+
}
234+
235+
if (data.length === 0) {
236+
return (
237+
<div
238+
className={cn(
239+
'flex items-center justify-center',
240+
!hasExternalWrapper && 'rounded-lg border bg-card p-4'
241+
)}
242+
style={{ width, height }}
243+
>
244+
<p className='text-muted-foreground text-sm'>No data</p>
245+
</div>
246+
)
247+
}
248+
225249
return (
226250
<div
227251
ref={containerRef}
@@ -386,7 +410,7 @@ export function LineChart({
386410
)
387411
})()}
388412

389-
{orderedSeries.map((s, idx) => {
413+
{visibleSeries.map((s, idx) => {
390414
const isActive = activeSeriesId ? activeSeriesId === s.id : true
391415
const isHovered = hoverSeriesId ? hoverSeriesId === s.id : false
392416
const baseOpacity = isActive ? 1 : 0.12
@@ -682,4 +706,8 @@ export function LineChart({
682706
)
683707
}
684708

709+
/**
710+
* Memoized LineChart component to prevent re-renders when parent updates.
711+
*/
712+
export const LineChart = memo(LineChartComponent)
685713
export default LineChart

apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/components/status-bar/status-bar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export function StatusBar({
3636
const end = new Date(start.getTime() + (segmentDurationMs || 0))
3737
const rangeLabel = Number.isNaN(start.getTime())
3838
? ''
39-
: `${start.toLocaleString('en-US', { month: 'short', day: 'numeric', hour: 'numeric' })}${end.toLocaleString('en-US', { hour: 'numeric', minute: '2-digit' })}`
39+
: `${start.toLocaleString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' })}${end.toLocaleString('en-US', { hour: 'numeric', minute: '2-digit' })}`
4040
return {
4141
rangeLabel,
4242
successLabel: `${segment.successRate.toFixed(1)}%`,

0 commit comments

Comments
 (0)