diff --git a/api/core/workflow/nodes/iteration/iteration_node.py b/api/core/workflow/nodes/iteration/iteration_node.py index a7d0aefc6d..a061dfc354 100644 --- a/api/core/workflow/nodes/iteration/iteration_node.py +++ b/api/core/workflow/nodes/iteration/iteration_node.py @@ -353,27 +353,26 @@ class IterationNode(BaseNode[IterationNodeData]): ) -> NodeRunStartedEvent | BaseNodeEvent | InNodeEvent: """ add iteration metadata to event. + ensures iteration context (ID, index/parallel_run_id) is added to metadata, """ if not isinstance(event, BaseNodeEvent): return event if self.node_data.is_parallel and isinstance(event, NodeRunStartedEvent): event.parallel_mode_run_id = parallel_mode_run_id - return event + + iter_metadata = { + NodeRunMetadataKey.ITERATION_ID: self.node_id, + NodeRunMetadataKey.ITERATION_INDEX: iter_run_index, + } + if parallel_mode_run_id: + # for parallel, the specific branch ID is more important than the sequential index + iter_metadata[NodeRunMetadataKey.PARALLEL_MODE_RUN_ID] = parallel_mode_run_id + if event.route_node_state.node_run_result: - metadata = event.route_node_state.node_run_result.metadata - if not metadata: - metadata = {} - if NodeRunMetadataKey.ITERATION_ID not in metadata: - metadata = { - **metadata, - NodeRunMetadataKey.ITERATION_ID: self.node_id, - NodeRunMetadataKey.PARALLEL_MODE_RUN_ID - if self.node_data.is_parallel - else NodeRunMetadataKey.ITERATION_INDEX: parallel_mode_run_id - if self.node_data.is_parallel - else iter_run_index, - } - event.route_node_state.node_run_result.metadata = metadata + current_metadata = event.route_node_state.node_run_result.metadata or {} + if NodeRunMetadataKey.ITERATION_ID not in current_metadata: + event.route_node_state.node_run_result.metadata = {**current_metadata, **iter_metadata} + return event def _run_single_iter( diff --git a/api/core/workflow/nodes/loop/loop_node.py b/api/core/workflow/nodes/loop/loop_node.py index eae33c0a92..bad3e2b928 100644 --- a/api/core/workflow/nodes/loop/loop_node.py +++ b/api/core/workflow/nodes/loop/loop_node.py @@ -337,7 +337,7 @@ class LoopNode(BaseNode[LoopNodeData]): return {"check_break_result": True} elif isinstance(event, NodeRunFailedEvent): # Loop run failed - yield event + yield self._handle_event_metadata(event=event, iter_run_index=current_index) yield LoopRunFailedEvent( loop_id=self.id, loop_node_id=self.node_id, diff --git a/web/app/components/workflow/run/iteration-log/iteration-log-trigger.tsx b/web/app/components/workflow/run/iteration-log/iteration-log-trigger.tsx index ee051883f7..91bcaf9485 100644 --- a/web/app/components/workflow/run/iteration-log/iteration-log-trigger.tsx +++ b/web/app/components/workflow/run/iteration-log/iteration-log-trigger.tsx @@ -9,44 +9,90 @@ import { Iteration } from '@/app/components/base/icons/src/vender/workflow' type IterationLogTriggerProps = { nodeInfo: NodeTracing + allExecutions?: NodeTracing[] onShowIterationResultList: (iterationResultList: NodeTracing[][], iterationResultDurationMap: IterationDurationMap) => void } const IterationLogTrigger = ({ nodeInfo, + allExecutions, onShowIterationResultList, }: IterationLogTriggerProps) => { const { t } = useTranslation() + + const filterNodesForInstance = (key: string): NodeTracing[] => { + if (!allExecutions) return [] + + const parallelNodes = allExecutions.filter(exec => + exec.execution_metadata?.parallel_mode_run_id === key, + ) + if (parallelNodes.length > 0) + return parallelNodes + + const serialIndex = parseInt(key, 10) + if (!isNaN(serialIndex)) { + const serialNodes = allExecutions.filter(exec => + exec.execution_metadata?.iteration_id === nodeInfo.node_id + && exec.execution_metadata?.iteration_index === serialIndex, + ) + if (serialNodes.length > 0) + return serialNodes + } + + return [] + } + + const handleOnShowIterationDetail = (e: React.MouseEvent) => { + e.stopPropagation() + e.nativeEvent.stopImmediatePropagation() + + const iterationNodeMeta = nodeInfo.execution_metadata + const iterDurationMap = nodeInfo?.iterDurationMap || iterationNodeMeta?.iteration_duration_map || {} + + let structuredList: NodeTracing[][] = [] + + if (iterationNodeMeta?.iteration_duration_map) { + const instanceKeys = Object.keys(iterationNodeMeta.iteration_duration_map) + structuredList = instanceKeys + .map(key => filterNodesForInstance(key)) + .filter(branchNodes => branchNodes.length > 0) + } + else if (nodeInfo.details?.length) { + structuredList = nodeInfo.details + } + + onShowIterationResultList(structuredList, iterDurationMap) + } + + let displayIterationCount = 0 + const iterMap = nodeInfo.execution_metadata?.iteration_duration_map + if (iterMap) + displayIterationCount = Object.keys(iterMap).length + else if (nodeInfo.details?.length) + displayIterationCount = nodeInfo.details.length + else if (nodeInfo.metadata?.iterator_length) + displayIterationCount = nodeInfo.metadata.iterator_length + const getErrorCount = (details: NodeTracing[][] | undefined) => { if (!details || details.length === 0) return 0 - return details.reduce((acc, iteration) => { if (iteration.some(item => item.status === 'failed')) acc++ return acc }, 0) } - const getCount = (iteration_curr_length: number | undefined, iteration_length: number) => { - if ((iteration_curr_length && iteration_curr_length < iteration_length) || !iteration_length) - return iteration_curr_length + const errorCount = getErrorCount(nodeInfo.details) - return iteration_length - } - const handleOnShowIterationDetail = (e: React.MouseEvent) => { - e.stopPropagation() - e.nativeEvent.stopImmediatePropagation() - onShowIterationResultList(nodeInfo.details || [], nodeInfo?.iterDurationMap || nodeInfo.execution_metadata?.iteration_duration_map || {}) - } return (