New: Allow Radarr List Sync by Source Tag

Fixes #4826
pull/4819/head
Qstick 4 years ago
parent 5a1b82e195
commit 56184905a9

@ -8,16 +8,28 @@ import DeviceInput from './DeviceInput';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state, { value }) => value, (state, { value }) => value,
(state, { name }) => name,
(state) => state.providerOptions, (state) => state.providerOptions,
(value, devices) => { (value, name, devices) => {
const {
isFetching,
isPopulated,
error,
items
} = devices;
return { return {
...devices, isFetching,
isPopulated,
error,
items: items[name] || [],
selectedDevices: value.map((valueDevice) => { selectedDevices: value.map((valueDevice) => {
const sectionItems = items[name] || [];
// Disable equality ESLint rule so we don't need to worry about // Disable equality ESLint rule so we don't need to worry about
// a type mismatch between the value items and the device ID. // a type mismatch between the value items and the device ID.
// eslint-disable-next-line eqeqeq // eslint-disable-next-line eqeqeq
const device = devices.items.find((d) => d.id == valueDevice); const device = sectionItems.find((d) => d.id == valueDevice);
if (device) { if (device) {
return { return {
@ -61,11 +73,14 @@ class DeviceInputConnector extends Component {
const { const {
provider, provider,
providerData, providerData,
dispatchFetchOptions dispatchFetchOptions,
requestAction,
name
} = this.props; } = this.props;
dispatchFetchOptions({ dispatchFetchOptions({
action: 'getDevices', action: requestAction,
itemSection: name,
provider, provider,
providerData providerData
}); });
@ -94,6 +109,7 @@ class DeviceInputConnector extends Component {
DeviceInputConnector.propTypes = { DeviceInputConnector.propTypes = {
provider: PropTypes.string.isRequired, provider: PropTypes.string.isRequired,
providerData: PropTypes.object.isRequired, providerData: PropTypes.object.isRequired,
requestAction: PropTypes.string.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
dispatchFetchOptions: PropTypes.func.isRequired, dispatchFetchOptions: PropTypes.func.isRequired,

@ -62,6 +62,7 @@ function ProviderFieldFormGroup(props) {
value, value,
type, type,
advanced, advanced,
requestAction,
hidden, hidden,
pending, pending,
errors, errors,
@ -98,6 +99,7 @@ function ProviderFieldFormGroup(props) {
pending={pending} pending={pending}
includeFiles={type === 'filePath' ? true : undefined} includeFiles={type === 'filePath' ? true : undefined}
onChange={onChange} onChange={onChange}
requestAction={requestAction}
{...otherProps} {...otherProps}
/> />
</FormGroup> </FormGroup>
@ -118,6 +120,7 @@ ProviderFieldFormGroup.propTypes = {
value: PropTypes.any, value: PropTypes.any,
type: PropTypes.string.isRequired, type: PropTypes.string.isRequired,
advanced: PropTypes.bool.isRequired, advanced: PropTypes.bool.isRequired,
requestAction: PropTypes.string,
hidden: PropTypes.string, hidden: PropTypes.string,
pending: PropTypes.bool.isRequired, pending: PropTypes.bool.isRequired,
errors: PropTypes.arrayOf(PropTypes.object).isRequired, errors: PropTypes.arrayOf(PropTypes.object).isRequired,

@ -14,7 +14,7 @@ export const section = 'providerOptions';
// State // State
export const defaultState = { export const defaultState = {
items: [], items: {},
isFetching: false, isFetching: false,
isPopulated: false, isPopulated: false,
error: false error: false
@ -43,15 +43,20 @@ export const actionHandlers = handleThunks({
isFetching: true isFetching: true
})); }));
const oldItems = getState().providerOptions.items;
const itemSection = payload.itemSection;
const promise = requestAction(payload); const promise = requestAction(payload);
promise.done((data) => { promise.done((data) => {
oldItems[itemSection] = data.options || [];
dispatch(set({ dispatch(set({
section, section,
isFetching: false, isFetching: false,
isPopulated: true, isPopulated: true,
error: null, error: null,
items: data.options || [] items: oldItems
})); }));
}); });

@ -20,6 +20,7 @@ namespace NzbDrone.Core.Annotations
public Type SelectOptions { get; set; } public Type SelectOptions { get; set; }
public string Section { get; set; } public string Section { get; set; }
public HiddenType Hidden { get; set; } public HiddenType Hidden { get; set; }
public string RequestAction { get; set; }
} }
public enum FieldType public enum FieldType

@ -16,6 +16,7 @@ namespace NzbDrone.Core.NetImport.Radarr
public int Year { get; set; } public int Year { get; set; }
public string TitleSlug { get; set; } public string TitleSlug { get; set; }
public int QualityProfileId { get; set; } public int QualityProfileId { get; set; }
public HashSet<int> Tags { get; set; }
} }
public class RadarrProfile public class RadarrProfile
@ -23,4 +24,10 @@ namespace NzbDrone.Core.NetImport.Radarr
public string Name { get; set; } public string Name { get; set; }
public int Id { get; set; } public int Id { get; set; }
} }
public class RadarrTag
{
public string Label { get; set; }
public int Id { get; set; }
}
} }

@ -41,7 +41,8 @@ namespace NzbDrone.Core.NetImport.Radarr
foreach (var remoteMovie in remoteMovies) 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 movies.Add(new Movie
{ {
@ -76,19 +77,19 @@ namespace NzbDrone.Core.NetImport.Radarr
public override object RequestAction(string action, IDictionary<string, string> query) public override object RequestAction(string action, IDictionary<string, string> 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 return new
if (Settings.ApiKey.IsNullOrWhiteSpace())
{ {
return new devices = new List<object>()
{ };
devices = new List<object>() }
};
}
Settings.Validate().Filter("ApiKey").ThrowOnError(); Settings.Validate().Filter("ApiKey").ThrowOnError();
if (action == "getProfiles")
{
var devices = _radarrV3Proxy.GetProfiles(Settings); var devices = _radarrV3Proxy.GetProfiles(Settings);
return new 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 { }; return new { };
} }

@ -24,6 +24,7 @@ namespace NzbDrone.Core.NetImport.Radarr
BaseUrl = ""; BaseUrl = "";
ApiKey = ""; ApiKey = "";
ProfileIds = new int[] { }; ProfileIds = new int[] { };
TagIds = new int[] { };
} }
[FieldDefinition(0, Label = "Full URL", HelpText = "URL, including port, of the Radarr V3 instance to import from")] [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")] [FieldDefinition(1, Label = "API Key", HelpText = "Apikey of the Radarr V3 instance to import from")]
public string ApiKey { get; set; } 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<int> ProfileIds { get; set; } public IEnumerable<int> ProfileIds { get; set; }
[FieldDefinition(3, Type = FieldType.Device, RequestAction = "getTags", Label = "Tags", HelpText = "Tags from the source instance to import from")]
public IEnumerable<int> TagIds { get; set; }
public NzbDroneValidationResult Validate() public NzbDroneValidationResult Validate()
{ {
return new NzbDroneValidationResult(Validator.Validate(this)); return new NzbDroneValidationResult(Validator.Validate(this));

@ -13,6 +13,7 @@ namespace NzbDrone.Core.NetImport.Radarr
{ {
List<RadarrMovie> GetMovies(RadarrSettings settings); List<RadarrMovie> GetMovies(RadarrSettings settings);
List<RadarrProfile> GetProfiles(RadarrSettings settings); List<RadarrProfile> GetProfiles(RadarrSettings settings);
List<RadarrTag> GetTags(RadarrSettings settings);
ValidationFailure Test(RadarrSettings settings); ValidationFailure Test(RadarrSettings settings);
} }
@ -37,6 +38,11 @@ namespace NzbDrone.Core.NetImport.Radarr
return Execute<RadarrProfile>("/api/v3/qualityprofile", settings); return Execute<RadarrProfile>("/api/v3/qualityprofile", settings);
} }
public List<RadarrTag> GetTags(RadarrSettings settings)
{
return Execute<RadarrTag>("/api/v3/tag", settings);
}
public ValidationFailure Test(RadarrSettings settings) public ValidationFailure Test(RadarrSettings settings)
{ {
try try

@ -17,6 +17,7 @@ namespace Radarr.Http.ClientSchema
public List<SelectOption> SelectOptions { get; set; } public List<SelectOption> SelectOptions { get; set; }
public string Section { get; set; } public string Section { get; set; }
public string Hidden { get; set; } public string Hidden { get; set; }
public string RequestAction { get; set; }
public Field Clone() public Field Clone()
{ {

@ -100,7 +100,8 @@ namespace Radarr.Http.ClientSchema
Order = fieldAttribute.Order, Order = fieldAttribute.Order,
Advanced = fieldAttribute.Advanced, Advanced = fieldAttribute.Advanced,
Type = fieldAttribute.Type.ToString().FirstCharToLower(), Type = fieldAttribute.Type.ToString().FirstCharToLower(),
Section = fieldAttribute.Section Section = fieldAttribute.Section,
RequestAction = fieldAttribute.RequestAction
}; };
if (fieldAttribute.Type == FieldType.Select || fieldAttribute.Type == FieldType.TagSelect) if (fieldAttribute.Type == FieldType.Select || fieldAttribute.Type == FieldType.TagSelect)

Loading…
Cancel
Save