Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,11 @@ export function Editor() {

const trimmedName = editedName.trim()
if (trimmedName && trimmedName !== currentBlock?.name) {
collaborativeUpdateBlockName(currentBlockId, trimmedName)
const result = collaborativeUpdateBlockName(currentBlockId, trimmedName)
if (!result.success) {
// Keep rename mode open on error so user can correct the name
return
}
}
setIsRenaming(false)
}, [currentBlockId, isRenaming, editedName, currentBlock?.name, collaborativeUpdateBlockName])
Expand Down
44 changes: 38 additions & 6 deletions apps/sim/hooks/use-collaborative-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import { TriggerUtils } from '@/lib/workflows/triggers/triggers'
import { useSocket } from '@/app/workspace/providers/socket-provider'
import { getBlock } from '@/blocks'
import { useUndoRedo } from '@/hooks/use-undo-redo'
import { useNotificationStore } from '@/stores/notifications'
import { registerEmitFunctions, useOperationQueue } from '@/stores/operation-queue/store'
import { usePanelEditorStore } from '@/stores/panel/editor/store'
import { useVariablesStore } from '@/stores/panel/variables/store'
import { useUndoRedoStore } from '@/stores/undo-redo'
import { useWorkflowDiffStore } from '@/stores/workflow-diff/store'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import { getUniqueBlockName, mergeSubblockState } from '@/stores/workflows/utils'
import { getUniqueBlockName, mergeSubblockState, normalizeName } from '@/stores/workflows/utils'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
import type { BlockState, Position } from '@/stores/workflows/workflow/types'

Expand Down Expand Up @@ -1016,14 +1017,43 @@ export function useCollaborativeWorkflow() {
)

const collaborativeUpdateBlockName = useCallback(
(id: string, name: string) => {
executeQueuedOperation('update-name', 'block', { id, name }, () => {
const result = workflowStore.updateBlockName(id, name)
(id: string, name: string): { success: boolean; error?: string } => {
const trimmedName = name.trim()
const normalizedNewName = normalizeName(trimmedName)

if (!normalizedNewName) {
logger.error('Cannot rename block to empty name')
useNotificationStore.getState().addNotification({
level: 'error',
message: 'Block name cannot be empty',
workflowId: activeWorkflowId || undefined,
})
return { success: false, error: 'Block name cannot be empty' }
}

const currentBlocks = workflowStore.blocks
const conflictingBlock = Object.entries(currentBlocks).find(
([blockId, block]) => blockId !== id && normalizeName(block.name) === normalizedNewName
)

if (conflictingBlock) {
const conflictName = conflictingBlock[1].name
logger.error(`Cannot rename block to "${trimmedName}" - conflicts with "${conflictName}"`)
useNotificationStore.getState().addNotification({
level: 'error',
message: `Block name "${trimmedName}" already exists`,
workflowId: activeWorkflowId || undefined,
})
return { success: false, error: `Block name "${trimmedName}" already exists` }
}

executeQueuedOperation('update-name', 'block', { id, name: trimmedName }, () => {
const result = workflowStore.updateBlockName(id, trimmedName)

if (result.success && result.changedSubblocks.length > 0) {
logger.info('Emitting cascaded subblock updates from block rename', {
blockId: id,
newName: name,
newName: trimmedName,
updateCount: result.changedSubblocks.length,
})

Expand All @@ -1043,7 +1073,7 @@ export function useCollaborativeWorkflow() {
operation: {
operation: 'subblock-update',
target: 'subblock',
payload: { blockId, subBlockId, value: newValue },
payload: { blockId, subblockId: subBlockId, value: newValue },
},
workflowId: activeWorkflowId || '',
userId: session?.user?.id || 'unknown',
Expand All @@ -1052,6 +1082,8 @@ export function useCollaborativeWorkflow() {
)
}
})

return { success: true }
},
[executeQueuedOperation, workflowStore, addToQueue, activeWorkflowId, session?.user?.id]
)
Expand Down
68 changes: 38 additions & 30 deletions apps/sim/stores/workflows/workflow/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -716,54 +716,62 @@ export const useWorkflowStore = create<WorkflowStore>()(
const workflowValues = subBlockStore.workflowValues[activeWorkflowId] || {}
const updatedWorkflowValues = { ...workflowValues }

// Helper function to recursively update references in any data structure
function updateReferences(value: any, regex: RegExp, replacement: string): any {
// Handle string values
if (typeof value === 'string') {
return regex.test(value) ? value.replace(regex, replacement) : value
}

// Handle arrays
if (Array.isArray(value)) {
return value.map((item) => updateReferences(item, regex, replacement))
}

// Handle objects
if (value !== null && typeof value === 'object') {
const result = { ...value }
for (const key in result) {
result[key] = updateReferences(result[key], regex, replacement)
}
return result
}

// Return unchanged for other types
return value
}

const oldBlockName = normalizeName(oldBlock.name)
const newBlockName = normalizeName(name)
const regex = new RegExp(`<${oldBlockName}\\.`, 'g')

// Loop through blocks
Object.entries(workflowValues).forEach(([blockId, blockValues]) => {
if (blockId === id) return // Skip the block being renamed

let blockHasChanges = false
const updatedBlockValues = { ...blockValues }

// Loop through subblocks and update references
Object.entries(blockValues).forEach(([subBlockId, value]) => {
const oldBlockName = normalizeName(oldBlock.name)
const newBlockName = normalizeName(name)
const regex = new RegExp(`<${oldBlockName}\\.`, 'g')

// Use a recursive function to handle all object types
const updatedValue = updateReferences(value, regex, `<${newBlockName}.`)

// Check if the value actually changed
if (JSON.stringify(updatedValue) !== JSON.stringify(value)) {
updatedWorkflowValues[blockId][subBlockId] = updatedValue
updatedBlockValues[subBlockId] = updatedValue
blockHasChanges = true
changedSubblocks.push({
blockId,
subBlockId,
newValue: updatedValue,
})
}

// Helper function to recursively update references in any data structure
function updateReferences(value: any, regex: RegExp, replacement: string): any {
// Handle string values
if (typeof value === 'string') {
return regex.test(value) ? value.replace(regex, replacement) : value
}

// Handle arrays
if (Array.isArray(value)) {
return value.map((item) => updateReferences(item, regex, replacement))
}

// Handle objects
if (value !== null && typeof value === 'object') {
const result = { ...value }
for (const key in result) {
result[key] = updateReferences(result[key], regex, replacement)
}
return result
}

// Return unchanged for other types
return value
}
})

if (blockHasChanges) {
updatedWorkflowValues[blockId] = updatedBlockValues
}
})

// Update the subblock store with the new values
Expand Down
Loading