fix(web): form in email test sender

This commit is contained in:
JzoNg 2026-05-08 11:40:56 +08:00
parent d65cc21e85
commit a9de4bd96b
4 changed files with 83 additions and 15 deletions

View File

@ -286,6 +286,9 @@ describe('human-input/panel', () => {
availableVars: [{
variable: ['start', 'email'],
type: VarType.string,
}, {
variable: ['code', 'result'],
type: VarType.arrayString,
}, {
variable: ['start', 'files'],
type: VarType.file,
@ -314,6 +317,12 @@ describe('human-input/panel', () => {
expect(screen.getByText('review_result:string:Form input value')).toBeInTheDocument()
expect(screen.getByText('__action_id:string:Action ID user triggered')).toBeInTheDocument()
expect(screen.getByText('__rendered_content:string:Rendered content')).toBeInTheDocument()
expect(mockDeliveryMethod).toHaveBeenCalledWith(expect.objectContaining({
nodesOutputVars: [
expect.objectContaining({ type: VarType.string }),
expect.objectContaining({ type: VarType.arrayString }),
],
}))
await user.click(screen.getByRole('button', { name: 'delivery-method:editable' }))
await user.click(screen.getByRole('button', { name: /workflow\.nodes\.humanInput\.formContent\.preview/ }))

View File

@ -2,7 +2,7 @@ import type { ReactNode } from 'react'
import type { EmailConfig, FormInputItem } from '../../../types'
import type { App, AppSSO } from '@/types/app'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { render, screen, waitFor } from '@testing-library/react'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { useStore as useAppStore } from '@/app/components/app/store'
import { HooksStoreContext } from '@/app/components/workflow/hooks-store/provider'
@ -161,7 +161,7 @@ const createDynamicSelectInput = (): FormInputItem => ({
output_variable_name: 'decision',
option_source: {
type: 'variable',
selector: ['start', 'choices'],
selector: ['code', 'result'],
value: [],
},
})
@ -243,7 +243,7 @@ describe('human-input/delivery-method/test-email-sender', () => {
delivery_method_id: 'delivery-1',
inputs: {
'#start.user_name#': 'Ada',
'#start.score#': '42',
'#start.score#': 42,
},
},
}))
@ -270,23 +270,32 @@ describe('human-input/delivery-method/test-email-sender', () => {
formInputs={[createDynamicSelectInput()]}
availableNodes={[
{
id: 'start',
id: 'code',
type: 'custom',
position: { x: 0, y: 0 },
data: {
title: 'Start',
title: 'Code',
desc: '',
type: BlockEnum.Start,
type: BlockEnum.Code,
variables: [],
code_language: 'python3',
code: '',
outputs: {
result: {
type: VarType.arrayString,
children: null,
},
},
},
},
]}
nodesOutputVars={[
{
nodeId: 'start',
title: 'Start',
nodeId: 'code',
title: 'Code',
vars: [
{
variable: 'choices',
variable: 'result',
type: VarType.arrayString,
},
],
@ -298,7 +307,11 @@ describe('human-input/delivery-method/test-email-sender', () => {
const sendButton = screen.getByRole('button', { name: 'workflow.nodes.humanInput.deliveryMethod.emailSender.send' })
expect(sendButton).toBeDisabled()
await user.type(screen.getByPlaceholderText('choices'), 'approve,reject')
expect(screen.queryByPlaceholderText('result')).not.toBeInTheDocument()
fireEvent.change(screen.getByTestId('monaco-editor'), {
target: { value: '["approve","reject"]' },
})
expect(sendButton).toBeEnabled()
await user.click(sendButton)
@ -309,7 +322,7 @@ describe('human-input/delivery-method/test-email-sender', () => {
body: {
delivery_method_id: 'delivery-1',
inputs: {
'#start.choices#': 'approve,reject',
'#code.result#': ['approve', 'reject'],
},
},
})))

View File

@ -7,6 +7,7 @@ import type {
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
import { toast } from '@langgenius/dify-ui/toast'
import { RiArrowRightSFill } from '@remixicon/react'
import { noop, unionBy } from 'es-toolkit/compat'
import { memo, useCallback, useMemo, useState } from 'react'
@ -15,6 +16,7 @@ import { useStore as useAppStore } from '@/app/components/app/store'
import Divider from '@/app/components/base/divider'
import { getInputVars as doGetInputVars } from '@/app/components/base/prompt-editor/constants'
import FormItem from '@/app/components/workflow/nodes/_base/components/before-run-form/form-item'
import { formatValue } from '@/app/components/workflow/nodes/_base/components/before-run-form/helpers'
import {
getNodeInfoById,
isConversationVar,
@ -69,6 +71,45 @@ const getOriginVar = (valueSelector: string[], list: NodeOutPutVar[]) => {
return undefined
}
const varTypeToInputVarType = (type: VarType) => {
if (type === VarType.number)
return InputVarType.number
if (type === VarType.boolean)
return InputVarType.checkbox
if ([VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject].includes(type))
return InputVarType.json
if (type === VarType.file)
return InputVarType.singleFile
if (type === VarType.arrayFile)
return InputVarType.multiFiles
return InputVarType.textInput
}
const formatEmailSenderInputs = (
variables: Array<{ variable: string, type: InputVarType, label: unknown }>,
values: Record<string, unknown>,
) => {
const formattedValues: Record<string, unknown> = {}
let parseErrorJsonField = ''
variables.forEach((variable) => {
try {
formattedValues[variable.variable] = formatValue(values[variable.variable], variable.type)
}
catch {
parseErrorJsonField = typeof variable.label === 'object' && variable.label !== null && 'variable' in variable.label
? String(variable.label.variable)
: variable.variable
}
})
return {
formattedValues,
parseErrorJsonField,
}
}
const EmailSenderModal = ({
nodeId,
deliveryId,
@ -126,7 +167,7 @@ const EmailSenderModal = ({
return {
label: item.label || item.variable,
variable: item.variable,
type: originalVar.type === VarType.number ? InputVarType.number : InputVarType.textInput,
type: varTypeToInputVarType(originalVar.type),
required: true,
}
})
@ -160,20 +201,25 @@ const EmailSenderModal = ({
const handleConfirm = useCallback(async () => {
if (!confirmChecked)
return
const { formattedValues, parseErrorJsonField } = formatEmailSenderInputs(generatedInputs, inputs)
if (parseErrorJsonField) {
toast.error(t('errorMsg.invalidJson', { ns: 'workflow', field: parseErrorJsonField }))
return
}
setSendingEmail(true)
try {
await testEmailSender({
appID: appDetail?.id || '',
nodeID: nodeId,
deliveryID: deliveryId,
inputs,
inputs: formattedValues,
})
setDone(true)
}
finally {
setSendingEmail(false)
}
}, [confirmChecked, testEmailSender, appDetail?.id, nodeId, deliveryId, inputs])
}, [confirmChecked, generatedInputs, inputs, testEmailSender, appDetail?.id, nodeId, deliveryId, t])
if (done) {
return (

View File

@ -69,7 +69,7 @@ const Panel: FC<NodePanelProps<HumanInputNodeType>> = ({
const { availableVars, availableNodesWithParent } = useAvailableVarList(id, {
onlyLeafNodeVar: false,
filterVar: (varPayload: Var) => {
return [VarType.string, VarType.number, VarType.secret].includes(varPayload.type)
return [VarType.string, VarType.number, VarType.secret, VarType.arrayString].includes(varPayload.type)
},
})