Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -108,26 +108,24 @@ export function getLeftmostBlockId(workflowState: WorkflowState | null | undefin
/** Execution status for edges/nodes in the preview */
type ExecutionStatus = 'success' | 'error' | 'not-executed'

/** Calculates absolute position, handling nested subflows. */
function calculateAbsolutePosition(
/** Calculates nesting depth, handling nested subflows. */
function calculateNestingDepth(
block: BlockState,
blocks: Record<string, BlockState>
): { x: number; y: number } {
if (!block.data?.parentId) {
return block.position
}

const parentBlock = blocks[block.data.parentId]
blocks: Record<string, BlockState>,
visited: Set<string> = new Set()
): number {
const parentId = block.data?.parentId
if (!parentId) return 0
if (visited.has(parentId)) return 0

const parentBlock = blocks[parentId]
if (!parentBlock) {
logger.warn(`Parent block not found for child block`)
return block.position
logger.warn('Parent block not found for child block')
return 0
}

const parentAbsolutePosition = calculateAbsolutePosition(parentBlock, blocks)
return {
x: parentAbsolutePosition.x + block.position.x,
y: parentAbsolutePosition.y + block.position.y,
}
visited.add(parentId)
return 1 + calculateNestingDepth(parentBlock, blocks, visited)
}

interface PreviewWorkflowProps {
Expand Down Expand Up @@ -329,7 +327,8 @@ export function PreviewWorkflow({

if (childStatuses.length === 0) return undefined
if (childStatuses.some((s) => s === 'error')) return 'error'
return 'success'
if (childStatuses.every((s) => s === 'success')) return 'success'
return undefined
}
return derive
}, [subflowChildrenMap, blockExecutionMap, workflowState.blocks])
Expand Down Expand Up @@ -367,13 +366,20 @@ export function PreviewWorkflow({

const nodeArray: Node[] = []

Object.entries(workflowState.blocks || {}).forEach(([blockId, block]) => {
const sortedBlocks = Object.entries(workflowState.blocks || {}).sort(
([, left], [, right]) =>
calculateNestingDepth(left, workflowState.blocks) -
calculateNestingDepth(right, workflowState.blocks)
)

sortedBlocks.forEach(([blockId, block]) => {
if (!block || !block.type) {
logger.warn(`Skipping invalid block: ${blockId}`)
return
}

const absolutePosition = calculateAbsolutePosition(block, workflowState.blocks)
const parentId = block.data?.parentId
const nestingDepth = calculateNestingDepth(block, workflowState.blocks)

if (block.type === 'loop' || block.type === 'parallel') {
const isSelected = selectedBlockId === blockId
Expand All @@ -391,9 +397,14 @@ export function PreviewWorkflow({
nodeArray.push({
id: blockId,
type: 'subflowNode',
position: absolutePosition,
position: block.position,
parentId,
extent: block.data?.extent || undefined,
draggable: false,
zIndex: nestingDepth,
className: parentId ? 'nested-subflow-node' : undefined,
data: {
...block.data,
name: block.name,
width: dimensions.width,
height: dimensions.height,
Expand Down Expand Up @@ -430,9 +441,11 @@ export function PreviewWorkflow({
nodeArray.push({
id: blockId,
type: nodeType,
position: absolutePosition,
position: block.position,
parentId,
extent: block.data?.extent || undefined,
draggable: false,
zIndex: block.data?.parentId ? 10 : undefined,
zIndex: parentId ? 1000 : undefined,
data: {
type: block.type,
name: block.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ interface BlockExecutionData {
childWorkflowSnapshotId?: string
}

function isPauseOutput(output: unknown): boolean {
return (
output !== null &&
typeof output === 'object' &&
'_pauseMetadata' in output &&
(output as Record<string, unknown>)._pauseMetadata !== undefined
)
}

/** Represents a level in the workflow navigation stack */
interface WorkflowStackEntry {
workflowState: WorkflowState
Expand Down Expand Up @@ -91,7 +100,7 @@ export function buildBlockExecutions(spans: TraceSpan[]): Record<string, BlockEx
blockExecutionMap[span.blockId] = {
input: redactApiKeys(span.input || {}),
output: redactApiKeys(span.output || {}),
status: span.status || 'unknown',
status: isPauseOutput(span.output) ? 'pending' : span.status || 'unknown',
durationMs: span.duration || 0,
children: span.children,
childWorkflowSnapshotId: span.childWorkflowSnapshotId,
Expand Down
14 changes: 14 additions & 0 deletions apps/sim/executor/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,20 @@ export const EDGE = {
DEFAULT: 'default',
} as const

export const SUBFLOW_CONTROL_EDGE_HANDLES = new Set<string>([
EDGE.LOOP_CONTINUE,
EDGE.LOOP_CONTINUE_ALT,
EDGE.LOOP_EXIT,
EDGE.PARALLEL_CONTINUE,
EDGE.PARALLEL_EXIT,
])

export const CONTROL_BACK_EDGE_HANDLES = new Set<string>([
EDGE.LOOP_CONTINUE,
EDGE.LOOP_CONTINUE_ALT,
EDGE.PARALLEL_CONTINUE,
])

export const LOOP = {
TYPE: {
FOR: 'for' as LoopType,
Expand Down
Loading
Loading