mirror of
https://github.com/Sonarr/Sonarr
synced 2026-05-07 04:20:50 +02:00
New: Filter series by episode release types
This commit is contained in:
parent
53eb9d8545
commit
39f043a96c
18 changed files with 130 additions and 0 deletions
|
|
@ -22,6 +22,7 @@ import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue';
|
|||
import QualityFilterBuilderRowValue from './QualityFilterBuilderRowValue';
|
||||
import QualityProfileFilterBuilderRowValue from './QualityProfileFilterBuilderRowValue';
|
||||
import QueueStatusFilterBuilderRowValue from './QueueStatusFilterBuilderRowValue';
|
||||
import ReleaseTypeFilterBuilderRowValue from './ReleaseTypeFilterBuilderRowValue';
|
||||
import SeriesFilterBuilderRowValue from './SeriesFilterBuilderRowValue';
|
||||
import SeriesStatusFilterBuilderRowValue from './SeriesStatusFilterBuilderRowValue';
|
||||
import SeriesTypeFilterBuilderRowValue from './SeriesTypeFilterBuilderRowValue';
|
||||
|
|
@ -113,6 +114,9 @@ function getRowValueConnector<T>(
|
|||
case filterBuilderValueTypes.MONITORED_STATUS:
|
||||
return MonitoredStatusFilterBuilderRowValue;
|
||||
|
||||
case filterBuilderValueTypes.RELEASE_TYPES:
|
||||
return ReleaseTypeFilterBuilderRowValue;
|
||||
|
||||
case filterBuilderValueTypes.SERIES:
|
||||
return SeriesFilterBuilderRowValue;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
import React from 'react';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import FilterBuilderRowValue, {
|
||||
FilterBuilderRowValueProps,
|
||||
} from './FilterBuilderRowValue';
|
||||
|
||||
const releaseTypeList = [
|
||||
{
|
||||
id: 'unknown',
|
||||
get name() {
|
||||
return translate('Unknown');
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'singleEpisode',
|
||||
get name() {
|
||||
return translate('SingleEpisode');
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'multiEpisode',
|
||||
get name() {
|
||||
return translate('MultiEpisode');
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'seasonPack',
|
||||
get name() {
|
||||
return translate('SeasonPack');
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
type ReleaseTypeFilterBuilderRowValueProps<T> = Omit<
|
||||
FilterBuilderRowValueProps<T, string, string>,
|
||||
'tagList'
|
||||
>;
|
||||
|
||||
function ReleaseTypeFilterBuilderRowValue<T>(
|
||||
props: ReleaseTypeFilterBuilderRowValueProps<T>
|
||||
) {
|
||||
return <FilterBuilderRowValue tagList={releaseTypeList} {...props} />;
|
||||
}
|
||||
|
||||
export default ReleaseTypeFilterBuilderRowValue;
|
||||
|
|
@ -10,6 +10,7 @@ export const QUALITY = 'quality';
|
|||
export const QUALITY_PROFILE = 'qualityProfile';
|
||||
export const QUEUE_STATUS = 'queueStatus';
|
||||
export const MONITORED_STATUS = 'monitoredStatus';
|
||||
export const RELEASE_TYPES = 'releaseTypes';
|
||||
export const SERIES = 'series';
|
||||
export const SERIES_STATUS = 'seriesStatus';
|
||||
export const SERIES_TYPES = 'seriesType';
|
||||
|
|
@ -28,6 +29,7 @@ export type FilterBuildValueType =
|
|||
| 'qualityProfile'
|
||||
| 'queueStatus'
|
||||
| 'monitoredStatus'
|
||||
| 'releaseTypes'
|
||||
| 'series'
|
||||
| 'seriesStatus'
|
||||
| 'seriesType'
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@
|
|||
}
|
||||
|
||||
.releaseGroups,
|
||||
.releaseTypes,
|
||||
.nextAiring,
|
||||
.previousAiring,
|
||||
.added,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ interface CssExports {
|
|||
'qualityProfileId': string;
|
||||
'ratings': string;
|
||||
'releaseGroups': string;
|
||||
'releaseTypes': string;
|
||||
'seasonCount': string;
|
||||
'seasonFolder': string;
|
||||
'seriesType': string;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import RelativeDateCell from 'Components/Table/Cells/RelativeDateCell';
|
|||
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
||||
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
|
||||
import Column from 'Components/Table/Column';
|
||||
import getReleaseTypeName from 'Episode/getReleaseTypeName';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import useCountryName from 'Internationalization/useCountryName';
|
||||
import DeleteSeriesModal from 'Series/Delete/DeleteSeriesModal';
|
||||
|
|
@ -149,6 +150,7 @@ function SeriesIndexRow(props: SeriesIndexRowProps) {
|
|||
totalEpisodeCount = 0,
|
||||
sizeOnDisk = 0,
|
||||
releaseGroups = [],
|
||||
releaseTypes = [],
|
||||
episodeFileQualities = [],
|
||||
} = statistics;
|
||||
|
||||
|
|
@ -448,6 +450,25 @@ function SeriesIndexRow(props: SeriesIndexRowProps) {
|
|||
);
|
||||
}
|
||||
|
||||
if (name === 'releaseTypes') {
|
||||
const joinedReleaseTypes = releaseTypes
|
||||
.map(getReleaseTypeName)
|
||||
.join(', ');
|
||||
const truncatedReleaseTypes =
|
||||
releaseTypes.length > 3
|
||||
? `${releaseTypes
|
||||
.slice(0, 3)
|
||||
.map(getReleaseTypeName)
|
||||
.join(', ')}...`
|
||||
: joinedReleaseTypes;
|
||||
|
||||
return (
|
||||
<VirtualTableRowCell key={name} className={styles[name]}>
|
||||
<span title={joinedReleaseTypes}>{truncatedReleaseTypes}</span>
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'episodeFileQualities') {
|
||||
const joinedQualities = episodeFileQualities
|
||||
.map((q) => q.name)
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
}
|
||||
|
||||
.releaseGroups,
|
||||
.releaseTypes,
|
||||
.nextAiring,
|
||||
.previousAiring,
|
||||
.added,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ interface CssExports {
|
|||
'qualityProfileId': string;
|
||||
'ratings': string;
|
||||
'releaseGroups': string;
|
||||
'releaseTypes': string;
|
||||
'seasonCount': string;
|
||||
'seasonFolder': string;
|
||||
'seriesType': string;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import ModelBase from 'App/ModelBase';
|
||||
import ReleaseType from 'InteractiveImport/ReleaseType';
|
||||
import Language from 'Language/Language';
|
||||
import Quality from 'Quality/Quality';
|
||||
|
||||
|
|
@ -35,6 +36,7 @@ export interface Statistics {
|
|||
percentOfEpisodes: number;
|
||||
previousAiring?: Date;
|
||||
releaseGroups: string[];
|
||||
releaseTypes: ReleaseType[];
|
||||
episodeFileQualities: Quality[];
|
||||
sizeOnDisk: number;
|
||||
totalEpisodeCount: number;
|
||||
|
|
|
|||
|
|
@ -221,6 +221,12 @@ const { useOptions, useOption, setOptions, setOption, setSort, getOptions } =
|
|||
isSortable: false,
|
||||
isVisible: false,
|
||||
},
|
||||
{
|
||||
name: 'releaseTypes',
|
||||
label: () => translate('ReleaseTypes'),
|
||||
isSortable: false,
|
||||
isVisible: false,
|
||||
},
|
||||
{
|
||||
name: 'episodeFileQualities',
|
||||
label: () => translate('EpisodeFileQualities'),
|
||||
|
|
|
|||
|
|
@ -254,6 +254,12 @@ const FILTER_PREDICATES = {
|
|||
return predicate(releaseGroups, filterValue);
|
||||
},
|
||||
|
||||
releaseTypes: (item: Series, filterValue: string[], type: FilterType) => {
|
||||
const releaseTypes = item.statistics?.releaseTypes ?? [];
|
||||
const predicate = getFilterTypePredicate(type);
|
||||
return predicate(releaseTypes, filterValue);
|
||||
},
|
||||
|
||||
episodeFileQualities: (
|
||||
item: Series,
|
||||
filterValue: number[],
|
||||
|
|
@ -533,6 +539,12 @@ export const FILTER_BUILDER: FilterBuilderProp<Series>[] = [
|
|||
label: () => translate('ReleaseGroups'),
|
||||
type: filterBuilderTypes.ARRAY,
|
||||
},
|
||||
{
|
||||
name: 'releaseTypes',
|
||||
label: () => translate('ReleaseTypes'),
|
||||
type: filterBuilderTypes.ARRAY,
|
||||
valueType: filterBuilderValueTypes.RELEASE_TYPES,
|
||||
},
|
||||
{
|
||||
name: 'episodeFileQualities',
|
||||
label: () => translate('EpisodeFileQualities'),
|
||||
|
|
|
|||
|
|
@ -1776,6 +1776,7 @@
|
|||
"ReleaseSource": "Release Source",
|
||||
"ReleaseTitle": "Release Title",
|
||||
"ReleaseType": "Release Type",
|
||||
"ReleaseTypes": "Release Types",
|
||||
"Reload": "Reload",
|
||||
"RemotePath": "Remote Path",
|
||||
"RemotePathMappingBadDockerPathHealthCheckMessage": "You are using docker; download client {downloadClientName} places downloads in {path} but this is not a valid {osName} path. Review your remote path mappings and download client settings.",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
|
||||
namespace NzbDrone.Core.SeriesStats
|
||||
|
|
@ -22,6 +23,7 @@ public class SeasonStatistics : ResultSet
|
|||
public int MonitoredEpisodeCount { get; set; }
|
||||
public long SizeOnDisk { get; set; }
|
||||
public string ReleaseGroupsString { get; set; }
|
||||
public string ReleaseTypesString { get; set; }
|
||||
public string EpisodeFileQualitiesString { get; set; }
|
||||
|
||||
public DateTime? NextAiring
|
||||
|
|
@ -113,6 +115,25 @@ public List<string> ReleaseGroups
|
|||
}
|
||||
}
|
||||
|
||||
public List<ReleaseType> ReleaseTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ReleaseTypesString.IsNullOrWhiteSpace())
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return ReleaseTypesString
|
||||
.Split('|', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.Select(int.Parse)
|
||||
.Distinct()
|
||||
.Where(type => Enum.IsDefined(typeof(ReleaseType), type))
|
||||
.Select(type => (ReleaseType)type)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public List<Quality> EpisodeFileQualities
|
||||
{
|
||||
get
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
|
||||
namespace NzbDrone.Core.SeriesStats
|
||||
|
|
@ -17,6 +18,7 @@ public class SeriesStatistics : ResultSet
|
|||
public int MonitoredEpisodeCount { get; set; }
|
||||
public long SizeOnDisk { get; set; }
|
||||
public List<string> ReleaseGroups { get; set; }
|
||||
public List<ReleaseType> ReleaseTypes { get; set; }
|
||||
public List<Quality> EpisodeFileQualities { get; set; }
|
||||
public List<SeasonStatistics> SeasonStatistics { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ private List<SeasonStatistics> MapResults(List<SeasonStatistics> episodesResult,
|
|||
|
||||
e.SizeOnDisk = file?.SizeOnDisk ?? 0;
|
||||
e.ReleaseGroupsString = file?.ReleaseGroupsString;
|
||||
e.ReleaseTypesString = file?.ReleaseTypesString;
|
||||
e.EpisodeFileQualitiesString = file?.EpisodeFileQualitiesString;
|
||||
});
|
||||
|
||||
|
|
@ -98,6 +99,7 @@ private SqlBuilder EpisodeFilesBuilder()
|
|||
""SeasonNumber"",
|
||||
SUM(COALESCE(""Size"", 0)) AS SizeOnDisk,
|
||||
GROUP_CONCAT(""ReleaseGroup"", '|') AS ReleaseGroupsString,
|
||||
GROUP_CONCAT(""ReleaseType"", '|') AS ReleaseTypesString,
|
||||
GROUP_CONCAT(JSON_EXTRACT(""Quality"", '$.quality'), '|') AS EpisodeFileQualitiesString")
|
||||
.GroupBy<EpisodeFile>(x => x.SeriesId)
|
||||
.GroupBy<EpisodeFile>(x => x.SeasonNumber);
|
||||
|
|
@ -108,6 +110,7 @@ private SqlBuilder EpisodeFilesBuilder()
|
|||
""SeasonNumber"",
|
||||
SUM(COALESCE(""Size"", 0)) AS SizeOnDisk,
|
||||
string_agg(""ReleaseGroup"", '|') AS ReleaseGroupsString,
|
||||
string_agg(""ReleaseType""::text, '|') AS ReleaseTypesString,
|
||||
string_agg(""Quality""::json->>'quality', '|') AS EpisodeFileQualitiesString")
|
||||
.GroupBy<EpisodeFile>(x => x.SeriesId)
|
||||
.GroupBy<EpisodeFile>(x => x.SeasonNumber);
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ private SeriesStatistics MapSeriesStatistics(List<SeasonStatistics> seasonStatis
|
|||
MonitoredEpisodeCount = seasonStatistics.Sum(s => s.MonitoredEpisodeCount),
|
||||
SizeOnDisk = seasonStatistics.Sum(s => s.SizeOnDisk),
|
||||
ReleaseGroups = seasonStatistics.SelectMany(s => s.ReleaseGroups).Distinct().ToList(),
|
||||
ReleaseTypes = seasonStatistics.SelectMany(s => s.ReleaseTypes).Distinct().OrderBy(s => s).ToList(),
|
||||
EpisodeFileQualities = SortQualities(seasonStatistics.SelectMany(s => s.EpisodeFileQualities).Distinct().ToList(), profile)
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.SeriesStats;
|
||||
|
||||
|
|
@ -13,6 +14,7 @@ public class SeasonStatisticsResource
|
|||
public int MonitoredEpisodeCount { get; set; }
|
||||
public long SizeOnDisk { get; set; }
|
||||
public List<string>? ReleaseGroups { get; set; }
|
||||
public List<ReleaseType>? ReleaseTypes { get; set; }
|
||||
public List<Quality>? EpisodeFileQualities { get; set; }
|
||||
|
||||
public decimal PercentOfEpisodes
|
||||
|
|
@ -43,6 +45,7 @@ public static SeasonStatisticsResource ToResource(this SeasonStatistics model)
|
|||
MonitoredEpisodeCount = model.MonitoredEpisodeCount,
|
||||
SizeOnDisk = model.SizeOnDisk,
|
||||
ReleaseGroups = model.ReleaseGroups,
|
||||
ReleaseTypes = model.ReleaseTypes,
|
||||
EpisodeFileQualities = model.EpisodeFileQualities
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.SeriesStats;
|
||||
|
||||
|
|
@ -12,6 +13,7 @@ public class SeriesStatisticsResource
|
|||
public int MonitoredEpisodeCount { get; set; }
|
||||
public long SizeOnDisk { get; set; }
|
||||
public List<string>? ReleaseGroups { get; set; }
|
||||
public List<ReleaseType>? ReleaseTypes { get; set; }
|
||||
public List<Quality>? EpisodeFileQualities { get; set; }
|
||||
|
||||
public decimal PercentOfEpisodes
|
||||
|
|
@ -41,6 +43,7 @@ public static SeriesStatisticsResource ToResource(this SeriesStatistics model, L
|
|||
MonitoredEpisodeCount = model.MonitoredEpisodeCount,
|
||||
SizeOnDisk = model.SizeOnDisk,
|
||||
ReleaseGroups = model.ReleaseGroups,
|
||||
ReleaseTypes = model.ReleaseTypes,
|
||||
EpisodeFileQualities = model.EpisodeFileQualities
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue