refactor(web): migrate external API input fields (#41052)

This commit is contained in:
yyh
2026-08-21 06:38:21 +00:00
committed by GitHub
parent 7b1339a156
commit 44e89d8844
5 changed files with 36 additions and 16 deletions
-5
View File
@@ -2293,11 +2293,6 @@
"count": 3
}
},
"web/app/components/datasets/external-api/external-api-modal/Form.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/datasets/external-knowledge-base/create/ExternalApiSelect.tsx": {
"eslint-react/set-state-in-effect": {
"count": 1
@@ -1,13 +1,15 @@
import type { FC } from 'react'
import type { FC, FormEventHandler } from 'react'
import type { CreateExternalAPIReq, FormSchema } from '../declarations'
import { cn } from '@langgenius/dify-ui/cn'
import { Input } from '@langgenius/dify-ui/input'
import { RiBookOpenLine } from '@remixicon/react'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import Input from '@/app/components/base/input'
import { useDocLink } from '@/context/i18n'
type FormProps = {
id: string
onSubmit: FormEventHandler<HTMLFormElement>
className?: string
itemClassName?: string
fieldLabelClassName?: string
@@ -19,6 +21,8 @@ type FormProps = {
const Form: FC<FormProps> = React.memo(
({
id,
onSubmit,
className,
itemClassName,
fieldLabelClassName,
@@ -78,6 +82,10 @@ const Form: FC<FormProps> = React.memo(
</div>
<Input
type={type === 'secret' ? 'password' : 'text'}
autoComplete="off"
inputMode={variable === 'endpoint' ? 'url' : undefined}
placeholder={t(($) => $['placeholder.input'], { ns: 'common' }) || ''}
spellCheck={variable === 'endpoint' || type === 'secret' ? false : undefined}
id={variable}
name={variable}
value={fieldValue}
@@ -91,6 +99,8 @@ const Form: FC<FormProps> = React.memo(
return (
<form
id={id}
onSubmit={onSubmit}
className={cn('flex flex-col items-start justify-center gap-4 self-stretch', className)}
>
{formSchemas.map((formSchema) => renderField(formSchema))}
@@ -40,6 +40,8 @@ describe('Form', () => {
}
const defaultProps = {
id: 'external-api-form',
onSubmit: vi.fn(),
value: defaultValue,
onChange: vi.fn(),
formSchemas: defaultFormSchemas,
@@ -1,5 +1,6 @@
import type { CreateExternalAPIReq } from '../../declarations'
import { fireEvent, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
// Import mocked service
import { createExternalAPI } from '@/service/datasets'
@@ -168,6 +169,7 @@ describe('AddExternalAPIModal', () => {
vi.mocked(createExternalAPI).mockResolvedValue(mockResponse)
const onSave = vi.fn()
const onCancel = vi.fn()
const user = userEvent.setup()
render(<AddExternalAPIModal {...defaultProps} onSave={onSave} onCancel={onCancel} />)
@@ -179,8 +181,7 @@ describe('AddExternalAPIModal', () => {
fireEvent.change(endpointInput, { target: { value: 'https://test.com' } })
fireEvent.change(apiKeyInput, { target: { value: 'key12345' } })
const saveButton = screen.getByText('dataset.externalAPIForm.save').closest('button')!
fireEvent.click(saveButton)
await user.click(screen.getByRole('button', { name: 'dataset.externalAPIForm.save' }))
await waitFor(() => {
expect(createExternalAPI).toHaveBeenCalledWith({
@@ -15,7 +15,7 @@ import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { toast } from '@langgenius/dify-ui/toast'
import { RiBook2Line, RiCloseLine, RiInformation2Line, RiLock2Fill } from '@remixicon/react'
import { memo, useState } from 'react'
import { memo, useId, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { createExternalAPI } from '@/service/datasets'
import Form from './Form'
@@ -75,6 +75,7 @@ const AddExternalAPIModal: FC<AddExternalAPIModalProps> = ({
onEdit,
}) => {
const { t } = useTranslation()
const formId = useId()
const [loading, setLoading] = useState(false)
const [showConfirm, setShowConfirm] = useState(false)
const [formData, setFormData] = useState<CreateExternalAPIReq>(() =>
@@ -121,6 +122,15 @@ const AddExternalAPIModal: FC<AddExternalAPIModalProps> = ({
setLoading(false)
}
}
const handleSubmit = () => {
if (hasEmptyInputs || loading) return
if (isEditMode && (datasetBindings?.length ?? 0) > 0) setShowConfirm(true)
else if (isEditMode && onEdit) onEdit(formData)
else handleSave()
}
return (
<Dialog
open
@@ -192,8 +202,13 @@ const AddExternalAPIModal: FC<AddExternalAPIModalProps> = ({
<RiCloseLine aria-hidden className="h-4.5 w-4.5 shrink-0 text-text-tertiary" />
</IconButton>
<Form
id={formId}
value={formData}
onChange={handleDataChange}
onSubmit={(event) => {
event.preventDefault()
handleSubmit()
}}
formSchemas={formSchemas}
className="min-h-0 w-full flex-1 overflow-y-auto px-6 py-3"
/>
@@ -202,14 +217,11 @@ const AddExternalAPIModal: FC<AddExternalAPIModalProps> = ({
{t(($) => $['externalAPIForm.cancel'], { ns: 'dataset' })}
</Button>
<Button
form={formId}
type="submit"
variant="primary"
onClick={() => {
if (isEditMode && (datasetBindings?.length ?? 0) > 0) setShowConfirm(true)
else if (isEditMode && onEdit) onEdit(formData)
else handleSave()
}}
disabled={hasEmptyInputs || loading}
loading={loading}
disabled={hasEmptyInputs}
>
{t(($) => $['externalAPIForm.save'], { ns: 'dataset' })}
</Button>