fix(requests): reset orphaned season statuses when a request is deleted#3279
fix(requests): reset orphaned season statuses when a request is deleted#3279Knat-Dev wants to merge 1 commit into
Conversation
Seasons left PENDING/PROCESSING after their request was deleted could never be re-requested ("No
seasons available to request"). Mirrors the existing decline-path season reset.
fix seerr-team#3278
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthrough
ChangesTV season status recalculation
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
fallenbagel
left a comment
There was a problem hiding this comment.
Also needs tests, since there aren't any in this PR. request.test.ts's delete-restoration suite is movie-only, which is how this bug shipped in the first place. At minimum:
- request-covered season at PROCESSING, delete, reset to UNKNOWN, re-request succeeds
- season NOT covered by the deleted request at PROCESSING, delete, untouched
- second active request covering the season, delete the first, stays PROCESSING
- delete a non-4K request, status4k untouched
| !activeSeasonNumbers.has(season.seasonNumber) | ||
| ) { | ||
| season[statusKey] = MediaStatus.UNKNOWN; | ||
| await manager.save(season); |
There was a problem hiding this comment.
Batch this instead of a save per row.
| await manager.save(season); | |
| changedSeasons.push(season); |
with const changedSeasons: Season[] = []; declared before the loop, and if (changedSeasons.length) await manager.save(changedSeasons); after it closes.
| const statusKey = entity.is4k ? 'status4k' : 'status'; | ||
| const activeSeasonNumbers = new Set( | ||
| fullMedia.requests | ||
| .filter( | ||
| (request) => | ||
| request.is4k === entity.is4k && | ||
| request.status !== MediaRequestStatus.COMPLETED && | ||
| request.status !== MediaRequestStatus.DECLINED | ||
| ) | ||
| .flatMap((request) => request.seasons.map((s) => s.seasonNumber)) | ||
| ); | ||
|
|
||
| for (const season of fullMedia.seasons) { | ||
| if ( | ||
| (season[statusKey] === MediaStatus.PENDING || | ||
| season[statusKey] === MediaStatus.PROCESSING) && | ||
| !activeSeasonNumbers.has(season.seasonNumber) |
There was a problem hiding this comment.
Sweeps every PENDING/PROCESSING season with no remaining active request, including seasons whose state came from arr scan and had nothing to do with this deleted request. Example: S1 requested, S2/S3 mid-download via Sonarr, delete the S1 request and S2/S3 flip back to requestable until the next scan runs, which could be hours away or disabled.
entity.seasons is eager-loaded, so scope this to what the deleted request actually covered:
| const statusKey = entity.is4k ? 'status4k' : 'status'; | |
| const activeSeasonNumbers = new Set( | |
| fullMedia.requests | |
| .filter( | |
| (request) => | |
| request.is4k === entity.is4k && | |
| request.status !== MediaRequestStatus.COMPLETED && | |
| request.status !== MediaRequestStatus.DECLINED | |
| ) | |
| .flatMap((request) => request.seasons.map((s) => s.seasonNumber)) | |
| ); | |
| for (const season of fullMedia.seasons) { | |
| if ( | |
| (season[statusKey] === MediaStatus.PENDING || | |
| season[statusKey] === MediaStatus.PROCESSING) && | |
| !activeSeasonNumbers.has(season.seasonNumber) | |
| const statusKey = entity.is4k ? 'status4k' : 'status'; | |
| const removedSeasonNumbers = new Set( | |
| entity.seasons.map((s) => s.seasonNumber) | |
| ); | |
| const activeSeasonNumbers = new Set( | |
| fullMedia.requests | |
| .filter( | |
| (request) => | |
| request.is4k === entity.is4k && | |
| request.status !== MediaRequestStatus.COMPLETED && | |
| request.status !== MediaRequestStatus.DECLINED | |
| ) | |
| .flatMap((request) => request.seasons.map((s) => s.seasonNumber)) | |
| ); | |
| for (const season of fullMedia.seasons) { | |
| if ( | |
| (season[statusKey] === MediaStatus.PENDING || | |
| season[statusKey] === MediaStatus.PROCESSING) && | |
| removedSeasonNumbers.has(season.seasonNumber) && | |
| !activeSeasonNumbers.has(season.seasonNumber) |
Description
Once a user deletes a request the season rows of the request stay PENDING/PROCESSING forever, so the show's seasons can not be re-requested.
In this change we make sure to reset those season rows back to UNKNOWN.
Disclosure: I used AI tooling to help investigate the root cause and draft the patch, though I reviewed it myself and questioned it heavily. I hit the bug myself on my own instance and verified both the reproduction and the fix by hand.
How Has This Been Tested?
I ran the reproduction which has been stated under #3278 with 3.3.0 as well as this branch, on my own live instance with real Sonarr.
Stock 3.3.0: after deleting the request the season row stayed PROCESSING in the db and the UI disabled the season toggle.
On this branch: after deleting the request the season toggle was off and enabled.
Also ran typecheck, lint and the full test suite on this branch (133/133)
Screenshots / Logs (if applicable)
Stock 3.3.0:

^ Stuck in PROCESSING
This branch:

^ Back to UNKNOWN
Checklist:
pnpm buildpnpm i18n:extractSummary by CodeRabbit