mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
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>
This commit is contained in:
committed by
Vikhyath Mondreti
co-authored by
Vikhyath Mondreti
parent
cac9ad250d
commit
231bfb9add
@@ -160,6 +160,7 @@ async function deleteFolderRecursively(
|
||||
}
|
||||
|
||||
// Delete all workflows in this folder (workspace-scoped, not user-scoped)
|
||||
// The database cascade will handle deleting related workflow_blocks, workflow_edges, workflow_subflows
|
||||
const workflowsInFolder = await db
|
||||
.select({ id: workflow.id })
|
||||
.from(workflow)
|
||||
|
||||
@@ -274,14 +274,6 @@ describe('Workflow By ID API Route', () => {
|
||||
}),
|
||||
}))
|
||||
|
||||
const mockTransaction = vi.fn().mockImplementation(async (callback) => {
|
||||
await callback({
|
||||
delete: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockResolvedValue(undefined),
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
vi.doMock('@/db', () => ({
|
||||
db: {
|
||||
select: vi.fn().mockReturnValue({
|
||||
@@ -291,7 +283,9 @@ describe('Workflow By ID API Route', () => {
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
transaction: mockTransaction,
|
||||
delete: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockResolvedValue(undefined),
|
||||
}),
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -326,14 +320,6 @@ describe('Workflow By ID API Route', () => {
|
||||
}),
|
||||
}))
|
||||
|
||||
const mockTransaction = vi.fn().mockImplementation(async (callback) => {
|
||||
await callback({
|
||||
delete: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockResolvedValue(undefined),
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
vi.doMock('@/db', () => ({
|
||||
db: {
|
||||
select: vi.fn().mockReturnValue({
|
||||
@@ -343,7 +329,9 @@ describe('Workflow By ID API Route', () => {
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
transaction: mockTransaction,
|
||||
delete: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockResolvedValue(undefined),
|
||||
}),
|
||||
},
|
||||
}))
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { getUserEntityPermissions, hasAdminPermission } from '@/lib/permissions/utils'
|
||||
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/db-helpers'
|
||||
import { db } from '@/db'
|
||||
import { workflow, workflowBlocks, workflowEdges, workflowSubflows } from '@/db/schema'
|
||||
import { workflow } from '@/db/schema'
|
||||
|
||||
const logger = createLogger('WorkflowByIdAPI')
|
||||
|
||||
@@ -206,16 +206,7 @@ export async function DELETE(
|
||||
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
|
||||
}
|
||||
|
||||
// Delete workflow and all related data in a transaction
|
||||
await db.transaction(async (tx) => {
|
||||
// Delete from normalized tables first (foreign key constraints)
|
||||
await tx.delete(workflowSubflows).where(eq(workflowSubflows.workflowId, workflowId))
|
||||
await tx.delete(workflowEdges).where(eq(workflowEdges.workflowId, workflowId))
|
||||
await tx.delete(workflowBlocks).where(eq(workflowBlocks.workflowId, workflowId))
|
||||
|
||||
// Delete the main workflow record
|
||||
await tx.delete(workflow).where(eq(workflow.id, workflowId))
|
||||
})
|
||||
await db.delete(workflow).where(eq(workflow.id, workflowId))
|
||||
|
||||
const elapsed = Date.now() - startTime
|
||||
logger.info(`[${requestId}] Successfully deleted workflow ${workflowId} in ${elapsed}ms`)
|
||||
|
||||
@@ -2,13 +2,7 @@ import { and, eq } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import {
|
||||
workflow,
|
||||
workflowBlocks,
|
||||
workflowEdges,
|
||||
workflowSubflows,
|
||||
workspaceMember,
|
||||
} from '@/db/schema'
|
||||
import { workflow, workspaceMember } from '@/db/schema'
|
||||
|
||||
const logger = createLogger('WorkspaceByIdAPI')
|
||||
|
||||
@@ -126,20 +120,10 @@ export async function DELETE(
|
||||
|
||||
// Delete workspace and all related data in a transaction
|
||||
await db.transaction(async (tx) => {
|
||||
// Get all workflows in this workspace
|
||||
const workspaceWorkflows = await tx
|
||||
.select({ id: workflow.id })
|
||||
.from(workflow)
|
||||
.where(eq(workflow.workspaceId, workspaceId))
|
||||
|
||||
// Delete all workflow-related data for each workflow
|
||||
for (const wf of workspaceWorkflows) {
|
||||
await tx.delete(workflowSubflows).where(eq(workflowSubflows.workflowId, wf.id))
|
||||
await tx.delete(workflowEdges).where(eq(workflowEdges.workflowId, wf.id))
|
||||
await tx.delete(workflowBlocks).where(eq(workflowBlocks.workflowId, wf.id))
|
||||
}
|
||||
|
||||
// Delete all workflows in the workspace
|
||||
// Delete all workflows in the workspace - database cascade will handle all workflow-related data
|
||||
// The database cascade will handle deleting related workflow_blocks, workflow_edges, workflow_subflows,
|
||||
// workflow_logs, workflow_execution_snapshots, workflow_execution_logs, workflow_execution_trace_spans,
|
||||
// workflow_schedule, webhook, marketplace, chat, and memory records
|
||||
await tx.delete(workflow).where(eq(workflow.workspaceId, workspaceId))
|
||||
|
||||
// Delete workspace members
|
||||
|
||||
+8
-3
@@ -63,12 +63,17 @@ export function FolderContextMenu({
|
||||
setShowRenameDialog(true)
|
||||
}
|
||||
|
||||
const handleDelete = () => {
|
||||
const handleDelete = async () => {
|
||||
if (onDelete) {
|
||||
onDelete(folderId)
|
||||
} else {
|
||||
// Default delete behavior
|
||||
deleteFolder(folderId, workspaceId)
|
||||
// Default delete behavior with proper error handling
|
||||
try {
|
||||
await deleteFolder(folderId, workspaceId)
|
||||
logger.info(`Successfully deleted folder from context menu: ${folderName}`)
|
||||
} catch (error) {
|
||||
logger.error('Failed to delete folder from context menu:', { error, folderId, folderName })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -314,6 +314,7 @@ export const useFolderStore = create<FolderState>()(
|
||||
|
||||
const responseData = await response.json()
|
||||
|
||||
// Remove the folder from local state
|
||||
get().removeFolder(id)
|
||||
|
||||
// Remove from expanded state
|
||||
@@ -323,33 +324,19 @@ export const useFolderStore = create<FolderState>()(
|
||||
return { expandedFolders: newExpanded }
|
||||
})
|
||||
|
||||
// Remove subfolders from local state
|
||||
get().removeSubfoldersRecursively(id)
|
||||
|
||||
// The backend has already deleted the workflows, so we just need to refresh
|
||||
// the workflow registry to sync with the server state
|
||||
const workflowRegistry = useWorkflowRegistry.getState()
|
||||
if (responseData.deletedItems) {
|
||||
try {
|
||||
const workflows = Object.values(workflowRegistry.workflows)
|
||||
const workflowsToDelete = workflows.filter(
|
||||
(workflow) =>
|
||||
workflow.folderId === id || get().isWorkflowInDeletedSubfolder(workflow, id)
|
||||
)
|
||||
|
||||
workflowsToDelete.forEach((workflow) => {
|
||||
workflowRegistry.removeWorkflow(workflow.id)
|
||||
})
|
||||
|
||||
get().removeSubfoldersRecursively(id)
|
||||
|
||||
logger.info(
|
||||
`Deleted ${responseData.deletedItems.workflows} workflow(s) and ${responseData.deletedItems.folders} folder(s)`
|
||||
)
|
||||
} catch (error) {
|
||||
logger.error('Error updating local state after folder deletion:', error)
|
||||
}
|
||||
}
|
||||
|
||||
if (workspaceId) {
|
||||
// Trigger workflow refresh through registry store
|
||||
await workflowRegistry.switchToWorkspace(workspaceId)
|
||||
await workflowRegistry.loadWorkflows(workspaceId)
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Deleted ${responseData.deletedItems.workflows} workflow(s) and ${responseData.deletedItems.folders} folder(s)`
|
||||
)
|
||||
},
|
||||
|
||||
isWorkflowInDeletedSubfolder: (workflow: Workflow, deletedFolderId: string) => {
|
||||
|
||||
Reference in New Issue
Block a user