fix(deploy): add sockets op for renaming blocks (#2043)

* fix(deploy): add sockets op for renaming blocks

* remove old unused helpers
This commit is contained in:
Waleed
2025-11-18 15:57:49 -08:00
committed by GitHub
parent e0aade85a6
commit a8a693f1ff
6 changed files with 60 additions and 148 deletions

View File

@@ -937,10 +937,42 @@ export function useCollaborativeWorkflow() {
const collaborativeUpdateBlockName = useCallback( const collaborativeUpdateBlockName = useCallback(
(id: string, name: string) => { (id: string, name: string) => {
executeQueuedOperation('update-name', 'block', { id, name }, () => { executeQueuedOperation('update-name', 'block', { id, name }, () => {
workflowStore.updateBlockName(id, name) const result = workflowStore.updateBlockName(id, name)
if (result.success && result.changedSubblocks.length > 0) {
logger.info('Emitting cascaded subblock updates from block rename', {
blockId: id,
newName: name,
updateCount: result.changedSubblocks.length,
})
result.changedSubblocks.forEach(
({
blockId,
subBlockId,
newValue,
}: {
blockId: string
subBlockId: string
newValue: any
}) => {
const operationId = crypto.randomUUID()
addToQueue({
id: operationId,
operation: {
operation: 'subblock-update',
target: 'subblock',
payload: { blockId, subBlockId, value: newValue },
},
workflowId: activeWorkflowId || '',
userId: session?.user?.id || 'unknown',
})
}
)
}
}) })
}, },
[executeQueuedOperation, workflowStore] [executeQueuedOperation, workflowStore, addToQueue, activeWorkflowId, session?.user?.id]
) )
const collaborativeToggleBlockEnabled = useCallback( const collaborativeToggleBlockEnabled = useCallback(

View File

@@ -791,101 +791,6 @@ describe('Database Helpers', () => {
}) })
}) })
describe('migrateWorkflowToNormalizedTables', () => {
const mockJsonState = {
blocks: mockWorkflowState.blocks,
edges: mockWorkflowState.edges,
loops: mockWorkflowState.loops,
parallels: mockWorkflowState.parallels,
lastSaved: Date.now(),
isDeployed: false,
deploymentStatuses: {},
}
it('should successfully migrate workflow from JSON to normalized tables', async () => {
const mockTransaction = vi.fn().mockImplementation(async (callback) => {
const tx = {
select: vi.fn().mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockResolvedValue([]),
}),
}),
delete: vi.fn().mockReturnValue({
where: vi.fn().mockResolvedValue([]),
}),
insert: vi.fn().mockReturnValue({
values: vi.fn().mockResolvedValue([]),
}),
}
return await callback(tx)
})
mockDb.transaction = mockTransaction
const result = await dbHelpers.migrateWorkflowToNormalizedTables(
mockWorkflowId,
mockJsonState
)
expect(result.success).toBe(true)
expect(result.error).toBeUndefined()
})
it('should return error when migration fails', async () => {
const mockTransaction = vi.fn().mockRejectedValue(new Error('Migration failed'))
mockDb.transaction = mockTransaction
const result = await dbHelpers.migrateWorkflowToNormalizedTables(
mockWorkflowId,
mockJsonState
)
expect(result.success).toBe(false)
expect(result.error).toBe('Migration failed')
})
it('should handle missing properties in JSON state gracefully', async () => {
const incompleteJsonState = {
blocks: mockWorkflowState.blocks,
edges: mockWorkflowState.edges,
// Missing loops, parallels, and other properties
}
const mockTransaction = vi.fn().mockImplementation(async (callback) => {
const tx = {
select: vi.fn().mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockResolvedValue([]),
}),
}),
delete: vi.fn().mockReturnValue({
where: vi.fn().mockResolvedValue([]),
}),
insert: vi.fn().mockReturnValue({
values: vi.fn().mockResolvedValue([]),
}),
}
return await callback(tx)
})
mockDb.transaction = mockTransaction
const result = await dbHelpers.migrateWorkflowToNormalizedTables(
mockWorkflowId,
incompleteJsonState
)
expect(result.success).toBe(true)
})
it('should handle null/undefined JSON state', async () => {
const result = await dbHelpers.migrateWorkflowToNormalizedTables(mockWorkflowId, null)
expect(result.success).toBe(false)
expect(result.error).toContain('Cannot read properties')
})
})
describe('error handling and edge cases', () => { describe('error handling and edge cases', () => {
it('should handle very large workflow data', async () => { it('should handle very large workflow data', async () => {
const largeWorkflowState: WorkflowState = { const largeWorkflowState: WorkflowState = {

View File

@@ -402,36 +402,6 @@ export async function workflowExistsInNormalizedTables(workflowId: string): Prom
} }
} }
/**
* Migrate a workflow from JSON blob to normalized tables
*/
export async function migrateWorkflowToNormalizedTables(
workflowId: string,
jsonState: any
): Promise<{ success: boolean; error?: string }> {
try {
// Convert JSON state to WorkflowState format
// Only include fields that are actually persisted to normalized tables
const workflowState: WorkflowState = {
blocks: jsonState.blocks || {},
edges: jsonState.edges || [],
loops: jsonState.loops || {},
parallels: jsonState.parallels || {},
lastSaved: jsonState.lastSaved,
isDeployed: jsonState.isDeployed,
deployedAt: jsonState.deployedAt,
}
return await saveWorkflowToNormalizedTables(workflowId, workflowState)
} catch (error) {
logger.error(`Error migrating workflow ${workflowId} to normalized tables:`, error)
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
}
}
}
/** /**
* Deploy a workflow by creating a new deployment version * Deploy a workflow by creating a new deployment version
*/ */

View File

@@ -587,7 +587,7 @@ describe('workflow store', () => {
const result = updateBlockName('block1', 'Data Processor') const result = updateBlockName('block1', 'Data Processor')
expect(result).toBe(true) expect(result.success).toBe(true)
const state = useWorkflowStore.getState() const state = useWorkflowStore.getState()
expect(state.blocks.block1.name).toBe('Data Processor') expect(state.blocks.block1.name).toBe('Data Processor')
@@ -598,7 +598,7 @@ describe('workflow store', () => {
const result = updateBlockName('block1', 'column ad') const result = updateBlockName('block1', 'column ad')
expect(result).toBe(true) expect(result.success).toBe(true)
const state = useWorkflowStore.getState() const state = useWorkflowStore.getState()
expect(state.blocks.block1.name).toBe('column ad') expect(state.blocks.block1.name).toBe('column ad')
@@ -609,7 +609,7 @@ describe('workflow store', () => {
const result = updateBlockName('block2', 'Column AD') const result = updateBlockName('block2', 'Column AD')
expect(result).toBe(false) expect(result.success).toBe(false)
const state = useWorkflowStore.getState() const state = useWorkflowStore.getState()
expect(state.blocks.block2.name).toBe('Employee Length') expect(state.blocks.block2.name).toBe('Employee Length')
@@ -620,7 +620,7 @@ describe('workflow store', () => {
const result = updateBlockName('block2', 'columnad') const result = updateBlockName('block2', 'columnad')
expect(result).toBe(false) expect(result.success).toBe(false)
const state = useWorkflowStore.getState() const state = useWorkflowStore.getState()
expect(state.blocks.block2.name).toBe('Employee Length') expect(state.blocks.block2.name).toBe('Employee Length')
@@ -631,7 +631,7 @@ describe('workflow store', () => {
const result = updateBlockName('block3', 'employee length') const result = updateBlockName('block3', 'employee length')
expect(result).toBe(false) expect(result.success).toBe(false)
const state = useWorkflowStore.getState() const state = useWorkflowStore.getState()
expect(state.blocks.block3.name).toBe('Start') expect(state.blocks.block3.name).toBe('Start')
@@ -641,10 +641,10 @@ describe('workflow store', () => {
const { updateBlockName } = useWorkflowStore.getState() const { updateBlockName } = useWorkflowStore.getState()
const result1 = updateBlockName('block1', '') const result1 = updateBlockName('block1', '')
expect(result1).toBe(true) expect(result1.success).toBe(true)
const result2 = updateBlockName('block2', ' ') const result2 = updateBlockName('block2', ' ')
expect(result2).toBe(true) expect(result2.success).toBe(true)
const state = useWorkflowStore.getState() const state = useWorkflowStore.getState()
expect(state.blocks.block1.name).toBe('') expect(state.blocks.block1.name).toBe('')
@@ -656,7 +656,7 @@ describe('workflow store', () => {
const result = updateBlockName('nonexistent', 'New Name') const result = updateBlockName('nonexistent', 'New Name')
expect(result).toBe(false) expect(result.success).toBe(false)
}) })
it('should handle complex normalization cases correctly', () => { it('should handle complex normalization cases correctly', () => {
@@ -673,11 +673,11 @@ describe('workflow store', () => {
for (const name of conflictingNames) { for (const name of conflictingNames) {
const result = updateBlockName('block2', name) const result = updateBlockName('block2', name)
expect(result).toBe(false) expect(result.success).toBe(false)
} }
const result = updateBlockName('block2', 'Unique Name') const result = updateBlockName('block2', 'Unique Name')
expect(result).toBe(true) expect(result.success).toBe(true)
const state = useWorkflowStore.getState() const state = useWorkflowStore.getState()
expect(state.blocks.block2.name).toBe('Unique Name') expect(state.blocks.block2.name).toBe('Unique Name')

View File

@@ -626,7 +626,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
updateBlockName: (id: string, name: string) => { updateBlockName: (id: string, name: string) => {
const oldBlock = get().blocks[id] const oldBlock = get().blocks[id]
if (!oldBlock) return false if (!oldBlock) return { success: false, changedSubblocks: [] }
// Check for normalized name collisions // Check for normalized name collisions
const normalizedNewName = normalizeBlockName(name) const normalizedNewName = normalizeBlockName(name)
@@ -646,7 +646,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
logger.error( logger.error(
`Cannot rename block to "${name}" - another block "${conflictingBlock[1].name}" already uses the normalized name "${normalizedNewName}"` `Cannot rename block to "${name}" - another block "${conflictingBlock[1].name}" already uses the normalized name "${normalizedNewName}"`
) )
return false return { success: false, changedSubblocks: [] }
} }
// Create a new state with the updated block name // Create a new state with the updated block name
@@ -666,12 +666,13 @@ export const useWorkflowStore = create<WorkflowStore>()(
// Update references in subblock store // Update references in subblock store
const subBlockStore = useSubBlockStore.getState() const subBlockStore = useSubBlockStore.getState()
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
const changedSubblocks: Array<{ blockId: string; subBlockId: string; newValue: any }> = []
if (activeWorkflowId) { if (activeWorkflowId) {
// Get the workflow values for the active workflow // Get the workflow values for the active workflow
// workflowValues: {[block_id]:{[subblock_id]:[subblock_value]}} // workflowValues: {[block_id]:{[subblock_id]:[subblock_value]}}
const workflowValues = subBlockStore.workflowValues[activeWorkflowId] || {} const workflowValues = subBlockStore.workflowValues[activeWorkflowId] || {}
const updatedWorkflowValues = { ...workflowValues } const updatedWorkflowValues = { ...workflowValues }
const changedSubblocks: Array<{ blockId: string; subBlockId: string; newValue: any }> = []
// Loop through blocks // Loop through blocks
Object.entries(workflowValues).forEach(([blockId, blockValues]) => { Object.entries(workflowValues).forEach(([blockId, blockValues]) => {
@@ -730,19 +731,17 @@ export const useWorkflowStore = create<WorkflowStore>()(
[activeWorkflowId]: updatedWorkflowValues, [activeWorkflowId]: updatedWorkflowValues,
}, },
}) })
// Store changed subblocks for collaborative sync
if (changedSubblocks.length > 0) {
// Store the changed subblocks for the collaborative function to pick up
;(window as any).__pendingSubblockUpdates = changedSubblocks
}
} }
set(newState) set(newState)
get().updateLastSaved() get().updateLastSaved()
// Note: Socket.IO handles real-time sync automatically // Note: Socket.IO handles real-time sync automatically
return true // Return both success status and changed subblocks for collaborative sync
return {
success: true,
changedSubblocks,
}
}, },
setBlockAdvancedMode: (id: string, advancedMode: boolean) => { setBlockAdvancedMode: (id: string, advancedMode: boolean) => {

View File

@@ -198,7 +198,13 @@ export interface WorkflowActions {
toggleBlockEnabled: (id: string) => void toggleBlockEnabled: (id: string) => void
duplicateBlock: (id: string) => void duplicateBlock: (id: string) => void
toggleBlockHandles: (id: string) => void toggleBlockHandles: (id: string) => void
updateBlockName: (id: string, name: string) => boolean updateBlockName: (
id: string,
name: string
) => {
success: boolean
changedSubblocks: Array<{ blockId: string; subBlockId: string; newValue: any }>
}
setBlockAdvancedMode: (id: string, advancedMode: boolean) => void setBlockAdvancedMode: (id: string, advancedMode: boolean) => void
setBlockTriggerMode: (id: string, triggerMode: boolean) => void setBlockTriggerMode: (id: string, triggerMode: boolean) => void
updateBlockLayoutMetrics: (id: string, dimensions: { width: number; height: number }) => void updateBlockLayoutMetrics: (id: string, dimensions: { width: number; height: number }) => void