Skip to content

Commit 8e0d8bc

Browse files
fix lint
1 parent 4148d2b commit 8e0d8bc

6 files changed

Lines changed: 169 additions & 122 deletions

File tree

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,9 @@ import { getSession } from '@/lib/auth'
99
import { getRetentionDefaultHours } from '@/lib/billing/cleanup-dispatcher'
1010
import { getHighestPrioritySubscription } from '@/lib/billing/core/plan'
1111
import { isEnterprisePlan } from '@/lib/billing/core/subscription'
12-
import {
13-
checkEnterprisePlan,
14-
checkProPlan,
15-
checkTeamPlan,
16-
} from '@/lib/billing/subscriptions/utils'
17-
import { getWorkspaceBilledAccountUserId } from '@/lib/workspaces/utils'
12+
import { checkEnterprisePlan, checkProPlan, checkTeamPlan } from '@/lib/billing/subscriptions/utils'
1813
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
14+
import { getWorkspaceBilledAccountUserId } from '@/lib/workspaces/utils'
1915

2016
const logger = createLogger('DataRetentionAPI')
2117

@@ -171,7 +167,7 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
171167
if (parsed.data.softDeleteRetentionHours !== undefined) {
172168
updateData.softDeleteRetentionHours = parsed.data.softDeleteRetentionHours
173169
}
174-
if (parsed.data.taskCleanupHours !== undefined) {
170+
if (parsed.data.taskCleanupHours !== undefined) {
175171
updateData.taskCleanupHours = parsed.data.taskCleanupHours
176172
}
177173

apps/sim/background/cleanup-logs.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import { workflowExecutionLogs, workspace } from '@sim/db/schema'
33
import { createLogger } from '@sim/logger'
44
import { task } from '@trigger.dev/sdk'
55
import { and, eq, inArray, lt } from 'drizzle-orm'
6-
import { snapshotService } from '@/lib/logs/execution/snapshot/service'
76
import {
87
type CleanupJobPayload,
98
getRetentionDefaultHours,
109
resolveTierWorkspaceIds,
1110
} from '@/lib/billing/cleanup-dispatcher'
11+
import { snapshotService } from '@/lib/logs/execution/snapshot/service'
1212
import { isUsingCloudStorage, StorageService } from '@/lib/uploads'
1313

1414
const logger = createLogger('CleanupLogs')
@@ -26,7 +26,14 @@ interface TierResults {
2626
}
2727

2828
function emptyTierResults(): TierResults {
29-
return { total: 0, deleted: 0, deleteFailed: 0, filesTotal: 0, filesDeleted: 0, filesDeleteFailed: 0 }
29+
return {
30+
total: 0,
31+
deleted: 0,
32+
deleteFailed: 0,
33+
filesTotal: 0,
34+
filesDeleted: 0,
35+
filesDeleteFailed: 0,
36+
}
3037
}
3138

3239
async function deleteExecutionFiles(files: unknown, results: TierResults): Promise<void> {

apps/sim/background/cleanup-soft-deletes.ts

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -120,16 +120,61 @@ async function cleanupWorkspaceFileStorage(
120120
}
121121

122122
const CLEANUP_TARGETS = [
123-
{ table: workflow, softDeleteCol: workflow.archivedAt, wsCol: workflow.workspaceId, name: 'workflow' },
124-
{ table: workflowFolder, softDeleteCol: workflowFolder.archivedAt, wsCol: workflowFolder.workspaceId, name: 'workflowFolder' },
125-
{ table: knowledgeBase, softDeleteCol: knowledgeBase.deletedAt, wsCol: knowledgeBase.workspaceId, name: 'knowledgeBase' },
126-
{ table: userTableDefinitions, softDeleteCol: userTableDefinitions.archivedAt, wsCol: userTableDefinitions.workspaceId, name: 'userTableDefinitions' },
127-
{ table: workspaceFile, softDeleteCol: workspaceFile.deletedAt, wsCol: workspaceFile.workspaceId, name: 'workspaceFile' },
128-
{ table: workspaceFiles, softDeleteCol: workspaceFiles.deletedAt, wsCol: workspaceFiles.workspaceId, name: 'workspaceFiles' },
123+
{
124+
table: workflow,
125+
softDeleteCol: workflow.archivedAt,
126+
wsCol: workflow.workspaceId,
127+
name: 'workflow',
128+
},
129+
{
130+
table: workflowFolder,
131+
softDeleteCol: workflowFolder.archivedAt,
132+
wsCol: workflowFolder.workspaceId,
133+
name: 'workflowFolder',
134+
},
135+
{
136+
table: knowledgeBase,
137+
softDeleteCol: knowledgeBase.deletedAt,
138+
wsCol: knowledgeBase.workspaceId,
139+
name: 'knowledgeBase',
140+
},
141+
{
142+
table: userTableDefinitions,
143+
softDeleteCol: userTableDefinitions.archivedAt,
144+
wsCol: userTableDefinitions.workspaceId,
145+
name: 'userTableDefinitions',
146+
},
147+
{
148+
table: workspaceFile,
149+
softDeleteCol: workspaceFile.deletedAt,
150+
wsCol: workspaceFile.workspaceId,
151+
name: 'workspaceFile',
152+
},
153+
{
154+
table: workspaceFiles,
155+
softDeleteCol: workspaceFiles.deletedAt,
156+
wsCol: workspaceFiles.workspaceId,
157+
name: 'workspaceFiles',
158+
},
129159
{ table: memory, softDeleteCol: memory.deletedAt, wsCol: memory.workspaceId, name: 'memory' },
130-
{ table: mcpServers, softDeleteCol: mcpServers.deletedAt, wsCol: mcpServers.workspaceId, name: 'mcpServers' },
131-
{ table: workflowMcpServer, softDeleteCol: workflowMcpServer.deletedAt, wsCol: workflowMcpServer.workspaceId, name: 'workflowMcpServer' },
132-
{ table: a2aAgent, softDeleteCol: a2aAgent.archivedAt, wsCol: a2aAgent.workspaceId, name: 'a2aAgent' },
160+
{
161+
table: mcpServers,
162+
softDeleteCol: mcpServers.deletedAt,
163+
wsCol: mcpServers.workspaceId,
164+
name: 'mcpServers',
165+
},
166+
{
167+
table: workflowMcpServer,
168+
softDeleteCol: workflowMcpServer.deletedAt,
169+
wsCol: workflowMcpServer.workspaceId,
170+
name: 'workflowMcpServer',
171+
},
172+
{
173+
table: a2aAgent,
174+
softDeleteCol: a2aAgent.archivedAt,
175+
wsCol: a2aAgent.workspaceId,
176+
name: 'a2aAgent',
177+
},
133178
] as const
134179

135180
async function resolvePayload(payload: CleanupJobPayload): Promise<{

apps/sim/background/cleanup-tasks.ts

Lines changed: 100 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ async function cleanupRunChildren(
8686
const runIds = await db
8787
.select({ id: copilotRuns.id })
8888
.from(copilotRuns)
89-
.where(and(inArray(copilotRuns.workspaceId, workspaceIds), lt(copilotRuns.updatedAt, retentionDate)))
89+
.where(
90+
and(inArray(copilotRuns.workspaceId, workspaceIds), lt(copilotRuns.updatedAt, retentionDate))
91+
)
9092
.limit(BATCH_SIZE * MAX_BATCHES_PER_TABLE)
9193

9294
if (runIds.length === 0) {
@@ -172,25 +174,54 @@ async function resolvePayload(payload: CleanupJobPayload): Promise<{
172174
}
173175

174176
export async function runCleanupTasks(payload: CleanupJobPayload): Promise<void> {
175-
const startTime = Date.now()
177+
const startTime = Date.now()
176178

177-
const resolved = await resolvePayload(payload)
178-
if (!resolved) return
179+
const resolved = await resolvePayload(payload)
180+
if (!resolved) return
179181

180-
const { workspaceIds, retentionHours, tierLabel } = resolved
182+
const { workspaceIds, retentionHours, tierLabel } = resolved
181183

182-
if (workspaceIds.length === 0) {
183-
logger.info(`[${tierLabel}] No workspaces to process`)
184-
return
185-
}
184+
if (workspaceIds.length === 0) {
185+
logger.info(`[${tierLabel}] No workspaces to process`)
186+
return
187+
}
186188

187-
const retentionDate = new Date(Date.now() - retentionHours * 60 * 60 * 1000)
188-
logger.info(
189-
`[${tierLabel}] Processing ${workspaceIds.length} workspaces, cutoff: ${retentionDate.toISOString()}`
189+
const retentionDate = new Date(Date.now() - retentionHours * 60 * 60 * 1000)
190+
logger.info(
191+
`[${tierLabel}] Processing ${workspaceIds.length} workspaces, cutoff: ${retentionDate.toISOString()}`
192+
)
193+
194+
// Collect chat IDs before deleting so we can clean up the copilot backend after
195+
const doomedChats = await db
196+
.select({ id: copilotChats.id })
197+
.from(copilotChats)
198+
.where(
199+
and(
200+
inArray(copilotChats.workspaceId, workspaceIds),
201+
lt(copilotChats.updatedAt, retentionDate)
202+
)
190203
)
204+
.limit(BATCH_SIZE * MAX_BATCHES_PER_TABLE)
205+
206+
const doomedChatIds = doomedChats.map((c) => c.id)
207+
208+
// Prepare chat cleanup (collect file keys + copilot backend call) BEFORE DB deletion
209+
const chatCleanup = await prepareChatCleanup(doomedChatIds, tierLabel)
210+
211+
// Delete run children first (checkpoints, tool calls) since they reference runs
212+
const runChildResults = await cleanupRunChildren(workspaceIds, retentionDate, tierLabel)
213+
for (const r of runChildResults) {
214+
if (r.deleted > 0) logger.info(`[${r.table}] ${r.deleted} deleted`)
215+
}
191216

192-
// Collect chat IDs before deleting so we can clean up the copilot backend after
193-
const doomedChats = await db
217+
// Delete feedback — no direct workspaceId, find via copilotChats
218+
const feedbackResult: TableCleanupResult = {
219+
table: `${tierLabel}/copilotFeedback`,
220+
deleted: 0,
221+
failed: 0,
222+
}
223+
try {
224+
const chatIds = await db
194225
.select({ id: copilotChats.id })
195226
.from(copilotChats)
196227
.where(
@@ -201,90 +232,66 @@ export async function runCleanupTasks(payload: CleanupJobPayload): Promise<void>
201232
)
202233
.limit(BATCH_SIZE * MAX_BATCHES_PER_TABLE)
203234

204-
const doomedChatIds = doomedChats.map((c) => c.id)
205-
206-
// Prepare chat cleanup (collect file keys + copilot backend call) BEFORE DB deletion
207-
const chatCleanup = await prepareChatCleanup(doomedChatIds, tierLabel)
208-
209-
// Delete run children first (checkpoints, tool calls) since they reference runs
210-
const runChildResults = await cleanupRunChildren(workspaceIds, retentionDate, tierLabel)
211-
for (const r of runChildResults) {
212-
if (r.deleted > 0) logger.info(`[${r.table}] ${r.deleted} deleted`)
213-
}
214-
215-
// Delete feedback — no direct workspaceId, find via copilotChats
216-
const feedbackResult: TableCleanupResult = {
217-
table: `${tierLabel}/copilotFeedback`,
218-
deleted: 0,
219-
failed: 0,
220-
}
221-
try {
222-
const chatIds = await db
223-
.select({ id: copilotChats.id })
224-
.from(copilotChats)
235+
if (chatIds.length > 0) {
236+
const deleted = await db
237+
.delete(copilotFeedback)
225238
.where(
226-
and(
227-
inArray(copilotChats.workspaceId, workspaceIds),
228-
lt(copilotChats.updatedAt, retentionDate)
239+
inArray(
240+
copilotFeedback.chatId,
241+
chatIds.map((c) => c.id)
229242
)
230243
)
231-
.limit(BATCH_SIZE * MAX_BATCHES_PER_TABLE)
232-
233-
if (chatIds.length > 0) {
234-
const deleted = await db
235-
.delete(copilotFeedback)
236-
.where(inArray(copilotFeedback.chatId, chatIds.map((c) => c.id)))
237-
.returning({ id: copilotFeedback.feedbackId })
238-
feedbackResult.deleted = deleted.length
239-
logger.info(`[${feedbackResult.table}] Deleted ${deleted.length} rows`)
240-
} else {
241-
logger.info(`[${feedbackResult.table}] No expired rows found`)
242-
}
243-
} catch (error) {
244-
feedbackResult.failed++
245-
logger.error(`[${feedbackResult.table}] Delete failed:`, { error })
244+
.returning({ id: copilotFeedback.feedbackId })
245+
feedbackResult.deleted = deleted.length
246+
logger.info(`[${feedbackResult.table}] Deleted ${deleted.length} rows`)
247+
} else {
248+
logger.info(`[${feedbackResult.table}] No expired rows found`)
246249
}
250+
} catch (error) {
251+
feedbackResult.failed++
252+
logger.error(`[${feedbackResult.table}] Delete failed:`, { error })
253+
}
247254

248-
// Delete copilot runs (has workspaceId directly, cascades checkpoints)
249-
const runsResult = await cleanupTable(
250-
copilotRuns,
251-
copilotRuns.workspaceId,
252-
copilotRuns.updatedAt,
253-
workspaceIds,
254-
retentionDate,
255-
`${tierLabel}/copilotRuns`
256-
)
257-
258-
// Delete copilot chats (has workspaceId directly)
259-
const chatsResult = await cleanupTable(
260-
copilotChats,
261-
copilotChats.workspaceId,
262-
copilotChats.updatedAt,
263-
workspaceIds,
264-
retentionDate,
265-
`${tierLabel}/copilotChats`
266-
)
267-
268-
// Delete mothership inbox tasks (has workspaceId directly)
269-
const inboxResult = await cleanupTable(
270-
mothershipInboxTask,
271-
mothershipInboxTask.workspaceId,
272-
mothershipInboxTask.createdAt,
273-
workspaceIds,
274-
retentionDate,
275-
`${tierLabel}/mothershipInboxTask`
276-
)
277-
278-
const totalDeleted =
279-
runChildResults.reduce((s, r) => s + r.deleted, 0) +
280-
runsResult.deleted +
281-
chatsResult.deleted +
282-
inboxResult.deleted
283-
284-
logger.info(`[${tierLabel}] Complete: ${totalDeleted} total rows deleted`)
285-
286-
// Clean up copilot backend + storage files after DB rows are gone
287-
await chatCleanup.execute()
255+
// Delete copilot runs (has workspaceId directly, cascades checkpoints)
256+
const runsResult = await cleanupTable(
257+
copilotRuns,
258+
copilotRuns.workspaceId,
259+
copilotRuns.updatedAt,
260+
workspaceIds,
261+
retentionDate,
262+
`${tierLabel}/copilotRuns`
263+
)
264+
265+
// Delete copilot chats (has workspaceId directly)
266+
const chatsResult = await cleanupTable(
267+
copilotChats,
268+
copilotChats.workspaceId,
269+
copilotChats.updatedAt,
270+
workspaceIds,
271+
retentionDate,
272+
`${tierLabel}/copilotChats`
273+
)
274+
275+
// Delete mothership inbox tasks (has workspaceId directly)
276+
const inboxResult = await cleanupTable(
277+
mothershipInboxTask,
278+
mothershipInboxTask.workspaceId,
279+
mothershipInboxTask.createdAt,
280+
workspaceIds,
281+
retentionDate,
282+
`${tierLabel}/mothershipInboxTask`
283+
)
284+
285+
const totalDeleted =
286+
runChildResults.reduce((s, r) => s + r.deleted, 0) +
287+
runsResult.deleted +
288+
chatsResult.deleted +
289+
inboxResult.deleted
290+
291+
logger.info(`[${tierLabel}] Complete: ${totalDeleted} total rows deleted`)
292+
293+
// Clean up copilot backend + storage files after DB rows are gone
294+
await chatCleanup.execute()
288295

289296
const timeElapsed = (Date.now() - startTime) / 1000
290297
logger.info(`Task cleanup completed in ${timeElapsed.toFixed(2)}s`)

apps/sim/ee/data-retention/components/data-retention-settings.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
SelectTrigger,
1313
SelectValue,
1414
} from '@/components/ui/select'
15-
import { cn } from '@/lib/core/utils/cn'
1615
import {
1716
type DataRetentionResponse,
1817
useUpdateWorkspaceRetention,
@@ -186,7 +185,7 @@ function EditableView({ data, workspaceId }: { data: DataRetentionResponse; work
186185
onChange={setSoftDeleteDays}
187186
disabled={false}
188187
/>
189-
<RetentionField
188+
<RetentionField
190189
label='Task Cleanup'
191190
description='How long before old tasks are permanently deleted.'
192191
value={taskCleanupDays}
@@ -196,11 +195,7 @@ function EditableView({ data, workspaceId }: { data: DataRetentionResponse; work
196195
</div>
197196

198197
<div className='flex items-center gap-3'>
199-
<Button
200-
onClick={handleSave}
201-
disabled={updateMutation.isPending}
202-
className='text-[13px]'
203-
>
198+
<Button onClick={handleSave} disabled={updateMutation.isPending} className='text-[13px]'>
204199
{updateMutation.isPending ? (
205200
<>
206201
<Loader2 className='mr-2 h-3.5 w-3.5 animate-spin' />

apps/sim/lib/billing/cleanup-dispatcher.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@ const logger = createLogger('RetentionDispatcher')
1313

1414
const BATCH_TRIGGER_CHUNK_SIZE = 1000
1515

16-
export type CleanupJobType =
17-
| 'cleanup-logs'
18-
| 'cleanup-soft-deletes'
19-
| 'cleanup-tasks'
16+
export type CleanupJobType = 'cleanup-logs' | 'cleanup-soft-deletes' | 'cleanup-tasks'
2017

2118
export type WorkspaceRetentionColumn =
2219
| 'logRetentionHours'

0 commit comments

Comments
 (0)