Pagination for missing is alive!

This commit is contained in:
Mark McDowall 2013-05-01 22:50:34 -07:00
parent c9b9d7b956
commit b4242f9fb2
17 changed files with 235 additions and 22 deletions

View File

@ -7,6 +7,7 @@ using NzbDrone.Api.QualityProfiles;
using NzbDrone.Api.QualityType; using NzbDrone.Api.QualityType;
using NzbDrone.Api.Resolvers; using NzbDrone.Api.Resolvers;
using NzbDrone.Api.Series; using NzbDrone.Api.Series;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
@ -38,6 +39,9 @@ namespace NzbDrone.Api
//Episode //Episode
Mapper.CreateMap<Episode, EpisodeResource>(); Mapper.CreateMap<Episode, EpisodeResource>();
//Episode Paging
Mapper.CreateMap<PagingSpec<Episode>, PagingResource<EpisodeResource>>();
} }
} }
} }

View File

@ -29,5 +29,6 @@ namespace NzbDrone.Api.Episodes
public DateTime? GrabDate { get; set; } public DateTime? GrabDate { get; set; }
public PostDownloadStatusType PostDownloadStatus { get; set; } public PostDownloadStatusType PostDownloadStatus { get; set; }
public Core.Tv.Series Series { get; set; } public Core.Tv.Series Series { get; set; }
public String SeriesTitle { get; set; }
} }
} }

View File

@ -1,10 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
using AutoMapper; using AutoMapper;
using Nancy; using Nancy;
using NzbDrone.Api.Episodes; using NzbDrone.Api.Episodes;
using NzbDrone.Api.Extensions; using NzbDrone.Api.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
namespace NzbDrone.Api.Missing namespace NzbDrone.Api.Missing
@ -25,11 +27,35 @@ namespace NzbDrone.Api.Missing
bool includeSpecials; bool includeSpecials;
Boolean.TryParse(PrimitiveExtensions.ToNullSafeString(Request.Query.IncludeSpecials), out includeSpecials); Boolean.TryParse(PrimitiveExtensions.ToNullSafeString(Request.Query.IncludeSpecials), out includeSpecials);
var episodes = _episodeService.EpisodesWithoutFiles(includeSpecials); int pageSize;
Int32.TryParse(PrimitiveExtensions.ToNullSafeString(Request.Query.PageSize), out pageSize);
if (pageSize == 0) pageSize = 20;
//TODO: Include the Series Title int page;
//TODO: Remove Take(20) Int32.TryParse(PrimitiveExtensions.ToNullSafeString(Request.Query.Page), out page);
return Mapper.Map<List<Episode>, List<EpisodeResource>>(episodes).Take(20).AsResponse(); if (page == 0) page = 1;
var sortKey = PrimitiveExtensions.ToNullSafeString(Request.Query.SortKey)
.Equals("SeriesTitle", StringComparison.InvariantCultureIgnoreCase)
? "SeriesTitle"
: "AirDate";
var sortDirection = PrimitiveExtensions.ToNullSafeString(Request.Query.SortDir)
.Equals("Asc", StringComparison.InvariantCultureIgnoreCase)
? ListSortDirection.Ascending
: ListSortDirection.Descending;
var pagingSpec = new PagingSpec<Episode>
{
Page = page,
PageSize = pageSize,
SortKey = sortKey,
SortDirection = sortDirection
};
var result = _episodeService.EpisodesWithoutFiles(pagingSpec, includeSpecials);
return Mapper.Map<PagingSpec<Episode>, PagingResource<EpisodeResource>>(result).AsResponse();
} }
} }
} }

View File

