Fixed: Faster artist endpoint (#874)

* Fixed: Speed up AllArtist API endpoint

* New: Display UI before artists have loaded

* Add test of new repository methods
pull/922/head
ta264 5 years ago committed by GitHub
parent 698d5e1cf5
commit 0352f8d3ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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>
}
{
!artistError &&
<Measure
whitelist={['width']}
onMeasure={this.onMeasure}
>
{
isMeasured ?
<PageComponent
useCurrentPage={useCurrentPage}
/> :
<div />
}
</Measure>
}
{
hasArtist &&
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…
Cancel
Save