Skip to content

Commit 8806508

Browse files
authored
fix(deploy): fix workflow change detection to handle old variable reference format (#2623)
1 parent 1c626df commit 8806508

File tree

3 files changed

+214
-5
lines changed

3 files changed

+214
-5
lines changed

apps/sim/lib/workflows/comparison/compare.test.ts

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2523,4 +2523,181 @@ describe('hasWorkflowChanged', () => {
25232523
}
25242524
)
25252525
})
2526+
2527+
describe('Variables (UI-only fields should not trigger change)', () => {
2528+
it.concurrent('should not detect change when validationError differs', () => {
2529+
const deployedState = createWorkflowState({
2530+
blocks: {
2531+
block1: createBlock('block1'),
2532+
},
2533+
})
2534+
;(deployedState as any).variables = {
2535+
var1: {
2536+
id: 'var1',
2537+
workflowId: 'workflow1',
2538+
name: 'myVar',
2539+
type: 'plain',
2540+
value: 'test',
2541+
},
2542+
}
2543+
2544+
const currentState = createWorkflowState({
2545+
blocks: {
2546+
block1: createBlock('block1'),
2547+
},
2548+
})
2549+
;(currentState as any).variables = {
2550+
var1: {
2551+
id: 'var1',
2552+
workflowId: 'workflow1',
2553+
name: 'myVar',
2554+
type: 'plain',
2555+
value: 'test',
2556+
validationError: undefined,
2557+
},
2558+
}
2559+
2560+
expect(hasWorkflowChanged(currentState, deployedState)).toBe(false)
2561+
})
2562+
2563+
it.concurrent('should not detect change when validationError has value vs missing', () => {
2564+
const deployedState = createWorkflowState({
2565+
blocks: {
2566+
block1: createBlock('block1'),
2567+
},
2568+
})
2569+
;(deployedState as any).variables = {
2570+
var1: {
2571+
id: 'var1',
2572+
workflowId: 'workflow1',
2573+
name: 'myVar',
2574+
type: 'number',
2575+
value: 'invalid',
2576+
},
2577+
}
2578+
2579+
const currentState = createWorkflowState({
2580+
blocks: {
2581+
block1: createBlock('block1'),
2582+
},
2583+
})
2584+
;(currentState as any).variables = {
2585+
var1: {
2586+
id: 'var1',
2587+
workflowId: 'workflow1',
2588+
name: 'myVar',
2589+
type: 'number',
2590+
value: 'invalid',
2591+
validationError: 'Not a valid number',
2592+
},
2593+
}
2594+
2595+
expect(hasWorkflowChanged(currentState, deployedState)).toBe(false)
2596+
})
2597+
2598+
it.concurrent('should detect change when variable value differs', () => {
2599+
const deployedState = createWorkflowState({
2600+
blocks: {
2601+
block1: createBlock('block1'),
2602+
},
2603+
})
2604+
;(deployedState as any).variables = {
2605+
var1: {
2606+
id: 'var1',
2607+
workflowId: 'workflow1',
2608+
name: 'myVar',
2609+
type: 'plain',
2610+
value: 'old value',
2611+
},
2612+
}
2613+
2614+
const currentState = createWorkflowState({
2615+
blocks: {
2616+
block1: createBlock('block1'),
2617+
},
2618+
})
2619+
;(currentState as any).variables = {
2620+
var1: {
2621+
id: 'var1',
2622+
workflowId: 'workflow1',
2623+
name: 'myVar',
2624+
type: 'plain',
2625+
value: 'new value',
2626+
validationError: undefined,
2627+
},
2628+
}
2629+
2630+
expect(hasWorkflowChanged(currentState, deployedState)).toBe(true)
2631+
})
2632+
2633+
it.concurrent('should detect change when variable is added', () => {
2634+
const deployedState = createWorkflowState({
2635+
blocks: {
2636+
block1: createBlock('block1'),
2637+
},
2638+
})
2639+
;(deployedState as any).variables = {}
2640+
2641+
const currentState = createWorkflowState({
2642+
blocks: {
2643+
block1: createBlock('block1'),
2644+
},
2645+
})
2646+
;(currentState as any).variables = {
2647+
var1: {
2648+
id: 'var1',
2649+
workflowId: 'workflow1',
2650+
name: 'myVar',
2651+
type: 'plain',
2652+
value: 'test',
2653+
},
2654+
}
2655+
2656+
expect(hasWorkflowChanged(currentState, deployedState)).toBe(true)
2657+
})
2658+
2659+
it.concurrent('should detect change when variable is removed', () => {
2660+
const deployedState = createWorkflowState({
2661+
blocks: {
2662+
block1: createBlock('block1'),
2663+
},
2664+
})
2665+
;(deployedState as any).variables = {
2666+
var1: {
2667+
id: 'var1',
2668+
workflowId: 'workflow1',
2669+
name: 'myVar',
2670+
type: 'plain',
2671+
value: 'test',
2672+
},
2673+
}
2674+
2675+
const currentState = createWorkflowState({
2676+
blocks: {
2677+
block1: createBlock('block1'),
2678+
},
2679+
})
2680+
;(currentState as any).variables = {}
2681+
2682+
expect(hasWorkflowChanged(currentState, deployedState)).toBe(true)
2683+
})
2684+
2685+
it.concurrent('should not detect change when empty array vs empty object', () => {
2686+
const deployedState = createWorkflowState({
2687+
blocks: {
2688+
block1: createBlock('block1'),
2689+
},
2690+
})
2691+
;(deployedState as any).variables = []
2692+
2693+
const currentState = createWorkflowState({
2694+
blocks: {
2695+
block1: createBlock('block1'),
2696+
},
2697+
})
2698+
;(currentState as any).variables = {}
2699+
2700+
expect(hasWorkflowChanged(currentState, deployedState)).toBe(false)
2701+
})
2702+
})
25262703
})

