feat: Support selecting variables in conditional filtering in list operations. (#23029)
Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: crazywoola <427733928@qq.com>
This commit is contained in:
@@ -299,7 +299,7 @@ def _endswith(value: str) -> Callable[[str], bool]:
|
|||||||
|
|
||||||
|
|
||||||
def _is(value: str) -> Callable[[str], bool]:
|
def _is(value: str) -> Callable[[str], bool]:
|
||||||
return lambda x: x is value
|
return lambda x: x == value
|
||||||
|
|
||||||
|
|
||||||
def _in(value: str | Sequence[str]) -> Callable[[str], bool]:
|
def _in(value: str | Sequence[str]) -> Callable[[str], bool]:
|
||||||
|
@@ -1,36 +1,60 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useCallback, useMemo } from 'react'
|
import React, { useCallback, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import ConditionOperator from '../../if-else/components/condition-list/condition-operator'
|
import ConditionOperator from '../../if-else/components/condition-list/condition-operator'
|
||||||
import { VarType } from '../../../types'
|
|
||||||
import type { Condition } from '../types'
|
import type { Condition } from '../types'
|
||||||
import { ComparisonOperator } from '../../if-else/types'
|
import { ComparisonOperator } from '../../if-else/types'
|
||||||
import { comparisonOperatorNotRequireValue, getOperators } from '../../if-else/utils'
|
import { comparisonOperatorNotRequireValue, getOperators } from '../../if-else/utils'
|
||||||
import SubVariablePicker from './sub-variable-picker'
|
import SubVariablePicker from './sub-variable-picker'
|
||||||
import Input from '@/app/components/base/input'
|
|
||||||
import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '@/app/components/workflow/nodes/constants'
|
import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '@/app/components/workflow/nodes/constants'
|
||||||
import { SimpleSelect as Select } from '@/app/components/base/select'
|
import { SimpleSelect as Select } from '@/app/components/base/select'
|
||||||
|
import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var'
|
||||||
|
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
import { VarType } from '../../../types'
|
||||||
|
|
||||||
const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName'
|
const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName'
|
||||||
|
|
||||||
|
const VAR_INPUT_SUPPORTED_KEYS: Record<string, VarType> = {
|
||||||
|
name: VarType.string,
|
||||||
|
url: VarType.string,
|
||||||
|
extension: VarType.string,
|
||||||
|
mime_type: VarType.string,
|
||||||
|
related_id: VarType.number,
|
||||||
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
condition: Condition
|
condition: Condition
|
||||||
onChange: (condition: Condition) => void
|
onChange: (condition: Condition) => void
|
||||||
varType: VarType
|
|
||||||
hasSubVariable: boolean
|
hasSubVariable: boolean
|
||||||
readOnly: boolean
|
readOnly: boolean
|
||||||
|
nodeId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const FilterCondition: FC<Props> = ({
|
const FilterCondition: FC<Props> = ({
|
||||||
condition = { key: '', comparison_operator: ComparisonOperator.equal, value: '' },
|
condition = { key: '', comparison_operator: ComparisonOperator.equal, value: '' },
|
||||||
varType,
|
|
||||||
onChange,
|
onChange,
|
||||||
hasSubVariable,
|
hasSubVariable,
|
||||||
readOnly,
|
readOnly,
|
||||||
|
nodeId,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const [isFocus, setIsFocus] = useState(false)
|
||||||
|
|
||||||
|
const expectedVarType = VAR_INPUT_SUPPORTED_KEYS[condition.key]
|
||||||
|
const supportVariableInput = !!expectedVarType
|
||||||
|
|
||||||
|
const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, {
|
||||||
|
onlyLeafNodeVar: false,
|
||||||
|
filterVar: (varPayload) => {
|
||||||
|
return expectedVarType ? varPayload.type === expectedVarType : true
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const isSelect = [ComparisonOperator.in, ComparisonOperator.notIn, ComparisonOperator.allOf].includes(condition.comparison_operator)
|
const isSelect = [ComparisonOperator.in, ComparisonOperator.notIn, ComparisonOperator.allOf].includes(condition.comparison_operator)
|
||||||
const isArrayValue = condition.key === 'transfer_method' || condition.key === 'type'
|
const isArrayValue = condition.key === 'transfer_method' || condition.key === 'type'
|
||||||
|
|
||||||
const selectOptions = useMemo(() => {
|
const selectOptions = useMemo(() => {
|
||||||
if (isSelect) {
|
if (isSelect) {
|
||||||
if (condition.key === 'type' || condition.comparison_operator === ComparisonOperator.allOf) {
|
if (condition.key === 'type' || condition.comparison_operator === ComparisonOperator.allOf) {
|
||||||
@@ -49,6 +73,7 @@ const FilterCondition: FC<Props> = ({
|
|||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
}, [condition.comparison_operator, condition.key, isSelect, t])
|
}, [condition.comparison_operator, condition.key, isSelect, t])
|
||||||
|
|
||||||
const handleChange = useCallback((key: string) => {
|
const handleChange = useCallback((key: string) => {
|
||||||
return (value: any) => {
|
return (value: any) => {
|
||||||
onChange({
|
onChange({
|
||||||
@@ -59,12 +84,14 @@ const FilterCondition: FC<Props> = ({
|
|||||||
}, [condition, onChange, isArrayValue])
|
}, [condition, onChange, isArrayValue])
|
||||||
|
|
||||||
const handleSubVariableChange = useCallback((value: string) => {
|
const handleSubVariableChange = useCallback((value: string) => {
|
||||||
|
const operators = getOperators(expectedVarType ?? VarType.string, { key: value })
|
||||||
|
const newOperator = operators.length > 0 ? operators[0] : ComparisonOperator.equal
|
||||||
onChange({
|
onChange({
|
||||||
key: value,
|
key: value,
|
||||||
comparison_operator: getOperators(varType, { key: value })[0],
|
comparison_operator: newOperator,
|
||||||
value: '',
|
value: '',
|
||||||
})
|
})
|
||||||
}, [onChange, varType])
|
}, [onChange, expectedVarType])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -78,7 +105,7 @@ const FilterCondition: FC<Props> = ({
|
|||||||
<div className='flex space-x-1'>
|
<div className='flex space-x-1'>
|
||||||
<ConditionOperator
|
<ConditionOperator
|
||||||
className='h-8 bg-components-input-bg-normal'
|
className='h-8 bg-components-input-bg-normal'
|
||||||
varType={varType}
|
varType={expectedVarType ?? VarType.string}
|
||||||
value={condition.comparison_operator}
|
value={condition.comparison_operator}
|
||||||
onSelect={handleChange('comparison_operator')}
|
onSelect={handleChange('comparison_operator')}
|
||||||
file={hasSubVariable ? { key: condition.key } : undefined}
|
file={hasSubVariable ? { key: condition.key } : undefined}
|
||||||
@@ -86,7 +113,7 @@ const FilterCondition: FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
{!comparisonOperatorNotRequireValue(condition.comparison_operator) && (
|
{!comparisonOperatorNotRequireValue(condition.comparison_operator) && (
|
||||||
<>
|
<>
|
||||||
{isSelect && (
|
{isSelect ? (
|
||||||
<Select
|
<Select
|
||||||
items={selectOptions}
|
items={selectOptions}
|
||||||
defaultValue={isArrayValue ? (condition.value as string[])[0] : condition.value as string}
|
defaultValue={isArrayValue ? (condition.value as string[])[0] : condition.value as string}
|
||||||
@@ -95,13 +122,31 @@ const FilterCondition: FC<Props> = ({
|
|||||||
wrapperClassName='grow h-8'
|
wrapperClassName='grow h-8'
|
||||||
placeholder='Select value'
|
placeholder='Select value'
|
||||||
/>
|
/>
|
||||||
)}
|
) : supportVariableInput ? (
|
||||||
{!isSelect && (
|
|
||||||
<Input
|
<Input
|
||||||
type={((hasSubVariable && condition.key === 'size') || (!hasSubVariable && varType === VarType.number)) ? 'number' : 'text'}
|
instanceId='filter-condition-input'
|
||||||
className='grow'
|
className={cn(
|
||||||
|
isFocus
|
||||||
|
? 'border-components-input-border-active bg-components-input-bg-active shadow-xs'
|
||||||
|
: 'border-components-input-border-hover bg-components-input-bg-normal',
|
||||||
|
'w-0 grow rounded-lg border px-3 py-[6px]',
|
||||||
|
)}
|
||||||
|
value={condition.value}
|
||||||
|
onChange={handleChange('value')}
|
||||||
|
readOnly={readOnly}
|
||||||
|
nodesOutputVars={availableVars}
|
||||||
|
availableNodes={availableNodesWithParent}
|
||||||
|
onFocusChange={setIsFocus}
|
||||||
|
placeholder={!readOnly ? t('workflow.nodes.http.extractListPlaceholder')! : ''}
|
||||||
|
placeholderClassName='!leading-[21px]'
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<input
|
||||||
|
type={(condition.key === 'size' || expectedVarType === VarType.number) ? 'number' : 'text'}
|
||||||
|
className='grow rounded-lg border border-components-input-border-hover bg-components-input-bg-normal px-3 py-[6px]'
|
||||||
value={condition.value}
|
value={condition.value}
|
||||||
onChange={e => handleChange('value')(e.target.value)}
|
onChange={e => handleChange('value')(e.target.value)}
|
||||||
|
readOnly={readOnly}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -110,4 +155,5 @@ const FilterCondition: FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default React.memo(FilterCondition)
|
export default React.memo(FilterCondition)
|
||||||
|
@@ -78,6 +78,7 @@ const Panel: FC<NodePanelProps<ListFilterNodeType>> = ({
|
|||||||
varType={itemVarType}
|
varType={itemVarType}
|
||||||
hasSubVariable={hasSubVariable}
|
hasSubVariable={hasSubVariable}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
|
nodeId={id}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
: null}
|
: null}
|
||||||
|
Reference in New Issue
Block a user