mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 03:01:01 -04:00
fix: preserve single-run input variable types (#35710)
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import {
|
||||
BlockEnum,
|
||||
InputVarType,
|
||||
VarType,
|
||||
} from '@/app/components/workflow/types'
|
||||
import { FlowType } from '@/types/common'
|
||||
import useOneStepRun from '../use-one-step-run'
|
||||
|
||||
const mockWorkflowState = {
|
||||
conversationVariables: [],
|
||||
dataSourceList: [],
|
||||
nodesWithInspectVars: [],
|
||||
setNodesWithInspectVars: vi.fn(),
|
||||
setShowSingleRunPanel: vi.fn(),
|
||||
setIsListening: vi.fn(),
|
||||
setListeningTriggerType: vi.fn(),
|
||||
setListeningTriggerNodeId: vi.fn(),
|
||||
setListeningTriggerNodeIds: vi.fn(),
|
||||
setListeningTriggerIsAll: vi.fn(),
|
||||
setShowVariableInspectPanel: vi.fn(),
|
||||
}
|
||||
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => key,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
toast: {
|
||||
error: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/amplitude', () => ({
|
||||
trackEvent: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/workflow/hooks', () => ({
|
||||
useIsChatMode: () => false,
|
||||
useNodeDataUpdate: () => ({
|
||||
handleNodeDataUpdate: vi.fn(),
|
||||
}),
|
||||
useWorkflow: () => ({
|
||||
getBeforeNodesInSameBranch: () => [
|
||||
{
|
||||
id: 'start',
|
||||
data: {
|
||||
type: 'start',
|
||||
title: 'Start',
|
||||
variables: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
getBeforeNodesInSameBranchIncludeParent: () => [
|
||||
{
|
||||
id: 'start',
|
||||
data: {
|
||||
type: 'start',
|
||||
title: 'Start',
|
||||
variables: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/workflow/hooks/use-inspect-vars-crud', () => ({
|
||||
default: () => ({
|
||||
appendNodeInspectVars: vi.fn(),
|
||||
invalidateSysVarValues: vi.fn(),
|
||||
invalidateConversationVarValues: vi.fn(),
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/workflow/store', () => ({
|
||||
useStore: (selector: (state: typeof mockWorkflowState) => unknown) => selector(mockWorkflowState),
|
||||
useWorkflowStore: () => ({
|
||||
getState: () => mockWorkflowState,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('reactflow', () => ({
|
||||
useStoreApi: () => ({
|
||||
getState: () => ({
|
||||
getNodes: () => [],
|
||||
}),
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-tools', () => ({
|
||||
useAllBuiltInTools: () => ({ data: [] }),
|
||||
useAllCustomTools: () => ({ data: [] }),
|
||||
useAllWorkflowTools: () => ({ data: [] }),
|
||||
useAllMCPTools: () => ({ data: [] }),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-workflow', () => ({
|
||||
useInvalidLastRun: () => vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/workflow', () => ({
|
||||
fetchNodeInspectVars: vi.fn(),
|
||||
getIterationSingleNodeRunUrl: vi.fn(),
|
||||
getLoopSingleNodeRunUrl: vi.fn(),
|
||||
singleNodeRun: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/base', () => ({
|
||||
post: vi.fn(),
|
||||
ssePost: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/event-emitter', () => ({
|
||||
useEventEmitterContextContext: () => ({
|
||||
eventEmitter: {
|
||||
useSubscription: vi.fn(),
|
||||
},
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('../components/variable/use-match-schema-type', () => ({
|
||||
default: () => ({
|
||||
schemaTypeDefinitions: [],
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/workflow/nodes/_base/components/variable/use-match-schema-type', () => ({
|
||||
default: () => ({
|
||||
schemaTypeDefinitions: [],
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/workflow/nodes/assigner/default', () => ({
|
||||
default: {},
|
||||
}))
|
||||
vi.mock('@/app/components/workflow/nodes/code/default', () => ({
|
||||
default: {},
|
||||
}))
|
||||
vi.mock('@/app/components/workflow/nodes/document-extractor/default', () => ({
|
||||
default: {},
|
||||
}))
|
||||
vi.mock('@/app/components/workflow/nodes/http/default', () => ({
|
||||
default: {},
|
||||
}))
|
||||
vi.mock('@/app/components/workflow/nodes/human-input/default', () => ({
|
||||
default: {},
|
||||
}))
|
||||
vi.mock('@/app/components/workflow/nodes/if-else/default', () => ({
|
||||
default: {},
|
||||
}))
|
||||
vi.mock('@/app/components/workflow/nodes/iteration/default', () => ({
|
||||
default: {},
|
||||
}))
|
||||
vi.mock('@/app/components/workflow/nodes/knowledge-retrieval/default', () => ({
|
||||
default: {},
|
||||
}))
|
||||
vi.mock('@/app/components/workflow/nodes/llm/default', () => ({
|
||||
default: {},
|
||||
}))
|
||||
vi.mock('@/app/components/workflow/nodes/loop/default', () => ({
|
||||
default: {},
|
||||
}))
|
||||
vi.mock('@/app/components/workflow/nodes/parameter-extractor/default', () => ({
|
||||
default: {},
|
||||
}))
|
||||
vi.mock('@/app/components/workflow/nodes/question-classifier/default', () => ({
|
||||
default: {},
|
||||
}))
|
||||
vi.mock('@/app/components/workflow/nodes/template-transform/default', () => ({
|
||||
default: {},
|
||||
}))
|
||||
vi.mock('@/app/components/workflow/nodes/tool/default', () => ({
|
||||
default: {},
|
||||
}))
|
||||
vi.mock('@/app/components/workflow/nodes/variable-assigner/default', () => ({
|
||||
default: {},
|
||||
}))
|
||||
|
||||
const renderUseOneStepRun = () => renderHook(() => useOneStepRun({
|
||||
id: 'if-else-node',
|
||||
flowId: 'app-id',
|
||||
flowType: FlowType.appFlow,
|
||||
data: {
|
||||
type: BlockEnum.IfElse,
|
||||
title: 'IF/ELSE',
|
||||
desc: '',
|
||||
},
|
||||
defaultRunInputData: {},
|
||||
isRunAfterSingleRun: false,
|
||||
isPaused: false,
|
||||
}))
|
||||
|
||||
describe('useOneStepRun single-run input vars', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
Object.defineProperty(globalThis, 'location', {
|
||||
value: {
|
||||
pathname: '/app/test-app/workflow',
|
||||
},
|
||||
configurable: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('uses value_type when the variable cannot be resolved from output vars', () => {
|
||||
const { result } = renderUseOneStepRun()
|
||||
|
||||
const inputs = result.current.toVarInputs([
|
||||
{
|
||||
variable: '#start.amount#',
|
||||
value_selector: ['start', 'amount'],
|
||||
value_type: VarType.number,
|
||||
},
|
||||
])
|
||||
|
||||
expect(inputs).toMatchObject([
|
||||
{
|
||||
variable: '#start.amount#',
|
||||
type: InputVarType.number,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('resolves global system vars by full variable name', () => {
|
||||
const { result } = renderUseOneStepRun()
|
||||
|
||||
const inputs = result.current.varSelectorsToVarInputs([
|
||||
['sys', 'timestamp'],
|
||||
])
|
||||
|
||||
expect(inputs).toMatchObject([
|
||||
{
|
||||
variable: '#sys.timestamp#',
|
||||
type: InputVarType.number,
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -178,13 +178,15 @@ const useOneStepRun = <T>({
|
||||
}
|
||||
|
||||
const allOutputVars = toNodeOutputVars(availableNodes, isChatMode, undefined, undefined, conversationVariables, [], allPluginInfoList, schemaTypeDefinitions)
|
||||
const targetVar = allOutputVars.find(item => isSystem ? !!item.isStartNode : item.nodeId === valueSelector[0])
|
||||
if (isSystem) {
|
||||
const selectorKey = valueSelector.join('.')
|
||||
return allOutputVars.flatMap(item => item.vars).find(item => item.variable === selectorKey)
|
||||
}
|
||||
|
||||
const targetVar = allOutputVars.find(item => item.nodeId === valueSelector[0])
|
||||
if (!targetVar)
|
||||
return undefined
|
||||
|
||||
if (isSystem)
|
||||
return targetVar.vars.find(item => item.variable.split('.')[1] === valueSelector[1])
|
||||
|
||||
let curr: any = targetVar.vars
|
||||
for (let i = 1; i < valueSelector.length; i++) {
|
||||
const key = valueSelector[i]
|
||||
@@ -1079,12 +1081,19 @@ const useOneStepRun = <T>({
|
||||
const varInputs = variables.filter(item => !isENV(item.value_selector)).map((item) => {
|
||||
const originalVar = getVar(item.value_selector)
|
||||
if (!originalVar) {
|
||||
const fallbackType = item.value_type
|
||||
? varTypeToInputVarType(item.value_type, {
|
||||
isSelect: !!item.options?.length,
|
||||
isParagraph: !!item.isParagraph,
|
||||
})
|
||||
: InputVarType.textInput
|
||||
return {
|
||||
label: item.label || item.variable,
|
||||
variable: item.variable,
|
||||
type: InputVarType.textInput,
|
||||
type: fallbackType,
|
||||
required: true,
|
||||
value_selector: item.value_selector,
|
||||
options: item.options,
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user