Skip to content

Commit 231bfb9

Browse files
icecrasher321Vikhyath Mondreti
andcommitted
fix(deletions): folder deletions were hanging + use cascade deletions throughout (#620)
* use cascade deletion * fix lint --------- Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@vikhyaths-air.lan>
1 parent cac9ad2 commit 231bfb9

6 files changed

Lines changed: 33 additions & 77 deletions

File tree

apps/sim/app/api/folders/[id]/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ async function deleteFolderRecursively(
160160
}
161161

162162
// Delete all workflows in this folder (workspace-scoped, not user-scoped)
163+
// The database cascade will handle deleting related workflow_blocks, workflow_edges, workflow_subflows
163164
const workflowsInFolder = await db
164165
.select({ id: workflow.id })
165166
.from(workflow)

apps/sim/app/api/workflows/[id]/route.test.ts

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -274,14 +274,6 @@ describe('Workflow By ID API Route', () => {
274274
}),
275275
}))
276276

277-
const mockTransaction = vi.fn().mockImplementation(async (callback) => {
278-
await callback({
279-
delete: vi.fn().mockReturnValue({
280-
where: vi.fn().mockResolvedValue(undefined),
281-
}),
282-
})
283-
})
284-
285277
vi.doMock('@/db', () => ({
286278
db: {
287279
select: vi.fn().mockReturnValue({
@@ -291,7 +283,9 @@ describe('Workflow By ID API Route', () => {
291283
}),
292284
}),
293285
}),
294-
transaction: mockTransaction,
286+
delete: vi.fn().mockReturnValue({
287+
where: vi.fn().mockResolvedValue(undefined),
288+
}),
295289
},
296290
}))
297291

@@ -326,14 +320,6 @@ describe('Workflow By ID API Route', () => {
326320
}),
327321
}))
328322

329-
const mockTransaction = vi.fn().mockImplementation(async (callback) => {
330-
await callback({
331-
delete: vi.fn().mockReturnValue({
332-
where: vi.fn().mockResolvedValue(undefined),
333-
}),
334-
})
335-
})
336-
337323
vi.doMock('@/db', () => ({
338324
db: {
339325
select: vi.fn().mockReturnValue({
@@ -343,7 +329,9 @@ describe('Workflow By ID API Route', () => {
343329
}),
344330
}),
345331
}),
346-
transaction: mockTransaction,
332+
delete: vi.fn().mockReturnValue({
333+
where: vi.fn().mockResolvedValue(undefined),
334+
}),
347335
},
348336
}))
349337

apps/sim/app/api/workflows/[id]/route.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { createLogger } from '@/lib/logs/console-logger'
77
import { getUserEntityPermissions, hasAdminPermission } from '@/lib/permissions/utils'
88
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/db-helpers'
99
import { db } from '@/db'
10-
import { workflow, workflowBlocks, workflowEdges, workflowSubflows } from '@/db/schema'
10+
import { workflow } from '@/db/schema'
1111

1212
const logger = createLogger('WorkflowByIdAPI')
1313

@@ -206,16 +206,7 @@ export async function DELETE(
206206
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
207207
}
208208

209-
// Delete workflow and all related data in a transaction
210-
await db.transaction(async (tx) => {
211-
// Delete from normalized tables first (foreign key constraints)
212-
await tx.delete(workflowSubflows).where(eq(workflowSubflows.workflowId, workflowId))
213-
await tx.delete(workflowEdges).where(eq(workflowEdges.workflowId, workflowId))
214-
await tx.delete(workflowBlocks).where(eq(workflowBlocks.workflowId, workflowId))
215-
216-
// Delete the main workflow record
217-
await tx.delete(workflow).where(eq(workflow.id, workflowId))
218-
})
209+
await db.delete(workflow).where(eq(workflow.id, workflowId))
219210

220211
const elapsed = Date.now() - startTime
221212
logger.info(`[${requestId}] Successfully deleted workflow ${workflowId} in ${elapsed}ms`)

apps/sim/app/api/workspaces/[id]/route.ts

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,7 @@ import { and, eq } from 'drizzle-orm'
22
import { type NextRequest, NextResponse } from 'next/server'
33
import { getSession } from '@/lib/auth'
44
import { createLogger } from '@/lib/logs/console-logger'
5-
import {
6-
workflow,
7-
workflowBlocks,
8-
workflowEdges,
9-
workflowSubflows,
10-
workspaceMember,
11-
} from '@/db/schema'
5+
import { workflow, workspaceMember } from '@/db/schema'
126

