From 0711ae0ac823e3f9ec6e02b40ec10be89af1bd1b Mon Sep 17 00:00:00 2001 From: James Kolpack Date: Fri, 27 Jan 2017 14:07:36 -0500 Subject: [PATCH] Add run time to queue --- Core/Core.csproj | 1 + Core/Entities/LeafInput.cs | 71 ++++++++++++++-- Core/Utility/TimeSpanExtensions.cs | 40 ++++++++++ .../Models/ResultStatusViewModelTests.cs | 80 ------------------- WebCms.Tests/WebCms.Tests.csproj | 1 - WebCms/App_Data/Models/all.dll.path | 2 +- WebCms/App_Start/RegisterServices.cs | 3 +- WebCms/Content/style.css | 2 +- WebCms/Content/style.min.css | 2 +- WebCms/Content/style.scss | 6 +- WebCms/Controllers/LeafInputController.cs | 4 + WebCms/Controllers/QueueController.cs | 25 ++---- WebCms/Controllers/ResultsController.cs | 5 +- WebCms/Models/QueueItemViewModel.cs | 62 -------------- WebCms/Models/QueueViewModel.cs | 3 +- .../PiscalQueue/PiscalQueueManager.cs | 47 ++++++++++- WebCms/Views/Partials/MainNavigation.cshtml | 2 +- WebCms/Views/Queue/Index.cshtml | 27 ++++--- WebCms/Views/Results/Index.cshtml | 12 +-- .../LeafInputStatusViewModels.cshtml | 2 +- .../DisplayTemplates/_CancelForm.cshtml | 2 +- .../DisplayTemplates/_DeleteForm.cshtml | 2 +- WebCms/Views/Web.config | 5 +- WebCms/WebCms.csproj | 1 - 24 files changed, 198 insertions(+), 209 deletions(-) create mode 100644 Core/Utility/TimeSpanExtensions.cs delete mode 100644 WebCms.Tests/Models/ResultStatusViewModelTests.cs delete mode 100644 WebCms/Models/QueueItemViewModel.cs diff --git a/Core/Core.csproj b/Core/Core.csproj index ecb3020..91b3299 100644 --- a/Core/Core.csproj +++ b/Core/Core.csproj @@ -144,6 +144,7 @@ + diff --git a/Core/Entities/LeafInput.cs b/Core/Entities/LeafInput.cs index 38beabd..3d41ad6 100644 --- a/Core/Entities/LeafInput.cs +++ b/Core/Entities/LeafInput.cs @@ -8,15 +8,13 @@ using System.Linq; namespace LeafWeb.Core.Entities { - public class LeafInput + public abstract class LeafInputBase { - public int Id { get; set; } - - public virtual ICollection InputFiles { get; set; } public virtual ICollection OutputFiles { get; set; } public LeafOutputFile OutputErrorMessage => OutputFiles?.FirstOrDefault(f => f.IsErrorMessage); public LeafOutputFile OutputWarningMessage => OutputFiles?.FirstOrDefault(f => f.IsWarningMessage); - public virtual ICollection LeafInputData { get; set; } + public bool HasOutputFiles => OutputFiles.Any(); + public bool HasLeafChart => OutputFiles.Any(f => f.IsLeafChartFile); public LeafInputStatusType CurrentStatus { get; set; } public virtual ICollection StatusHistory { get; set; } @@ -36,12 +34,71 @@ namespace LeafWeb.Core.Entities || IsFinishing || IsCancelPending || IsCancelling; - public bool IsDeletable => !IsInProgress; - public bool HasOutputFiles => OutputFiles.Any(); + public bool IsCancellable => IsRunning || IsPending; + public bool IsDeletable => !IsInProgress; + public bool IsAtEndState => IsComplete || IsException || IsCancelled; + + + public DateTime? StartTime + { + get + { + if (IsPending) + { + return null; + } + return StatusHistory.FirstOrDefault(s => s.Status == LeafInputStatusType.Starting)?.DateTime; + } + } + + public DateTime? EndTime + { + get + { + if (!IsAtEndState) + { + return null; + } + return StatusHistory.FirstOrDefault( + s => + s.Status == LeafInputStatusType.Complete + || s.Status == LeafInputStatusType.Exception + || s.Status == LeafInputStatusType.Cancelled)?.DateTime; + } + } + + public TimeSpan TotalInProgressTime + { + get + { + var start = StartTime; + if (!start.HasValue) + { + return TimeSpan.Zero; + } + + var end = EndTime; + if (!end.HasValue) + { + return DateTime.Now - start.Value; + } + + return end.Value - start.Value; + } + } + } + + public class LeafInput : LeafInputBase + { + public int Id { get; set; } + + public virtual ICollection InputFiles { get; set; } + public virtual ICollection LeafInputData { get; set; } + [Required(ErrorMessage = "Name required")] public string Name { get; set; } diff --git a/Core/Utility/TimeSpanExtensions.cs b/Core/Utility/TimeSpanExtensions.cs new file mode 100644 index 0000000..9c67615 --- /dev/null +++ b/Core/Utility/TimeSpanExtensions.cs @@ -0,0 +1,40 @@ +using System; +using System.Runtime.Remoting.Messaging; + +namespace LeafWeb.Core.Utility +{ + public static class TimeSpanExtensions + { + public static string ToReadableString(this TimeSpan span) + { + Func pluralize = i => i > 1 ? "s" : string.Empty; + var formatted = string.Format("{0}{1}{2}{3}", + span.Duration().Days > 0 ? $"{span.Days:0} day{pluralize(span.Days)}, " : string.Empty, + span.Duration().Hours > 0 ? $"{span.Hours:0} hour{pluralize(span.Hours)}, " : string.Empty, + span.Duration().Minutes > 0 ? $"{span.Minutes:0} minute{pluralize(span.Minutes)}, " : string.Empty, + span.Duration().Seconds > 0 ? $"{span.Seconds:0} second{pluralize(span.Seconds)}" : string.Empty); + + if (formatted.EndsWith(", ")) formatted = formatted.Substring(0, formatted.Length - 2); + + if (string.IsNullOrEmpty(formatted)) formatted = "0 seconds"; + + return formatted; + } + + public static string ToRoundedReadableString(this TimeSpan span) + { + Func pluralize = i => i > 1 ? "s" : string.Empty; + Func formatTime = (i, s) => $"{i:0} {s}{pluralize(i)}"; + if (span.Duration().Days > 0) + return formatTime(span.Days, "day"); + if (span.Duration().Hours > 0) + return formatTime(span.Hours, "hour"); + if (span.Duration().Minutes > 0) + return formatTime(span.Minutes, "minute"); + if (span.Duration().Seconds > 0) + return formatTime(span.Seconds, "second"); + + return "0 seconds"; + } + } +} diff --git a/WebCms.Tests/Models/ResultStatusViewModelTests.cs b/WebCms.Tests/Models/ResultStatusViewModelTests.cs deleted file mode 100644 index 82e68d3..0000000 --- a/WebCms.Tests/Models/ResultStatusViewModelTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using LeafWeb.Core.Entities; -using LeafWeb.WebCms.Models; -using NUnit.Framework; - -namespace LeafWeb.WebCms.Tests.Models -{ - [TestFixture] - public class ResultStatusViewModelTests - { - private LeafInput GetLeafInput() - { - return new LeafInput - { - CurrentStatus = LeafInputStatusType.Complete, - OutputFiles = new[] {new LeafOutputFile {Filename = "OutputFilename.txt"}}, - Added = DateTime.Today, - Email = "test@email.com", - Identifier = "Ident I Fier", - Name = "My Name", - PhotosynthesisType = new PhotosynthesisType {Id = "1", Name = "1", SortOrder = 1}, - InputFiles = new[] - { - new LeafInputFile - { - Filename = "MyFilename.ext", - Id = 3 - } - } - }; - } - - [Test] - public void CanConstructFromLeafInputFile() - { - var leafInput = GetLeafInput(); - var viewModel = new QueueItemViewModel(leafInput); - - Assert.That(viewModel.CurrentStatus, Is.EqualTo(leafInput.CurrentStatus.ToString())); - Assert.That(viewModel.LeafInputId, Is.EqualTo(leafInput.Id)); - //Assert.That(viewModel.LeafOutputFilenames, Has.Length.EqualTo(1)); - Assert.That(viewModel.LeafInputIdentifier, Is.EqualTo(leafInput.Identifier)); - Assert.That(viewModel.LeafInputSiteId, Is.EqualTo(leafInput.SiteId)); - Assert.That(viewModel.LeafInputPhotosynthesisType, Is.EqualTo(leafInput.PhotosynthesisType.Name)); - } - - [Test] - public void CanConstructFromLeafInputFile_Running() - { - var leafInput = GetLeafInput(); - leafInput.CurrentStatus = LeafInputStatusType.Running; - leafInput.OutputFiles = new LeafOutputFile[0]; - var viewModel = new QueueItemViewModel(leafInput); - - Assert.That(viewModel.CurrentStatus, Is.EqualTo(LeafInputStatusType.Running.ToString())); - //Assert.That(viewModel.LeafOutputFilenames, Has.Length.EqualTo(0)); - } - - [Test] - public void CanConstructFromLeafInputFile_Error() - { - var leafInput = GetLeafInput(); - leafInput.CurrentStatus = LeafInputStatusType.Exception; - leafInput.StatusHistory = new [] - { - new LeafInputStatus - { - DateTime = DateTime.Today, - LeafInput = leafInput, - Description = "My Error", - Status = LeafInputStatusType.Exception - } - }; - var viewModel = new QueueItemViewModel(leafInput); - - Assert.That(viewModel.CurrentStatus, Is.EqualTo(LeafInputStatusType.Exception.ToString())); - //Assert.That(viewModel.ErrorMessages[0], Is.EqualTo(leafInput.StatusHistory.First().Description)); - } - } -} diff --git a/WebCms.Tests/WebCms.Tests.csproj b/WebCms.Tests/WebCms.Tests.csproj index 67d1213..21341f5 100644 --- a/WebCms.Tests/WebCms.Tests.csproj +++ b/WebCms.Tests/WebCms.Tests.csproj @@ -46,7 +46,6 @@ - diff --git a/WebCms/App_Data/Models/all.dll.path b/WebCms/App_Data/Models/all.dll.path index f027e12..cdffceb 100644 --- a/WebCms/App_Data/Models/all.dll.path +++ b/WebCms/App_Data/Models/all.dll.path @@ -1 +1 @@ -C:\Users\poprhythm\AppData\Local\Temp\Temporary ASP.NET Files\vs\f80e29bb\faae20bf\App_Web_all.generated.cs.8f9494c4._retrxtu.dll \ No newline at end of file +C:\Users\poprhythm\AppData\Local\Temp\Temporary ASP.NET Files\vs\f80e29bb\faae20bf\App_Web_all.generated.cs.8f9494c4.tezx-cyz.dll \ No newline at end of file diff --git a/WebCms/App_Start/RegisterServices.cs b/WebCms/App_Start/RegisterServices.cs index 0c2b66f..1679ba6 100644 --- a/WebCms/App_Start/RegisterServices.cs +++ b/WebCms/App_Start/RegisterServices.cs @@ -3,6 +3,7 @@ using System.Web.Optimization; using System.Web.Routing; using Backload.Bundles; using LeafWeb.Core.DAL; +using LeafWeb.WebCms.Models; using Umbraco.Core; namespace LeafWeb.WebCms.App_Start @@ -24,7 +25,7 @@ namespace LeafWeb.WebCms.App_Start "Results/Download", // URL with parameters new { controller = "Results", action = "Download" } // Parameter defaults ); - + base.ApplicationStarted(umbracoApplication, applicationContext); } } diff --git a/WebCms/Content/style.css b/WebCms/Content/style.css index 54a5479..315e070 100644 --- a/WebCms/Content/style.css +++ b/WebCms/Content/style.css @@ -42,7 +42,7 @@ a.banner-link:hover { text-decoration: none; background: rgba(0, 0, 0, 0.6); } a.banner-link:hover .glyphicon { - color: #a8ed4e; } + color: #a8ed4f; } .headline-icon { /* http://glyphicons.bootstrapcheatsheets.com/ */ } diff --git a/WebCms/Content/style.min.css b/WebCms/Content/style.min.css index 011b68e..5746497 100644 --- a/WebCms/Content/style.min.css +++ b/WebCms/Content/style.min.css @@ -1 +1 @@ -h1{padding:24px 0 12px 0;}p{padding:12px 0;}footer{margin-top:24px !important;}.row-no-padding [class*="col-"]{padding-left:0 !important;padding-right:0 !important;}.home .dark .row:first-child .column:first-child h1{padding-top:0;}.home .blogarchive{padding-top:20px;}.top-buffer{margin-top:20px;}.detail-actions>a,.detail-actions>form>button{margin-top:20px;float:left;clear:left;}.banner-link{white-space:normal;padding:20px;background:#000;background:rgba(0,0,0,.5);-moz-border-radius:10px;border-radius:10px;}.banner-link .glyphicon{color:#8cc641;}a.banner-link:hover{text-decoration:none;background:rgba(0,0,0,.6);}a.banner-link:hover .glyphicon{color:#a8ed4e;}.headline-icon h1:after{color:rgba(172,214,118,.8);font-family:"Glyphicons Halflings";font-size:.8em;padding-left:10px;}.headline-icon.headline-icon-file h1:after{content:"";}.headline-icon.headline-icon-leaf h1:after{content:"";}.headline-icon.headline-icon-question h1:after{content:"";}.headline-icon.headline-icon-stats h1:after{content:"";}.headline-icon.headline-icon-user h1:after{content:"";}.headline-icon.headline-icon-list h1:after{content:"";}.status{white-space:nowrap;}.status:after{font-family:"Glyphicons Halflings";font-size:.8em;padding-left:5px;}.status.status-pending{color:#f0ad4e;}.status.status-pending:after{content:"";}.status.status-complete{color:#337ab7;}.status.status-complete:after{content:"";}.status.status-exception{color:#a94442;}.status.status-exception:after{content:"";}.status.status-running,.status.status-starting,.status.status-finishing{color:#3c763d;}.status.status-running:after{content:"";}.status.status-starting:after{content:"";}.status.status-finishing:after{content:"";}.status.status-cancelpending,.status.status-cancelling{color:#f0ad4e;}.status.status-cancelled{color:#ec971f;}.status.status-cancelpending:after{content:"";}.status.status-cancelling:after{content:"";}.status.status-cancelled:after{content:"";}#chart{padding-top:20px;}.btn-file{position:relative;overflow:hidden;}.btn-file input[type=file]{position:absolute;top:0;right:0;min-width:100%;min-height:100%;font-size:100px;text-align:right;filter:alpha(opacity=0);opacity:0;outline:none;background:#fff;cursor:inherit;display:block;}form .validation-summary-errors ul{list-style-type:none;}.autocomplete-suggestions{border:1px solid #999;background:#fff;overflow:auto;}.autocomplete-suggestion{padding:2px 5px;white-space:nowrap;overflow:hidden;}.autocomplete-selected{background:#f0f0f0;}.autocomplete-suggestions strong{font-weight:normal;color:#39f;}.autocomplete-group{padding:2px 5px;}.autocomplete-group strong{display:block;border-bottom:1px solid #000;}.toggle{width:15px;}.dropdown-menu li form .btn-link{display:block;color:#333;clear:both;float:left;font-size:1rem;font-weight:normal;line-height:1.42857;min-width:160px;padding:3px 20px;text-align:left;white-space:nowrap;}.dropdown-menu li form .btn-link:focus,.dropdown-menu li form .btn-link:hover{text-decoration:none;color:#262626;background-color:#f5f5f5;}.divider-right{border-right:1px dashed #333;} \ No newline at end of file +h1{padding:24px 0 12px 0;}p{padding:12px 0;}footer{margin-top:24px !important;}.row-no-padding [class*="col-"]{padding-left:0 !important;padding-right:0 !important;}.home .dark .row:first-child .column:first-child h1{padding-top:0;}.home .blogarchive{padding-top:20px;}.top-buffer{margin-top:20px;}.detail-actions>a,.detail-actions>form>button{margin-top:20px;float:left;clear:left;}.banner-link{white-space:normal;padding:20px;background:#000;background:rgba(0,0,0,.5);-moz-border-radius:10px;border-radius:10px;}.banner-link .glyphicon{color:#8cc641;}a.banner-link:hover{text-decoration:none;background:rgba(0,0,0,.6);}a.banner-link:hover .glyphicon{color:#a8ed4f;}.headline-icon h1:after{color:rgba(172,214,118,.8);font-family:"Glyphicons Halflings";font-size:.8em;padding-left:10px;}.headline-icon.headline-icon-file h1:after{content:"";}.headline-icon.headline-icon-leaf h1:after{content:"";}.headline-icon.headline-icon-question h1:after{content:"";}.headline-icon.headline-icon-stats h1:after{content:"";}.headline-icon.headline-icon-user h1:after{content:"";}.headline-icon.headline-icon-list h1:after{content:"";}.status{white-space:nowrap;}.status:after{font-family:"Glyphicons Halflings";font-size:.8em;padding-left:5px;}.status.status-pending{color:#f0ad4e;}.status.status-pending:after{content:"";}.status.status-complete{color:#337ab7;}.status.status-complete:after{content:"";}.status.status-exception{color:#a94442;}.status.status-exception:after{content:"";}.status.status-running,.status.status-starting,.status.status-finishing{color:#3c763d;}.status.status-running:after{content:"";}.status.status-starting:after{content:"";}.status.status-finishing:after{content:"";}.status.status-cancelpending,.status.status-cancelling{color:#f0ad4e;}.status.status-cancelled{color:#ec971f;}.status.status-cancelpending:after{content:"";}.status.status-cancelling:after{content:"";}.status.status-cancelled:after{content:"";}#chart{padding-top:20px;}.btn-file{position:relative;overflow:hidden;}.btn-file input[type=file]{position:absolute;top:0;right:0;min-width:100%;min-height:100%;font-size:100px;text-align:right;filter:alpha(opacity=0);opacity:0;outline:none;background:#fff;cursor:inherit;display:block;}form .validation-summary-errors ul{list-style-type:none;}.autocomplete-suggestions{border:1px solid #999;background:#fff;overflow:auto;}.autocomplete-suggestion{padding:2px 5px;white-space:nowrap;overflow:hidden;}.autocomplete-selected{background:#f0f0f0;}.autocomplete-suggestions strong{font-weight:normal;color:#39f;}.autocomplete-group{padding:2px 5px;}.autocomplete-group strong{display:block;border-bottom:1px solid #000;}.toggle{width:15px;}.dropdown-menu li form .btn-link{display:block;color:#333;clear:both;float:left;font-size:1rem;font-weight:normal;line-height:1.42857;min-width:160px;padding:3px 20px;text-align:left;white-space:nowrap;}.dropdown-menu li form .btn-link:focus,.dropdown-menu li form .btn-link:hover{text-decoration:none;color:#262626;background-color:#f5f5f5;}.divider-right{border-right:1px dashed #333;} \ No newline at end of file diff --git a/WebCms/Content/style.scss b/WebCms/Content/style.scss index 37a7d4e..78f63b4 100644 --- a/WebCms/Content/style.scss +++ b/WebCms/Content/style.scss @@ -61,7 +61,7 @@ a.banner-link:hover { background: rgba(0, 0, 0, 0.6); .glyphicon { - color: #a8ed4e; + color: #a8ed4f; } } @@ -126,7 +126,7 @@ a.banner-link:hover { } } - &.status-running,&.status-starting,&.status-finishing { + &.status-running, &.status-starting, &.status-finishing { color: #3c763d; } &.status-running:after { @@ -139,7 +139,7 @@ a.banner-link:hover { content:"\e027"; } - &.status-cancelpending,&.status-cancelling { + &.status-cancelpending, &.status-cancelling { color: #f0ad4e; } &.status-cancelled { diff --git a/WebCms/Controllers/LeafInputController.cs b/WebCms/Controllers/LeafInputController.cs index c27024e..37c0f30 100644 --- a/WebCms/Controllers/LeafInputController.cs +++ b/WebCms/Controllers/LeafInputController.cs @@ -33,6 +33,10 @@ namespace LeafWeb.WebCms.Controllers if (!files.Any()) ModelState.AddModelError("Files", "Must select at least one file"); + // TODO: this keeps randomly not being mapable because string->bool binding fails. WHY + if (ModelState.ContainsKey("TermsOfService")) + ModelState["TermsOfService"].Errors.Clear(); + if (ModelState.IsValid) // HttpParamMatch indicates it's backing out from Confirm { // convert viewModel into Model diff --git a/WebCms/Controllers/QueueController.cs b/WebCms/Controllers/QueueController.cs index a6afe9e..8ddb862 100644 --- a/WebCms/Controllers/QueueController.cs +++ b/WebCms/Controllers/QueueController.cs @@ -2,25 +2,23 @@ using System.Linq; using System.Web.Mvc; using Hangfire; -using log4net; using LeafWeb.Core.Entities; using LeafWeb.Core.Utility; -using LeafWeb.WebCms.App_Start; using LeafWeb.WebCms.Models; using LeafWeb.WebCms.Services; using LeafWeb.WebCms.Services.PiscalQueue; +using Umbraco.Web.Mvc; namespace LeafWeb.WebCms.Controllers { + [MemberAuthorize] public class QueueController : BaseController { public ActionResult Index() { var resultItems = DataService.GetLeafInputs() - .OrderByDescending(f => f.Id) - .ToList() - .Select(leafInput => new QueueItemViewModel(leafInput)); + .OrderByDescending(f => f.Id); string serviceDescription; try @@ -134,27 +132,16 @@ namespace LeafWeb.WebCms.Controllers return RedirectToUmbracoPage(LeafWebPageIds.ManageQueue); } - if (leafInput.IsPending) - { - LogManager.GetLogger(LoggerName(RouteData)).DebugFormat("LeafInput: {0}, Set Cancelled from Pending", leafInput.Id); - DataService.SetLeafInputStatus(leafInput, LeafInputStatusType.Cancelled, - "Emailing cancellation notification to user", - $"Email: \'{leafInput.Email}\'"); + var cancelPendingSuccess = new PiscalQueueManager().CancelPending(id); - // send notification immediately - BackgroundJob.Enqueue(email => email.SendLeafWebCancelled(leafInput.Id)); - SetStatusMessage($"Cancelling LeafInput '{leafInput.Identifier}'", StatusType.Success); - } - else if (leafInput.IsRunning) + if (cancelPendingSuccess) { - DataService.SetLeafInputStatus(leafInput, LeafInputStatusType.CancelPending); SetStatusMessage($"Cancelling LeafInput '{leafInput.Identifier}'", StatusType.Success); - HangfireStartup.TriggerPiscalProcessQueue(); } else { // don't allow to be cancelled if it isn't currently running - SetStatusMessage($"LeafInput '{leafInput.Identifier}' is not currently running!", StatusType.Error); + SetStatusMessage($"Couldn't cancel '{leafInput.Identifier}', is it currently running?", StatusType.Error); } return RedirectToCurrentUmbracoUrl(); } diff --git a/WebCms/Controllers/ResultsController.cs b/WebCms/Controllers/ResultsController.cs index dcd3314..fcfb7d4 100644 --- a/WebCms/Controllers/ResultsController.cs +++ b/WebCms/Controllers/ResultsController.cs @@ -12,14 +12,13 @@ namespace LeafWeb.WebCms.Controllers public ActionResult Index() { var dateThreshold = DateTime.Today.Subtract(TimeSpan.FromDays(90)); - var viewModel = + var viewModel = ( from li in DataService.GetLeafInputs() where li.Added >= dateThreshold orderby li.Id descending select li - ).ToList() - .Select(leafInput => new QueueItemViewModel(leafInput)); + ); return View(viewModel); } diff --git a/WebCms/Models/QueueItemViewModel.cs b/WebCms/Models/QueueItemViewModel.cs deleted file mode 100644 index 6c29f50..0000000 --- a/WebCms/Models/QueueItemViewModel.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Linq; -using AutoMapper; -using LeafWeb.Core.Entities; - -namespace LeafWeb.WebCms.Models -{ - public class QueueItemViewModel - { - public int LeafInputId { get; set; } - public string LeafInputName { get; set; } - public string LeafInputIdentifier { get; set; } - public string LeafInputSiteId { get; set; } - public string LeafInputPhotosynthesisType { get; set; } - public bool HasLeafChart { get; set; } - public bool IsRunning { get; set; } - public bool IsComplete { get; set; } - public bool IsDeletable { get; set; } - public bool IsCancellable { get; set; } - public bool IsPending { get; set; } - public bool HasOutputFiles { get; set; } - public string CurrentStatus { get; set; } - - //public string[] ErrorMessages { get; set; } - //public string[] LeafOutputFilenames { get; set; } - //public bool HasLeafChartOutputFile { get; set; } - - static QueueItemViewModel() - { - Mapper.CreateMap() - .ForMember(dest => dest.LeafInputId, opt => opt.MapFrom(src => src.Id)) - .ForMember(dest => dest.HasLeafChart, opt => opt.ResolveUsing(src => src.OutputFiles.Any(o => o.IsLeafChartFile))) - //.ForMember(dest => dest.LeafOutputFilenames, - // opt => opt.ResolveUsing( - // src => - // src.OutputFiles? - // .Select(o => o.Filename) - // .ToArray() - // ?? new string[] {})) - //.ForMember(dest => dest.HasLeafChartOutputFile, - // opt => opt.ResolveUsing( - // file => file.OutputFiles?.Any(o => o.IsLeafChartFile))) - .ForMember(dest => dest.LeafInputName, opt => opt.MapFrom(src => src.Name)) - .ForMember(dest => dest.LeafInputIdentifier, opt => opt.MapFrom(src => src.Identifier)) - .ForMember(dest => dest.LeafInputSiteId, opt => opt.MapFrom(src => src.SiteId)) - .ForMember(dest => dest.LeafInputPhotosynthesisType, opt => opt.MapFrom(src => src.PhotosynthesisType.Name)) - //.ForMember(dest => dest.ErrorMessages, - // opt => opt.ResolveUsing( - // src => - // src.StatusHistory? - // .Where(sh => sh.Status == LeafInputStatusType.Exception) - // .Select(sh => sh.Description) - // .ToArray() - // ?? new string[] {})) - ; - } - - public QueueItemViewModel(LeafInput leafInput) - { - Mapper.Map(leafInput, this); - } - } -} \ No newline at end of file diff --git a/WebCms/Models/QueueViewModel.cs b/WebCms/Models/QueueViewModel.cs index 5a2586a..7d053b5 100644 --- a/WebCms/Models/QueueViewModel.cs +++ b/WebCms/Models/QueueViewModel.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using LeafWeb.Core.Entities; namespace LeafWeb.WebCms.Models { @@ -6,6 +7,6 @@ namespace LeafWeb.WebCms.Models { public string ServerDescription { get; set; } public string ServerStatus { get; set; } - public IEnumerable Items { get; set; } + public IEnumerable Items { get; set; } } } \ No newline at end of file diff --git a/WebCms/Services/PiscalQueue/PiscalQueueManager.cs b/WebCms/Services/PiscalQueue/PiscalQueueManager.cs index 22626ae..259440d 100644 --- a/WebCms/Services/PiscalQueue/PiscalQueueManager.cs +++ b/WebCms/Services/PiscalQueue/PiscalQueueManager.cs @@ -3,17 +3,18 @@ using System.Linq; using System.Threading; using LeafWeb.Core.Entities; using LeafWeb.Core.Remote; +using LeafWeb.WebCms.App_Start; namespace LeafWeb.WebCms.Services.PiscalQueue { public class PiscalQueueManager : PiscalQueueBase { - private static readonly object ProcessQueueLock = new object(); + private static readonly object Lock = new object(); public void ProcessQueue() { // prevent multiple entry into processing the queue - if (Monitor.TryEnter(ProcessQueueLock)) + if (Monitor.TryEnter(Lock)) { Logger.DebugFormat("ProcessQueue entered"); @@ -31,7 +32,7 @@ namespace LeafWeb.WebCms.Services.PiscalQueue { Logger.DebugFormat("ProcessQueue exit"); - Monitor.Exit(ProcessQueueLock); + Monitor.Exit(Lock); } } else @@ -40,6 +41,46 @@ namespace LeafWeb.WebCms.Services.PiscalQueue } } + public bool CancelPending(int leafInputId) + { + if (Monitor.TryEnter(Lock, TimeSpan.FromSeconds(1))) + { + Logger.DebugFormat("CancelLeafInput entered"); + + try + { + var leafInput = DataService.GetLeafInput(leafInputId); + if (!leafInput.IsCancellable) + return false; + if (leafInput.IsPending) + { + Logger.DebugFormat("LeafInput: {0}, Set Cancelled from Pending", leafInput.Id); + DataService.SetLeafInputStatus(leafInput, LeafInputStatusType.Cancelled, + "Emailing cancellation notification to user", + $"Email: \'{leafInput.Email}\'"); + + // send notification immediately + BackgroundJobEnqueueRetry(email => email.SendLeafWebCancelled(leafInput.Id)); + } + else if (leafInput.IsRunning) + { + DataService.SetLeafInputStatus(leafInput, LeafInputStatusType.CancelPending); + HangfireStartup.TriggerPiscalProcessQueue(); + } + return true; + } + finally + { + Logger.DebugFormat("CancelLeafInput exit"); + + Monitor.Exit(Lock); + } + } + + Logger.DebugFormat("CancelLeafInput locked"); + return false; + } + private void StartCancelPending() { var cancelPendingLeafInputs = diff --git a/WebCms/Views/Partials/MainNavigation.cshtml b/WebCms/Views/Partials/MainNavigation.cshtml index 20d0257..df20902 100644 --- a/WebCms/Views/Partials/MainNavigation.cshtml +++ b/WebCms/Views/Partials/MainNavigation.cshtml @@ -55,7 +55,7 @@ @page.Name @if (library.IsProtected(page.id, page.path)) { - + } diff --git a/WebCms/Views/Queue/Index.cshtml b/WebCms/Views/Queue/Index.cshtml index c1a5144..fad3c1d 100644 --- a/WebCms/Views/Queue/Index.cshtml +++ b/WebCms/Views/Queue/Index.cshtml @@ -9,13 +9,14 @@
@Model.ServerDescription
- @grid.Table(columns: grid.Columns( - grid.Column("LeafInputIdentifier", "Identifier"), - grid.Column("LeafInputSiteId", "Site Id"), - grid.Column("LeafInputName", "Submitted By"), - grid.Column("CurrentStatus", "Status", item => Html.Partial("DisplayTemplates/_LeafInputStatus", (string)item.CurrentStatus)), + grid.Column("Identifier", "Identifier"), + grid.Column("SiteId", "Site Id"), + grid.Column("Name", "Submitted By"), + //grid.Column("FileCount", "Input files"), + grid.Column("TotalInProgressTime", "In-Progress Time", item => ((TimeSpan)item.TotalInProgressTime).ToRoundedReadableString()), + grid.Column("CurrentStatus", "Status", item => Html.Partial("DisplayTemplates/_LeafInputStatus", (string)item.CurrentStatus.ToString())), grid.Column("Total Results: " + Model.Items.Count(), format: item => Btns(item))), htmlAttributes: new { @class = "table table-striped table-bordered table-hover table-condensed" } ) @@ -54,47 +55,47 @@ @helper DetailsLink(dynamic item) { -@Html.Partial("DisplayTemplates/_DetailsLink", (int)item.LeafInputId) +@Html.Partial("DisplayTemplates/_DetailsLink", (int)item.Id) } @helper ChartLink(dynamic item) { -@Html.Partial("DisplayTemplates/_ChartLink", (int)item.LeafInputId) +@Html.Partial("DisplayTemplates/_ChartLink", (int)item.Id) } @helper SetPriorityHigh(dynamic item, string label) { - + @label } @helper SetPriorityLow(dynamic item, string label) { - + @label } @helper DownloadInput(dynamic item) { - + Input } @helper DownloadOutputToUser(dynamic item) { - + ToUser } @helper DeleteLink(dynamic item) { -@Html.Partial("DisplayTemplates/_DeleteForm", (Tuple)Tuple.Create(item.LeafInputId, item.LeafInputIdentifier, item.IsDeletable)) +@Html.Partial("DisplayTemplates/_DeleteForm", (Tuple)Tuple.Create(item.Id, item.Identifier, item.IsDeletable)) } @helper CancelLink(dynamic item) { -@Html.Partial("DisplayTemplates/_CancelForm", (Tuple)Tuple.Create(item.LeafInputId, item.LeafInputIdentifier)) +@Html.Partial("DisplayTemplates/_CancelForm", (Tuple)Tuple.Create(item.Id, item.Identifier)) } @helper DisableItem(bool disabled) diff --git a/WebCms/Views/Results/Index.cshtml b/WebCms/Views/Results/Index.cshtml index bec63d0..a15b83e 100644 --- a/WebCms/Views/Results/Index.cshtml +++ b/WebCms/Views/Results/Index.cshtml @@ -1,4 +1,4 @@ -@model IEnumerable +@model IOrderedQueryable @{ var grid = new WebGrid(Model, rowsPerPage: 45); @@ -6,10 +6,10 @@ @grid.Table(columns: grid.Columns( - grid.Column("LeafInputIdentifier", "Identifier"), - grid.Column("LeafInputSiteId", "Site Id"), - //grid.Column("LeafInputName", "Submitted By"), - grid.Column("CurrentStatus", "Status", item => Html.Partial("DisplayTemplates/_LeafInputStatus", (string)item.CurrentStatus)), + grid.Column("Identifier", "Identifier"), + grid.Column("SiteId", "Site Id"), + //grid.Column("Name", "Submitted By"), + grid.Column("CurrentStatus", "Status", item => Html.Partial("DisplayTemplates/_LeafInputStatus", (string)item.CurrentStatus.ToString())), grid.Column("", "", item => ChartLink(item)) ), htmlAttributes: new { @class = "table table-striped table-bordered table-hover table-condensed" } @@ -18,5 +18,5 @@ @helper ChartLink(dynamic item) { - @Html.Partial("DisplayTemplates/_ChartButton", (int)item.LeafInputId, new ViewDataDictionary { { "Disabled", !item.HasLeafChart }, {"xs", true} }) + @Html.Partial("DisplayTemplates/_ChartButton", (int)item.Id, new ViewDataDictionary { { "Disabled", !item.HasLeafChart }, {"xs", true} }) } \ No newline at end of file diff --git a/WebCms/Views/Shared/DisplayTemplates/LeafInputStatusViewModels.cshtml b/WebCms/Views/Shared/DisplayTemplates/LeafInputStatusViewModels.cshtml index c002032..b6932f6 100644 --- a/WebCms/Views/Shared/DisplayTemplates/LeafInputStatusViewModels.cshtml +++ b/WebCms/Views/Shared/DisplayTemplates/LeafInputStatusViewModels.cshtml @@ -1,4 +1,4 @@ -@model IEnumerable +@model IEnumerable @{ Layout = "~/Views/Shared/DisplayTemplates/_FieldLayout.cshtml"; var grid = new WebGrid(Model, rowsPerPage: 45) diff --git a/WebCms/Views/Shared/DisplayTemplates/_CancelForm.cshtml b/WebCms/Views/Shared/DisplayTemplates/_CancelForm.cshtml index dcafe63..e40396e 100644 --- a/WebCms/Views/Shared/DisplayTemplates/_CancelForm.cshtml +++ b/WebCms/Views/Shared/DisplayTemplates/_CancelForm.cshtml @@ -5,7 +5,7 @@ var identifier = Model.Item2; } @using (Html.BeginUmbracoForm("Cancel", null, - new { @class = "confirm", confirm_msg = "Cancelling cannot be undone! Confirm cancelling '" + identifier + "'." })) + new { @class = "confirm clearfix", confirm_msg = "Cancelling cannot be undone! Confirm cancelling '" + identifier + "'." })) {