mirror of https://github.com/lidarr/Lidarr
Fixed: Faster artist endpoint (#874)
* Fixed: Speed up AllArtist API endpoint * New: Display UI before artists have loaded * Add test of new repository methods
This commit is contained in:
parent
698d5e1cf5
commit
0352f8d3ff
|
@ -50,6 +50,10 @@ class BlacklistRow extends Component {
|
|||
onRemovePress
|
||||
} = this.props;
|
||||
|
||||
if (!artist) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
{
|
||||
|
|
|
@ -67,7 +67,7 @@ class HistoryRow extends Component {
|
|||
onMarkAsFailedPress
|
||||
} = this.props;
|
||||
|
||||
if (!album) {
|
||||
if (!artist || !album) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||
import selectAll from 'Utilities/Table/selectAll';
|
||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||
|
@ -145,7 +146,7 @@ class AlbumStudio extends Component {
|
|||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>Unable to load the Album Studio</div>
|
||||
<div>{getErrorMessage(error, 'Failed to load artist from API')}</div>
|
||||
}
|
||||
|
||||
{
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||
import selectAll from 'Utilities/Table/selectAll';
|
||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||
|
@ -209,7 +210,7 @@ class ArtistEditor extends Component {
|
|||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>Unable to load the calendar</div>
|
||||
<div>{getErrorMessage(error, 'Failed to load artist from API')}</div>
|
||||
}
|
||||
|
||||
{
|
||||
|
|
|
@ -4,6 +4,12 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.errorMessage {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.contentBody {
|
||||
composes: contentBody from '~Components/Page/PageContentBody.css';
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import _ from 'lodash';
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import { align, icons, sortDirections } from 'Helpers/Props';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
|
@ -340,7 +341,9 @@ class ArtistIndex extends Component {
|
|||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>Unable to load artist</div>
|
||||
<div className={styles.errorMessage}>
|
||||
{getErrorMessage(error, 'Failed to load artist from API')}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
|
|
|
@ -12,3 +12,9 @@
|
|||
flex-grow: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.errorMessage {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import { align, icons } from 'Helpers/Props';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import Measure from 'Components/Measure';
|
||||
|
@ -75,6 +76,7 @@ class CalendarPage extends Component {
|
|||
selectedFilterKey,
|
||||
filters,
|
||||
hasArtist,
|
||||
artistError,
|
||||
missingAlbumIds,
|
||||
isSearchingForMissing,
|
||||
useCurrentPage,
|
||||
|
@ -131,21 +133,31 @@ class CalendarPage extends Component {
|
|||
className={styles.calendarPageBody}
|
||||
innerClassName={styles.calendarInnerPageBody}
|
||||
>
|
||||
<Measure
|
||||
whitelist={['width']}
|
||||
onMeasure={this.onMeasure}
|
||||
>
|
||||
{
|
||||
isMeasured ?
|
||||
<PageComponent
|
||||
useCurrentPage={useCurrentPage}
|
||||
/> :
|
||||
<div />
|
||||
}
|
||||
</Measure>
|
||||
{
|
||||
artistError &&
|
||||
<div className={styles.errorMessage}>
|
||||
{getErrorMessage(artistError, 'Failed to load artist from API')}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
hasArtist &&
|
||||
!artistError &&
|
||||
<Measure
|
||||
whitelist={['width']}
|
||||
onMeasure={this.onMeasure}
|
||||
>
|
||||
{
|
||||
isMeasured ?
|
||||
<PageComponent
|
||||
useCurrentPage={useCurrentPage}
|
||||
/> :
|
||||
<div />
|
||||
}
|
||||
</Measure>
|
||||
}
|
||||
|
||||
{
|
||||
hasArtist && !!artistError &&
|
||||
<LegendConnector />
|
||||
}
|
||||
</PageContentBodyConnector>
|
||||
|
@ -169,6 +181,7 @@ CalendarPage.propTypes = {
|
|||
selectedFilterKey: PropTypes.string.isRequired,
|
||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
hasArtist: PropTypes.bool.isRequired,
|
||||
artistError: PropTypes.object,
|
||||
missingAlbumIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
isSearchingForMissing: PropTypes.bool.isRequired,
|
||||
useCurrentPage: PropTypes.bool.isRequired,
|
||||
|
|
|
@ -72,7 +72,8 @@ function createMapStateToProps() {
|
|||
selectedFilterKey,
|
||||
filters,
|
||||
colorImpairedMode: uiSettings.enableColorImpairedMode,
|
||||
hasArtist: !!artistCount,
|
||||
hasArtist: !!artistCount.count,
|
||||
artistError: artistCount.error,
|
||||
missingAlbumIds,
|
||||
isSearchingForMissing
|
||||
};
|
||||
|
|
|
@ -43,7 +43,6 @@ const selectAppProps = createSelector(
|
|||
);
|
||||
|
||||
const selectIsPopulated = createSelector(
|
||||
(state) => state.artist.isPopulated,
|
||||
(state) => state.customFilters.isPopulated,
|
||||
(state) => state.tags.isPopulated,
|
||||
(state) => state.settings.ui.isPopulated,
|
||||
|
@ -52,7 +51,6 @@ const selectIsPopulated = createSelector(
|
|||
(state) => state.settings.importLists.isPopulated,
|
||||
(state) => state.system.status.isPopulated,
|
||||
(
|
||||
artistIsPopulated,
|
||||
customFiltersIsPopulated,
|
||||
tagsIsPopulated,
|
||||
uiSettingsIsPopulated,
|
||||
|
@ -62,7 +60,6 @@ const selectIsPopulated = createSelector(
|
|||
systemStatusIsPopulated
|
||||
) => {
|
||||
return (
|
||||
artistIsPopulated &&
|
||||
customFiltersIsPopulated &&
|
||||
tagsIsPopulated &&
|
||||
uiSettingsIsPopulated &&
|
||||
|
@ -75,7 +72,6 @@ const selectIsPopulated = createSelector(
|
|||
);
|
||||
|
||||
const selectErrors = createSelector(
|
||||
(state) => state.artist.error,
|
||||
(state) => state.customFilters.error,
|
||||
(state) => state.tags.error,
|
||||
(state) => state.settings.ui.error,
|
||||
|
@ -84,7 +80,6 @@ const selectErrors = createSelector(
|
|||
(state) => state.settings.importLists.error,
|
||||
(state) => state.system.status.error,
|
||||
(
|
||||
artistError,
|
||||
customFiltersError,
|
||||
tagsError,
|
||||
uiSettingsError,
|
||||
|
@ -94,7 +89,6 @@ const selectErrors = createSelector(
|
|||
systemStatusError
|
||||
) => {
|
||||
const hasError = !!(
|
||||
artistError ||
|
||||
customFiltersError ||
|
||||
tagsError ||
|
||||
uiSettingsError ||
|
||||
|
@ -106,7 +100,6 @@ const selectErrors = createSelector(
|
|||
|
||||
return {
|
||||
hasError,
|
||||
artistError,
|
||||
customFiltersError,
|
||||
tagsError,
|
||||
uiSettingsError,
|
||||
|
|
|
@ -4,8 +4,12 @@ import createAllArtistSelector from './createAllArtistSelector';
|
|||
function createArtistCountSelector() {
|
||||
return createSelector(
|
||||
createAllArtistSelector(),
|
||||
(artists) => {
|
||||
return artists.length;
|
||||
(state) => state.artist.error,
|
||||
(artists, error) => {
|
||||
return {
|
||||
count: artists.length,
|
||||
error
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -26,6 +26,10 @@ function CutoffUnmetRow(props) {
|
|||
onSelectedChange
|
||||
} = props;
|
||||
|
||||
if (!artist) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableSelectCell
|
||||
|
|
|
@ -184,11 +184,13 @@ namespace Lidarr.Api.V1.Artist
|
|||
|
||||
private void LinkNextPreviousAlbums(params ArtistResource[] artists)
|
||||
{
|
||||
var nextAlbums = _albumService.GetNextAlbumsByArtistMetadataId(artists.Select(x => x.ArtistMetadataId));
|
||||
var lastAlbums = _albumService.GetLastAlbumsByArtistMetadataId(artists.Select(x => x.ArtistMetadataId));
|
||||
|
||||
foreach (var artistResource in artists)
|
||||
{
|
||||
var artistAlbums = _albumService.GetAlbumsByArtist(artistResource.Id).OrderBy(s=>s.ReleaseDate);
|
||||
artistResource.NextAlbum = artistAlbums.Where(s => s.ReleaseDate >= DateTime.UtcNow && s.Monitored).FirstOrDefault();
|
||||
artistResource.LastAlbum = artistAlbums.Where(s => s.ReleaseDate <= DateTime.UtcNow && s.Monitored).LastOrDefault();
|
||||
artistResource.NextAlbum = nextAlbums.FirstOrDefault(x => x.ArtistMetadataId == artistResource.ArtistMetadataId);
|
||||
artistResource.LastAlbum = lastAlbums.FirstOrDefault(x => x.ArtistMetadataId == artistResource.ArtistMetadataId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ using NzbDrone.Common.Extensions;
|
|||
using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.Music;
|
||||
using Lidarr.Http.REST;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Lidarr.Api.V1.Artist
|
||||
{
|
||||
|
@ -14,6 +15,8 @@ namespace Lidarr.Api.V1.Artist
|
|||
//Todo: Is there an easy way to keep IgnoreArticlesWhenSorting in sync between, Series, History, Missing?
|
||||
//Todo: We should get the entire Profile instead of ID and Name separately
|
||||
|
||||
[JsonIgnore]
|
||||
public int ArtistMetadataId { get; set; }
|
||||
public ArtistStatusType Status { get; set; }
|
||||
|
||||
public bool Ended => Status == ArtistStatusType.Ended;
|
||||
|
@ -70,6 +73,7 @@ namespace Lidarr.Api.V1.Artist
|
|||
return new ArtistResource
|
||||
{
|
||||
Id = model.Id,
|
||||
ArtistMetadataId = model.ArtistMetadataId,
|
||||
|
||||
ArtistName = model.Name,
|
||||
//AlternateTitles
|
||||
|
|
|
@ -3,7 +3,9 @@ using FluentAssertions;
|
|||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests
|
||||
{
|
||||
|
@ -13,6 +15,7 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests
|
|||
private Artist _artist;
|
||||
private Album _album;
|
||||
private Album _albumSpecial;
|
||||
private List<Album> _albums;
|
||||
private AlbumRelease _release;
|
||||
private AlbumRepository _albumRepo;
|
||||
private ReleaseRepository _releaseRepo;
|
||||
|
@ -150,5 +153,47 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests
|
|||
|
||||
album.Should().BeNull();
|
||||
}
|
||||
|
||||
private void GivenMultipleAlbums()
|
||||
{
|
||||
_albums = Builder<Album>.CreateListOfSize(4)
|
||||
.All()
|
||||
.With(x => x.Id = 0)
|
||||
.With(x => x.Artist = _artist)
|
||||
.With(x => x.ArtistMetadataId = _artist.ArtistMetadataId)
|
||||
.TheFirst(1)
|
||||
// next
|
||||
.With(x => x.ReleaseDate = DateTime.UtcNow.AddDays(1))
|
||||
.TheNext(1)
|
||||
// another future one
|
||||
.With(x => x.ReleaseDate = DateTime.UtcNow.AddDays(2))
|
||||
.TheNext(1)
|
||||
// most recent
|
||||
.With(x => x.ReleaseDate = DateTime.UtcNow.AddDays(-1))
|
||||
.TheNext(1)
|
||||
// an older one
|
||||
.With(x => x.ReleaseDate = DateTime.UtcNow.AddDays(-2))
|
||||
.BuildList();
|
||||
|
||||
_albumRepo.InsertMany(_albums);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void get_next_albums_should_return_next_album()
|
||||
{
|
||||
GivenMultipleAlbums();
|
||||
|
||||
var result = _albumRepo.GetNextAlbums(new [] { _artist.ArtistMetadataId });
|
||||
result.Should().BeEquivalentTo(_albums.Take(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void get_last_albums_should_return_next_album()
|
||||
{
|
||||
GivenMultipleAlbums();
|
||||
|
||||
var result = _albumRepo.GetLastAlbums(new [] { _artist.ArtistMetadataId });
|
||||
result.Should().BeEquivalentTo(_albums.Skip(2).Take(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ namespace NzbDrone.Core.Datastore
|
|||
|
||||
public IEnumerable<TModel> All()
|
||||
{
|
||||
return DataMapper.Query<TModel>().ToList();
|
||||
return Query.ToList();
|
||||
}
|
||||
|
||||
public int Count()
|
||||
|
|
|
@ -14,6 +14,8 @@ namespace NzbDrone.Core.Music
|
|||
public interface IAlbumRepository : IBasicRepository<Album>
|
||||
{
|
||||
List<Album> GetAlbums(int artistId);
|
||||
List<Album> GetLastAlbums(IEnumerable<int> artistMetadataIds);
|
||||
List<Album> GetNextAlbums(IEnumerable<int> artistMetadataIds);
|
||||
List<Album> GetAlbumsByArtistMetadataId(int artistMetadataId);
|
||||
List<Album> GetAlbumsForRefresh(int artistId, IEnumerable<string> foreignIds);
|
||||
Album FindByTitle(int artistMetadataId, string title);
|
||||
|
@ -47,6 +49,32 @@ namespace NzbDrone.Core.Music
|
|||
.Where<Artist>(a => a.Id == artistId).ToList();
|
||||
}
|
||||
|
||||
public List<Album> GetLastAlbums(IEnumerable<int> artistMetadataIds)
|
||||
{
|
||||
string query = string.Format("SELECT Albums.* " +
|
||||
"FROM Albums " +
|
||||
"WHERE Albums.ArtistMetadataId IN ({0}) " +
|
||||
"AND Albums.ReleaseDate < datetime('now') " +
|
||||
"GROUP BY Albums.ArtistMetadataId " +
|
||||
"HAVING Albums.ReleaseDate = MAX(Albums.ReleaseDate)",
|
||||
string.Join(", ", artistMetadataIds));
|
||||
|
||||
return Query.QueryText(query);
|
||||
}
|
||||
|
||||
public List<Album> GetNextAlbums(IEnumerable<int> artistMetadataIds)
|
||||
{
|
||||
string query = string.Format("SELECT Albums.* " +
|
||||
"FROM Albums " +
|
||||
"WHERE Albums.ArtistMetadataId IN ({0}) " +
|
||||
"AND Albums.ReleaseDate > datetime('now') " +
|
||||
"GROUP BY Albums.ArtistMetadataId " +
|
||||
"HAVING Albums.ReleaseDate = MIN(Albums.ReleaseDate)",
|
||||
string.Join(", ", artistMetadataIds));
|
||||
|
||||
return Query.QueryText(query);
|
||||
}
|
||||
|
||||
public List<Album> GetAlbumsByArtistMetadataId(int artistMetadataId)
|
||||
{
|
||||
return Query.Where(s => s.ArtistMetadataId == artistMetadataId);
|
||||
|
|
|
@ -15,6 +15,8 @@ namespace NzbDrone.Core.Music
|
|||
Album GetAlbum(int albumId);
|
||||
List<Album> GetAlbums(IEnumerable<int> albumIds);
|
||||
List<Album> GetAlbumsByArtist(int artistId);
|
||||
List<Album> GetNextAlbumsByArtistMetadataId(IEnumerable<int> artistMetadataIds);
|
||||
List<Album> GetLastAlbumsByArtistMetadataId(IEnumerable<int> artistMetadataIds);
|
||||
List<Album> GetAlbumsByArtistMetadataId(int artistMetadataId);
|
||||
List<Album> GetAlbumsForRefresh(int artistMetadataId, IEnumerable<string> foreignIds);
|
||||
Album AddAlbum(Album newAlbum);
|
||||
|
@ -170,6 +172,16 @@ namespace NzbDrone.Core.Music
|
|||
return _albumRepository.GetAlbums(artistId).ToList();
|
||||
}
|
||||
|
||||
public List<Album> GetNextAlbumsByArtistMetadataId(IEnumerable<int> artistMetadataIds)
|
||||
{
|
||||
return _albumRepository.GetNextAlbums(artistMetadataIds).ToList();
|
||||
}
|
||||
|
||||
public List<Album> GetLastAlbumsByArtistMetadataId(IEnumerable<int> artistMetadataIds)
|
||||
{
|
||||
return _albumRepository.GetLastAlbums(artistMetadataIds).ToList();
|
||||
}
|
||||
|
||||
public List<Album> GetAlbumsByArtistMetadataId(int artistMetadataId)
|
||||
{
|
||||
return _albumRepository.GetAlbumsByArtistMetadataId(artistMetadataId).ToList();
|
||||
|
|
Loading…
Reference in New Issue