mirror of
https://github.com/langgenius/dify.git
synced 2026-07-20 09:38:32 +08:00
fix(web): settle running nodes after workflow failure (#39173)
This commit is contained in:
parent
af7e59de7c
commit
f962c9e47a
@ -1,15 +1,118 @@
|
||||
import { baseRunningData, renderWorkflowHook } from '../../../__tests__/workflow-test-env'
|
||||
import { WorkflowRunningStatus } from '../../../types'
|
||||
import { act, waitFor } from '@testing-library/react'
|
||||
import { createEdge, createNode, createNodeTracing } from '../../../__tests__/fixtures'
|
||||
import { baseRunningData } from '../../../__tests__/workflow-test-env'
|
||||
import { NodeRunningStatus, WorkflowRunningStatus } from '../../../types'
|
||||
import { useWorkflowFailed } from '../use-workflow-failed'
|
||||
import { getEdgeRuntimeState, getNodeRuntimeState, renderRunEventHook } from './test-helpers'
|
||||
|
||||
describe('useWorkflowFailed', () => {
|
||||
it('sets status to Failed', () => {
|
||||
const { result, store } = renderWorkflowHook(() => useWorkflowFailed(), {
|
||||
initialStoreState: { workflowRunningData: baseRunningData() },
|
||||
it('settles active workflow and canvas state as failed', async () => {
|
||||
const { result, store } = renderRunEventHook(() => useWorkflowFailed(), {
|
||||
nodes: [
|
||||
createNode({
|
||||
id: 'running-node',
|
||||
data: {
|
||||
_runningStatus: NodeRunningStatus.Running,
|
||||
_waitingRun: false,
|
||||
},
|
||||
}),
|
||||
createNode({
|
||||
id: 'succeeded-node',
|
||||
data: {
|
||||
_runningStatus: NodeRunningStatus.Succeeded,
|
||||
_waitingRun: false,
|
||||
},
|
||||
}),
|
||||
createNode({
|
||||
id: 'waiting-node',
|
||||
data: { _waitingRun: true },
|
||||
}),
|
||||
],
|
||||
edges: [
|
||||
createEdge({
|
||||
id: 'running-edge',
|
||||
source: 'succeeded-node',
|
||||
target: 'running-node',
|
||||
data: {
|
||||
_sourceRunningStatus: NodeRunningStatus.Succeeded,
|
||||
_targetRunningStatus: NodeRunningStatus.Running,
|
||||
_waitingRun: false,
|
||||
},
|
||||
}),
|
||||
createEdge({
|
||||
id: 'waiting-edge',
|
||||
source: 'running-node',
|
||||
target: 'waiting-node',
|
||||
data: {
|
||||
_sourceRunningStatus: NodeRunningStatus.Running,
|
||||
_waitingRun: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
initialStoreState: {
|
||||
workflowRunningData: baseRunningData({
|
||||
tracing: [
|
||||
createNodeTracing({
|
||||
node_id: 'running-node',
|
||||
status: NodeRunningStatus.Running,
|
||||
}),
|
||||
createNodeTracing({
|
||||
node_id: 'succeeded-node',
|
||||
status: NodeRunningStatus.Succeeded,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
result.current.handleWorkflowFailed()
|
||||
act(() => {
|
||||
result.current.handleWorkflowFailed()
|
||||
})
|
||||
|
||||
expect(store.getState().workflowRunningData!.result.status).toBe(WorkflowRunningStatus.Failed)
|
||||
expect(store.getState().workflowRunningData!.tracing!.map((trace) => trace.status)).toEqual([
|
||||
NodeRunningStatus.Failed,
|
||||
NodeRunningStatus.Succeeded,
|
||||
])
|
||||
|
||||
await waitFor(() => {
|
||||
const runningNode = result.current.nodes.find((node) => node.id === 'running-node')
|
||||
const succeededNode = result.current.nodes.find((node) => node.id === 'succeeded-node')
|
||||
const waitingNode = result.current.nodes.find((node) => node.id === 'waiting-node')
|
||||
const runningEdge = result.current.edges.find((edge) => edge.id === 'running-edge')
|
||||
const waitingEdge = result.current.edges.find((edge) => edge.id === 'waiting-edge')
|
||||
|
||||
expect(getNodeRuntimeState(runningNode)).toMatchObject({
|
||||
_runningStatus: NodeRunningStatus.Failed,
|
||||
_waitingRun: false,
|
||||
})
|
||||
expect(getNodeRuntimeState(succeededNode)._runningStatus).toBe(NodeRunningStatus.Succeeded)
|
||||
expect(getNodeRuntimeState(waitingNode)._waitingRun).toBe(false)
|
||||
expect(getEdgeRuntimeState(runningEdge)).toMatchObject({
|
||||
_sourceRunningStatus: NodeRunningStatus.Succeeded,
|
||||
_targetRunningStatus: NodeRunningStatus.Failed,
|
||||
_waitingRun: false,
|
||||
})
|
||||
expect(getEdgeRuntimeState(waitingEdge)).toMatchObject({
|
||||
_sourceRunningStatus: NodeRunningStatus.Failed,
|
||||
_waitingRun: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('ignores a late failure after the workflow has stopped', () => {
|
||||
const { result, store } = renderRunEventHook(() => useWorkflowFailed(), {
|
||||
initialStoreState: {
|
||||
workflowRunningData: baseRunningData({
|
||||
result: { status: WorkflowRunningStatus.Stopped },
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
act(() => {
|
||||
result.current.handleWorkflowFailed()
|
||||
})
|
||||
|
||||
expect(store.getState().workflowRunningData!.result.status).toBe(WorkflowRunningStatus.Stopped)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,23 +1,59 @@
|
||||
import { produce } from 'immer'
|
||||
import { useCallback } from 'react'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
import { NodeRunningStatus, WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
|
||||
export const useWorkflowFailed = () => {
|
||||
const store = useStoreApi()
|
||||
const workflowStore = useWorkflowStore()
|
||||
|
||||
const handleWorkflowFailed = useCallback(() => {
|
||||
const { workflowRunningData, setWorkflowRunningData } = workflowStore.getState()
|
||||
if (!workflowRunningData) return
|
||||
if (
|
||||
workflowRunningData.result.status === WorkflowRunningStatus.Succeeded ||
|
||||
workflowRunningData.result.status === WorkflowRunningStatus.Failed ||
|
||||
workflowRunningData.result.status === WorkflowRunningStatus.Stopped
|
||||
)
|
||||
return
|
||||
|
||||
const { getNodes, setNodes, edges, setEdges } = store.getState()
|
||||
|
||||
setWorkflowRunningData(
|
||||
produce(workflowRunningData!, (draft) => {
|
||||
produce(workflowRunningData, (draft) => {
|
||||
draft.result = {
|
||||
...draft.result,
|
||||
status: WorkflowRunningStatus.Failed,
|
||||
}
|
||||
draft.tracing?.forEach((trace) => {
|
||||
if (trace.status === NodeRunningStatus.Running) trace.status = NodeRunningStatus.Failed
|
||||
})
|
||||
}),
|
||||
)
|
||||
}, [workflowStore])
|
||||
|
||||
setNodes(
|
||||
produce(getNodes(), (draft) => {
|
||||
draft.forEach((node) => {
|
||||
if (node.data._runningStatus === NodeRunningStatus.Running)
|
||||
node.data._runningStatus = NodeRunningStatus.Failed
|
||||
node.data._waitingRun = false
|
||||
})
|
||||
}),
|
||||
)
|
||||
setEdges(
|
||||
produce(edges, (draft) => {
|
||||
draft.forEach((edge) => {
|
||||
if (!edge.data) return
|
||||
if (edge.data._sourceRunningStatus === NodeRunningStatus.Running)
|
||||
edge.data._sourceRunningStatus = NodeRunningStatus.Failed
|
||||
if (edge.data._targetRunningStatus === NodeRunningStatus.Running)
|
||||
edge.data._targetRunningStatus = NodeRunningStatus.Failed
|
||||
edge.data._waitingRun = false
|
||||
})
|
||||
}),
|
||||
)
|
||||
}, [store, workflowStore])
|
||||
|
||||
return {
|
||||
handleWorkflowFailed,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user