fix(downloads): keep the drug download card live across all parts

The Active Downloads card filtered to the deterministic jobId, but the
download's continuations run under auto-generated jobIds (only part 0 uses the
deterministic one). So the card tracked part 0 and then vanished while parts
2..N kept downloading. The queue is concurrency 1, so collapse to whichever
single part is in flight and report the deterministic jobId: one card tracks
aggregate progress through the whole download and cancel/remove still routes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Chris 2026-06-24 17:46:44 -05:00
parent 374ccda92a
commit 6725753ae0
1 changed files with 32 additions and 24 deletions

View File

@ -103,30 +103,38 @@ export class DownloadService {
failedReason: job.failedReason || undefined,
}))
// FDA drug dataset — DOWNLOAD phase only. The job fans the manifest's N
// partitions into continuations under auto-generated jobIds, but the user
// sees ONE download, so collapse to a single card by keeping ONLY the
// deterministic jobId job (the part currently downloading lives under it; the
// continuations are an internal implementation detail). The heavy ingest is
// deliberately EXCLUDED here — it stays on the IngestStatus surface.
const drugDownloads = drugTagged
.filter(({ job }) => job.id?.toString() === DownloadDrugDataJob.jobId)
.map(({ job, state }) => {
const parsed = this.parseProgress(job.progress)
return {
jobId: job.id!.toString(),
url: 'https://api.fda.gov/download.json',
progress: parsed.percent,
filepath: job.data.currentPartName || 'FDA Drug Reference',
filetype: 'drug-data',
title: 'FDA Drug Reference',
downloadedBytes: parsed.downloadedBytes,
totalBytes: parsed.totalBytes,
lastProgressTime: parsed.lastProgressTime,
status: state,
failedReason: job.failedReason || undefined,
}
})
// FDA drug dataset — DOWNLOAD phase only, collapsed to ONE card. The job fans
// the manifest's N partitions into continuations under AUTO-GENERATED jobIds
// (only part 0 runs under the deterministic jobId), and the queue is
// concurrency 1, so at most one part is ever in flight. Filtering to the
// deterministic jobId would track only part 0 and then drop the card while
// parts 2..N keep downloading. Instead represent the whole download with the
// single in-flight job's aggregate progress (the progress emit already spans
// all parts), and always report the deterministic jobId so the cancel/remove
// button routes to _cancelDrugDownloadJob whichever part is active. The heavy
// ingest is EXCLUDED here — it stays on the IngestStatus surface.
const drugInFlight =
drugTagged.find(({ state }) => state === 'active') ??
drugTagged.find(({ state }) => state === 'waiting' || state === 'delayed') ??
drugTagged.find(({ state }) => state === 'failed')
const drugDownloads = drugInFlight
? [drugInFlight].map(({ job, state }) => {
const parsed = this.parseProgress(job.progress)
return {
jobId: DownloadDrugDataJob.jobId,
url: 'https://api.fda.gov/download.json',
progress: parsed.percent,
filepath: job.data.currentPartName || 'FDA Drug Reference',
filetype: 'drug-data',
title: 'FDA Drug Reference',
downloadedBytes: parsed.downloadedBytes,
totalBytes: parsed.totalBytes,
lastProgressTime: parsed.lastProgressTime,
status: state,
failedReason: job.failedReason || undefined,
}
})
: []
const allDownloads = [...fileDownloads, ...extractDownloads, ...modelDownloads, ...drugDownloads]
const filtered = allDownloads.filter((job) => !filetype || job.filetype === filetype)