refactor(web): migrate empty dataset input (#41030)

This commit is contained in:
yyh
2026-08-20 13:38:53 +00:00
committed by GitHub
parent aba3f9c1d6
commit f4e50f3c8a
4 changed files with 70 additions and 34 deletions
@@ -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(<EmptyDatasetCreationModal show={true} onHide={mockOnHide} />)
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<ReturnType<typeof createEmptyDataset>>) => void)
| undefined
mockCreateEmptyDataset.mockReturnValueOnce(
new Promise((resolve) => {
resolveRequest = resolve
}),
)
const onHide = vi.fn()
render(<EmptyDatasetCreationModal show={true} onHide={onHide} />)
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<ReturnType<typeof createEmptyDataset>>)
await waitFor(() => {
expect(onHide).toHaveBeenCalledTimes(1)
})
})
it('should call invalidDatasetList after successful creation', async () => {
const mockOnHide = vi.fn()
render(<EmptyDatasetCreationModal show={true} onHide={mockOnHide} />)
@@ -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;
@@ -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) => {
>
<DialogContent className="w-full max-w-130! overflow-hidden! border-none px-8 text-left align-middle">
<div className={s.modalHeader}>
<div className={s.title}>
<DialogTitle className={s.title}>
{t(($) => $['stepOne.modal.title'], { ns: 'datasetCreation' })}
</div>
</DialogTitle>
<button
type="button"
className={cn(
@@ -67,24 +75,28 @@ const EmptyDatasetCreationModal = ({ show = false, onHide }: IProps) => {
/>
</div>
<div className={s.tip}>{t(($) => $['stepOne.modal.tip'], { ns: 'datasetCreation' })}</div>
<div className={s.form}>
<div className={s.label}>
{t(($) => $['stepOne.modal.input'], { ns: 'datasetCreation' })}
<Form onFormSubmit={() => void submit()}>
<Field name="datasetName" className={cn(s.form, 'gap-2')}>
<FieldLabel className={s.label}>
{t(($) => $['stepOne.modal.input'], { ns: 'datasetCreation' })}
</FieldLabel>
<Input
value={inputValue}
placeholder={
t(($) => $['stepOne.modal.placeholder'], { ns: 'datasetCreation' }) || ''
}
onValueChange={setInputValue}
/>
</Field>
<div className="flex flex-row-reverse">
<Button type="submit" className="ml-2 w-24" variant="primary" loading={isSubmitting}>
{t(($) => $['stepOne.modal.confirmButton'], { ns: 'datasetCreation' })}
</Button>
<Button type="button" className="w-24" onClick={onHide}>
{t(($) => $['stepOne.modal.cancelButton'], { ns: 'datasetCreation' })}
</Button>
</div>
<Input
value={inputValue}
placeholder={t(($) => $['stepOne.modal.placeholder'], { ns: 'datasetCreation' }) || ''}
onChange={(e) => setInputValue(e.target.value)}
/>
</div>
<div className="flex flex-row-reverse">
<Button className="ml-2 w-24" variant="primary" onClick={submit}>
{t(($) => $['stepOne.modal.confirmButton'], { ns: 'datasetCreation' })}
</Button>
<Button className="w-24" onClick={onHide}>
{t(($) => $['stepOne.modal.cancelButton'], { ns: 'datasetCreation' })}
</Button>
</div>
</Form>
</DialogContent>
</Dialog>
)