diff --git a/web/app/components/workflow/nodes/trigger-webhook/__tests__/default.test.ts b/web/app/components/workflow/nodes/trigger-webhook/__tests__/default.test.ts index 7e402ded21..5036e6feac 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/__tests__/default.test.ts +++ b/web/app/components/workflow/nodes/trigger-webhook/__tests__/default.test.ts @@ -38,11 +38,18 @@ describe('Webhook Trigger Node Default', () => { expect(Array.isArray(defaultValue.headers)).toBe(true) expect(Array.isArray(defaultValue.params)).toBe(true) expect(Array.isArray(defaultValue.body)).toBe(true) + expect(Array.isArray(defaultValue.variables)).toBe(true) // Initial arrays should be empty expect(defaultValue.headers).toHaveLength(0) expect(defaultValue.params).toHaveLength(0) expect(defaultValue.body).toHaveLength(0) + expect(defaultValue.variables).toHaveLength(1) + + const rawVariable = defaultValue.variables?.[0] + expect(rawVariable?.variable).toBe('_webhook_raw') + expect(rawVariable?.label).toBe('raw') + expect(rawVariable?.value_type).toBe('object') }) it('should have correct metadata for trigger node', () => { diff --git a/web/app/components/workflow/nodes/trigger-webhook/default.ts b/web/app/components/workflow/nodes/trigger-webhook/default.ts index de940d8c10..176b365b03 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/default.ts +++ b/web/app/components/workflow/nodes/trigger-webhook/default.ts @@ -3,6 +3,7 @@ import type { NodeDefault } from '../../types' import { genNodeMetaData } from '../../utils' import type { WebhookTriggerNodeType } from './types' import { isValidParameterType } from './utils/parameter-type-utils' +import { createWebhookRawVariable } from './utils/raw-variable' const metaData = genNodeMetaData({ sort: 3, @@ -22,6 +23,7 @@ const nodeDefault: NodeDefault = { async_mode: true, status_code: 200, response_body: '', + variables: [createWebhookRawVariable()], }, checkValid(payload: WebhookTriggerNodeType, t: any) { // Require webhook_url to be configured diff --git a/web/app/components/workflow/nodes/trigger-webhook/use-config.ts b/web/app/components/workflow/nodes/trigger-webhook/use-config.ts index e754d563f3..76a3be2509 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/use-config.ts +++ b/web/app/components/workflow/nodes/trigger-webhook/use-config.ts @@ -1,4 +1,4 @@ -import { useCallback } from 'react' +import { useCallback, useEffect } from 'react' import produce from 'immer' import { useTranslation } from 'react-i18next' import type { HttpMethod, WebhookHeader, WebhookParameter, WebhookTriggerNodeType } from './types' @@ -11,6 +11,7 @@ import type { Variable } from '@/app/components/workflow/types' import { VarType } from '@/app/components/workflow/types' import Toast from '@/app/components/base/toast' import { hasDuplicateStr } from '@/utils/var' +import { WEBHOOK_RAW_VARIABLE_NAME, ensureWebhookRawVariable } from './utils/raw-variable' const useConfig = (id: string, payload: WebhookTriggerNodeType) => { const { t } = useTranslation() @@ -18,15 +19,29 @@ const useConfig = (id: string, payload: WebhookTriggerNodeType) => { const { inputs, setInputs } = useNodeCrud(id, payload) const appId = useAppStore.getState().appDetail?.id const { isVarUsedInNodes, removeUsedVarInNodes } = useWorkflow() + const hasWebhookRawVariable = inputs.variables?.some(variable => variable.variable === WEBHOOK_RAW_VARIABLE_NAME) ?? false + + useEffect(() => { + if (readOnly) + return + + if (!hasWebhookRawVariable) { + setInputs(produce(inputs, (draft) => { + ensureWebhookRawVariable(draft) + })) + } + }, [readOnly, hasWebhookRawVariable, inputs, setInputs]) const handleMethodChange = useCallback((method: HttpMethod) => { setInputs(produce(inputs, (draft) => { + ensureWebhookRawVariable(draft) draft.method = method })) }, [inputs, setInputs]) const handleContentTypeChange = useCallback((contentType: string) => { setInputs(produce(inputs, (draft) => { + ensureWebhookRawVariable(draft) const previousContentType = draft.content_type draft.content_type = contentType @@ -107,6 +122,7 @@ const useConfig = (id: string, payload: WebhookTriggerNodeType) => { draft.variables.push(newVar) }) + ensureWebhookRawVariable(draft) return true }, [t, id, isVarUsedInNodes, removeUsedVarInNodes]) @@ -171,6 +187,7 @@ const useConfig = (id: string, payload: WebhookTriggerNodeType) => { const response = await fetchWebhookUrl({ appId, nodeId: id }) const newInputs = produce(inputs, (draft) => { + ensureWebhookRawVariable(draft) draft.webhook_url = response.webhook_url draft.webhook_debug_url = response.webhook_debug_url }) @@ -181,6 +198,7 @@ const useConfig = (id: string, payload: WebhookTriggerNodeType) => { // Keep the UI unblocked and allow users to proceed in local/dev environments. console.error('Failed to generate webhook URL:', error) const newInputs = produce(inputs, (draft) => { + ensureWebhookRawVariable(draft) draft.webhook_url = '' }) setInputs(newInputs) diff --git a/web/app/components/workflow/nodes/trigger-webhook/utils/raw-variable.ts b/web/app/components/workflow/nodes/trigger-webhook/utils/raw-variable.ts new file mode 100644 index 0000000000..10992d11c0 --- /dev/null +++ b/web/app/components/workflow/nodes/trigger-webhook/utils/raw-variable.ts @@ -0,0 +1,25 @@ +import { VarType, type Variable } from '@/app/components/workflow/types' + +export const WEBHOOK_RAW_VARIABLE_NAME = '_webhook_raw' +export const WEBHOOK_RAW_VARIABLE_LABEL = 'raw' + +export const createWebhookRawVariable = (): Variable => ({ + variable: WEBHOOK_RAW_VARIABLE_NAME, + label: WEBHOOK_RAW_VARIABLE_LABEL, + value_type: VarType.object, + value_selector: [], + required: true, +}) + +type WithVariables = { + variables?: Variable[] +} + +export const ensureWebhookRawVariable = (payload: T): void => { + if (!payload.variables) + payload.variables = [] + + const hasRawVariable = payload.variables.some(variable => variable.variable === WEBHOOK_RAW_VARIABLE_NAME) + if (!hasRawVariable) + payload.variables.push(createWebhookRawVariable()) +} diff --git a/web/app/components/workflow/nodes/trigger-webhook/utils/render-output-vars.tsx b/web/app/components/workflow/nodes/trigger-webhook/utils/render-output-vars.tsx index d5d5da71f9..0e9cb8a309 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/utils/render-output-vars.tsx +++ b/web/app/components/workflow/nodes/trigger-webhook/utils/render-output-vars.tsx @@ -7,10 +7,11 @@ type OutputVariablesContentProps = { } // Define the display order for variable labels to match the table order in the UI -const LABEL_ORDER = { param: 1, header: 2, body: 3 } as const +const LABEL_ORDER = { raw: 0, param: 1, header: 2, body: 3 } as const const getLabelPrefix = (label: string): string => { const prefixMap: Record = { + raw: 'payload', param: 'query_params', header: 'header_params', body: 'req_body_params',