fix(workflow): reset onboarding auto-open flag across flows

This commit is contained in:
lyzno1
2025-10-21 11:19:26 +08:00
parent f02d575379
commit 075173e67d
7 changed files with 108 additions and 22 deletions

View File

@ -18,6 +18,7 @@ describe('Workflow Onboarding Integration Logic', () => {
const mockSetShowOnboarding = jest.fn() const mockSetShowOnboarding = jest.fn()
const mockSetHasSelectedStartNode = jest.fn() const mockSetHasSelectedStartNode = jest.fn()
const mockSetHasShownOnboarding = jest.fn() const mockSetHasShownOnboarding = jest.fn()
const mockSetShouldAutoOpenStartNodeSelector = jest.fn()
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks() jest.clearAllMocks()
@ -31,6 +32,8 @@ describe('Workflow Onboarding Integration Logic', () => {
hasShownOnboarding: false, hasShownOnboarding: false,
setHasShownOnboarding: mockSetHasShownOnboarding, setHasShownOnboarding: mockSetHasShownOnboarding,
notInitialWorkflow: false, notInitialWorkflow: false,
shouldAutoOpenStartNodeSelector: false,
setShouldAutoOpenStartNodeSelector: mockSetShouldAutoOpenStartNodeSelector,
}) })
}) })
@ -180,17 +183,17 @@ describe('Workflow Onboarding Integration Logic', () => {
}) })
}) })
describe('Auto-expand Logic for Node Handles', () => { describe('Auto-open Logic for Node Handles', () => {
/** /**
* Test the auto-expand logic from node-handle.tsx * Test the auto-open logic from node-handle.tsx
* This ensures all trigger types auto-expand the block selector * This ensures all trigger types auto-open the block selector when flagged
*/ */
it('should auto-expand for Start node in new workflow', () => { it('should auto-expand for Start node in new workflow', () => {
const notInitialWorkflow = true const shouldAutoOpenStartNodeSelector = true
const nodeType = BlockEnum.Start const nodeType = BlockEnum.Start
const isChatMode = false const isChatMode = false
const shouldAutoExpand = notInitialWorkflow && ( const shouldAutoExpand = shouldAutoOpenStartNodeSelector && (
nodeType === BlockEnum.Start nodeType === BlockEnum.Start
|| nodeType === BlockEnum.TriggerSchedule || nodeType === BlockEnum.TriggerSchedule
|| nodeType === BlockEnum.TriggerWebhook || nodeType === BlockEnum.TriggerWebhook
@ -201,11 +204,11 @@ describe('Workflow Onboarding Integration Logic', () => {
}) })
it('should auto-expand for TriggerSchedule in new workflow', () => { it('should auto-expand for TriggerSchedule in new workflow', () => {
const notInitialWorkflow = true const shouldAutoOpenStartNodeSelector = true
const nodeType = BlockEnum.TriggerSchedule const nodeType = BlockEnum.TriggerSchedule
const isChatMode = false const isChatMode = false
const shouldAutoExpand = notInitialWorkflow && ( const shouldAutoExpand = shouldAutoOpenStartNodeSelector && (
nodeType === BlockEnum.Start nodeType === BlockEnum.Start
|| nodeType === BlockEnum.TriggerSchedule || nodeType === BlockEnum.TriggerSchedule
|| nodeType === BlockEnum.TriggerWebhook || nodeType === BlockEnum.TriggerWebhook
@ -216,11 +219,11 @@ describe('Workflow Onboarding Integration Logic', () => {
}) })
it('should auto-expand for TriggerWebhook in new workflow', () => { it('should auto-expand for TriggerWebhook in new workflow', () => {
const notInitialWorkflow = true const shouldAutoOpenStartNodeSelector = true
const nodeType = BlockEnum.TriggerWebhook const nodeType = BlockEnum.TriggerWebhook
const isChatMode = false const isChatMode = false
const shouldAutoExpand = notInitialWorkflow && ( const shouldAutoExpand = shouldAutoOpenStartNodeSelector && (
nodeType === BlockEnum.Start nodeType === BlockEnum.Start
|| nodeType === BlockEnum.TriggerSchedule || nodeType === BlockEnum.TriggerSchedule
|| nodeType === BlockEnum.TriggerWebhook || nodeType === BlockEnum.TriggerWebhook
@ -231,11 +234,11 @@ describe('Workflow Onboarding Integration Logic', () => {
}) })
it('should auto-expand for TriggerPlugin in new workflow', () => { it('should auto-expand for TriggerPlugin in new workflow', () => {
const notInitialWorkflow = true const shouldAutoOpenStartNodeSelector = true
const nodeType = BlockEnum.TriggerPlugin const nodeType = BlockEnum.TriggerPlugin
const isChatMode = false const isChatMode = false
const shouldAutoExpand = notInitialWorkflow && ( const shouldAutoExpand = shouldAutoOpenStartNodeSelector && (
nodeType === BlockEnum.Start nodeType === BlockEnum.Start
|| nodeType === BlockEnum.TriggerSchedule || nodeType === BlockEnum.TriggerSchedule
|| nodeType === BlockEnum.TriggerWebhook || nodeType === BlockEnum.TriggerWebhook
@ -246,11 +249,11 @@ describe('Workflow Onboarding Integration Logic', () => {
}) })
it('should not auto-expand for non-trigger nodes', () => { it('should not auto-expand for non-trigger nodes', () => {
const notInitialWorkflow = true const shouldAutoOpenStartNodeSelector = true
const nodeType = BlockEnum.LLM const nodeType = BlockEnum.LLM
const isChatMode = false const isChatMode = false
const shouldAutoExpand = notInitialWorkflow && ( const shouldAutoExpand = shouldAutoOpenStartNodeSelector && (
nodeType === BlockEnum.Start nodeType === BlockEnum.Start
|| nodeType === BlockEnum.TriggerSchedule || nodeType === BlockEnum.TriggerSchedule
|| nodeType === BlockEnum.TriggerWebhook || nodeType === BlockEnum.TriggerWebhook
@ -261,11 +264,11 @@ describe('Workflow Onboarding Integration Logic', () => {
}) })
it('should not auto-expand in chat mode', () => { it('should not auto-expand in chat mode', () => {
const notInitialWorkflow = true const shouldAutoOpenStartNodeSelector = true
const nodeType = BlockEnum.Start const nodeType = BlockEnum.Start
const isChatMode = true const isChatMode = true
const shouldAutoExpand = notInitialWorkflow && ( const shouldAutoExpand = shouldAutoOpenStartNodeSelector && (
nodeType === BlockEnum.Start nodeType === BlockEnum.Start
|| nodeType === BlockEnum.TriggerSchedule || nodeType === BlockEnum.TriggerSchedule
|| nodeType === BlockEnum.TriggerWebhook || nodeType === BlockEnum.TriggerWebhook
@ -276,11 +279,11 @@ describe('Workflow Onboarding Integration Logic', () => {
}) })
it('should not auto-expand for existing workflows', () => { it('should not auto-expand for existing workflows', () => {
const notInitialWorkflow = false const shouldAutoOpenStartNodeSelector = false
const nodeType = BlockEnum.Start const nodeType = BlockEnum.Start
const isChatMode = false const isChatMode = false
const shouldAutoExpand = notInitialWorkflow && ( const shouldAutoExpand = shouldAutoOpenStartNodeSelector && (
nodeType === BlockEnum.Start nodeType === BlockEnum.Start
|| nodeType === BlockEnum.TriggerSchedule || nodeType === BlockEnum.TriggerSchedule
|| nodeType === BlockEnum.TriggerWebhook || nodeType === BlockEnum.TriggerWebhook
@ -289,6 +292,24 @@ describe('Workflow Onboarding Integration Logic', () => {
expect(shouldAutoExpand).toBe(false) expect(shouldAutoExpand).toBe(false)
}) })
it('should reset auto-open flag after triggering once', () => {
let shouldAutoOpenStartNodeSelector = true
const nodeType = BlockEnum.Start
const isChatMode = false
const shouldAutoExpand = shouldAutoOpenStartNodeSelector && (
nodeType === BlockEnum.Start
|| nodeType === BlockEnum.TriggerSchedule
|| nodeType === BlockEnum.TriggerWebhook
|| nodeType === BlockEnum.TriggerPlugin
) && !isChatMode
if (shouldAutoExpand)
shouldAutoOpenStartNodeSelector = false
expect(shouldAutoExpand).toBe(true)
expect(shouldAutoOpenStartNodeSelector).toBe(false)
})
}) })
describe('Node Creation Without Auto-selection', () => { describe('Node Creation Without Auto-selection', () => {
@ -450,12 +471,19 @@ describe('Workflow Onboarding Integration Logic', () => {
notInitialWorkflow: false, notInitialWorkflow: false,
setShowOnboarding: mockSetShowOnboarding, setShowOnboarding: mockSetShowOnboarding,
setHasShownOnboarding: mockSetHasShownOnboarding, setHasShownOnboarding: mockSetHasShownOnboarding,
hasSelectedStartNode: false,
setHasSelectedStartNode: mockSetHasSelectedStartNode,
shouldAutoOpenStartNodeSelector: false,
setShouldAutoOpenStartNodeSelector: mockSetShouldAutoOpenStartNodeSelector,
getState: () => ({ getState: () => ({
showOnboarding: false, showOnboarding: false,
hasShownOnboarding: false, hasShownOnboarding: false,
notInitialWorkflow: false, notInitialWorkflow: false,
setShowOnboarding: mockSetShowOnboarding, setShowOnboarding: mockSetShowOnboarding,
setHasShownOnboarding: mockSetHasShownOnboarding, setHasShownOnboarding: mockSetHasShownOnboarding,
hasSelectedStartNode: false,
setHasSelectedStartNode: mockSetHasSelectedStartNode,
setShouldAutoOpenStartNodeSelector: mockSetShouldAutoOpenStartNodeSelector,
}), }),
}) })
@ -526,12 +554,19 @@ describe('Workflow Onboarding Integration Logic', () => {
notInitialWorkflow: false, notInitialWorkflow: false,
setShowOnboarding: mockSetShowOnboarding, setShowOnboarding: mockSetShowOnboarding,
setHasShownOnboarding: mockSetHasShownOnboarding, setHasShownOnboarding: mockSetHasShownOnboarding,
hasSelectedStartNode: false,
setHasSelectedStartNode: mockSetHasSelectedStartNode,
shouldAutoOpenStartNodeSelector: false,
setShouldAutoOpenStartNodeSelector: mockSetShouldAutoOpenStartNodeSelector,
getState: () => ({ getState: () => ({
showOnboarding: false, showOnboarding: false,
hasShownOnboarding: true, hasShownOnboarding: true,
notInitialWorkflow: false, notInitialWorkflow: false,
setShowOnboarding: mockSetShowOnboarding, setShowOnboarding: mockSetShowOnboarding,
setHasShownOnboarding: mockSetHasShownOnboarding, setHasShownOnboarding: mockSetHasShownOnboarding,
hasSelectedStartNode: false,
setHasSelectedStartNode: mockSetHasSelectedStartNode,
setShouldAutoOpenStartNodeSelector: mockSetShouldAutoOpenStartNodeSelector,
}), }),
}) })
@ -553,12 +588,19 @@ describe('Workflow Onboarding Integration Logic', () => {
notInitialWorkflow: true, // Initial workflow creation notInitialWorkflow: true, // Initial workflow creation
setShowOnboarding: mockSetShowOnboarding, setShowOnboarding: mockSetShowOnboarding,
setHasShownOnboarding: mockSetHasShownOnboarding, setHasShownOnboarding: mockSetHasShownOnboarding,
hasSelectedStartNode: false,
setHasSelectedStartNode: mockSetHasSelectedStartNode,
shouldAutoOpenStartNodeSelector: false,
setShouldAutoOpenStartNodeSelector: mockSetShouldAutoOpenStartNodeSelector,
getState: () => ({ getState: () => ({
showOnboarding: false, showOnboarding: false,
hasShownOnboarding: false, hasShownOnboarding: false,
notInitialWorkflow: true, notInitialWorkflow: true,
setShowOnboarding: mockSetShowOnboarding, setShowOnboarding: mockSetShowOnboarding,
setHasShownOnboarding: mockSetHasShownOnboarding, setHasShownOnboarding: mockSetHasShownOnboarding,
hasSelectedStartNode: false,
setHasSelectedStartNode: mockSetHasSelectedStartNode,
setShouldAutoOpenStartNodeSelector: mockSetShouldAutoOpenStartNodeSelector,
}), }),
}) })

View File

@ -60,7 +60,10 @@ export const usePipelineInit = () => {
if (error && error.json && !error.bodyUsed && datasetId) { if (error && error.json && !error.bodyUsed && datasetId) {
error.json().then((err: any) => { error.json().then((err: any) => {
if (err.code === 'draft_workflow_not_exist') { if (err.code === 'draft_workflow_not_exist') {
workflowStore.setState({ notInitialWorkflow: true }) workflowStore.setState({
notInitialWorkflow: true,
shouldAutoOpenStartNodeSelector: true,
})
syncWorkflowDraft({ syncWorkflowDraft({
url: `/rag/pipelines/${datasetId}/workflows/draft`, url: `/rag/pipelines/${datasetId}/workflows/draft`,
params: { params: {

View File

@ -75,6 +75,7 @@ const WorkflowChildren = () => {
const showOnboarding = useStore(s => s.showOnboarding) const showOnboarding = useStore(s => s.showOnboarding)
const setShowOnboarding = useStore(s => s.setShowOnboarding) const setShowOnboarding = useStore(s => s.setShowOnboarding)
const setHasSelectedStartNode = useStore(s => s.setHasSelectedStartNode) const setHasSelectedStartNode = useStore(s => s.setHasSelectedStartNode)
const setShouldAutoOpenStartNodeSelector = useStore(s => s.setShouldAutoOpenStartNodeSelector)
const reactFlowStore = useStoreApi() const reactFlowStore = useStoreApi()
const availableNodesMetaData = useAvailableNodesMetaData() const availableNodesMetaData = useAvailableNodesMetaData()
const { handleSyncWorkflowDraft } = useNodesSyncDraft() const { handleSyncWorkflowDraft } = useNodesSyncDraft()
@ -142,6 +143,7 @@ const WorkflowChildren = () => {
setShowOnboarding?.(false) setShowOnboarding?.(false)
setHasSelectedStartNode?.(true) setHasSelectedStartNode?.(true)
setShouldAutoOpenStartNodeSelector?.(true)
handleSyncWorkflowDraft(true, false, { handleSyncWorkflowDraft(true, false, {
onSuccess: () => { onSuccess: () => {

View File

@ -14,6 +14,7 @@ export const useAutoOnboarding = () => {
notInitialWorkflow, notInitialWorkflow,
setShowOnboarding, setShowOnboarding,
setHasShownOnboarding, setHasShownOnboarding,
setShouldAutoOpenStartNodeSelector,
} = workflowStore.getState() } = workflowStore.getState()
// Skip if already showing onboarding or it's the initial workflow creation // Skip if already showing onboarding or it's the initial workflow creation
@ -30,13 +31,24 @@ export const useAutoOnboarding = () => {
if (isCompletelyEmpty && !hasShownOnboarding) { if (isCompletelyEmpty && !hasShownOnboarding) {
setShowOnboarding?.(true) setShowOnboarding?.(true)
setHasShownOnboarding?.(true) setHasShownOnboarding?.(true)
setShouldAutoOpenStartNodeSelector?.(true)
} }
}, [store, workflowStore]) }, [store, workflowStore])
const handleOnboardingClose = useCallback(() => { const handleOnboardingClose = useCallback(() => {
const { setShowOnboarding, setHasShownOnboarding } = workflowStore.getState() const {
setShowOnboarding,
setHasShownOnboarding,
setShouldAutoOpenStartNodeSelector,
hasSelectedStartNode,
setHasSelectedStartNode,
} = workflowStore.getState()
setShowOnboarding?.(false) setShowOnboarding?.(false)
setHasShownOnboarding?.(true) setHasShownOnboarding?.(true)
if (hasSelectedStartNode)
setHasSelectedStartNode?.(false)
else
setShouldAutoOpenStartNodeSelector?.(false)
}, [workflowStore]) }, [workflowStore])
// Check on mount and when nodes change // Check on mount and when nodes change

View File

@ -77,6 +77,7 @@ export const useWorkflowInit = () => {
workflowStore.setState({ workflowStore.setState({
notInitialWorkflow: true, notInitialWorkflow: true,
showOnboarding: !isAdvancedChat, showOnboarding: !isAdvancedChat,
shouldAutoOpenStartNodeSelector: !isAdvancedChat,
hasShownOnboarding: false, hasShownOnboarding: false,
}) })
const nodesData = isAdvancedChat ? nodesTemplate : [] const nodesData = isAdvancedChat ? nodesTemplate : []

View File

@ -5,6 +5,8 @@ export type WorkflowSliceShape = {
appName: string appName: string
notInitialWorkflow: boolean notInitialWorkflow: boolean
setNotInitialWorkflow: (notInitialWorkflow: boolean) => void setNotInitialWorkflow: (notInitialWorkflow: boolean) => void
shouldAutoOpenStartNodeSelector: boolean
setShouldAutoOpenStartNodeSelector: (shouldAutoOpen: boolean) => void
nodesDefaultConfigs: Record<string, any> nodesDefaultConfigs: Record<string, any>
setNodesDefaultConfigs: (nodesDefaultConfigs: Record<string, any>) => void setNodesDefaultConfigs: (nodesDefaultConfigs: Record<string, any>) => void
showOnboarding: boolean showOnboarding: boolean
@ -21,6 +23,8 @@ export const createWorkflowSlice: StateCreator<WorkflowSliceShape> = set => ({
appName: '', appName: '',
notInitialWorkflow: false, notInitialWorkflow: false,
setNotInitialWorkflow: notInitialWorkflow => set(() => ({ notInitialWorkflow })), setNotInitialWorkflow: notInitialWorkflow => set(() => ({ notInitialWorkflow })),
shouldAutoOpenStartNodeSelector: false,
setShouldAutoOpenStartNodeSelector: shouldAutoOpenStartNodeSelector => set(() => ({ shouldAutoOpenStartNodeSelector })),
nodesDefaultConfigs: {}, nodesDefaultConfigs: {},
setNodesDefaultConfigs: nodesDefaultConfigs => set(() => ({ nodesDefaultConfigs })), setNodesDefaultConfigs: nodesDefaultConfigs => set(() => ({ nodesDefaultConfigs })),
showOnboarding: false, showOnboarding: false,

View File

@ -25,6 +25,7 @@ import {
} from '../../../hooks' } from '../../../hooks'
import { import {
useStore, useStore,
useWorkflowStore,
} from '../../../store' } from '../../../store'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
@ -127,7 +128,10 @@ export const NodeSourceHandle = memo(({
showExceptionStatus, showExceptionStatus,
}: NodeHandleProps) => { }: NodeHandleProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const notInitialWorkflow = useStore(s => s.notInitialWorkflow) const shouldAutoOpenStartNodeSelector = useStore(s => s.shouldAutoOpenStartNodeSelector)
const setShouldAutoOpenStartNodeSelector = useStore(s => s.setShouldAutoOpenStartNodeSelector)
const setHasSelectedStartNode = useStore(s => s.setHasSelectedStartNode)
const workflowStoreApi = useWorkflowStore()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const { handleNodeAdd } = useNodesInteractions() const { handleNodeAdd } = useNodesInteractions()
const { getNodesReadOnly } = useNodesReadOnly() const { getNodesReadOnly } = useNodesReadOnly()
@ -157,9 +161,27 @@ export const NodeSourceHandle = memo(({
}, [handleNodeAdd, id, handleId]) }, [handleNodeAdd, id, handleId])
useEffect(() => { useEffect(() => {
if (notInitialWorkflow && (data.type === BlockEnum.Start || data.type === BlockEnum.TriggerSchedule || data.type === BlockEnum.TriggerWebhook || data.type === BlockEnum.TriggerPlugin) && !isChatMode) if (!shouldAutoOpenStartNodeSelector)
return
if (isChatMode) {
setShouldAutoOpenStartNodeSelector?.(false)
return
}
if (data.type === BlockEnum.Start || data.type === BlockEnum.TriggerSchedule || data.type === BlockEnum.TriggerWebhook || data.type === BlockEnum.TriggerPlugin) {
setOpen(true) setOpen(true)
}, [notInitialWorkflow, data.type, isChatMode]) if (setShouldAutoOpenStartNodeSelector)
setShouldAutoOpenStartNodeSelector(false)
else
workflowStoreApi?.setState?.({ shouldAutoOpenStartNodeSelector: false })
if (setHasSelectedStartNode)
setHasSelectedStartNode(false)
else
workflowStoreApi?.setState?.({ hasSelectedStartNode: false })
}
}, [shouldAutoOpenStartNodeSelector, data.type, isChatMode, setShouldAutoOpenStartNodeSelector, setHasSelectedStartNode, workflowStoreApi])
return ( return (
<Handle <Handle