From 9757e3ae0c572fb46177e25154b29e0ceced665f Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Sun, 14 Feb 2021 05:21:49 -0500 Subject: [PATCH] feat(ui): Add 'Available' filter to request list and remove unused MediaRequestStatus.AVAILABLE enum value (#905) --- overseerr-api.yml | 14 +- server/constants/media.ts | 1 - server/routes/request.ts | 158 +++++++++++++----- src/components/RequestList/index.tsx | 10 +- .../RequestModal/TvRequestModal.tsx | 7 - src/i18n/locale/en.json | 2 + 6 files changed, 136 insertions(+), 56 deletions(-) diff --git a/overseerr-api.yml b/overseerr-api.yml index dc1c5067..f9acf65b 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -27,13 +27,13 @@ tags: - name: tv description: Endpoints related to retrieving TV series and their details. - name: person - description: Endpoints related to retrieving Person details. + description: Endpoints related to retrieving person details. - name: media description: Endpoints related to media management. - name: collection - description: Endpoints related to retrieving Collection details. + description: Endpoints related to retrieving collection details. - name: service - description: Endpoinst related to getting Service (Radarr/Sonarr) details. + description: Endpoints related to getting service (Radarr/Sonarr) details. servers: - url: '{server}/api/v1' variables: @@ -3095,7 +3095,7 @@ paths: schema: type: string nullable: true - enum: [all, available, approved, pending, unavailable] + enum: [all, approved, available, pending, processing, unavailable] - in: query name: sort schema: @@ -3187,6 +3187,12 @@ paths: approved: type: number example: 10 + processing: + type: number + example: 4 + available: + type: number + example: 6 required: - pending - approved diff --git a/server/constants/media.ts b/server/constants/media.ts index a15dd7ee..d9ef9e02 100644 --- a/server/constants/media.ts +++ b/server/constants/media.ts @@ -2,7 +2,6 @@ export enum MediaRequestStatus { PENDING = 1, APPROVED, DECLINED, - AVAILABLE, } export enum MediaType { diff --git a/server/routes/request.ts b/server/routes/request.ts index a97bac0a..e63d7121 100644 --- a/server/routes/request.ts +++ b/server/routes/request.ts @@ -1,7 +1,7 @@ import { Router } from 'express'; import { isAuthenticated } from '../middleware/auth'; import { Permission } from '../lib/permissions'; -import { getRepository, FindOperator, FindOneOptions, In } from 'typeorm'; +import { getRepository } from 'typeorm'; import { MediaRequest } from '../entity/MediaRequest'; import TheMovieDb from '../api/themoviedb'; import Media from '../entity/Media'; @@ -19,61 +19,98 @@ requestRoutes.get('/', async (req, res, next) => { const pageSize = req.query.take ? Number(req.query.take) : 20; const skip = req.query.skip ? Number(req.query.skip) : 0; - let statusFilter: - | MediaRequestStatus - | FindOperator - | undefined = undefined; + let statusFilter: MediaRequestStatus[]; switch (req.query.filter) { - case 'available': - statusFilter = MediaRequestStatus.AVAILABLE; - break; case 'approved': - statusFilter = MediaRequestStatus.APPROVED; + case 'processing': + case 'available': + statusFilter = [MediaRequestStatus.APPROVED]; break; case 'pending': - statusFilter = MediaRequestStatus.PENDING; + statusFilter = [MediaRequestStatus.PENDING]; break; case 'unavailable': - statusFilter = In([ + statusFilter = [ + MediaRequestStatus.PENDING, + MediaRequestStatus.APPROVED, + ]; + break; + default: + statusFilter = [ MediaRequestStatus.PENDING, MediaRequestStatus.APPROVED, - ]); + MediaRequestStatus.DECLINED, + ]; + } + + let mediaStatusFilter: MediaStatus[]; + + switch (req.query.filter) { + case 'available': + mediaStatusFilter = [MediaStatus.AVAILABLE]; + break; + case 'processing': + case 'unavailable': + mediaStatusFilter = [ + MediaStatus.UNKNOWN, + MediaStatus.PENDING, + MediaStatus.PROCESSING, + MediaStatus.PARTIALLY_AVAILABLE, + ]; break; default: - statusFilter = In(Object.values(MediaRequestStatus)); + mediaStatusFilter = [ + MediaStatus.UNKNOWN, + MediaStatus.PENDING, + MediaStatus.PROCESSING, + MediaStatus.PARTIALLY_AVAILABLE, + MediaStatus.AVAILABLE, + ]; } - let sortFilter: FindOneOptions['order'] = { - id: 'DESC', - }; + let sortFilter: string; switch (req.query.sort) { case 'modified': - sortFilter = { - updatedAt: 'DESC', - }; + sortFilter = 'request.updatedAt'; break; + default: + sortFilter = 'request.id'; } - const [requests, requestCount] = req.user?.hasPermission( - [Permission.MANAGE_REQUESTS, Permission.REQUEST_VIEW], - { type: 'or' } - ) - ? await requestRepository.findAndCount({ - order: sortFilter, - relations: ['media', 'modifiedBy'], - where: { status: statusFilter }, - take: Number(req.query.take) ?? 20, - skip, - }) - : await requestRepository.findAndCount({ - where: { requestedBy: { id: req.user?.id }, status: statusFilter }, - relations: ['media', 'modifiedBy'], - order: sortFilter, - take: Number(req.query.limit) ?? 20, - skip, - }); + let query = requestRepository + .createQueryBuilder('request') + .leftJoinAndSelect('request.media', 'media') + .leftJoinAndSelect('request.seasons', 'seasons') + .leftJoinAndSelect('request.modifiedBy', 'modifiedBy') + .leftJoinAndSelect('request.requestedBy', 'requestedBy') + .where('request.status IN (:...requestStatus)', { + requestStatus: statusFilter, + }) + .andWhere( + '(request.is4k = false AND media.status IN (:...mediaStatus)) OR (request.is4k = true AND media.status4k IN (:...mediaStatus))', + { + mediaStatus: mediaStatusFilter, + } + ); + + if ( + !req.user?.hasPermission( + [Permission.MANAGE_REQUESTS, Permission.REQUEST_VIEW], + { type: 'or' } + ) + ) { + query = query.andWhere('request.requestedBy.id = :id', { + id: req.user?.id, + }); + } + + const [requests, requestCount] = await query + .orderBy(sortFilter, 'DESC') + .take(pageSize) + .skip(skip) + .getManyAndCount(); return res.status(200).json({ pageInfo: { @@ -279,16 +316,51 @@ requestRoutes.get('/count', async (_req, res, next) => { const requestRepository = getRepository(MediaRequest); try { - const pendingCount = await requestRepository.count({ - status: MediaRequestStatus.PENDING, - }); - const approvedCount = await requestRepository.count({ - status: MediaRequestStatus.APPROVED, - }); + const query = requestRepository + .createQueryBuilder('request') + .leftJoinAndSelect('request.media', 'media'); + + const pendingCount = await query + .where('request.status = :requestStatus', { + requestStatus: MediaRequestStatus.PENDING, + }) + .getCount(); + + const approvedCount = await query + .where('request.status = :requestStatus', { + requestStatus: MediaRequestStatus.APPROVED, + }) + .getCount(); + + const processingCount = await query + .where('request.status = :requestStatus', { + requestStatus: MediaRequestStatus.APPROVED, + }) + .andWhere( + '(request.is4k = false AND media.status != :availableStatus) OR (request.is4k = true AND media.status4k != :availableStatus)', + { + availableStatus: MediaStatus.AVAILABLE, + } + ) + .getCount(); + + const availableCount = await query + .where('request.status = :requestStatus', { + requestStatus: MediaRequestStatus.APPROVED, + }) + .andWhere( + '(request.is4k = false AND media.status = :availableStatus) OR (request.is4k = true AND media.status4k = :availableStatus)', + { + availableStatus: MediaStatus.AVAILABLE, + } + ) + .getCount(); return res.status(200).json({ pending: pendingCount, approved: approvedCount, + processing: processingCount, + available: availableCount, }); } catch (e) { next({ status: 500, message: e.message }); diff --git a/src/components/RequestList/index.tsx b/src/components/RequestList/index.tsx index 741729f3..a7cf1df1 100644 --- a/src/components/RequestList/index.tsx +++ b/src/components/RequestList/index.tsx @@ -22,13 +22,15 @@ const messages = defineMessages({ filterAll: 'All', filterPending: 'Pending', filterApproved: 'Approved', + filterAvailable: 'Available', + filterProcessing: 'Processing', noresults: 'No results.', showallrequests: 'Show All Requests', sortAdded: 'Request Date', sortModified: 'Last Modified', }); -type Filter = 'all' | 'approved' | 'pending'; +type Filter = 'all' | 'pending' | 'approved' | 'processing' | 'available'; type Sort = 'added' | 'modified'; const RequestList: React.FC = () => { @@ -93,6 +95,12 @@ const RequestList: React.FC = () => { + +
diff --git a/src/components/RequestModal/TvRequestModal.tsx b/src/components/RequestModal/TvRequestModal.tsx index 5c2a88d5..b29f1725 100644 --- a/src/components/RequestModal/TvRequestModal.tsx +++ b/src/components/RequestModal/TvRequestModal.tsx @@ -523,13 +523,6 @@ const TvRequestModal: React.FC = ({ {intl.formatMessage(globalMessages.requested)} )} - {!mediaSeason && - seasonRequest?.status === - MediaRequestStatus.AVAILABLE && ( - - {intl.formatMessage(globalMessages.available)} - - )} {mediaSeason?.[is4k ? 'status4k' : 'status'] === MediaStatus.PARTIALLY_AVAILABLE && ( diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index b4946550..4eb81de9 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -152,7 +152,9 @@ "components.RequestList.RequestItem.seasons": "Seasons", "components.RequestList.filterAll": "All", "components.RequestList.filterApproved": "Approved", + "components.RequestList.filterAvailable": "Available", "components.RequestList.filterPending": "Pending", + "components.RequestList.filterProcessing": "Processing", "components.RequestList.mediaInfo": "Media Info", "components.RequestList.modifiedBy": "Last Modified By", "components.RequestList.next": "Next",