From 023e3f30af823c6ee2541406d55df1304b16ac15 Mon Sep 17 00:00:00 2001 From: Chris Sherwood Date: Fri, 20 Mar 2026 07:53:55 -0700 Subject: [PATCH] fix(downloads): allow users to dismiss failed downloads Failed download jobs persist in BullMQ forever with no way to clear them, leaving stale error notifications in Content Explorer and Easy Setup. Adds a dismiss button (X) on failed download cards that removes the job from the queue via a new DELETE endpoint. - Backend: DELETE /api/downloads/jobs/:jobId endpoint - Frontend: X button on failed download cards with immediate refresh Co-Authored-By: Claude Opus 4.6 (1M context) --- admin/app/controllers/downloads_controller.ts | 5 +++++ admin/app/services/download_service.ts | 11 +++++++++++ admin/inertia/components/ActiveDownloads.tsx | 17 +++++++++++++++-- admin/inertia/lib/api.ts | 6 ++++++ admin/start/routes.ts | 1 + 5 files changed, 38 insertions(+), 2 deletions(-) diff --git a/admin/app/controllers/downloads_controller.ts b/admin/app/controllers/downloads_controller.ts index bd58790..023806b 100644 --- a/admin/app/controllers/downloads_controller.ts +++ b/admin/app/controllers/downloads_controller.ts @@ -15,4 +15,9 @@ export default class DownloadsController { const payload = await request.validateUsing(downloadJobsByFiletypeSchema) return this.downloadService.listDownloadJobs(payload.params.filetype) } + + async removeJob({ params }: HttpContext) { + await this.downloadService.removeFailedJob(params.jobId) + return { success: true } + } } diff --git a/admin/app/services/download_service.ts b/admin/app/services/download_service.ts index 63c7ecd..a2b7faf 100644 --- a/admin/app/services/download_service.ts +++ b/admin/app/services/download_service.ts @@ -50,4 +50,15 @@ export class DownloadService { return b.progress - a.progress }) } + + async removeFailedJob(jobId: string): Promise { + for (const queueName of [RunDownloadJob.queue, DownloadModelJob.queue]) { + const queue = this.queueService.getQueue(queueName) + const job = await queue.getJob(jobId) + if (job) { + await job.remove() + return + } + } + } } diff --git a/admin/inertia/components/ActiveDownloads.tsx b/admin/inertia/components/ActiveDownloads.tsx index 69bbb8f..9661f22 100644 --- a/admin/inertia/components/ActiveDownloads.tsx +++ b/admin/inertia/components/ActiveDownloads.tsx @@ -2,7 +2,8 @@ import useDownloads, { useDownloadsProps } from '~/hooks/useDownloads' import HorizontalBarChart from './HorizontalBarChart' import { extractFileName } from '~/lib/util' import StyledSectionHeader from './StyledSectionHeader' -import { IconAlertTriangle } from '@tabler/icons-react' +import { IconAlertTriangle, IconX } from '@tabler/icons-react' +import api from '~/lib/api' interface ActiveDownloadProps { filetype?: useDownloadsProps['filetype'] @@ -10,7 +11,12 @@ interface ActiveDownloadProps { } const ActiveDownloads = ({ filetype, withHeader = false }: ActiveDownloadProps) => { - const { data: downloads } = useDownloads({ filetype }) + const { data: downloads, invalidate } = useDownloads({ filetype }) + + const handleDismiss = async (jobId: string) => { + await api.removeDownloadJob(jobId) + invalidate() + } return ( <> @@ -37,6 +43,13 @@ const ActiveDownloads = ({ filetype, withHeader = false }: ActiveDownloadProps) Download failed{download.failedReason ? `: ${download.failedReason}` : ''}

+ ) : ( { + return catchInternal(async () => { + await this.client.delete(`/downloads/jobs/${jobId}`) + })() + } + async runBenchmark(type: BenchmarkType, sync: boolean = false) { return catchInternal(async () => { const response = await this.client.post( diff --git a/admin/start/routes.ts b/admin/start/routes.ts index 2045b27..631c528 100644 --- a/admin/start/routes.ts +++ b/admin/start/routes.ts @@ -92,6 +92,7 @@ router .group(() => { router.get('/jobs', [DownloadsController, 'index']) router.get('/jobs/:filetype', [DownloadsController, 'filetype']) + router.delete('/jobs/:jobId', [DownloadsController, 'removeJob']) }) .prefix('/api/downloads')