Skip to content

Commit 1af0c2c

Browse files
committed
fix(sidebar): cover v1 workflow import fan-out and scope folder invalidation per review
1 parent 7355064 commit 1af0c2c

5 files changed

Lines changed: 20 additions & 4 deletions

File tree

apps/sim/app/api/v1/workflows/import/route.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const {
2424
mockDbDelete,
2525
mockDbUpdate,
2626
mockWorkspaceRows,
27+
mockNotifyWorkspaceWorkflowsChanged,
2728
} = vi.hoisted(() => ({
2829
mockCheckRateLimit: vi.fn(),
2930
mockValidateWorkspaceAccess: vi.fn(),
@@ -37,6 +38,7 @@ const {
3738
mockDbDelete: vi.fn(),
3839
mockDbUpdate: vi.fn(),
3940
mockWorkspaceRows: { value: [{ id: 'ws-1' }] as Array<{ id: string }> },
41+
mockNotifyWorkspaceWorkflowsChanged: vi.fn(),
4042
}))
4143

4244
vi.mock('@/app/api/v1/middleware', () => ({
@@ -53,6 +55,10 @@ vi.mock('@/lib/workflows/orchestration', () => ({
5355
performCreateWorkflow: mockPerformCreateWorkflow,
5456
}))
5557

58+
vi.mock('@/lib/realtime/notify', () => ({
59+
notifyWorkspaceWorkflowsChanged: mockNotifyWorkspaceWorkflowsChanged,
60+
}))
61+
5662
vi.mock('@/lib/workflows/persistence/utils', () => ({
5763
saveWorkflowToNormalizedTables: mockSaveWorkflowToNormalizedTables,
5864
}))
@@ -270,6 +276,7 @@ describe('POST /api/v1/workflows/import', () => {
270276
expect.anything(),
271277
expect.anything()
272278
)
279+
expect(mockNotifyWorkspaceWorkflowsChanged).toHaveBeenCalledWith(WORKSPACE_ID)
273280
})
274281

275282
it('derives the name from the export envelope and deduplicates it', async () => {

apps/sim/app/api/v1/workflows/import/route.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from '@/lib/api/contracts/v1/workflows'
99
import { parseRequest } from '@/lib/api/server'
1010
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
11+
import { notifyWorkspaceWorkflowsChanged } from '@/lib/realtime/notify'
1112
import {
1213
importWorkflowIntoWorkspace,
1314
MAX_IMPORT_BODY_BYTES,
@@ -82,6 +83,8 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
8283
)
8384
}
8485

86+
await notifyWorkspaceWorkflowsChanged(result.workflow.workspaceId)
87+
8588
const data: V1ImportWorkflowData = {
8689
id: result.workflow.id,
8790
name: result.workflow.name,

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-workspace-workflows-room.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,17 @@ import { invalidateWorkflowLists } from '@/hooks/queries/utils/invalidate-workfl
1313
* workflow lists AND the workflow folders so every viewer refetches without waiting for staleness.
1414
* A created/renamed/moved/deleted/duplicated/imported/restored/reordered workflow changes the list
1515
* result; a folder create/rename/delete/restore changes the folder tree — the sidebar renders both,
16-
* so both are invalidated. Lists go through {@link invalidateWorkflowLists} (both scopes, plus the
17-
* workflow selectors) so a remote change refreshes exactly what a local mutation would. Thin
18-
* binding over {@link useWorkspaceInvalidationRoom}, mirroring `useWorkspaceTablesRoom`.
16+
* so both are invalidated — each scoped to this workspace, in both scopes, so one workspace's
17+
* broadcast never touches another workspace's cache. Lists go through
18+
* {@link invalidateWorkflowLists} (which also covers the workflow selectors) so a remote change
19+
* refreshes exactly what a local mutation would. Thin binding over
20+
* {@link useWorkspaceInvalidationRoom}, mirroring `useWorkspaceTablesRoom`.
1921
*/
2022
export function useWorkspaceWorkflowsRoom(workspaceId: string): void {
2123
const queryClient = useQueryClient()
2224
useWorkspaceInvalidationRoom(workspaceId, ROOM_TYPES.WORKSPACE_WORKFLOWS, () => {
2325
invalidateWorkflowLists(queryClient, workspaceId, ['active', 'archived'])
24-
queryClient.invalidateQueries({ queryKey: folderKeys.resource('workflow') })
26+
queryClient.invalidateQueries({ queryKey: folderKeys.list(workspaceId, 'active') })
27+
queryClient.invalidateQueries({ queryKey: folderKeys.list(workspaceId, 'archived') })
2528
})
2629
}

apps/sim/lib/workflows/application/import-export.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ describe('workflow import and export application operations', () => {
158158
}),
159159
})
160160
)
161+
expect(mocks.notifyWorkspace).toHaveBeenCalledWith('ws-1')
161162
})
162163

163164
it('preserves classified import details and does not audit a failure', async () => {

apps/sim/lib/workflows/application/restore-workflow.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ describe('restoreWorkflow', () => {
9191
})
9292
)
9393
expect(mocks.recordAudit).toHaveBeenCalledBefore(mocks.notify)
94+
expect(mocks.notify).toHaveBeenCalledWith('workflow-1')
95+
expect(mocks.notifyWorkspace).toHaveBeenCalledWith('workspace-1')
9496
})
9597

9698
it('refuses a workflow that is not archived as a conflict', async () => {

0 commit comments

Comments
 (0)