diff --git a/Gruntfile.js b/Gruntfile.js index e608d7630..21dfd6b5a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -169,35 +169,35 @@ module.exports = function (grunt) { tasks: ['handlebars'] }, copyIndex : { - files: '<%= copy.index.src %>', + files: '<%= copy.index.cwd %><%= copy.index.src %>', tasks: ['copy:index'] }, copyScripts: { - files: '<%= copy.scripts.src %>', + files: '<%= copy.scripts.cwd %><%= copy.scripts.src %>', tasks: ['copy:scripts'] }, copyStyles : { - files: '<%= copy.styles.src %>', + files: '<%= copy.styles.cwd %><%= copy.styles.src %>', tasks: ['copy:styles'] }, copyImages : { - files: '<%= copy.images.src %>', + files: '<%= copy.images.cwd %><%= copy.images.src %>', tasks: ['copy:images'] }, copyJpg : { - files: '<%= copy.jpg.src %>', + files: '<%= copy.jpg.cwd %><%= copy.jpg.src %>', tasks: ['copy:jpg'] }, copyIcon : { - files: '<%= copy.icon.src %>', + files: '<%= copy.icon.cwd %><%= copy.icon.src %>', tasks: ['copy:icon'] }, copyFontAwesome : { - files: '<%= copy.fontAwesome.src %>', + files: '<%= copy.fontAwesome.cwd %><%= copy.fontAwesome.src %>', tasks: ['copy:fontAwesome'] }, copyFonts : { - files: '<%= copy.fonts.src %>', + files: '<%= copy.fonts.cwd %><%= copy.fonts.src %>', tasks: ['copy:fonts'] } } diff --git a/src/NzbDrone.Api/Episodes/EpisodeActivityModule.cs b/src/NzbDrone.Api/Episodes/EpisodeActivityModule.cs new file mode 100644 index 000000000..35eae7fcc --- /dev/null +++ b/src/NzbDrone.Api/Episodes/EpisodeActivityModule.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using NzbDrone.Api.History; +using NzbDrone.Api.Mapping; +using NzbDrone.Api.REST; +using NzbDrone.Core.Datastore.Events; +using NzbDrone.Core.Download; +using NzbDrone.Core.History; +using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Api.Episodes +{ + public class EpisodeActivityModule : NzbDroneRestModule + + { + private readonly IHistoryService _historyService; + + public EpisodeActivityModule(IHistoryService historyService) + : base("episodes/activity") + { + _historyService = historyService; + + GetResourceAll = GetActivity; + } + + private List GetActivity() + { + var episodeId = (int?)Request.Query.EpisodeId; + + if (episodeId == null) + { + throw new BadRequestException("episodeId is missing"); + } + + return ToListResource(() => _historyService.ByEpisode(episodeId.Value)); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 7b885fe48..19d51cea7 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -91,6 +91,7 @@ + diff --git a/src/NzbDrone.Core/History/HistoryRepository.cs b/src/NzbDrone.Core/History/HistoryRepository.cs index a39e117ea..b622ac3b8 100644 --- a/src/NzbDrone.Core/History/HistoryRepository.cs +++ b/src/NzbDrone.Core/History/HistoryRepository.cs @@ -12,6 +12,7 @@ namespace NzbDrone.Core.History { void Trim(); List GetEpisodeHistory(int episodeId); + List ByEpisode(int episodeId); } public class HistoryRepository : BasicRepository, IHistoryRepository @@ -37,6 +38,11 @@ namespace NzbDrone.Core.History return history.Select(h => h.Quality).ToList(); } + public List ByEpisode(int episodeId) + { + return Query.Where(h => h.EpisodeId == episodeId).ToList(); + } + public override PagingSpec GetPaged(PagingSpec pagingSpec) { var pagingQuery = Query.Join(JoinType.Inner, h => h.Series, (h, s) => h.SeriesId == s.Id) diff --git a/src/NzbDrone.Core/History/HistoryService.cs b/src/NzbDrone.Core/History/HistoryService.cs index 64736c9f0..5a32aae21 100644 --- a/src/NzbDrone.Core/History/HistoryService.cs +++ b/src/NzbDrone.Core/History/HistoryService.cs @@ -17,6 +17,7 @@ namespace NzbDrone.Core.History void Trim(); QualityModel GetBestQualityInHistory(int episodeId); PagingSpec Paged(PagingSpec pagingSpec); + List ByEpisode(int episodeId); } public class HistoryService : IHistoryService, IHandle, IHandle @@ -40,6 +41,11 @@ namespace NzbDrone.Core.History return _historyRepository.GetPaged(pagingSpec); } + public List ByEpisode(int episodeId) + { + return _historyRepository.ByEpisode(episodeId); + } + public void Purge() { _historyRepository.Purge(); diff --git a/src/UI/History/Table/EventTypeCell.js b/src/UI/Cells/EventTypeCell.js similarity index 92% rename from src/UI/History/Table/EventTypeCell.js rename to src/UI/Cells/EventTypeCell.js index d328b9bf1..3b9bb8b0a 100644 --- a/src/UI/History/Table/EventTypeCell.js +++ b/src/UI/Cells/EventTypeCell.js @@ -35,7 +35,7 @@ define( } - this.$el.html(''.format(icon, toolTip)); + this.$el.html(''.format(icon, toolTip)); } return this; diff --git a/src/UI/Episode/Activity/EpisodeActivityCollection.js b/src/UI/Episode/Activity/EpisodeActivityCollection.js new file mode 100644 index 000000000..968286c08 --- /dev/null +++ b/src/UI/Episode/Activity/EpisodeActivityCollection.js @@ -0,0 +1,31 @@ +'use strict'; +define( + [ + 'backbone', + 'Episode/Activity/EpisodeActivityModel' + ], function (Backbone, EpisodeActivityModel) { + return Backbone.Collection.extend({ + url : window.NzbDrone.ApiRoot + '/episodes/activity', + model: EpisodeActivityModel, + + originalFetch: Backbone.Collection.prototype.fetch, + + initialize: function (options) { + this.episodeId = options.episodeId; + }, + + fetch: function (options) { + if (!this.episodeId) { + throw 'episodeId is required'; + } + + if (!options) { + options = {}; + } + + options.data = { episodeId: this.episodeId }; + + return this.originalFetch.call(this, options); + } + }); + }); diff --git a/src/UI/Episode/Activity/EpisodeActivityLayout.js b/src/UI/Episode/Activity/EpisodeActivityLayout.js new file mode 100644 index 000000000..1a451391e --- /dev/null +++ b/src/UI/Episode/Activity/EpisodeActivityLayout.js @@ -0,0 +1,72 @@ +'use strict'; +define( + [ + 'app', + 'marionette', + 'backgrid', + 'Episode/Activity/EpisodeActivityCollection', + 'Cells/EventTypeCell', + 'Cells/QualityCell', + 'Cells/RelativeDateCell', + 'Shared/LoadingView' + ], function (App, Marionette, Backgrid, EpisodeActivityCollection, EventTypeCell, QualityCell, RelativeDateCell, LoadingView) { + + return Marionette.Layout.extend({ + template: 'Episode/Activity/EpisodeActivityLayoutTemplate', + + regions: { + activityTable: '.activity-table' + }, + + columns: + [ + { + name : 'eventType', + label : '', + cell : EventTypeCell, + cellValue: 'this' + }, + { + name : 'sourceTitle', + label: 'Source Title', + cell : 'string' + }, + { + name : 'quality', + label: 'Quality', + cell : QualityCell + }, + { + name : 'date', + label: 'Date', + cell : RelativeDateCell + } + ], + + initialize: function (options) { + this.model = options.model; + this.series = options.series; + + this.collection = new EpisodeActivityCollection({ episodeId: this.model.id }); + }, + + onShow: function () { + var self = this; + this.activityTable.show(new LoadingView()); + + var promise = this.collection.fetch(); + + promise.done(function () { + self._showTable(); + }); + }, + + _showTable: function () { + this.activityTable.show(new Backgrid.Grid({ + collection: this.collection, + columns : this.columns, + className : 'table table-hover table-condensed' + })); + } + }); + }); diff --git a/src/UI/Episode/Activity/EpisodeActivityLayoutTemplate.html b/src/UI/Episode/Activity/EpisodeActivityLayoutTemplate.html new file mode 100644 index 000000000..459032937 --- /dev/null +++ b/src/UI/Episode/Activity/EpisodeActivityLayoutTemplate.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/UI/Episode/Activity/EpisodeActivityModel.js b/src/UI/Episode/Activity/EpisodeActivityModel.js new file mode 100644 index 000000000..808c9c77c --- /dev/null +++ b/src/UI/Episode/Activity/EpisodeActivityModel.js @@ -0,0 +1,9 @@ +'use strict'; +define( + [ + 'backbone' + ], function (Backbone) { + return Backbone.Model.extend({ + + }); + }); diff --git a/src/UI/Episode/EpisodeDetailsLayout.js b/src/UI/Episode/EpisodeDetailsLayout.js index 590351b40..fe60cad1b 100644 --- a/src/UI/Episode/EpisodeDetailsLayout.js +++ b/src/UI/Episode/EpisodeDetailsLayout.js @@ -4,8 +4,9 @@ define( 'marionette', 'Episode/Summary/EpisodeSummaryLayout', 'Episode/Search/EpisodeSearchLayout', + 'Episode/Activity/EpisodeActivityLayout', 'Series/SeriesCollection' - ], function (Marionette, SummaryLayout, SearchLayout, SeriesCollection) { + ], function (Marionette, SummaryLayout, SearchLayout, EpisodeActivityLayout, SeriesCollection) { return Marionette.Layout.extend({ template: 'Episode/EpisodeDetailsLayoutTemplate', @@ -38,7 +39,7 @@ define( this.series = SeriesCollection.find({ id: this.model.get('seriesId') }); this.templateHelpers.series = this.series.toJSON(); - this.openingTab = options.openingTab || 'summary' + this.openingTab = options.openingTab || 'summary'; }, onShow: function () { @@ -71,6 +72,7 @@ define( } this.ui.activity.tab('show'); + this.activity.show(new EpisodeActivityLayout({model: this.model, series: this.series})); }, _showSearch: function (e) { diff --git a/src/UI/Episode/EpisodeDetailsLayoutTemplate.html b/src/UI/Episode/EpisodeDetailsLayoutTemplate.html index 982110d6a..1031118b1 100644 --- a/src/UI/Episode/EpisodeDetailsLayoutTemplate.html +++ b/src/UI/Episode/EpisodeDetailsLayoutTemplate.html @@ -15,12 +15,12 @@