@@ -388,7 +376,7 @@ describe('StepOne', () => {
fireEvent.click(screen.getByRole('button', { name: /datasetCreation.stepOne.button/i }))
- expect(screen.getByTestId('plan-upgrade-modal')).toBeInTheDocument()
+ expect(screen.getByRole('dialog')).toBeInTheDocument()
})
it('should show upgrade card when in sandbox plan with files', () => {
diff --git a/web/app/components/datasets/create/step-one/components/__tests__/preview-panel.spec.tsx b/web/app/components/datasets/create/step-one/components/__tests__/preview-panel.spec.tsx
index f495dd9f3f..a807412008 100644
--- a/web/app/components/datasets/create/step-one/components/__tests__/preview-panel.spec.tsx
+++ b/web/app/components/datasets/create/step-one/components/__tests__/preview-panel.spec.tsx
@@ -31,17 +31,6 @@ vi.mock('../../../website/preview', () => ({
),
}))
-vi.mock('@/app/components/billing/plan-upgrade-modal', () => ({
- default: ({ show, onClose, title }: { show: boolean, onClose: () => void, title: string }) => show
- ? (
-
- {title}
-
-
- )
- : null,
-}))
-
const { default: PreviewPanel } = await import('../preview-panel')
describe('PreviewPanel', () => {
@@ -87,7 +76,7 @@ describe('PreviewPanel', () => {
it('should render plan upgrade modal when isShowPlanUpgradeModal is true', () => {
render(
)
- expect(screen.getByTestId('plan-upgrade-modal')).toBeInTheDocument()
+ expect(screen.getByRole('dialog')).toBeInTheDocument()
})
})
@@ -100,7 +89,7 @@ describe('PreviewPanel', () => {
it('should call hidePlanUpgradeModal when modal close clicked', () => {
render(
)
- fireEvent.click(screen.getByTestId('close-modal'))
+ fireEvent.click(screen.getByRole('button', { name: 'billing.triggerLimitModal.dismiss' }))
expect(defaultProps.hidePlanUpgradeModal).toHaveBeenCalledOnce()
})
diff --git a/web/app/components/datasets/create/step-one/components/preview-panel.tsx b/web/app/components/datasets/create/step-one/components/preview-panel.tsx
index 8ae0b7df55..51b6568154 100644
--- a/web/app/components/datasets/create/step-one/components/preview-panel.tsx
+++ b/web/app/components/datasets/create/step-one/components/preview-panel.tsx
@@ -3,7 +3,7 @@
import type { NotionPage } from '@/models/common'
import type { CrawlResultItem } from '@/models/datasets'
import { useTranslation } from 'react-i18next'
-import PlanUpgradeModal from '@/app/components/billing/plan-upgrade-modal'
+import { PlanUpgradeModal } from '@/app/components/billing/plan-upgrade-modal'
import FilePreview from '../../file-preview'
import NotionPagePreview from '../../notion-page-preview'
import WebsitePreview from '../../website/preview'
diff --git a/web/app/components/datasets/documents/create-from-pipeline/__tests__/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/__tests__/index.spec.tsx
index 7daff43a8b..18241f2139 100644
--- a/web/app/components/datasets/documents/create-from-pipeline/__tests__/index.spec.tsx
+++ b/web/app/components/datasets/documents/create-from-pipeline/__tests__/index.spec.tsx
@@ -112,18 +112,6 @@ vi.mock('@/app/components/billing/vector-space-full', () => ({
default: () =>
Vector Space Full
,
}))
-vi.mock('@/app/components/billing/plan-upgrade-modal', () => ({
- default: ({ show, onClose }: { show: boolean, onClose: () => void }) => (
- show
- ? (
-
-
-
- )
- : null
- ),
-}))
-
vi.mock('@/app/components/datasets/create/step-one/upgrade-card', () => ({
default: () =>
Upgrade Card
,
}))
diff --git a/web/app/components/datasets/documents/create-from-pipeline/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/index.tsx
index 8b8fad5885..799f24fa2a 100644
--- a/web/app/components/datasets/documents/create-from-pipeline/index.tsx
+++ b/web/app/components/datasets/documents/create-from-pipeline/index.tsx
@@ -8,7 +8,7 @@ import { useBoolean } from 'ahooks'
import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Loading from '@/app/components/base/loading'
-import PlanUpgradeModal from '@/app/components/billing/plan-upgrade-modal'
+import { PlanUpgradeModal } from '@/app/components/billing/plan-upgrade-modal'
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
import { useProviderContextSelector } from '@/context/provider-context'
import { DatasourceType } from '@/models/pipeline'
diff --git a/web/app/components/datasets/documents/detail/segment-add/__tests__/index.spec.tsx b/web/app/components/datasets/documents/detail/segment-add/__tests__/index.spec.tsx
index 35b62915da..b9d967a692 100644
--- a/web/app/components/datasets/documents/detail/segment-add/__tests__/index.spec.tsx
+++ b/web/app/components/datasets/documents/detail/segment-add/__tests__/index.spec.tsx
@@ -14,21 +14,6 @@ vi.mock('@/context/provider-context', () => ({
}),
}))
-// Mock PlanUpgradeModal
-vi.mock('@/app/components/billing/plan-upgrade-modal', () => ({
- default: ({ show, onClose, title, description }: { show: boolean, onClose: () => void, title?: string, description?: string }) => (
- show
- ? (
-
- {title}
- {description}
-
-
- )
- : null
- ),
-}))
-
describe('SegmentAdd', () => {
beforeEach(() => {
vi.clearAllMocks()
@@ -189,7 +174,7 @@ describe('SegmentAdd', () => {
fireEvent.click(screen.getByText(/list\.action\.addButton/i))
- expect(screen.getByTestId('plan-upgrade-modal')).toBeInTheDocument()
+ expect(screen.getByRole('dialog')).toBeInTheDocument()
})
it('should not call showNewSegmentModal for sandbox users', () => {
@@ -219,11 +204,11 @@ describe('SegmentAdd', () => {
// Show modal
fireEvent.click(screen.getByText(/list\.action\.addButton/i))
- expect(screen.getByTestId('plan-upgrade-modal')).toBeInTheDocument()
+ expect(screen.getByRole('dialog')).toBeInTheDocument()
- fireEvent.click(screen.getByTestId('close-modal'))
+ fireEvent.click(screen.getByRole('button', { name: 'billing.triggerLimitModal.dismiss' }))
- expect(screen.queryByTestId('plan-upgrade-modal')).not.toBeInTheDocument()
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
})
})
diff --git a/web/app/components/datasets/documents/detail/segment-add/index.tsx b/web/app/components/datasets/documents/detail/segment-add/index.tsx
index de9735f62f..5ee0a2bcb3 100644
--- a/web/app/components/datasets/documents/detail/segment-add/index.tsx
+++ b/web/app/components/datasets/documents/detail/segment-add/index.tsx
@@ -11,7 +11,7 @@ import { useBoolean } from 'ahooks'
import * as React from 'react'
import { useCallback, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
-import PlanUpgradeModal from '@/app/components/billing/plan-upgrade-modal'
+import { PlanUpgradeModal } from '@/app/components/billing/plan-upgrade-modal'
import { Plan } from '@/app/components/billing/type'
import { useProviderContext } from '@/context/provider-context'
diff --git a/web/app/components/workflow/nodes/human-input/__tests__/panel.spec.tsx b/web/app/components/workflow/nodes/human-input/__tests__/panel.spec.tsx
index 143b05afae..6ff61dce3f 100644
--- a/web/app/components/workflow/nodes/human-input/__tests__/panel.spec.tsx
+++ b/web/app/components/workflow/nodes/human-input/__tests__/panel.spec.tsx
@@ -30,11 +30,6 @@ vi.mock('@langgenius/dify-ui/toast', () => ({
},
}))
-vi.mock('@/app/components/base/tooltip', () => ({
- __esModule: true,
- default: () =>
tooltip
,
-}))
-
vi.mock('@/app/components/base/action-button', () => ({
__esModule: true,
default: (props: {
diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/email-configure-modal.spec.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/email-configure-modal.spec.tsx
index c5b5c680dc..d9875c7539 100644
--- a/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/email-configure-modal.spec.tsx
+++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/email-configure-modal.spec.tsx
@@ -92,9 +92,9 @@ describe('human-input/delivery-method/email-configure-modal', () => {
render(
,
)
@@ -127,8 +127,8 @@ describe('human-input/delivery-method/email-configure-modal', () => {
render(
,
)
@@ -162,12 +162,12 @@ describe('human-input/delivery-method/email-configure-modal', () => {
})
it('should close from both the icon trigger and the cancel button', () => {
- const handleClose = vi.fn()
+ const handleOpenChange = vi.fn()
render(
,
)
@@ -175,6 +175,7 @@ describe('human-input/delivery-method/email-configure-modal', () => {
fireEvent.click(screen.getByRole('dialog').querySelector('.absolute') as HTMLDivElement)
fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
- expect(handleClose).toHaveBeenCalledTimes(2)
+ expect(handleOpenChange).toHaveBeenCalledTimes(2)
+ expect(handleOpenChange).toHaveBeenCalledWith(false)
})
})
diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/index.spec.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/index.spec.tsx
index 03bc0f2b79..087440c62d 100644
--- a/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/index.spec.tsx
+++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/index.spec.tsx
@@ -1,4 +1,4 @@
-import { fireEvent, render, screen } from '@testing-library/react'
+import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { DeliveryMethodType } from '../../../types'
import DeliveryMethodForm from '../index'
@@ -9,11 +9,6 @@ vi.mock('react-i18next', () => ({
useTranslation: () => mockUseTranslation(),
}))
-vi.mock('@/app/components/base/tooltip', () => ({
- __esModule: true,
- default: ({ popupContent }: { popupContent: string }) =>
{popupContent}
,
-}))
-
vi.mock('@/app/components/workflow/hooks', () => ({
useNodesSyncDraft: () => mockUseNodesSyncDraft(),
}))
@@ -62,15 +57,6 @@ vi.mock('../method-item', () => ({
),
}))
-vi.mock('../upgrade-modal', () => ({
- __esModule: true,
- default: ({ onClose }: { onClose: () => void }) => (
-
- ),
-}))
-
describe('DeliveryMethodForm', () => {
const onChange = vi.fn()
const mockHandleSyncWorkflowDraft = vi.fn()
@@ -132,7 +118,7 @@ describe('DeliveryMethodForm', () => {
expect(mockHandleSyncWorkflowDraft).toHaveBeenCalledWith(true, true)
})
- it('should open and close the upgrade modal', () => {
+ it('should open and close the upgrade modal', async () => {
render(
{
)
fireEvent.click(screen.getByText('show-upgrade'))
- expect(screen.getByText('upgrade-modal')).toBeInTheDocument()
+ expect(screen.getByRole('dialog')).toBeInTheDocument()
- fireEvent.click(screen.getByText('upgrade-modal'))
- expect(screen.queryByText('upgrade-modal')).not.toBeInTheDocument()
+ fireEvent.click(screen.getByRole('button', { name: 'nodes.humanInput.deliveryMethod.upgradeTipHide' }))
+ await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument())
})
})
diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/method-item.spec.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/method-item.spec.tsx
index 5f11552c70..8ad330e3f2 100644
--- a/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/method-item.spec.tsx
+++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/method-item.spec.tsx
@@ -6,15 +6,15 @@ import { DeliveryMethodType } from '../../../types'
import DeliveryMethodItem from '../method-item'
type EmailConfigureModalProps = {
- isShow: boolean
+ open: boolean
config?: EmailConfig
- onClose: () => void
+ onOpenChange: (open: boolean) => void
onConfirm: (data: EmailConfig) => void
}
type TestEmailSenderProps = {
- isShow: boolean
- onClose: () => void
+ open: boolean
+ onOpenChange: (open: boolean) => void
jumpToEmailConfigModal: () => void
}
@@ -30,7 +30,7 @@ vi.mock('@/context/app-context', () => ({
vi.mock('../email-configure-modal', () => ({
default: (props: EmailConfigureModalProps) => {
mockEmailConfigureModal(props)
- return props.isShow
+ return props.open
? (
-
+
)
: null
@@ -54,11 +54,11 @@ vi.mock('../email-configure-modal', () => ({
vi.mock('../test-email-sender', () => ({
default: (props: TestEmailSenderProps) => {
mockTestEmailSender(props)
- return props.isShow
+ return props.open
? (
-
+
)
: null
@@ -140,14 +140,14 @@ describe('human-input/delivery-method/method-item', () => {
const row = getMethodRow('webapp')
const actionButtons = within(row).getAllByRole('button')
- const deleteButtonWrapper = actionButtons[0]!.parentElement as HTMLDivElement
+ const deleteButton = actionButtons[0]!
- fireEvent.mouseEnter(deleteButtonWrapper)
+ fireEvent.mouseEnter(deleteButton)
expect(row)!.toHaveClass('border-state-destructive-border')
- fireEvent.mouseLeave(deleteButtonWrapper)
+ fireEvent.mouseLeave(deleteButton)
expect(row).not.toHaveClass('border-state-destructive-border')
- fireEvent.click(actionButtons[0]!)
+ fireEvent.click(deleteButton)
expect(handleDelete).toHaveBeenCalledWith(DeliveryMethodType.WebApp)
})
diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/method-selector.spec.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/method-selector.spec.tsx
index e1008d7457..9cbcd1c189 100644
--- a/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/method-selector.spec.tsx
+++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/method-selector.spec.tsx
@@ -67,7 +67,7 @@ describe('human-input/delivery-method/method-selector', () => {
})
expect(handleShowUpgradeTip).not.toHaveBeenCalled()
expect(screen.getByText('workflow.nodes.humanInput.deliveryMethod.contactTip1')).toBeInTheDocument()
- expect(screen.getByRole('tooltip')).toHaveTextContent('nodes.humanInput.deliveryMethod.contactTip2')
+ expect(screen.getByText('nodes.humanInput.deliveryMethod.contactTip2')).toBeInTheDocument()
})
it('should disable webapp in trigger mode and show added states without creating duplicates', () => {
diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/test-email-sender.spec.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/test-email-sender.spec.tsx
new file mode 100644
index 0000000000..7c62b20b8a
--- /dev/null
+++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/test-email-sender.spec.tsx
@@ -0,0 +1,298 @@
+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 userEvent from '@testing-library/user-event'
+import { useStore as useAppStore } from '@/app/components/app/store'
+import { HooksStoreContext } from '@/app/components/workflow/hooks-store/provider'
+import { createHooksStore } from '@/app/components/workflow/hooks-store/store'
+import { BlockEnum, InputVarType, VarType } from '@/app/components/workflow/types'
+import { AppContext, initialLangGeniusVersionInfo, initialWorkspaceInfo, userProfilePlaceholder } from '@/context/app-context'
+import EmailSenderModal from '../test-email-sender'
+
+type RecordedRequest = {
+ url: string
+ method: string
+ body?: unknown
+}
+
+const createQueryClient = () => new QueryClient({
+ defaultOptions: {
+ queries: {
+ retry: false,
+ },
+ mutations: {
+ retry: false,
+ },
+ },
+})
+
+const renderWithProviders = (ui: ReactNode) => {
+ const queryClient = createQueryClient()
+ const hooksStore = createHooksStore({})
+
+ return render(
+
+ selector({
+ userProfile: {
+ ...userProfilePlaceholder,
+ id: 'user-1',
+ email: 'owner@example.com',
+ name: 'Owner',
+ },
+ currentWorkspace: {
+ ...initialWorkspaceInfo,
+ id: 'workspace-1',
+ name: 'Product Team',
+ },
+ isCurrentWorkspaceManager: true,
+ isCurrentWorkspaceOwner: true,
+ isCurrentWorkspaceEditor: true,
+ isCurrentWorkspaceDatasetOperator: true,
+ mutateUserProfile: vi.fn(),
+ mutateCurrentWorkspace: vi.fn(),
+ langGeniusVersionInfo: initialLangGeniusVersionInfo,
+ useSelector: vi.fn(),
+ isLoadingCurrentWorkspace: false,
+ isValidatingCurrentWorkspace: false,
+ }),
+ isLoadingCurrentWorkspace: false,
+ isValidatingCurrentWorkspace: false,
+ }}
+ >
+
+ {ui}
+
+
+ ,
+ )
+}
+
+const setupFetch = () => {
+ const requests: RecordedRequest[] = []
+ const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(async (resource: RequestInfo | URL, options?: RequestInit) => {
+ const request = resource instanceof Request ? resource : new Request(resource, options)
+ const body = request.method === 'GET' ? undefined : await request.clone().json()
+ requests.push({
+ url: request.url,
+ method: request.method,
+ body,
+ })
+
+ if (request.url.includes('/workspaces/current/members')) {
+ return new Response(JSON.stringify({
+ accounts: [
+ {
+ id: 'member-1',
+ email: 'member@example.com',
+ name: 'Member One',
+ avatar: '',
+ avatar_url: '',
+ status: 'active',
+ role: 'normal',
+ created_at: '',
+ last_active_at: '',
+ last_login_at: '',
+ },
+ ],
+ }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ })
+ }
+
+ return new Response(JSON.stringify({ result: 'success' }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ })
+ })
+
+ return {
+ fetchSpy,
+ requests,
+ }
+}
+
+const createConfig = (overrides: Partial = {}): EmailConfig => ({
+ recipients: {
+ whole_workspace: true,
+ items: [],
+ },
+ subject: 'Review request',
+ body: 'Please review {{#start.score#}}',
+ debug_mode: false,
+ ...overrides,
+})
+
+const createFormInput = (overrides: Partial = {}): FormInputItem => ({
+ type: InputVarType.textInput,
+ output_variable_name: 'user_name',
+ default: {
+ type: 'variable',
+ selector: ['start', 'user_name'],
+ value: '',
+ },
+ ...overrides,
+})
+
+describe('human-input/delivery-method/test-email-sender', () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ useAppStore.setState({
+ appDetail: {
+ id: 'app-1',
+ name: 'Workflow App',
+ } as App & Partial,
+ })
+ })
+
+ afterEach(() => {
+ vi.restoreAllMocks()
+ })
+
+ it('should submit generated variable inputs and show the success state', async () => {
+ const user = userEvent.setup()
+ const { requests } = setupFetch()
+ const handleOpenChange = vi.fn()
+
+ renderWithProviders(
+ ,
+ )
+
+ const sendButton = screen.getByRole('button', { name: 'workflow.nodes.humanInput.deliveryMethod.emailSender.send' })
+ expect(sendButton).toBeDisabled()
+
+ await user.type(screen.getByPlaceholderText('user_name'), 'Ada')
+ await user.type(screen.getByPlaceholderText('score'), '42')
+ expect(sendButton).toBeEnabled()
+
+ await user.click(sendButton)
+
+ await waitFor(() => expect(screen.getByText('workflow.nodes.humanInput.deliveryMethod.emailSender.done')).toBeInTheDocument())
+ expect(requests).toContainEqual(expect.objectContaining({
+ url: 'http://localhost:5001/console/api/apps/app-1/workflows/draft/human-input/nodes/human-node/delivery-test',
+ method: 'POST',
+ body: {
+ delivery_method_id: 'delivery-1',
+ inputs: {
+ '#start.user_name#': 'Ada',
+ '#start.score#': '42',
+ },
+ },
+ }))
+
+ await user.click(screen.getByRole('button', { name: 'common.operation.ok' }))
+
+ expect(handleOpenChange).toHaveBeenCalledWith(false)
+ })
+
+ it('should render fallback variable inputs and allow cancelling', async () => {
+ const user = userEvent.setup()
+ setupFetch()
+ const handleOpenChange = vi.fn()
+
+ renderWithProviders(
+ ,
+ )
+
+ expect(screen.getByPlaceholderText('message')).toBeInTheDocument()
+
+ await user.click(screen.getByText('workflow.nodes.humanInput.deliveryMethod.emailSender.vars'))
+
+ expect(screen.queryByPlaceholderText('message')).not.toBeInTheDocument()
+
+ await user.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
+
+ expect(handleOpenChange).toHaveBeenCalledWith(false)
+ })
+
+ it('should show selected recipients with the email configuration tip', () => {
+ setupFetch()
+
+ renderWithProviders(
+ ,
+ )
+
+ expect(screen.getByText('external@example.com')).toBeInTheDocument()
+ expect(screen.getByText('nodes.humanInput.deliveryMethod.emailSender.tip')).toBeInTheDocument()
+ })
+})
diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/upgrade-modal.spec.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/upgrade-modal.spec.tsx
index 537fa351e1..0abae16f9a 100644
--- a/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/upgrade-modal.spec.tsx
+++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/__tests__/upgrade-modal.spec.tsx
@@ -1,5 +1,5 @@
import { fireEvent, render, screen } from '@testing-library/react'
-import UpgradeModal from '../upgrade-modal'
+import { UpgradeModal } from '../upgrade-modal'
const mockUseModalContextSelector = vi.hoisted(() => vi.fn())
@@ -32,8 +32,8 @@ describe('human-input/delivery-method/upgrade-modal', () => {
render(
,
)
@@ -41,7 +41,7 @@ describe('human-input/delivery-method/upgrade-modal', () => {
expect(screen.getByText('workflow.nodes.humanInput.deliveryMethod.upgradeTipContent')).toBeInTheDocument()
fireEvent.click(screen.getByRole('button', { name: 'workflow.nodes.humanInput.deliveryMethod.upgradeTipHide' }))
- expect(handleClose).toHaveBeenCalledTimes(1)
+ expect(handleClose).toHaveBeenCalledWith(false)
fireEvent.click(screen.getByRole('button', { name: /billing.upgradeBtn.encourageShort/i }))
expect(handleShowPricingModal).toHaveBeenCalledTimes(1)
diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/email-configure-modal.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/email-configure-modal.tsx
index de38564d95..046320ab37 100644
--- a/web/app/components/workflow/nodes/human-input/components/delivery-method/email-configure-modal.tsx
+++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/email-configure-modal.tsx
@@ -4,15 +4,13 @@ import type {
NodeOutPutVar,
} from '@/app/components/workflow/types'
import { Button } from '@langgenius/dify-ui/button'
+import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
import { Switch } from '@langgenius/dify-ui/switch'
import { toast } from '@langgenius/dify-ui/toast'
-import { RiBugLine, RiCloseLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { RiBugLine } from '@remixicon/react'
import { memo, useCallback, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
-import Divider from '@/app/components/base/divider'
import Input from '@/app/components/base/input'
-import Modal from '@/app/components/base/modal'
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
import MailBodyInput from './mail-body-input'
import Recipient from './recipient'
@@ -20,8 +18,8 @@ import Recipient from './recipient'
const i18nPrefix = 'nodes.humanInput'
type EmailConfigureModalProps = {
- isShow: boolean
- onClose: () => void
+ open: boolean
+ onOpenChange: (open: boolean) => void
onConfirm: (data: EmailConfig) => void
config?: EmailConfig
nodesOutputVars?: NodeOutPutVar[]
@@ -29,8 +27,8 @@ type EmailConfigureModalProps = {
}
const EmailConfigureModal = ({
- isShow,
- onClose,
+ open,
+ onOpenChange,
onConfirm,
config,
nodesOutputVars = [],
@@ -78,89 +76,87 @@ const EmailConfigureModal = ({
}, [checkValidConfig, onConfirm, recipients, subject, body, debugMode])
return (
-
-
-
-
-
-
{t(`${i18nPrefix}.deliveryMethod.emailConfigure.title`, { ns: 'workflow' })}
-
{t(`${i18nPrefix}.deliveryMethod.emailConfigure.description`, { ns: 'workflow' })}
-
-
-
-
- {t(`${i18nPrefix}.deliveryMethod.emailConfigure.subject`, { ns: 'workflow' })}
-
-
setSubject(e.target.value)}
- placeholder={t(`${i18nPrefix}.deliveryMethod.emailConfigure.subjectPlaceholder`, { ns: 'workflow' })}
- />
+
+
+
+
{t(`${i18nPrefix}.deliveryMethod.emailConfigure.title`, { ns: 'workflow' })}
+
{t(`${i18nPrefix}.deliveryMethod.emailConfigure.description`, { ns: 'workflow' })}
-
-
- {t(`${i18nPrefix}.deliveryMethod.emailConfigure.body`, { ns: 'workflow' })}
-
-
-
-
-
- {t(`${i18nPrefix}.deliveryMethod.emailConfigure.recipient`, { ns: 'workflow' })}
-
-
-
-
-
-
-
-
-
-
{t(`${i18nPrefix}.deliveryMethod.emailConfigure.debugMode`, { ns: 'workflow' })}
-
-
{email} }}
- values={{ email }}
- />
- {t(`${i18nPrefix}.deliveryMethod.emailConfigure.debugModeTip2`, { ns: 'workflow' })}
+
+
+
+ {t(`${i18nPrefix}.deliveryMethod.emailConfigure.subject`, { ns: 'workflow' })}
+
setSubject(e.target.value)}
+ placeholder={t(`${i18nPrefix}.deliveryMethod.emailConfigure.subjectPlaceholder`, { ns: 'workflow' })}
+ />
+
+
+
+ {t(`${i18nPrefix}.deliveryMethod.emailConfigure.body`, { ns: 'workflow' })}
+
+
+
+
+
+ {t(`${i18nPrefix}.deliveryMethod.emailConfigure.recipient`, { ns: 'workflow' })}
+
+
+
+
+
+
+
+
+
{t(`${i18nPrefix}.deliveryMethod.emailConfigure.debugMode`, { ns: 'workflow' })}
+
+
{email} }}
+ values={{ email }}
+ />
+ {t(`${i18nPrefix}.deliveryMethod.emailConfigure.debugModeTip2`, { ns: 'workflow' })}
+
+
+
setDebugMode(checked)}
+ />
-
setDebugMode(checked)}
- />
-
-
-
-
-
-
+
+
+
+
+
+
)
}
diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/index.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/index.tsx
index cdfda74aeb..50c2bf333a 100644
--- a/web/app/components/workflow/nodes/human-input/components/delivery-method/index.tsx
+++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/index.tsx
@@ -3,14 +3,14 @@ import type {
Node,
NodeOutPutVar,
} from '@/app/components/workflow/types'
-import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { produce } from 'immer'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
+import { Infotip } from '@/app/components/base/infotip'
import { useNodesSyncDraft } from '@/app/components/workflow/hooks'
import MethodItem from './method-item'
import MethodSelector from './method-selector'
-import UpgradeModal from './upgrade-modal'
+import { UpgradeModal } from './upgrade-modal'
const i18nPrefix = 'nodes.humanInput'
@@ -62,27 +62,15 @@ const DeliveryMethodForm: React.FC
= ({
const handleShowUpgradeModal = () => {
setShowUpgradeModal(true)
}
- const handleCloseUpgradeModal = () => {
- setShowUpgradeModal(false)
- }
return (
{t(`${i18nPrefix}.deliveryMethod.title`, { ns: 'workflow' })}
-
-
-
-
- )}
- />
-
- {t(`${i18nPrefix}.deliveryMethod.tooltip`, { ns: 'workflow' })}
-
-
+
+ {t(`${i18nPrefix}.deliveryMethod.tooltip`, { ns: 'workflow' })}
+
{!readonly && (
@@ -115,12 +103,10 @@ const DeliveryMethodForm: React.FC
= ({
))}
)}
- {showUpgradeModal && (
-
- )}
+
)
}
diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/method-item.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/method-item.tsx
index 9cd63e96dd..8abace95f8 100644
--- a/web/app/components/workflow/nodes/human-input/components/delivery-method/method-item.tsx
+++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/method-item.tsx
@@ -7,6 +7,7 @@ import type {
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Switch } from '@langgenius/dify-ui/switch'
+import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
RiDeleteBinLine,
RiEqualizer2Line,
@@ -18,7 +19,6 @@ import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
import Badge from '@/app/components/base/badge/index'
-import Tooltip from '@/app/components/base/tooltip'
import Indicator from '@/app/components/header/indicator'
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
import { DeliveryMethodType } from '../../types'
@@ -79,6 +79,8 @@ const DeliveryMethodItem: FC
= ({
}
return t(`${i18nPrefix}.deliveryMethod.emailSender.testSendTip`, { ns: 'workflow' })
}, [method.type, method.config?.debug_mode, t, email])
+ const configureLabel = t('common.configure', { ns: 'workflow' })
+ const removeLabel = t('operation.remove', { ns: 'common' })
const jumpToEmailConfigModal = useCallback(() => {
setShowTestEmailModal(false)
@@ -114,47 +116,49 @@ const DeliveryMethodItem: FC = ({
{method.type === DeliveryMethodType.Email && method.config && (
<>
-
- {
- setShowTestEmailModal(true)
- }}
- >
-
-
+
+ setShowTestEmailModal(true)}
+ >
+
+
+ )}
+ />
+ {emailSenderTooltipContent}
-
- setShowEmailModal(true)}>
-
-
-
+
+ setShowEmailModal(true)}
+ >
+
+
+ )}
+ />
+ {configureLabel}
>
)}
-
- setIsHovering(true)}
- onMouseLeave={() => setIsHovering(false)}
- >
-
onDelete(method.type)}
- >
-
-
-
+
+ setIsHovering(true)}
+ onMouseLeave={() => setIsHovering(false)}
+ onClick={() => onDelete(method.type)}
+ >
+
+
+ )}
+ />
+ {removeLabel}
)}
@@ -178,33 +182,29 @@ const DeliveryMethodItem: FC = ({
)}
- {showEmailModal && (
-
setShowEmailModal(false)}
- onConfirm={(data) => {
- handleConfigChange(data)
- setShowEmailModal(false)
- }}
- />
- )}
- {showTestEmailModal && (
- setShowTestEmailModal(false)}
- jumpToEmailConfigModal={jumpToEmailConfigModal}
- />
- )}
+ {
+ handleConfigChange(data)
+ setShowEmailModal(false)
+ }}
+ />
+
>
)
}
diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/method-selector.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/method-selector.tsx
index 16c2345549..9ef75fd639 100644
--- a/web/app/components/workflow/nodes/human-input/components/delivery-method/method-selector.tsx
+++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/method-selector.tsx
@@ -2,6 +2,11 @@
import type { FC } from 'react'
import type { DeliveryMethod } from '../../types'
import { cn } from '@langgenius/dify-ui/cn'
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from '@langgenius/dify-ui/popover'
import {
RiAddLine,
RiDiscordFill,
@@ -9,17 +14,12 @@ import {
RiMailSendFill,
RiRobot2Fill,
} from '@remixicon/react'
-import { memo, useCallback, useMemo, useRef, useState } from 'react'
+import { memo, useMemo, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { v4 as uuid4 } from 'uuid'
import ActionButton from '@/app/components/base/action-button'
import Badge from '@/app/components/base/badge'
import { Slack, Teams } from '@/app/components/base/icons/src/public/other'
-import {
- PortalToFollowElem,
- PortalToFollowElemContent,
- PortalToFollowElemTrigger,
-} from '@/app/components/base/portal-to-follow-elem'
import useWorkflowNodes from '@/app/components/workflow/store/workflow/use-nodes'
import { isTriggerWorkflow } from '@/app/components/workflow/utils/workflow-entry'
import { IS_CE_EDITION } from '@/config'
@@ -40,20 +40,10 @@ const MethodSelector: FC = ({
onShowUpgradeTip,
}) => {
const { t } = useTranslation()
- const [open, doSetOpen] = useState(false)
+ const [open, setOpen] = useState(false)
const humanInputEmailDeliveryEnabled = useProviderContextSelector(s => s.humanInputEmailDeliveryEnabled)
- const openRef = useRef(open)
const nodes = useWorkflowNodes()
- const setOpen = useCallback((v: boolean) => {
- doSetOpen(v)
- openRef.current = v
- }, [doSetOpen])
-
- const handleTrigger = useCallback(() => {
- setOpen(!openRef.current)
- }, [setOpen])
-
const webAppDeliveryInfo = useMemo(() => {
const isTriggerMode = isTriggerWorkflow(nodes)
return {
@@ -71,23 +61,25 @@ const MethodSelector: FC = ({
}, [data, humanInputEmailDeliveryEnabled])
return (
-
-
-
-
-
+ )}
+ />
+
)}
-
-
+
+
)
}
export default memo(MethodSelector)
diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/test-email-sender.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/test-email-sender.tsx
index 9ad52fa13e..b88ec6cef6 100644
--- a/web/app/components/workflow/nodes/human-input/components/delivery-method/test-email-sender.tsx
+++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/test-email-sender.tsx
@@ -3,16 +3,17 @@ import type {
Node,
NodeOutPutVar,
ValueSelector,
+ Var,
} from '@/app/components/workflow/types'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
-import { RiArrowRightSFill, RiCloseLine } from '@remixicon/react'
+import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
+import { RiArrowRightSFill } from '@remixicon/react'
import { noop, unionBy } from 'es-toolkit/compat'
import { memo, useCallback, useMemo, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { useStore as useAppStore } from '@/app/components/app/store'
import Divider from '@/app/components/base/divider'
-import Modal from '@/app/components/base/modal'
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 {
@@ -30,11 +31,11 @@ import EmailInput from './recipient/email-input'
const i18nPrefix = 'nodes.humanInput'
-type EmailConfigureModalProps = {
+type EmailSenderModalProps = {
nodeId: string
deliveryId: string
- isShow: boolean
- onClose: () => void
+ open: boolean
+ onOpenChange: (open: boolean) => void
jumpToEmailConfigModal: () => void
config?: EmailConfig
formContent?: string
@@ -48,18 +49,22 @@ const getOriginVar = (valueSelector: string[], list: NodeOutPutVar[]) => {
if (!targetVar)
return undefined
- let curr: any = targetVar.vars
+ let curr: Var[] | undefined = targetVar.vars
for (let i = 1; i < valueSelector.length; i++) {
const key = valueSelector[i]
const isLast = i === valueSelector.length - 1
+ const currentVar: Var | undefined = curr?.find(v => v.variable.replace('conversation.', '') === key)
- if (Array.isArray(curr))
- curr = curr.find((v: any) => v.variable.replace('conversation.', '') === key)
+ if (!currentVar)
+ return undefined
if (isLast)
- return curr
- else if (curr?.type === VarType.object || curr?.type === VarType.file)
- curr = curr.children
+ return currentVar
+
+ if ((currentVar.type === VarType.object || currentVar.type === VarType.file) && Array.isArray(currentVar.children))
+ curr = currentVar.children
+ else
+ return undefined
}
return undefined
@@ -68,15 +73,15 @@ const getOriginVar = (valueSelector: string[], list: NodeOutPutVar[]) => {
const EmailSenderModal = ({
nodeId,
deliveryId,
- isShow,
- onClose,
+ open,
+ onOpenChange,
jumpToEmailConfigModal,
config,
formContent,
formInputs,
nodesOutputVars = [],
availableNodes = [],
-}: EmailConfigureModalProps) => {
+}: EmailSenderModalProps) => {
const { t } = useTranslation()
const { userProfile, currentWorkspace } = useAppContext()
const appDetail = useAppStore(state => state.appDetail)
@@ -104,7 +109,7 @@ const EmailSenderModal = ({
return {
label: {
nodeType: varInfo?.type,
- nodeName: varInfo?.title || availableNodes[0]?.data.title!, // default start node title
+ nodeName: varInfo?.title || availableNodes[0]?.data.title || '',
variable: isSystemVar(item) ? item.join('.') : item[item.length - 1]!,
isChatVar: isConversationVar(item),
},
@@ -178,194 +183,194 @@ const EmailSenderModal = ({
if (done) {
return (
-
-
-
{t(`${i18nPrefix}.deliveryMethod.emailSender.done`, { ns: 'workflow' })}
- {debugEnabled && (
-
-
}}
- values={{ email: userProfile.email }}
+
+
+
{t(`${i18nPrefix}.deliveryMethod.emailSender.done`, { ns: 'workflow' })}
+ {debugEnabled && (
+
+ }}
+ values={{ email: userProfile.email }}
+ />
+
+ )}
+ {!debugEnabled && onlyWholeTeam && (
+
+ }}
+ values={{ team: currentWorkspace.name.replace(/'/g, '’') }}
+ />
+
+ )}
+ {!debugEnabled && onlySpecificUsers && (
+
{t(`${i18nPrefix}.deliveryMethod.emailSender.wholeTeamDone3`, { ns: 'workflow' })}
+ )}
+ {!debugEnabled && combinedRecipients && (
+
+ }}
+ values={{ team: currentWorkspace.name.replace(/'/g, '’') }}
+ />
+
+ )}
+
+ {(onlySpecificUsers || combinedRecipients) && !debugEnabled && (
+
+
)}
+
+
+
+
+
+ )
+ }
+
+ return (
+