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

* New: Added filter and sort options to Collections

* Add AllMovieWithCollectionsTmdbIds method to MovieService and MovieRepository
pull/8733/head
Ricardo Christmann 1 year ago committed by GitHub
parent fed98a648f
commit cbae355402
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

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

@ -1,7 +1,7 @@
import _ from 'lodash'; import _ from 'lodash';
import { createAction } from 'redux-actions'; import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-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 { createThunk, handleThunks } from 'Store/thunks';
import sortByName from 'Utilities/Array/sortByName'; import sortByName from 'Utilities/Array/sortByName';
import createAjaxRequest from 'Utilities/createAjaxRequest'; import createAjaxRequest from 'Utilities/createAjaxRequest';
@ -62,6 +62,28 @@ export const defaultState = {
key: 'all', key: 'all',
label: 'All', label: 'All',
filters: [] 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
}
]
} }
], ],

@ -31,6 +31,7 @@ namespace NzbDrone.Core.Movies
Dictionary<int, List<int>> AllMovieTags(); Dictionary<int, List<int>> AllMovieTags();
List<int> GetRecommendations(); List<int> GetRecommendations();
bool ExistsByMetadataId(int metadataId); bool ExistsByMetadataId(int metadataId);
HashSet<int> AllMovieWithCollectionsTmdbIds();
} }
public class MovieRepository : BasicRepository<Movie>, IMovieRepository public class MovieRepository : BasicRepository<Movie>, IMovieRepository
@ -373,5 +374,13 @@ namespace NzbDrone.Core.Movies
return movies.Any(); 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();
}
}
} }
} }

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

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

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

Loading…
Cancel
Save