Skip to content

Commit b43ebf5

Browse files
authored
fix: preserve single-run input variable types (#35710)
1 parent 853b859 commit b43ebf5

2 files changed

Lines changed: 253 additions & 5 deletions

File tree

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
import { renderHook } from '@testing-library/react'
2+
import {
3+
BlockEnum,
4+
InputVarType,
5+
VarType,
6+
} from '@/app/components/workflow/types'
7+
import { FlowType } from '@/types/common'
8+
import useOneStepRun from '../use-one-step-run'
9+
10+
const mockWorkflowState = {
11+
conversationVariables: [],
12+
dataSourceList: [],
13+
nodesWithInspectVars: [],
14+
setNodesWithInspectVars: vi.fn(),
15+
setShowSingleRunPanel: vi.fn(),
16+
setIsListening: vi.fn(),
17+
setListeningTriggerType: vi.fn(),
18+
setListeningTriggerNodeId: vi.fn(),
19+
setListeningTriggerNodeIds: vi.fn(),
20+
setListeningTriggerIsAll: vi.fn(),
21+
setShowVariableInspectPanel: vi.fn(),
22+
}
23+
24+
vi.mock('react-i18next', () => ({
25+
useTranslation: () => ({
26+
t: (key: string) => key,
27+
}),
28+
}))
29+
30+
vi.mock('@langgenius/dify-ui/toast', () => ({
31+
toast: {
32+
error: vi.fn(),
33+
},
34+
}))
35+
36+
vi.mock('@/app/components/base/amplitude', () => ({
37+
trackEvent: vi.fn(),
38+
}))
39+
40+
vi.mock('@/app/components/workflow/hooks', () => ({
41+
useIsChatMode: () => false,
42+
useNodeDataUpdate: () => ({
43+
handleNodeDataUpdate: vi.fn(),
44+
}),
45+
useWorkflow: () => ({
46+
getBeforeNodesInSameBranch: () => [
47+
{
48+
id: 'start',
49+
data: {
50+
type: 'start',
51+
title: 'Start',
52+
variables: [],
53+
},
54+
},
55+
],
56+
getBeforeNodesInSameBranchIncludeParent: () => [
57+
{
58+
id: 'start',
59+
data: {
60+
type: 'start',
61+
title: 'Start',
62+
variables: [],
63+
},
64+
},
65+
],
66+
}),
67+
}))
68+
69+
vi.mock('@/app/components/workflow/hooks/use-inspect-vars-crud', () => ({
70+
default: () => ({
71+
appendNodeInspectVars: vi.fn(),
72+
invalidateSysVarValues: vi.fn(),
73+
invalidateConversationVarValues: vi.fn(),
74+
}),
75+
}))
76+
77+
vi.mock('@/app/components/workflow/store', () => ({
78+
useStore: (selector: (state: typeof mockWorkflowState) => unknown) => selector(mockWorkflowState),
79+
useWorkflowStore: () => ({
80+
getState: () => mockWorkflowState,
81+
}),
82+
}))
83+
84+
vi.mock('reactflow', () => ({
85+
useStoreApi: () => ({
86+
getState: () => ({
87+
getNodes: () => [],
88+
}),
89+
}),
90+
}))
91+
92+
vi.mock('@/service/use-tools', () => ({
93+
useAllBuiltInTools: () => ({ data: [] }),
94+
useAllCustomTools: () => ({ data: [] }),
95+
useAllWorkflowTools: () => ({ data: [] }),
96+
useAllMCPTools: () => ({ data: [] }),
97+
}))
98+
99+
vi.mock('@/service/use-workflow', () => ({
100+
useInvalidLastRun: () => vi.fn(),
101+
}))
102+
103+
vi.mock('@/service/workflow', () => ({
104+
fetchNodeInspectVars: vi.fn(),
105+
getIterationSingleNodeRunUrl: vi.fn(),
106+
getLoopSingleNodeRunUrl: vi.fn(),
107+
singleNodeRun: vi.fn(),
108+
}))
109+
110+
vi.mock('@/service/base', () => ({
111+
post: vi.fn(),
112+
ssePost: vi.fn(),
113+
}))
114+
115+
vi.mock('@/context/event-emitter', () => ({
116+
useEventEmitterContextContext: () => ({
117+
eventEmitter: {
118+
useSubscription: vi.fn(),
119+
},
120+
}),
121+
}))
122+
123+
vi.mock('../components/variable/use-match-schema-type', () => ({
124+
default: () => ({
125+
schemaTypeDefinitions: [],
126+
}),
127+
}))
128+
129+
vi.mock('@/app/components/workflow/nodes/_base/components/variable/use-match-schema-type', () => ({
130+
default: () => ({
131+
schemaTypeDefinitions: [],
132+
}),
133+
}))
134+
135+
vi.mock('@/app/components/workflow/nodes/assigner/default', () => ({
136+
default: {},
137+
}))
138+
vi.mock('@/app/components/workflow/nodes/code/default', () => ({
139+
default: {},
140+
}))
141+
vi.mock('@/app/components/workflow/nodes/document-extractor/default', () => ({
142+
default: {},
143+
}))
144+
vi.mock('@/app/components/workflow/nodes/http/default', () => ({
145+
default: {},
146+
}))
147+
vi.mock('@/app/components/workflow/nodes/human-input/default', () => ({
148+
default: {},
149+
}))
150+
vi.mock('@/app/components/workflow/nodes/if-else/default', () => ({
151+
default: {},
152+
}))
153+
vi.mock('@/app/components/workflow/nodes/iteration/default', () => ({
154+
default: {},
155+
}))
156+
vi.mock('@/app/components/workflow/nodes/knowledge-retrieval/default', () => ({
157+
default: {},
158+
}))
159+
vi.mock('@/app/components/workflow/nodes/llm/default', () => ({
160+
default: {},
161+
}))
162+
vi.mock('@/app/components/workflow/nodes/loop/default', () => ({
163+
default: {},
164+
}))
165+
vi.mock('@/app/components/workflow/nodes/parameter-extractor/default', () => ({
166+
default: {},
167+
}))
168+
vi.mock('@/app/components/workflow/nodes/question-classifier/default', () => ({
169+
default: {},
170+
}))
171+
vi.mock('@/app/components/workflow/nodes/template-transform/default', () => ({
172+
default: {},
173+
}))
174+
vi.mock('@/app/components/workflow/nodes/tool/default', () => ({
175+
default: {},
176+
}))
177+
vi.mock('@/app/components/workflow/nodes/variable-assigner/default', () => ({
178+
default: {},
179+
}))
180+
181+
const renderUseOneStepRun = () => renderHook(() => useOneStepRun({
182+
id: 'if-else-node',
183+
flowId: 'app-id',
184+
flowType: FlowType.appFlow,
185+
data: {
186+
type: BlockEnum.IfElse,
187+
title: 'IF/ELSE',
188+
desc: '',
189+
},
190+
defaultRunInputData: {},
191+
isRunAfterSingleRun: false,
192+
isPaused: false,
193+
}))
194+
195+
describe('useOneStepRun single-run input vars', () => {
196+
beforeEach(() => {
197+
vi.clearAllMocks()
198+
Object.defineProperty(globalThis, 'location', {
199+
value: {
200+
pathname: '/app/test-app/workflow',
201+
},
202+
configurable: true,
203+
})
204+
})
205+
206+
it('uses value_type when the variable cannot be resolved from output vars', () => {
207+
const { result } = renderUseOneStepRun()
208+
209+
const inputs = result.current.toVarInputs([
210+
{
211+
variable: '#start.amount#',
212+
value_selector: ['start', 'amount'],
213+
value_type: VarType.number,
214+
},
215+
])
216+
217+
expect(inputs).toMatchObject([
218+
{
219+
variable: '#start.amount#',
220+
type: InputVarType.number,
221+
},
222+
])
223+
})
224+
225+
it('resolves global system vars by full variable name', () => {
226+
const { result } = renderUseOneStepRun()
227+
228+
const inputs = result.current.varSelectorsToVarInputs([
229+
['sys', 'timestamp'],
230+
])
231+
232+
expect(inputs).toMatchObject([
233+
{
234+
variable: '#sys.timestamp#',
235+
type: InputVarType.number,
236+
},
237+
])
238+
})
239+
})

web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,15 @@ const useOneStepRun = <T>({
178178
}
179179

180180
const allOutputVars = toNodeOutputVars(availableNodes, isChatMode, undefined, undefined, conversationVariables, [], allPluginInfoList, schemaTypeDefinitions)
181-
const targetVar = allOutputVars.find(item => isSystem ? !!item.isStartNode : item.nodeId === valueSelector[0])
181+
if (isSystem) {
182+
const selectorKey = valueSelector.join('.')
183+
return allOutputVars.flatMap(item => item.vars).find(item => item.variable === selectorKey)
184+
}
185+
186+
const targetVar = allOutputVars.find(item => item.nodeId === valueSelector[0])
182187
if (!targetVar)
183188
return undefined
184189

185-
if (isSystem)
186-
return targetVar.vars.find(item => item.variable.split('.')[1] === valueSelector[1])
187-
188190
let curr: any = targetVar.vars
189191
for (let i = 1; i < valueSelector.length; i++) {
190192
const key = valueSelector[i]
@@ -1079,12 +1081,19 @@ const useOneStepRun = <T>({
10791081
const varInputs = variables.filter(item => !isENV(item.value_selector)).map((item) => {
10801082
const originalVar = getVar(item.value_selector)
10811083
if (!originalVar) {
1084+
const fallbackType = item.value_type
1085+
? varTypeToInputVarType(item.value_type, {
1086+
isSelect: !!item.options?.length,
1087+
isParagraph: !!item.isParagraph,
1088+
})
1089+
: InputVarType.textInput
10821090
return {
10831091
label: item.label || item.variable,
10841092
variable: item.variable,
1085-
type: InputVarType.textInput,
1093+
type: fallbackType,
10861094
required: true,
10871095
value_selector: item.value_selector,
1096+
options: item.options,
10881097
}
10891098
}
10901099
return {

0 commit comments

Comments
 (0)