Skip to content

Commit 8d86023

Browse files
waleedlatif1claude
andcommitted
refactor(logs): migrate stores/components to contract types
Replace the legacy `WorkflowLog` / `LogsResponse` / `WorkflowData` / `CostMetadata` / `ToolCallMetadata` shapes in `stores/logs/filters/types.ts` with direct use of the contract types `WorkflowLogSummary`, `WorkflowLogDetail`, and a new `WorkflowLogRow` alias for surfaces that render either form. Removes the `summaryToWorkflowLog` / `detailToWorkflowLog` bridge in the React Query layer along with their double-cast annotations. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent fd7ea59 commit 8d86023

8 files changed

Lines changed: 55 additions & 157 deletions

File tree

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ import {
2222
SModalTabsTrigger,
2323
Tooltip,
2424
} from '@/components/emcn'
25+
import type { WorkflowLogRow } from '@/lib/api/contracts/logs'
2526
import { BASE_EXECUTION_CHARGE } from '@/lib/billing/constants'
2627
import { cn } from '@/lib/core/utils/cn'
2728
import { filterHiddenOutputKeys } from '@/lib/logs/execution/trace-spans/trace-spans'
29+
import type { TraceSpan } from '@/lib/logs/types'
2830
import { workflowBorderColor } from '@/lib/workspaces/colors'
2931
import {
3032
ExecutionSnapshot,
@@ -43,7 +45,6 @@ import {
4345
import { useCodeViewerFeatures } from '@/hooks/use-code-viewer'
4446
import { usePermissionConfig } from '@/hooks/use-permission-config'
4547
import { formatCost } from '@/providers/utils'
46-
import type { WorkflowLog } from '@/stores/logs/filters/types'
4748
import { useLogDetailsUIStore } from '@/stores/logs/store'
4849
import { MAX_LOG_DETAILS_WIDTH_RATIO, MIN_LOG_DETAILS_WIDTH } from '@/stores/logs/utils'
4950

@@ -258,7 +259,7 @@ export type LogDetailsTab = 'overview' | 'trace'
258259

259260
interface LogDetailsContentProps {
260261
/** The log to display */
261-
log: WorkflowLog
262+
log: WorkflowLogRow
262263
/** Fires when the active tab changes, so embedders can gate their own keyboard handlers */
263264
onActiveTabChange?: (tab: LogDetailsTab) => void
264265
}
@@ -309,7 +310,8 @@ export function LogDetailsContent({ log, onActiveTabChange }: LogDetailsContentP
309310
!permissionConfig.hideTraceSpans
310311

311312
const showTraceTab = !permissionConfig.hideTraceSpans && isLikelyExecution
312-
const traceSpans = log.executionData?.traceSpans
313+
// double-cast-allowed: contract trace span schema is intentionally permissive (optional duration/startTime/endTime to tolerate legacy persisted JSON); the canonical TraceSpan used by TraceView/ExecutionSnapshot requires them, and runtime data from the executor always supplies them.
314+
const traceSpans = log.executionData?.traceSpans as unknown as TraceSpan[] | undefined
313315

314316
const resolvedTab: LogDetailsTab = activeTab === 'trace' && !showTraceTab ? 'overview' : activeTab
315317

@@ -621,7 +623,7 @@ export function LogDetailsContent({ log, onActiveTabChange }: LogDetailsContentP
621623
{log.executionId && (
622624
<ExecutionSnapshot
623625
executionId={log.executionId}
624-
traceSpans={log.executionData?.traceSpans}
626+
traceSpans={traceSpans}
625627
isModal
626628
isOpen={isExecutionSnapshotOpen}
627629
onClose={() => setIsExecutionSnapshotOpen(false)}
@@ -633,7 +635,7 @@ export function LogDetailsContent({ log, onActiveTabChange }: LogDetailsContentP
633635

634636
interface LogDetailsProps {
635637
/** The log to display details for */
636-
log: WorkflowLog | null
638+
log: WorkflowLogRow | null
637639
/** Whether the sidebar is open */
638640
isOpen: boolean
639641
/** Callback when closing the sidebar */

apps/sim/app/workspace/[workspaceId]/logs/components/log-row-context-menu/log-row-context-menu.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ import {
1515
SquareArrowUpRight,
1616
X,
1717
} from '@/components/emcn'
18-
import type { WorkflowLog } from '@/stores/logs/filters/types'
18+
import type { WorkflowLogSummary } from '@/lib/api/contracts/logs'
1919

2020
interface LogRowContextMenuProps {
2121
isOpen: boolean
2222
position: { x: number; y: number }
2323
onClose: () => void
24-
log: WorkflowLog | null
24+
log: WorkflowLogSummary | null
2525
onCopyExecutionId: () => void
2626
onCopyLink: () => void
2727
onOpenWorkflow: () => void

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

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { ArrowUpRight } from 'lucide-react'
66
import Link from 'next/link'
77
import { List, type RowComponentProps, useListRef } from 'react-window'
88
import { Badge, buttonVariants, Loader } from '@/components/emcn'
9+
import type { WorkflowLogSummary } from '@/lib/api/contracts/logs'
910
import { dollarsToCredits } from '@/lib/billing/credits/conversion'
1011
import { cn } from '@/lib/core/utils/cn'
1112
import { workflowBorderColor } from '@/lib/workspaces/colors'
@@ -18,16 +19,15 @@ import {
1819
StatusBadge,
1920
TriggerBadge,
2021
} from '@/app/workspace/[workspaceId]/logs/utils'
21-
import type { WorkflowLog } from '@/stores/logs/filters/types'
2222

2323
const LOG_ROW_HEIGHT = 44 as const
2424

2525
interface LogRowProps {
26-
log: WorkflowLog
26+
log: WorkflowLogSummary
2727
isSelected: boolean
28-
onClick: (log: WorkflowLog) => void
29-
onHover?: (log: WorkflowLog) => void
30-
onContextMenu?: (e: React.MouseEvent, log: WorkflowLog) => void
28+
onClick: (log: WorkflowLogSummary) => void
29+
onHover?: (log: WorkflowLogSummary) => void
30+
onContextMenu?: (e: React.MouseEvent, log: WorkflowLogSummary) => void
3131
selectedRowRef: React.RefObject<HTMLTableRowElement | null> | null
3232
}
3333

@@ -56,7 +56,7 @@ const LogRow = memo(
5656
? '#ec4899'
5757
: isDeletedWorkflow
5858
? DELETED_WORKFLOW_COLOR
59-
: log.workflow?.color
59+
: (log.workflow?.color ?? undefined)
6060

6161
const handleClick = () => onClick(log)
6262
const handleMouseEnter = () => onHover?.(log)
@@ -164,11 +164,11 @@ const LogRow = memo(
164164
)
165165

166166
interface RowProps {
167-
logs: WorkflowLog[]
167+
logs: WorkflowLogSummary[]
168168
selectedLogId: string | null
169-
onLogClick: (log: WorkflowLog) => void
170-
onLogHover?: (log: WorkflowLog) => void
171-
onLogContextMenu?: (e: React.MouseEvent, log: WorkflowLog) => void
169+
onLogClick: (log: WorkflowLogSummary) => void
170+
onLogHover?: (log: WorkflowLogSummary) => void
171+
onLogContextMenu?: (e: React.MouseEvent, log: WorkflowLogSummary) => void
172172
selectedRowRef: React.RefObject<HTMLTableRowElement | null>
173173
isFetchingNextPage: boolean
174174
loaderRef: React.RefObject<HTMLDivElement | null>
@@ -225,11 +225,11 @@ function Row({
225225
}
226226

227227
export interface LogsListProps {
228-
logs: WorkflowLog[]
228+
logs: WorkflowLogSummary[]
229229
selectedLogId: string | null
230-
onLogClick: (log: WorkflowLog) => void
231-
onLogHover?: (log: WorkflowLog) => void
232-
onLogContextMenu?: (e: React.MouseEvent, log: WorkflowLog) => void
230+
onLogClick: (log: WorkflowLogSummary) => void
231+
onLogHover?: (log: WorkflowLogSummary) => void
232+
onLogContextMenu?: (e: React.MouseEvent, log: WorkflowLogSummary) => void
233233
selectedRowRef: React.RefObject<HTMLTableRowElement | null>
234234
hasNextPage: boolean
235235
isFetchingNextPage: boolean

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ import {
1616
RefreshCw,
1717
toast,
1818
} from '@/components/emcn'
19+
import type {
20+
WorkflowLogDetail,
21+
WorkflowLogRow,
22+
WorkflowLogSummary,
23+
} from '@/lib/api/contracts/logs'
1924
import { dollarsToCredits } from '@/lib/billing/credits/conversion'
2025
import { cn } from '@/lib/core/utils/cn'
2126
import {
@@ -65,7 +70,6 @@ import {
6570
import { useWorkflowMap, useWorkflows } from '@/hooks/queries/workflows'
6671
import { useDebounce } from '@/hooks/use-debounce'
6772
import { useFilterStore } from '@/stores/logs/filters/store'
68-
import type { WorkflowLog } from '@/stores/logs/filters/types'
6973
import { CORE_TRIGGER_TYPES } from '@/stores/logs/filters/types'
7074
import {
7175
Dashboard,
@@ -286,7 +290,7 @@ export default function Logs() {
286290
const [isVisuallyRefreshing, setIsVisuallyRefreshing] = useState(false)
287291
const [isExporting, setIsExporting] = useState(false)
288292
const refreshTimersRef = useRef(new Set<number>())
289-
const logsRef = useRef<WorkflowLog[]>([])
293+
const logsRef = useRef<WorkflowLogSummary[]>([])
290294
const selectedLogIndexRef = useRef(-1)
291295
const selectedLogIdRef = useRef<string | null>(null)
292296
const shouldScrollIntoViewRef = useRef(false)
@@ -303,14 +307,14 @@ export default function Logs() {
303307

304308
const [contextMenuOpen, setContextMenuOpen] = useState(false)
305309
const [contextMenuPosition, setContextMenuPosition] = useState({ x: 0, y: 0 })
306-
const [contextMenuLog, setContextMenuLog] = useState<WorkflowLog | null>(null)
310+
const [contextMenuLog, setContextMenuLog] = useState<WorkflowLogSummary | null>(null)
307311

308312
const [previewLogId, setPreviewLogId] = useState<string | null>(null)
309313

310314
const queryClient = useQueryClient()
311315

312316
const refetchInterval = useCallback(
313-
(query: { state: { data?: WorkflowLog } }) => {
317+
(query: { state: { data?: WorkflowLogDetail } }) => {
314318
if (!isLive) return false
315319
const status = query.state.data?.status
316320
return status === 'running' || status === 'pending' ? 3000 : false
@@ -528,7 +532,7 @@ export default function Logs() {
528532
}, [contextMenuLog])
529533

530534
const retryLog = useCallback(
531-
async (log: WorkflowLog | null) => {
535+
async (log: WorkflowLogRow | null) => {
532536
const workflowId = log?.workflow?.id || log?.workflowId
533537
const logId = log?.id
534538
if (!workflowId || !logId) return

apps/sim/app/workspace/[workspaceId]/logs/utils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import React from 'react'
22
import { formatDuration } from '@sim/utils/formatting'
33
import { format } from 'date-fns'
44
import { Badge } from '@/components/emcn'
5+
import type { WorkflowLogDetail } from '@/lib/api/contracts/logs'
56
import { getIntegrationMetadata } from '@/lib/logs/get-trigger-options'
67
import { getBlock } from '@/blocks/registry'
7-
import type { WorkflowLog } from '@/stores/logs/filters/types'
88
import { CORE_TRIGGER_TYPES } from '@/stores/logs/filters/types'
99

1010
export const LOG_COLUMNS = {
@@ -449,15 +449,15 @@ export const formatDate = (dateString: string) => {
449449
* Prefers the persisted `workflowInput` field (new logs), falls back to
450450
* reconstructing from `executionState.blockStates` (old logs).
451451
*/
452-
export function extractRetryInput(log: WorkflowLog): unknown | undefined {
453-
const execData = log.executionData as Record<string, unknown> | undefined
452+
export function extractRetryInput(log: WorkflowLogDetail): unknown | undefined {
453+
const execData = log.executionData
454454
if (!execData) return undefined
455455

456456
if (execData.workflowInput !== undefined) {
457457
return execData.workflowInput
458458
}
459459

460-
const executionState = execData.executionState as
460+
const executionState = (execData as Record<string, unknown>).executionState as
461461
| {
462462
blockStates?: Record<
463463
string,

apps/sim/hooks/queries/logs.ts

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
} from '@/lib/api/contracts/logs'
2525
import { getEndDateFromTimeRange, getStartDateFromTimeRange } from '@/lib/logs/filters'
2626
import { parseQuery, queryToApiParams } from '@/lib/logs/query-parser'
27-
import type { TimeRange, WorkflowLog } from '@/stores/logs/filters/types'
27+
import type { TimeRange } from '@/stores/logs/filters/types'
2828

2929
export type { DashboardStatsResponse, SegmentStats, WorkflowStats }
3030

@@ -63,11 +63,6 @@ export interface LogFilters {
6363
sortOrder: LogSortOrder
6464
}
6565

66-
// double-cast-allowed: bridge from contract type to legacy WorkflowLog used by stores/components
67-
const summaryToWorkflowLog = (log: WorkflowLogSummary): WorkflowLog => log as unknown as WorkflowLog
68-
// double-cast-allowed: bridge from contract type to legacy WorkflowLog used by stores/components
69-
const detailToWorkflowLog = (log: WorkflowLogDetail): WorkflowLog => log as unknown as WorkflowLog
70-
7166
function applyFilterParams(
7267
params: URLSearchParams,
7368
filters: Omit<LogFilters, 'limit' | 'sortBy' | 'sortOrder'>
@@ -123,7 +118,7 @@ function buildListQuery(workspaceId: string, filters: LogFilters, cursor: string
123118
}
124119

125120
interface LogsPage {
126-
logs: WorkflowLog[]
121+
logs: WorkflowLogSummary[]
127122
nextCursor: string | null
128123
}
129124

@@ -139,7 +134,7 @@ async function fetchLogsPage(
139134
})
140135

141136
return {
142-
logs: apiData.data.map(summaryToWorkflowLog),
137+
logs: apiData.data,
143138
nextCursor: apiData.nextCursor,
144139
}
145140
}
@@ -148,13 +143,13 @@ export async function fetchLogDetail(
148143
logId: string,
149144
workspaceId: string,
150145
signal?: AbortSignal
151-
): Promise<WorkflowLog> {
146+
): Promise<WorkflowLogDetail> {
152147
const { data } = await requestJson(getLogDetailContract, {
153148
params: { id: logId },
154149
query: { workspaceId },
155150
signal,
156151
})
157-
return detailToWorkflowLog(data)
152+
return data
158153
}
159154

160155
interface UseLogsListOptions {
@@ -185,7 +180,7 @@ interface UseLogDetailOptions {
185180
refetchInterval?:
186181
| number
187182
| false
188-
| ((query: { state: { data?: WorkflowLog } }) => number | false | undefined)
183+
| ((query: { state: { data?: WorkflowLogDetail } }) => number | false | undefined)
189184
}
190185

191186
export function useLogDetail(
@@ -220,9 +215,8 @@ export function useLogByExecutionId(
220215
query: { workspaceId: workspaceId as string },
221216
signal,
222217
})
223-
const log = detailToWorkflowLog(data)
224-
queryClient.setQueryData(logKeys.detail(log.id), log)
225-
return log
218+
queryClient.setQueryData(logKeys.detail(data.id), data)
219+
return data
226220
},
227221
enabled: Boolean(workspaceId) && Boolean(executionId),
228222
staleTime: 30 * 1000,
@@ -339,11 +333,11 @@ export function useCancelExecution() {
339333
}
340334
})
341335

342-
let previousDetail: WorkflowLog | undefined
336+
let previousDetail: WorkflowLogDetail | undefined
343337
if (affectedLogId) {
344-
previousDetail = queryClient.getQueryData<WorkflowLog>(logKeys.detail(affectedLogId))
338+
previousDetail = queryClient.getQueryData<WorkflowLogDetail>(logKeys.detail(affectedLogId))
345339
if (previousDetail) {
346-
queryClient.setQueryData<WorkflowLog>(logKeys.detail(affectedLogId), {
340+
queryClient.setQueryData<WorkflowLogDetail>(logKeys.detail(affectedLogId), {
347341
...previousDetail,
348342
status: 'cancelling',
349343
})

apps/sim/lib/api/contracts/logs.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,13 @@ export const workflowLogDetailSchema = workflowLogSummarySchema.extend({
243243
export type WorkflowLogSummary = z.output<typeof workflowLogSummarySchema>
244244
export type WorkflowLogDetail = z.output<typeof workflowLogDetailSchema>
245245

246+
/**
247+
* A row that may be either a list-view summary or a fully loaded detail. Used by
248+
* UI surfaces that render the same log before and after its detail query resolves.
249+
*/
250+
export type WorkflowLogRow = WorkflowLogSummary &
251+
Partial<Pick<WorkflowLogDetail, 'executionData' | 'files'>>
252+
246253
export const listLogsResponseSchema = z.object({
247254
data: z.array(workflowLogSummarySchema),
248255
nextCursor: z.string().nullable(),

0 commit comments

Comments
 (0)