New: Refresh only selected or filtered artists

(cherry picked from commit bf90c3cbddffca4f7ad07c3ae51fa705988cd80b)

Closes #3717
pull/4256/head
Stevie Robinson 2 years ago committed by Bogdan
parent 0e7b368c3a
commit cbb3cb78f9

@ -0,0 +1,8 @@
import { CustomFilter } from './AppState';
interface ClientSideCollectionAppState {
totalItems: number;
customFilters: CustomFilter[];
}
export default ClientSideCollectionAppState;

@ -2,7 +2,7 @@ import React, { useCallback, useMemo, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { SelectProvider } from 'App/SelectContext'; import { SelectProvider } from 'App/SelectContext';
import NoArtist from 'Artist/NoArtist'; import NoArtist from 'Artist/NoArtist';
import { REFRESH_ARTIST, RSS_SYNC } from 'Commands/commandNames'; import { RSS_SYNC } from 'Commands/commandNames';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent'; import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody'; import PageContentBody from 'Components/Page/PageContentBody';
@ -29,6 +29,7 @@ import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import getErrorMessage from 'Utilities/Object/getErrorMessage'; import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import ArtistIndexFooter from './ArtistIndexFooter'; import ArtistIndexFooter from './ArtistIndexFooter';
import ArtistIndexRefreshArtistsButton from './ArtistIndexRefreshArtistsButton';
import ArtistIndexBanners from './Banners/ArtistIndexBanners'; import ArtistIndexBanners from './Banners/ArtistIndexBanners';
import ArtistIndexBannerOptionsModal from './Banners/Options/ArtistIndexBannerOptionsModal'; import ArtistIndexBannerOptionsModal from './Banners/Options/ArtistIndexBannerOptionsModal';
import ArtistIndexFilterMenu from './Menus/ArtistIndexFilterMenu'; import ArtistIndexFilterMenu from './Menus/ArtistIndexFilterMenu';
@ -83,9 +84,6 @@ const ArtistIndex = withScrollPosition((props: ArtistIndexProps) => {
view, view,
} = useSelector(createArtistClientSideCollectionItemsSelector('artistIndex')); } = useSelector(createArtistClientSideCollectionItemsSelector('artistIndex'));
const isRefreshingArtist = useSelector(
createCommandExecutingSelector(REFRESH_ARTIST)
);
const isRssSyncExecuting = useSelector( const isRssSyncExecuting = useSelector(
createCommandExecutingSelector(RSS_SYNC) createCommandExecutingSelector(RSS_SYNC)
); );
@ -96,14 +94,6 @@ const ArtistIndex = withScrollPosition((props: ArtistIndexProps) => {
const [jumpToCharacter, setJumpToCharacter] = useState<string | null>(null); const [jumpToCharacter, setJumpToCharacter] = useState<string | null>(null);
const [isSelectMode, setIsSelectMode] = useState(false); const [isSelectMode, setIsSelectMode] = useState(false);
const onRefreshArtistPress = useCallback(() => {
dispatch(
executeCommand({
name: REFRESH_ARTIST,
})
);
}, [dispatch]);
const onRssSyncPress = useCallback(() => { const onRssSyncPress = useCallback(() => {
dispatch( dispatch(
executeCommand({ executeCommand({
@ -217,13 +207,9 @@ const ArtistIndex = withScrollPosition((props: ArtistIndexProps) => {
<PageContent> <PageContent>
<PageToolbar> <PageToolbar>
<PageToolbarSection> <PageToolbarSection>
<PageToolbarButton <ArtistIndexRefreshArtistsButton
label={translate('UpdateAll')} isSelectMode={isSelectMode}
iconName={icons.REFRESH} selectedFilterKey={selectedFilterKey}
spinningName={icons.REFRESH}
isSpinning={isRefreshingArtist}
isDisabled={hasNoArtist}
onPress={onRefreshArtistPress}
/> />
<PageToolbarButton <PageToolbarButton

@ -0,0 +1,74 @@
import React, { useCallback, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useSelect } from 'App/SelectContext';
import ArtistAppState, { ArtistIndexAppState } from 'App/State/ArtistAppState';
import ClientSideCollectionAppState from 'App/State/ClientSideCollectionAppState';
import { REFRESH_ARTIST } from 'Commands/commandNames';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import { icons } from 'Helpers/Props';
import { executeCommand } from 'Store/Actions/commandActions';
import createArtistClientSideCollectionItemsSelector from 'Store/Selectors/createArtistClientSideCollectionItemsSelector';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
interface ArtistIndexRefreshArtistsButtonProps {
isSelectMode: boolean;
selectedFilterKey: string;
}
function ArtistIndexRefreshArtistsButton(
props: ArtistIndexRefreshArtistsButtonProps
) {
const isRefreshing = useSelector(
createCommandExecutingSelector(REFRESH_ARTIST)
);
const {
items,
totalItems,
}: ArtistAppState & ArtistIndexAppState & ClientSideCollectionAppState =
useSelector(createArtistClientSideCollectionItemsSelector('artistIndex'));
const dispatch = useDispatch();
const { isSelectMode, selectedFilterKey } = props;
const [selectState] = useSelect();
const { selectedState } = selectState;
const selectedArtistIds = useMemo(() => {
return getSelectedIds(selectedState);
}, [selectedState]);
const artistsToRefresh =
isSelectMode && selectedArtistIds.length > 0
? selectedArtistIds
: items.map((m) => m.id);
let refreshLabel = translate('UpdateAll');
if (selectedArtistIds.length > 0) {
refreshLabel = translate('UpdateSelected');
} else if (selectedFilterKey !== 'all') {
refreshLabel = translate('UpdateFiltered');
}
const onPress = useCallback(() => {
dispatch(
executeCommand({
name: REFRESH_ARTIST,
artistIds: artistsToRefresh,
})
);
}, [dispatch, artistsToRefresh]);
return (
<PageToolbarButton
label={refreshLabel}
isSpinning={isRefreshing}
isDisabled={!totalItems}
iconName={icons.REFRESH}
onPress={onPress}
/>
);
}
export default ArtistIndexRefreshArtistsButton;

@ -119,7 +119,7 @@ namespace NzbDrone.Core.Test.MusicTests
GivenAlbumsForRefresh(_albums); GivenAlbumsForRefresh(_albums);
AllowArtistUpdate(); AllowArtistUpdate();
Subject.Execute(new RefreshArtistCommand(_artist.Id)); Subject.Execute(new RefreshArtistCommand(new List<int> { _artist.Id }));
VerifyEventNotPublished<ArtistUpdatedEvent>(); VerifyEventNotPublished<ArtistUpdatedEvent>();
VerifyEventPublished<ArtistRefreshCompleteEvent>(); VerifyEventPublished<ArtistRefreshCompleteEvent>();
@ -140,7 +140,7 @@ namespace NzbDrone.Core.Test.MusicTests
GivenAlbumsForRefresh(new List<Album>()); GivenAlbumsForRefresh(new List<Album>());
AllowArtistUpdate(); AllowArtistUpdate();
Subject.Execute(new RefreshArtistCommand(_artist.Id)); Subject.Execute(new RefreshArtistCommand(new List<int> { _artist.Id }));
VerifyEventPublished<ArtistUpdatedEvent>(); VerifyEventPublished<ArtistUpdatedEvent>();
VerifyEventPublished<ArtistRefreshCompleteEvent>(); VerifyEventPublished<ArtistRefreshCompleteEvent>();
@ -163,7 +163,7 @@ namespace NzbDrone.Core.Test.MusicTests
GivenAlbumsForRefresh(_albums); GivenAlbumsForRefresh(_albums);
AllowArtistUpdate(); AllowArtistUpdate();
Subject.Execute(new RefreshArtistCommand(_artist.Id)); Subject.Execute(new RefreshArtistCommand(new List<int> { _artist.Id }));
Mocker.GetMock<IMonitorNewAlbumService>() Mocker.GetMock<IMonitorNewAlbumService>()
.Verify(x => x.ShouldMonitorNewAlbum(newAlbum, _albums, _artist.MonitorNewItems), Times.Once()); .Verify(x => x.ShouldMonitorNewAlbum(newAlbum, _albums, _artist.MonitorNewItems), Times.Once());
@ -175,7 +175,7 @@ namespace NzbDrone.Core.Test.MusicTests
Mocker.GetMock<IArtistService>() Mocker.GetMock<IArtistService>()
.Setup(x => x.DeleteArtist(It.IsAny<int>(), It.IsAny<bool>(), It.IsAny<bool>())); .Setup(x => x.DeleteArtist(It.IsAny<int>(), It.IsAny<bool>(), It.IsAny<bool>()));
Subject.Execute(new RefreshArtistCommand(_artist.Id)); Subject.Execute(new RefreshArtistCommand(new List<int> { _artist.Id }));
Mocker.GetMock<IArtistService>() Mocker.GetMock<IArtistService>()
.Verify(v => v.UpdateArtist(It.IsAny<Artist>(), It.IsAny<bool>()), Times.Never()); .Verify(v => v.UpdateArtist(It.IsAny<Artist>(), It.IsAny<bool>()), Times.Never());
@ -193,7 +193,7 @@ namespace NzbDrone.Core.Test.MusicTests
GivenArtistFiles(); GivenArtistFiles();
GivenAlbumsForRefresh(new List<Album>()); GivenAlbumsForRefresh(new List<Album>());
Subject.Execute(new RefreshArtistCommand(_artist.Id)); Subject.Execute(new RefreshArtistCommand(new List<int> { _artist.Id }));
Mocker.GetMock<IArtistService>() Mocker.GetMock<IArtistService>()
.Verify(v => v.UpdateArtist(It.IsAny<Artist>(), It.IsAny<bool>()), Times.Never()); .Verify(v => v.UpdateArtist(It.IsAny<Artist>(), It.IsAny<bool>()), Times.Never());
@ -238,7 +238,7 @@ namespace NzbDrone.Core.Test.MusicTests
.Setup(x => x.UpdateArtist(It.IsAny<Artist>(), It.IsAny<bool>())) .Setup(x => x.UpdateArtist(It.IsAny<Artist>(), It.IsAny<bool>()))
.Returns((Artist a, bool updated) => a); .Returns((Artist a, bool updated) => a);
Subject.Execute(new RefreshArtistCommand(_artist.Id)); Subject.Execute(new RefreshArtistCommand(new List<int> { _artist.Id }));
Mocker.GetMock<IArtistService>() Mocker.GetMock<IArtistService>()
.Verify(v => v.UpdateArtist(It.Is<Artist>(s => s.ArtistMetadataId == 100 && s.ForeignArtistId == newArtistInfo.ForeignArtistId), It.IsAny<bool>()), .Verify(v => v.UpdateArtist(It.Is<Artist>(s => s.ArtistMetadataId == 100 && s.ForeignArtistId == newArtistInfo.ForeignArtistId), It.IsAny<bool>()),
@ -298,7 +298,7 @@ namespace NzbDrone.Core.Test.MusicTests
.Setup(x => x.UpdateArtist(It.IsAny<Artist>(), It.IsAny<bool>())) .Setup(x => x.UpdateArtist(It.IsAny<Artist>(), It.IsAny<bool>()))
.Returns((Artist a, bool updated) => a); .Returns((Artist a, bool updated) => a);
Subject.Execute(new RefreshArtistCommand(_artist.Id)); Subject.Execute(new RefreshArtistCommand(new List<int> { _artist.Id }));
// the retained artist gets updated // the retained artist gets updated
Mocker.GetMock<IArtistService>() Mocker.GetMock<IArtistService>()

@ -1089,12 +1089,13 @@
"Unmonitored": "Unmonitored", "Unmonitored": "Unmonitored",
"UnmonitoredHelpText": "Include unmonitored albums in the iCal feed", "UnmonitoredHelpText": "Include unmonitored albums in the iCal feed",
"UnmonitoredOnly": "Unmonitored Only", "UnmonitoredOnly": "Unmonitored Only",
"UpdateAll": "Update all", "UpdateAll": "Update All",
"UpdateAutomaticallyHelpText": "Automatically download and install updates. You will still be able to install from System: Updates", "UpdateAutomaticallyHelpText": "Automatically download and install updates. You will still be able to install from System: Updates",
"UpdateAvailable": "New update is available", "UpdateAvailable": "New update is available",
"UpdateCheckStartupNotWritableMessage": "Cannot install update because startup folder '{0}' is not writable by the user '{1}'.", "UpdateCheckStartupNotWritableMessage": "Cannot install update because startup folder '{0}' is not writable by the user '{1}'.",
"UpdateCheckStartupTranslocationMessage": "Cannot install update because startup folder '{0}' is in an App Translocation folder.", "UpdateCheckStartupTranslocationMessage": "Cannot install update because startup folder '{0}' is in an App Translocation folder.",
"UpdateCheckUINotWritableMessage": "Cannot install update because UI folder '{0}' is not writable by the user '{1}'.", "UpdateCheckUINotWritableMessage": "Cannot install update because UI folder '{0}' is not writable by the user '{1}'.",
"UpdateFiltered": "Update Filtered",
"UpdateMechanismHelpText": "Use Lidarr's built-in updater or a script", "UpdateMechanismHelpText": "Use Lidarr's built-in updater or a script",
"UpdateMonitoring": "Update Monitoring", "UpdateMonitoring": "Update Monitoring",
"UpdateScriptPathHelpText": "Path to a custom script that takes an extracted update package and handle the remainder of the update process", "UpdateScriptPathHelpText": "Path to a custom script that takes an extracted update package and handle the remainder of the update process",

@ -1,24 +1,41 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.Music.Commands namespace NzbDrone.Core.Music.Commands
{ {
public class RefreshArtistCommand : Command public class RefreshArtistCommand : Command
{ {
public int? ArtistId { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public int ArtistId
{
get => 0;
set
{
if (ArtistIds.Empty())
{
ArtistIds.Add(value);
}
}
}
public List<int> ArtistIds { get; set; }
public bool IsNewArtist { get; set; } public bool IsNewArtist { get; set; }
public RefreshArtistCommand() public RefreshArtistCommand()
{ {
ArtistIds = new List<int>();
} }
public RefreshArtistCommand(int? artistId, bool isNewArtist = false) public RefreshArtistCommand(List<int> artistIds, bool isNewArtist = false)
{ {
ArtistId = artistId; ArtistIds = artistIds;
IsNewArtist = isNewArtist; IsNewArtist = isNewArtist;
} }
public override bool SendUpdatesToClient => true; public override bool SendUpdatesToClient => true;
public override bool UpdateScheduledTask => !ArtistId.HasValue; public override bool UpdateScheduledTask => ArtistIds.Empty();
} }
} }

@ -1,3 +1,4 @@
using System.Collections.Generic;
using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Music.Commands; using NzbDrone.Core.Music.Commands;
@ -25,7 +26,7 @@ namespace NzbDrone.Core.Music
if (_checkIfArtistShouldBeRefreshed.ShouldRefresh(artist)) if (_checkIfArtistShouldBeRefreshed.ShouldRefresh(artist))
{ {
_commandQueueManager.Push(new RefreshArtistCommand(artist.Id)); _commandQueueManager.Push(new RefreshArtistCommand(new List<int> { artist.Id }));
} }
else else
{ {

@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
@ -22,7 +23,7 @@ namespace NzbDrone.Core.Music
{ {
if (message.DoRefresh) if (message.DoRefresh)
{ {
_commandQueueManager.Push(new RefreshArtistCommand(message.Artist.Id, true)); _commandQueueManager.Push(new RefreshArtistCommand(new List<int> { message.Artist.Id }, true));
} }
else else
{ {

@ -1,3 +1,4 @@
using System.Collections.Generic;
using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Music.Commands; using NzbDrone.Core.Music.Commands;
@ -19,7 +20,7 @@ namespace NzbDrone.Core.Music
// Refresh Artist is we change AlbumType Preferences // Refresh Artist is we change AlbumType Preferences
if (message.Artist.MetadataProfileId != message.OldArtist.MetadataProfileId) if (message.Artist.MetadataProfileId != message.OldArtist.MetadataProfileId)
{ {
_commandQueueManager.Push(new RefreshArtistCommand(message.Artist.Id, false)); _commandQueueManager.Push(new RefreshArtistCommand(new List<int> { message.Artist.Id }, false));
} }
} }
} }

@ -352,9 +352,9 @@ namespace NzbDrone.Core.Music
var trigger = message.Trigger; var trigger = message.Trigger;
var isNew = message.IsNewArtist; var isNew = message.IsNewArtist;
if (message.ArtistId.HasValue) if (message.ArtistIds.Any())
{ {
RefreshSelectedArtists(new List<int> { message.ArtistId.Value }, isNew, trigger); RefreshSelectedArtists(message.ArtistIds, isNew, trigger);
} }
else else
{ {

Loading…
Cancel
Save