137
const logger = createLogger('WorkspaceByIdAPI')
148

@@ -126,20 +120,10 @@ export async function DELETE(
126120

127121
// Delete workspace and all related data in a transaction
128122
await db.transaction(async (tx) => {
129-
// Get all workflows in this workspace
130-
const workspaceWorkflows = await tx
131-
.select({ id: workflow.id })
132-
.from(workflow)
133-
.where(eq(workflow.workspaceId, workspaceId))
134-
135-
// Delete all workflow-related data for each workflow
136-
for (const wf of workspaceWorkflows) {
137-
await tx.delete(workflowSubflows).where(eq(workflowSubflows.workflowId, wf.id))
138-
await tx.delete(workflowEdges).where(eq(workflowEdges.workflowId, wf.id))
139-
await tx.delete(workflowBlocks).where(eq(workflowBlocks.workflowId, wf.id))
140-
}
141-
142-
// Delete all workflows in the workspace
123+
// Delete all workflows in the workspace - database cascade will handle all workflow-related data
124+
// The database cascade will handle deleting related workflow_blocks, workflow_edges, workflow_subflows,
125+
// workflow_logs, workflow_execution_snapshots, workflow_execution_logs, workflow_execution_trace_spans,
126+
// workflow_schedule, webhook, marketplace, chat, and memory records
143127
await tx.delete(workflow).where(eq(workflow.workspaceId, workspaceId))
144128

145129
// Delete workspace members

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/folder-context-menu/folder-context-menu.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,17 @@ export function FolderContextMenu({
6363
setShowRenameDialog(true)
6464
}
6565

66-
const handleDelete = () => {
66+
const handleDelete = async () => {
6767
if (onDelete) {
6868
onDelete(folderId)
6969
} else {
70-
// Default delete behavior
71-
deleteFolder(folderId, workspaceId)
70+
// Default delete behavior with proper error handling
71+
try {
72+
await deleteFolder(folderId, workspaceId)
73+
logger.info(`Successfully deleted folder from context menu: ${folderName}`)
74+
} catch (error) {
75+
logger.error('Failed to delete folder from context menu:', { error, folderId, folderName })
76+
}
7277
}
7378
}
7479

apps/sim/stores/folders/store.ts

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ export const useFolderStore = create<FolderState>()(
314314

315315
const responseData = await response.json()
316316

317+
// Remove the folder from local state
317318
get().removeFolder(id)
318319

319320
// Remove from expanded state
@@ -323,33 +324,19 @@ export const useFolderStore = create<FolderState>()(
323324
return { expandedFolders: newExpanded }
324325
})
325326

326-
const workflowRegistry = useWorkflowRegistry.getState()
327-
if (responseData.deletedItems) {
328-
try {
329-
const workflows = Object.values(workflowRegistry.workflows)
330-
const workflowsToDelete = workflows.filter(
331-
(workflow) =>
332-
workflow.folderId === id || get().isWorkflowInDeletedSubfolder(workflow, id)
333-
)
334-
335-
workflowsToDelete.forEach((workflow) => {
336-
workflowRegistry.removeWorkflow(workflow.id)
337-
})
338-
339-
get().removeSubfoldersRecursively(id)
340-
341-
logger.info(
342-
`Deleted ${responseData.deletedItems.workflows} workflow(s) and ${responseData.deletedItems.folders} folder(s)`
343-
)
344-
} catch (error) {
345-
logger.error('Error updating local state after folder deletion:', error)
346-
}
347-
}
327+
// Remove subfolders from local state
328+
get().removeSubfoldersRecursively(id)
348329

330+
// The backend has already deleted the workflows, so we just need to refresh
331+
// the workflow registry to sync with the server state
332+
const workflowRegistry = useWorkflowRegistry.getState()
349333
if (workspaceId) {
350-
// Trigger workflow refresh through registry store
351-
await workflowRegistry.switchToWorkspace(workspaceId)
334+
await workflowRegistry.loadWorkflows(workspaceId)
352335
}
336+
337+
logger.info(
338+
`Deleted ${responseData.deletedItems.workflows} workflow(s) and ${responseData.deletedItems.folders} folder(s)`
339+
)
353340
},
354341

355342
isWorkflowInDeletedSubfolder: (workflow: Workflow, deletedFolderId: string) => {

0 commit comments

Comments
 (0)