Skip to content

Commit e138cd1

Browse files
fix: restore batch parent auto-connect sync
1 parent 568f65c commit e138cd1

File tree

3 files changed

+164
-2
lines changed

3 files changed

+164
-2
lines changed

apps/sim/socket/database/operations.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1630,6 +1630,7 @@ async function handleBlocksOperationTx(
16301630
)
16311631

16321632
const allRemovedEdgeIds: string[] = []
1633+
const allAddedEdges: NonNullable<OperationResult['addedEdges']> = []
16331634
const applicableUpdates: Array<(typeof updates)[number]> = []
16341635

16351636
for (const update of updates) {
@@ -1715,6 +1716,11 @@ async function handleBlocksOperationTx(
17151716
)
17161717
allRemovedEdgeIds.push(...removedEdgeIds)
17171718

1719+
if (parentId) {
1720+
const autoConnect = await autoConnectToContainerStart(tx, workflowId, id, parentId)
1721+
allAddedEdges.push(...autoConnect.edges)
1722+
}
1723+
17181724
// If the block now has a parent, update the new parent's subflow node list
17191725
if (parentId) {
17201726
await updateSubflowNodeList(tx, workflowId, parentId)
@@ -1726,7 +1732,7 @@ async function handleBlocksOperationTx(
17261732
}
17271733

17281734
logger.debug(`Batch updated parent for ${updates.length} blocks`)
1729-
return { removedEdgeIds: allRemovedEdgeIds }
1735+
return { removedEdgeIds: allRemovedEdgeIds, addedEdges: allAddedEdges }
17301736
}
17311737

17321738
default:
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
5+
import { beforeEach, describe, expect, it, vi } from 'vitest'
6+
7+
const { mockPersistWorkflowOperation, mockCheckRolePermission, mockWorkflowOperationParse } =
8+
vi.hoisted(() => ({
9+
mockPersistWorkflowOperation: vi.fn(),
10+
mockCheckRolePermission: vi.fn(),
11+
mockWorkflowOperationParse: vi.fn((data) => data),
12+
}))
13+
14+
vi.mock('@/socket/database/operations', () => ({
15+
enrichBatchAddBlocksPayload: vi.fn(),
16+
persistWorkflowOperation: mockPersistWorkflowOperation,
17+
}))
18+
19+
vi.mock('@/socket/middleware/permissions', () => ({
20+
checkRolePermission: mockCheckRolePermission,
21+
}))
22+
23+
vi.mock('@/socket/validation/schemas', () => ({
24+
WorkflowOperationSchema: {
25+
parse: mockWorkflowOperationParse,
26+
},
27+
}))
28+
29+
import { BLOCKS_OPERATIONS, EDGES_OPERATIONS, OPERATION_TARGETS } from '@/socket/constants'
30+
import { setupOperationsHandlers } from '@/socket/handlers/operations'
31+
32+
describe('setupOperationsHandlers', () => {
33+
beforeEach(() => {
34+
vi.clearAllMocks()
35+
mockCheckRolePermission.mockReturnValue({ allowed: true })
36+
})
37+
38+
it('broadcasts batch parent auto-connect edges to the whole workflow', async () => {
39+
mockPersistWorkflowOperation.mockResolvedValue({
40+
removedEdgeIds: ['edge-removed'],
41+
addedEdges: [
42+
{
43+
id: 'edge-added',
44+
source: 'loop-1',
45+
target: 'block-1',
46+
sourceHandle: 'loop-start-source',
47+
targetHandle: 'target',
48+
type: 'workflowEdge',
49+
},
50+
],
51+
})
52+
53+
const socketEmit = vi.fn()
54+
const socketRoomEmit = vi.fn()
55+
const emitToWorkflow = vi.fn()
56+
const socketHandlers = new Map<string, (data: unknown) => Promise<void>>()
57+
58+
const socket = {
59+
id: 'socket-1',
60+
on: vi.fn((event: string, handler: (data: unknown) => Promise<void>) => {
61+
socketHandlers.set(event, handler)
62+
}),
63+
emit: socketEmit,
64+
to: vi.fn(() => ({
65+
emit: socketRoomEmit,
66+
})),
67+
}
68+
69+
const roomManager = {
70+
io: {} as never,
71+
initialize: vi.fn(),
72+
isReady: vi.fn(() => true),
73+
shutdown: vi.fn(),
74+
addUserToRoom: vi.fn(),
75+
removeUserFromRoom: vi.fn(),
76+
getWorkflowIdForSocket: vi.fn().mockResolvedValue('workflow-1'),
77+
getUserSession: vi.fn().mockResolvedValue({ userId: 'user-1', userName: 'Test User' }),
78+
getWorkflowUsers: vi.fn().mockResolvedValue([
79+
{
80+
socketId: 'socket-1',
81+
userId: 'user-1',
82+
workflowId: 'workflow-1',
83+
userName: 'Test User',
84+
joinedAt: Date.now(),
85+
lastActivity: Date.now(),
86+
role: 'admin',
87+
},
88+
]),
89+
hasWorkflowRoom: vi.fn().mockResolvedValue(true),
90+
updateUserActivity: vi.fn(),
91+
updateRoomLastModified: vi.fn(),
92+
broadcastPresenceUpdate: vi.fn(),
93+
emitToWorkflow,
94+
getUniqueUserCount: vi.fn(),
95+
getTotalActiveConnections: vi.fn(),
96+
handleWorkflowDeletion: vi.fn(),
97+
handleWorkflowRevert: vi.fn(),
98+
handleWorkflowUpdate: vi.fn(),
99+
}
100+
101+
setupOperationsHandlers(socket as never, roomManager)
102+
103+
const workflowOperationHandler = socketHandlers.get('workflow-operation')
104+
105+
expect(workflowOperationHandler).toBeDefined()
106+
107+
await workflowOperationHandler?.({
108+
operationId: 'op-1',
109+
operation: BLOCKS_OPERATIONS.BATCH_UPDATE_PARENT,
110+
target: OPERATION_TARGETS.BLOCKS,
111+
payload: {
112+
updates: [{ id: 'block-1', parentId: 'loop-1', position: { x: 10, y: 20 } }],
113+
},
114+
timestamp: 123,
115+
})
116+
117+
expect(mockPersistWorkflowOperation).toHaveBeenCalledWith('workflow-1', {
118+
operation: BLOCKS_OPERATIONS.BATCH_UPDATE_PARENT,
119+
target: OPERATION_TARGETS.BLOCKS,
120+
payload: {
121+
updates: [{ id: 'block-1', parentId: 'loop-1', position: { x: 10, y: 20 } }],
122+
},
123+
timestamp: expect.any(Number),
124+
userId: 'user-1',
125+
})
126+
expect(socketRoomEmit).toHaveBeenCalledWith(
127+
'workflow-operation',
128+
expect.objectContaining({
129+
operation: EDGES_OPERATIONS.BATCH_REMOVE_EDGES,
130+
target: OPERATION_TARGETS.EDGES,
131+
payload: { ids: ['edge-removed'] },
132+
})
133+
)
134+
expect(emitToWorkflow).toHaveBeenCalledWith(
135+
'workflow-1',
136+
'workflow-operation',
137+
expect.objectContaining({
138+
operation: EDGES_OPERATIONS.BATCH_ADD_EDGES,
139+
target: OPERATION_TARGETS.EDGES,
140+
payload: {
141+
edges: [
142+
expect.objectContaining({
143+
id: 'edge-added',
144+
source: 'loop-1',
145+
target: 'block-1',
146+
}),
147+
],
148+
},
149+
})
150+
)
151+
expect(socketEmit).toHaveBeenCalledWith(
152+
'operation-confirmed',
153+
expect.objectContaining({ operationId: 'op-1', serverTimestamp: expect.any(Number) })
154+
)
155+
})
156+
})

apps/sim/socket/handlers/operations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,7 @@ export function setupOperationsHandlers(socket: AuthenticatedSocket, roomManager
517517

518518
// Broadcast auto-connected edges so clients add them to local state
519519
if (result?.addedEdges && result.addedEdges.length > 0) {
520-
socket.to(workflowId).emit('workflow-operation', {
520+
roomManager.emitToWorkflow(workflowId, 'workflow-operation', {
521521
operation: EDGES_OPERATIONS.BATCH_ADD_EDGES,
522522
target: OPERATION_TARGETS.EDGES,
523523
payload: { edges: result.addedEdges },

0 commit comments

Comments
 (0)