From f79c1fa79fa99b03c7385f908b4fa804b818771b Mon Sep 17 00:00:00 2001 From: majiayu000 <1835304752@qq.com> Date: Mon, 29 Dec 2025 11:23:00 +0800 Subject: [PATCH] fix: restore draft version correctly in version history panel When selecting the draft version in version history panel, the code now correctly calls handleLoadBackupDraft() to restore the in-memory backup of the draft, instead of calling handleRestoreFromPublishedWorkflow() which would show the DSL from the backend response (which is the published version's DSL, not the unsaved draft changes). Fixes #26107 Signed-off-by: majiayu000 <1835304752@qq.com> --- .../version-history-panel/index.spec.tsx | 156 ++++++++++++++++++ .../panel/version-history-panel/index.tsx | 9 +- 2 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 web/app/components/workflow/panel/version-history-panel/index.spec.tsx diff --git a/web/app/components/workflow/panel/version-history-panel/index.spec.tsx b/web/app/components/workflow/panel/version-history-panel/index.spec.tsx new file mode 100644 index 0000000000..5ad68ae0dc --- /dev/null +++ b/web/app/components/workflow/panel/version-history-panel/index.spec.tsx @@ -0,0 +1,156 @@ +import { fireEvent, render, screen } from '@testing-library/react' +import { WorkflowVersion } from '../../types' + +const mockHandleRestoreFromPublishedWorkflow = vi.fn() +const mockHandleLoadBackupDraft = vi.fn() +const mockSetCurrentVersion = vi.fn() + +vi.mock('@/context/app-context', () => ({ + useSelector: () => ({ id: 'test-user-id' }), +})) + +vi.mock('@/service/use-workflow', () => ({ + useDeleteWorkflow: () => ({ mutateAsync: vi.fn() }), + useInvalidAllLastRun: () => vi.fn(), + useResetWorkflowVersionHistory: () => vi.fn(), + useUpdateWorkflow: () => ({ mutateAsync: vi.fn() }), + useWorkflowVersionHistory: () => ({ + data: { + pages: [ + { + items: [ + { + id: 'draft-version-id', + version: WorkflowVersion.Draft, + graph: { nodes: [], edges: [], viewport: null }, + features: { + opening_statement: '', + suggested_questions: [], + suggested_questions_after_answer: { enabled: false }, + text_to_speech: { enabled: false }, + speech_to_text: { enabled: false }, + retriever_resource: { enabled: false }, + sensitive_word_avoidance: { enabled: false }, + file_upload: { image: { enabled: false } }, + }, + created_at: Date.now() / 1000, + created_by: { id: 'user-1', name: 'User 1' }, + environment_variables: [], + marked_name: '', + marked_comment: '', + }, + { + id: 'published-version-id', + version: '2024-01-01T00:00:00Z', + graph: { nodes: [], edges: [], viewport: null }, + features: { + opening_statement: '', + suggested_questions: [], + suggested_questions_after_answer: { enabled: false }, + text_to_speech: { enabled: false }, + speech_to_text: { enabled: false }, + retriever_resource: { enabled: false }, + sensitive_word_avoidance: { enabled: false }, + file_upload: { image: { enabled: false } }, + }, + created_at: Date.now() / 1000, + created_by: { id: 'user-1', name: 'User 1' }, + environment_variables: [], + marked_name: 'v1.0', + marked_comment: 'First release', + }, + ], + }, + ], + }, + fetchNextPage: vi.fn(), + hasNextPage: false, + isFetching: false, + }), +})) + +vi.mock('../../hooks', () => ({ + useDSL: () => ({ handleExportDSL: vi.fn() }), + useNodesSyncDraft: () => ({ handleSyncWorkflowDraft: vi.fn() }), + useWorkflowRun: () => ({ + handleRestoreFromPublishedWorkflow: mockHandleRestoreFromPublishedWorkflow, + handleLoadBackupDraft: mockHandleLoadBackupDraft, + }), +})) + +vi.mock('../../hooks-store', () => ({ + useHooksStore: () => ({ + flowId: 'test-flow-id', + flowType: 'workflow', + }), +})) + +vi.mock('../../store', () => ({ + useStore: (selector: (state: any) => any) => { + const state = { + setShowWorkflowVersionHistoryPanel: vi.fn(), + currentVersion: null, + setCurrentVersion: mockSetCurrentVersion, + } + return selector(state) + }, + useWorkflowStore: () => ({ + getState: () => ({ + deleteAllInspectVars: vi.fn(), + }), + setState: vi.fn(), + }), +})) + +vi.mock('./delete-confirm-modal', () => ({ + default: () => null, +})) + +vi.mock('./restore-confirm-modal', () => ({ + default: () => null, +})) + +vi.mock('@/app/components/app/app-publisher/version-info-modal', () => ({ + default: () => null, +})) + +describe('VersionHistoryPanel', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + describe('Version Click Behavior', () => { + it('should call handleLoadBackupDraft when draft version is selected on mount', async () => { + const { VersionHistoryPanel } = await import('./index') + + render( + , + ) + + // Draft version auto-clicks on mount via useEffect in VersionHistoryItem + expect(mockHandleLoadBackupDraft).toHaveBeenCalled() + expect(mockHandleRestoreFromPublishedWorkflow).not.toHaveBeenCalled() + }) + + it('should call handleRestoreFromPublishedWorkflow when clicking published version', async () => { + const { VersionHistoryPanel } = await import('./index') + + render( + , + ) + + // Clear mocks after initial render (draft version auto-clicks on mount) + vi.clearAllMocks() + + const publishedItem = screen.getByText('v1.0') + fireEvent.click(publishedItem) + + expect(mockHandleRestoreFromPublishedWorkflow).toHaveBeenCalled() + expect(mockHandleLoadBackupDraft).not.toHaveBeenCalled() + }) + }) +}) diff --git a/web/app/components/workflow/panel/version-history-panel/index.tsx b/web/app/components/workflow/panel/version-history-panel/index.tsx index 06a27eb7c7..891ab6335d 100644 --- a/web/app/components/workflow/panel/version-history-panel/index.tsx +++ b/web/app/components/workflow/panel/version-history-panel/index.tsx @@ -13,7 +13,7 @@ import { useDeleteWorkflow, useInvalidAllLastRun, useResetWorkflowVersionHistory import { useDSL, useNodesSyncDraft, useWorkflowRun } from '../../hooks' import { useHooksStore } from '../../hooks-store' import { useStore, useWorkflowStore } from '../../store' -import { VersionHistoryContextMenuOptions, WorkflowVersionFilterOptions } from '../../types' +import { VersionHistoryContextMenuOptions, WorkflowVersion, WorkflowVersionFilterOptions } from '../../types' import DeleteConfirmModal from './delete-confirm-modal' import Empty from './empty' import Filter from './filter' @@ -73,9 +73,12 @@ export const VersionHistoryPanel = ({ const handleVersionClick = useCallback((item: VersionHistory) => { if (item.id !== currentVersion?.id) { setCurrentVersion(item) - handleRestoreFromPublishedWorkflow(item) + if (item.version === WorkflowVersion.Draft) + handleLoadBackupDraft() + else + handleRestoreFromPublishedWorkflow(item) } - }, [currentVersion?.id, setCurrentVersion, handleRestoreFromPublishedWorkflow]) + }, [currentVersion?.id, setCurrentVersion, handleLoadBackupDraft, handleRestoreFromPublishedWorkflow]) const handleNextPage = () => { if (hasNextPage)