From fb91984fcb5e3da96212068af9bdf3ba22b7feac Mon Sep 17 00:00:00 2001 From: JzoNg Date: Thu, 9 Apr 2026 13:26:43 +0800 Subject: [PATCH] feat(web): add evaluation navigation for rag-pipeline --- .../__tests__/layout-main.spec.tsx | 209 ++++++++++++++++++ .../[datasetId]/layout-main.tsx | 19 +- web/app/components/evaluation/index.tsx | 2 +- 3 files changed, 221 insertions(+), 9 deletions(-) create mode 100644 web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/__tests__/layout-main.spec.tsx diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/__tests__/layout-main.spec.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/__tests__/layout-main.spec.tsx new file mode 100644 index 0000000000..74dc55efb8 --- /dev/null +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/__tests__/layout-main.spec.tsx @@ -0,0 +1,209 @@ +import type { ReactNode } from 'react' +import type { DataSet } from '@/models/datasets' +import { render, screen } from '@testing-library/react' +import { IndexingType } from '@/app/components/datasets/create/step-two' +import { ChunkingMode, DatasetPermission, DataSourceType } from '@/models/datasets' +import { RETRIEVE_METHOD } from '@/types/app' +import DatasetDetailLayout from '../layout-main' + +let mockPathname = '/datasets/test-dataset-id/documents' +let mockDataset: DataSet | undefined + +const mockSetAppSidebarExpand = vi.fn() +const mockMutateDatasetRes = vi.fn() + +vi.mock('@/next/navigation', () => ({ + usePathname: () => mockPathname, +})) + +vi.mock('@/app/components/app/store', () => ({ + useStore: (selector: (state: { setAppSidebarExpand: typeof mockSetAppSidebarExpand }) => unknown) => selector({ + setAppSidebarExpand: mockSetAppSidebarExpand, + }), +})) + +vi.mock('@/hooks/use-breakpoints', () => ({ + default: () => 'desktop', + MediaType: { + mobile: 'mobile', + desktop: 'desktop', + }, +})) + +vi.mock('@/context/event-emitter', () => ({ + useEventEmitterContextContext: () => ({ + eventEmitter: { + useSubscription: vi.fn(), + }, + }), +})) + +vi.mock('@/context/app-context', () => ({ + useAppContext: () => ({ + isCurrentWorkspaceDatasetOperator: false, + }), +})) + +vi.mock('@/hooks/use-document-title', () => ({ + default: vi.fn(), +})) + +vi.mock('@/service/knowledge/use-dataset', () => ({ + useDatasetDetail: () => ({ + data: mockDataset, + error: null, + refetch: mockMutateDatasetRes, + }), + useDatasetRelatedApps: () => ({ + data: [], + }), +})) + +vi.mock('@/app/components/app-sidebar', () => ({ + default: ({ + navigation, + children, + }: { + navigation: Array<{ name: string, href: string, disabled?: boolean }> + children?: ReactNode + }) => ( +
+ {navigation.map(item => ( + + ))} + {children} +
+ ), +})) + +vi.mock('@/app/components/datasets/extra-info', () => ({ + default: () =>
, +})) + +vi.mock('@/app/components/base/loading', () => ({ + default: () =>
loading
, +})) + +const createDataset = (overrides: Partial = {}): DataSet => ({ + id: 'test-dataset-id', + name: 'Test Dataset', + indexing_status: 'completed', + icon_info: { + icon: 'book', + icon_background: '#fff', + icon_type: 'emoji', + icon_url: '', + }, + description: '', + permission: DatasetPermission.onlyMe, + data_source_type: DataSourceType.FILE, + indexing_technique: IndexingType.QUALIFIED, + created_by: 'user-1', + updated_by: 'user-1', + updated_at: 0, + app_count: 0, + doc_form: ChunkingMode.text, + document_count: 0, + total_document_count: 0, + word_count: 0, + provider: 'vendor', + embedding_model: 'text-embedding', + embedding_model_provider: 'openai', + embedding_available: true, + retrieval_model_dict: { + search_method: RETRIEVE_METHOD.semantic, + reranking_enable: false, + reranking_model: { + reranking_provider_name: '', + reranking_model_name: '', + }, + top_k: 3, + score_threshold_enabled: false, + score_threshold: 0.5, + }, + retrieval_model: { + search_method: RETRIEVE_METHOD.semantic, + reranking_enable: false, + reranking_model: { + reranking_provider_name: '', + reranking_model_name: '', + }, + top_k: 3, + score_threshold_enabled: false, + score_threshold: 0.5, + }, + tags: [], + external_knowledge_info: { + external_knowledge_id: '', + external_knowledge_api_id: '', + external_knowledge_api_name: '', + external_knowledge_api_endpoint: '', + }, + external_retrieval_model: { + top_k: 3, + score_threshold: 0.5, + score_threshold_enabled: false, + }, + built_in_field_enabled: false, + pipeline_id: 'pipeline-1', + is_published: true, + runtime_mode: 'rag_pipeline', + enable_api: false, + is_multimodal: false, + ...overrides, +}) + +describe('DatasetDetailLayout', () => { + beforeEach(() => { + vi.clearAllMocks() + mockPathname = '/datasets/test-dataset-id/documents' + mockDataset = createDataset() + }) + + describe('Evaluation navigation', () => { + it('should hide the evaluation menu when the dataset is not a rag pipeline', () => { + mockDataset = createDataset({ + runtime_mode: 'general', + is_published: false, + }) + + render( + +
content
+
, + ) + + expect(screen.queryByRole('button', { name: 'common.datasetMenus.evaluation' })).not.toBeInTheDocument() + }) + + it('should disable the evaluation menu when the rag pipeline is unpublished', () => { + mockDataset = createDataset({ + is_published: false, + }) + + render( + +
content
+
, + ) + + expect(screen.getByRole('button', { name: 'common.datasetMenus.evaluation' })).toBeDisabled() + }) + + it('should enable the evaluation menu when the rag pipeline is published', () => { + render( + +
content
+
, + ) + + expect(screen.getByRole('button', { name: 'common.datasetMenus.evaluation' })).toBeEnabled() + }) + }) +}) diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx index bc6837961a..b7a6347b4c 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx @@ -58,6 +58,7 @@ const DatasetDetailLayout: FC = (props) => { const { data: datasetRes, error, refetch: mutateDatasetRes } = useDatasetDetail(datasetId) const { data: relatedApps } = useDatasetRelatedApps(datasetId) + const isRagPipelineDataset = datasetRes?.runtime_mode === 'rag_pipeline' const isButtonDisabledWithPipeline = useMemo(() => { if (!datasetRes) @@ -103,19 +104,21 @@ const DatasetDetailLayout: FC = (props) => { selectedIcon: PipelineFill as RemixiconComponentType, disabled: false, }, - { - name: t('datasetMenus.evaluation', { ns: 'common' }), - href: `/datasets/${datasetId}/evaluation`, - icon: RiFlaskLine, - selectedIcon: RiFlaskFill, - disabled: false, - }, + ...(isRagPipelineDataset + ? [{ + name: t('datasetMenus.evaluation', { ns: 'common' }), + href: `/datasets/${datasetId}/evaluation`, + icon: RiFlaskLine, + selectedIcon: RiFlaskFill, + disabled: isButtonDisabledWithPipeline, + }] + : []), ...baseNavigation, ] } return baseNavigation - }, [t, datasetId, isButtonDisabledWithPipeline, datasetRes?.provider]) + }, [t, datasetId, isButtonDisabledWithPipeline, isRagPipelineDataset, datasetRes?.provider]) useDocumentTitle(datasetRes?.name || t('menus.datasets', { ns: 'common' })) diff --git a/web/app/components/evaluation/index.tsx b/web/app/components/evaluation/index.tsx index c7ebca62f0..b031cd7afb 100644 --- a/web/app/components/evaluation/index.tsx +++ b/web/app/components/evaluation/index.tsx @@ -59,7 +59,7 @@ const Evaluation = ({
-
+