fix(web): snippet publish check

This commit is contained in:
JzoNg 2026-04-28 15:42:31 +08:00
parent d95d4335bf
commit 3a7f09a250
4 changed files with 111 additions and 16 deletions

View File

@ -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,

View File

@ -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'))

View File

@ -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)

View File

@ -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}