fix: better ordering of RequestButton options & properly handle failed requests (#2944)

* fix: better ordering of RequestButton options & properly handle failed requests

* fix: appease prettier
pull/2944/merge
TheCatLady 2 years ago committed by GitHub
parent e5d8c93ab8
commit c143c0b8d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4430,7 +4430,16 @@ paths:
schema: schema:
type: string type: string
nullable: true nullable: true
enum: [all, approved, available, pending, processing, unavailable] enum:
[
all,
approved,
available,
pending,
processing,
unavailable,
failed,
]
- in: query - in: query
name: sort name: sort
schema: schema:

@ -2,6 +2,7 @@ export enum MediaRequestStatus {
PENDING = 1, PENDING = 1,
APPROVED, APPROVED,
DECLINED, DECLINED,
FAILED,
} }
export enum MediaType { export enum MediaType {

@ -451,10 +451,13 @@ export class MediaRequest {
await mediaRepository.save(media); await mediaRepository.save(media);
}) })
.catch(async () => { .catch(async () => {
media[this.is4k ? 'status4k' : 'status'] = MediaStatus.UNKNOWN; const requestRepository = getRepository(MediaRequest);
await mediaRepository.save(media);
this.status = MediaRequestStatus.FAILED;
requestRepository.save(this);
logger.warn( logger.warn(
'Something went wrong sending movie request to Radarr, marking status as UNKNOWN', 'Something went wrong sending movie request to Radarr, marking status as FAILED',
{ {
label: 'Media Request', label: 'Media Request',
requestId: this.id, requestId: this.id,
@ -684,10 +687,13 @@ export class MediaRequest {
await mediaRepository.save(media); await mediaRepository.save(media);
}) })
.catch(async () => { .catch(async () => {
media[this.is4k ? 'status4k' : 'status'] = MediaStatus.UNKNOWN; const requestRepository = getRepository(MediaRequest);
await mediaRepository.save(media);
this.status = MediaRequestStatus.FAILED;
requestRepository.save(this);
logger.warn( logger.warn(
'Something went wrong sending series request to Sonarr, marking status as UNKNOWN', 'Something went wrong sending series request to Sonarr, marking status as FAILED',
{ {
label: 'Media Request', label: 'Media Request',
requestId: this.id, requestId: this.id,

@ -40,11 +40,15 @@ requestRoutes.get<Record<string, unknown>, RequestResultsResponse>(
MediaRequestStatus.APPROVED, MediaRequestStatus.APPROVED,
]; ];
break; break;
case 'failed':
statusFilter = [MediaRequestStatus.FAILED];
break;
default: default:
statusFilter = [ statusFilter = [
MediaRequestStatus.PENDING, MediaRequestStatus.PENDING,
MediaRequestStatus.APPROVED, MediaRequestStatus.APPROVED,
MediaRequestStatus.DECLINED, MediaRequestStatus.DECLINED,
MediaRequestStatus.FAILED,
]; ];
} }

@ -179,6 +179,11 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
{intl.formatMessage(globalMessages.pending)} {intl.formatMessage(globalMessages.pending)}
</Badge> </Badge>
)} )}
{request.status === MediaRequestStatus.FAILED && (
<Badge badgeType="danger">
{intl.formatMessage(globalMessages.failed)}
</Badge>
)}
</div> </div>
</div> </div>
<div className="mt-2 flex items-center text-sm leading-5 sm:mt-0"> <div className="mt-2 flex items-center text-sm leading-5 sm:mt-0">

@ -77,13 +77,13 @@ const RequestButton = ({
(request) => request.status === MediaRequestStatus.PENDING && request.is4k (request) => request.status === MediaRequestStatus.PENDING && request.is4k
); );
// Current user's pending request, or the first pending request
const activeRequest = useMemo(() => { const activeRequest = useMemo(() => {
return activeRequests && activeRequests.length > 0 return activeRequests && activeRequests.length > 0
? activeRequests.find((request) => request.requestedBy.id === user?.id) ?? ? activeRequests.find((request) => request.requestedBy.id === user?.id) ??
activeRequests[0] activeRequests[0]
: undefined; : undefined;
}, [activeRequests, user]); }, [activeRequests, user]);
const active4kRequest = useMemo(() => { const active4kRequest = useMemo(() => {
return active4kRequests && active4kRequests.length > 0 return active4kRequests && active4kRequests.length > 0
? active4kRequests.find( ? active4kRequests.find(
@ -121,54 +121,9 @@ const RequestButton = ({
}; };
const buttons: ButtonOption[] = []; const buttons: ButtonOption[] = [];
if (
(!media || media.status === MediaStatus.UNKNOWN) &&
hasPermission(
[
Permission.REQUEST,
mediaType === 'movie'
? Permission.REQUEST_MOVIE
: Permission.REQUEST_TV,
],
{ type: 'or' }
)
) {
buttons.push({
id: 'request',
text: intl.formatMessage(globalMessages.request),
action: () => {
setEditRequest(false);
setShowRequestModal(true);
},
svg: <DownloadIcon />,
});
}
if (
(!media || media.status4k === MediaStatus.UNKNOWN) &&
hasPermission(
[
Permission.REQUEST_4K,
mediaType === 'movie'
? Permission.REQUEST_4K_MOVIE
: Permission.REQUEST_4K_TV,
],
{ type: 'or' }
) &&
((settings.currentSettings.movie4kEnabled && mediaType === 'movie') ||
(settings.currentSettings.series4kEnabled && mediaType === 'tv'))
) {
buttons.push({
id: 'request4k',
text: intl.formatMessage(globalMessages.request4k),
action: () => {
setEditRequest(false);
setShowRequest4kModal(true);
},
svg: <DownloadIcon />,
});
}
// If there are pending requests, show request management options first
if (activeRequest || active4kRequest) {
if ( if (
activeRequest && activeRequest &&
(activeRequest.requestedBy.id === user?.id || (activeRequest.requestedBy.id === user?.id ||
@ -186,23 +141,6 @@ const RequestButton = ({
}); });
} }
if (
active4kRequest &&
(active4kRequest.requestedBy.id === user?.id ||
(active4kRequests?.length === 1 &&
hasPermission(Permission.MANAGE_REQUESTS)))
) {
buttons.push({
id: 'active-4k-request',
text: intl.formatMessage(messages.viewrequest4k),
action: () => {
setEditRequest(true);
setShowRequest4kModal(true);
},
svg: <InformationCircleIcon />,
});
}
if ( if (
activeRequest && activeRequest &&
hasPermission(Permission.MANAGE_REQUESTS) && hasPermission(Permission.MANAGE_REQUESTS) &&
@ -226,9 +164,7 @@ const RequestButton = ({
svg: <XIcon />, svg: <XIcon />,
} }
); );
} } else if (
if (
activeRequests && activeRequests &&
activeRequests.length > 0 && activeRequests.length > 0 &&
hasPermission(Permission.MANAGE_REQUESTS) && hasPermission(Permission.MANAGE_REQUESTS) &&
@ -258,6 +194,23 @@ const RequestButton = ({
); );
} }
if (
active4kRequest &&
(active4kRequest.requestedBy.id === user?.id ||
(active4kRequests?.length === 1 &&
hasPermission(Permission.MANAGE_REQUESTS)))
) {
buttons.push({
id: 'active-4k-request',
text: intl.formatMessage(messages.viewrequest4k),
action: () => {
setEditRequest(true);
setShowRequest4kModal(true);
},
svg: <InformationCircleIcon />,
});
}
if ( if (
active4kRequest && active4kRequest &&
hasPermission(Permission.MANAGE_REQUESTS) && hasPermission(Permission.MANAGE_REQUESTS) &&
@ -281,9 +234,7 @@ const RequestButton = ({
svg: <XIcon />, svg: <XIcon />,
} }
); );
} } else if (
if (
active4kRequests && active4kRequests &&
active4kRequests.length > 0 && active4kRequests.length > 0 &&
hasPermission(Permission.MANAGE_REQUESTS) && hasPermission(Permission.MANAGE_REQUESTS) &&
@ -312,8 +263,31 @@ const RequestButton = ({
} }
); );
} }
}
// Standard request button
if ( if (
(!media || media.status === MediaStatus.UNKNOWN) &&
hasPermission(
[
Permission.REQUEST,
mediaType === 'movie'
? Permission.REQUEST_MOVIE
: Permission.REQUEST_TV,
],
{ type: 'or' }
)
) {
buttons.push({
id: 'request',
text: intl.formatMessage(globalMessages.request),
action: () => {
setEditRequest(false);
setShowRequestModal(true);
},
svg: <DownloadIcon />,
});
} else if (
mediaType === 'tv' && mediaType === 'tv' &&
(!activeRequest || activeRequest.requestedBy.id !== user?.id) && (!activeRequest || activeRequest.requestedBy.id !== user?.id) &&
hasPermission([Permission.REQUEST, Permission.REQUEST_TV], { hasPermission([Permission.REQUEST, Permission.REQUEST_TV], {
@ -335,7 +309,31 @@ const RequestButton = ({
}); });
} }
// 4K request button
if ( if (
(!media || media.status4k === MediaStatus.UNKNOWN) &&
hasPermission(
[
Permission.REQUEST_4K,
mediaType === 'movie'
? Permission.REQUEST_4K_MOVIE
: Permission.REQUEST_4K_TV,
],
{ type: 'or' }
) &&
((settings.currentSettings.movie4kEnabled && mediaType === 'movie') ||
(settings.currentSettings.series4kEnabled && mediaType === 'tv'))
) {
buttons.push({
id: 'request4k',
text: intl.formatMessage(globalMessages.request4k),
action: () => {
setEditRequest(false);
setShowRequest4kModal(true);
},
svg: <DownloadIcon />,
});
} else if (
mediaType === 'tv' && mediaType === 'tv' &&
(!active4kRequest || active4kRequest.requestedBy.id !== user?.id) && (!active4kRequest || active4kRequest.requestedBy.id !== user?.id) &&
hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], { hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], {

@ -12,10 +12,7 @@ import { useInView } from 'react-intersection-observer';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications'; import { useToasts } from 'react-toast-notifications';
import useSWR, { mutate } from 'swr'; import useSWR, { mutate } from 'swr';
import { import { MediaRequestStatus } from '../../../server/constants/media';
MediaRequestStatus,
MediaStatus,
} from '../../../server/constants/media';
import type { MediaRequest } from '../../../server/entity/MediaRequest'; import type { MediaRequest } from '../../../server/entity/MediaRequest';
import type { MovieDetails } from '../../../server/models/Movie'; import type { MovieDetails } from '../../../server/models/Movie';
import type { TvDetails } from '../../../server/models/Tv'; import type { TvDetails } from '../../../server/models/Tv';
@ -275,8 +272,7 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
<Badge badgeType="danger"> <Badge badgeType="danger">
{intl.formatMessage(globalMessages.declined)} {intl.formatMessage(globalMessages.declined)}
</Badge> </Badge>
) : requestData.media[requestData.is4k ? 'status4k' : 'status'] === ) : requestData.status === MediaRequestStatus.FAILED ? (
MediaStatus.UNKNOWN ? (
<Badge <Badge
badgeType="danger" badgeType="danger"
href={`/${requestData.type}/${requestData.media.tmdbId}?manage=1`} href={`/${requestData.type}/${requestData.media.tmdbId}?manage=1`}
@ -305,9 +301,7 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
)} )}
</div> </div>
<div className="flex flex-1 items-end space-x-2"> <div className="flex flex-1 items-end space-x-2">
{requestData.media[requestData.is4k ? 'status4k' : 'status'] === {requestData.status === MediaRequestStatus.FAILED &&
MediaStatus.UNKNOWN &&
requestData.status !== MediaRequestStatus.DECLINED &&
hasPermission(Permission.MANAGE_REQUESTS) && ( hasPermission(Permission.MANAGE_REQUESTS) && (
<Button <Button
buttonType="primary" buttonType="primary"

@ -12,10 +12,7 @@ import { useInView } from 'react-intersection-observer';
import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl'; import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications'; import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr'; import useSWR from 'swr';
import { import { MediaRequestStatus } from '../../../../server/constants/media';
MediaRequestStatus,
MediaStatus,
} from '../../../../server/constants/media';
import type { MediaRequest } from '../../../../server/entity/MediaRequest'; import type { MediaRequest } from '../../../../server/entity/MediaRequest';
import type { MovieDetails } from '../../../../server/models/Movie'; import type { MovieDetails } from '../../../../server/models/Movie';
import type { TvDetails } from '../../../../server/models/Tv'; import type { TvDetails } from '../../../../server/models/Tv';
@ -273,9 +270,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
<Badge badgeType="danger"> <Badge badgeType="danger">
{intl.formatMessage(globalMessages.declined)} {intl.formatMessage(globalMessages.declined)}
</Badge> </Badge>
) : requestData.media[ ) : requestData.status === MediaRequestStatus.FAILED ? (
requestData.is4k ? 'status4k' : 'status'
] === MediaStatus.UNKNOWN ? (
<Badge <Badge
badgeType="danger" badgeType="danger"
href={`/${requestData.type}/${requestData.media.tmdbId}?manage=1`} href={`/${requestData.type}/${requestData.media.tmdbId}?manage=1`}
@ -402,9 +397,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
</div> </div>
</div> </div>
<div className="z-10 mt-4 flex w-full flex-col justify-center space-y-2 pl-4 pr-4 xl:mt-0 xl:w-96 xl:items-end xl:pl-0"> <div className="z-10 mt-4 flex w-full flex-col justify-center space-y-2 pl-4 pr-4 xl:mt-0 xl:w-96 xl:items-end xl:pl-0">
{requestData.media[requestData.is4k ? 'status4k' : 'status'] === {requestData.status === MediaRequestStatus.FAILED &&
MediaStatus.UNKNOWN &&
requestData.status !== MediaRequestStatus.DECLINED &&
hasPermission(Permission.MANAGE_REQUESTS) && ( hasPermission(Permission.MANAGE_REQUESTS) && (
<Button <Button
className="w-full" className="w-full"

@ -33,6 +33,7 @@ enum Filter {
PROCESSING = 'processing', PROCESSING = 'processing',
AVAILABLE = 'available', AVAILABLE = 'available',
UNAVAILABLE = 'unavailable', UNAVAILABLE = 'unavailable',
FAILED = 'failed',
} }
type Sort = 'added' | 'modified'; type Sort = 'added' | 'modified';
@ -158,6 +159,9 @@ const RequestList = () => {
<option value="processing"> <option value="processing">
{intl.formatMessage(globalMessages.processing)} {intl.formatMessage(globalMessages.processing)}
</option> </option>
<option value="failed">
{intl.formatMessage(globalMessages.failed)}
</option>
<option value="available"> <option value="available">
{intl.formatMessage(globalMessages.available)} {intl.formatMessage(globalMessages.available)}
</option> </option>

Loading…
Cancel
Save