From 4ba22527b66b85acbbd469811d37021777ca7703 Mon Sep 17 00:00:00 2001 From: Waleed Date: Tue, 3 Feb 2026 18:32:40 -0800 Subject: [PATCH] improvement(tag-dropdown): removed custom styling on tag dropdown popover, fixed execution ordering in terminal and loops entries (#3126) * improvement(tag-dropdown): removeed custom styling on tag dropdown popover, fixed execution ordering in terminal and loops entries * ack pr comments * handle old records --- .../app/api/workflows/[id]/execute/route.ts | 4 ++ .../components/tag-dropdown/tag-dropdown.tsx | 37 ++++++++------- .../terminal/hooks/use-terminal-filters.ts | 6 +-- .../[workflowId]/components/terminal/utils.ts | 23 ++++----- .../hooks/use-workflow-execution.ts | 16 ++++++- .../utils/workflow-execution-utils.ts | 2 + apps/sim/executor/execution/block-executor.ts | 47 +++++++++++++------ apps/sim/executor/execution/types.ts | 10 +++- apps/sim/executor/orchestrators/loop.ts | 7 ++- apps/sim/executor/orchestrators/parallel.ts | 7 ++- apps/sim/executor/types.ts | 29 +++++++++++- apps/sim/executor/utils/subflow-utils.ts | 5 +- .../workflows/executor/execution-events.ts | 15 +++--- apps/sim/stores/terminal/console/store.ts | 20 +++++++- apps/sim/stores/terminal/console/types.ts | 7 +-- 15 files changed, 167 insertions(+), 68 deletions(-) diff --git a/apps/sim/app/api/workflows/[id]/execute/route.ts b/apps/sim/app/api/workflows/[id]/execute/route.ts index 53161e42a..2ef6b0270 100644 --- a/apps/sim/app/api/workflows/[id]/execute/route.ts +++ b/apps/sim/app/api/workflows/[id]/execute/route.ts @@ -567,6 +567,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: blockId: string, blockName: string, blockType: string, + executionOrder: number, iterationContext?: IterationContext ) => { logger.info(`[${requestId}] 🔷 onBlockStart called:`, { blockId, blockName, blockType }) @@ -579,6 +580,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: blockId, blockName, blockType, + executionOrder, ...(iterationContext && { iterationCurrent: iterationContext.iterationCurrent, iterationTotal: iterationContext.iterationTotal, @@ -617,6 +619,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: error: callbackData.output.error, durationMs: callbackData.executionTime || 0, startedAt: callbackData.startedAt, + executionOrder: callbackData.executionOrder, endedAt: callbackData.endedAt, ...(iterationContext && { iterationCurrent: iterationContext.iterationCurrent, @@ -644,6 +647,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: output: callbackData.output, durationMs: callbackData.executionTime || 0, startedAt: callbackData.startedAt, + executionOrder: callbackData.executionOrder, endedAt: callbackData.endedAt, ...(iterationContext && { iterationCurrent: iterationContext.iterationCurrent, diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown.tsx index bc982daec..f233fe025 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown.tsx @@ -908,8 +908,10 @@ const PopoverContextCapture: React.FC<{ * When in nested folders, goes back one level at a time. * At the root folder level, closes the folder. */ -const TagDropdownBackButton: React.FC = () => { - const { isInFolder, closeFolder, colorScheme, size } = usePopoverContext() +const TagDropdownBackButton: React.FC<{ setSelectedIndex: (index: number) => void }> = ({ + setSelectedIndex, +}) => { + const { isInFolder, closeFolder, size, isKeyboardNav, setKeyboardNav } = usePopoverContext() const nestedNav = useNestedNavigation() if (!isInFolder) return null @@ -922,28 +924,31 @@ const TagDropdownBackButton: React.FC = () => { closeFolder() } + const handleMouseEnter = () => { + if (isKeyboardNav) return + setKeyboardNav(false) + setSelectedIndex(-1) + } + return ( -
{ + e.preventDefault() + e.stopPropagation() + handleBackClick(e) + }} + onMouseEnter={handleMouseEnter} > - Back -
+ Back + ) } @@ -1961,8 +1966,8 @@ export const TagDropdown: React.FC = ({ onOpenAutoFocus={(e) => e.preventDefault()} onCloseAutoFocus={(e) => e.preventDefault()} > - + {flatTagList.length === 0 ? (
No matching tags found diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/hooks/use-terminal-filters.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/hooks/use-terminal-filters.ts index 1807828f4..c712864cf 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/hooks/use-terminal-filters.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/hooks/use-terminal-filters.ts @@ -105,11 +105,9 @@ export function useTerminalFilters() { }) } - // Apply sorting by timestamp + // Sort by executionOrder (monotonically increasing integer from server) result = [...result].sort((a, b) => { - const timeA = new Date(a.timestamp).getTime() - const timeB = new Date(b.timestamp).getTime() - const comparison = timeA - timeB + const comparison = a.executionOrder - b.executionOrder return sortConfig.direction === 'asc' ? comparison : -comparison }) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/utils.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/utils.ts index 18b8cfef6..d54ccbfdf 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/utils.ts @@ -184,13 +184,9 @@ function buildEntryTree(entries: ConsoleEntry[]): EntryNode[] { group.blocks.push(entry) } - // Sort blocks within each iteration by start time ascending (oldest first, top-down) + // Sort blocks within each iteration by executionOrder ascending (oldest first, top-down) for (const group of iterationGroupsMap.values()) { - group.blocks.sort((a, b) => { - const aStart = new Date(a.startedAt || a.timestamp).getTime() - const bStart = new Date(b.startedAt || b.timestamp).getTime() - return aStart - bStart - }) + group.blocks.sort((a, b) => a.executionOrder - b.executionOrder) } // Group iterations by iterationType to create subflow parents @@ -225,6 +221,8 @@ function buildEntryTree(entries: ConsoleEntry[]): EntryNode[] { const totalDuration = allBlocks.reduce((sum, b) => sum + (b.durationMs || 0), 0) // Create synthetic subflow parent entry + // Use the minimum executionOrder from all child blocks for proper ordering + const subflowExecutionOrder = Math.min(...allBlocks.map((b) => b.executionOrder)) const syntheticSubflow: ConsoleEntry = { id: `subflow-${iterationType}-${firstIteration.blocks[0]?.executionId || 'unknown'}`, timestamp: new Date(subflowStartMs).toISOString(), @@ -234,6 +232,7 @@ function buildEntryTree(entries: ConsoleEntry[]): EntryNode[] { blockType: iterationType, executionId: firstIteration.blocks[0]?.executionId, startedAt: new Date(subflowStartMs).toISOString(), + executionOrder: subflowExecutionOrder, endedAt: new Date(subflowEndMs).toISOString(), durationMs: totalDuration, success: !allBlocks.some((b) => b.error), @@ -251,6 +250,8 @@ function buildEntryTree(entries: ConsoleEntry[]): EntryNode[] { ) const iterDuration = iterBlocks.reduce((sum, b) => sum + (b.durationMs || 0), 0) + // Use the minimum executionOrder from blocks in this iteration + const iterExecutionOrder = Math.min(...iterBlocks.map((b) => b.executionOrder)) const syntheticIteration: ConsoleEntry = { id: `iteration-${iterationType}-${iterGroup.iterationCurrent}-${iterBlocks[0]?.executionId || 'unknown'}`, timestamp: new Date(iterStartMs).toISOString(), @@ -260,6 +261,7 @@ function buildEntryTree(entries: ConsoleEntry[]): EntryNode[] { blockType: iterationType, executionId: iterBlocks[0]?.executionId, startedAt: new Date(iterStartMs).toISOString(), + executionOrder: iterExecutionOrder, endedAt: new Date(iterEndMs).toISOString(), durationMs: iterDuration, success: !iterBlocks.some((b) => b.error), @@ -300,14 +302,9 @@ function buildEntryTree(entries: ConsoleEntry[]): EntryNode[] { nodeType: 'block' as const, })) - // Combine all nodes and sort by start time ascending (oldest first, top-down) + // Combine all nodes and sort by executionOrder ascending (oldest first, top-down) const allNodes = [...subflowNodes, ...regularNodes] - allNodes.sort((a, b) => { - const aStart = new Date(a.entry.startedAt || a.entry.timestamp).getTime() - const bStart = new Date(b.entry.startedAt || b.entry.timestamp).getTime() - return aStart - bStart - }) - + allNodes.sort((a, b) => a.entry.executionOrder - b.entry.executionOrder) return allNodes } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts index 6dcad6c17..2b021b3c5 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts @@ -926,6 +926,7 @@ export function useWorkflowExecution() { }) // Add entry to terminal immediately with isRunning=true + // Use server-provided executionOrder to ensure correct sort order const startedAt = new Date().toISOString() addConsole({ input: {}, @@ -933,6 +934,7 @@ export function useWorkflowExecution() { success: undefined, durationMs: undefined, startedAt, + executionOrder: data.executionOrder, endedAt: undefined, workflowId: activeWorkflowId, blockId: data.blockId, @@ -948,8 +950,6 @@ export function useWorkflowExecution() { }, onBlockCompleted: (data) => { - logger.info('onBlockCompleted received:', { data }) - activeBlocksSet.delete(data.blockId) setActiveBlocks(new Set(activeBlocksSet)) setBlockRunStatus(data.blockId, 'success') @@ -976,6 +976,7 @@ export function useWorkflowExecution() { success: true, durationMs: data.durationMs, startedAt, + executionOrder: data.executionOrder, endedAt, }) @@ -987,6 +988,7 @@ export function useWorkflowExecution() { replaceOutput: data.output, success: true, durationMs: data.durationMs, + startedAt, endedAt, isRunning: false, // Pass through iteration context for subflow grouping @@ -1027,6 +1029,7 @@ export function useWorkflowExecution() { error: data.error, durationMs: data.durationMs, startedAt, + executionOrder: data.executionOrder, endedAt, }) @@ -1039,6 +1042,7 @@ export function useWorkflowExecution() { success: false, error: data.error, durationMs: data.durationMs, + startedAt, endedAt, isRunning: false, // Pass through iteration context for subflow grouping @@ -1163,6 +1167,7 @@ export function useWorkflowExecution() { if (existingLogs.length === 0) { // No blocks executed yet - this is a pre-execution error + // Use 0 for executionOrder so validation errors appear first addConsole({ input: {}, output: {}, @@ -1170,6 +1175,7 @@ export function useWorkflowExecution() { error: data.error, durationMs: data.duration || 0, startedAt: new Date(Date.now() - (data.duration || 0)).toISOString(), + executionOrder: 0, endedAt: new Date().toISOString(), workflowId: activeWorkflowId, blockId: 'validation', @@ -1237,6 +1243,7 @@ export function useWorkflowExecution() { blockType = error.blockType || blockType } + // Use MAX_SAFE_INTEGER so execution errors appear at the end of the log useTerminalConsoleStore.getState().addConsole({ input: {}, output: {}, @@ -1244,6 +1251,7 @@ export function useWorkflowExecution() { error: normalizedMessage, durationMs: 0, startedAt: new Date().toISOString(), + executionOrder: Number.MAX_SAFE_INTEGER, endedAt: new Date().toISOString(), workflowId: activeWorkflowId || '', blockId, @@ -1615,6 +1623,7 @@ export function useWorkflowExecution() { success: true, durationMs: data.durationMs, startedAt, + executionOrder: data.executionOrder, endedAt, }) @@ -1624,6 +1633,7 @@ export function useWorkflowExecution() { success: true, durationMs: data.durationMs, startedAt, + executionOrder: data.executionOrder, endedAt, workflowId, blockId: data.blockId, @@ -1653,6 +1663,7 @@ export function useWorkflowExecution() { output: {}, success: false, error: data.error, + executionOrder: data.executionOrder, durationMs: data.durationMs, startedAt, endedAt, @@ -1665,6 +1676,7 @@ export function useWorkflowExecution() { error: data.error, durationMs: data.durationMs, startedAt, + executionOrder: data.executionOrder, endedAt, workflowId, blockId: data.blockId, diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts index c69670f8d..0d0597f9a 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts @@ -111,6 +111,7 @@ export async function executeWorkflowWithFullLogging( success: true, durationMs: event.data.durationMs, startedAt: new Date(Date.now() - event.data.durationMs).toISOString(), + executionOrder: event.data.executionOrder, endedAt: new Date().toISOString(), workflowId: activeWorkflowId, blockId: event.data.blockId, @@ -140,6 +141,7 @@ export async function executeWorkflowWithFullLogging( error: event.data.error, durationMs: event.data.durationMs, startedAt: new Date(Date.now() - event.data.durationMs).toISOString(), + executionOrder: event.data.executionOrder, endedAt: new Date().toISOString(), workflowId: activeWorkflowId, blockId: event.data.blockId, diff --git a/apps/sim/executor/execution/block-executor.ts b/apps/sim/executor/execution/block-executor.ts index 59b08e4a9..6c97dd1a1 100644 --- a/apps/sim/executor/execution/block-executor.ts +++ b/apps/sim/executor/execution/block-executor.ts @@ -21,12 +21,13 @@ import { generatePauseContextId, mapNodeMetadataToPauseScopes, } from '@/executor/human-in-the-loop/utils.ts' -import type { - BlockHandler, - BlockLog, - BlockState, - ExecutionContext, - NormalizedBlockOutput, +import { + type BlockHandler, + type BlockLog, + type BlockState, + type ExecutionContext, + getNextExecutionOrder, + type NormalizedBlockOutput, } from '@/executor/types' import { streamingResponseFormatProcessor } from '@/executor/utils' import { buildBlockExecutionError, normalizeError } from '@/executor/utils/errors' @@ -68,7 +69,7 @@ export class BlockExecutor { if (!isSentinel) { blockLog = this.createBlockLog(ctx, node.id, block, node) ctx.blockLogs.push(blockLog) - this.callOnBlockStart(ctx, node, block) + this.callOnBlockStart(ctx, node, block, blockLog.executionOrder) } const startTime = performance.now() @@ -159,7 +160,7 @@ export class BlockExecutor { this.state.setBlockOutput(node.id, normalizedOutput, duration) - if (!isSentinel) { + if (!isSentinel && blockLog) { const displayOutput = filterOutputForLog(block.metadata?.id || '', normalizedOutput, { block, }) @@ -170,8 +171,9 @@ export class BlockExecutor { this.sanitizeInputsForLog(resolvedInputs), displayOutput, duration, - blockLog!.startedAt, - blockLog!.endedAt + blockLog.startedAt, + blockLog.executionOrder, + blockLog.endedAt ) } @@ -268,7 +270,7 @@ export class BlockExecutor { } ) - if (!isSentinel) { + if (!isSentinel && blockLog) { const displayOutput = filterOutputForLog(block.metadata?.id || '', errorOutput, { block }) this.callOnBlockComplete( ctx, @@ -277,8 +279,9 @@ export class BlockExecutor { this.sanitizeInputsForLog(input), displayOutput, duration, - blockLog!.startedAt, - blockLog!.endedAt + blockLog.startedAt, + blockLog.executionOrder, + blockLog.endedAt ) } @@ -346,6 +349,7 @@ export class BlockExecutor { blockName, blockType: block.metadata?.id ?? DEFAULTS.BLOCK_TYPE, startedAt: new Date().toISOString(), + executionOrder: getNextExecutionOrder(ctx), endedAt: '', durationMs: 0, success: false, @@ -409,7 +413,12 @@ export class BlockExecutor { return result } - private callOnBlockStart(ctx: ExecutionContext, node: DAGNode, block: SerializedBlock): void { + private callOnBlockStart( + ctx: ExecutionContext, + node: DAGNode, + block: SerializedBlock, + executionOrder: number + ): void { const blockId = node.id const blockName = block.metadata?.name ?? blockId const blockType = block.metadata?.id ?? DEFAULTS.BLOCK_TYPE @@ -417,7 +426,13 @@ export class BlockExecutor { const iterationContext = this.getIterationContext(ctx, node) if (this.contextExtensions.onBlockStart) { - this.contextExtensions.onBlockStart(blockId, blockName, blockType, iterationContext) + this.contextExtensions.onBlockStart( + blockId, + blockName, + blockType, + executionOrder, + iterationContext + ) } } @@ -429,6 +444,7 @@ export class BlockExecutor { output: NormalizedBlockOutput, duration: number, startedAt: string, + executionOrder: number, endedAt: string ): void { const blockId = node.id @@ -447,6 +463,7 @@ export class BlockExecutor { output, executionTime: duration, startedAt, + executionOrder, endedAt, }, iterationContext diff --git a/apps/sim/executor/execution/types.ts b/apps/sim/executor/execution/types.ts index e770989b6..91dfe2c6a 100644 --- a/apps/sim/executor/execution/types.ts +++ b/apps/sim/executor/execution/types.ts @@ -55,7 +55,13 @@ export interface IterationContext { export interface ExecutionCallbacks { onStream?: (streamingExec: any) => Promise - onBlockStart?: (blockId: string, blockName: string, blockType: string) => Promise + onBlockStart?: ( + blockId: string, + blockName: string, + blockType: string, + executionOrder: number, + iterationContext?: IterationContext + ) => Promise onBlockComplete?: ( blockId: string, blockName: string, @@ -97,6 +103,7 @@ export interface ContextExtensions { blockId: string, blockName: string, blockType: string, + executionOrder: number, iterationContext?: IterationContext ) => Promise onBlockComplete?: ( @@ -108,6 +115,7 @@ export interface ContextExtensions { output: NormalizedBlockOutput executionTime: number startedAt: string + executionOrder: number endedAt: string }, iterationContext?: IterationContext diff --git a/apps/sim/executor/orchestrators/loop.ts b/apps/sim/executor/orchestrators/loop.ts index bd72b8498..8bdf8edd2 100644 --- a/apps/sim/executor/orchestrators/loop.ts +++ b/apps/sim/executor/orchestrators/loop.ts @@ -7,7 +7,11 @@ import type { DAG } from '@/executor/dag/builder' import type { EdgeManager } from '@/executor/execution/edge-manager' import type { LoopScope } from '@/executor/execution/state' import type { BlockStateController, ContextExtensions } from '@/executor/execution/types' -import type { ExecutionContext, NormalizedBlockOutput } from '@/executor/types' +import { + type ExecutionContext, + getNextExecutionOrder, + type NormalizedBlockOutput, +} from '@/executor/types' import type { LoopConfigWithNodes } from '@/executor/types/loop' import { replaceValidReferences } from '@/executor/utils/reference-validation' import { @@ -286,6 +290,7 @@ export class LoopOrchestrator { output, executionTime: DEFAULTS.EXECUTION_TIME, startedAt: now, + executionOrder: getNextExecutionOrder(ctx), endedAt: now, }) } diff --git a/apps/sim/executor/orchestrators/parallel.ts b/apps/sim/executor/orchestrators/parallel.ts index 88942b8cb..6d7ea2dfe 100644 --- a/apps/sim/executor/orchestrators/parallel.ts +++ b/apps/sim/executor/orchestrators/parallel.ts @@ -3,7 +3,11 @@ import { DEFAULTS } from '@/executor/constants' import type { DAG } from '@/executor/dag/builder' import type { ParallelScope } from '@/executor/execution/state' import type { BlockStateWriter, ContextExtensions } from '@/executor/execution/types' -import type { ExecutionContext, NormalizedBlockOutput } from '@/executor/types' +import { + type ExecutionContext, + getNextExecutionOrder, + type NormalizedBlockOutput, +} from '@/executor/types' import type { ParallelConfigWithNodes } from '@/executor/types/parallel' import { ParallelExpander } from '@/executor/utils/parallel-expansion' import { @@ -270,6 +274,7 @@ export class ParallelOrchestrator { output, executionTime: 0, startedAt: now, + executionOrder: getNextExecutionOrder(ctx), endedAt: now, }) } diff --git a/apps/sim/executor/types.ts b/apps/sim/executor/types.ts index 6c87eed25..10c1996b3 100644 --- a/apps/sim/executor/types.ts +++ b/apps/sim/executor/types.ts @@ -114,6 +114,11 @@ export interface BlockLog { loopId?: string parallelId?: string iterationIndex?: number + /** + * Monotonically increasing integer (1, 2, 3, ...) for accurate block ordering. + * Generated via getNextExecutionOrder() to ensure deterministic sorting. + */ + executionOrder: number /** * Child workflow trace spans for nested workflow execution. * Stored separately from output to keep output clean for display @@ -227,7 +232,12 @@ export interface ExecutionContext { edges?: Array<{ source: string; target: string }> onStream?: (streamingExecution: StreamingExecution) => Promise - onBlockStart?: (blockId: string, blockName: string, blockType: string) => Promise + onBlockStart?: ( + blockId: string, + blockName: string, + blockType: string, + executionOrder: number + ) => Promise onBlockComplete?: ( blockId: string, blockName: string, @@ -268,6 +278,23 @@ export interface ExecutionContext { * Stop execution after this block completes. Used for "run until block" feature. */ stopAfterBlockId?: string + + /** + * Counter for generating monotonically increasing execution order values. + * Starts at 0 and increments for each block. Use getNextExecutionOrder() to access. + */ + executionOrderCounter?: { value: number } +} + +/** + * Gets the next execution order value for a block. + * Returns a simple incrementing integer (1, 2, 3, ...) for clear ordering. + */ +export function getNextExecutionOrder(ctx: ExecutionContext): number { + if (!ctx.executionOrderCounter) { + ctx.executionOrderCounter = { value: 0 } + } + return ++ctx.executionOrderCounter.value } export interface ExecutionResult { diff --git a/apps/sim/executor/utils/subflow-utils.ts b/apps/sim/executor/utils/subflow-utils.ts index 5ef3a51b5..c4eb23f38 100644 --- a/apps/sim/executor/utils/subflow-utils.ts +++ b/apps/sim/executor/utils/subflow-utils.ts @@ -1,7 +1,7 @@ import { createLogger } from '@sim/logger' import { LOOP, PARALLEL, PARSING, REFERENCE } from '@/executor/constants' import type { ContextExtensions } from '@/executor/execution/types' -import type { BlockLog, ExecutionContext } from '@/executor/types' +import { type BlockLog, type ExecutionContext, getNextExecutionOrder } from '@/executor/types' import type { VariableResolver } from '@/executor/variables/resolver' const logger = createLogger('SubflowUtils') @@ -208,6 +208,7 @@ export function addSubflowErrorLog( contextExtensions: ContextExtensions | null ): void { const now = new Date().toISOString() + const execOrder = getNextExecutionOrder(ctx) const block = ctx.workflow?.blocks?.find((b) => b.id === blockId) const blockName = block?.metadata?.name || (blockType === 'loop' ? 'Loop' : 'Parallel') @@ -217,6 +218,7 @@ export function addSubflowErrorLog( blockName, blockType, startedAt: now, + executionOrder: execOrder, endedAt: now, durationMs: 0, success: false, @@ -233,6 +235,7 @@ export function addSubflowErrorLog( output: { error: errorMessage }, executionTime: 0, startedAt: now, + executionOrder: execOrder, endedAt: now, }) } diff --git a/apps/sim/lib/workflows/executor/execution-events.ts b/apps/sim/lib/workflows/executor/execution-events.ts index 6c3998e23..ba36f9787 100644 --- a/apps/sim/lib/workflows/executor/execution-events.ts +++ b/apps/sim/lib/workflows/executor/execution-events.ts @@ -1,7 +1,3 @@ -/** - * SSE Event types for workflow execution - */ - import type { SubflowType } from '@/stores/workflows/workflow/types' export type ExecutionEventType = @@ -83,7 +79,7 @@ export interface BlockStartedEvent extends BaseExecutionEvent { blockId: string blockName: string blockType: string - // Iteration context for loops and parallels + executionOrder: number iterationCurrent?: number iterationTotal?: number iterationType?: SubflowType @@ -104,8 +100,8 @@ export interface BlockCompletedEvent extends BaseExecutionEvent { output: any durationMs: number startedAt: string + executionOrder: number endedAt: string - // Iteration context for loops and parallels iterationCurrent?: number iterationTotal?: number iterationType?: SubflowType @@ -126,8 +122,8 @@ export interface BlockErrorEvent extends BaseExecutionEvent { error: string durationMs: number startedAt: string + executionOrder: number endedAt: string - // Iteration context for loops and parallels iterationCurrent?: number iterationTotal?: number iterationType?: SubflowType @@ -228,6 +224,7 @@ export function createSSECallbacks(options: SSECallbackOptions) { blockId: string, blockName: string, blockType: string, + executionOrder: number, iterationContext?: { iterationCurrent: number; iterationTotal: number; iterationType: string } ) => { sendEvent({ @@ -239,6 +236,7 @@ export function createSSECallbacks(options: SSECallbackOptions) { blockId, blockName, blockType, + executionOrder, ...(iterationContext && { iterationCurrent: iterationContext.iterationCurrent, iterationTotal: iterationContext.iterationTotal, @@ -257,6 +255,7 @@ export function createSSECallbacks(options: SSECallbackOptions) { output: any executionTime: number startedAt: string + executionOrder: number endedAt: string }, iterationContext?: { iterationCurrent: number; iterationTotal: number; iterationType: string } @@ -284,6 +283,7 @@ export function createSSECallbacks(options: SSECallbackOptions) { error: callbackData.output.error, durationMs: callbackData.executionTime || 0, startedAt: callbackData.startedAt, + executionOrder: callbackData.executionOrder, endedAt: callbackData.endedAt, ...iterationData, }, @@ -302,6 +302,7 @@ export function createSSECallbacks(options: SSECallbackOptions) { output: callbackData.output, durationMs: callbackData.executionTime || 0, startedAt: callbackData.startedAt, + executionOrder: callbackData.executionOrder, endedAt: callbackData.endedAt, ...iterationData, }, diff --git a/apps/sim/stores/terminal/console/store.ts b/apps/sim/stores/terminal/console/store.ts index 15298c625..9b1386da1 100644 --- a/apps/sim/stores/terminal/console/store.ts +++ b/apps/sim/stores/terminal/console/store.ts @@ -287,6 +287,14 @@ export const useTerminalConsoleStore = create()( return entry } + if ( + typeof update === 'object' && + update.iterationCurrent !== undefined && + entry.iterationCurrent !== update.iterationCurrent + ) { + return entry + } + if (typeof update === 'string') { const newOutput = updateBlockOutput(entry.output, update) return { ...entry, output: newOutput } @@ -324,6 +332,10 @@ export const useTerminalConsoleStore = create()( updatedEntry.success = update.success } + if (update.startedAt !== undefined) { + updatedEntry.startedAt = update.startedAt + } + if (update.endedAt !== undefined) { updatedEntry.endedAt = update.endedAt } @@ -397,9 +409,15 @@ export const useTerminalConsoleStore = create()( }, merge: (persistedState, currentState) => { const persisted = persistedState as Partial | undefined + const entries = (persisted?.entries ?? currentState.entries).map((entry, index) => { + if (entry.executionOrder === undefined) { + return { ...entry, executionOrder: index + 1 } + } + return entry + }) return { ...currentState, - entries: persisted?.entries ?? currentState.entries, + entries, isOpen: persisted?.isOpen ?? currentState.isOpen, } }, diff --git a/apps/sim/stores/terminal/console/types.ts b/apps/sim/stores/terminal/console/types.ts index ca31112eb..3ddb4b424 100644 --- a/apps/sim/stores/terminal/console/types.ts +++ b/apps/sim/stores/terminal/console/types.ts @@ -10,6 +10,7 @@ export interface ConsoleEntry { blockType: string executionId?: string startedAt?: string + executionOrder: number endedAt?: string durationMs?: number success?: boolean @@ -20,9 +21,7 @@ export interface ConsoleEntry { iterationCurrent?: number iterationTotal?: number iterationType?: SubflowType - /** Whether this block is currently running */ isRunning?: boolean - /** Whether this block execution was canceled */ isCanceled?: boolean } @@ -33,14 +32,12 @@ export interface ConsoleUpdate { error?: string | Error | null warning?: string success?: boolean + startedAt?: string endedAt?: string durationMs?: number input?: any - /** Whether this block is currently running */ isRunning?: boolean - /** Whether this block execution was canceled */ isCanceled?: boolean - /** Iteration context for subflow blocks */ iterationCurrent?: number iterationTotal?: number iterationType?: SubflowType