diff --git a/frontend/src/Components/Filter/Builder/FilterBuilderRow.tsx b/frontend/src/Components/Filter/Builder/FilterBuilderRow.tsx index 26297e397..0ccc3070d 100644 --- a/frontend/src/Components/Filter/Builder/FilterBuilderRow.tsx +++ b/frontend/src/Components/Filter/Builder/FilterBuilderRow.tsx @@ -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( case filterBuilderValueTypes.QUEUE_STATUS: return QueueStatusFilterBuilderRowValue; - case filterBuilderValueTypes.SEASONS_MONITORED_STATUS: - return SeasonsMonitoredStatusFilterBuilderRowValue; + case filterBuilderValueTypes.MONITORED_STATUS: + return MonitoredStatusFilterBuilderRowValue; case filterBuilderValueTypes.SERIES: return SeriesFilterBuilderRowValue; diff --git a/frontend/src/Components/Filter/Builder/MonitoredStatusFilterBuilderRowValue.tsx b/frontend/src/Components/Filter/Builder/MonitoredStatusFilterBuilderRowValue.tsx new file mode 100644 index 000000000..9e3249a13 --- /dev/null +++ b/frontend/src/Components/Filter/Builder/MonitoredStatusFilterBuilderRowValue.tsx @@ -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 = Omit< + FilterBuilderRowValueProps, + 'tagList' +>; + +function MonitoredStatusFilterBuilderRowValue( + props: MonitoredStatusFilterBuilderRowValueProps +) { + return ; +} + +export default MonitoredStatusFilterBuilderRowValue; diff --git a/frontend/src/Components/Filter/Builder/SeasonsMonitoredStatusFilterBuilderRowValue.tsx b/frontend/src/Components/Filter/Builder/SeasonsMonitoredStatusFilterBuilderRowValue.tsx deleted file mode 100644 index 1d99bbeb9..000000000 --- a/frontend/src/Components/Filter/Builder/SeasonsMonitoredStatusFilterBuilderRowValue.tsx +++ /dev/null @@ -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 = Omit< - FilterBuilderRowValueProps, - 'tagList' ->; - -function SeasonsMonitoredStatusFilterBuilderRowValue( - props: SeasonsMonitoredStatusFilterBuilderRowValueProps -) { - return ( - - ); -} - -export default SeasonsMonitoredStatusFilterBuilderRowValue; diff --git a/frontend/src/Helpers/Props/filterBuilderValueTypes.ts b/frontend/src/Helpers/Props/filterBuilderValueTypes.ts index 2cf5a97d5..4a12865c2 100644 --- a/frontend/src/Helpers/Props/filterBuilderValueTypes.ts +++ b/frontend/src/Helpers/Props/filterBuilderValueTypes.ts @@ -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' diff --git a/frontend/src/Series/Series.ts b/frontend/src/Series/Series.ts index b919a2ddb..d3d933ac8 100644 --- a/frontend/src/Series/Series.ts +++ b/frontend/src/Series/Series.ts @@ -36,6 +36,7 @@ export interface Statistics { releaseGroups: string[]; sizeOnDisk: number; totalEpisodeCount: number; + monitoredEpisodeCount: number; lastAired?: string; } diff --git a/frontend/src/Series/useSeries.ts b/frontend/src/Series/useSeries.ts index dbe76e009..63497121d 100644 --- a/frontend/src/Series/useSeries.ts +++ b/frontend/src/Series/useSeries.ts @@ -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[] = [ @@ -498,7 +529,13 @@ export const FILTER_BUILDER: FilterBuilderProp[] = [ 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', diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 66f0f2746..5f29471ae 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -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", diff --git a/src/NzbDrone.Core/SeriesStats/SeasonStatistics.cs b/src/NzbDrone.Core/SeriesStats/SeasonStatistics.cs index 793a56f85..cf615a731 100644 --- a/src/NzbDrone.Core/SeriesStats/SeasonStatistics.cs +++ b/src/NzbDrone.Core/SeriesStats/SeasonStatistics.cs @@ -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; } diff --git a/src/NzbDrone.Core/SeriesStats/SeriesStatistics.cs b/src/NzbDrone.Core/SeriesStats/SeriesStatistics.cs index 030b04ee3..e3f10d24f 100644 --- a/src/NzbDrone.Core/SeriesStats/SeriesStatistics.cs +++ b/src/NzbDrone.Core/SeriesStats/SeriesStatistics.cs @@ -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 ReleaseGroups { get; set; } public List SeasonStatistics { get; set; } diff --git a/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs b/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs index c7fd1bc48..68b4f91ff 100644 --- a/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs +++ b/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs @@ -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", diff --git a/src/NzbDrone.Core/SeriesStats/SeriesStatisticsService.cs b/src/NzbDrone.Core/SeriesStats/SeriesStatisticsService.cs index e9c05da97..6e7b75d85 100644 --- a/src/NzbDrone.Core/SeriesStats/SeriesStatisticsService.cs +++ b/src/NzbDrone.Core/SeriesStats/SeriesStatisticsService.cs @@ -46,6 +46,7 @@ private SeriesStatistics MapSeriesStatistics(List 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() }; diff --git a/src/Sonarr.Api.V5/Series/SeasonStatisticsResource.cs b/src/Sonarr.Api.V5/Series/SeasonStatisticsResource.cs index 8ca48ad1f..c338cfbcb 100644 --- a/src/Sonarr.Api.V5/Series/SeasonStatisticsResource.cs +++ b/src/Sonarr.Api.V5/Series/SeasonStatisticsResource.cs @@ -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? 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 }; diff --git a/src/Sonarr.Api.V5/Series/SeriesStatisticsResource.cs b/src/Sonarr.Api.V5/Series/SeriesStatisticsResource.cs index e9fefba3d..d04bed7a6 100644 --- a/src/Sonarr.Api.V5/Series/SeriesStatisticsResource.cs +++ b/src/Sonarr.Api.V5/Series/SeriesStatisticsResource.cs @@ -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? 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 };