From ec058436d3e0fb884c23087cce59898fe02aa4cf Mon Sep 17 00:00:00 2001 From: Robin Dadswell <19610103+RobinDadswell@users.noreply.github.com> Date: Sat, 30 Jan 2021 22:34:20 +0000 Subject: [PATCH] New: Unify series custom filter options Closes #3548 --- .../SeasonPassFilterModalConnector.js | 2 +- frontend/src/SeasonPass/SeasonPassRow.js | 8 +- .../SeriesEditorFilterModalConnector.js | 2 +- frontend/src/Series/Editor/SeriesEditorRow.js | 12 +- .../Index/SeriesIndexFilterModalConnector.js | 2 +- .../src/Store/Actions/seasonPassActions.js | 47 +----- frontend/src/Store/Actions/seriesActions.js | 156 ++++++++++++++++- .../src/Store/Actions/seriesEditorActions.js | 60 +------ .../src/Store/Actions/seriesIndexActions.js | 157 +----------------- .../151_remove_custom_filter_type.cs | 17 ++ 10 files changed, 195 insertions(+), 268 deletions(-) create mode 100644 src/NzbDrone.Core/Datastore/Migration/151_remove_custom_filter_type.cs diff --git a/frontend/src/SeasonPass/SeasonPassFilterModalConnector.js b/frontend/src/SeasonPass/SeasonPassFilterModalConnector.js index ba3308cea..ee726f3a3 100644 --- a/frontend/src/SeasonPass/SeasonPassFilterModalConnector.js +++ b/frontend/src/SeasonPass/SeasonPassFilterModalConnector.js @@ -11,7 +11,7 @@ function createMapStateToProps() { return { sectionItems, filterBuilderProps, - customFilterType: 'seasonPass' + customFilterType: 'series' }; } ); diff --git a/frontend/src/SeasonPass/SeasonPassRow.js b/frontend/src/SeasonPass/SeasonPassRow.js index 6fb903d1a..dcdfeab9f 100644 --- a/frontend/src/SeasonPass/SeasonPassRow.js +++ b/frontend/src/SeasonPass/SeasonPassRow.js @@ -18,10 +18,10 @@ class SeasonPassRow extends Component { render() { const { seriesId, + monitored, status, - titleSlug, title, - monitored, + titleSlug, seasons, isSaving, isSelected, @@ -84,10 +84,10 @@ class SeasonPassRow extends Component { SeasonPassRow.propTypes = { seriesId: PropTypes.number.isRequired, + monitored: PropTypes.bool.isRequired, status: PropTypes.string.isRequired, - titleSlug: PropTypes.string.isRequired, title: PropTypes.string.isRequired, - monitored: PropTypes.bool.isRequired, + titleSlug: PropTypes.string.isRequired, seasons: PropTypes.arrayOf(PropTypes.object).isRequired, isSaving: PropTypes.bool.isRequired, isSelected: PropTypes.bool, diff --git a/frontend/src/Series/Editor/SeriesEditorFilterModalConnector.js b/frontend/src/Series/Editor/SeriesEditorFilterModalConnector.js index 07a5230b2..21639c319 100644 --- a/frontend/src/Series/Editor/SeriesEditorFilterModalConnector.js +++ b/frontend/src/Series/Editor/SeriesEditorFilterModalConnector.js @@ -11,7 +11,7 @@ function createMapStateToProps() { return { sectionItems, filterBuilderProps, - customFilterType: 'seriesEditor' + customFilterType: 'series' }; } ); diff --git a/frontend/src/Series/Editor/SeriesEditorRow.js b/frontend/src/Series/Editor/SeriesEditorRow.js index 3fe72d543..e2abd5abf 100644 --- a/frontend/src/Series/Editor/SeriesEditorRow.js +++ b/frontend/src/Series/Editor/SeriesEditorRow.js @@ -27,17 +27,17 @@ class SeriesEditorRow extends Component { render() { const { id, + monitored, status, - titleSlug, title, - monitored, - languageProfile, - qualityProfile, + titleSlug, seriesType, - seasonFolder, + qualityProfile, + languageProfile, path, - statistics = {}, tags, + seasonFolder, + statistics = {}, columns, isSelected, onSelectedChange diff --git a/frontend/src/Series/Index/SeriesIndexFilterModalConnector.js b/frontend/src/Series/Index/SeriesIndexFilterModalConnector.js index 4833f42f2..232fa11a1 100644 --- a/frontend/src/Series/Index/SeriesIndexFilterModalConnector.js +++ b/frontend/src/Series/Index/SeriesIndexFilterModalConnector.js @@ -11,7 +11,7 @@ function createMapStateToProps() { return { sectionItems, filterBuilderProps, - customFilterType: 'seriesIndex' + customFilterType: 'series' }; } ); diff --git a/frontend/src/Store/Actions/seasonPassActions.js b/frontend/src/Store/Actions/seasonPassActions.js index b271e8b89..e91281c23 100644 --- a/frontend/src/Store/Actions/seasonPassActions.js +++ b/frontend/src/Store/Actions/seasonPassActions.js @@ -1,12 +1,12 @@ import { createAction } from 'redux-actions'; import createAjaxRequest from 'Utilities/createAjaxRequest'; -import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props'; +import { sortDirections } from 'Helpers/Props'; import { createThunk, handleThunks } from 'Store/thunks'; import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer'; import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer'; import createHandleActions from './Creators/createHandleActions'; import { set } from './baseActions'; -import { fetchSeries, filters, filterPredicates } from './seriesActions'; +import { fetchSeries, filters, filterPredicates, filterBuilderProps } from './seriesActions'; // // Variables @@ -27,48 +27,7 @@ export const defaultState = { filters, filterPredicates, - filterBuilderProps: [ - { - name: 'monitored', - label: 'Monitored', - type: filterBuilderTypes.EXACT, - valueType: filterBuilderValueTypes.BOOL - }, - { - name: 'status', - label: 'Status', - type: filterBuilderTypes.EXACT, - valueType: filterBuilderValueTypes.SERIES_STATUS - }, - { - name: 'seriesType', - label: 'Series Type', - type: filterBuilderTypes.EXACT - }, - { - name: 'qualityProfileId', - label: 'Quality Profile', - type: filterBuilderTypes.EXACT, - valueType: filterBuilderValueTypes.QUALITY_PROFILE - }, - { - name: 'languageProfileId', - label: 'Language Profile', - type: filterBuilderTypes.EXACT, - valueType: filterBuilderValueTypes.LANGUAGE_PROFILE - }, - { - name: 'rootFolderPath', - label: 'Root Folder Path', - type: filterBuilderTypes.EXACT - }, - { - name: 'tags', - label: 'Tags', - type: filterBuilderTypes.ARRAY, - valueType: filterBuilderValueTypes.TAG - } - ] + filterBuilderProps }; export const persistState = [ diff --git a/frontend/src/Store/Actions/seriesActions.js b/frontend/src/Store/Actions/seriesActions.js index b26701a3c..5b66e1472 100644 --- a/frontend/src/Store/Actions/seriesActions.js +++ b/frontend/src/Store/Actions/seriesActions.js @@ -2,8 +2,9 @@ import _ from 'lodash'; import { createAction } from 'redux-actions'; import { batchActions } from 'redux-batched-actions'; import createAjaxRequest from 'Utilities/createAjaxRequest'; +import sortByName from 'Utilities/Array/sortByName'; import dateFilterPredicate from 'Utilities/Date/dateFilterPredicate'; -import { filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props'; +import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props'; import { createThunk, handleThunks } from 'Store/thunks'; import createSetSettingValueReducer from './Creators/Reducers/createSetSettingValueReducer'; import createFetchHandler from './Creators/createFetchHandler'; @@ -89,6 +90,23 @@ export const filters = [ ]; export const filterPredicates = { + episodeProgress: function(item, filterValue, type) { + const { statistics = {} } = item; + + const { + episodeCount = 0, + episodeFileCount + } = statistics; + + const progress = episodeCount ? + episodeFileCount / episodeCount * 100 : + 100; + + const predicate = filterTypePredicates[type]; + + return predicate(progress, filterValue); + }, + missing: function(item) { const { statistics = {} } = item; @@ -130,6 +148,142 @@ export const filterPredicates = { } }; +export const filterBuilderProps = [ + { + name: 'monitored', + label: 'Monitored', + type: filterBuilderTypes.EXACT, + valueType: filterBuilderValueTypes.BOOL + }, + { + name: 'status', + label: 'Status', + type: filterBuilderTypes.EXACT, + valueType: filterBuilderValueTypes.SERIES_STATUS + }, + { + name: 'seriesType', + label: 'Type', + type: filterBuilderTypes.EXACT, + valueType: filterBuilderValueTypes.SERIES_TYPES + }, + { + name: 'network', + label: 'Network', + type: filterBuilderTypes.STRING, + optionsSelector: function(items) { + const tagList = items.reduce((acc, series) => { + if (series.network) { + acc.push({ + id: series.network, + name: series.network + }); + } + + return acc; + }, []); + + return tagList.sort(sortByName); + } + }, + { + name: 'qualityProfileId', + label: 'Quality Profile', + type: filterBuilderTypes.EXACT, + valueType: filterBuilderValueTypes.QUALITY_PROFILE + }, + { + name: 'languageProfileId', + label: 'Language Profile', + type: filterBuilderTypes.EXACT, + valueType: filterBuilderValueTypes.LANGUAGE_PROFILE + }, + { + name: 'nextAiring', + label: 'Next Airing', + type: filterBuilderTypes.DATE, + valueType: filterBuilderValueTypes.DATE + }, + { + name: 'previousAiring', + label: 'Previous Airing', + type: filterBuilderTypes.DATE, + valueType: filterBuilderValueTypes.DATE + }, + { + name: 'added', + label: 'Added', + type: filterBuilderTypes.DATE, + valueType: filterBuilderValueTypes.DATE + }, + { + name: 'seasonCount', + label: 'Season Count', + type: filterBuilderTypes.NUMBER + }, + { + name: 'episodeProgress', + label: 'Episode Progress', + type: filterBuilderTypes.NUMBER + }, + { + name: 'path', + label: 'Path', + type: filterBuilderTypes.STRING + }, + { + name: 'rootFolderPath', + label: 'Root Folder Path', + type: filterBuilderTypes.EXACT + }, + { + name: 'sizeOnDisk', + label: 'Size on Disk', + type: filterBuilderTypes.NUMBER, + valueType: filterBuilderValueTypes.BYTES + }, + { + name: 'genres', + label: 'Genres', + type: filterBuilderTypes.ARRAY, + optionsSelector: function(items) { + const tagList = items.reduce((acc, series) => { + series.genres.forEach((genre) => { + acc.push({ + id: genre, + name: genre + }); + }); + + return acc; + }, []); + + return tagList.sort(sortByName); + } + }, + { + name: 'ratings', + label: 'Rating', + type: filterBuilderTypes.NUMBER + }, + { + name: 'certification', + label: 'Certification', + type: filterBuilderTypes.EXACT + }, + { + name: 'tags', + label: 'Tags', + type: filterBuilderTypes.ARRAY, + valueType: filterBuilderValueTypes.TAG + }, + { + name: 'useSceneNumbering', + label: 'Scene Numbering', + type: filterBuilderTypes.EXACT + } +]; + export const sortPredicates = { status: function(item) { let result = 0; diff --git a/frontend/src/Store/Actions/seriesEditorActions.js b/frontend/src/Store/Actions/seriesEditorActions.js index d0e7b55aa..b038869b3 100644 --- a/frontend/src/Store/Actions/seriesEditorActions.js +++ b/frontend/src/Store/Actions/seriesEditorActions.js @@ -1,14 +1,14 @@ import { createAction } from 'redux-actions'; import { batchActions } from 'redux-batched-actions'; import createAjaxRequest from 'Utilities/createAjaxRequest'; -import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props'; +import { sortDirections } from 'Helpers/Props'; import { createThunk, handleThunks } from 'Store/thunks'; import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer'; import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer'; import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer'; import createHandleActions from './Creators/createHandleActions'; import { set, updateItem } from './baseActions'; -import { filters, filterPredicates, sortPredicates } from './seriesActions'; +import { filters, filterPredicates, filterBuilderProps } from './seriesActions'; // // Variables @@ -90,61 +90,7 @@ export const defaultState = { } ], - filterBuilderProps: [ - { - name: 'monitored', - label: 'Monitored', - type: filterBuilderTypes.EXACT, - valueType: filterBuilderValueTypes.BOOL - }, - { - name: 'status', - label: 'Status', - type: filterBuilderTypes.EXACT, - valueType: filterBuilderValueTypes.SERIES_STATUS - }, - { - name: 'seriesType', - label: 'Series Type', - type: filterBuilderTypes.EXACT - }, - { - name: 'qualityProfileId', - label: 'Quality Profile', - type: filterBuilderTypes.EXACT, - valueType: filterBuilderValueTypes.QUALITY_PROFILE - }, - { - name: 'languageProfileId', - label: 'Language Profile', - type: filterBuilderTypes.EXACT, - valueType: filterBuilderValueTypes.LANGUAGE_PROFILE - }, - { - name: 'path', - label: 'Path', - type: filterBuilderTypes.STRING - }, - { - name: 'rootFolderPath', - label: 'Root Folder Path', - type: filterBuilderTypes.EXACT - }, - { - name: 'sizeOnDisk', - label: 'Size on Disk', - type: filterBuilderTypes.NUMBER, - valueType: filterBuilderValueTypes.BYTES - }, - { - name: 'tags', - label: 'Tags', - type: filterBuilderTypes.ARRAY, - valueType: filterBuilderValueTypes.TAG - } - ], - - sortPredicates + filterBuilderProps }; export const persistState = [ diff --git a/frontend/src/Store/Actions/seriesIndexActions.js b/frontend/src/Store/Actions/seriesIndexActions.js index be706bd5f..a953652b6 100644 --- a/frontend/src/Store/Actions/seriesIndexActions.js +++ b/frontend/src/Store/Actions/seriesIndexActions.js @@ -1,12 +1,11 @@ import moment from 'moment'; import { createAction } from 'redux-actions'; -import sortByName from 'Utilities/Array/sortByName'; -import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, sortDirections } from 'Helpers/Props'; +import { sortDirections } from 'Helpers/Props'; import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer'; import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer'; import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer'; import createHandleActions from './Creators/createHandleActions'; -import { filters, filterPredicates, sortPredicates } from './seriesActions'; +import { filters, filterPredicates, filterBuilderProps, sortPredicates } from './seriesActions'; // // Variables @@ -247,157 +246,9 @@ export const defaultState = { filters, - filterPredicates: { - ...filterPredicates, + filterPredicates, - episodeProgress: function(item, filterValue, type) { - const { statistics = {} } = item; - - const { - episodeCount = 0, - episodeFileCount - } = statistics; - - const progress = episodeCount ? - episodeFileCount / episodeCount * 100 : - 100; - - const predicate = filterTypePredicates[type]; - - return predicate(progress, filterValue); - } - }, - - filterBuilderProps: [ - { - name: 'monitored', - label: 'Monitored', - type: filterBuilderTypes.EXACT, - valueType: filterBuilderValueTypes.BOOL - }, - { - name: 'status', - label: 'Status', - type: filterBuilderTypes.EXACT, - valueType: filterBuilderValueTypes.SERIES_STATUS - }, - { - name: 'seriesType', - label: 'Type', - type: filterBuilderTypes.EXACT, - valueType: filterBuilderValueTypes.SERIES_TYPES - }, - { - name: 'network', - label: 'Network', - type: filterBuilderTypes.STRING, - optionsSelector: function(items) { - const tagList = items.reduce((acc, series) => { - if (series.network) { - acc.push({ - id: series.network, - name: series.network - }); - } - - return acc; - }, []); - - return tagList.sort(sortByName); - } - }, - { - name: 'qualityProfileId', - label: 'Quality Profile', - type: filterBuilderTypes.EXACT, - valueType: filterBuilderValueTypes.QUALITY_PROFILE - }, - { - name: 'languageProfileId', - label: 'Language Profile', - type: filterBuilderTypes.EXACT, - valueType: filterBuilderValueTypes.LANGUAGE_PROFILE - }, - { - name: 'nextAiring', - label: 'Next Airing', - type: filterBuilderTypes.DATE, - valueType: filterBuilderValueTypes.DATE - }, - { - name: 'previousAiring', - label: 'Previous Airing', - type: filterBuilderTypes.DATE, - valueType: filterBuilderValueTypes.DATE - }, - { - name: 'added', - label: 'Added', - type: filterBuilderTypes.DATE, - valueType: filterBuilderValueTypes.DATE - }, - { - name: 'seasonCount', - label: 'Season Count', - type: filterBuilderTypes.NUMBER - }, - { - name: 'episodeProgress', - label: 'Episode Progress', - type: filterBuilderTypes.NUMBER - }, - { - name: 'path', - label: 'Path', - type: filterBuilderTypes.STRING - }, - { - name: 'sizeOnDisk', - label: 'Size on Disk', - type: filterBuilderTypes.NUMBER, - valueType: filterBuilderValueTypes.BYTES - }, - { - name: 'genres', - label: 'Genres', - type: filterBuilderTypes.ARRAY, - optionsSelector: function(items) { - const tagList = items.reduce((acc, series) => { - series.genres.forEach((genre) => { - acc.push({ - id: genre, - name: genre - }); - }); - - return acc; - }, []); - - return tagList.sort(sortByName); - } - }, - { - name: 'ratings', - label: 'Rating', - type: filterBuilderTypes.NUMBER - }, - { - name: 'certification', - label: 'Certification', - type: filterBuilderTypes.EXACT - }, - { - name: 'tags', - label: 'Tags', - type: filterBuilderTypes.ARRAY, - valueType: filterBuilderValueTypes.TAG - }, - { - name: 'useSceneNumbering', - label: 'Scene Numbering', - type: filterBuilderTypes.EXACT - } - ] + filterBuilderProps }; export const persistState = [ diff --git a/src/NzbDrone.Core/Datastore/Migration/151_remove_custom_filter_type.cs b/src/NzbDrone.Core/Datastore/Migration/151_remove_custom_filter_type.cs new file mode 100644 index 000000000..a7bceab5d --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/151_remove_custom_filter_type.cs @@ -0,0 +1,17 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(151)] + public class remove_custom_filter_type : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Update.Table("CustomFilters").Set(new { Type = "series" }).Where(new { Type = "seriesIndex" }); + Update.Table("CustomFilters").Set(new { Type = "series" }).Where(new { Type = "seriesEditor" }); + Update.Table("CustomFilters").Set(new { Type = "series" }).Where(new { Type = "seasonPass" }); + } + } +} +