diff --git a/oxlint-suppressions.json b/oxlint-suppressions.json index 8443bef451e..75d2a7371ae 100644 --- a/oxlint-suppressions.json +++ b/oxlint-suppressions.json @@ -1922,11 +1922,6 @@ "count": 3 } }, - "web/app/components/datasets/create/empty-dataset-creation-modal/index.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/datasets/create/file-preview/index.tsx": { "eslint-react/set-state-in-effect": { "count": 1 diff --git a/web/app/components/datasets/create/empty-dataset-creation-modal/__tests__/index.spec.tsx b/web/app/components/datasets/create/empty-dataset-creation-modal/__tests__/index.spec.tsx index 912ee8a52d1..884fc5b9446 100644 --- a/web/app/components/datasets/create/empty-dataset-creation-modal/__tests__/index.spec.tsx +++ b/web/app/components/datasets/create/empty-dataset-creation-modal/__tests__/index.spec.tsx @@ -1,5 +1,6 @@ import type { MockedFunction } from 'vite-plus/test' import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createEmptyDataset } from '@/service/datasets' import { useInvalidDatasetList } from '@/service/knowledge/use-dataset' import EmptyDatasetCreationModal from '../index' @@ -73,9 +74,8 @@ describe('EmptyDatasetCreationModal', () => { expect(screen.getByText('datasetCreation.stepOne.modal.title')).toBeInTheDocument() expect(screen.getByText('datasetCreation.stepOne.modal.tip')).toBeInTheDocument() - expect(screen.getByText('datasetCreation.stepOne.modal.input')).toBeInTheDocument() expect( - screen.getByPlaceholderText('datasetCreation.stepOne.modal.placeholder'), + screen.getByRole('textbox', { name: 'datasetCreation.stepOne.modal.input' }), ).toBeInTheDocument() expect(screen.getByText('datasetCreation.stepOne.modal.confirmButton')).toBeInTheDocument() expect(screen.getByText('datasetCreation.stepOne.modal.cancelButton')).toBeInTheDocument() @@ -289,20 +289,49 @@ describe('EmptyDatasetCreationModal', () => { // API Calls - Test API interactions describe('API Calls', () => { - it('should call createEmptyDataset with correct parameters', async () => { + it('should submit from the dataset name input with Enter', async () => { const mockOnHide = vi.fn() render() - const input = screen.getByPlaceholderText('datasetCreation.stepOne.modal.placeholder') - const confirmButton = screen.getByText('datasetCreation.stepOne.modal.confirmButton') + const user = userEvent.setup() + const input = screen.getByRole('textbox', { name: 'datasetCreation.stepOne.modal.input' }) - fireEvent.change(input, { target: { value: 'New Dataset' } }) - fireEvent.click(confirmButton) + await user.type(input, 'New Dataset{Enter}') await waitFor(() => { expect(mockCreateEmptyDataset).toHaveBeenCalledWith({ name: 'New Dataset' }) }) }) + it('should not submit again while dataset creation is pending', async () => { + let resolveRequest: + | ((value: Awaited>) => void) + | undefined + mockCreateEmptyDataset.mockReturnValueOnce( + new Promise((resolve) => { + resolveRequest = resolve + }), + ) + const onHide = vi.fn() + render() + const user = userEvent.setup() + const input = screen.getByRole('textbox', { name: 'datasetCreation.stepOne.modal.input' }) + + await user.type(input, 'New Dataset{Enter}') + await waitFor(() => { + expect(mockCreateEmptyDataset).toHaveBeenCalledTimes(1) + }) + await user.keyboard('{Enter}') + expect(mockCreateEmptyDataset).toHaveBeenCalledTimes(1) + + resolveRequest?.({ + id: 'dataset-123', + name: 'New Dataset', + } as Awaited>) + await waitFor(() => { + expect(onHide).toHaveBeenCalledTimes(1) + }) + }) + it('should call invalidDatasetList after successful creation', async () => { const mockOnHide = vi.fn() render() diff --git a/web/app/components/datasets/create/empty-dataset-creation-modal/index.module.css b/web/app/components/datasets/create/empty-dataset-creation-modal/index.module.css index c5370ce6507..d284faa2206 100644 --- a/web/app/components/datasets/create/empty-dataset-creation-modal/index.module.css +++ b/web/app/components/datasets/create/empty-dataset-creation-modal/index.module.css @@ -26,7 +26,7 @@ @apply mb-8; } .form .label { - @apply mb-2 text-text-primary; + @apply py-0 text-text-primary; font-weight: 500; font-size: 14px; line-height: 20px; diff --git a/web/app/components/datasets/create/empty-dataset-creation-modal/index.tsx b/web/app/components/datasets/create/empty-dataset-creation-modal/index.tsx index b55c9f3e0c2..c725e3a7d17 100644 --- a/web/app/components/datasets/create/empty-dataset-creation-modal/index.tsx +++ b/web/app/components/datasets/create/empty-dataset-creation-modal/index.tsx @@ -1,13 +1,15 @@ 'use client' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' -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 { Form } from '@langgenius/dify-ui/form' +import { Input } from '@langgenius/dify-ui/input' import { toast } from '@langgenius/dify-ui/toast' import * as React from 'react' import { useState } from 'react' import { useTranslation } from 'react-i18next' import { trackEvent } from '@/app/components/base/amplitude' -import Input from '@/app/components/base/input' import { useRouter } from '@/next/navigation' import { createEmptyDataset } from '@/service/datasets' import { useInvalidDatasetList } from '@/service/knowledge/use-dataset' @@ -19,10 +21,13 @@ type IProps = { } const EmptyDatasetCreationModal = ({ show = false, onHide }: IProps) => { const [inputValue, setInputValue] = useState('') + const [isSubmitting, setIsSubmitting] = useState(false) const { t } = useTranslation() const router = useRouter() const invalidDatasetList = useInvalidDatasetList() const submit = async () => { + if (isSubmitting) return + if (!inputValue) { toast.error(t(($) => $['stepOne.modal.nameNotEmpty'], { ns: 'datasetCreation' })) return @@ -31,6 +36,7 @@ const EmptyDatasetCreationModal = ({ show = false, onHide }: IProps) => { toast.error(t(($) => $['stepOne.modal.nameLengthInvalid'], { ns: 'datasetCreation' })) return } + setIsSubmitting(true) try { const dataset = await createEmptyDataset({ name: inputValue }) invalidDatasetList() @@ -42,6 +48,8 @@ const EmptyDatasetCreationModal = ({ show = false, onHide }: IProps) => { router.push(`/datasets/${dataset.id}/documents`) } catch { toast.error(t(($) => $['stepOne.modal.failed'], { ns: 'datasetCreation' })) + } finally { + setIsSubmitting(false) } } return ( @@ -53,9 +61,9 @@ const EmptyDatasetCreationModal = ({ show = false, onHide }: IProps) => { > - + {t(($) => $['stepOne.modal.title'], { ns: 'datasetCreation' })} - + { /> {t(($) => $['stepOne.modal.tip'], { ns: 'datasetCreation' })} - - - {t(($) => $['stepOne.modal.input'], { ns: 'datasetCreation' })} + void submit()}> + + + {t(($) => $['stepOne.modal.input'], { ns: 'datasetCreation' })} + + $['stepOne.modal.placeholder'], { ns: 'datasetCreation' }) || '' + } + onValueChange={setInputValue} + /> + + + + {t(($) => $['stepOne.modal.confirmButton'], { ns: 'datasetCreation' })} + + + {t(($) => $['stepOne.modal.cancelButton'], { ns: 'datasetCreation' })} + - $['stepOne.modal.placeholder'], { ns: 'datasetCreation' }) || ''} - onChange={(e) => setInputValue(e.target.value)} - /> - - - - {t(($) => $['stepOne.modal.confirmButton'], { ns: 'datasetCreation' })} - - - {t(($) => $['stepOne.modal.cancelButton'], { ns: 'datasetCreation' })} - - + )