@ -102,6 +102,7 @@
<Compile Include="Mapping\ValueInjectorExtensions.cs" /> <Compile Include="Mapping\ValueInjectorExtensions.cs" />
<Compile Include="Missing\MissingModule.cs" /> <Compile Include="Missing\MissingModule.cs" />
<Compile Include="NzbDroneRestModule.cs" /> <Compile Include="NzbDroneRestModule.cs" />
<Compile Include="PagingResource.cs" />
<Compile Include="Resolvers\EndTimeResolver.cs" /> <Compile Include="Resolvers\EndTimeResolver.cs" />
<Compile Include="Resolvers\NextAiringResolver.cs" /> <Compile Include="Resolvers\NextAiringResolver.cs" />
<Compile Include="Resolvers\NullableDatetimeToString.cs" /> <Compile Include="Resolvers\NullableDatetimeToString.cs" />

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Api
{
public class PagingResource<TModel>
{
public int Page { get; set; }
public string SortKey { get; set; }
public int TotalRecords { get; set; }
public List<TModel> Records { get; set; }
}
}

View File

@ -1,10 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using FizzWare.NBuilder; using FizzWare.NBuilder;
using FluentAssertions; using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
@ -38,15 +40,30 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests
[Test] [Test]
public void should_get_episodes() public void should_get_episodes()
{ {
var episodes = Subject.EpisodesWithoutFiles(false); var episodes =
episodes.Should().HaveCount(1); Subject.EpisodesWithoutFiles(new PagingSpec<Episode>
{
Page = 1,
PageSize = 10,
SortKey = "AirDate",
SortDirection = ListSortDirection.Ascending
}, false);
episodes.Records.Should().HaveCount(1);
} }
[Test] [Test]
[Ignore("Specials not implemented")]
public void should_get_episode_including_specials() public void should_get_episode_including_specials()
{ {
var episodes = Subject.EpisodesWithoutFiles(true); var episodes =
episodes.Should().HaveCount(2); Subject.EpisodesWithoutFiles(new PagingSpec<Episode>
{
Page = 1,
PageSize = 10,
SortKey = "AirDate",
SortDirection = ListSortDirection.Ascending
}, true);
episodes.Records.Should().HaveCount(2);
} }
} }
} }

View File

@ -114,7 +114,6 @@ namespace NzbDrone.Core.Datastore
return model; return model;
} }
public void Delete(TModel model) public void Delete(TModel model)
{ {
_dataMapper.Delete<TModel>(c => c.Id == model.Id); _dataMapper.Delete<TModel>(c => c.Id == model.Id);
@ -185,6 +184,5 @@ namespace NzbDrone.Core.Datastore
.Entity(model) .Entity(model)
.Execute(); .Execute();
} }
} }
} }

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Datastore
{
public class PagingSpec<TModel>
{
public int Page { get; set; }
public int PageSize { get; set; }
public int TotalRecords { get; set; }
public string SortKey { get; set; }
public ListSortDirection SortDirection { get; set; }
public List<TModel> Records { get; set; }
}
}

View File

@ -208,6 +208,7 @@
<Compile Include="Datastore\MigrationType.cs" /> <Compile Include="Datastore\MigrationType.cs" />
<Compile Include="Datastore\ModelBase.cs" /> <Compile Include="Datastore\ModelBase.cs" />
<Compile Include="Datastore\BasicRepository.cs" /> <Compile Include="Datastore\BasicRepository.cs" />
<Compile Include="Datastore\PagingSpec.cs" />
<Compile Include="Datastore\RelationshipExtensions.cs" /> <Compile Include="Datastore\RelationshipExtensions.cs" />
<Compile Include="Datastore\TableMapping.cs" /> <Compile Include="Datastore\TableMapping.cs" />
<Compile Include="DecisionEngine\DownloadDecision.cs" /> <Compile Include="DecisionEngine\DownloadDecision.cs" />

View File

@ -71,6 +71,8 @@ namespace NzbDrone.Core.Tv
} }
} }
public String SeriesTitle { get; private set; }
public Series Series { get; set; } public Series Series { get; set; }
public EpisodeFile EpisodeFile { get; set; } public EpisodeFile EpisodeFile { get; set; }

View File

