From 7fdbc10cccbc02525dfc3cfd08204aaba23c6079 Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Mon, 1 Apr 2019 14:47:20 +0100 Subject: [PATCH] More work on the calendar, including unit tests --- .../Engine/CalendarEngineTests.cs | 152 ++++++++++++++++++ src/Ombi.Core/Engine/V2/CalendarEngine.cs | 82 +++++++++- .../Models/Search/V2/CalendarViewModel.cs | 29 ++++ src/Ombi.Helpers/CacheService.cs | 2 +- src/Ombi/ClientApp/package-lock.json | 82 +++++++++- .../calendar/components/calendar.component.ts | 30 +--- .../movie/movie-details.component.html | 2 +- ...darController.cs => CalendarController.cs} | 2 +- 8 files changed, 344 insertions(+), 37 deletions(-) create mode 100644 src/Ombi.Core.Tests/Engine/CalendarEngineTests.cs rename src/Ombi/Controllers/V2/{CallendarController.cs => CalendarController.cs} (98%) diff --git a/src/Ombi.Core.Tests/Engine/CalendarEngineTests.cs b/src/Ombi.Core.Tests/Engine/CalendarEngineTests.cs new file mode 100644 index 000000000..f660dff75 --- /dev/null +++ b/src/Ombi.Core.Tests/Engine/CalendarEngineTests.cs @@ -0,0 +1,152 @@ + +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Linq; +using System.Security.Principal; +using Moq; +using NUnit.Framework; +using Ombi.Core.Authentication; +using Ombi.Core.Engine.V2; +using Ombi.Store.Entities.Requests; +using Ombi.Store.Repository.Requests; + +namespace Ombi.Core.Tests.Engine +{ + [TestFixture] + public class CalendarEngineTests + { + public Mock MovieRepo { get; set; } + public Mock TvRepo { get; set; } + public CalendarEngine CalendarEngine { get; set; } + + [SetUp] + public void Setup() + { + MovieRepo = new Mock(); + TvRepo = new Mock(); + var principle = new Mock(); + var identity = new Mock(); + identity.Setup(x => x.Name).Returns("UnitTest"); + principle.Setup(x => x.Identity).Returns(identity.Object); + CalendarEngine = new CalendarEngine(principle.Object, null, null, MovieRepo.Object, TvRepo.Object); + } + + [Test] + public async Task Calendar_Movies_OnlyGet_PreviousAndFuture_90_Days() + { + var movies = new List + { + new MovieRequests + { + Title="Invalid", + ReleaseDate = new DateTime(2018,10,01) + }, + new MovieRequests + { + Title="Invalid", + ReleaseDate = DateTime.Now.AddDays(91) + }, + + new MovieRequests + { + Title="Valid", + ReleaseDate = DateTime.Now + } + }; + MovieRepo.Setup(x => x.GetAll()).Returns(movies.AsQueryable()); + var data = await CalendarEngine.GetCalendarData(); + + Assert.That(data.Count, Is.EqualTo(1)); + Assert.That(data[0].Title, Is.EqualTo("Valid")); + } + + [Test] + public async Task Calendar_Episodes_OnlyGet_PreviousAndFuture_90_Days() + { + var tv = new List + { + new ChildRequests + { + SeasonRequests = new List + { + new SeasonRequests + { + Episodes = new List + { + new EpisodeRequests + { + Title = "Invalid", + AirDate = new DateTime(2018,01,01) + }, + new EpisodeRequests + { + Title = "Invalid", + AirDate = DateTime.Now.AddDays(91) + }, + new EpisodeRequests + { + Title = "Valid", + AirDate = DateTime.Now + }, + } + } + } + }, + }; + TvRepo.Setup(x => x.GetChild()).Returns(tv.AsQueryable()); + var data = await CalendarEngine.GetCalendarData(); + + Assert.That(data.Count, Is.EqualTo(1)); + Assert.That(data[0].Title, Is.EqualTo("Valid")); + } + + [TestCaseSource(nameof(StatusColorData))] + public async Task Calendar_StatusColor(AvailabilityTestModel model) + { + var movies = new List + { + new MovieRequests + { + Title="Valid", + ReleaseDate = DateTime.Now, + Denied = model.Denied, + Approved = model.Approved, + Available = model.Available + }, + }; + MovieRepo.Setup(x => x.GetAll()).Returns(movies.AsQueryable()); + var data = await CalendarEngine.GetCalendarData(); + + return data[0].BackgroundColor; + } + + public static IEnumerable StatusColorData + { + get + { + yield return new TestCaseData(new AvailabilityTestModel + { + Approved = true, + Denied = true + }).Returns("red").SetName("Calendar_DeniedRequest"); + yield return new TestCaseData(new AvailabilityTestModel + { + Available = true, + Approved = true + }).Returns("#469c83").SetName("Calendar_AvailableRequest"); + yield return new TestCaseData(new AvailabilityTestModel + { + Approved = true + }).Returns("teal").SetName("Calendar_ApprovedRequest"); + } + } + } + + public class AvailabilityTestModel + { + public bool Available { get; set; } + public bool Denied { get; set; } + public bool Approved { get; set; } + } +} diff --git a/src/Ombi.Core/Engine/V2/CalendarEngine.cs b/src/Ombi.Core/Engine/V2/CalendarEngine.cs index b3cc49084..b160abcaf 100644 --- a/src/Ombi.Core/Engine/V2/CalendarEngine.cs +++ b/src/Ombi.Core/Engine/V2/CalendarEngine.cs @@ -3,16 +3,22 @@ using System.Collections.Generic; using System.Linq; using System.Security.Principal; using System.Threading.Tasks; +using Ombi.Api.Sonarr.Models; using Ombi.Core.Authentication; using Ombi.Core.Engine.Interfaces; using Ombi.Core.Models.Search.V2; using Ombi.Core.Rule.Interfaces; +using Ombi.Helpers; +using Ombi.Store.Entities; +using Ombi.Store.Entities.Requests; using Ombi.Store.Repository.Requests; namespace Ombi.Core.Engine.V2 { public class CalendarEngine : BaseEngine, ICalendarEngine { + public DateTime DaysAgo => DateTime.Now.AddDays(-90); + public DateTime DaysAhead => DateTime.Now.AddDays(90); public CalendarEngine(IPrincipal user, OmbiUserManager um, IRuleEvaluator rules, IMovieRequestRepository movieRepo, ITvRequestRepository tvRequestRepo) : base(user, um, rules) { @@ -27,14 +33,21 @@ namespace Ombi.Core.Engine.V2 { var viewModel = new List(); var movies = _movieRepo.GetAll().Where(x => - x.ReleaseDate > DateTime.Now.AddDays(-30) && x.ReleaseDate < DateTime.Now.AddDays(30)); - var episodes = _tvRepo.GetChild().SelectMany(x => x.SeasonRequests.SelectMany(e => e.Episodes)).ToList(); + x.ReleaseDate > DaysAgo && x.ReleaseDate < DaysAhead); + var episodes = _tvRepo.GetChild().SelectMany(x => x.SeasonRequests.SelectMany(e => e.Episodes + .Where(w => w.AirDate > DaysAgo && w.AirDate < DaysAhead))); foreach (var e in episodes) { viewModel.Add(new CalendarViewModel { Title = e.Title, - Start = e.AirDate.Date + Start = e.AirDate.Date, + Type = RequestType.TvShow, + BackgroundColor = GetBackgroundColor(e), + ExtraParams = new List + { + new ExtraParams { Overview = e.Season?.ChildRequest?.ParentRequest?.Overview ?? string.Empty, ProviderId = e.Season?.ChildRequest?.ParentRequest?.TvDbId ?? 0} + } }); } @@ -43,11 +56,72 @@ namespace Ombi.Core.Engine.V2 viewModel.Add(new CalendarViewModel { Title = m.Title, - Start = m.ReleaseDate.Date + Start = m.ReleaseDate.Date, + BackgroundColor = GetBackgroundColor(m), + Type = RequestType.Movie, + ExtraParams = new List + { + new ExtraParams { Overview = m.Overview, ProviderId = m.TheMovieDbId} + } }); } return viewModel; } + + private string GetBackgroundColor(MovieRequests req) + { + if (req.Available) + { + return "#469c83"; + } + + if (!req.Available) + { + if (req.Denied ?? false) + { + return "red"; + } + if (req.Approved) + { + // We are approved state + return "blue"; + } + + if (!req.Approved) + { + // Processing + return "teal"; + } + } + + return "gray"; + } + + private string GetBackgroundColor(EpisodeRequests req) + { + if (req.Available) + { + return "#469c83"; + } + + if (!req.Available) + { + if (req.Approved) + { + // We are approved state + return "blue"; + } + + if (!req.Approved) + { + // Processing + return "teal"; + } + + } + + return "gray"; + } } } \ No newline at end of file diff --git a/src/Ombi.Core/Models/Search/V2/CalendarViewModel.cs b/src/Ombi.Core/Models/Search/V2/CalendarViewModel.cs index a4d5a3158..3cdedce7c 100644 --- a/src/Ombi.Core/Models/Search/V2/CalendarViewModel.cs +++ b/src/Ombi.Core/Models/Search/V2/CalendarViewModel.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using Ombi.Store.Entities; namespace Ombi.Core.Models.Search.V2 { @@ -6,5 +8,32 @@ namespace Ombi.Core.Models.Search.V2 { public string Title { get; set; } public DateTime Start { get; set; } + public string BackgroundColor { get; set; } + public RequestType Type { get; set; } + public List ExtraParams { get; set; } + + public string BorderColor + { + get + { + switch (Type) + { + case RequestType.TvShow: + return "#ff0000"; + case RequestType.Movie: + return "#0d5a3e"; + case RequestType.Album: + return "#797979"; + default: + throw new ArgumentOutOfRangeException(); + } + } + } + } + + public class ExtraParams + { + public int ProviderId { get; set; } + public string Overview { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Helpers/CacheService.cs b/src/Ombi.Helpers/CacheService.cs index 4eef62bda..3e98e94ce 100644 --- a/src/Ombi.Helpers/CacheService.cs +++ b/src/Ombi.Helpers/CacheService.cs @@ -28,7 +28,7 @@ namespace Ombi.Helpers return result; } - using (await _mutex.LockAsync()) + //using (await _mutex.LockAsync()) { if (_memoryCache.TryGetValue(cacheKey, out result)) { diff --git a/src/Ombi/ClientApp/package-lock.json b/src/Ombi/ClientApp/package-lock.json index 813604eff..154a12ed0 100644 --- a/src/Ombi/ClientApp/package-lock.json +++ b/src/Ombi/ClientApp/package-lock.json @@ -2631,6 +2631,11 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "dev": true }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" + }, "copy-concurrently": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", @@ -3881,6 +3886,11 @@ "mime-types": "^2.1.12" } }, + "formidable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", + "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -4477,6 +4487,18 @@ "rimraf": "2" } }, + "fullcalendar": { + "version": "4.0.0-alpha.2", + "resolved": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-4.0.0-alpha.2.tgz", + "integrity": "sha512-2trFzbvQWHijyt+u8Zv98PPfDkFH5bU5Yoqvn2ot5PTwIkLK95xrNat5jTHfpBMwh+KqHQSnux/BcGXARYgwcw==", + "requires": { + "luxon": "^1.4.2", + "moment": "^2.22.2", + "moment-timezone": "^0.5.21", + "rrule": "^2.5.6", + "superagent": "^3.8.3" + } + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -5988,6 +6010,11 @@ "yallist": "^2.1.2" } }, + "luxon": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.12.0.tgz", + "integrity": "sha512-enPnPIHd5ZnZT0vpj9Xv8aq4j0yueAkhnh4xUKUHpqlgSm1r/8s6xTMjfyp2ugOWP7zivqJqgVTkW+rpHed61w==" + }, "magic-string": { "version": "0.25.2", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.2.tgz", @@ -6220,8 +6247,7 @@ "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "micromatch": { "version": "3.1.10", @@ -6256,9 +6282,7 @@ "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "optional": true + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { "version": "1.38.0", @@ -6416,6 +6440,14 @@ "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" }, + "moment-timezone": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.23.tgz", + "integrity": "sha512-WHFH85DkCfiNMDX5D3X7hpNH3/PUhjTGcD0U1SgfBGZxJ3qUmJh5FdvaFjcClxOvB3rzdfj4oRffbI38jEnC1w==", + "requires": { + "moment": ">= 2.9.0" + } + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -8168,6 +8200,14 @@ "inherits": "^2.0.1" } }, + "rrule": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/rrule/-/rrule-2.6.0.tgz", + "integrity": "sha512-TRigkTJtG7Y1yOjNSKvFvVmvj/PzRZLR8lLcPW9GASOlaoqoL1J0kNuUV9I3LuZc7qFT+QB2NbxSLL9d33/ylg==", + "requires": { + "luxon": "^1.3.3" + } + }, "run-async": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", @@ -9145,6 +9185,38 @@ "when": "~3.6.x" } }, + "superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "requires": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.2.0", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", diff --git a/src/Ombi/ClientApp/src/app/calendar/components/calendar.component.ts b/src/Ombi/ClientApp/src/app/calendar/components/calendar.component.ts index 722a100f9..0a183651a 100644 --- a/src/Ombi/ClientApp/src/app/calendar/components/calendar.component.ts +++ b/src/Ombi/ClientApp/src/app/calendar/components/calendar.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit } from "@angular/core"; + import { CalendarService } from "../../services/calendar.service"; import { ICalendarModel } from "../../interfaces/ICalendar"; @@ -19,31 +20,6 @@ export class CalendarComponent implements OnInit { debugger; this.loading() this.entries = await this.calendarService.getCalendarEntries(); - this.events = [ - { - "title": "All Day Event", - "start": new Date(), - "eventColor":"black" - }, - { - "title": "Long Event", - "start": "2016-01-07", - "end": "2016-01-10" - }, - { - "title": "Repeating Event", - "start": "2016-01-09T16:00:00" - }, - { - "title": "Repeating Event", - "start": "2016-01-16T16:00:00" - }, - { - "title": "Conference", - "start": "2016-01-11", - "end": "2016-01-13" - } - ]; this.options = { defaultDate: new Date(), @@ -52,6 +28,10 @@ export class CalendarComponent implements OnInit { center: 'title', right: 'month,agendaWeek' }, + eventClick: (e: any) => { + debugger; + e.preventDefault(); + } }; this.finishLoading(); } diff --git a/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html b/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html index 444ce66a4..458693977 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html +++ b/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html @@ -25,7 +25,7 @@ - +