mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 18:27:19 +08:00
fix(web): snippet publish check
This commit is contained in:
parent
d95d4335bf
commit
3a7f09a250
@ -23,6 +23,7 @@ const mockHandleRun = vi.fn()
|
||||
const mockHandleStartWorkflowRun = vi.fn()
|
||||
const mockHandleStopRun = vi.fn()
|
||||
const mockHandleWorkflowStartRunInWorkflow = vi.fn()
|
||||
const mockHandleCheckBeforePublish = vi.fn()
|
||||
const mockInspectVarsCrud = {
|
||||
hasNodeInspectVars: vi.fn(),
|
||||
hasSetInspectVar: vi.fn(),
|
||||
@ -81,6 +82,12 @@ vi.mock('@/app/components/workflow/hooks/use-fetch-workflow-inspect-vars', () =>
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/workflow/hooks/use-checklist', () => ({
|
||||
useChecklistBeforePublish: () => ({
|
||||
handleCheckBeforePublish: mockHandleCheckBeforePublish,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/snippets/hooks/use-inspect-vars-crud', () => ({
|
||||
useInspectVarsCrud: () => mockInspectVarsCrud,
|
||||
}))
|
||||
@ -208,6 +215,7 @@ describe('SnippetMain', () => {
|
||||
vi.clearAllMocks()
|
||||
mockSyncInputFieldsDraft.mockResolvedValue(undefined)
|
||||
mockPublishSnippetMutateAsync.mockResolvedValue({ created_at: 1_744_000_000 })
|
||||
mockHandleCheckBeforePublish.mockResolvedValue(true)
|
||||
capturedHooksStore = undefined
|
||||
snippetDetailStoreState = {
|
||||
editingField: null,
|
||||
|
||||
@ -7,6 +7,7 @@ const mockSetPublishMenuOpen = vi.fn()
|
||||
const mockUseKeyPress = vi.fn()
|
||||
const mockSetPublishedAt = vi.fn()
|
||||
const mockSetQueryData = vi.fn()
|
||||
const mockHandleCheckBeforePublish = vi.fn<() => Promise<boolean>>()
|
||||
|
||||
let isPublishMenuOpen = false
|
||||
let isPending = false
|
||||
@ -44,6 +45,12 @@ vi.mock('@/app/components/workflow/store', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/workflow/hooks/use-checklist', () => ({
|
||||
useChecklistBeforePublish: () => ({
|
||||
handleCheckBeforePublish: mockHandleCheckBeforePublish,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('../../../store', () => ({
|
||||
useSnippetDetailStore: (selector: (state: {
|
||||
isPublishMenuOpen: boolean
|
||||
@ -60,6 +67,7 @@ describe('useSnippetPublish', () => {
|
||||
isPublishMenuOpen = false
|
||||
isPending = false
|
||||
shortcutHandler = undefined
|
||||
mockHandleCheckBeforePublish.mockResolvedValue(true)
|
||||
mockMutateAsync.mockResolvedValue({ created_at: 1_712_345_678 })
|
||||
mockUseKeyPress.mockImplementation((_key, handler) => {
|
||||
shortcutHandler = handler
|
||||
@ -76,17 +84,39 @@ describe('useSnippetPublish', () => {
|
||||
await result.current.handlePublish()
|
||||
})
|
||||
|
||||
expect(mockHandleCheckBeforePublish).toHaveBeenCalledTimes(1)
|
||||
expect(mockMutateAsync).toHaveBeenCalledWith({
|
||||
params: { snippetId: 'snippet-1' },
|
||||
})
|
||||
expect(mockSetQueryData).toHaveBeenCalledTimes(1)
|
||||
const updateSnippetDetail = mockSetQueryData.mock.calls[0][1] as (old: { is_published: boolean }) => { is_published: boolean }
|
||||
const setQueryDataCall = mockSetQueryData.mock.calls[0]
|
||||
expect(setQueryDataCall).toBeDefined()
|
||||
const updateSnippetDetail = setQueryDataCall![1] as (old: { is_published: boolean }) => { is_published: boolean }
|
||||
expect(updateSnippetDetail({ is_published: false })).toEqual({ is_published: true })
|
||||
expect(mockSetPublishedAt).toHaveBeenCalledWith(1_712_345_678)
|
||||
expect(mockSetPublishMenuOpen).toHaveBeenCalledWith(false)
|
||||
expect(toast.success).toHaveBeenCalledWith('snippet.publishSuccess')
|
||||
})
|
||||
|
||||
it('should not publish the snippet when checklist validation fails', async () => {
|
||||
mockHandleCheckBeforePublish.mockResolvedValue(false)
|
||||
|
||||
const { result } = renderHook(() => useSnippetPublish({
|
||||
snippetId: 'snippet-1',
|
||||
}))
|
||||
|
||||
await act(async () => {
|
||||
await result.current.handlePublish()
|
||||
})
|
||||
|
||||
expect(mockHandleCheckBeforePublish).toHaveBeenCalledTimes(1)
|
||||
expect(mockMutateAsync).not.toHaveBeenCalled()
|
||||
expect(mockSetQueryData).not.toHaveBeenCalled()
|
||||
expect(mockSetPublishedAt).not.toHaveBeenCalled()
|
||||
expect(mockSetPublishMenuOpen).not.toHaveBeenCalled()
|
||||
expect(toast.success).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should surface publish errors through toast feedback', async () => {
|
||||
mockMutateAsync.mockRejectedValue(new Error('publish failed'))
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import { useKeyPress } from 'ahooks'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { useChecklistBeforePublish } from '@/app/components/workflow/hooks/use-checklist'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { getKeyboardKeyCodeBySystem } from '@/app/components/workflow/utils'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
@ -22,6 +23,7 @@ export const useSnippetPublish = ({
|
||||
const workflowStore = useWorkflowStore()
|
||||
const queryClient = useQueryClient()
|
||||
const publishSnippetMutation = usePublishSnippetWorkflowMutation(snippetId)
|
||||
const { handleCheckBeforePublish } = useChecklistBeforePublish()
|
||||
const {
|
||||
isPublishMenuOpen,
|
||||
setPublishMenuOpen,
|
||||
@ -32,6 +34,10 @@ export const useSnippetPublish = ({
|
||||
|
||||
const handlePublish = useCallback(async () => {
|
||||
try {
|
||||
const canPublish = await handleCheckBeforePublish()
|
||||
if (!canPublish)
|
||||
return
|
||||
|
||||
const publishedWorkflow = await publishSnippetMutation.mutateAsync({
|
||||
params: { snippetId },
|
||||
})
|
||||
@ -50,7 +56,7 @@ export const useSnippetPublish = ({
|
||||
catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : t('publishFailed'))
|
||||
}
|
||||
}, [publishSnippetMutation, queryClient, setPublishMenuOpen, snippetId, t, workflowStore])
|
||||
}, [handleCheckBeforePublish, publishSnippetMutation, queryClient, setPublishMenuOpen, snippetId, t, workflowStore])
|
||||
|
||||
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.shift.p`, (event) => {
|
||||
if (publishSnippetMutation.isPending)
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import type { WorkflowProps } from '@/app/components/workflow'
|
||||
import type { Shape as HooksStoreShape } from '@/app/components/workflow/hooks-store'
|
||||
import type { SnippetDetailPayload } from '@/models/snippet'
|
||||
import type { SnippetDetailPayload, SnippetDetailUIModel, SnippetInputField } from '@/models/snippet'
|
||||
import {
|
||||
useEffect,
|
||||
useMemo,
|
||||
@ -28,6 +28,69 @@ type SnippetMainProps = {
|
||||
snippetId: string
|
||||
} & Pick<WorkflowProps, 'nodes' | 'edges' | 'viewport'>
|
||||
|
||||
type SnippetMainContentProps = {
|
||||
snippetId: string
|
||||
fields: SnippetInputField[]
|
||||
uiMeta: SnippetDetailUIModel
|
||||
editingField: SnippetInputField | null
|
||||
isEditorOpen: boolean
|
||||
isInputPanelOpen: boolean
|
||||
onToggleInputPanel: () => void
|
||||
onCloseInputPanel: () => void
|
||||
onOpenEditor: (field?: SnippetInputField | null) => void
|
||||
onCloseEditor: () => void
|
||||
onSubmitField: (field: SnippetInputField) => void
|
||||
onRemoveField: (index: number) => void
|
||||
onSortChange: (fields: SnippetInputField[]) => void
|
||||
}
|
||||
|
||||
const SnippetMainContent = ({
|
||||
snippetId,
|
||||
fields,
|
||||
uiMeta,
|
||||
editingField,
|
||||
isEditorOpen,
|
||||
isInputPanelOpen,
|
||||
onToggleInputPanel,
|
||||
onCloseInputPanel,
|
||||
onOpenEditor,
|
||||
onCloseEditor,
|
||||
onSubmitField,
|
||||
onRemoveField,
|
||||
onSortChange,
|
||||
}: SnippetMainContentProps) => {
|
||||
const {
|
||||
handlePublish,
|
||||
isPublishMenuOpen,
|
||||
isPublishing,
|
||||
setPublishMenuOpen,
|
||||
} = useSnippetPublish({
|
||||
snippetId,
|
||||
})
|
||||
|
||||
return (
|
||||
<SnippetChildren
|
||||
snippetId={snippetId}
|
||||
fields={fields}
|
||||
uiMeta={uiMeta}
|
||||
editingField={editingField}
|
||||
isEditorOpen={isEditorOpen}
|
||||
isInputPanelOpen={isInputPanelOpen}
|
||||
isPublishMenuOpen={isPublishMenuOpen}
|
||||
isPublishing={isPublishing}
|
||||
onToggleInputPanel={onToggleInputPanel}
|
||||
onPublishMenuOpenChange={setPublishMenuOpen}
|
||||
onCloseInputPanel={onCloseInputPanel}
|
||||
onPublish={handlePublish}
|
||||
onOpenEditor={onOpenEditor}
|
||||
onCloseEditor={onCloseEditor}
|
||||
onSubmitField={onSubmitField}
|
||||
onRemoveField={onRemoveField}
|
||||
onSortChange={onSortChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const SnippetMain = ({
|
||||
payload,
|
||||
snippetId,
|
||||
@ -109,14 +172,6 @@ const SnippetMain = ({
|
||||
} = useSnippetInputFieldActions({
|
||||
snippetId,
|
||||
})
|
||||
const {
|
||||
handlePublish,
|
||||
isPublishMenuOpen,
|
||||
isPublishing,
|
||||
setPublishMenuOpen,
|
||||
} = useSnippetPublish({
|
||||
snippetId,
|
||||
})
|
||||
const {
|
||||
handleStartWorkflowRun,
|
||||
handleWorkflowStartRunInWorkflow,
|
||||
@ -200,19 +255,15 @@ const SnippetMain = ({
|
||||
viewport={viewport ?? graph.viewport}
|
||||
hooksStore={hooksStore as unknown as Partial<HooksStoreShape>}
|
||||
>
|
||||
<SnippetChildren
|
||||
<SnippetMainContent
|
||||
snippetId={snippetId}
|
||||
fields={fields}
|
||||
uiMeta={uiMeta}
|
||||
editingField={editingField}
|
||||
isEditorOpen={isEditorOpen}
|
||||
isInputPanelOpen={isInputPanelOpen}
|
||||
isPublishMenuOpen={isPublishMenuOpen}
|
||||
isPublishing={isPublishing}
|
||||
onToggleInputPanel={handleToggleInputPanel}
|
||||
onPublishMenuOpenChange={setPublishMenuOpen}
|
||||
onCloseInputPanel={handleCloseInputPanel}
|
||||
onPublish={handlePublish}
|
||||
onOpenEditor={openEditor}
|
||||
onCloseEditor={closeEditor}
|
||||
onSubmitField={handleSubmitField}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user