Skip to content

Commit 1a1f3e8

Browse files
committed
removed redundant logic, kept single source of truth for diff
1 parent 8aaaefc commit 1a1f3e8

File tree

4 files changed

+469
-287
lines changed

4 files changed

+469
-287
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/general/components/version-description-modal.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { useCallback, useState } from 'react'
3+
import { useCallback, useRef, useState } from 'react'
44
import {
55
Button,
66
Modal,
@@ -32,14 +32,14 @@ export function VersionDescriptionModal({
3232
versionName,
3333
currentDescription,
3434
}: VersionDescriptionModalProps) {
35-
const initialDescription = currentDescription || ''
36-
const [description, setDescription] = useState(initialDescription)
35+
const initialDescriptionRef = useRef(currentDescription || '')
36+
const [description, setDescription] = useState(initialDescriptionRef.current)
3737
const [showUnsavedChangesAlert, setShowUnsavedChangesAlert] = useState(false)
3838

3939
const updateMutation = useUpdateDeploymentVersion()
4040
const generateMutation = useGenerateVersionDescription()
4141

42-
const hasChanges = description.trim() !== initialDescription.trim()
42+
const hasChanges = description.trim() !== initialDescriptionRef.current.trim()
4343
const isGenerating = generateMutation.isPending
4444

4545
const handleCloseAttempt = useCallback(() => {
@@ -55,9 +55,9 @@ export function VersionDescriptionModal({
5555

5656
const handleDiscardChanges = useCallback(() => {
5757
setShowUnsavedChangesAlert(false)
58-
setDescription(initialDescription)
58+
setDescription(initialDescriptionRef.current)
5959
onOpenChange(false)
60-
}, [initialDescription, onOpenChange])
60+
}, [onOpenChange])
6161

6262
const handleGenerateDescription = useCallback(() => {
6363
generateMutation.mutate({

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/general/components/versions.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export function Versions({
7373
}
7474

7575
const handleSaveRename = (version: number) => {
76+
if (renameMutation.isPending) return
7677
if (!workflowId || !editValue.trim()) {
7778
setEditingVersion(null)
7879
return

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

Lines changed: 301 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import {
77
} from '@sim/testing'
88
import { describe, expect, it } from 'vitest'
99
import type { WorkflowState } from '@/stores/workflows/workflow/types'
10-
import { hasWorkflowChanged } from './compare'
10+
import {
11+
formatDiffSummaryForDescription,
12+
generateWorkflowDiffSummary,
13+
hasWorkflowChanged,
14+
} from './compare'
1115

1216
/**
1317
* Type helper for converting test workflow state to app workflow state.
@@ -2735,3 +2739,299 @@ describe('hasWorkflowChanged', () => {
27352739
})
27362740
})
27372741
})
2742+
2743+
describe('generateWorkflowDiffSummary', () => {
2744+
describe('Basic Cases', () => {
2745+
it.concurrent('should return hasChanges=true when previousState is null', () => {
2746+
const currentState = createWorkflowState({
2747+
blocks: { block1: createBlock('block1') },
2748+
})
2749+
const result = generateWorkflowDiffSummary(currentState, null)
2750+
expect(result.hasChanges).toBe(true)
2751+
expect(result.addedBlocks).toHaveLength(1)
2752+
expect(result.addedBlocks[0].id).toBe('block1')
2753+
})
2754+
2755+
it.concurrent('should return hasChanges=false for identical states', () => {
2756+
const state = createWorkflowState({
2757+
blocks: { block1: createBlock('block1') },
2758+
})
2759+
const result = generateWorkflowDiffSummary(state, state)
2760+
expect(result.hasChanges).toBe(false)
2761+
expect(result.addedBlocks).toHaveLength(0)
2762+
expect(result.removedBlocks).toHaveLength(0)
2763+
expect(result.modifiedBlocks).toHaveLength(0)
2764+
})
2765+
})
2766+
2767+
describe('Block Changes', () => {
2768+
it.concurrent('should detect added blocks', () => {
2769+
const previousState = createWorkflowState({
2770+
blocks: { block1: createBlock('block1') },
2771+
})
2772+
const currentState = createWorkflowState({
2773+
blocks: {
2774+
block1: createBlock('block1'),
2775+
block2: createBlock('block2'),
2776+
},
2777+
})
2778+
const result = generateWorkflowDiffSummary(currentState, previousState)
2779+
expect(result.hasChanges).toBe(true)
2780+
expect(result.addedBlocks).toHaveLength(1)
2781+
expect(result.addedBlocks[0].id).toBe('block2')
2782+
})
2783+
2784+
it.concurrent('should detect removed blocks', () => {
2785+
const previousState = createWorkflowState({
2786+
blocks: {
2787+
block1: createBlock('block1'),
2788+
block2: createBlock('block2'),
2789+
},
2790+
})
2791+
const currentState = createWorkflowState({
2792+
blocks: { block1: createBlock('block1') },
2793+
})
2794+
const result = generateWorkflowDiffSummary(currentState, previousState)
2795+
expect(result.hasChanges).toBe(true)
2796+
expect(result.removedBlocks).toHaveLength(1)
2797+
expect(result.removedBlocks[0].id).toBe('block2')
2798+
})
2799+
2800+
it.concurrent('should detect modified blocks with field changes', () => {
2801+
const previousState = createWorkflowState({
2802+
blocks: {
2803+
block1: createBlock('block1', {
2804+
subBlocks: { model: { id: 'model', type: 'dropdown', value: 'gpt-4o' } },
2805+
}),
2806+
},
2807+
})
2808+
const currentState = createWorkflowState({
2809+
blocks: {
2810+
block1: createBlock('block1', {
2811+
subBlocks: { model: { id: 'model', type: 'dropdown', value: 'claude-sonnet' } },
2812+
}),
2813+
},
2814+
})
2815+
const result = generateWorkflowDiffSummary(currentState, previousState)
2816+
expect(result.hasChanges).toBe(true)
2817+
expect(result.modifiedBlocks).toHaveLength(1)
2818+
expect(result.modifiedBlocks[0].id).toBe('block1')
2819+
expect(result.modifiedBlocks[0].changes.length).toBeGreaterThan(0)
2820+
const modelChange = result.modifiedBlocks[0].changes.find((c) => c.field === 'model')
2821+
expect(modelChange).toBeDefined()
2822+
expect(modelChange?.oldValue).toBe('gpt-4o')
2823+
expect(modelChange?.newValue).toBe('claude-sonnet')
2824+
})
2825+
})
2826+
2827+
describe('Edge Changes', () => {
2828+
it.concurrent('should detect added edges', () => {
2829+
const previousState = createWorkflowState({
2830+
blocks: {
2831+
block1: createBlock('block1'),
2832+
block2: createBlock('block2'),
2833+
},
2834+
edges: [],
2835+
})
2836+
const currentState = createWorkflowState({
2837+
blocks: {
2838+
block1: createBlock('block1'),
2839+
block2: createBlock('block2'),
2840+
},
2841+
edges: [{ id: 'e1', source: 'block1', target: 'block2' }],
2842+
})
2843+
const result = generateWorkflowDiffSummary(currentState, previousState)
2844+
expect(result.hasChanges).toBe(true)
2845+
expect(result.edgeChanges.added).toBe(1)
2846+
expect(result.edgeChanges.removed).toBe(0)
2847+
})
2848+
2849+
it.concurrent('should detect removed edges', () => {
2850+
const previousState = createWorkflowState({
2851+
blocks: {
2852+
block1: createBlock('block1'),
2853+
block2: createBlock('block2'),
2854+
},
2855+
edges: [{ id: 'e1', source: 'block1', target: 'block2' }],
2856+
})
2857+
const currentState = createWorkflowState({
2858+
blocks: {
2859+
block1: createBlock('block1'),
2860+
block2: createBlock('block2'),
2861+
},
2862+
edges: [],
2863+
})
2864+
const result = generateWorkflowDiffSummary(currentState, previousState)
2865+
expect(result.hasChanges).toBe(true)
2866+
expect(result.edgeChanges.added).toBe(0)
2867+
expect(result.edgeChanges.removed).toBe(1)
2868+
})
2869+
})
2870+
2871+
describe('Variable Changes', () => {
2872+
it.concurrent('should detect added variables', () => {
2873+
const previousState = createWorkflowState({
2874+
blocks: { block1: createBlock('block1') },
2875+
variables: {},
2876+
})
2877+
const currentState = createWorkflowState({
2878+
blocks: { block1: createBlock('block1') },
2879+
variables: { var1: { id: 'var1', name: 'test', type: 'string', value: 'hello' } },
2880+
})
2881+
const result = generateWorkflowDiffSummary(currentState, previousState)
2882+
expect(result.hasChanges).toBe(true)
2883+
expect(result.variableChanges.added).toBe(1)
2884+
})
2885+
2886+
it.concurrent('should detect modified variables', () => {
2887+
const previousState = createWorkflowState({
2888+
blocks: { block1: createBlock('block1') },
2889+
variables: { var1: { id: 'var1', name: 'test', type: 'string', value: 'hello' } },
2890+
})
2891+
const currentState = createWorkflowState({
2892+
blocks: { block1: createBlock('block1') },
2893+
variables: { var1: { id: 'var1', name: 'test', type: 'string', value: 'world' } },
2894+
})
2895+
const result = generateWorkflowDiffSummary(currentState, previousState)
2896+
expect(result.hasChanges).toBe(true)
2897+
expect(result.variableChanges.modified).toBe(1)
2898+
})
2899+
})
2900+
2901+
describe('Consistency with hasWorkflowChanged', () => {
2902+
it.concurrent('hasChanges should match hasWorkflowChanged result', () => {
2903+
const state1 = createWorkflowState({
2904+
blocks: { block1: createBlock('block1') },
2905+
})
2906+
const state2 = createWorkflowState({
2907+
blocks: {
2908+
block1: createBlock('block1', {
2909+
subBlocks: { prompt: { id: 'prompt', type: 'long-input', value: 'new value' } },
2910+
}),
2911+
},
2912+
})
2913+
2914+
const diffResult = generateWorkflowDiffSummary(state2, state1)
2915+
const hasChangedResult = hasWorkflowChanged(state2, state1)
2916+
2917+
expect(diffResult.hasChanges).toBe(hasChangedResult)
2918+
})
2919+
2920+
it.concurrent('should return same result as hasWorkflowChanged for no changes', () => {
2921+
const state = createWorkflowState({
2922+
blocks: { block1: createBlock('block1') },
2923+
})
2924+
2925+
const diffResult = generateWorkflowDiffSummary(state, state)
2926+
const hasChangedResult = hasWorkflowChanged(state, state)
2927+
2928+
expect(diffResult.hasChanges).toBe(hasChangedResult)
2929+
expect(diffResult.hasChanges).toBe(false)
2930+
})
2931+
})
2932+
})
2933+
2934+
describe('formatDiffSummaryForDescription', () => {
2935+
it.concurrent('should return no changes message when hasChanges is false', () => {
2936+
const summary = {
2937+
addedBlocks: [],
2938+
removedBlocks: [],
2939+
modifiedBlocks: [],
2940+
edgeChanges: { added: 0, removed: 0 },
2941+
loopChanges: { added: 0, removed: 0 },
2942+
parallelChanges: { added: 0, removed: 0 },
2943+
variableChanges: { added: 0, removed: 0, modified: 0 },
2944+
hasChanges: false,
2945+
}
2946+
const result = formatDiffSummaryForDescription(summary)
2947+
expect(result).toContain('No structural changes')
2948+
})
2949+
2950+
it.concurrent('should format added blocks', () => {
2951+
const summary = {
2952+
addedBlocks: [{ id: 'block1', type: 'agent', name: 'My Agent' }],
2953+
removedBlocks: [],
2954+
modifiedBlocks: [],
2955+
edgeChanges: { added: 0, removed: 0 },
2956+
loopChanges: { added: 0, removed: 0 },
2957+
parallelChanges: { added: 0, removed: 0 },
2958+
variableChanges: { added: 0, removed: 0, modified: 0 },
2959+
hasChanges: true,
2960+
}
2961+
const result = formatDiffSummaryForDescription(summary)
2962+
expect(result).toContain('Added block: My Agent (agent)')
2963+
})
2964+
2965+
it.concurrent('should format removed blocks', () => {
2966+
const summary = {
2967+
addedBlocks: [],
2968+
removedBlocks: [{ id: 'block1', type: 'function', name: 'Old Function' }],
2969+
modifiedBlocks: [],
2970+
edgeChanges: { added: 0, removed: 0 },
2971+
loopChanges: { added: 0, removed: 0 },
2972+
parallelChanges: { added: 0, removed: 0 },
2973+
variableChanges: { added: 0, removed: 0, modified: 0 },
2974+
hasChanges: true,
2975+
}
2976+
const result = formatDiffSummaryForDescription(summary)
2977+
expect(result).toContain('Removed block: Old Function (function)')
2978+
})
2979+
2980+
it.concurrent('should format modified blocks with field changes', () => {
2981+
const summary = {
2982+
addedBlocks: [],
2983+
removedBlocks: [],
2984+
modifiedBlocks: [
2985+
{
2986+
id: 'block1',
2987+
type: 'agent',
2988+
name: 'Agent 1',
2989+
changes: [{ field: 'model', oldValue: 'gpt-4o', newValue: 'claude-sonnet' }],
2990+
},
2991+
],
2992+
edgeChanges: { added: 0, removed: 0 },
2993+
loopChanges: { added: 0, removed: 0 },
2994+
parallelChanges: { added: 0, removed: 0 },
2995+
variableChanges: { added: 0, removed: 0, modified: 0 },
2996+
hasChanges: true,
2997+
}
2998+
const result = formatDiffSummaryForDescription(summary)
2999+
expect(result).toContain('Modified Agent 1')
3000+
expect(result).toContain('model')
3001+
expect(result).toContain('gpt-4o')
3002+
expect(result).toContain('claude-sonnet')
3003+
})
3004+
3005+
it.concurrent('should format edge changes', () => {
3006+
const summary = {
3007+
addedBlocks: [],
3008+
removedBlocks: [],
3009+
modifiedBlocks: [],
3010+
edgeChanges: { added: 2, removed: 1 },
3011+
loopChanges: { added: 0, removed: 0 },
3012+
parallelChanges: { added: 0, removed: 0 },
3013+
variableChanges: { added: 0, removed: 0, modified: 0 },
3014+
hasChanges: true,
3015+
}
3016+
const result = formatDiffSummaryForDescription(summary)
3017+
expect(result).toContain('Added 2 connection(s)')
3018+
expect(result).toContain('Removed 1 connection(s)')
3019+
})
3020+
3021+
it.concurrent('should format variable changes', () => {
3022+
const summary = {
3023+
addedBlocks: [],
3024+
removedBlocks: [],
3025+
modifiedBlocks: [],
3026+
edgeChanges: { added: 0, removed: 0 },
3027+
loopChanges: { added: 0, removed: 0 },
3028+
parallelChanges: { added: 0, removed: 0 },
3029+
variableChanges: { added: 1, removed: 0, modified: 2 },
3030+
hasChanges: true,
3031+
}
3032+
const result = formatDiffSummaryForDescription(summary)
3033+
expect(result).toContain('Variables:')
3034+
expect(result).toContain('1 added')
3035+
expect(result).toContain('2 modified')
3036+
})
3037+
})

0 commit comments

Comments
 (0)