mirror of https://github.com/langgenius/dify.git
fix(web): preserve public workflow SSE reconnect after pause (#33552)
This commit is contained in:
parent
18ff5d9288
commit
239e09473e
|
|
@ -328,7 +328,7 @@ describe('createWorkflowStreamHandlers', () => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
const setupHandlers = (overrides: { isTimedOut?: () => boolean } = {}) => {
|
const setupHandlers = (overrides: { isPublicAPI?: boolean, isTimedOut?: () => boolean } = {}) => {
|
||||||
let completionRes = ''
|
let completionRes = ''
|
||||||
let currentTaskId: string | null = null
|
let currentTaskId: string | null = null
|
||||||
let isStopping = false
|
let isStopping = false
|
||||||
|
|
@ -359,6 +359,7 @@ describe('createWorkflowStreamHandlers', () => {
|
||||||
const handlers = createWorkflowStreamHandlers({
|
const handlers = createWorkflowStreamHandlers({
|
||||||
getCompletionRes: () => completionRes,
|
getCompletionRes: () => completionRes,
|
||||||
getWorkflowProcessData: () => workflowProcessData,
|
getWorkflowProcessData: () => workflowProcessData,
|
||||||
|
isPublicAPI: overrides.isPublicAPI ?? false,
|
||||||
isTimedOut: overrides.isTimedOut ?? (() => false),
|
isTimedOut: overrides.isTimedOut ?? (() => false),
|
||||||
markEnded,
|
markEnded,
|
||||||
notify,
|
notify,
|
||||||
|
|
@ -391,7 +392,7 @@ describe('createWorkflowStreamHandlers', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should process workflow success and paused events', () => {
|
it('should process workflow success and paused events', () => {
|
||||||
const setup = setupHandlers()
|
const setup = setupHandlers({ isPublicAPI: true })
|
||||||
const handlers = setup.handlers as Required<Pick<IOtherOptions, 'onWorkflowStarted' | 'onTextChunk' | 'onHumanInputRequired' | 'onHumanInputFormFilled' | 'onHumanInputFormTimeout' | 'onWorkflowPaused' | 'onWorkflowFinished' | 'onNodeStarted' | 'onNodeFinished' | 'onIterationStart' | 'onIterationNext' | 'onIterationFinish' | 'onLoopStart' | 'onLoopNext' | 'onLoopFinish'>>
|
const handlers = setup.handlers as Required<Pick<IOtherOptions, 'onWorkflowStarted' | 'onTextChunk' | 'onHumanInputRequired' | 'onHumanInputFormFilled' | 'onHumanInputFormTimeout' | 'onWorkflowPaused' | 'onWorkflowFinished' | 'onNodeStarted' | 'onNodeFinished' | 'onIterationStart' | 'onIterationNext' | 'onIterationFinish' | 'onLoopStart' | 'onLoopNext' | 'onLoopFinish'>>
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
|
|
@ -546,7 +547,11 @@ describe('createWorkflowStreamHandlers', () => {
|
||||||
resultText: 'Hello',
|
resultText: 'Hello',
|
||||||
status: WorkflowRunningStatus.Succeeded,
|
status: WorkflowRunningStatus.Succeeded,
|
||||||
}))
|
}))
|
||||||
expect(sseGetMock).toHaveBeenCalledWith('/workflow/run-1/events', {}, expect.any(Object))
|
expect(sseGetMock).toHaveBeenCalledWith(
|
||||||
|
'/workflow/run-1/events',
|
||||||
|
{},
|
||||||
|
expect.objectContaining({ isPublicAPI: true }),
|
||||||
|
)
|
||||||
expect(setup.messageId()).toBe('run-1')
|
expect(setup.messageId()).toBe('run-1')
|
||||||
expect(setup.onCompleted).toHaveBeenCalledWith('{"answer":"Hello"}', 3, true)
|
expect(setup.onCompleted).toHaveBeenCalledWith('{"answer":"Hello"}', 3, true)
|
||||||
expect(setup.setRespondingFalse).toHaveBeenCalled()
|
expect(setup.setRespondingFalse).toHaveBeenCalled()
|
||||||
|
|
@ -647,6 +652,7 @@ describe('createWorkflowStreamHandlers', () => {
|
||||||
const handlers = createWorkflowStreamHandlers({
|
const handlers = createWorkflowStreamHandlers({
|
||||||
getCompletionRes: () => '',
|
getCompletionRes: () => '',
|
||||||
getWorkflowProcessData: () => existingProcess,
|
getWorkflowProcessData: () => existingProcess,
|
||||||
|
isPublicAPI: false,
|
||||||
isTimedOut: () => false,
|
isTimedOut: () => false,
|
||||||
markEnded: vi.fn(),
|
markEnded: vi.fn(),
|
||||||
notify: setup.notify,
|
notify: setup.notify,
|
||||||
|
|
|
||||||
|
|
@ -351,6 +351,7 @@ describe('useResultSender', () => {
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(createWorkflowStreamHandlersMock).toHaveBeenCalledWith(expect.objectContaining({
|
expect(createWorkflowStreamHandlersMock).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
getCompletionRes: harness.runState.getCompletionRes,
|
getCompletionRes: harness.runState.getCompletionRes,
|
||||||
|
isPublicAPI: true,
|
||||||
resetRunState: harness.runState.resetRunState,
|
resetRunState: harness.runState.resetRunState,
|
||||||
setWorkflowProcessData: harness.runState.setWorkflowProcessData,
|
setWorkflowProcessData: harness.runState.setWorkflowProcessData,
|
||||||
}))
|
}))
|
||||||
|
|
@ -373,6 +374,30 @@ describe('useResultSender', () => {
|
||||||
expect(harness.runState.clearMoreLikeThis).not.toHaveBeenCalled()
|
expect(harness.runState.clearMoreLikeThis).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should configure workflow handlers for installed apps as non-public', async () => {
|
||||||
|
const harness = createRunStateHarness()
|
||||||
|
|
||||||
|
const { result } = renderSender({
|
||||||
|
appSourceType: AppSourceTypeEnum.installedApp,
|
||||||
|
isWorkflow: true,
|
||||||
|
runState: harness.runState,
|
||||||
|
})
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
expect(await result.current.handleSend()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(createWorkflowStreamHandlersMock).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
isPublicAPI: false,
|
||||||
|
}))
|
||||||
|
expect(sendWorkflowMessageMock).toHaveBeenCalledWith(
|
||||||
|
{ inputs: { name: 'Alice' } },
|
||||||
|
expect.any(Object),
|
||||||
|
AppSourceTypeEnum.installedApp,
|
||||||
|
'app-1',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
it('should stringify non-Error workflow failures', async () => {
|
it('should stringify non-Error workflow failures', async () => {
|
||||||
const harness = createRunStateHarness()
|
const harness = createRunStateHarness()
|
||||||
sendWorkflowMessageMock.mockRejectedValue('workflow failed')
|
sendWorkflowMessageMock.mockRejectedValue('workflow failed')
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import type { ResultInputValue } from '../result-request'
|
import type { ResultInputValue } from '../result-request'
|
||||||
import type { ResultRunStateController } from './use-result-run-state'
|
import type { ResultRunStateController } from './use-result-run-state'
|
||||||
import type { PromptConfig } from '@/models/debug'
|
import type { PromptConfig } from '@/models/debug'
|
||||||
import type { AppSourceType } from '@/service/share'
|
|
||||||
import type { VisionFile, VisionSettings } from '@/types/app'
|
import type { VisionFile, VisionSettings } from '@/types/app'
|
||||||
import { useCallback, useEffect, useRef } from 'react'
|
import { useCallback, useEffect, useRef } from 'react'
|
||||||
import { TEXT_GENERATION_TIMEOUT_MS } from '@/config'
|
import { TEXT_GENERATION_TIMEOUT_MS } from '@/config'
|
||||||
import {
|
import {
|
||||||
|
AppSourceType,
|
||||||
sendCompletionMessage,
|
sendCompletionMessage,
|
||||||
sendWorkflowMessage,
|
sendWorkflowMessage,
|
||||||
} from '@/service/share'
|
} from '@/service/share'
|
||||||
|
|
@ -117,6 +117,7 @@ export const useResultSender = ({
|
||||||
const otherOptions = createWorkflowStreamHandlers({
|
const otherOptions = createWorkflowStreamHandlers({
|
||||||
getCompletionRes: runState.getCompletionRes,
|
getCompletionRes: runState.getCompletionRes,
|
||||||
getWorkflowProcessData: runState.getWorkflowProcessData,
|
getWorkflowProcessData: runState.getWorkflowProcessData,
|
||||||
|
isPublicAPI: appSourceType === AppSourceType.webApp,
|
||||||
isTimedOut: () => isTimeout,
|
isTimedOut: () => isTimeout,
|
||||||
markEnded: () => {
|
markEnded: () => {
|
||||||
isEnd = true
|
isEnd = true
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ type Translate = (key: string, options?: Record<string, unknown>) => string
|
||||||
type CreateWorkflowStreamHandlersParams = {
|
type CreateWorkflowStreamHandlersParams = {
|
||||||
getCompletionRes: () => string
|
getCompletionRes: () => string
|
||||||
getWorkflowProcessData: () => WorkflowProcess | undefined
|
getWorkflowProcessData: () => WorkflowProcess | undefined
|
||||||
|
isPublicAPI: boolean
|
||||||
isTimedOut: () => boolean
|
isTimedOut: () => boolean
|
||||||
markEnded: () => void
|
markEnded: () => void
|
||||||
notify: Notify
|
notify: Notify
|
||||||
|
|
@ -255,6 +256,7 @@ const serializeWorkflowOutputs = (outputs: WorkflowFinishedResponse['data']['out
|
||||||
export const createWorkflowStreamHandlers = ({
|
export const createWorkflowStreamHandlers = ({
|
||||||
getCompletionRes,
|
getCompletionRes,
|
||||||
getWorkflowProcessData,
|
getWorkflowProcessData,
|
||||||
|
isPublicAPI,
|
||||||
isTimedOut,
|
isTimedOut,
|
||||||
markEnded,
|
markEnded,
|
||||||
notify,
|
notify,
|
||||||
|
|
@ -287,6 +289,7 @@ export const createWorkflowStreamHandlers = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
const otherOptions: IOtherOptions = {
|
const otherOptions: IOtherOptions = {
|
||||||
|
isPublicAPI,
|
||||||
onWorkflowStarted: ({ workflow_run_id, task_id }) => {
|
onWorkflowStarted: ({ workflow_run_id, task_id }) => {
|
||||||
const workflowProcessData = getWorkflowProcessData()
|
const workflowProcessData = getWorkflowProcessData()
|
||||||
if (workflowProcessData?.tracing.length) {
|
if (workflowProcessData?.tracing.length) {
|
||||||
|
|
@ -378,6 +381,7 @@ export const createWorkflowStreamHandlers = ({
|
||||||
},
|
},
|
||||||
onWorkflowPaused: ({ data }) => {
|
onWorkflowPaused: ({ data }) => {
|
||||||
tempMessageId = data.workflow_run_id
|
tempMessageId = data.workflow_run_id
|
||||||
|
// WebApp workflows must keep using the public API namespace after pause/resume.
|
||||||
void sseGet(`/workflow/${data.workflow_run_id}/events`, {}, otherOptions)
|
void sseGet(`/workflow/${data.workflow_run_id}/events`, {}, otherOptions)
|
||||||
setWorkflowProcessData(applyWorkflowPaused(getWorkflowProcessData()))
|
setWorkflowProcessData(applyWorkflowPaused(getWorkflowProcessData()))
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue