Skip to content

Commit 60c4668

Browse files
authored
fix(naming): prevent identical normalized block names (#1105)
1 parent a268fb7 commit 60c4668

File tree

3 files changed

+159
-2
lines changed

3 files changed

+159
-2
lines changed

apps/sim/stores/workflows/workflow/store.test.ts

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,4 +603,133 @@ describe('workflow store', () => {
603603
expect(childBlock.data?.extent).toBe('parent')
604604
})
605605
})
606+
607+
describe('updateBlockName', () => {
608+
beforeEach(() => {
609+
useWorkflowStore.setState({
610+
blocks: {},
611+
edges: [],
612+
loops: {},
613+
parallels: {},
614+
})
615+
616+
const { addBlock } = useWorkflowStore.getState()
617+
618+
addBlock('block1', 'agent', 'Column AD', { x: 0, y: 0 })
619+
addBlock('block2', 'function', 'Employee Length', { x: 100, y: 0 })
620+
addBlock('block3', 'trigger', 'Start', { x: 200, y: 0 })
621+
})
622+
623+
it('should have test blocks set up correctly', () => {
624+
const state = useWorkflowStore.getState()
625+
626+
expect(state.blocks.block1).toBeDefined()
627+
expect(state.blocks.block1.name).toBe('Column AD')
628+
expect(state.blocks.block2).toBeDefined()
629+
expect(state.blocks.block2.name).toBe('Employee Length')
630+
expect(state.blocks.block3).toBeDefined()
631+
expect(state.blocks.block3.name).toBe('Start')
632+
})
633+
634+
it('should successfully rename a block when no conflicts exist', () => {
635+
const { updateBlockName } = useWorkflowStore.getState()
636+
637+
const result = updateBlockName('block1', 'Data Processor')
638+
639+
expect(result).toBe(true)
640+
641+
const state = useWorkflowStore.getState()
642+
expect(state.blocks.block1.name).toBe('Data Processor')
643+
})
644+
645+
it('should allow renaming a block to a different case/spacing of its current name', () => {
646+
const { updateBlockName } = useWorkflowStore.getState()
647+
648+
const result = updateBlockName('block1', 'column ad')
649+
650+
expect(result).toBe(true)
651+
652+
const state = useWorkflowStore.getState()
653+
expect(state.blocks.block1.name).toBe('column ad')
654+
})
655+
656+
it('should prevent renaming when another block has the same normalized name', () => {
657+
const { updateBlockName } = useWorkflowStore.getState()
658+
659+
const result = updateBlockName('block2', 'Column AD')
660+
661+
expect(result).toBe(false)
662+
663+
const state = useWorkflowStore.getState()
664+
expect(state.blocks.block2.name).toBe('Employee Length')
665+
})
666+
667+
it('should prevent renaming when another block has a name that normalizes to the same value', () => {
668+
const { updateBlockName } = useWorkflowStore.getState()
669+
670+
const result = updateBlockName('block2', 'columnad')
671+
672+
expect(result).toBe(false)
673+
674+
const state = useWorkflowStore.getState()
675+
expect(state.blocks.block2.name).toBe('Employee Length')
676+
})
677+
678+
it('should prevent renaming when another block has a similar name with different spacing', () => {
679+
const { updateBlockName } = useWorkflowStore.getState()
680+
681+
const result = updateBlockName('block3', 'employee length')
682+
683+
expect(result).toBe(false)
684+
685+
const state = useWorkflowStore.getState()
686+
expect(state.blocks.block3.name).toBe('Start')
687+
})
688+
689+
it('should handle edge cases with empty or whitespace-only names', () => {
690+
const { updateBlockName } = useWorkflowStore.getState()
691+
692+
const result1 = updateBlockName('block1', '')
693+
expect(result1).toBe(true)
694+
695+
const result2 = updateBlockName('block2', ' ')
696+
expect(result2).toBe(true)
697+
698+
const state = useWorkflowStore.getState()
699+
expect(state.blocks.block1.name).toBe('')
700+
expect(state.blocks.block2.name).toBe(' ')
701+
})
702+
703+
it('should return false when trying to rename a non-existent block', () => {
704+
const { updateBlockName } = useWorkflowStore.getState()
705+
706+
const result = updateBlockName('nonexistent', 'New Name')
707+
708+
expect(result).toBe(false)
709+
})
710+
711+
it('should handle complex normalization cases correctly', () => {
712+
const { updateBlockName } = useWorkflowStore.getState()
713+
714+
const conflictingNames = [
715+
'column ad',
716+
'COLUMN AD',
717+
'Column AD',
718+
'columnad',
719+
'ColumnAD',
720+
'COLUMNAD',
721+
]
722+
723+
for (const name of conflictingNames) {
724+
const result = updateBlockName('block2', name)
725+
expect(result).toBe(false)
726+
}
727+
728+
const result = updateBlockName('block2', 'Unique Name')
729+
expect(result).toBe(true)
730+
731+
const state = useWorkflowStore.getState()
732+
expect(state.blocks.block2.name).toBe('Unique Name')
733+
})
734+
})
606735
})

apps/sim/stores/workflows/workflow/store.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,33 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
601601

602602
updateBlockName: (id: string, name: string) => {
603603
const oldBlock = get().blocks[id]
604-
if (!oldBlock) return
604+
if (!oldBlock) return false
605+
606+
// Helper function to normalize block names (same as resolver)
607+
const normalizeBlockName = (blockName: string): string => {
608+
return blockName.toLowerCase().replace(/\s+/g, '')
609+
}
610+
611+
// Check for normalized name collisions
612+
const normalizedNewName = normalizeBlockName(name)
613+
const currentBlocks = get().blocks
614+
615+
// Find any other block with the same normalized name
616+
const conflictingBlock = Object.entries(currentBlocks).find(([blockId, block]) => {
617+
return (
618+
blockId !== id && // Different block
619+
block.name && // Has a name
620+
normalizeBlockName(block.name) === normalizedNewName // Same normalized name
621+
)
622+
})
623+
624+
if (conflictingBlock) {
625+
// Don't allow the rename - another block already uses this normalized name
626+
logger.error(
627+
`Cannot rename block to "${name}" - another block "${conflictingBlock[1].name}" already uses the normalized name "${normalizedNewName}"`
628+
)
629+
return false
630+
}
605631

606632
// Create a new state with the updated block name
607633
const newState = {
@@ -696,6 +722,8 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
696722
pushHistory(set, get, newState, `${name} block name updated`)
697723
get().updateLastSaved()
698724
// Note: Socket.IO handles real-time sync automatically
725+
726+
return true
699727
},
700728

701729
toggleBlockWide: (id: string) => {

apps/sim/stores/workflows/workflow/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ export interface WorkflowActions {
183183
toggleBlockEnabled: (id: string) => void
184184
duplicateBlock: (id: string) => void
185185
toggleBlockHandles: (id: string) => void
186-
updateBlockName: (id: string, name: string) => void
186+
updateBlockName: (id: string, name: string) => boolean
187187
toggleBlockWide: (id: string) => void
188188
setBlockWide: (id: string, isWide: boolean) => void
189189
setBlockAdvancedMode: (id: string, advancedMode: boolean) => void

0 commit comments

Comments
 (0)