diff --git a/web/app/components/workflow-app/hooks/__tests__/use-workflow-run-callbacks.spec.ts b/web/app/components/workflow-app/hooks/__tests__/use-workflow-run-callbacks.spec.ts index d75a54101f7..eff299a56c2 100644 --- a/web/app/components/workflow-app/hooks/__tests__/use-workflow-run-callbacks.spec.ts +++ b/web/app/components/workflow-app/hooks/__tests__/use-workflow-run-callbacks.spec.ts @@ -124,13 +124,15 @@ describe('useWorkflowRun callbacks helpers', () => { expect(fetchInspectVars).toHaveBeenCalledWith({}) expect(invalidAllLastRun).toHaveBeenCalled() - callbacks.onError?.({ error: 'failed', node_type: 'llm' } as never) + callbacks.onError?.('LLM provider and model are required.') expect(clearAbortController).toHaveBeenCalled() - expect(handlers.handleWorkflowFailed).toHaveBeenCalled() + expect(handlers.handleWorkflowFailed).toHaveBeenCalledWith( + 'LLM provider and model are required.', + ) expect(userOnError).toHaveBeenCalled() expect(getWorkflowRunningData).toHaveBeenCalled() expect(trackWorkflowRunFailed).toHaveBeenCalledWith( - { error: 'failed', node_type: 'llm' }, + 'LLM provider and model are required.', workflowData, ) diff --git a/web/app/components/workflow-app/hooks/use-workflow-run-callbacks.ts b/web/app/components/workflow-app/hooks/use-workflow-run-callbacks.ts index 30411c04829..c0f7524a8c9 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-run-callbacks.ts +++ b/web/app/components/workflow-app/hooks/use-workflow-run-callbacks.ts @@ -11,7 +11,7 @@ type ContainerSize = { type WorkflowRunEventHandlers = { handleWorkflowStarted: NonNullable handleWorkflowFinished: NonNullable - handleWorkflowFailed: () => void + handleWorkflowFailed: (error?: string) => void handleWorkflowNodeStarted: ( params: Parameters>[0], containerParams: ContainerSize, @@ -150,7 +150,7 @@ export const createBaseWorkflowRunCallbacks = ({ const wrappedOnError: IOtherOptions['onError'] = (params, code) => { clearAbortController() - handleWorkflowFailed() + handleWorkflowFailed(params) const workflowData = getWorkflowRunningData() invalidateRunHistory(runHistoryUrl) clearListeningState() @@ -358,7 +358,7 @@ export const createFinalWorkflowRunCallbacks = ({ }, onError: (params, code) => { clearAbortController() - handleWorkflowFailed() + handleWorkflowFailed(params) const workflowData = getWorkflowRunningData() invalidateRunHistory(runHistoryUrl) clearListeningState() diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/__tests__/use-workflow-failed.spec.ts b/web/app/components/workflow/hooks/use-workflow-run-event/__tests__/use-workflow-failed.spec.ts index b381801aabd..f54e9c4bb27 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/__tests__/use-workflow-failed.spec.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/__tests__/use-workflow-failed.spec.ts @@ -66,10 +66,13 @@ describe('useWorkflowFailed', () => { }) act(() => { - result.current.handleWorkflowFailed() + result.current.handleWorkflowFailed('LLM provider and model are required.') }) expect(store.getState().workflowRunningData!.result.status).toBe(WorkflowRunningStatus.Failed) + expect(store.getState().workflowRunningData!.result.error).toBe( + 'LLM provider and model are required.', + ) expect(store.getState().workflowRunningData!.tracing!.map((trace) => trace.status)).toEqual([ NodeRunningStatus.Failed, NodeRunningStatus.Succeeded, @@ -115,4 +118,44 @@ describe('useWorkflowFailed', () => { expect(store.getState().workflowRunningData!.result.status).toBe(WorkflowRunningStatus.Stopped) }) + + it('adds a late error message to an already failed workflow', () => { + const { result, store } = renderRunEventHook(() => useWorkflowFailed(), { + initialStoreState: { + workflowRunningData: baseRunningData({ + result: { status: WorkflowRunningStatus.Failed }, + }), + }, + }) + + act(() => { + result.current.handleWorkflowFailed('LLM provider and model are required.') + }) + + expect(store.getState().workflowRunningData!.result).toMatchObject({ + status: WorkflowRunningStatus.Failed, + error: 'LLM provider and model are required.', + }) + }) + + it('keeps the workflow_finished error when a generic error arrives later', () => { + const { result, store } = renderRunEventHook(() => useWorkflowFailed(), { + initialStoreState: { + workflowRunningData: baseRunningData({ + result: { + status: WorkflowRunningStatus.Failed, + error: 'LLM provider and model are required.', + }, + }), + }, + }) + + act(() => { + result.current.handleWorkflowFailed('Server Error') + }) + + expect(store.getState().workflowRunningData!.result.error).toBe( + 'LLM provider and model are required.', + ) + }) }) diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-failed.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-failed.ts index b50663dc214..3a56ed87bcd 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-failed.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-failed.ts @@ -8,52 +8,66 @@ 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 handleWorkflowFailed = useCallback( + (error?: string) => { + const { workflowRunningData, setWorkflowRunningData } = workflowStore.getState() + if (!workflowRunningData) return + if ( + workflowRunningData.result.status === WorkflowRunningStatus.Succeeded || + workflowRunningData.result.status === WorkflowRunningStatus.Stopped + ) + return - const { getNodes, setNodes, edges, setEdges } = store.getState() + if (workflowRunningData.result.status === WorkflowRunningStatus.Failed) { + if (!error || workflowRunningData.result.error) return - setWorkflowRunningData( - produce(workflowRunningData, (draft) => { - draft.result = { - ...draft.result, - status: WorkflowRunningStatus.Failed, - } - draft.tracing?.forEach((trace) => { - if (trace.status === NodeRunningStatus.Running) trace.status = NodeRunningStatus.Failed - }) - }), - ) + setWorkflowRunningData( + produce(workflowRunningData, (draft) => { + draft.result.error = error + }), + ) + return + } - 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]) + const { getNodes, setNodes, edges, setEdges } = store.getState() + + setWorkflowRunningData( + produce(workflowRunningData, (draft) => { + draft.result = { + ...draft.result, + status: WorkflowRunningStatus.Failed, + error, + } + draft.tracing?.forEach((trace) => { + if (trace.status === NodeRunningStatus.Running) trace.status = NodeRunningStatus.Failed + }) + }), + ) + + 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,