New: Added filter and sort options to Collections (#8731)

* New: Added filter and sort options to Collections

* Add AllMovieWithCollectionsTmdbIds method to MovieService and MovieRepository
This commit is contained in:
Ricardo Christmann 2023-06-25 16:04:57 +02:00 committed by GitHub
parent fed98a648f
commit cbae355402
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 62 additions and 19 deletions

View File

@ -2,17 +2,12 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
import createCollectionSelector from 'Store/Selectors/createCollectionSelector';
function createMapStateToProps() {
return createSelector(
createCollectionSelector(),
createAllMoviesSelector(),
(
collection,
allMovies
) => {
(collection) => {
// If a movie is deleted this selector may fire before the parent
// selecors, which will result in an undefined movie, if that happens
// we want to return early here and again in the render function to avoid
@ -22,21 +17,11 @@ function createMapStateToProps() {
return {};
}
let allGenres = [];
let libraryMovies = 0;
collection.movies.forEach((movie) => {
allGenres = allGenres.concat(movie.genres);
if (allMovies.find((libraryMovie) => libraryMovie.tmdbId === movie.tmdbId)) {
libraryMovies++;
}
});
const allGenres = collection.movies.flatMap((movie) => movie.genres);
return {
...collection,
genres: Array.from(new Set(allGenres)).slice(0, 3),
missingMovies: collection.movies.length - libraryMovies
genres: Array.from(new Set(allGenres)).slice(0, 3)
};
}
);

View File

@ -28,6 +28,14 @@ function CollectionSortMenu(props) {
>
{translate('Title')}
</SortMenuItem>
<SortMenuItem
name="missingMovies"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('Missing')}
</SortMenuItem>
</MenuContent>
</SortMenu>
);

View File

@ -1,7 +1,7 @@
import _ from 'lodash';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, sortDirections } from 'Helpers/Props';
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import sortByName from 'Utilities/Array/sortByName';
import createAjaxRequest from 'Utilities/createAjaxRequest';
@ -62,6 +62,28 @@ export const defaultState = {
key: 'all',
label: 'All',
filters: []
},
{
key: 'missing',
label: 'Missing',
filters: [
{
key: 'missingMovies',
value: 0,
type: filterTypes.GREATER_THAN
}
]
},
{
key: 'complete',
label: 'Complete',
filters: [
{
key: 'missingMovies',
value: 0,
type: filterTypes.EQUAL
}
]
}
],

View File

@ -31,6 +31,7 @@ namespace NzbDrone.Core.Movies
Dictionary<int, List<int>> AllMovieTags();
List<int> GetRecommendations();
bool ExistsByMetadataId(int metadataId);
HashSet<int> AllMovieWithCollectionsTmdbIds();
}
public class MovieRepository : BasicRepository<Movie>, IMovieRepository
@ -373,5 +374,13 @@ namespace NzbDrone.Core.Movies
return movies.Any();
}
public HashSet<int> AllMovieWithCollectionsTmdbIds()
{
using (var conn = _database.OpenConnection())
{
return conn.Query<int>("SELECT \"TmdbId\" FROM \"MovieMetadata\" JOIN \"Movies\" ON (\"Movies\".\"MovieMetadataId\" = \"MovieMetadata\".\"Id\") WHERE \"CollectionTmdbId\" > 0").ToHashSet();
}
}
}
}

View File

@ -48,6 +48,7 @@ namespace NzbDrone.Core.Movies
bool MoviePathExists(string folder);
void RemoveAddOptions(Movie movie);
bool ExistsByMetadataId(int metadataId);
HashSet<int> AllMovieWithCollectionsTmdbIds();
}
public class MovieService : IMovieService, IHandle<MovieFileAddedEvent>,
@ -390,6 +391,11 @@ namespace NzbDrone.Core.Movies
return _movieRepository.ExistsByMetadataId(metadataId);
}
public HashSet<int> AllMovieWithCollectionsTmdbIds()
{
return _movieRepository.AllMovieWithCollectionsTmdbIds();
}
private Movie ReturnSingleMovieOrThrow(List<Movie> movies)
{
if (movies.Count == 0)

View File

@ -135,6 +135,7 @@ namespace Radarr.Api.V3.Collections
// Avoid calling for naming spec on every movie in filenamebuilder
var namingConfig = _namingService.GetConfig();
var collectionMovies = _movieMetadataService.GetMoviesWithCollections();
var existingMoviesTmdbIds = _movieService.AllMovieWithCollectionsTmdbIds();
foreach (var collection in collections)
{
@ -145,6 +146,11 @@ namespace Radarr.Api.V3.Collections
var movieResource = movie.ToResource();
movieResource.Folder = _fileNameBuilder.GetMovieFolder(new Movie { MovieMetadata = movie }, namingConfig);
if (!existingMoviesTmdbIds.Contains(movie.TmdbId))
{
resource.MissingMovies++;
}
resource.Movies.Add(movieResource);
}
@ -155,12 +161,18 @@ namespace Radarr.Api.V3.Collections
private CollectionResource MapToResource(MovieCollection collection)
{
var resource = collection.ToResource();
var existingMoviesTmdbIds = _movieService.AllMovieWithCollectionsTmdbIds();
foreach (var movie in _movieMetadataService.GetMoviesByCollectionTmdbId(collection.TmdbId))
{
var movieResource = movie.ToResource();
movieResource.Folder = _fileNameBuilder.GetMovieFolder(new Movie { MovieMetadata = movie });
if (!existingMoviesTmdbIds.Contains(movie.TmdbId))
{
resource.MissingMovies++;
}
resource.Movies.Add(movieResource);
}

View File

@ -25,6 +25,7 @@ namespace Radarr.Api.V3.Collections
public bool SearchOnAdd { get; set; }
public MovieStatusType MinimumAvailability { get; set; }
public List<CollectionMovieResource> Movies { get; set; }
public int MissingMovies { get; set; }
}
public static class CollectionResourceMapper