|
@@ -1,6 +1,6 @@
|
|
|
import { useEffect, useMemo, useState } from 'react'
|
|
import { useEffect, useMemo, useState } from 'react'
|
|
|
import { Node, Edge } from 'reactflow'
|
|
import { Node, Edge } from 'reactflow'
|
|
|
-import { X, ChevronRight, ChevronDown, Loader2, CheckCircle, XCircle, SkipForward, Ban, Repeat } from 'lucide-react'
|
|
|
|
|
|
|
+import { X, ChevronRight, ChevronDown, Loader2, CheckCircle, XCircle, SkipForward, Ban, Repeat, FlaskConical } from 'lucide-react'
|
|
|
import { NodeExecutionState } from './WorkflowNode'
|
|
import { NodeExecutionState } from './WorkflowNode'
|
|
|
import { NodeOutputView } from './NodeOutputView'
|
|
import { NodeOutputView } from './NodeOutputView'
|
|
|
import type { NodeExecution } from '../../api/workflows'
|
|
import type { NodeExecution } from '../../api/workflows'
|
|
@@ -18,6 +18,9 @@ interface ExecutionState {
|
|
|
// record) means "fall back to the flat per-node list", which is exactly
|
|
// record) means "fall back to the flat per-node list", which is exactly
|
|
|
// how this panel behaved before grouping existed.
|
|
// how this panel behaved before grouping existed.
|
|
|
rawNodeExecutions?: NodeExecution[]
|
|
rawNodeExecutions?: NodeExecution[]
|
|
|
|
|
+ // The targeted node's id when this run was a "Test this node" run - see
|
|
|
|
|
+ // WorkflowEditorPage. Drives the banner below; absent means a normal run.
|
|
|
|
|
+ singleNodeTarget?: string
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// One pass of a loop body: every node execution that shares a loopNodeId and
|
|
// One pass of a loop body: every node execution that shares a loopNodeId and
|
|
@@ -576,6 +579,14 @@ export function ExecutionResultsPanel({
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Label for the "Test this node" banner below. Falls back to the raw id
|
|
|
|
|
+ // if the node isn't on the canvas anymore (renamed, deleted since the run).
|
|
|
|
|
+ const singleNodeTargetLabel = useMemo(() => {
|
|
|
|
|
+ if (!executionState.singleNodeTarget) return null
|
|
|
|
|
+ const node = nodes.find(n => n.id === executionState.singleNodeTarget)
|
|
|
|
|
+ return node?.data.config?._customLabel || node?.data.label || executionState.singleNodeTarget
|
|
|
|
|
+ }, [executionState.singleNodeTarget, nodes])
|
|
|
|
|
+
|
|
|
const hasAnyResults = timeline
|
|
const hasAnyResults = timeline
|
|
|
? timeline.length > 0
|
|
? timeline.length > 0
|
|
|
: Object.keys(executionState.nodeStates).length > 0
|
|
: Object.keys(executionState.nodeStates).length > 0
|
|
@@ -631,6 +642,22 @@ export function ExecutionResultsPanel({
|
|
|
)}
|
|
)}
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
+ {/* This run only tested one node (plus its upstream) - the single fact
|
|
|
|
|
+ that would have saved an hour of "why did the loop only do one
|
|
|
|
|
+ item" confusion. Shown as its own block so it can't be missed the
|
|
|
|
|
+ way a line buried in the summary above could be. */}
|
|
|
|
|
+ {singleNodeTargetLabel && (
|
|
|
|
|
+ <div className="flex-shrink-0 px-4 py-3 border-b border-gray-200 dark:border-slate-700 bg-amber-50 dark:bg-amber-900/20 flex items-start gap-2">
|
|
|
|
|
+ <FlaskConical className="w-4 h-4 text-amber-600 dark:text-amber-400 shrink-0 mt-0.5" />
|
|
|
|
|
+ <div className="text-xs text-amber-800 dark:text-amber-300">
|
|
|
|
|
+ <span className="font-medium">Single-node test:</span> this run tested only{' '}
|
|
|
|
|
+ <span className="font-medium">{singleNodeTargetLabel}</span> and the nodes upstream
|
|
|
|
|
+ of it - nothing downstream ran. If a loop appears here, it ran a single item on
|
|
|
|
|
+ purpose. One iteration is expected, not a fault.
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
{/* Node results, in the order they actually ran. Loop bodies collapse
|
|
{/* Node results, in the order they actually ran. Loop bodies collapse
|
|
|
into one card per iteration when the execution has loopNodeId /
|
|
into one card per iteration when the execution has loopNodeId /
|
|
|
loopIteration on its records; otherwise this is the same flat,
|
|
loopIteration on its records; otherwise this is the same flat,
|