Skip to content

Commit e9836d0

Browse files
committed
improvement(preview): added error paths, loop logic
1 parent 1e47528 commit e9836d0

File tree

2 files changed

+110
-112
lines changed

2 files changed

+110
-112
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-edge/workflow-edge.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { EdgeDiffStatus } from '@/lib/workflows/diff/types'
66
import { useExecutionStore } from '@/stores/execution'
77
import { useWorkflowDiffStore } from '@/stores/workflow-diff'
88

9+
/** Extended edge props with optional handle identifiers */
910
interface WorkflowEdgeProps extends EdgeProps {
1011
sourceHandle?: string | null
1112
targetHandle?: string | null
@@ -90,15 +91,17 @@ const WorkflowEdgeComponent = ({
9091
if (edgeDiffStatus === 'deleted') {
9192
color = 'var(--text-error)'
9293
opacity = 0.7
93-
} else if (isErrorEdge) {
94-
color = 'var(--text-error)'
9594
} else if (edgeDiffStatus === 'new') {
9695
color = 'var(--brand-tertiary-2)'
9796
} else if (edgeRunStatus === 'success') {
9897
// Use green for preview mode, default for canvas execution
98+
// This also applies to error edges that were taken (error path executed)
9999
color = previewExecutionStatus ? 'var(--brand-tertiary-2)' : 'var(--border-success)'
100100
} else if (edgeRunStatus === 'error') {
101101
color = 'var(--text-error)'
102+
} else if (isErrorEdge) {
103+
// Error edges that weren't taken stay red
104+
color = 'var(--text-error)'
102105
}
103106

104107
if (isSelected) {
@@ -151,4 +154,14 @@ const WorkflowEdgeComponent = ({
151154
)
152155
}
153156

157+
/**
158+
* Workflow edge component with execution status and diff visualization.
159+
*
160+
* @remarks
161+
* Edge coloring priority:
162+
* 1. Diff status (deleted/new) - for version comparison
163+
* 2. Execution status (success/error) - for run visualization
164+
* 3. Error edge default (red) - for untaken error paths
165+
* 4. Default edge color - normal workflow connections
166+
*/
154167
export const WorkflowEdge = memo(WorkflowEdgeComponent)

apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor.tsx

Lines changed: 95 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -123,44 +123,31 @@ function formatInlineValue(value: unknown): string {
123123
return String(value)
124124
}
125125

126-
interface ExecutionDataSectionProps {
126+
interface CollapsibleSectionProps {
127127
title: string
128-
data: unknown
128+
defaultExpanded?: boolean
129+
children: React.ReactNode
130+
isEmpty?: boolean
131+
emptyMessage?: string
132+
/** Whether this section represents an error state (styles title red) */
129133
isError?: boolean
130-
wrapText?: boolean
131-
searchQuery?: string
132-
currentMatchIndex?: number
133-
onMatchCountChange?: (count: number) => void
134-
contentRef?: React.RefObject<HTMLDivElement | null>
135-
onContextMenu?: (e: React.MouseEvent) => void
136134
}
137135

138136
/**
139-
* Collapsible section for execution data (input/output)
140-
* Uses Code.Viewer for proper syntax highlighting matching the logs UI
137+
* Collapsible section wrapper for organizing preview editor content
141138
*/
142-
function ExecutionDataSection({
139+
function CollapsibleSection({
143140
title,
144-
data,
141+
defaultExpanded = false,
142+
children,
143+
isEmpty = false,
144+
emptyMessage = 'No data',
145145
isError = false,
146-
wrapText = true,
147-
searchQuery,
148-
currentMatchIndex = 0,
149-
onMatchCountChange,
150-
contentRef,
151-
onContextMenu,
152-
}: ExecutionDataSectionProps) {
153-
const [isExpanded, setIsExpanded] = useState(false)
154-
155-
const jsonString = useMemo(() => {
156-
if (!data) return ''
157-
return formatValueAsJson(data)
158-
}, [data])
159-
160-
const isEmpty = jsonString === '—' || jsonString === ''
146+
}: CollapsibleSectionProps) {
147+
const [isExpanded, setIsExpanded] = useState(defaultExpanded)
161148

162149
return (
163-
<div className='flex min-w-0 flex-col gap-[8px] overflow-hidden'>
150+
<div className='flex min-w-0 flex-col gap-[8px] overflow-hidden border-[var(--border)] border-b px-[12px] py-[10px]'>
164151
<div
165152
className='group flex cursor-pointer items-center justify-between'
166153
onClick={() => setIsExpanded(!isExpanded)}
@@ -199,20 +186,10 @@ function ExecutionDataSection({
199186
<>
200187
{isEmpty ? (
201188
<div className='rounded-[6px] bg-[var(--surface-3)] px-[10px] py-[8px]'>
202-
<span className='text-[12px] text-[var(--text-tertiary)]'>No data</span>
189+
<span className='text-[12px] text-[var(--text-tertiary)]'>{emptyMessage}</span>
203190
</div>
204191
) : (
205-
<div onContextMenu={onContextMenu} ref={contentRef}>
206-
<Code.Viewer
207-
code={jsonString}
208-
language='json'
209-
className='!bg-[var(--surface-3)] max-h-[300px] min-h-0 max-w-full rounded-[6px] border-0 [word-break:break-all]'
210-
wrapText={wrapText}
211-
searchQuery={searchQuery}
212-
currentMatchIndex={currentMatchIndex}
213-
onMatchCountChange={onMatchCountChange}
214-
/>
215-
</div>
192+
children
216193
)}
217194
</>
218195
)}
@@ -601,7 +578,7 @@ function SubflowConfigDisplay({ block, loop, parallel }: SubflowConfigDisplayPro
601578
}
602579

603580
return (
604-
<div className='flex-1 overflow-y-auto overflow-x-hidden pt-[5px] pb-[8px]'>
581+
<div className='flex-1 overflow-y-auto overflow-x-hidden pt-[8px] pb-[8px]'>
605582
{/* Type Selection - matches SubflowEditor */}
606583
<div>
607584
<Label className='mb-[6.5px] block pl-[2px] font-medium text-[13px] text-[var(--text-primary)]'>
@@ -1145,7 +1122,7 @@ function PreviewEditorContent({
11451122

11461123
{/* Content area */}
11471124
<div className='flex flex-1 flex-col overflow-hidden pt-[0px]'>
1148-
{/* Subblocks Section */}
1125+
{/* Main content sections */}
11491126
<div ref={subBlocksRef} className='subblocks-section flex flex-1 flex-col overflow-hidden'>
11501127
<div className='flex-1 overflow-y-auto overflow-x-hidden'>
11511128
{/* Not Executed Banner - shown when in execution mode but block wasn't executed */}
@@ -1159,91 +1136,99 @@ function PreviewEditorContent({
11591136
</div>
11601137
)}
11611138

1162-
{/* Execution Input/Output (if provided) */}
1163-
{executionData &&
1164-
(executionData.input !== undefined || executionData.output !== undefined) ? (
1165-
<div className='flex min-w-0 flex-col gap-[8px] overflow-hidden border-[var(--border)] border-b px-[12px] py-[10px]'>
1166-
{/* Execution Status & Duration Header */}
1167-
{(executionData.status || executionData.durationMs !== undefined) && (
1168-
<div className='flex items-center justify-between'>
1169-
{executionData.status && (
1170-
<Badge variant={statusVariant} size='sm' dot>
1171-
<span className='capitalize'>{executionData.status}</span>
1172-
</Badge>
1173-
)}
1174-
{executionData.durationMs !== undefined && (
1175-
<span className='font-medium text-[12px] text-[var(--text-tertiary)]'>
1176-
{formatDuration(executionData.durationMs, { precision: 2 })}
1177-
</span>
1178-
)}
1179-
</div>
1139+
{/* Execution Status & Duration Header */}
1140+
{executionData && (executionData.status || executionData.durationMs !== undefined) && (
1141+
<div className='flex min-w-0 items-center justify-between overflow-hidden border-[var(--border)] border-b px-[12px] py-[10px]'>
1142+
{executionData.status && (
1143+
<Badge variant={statusVariant} size='sm' dot>
1144+
<span className='capitalize'>{executionData.status}</span>
1145+
</Badge>
11801146
)}
1147+
{executionData.durationMs !== undefined && (
1148+
<span className='font-medium text-[12px] text-[var(--text-tertiary)]'>
1149+
{formatDuration(executionData.durationMs, { precision: 2 })}
1150+
</span>
1151+
)}
1152+
</div>
1153+
)}
11811154

1182-
{/* Divider between Status/Duration and Input/Output */}
1183-
{(executionData.status || executionData.durationMs !== undefined) &&
1184-
(executionData.input !== undefined || executionData.output !== undefined) && (
1185-
<div className='border-[var(--border)] border-t border-dashed' />
1186-
)}
1187-
1188-
{/* Input Section */}
1189-
{executionData.input !== undefined && (
1190-
<ExecutionDataSection
1191-
title='Input'
1192-
data={executionData.input}
1155+
{/* Input Section - Collapsible */}
1156+
{executionData?.input !== undefined && (
1157+
<CollapsibleSection
1158+
title='Input'
1159+
defaultExpanded={false}
1160+
isEmpty={
1161+
formatValueAsJson(executionData.input) === '—' ||
1162+
formatValueAsJson(executionData.input) === ''
1163+
}
1164+
emptyMessage='No input data'
1165+
>
1166+
<div onContextMenu={handleExecutionContextMenu} ref={contentRef}>
1167+
<Code.Viewer
1168+
code={formatValueAsJson(executionData.input)}
1169+
language='json'
1170+
className='!bg-[var(--surface-3)] max-h-[300px] min-h-0 max-w-full rounded-[6px] border-0 [word-break:break-all]'
11931171
wrapText={wrapText}
11941172
searchQuery={isSearchActive ? searchQuery : undefined}
11951173
currentMatchIndex={currentMatchIndex}
11961174
onMatchCountChange={handleMatchCountChange}
1197-
contentRef={contentRef}
1198-
onContextMenu={handleExecutionContextMenu}
11991175
/>
1200-
)}
1201-
1202-
{/* Divider between Input and Output */}
1203-
{executionData.input !== undefined && executionData.output !== undefined && (
1204-
<div className='border-[var(--border)] border-t border-dashed' />
1205-
)}
1176+
</div>
1177+
</CollapsibleSection>
1178+
)}
12061179

1207-
{/* Output Section */}
1208-
{executionData.output !== undefined && (
1209-
<ExecutionDataSection
1210-
title={executionData.status === 'error' ? 'Error' : 'Output'}
1211-
data={executionData.output}
1212-
isError={executionData.status === 'error'}
1180+
{/* Output Section - Collapsible, expanded by default */}
1181+
{executionData?.output !== undefined && (
1182+
<CollapsibleSection
1183+
title={executionData.status === 'error' ? 'Error' : 'Output'}
1184+
defaultExpanded={true}
1185+
isEmpty={
1186+
formatValueAsJson(executionData.output) === '—' ||
1187+
formatValueAsJson(executionData.output) === ''
1188+
}
1189+
emptyMessage='No output data'
1190+
isError={executionData.status === 'error'}
1191+
>
1192+
<div onContextMenu={handleExecutionContextMenu}>
1193+
<Code.Viewer
1194+
code={formatValueAsJson(executionData.output)}
1195+
language='json'
1196+
className={cn(
1197+
'!bg-[var(--surface-3)] max-h-[300px] min-h-0 max-w-full rounded-[6px] border-0 [word-break:break-all]',
1198+
executionData.status === 'error' && 'text-[var(--text-error)]'
1199+
)}
12131200
wrapText={wrapText}
12141201
searchQuery={isSearchActive ? searchQuery : undefined}
12151202
currentMatchIndex={currentMatchIndex}
12161203
onMatchCountChange={handleMatchCountChange}
1217-
contentRef={contentRef}
1218-
onContextMenu={handleExecutionContextMenu}
12191204
/>
1220-
)}
1221-
</div>
1222-
) : null}
1205+
</div>
1206+
</CollapsibleSection>
1207+
)}
12231208

12241209
{/* Subblock Values - Using SubBlock components in preview mode */}
1225-
<div className='readonly-preview px-[8px] py-[8px]'>
1210+
<div className='readonly-preview px-[8px] pt-[12px] pb-[8px]'>
12261211
{/* CSS override to show full opacity and prevent interaction instead of dimmed disabled state */}
12271212
<style>{`
1228-
.readonly-preview,
1229-
.readonly-preview * {
1230-
cursor: default !important;
1231-
}
1232-
.readonly-preview [disabled],
1233-
.readonly-preview [data-disabled],
1234-
.readonly-preview input,
1235-
.readonly-preview textarea,
1236-
.readonly-preview [role="combobox"],
1237-
.readonly-preview [role="slider"],
1238-
.readonly-preview [role="switch"],
1239-
.readonly-preview [role="checkbox"] {
1240-
opacity: 1 !important;
1241-
pointer-events: none;
1242-
}
1243-
.readonly-preview .opacity-50 {
1244-
opacity: 1 !important;
1245-
}
1246-
`}</style>
1213+
.readonly-preview,
1214+
.readonly-preview * {
1215+
cursor: default !important;
1216+
}
1217+
.readonly-preview [disabled],
1218+
.readonly-preview [data-disabled],
1219+
.readonly-preview input,
1220+
.readonly-preview textarea,
1221+
.readonly-preview [role="combobox"],
1222+
.readonly-preview [role="slider"],
1223+
.readonly-preview [role="switch"],
1224+
.readonly-preview [role="checkbox"] {
1225+
opacity: 1 !important;
1226+
pointer-events: none;
1227+
}
1228+
.readonly-preview .opacity-50 {
1229+
opacity: 1 !important;
1230+
}
1231+
`}</style>
12471232
{visibleSubBlocks.length > 0 ? (
12481233
<div className='flex flex-col'>
12491234
{visibleSubBlocks.map((subBlockConfig, index) => (

0 commit comments

Comments
 (0)