mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 15:07:55 -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(
|
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(
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user