fix(workflow): reset block selector tab on reopen (#37469)

This commit is contained in:
Jingyi 2026-06-15 17:22:54 -07:00 committed by GitHub
parent de2ec990d8
commit 7bed801b0d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 63 additions and 18 deletions

View File

@ -81,4 +81,22 @@ describe('block-selector hooks', () => {
expect(result.current.tabs.some(tab => tab.key === TabsEnum.Snippets)).toBe(false)
expect(result.current.activeTab).toBe(TabsEnum.Blocks)
})
it('resets the active tab to the current default tab', () => {
const { result } = renderHook(() => useTabs({
noStart: false,
}))
act(() => {
result.current.setActiveTab(TabsEnum.Start)
})
expect(result.current.activeTab).toBe(TabsEnum.Start)
act(() => {
result.current.resetActiveTab()
})
expect(result.current.activeTab).toBe(TabsEnum.Blocks)
})
})

View File

@ -127,6 +127,40 @@ describe('NodeSelector', () => {
expect(screen.getByText('End')).toBeInTheDocument()
})
it('resets to the default tab after closing', async () => {
const user = userEvent.setup()
renderNodeSelector(
<NodeSelector
onSelect={vi.fn()}
blocks={[
createBlock(BlockEnum.LLM, 'LLM'),
]}
availableBlocksTypes={[BlockEnum.LLM, BlockEnum.Start]}
showStartTab
trigger={open => (
<button type="button">
{open ? 'selector-open' : 'selector-closed'}
</button>
)}
/>,
)
await user.click(screen.getByRole('button', { name: 'selector-closed' }))
await user.click(screen.getByText('workflow.tabs.start'))
expect(screen.getByPlaceholderText('workflow.tabs.searchTrigger')).toBeInTheDocument()
await user.click(screen.getByRole('button', { name: 'selector-open' }))
await waitFor(() => {
expect(screen.queryByPlaceholderText('workflow.tabs.searchTrigger')).not.toBeInTheDocument()
})
await user.click(screen.getByRole('button', { name: 'selector-closed' }))
expect(screen.getByPlaceholderText('workflow.tabs.searchBlock')).toBeInTheDocument()
})
it('does not open or emit open changes when disabled', async () => {
const user = userEvent.setup()
const onOpenChange = vi.fn()

View File

@ -119,17 +119,21 @@ export const useTabs = ({
return fallbackTab
}, [defaultActiveTab, noBlocks, noSources, noTools, noSnippets, noStart, tabs, getValidTabKey])
const [activeTab, setActiveTab] = useState(initialTab)
const resetActiveTab = useCallback(() => {
setActiveTab(initialTab)
}, [initialTab])
useEffect(() => {
const currentTab = tabs.find(tab => tab.key === activeTab)
if (!currentTab || currentTab.disabled)
setActiveTab(initialTab)
}, [tabs, activeTab, initialTab])
resetActiveTab()
}, [tabs, activeTab, resetActiveTab])
return {
tabs,
activeTab,
setActiveTab,
resetActiveTab,
}
}

View File

@ -135,6 +135,7 @@ function NodeSelector({
const disableSnippetsTab = flowType === FlowType.snippet
const {
activeTab,
resetActiveTab,
setActiveTab,
tabs,
} = useTabs({
@ -158,6 +159,7 @@ function NodeSelector({
if (!newOpen) {
setSearchText('')
setSnippetsLoading(false)
resetActiveTab()
}
else if (activeTab === TabsEnum.Snippets) {
setSnippetsLoading(true)
@ -165,7 +167,7 @@ function NodeSelector({
if (onOpenChange)
onOpenChange(newOpen)
}, [activeTab, disabled, onOpenChange])
}, [activeTab, disabled, onOpenChange, resetActiveTab])
const handleTrigger = useCallback<MouseEventHandler<HTMLElement>>((e) => {
e.stopPropagation()
}, [])

View File

@ -3,7 +3,6 @@ import { act, screen, waitFor } from '@testing-library/react'
import { FlowType } from '@/types/common'
import { createNode } from '../../__tests__/fixtures'
import { renderWorkflowFlowComponent } from '../../__tests__/workflow-test-env'
import { TabsEnum } from '../../block-selector/types'
import { BlockEnum } from '../../types'
import AddBlock from '../add-block'
@ -21,7 +20,7 @@ type BlockSelectorMockProps = {
popupClassName: string
availableBlocksTypes: BlockEnum[]
showStartTab: boolean
defaultActiveTab?: TabsEnum
defaultActiveTab?: unknown
}
const {
@ -129,10 +128,10 @@ describe('AddBlock', () => {
disabled: false,
availableBlocksTypes: mockAvailableNextBlocks,
showStartTab: true,
defaultActiveTab: TabsEnum.Start,
placement: 'right-start',
popupClassName: 'min-w-[256px]!',
})
expect(latestBlockSelectorProps?.defaultActiveTab).toBeUndefined()
expect(latestBlockSelectorProps?.offset).toEqual({
mainAxis: 4,
crossAxis: -8,

View File

@ -11,16 +11,13 @@ import {
} from 'react'
import { useTranslation } from 'react-i18next'
import {
useNodes,
useStoreApi,
} from 'reactflow'
import BlockSelector from '@/app/components/workflow/block-selector'
import {
BlockEnum,
isTriggerNode,
} from '@/app/components/workflow/types'
import { FlowType } from '@/types/common'
import { TabsEnum } from '../block-selector/types'
import {
useAvailableBlocks,
useIsChatMode,
@ -55,18 +52,10 @@ const AddBlock = ({
const { nodesReadOnly } = useNodesReadOnly()
const { handlePaneContextmenuCancel } = usePanelInteractions()
const [open, setOpen] = useState(false)
const nodes = useNodes()
const { availableNextBlocks } = useAvailableBlocks(BlockEnum.Start, false)
const { nodesMap: nodesMetaDataMap } = useNodesMetaData()
const flowType = useHooksStore(s => s.configsMap?.flowType)
const showStartTab = flowType !== FlowType.ragPipeline && !isChatMode
const hasEntryNode = nodes.some((node) => {
const nodeData = node.data as { type?: BlockEnum }
const nodeType = nodeData.type
return nodeType === BlockEnum.Start || (nodeType ? isTriggerNode(nodeType) : false)
})
const defaultActiveTab = showStartTab && !hasEntryNode ? TabsEnum.Start : undefined
const handleOpenChange = useCallback((open: boolean) => {
setOpen(open)
@ -134,7 +123,6 @@ const AddBlock = ({
popupClassName="min-w-[256px]!"
availableBlocksTypes={availableNextBlocks}
showStartTab={showStartTab}
defaultActiveTab={defaultActiveTab}
/>
)
}