mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-08 22:48:14 -05:00
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:
@@ -937,10 +937,42 @@ export function useCollaborativeWorkflow() {
|
||||
const collaborativeUpdateBlockName = useCallback(
|
||||
(id: string, name: string) => {
|
||||
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(
|
||||
|
||||
@@ -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', () => {
|
||||
it('should handle very large workflow data', async () => {
|
||||
const largeWorkflowState: WorkflowState = {
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -587,7 +587,7 @@ describe('workflow store', () => {
|
||||
|
||||
const result = updateBlockName('block1', 'Data Processor')
|
||||
|
||||
expect(result).toBe(true)
|
||||
expect(result.success).toBe(true)
|
||||
|
||||
const state = useWorkflowStore.getState()
|
||||
expect(state.blocks.block1.name).toBe('Data Processor')
|
||||
@@ -598,7 +598,7 @@ describe('workflow store', () => {
|
||||
|
||||
const result = updateBlockName('block1', 'column ad')
|
||||
|
||||
expect(result).toBe(true)
|
||||
expect(result.success).toBe(true)
|
||||
|
||||
const state = useWorkflowStore.getState()
|
||||
expect(state.blocks.block1.name).toBe('column ad')
|
||||
@@ -609,7 +609,7 @@ describe('workflow store', () => {
|
||||
|
||||
const result = updateBlockName('block2', 'Column AD')
|
||||
|
||||
expect(result).toBe(false)
|
||||
expect(result.success).toBe(false)
|
||||
|
||||
const state = useWorkflowStore.getState()
|
||||
expect(state.blocks.block2.name).toBe('Employee Length')
|
||||
@@ -620,7 +620,7 @@ describe('workflow store', () => {
|
||||
|
||||
const result = updateBlockName('block2', 'columnad')
|
||||
|
||||
expect(result).toBe(false)
|
||||
expect(result.success).toBe(false)
|
||||
|
||||
const state = useWorkflowStore.getState()
|
||||
expect(state.blocks.block2.name).toBe('Employee Length')
|
||||
@@ -631,7 +631,7 @@ describe('workflow store', () => {
|
||||
|
||||
const result = updateBlockName('block3', 'employee length')
|
||||
|
||||
expect(result).toBe(false)
|
||||
expect(result.success).toBe(false)
|
||||
|
||||
const state = useWorkflowStore.getState()
|
||||
expect(state.blocks.block3.name).toBe('Start')
|
||||
@@ -641,10 +641,10 @@ describe('workflow store', () => {
|
||||
const { updateBlockName } = useWorkflowStore.getState()
|
||||
|
||||
const result1 = updateBlockName('block1', '')
|
||||
expect(result1).toBe(true)
|
||||
expect(result1.success).toBe(true)
|
||||
|
||||
const result2 = updateBlockName('block2', ' ')
|
||||
expect(result2).toBe(true)
|
||||
expect(result2.success).toBe(true)
|
||||
|
||||
const state = useWorkflowStore.getState()
|
||||
expect(state.blocks.block1.name).toBe('')
|
||||
@@ -656,7 +656,7 @@ describe('workflow store', () => {
|
||||
|
||||
const result = updateBlockName('nonexistent', 'New Name')
|
||||
|
||||
expect(result).toBe(false)
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
|
||||
it('should handle complex normalization cases correctly', () => {
|
||||
@@ -673,11 +673,11 @@ describe('workflow store', () => {
|
||||
|
||||
for (const name of conflictingNames) {
|
||||
const result = updateBlockName('block2', name)
|
||||
expect(result).toBe(false)
|
||||
expect(result.success).toBe(false)
|
||||
}
|
||||
|
||||
const result = updateBlockName('block2', 'Unique Name')
|
||||
expect(result).toBe(true)
|
||||
expect(result.success).toBe(true)
|
||||
|
||||
const state = useWorkflowStore.getState()
|
||||
expect(state.blocks.block2.name).toBe('Unique Name')
|
||||
|
||||
@@ -626,7 +626,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
|
||||
updateBlockName: (id: string, name: string) => {
|
||||
const oldBlock = get().blocks[id]
|
||||
if (!oldBlock) return false
|
||||
if (!oldBlock) return { success: false, changedSubblocks: [] }
|
||||
|
||||
// Check for normalized name collisions
|
||||
const normalizedNewName = normalizeBlockName(name)
|
||||
@@ -646,7 +646,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
logger.error(
|
||||
`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
|
||||
@@ -666,12 +666,13 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
// Update references in subblock store
|
||||
const subBlockStore = useSubBlockStore.getState()
|
||||
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
|
||||
const changedSubblocks: Array<{ blockId: string; subBlockId: string; newValue: any }> = []
|
||||
|
||||
if (activeWorkflowId) {
|
||||
// Get the workflow values for the active workflow
|
||||
// workflowValues: {[block_id]:{[subblock_id]:[subblock_value]}}
|
||||
const workflowValues = subBlockStore.workflowValues[activeWorkflowId] || {}
|
||||
const updatedWorkflowValues = { ...workflowValues }
|
||||
const changedSubblocks: Array<{ blockId: string; subBlockId: string; newValue: any }> = []
|
||||
|
||||
// Loop through blocks
|
||||
Object.entries(workflowValues).forEach(([blockId, blockValues]) => {
|
||||
@@ -730,19 +731,17 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
[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)
|
||||
get().updateLastSaved()
|
||||
// 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) => {
|
||||
|
||||
@@ -198,7 +198,13 @@ export interface WorkflowActions {
|
||||
toggleBlockEnabled: (id: string) => void
|
||||
duplicateBlock: (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
|
||||
setBlockTriggerMode: (id: string, triggerMode: boolean) => void
|
||||
updateBlockLayoutMetrics: (id: string, dimensions: { width: number; height: number }) => void
|
||||
|
||||
Reference in New Issue
Block a user