mirror of
https://github.com/langgenius/dify.git
synced 2026-04-19 18:27:27 +08:00
fix(web): snippet draft sync
This commit is contained in:
@ -8,6 +8,7 @@ const mockSyncInputFieldsDraft = vi.fn()
|
||||
const mockCloseEditor = vi.fn()
|
||||
const mockOpenEditor = vi.fn()
|
||||
const mockReset = vi.fn()
|
||||
const mockSetFields = vi.fn()
|
||||
const mockSetInputPanelOpen = vi.fn()
|
||||
const mockSetPublishMenuOpen = vi.fn()
|
||||
const mockToggleInputPanel = vi.fn()
|
||||
@ -38,33 +39,24 @@ const mockInspectVarsCrud = {
|
||||
invalidateConversationVarValues: vi.fn(),
|
||||
}
|
||||
let capturedHooksStore: Record<string, unknown> | undefined
|
||||
let snippetDetailStoreState: {
|
||||
editingField: SnippetInputField | null
|
||||
fields: SnippetInputField[]
|
||||
isEditorOpen: boolean
|
||||
isInputPanelOpen: boolean
|
||||
isPublishMenuOpen: boolean
|
||||
closeEditor: typeof mockCloseEditor
|
||||
openEditor: typeof mockOpenEditor
|
||||
reset: typeof mockReset
|
||||
setFields: typeof mockSetFields
|
||||
setInputPanelOpen: typeof mockSetInputPanelOpen
|
||||
setPublishMenuOpen: typeof mockSetPublishMenuOpen
|
||||
toggleInputPanel: typeof mockToggleInputPanel
|
||||
togglePublishMenu: typeof mockTogglePublishMenu
|
||||
}
|
||||
|
||||
vi.mock('@/app/components/snippets/store', () => ({
|
||||
useSnippetDetailStore: (selector: (state: {
|
||||
editingField: SnippetInputField | null
|
||||
isEditorOpen: boolean
|
||||
isInputPanelOpen: boolean
|
||||
isPublishMenuOpen: boolean
|
||||
closeEditor: typeof mockCloseEditor
|
||||
openEditor: typeof mockOpenEditor
|
||||
reset: typeof mockReset
|
||||
setInputPanelOpen: typeof mockSetInputPanelOpen
|
||||
setPublishMenuOpen: typeof mockSetPublishMenuOpen
|
||||
toggleInputPanel: typeof mockToggleInputPanel
|
||||
togglePublishMenu: typeof mockTogglePublishMenu
|
||||
}) => unknown) => selector({
|
||||
editingField: null,
|
||||
isEditorOpen: false,
|
||||
isInputPanelOpen: true,
|
||||
isPublishMenuOpen: false,
|
||||
closeEditor: mockCloseEditor,
|
||||
openEditor: mockOpenEditor,
|
||||
reset: mockReset,
|
||||
setInputPanelOpen: mockSetInputPanelOpen,
|
||||
setPublishMenuOpen: mockSetPublishMenuOpen,
|
||||
toggleInputPanel: mockToggleInputPanel,
|
||||
togglePublishMenu: mockTogglePublishMenu,
|
||||
}),
|
||||
useSnippetDetailStore: (selector: (state: typeof snippetDetailStoreState) => unknown) => selector(snippetDetailStoreState),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-snippet-workflows', () => ({
|
||||
@ -216,6 +208,21 @@ describe('SnippetMain', () => {
|
||||
mockSyncInputFieldsDraft.mockResolvedValue(undefined)
|
||||
mockPublishSnippetMutateAsync.mockResolvedValue(undefined)
|
||||
capturedHooksStore = undefined
|
||||
snippetDetailStoreState = {
|
||||
editingField: null,
|
||||
fields: [...payload.inputFields],
|
||||
isEditorOpen: false,
|
||||
isInputPanelOpen: true,
|
||||
isPublishMenuOpen: false,
|
||||
closeEditor: mockCloseEditor,
|
||||
openEditor: mockOpenEditor,
|
||||
reset: mockReset,
|
||||
setFields: mockSetFields,
|
||||
setInputPanelOpen: mockSetInputPanelOpen,
|
||||
setPublishMenuOpen: mockSetPublishMenuOpen,
|
||||
toggleInputPanel: mockToggleInputPanel,
|
||||
togglePublishMenu: mockTogglePublishMenu,
|
||||
}
|
||||
})
|
||||
|
||||
describe('Input Fields Sync', () => {
|
||||
|
||||
@ -7,15 +7,18 @@ import { useSnippetInputFieldActions } from '../use-snippet-input-field-actions'
|
||||
const mockSyncInputFieldsDraft = vi.fn()
|
||||
const mockCloseEditor = vi.fn()
|
||||
const mockOpenEditor = vi.fn()
|
||||
const mockSetFields = vi.fn()
|
||||
const mockSetInputPanelOpen = vi.fn()
|
||||
const mockToggleInputPanel = vi.fn()
|
||||
|
||||
let snippetDetailStoreState: {
|
||||
editingField: SnippetInputField | null
|
||||
fields: SnippetInputField[]
|
||||
isEditorOpen: boolean
|
||||
isInputPanelOpen: boolean
|
||||
closeEditor: typeof mockCloseEditor
|
||||
openEditor: typeof mockOpenEditor
|
||||
setFields: typeof mockSetFields
|
||||
setInputPanelOpen: typeof mockSetInputPanelOpen
|
||||
toggleInputPanel: typeof mockToggleInputPanel
|
||||
}
|
||||
@ -49,37 +52,42 @@ describe('useSnippetInputFieldActions', () => {
|
||||
vi.clearAllMocks()
|
||||
snippetDetailStoreState = {
|
||||
editingField: null,
|
||||
fields: [],
|
||||
isEditorOpen: false,
|
||||
isInputPanelOpen: true,
|
||||
closeEditor: mockCloseEditor,
|
||||
openEditor: mockOpenEditor,
|
||||
setFields: mockSetFields,
|
||||
setInputPanelOpen: mockSetInputPanelOpen,
|
||||
toggleInputPanel: mockToggleInputPanel,
|
||||
}
|
||||
mockSetFields.mockImplementation((fields: SnippetInputField[]) => {
|
||||
snippetDetailStoreState.fields = fields
|
||||
})
|
||||
mockSyncInputFieldsDraft.mockResolvedValue(undefined)
|
||||
})
|
||||
|
||||
describe('Field sync', () => {
|
||||
it('should remove a field and sync the draft', () => {
|
||||
snippetDetailStoreState.fields = [createField()]
|
||||
const { result } = renderHook(() => useSnippetInputFieldActions({
|
||||
snippetId: 'snippet-1',
|
||||
initialFields: [createField()],
|
||||
}))
|
||||
|
||||
act(() => {
|
||||
result.current.handleRemoveField(0)
|
||||
})
|
||||
|
||||
expect(result.current.fields).toEqual([])
|
||||
expect(mockSetFields).toHaveBeenCalledWith([])
|
||||
expect(mockSyncInputFieldsDraft).toHaveBeenCalledWith([], {
|
||||
onRefresh: expect.any(Function),
|
||||
})
|
||||
})
|
||||
|
||||
it('should append a new field and close the editor after syncing', () => {
|
||||
snippetDetailStoreState.fields = [createField()]
|
||||
const { result } = renderHook(() => useSnippetInputFieldActions({
|
||||
snippetId: 'snippet-1',
|
||||
initialFields: [createField()],
|
||||
}))
|
||||
|
||||
act(() => {
|
||||
@ -89,7 +97,7 @@ describe('useSnippetInputFieldActions', () => {
|
||||
}))
|
||||
})
|
||||
|
||||
expect(result.current.fields).toEqual([
|
||||
expect(mockSetFields).toHaveBeenCalledWith([
|
||||
createField(),
|
||||
createField({
|
||||
label: 'Topic',
|
||||
@ -109,9 +117,9 @@ describe('useSnippetInputFieldActions', () => {
|
||||
})
|
||||
|
||||
it('should reject duplicated variables without syncing', () => {
|
||||
snippetDetailStoreState.fields = [createField()]
|
||||
const { result } = renderHook(() => useSnippetInputFieldActions({
|
||||
snippetId: 'snippet-1',
|
||||
initialFields: [createField()],
|
||||
}))
|
||||
|
||||
act(() => {
|
||||
@ -124,15 +132,15 @@ describe('useSnippetInputFieldActions', () => {
|
||||
expect(toast.error).toHaveBeenCalledWith('datasetPipeline.inputFieldPanel.error.variableDuplicate')
|
||||
expect(mockSyncInputFieldsDraft).not.toHaveBeenCalled()
|
||||
expect(mockCloseEditor).not.toHaveBeenCalled()
|
||||
expect(result.current.fields).toEqual([createField()])
|
||||
expect(mockSetFields).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Panel actions', () => {
|
||||
it('should close the editor before toggling the input panel when the panel is open', () => {
|
||||
snippetDetailStoreState.fields = [createField()]
|
||||
const { result } = renderHook(() => useSnippetInputFieldActions({
|
||||
snippetId: 'snippet-1',
|
||||
initialFields: [createField()],
|
||||
}))
|
||||
|
||||
act(() => {
|
||||
@ -144,9 +152,9 @@ describe('useSnippetInputFieldActions', () => {
|
||||
})
|
||||
|
||||
it('should close the input panel and clear the editor state', () => {
|
||||
snippetDetailStoreState.fields = [createField()]
|
||||
const { result } = renderHook(() => useSnippetInputFieldActions({
|
||||
snippetId: 'snippet-1',
|
||||
initialFields: [createField()],
|
||||
}))
|
||||
|
||||
act(() => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { SnippetInputField } from '@/models/snippet'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
@ -8,37 +8,38 @@ import { useSnippetDetailStore } from '../../store'
|
||||
|
||||
type UseSnippetInputFieldActionsOptions = {
|
||||
snippetId: string
|
||||
initialFields: SnippetInputField[]
|
||||
}
|
||||
|
||||
export const useSnippetInputFieldActions = ({
|
||||
snippetId,
|
||||
initialFields,
|
||||
}: UseSnippetInputFieldActionsOptions) => {
|
||||
const { t } = useTranslation('snippet')
|
||||
const [fields, setFields] = useState<SnippetInputField[]>(initialFields)
|
||||
const { syncInputFieldsDraft } = useNodesSyncDraft(snippetId)
|
||||
const {
|
||||
editingField,
|
||||
fields,
|
||||
isEditorOpen,
|
||||
isInputPanelOpen,
|
||||
closeEditor,
|
||||
openEditor,
|
||||
setFields,
|
||||
setInputPanelOpen,
|
||||
toggleInputPanel,
|
||||
} = useSnippetDetailStore(useShallow(state => ({
|
||||
editingField: state.editingField,
|
||||
fields: state.fields,
|
||||
isEditorOpen: state.isEditorOpen,
|
||||
isInputPanelOpen: state.isInputPanelOpen,
|
||||
closeEditor: state.closeEditor,
|
||||
openEditor: state.openEditor,
|
||||
setFields: state.setFields,
|
||||
setInputPanelOpen: state.setInputPanelOpen,
|
||||
toggleInputPanel: state.toggleInputPanel,
|
||||
})))
|
||||
|
||||
const handleSortChange = useCallback((newFields: SnippetInputField[]) => {
|
||||
setFields(newFields)
|
||||
}, [])
|
||||
}, [setFields])
|
||||
|
||||
const handleRemoveField = useCallback((index: number) => {
|
||||
const nextFields = fields.filter((_, currentIndex) => currentIndex !== index)
|
||||
@ -46,7 +47,7 @@ export const useSnippetInputFieldActions = ({
|
||||
void syncInputFieldsDraft(nextFields, {
|
||||
onRefresh: setFields,
|
||||
})
|
||||
}, [fields, syncInputFieldsDraft])
|
||||
}, [fields, setFields, syncInputFieldsDraft])
|
||||
|
||||
const handleSubmitField = useCallback((field: SnippetInputField) => {
|
||||
const originalVariable = editingField?.variable
|
||||
@ -66,7 +67,7 @@ export const useSnippetInputFieldActions = ({
|
||||
onRefresh: setFields,
|
||||
})
|
||||
closeEditor()
|
||||
}, [closeEditor, editingField?.variable, fields, syncInputFieldsDraft, t])
|
||||
}, [closeEditor, editingField?.variable, fields, setFields, syncInputFieldsDraft, t])
|
||||
|
||||
const handleToggleInputPanel = useCallback(() => {
|
||||
if (isInputPanelOpen)
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
useEffect,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { WorkflowWithInnerContext } from '@/app/components/workflow'
|
||||
import { useAvailableNodesMetaData } from '@/app/components/workflow-app/hooks'
|
||||
import { useSetWorkflowVarsWithValue } from '@/app/components/workflow/hooks/use-fetch-workflow-inspect-vars'
|
||||
@ -85,7 +86,13 @@ const SnippetMain = ({
|
||||
nodesMap,
|
||||
}
|
||||
}, [workflowAvailableNodesMetaData])
|
||||
const reset = useSnippetDetailStore(state => state.reset)
|
||||
const {
|
||||
reset,
|
||||
setFields,
|
||||
} = useSnippetDetailStore(useShallow(state => ({
|
||||
reset: state.reset,
|
||||
setFields: state.setFields,
|
||||
})))
|
||||
const {
|
||||
editingField,
|
||||
fields,
|
||||
@ -100,7 +107,6 @@ const SnippetMain = ({
|
||||
handleToggleInputPanel,
|
||||
} = useSnippetInputFieldActions({
|
||||
snippetId,
|
||||
initialFields: payload.inputFields,
|
||||
})
|
||||
const {
|
||||
handlePublish,
|
||||
@ -122,6 +128,10 @@ const SnippetMain = ({
|
||||
reset()
|
||||
}, [reset, snippetId])
|
||||
|
||||
useEffect(() => {
|
||||
setFields(payload.inputFields)
|
||||
}, [payload.inputFields, setFields, snippetId])
|
||||
|
||||
const hooksStore = useMemo(() => {
|
||||
return {
|
||||
doSyncWorkflowDraft,
|
||||
|
||||
@ -0,0 +1,163 @@
|
||||
import type { SnippetInputField } from '@/models/snippet'
|
||||
import { act, renderHook } from '@testing-library/react'
|
||||
import { PipelineInputVarType } from '@/models/pipeline'
|
||||
import { useSnippetDetailStore } from '../../store'
|
||||
import { useNodesSyncDraft } from '../use-nodes-sync-draft'
|
||||
|
||||
const mockGetNodes = vi.fn()
|
||||
const mockGetNodesReadOnly = vi.fn()
|
||||
const mockPostWithKeepalive = vi.fn()
|
||||
const mockSyncDraftWorkflow = vi.fn()
|
||||
const mockSetDraftUpdatedAt = vi.fn()
|
||||
const mockSetSyncWorkflowDraftHash = vi.fn()
|
||||
|
||||
let reactFlowState: {
|
||||
getNodes: typeof mockGetNodes
|
||||
edges: Array<Record<string, unknown>>
|
||||
transform: [number, number, number]
|
||||
}
|
||||
|
||||
let workflowStoreState: {
|
||||
syncWorkflowDraftHash: string | null
|
||||
setDraftUpdatedAt: typeof mockSetDraftUpdatedAt
|
||||
setSyncWorkflowDraftHash: typeof mockSetSyncWorkflowDraftHash
|
||||
}
|
||||
|
||||
vi.mock('reactflow', () => ({
|
||||
useStoreApi: () => ({ getState: () => reactFlowState }),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/workflow/hooks/use-workflow', () => ({
|
||||
useNodesReadOnly: () => ({ getNodesReadOnly: mockGetNodesReadOnly }),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/workflow/hooks/use-serial-async-callback', () => ({
|
||||
useSerialAsyncCallback: (fn: (...args: unknown[]) => Promise<void>, checkFn?: () => boolean) =>
|
||||
(...args: unknown[]) => {
|
||||
if (checkFn?.())
|
||||
return
|
||||
|
||||
return fn(...args)
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/workflow/store', () => ({
|
||||
useWorkflowStore: () => ({
|
||||
getState: () => workflowStoreState,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleClient: {
|
||||
snippets: {
|
||||
syncDraftWorkflow: (...args: unknown[]) => mockSyncDraftWorkflow(...args),
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/service/fetch', () => ({
|
||||
postWithKeepalive: (...args: unknown[]) => mockPostWithKeepalive(...args),
|
||||
}))
|
||||
|
||||
vi.mock('@/config', () => ({ API_PREFIX: '/api' }))
|
||||
|
||||
vi.mock('../use-snippet-refresh-draft', () => ({
|
||||
useSnippetRefreshDraft: () => ({
|
||||
handleRefreshWorkflowDraft: vi.fn(),
|
||||
}),
|
||||
}))
|
||||
|
||||
const createInputField = (variable: string): SnippetInputField => ({
|
||||
type: PipelineInputVarType.textInput,
|
||||
label: variable,
|
||||
variable,
|
||||
required: false,
|
||||
})
|
||||
|
||||
describe('snippet/use-nodes-sync-draft', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
reactFlowState = {
|
||||
getNodes: mockGetNodes,
|
||||
edges: [{ id: 'edge-1', source: 'node-1', target: 'node-2', data: { stable: true } }],
|
||||
transform: [12, 24, 1.5],
|
||||
}
|
||||
workflowStoreState = {
|
||||
syncWorkflowDraftHash: 'draft-hash',
|
||||
setDraftUpdatedAt: mockSetDraftUpdatedAt,
|
||||
setSyncWorkflowDraftHash: mockSetSyncWorkflowDraftHash,
|
||||
}
|
||||
mockGetNodesReadOnly.mockReturnValue(false)
|
||||
mockGetNodes.mockReturnValue([
|
||||
{ id: 'node-1', position: { x: 0, y: 0 }, data: { title: 'Start', _temp: 'drop' } },
|
||||
])
|
||||
mockSyncDraftWorkflow.mockResolvedValue({
|
||||
hash: 'next-hash',
|
||||
updated_at: 123,
|
||||
})
|
||||
useSnippetDetailStore.setState({
|
||||
fields: [createInputField('topic')],
|
||||
})
|
||||
})
|
||||
|
||||
it('should include current input_fields when syncing the draft graph', async () => {
|
||||
const { result } = renderHook(() => useNodesSyncDraft('snippet-1'))
|
||||
|
||||
await act(async () => {
|
||||
await result.current.doSyncWorkflowDraft()
|
||||
})
|
||||
|
||||
expect(mockSyncDraftWorkflow).toHaveBeenCalledWith({
|
||||
params: { snippetId: 'snippet-1' },
|
||||
body: {
|
||||
graph: {
|
||||
nodes: [{ id: 'node-1', position: { x: 0, y: 0 }, data: { title: 'Start' } }],
|
||||
edges: [{ id: 'edge-1', source: 'node-1', target: 'node-2', data: { stable: true } }],
|
||||
viewport: { x: 12, y: 24, zoom: 1.5 },
|
||||
},
|
||||
input_fields: [createInputField('topic')],
|
||||
hash: 'draft-hash',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should include the latest graph when syncing input fields', async () => {
|
||||
const { result } = renderHook(() => useNodesSyncDraft('snippet-1'))
|
||||
const nextFields = [createInputField('summary')]
|
||||
|
||||
await act(async () => {
|
||||
await result.current.syncInputFieldsDraft(nextFields)
|
||||
})
|
||||
|
||||
expect(mockSyncDraftWorkflow).toHaveBeenCalledWith({
|
||||
params: { snippetId: 'snippet-1' },
|
||||
body: {
|
||||
graph: {
|
||||
nodes: [{ id: 'node-1', position: { x: 0, y: 0 }, data: { title: 'Start' } }],
|
||||
edges: [{ id: 'edge-1', source: 'node-1', target: 'node-2', data: { stable: true } }],
|
||||
viewport: { x: 12, y: 24, zoom: 1.5 },
|
||||
},
|
||||
input_fields: nextFields,
|
||||
hash: 'draft-hash',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should send input_fields together with graph on page close', () => {
|
||||
const { result } = renderHook(() => useNodesSyncDraft('snippet-1'))
|
||||
|
||||
act(() => {
|
||||
result.current.syncWorkflowDraftWhenPageClose()
|
||||
})
|
||||
|
||||
expect(mockPostWithKeepalive).toHaveBeenCalledWith('/api/snippets/snippet-1/workflows/draft', {
|
||||
graph: {
|
||||
nodes: [{ id: 'node-1', position: { x: 0, y: 0 }, data: { title: 'Start' } }],
|
||||
edges: [{ id: 'edge-1', source: 'node-1', target: 'node-2', data: { stable: true } }],
|
||||
viewport: { x: 12, y: 24, zoom: 1.5 },
|
||||
},
|
||||
input_fields: [createInputField('topic')],
|
||||
hash: 'draft-hash',
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -10,6 +10,7 @@ import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { API_PREFIX } from '@/config'
|
||||
import { consoleClient } from '@/service/client'
|
||||
import { postWithKeepalive } from '@/service/fetch'
|
||||
import { useSnippetDetailStore } from '../store'
|
||||
import { useSnippetRefreshDraft } from './use-snippet-refresh-draft'
|
||||
|
||||
const isSyncConflictError = (error: unknown): error is { bodyUsed: boolean, json: () => Promise<{ code?: string }> } => {
|
||||
@ -30,7 +31,13 @@ export const useNodesSyncDraft = (snippetId: string) => {
|
||||
const { getNodesReadOnly } = useNodesReadOnly()
|
||||
const { handleRefreshWorkflowDraft } = useSnippetRefreshDraft(snippetId)
|
||||
|
||||
const getGraphSyncPayload = useCallback(() => {
|
||||
const getInputFieldsSyncPayload = useCallback((inputFields?: SnippetInputField[]) => {
|
||||
return {
|
||||
input_fields: inputFields ?? useSnippetDetailStore.getState().fields,
|
||||
}
|
||||
}, [])
|
||||
|
||||
const getDraftSyncPayload = useCallback((inputFields?: SnippetInputField[]) => {
|
||||
const {
|
||||
getNodes,
|
||||
edges,
|
||||
@ -59,13 +66,14 @@ export const useNodesSyncDraft = (snippetId: string) => {
|
||||
})
|
||||
|
||||
return {
|
||||
...getInputFieldsSyncPayload(inputFields),
|
||||
graph: {
|
||||
nodes: producedNodes,
|
||||
edges: producedEdges,
|
||||
viewport: { x, y, zoom },
|
||||
},
|
||||
}
|
||||
}, [snippetId, store])
|
||||
}, [getInputFieldsSyncPayload, snippetId, store])
|
||||
|
||||
const syncDraft = useCallback(async (
|
||||
payload: Omit<SnippetDraftSyncPayload, 'hash'>,
|
||||
@ -116,34 +124,38 @@ export const useNodesSyncDraft = (snippetId: string) => {
|
||||
if (getNodesReadOnly())
|
||||
return
|
||||
|
||||
const graphPayload = getGraphSyncPayload()
|
||||
if (!graphPayload)
|
||||
const draftPayload = getDraftSyncPayload()
|
||||
if (!draftPayload)
|
||||
return
|
||||
|
||||
const { syncWorkflowDraftHash } = workflowStore.getState()
|
||||
postWithKeepalive(`${API_PREFIX}/snippets/${snippetId}/workflows/draft`, {
|
||||
...graphPayload,
|
||||
...draftPayload,
|
||||
hash: syncWorkflowDraftHash,
|
||||
})
|
||||
}, [getGraphSyncPayload, getNodesReadOnly, snippetId, workflowStore])
|
||||
}, [getDraftSyncPayload, getNodesReadOnly, snippetId, workflowStore])
|
||||
|
||||
const performSync = useCallback(async (
|
||||
notRefreshWhenSyncError?: boolean,
|
||||
callback?: SyncDraftCallback,
|
||||
) => {
|
||||
const graphPayload = getGraphSyncPayload()
|
||||
if (!graphPayload)
|
||||
const draftPayload = getDraftSyncPayload()
|
||||
if (!draftPayload)
|
||||
return
|
||||
|
||||
await syncDraft(graphPayload, notRefreshWhenSyncError, callback)
|
||||
}, [getGraphSyncPayload, syncDraft])
|
||||
await syncDraft(draftPayload, notRefreshWhenSyncError, callback)
|
||||
}, [getDraftSyncPayload, syncDraft])
|
||||
|
||||
const performInputFieldsSync = useCallback(async (
|
||||
inputFields: SnippetInputField[],
|
||||
callback?: SyncInputFieldsDraftCallback,
|
||||
) => {
|
||||
const draftPayload = getDraftSyncPayload(inputFields)
|
||||
if (!draftPayload)
|
||||
return
|
||||
|
||||
await syncDraft(
|
||||
{ input_fields: inputFields },
|
||||
draftPayload,
|
||||
false,
|
||||
callback,
|
||||
(draftWorkflow) => {
|
||||
@ -153,7 +165,7 @@ export const useNodesSyncDraft = (snippetId: string) => {
|
||||
callback?.onRefresh?.(refreshedInputFields)
|
||||
},
|
||||
)
|
||||
}, [syncDraft])
|
||||
}, [getDraftSyncPayload, syncDraft])
|
||||
|
||||
const doSyncWorkflowDraft = useSerialAsyncCallback(performSync, getNodesReadOnly)
|
||||
const syncInputFieldsDraft = useSerialAsyncCallback(performInputFieldsSync)
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import type { WorkflowDataUpdater } from '@/app/components/workflow/types'
|
||||
import type { SnippetInputField } from '@/models/snippet'
|
||||
import type { SnippetWorkflow } from '@/types/snippet'
|
||||
import { useCallback } from 'react'
|
||||
import { useWorkflowUpdate } from '@/app/components/workflow/hooks'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { consoleClient } from '@/service/client'
|
||||
import { useSnippetDetailStore } from '../store'
|
||||
|
||||
export const useSnippetRefreshDraft = (snippetId: string) => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
@ -23,12 +25,19 @@ export const useSnippetRefreshDraft = (snippetId: string) => {
|
||||
consoleClient.snippets.draftWorkflow({
|
||||
params: { snippetId },
|
||||
}).then((response) => {
|
||||
const inputFields = Array.isArray(response.input_fields)
|
||||
? response.input_fields as SnippetInputField[]
|
||||
: []
|
||||
|
||||
handleUpdateWorkflowCanvas({
|
||||
...response.graph,
|
||||
nodes: response.graph?.nodes || [],
|
||||
edges: response.graph?.edges || [],
|
||||
viewport: response.graph?.viewport || { x: 0, y: 0, zoom: 1 },
|
||||
} as WorkflowDataUpdater)
|
||||
useSnippetDetailStore.setState({
|
||||
fields: inputFields,
|
||||
})
|
||||
setSyncWorkflowDraftHash(response.hash)
|
||||
setDraftUpdatedAt(response.updated_at)
|
||||
onSuccess?.(response)
|
||||
|
||||
@ -5,12 +5,14 @@ import { create } from 'zustand'
|
||||
|
||||
type SnippetDetailUIState = {
|
||||
activeSection: SnippetSection
|
||||
fields: SnippetInputField[]
|
||||
isInputPanelOpen: boolean
|
||||
isPublishMenuOpen: boolean
|
||||
isPreviewMode: boolean
|
||||
isEditorOpen: boolean
|
||||
editingField: SnippetInputField | null
|
||||
setActiveSection: (section: SnippetSection) => void
|
||||
setFields: (fields: SnippetInputField[]) => void
|
||||
setInputPanelOpen: (value: boolean) => void
|
||||
toggleInputPanel: () => void
|
||||
setPublishMenuOpen: (value: boolean) => void
|
||||
@ -23,6 +25,7 @@ type SnippetDetailUIState = {
|
||||
|
||||
const initialState = {
|
||||
activeSection: 'orchestrate' as SnippetSection,
|
||||
fields: [] as SnippetInputField[],
|
||||
isInputPanelOpen: false,
|
||||
isPublishMenuOpen: false,
|
||||
isPreviewMode: false,
|
||||
@ -33,6 +36,7 @@ const initialState = {
|
||||
export const useSnippetDetailStore = create<SnippetDetailUIState>(set => ({
|
||||
...initialState,
|
||||
setActiveSection: activeSection => set({ activeSection }),
|
||||
setFields: fields => set({ fields }),
|
||||
setInputPanelOpen: isInputPanelOpen => set({ isInputPanelOpen }),
|
||||
toggleInputPanel: () => set(state => ({ isInputPanelOpen: !state.isInputPanelOpen, isPublishMenuOpen: false })),
|
||||
setPublishMenuOpen: isPublishMenuOpen => set({ isPublishMenuOpen }),
|
||||
|
||||
Reference in New Issue
Block a user