diff --git a/frontend/src/Components/Form/DeviceInputConnector.js b/frontend/src/Components/Form/DeviceInputConnector.js index 8fa9d41c3..2053d3a30 100644 --- a/frontend/src/Components/Form/DeviceInputConnector.js +++ b/frontend/src/Components/Form/DeviceInputConnector.js @@ -8,16 +8,28 @@ import DeviceInput from './DeviceInput'; function createMapStateToProps() { return createSelector( (state, { value }) => value, + (state, { name }) => name, (state) => state.providerOptions, - (value, devices) => { + (value, name, devices) => { + const { + isFetching, + isPopulated, + error, + items + } = devices; return { - ...devices, + isFetching, + isPopulated, + error, + items: items[name] || [], selectedDevices: value.map((valueDevice) => { + const sectionItems = items[name] || []; + // Disable equality ESLint rule so we don't need to worry about // a type mismatch between the value items and the device ID. // eslint-disable-next-line eqeqeq - const device = devices.items.find((d) => d.id == valueDevice); + const device = sectionItems.find((d) => d.id == valueDevice); if (device) { return { @@ -61,11 +73,14 @@ class DeviceInputConnector extends Component { const { provider, providerData, - dispatchFetchOptions + dispatchFetchOptions, + requestAction, + name } = this.props; dispatchFetchOptions({ - action: 'getDevices', + action: requestAction, + itemSection: name, provider, providerData }); @@ -94,6 +109,7 @@ class DeviceInputConnector extends Component { DeviceInputConnector.propTypes = { provider: PropTypes.string.isRequired, providerData: PropTypes.object.isRequired, + requestAction: PropTypes.string.isRequired, name: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, dispatchFetchOptions: PropTypes.func.isRequired, diff --git a/frontend/src/Components/Form/ProviderFieldFormGroup.js b/frontend/src/Components/Form/ProviderFieldFormGroup.js index d0ea9916e..e1263a096 100644 --- a/frontend/src/Components/Form/ProviderFieldFormGroup.js +++ b/frontend/src/Components/Form/ProviderFieldFormGroup.js @@ -62,6 +62,7 @@ function ProviderFieldFormGroup(props) { value, type, advanced, + requestAction, hidden, pending, errors, @@ -98,6 +99,7 @@ function ProviderFieldFormGroup(props) { pending={pending} includeFiles={type === 'filePath' ? true : undefined} onChange={onChange} + requestAction={requestAction} {...otherProps} /> @@ -118,6 +120,7 @@ ProviderFieldFormGroup.propTypes = { value: PropTypes.any, type: PropTypes.string.isRequired, advanced: PropTypes.bool.isRequired, + requestAction: PropTypes.string, hidden: PropTypes.string, pending: PropTypes.bool.isRequired, errors: PropTypes.arrayOf(PropTypes.object).isRequired, diff --git a/frontend/src/Store/Actions/providerOptionActions.js b/frontend/src/Store/Actions/providerOptionActions.js index 4a8956117..72a8b1745 100644 --- a/frontend/src/Store/Actions/providerOptionActions.js +++ b/frontend/src/Store/Actions/providerOptionActions.js @@ -14,7 +14,7 @@ export const section = 'providerOptions'; // State export const defaultState = { - items: [], + items: {}, isFetching: false, isPopulated: false, error: false @@ -43,15 +43,20 @@ export const actionHandlers = handleThunks({ isFetching: true })); + const oldItems = getState().providerOptions.items; + const itemSection = payload.itemSection; + const promise = requestAction(payload); promise.done((data) => { + oldItems[itemSection] = data.options || []; + dispatch(set({ section, isFetching: false, isPopulated: true, error: null, - items: data.options || [] + items: oldItems })); }); diff --git a/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs b/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs index 4785a382c..ab0209c00 100644 --- a/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs +++ b/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs @@ -20,6 +20,7 @@ namespace NzbDrone.Core.Annotations public Type SelectOptions { get; set; } public string Section { get; set; } public HiddenType Hidden { get; set; } + public string RequestAction { get; set; } } public enum FieldType diff --git a/src/NzbDrone.Core/NetImport/Radarr/RadarrAPIResource.cs b/src/NzbDrone.Core/NetImport/Radarr/RadarrAPIResource.cs index 7778bdb66..69b967098 100644 --- a/src/NzbDrone.Core/NetImport/Radarr/RadarrAPIResource.cs +++ b/src/NzbDrone.Core/NetImport/Radarr/RadarrAPIResource.cs @@ -16,6 +16,7 @@ namespace NzbDrone.Core.NetImport.Radarr public int Year { get; set; } public string TitleSlug { get; set; } public int QualityProfileId { get; set; } + public HashSet Tags { get; set; } } public class RadarrProfile @@ -23,4 +24,10 @@ namespace NzbDrone.Core.NetImport.Radarr public string Name { get; set; } public int Id { get; set; } } + + public class RadarrTag + { + public string Label { get; set; } + public int Id { get; set; } + } } diff --git a/src/NzbDrone.Core/NetImport/Radarr/RadarrImport.cs b/src/NzbDrone.Core/NetImport/Radarr/RadarrImport.cs index d6666858c..7b44577cd 100644 --- a/src/NzbDrone.Core/NetImport/Radarr/RadarrImport.cs +++ b/src/NzbDrone.Core/NetImport/Radarr/RadarrImport.cs @@ -41,7 +41,8 @@ namespace NzbDrone.Core.NetImport.Radarr foreach (var remoteMovie in remoteMovies) { - if (!Settings.ProfileIds.Any() || Settings.ProfileIds.Contains(remoteMovie.QualityProfileId)) + if ((!Settings.ProfileIds.Any() || Settings.ProfileIds.Contains(remoteMovie.QualityProfileId)) && + (!Settings.TagIds.Any() || Settings.TagIds.Any(x => remoteMovie.Tags.Any(y => y == x)))) { movies.Add(new Movie { @@ -76,19 +77,19 @@ namespace NzbDrone.Core.NetImport.Radarr public override object RequestAction(string action, IDictionary query) { - if (action == "getDevices") + // Return early if there is not an API key + if (Settings.ApiKey.IsNullOrWhiteSpace()) { - // Return early if there is not an API key - if (Settings.ApiKey.IsNullOrWhiteSpace()) + return new { - return new - { - devices = new List() - }; - } + devices = new List() + }; + } - Settings.Validate().Filter("ApiKey").ThrowOnError(); + Settings.Validate().Filter("ApiKey").ThrowOnError(); + if (action == "getProfiles") + { var devices = _radarrV3Proxy.GetProfiles(Settings); return new @@ -102,6 +103,21 @@ namespace NzbDrone.Core.NetImport.Radarr }; } + if (action == "getTags") + { + var devices = _radarrV3Proxy.GetTags(Settings); + + return new + { + options = devices.OrderBy(d => d.Label, StringComparer.InvariantCultureIgnoreCase) + .Select(d => new + { + id = d.Id, + name = d.Label + }) + }; + } + return new { }; } diff --git a/src/NzbDrone.Core/NetImport/Radarr/RadarrSettings.cs b/src/NzbDrone.Core/NetImport/Radarr/RadarrSettings.cs index 0b5cbdaa0..095ee1b4f 100644 --- a/src/NzbDrone.Core/NetImport/Radarr/RadarrSettings.cs +++ b/src/NzbDrone.Core/NetImport/Radarr/RadarrSettings.cs @@ -24,6 +24,7 @@ namespace NzbDrone.Core.NetImport.Radarr BaseUrl = ""; ApiKey = ""; ProfileIds = new int[] { }; + TagIds = new int[] { }; } [FieldDefinition(0, Label = "Full URL", HelpText = "URL, including port, of the Radarr V3 instance to import from")] @@ -32,9 +33,12 @@ namespace NzbDrone.Core.NetImport.Radarr [FieldDefinition(1, Label = "API Key", HelpText = "Apikey of the Radarr V3 instance to import from")] public string ApiKey { get; set; } - [FieldDefinition(2, Type = FieldType.Device, Label = "Profiles", HelpText = "Profiles from the source instance to import from")] + [FieldDefinition(2, Type = FieldType.Device, RequestAction = "getProfiles", Label = "Profiles", HelpText = "Profiles from the source instance to import from")] public IEnumerable ProfileIds { get; set; } + [FieldDefinition(3, Type = FieldType.Device, RequestAction = "getTags", Label = "Tags", HelpText = "Tags from the source instance to import from")] + public IEnumerable TagIds { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/NetImport/Radarr/RadarrV3Proxy.cs b/src/NzbDrone.Core/NetImport/Radarr/RadarrV3Proxy.cs index 79e152c5f..ce23e962c 100644 --- a/src/NzbDrone.Core/NetImport/Radarr/RadarrV3Proxy.cs +++ b/src/NzbDrone.Core/NetImport/Radarr/RadarrV3Proxy.cs @@ -13,6 +13,7 @@ namespace NzbDrone.Core.NetImport.Radarr { List GetMovies(RadarrSettings settings); List GetProfiles(RadarrSettings settings); + List GetTags(RadarrSettings settings); ValidationFailure Test(RadarrSettings settings); } @@ -37,6 +38,11 @@ namespace NzbDrone.Core.NetImport.Radarr return Execute("/api/v3/qualityprofile", settings); } + public List GetTags(RadarrSettings settings) + { + return Execute("/api/v3/tag", settings); + } + public ValidationFailure Test(RadarrSettings settings) { try diff --git a/src/Radarr.Http/ClientSchema/Field.cs b/src/Radarr.Http/ClientSchema/Field.cs index 560910c7e..eb8fa0edd 100644 --- a/src/Radarr.Http/ClientSchema/Field.cs +++ b/src/Radarr.Http/ClientSchema/Field.cs @@ -17,6 +17,7 @@ namespace Radarr.Http.ClientSchema public List SelectOptions { get; set; } public string Section { get; set; } public string Hidden { get; set; } + public string RequestAction { get; set; } public Field Clone() { diff --git a/src/Radarr.Http/ClientSchema/SchemaBuilder.cs b/src/Radarr.Http/ClientSchema/SchemaBuilder.cs index eb8efc7da..6805d12b9 100644 --- a/src/Radarr.Http/ClientSchema/SchemaBuilder.cs +++ b/src/Radarr.Http/ClientSchema/SchemaBuilder.cs @@ -100,7 +100,8 @@ namespace Radarr.Http.ClientSchema Order = fieldAttribute.Order, Advanced = fieldAttribute.Advanced, Type = fieldAttribute.Type.ToString().FirstCharToLower(), - Section = fieldAttribute.Section + Section = fieldAttribute.Section, + RequestAction = fieldAttribute.RequestAction }; if (fieldAttribute.Type == FieldType.Select || fieldAttribute.Type == FieldType.TagSelect)