apps/sim/lib/workflows/comparison/compare.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import {
66
normalizeLoop,
77
normalizeParallel,
88
normalizeValue,
9+
normalizeVariables,
910
sanitizeInputFormat,
1011
sanitizeTools,
12+
sanitizeVariable,
1113
sortEdges,
1214
} from './normalize'
1315

@@ -228,11 +230,17 @@ export function hasWorkflowChanged(
228230
}
229231

230232
// 6. Compare variables
231-
const currentVariables = (currentState as any).variables || {}
232-
const deployedVariables = (deployedState as any).variables || {}
233-
234-
const normalizedCurrentVars = normalizeValue(currentVariables)
235-
const normalizedDeployedVars = normalizeValue(deployedVariables)
233+
const currentVariables = normalizeVariables((currentState as any).variables)
234+
const deployedVariables = normalizeVariables((deployedState as any).variables)
235+
236+
const normalizedCurrentVars = normalizeValue(
237+
Object.fromEntries(Object.entries(currentVariables).map(([id, v]) => [id, sanitizeVariable(v)]))
238+
)
239+
const normalizedDeployedVars = normalizeValue(
240+
Object.fromEntries(
241+
Object.entries(deployedVariables).map(([id, v]) => [id, sanitizeVariable(v)])
242+
)
243+
)
236244

237245
if (normalizedStringify(normalizedCurrentVars) !== normalizedStringify(normalizedDeployedVars)) {
238246
return true

apps/sim/lib/workflows/comparison/normalize.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,30 @@ export function sanitizeTools(tools: any[] | undefined): any[] {
8888
return tools.map(({ isExpanded, ...rest }) => rest)
8989
}
9090

91+
/**
92+
* Sanitizes a variable by removing UI-only fields like validationError
93+
* @param variable - The variable object
94+
* @returns Sanitized variable object
95+
*/
96+
export function sanitizeVariable(variable: any): any {
97+
if (!variable || typeof variable !== 'object') return variable
98+
const { validationError, ...rest } = variable
99+
return rest
100+
}
101+
102+
/**
103+
* Normalizes the variables structure to always be an object.
104+
* Handles legacy data where variables might be stored as an empty array.
105+
* @param variables - The variables to normalize
106+
* @returns A normalized variables object
107+
*/
108+
export function normalizeVariables(variables: any): Record<string, any> {
109+
if (!variables) return {}
110+
if (Array.isArray(variables)) return {}
111+
if (typeof variables !== 'object') return {}
112+
return variables
113+
}
114+
91115
/**
92116
* Sanitizes inputFormat array by removing UI-only fields like value and collapsed
93117
* @param inputFormat - Array of input format configurations

0 commit comments

Comments
 (0)