fix: restore unified workflow validation system

Major fixes to workflow checklist validation:

## Fixed getValidTreeNodes function (workflow.ts)
- Restore original function signature: (nodes, edges) instead of (startNode, nodes, edges)
- Re-implement automatic start node discovery for all entry types
- Unified traversal from Start, TriggerWebhook, TriggerSchedule, TriggerPlugin nodes
- Single call now discovers all valid connected nodes correctly

## Simplified useChecklist validation (use-checklist.ts)
- Remove complex manual start node iteration and result aggregation
- Unified entry node validation concept for all start node types
- Remove dependency on getStartNodes() utility
- Simplified validation logic matching backup branch approach

## Resolved Issues
-  End node connectivity: Now correctly detects connections from any entry node
-  Unified entry validation: All start types (Start/Triggers) validated consistently
-  Simplified architecture: Restored proven validation approach from backup branch

This restores the reliable workflow validation system while maintaining trigger node support.
This commit is contained in:
lyzno1
2025-09-26 20:54:28 +08:00
parent 6f57aa3f53
commit 2dca0c20db
2 changed files with 22 additions and 39 deletions

View File

@ -27,7 +27,6 @@ import {
} from '../constants' } from '../constants'
import { import {
useGetToolIcon, useGetToolIcon,
useWorkflow,
} from '../hooks' } from '../hooks'
import type { ToolNodeType } from '../nodes/tool/types' import type { ToolNodeType } from '../nodes/tool/types'
import type { DataSourceNodeType } from '../nodes/data-source/types' import type { DataSourceNodeType } from '../nodes/data-source/types'
@ -54,7 +53,6 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
const dataSourceList = useStore(s => s.dataSourceList) const dataSourceList = useStore(s => s.dataSourceList)
const { data: strategyProviders } = useStrategyProviders() const { data: strategyProviders } = useStrategyProviders()
const datasetsDetail = useDatasetsDetailStore(s => s.datasetsDetail) const datasetsDetail = useDatasetsDetailStore(s => s.datasetsDetail)
const { getStartNodes } = useWorkflow()
const getToolIcon = useGetToolIcon() const getToolIcon = useGetToolIcon()
const map = useNodesAvailableVarList(nodes) const map = useNodesAvailableVarList(nodes)
@ -79,13 +77,7 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
const needWarningNodes = useMemo(() => { const needWarningNodes = useMemo(() => {
const list = [] const list = []
const filteredNodes = nodes.filter(node => node.type === CUSTOM_NODE) const filteredNodes = nodes.filter(node => node.type === CUSTOM_NODE)
const startNodes = getStartNodes(filteredNodes) const { validNodes } = getValidTreeNodes(filteredNodes, edges)
const validNodesFlattened = startNodes.map(startNode => getValidTreeNodes(startNode, filteredNodes, edges))
const validNodes = validNodesFlattened.reduce((acc, curr) => {
if (curr.validNodes)
acc.push(...curr.validNodes)
return acc
}, [] as Node[])
for (let i = 0; i < filteredNodes.length; i++) { for (let i = 0; i < filteredNodes.length; i++) {
const node = filteredNodes[i] const node = filteredNodes[i]
@ -192,7 +184,7 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
}) })
return list return list
}, [nodes, getStartNodes, nodesExtraData, edges, buildInTools, customTools, workflowTools, language, dataSourceList, getToolIcon, strategyProviders, getCheckData, t, map]) }, [nodes, nodesExtraData, edges, buildInTools, customTools, workflowTools, language, dataSourceList, getToolIcon, strategyProviders, getCheckData, t, map])
return needWarningNodes return needWarningNodes
} }
@ -206,7 +198,6 @@ export const useChecklistBeforePublish = () => {
const { data: strategyProviders } = useStrategyProviders() const { data: strategyProviders } = useStrategyProviders()
const updateDatasetsDetail = useDatasetsDetailStore(s => s.updateDatasetsDetail) const updateDatasetsDetail = useDatasetsDetailStore(s => s.updateDatasetsDetail)
const updateTime = useRef(0) const updateTime = useRef(0)
const { getStartNodes } = useWorkflow()
const workflowStore = useWorkflowStore() const workflowStore = useWorkflowStore()
const { getNodesAvailableVarList } = useGetNodesAvailableVarList() const { getNodesAvailableVarList } = useGetNodesAvailableVarList()
@ -244,20 +235,11 @@ export const useChecklistBeforePublish = () => {
} = workflowStore.getState() } = workflowStore.getState()
const nodes = getNodes() const nodes = getNodes()
const filteredNodes = nodes.filter(node => node.type === CUSTOM_NODE) const filteredNodes = nodes.filter(node => node.type === CUSTOM_NODE)
const startNodes = getStartNodes(filteredNodes) const { validNodes, maxDepth } = getValidTreeNodes(filteredNodes, edges)
const validNodesFlattened = startNodes.map(startNode => getValidTreeNodes(startNode, filteredNodes, edges))
const validNodes = validNodesFlattened.reduce((acc, curr) => {
if (curr.validNodes)
acc.push(...curr.validNodes)
return acc
}, [] as Node[])
const maxDepthArr = validNodesFlattened.map(item => item.maxDepth)
for (let i = 0; i < maxDepthArr.length; i++) { if (maxDepth > MAX_TREE_DEPTH) {
if (maxDepthArr[i] > MAX_TREE_DEPTH) { notify({ type: 'error', message: t('workflow.common.maxTreeDepth', { depth: MAX_TREE_DEPTH }) })
notify({ type: 'error', message: t('workflow.common.maxTreeDepth', { depth: MAX_TREE_DEPTH }) }) return false
return false
}
} }
// Before publish, we need to fetch datasets detail, in case of the settings of datasets have been changed // Before publish, we need to fetch datasets detail, in case of the settings of datasets have been changed
const knowledgeRetrievalNodes = filteredNodes.filter(node => node.data.type === BlockEnum.KnowledgeRetrieval) const knowledgeRetrievalNodes = filteredNodes.filter(node => node.data.type === BlockEnum.KnowledgeRetrieval)
@ -360,7 +342,7 @@ export const useChecklistBeforePublish = () => {
} }
return true return true
}, [store, notify, t, language, nodesExtraData, strategyProviders, updateDatasetsDetail, getCheckData, getStartNodes, workflowStore]) }, [store, notify, t, language, nodesExtraData, strategyProviders, updateDatasetsDetail, getCheckData, workflowStore])
return { return {
handleCheckBeforePublish, handleCheckBeforePublish,

View File

@ -92,8 +92,16 @@ export const getNodesConnectedSourceOrTargetHandleIdsMap = (changes: ConnectedSo
return nodesConnectedSourceOrTargetHandleIdsMap return nodesConnectedSourceOrTargetHandleIdsMap
} }
export const getValidTreeNodes = (startNode: Node, nodes: Node[], edges: Edge[]) => { export const getValidTreeNodes = (nodes: Node[], edges: Edge[]) => {
if (!startNode) { // Find all start nodes (Start and Trigger nodes)
const startNodes = nodes.filter(node =>
node.data.type === BlockEnum.Start
|| node.data.type === BlockEnum.TriggerSchedule
|| node.data.type === BlockEnum.TriggerWebhook
|| node.data.type === BlockEnum.TriggerPlugin,
)
if (startNodes.length === 0) {
return { return {
validNodes: [], validNodes: [],
maxDepth: 0, maxDepth: 0,
@ -134,18 +142,11 @@ export const getValidTreeNodes = (startNode: Node, nodes: Node[], edges: Edge[])
} }
} }
// const startNodes = nodes.filter(node => // Start traversal from all start nodes
// node.data.type === BlockEnum.Start startNodes.forEach((startNode) => {
// || node.data.type === BlockEnum.TriggerSchedule if (!list.find(n => n.id === startNode.id))
// || node.data.type === BlockEnum.TriggerWebhook traverse(startNode, 1)
// || node.data.type === BlockEnum.TriggerPlugin, })
// )
// // Start traversal from all start nodes
// startNodes.forEach((startNode) => {
// if (!list.find(n => n.id === startNode.id))
// traverse(startNode, 1)
// })
return { return {
validNodes: uniqBy(list, 'id'), validNodes: uniqBy(list, 'id'),