mirror of
https://github.com/seerr-team/seerr.git
synced 2026-10-12 02:22:17 +00:00
fix(requests): scope download status to requested seasons on request cards (#3412)
This commit is contained in:
@@ -59,6 +59,9 @@ if (positionals.length > 0) {
|
||||
for await (const entry of glob(join(BASE_DIR, 'server/**/*.test.ts'))) {
|
||||
files.push(resolve(entry));
|
||||
}
|
||||
for await (const entry of glob(join(BASE_DIR, 'src/**/*.test.ts'))) {
|
||||
files.push(resolve(entry));
|
||||
}
|
||||
files.sort();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,10 @@ import useToasts from '@app/hooks/useToasts';
|
||||
import { Permission, useUser } from '@app/hooks/useUser';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper';
|
||||
import {
|
||||
getRequestDownloadStatus,
|
||||
refreshIntervalHelper,
|
||||
} from '@app/utils/refreshIntervalHelper';
|
||||
import { withProperties } from '@app/utils/typeHelpers';
|
||||
import {
|
||||
ArrowPathIcon,
|
||||
@@ -75,6 +78,15 @@ const RequestCardError = ({ requestData }: RequestCardErrorProps) => {
|
||||
iOSPlexUrl4k: requestData?.media?.iOSPlexUrl4k,
|
||||
});
|
||||
|
||||
const requestDownloadStatus = getRequestDownloadStatus(
|
||||
requestData?.media?.[
|
||||
requestData?.is4k ? 'downloadStatus4k' : 'downloadStatus'
|
||||
],
|
||||
requestData?.type === 'tv'
|
||||
? (requestData?.seasons ?? []).map((season) => season.seasonNumber)
|
||||
: []
|
||||
);
|
||||
|
||||
const deleteRequest = async () => {
|
||||
await axios.delete(`/api/v1/media/${requestData?.media.id}`);
|
||||
mutate('/api/v1/media?filter=allavailable&take=20&sort=mediaAdded');
|
||||
@@ -149,23 +161,9 @@ const RequestCardError = ({ requestData }: RequestCardErrorProps) => {
|
||||
requestData.is4k ? 'status4k' : 'status'
|
||||
]
|
||||
}
|
||||
downloadItem={
|
||||
requestData.media[
|
||||
requestData.is4k
|
||||
? 'downloadStatus4k'
|
||||
: 'downloadStatus'
|
||||
]
|
||||
}
|
||||
downloadItem={requestDownloadStatus}
|
||||
title={intl.formatMessage(messages.unknowntitle)}
|
||||
inProgress={
|
||||
(
|
||||
requestData.media[
|
||||
requestData.is4k
|
||||
? 'downloadStatus4k'
|
||||
: 'downloadStatus'
|
||||
] ?? []
|
||||
).length > 0
|
||||
}
|
||||
inProgress={requestDownloadStatus.length > 0}
|
||||
is4k={requestData.is4k}
|
||||
mediaType={requestData.type}
|
||||
plexUrl={requestData.is4k ? plexUrl4k : plexUrl}
|
||||
@@ -327,6 +325,13 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
|
||||
return <RequestCardError requestData={requestData} />;
|
||||
}
|
||||
|
||||
const requestDownloadStatus = getRequestDownloadStatus(
|
||||
requestData.media[requestData.is4k ? 'downloadStatus4k' : 'downloadStatus'],
|
||||
requestData.type === 'tv'
|
||||
? requestData.seasons.map((season) => season.seasonNumber)
|
||||
: []
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<RequestModal
|
||||
@@ -457,19 +462,9 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
|
||||
status={
|
||||
requestData.media[requestData.is4k ? 'status4k' : 'status']
|
||||
}
|
||||
downloadItem={
|
||||
requestData.media[
|
||||
requestData.is4k ? 'downloadStatus4k' : 'downloadStatus'
|
||||
]
|
||||
}
|
||||
downloadItem={requestDownloadStatus}
|
||||
title={isMovie(title) ? title.title : title.name}
|
||||
inProgress={
|
||||
(
|
||||
requestData.media[
|
||||
requestData.is4k ? 'downloadStatus4k' : 'downloadStatus'
|
||||
] ?? []
|
||||
).length > 0
|
||||
}
|
||||
inProgress={requestDownloadStatus.length > 0}
|
||||
is4k={requestData.is4k}
|
||||
tmdbId={requestData.media.tmdbId}
|
||||
mediaType={requestData.type}
|
||||
|
||||
@@ -10,7 +10,10 @@ import useToasts from '@app/hooks/useToasts';
|
||||
import { Permission, useUser } from '@app/hooks/useUser';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper';
|
||||
import {
|
||||
getRequestDownloadStatus,
|
||||
refreshIntervalHelper,
|
||||
} from '@app/utils/refreshIntervalHelper';
|
||||
import {
|
||||
ArrowPathIcon,
|
||||
CheckIcon,
|
||||
@@ -80,6 +83,15 @@ const RequestItemError = ({
|
||||
iOSPlexUrl4k: requestData?.media?.iOSPlexUrl4k,
|
||||
});
|
||||
|
||||
const requestDownloadStatus = getRequestDownloadStatus(
|
||||
requestData?.media?.[
|
||||
requestData?.is4k ? 'downloadStatus4k' : 'downloadStatus'
|
||||
],
|
||||
requestData?.type === 'tv'
|
||||
? (requestData?.seasons ?? []).map((season) => season.seasonNumber)
|
||||
: []
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex h-64 w-full flex-col justify-center rounded-xl bg-gray-800 py-4 text-gray-400 shadow-md ring-1 ring-red-500 xl:h-28 xl:flex-row">
|
||||
<div className="flex w-full flex-col justify-between overflow-hidden sm:flex-row">
|
||||
@@ -139,21 +151,9 @@ const RequestItemError = ({
|
||||
requestData.is4k ? 'status4k' : 'status'
|
||||
]
|
||||
}
|
||||
downloadItem={
|
||||
requestData.media[
|
||||
requestData.is4k ? 'downloadStatus4k' : 'downloadStatus'
|
||||
]
|
||||
}
|
||||
downloadItem={requestDownloadStatus}
|
||||
title={intl.formatMessage(messages.unknowntitle)}
|
||||
inProgress={
|
||||
(
|
||||
requestData.media[
|
||||
requestData.is4k
|
||||
? 'downloadStatus4k'
|
||||
: 'downloadStatus'
|
||||
] ?? []
|
||||
).length > 0
|
||||
}
|
||||
inProgress={requestDownloadStatus.length > 0}
|
||||
is4k={requestData.is4k}
|
||||
mediaType={requestData.type}
|
||||
plexUrl={requestData.is4k ? plexUrl4k : plexUrl}
|
||||
@@ -414,6 +414,13 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
const requestDownloadStatus = getRequestDownloadStatus(
|
||||
requestData.media[requestData.is4k ? 'downloadStatus4k' : 'downloadStatus'],
|
||||
requestData.type === 'tv'
|
||||
? requestData.seasons.map((season) => season.seasonNumber)
|
||||
: []
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<RequestModal
|
||||
@@ -540,19 +547,9 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
|
||||
status={
|
||||
requestData.media[requestData.is4k ? 'status4k' : 'status']
|
||||
}
|
||||
downloadItem={
|
||||
requestData.media[
|
||||
requestData.is4k ? 'downloadStatus4k' : 'downloadStatus'
|
||||
]
|
||||
}
|
||||
downloadItem={requestDownloadStatus}
|
||||
title={isMovie(title) ? title.title : title.name}
|
||||
inProgress={
|
||||
(
|
||||
requestData.media[
|
||||
requestData.is4k ? 'downloadStatus4k' : 'downloadStatus'
|
||||
] ?? []
|
||||
).length > 0
|
||||
}
|
||||
inProgress={requestDownloadStatus.length > 0}
|
||||
is4k={requestData.is4k}
|
||||
tmdbId={requestData.media.tmdbId}
|
||||
mediaType={requestData.type}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import { MediaType } from '@server/constants/media';
|
||||
import type { DownloadingItem } from '@server/lib/downloadtracker';
|
||||
import assert from 'node:assert/strict';
|
||||
import { describe, it } from 'node:test';
|
||||
import { getRequestDownloadStatus } from './refreshIntervalHelper';
|
||||
|
||||
const createDownloadItem = (
|
||||
overrides: Partial<DownloadingItem> & Pick<DownloadingItem, 'downloadId'>
|
||||
): DownloadingItem => ({
|
||||
mediaType: MediaType.TV,
|
||||
externalId: 1,
|
||||
size: 1000,
|
||||
sizeLeft: 500,
|
||||
status: 'downloading',
|
||||
timeLeft: '00:05:00',
|
||||
estimatedCompletionTime: new Date('2026-01-01T00:00:00Z'),
|
||||
title: 'Test Show',
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const getRequestStatus = (
|
||||
downloadStatus: DownloadingItem[] | undefined,
|
||||
seasonNumbers: number[]
|
||||
) => {
|
||||
const downloadItem = getRequestDownloadStatus(downloadStatus, seasonNumbers);
|
||||
|
||||
return {
|
||||
downloadItem,
|
||||
inProgress: downloadItem.length > 0,
|
||||
};
|
||||
};
|
||||
|
||||
describe('getRequestDownloadStatus', () => {
|
||||
it('returns an empty array when downloadStatus is undefined', () => {
|
||||
const status = getRequestStatus(undefined, [1]);
|
||||
|
||||
assert.deepEqual(status.downloadItem, []);
|
||||
assert.equal(status.inProgress, false);
|
||||
});
|
||||
|
||||
it('returns all items when seasonNumbers is empty', () => {
|
||||
const movieDownload = createDownloadItem({
|
||||
downloadId: 'movie-1',
|
||||
mediaType: MediaType.MOVIE,
|
||||
});
|
||||
const tvDownload = createDownloadItem({
|
||||
downloadId: 'tv-1',
|
||||
episode: {
|
||||
id: 1,
|
||||
seasonNumber: 2,
|
||||
episodeNumber: 3,
|
||||
absoluteEpisodeNumber: 3,
|
||||
},
|
||||
});
|
||||
const downloads = [movieDownload, tvDownload];
|
||||
|
||||
const status = getRequestStatus(downloads, []);
|
||||
|
||||
assert.deepEqual(status.downloadItem, downloads);
|
||||
assert.equal(status.inProgress, true);
|
||||
});
|
||||
|
||||
it('returns only items whose episode season matches the request seasons', () => {
|
||||
const seasonOne = createDownloadItem({
|
||||
downloadId: 's1',
|
||||
episode: {
|
||||
id: 1,
|
||||
seasonNumber: 1,
|
||||
episodeNumber: 1,
|
||||
absoluteEpisodeNumber: 1,
|
||||
},
|
||||
});
|
||||
const seasonThree = createDownloadItem({
|
||||
downloadId: 's3',
|
||||
episode: {
|
||||
id: 3,
|
||||
seasonNumber: 3,
|
||||
episodeNumber: 1,
|
||||
absoluteEpisodeNumber: 10,
|
||||
},
|
||||
});
|
||||
const downloads = [seasonOne, seasonThree];
|
||||
|
||||
const status = getRequestStatus(downloads, [3]);
|
||||
|
||||
assert.deepEqual(status.downloadItem, [seasonThree]);
|
||||
assert.equal(status.inProgress, true);
|
||||
});
|
||||
|
||||
it('returns an empty array when no items match the request seasons', () => {
|
||||
const seasonThree = createDownloadItem({
|
||||
downloadId: 's3',
|
||||
episode: {
|
||||
id: 3,
|
||||
seasonNumber: 3,
|
||||
episodeNumber: 1,
|
||||
absoluteEpisodeNumber: 10,
|
||||
},
|
||||
});
|
||||
|
||||
const status = getRequestStatus([seasonThree], [1, 2]);
|
||||
|
||||
assert.deepEqual(status.downloadItem, []);
|
||||
assert.equal(status.inProgress, false);
|
||||
});
|
||||
|
||||
it('excludes items without episode metadata when filtering by season', () => {
|
||||
const withoutEpisode = createDownloadItem({
|
||||
downloadId: 'missing-episode',
|
||||
});
|
||||
const seasonTwo = createDownloadItem({
|
||||
downloadId: 's2',
|
||||
episode: {
|
||||
id: 2,
|
||||
seasonNumber: 2,
|
||||
episodeNumber: 4,
|
||||
absoluteEpisodeNumber: 4,
|
||||
},
|
||||
});
|
||||
const downloads = [withoutEpisode, seasonTwo];
|
||||
|
||||
const status = getRequestStatus(downloads, [2]);
|
||||
|
||||
assert.deepEqual(status.downloadItem, [seasonTwo]);
|
||||
assert.equal(status.inProgress, true);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,20 @@
|
||||
import type { DownloadingItem } from '@server/lib/downloadtracker';
|
||||
|
||||
export const getRequestDownloadStatus = (
|
||||
downloadStatus: DownloadingItem[] | undefined,
|
||||
seasonNumbers: number[]
|
||||
): DownloadingItem[] => {
|
||||
const items = downloadStatus ?? [];
|
||||
|
||||
if (!seasonNumbers.length) {
|
||||
return items;
|
||||
}
|
||||
|
||||
return items.filter(
|
||||
(item) => item.episode && seasonNumbers.includes(item.episode.seasonNumber)
|
||||
);
|
||||
};
|
||||
|
||||
export const refreshIntervalHelper = (
|
||||
downloadItem: {
|
||||
downloadStatus: DownloadingItem[] | undefined;
|
||||
|
||||
Reference in New Issue
Block a user