Skip to content

Commit 72d613e

Browse files
committed
fix(logs): instant log detail panel with initialData and hover prefetch
1 parent 3ef6b05 commit 72d613e

File tree

3 files changed

+85
-7
lines changed

3 files changed

+85
-7
lines changed

apps/sim/app/workspace/[workspaceId]/logs/components/logs-list/logs-list.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ interface LogRowProps {
2424
log: WorkflowLog
2525
isSelected: boolean
2626
onClick: (log: WorkflowLog) => void
27+
onHover?: (log: WorkflowLog) => void
2728
onContextMenu?: (e: React.MouseEvent, log: WorkflowLog) => void
2829
selectedRowRef: React.RefObject<HTMLTableRowElement | null> | null
2930
}
@@ -33,7 +34,14 @@ interface LogRowProps {
3334
* Uses shallow comparison for the log object.
3435
*/
3536
const LogRow = memo(
36-
function LogRow({ log, isSelected, onClick, onContextMenu, selectedRowRef }: LogRowProps) {
37+
function LogRow({
38+
log,
39+
isSelected,
40+
onClick,
41+
onHover,
42+
onContextMenu,
43+
selectedRowRef,
44+
}: LogRowProps) {
3745
const formattedDate = useMemo(() => formatDate(log.createdAt), [log.createdAt])
3846
const isDeletedWorkflow = !log.workflow?.id && !log.workflowId
3947
const workflowName = isDeletedWorkflow
@@ -43,6 +51,8 @@ const LogRow = memo(
4351

4452
const handleClick = useCallback(() => onClick(log), [onClick, log])
4553

54+
const handleMouseEnter = useCallback(() => onHover?.(log), [onHover, log])
55+
4656
const handleContextMenu = useCallback(
4757
(e: React.MouseEvent) => {
4858
if (onContextMenu) {
@@ -61,6 +71,7 @@ const LogRow = memo(
6171
isSelected && 'bg-[var(--surface-3)] dark:bg-[var(--surface-4)]'
6272
)}
6373
onClick={handleClick}
74+
onMouseEnter={handleMouseEnter}
6475
onContextMenu={handleContextMenu}
6576
>
6677
<div className='flex flex-1 items-center'>
@@ -142,7 +153,8 @@ const LogRow = memo(
142153
prevProps.log.id === nextProps.log.id &&
143154
prevProps.log.duration === nextProps.log.duration &&
144155
prevProps.log.status === nextProps.log.status &&
145-
prevProps.isSelected === nextProps.isSelected
156+
prevProps.isSelected === nextProps.isSelected &&
157+
prevProps.onHover === nextProps.onHover
146158
)
147159
}
148160
)
@@ -151,6 +163,7 @@ interface RowProps {
151163
logs: WorkflowLog[]
152164
selectedLogId: string | null
153165
onLogClick: (log: WorkflowLog) => void
166+
onLogHover?: (log: WorkflowLog) => void
154167
onLogContextMenu?: (e: React.MouseEvent, log: WorkflowLog) => void
155168
selectedRowRef: React.RefObject<HTMLTableRowElement | null>
156169
isFetchingNextPage: boolean
@@ -167,6 +180,7 @@ function Row({
167180
logs,
168181
selectedLogId,
169182
onLogClick,
183+
onLogHover,
170184
onLogContextMenu,
171185
selectedRowRef,
172186
isFetchingNextPage,
@@ -198,6 +212,7 @@ function Row({
198212
log={log}
199213
isSelected={isSelected}
200214
onClick={onLogClick}
215+
onHover={onLogHover}
201216
onContextMenu={onLogContextMenu}
202217
selectedRowRef={isSelected ? selectedRowRef : null}
203218
/>
@@ -209,6 +224,7 @@ export interface LogsListProps {
209224
logs: WorkflowLog[]
210225
selectedLogId: string | null
211226
onLogClick: (log: WorkflowLog) => void
227+
onLogHover?: (log: WorkflowLog) => void
212228
onLogContextMenu?: (e: React.MouseEvent, log: WorkflowLog) => void
213229
selectedRowRef: React.RefObject<HTMLTableRowElement | null>
214230
hasNextPage: boolean
@@ -227,6 +243,7 @@ export function LogsList({
227243
logs,
228244
selectedLogId,
229245
onLogClick,
246+
onLogHover,
230247
onLogContextMenu,
231248
selectedRowRef,
232249
hasNextPage,
@@ -272,6 +289,7 @@ export function LogsList({
272289
logs,
273290
selectedLogId,
274291
onLogClick,
292+
onLogHover,
275293
onLogContextMenu,
276294
selectedRowRef,
277295
isFetchingNextPage,
@@ -281,6 +299,7 @@ export function LogsList({
281299
logs,
282300
selectedLogId,
283301
onLogClick,
302+
onLogHover,
284303
onLogContextMenu,
285304
selectedRowRef,
286305
isFetchingNextPage,

apps/sim/app/workspace/[workspaceId]/logs/logs.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client'
22

33
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
4+
import { useQueryClient } from '@tanstack/react-query'
45
import { Loader2 } from 'lucide-react'
56
import { useParams } from 'next/navigation'
67
import { cn } from '@/lib/core/utils/cn'
@@ -11,7 +12,12 @@ import {
1112
} from '@/lib/logs/filters'
1213
import { parseQuery, queryToApiParams } from '@/lib/logs/query-parser'
1314
import { useFolders } from '@/hooks/queries/folders'
14-
import { useDashboardStats, useLogDetail, useLogsList } from '@/hooks/queries/logs'
15+
import {
16+
prefetchLogDetail,
17+
useDashboardStats,
18+
useLogDetail,
19+
useLogsList,
20+
} from '@/hooks/queries/logs'
1521
import { useDebounce } from '@/hooks/use-debounce'
1622
import { useFilterStore } from '@/stores/logs/filters/store'
1723
import type { WorkflowLog } from '@/stores/logs/filters/types'
@@ -94,8 +100,19 @@ export default function Logs() {
94100
const [previewLogId, setPreviewLogId] = useState<string | null>(null)
95101

96102
const activeLogId = isPreviewOpen ? previewLogId : selectedLogId
103+
const queryClient = useQueryClient()
104+
105+
const detailRefetchInterval = useCallback(
106+
(query: { state: { data?: WorkflowLog } }) => {
107+
if (!isLive) return false
108+
const status = query.state.data?.status
109+
return status === 'running' || status === 'pending' ? 3000 : false
110+
},
111+
[isLive]
112+
)
113+
97114
const activeLogQuery = useLogDetail(activeLogId ?? undefined, {
98-
refetchInterval: isLive ? 3000 : false,
115+
refetchInterval: detailRefetchInterval,
99116
})
100117

101118
const logFilters = useMemo(
@@ -154,6 +171,13 @@ export default function Logs() {
154171
return { ...selectedLogFromList, ...activeLogQuery.data }
155172
}, [selectedLogFromList, activeLogQuery.data, isPreviewOpen])
156173

174+
const handleLogHover = useCallback(
175+
(log: WorkflowLog) => {
176+
prefetchLogDetail(queryClient, log.id)
177+
},
178+
[queryClient]
179+
)
180+
157181
useFolders(workspaceId)
158182

159183
useEffect(() => {
@@ -476,6 +500,7 @@ export default function Logs() {
476500
logs={logs}
477501
selectedLogId={selectedLogId}
478502
onLogClick={handleLogClick}
503+
onLogHover={handleLogHover}
479504
onLogContextMenu={handleLogContextMenu}
480505
selectedRowRef={selectedRowRef}
481506
hasNextPage={logsQuery.hasNextPage ?? false}

apps/sim/hooks/queries/logs.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { keepPreviousData, useInfiniteQuery, useQuery } from '@tanstack/react-query'
1+
import {
2+
keepPreviousData,
3+
type QueryClient,
4+
useInfiniteQuery,
5+
useQuery,
6+
useQueryClient,
7+
} from '@tanstack/react-query'
28
import { getEndDateFromTimeRange, getStartDateFromTimeRange } from '@/lib/logs/filters'
39
import { parseQuery, queryToApiParams } from '@/lib/logs/query-parser'
410
import type {
@@ -146,17 +152,45 @@ export function useLogsList(
146152

147153
interface UseLogDetailOptions {
148154
enabled?: boolean
149-
refetchInterval?: number | false
155+
refetchInterval?:
156+
| number
157+
| false
158+
| ((query: { state: { data?: WorkflowLog } }) => number | false | undefined)
150159
}
151160

152161
export function useLogDetail(logId: string | undefined, options?: UseLogDetailOptions) {
162+
const queryClient = useQueryClient()
153163
return useQuery({
154164
queryKey: logKeys.detail(logId),
155165
queryFn: () => fetchLogDetail(logId as string),
156166
enabled: Boolean(logId) && (options?.enabled ?? true),
157167
refetchInterval: options?.refetchInterval ?? false,
158168
staleTime: 30 * 1000,
159-
placeholderData: keepPreviousData,
169+
initialData: () => {
170+
if (!logId) return undefined
171+
const listQueries = queryClient.getQueriesData<{
172+
pages: { logs: WorkflowLog[] }[]
173+
}>({
174+
queryKey: logKeys.lists(),
175+
})
176+
for (const [, data] of listQueries) {
177+
const match = data?.pages?.flatMap((p) => p.logs).find((l) => l.id === logId)
178+
if (match) return match
179+
}
180+
return undefined
181+
},
182+
initialDataUpdatedAt: 0,
183+
})
184+
}
185+
186+
/**
187+
* Prefetches log detail data on hover for instant panel rendering on click.
188+
*/
189+
export function prefetchLogDetail(queryClient: QueryClient, logId: string) {
190+
queryClient.prefetchQuery({
191+
queryKey: logKeys.detail(logId),
192+
queryFn: () => fetchLogDetail(logId),
193+
staleTime: 30 * 1000,
160194
})
161195
}
162196

0 commit comments

Comments
 (0)