mirror of https://github.com/langgenius/dify.git
chore: (webhook) use variable instead of InputVar (#25119)
This commit is contained in:
parent
5cf3d24018
commit
cc84a45244
|
|
@ -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
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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<ParameterTableProps> = ({
|
|||
]
|
||||
|
||||
// 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<ParameterTableProps> = ({
|
|||
.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),
|
||||
}))
|
||||
|
||||
|
|
|
|||
|
|
@ -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[]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> = {
|
||||
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) => {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<ParameterType, string> = {
|
||||
'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> = {
|
||||
[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<ParameterType, `array[${ArrayElementType}]`> => {
|
||||
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()
|
||||
|
|
|
|||
Loading…
Reference in New Issue