diff --git a/web/app/components/workflow/nodes/_base/components/variable/utils.ts b/web/app/components/workflow/nodes/_base/components/variable/utils.ts index 7a9294d614..f42feea98e 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/utils.ts +++ b/web/app/components/workflow/nodes/_base/components/variable/utils.ts @@ -296,25 +296,15 @@ const formatItem = ( variables = [], } = data as WebhookTriggerNodeType res.vars = variables.map((v) => { - const type = inputVarTypeToVarType(v.type) + const type = v.value_type || VarType.string const varRes: Var = { variable: v.variable, type, - isParagraph: v.type === InputVarType.paragraph, - isSelect: v.type === InputVarType.select, + isParagraph: false, + isSelect: false, options: v.options, required: v.required, } - try { - if(type === VarType.object && v.json_schema) { - varRes.children = { - schema: JSON.parse(v.json_schema), - } - } - } - catch (error) { - console.error('Error formatting TriggerWebhook variable:', error) - } return varRes }) diff --git a/web/app/components/workflow/nodes/trigger-webhook/components/parameter-table.tsx b/web/app/components/workflow/nodes/trigger-webhook/components/parameter-table.tsx index 1d35cda126..2b57cb1283 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/components/parameter-table.tsx +++ b/web/app/components/workflow/nodes/trigger-webhook/components/parameter-table.tsx @@ -4,8 +4,9 @@ import React from 'react' import { useTranslation } from 'react-i18next' import GenericTable from './generic-table' import type { ColumnConfig, GenericTableRow } from './generic-table' -import type { ParameterType, WebhookParameter } from '../types' +import type { WebhookParameter } from '../types' import { createParameterTypeOptions, normalizeParameterType } from '../utils/parameter-type-utils' +import { VarType } from '@/app/components/workflow/types' type ParameterTableProps = { title: string @@ -60,7 +61,7 @@ const ParameterTable: FC = ({ ] // Choose sensible default type for new rows according to content type - const defaultTypeValue: ParameterType = typeOptions[0]?.value || 'string' + const defaultTypeValue: VarType = typeOptions[0]?.value || 'string' // Empty row template for new rows const emptyRowData: GenericTableRow = { @@ -83,7 +84,7 @@ const ParameterTable: FC = ({ .filter(row => typeof row.key === 'string' && (row.key as string).trim() !== '') .map(row => ({ name: String(row.key), - type: isTextPlain ? 'string' : normalizeParameterType((row.type as string) || 'string'), + type: isTextPlain ? VarType.string : normalizeParameterType((row.type as string)), required: Boolean(row.required), })) diff --git a/web/app/components/workflow/nodes/trigger-webhook/types.ts b/web/app/components/workflow/nodes/trigger-webhook/types.ts index 587e9e5ebb..d9632f20e1 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/types.ts +++ b/web/app/components/workflow/nodes/trigger-webhook/types.ts @@ -1,15 +1,9 @@ -import type { CommonNodeType, InputVar } from '@/app/components/workflow/types' +import type { CommonNodeType, VarType, Variable } from '@/app/components/workflow/types' export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' -export type ParameterType = 'string' | 'number' | 'boolean' | 'array[string]' | 'array[number]' | 'array[boolean]' | 'array[object]' | 'object' | 'file' - export type ArrayElementType = 'string' | 'number' | 'boolean' | 'object' -export const isArrayType = (type: ParameterType): type is `array[${ArrayElementType}]` => { - return type.startsWith('array[') && type.endsWith(']') -} - export const getArrayElementType = (arrayType: `array[${ArrayElementType}]`): ArrayElementType => { const match = arrayType.match(/^array\[(.+)\]$/) return (match?.[1] as ArrayElementType) || 'string' @@ -17,7 +11,7 @@ export const getArrayElementType = (arrayType: `array[${ArrayElementType}]`): Ar export type WebhookParameter = { name: string - type: ParameterType + type: VarType required: boolean } @@ -37,5 +31,5 @@ export type WebhookTriggerNodeType = CommonNodeType & { async_mode: boolean status_code: number response_body: string - variables: InputVar[] + variables: Variable[] } 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 c1c86eceac..f22b596dd8 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/use-config.ts +++ b/web/app/components/workflow/nodes/trigger-webhook/use-config.ts @@ -1,15 +1,14 @@ import { useCallback } from 'react' import produce from 'immer' import { useTranslation } from 'react-i18next' -import type { HttpMethod, ParameterType, WebhookHeader, WebhookParameter, WebhookTriggerNodeType } from './types' -import { getArrayElementType, isArrayType } from './types' +import type { HttpMethod, WebhookHeader, WebhookParameter, WebhookTriggerNodeType } from './types' import { useNodesReadOnly, useWorkflow } from '@/app/components/workflow/hooks' import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' import { useStore as useAppStore } from '@/app/components/app/store' import { fetchWebhookUrl } from '@/service/apps' -import type { InputVar } from '@/app/components/workflow/types' -import { InputVarType } from '@/app/components/workflow/types' +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' @@ -32,36 +31,6 @@ const useConfig = (id: string, payload: WebhookTriggerNodeType) => { })) }, [inputs, setInputs]) - // Helper function to convert ParameterType to InputVarType - const toInputVarType = useCallback((type: ParameterType): InputVarType => { - // Handle specific array types - if (isArrayType(type)) { - const elementType = getArrayElementType(type) - switch (elementType) { - case 'string': - return InputVarType.textInput - case 'number': - return InputVarType.number - case 'boolean': - return InputVarType.checkbox - case 'object': - return InputVarType.jsonObject - default: - return InputVarType.textInput - } - } - - // Handle non-array types - const typeMap: Record = { - string: InputVarType.textInput, - number: InputVarType.number, - boolean: InputVarType.checkbox, - object: InputVarType.jsonObject, - file: InputVarType.singleFile, - } - return typeMap[type] || InputVarType.textInput - }, []) - const syncVariablesInDraft = useCallback(( draft: WebhookTriggerNodeType, newData: (WebhookParameter | WebhookHeader)[], @@ -105,13 +74,14 @@ const useConfig = (id: string, payload: WebhookTriggerNodeType) => { const existingVarIndex = draft.variables.findIndex(v => v.variable === varName) const inputVarType = 'type' in item - ? toInputVarType(item.type) - : InputVarType.textInput + ? item.type + : VarType.string // Default to string for headers - const newVar: InputVar = { - type: inputVarType, + const newVar: Variable = { + value_type: inputVarType, label: sourceType, // Use sourceType as label to identify source variable: varName, + value_selector: [], required: item.required, } @@ -122,7 +92,7 @@ const useConfig = (id: string, payload: WebhookTriggerNodeType) => { }) return true - }, [toInputVarType, t, id, isVarUsedInNodes, removeUsedVarInNodes]) + }, [t, id, isVarUsedInNodes, removeUsedVarInNodes]) const handleParamsChange = useCallback((params: WebhookParameter[]) => { setInputs(produce(inputs, (draft) => { diff --git a/web/app/components/workflow/nodes/trigger-webhook/utils/parameter-type-utils.test.ts b/web/app/components/workflow/nodes/trigger-webhook/utils/parameter-type-utils.test.ts index f073b185c6..34b1ec4e6f 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/utils/parameter-type-utils.test.ts +++ b/web/app/components/workflow/nodes/trigger-webhook/utils/parameter-type-utils.test.ts @@ -1,10 +1,8 @@ import { createParameterTypeOptions, getAvailableParameterTypes, - getParameterTypeDisplayName, isValidParameterType, normalizeParameterType, - validateParameterValue, } from './parameter-type-utils' describe('Parameter Type Utils', () => { @@ -46,72 +44,6 @@ describe('Parameter Type Utils', () => { }) }) - describe('getParameterTypeDisplayName', () => { - it('should return correct display names for array types', () => { - expect(getParameterTypeDisplayName('array[string]')).toBe('Array[String]') - expect(getParameterTypeDisplayName('array[number]')).toBe('Array[Number]') - expect(getParameterTypeDisplayName('array[boolean]')).toBe('Array[Boolean]') - expect(getParameterTypeDisplayName('array[object]')).toBe('Array[Object]') - }) - - it('should return correct display names for basic types', () => { - expect(getParameterTypeDisplayName('string')).toBe('String') - expect(getParameterTypeDisplayName('number')).toBe('Number') - expect(getParameterTypeDisplayName('boolean')).toBe('Boolean') - expect(getParameterTypeDisplayName('object')).toBe('Object') - expect(getParameterTypeDisplayName('file')).toBe('File') - }) - }) - - describe('validateParameterValue', () => { - it('should validate string values', () => { - expect(validateParameterValue('test', 'string')).toBe(true) - expect(validateParameterValue('', 'string')).toBe(true) - expect(validateParameterValue(123, 'string')).toBe(false) - }) - - it('should validate number values', () => { - expect(validateParameterValue(123, 'number')).toBe(true) - expect(validateParameterValue(123.45, 'number')).toBe(true) - expect(validateParameterValue('abc', 'number')).toBe(false) - expect(validateParameterValue(Number.NaN, 'number')).toBe(false) - }) - - it('should validate boolean values', () => { - expect(validateParameterValue(true, 'boolean')).toBe(true) - expect(validateParameterValue(false, 'boolean')).toBe(true) - expect(validateParameterValue('true', 'boolean')).toBe(false) - }) - - it('should validate array values', () => { - expect(validateParameterValue(['a', 'b'], 'array[string]')).toBe(true) - expect(validateParameterValue([1, 2, 3], 'array[number]')).toBe(true) - expect(validateParameterValue([true, false], 'array[boolean]')).toBe(true) - expect(validateParameterValue([{ key: 'value' }], 'array[object]')).toBe(true) - expect(validateParameterValue(['a', 1], 'array[string]')).toBe(false) - expect(validateParameterValue('not an array', 'array[string]')).toBe(false) - }) - - it('should validate object values', () => { - expect(validateParameterValue({ key: 'value' }, 'object')).toBe(true) - expect(validateParameterValue({}, 'object')).toBe(true) - expect(validateParameterValue(null, 'object')).toBe(false) - expect(validateParameterValue([], 'object')).toBe(false) - expect(validateParameterValue('string', 'object')).toBe(false) - }) - - it('should validate file values', () => { - const mockFile = new File(['content'], 'test.txt', { type: 'text/plain' }) - expect(validateParameterValue(mockFile, 'file')).toBe(true) - expect(validateParameterValue({ name: 'file.txt' }, 'file')).toBe(true) - expect(validateParameterValue('not a file', 'file')).toBe(false) - }) - - it('should return false for invalid types', () => { - expect(validateParameterValue('test', 'invalid' as any)).toBe(false) - }) - }) - describe('getAvailableParameterTypes', () => { it('should return only string for non-request body', () => { const types = getAvailableParameterTypes('application/json', false) diff --git a/web/app/components/workflow/nodes/trigger-webhook/utils/parameter-type-utils.ts b/web/app/components/workflow/nodes/trigger-webhook/utils/parameter-type-utils.ts index c0acee2a79..fabe38c40b 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/utils/parameter-type-utils.ts +++ b/web/app/components/workflow/nodes/trigger-webhook/utils/parameter-type-utils.ts @@ -1,48 +1,53 @@ -import type { ArrayElementType, ParameterType } from '../types' +import { VarType } from '@/app/components/workflow/types' // Constants for better maintainability and reusability -const BASIC_TYPES = ['string', 'number', 'boolean', 'object', 'file'] as const -const ARRAY_ELEMENT_TYPES = ['string', 'number', 'boolean', 'object'] as const +const BASIC_TYPES = [VarType.string, VarType.number, VarType.boolean, VarType.object, VarType.file] as const +const ARRAY_ELEMENT_TYPES = [VarType.arrayString, VarType.arrayNumber, VarType.arrayBoolean, VarType.arrayObject] as const // Generate all valid parameter types programmatically -const VALID_PARAMETER_TYPES: readonly ParameterType[] = [ +const VALID_PARAMETER_TYPES: readonly VarType[] = [ ...BASIC_TYPES, - ...ARRAY_ELEMENT_TYPES.map(type => `array[${type}]` as const), + ...ARRAY_ELEMENT_TYPES, ] as const // Type display name mappings -const TYPE_DISPLAY_NAMES: Record = { - 'string': 'String', - 'number': 'Number', - 'boolean': 'Boolean', - 'object': 'Object', - 'file': 'File', - 'array[string]': 'Array[String]', - 'array[number]': 'Array[Number]', - 'array[boolean]': 'Array[Boolean]', - 'array[object]': 'Array[Object]', +const TYPE_DISPLAY_NAMES: Record = { + [VarType.string]: 'String', + [VarType.number]: 'Number', + [VarType.boolean]: 'Boolean', + [VarType.object]: 'Object', + [VarType.file]: 'File', + [VarType.arrayString]: 'Array[String]', + [VarType.arrayNumber]: 'Array[Number]', + [VarType.arrayBoolean]: 'Array[Boolean]', + [VarType.arrayObject]: 'Array[Object]', + [VarType.secret]: 'Secret', + [VarType.array]: 'Array', + 'array[file]': 'Array[File]', + [VarType.any]: 'Any', + 'array[any]': 'Array[Any]', } as const // Content type configurations const CONTENT_TYPE_CONFIGS = { 'application/json': { - supportedTypes: [...BASIC_TYPES.filter(t => t !== 'file'), ...ARRAY_ELEMENT_TYPES.map(t => `array[${t}]` as const)], + supportedTypes: [...BASIC_TYPES.filter(t => t !== 'file'), ...ARRAY_ELEMENT_TYPES], description: 'JSON supports all types including arrays', }, 'text/plain': { - supportedTypes: ['string'] as const, + supportedTypes: [VarType.string] as const, description: 'Plain text only supports string', }, 'application/x-www-form-urlencoded': { - supportedTypes: ['string', 'number', 'boolean'] as const, + supportedTypes: [VarType.string, VarType.number, VarType.boolean] as const, description: 'Form data supports basic types', }, 'forms': { - supportedTypes: ['string', 'number', 'boolean'] as const, + supportedTypes: [VarType.string, VarType.number, VarType.boolean] as const, description: 'Form data supports basic types', }, 'multipart/form-data': { - supportedTypes: ['string', 'number', 'boolean', 'file'] as const, + supportedTypes: [VarType.string, VarType.number, VarType.boolean, VarType.file] as const, description: 'Multipart supports basic types plus files', }, } as const @@ -50,113 +55,50 @@ const CONTENT_TYPE_CONFIGS = { /** * Type guard to check if a string is a valid parameter type */ -export const isValidParameterType = (type: string): type is ParameterType => { +export const isValidParameterType = (type: string): type is VarType => { return (VALID_PARAMETER_TYPES as readonly string[]).includes(type) } -/** - * Type-safe helper to check if a string is a valid array element type - */ -const isValidArrayElementType = (type: string): type is ArrayElementType => { - return (ARRAY_ELEMENT_TYPES as readonly string[]).includes(type) -} - -/** - * Type-safe helper to check if a string is a valid basic type - */ -const isValidBasicType = (type: string): type is Exclude => { - return (BASIC_TYPES as readonly string[]).includes(type) -} - -/** - * Normalizes parameter type from various input formats to the new type system - * Handles legacy 'array' type and malformed inputs gracefully - */ -export const normalizeParameterType = (input: string | undefined | null): ParameterType => { +export const normalizeParameterType = (input: string | undefined | null): VarType => { if (!input || typeof input !== 'string') - return 'string' + return VarType.string const trimmed = input.trim().toLowerCase() + if (trimmed === 'array[string]') + return VarType.arrayString + else if (trimmed === 'array[number]') + return VarType.arrayNumber + else if (trimmed === 'array[boolean]') + return VarType.arrayBoolean + else if (trimmed === 'array[object]') + return VarType.arrayObject + else if (trimmed === 'number') + return VarType.number + else if (trimmed === 'boolean') + return VarType.boolean + else if (trimmed === 'object') + return VarType.object + else if (trimmed === 'file') + return VarType.file - // Handle legacy array type - if (trimmed === 'array') - return 'array[string]' // Default to string array for backward compatibility - - // Handle specific array types - if (trimmed.startsWith('array[') && trimmed.endsWith(']')) { - const elementType = trimmed.slice(6, -1) // Extract content between 'array[' and ']' - - if (isValidArrayElementType(elementType)) - return `array[${elementType}]` - - // Invalid array element type, default to string array - return 'array[string]' - } - - // Handle basic types - if (isValidBasicType(trimmed)) - return trimmed - - // Fallback to string for unknown types - return 'string' + return VarType.string } /** * Gets display name for parameter types in UI components */ -export const getParameterTypeDisplayName = (type: ParameterType): string => { +export const getParameterTypeDisplayName = (type: VarType): string => { return TYPE_DISPLAY_NAMES[type] } -// Type validation functions for better reusability -const validators = { - string: (value: unknown): value is string => typeof value === 'string', - number: (value: unknown): value is number => typeof value === 'number' && !isNaN(value), - boolean: (value: unknown): value is boolean => typeof value === 'boolean', - object: (value: unknown): value is object => - typeof value === 'object' && value !== null && !Array.isArray(value), - file: (value: unknown): value is File => - value instanceof File || (typeof value === 'object' && value !== null), -} as const - -/** - * Validates array elements based on element type - */ -const validateArrayElements = (value: unknown[], elementType: ArrayElementType): boolean => { - const validator = validators[elementType] - return value.every(item => validator(item)) -} - -/** - * Validates parameter value against its declared type - * Provides runtime type checking for webhook parameters - */ -export const validateParameterValue = (value: unknown, type: ParameterType): boolean => { - // Handle basic types - if (type in validators) { - const validator = validators[type as keyof typeof validators] - return validator(value) - } - - // Handle array types - if (type.startsWith('array[') && type.endsWith(']')) { - if (!Array.isArray(value)) return false - - const elementType = type.slice(6, -1) - return isValidArrayElementType(elementType) && validateArrayElements(value, elementType) - } - - return false -} - /** * Gets available parameter types based on content type * Provides context-aware type filtering for different webhook content types */ -export const getAvailableParameterTypes = (contentType?: string, isRequestBody = false): ParameterType[] => { +export const getAvailableParameterTypes = (contentType?: string, isRequestBody = false): VarType[] => { if (!isRequestBody) { // Query parameters and headers are always strings - return ['string'] + return [VarType.string] } const normalizedContentType = (contentType || '').toLowerCase()