From 7b3d97cb425b45c3265e117cbcc935bf46b48f37 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Mon, 24 Aug 2026 04:05:39 +0000 Subject: [PATCH] refactor(web): migrate pipeline publishing input form (#41129) --- oxlint-suppressions.json | 13 --- ...blish-as-knowledge-pipeline-modal.spec.tsx | 75 ++++++++------- .../publish-as-knowledge-pipeline-modal.tsx | 96 +++++++++++-------- 3 files changed, 99 insertions(+), 85 deletions(-) diff --git a/oxlint-suppressions.json b/oxlint-suppressions.json index 09627b7eeea..e7d86cca33b 100644 --- a/oxlint-suppressions.json +++ b/oxlint-suppressions.json @@ -2929,14 +2929,6 @@ "count": 25 } }, - "web/app/components/rag-pipeline/components/__tests__/publish-as-knowledge-pipeline-modal.spec.tsx": { - "jsx-a11y/click-events-have-key-events": { - "count": 1 - }, - "jsx-a11y/no-static-element-interactions": { - "count": 1 - } - }, "web/app/components/rag-pipeline/components/panel/input-field/editor/form/__tests__/hidden-fields.spec.tsx": { "eslint-react/static-components": { "count": 4 @@ -3034,11 +3026,6 @@ "count": 4 } }, - "web/app/components/rag-pipeline/components/publish-as-knowledge-pipeline-modal.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/rag-pipeline/components/publish-toast.tsx": { "jsx-a11y/click-events-have-key-events": { "count": 1 diff --git a/web/app/components/rag-pipeline/components/__tests__/publish-as-knowledge-pipeline-modal.spec.tsx b/web/app/components/rag-pipeline/components/__tests__/publish-as-knowledge-pipeline-modal.spec.tsx index 045dda8bced..0fb859784a8 100644 --- a/web/app/components/rag-pipeline/components/__tests__/publish-as-knowledge-pipeline-modal.spec.tsx +++ b/web/app/components/rag-pipeline/components/__tests__/publish-as-knowledge-pipeline-modal.spec.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test' import PublishAsKnowledgePipelineModal from '../publish-as-knowledge-pipeline-modal' @@ -16,25 +17,8 @@ vi.mock('@/app/components/workflow/store', () => ({ }), })) -vi.mock('@/app/components/base/input', () => ({ - default: ({ value, onChange, ...props }: Record) => ( - void} - {...props} - /> - ), -})) - vi.mock('@/app/components/base/app-icon', () => ({ - default: ({ onClick }: { onClick?: () => void }) => ( -
- ), -})) - -vi.mock('es-toolkit/function', () => ({ - noop: () => {}, + default: () =>
, })) describe('PublishAsKnowledgePipelineModal', () => { @@ -45,6 +29,12 @@ describe('PublishAsKnowledgePipelineModal', () => { onCancel: mockOnCancel, onConfirm: mockOnConfirm, } + const getNameInput = () => + screen.getByRole('textbox', { name: 'pipeline.common.publishAsPipeline.name' }) + const getIconButton = () => + screen.getByRole('button', { + name: 'common.operation.edit pipeline.common.publishAsPipeline.name', + }) beforeEach(() => { vi.clearAllMocks() @@ -57,14 +47,13 @@ describe('PublishAsKnowledgePipelineModal', () => { it('should render modal with title', () => { render() - expect(screen.getByRole('dialog')).toBeInTheDocument() - expect(screen.getByText('pipeline.common.publishAs')).toBeInTheDocument() + expect(screen.getByRole('dialog', { name: 'pipeline.common.publishAs' })).toBeInTheDocument() }) it('should initialize with knowledgeName from store', () => { render() - const nameInput = screen.getByTestId('name-input') as HTMLInputElement + const nameInput = getNameInput() as HTMLInputElement expect(nameInput.value).toBe('Test Pipeline') }) @@ -93,6 +82,15 @@ describe('PublishAsKnowledgePipelineModal', () => { expect(mockOnCancel).toHaveBeenCalled() }) + it('should call onCancel when Escape is pressed', async () => { + const user = userEvent.setup() + render() + + await user.keyboard('{Escape}') + + expect(mockOnCancel).toHaveBeenCalled() + }) + it('should call onConfirm with name, icon, and description when confirm clicked', () => { render() @@ -108,7 +106,7 @@ describe('PublishAsKnowledgePipelineModal', () => { it('should update pipeline name when input changes', () => { render() - const nameInput = screen.getByTestId('name-input') + const nameInput = getNameInput() fireEvent.change(nameInput, { target: { value: 'New Name' } }) expect((nameInput as HTMLInputElement).value).toBe('New Name') @@ -125,14 +123,20 @@ describe('PublishAsKnowledgePipelineModal', () => { expect((textarea as HTMLTextAreaElement).value).toBe('My description') }) - it('should disable confirm button when name is empty', () => { + it('should not submit with Enter when name is empty', async () => { + const user = userEvent.setup() render() - const nameInput = screen.getByTestId('name-input') - fireEvent.change(nameInput, { target: { value: '' } }) + const nameInput = getNameInput() + await user.clear(nameInput) const confirmBtn = screen.getByText('workflow.common.publish') expect(confirmBtn).toBeDisabled() + + await user.click(nameInput) + await user.keyboard('{Enter}') + + expect(mockOnConfirm).not.toHaveBeenCalled() }) it('should disable confirm button when confirmDisabled is true', () => { @@ -155,7 +159,7 @@ describe('PublishAsKnowledgePipelineModal', () => { expect(screen.queryByPlaceholderText('Search emojis...')).not.toBeInTheDocument() - fireEvent.click(screen.getByTestId('app-icon')) + fireEvent.click(getIconButton()) expect(screen.getByPlaceholderText('Search emojis...')).toBeInTheDocument() }) @@ -163,7 +167,7 @@ describe('PublishAsKnowledgePipelineModal', () => { it('should update icon when emoji style is selected', async () => { render() - fireEvent.click(screen.getByTestId('app-icon')) + fireEvent.click(getIconButton()) fireEvent.click(screen.getByRole('button', { name: '#E4FBCC' })) fireEvent.click(screen.getByRole('button', { name: /iconPicker\.ok/ })) @@ -175,7 +179,7 @@ describe('PublishAsKnowledgePipelineModal', () => { it('should keep icon picker open until confirmation', () => { render() - fireEvent.click(screen.getByTestId('app-icon')) + fireEvent.click(getIconButton()) fireEvent.click(screen.getByRole('button', { name: '#E4FBCC' })) expect(screen.getByPlaceholderText('Search emojis...')).toBeInTheDocument() @@ -184,7 +188,7 @@ describe('PublishAsKnowledgePipelineModal', () => { it('should close icon picker when cancel is clicked', async () => { render() - fireEvent.click(screen.getByTestId('app-icon')) + fireEvent.click(getIconButton()) fireEvent.click(screen.getByRole('button', { name: /iconPicker\.cancel/ })) await waitFor(() => { @@ -192,18 +196,21 @@ describe('PublishAsKnowledgePipelineModal', () => { }) }) - it('should trim name and description before submitting', () => { + it('should trim name and description when submitted with Enter', async () => { + const user = userEvent.setup() render() - const nameInput = screen.getByTestId('name-input') - fireEvent.change(nameInput, { target: { value: ' Trimmed Name ' } }) + const nameInput = getNameInput() + await user.clear(nameInput) + await user.type(nameInput, ' Trimmed Name ') const textarea = screen.getByRole('textbox', { name: 'pipeline.common.publishAsPipeline.description', }) - fireEvent.change(textarea, { target: { value: ' Some desc ' } }) + await user.type(textarea, ' Some desc ') - fireEvent.click(screen.getByText('workflow.common.publish')) + await user.click(nameInput) + await user.keyboard('{Enter}') expect(mockOnConfirm).toHaveBeenCalledWith('Trimmed Name', expect.any(Object), 'Some desc') }) diff --git a/web/app/components/rag-pipeline/components/publish-as-knowledge-pipeline-modal.tsx b/web/app/components/rag-pipeline/components/publish-as-knowledge-pipeline-modal.tsx index b93b00a8090..4f3f051d6dd 100644 --- a/web/app/components/rag-pipeline/components/publish-as-knowledge-pipeline-modal.tsx +++ b/web/app/components/rag-pipeline/components/publish-as-knowledge-pipeline-modal.tsx @@ -2,14 +2,15 @@ import type { AppIconSelection } from '@/app/components/base/app-icon-picker' import type { IconInfo } from '@/models/datasets' import { Button } from '@langgenius/dify-ui/button' -import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog' +import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog' +import { Field, FieldLabel } from '@langgenius/dify-ui/field' +import { IconButton } from '@langgenius/dify-ui/icon-button' +import { Input } from '@langgenius/dify-ui/input' import { Textarea } from '@langgenius/dify-ui/textarea' -import { RiCloseLine } from '@remixicon/react' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import AppIconPicker from '@/app/components/base/app-icon-picker' -import Input from '@/app/components/base/input' import { useWorkflowStore } from '@/app/components/workflow/store' type PublishAsKnowledgePipelineModalProps = { @@ -50,33 +51,47 @@ const PublishAsKnowledgePipelineModal = ({ }, []) const handleConfirm = () => { - if (confirmDisabled) return + const name = pipelineName?.trim() + if (!name || confirmDisabled) return - onConfirm(pipelineName?.trim() || '', pipelineIcon, description?.trim()) + onConfirm(name, pipelineIcon, description?.trim()) } return ( - <> - - -
- {t(($) => $['common.publishAs'], { ns: 'pipeline' })} - +
-
-
+ + {t(($) => $['common.publishAsPipeline.name'], { ns: 'pipeline' })} -
+ setPipelineName(e.target.value)} placeholder={ @@ -84,26 +99,31 @@ const PublishAsKnowledgePipelineModal = ({ '' } /> -
- +
-
-
+ + {t(($) => $['common.publishAsPipeline.description'], { ns: 'pipeline' })} -
+