@ -4,7 +4,9 @@ using System.Linq;
using NLog; using NLog;
using NzbDrone.Common.Messaging; using NzbDrone.Common.Messaging;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Helpers;
using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.MetadataSource; using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.Model; using NzbDrone.Core.Model;
@ -19,7 +21,7 @@ namespace NzbDrone.Core.Tv
Episode GetEpisode(int seriesId, DateTime date); Episode GetEpisode(int seriesId, DateTime date);
List<Episode> GetEpisodeBySeries(int seriesId); List<Episode> GetEpisodeBySeries(int seriesId);
List<Episode> GetEpisodesBySeason(int seriesId, int seasonNumber); List<Episode> GetEpisodesBySeason(int seriesId, int seasonNumber);
List<Episode> EpisodesWithoutFiles(bool includeSpecials); PagingSpec<Episode> EpisodesWithoutFiles(PagingSpec<Episode> pagingSpec, bool includeSpecials);
List<Episode> GetEpisodesByFileId(int episodeFileId); List<Episode> GetEpisodesByFileId(int episodeFileId);
List<Episode> EpisodesWithFiles(); List<Episode> EpisodesWithFiles();
void RefreshEpisodeInfo(Series series); void RefreshEpisodeInfo(Series series);
@ -93,11 +95,13 @@ namespace NzbDrone.Core.Tv
return _episodeRepository.GetEpisodes(seriesId, seasonNumber); return _episodeRepository.GetEpisodes(seriesId, seasonNumber);
} }
public List<Episode> EpisodesWithoutFiles(bool includeSpecials) public PagingSpec<Episode> EpisodesWithoutFiles(PagingSpec<Episode> pagingSpec, bool includeSpecials)
{ {
var episodes = _episodeRepository.EpisodesWithoutFiles(includeSpecials); var episodeResult = _episodeRepository.EpisodesWithoutFiles(pagingSpec, includeSpecials);
return LinkSeriesToEpisodes(episodes); episodeResult.Records = LinkSeriesToEpisodes(episodeResult.Records);
return episodeResult;
} }
public List<Episode> GetEpisodesByFileId(int episodeFileId) public List<Episode> GetEpisodesByFileId(int episodeFileId)

View File

@ -58,7 +58,7 @@
@param {boolean} [options.fastForwardHandleLabels] Whether to render fast forward buttons. @param {boolean} [options.fastForwardHandleLabels] Whether to render fast forward buttons.
*/ */
initialize: function (options) { initialize: function (options) {
Backgrid.requireOptions(options, ["columns", "collection"]); //Backgrid.requireOptions(options, ["columns", "collection"]);
this.columns = options.columns; this.columns = options.columns;
if (!(this.columns instanceof Backbone.Collection)) { if (!(this.columns instanceof Backbone.Collection)) {

View File

@ -4,10 +4,6 @@ define(['app', 'Series/EpisodeModel'], function () {
url : NzbDrone.Constants.ApiRoot + '/missing', url : NzbDrone.Constants.ApiRoot + '/missing',
model : NzbDrone.Series.EpisodeModel, model : NzbDrone.Series.EpisodeModel,
comparator: function (model) {
return model.get('airDate');
},
state: { state: {
pageSize: 10, pageSize: 10,
sortKey: "airDate", sortKey: "airDate",
@ -18,12 +14,24 @@ define(['app', 'Series/EpisodeModel'], function () {
totalPages: null, totalPages: null,
totalRecords: null, totalRecords: null,
pageSize: 'pageSize', pageSize: 'pageSize',
sortKey: "sortBy", sortKey: "sortKey",
order: "direction", order: "sortDir",
directions: { directions: {
"-1": "asc", "-1": "asc",
"1": "desc" "1": "desc"
} }
},
parseState: function (resp, queryParams, state) {
return {totalRecords: resp.totalRecords};
},
parseRecords: function (resp) {
if (resp) {
return resp.records;
}
return resp;
} }
}); });
}); });

View File

