mirror of
https://github.com/langgenius/dify.git
synced 2026-06-24 13:01:16 +08:00
feat(web): centralize snippet draft fields
This commit is contained in:
parent
0bfce31e25
commit
362129e53b
@ -28,13 +28,12 @@ import { DETAIL_SIDEBAR_STORAGE_KEY } from '../storage'
|
||||
const activeEdgeClassName = 'before:pointer-events-none'
|
||||
|
||||
type SnippetNavigationTestState = {
|
||||
fields: SnippetInputField[]
|
||||
onFieldsChange?: (fields: SnippetInputField[]) => void
|
||||
readonly: boolean
|
||||
snippet?: SnippetDetail
|
||||
}
|
||||
|
||||
const { mockIsAgentV2Enabled, mockSnippetFieldsChange, mockSwitchWorkspace, mockToastSuccess, hotkeyRegistrations, snippetNavigationState } = vi.hoisted(() => ({
|
||||
const { mockIsAgentV2Enabled, mockSnippetFieldsChange, mockSwitchWorkspace, mockToastSuccess, hotkeyRegistrations, snippetDraftState, snippetNavigationState } = vi.hoisted(() => ({
|
||||
mockSwitchWorkspace: vi.fn(),
|
||||
mockSnippetFieldsChange: vi.fn(),
|
||||
mockToastSuccess: vi.fn(),
|
||||
@ -43,8 +42,10 @@ const { mockIsAgentV2Enabled, mockSnippetFieldsChange, mockSwitchWorkspace, mock
|
||||
handler: (event: { preventDefault: () => void }) => void
|
||||
options?: { ignoreInputs?: boolean }
|
||||
}>(),
|
||||
snippetDraftState: {
|
||||
inputFields: [],
|
||||
} as { inputFields: SnippetInputField[] },
|
||||
snippetNavigationState: {
|
||||
fields: [],
|
||||
readonly: true,
|
||||
snippet: undefined,
|
||||
onFieldsChange: undefined,
|
||||
@ -204,6 +205,10 @@ vi.mock('@/app/components/snippets/store', () => ({
|
||||
useSnippetDetailStore: (selector: (state: SnippetNavigationTestState) => unknown) => selector(snippetNavigationState),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/snippets/draft-store', () => ({
|
||||
useSnippetDraftStore: (selector: (state: typeof snippetDraftState) => unknown) => selector(snippetDraftState),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/snippets/components/snippet-sidebar', () => ({
|
||||
SnippetSidebarContent: ({
|
||||
fields,
|
||||
@ -423,7 +428,7 @@ describe('MainNav', () => {
|
||||
})
|
||||
mockSwitchWorkspace.mockReturnValue(new Promise(() => {}))
|
||||
hotkeyRegistrations.clear()
|
||||
snippetNavigationState.fields = []
|
||||
snippetDraftState.inputFields = []
|
||||
snippetNavigationState.onFieldsChange = undefined
|
||||
snippetNavigationState.readonly = true
|
||||
snippetNavigationState.snippet = undefined
|
||||
@ -634,7 +639,7 @@ describe('MainNav', () => {
|
||||
|
||||
it('replaces global navigation with snippet detail navigation on snippet routes', () => {
|
||||
mockPathname = '/snippets/snippet-1/orchestrate'
|
||||
snippetNavigationState.fields = snippetFields
|
||||
snippetDraftState.inputFields = snippetFields
|
||||
snippetNavigationState.onFieldsChange = mockSnippetFieldsChange
|
||||
snippetNavigationState.readonly = false
|
||||
snippetNavigationState.snippet = snippet
|
||||
@ -661,7 +666,7 @@ describe('MainNav', () => {
|
||||
|
||||
it('collapses snippet detail navigation from the top-right toggle', () => {
|
||||
mockPathname = '/snippets/snippet-1/orchestrate'
|
||||
snippetNavigationState.fields = snippetFields
|
||||
snippetDraftState.inputFields = snippetFields
|
||||
snippetNavigationState.onFieldsChange = mockSnippetFieldsChange
|
||||
snippetNavigationState.snippet = snippet
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import DifyLogo from '@/app/components/base/logo/dify-logo'
|
||||
import EnvNav from '@/app/components/header/env-nav'
|
||||
import { SnippetSidebarContent } from '@/app/components/snippets/components/snippet-sidebar'
|
||||
import { useSnippetDraftStore } from '@/app/components/snippets/draft-store'
|
||||
import { useSnippetDetailStore } from '@/app/components/snippets/store'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { AgentDetailSection, AgentDetailTop } from '@/features/agent-v2/agent-detail/navigation'
|
||||
@ -101,11 +102,11 @@ const MainNav = ({
|
||||
const showSnippetDetailNavigation = isSnippetDetailPathname(pathname)
|
||||
const showDetailNavigation = showAppDetailNavigation || showDatasetDetailNavigation || showAgentDetailNavigation || showDeploymentDetailNavigation || showSnippetDetailNavigation
|
||||
const snippetNavigation = useSnippetDetailStore(useShallow(state => ({
|
||||
fields: state.fields,
|
||||
onFieldsChange: state.onFieldsChange,
|
||||
readonly: state.readonly,
|
||||
snippet: state.snippet,
|
||||
})))
|
||||
const snippetInputFields = useSnippetDraftStore(state => state.inputFields)
|
||||
const { hasAppDetail, setAppDetail } = useAppStore(useShallow(state => ({
|
||||
hasAppDetail: !!state.appDetail,
|
||||
setAppDetail: state.setAppDetail,
|
||||
@ -307,7 +308,7 @@ const MainNav = ({
|
||||
? (
|
||||
<SnippetSidebarContent
|
||||
snippet={snippetNavigation.snippet}
|
||||
fields={snippetNavigation.fields}
|
||||
fields={snippetInputFields}
|
||||
readonly={snippetNavigation.readonly}
|
||||
onFieldsChange={snippetNavigation.onFieldsChange}
|
||||
/>
|
||||
|
||||
@ -6,13 +6,13 @@ import { act, fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { renderWorkflowComponent } from '@/app/components/workflow/__tests__/workflow-test-env'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { PipelineInputVarType } from '@/models/pipeline'
|
||||
import { useSnippetDraftStore } from '../../draft-store'
|
||||
import SnippetMain from '../snippet-main'
|
||||
|
||||
const mockSyncInputFieldsDraft = vi.fn()
|
||||
const mockDoSyncWorkflowDraft = vi.fn()
|
||||
const mockSyncWorkflowDraftWhenPageClose = vi.fn()
|
||||
const mockReset = vi.fn()
|
||||
const mockSetFields = vi.fn()
|
||||
const mockSetNavigationState = vi.fn()
|
||||
const mockPublishSnippetMutateAsync = vi.fn()
|
||||
const mockFetchInspectVars = vi.fn()
|
||||
@ -52,11 +52,9 @@ vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
let capturedHooksStore: Record<string, unknown> | undefined
|
||||
let capturedWorkflowNodes: WorkflowProps['nodes'] | undefined
|
||||
let snippetDetailStoreState: {
|
||||
fields: SnippetInputField[]
|
||||
onFieldsChange?: (fields: SnippetInputField[]) => void
|
||||
readonly: boolean
|
||||
reset: typeof mockReset
|
||||
setFields: typeof mockSetFields
|
||||
setNavigationState: typeof mockSetNavigationState
|
||||
snippet?: SnippetDetail
|
||||
snippetId?: string
|
||||
@ -277,11 +275,10 @@ describe('SnippetMain', () => {
|
||||
})
|
||||
capturedHooksStore = undefined
|
||||
capturedWorkflowNodes = undefined
|
||||
useSnippetDraftStore.getState().reset()
|
||||
snippetDetailStoreState = {
|
||||
fields: [...payload.inputFields],
|
||||
readonly: true,
|
||||
reset: mockReset,
|
||||
setFields: mockSetFields,
|
||||
setNavigationState: mockSetNavigationState,
|
||||
}
|
||||
})
|
||||
|
||||
@ -1,15 +1,10 @@
|
||||
import type { SnippetInputField } from '@/models/snippet'
|
||||
import { act, renderHook } from '@testing-library/react'
|
||||
import { PipelineInputVarType } from '@/models/pipeline'
|
||||
import { useSnippetDraftStore } from '../../../draft-store'
|
||||
import { useSnippetInputFieldActions } from '../use-snippet-input-field-actions'
|
||||
|
||||
const mockSyncInputFieldsDraft = vi.fn()
|
||||
const mockSetFields = vi.fn()
|
||||
|
||||
let snippetDetailStoreState: {
|
||||
fields: SnippetInputField[]
|
||||
setFields: typeof mockSetFields
|
||||
}
|
||||
|
||||
vi.mock('../../../hooks/use-nodes-sync-draft', () => ({
|
||||
useNodesSyncDraft: () => ({
|
||||
@ -17,10 +12,6 @@ vi.mock('../../../hooks/use-nodes-sync-draft', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('../../../store', () => ({
|
||||
useSnippetDetailStore: (selector: (state: typeof snippetDetailStoreState) => unknown) => selector(snippetDetailStoreState),
|
||||
}))
|
||||
|
||||
const createField = (overrides: Partial<SnippetInputField> = {}): SnippetInputField => ({
|
||||
type: PipelineInputVarType.textInput,
|
||||
label: 'Blog URL',
|
||||
@ -32,19 +23,13 @@ const createField = (overrides: Partial<SnippetInputField> = {}): SnippetInputFi
|
||||
describe('useSnippetInputFieldActions', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
snippetDetailStoreState = {
|
||||
fields: [],
|
||||
setFields: mockSetFields,
|
||||
}
|
||||
mockSetFields.mockImplementation((fields: SnippetInputField[]) => {
|
||||
snippetDetailStoreState.fields = fields
|
||||
})
|
||||
useSnippetDraftStore.getState().reset()
|
||||
mockSyncInputFieldsDraft.mockResolvedValue(undefined)
|
||||
})
|
||||
|
||||
describe('Field sync', () => {
|
||||
it('should update fields and sync the draft', () => {
|
||||
snippetDetailStoreState.fields = [createField()]
|
||||
useSnippetDraftStore.getState().setInputFields([createField()])
|
||||
const { result } = renderHook(() => useSnippetInputFieldActions({
|
||||
snippetId: 'snippet-1',
|
||||
}))
|
||||
@ -60,8 +45,8 @@ describe('useSnippetInputFieldActions', () => {
|
||||
result.current.handleFieldsChange(nextFields)
|
||||
})
|
||||
|
||||
expect(result.current.fields).toEqual([createField()])
|
||||
expect(mockSetFields).toHaveBeenCalledWith(nextFields)
|
||||
expect(result.current.fields).toEqual(nextFields)
|
||||
expect(useSnippetDraftStore.getState().inputFields).toEqual(nextFields)
|
||||
expect(mockSyncInputFieldsDraft).toHaveBeenCalledWith(nextFields, {
|
||||
onRefresh: expect.any(Function),
|
||||
})
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import type { SnippetInputField } from '@/models/snippet'
|
||||
import { useCallback } from 'react'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { useSnippetDraftStore } from '../../draft-store'
|
||||
import { useNodesSyncDraft } from '../../hooks/use-nodes-sync-draft'
|
||||
import { useSnippetDetailStore } from '../../store'
|
||||
|
||||
type UseSnippetInputFieldActionsOptions = {
|
||||
canEdit?: boolean
|
||||
@ -15,25 +15,25 @@ export const useSnippetInputFieldActions = ({
|
||||
}: UseSnippetInputFieldActionsOptions) => {
|
||||
const { syncInputFieldsDraft } = useNodesSyncDraft(snippetId)
|
||||
const {
|
||||
fields,
|
||||
setFields,
|
||||
} = useSnippetDetailStore(useShallow(state => ({
|
||||
fields: state.fields,
|
||||
setFields: state.setFields,
|
||||
inputFields,
|
||||
setInputFields,
|
||||
} = useSnippetDraftStore(useShallow(state => ({
|
||||
inputFields: state.inputFields,
|
||||
setInputFields: state.setInputFields,
|
||||
})))
|
||||
|
||||
const handleFieldsChange = useCallback((newFields: SnippetInputField[]) => {
|
||||
if (!canEdit)
|
||||
return
|
||||
|
||||
setFields(newFields)
|
||||
setInputFields(newFields)
|
||||
void syncInputFieldsDraft(newFields, {
|
||||
onRefresh: setFields,
|
||||
onRefresh: setInputFields,
|
||||
})
|
||||
}, [canEdit, setFields, syncInputFieldsDraft])
|
||||
}, [canEdit, setInputFields, syncInputFieldsDraft])
|
||||
|
||||
return {
|
||||
fields,
|
||||
fields: inputFields,
|
||||
handleFieldsChange,
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,8 +8,8 @@ import { toast } from '@langgenius/dify-ui/toast'
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -23,6 +23,7 @@ import {
|
||||
initialEdges,
|
||||
initialNodes,
|
||||
} from '@/app/components/workflow/utils'
|
||||
import { useSnippetDraftStore } from '../draft-store'
|
||||
import { useConfigsMap } from '../hooks/use-configs-map'
|
||||
import { useGetRunAndTraceUrl } from '../hooks/use-get-run-and-trace-url'
|
||||
import { useInspectVarsCrud } from '../hooks/use-inspect-vars-crud'
|
||||
@ -136,16 +137,12 @@ const SnippetMain = ({
|
||||
const effectiveDraftNodes = localDraftState?.nodes ?? draftNodes
|
||||
const effectiveDraftEdges = localDraftState?.edges ?? draftEdges
|
||||
const effectiveDraftViewport = localDraftState?.viewport ?? draftViewport
|
||||
const currentInputFieldsRef = useRef<SnippetInputField[]>(effectiveDraftPayload.inputFields)
|
||||
const { graph, snippet } = effectiveDraftPayload
|
||||
const canSave = currentCanvasNodeCount > 0
|
||||
const getCurrentInputFields = useCallback(() => currentInputFieldsRef.current, [])
|
||||
const {
|
||||
doSyncWorkflowDraft: syncWorkflowDraft,
|
||||
syncWorkflowDraftWhenPageClose,
|
||||
} = useNodesSyncDraft(snippetId, {
|
||||
getInputFields: getCurrentInputFields,
|
||||
})
|
||||
} = useNodesSyncDraft(snippetId)
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { handleRefreshWorkflowDraft } = useSnippetRefreshDraft(snippetId)
|
||||
const {
|
||||
@ -197,13 +194,18 @@ const SnippetMain = ({
|
||||
}, [workflowAvailableNodesMetaData])
|
||||
const {
|
||||
reset,
|
||||
setFields,
|
||||
setNavigationState,
|
||||
} = useSnippetDetailStore(useShallow(state => ({
|
||||
reset: state.reset,
|
||||
setFields: state.setFields,
|
||||
setNavigationState: state.setNavigationState,
|
||||
})))
|
||||
const {
|
||||
hydrateDraft,
|
||||
setInputFields,
|
||||
} = useSnippetDraftStore(useShallow(state => ({
|
||||
hydrateDraft: state.hydrateDraft,
|
||||
setInputFields: state.setInputFields,
|
||||
})))
|
||||
const {
|
||||
fields,
|
||||
handleFieldsChange: handleSnippetFieldsChange,
|
||||
@ -224,10 +226,12 @@ const SnippetMain = ({
|
||||
return () => reset()
|
||||
}, [reset, snippetId])
|
||||
|
||||
useEffect(() => {
|
||||
currentInputFieldsRef.current = effectiveDraftPayload.inputFields
|
||||
setFields(effectiveDraftPayload.inputFields)
|
||||
}, [effectiveDraftPayload.inputFields, setFields, snippetId])
|
||||
useLayoutEffect(() => {
|
||||
hydrateDraft({
|
||||
snippetId,
|
||||
inputFields: effectiveDraftPayload.inputFields,
|
||||
})
|
||||
}, [effectiveDraftPayload.inputFields, hydrateDraft, snippetId])
|
||||
|
||||
useEffect(() => {
|
||||
workflowStore.setState({ canvasReadOnly: false })
|
||||
@ -254,7 +258,6 @@ const SnippetMain = ({
|
||||
) => syncWorkflowDraft(...args), [syncWorkflowDraft])
|
||||
|
||||
const handleFieldsChange = useCallback((nextFields: SnippetInputField[]) => {
|
||||
currentInputFieldsRef.current = nextFields
|
||||
handleSnippetFieldsChange(nextFields)
|
||||
}, [handleSnippetFieldsChange])
|
||||
|
||||
@ -286,7 +289,6 @@ const SnippetMain = ({
|
||||
? syncedDraftPayload.input_fields as SnippetInputField[]
|
||||
: fields
|
||||
|
||||
currentInputFieldsRef.current = inputFields
|
||||
setLocalDraftState({
|
||||
payload: {
|
||||
...draftPayload,
|
||||
@ -297,8 +299,8 @@ const SnippetMain = ({
|
||||
edges: initialEdges(draftGraph.edges, draftGraph.nodes),
|
||||
viewport: draftGraph.viewport,
|
||||
})
|
||||
setFields(inputFields)
|
||||
}, [draftPayload, fields, setFields])
|
||||
setInputFields(inputFields)
|
||||
}, [draftPayload, fields, setInputFields])
|
||||
|
||||
const hooksStore = useMemo(() => {
|
||||
return {
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
import type { SnippetInputField } from '@/models/snippet'
|
||||
import { PipelineInputVarType } from '@/models/pipeline'
|
||||
import { useSnippetDraftStore } from '..'
|
||||
|
||||
const createField = (variable: string): SnippetInputField => ({
|
||||
label: variable,
|
||||
variable,
|
||||
type: PipelineInputVarType.textInput,
|
||||
required: true,
|
||||
})
|
||||
|
||||
describe('useSnippetDraftStore', () => {
|
||||
beforeEach(() => {
|
||||
useSnippetDraftStore.getState().reset()
|
||||
})
|
||||
|
||||
it('should store and reset snippet input fields', () => {
|
||||
const inputFields = [
|
||||
createField('topic'),
|
||||
createField('audience'),
|
||||
]
|
||||
|
||||
useSnippetDraftStore.getState().hydrateDraft({
|
||||
snippetId: 'snippet-1',
|
||||
inputFields,
|
||||
})
|
||||
|
||||
expect(useSnippetDraftStore.getState().snippetId).toBe('snippet-1')
|
||||
expect(useSnippetDraftStore.getState().inputFields).toEqual(inputFields)
|
||||
|
||||
useSnippetDraftStore.getState().reset()
|
||||
|
||||
expect(useSnippetDraftStore.getState().snippetId).toBeUndefined()
|
||||
expect(useSnippetDraftStore.getState().inputFields).toEqual([])
|
||||
})
|
||||
})
|
||||
24
web/app/components/snippets/draft-store/index.ts
Normal file
24
web/app/components/snippets/draft-store/index.ts
Normal file
@ -0,0 +1,24 @@
|
||||
'use client'
|
||||
|
||||
import type { SnippetInputField } from '@/models/snippet'
|
||||
import { create } from 'zustand'
|
||||
|
||||
type SnippetDraftState = {
|
||||
snippetId?: string
|
||||
inputFields: SnippetInputField[]
|
||||
hydrateDraft: (payload: { snippetId: string, inputFields: SnippetInputField[] }) => void
|
||||
setInputFields: (inputFields: SnippetInputField[]) => void
|
||||
reset: () => void
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
snippetId: undefined,
|
||||
inputFields: [] as SnippetInputField[],
|
||||
}
|
||||
|
||||
export const useSnippetDraftStore = create<SnippetDraftState>(set => ({
|
||||
...initialState,
|
||||
hydrateDraft: ({ snippetId, inputFields }) => set({ snippetId, inputFields }),
|
||||
setInputFields: inputFields => set({ inputFields }),
|
||||
reset: () => set(initialState),
|
||||
}))
|
||||
@ -1,7 +1,7 @@
|
||||
import type { SnippetInputField } from '@/models/snippet'
|
||||
import { act, renderHook } from '@testing-library/react'
|
||||
import { PipelineInputVarType } from '@/models/pipeline'
|
||||
import { useSnippetDetailStore } from '../../store'
|
||||
import { useSnippetDraftStore } from '../../draft-store'
|
||||
import { useNodesSyncDraft } from '../use-nodes-sync-draft'
|
||||
|
||||
const mockGetNodes = vi.fn()
|
||||
@ -112,9 +112,7 @@ describe('snippet/use-nodes-sync-draft', () => {
|
||||
mockSetSyncWorkflowDraftHash.mockImplementation((hash: string) => {
|
||||
workflowStoreState.syncWorkflowDraftHash = hash
|
||||
})
|
||||
useSnippetDetailStore.setState({
|
||||
fields: [createInputField('topic')],
|
||||
})
|
||||
useSnippetDraftStore.getState().setInputFields([createInputField('topic')])
|
||||
})
|
||||
|
||||
it('should include current input_fields when syncing the draft graph', async () => {
|
||||
@ -139,25 +137,16 @@ describe('snippet/use-nodes-sync-draft', () => {
|
||||
expect(mockUseNodesReadOnlyByCanEdit).toHaveBeenCalledWith(true)
|
||||
})
|
||||
|
||||
it('should use provided input_fields when the snippet store is not initialized yet', async () => {
|
||||
useSnippetDetailStore.setState({
|
||||
fields: [],
|
||||
it('should keep draft input_fields when the navigation store is reset during route leave', () => {
|
||||
const { result } = renderHook(() => useNodesSyncDraft('snippet-1'))
|
||||
|
||||
act(() => {
|
||||
result.current.syncWorkflowDraftWhenPageClose()
|
||||
})
|
||||
const inputFields = [createInputField('topic')]
|
||||
const { result } = renderHook(() => useNodesSyncDraft('snippet-1', {
|
||||
getInputFields: () => inputFields,
|
||||
|
||||
expect(mockPostWithKeepalive).toHaveBeenCalledWith('/api/snippets/snippet-1/workflows/draft', expect.objectContaining({
|
||||
input_fields: [createInputField('topic')],
|
||||
}))
|
||||
|
||||
await act(async () => {
|
||||
await result.current.doSyncWorkflowDraft()
|
||||
})
|
||||
|
||||
expect(mockSyncDraftWorkflow).toHaveBeenCalledWith({
|
||||
params: { snippetId: 'snippet-1' },
|
||||
body: expect.objectContaining({
|
||||
input_fields: inputFields,
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
it('should snapshot graph before queued draft sync executes', async () => {
|
||||
@ -267,22 +256,4 @@ describe('snippet/use-nodes-sync-draft', () => {
|
||||
hash: 'draft-hash',
|
||||
})
|
||||
})
|
||||
|
||||
it('should use provided input_fields when syncing on page close before the snippet store initializes', () => {
|
||||
useSnippetDetailStore.setState({
|
||||
fields: [],
|
||||
})
|
||||
const inputFields = [createInputField('topic')]
|
||||
const { result } = renderHook(() => useNodesSyncDraft('snippet-1', {
|
||||
getInputFields: () => inputFields,
|
||||
}))
|
||||
|
||||
act(() => {
|
||||
result.current.syncWorkflowDraftWhenPageClose()
|
||||
})
|
||||
|
||||
expect(mockPostWithKeepalive).toHaveBeenCalledWith('/api/snippets/snippet-1/workflows/draft', expect.objectContaining({
|
||||
input_fields: inputFields,
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
@ -31,8 +31,8 @@ vi.mock('@/app/components/workflow/store', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('../../store', () => ({
|
||||
useSnippetDetailStore: {
|
||||
vi.mock('../../draft-store', () => ({
|
||||
useSnippetDraftStore: {
|
||||
setState: (...args: unknown[]) => mockSnippetSetState(...args),
|
||||
},
|
||||
}))
|
||||
@ -75,7 +75,7 @@ describe('useSnippetRefreshDraft', () => {
|
||||
})
|
||||
expect(mockFetchSnippetDraftWorkflow).toHaveBeenCalledWith('snippet-1')
|
||||
expect(mockSnippetSetState).toHaveBeenCalledWith({
|
||||
fields: [],
|
||||
inputFields: [],
|
||||
})
|
||||
expect(mockSetSyncWorkflowDraftHash).toHaveBeenCalledWith('draft-hash')
|
||||
expect(mockSetDraftUpdatedAt).toHaveBeenCalledWith(1_712_345_678)
|
||||
|
||||
@ -4,7 +4,7 @@ import { act } from 'react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
import { PipelineInputVarType } from '@/models/pipeline'
|
||||
import { useSnippetDetailStore } from '../../store'
|
||||
import { useSnippetDraftStore } from '../../draft-store'
|
||||
import { useSnippetStartRun } from '../use-snippet-start-run'
|
||||
|
||||
const mockWorkflowStoreGetState = vi.fn()
|
||||
@ -39,7 +39,7 @@ const inputFields: SnippetInputField[] = [
|
||||
describe('useSnippetStartRun', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
useSnippetDetailStore.getState().reset()
|
||||
useSnippetDraftStore.getState().reset()
|
||||
mockWorkflowStoreGetState.mockReturnValue({
|
||||
workflowRunningData: undefined,
|
||||
showDebugAndPreviewPanel: false,
|
||||
@ -51,7 +51,7 @@ describe('useSnippetStartRun', () => {
|
||||
})
|
||||
|
||||
it('should open the debug panel and input form when snippet has input fields', () => {
|
||||
useSnippetDetailStore.setState({ fields: inputFields })
|
||||
useSnippetDraftStore.getState().setInputFields(inputFields)
|
||||
|
||||
const { result } = renderHook(() => useSnippetStartRun({
|
||||
handleRun: mockHandleRun,
|
||||
@ -83,7 +83,7 @@ describe('useSnippetStartRun', () => {
|
||||
})
|
||||
|
||||
it('should use current snippet input fields from the store before starting a run', () => {
|
||||
useSnippetDetailStore.setState({ fields: inputFields })
|
||||
useSnippetDraftStore.getState().setInputFields(inputFields)
|
||||
|
||||
const { result } = renderHook(() => useSnippetStartRun({
|
||||
handleRun: mockHandleRun,
|
||||
@ -99,7 +99,7 @@ describe('useSnippetStartRun', () => {
|
||||
})
|
||||
|
||||
it('should close the panel when debug panel is already open', () => {
|
||||
useSnippetDetailStore.setState({ fields: inputFields })
|
||||
useSnippetDraftStore.getState().setInputFields(inputFields)
|
||||
|
||||
mockWorkflowStoreGetState.mockReturnValue({
|
||||
workflowRunningData: undefined,
|
||||
@ -122,7 +122,7 @@ describe('useSnippetStartRun', () => {
|
||||
})
|
||||
|
||||
it('should do nothing when workflow is already running', () => {
|
||||
useSnippetDetailStore.setState({ fields: inputFields })
|
||||
useSnippetDraftStore.getState().setInputFields(inputFields)
|
||||
|
||||
mockWorkflowStoreGetState.mockReturnValue({
|
||||
workflowRunningData: {
|
||||
|
||||
@ -10,7 +10,7 @@ import { API_PREFIX } from '@/config'
|
||||
import { consoleClient } from '@/service/client'
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { postWithKeepalive } from '@/service/fetch'
|
||||
import { useSnippetDetailStore } from '../store'
|
||||
import { useSnippetDraftStore } from '../draft-store'
|
||||
import { useSnippetRefreshDraft } from './use-snippet-refresh-draft'
|
||||
|
||||
const isSyncConflictError = (error: unknown): error is { bodyUsed: boolean, json: () => Promise<{ code?: string }> } => {
|
||||
@ -25,10 +25,6 @@ type SyncInputFieldsDraftCallback = SyncDraftCallback & {
|
||||
onRefresh?: (inputFields: SnippetInputField[]) => void
|
||||
}
|
||||
|
||||
type UseNodesSyncDraftOptions = {
|
||||
getInputFields?: () => SnippetInputField[]
|
||||
}
|
||||
|
||||
const snippetDraftSyncQueues = new Map<string, Promise<unknown>>()
|
||||
|
||||
const enqueueSnippetDraftSync = <Result>(
|
||||
@ -48,18 +44,17 @@ const enqueueSnippetDraftSync = <Result>(
|
||||
return nextTask
|
||||
}
|
||||
|
||||
export const useNodesSyncDraft = (snippetId: string, options: UseNodesSyncDraftOptions = {}) => {
|
||||
export const useNodesSyncDraft = (snippetId: string) => {
|
||||
const store = useStoreApi()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { getNodesReadOnly } = useNodesReadOnlyByCanEdit(true)
|
||||
const { handleRefreshWorkflowDraft } = useSnippetRefreshDraft(snippetId)
|
||||
const { getInputFields } = options
|
||||
|
||||
const getInputFieldsSyncPayload = useCallback((inputFields?: SnippetInputField[]) => {
|
||||
return {
|
||||
input_fields: inputFields ?? getInputFields?.() ?? useSnippetDetailStore.getState().fields,
|
||||
input_fields: inputFields ?? useSnippetDraftStore.getState().inputFields,
|
||||
}
|
||||
}, [getInputFields])
|
||||
}, [])
|
||||
|
||||
const getDraftSyncPayload = useCallback((inputFields?: SnippetInputField[]) => {
|
||||
const {
|
||||
|
||||
@ -5,7 +5,7 @@ import { useCallback } from 'react'
|
||||
import { useWorkflowUpdate } from '@/app/components/workflow/hooks'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { fetchSnippetDraftWorkflow } from '@/service/use-snippet-workflows'
|
||||
import { useSnippetDetailStore } from '../store'
|
||||
import { useSnippetDraftStore } from '../draft-store'
|
||||
|
||||
export const useSnippetRefreshDraft = (snippetId: string) => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
@ -36,8 +36,8 @@ export const useSnippetRefreshDraft = (snippetId: string) => {
|
||||
edges: response.graph?.edges || [],
|
||||
viewport: response.graph?.viewport || { x: 0, y: 0, zoom: 1 },
|
||||
} as WorkflowDataUpdater)
|
||||
useSnippetDetailStore.setState({
|
||||
fields: inputFields,
|
||||
useSnippetDraftStore.setState({
|
||||
inputFields,
|
||||
})
|
||||
setSyncWorkflowDraftHash(response.hash)
|
||||
setDraftUpdatedAt(response.updated_at)
|
||||
|
||||
@ -3,7 +3,7 @@ import { useCallback } from 'react'
|
||||
import { useWorkflowInteractions } from '@/app/components/workflow/hooks'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
import { useSnippetDetailStore } from '../store'
|
||||
import { useSnippetDraftStore } from '../draft-store'
|
||||
|
||||
type UseSnippetStartRunOptions = {
|
||||
handleRun: (params: SnippetDraftRunPayload) => void
|
||||
@ -38,7 +38,7 @@ export const useSnippetStartRun = ({
|
||||
|
||||
setShowDebugAndPreviewPanel(true)
|
||||
|
||||
const currentInputFields = useSnippetDetailStore.getState().fields
|
||||
const currentInputFields = useSnippetDraftStore.getState().inputFields
|
||||
|
||||
if (currentInputFields.length > 0) {
|
||||
setShowInputsPanel(true)
|
||||
|
||||
@ -1,14 +1,6 @@
|
||||
import type { SnippetDetail, SnippetInputField } from '@/models/snippet'
|
||||
import { PipelineInputVarType } from '@/models/pipeline'
|
||||
import type { SnippetDetail } from '@/models/snippet'
|
||||
import { useSnippetDetailStore } from '..'
|
||||
|
||||
const createField = (variable: string): SnippetInputField => ({
|
||||
label: variable,
|
||||
variable,
|
||||
type: PipelineInputVarType.textInput,
|
||||
required: true,
|
||||
})
|
||||
|
||||
const snippet: SnippetDetail = {
|
||||
id: 'snippet-1',
|
||||
name: 'Snippet',
|
||||
@ -23,21 +15,6 @@ describe('useSnippetDetailStore', () => {
|
||||
useSnippetDetailStore.getState().reset()
|
||||
})
|
||||
|
||||
it('should store and reset snippet input fields', () => {
|
||||
const fields = [
|
||||
createField('topic'),
|
||||
createField('audience'),
|
||||
]
|
||||
|
||||
useSnippetDetailStore.getState().setFields(fields)
|
||||
|
||||
expect(useSnippetDetailStore.getState().fields).toEqual(fields)
|
||||
|
||||
useSnippetDetailStore.getState().reset()
|
||||
|
||||
expect(useSnippetDetailStore.getState().fields).toEqual([])
|
||||
})
|
||||
|
||||
it('should store and reset snippet navigation state', () => {
|
||||
const onFieldsChange = vi.fn()
|
||||
|
||||
@ -58,7 +35,6 @@ describe('useSnippetDetailStore', () => {
|
||||
useSnippetDetailStore.getState().reset()
|
||||
|
||||
expect(useSnippetDetailStore.getState()).toMatchObject({
|
||||
fields: [],
|
||||
readonly: true,
|
||||
snippet: undefined,
|
||||
snippetId: undefined,
|
||||
|
||||
@ -11,14 +11,11 @@ type SnippetNavigationState = {
|
||||
}
|
||||
|
||||
type SnippetDetailUIState = {
|
||||
fields: SnippetInputField[]
|
||||
setFields: (fields: SnippetInputField[]) => void
|
||||
setNavigationState: (state: SnippetNavigationState) => void
|
||||
reset: () => void
|
||||
} & SnippetNavigationState
|
||||
|
||||
const initialState = {
|
||||
fields: [] as SnippetInputField[],
|
||||
readonly: true,
|
||||
snippet: undefined,
|
||||
snippetId: undefined,
|
||||
@ -27,7 +24,6 @@ const initialState = {
|
||||
|
||||
export const useSnippetDetailStore = create<SnippetDetailUIState>(set => ({
|
||||
...initialState,
|
||||
setFields: fields => set({ fields }),
|
||||
setNavigationState: state => set(state),
|
||||
reset: () => set(initialState),
|
||||
}))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user