New: Series custom filter for Monitored Episodes

Closes #7552
This commit is contained in:
Mark McDowall 2025-12-24 16:52:29 -08:00
parent ee875ae654
commit 5f8297da6c
13 changed files with 96 additions and 51 deletions

View file

@ -16,11 +16,11 @@ import DefaultFilterBuilderRowValue from './DefaultFilterBuilderRowValue';
import HistoryEventTypeFilterBuilderRowValue from './HistoryEventTypeFilterBuilderRowValue';
import IndexerFilterBuilderRowValue from './IndexerFilterBuilderRowValue';
import LanguageFilterBuilderRowValue from './LanguageFilterBuilderRowValue';
import MonitoredStatusFilterBuilderRowValue from './MonitoredStatusFilterBuilderRowValue';
import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue';
import QualityFilterBuilderRowValue from './QualityFilterBuilderRowValue';
import QualityProfileFilterBuilderRowValue from './QualityProfileFilterBuilderRowValue';
import QueueStatusFilterBuilderRowValue from './QueueStatusFilterBuilderRowValue';
import SeasonsMonitoredStatusFilterBuilderRowValue from './SeasonsMonitoredStatusFilterBuilderRowValue';
import SeriesFilterBuilderRowValue from './SeriesFilterBuilderRowValue';
import SeriesStatusFilterBuilderRowValue from './SeriesStatusFilterBuilderRowValue';
import SeriesTypeFilterBuilderRowValue from './SeriesTypeFilterBuilderRowValue';
@ -109,8 +109,8 @@ function getRowValueConnector<T>(
case filterBuilderValueTypes.QUEUE_STATUS:
return QueueStatusFilterBuilderRowValue;
case filterBuilderValueTypes.SEASONS_MONITORED_STATUS:
return SeasonsMonitoredStatusFilterBuilderRowValue;
case filterBuilderValueTypes.MONITORED_STATUS:
return MonitoredStatusFilterBuilderRowValue;
case filterBuilderValueTypes.SERIES:
return SeriesFilterBuilderRowValue;

View file

@ -0,0 +1,39 @@
import React from 'react';
import translate from 'Utilities/String/translate';
import FilterBuilderRowValue, {
FilterBuilderRowValueProps,
} from './FilterBuilderRowValue';
const monitoredStatusList = [
{
id: 'all',
get name() {
return translate('MonitoredAll');
},
},
{
id: 'partial',
get name() {
return translate('MonitoredPartial');
},
},
{
id: 'none',
get name() {
return translate('MonitoredNone');
},
},
];
type MonitoredStatusFilterBuilderRowValueProps<T> = Omit<
FilterBuilderRowValueProps<T, string, string>,
'tagList'
>;
function MonitoredStatusFilterBuilderRowValue<T>(
props: MonitoredStatusFilterBuilderRowValueProps<T>
) {
return <FilterBuilderRowValue tagList={monitoredStatusList} {...props} />;
}
export default MonitoredStatusFilterBuilderRowValue;

View file

@ -1,41 +0,0 @@
import React from 'react';
import translate from 'Utilities/String/translate';
import FilterBuilderRowValue, {
FilterBuilderRowValueProps,
} from './FilterBuilderRowValue';
const seasonsMonitoredStatusList = [
{
id: 'all',
get name() {
return translate('SeasonsMonitoredAll');
},
},
{
id: 'partial',
get name() {
return translate('SeasonsMonitoredPartial');
},
},
{
id: 'none',
get name() {
return translate('SeasonsMonitoredNone');
},
},
];
type SeasonsMonitoredStatusFilterBuilderRowValueProps<T> = Omit<
FilterBuilderRowValueProps<T, string, string>,
'tagList'
>;
function SeasonsMonitoredStatusFilterBuilderRowValue<T>(
props: SeasonsMonitoredStatusFilterBuilderRowValueProps<T>
) {
return (
<FilterBuilderRowValue tagList={seasonsMonitoredStatusList} {...props} />
);
}
export default SeasonsMonitoredStatusFilterBuilderRowValue;

View file

@ -9,7 +9,7 @@ export const PROTOCOL = 'protocol';
export const QUALITY = 'quality';
export const QUALITY_PROFILE = 'qualityProfile';
export const QUEUE_STATUS = 'queueStatus';
export const SEASONS_MONITORED_STATUS = 'seasonsMonitoredStatus';
export const MONITORED_STATUS = 'monitoredStatus';
export const SERIES = 'series';
export const SERIES_STATUS = 'seriesStatus';
export const SERIES_TYPES = 'seriesType';
@ -27,7 +27,7 @@ export type FilterBuildValueType =
| 'quality'
| 'qualityProfile'
| 'queueStatus'
| 'seasonsMonitoredStatus'
| 'monitoredStatus'
| 'series'
| 'seriesStatus'
| 'seriesType'

View file

@ -36,6 +36,7 @@ export interface Statistics {
releaseGroups: string[];
sizeOnDisk: number;
totalEpisodeCount: number;
monitoredEpisodeCount: number;
lastAired?: string;
}

View file

@ -17,7 +17,7 @@ import { SortDirection } from 'Helpers/Props/sortDirections';
import sortByProp from 'Utilities/Array/sortByProp';
import clientSideFilterAndSort from 'Utilities/Filter/clientSideFilterAndSort';
import translate from 'Utilities/String/translate';
import Series from './Series';
import Series, { Statistics } from './Series';
import { useSeriesOptions } from './seriesOptionsStore';
// Date filter predicate helper
@ -315,6 +315,37 @@ const FILTER_PREDICATES = {
return predicate(seasonsMonitoredStatus, filterValue);
},
episodesMonitoredStatus: (
item: Series,
filterValue: string,
type: FilterType
) => {
const predicate = getFilterTypePredicate(type);
const { seasons, statistics = {} as Statistics } = item;
const { monitoredEpisodeCount = 0, totalEpisodeCount = 0 } = statistics;
const specials = seasons?.find((s) => s.seasonNumber === 0);
// The monitored count and total count include specials, but those areskipped
// for seasons monitored status so we should to exclude them here too.
const monitoredCount =
monitoredEpisodeCount -
(specials?.statistics?.monitoredEpisodeCount ?? 0);
const totalCount =
totalEpisodeCount - (specials?.statistics?.totalEpisodeCount ?? 0);
let episodesMonitoredStatus = 'partial';
if (monitoredCount === 0) {
episodesMonitoredStatus = 'none';
} else if (totalCount - monitoredCount === 0) {
episodesMonitoredStatus = 'all';
}
return predicate(episodesMonitoredStatus, filterValue);
},
} as const;
export const FILTER_BUILDER: FilterBuilderProp<Series>[] = [
@ -498,7 +529,13 @@ export const FILTER_BUILDER: FilterBuilderProp<Series>[] = [
name: 'seasonsMonitoredStatus',
label: () => translate('SeasonsMonitoredStatus'),
type: filterBuilderTypes.EXACT,
valueType: filterBuilderValueTypes.SEASONS_MONITORED_STATUS,
valueType: filterBuilderValueTypes.MONITORED_STATUS,
},
{
name: 'episodesMonitoredStatus',
label: () => translate('EpisodesMonitoredStatus'),
type: filterBuilderTypes.EXACT,
valueType: filterBuilderValueTypes.MONITORED_STATUS,
},
{
name: 'year',

View file

@ -683,6 +683,7 @@
"Episodes": "Episodes",
"EpisodesInSeason": "{episodeCount} episodes in season",
"EpisodesLoadError": "Unable to load episodes",
"EpisodesMonitoredStatus": "Episodes Monitored",
"Error": "Error",
"ErrorLoadingContent": "There was an error loading this content",
"ErrorLoadingContents": "Error loading contents",
@ -1282,8 +1283,11 @@
"MonitorSpecialEpisodes": "Monitor Specials",
"MonitorSpecialEpisodesDescription": "Monitor all special episodes without changing the monitored status of other episodes",
"Monitored": "Monitored",
"MonitoredAll": "All",
"MonitoredEpisodesHelpText": "Download monitored episodes in this series",
"MonitoredNone": "None",
"MonitoredOnly": "Monitored Only",
"MonitoredPartial": "Partial",
"MonitoredStatus": "Monitored/Status",
"Monitoring": "Monitoring",
"MonitoringOptions": "Monitoring Options",
@ -1892,9 +1896,6 @@
"SeasonPremiere": "Season Premiere",
"SeasonPremieresOnly": "Season Premieres Only",
"Seasons": "Seasons",
"SeasonsMonitoredAll": "All",
"SeasonsMonitoredNone": "None",
"SeasonsMonitoredPartial": "Partial",
"SeasonsMonitoredStatus": "Seasons Monitored",
"SecretToken": "Secret Token",
"Security": "Security",

View file

@ -18,6 +18,7 @@ public class SeasonStatistics : ResultSet
public int EpisodeCount { get; set; }
public int AvailableEpisodeCount { get; set; }
public int TotalEpisodeCount { get; set; }
public int MonitoredEpisodeCount { get; set; }
public long SizeOnDisk { get; set; }
public string ReleaseGroupsString { get; set; }

View file

@ -13,6 +13,7 @@ public class SeriesStatistics : ResultSet
public int EpisodeFileCount { get; set; }
public int EpisodeCount { get; set; }
public int TotalEpisodeCount { get; set; }
public int MonitoredEpisodeCount { get; set; }
public long SizeOnDisk { get; set; }
public List<string> ReleaseGroups { get; set; }
public List<SeasonStatistics> SeasonStatistics { get; set; }

View file

@ -79,6 +79,7 @@ private SqlBuilder EpisodesBuilder(DateTime currentDate)
SUM(CASE WHEN ""AirDateUtc"" <= @currentDate OR ""EpisodeFileId"" > 0 THEN 1 ELSE 0 END) AS AvailableEpisodeCount,
SUM(CASE WHEN (""Monitored"" = {trueIndicator} AND ""AirDateUtc"" <= @currentDate) OR ""EpisodeFileId"" > 0 THEN 1 ELSE 0 END) AS EpisodeCount,
SUM(CASE WHEN ""EpisodeFileId"" > 0 THEN 1 ELSE 0 END) AS EpisodeFileCount,
SUM(CASE WHEN ""Monitored"" = {trueIndicator} THEN 1 ELSE 0 END) AS MonitoredEpisodeCount,
MIN(CASE WHEN ""AirDateUtc"" < @currentDate OR ""Monitored"" = {falseIndicator} THEN NULL ELSE ""AirDateUtc"" END) AS NextAiringString,
MAX(CASE WHEN ""AirDateUtc"" >= @currentDate OR ""Monitored"" = {falseIndicator} THEN NULL ELSE ""AirDateUtc"" END) AS PreviousAiringString,
MAX(""AirDate"") AS LastAiredString",

View file

@ -46,6 +46,7 @@ private SeriesStatistics MapSeriesStatistics(List<SeasonStatistics> seasonStatis
EpisodeFileCount = seasonStatistics.Sum(s => s.EpisodeFileCount),
EpisodeCount = seasonStatistics.Sum(s => s.EpisodeCount),
TotalEpisodeCount = seasonStatistics.Sum(s => s.TotalEpisodeCount),
MonitoredEpisodeCount = seasonStatistics.Sum(s => s.MonitoredEpisodeCount),
SizeOnDisk = seasonStatistics.Sum(s => s.SizeOnDisk),
ReleaseGroups = seasonStatistics.SelectMany(s => s.ReleaseGroups).Distinct().ToList()
};

View file

@ -9,6 +9,7 @@ public class SeasonStatisticsResource
public int EpisodeFileCount { get; set; }
public int EpisodeCount { get; set; }
public int TotalEpisodeCount { get; set; }
public int MonitoredEpisodeCount { get; set; }
public long SizeOnDisk { get; set; }
public List<string>? ReleaseGroups { get; set; }
@ -37,6 +38,7 @@ public static SeasonStatisticsResource ToResource(this SeasonStatistics model)
EpisodeFileCount = model.EpisodeFileCount,
EpisodeCount = model.EpisodeCount,
TotalEpisodeCount = model.TotalEpisodeCount,
MonitoredEpisodeCount = model.MonitoredEpisodeCount,
SizeOnDisk = model.SizeOnDisk,
ReleaseGroups = model.ReleaseGroups
};

View file

@ -8,6 +8,7 @@ public class SeriesStatisticsResource
public int EpisodeFileCount { get; set; }
public int EpisodeCount { get; set; }
public int TotalEpisodeCount { get; set; }
public int MonitoredEpisodeCount { get; set; }
public long SizeOnDisk { get; set; }
public List<string>? ReleaseGroups { get; set; }
@ -35,6 +36,7 @@ public static SeriesStatisticsResource ToResource(this SeriesStatistics model, L
EpisodeFileCount = model.EpisodeFileCount,
EpisodeCount = model.EpisodeCount,
TotalEpisodeCount = model.TotalEpisodeCount,
MonitoredEpisodeCount = model.MonitoredEpisodeCount,
SizeOnDisk = model.SizeOnDisk,
ReleaseGroups = model.ReleaseGroups
};