@ -13,7 +13,8 @@ define([
regions: { regions: {
missing: '#x-missing', missing: '#x-missing',
toolbar: '#x-toolbar' toolbar: '#x-toolbar',
pager : '#x-pager'
}, },
showTable: function () { showTable: function () {
@ -48,6 +49,12 @@ define([
editable : false, editable : false,
cell : 'airDate', cell : 'airDate',
headerCell: 'nzbDrone' headerCell: 'nzbDrone'
// headerCell: Backgrid.NzbDroneHeaderCell.extend({
// initialize: function(options) {
// this.constructor.__super__.initialize.apply(this, [options]);
// this.direction('descending');
// }
// })
}, },
{ {
name : 'edit', name : 'edit',
@ -66,6 +73,13 @@ define([
collection: this.missingCollection, collection: this.missingCollection,
className : 'table table-hover' className : 'table table-hover'
})); }));
this.pager.show(new Backgrid.NzbDronePaginator({
columns: columns,
collection: this.missingCollection
}));
this.missingCollection.getFirstPage();
}, },
initialize: function () { initialize: function () {

View File

@ -4,3 +4,8 @@
<div id="x-missing"></div> <div id="x-missing"></div>
</div> </div>
</div> </div>
<div class="row">
<div class="span12">
<div id="x-pager"></div>
</div>
</div>

View File

@ -40,6 +40,40 @@ Backgrid.NzbDroneHeaderCell = Backgrid.HeaderCell.extend({
return this._direction; return this._direction;
}, },
onClick: function (e) {
e.preventDefault();
var columnName = this.column.get("name");
if (this.column.get("sortable")) {
if (this.direction() === "ascending") {
this.sort(columnName, "descending", function (left, right) {
var leftVal = left.get(columnName);
var rightVal = right.get(columnName);
if (leftVal === rightVal) {
return 0;
}
else if (leftVal > rightVal) { return -1; }
return 1;
});
}
else if (this.direction() === "descending") {
this.sort(columnName, "ascending");
}
else {
this.sort(columnName, "ascending", function (left, right) {
var leftVal = left.get(columnName);
var rightVal = right.get(columnName);
if (leftVal === rightVal) {
return 0;
}
else if (leftVal < rightVal) { return -1; }
return 1;
});
}
}
},
_convertDirectionToIcon: function (dir) { _convertDirectionToIcon: function (dir) {
if (dir === 'ascending') { if (dir === 'ascending') {
return 'icon-sort-up'; return 'icon-sort-up';
@ -47,4 +81,69 @@ Backgrid.NzbDroneHeaderCell = Backgrid.HeaderCell.extend({
return 'icon-sort-down'; return 'icon-sort-down';
} }
});
Backgrid.NzbDronePaginator = Backgrid.Extension.Paginator.extend({
events: {
"click a": "changePage",
"click i": "preventLinkClick"
},
windowSize: 1,
fastForwardHandleLabels: {
first: '<i class="icon-fast-backward"></i>',
prev: '<i class="icon-backward"></i>',
next: '<i class="icon-forward"></i>',
last: '<i class="icon-fast-forward"></i>'
},
changePage: function (e) {
e.preventDefault();
var target = $(e.target);
if (target.closest('li').hasClass('disabled')) {
return;
}
if (!$(target).is('a')){
target = target.parent('a');
}
var label = target.html();
var ffLabels = this.fastForwardHandleLabels;
var collection = this.collection;
if (ffLabels) {
switch (label) {
case ffLabels.first:
collection.getFirstPage();
return;
case ffLabels.prev:
if (collection.hasPrevious()) {
collection.getPreviousPage();
}
return;
case ffLabels.next:
if (collection.hasNext()) {
collection.getNextPage();
}
return;
case ffLabels.last:
collection.getLastPage();
return;
}
}
var state = collection.state;
var pageIndex = $(e.target).text() * 1;
collection.getPage(state.firstPage === 0 ? pageIndex - 1 : pageIndex);
},
preventLinkClick: function (e) {
e.preventDefault();
}
}); });