From 9e86b92f08301cc05fbf32d04f746a5f1dc355ea Mon Sep 17 00:00:00 2001 From: James Kolpack Date: Mon, 28 Mar 2016 10:20:50 -0400 Subject: [PATCH] Submit all LeafInputFiles together --- Core.Tests/Parsers/LeafInputCsvParserTests.cs | 2 +- Core.Tests/Remote/PiscalSshClientTests.cs | 20 +++-- Core/Core.csproj | 2 +- Core/DAL/DataService.cs | 69 ++++++++-------- Core/DAL/LeafWebContext.cs | 2 +- Core/Entities/LeafInput.cs | 11 ++- Core/Entities/LeafInputFile.cs | 9 --- ...fInputFileStatus.cs => LeafInputStatus.cs} | 4 +- Core/Entities/LeafInputStatusType.cs | 8 +- Core/Entities/LeafOutputFile.cs | 2 +- Core/Remote/IPiscalClient.cs | 10 +-- Core/Remote/PiscalLeafInputFile.cs | 50 +++++++++--- Core/Remote/PiscalSshClient.cs | 54 ++++++++----- Core/Remote/PiscalUtility.cs | 10 +-- .../LeafOutput/LeafOutputViewModelTests.cs | 79 ------------------ .../ResultStatusViewModelTests.cs | 81 +++++++++++++++++++ Web.Tests/Web.Tests.csproj | 2 +- Web/Controllers/LeafCharterController.cs | 14 ++-- Web/Controllers/LeafInputController.cs | 4 +- ...ontroller.cs => ResultStatusController.cs} | 8 +- Web/Properties/PublishProfiles/LeafWeb.pubxml | 39 +++++++++ Web/Services/EmailNotificationService.cs | 18 ++--- Web/Services/PiscalQueueManager.cs | 32 ++++---- Web/Services/PiscalService.cs | 30 +++---- .../ResultStatusViewModel.cs} | 38 +++++---- Web/Views/LeafInput/Index.cshtml | 8 +- .../{LeafOutput => ResultStatus}/Index.cshtml | 6 +- Web/Views/Shared/_Layout.cshtml | 2 +- Web/Web.csproj | 7 +- 29 files changed, 353 insertions(+), 268 deletions(-) rename Core/Entities/{LeafInputFileStatus.cs => LeafInputStatus.cs} (75%) delete mode 100644 Web.Tests/ViewModels/LeafOutput/LeafOutputViewModelTests.cs create mode 100644 Web.Tests/ViewModels/ResultStatus/ResultStatusViewModelTests.cs rename Web/Controllers/{LeafOutputController.cs => ResultStatusController.cs} (53%) create mode 100644 Web/Properties/PublishProfiles/LeafWeb.pubxml rename Web/ViewModels/{LeafOutput/LeafOutputViewModel.cs => ResultStatus/ResultStatusViewModel.cs} (65%) rename Web/Views/{LeafOutput => ResultStatus}/Index.cshtml (56%) diff --git a/Core.Tests/Parsers/LeafInputCsvParserTests.cs b/Core.Tests/Parsers/LeafInputCsvParserTests.cs index fd6dcab..f82d046 100644 --- a/Core.Tests/Parsers/LeafInputCsvParserTests.cs +++ b/Core.Tests/Parsers/LeafInputCsvParserTests.cs @@ -135,7 +135,7 @@ namespace LeafWeb.Core.Tests.Parsers var input = parser.Parse(); } - [Test, Explicit] + [Explicit] public void Parse_FindIssues() { var dir = @"C:\Users\poprhythm\Documents\code\LeafWeb\Notes\leafweb database work\newcurves\RemovableDisk\curves"; diff --git a/Core.Tests/Remote/PiscalSshClientTests.cs b/Core.Tests/Remote/PiscalSshClientTests.cs index 5d7774d..724ed4e 100644 --- a/Core.Tests/Remote/PiscalSshClientTests.cs +++ b/Core.Tests/Remote/PiscalSshClientTests.cs @@ -10,13 +10,19 @@ namespace LeafWeb.Core.Tests.Remote [TestFixture] public class PiscalSshClientTests { - private readonly PiscalLeafInputFile _testInput = - new PiscalLeafInputFile + private readonly PiscalLeafInput _testInput = + new PiscalLeafInput { - Filename = "blah", LeafInputId = 1, - Contents = "test".GetBytes(), - DirectoryName = "TestDirectory2" + DirectoryName = "TestDirectory2", + PhotosyntheticType = "C4_photosynthesis_leafweb", + InputFiles = new[] + { + new PiscalLeafInputFile + { + Filename = "blah", Contents = "test".GetBytes() + } + } }; private readonly string _piscalConnectionString = @@ -26,14 +32,14 @@ namespace LeafWeb.Core.Tests.Remote public void SubmitLeafInputFile() { var client = new PiscalSshClient(_piscalConnectionString); - client.RunLeafInputFile(_testInput); + client.RunLeafInput(_testInput); } [Test] public void GetLeafInputStatus() { var client = new PiscalSshClient(_piscalConnectionString); - var leafInputStatus = client.GetLeafInputFileStatus(_testInput); + var leafInputStatus = client.GetLeafInputStatus(_testInput); Console.WriteLine(leafInputStatus); } diff --git a/Core/Core.csproj b/Core/Core.csproj index 9d83762..611281f 100644 --- a/Core/Core.csproj +++ b/Core/Core.csproj @@ -85,7 +85,7 @@ - + diff --git a/Core/DAL/DataService.cs b/Core/DAL/DataService.cs index 9f53dd5..604ccb4 100644 --- a/Core/DAL/DataService.cs +++ b/Core/DAL/DataService.cs @@ -50,16 +50,49 @@ namespace LeafWeb.Core.DAL return _db.LeafInputs.FirstOrDefault(li => li.Id == id); } + public IQueryable GetLeafInputs(LeafInputStatusType status) + { + return + from file in _db.LeafInputs + where file.CurrentStatus == status + select file; + } + public void AddLeafInput(LeafInput leafInput) { leafInput.Added = DateTime.Now; _db.LeafInputs.Add(leafInput); - foreach (var leafInputFile in leafInput.Files) - { - SetLeafInputFileStatusNoUpdate(leafInputFile, LeafInputStatusType.Queued); - } + SetLeafInputStatusNoUpdate(leafInput, LeafInputStatusType.Pending); _db.SaveChanges(); } + + private void SetLeafInputStatusNoUpdate(LeafInput leafInputFile, LeafInputStatusType status, string description = null) + { + leafInputFile.CurrentStatus = status; + var leafInputFileStatus = new LeafInputStatus + { + Status = status, + DateTime = DateTime.Now, + Description = description, + LeafInput = leafInputFile + }; + if (leafInputFile.StatusHistory == null) + leafInputFile.StatusHistory = new List(); + leafInputFile.StatusHistory.Add(leafInputFileStatus); + } + + public void SetLeafInputStatus(LeafInput leafInput, LeafInputStatusType status, string description = null) + { + SetLeafInputStatusNoUpdate(leafInput, status, description); + UpdateLeafInput(leafInput); + } + + public void UpdateLeafInput(LeafInput leafInput) + { + _db.Entry(leafInput).State = EntityState.Modified; + _db.SaveChanges(); + } + #endregion #region LeafInputFile @@ -74,40 +107,12 @@ namespace LeafWeb.Core.DAL return _db.LeafInputFiles.Find(id); } - public IQueryable GetLeafInputFiles(LeafInputStatusType status) - { - return - from file in _db.LeafInputFiles - where file.CurrentStatus == status - select file; - } - public void UpdateLeafInputFile(LeafInputFile leafInputFile) { _db.Entry(leafInputFile).State = EntityState.Modified; _db.SaveChanges(); } - private void SetLeafInputFileStatusNoUpdate(LeafInputFile leafInputFile, LeafInputStatusType status, string description = null) - { - leafInputFile.CurrentStatus = status; - var leafInputFileStatus = new LeafInputFileStatus - { - Status = status, - DateTime = DateTime.Now, - Description = description, - LeafInputFile = leafInputFile - }; - if (leafInputFile.StatusHistory == null) - leafInputFile.StatusHistory = new List(); - leafInputFile.StatusHistory.Add(leafInputFileStatus); - } - - public void SetLeafInputFileStatus(LeafInputFile leafInputFile, LeafInputStatusType status, string description = null) - { - SetLeafInputFileStatusNoUpdate(leafInputFile, status, description); - UpdateLeafInputFile(leafInputFile); - } #endregion diff --git a/Core/DAL/LeafWebContext.cs b/Core/DAL/LeafWebContext.cs index 3e943ce..acf0e8b 100644 --- a/Core/DAL/LeafWebContext.cs +++ b/Core/DAL/LeafWebContext.cs @@ -8,7 +8,7 @@ namespace LeafWeb.Core.DAL { public DbSet LeafInputs { get; set; } public DbSet LeafInputFiles { get; set; } - public DbSet LeafInputStatus { get; set; } + public DbSet LeafInputStatus { get; set; } public DbSet FluxnetSites { get; set; } public DbSet PhotosynthesisTypes { get; set; } public DbSet LeafOutputFiles { get; set; } diff --git a/Core/Entities/LeafInput.cs b/Core/Entities/LeafInput.cs index 452bf9a..2a5eb98 100644 --- a/Core/Entities/LeafInput.cs +++ b/Core/Entities/LeafInput.cs @@ -8,7 +8,11 @@ namespace LeafWeb.Core.Entities { public int Id { get; set; } - public virtual ICollection Files { get; set; } + public virtual ICollection InputFiles { get; set; } + public virtual ICollection OutputFiles { get; set; } + + public LeafInputStatusType CurrentStatus { get; set; } + public virtual ICollection StatusHistory { get; set; } [Required(ErrorMessage = "Name required")] public string Name { get; set; } @@ -28,5 +32,10 @@ namespace LeafWeb.Core.Entities [DataType(DataType.Date)] [Required] public DateTime Added { get; set; } + + public override string ToString() + { + return $"{Id}_{Identifier}"; + } } } \ No newline at end of file diff --git a/Core/Entities/LeafInputFile.cs b/Core/Entities/LeafInputFile.cs index dedbce2..42a15cb 100644 --- a/Core/Entities/LeafInputFile.cs +++ b/Core/Entities/LeafInputFile.cs @@ -7,10 +7,6 @@ namespace LeafWeb.Core.Entities public int Id { get; set; } public virtual LeafInput LeafInput { get; set; } - public virtual ICollection LeafOutputFiles { get; set; } - - public LeafInputStatusType CurrentStatus { get; set; } - public virtual ICollection StatusHistory { get; set; } /// /// Parsed values from the LeafInput used in LeafWeb for filtering/searching @@ -20,10 +16,5 @@ namespace LeafWeb.Core.Entities public string Filename { get; set; } public byte[] Contents { get; set; } - - public override string ToString() - { - return $"{Id}_{Filename}"; - } } } \ No newline at end of file diff --git a/Core/Entities/LeafInputFileStatus.cs b/Core/Entities/LeafInputStatus.cs similarity index 75% rename from Core/Entities/LeafInputFileStatus.cs rename to Core/Entities/LeafInputStatus.cs index f78e55b..a42d579 100644 --- a/Core/Entities/LeafInputFileStatus.cs +++ b/Core/Entities/LeafInputStatus.cs @@ -5,10 +5,10 @@ namespace LeafWeb.Core.Entities /// /// Status of processing LeafInput /// - public class LeafInputFileStatus + public class LeafInputStatus { public int Id { get; set; } - public virtual LeafInputFile LeafInputFile { get; set; } + public virtual LeafInput LeafInput { get; set; } public LeafInputStatusType Status { get; set; } public string Description { get; set; } public DateTime DateTime { get; set; } diff --git a/Core/Entities/LeafInputStatusType.cs b/Core/Entities/LeafInputStatusType.cs index 3a1fde5..6d648b4 100644 --- a/Core/Entities/LeafInputStatusType.cs +++ b/Core/Entities/LeafInputStatusType.cs @@ -2,9 +2,9 @@ namespace LeafWeb.Core.Entities { public enum LeafInputStatusType { - Queued, - Running, - Complete, - Error + Pending = 0, + Running = 1, + Complete = 2, + Error = 3 } } \ No newline at end of file diff --git a/Core/Entities/LeafOutputFile.cs b/Core/Entities/LeafOutputFile.cs index 0793f8c..a557a67 100644 --- a/Core/Entities/LeafOutputFile.cs +++ b/Core/Entities/LeafOutputFile.cs @@ -5,7 +5,7 @@ namespace LeafWeb.Core.Entities { public int Id { get; set; } - public virtual LeafInputFile LeafInputFile { get; set; } + public virtual LeafInput LeafInput { get; set; } public string Filename { get; set; } diff --git a/Core/Remote/IPiscalClient.cs b/Core/Remote/IPiscalClient.cs index c3bb407..76c578d 100644 --- a/Core/Remote/IPiscalClient.cs +++ b/Core/Remote/IPiscalClient.cs @@ -4,10 +4,10 @@ namespace LeafWeb.Core.Remote { public interface IPiscalClient { - void RunLeafInputFile(PiscalLeafInputFile file); - PiscalStatus GetLeafInputFileStatus(PiscalLeafInputFile file); - IEnumerable RetrieveLeafOutput(PiscalLeafInputFile file); - void CleanupLeafProcess(PiscalLeafInputFile file); - string GetErrorMessage(PiscalLeafInputFile file); + void RunLeafInput(PiscalLeafInput leafInput); + PiscalStatus GetLeafInputStatus(PiscalLeafInput leafInput); + IEnumerable RetrieveLeafOutput(PiscalLeafInput leafInput); + void CleanupLeafProcess(PiscalLeafInput leafInput); + string GetErrorMessage(PiscalLeafInput leafInput); } } diff --git a/Core/Remote/PiscalLeafInputFile.cs b/Core/Remote/PiscalLeafInputFile.cs index fa27843..8925512 100644 --- a/Core/Remote/PiscalLeafInputFile.cs +++ b/Core/Remote/PiscalLeafInputFile.cs @@ -1,18 +1,48 @@ -using AutoMapper; +using System.Linq; +using AutoMapper; using LeafWeb.Core.Entities; using LeafWeb.Core.Utility; namespace LeafWeb.Core.Remote { + public class PiscalLeafInput + { + private static readonly IMapper Mapper; + public int LeafInputId { get; set; } + public string PhotosyntheticType { get; set; } + public string DirectoryName { get; set; } + public PiscalLeafInputFile[] InputFiles { get; set; } + + static PiscalLeafInput() + { + var config = + new MapperConfiguration(cfg => + { + cfg.CreateMap() + .ForMember(dest => dest.DirectoryName, + opt => opt.MapFrom(src => PiscalUtility.GetPiscalDirectoryName(src))) + .ForMember(dest => dest.LeafInputId, opt => opt.MapFrom(src => src.Id)) + .ForMember(dest => dest.InputFiles, opt => opt.MapFrom(src => src.InputFiles.Select(f => new PiscalLeafInputFile(f)).ToArray())) + .ForMember( + dest => dest.PhotosyntheticType, + opt => opt.MapFrom(src => src.PhotosynthesisType.Id.WhitespaceToUnderscore())); + }); + Mapper = config.CreateMapper(); + } + + public PiscalLeafInput() { } + + public PiscalLeafInput(LeafInput leafInput) + { + Mapper.Map(leafInput, this); + } + } + public class PiscalLeafInputFile { private static readonly IMapper Mapper; - - public int LeafInputId { get; set; } public string Filename { get; set; } public byte[] Contents { get; set; } - public string DirectoryName { get; set; } - public string PhotosyntheticType { get; set; } static PiscalLeafInputFile() { @@ -20,14 +50,8 @@ namespace LeafWeb.Core.Remote new MapperConfiguration(cfg => { cfg.CreateMap() - .ForMember(dest => dest.DirectoryName, - opt => opt.MapFrom(src => PiscalUtility.GetPiscalDirectoryName(src))) - .ForMember(dest => dest.Filename, opt => - opt.MapFrom(src => src.Filename.WhitespaceToUnderscore().FilterValidFilename())) - .ForMember(dest => dest.LeafInputId, opt => opt.MapFrom(src => src.Id)) - .ForMember( - dest => dest.PhotosyntheticType, - opt => opt.MapFrom(src => src.LeafInput.PhotosynthesisType.Id.WhitespaceToUnderscore())); + .ForMember(dest => dest.Filename, opt => + opt.MapFrom(src => src.Filename.WhitespaceToUnderscore().FilterValidFilename())); }); Mapper = config.CreateMapper(); } diff --git a/Core/Remote/PiscalSshClient.cs b/Core/Remote/PiscalSshClient.cs index fdb6730..e655e91 100644 --- a/Core/Remote/PiscalSshClient.cs +++ b/Core/Remote/PiscalSshClient.cs @@ -39,25 +39,35 @@ namespace LeafWeb.Core.Remote return new ScpClient(_connectionInfo); } - public void RunLeafInputFile(PiscalLeafInputFile file) + private void CopyLeafInput(PiscalLeafInput leafInput, string directory) { - var inputPath = $"{BaseDirectory}/{file.DirectoryName}/{file.Filename}"; - - // copy file + // copy files using (var scp = GetScpClient()) - using (var stream = new MemoryStream(file.Contents)) + foreach (var file in leafInput.InputFiles) { - Console.WriteLine(inputPath); - scp.Connect(); - scp.Upload(stream, inputPath); - scp.Disconnect(); + var inputPath = $"{directory}/{file.Filename}"; + using (var stream = new MemoryStream(file.Contents)) + { + Console.WriteLine(inputPath); + scp.Connect(); + scp.Upload(stream, inputPath); + scp.Disconnect(); + } } + } + + public void RunLeafInput(PiscalLeafInput leafInput) + { + var inputDirectory = $"{BaseDirectory}/{leafInput.DirectoryName}"; + + CopyLeafInput(leafInput, inputDirectory); // begin processing using (var ssh = GetSshClient()) { ssh.Connect(); - var commandText = $"{RemoteScriptPath} -d {file.DirectoryName} -f {file.Filename} -p {file.PhotosyntheticType}"; + var commandText = $"{RemoteScriptPath} -d {leafInput.DirectoryName} -p {leafInput.PhotosyntheticType}"; + Console.Write(commandText); var command = ssh.CreateCommand(commandText); command.Execute(); ssh.Disconnect(); @@ -69,9 +79,9 @@ namespace LeafWeb.Core.Remote } } - public PiscalStatus GetLeafInputFileStatus(PiscalLeafInputFile file) + public PiscalStatus GetLeafInputStatus(PiscalLeafInput leafInput) { - var statusRaw = GetLeafInputStatusRaw(file); + var statusRaw = GetLeafInputStatusRaw(leafInput); switch (statusRaw[0]) { @@ -86,12 +96,12 @@ namespace LeafWeb.Core.Remote } } - private string[] GetLeafInputStatusRaw(PiscalLeafInputFile file) + private string[] GetLeafInputStatusRaw(PiscalLeafInput leafInput) { using (var ssh = GetSshClient()) { ssh.Connect(); - var commandText = $"{RemoteScriptPath} -d {file.DirectoryName} -s"; + var commandText = $"{RemoteScriptPath} -d {leafInput.DirectoryName} -s"; var command = ssh.CreateCommand(commandText); command.Execute(); ssh.Disconnect(); @@ -109,10 +119,10 @@ namespace LeafWeb.Core.Remote /// /// Gets the leaf output from piscal, only run on if result status is success /// - public IEnumerable RetrieveLeafOutput(PiscalLeafInputFile file) + public IEnumerable RetrieveLeafOutput(PiscalLeafInput leafInput) { // get output files - var status = GetLeafInputStatusRaw(file); + var status = GetLeafInputStatusRaw(leafInput); if (status[0] != StatusSuccess) throw new PiscalClientException("output not available, status is " + status[0]); @@ -131,7 +141,7 @@ namespace LeafWeb.Core.Remote { Contents = stream.ToArray(), Filename = filePath.FilenameFromPath(), - DirectoryName = file.DirectoryName + DirectoryName = leafInput.DirectoryName }; } } @@ -140,9 +150,9 @@ namespace LeafWeb.Core.Remote } } - public string GetErrorMessage(PiscalLeafInputFile file) + public string GetErrorMessage(PiscalLeafInput leafInput) { - var status = GetLeafInputStatusRaw(file); + var status = GetLeafInputStatusRaw(leafInput); if (status[0] != StatusError) return string.Empty; @@ -150,16 +160,16 @@ namespace LeafWeb.Core.Remote return errorLines.Join(Environment.NewLine); } - public void CleanupLeafProcess(PiscalLeafInputFile file) + public void CleanupLeafProcess(PiscalLeafInput leafInput) { - var status = GetLeafInputStatusRaw(file); + var status = GetLeafInputStatusRaw(leafInput); if (status[0] == StatusRunning) throw new PiscalClientException("Trying to cleanup a running process"); using (var ssh = GetSshClient()) { ssh.Connect(); - var commandText = $"{RemoteScriptPath} -d {file.DirectoryName} -c"; + var commandText = $"{RemoteScriptPath} -d {leafInput.DirectoryName} -c"; var command = ssh.CreateCommand(commandText); command.Execute(); ssh.Disconnect(); diff --git a/Core/Remote/PiscalUtility.cs b/Core/Remote/PiscalUtility.cs index fda32d0..b89586d 100644 --- a/Core/Remote/PiscalUtility.cs +++ b/Core/Remote/PiscalUtility.cs @@ -7,21 +7,21 @@ namespace LeafWeb.Core.Remote { public static class PiscalUtility { - public static string GetPiscalDirectoryName(int id, string name, string identifier) + public static string GetPiscalDirectoryName(int id, string identifier) { - return $"{id}_{name.FilterAlphaNumeric()}_{identifier.FilterAlphaNumeric()}"; + return $"{id}_{identifier.FilterAlphaNumeric()}"; } - public static string GetPiscalDirectoryName(LeafInputFile leafInputFile) + public static string GetPiscalDirectoryName(LeafInput leafInput) { - return GetPiscalDirectoryName(leafInputFile.Id, leafInputFile.LeafInput.Name, leafInputFile.LeafInput.Identifier); + return GetPiscalDirectoryName(leafInput.Id, leafInput.Identifier); } public static int GetIdFromDirectoryName(string directoryName) { var match = Regex.Match(directoryName, @"\d_"); if (!match.Success) - throw new FormatException("DirectoryName expected to be formatted {number}_{name}_{identifier}: " + directoryName); + throw new FormatException("DirectoryName expected to be formatted {number}_{identifier}: " + directoryName); return int.Parse(match.Captures[0].Value); } } diff --git a/Web.Tests/ViewModels/LeafOutput/LeafOutputViewModelTests.cs b/Web.Tests/ViewModels/LeafOutput/LeafOutputViewModelTests.cs deleted file mode 100644 index 6c3ccde..0000000 --- a/Web.Tests/ViewModels/LeafOutput/LeafOutputViewModelTests.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Linq; -using LeafWeb.Core.Entities; -using LeafWeb.Web.ViewModels.LeafOutput; -using NUnit.Framework; - -namespace LeafWeb.Web.Tests.ViewModels.LeafOutput -{ - [TestFixture] - public class LeafOutputViewModelTests - { - private LeafInputFile GetLeafInputFile() - { - return new LeafInputFile - { - Filename = "MyFilename.ext", - Id = 3, - CurrentStatus = LeafInputStatusType.Complete, - LeafInput = new LeafInput - { - Added = DateTime.Today, - Email = "test@email.com", - Identifier = "Ident I Fier", - Name = "My Name", - PhotosynthesisType = new PhotosynthesisType {Id = "1", Name = "1", SortOrder = 1} - }, - LeafOutputFiles = new[] {new LeafOutputFile {Filename = "OutputFilename.txt"}} - }; - } - - [Test] - public void CanConstructFromLeafInputFile() - { - var file = GetLeafInputFile(); - var viewModel = new LeafOutputViewModel(file); - - Assert.That(viewModel.CurrentStatus, Is.EqualTo(file.CurrentStatus.ToString())); - Assert.That(viewModel.LeafInputFileId, Is.EqualTo(file.Id)); - Assert.That(viewModel.LeafInputFilename, Is.EqualTo(file.Filename)); - Assert.That(viewModel.LeafOutputFilenames, Has.Length.EqualTo(1)); - Assert.That(viewModel.LeafInputIdentifier, Is.EqualTo(file.LeafInput.Identifier)); - Assert.That(viewModel.LeafInputSiteId, Is.EqualTo(file.LeafInput.SiteId)); - Assert.That(viewModel.LeafInputPhotosynthesisType, Is.EqualTo(file.LeafInput.PhotosynthesisType.Name)); - } - - [Test] - public void CanConstructFromLeafInputFile_Running() - { - var file = GetLeafInputFile(); - file.CurrentStatus = LeafInputStatusType.Running; - file.LeafOutputFiles = new LeafOutputFile[0]; - var viewModel = new LeafOutputViewModel(file); - - Assert.That(viewModel.CurrentStatus, Is.EqualTo(LeafInputStatusType.Running.ToString())); - Assert.That(viewModel.LeafOutputFilenames, Has.Length.EqualTo(0)); - } - - [Test] - public void CanConstructFromLeafInputFile_Error() - { - var file = GetLeafInputFile(); - file.CurrentStatus = LeafInputStatusType.Error; - file.StatusHistory = new [] - { - new LeafInputFileStatus - { - DateTime = DateTime.Today, - LeafInputFile = file, - Description = "My Error", - Status = LeafInputStatusType.Error - } - }; - var viewModel = new LeafOutputViewModel(file); - - Assert.That(viewModel.CurrentStatus, Is.EqualTo(LeafInputStatusType.Error.ToString())); - Assert.That(viewModel.ErrorMessages[0], Is.EqualTo(file.StatusHistory.First().Description)); - } - } -} diff --git a/Web.Tests/ViewModels/ResultStatus/ResultStatusViewModelTests.cs b/Web.Tests/ViewModels/ResultStatus/ResultStatusViewModelTests.cs new file mode 100644 index 0000000..62c7590 --- /dev/null +++ b/Web.Tests/ViewModels/ResultStatus/ResultStatusViewModelTests.cs @@ -0,0 +1,81 @@ +using System; +using System.Linq; +using LeafWeb.Core.Entities; +using LeafWeb.Web.ViewModels.ResultStatus; +using NUnit.Framework; + +namespace LeafWeb.Web.Tests.ViewModels.ResultStatus +{ + [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 ResultStatusViewModel(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 ResultStatusViewModel(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.Error; + leafInput.StatusHistory = new [] + { + new LeafInputStatus + { + DateTime = DateTime.Today, + LeafInput = leafInput, + Description = "My Error", + Status = LeafInputStatusType.Error + } + }; + var viewModel = new ResultStatusViewModel(leafInput); + + Assert.That(viewModel.CurrentStatus, Is.EqualTo(LeafInputStatusType.Error.ToString())); + Assert.That(viewModel.ErrorMessages[0], Is.EqualTo(leafInput.StatusHistory.First().Description)); + } + } +} diff --git a/Web.Tests/Web.Tests.csproj b/Web.Tests/Web.Tests.csproj index 7f8b08f..838b639 100644 --- a/Web.Tests/Web.Tests.csproj +++ b/Web.Tests/Web.Tests.csproj @@ -44,7 +44,7 @@ - + diff --git a/Web/Controllers/LeafCharterController.cs b/Web/Controllers/LeafCharterController.cs index ed087cb..ca6169f 100644 --- a/Web/Controllers/LeafCharterController.cs +++ b/Web/Controllers/LeafCharterController.cs @@ -14,23 +14,23 @@ namespace LeafWeb.Web.Controllers { public class LeafCharterController : ControllerBase { - public ActionResult Index(int leafInputFileId) + public ActionResult Index(int leafInputId) { var hasLeafOutputFile = DataService - .GetLeafInputFile(leafInputFileId)? - .LeafOutputFiles? + .GetLeafInput(leafInputId)? + .OutputFiles? .FirstOrDefault(f => f.IsLeafChartFile) != null; - return View(leafInputFileId); + return View(leafInputId); } - public ActionResult LeafCharts(int leafInputFileId, int number) + public ActionResult LeafCharts(int leafInputId, int number) { var leafOutputFile = DataService - .GetLeafInputFile(leafInputFileId)? - .LeafOutputFiles? + .GetLeafInput(leafInputId)? + .OutputFiles? .FirstOrDefault(f => f.IsLeafChartFile); if (leafOutputFile == null) diff --git a/Web/Controllers/LeafInputController.cs b/Web/Controllers/LeafInputController.cs index 111fe8d..0073e78 100644 --- a/Web/Controllers/LeafInputController.cs +++ b/Web/Controllers/LeafInputController.cs @@ -72,7 +72,7 @@ namespace LeafWeb.Web.Controllers // convert viewModel into Model var leafInput = viewModel.GetFileInput(DataService); // load files into LeafInputFile - leafInput.Files = + leafInput.InputFiles = (from f in files let bytes = System.IO.File.ReadAllBytes(f.FullName) select new LeafInputFile {Filename = f.Name, Contents = bytes}).ToList(); @@ -89,7 +89,7 @@ namespace LeafWeb.Web.Controllers var logger = LogManager.GetCurrentClassLogger(); logger.Info("LeafInput: {0} Added, {1}, {2}, {3}", leafInput.Id, leafInput.Identifier, leafInput.SiteId, leafInput.Email); - logger.Info("LeafInputFiles: {0}, Queued", leafInput.Files.Select(f => f.Id.ToString()).Join(", ")); + logger.Info("LeafInputFiles: {0}, Queued", leafInput.InputFiles.Select(f => f.Id.ToString()).Join(", ")); HangfireStartup.TriggerPiscalProcessQueue(); diff --git a/Web/Controllers/LeafOutputController.cs b/Web/Controllers/ResultStatusController.cs similarity index 53% rename from Web/Controllers/LeafOutputController.cs rename to Web/Controllers/ResultStatusController.cs index 13e323e..b55f1f0 100644 --- a/Web/Controllers/LeafOutputController.cs +++ b/Web/Controllers/ResultStatusController.cs @@ -1,18 +1,18 @@ using System.Linq; using System.Web.Mvc; -using LeafWeb.Web.ViewModels.LeafOutput; +using LeafWeb.Web.ViewModels.ResultStatus; namespace LeafWeb.Web.Controllers { - public class LeafOutputController : ControllerBase + public class ResultStatusController : ControllerBase { public ActionResult Index() { var viewModel = - DataService.GetLeafInputFiles() + DataService.GetLeafInputs() .OrderByDescending(f => f.Id) .ToList() - .Select(f => new LeafOutputViewModel(f)); + .Select(leafInput => new ResultStatusViewModel(leafInput)); return View(viewModel); } } diff --git a/Web/Properties/PublishProfiles/LeafWeb.pubxml b/Web/Properties/PublishProfiles/LeafWeb.pubxml new file mode 100644 index 0000000..bf16111 --- /dev/null +++ b/Web/Properties/PublishProfiles/LeafWeb.pubxml @@ -0,0 +1,39 @@ + + + + + MSDeploy + Release + Any CPU + http://leafweb.azurewebsites.net + True + False + leafweb.scm.azurewebsites.net:443 + LeafWeb + + True + WMSVC + True + $LeafWeb + <_SavePWD>True + <_DestinationType>AzureWebSite + + + + + + + + + + + + + + Server=tcp:leafweb.database.windows.net;Database=leafweb;User ID=lwadmin@leafweb;Password=j4f1a2e!;Trusted_Connection=False;Encrypt=True + + + \ No newline at end of file diff --git a/Web/Services/EmailNotificationService.cs b/Web/Services/EmailNotificationService.cs index 9c0a42b..ba0ccb1 100644 --- a/Web/Services/EmailNotificationService.cs +++ b/Web/Services/EmailNotificationService.cs @@ -31,25 +31,25 @@ namespace LeafWeb.Web.Services public EmailNotificationService() : this(new DataService()) { } - public void SendLeafWebError(int leafWebFileId, string errorMessage) + public void SendLeafWebError(int leafInputId, string errorMessage) { - var file = _dataService.GetLeafInputFile(leafWebFileId); - var body = $"Your LeafWeb analysis job, {file.LeafInput.Identifier} (with filename {file.Filename}), encountered the following errors." + Environment.NewLine + var leafInput = _dataService.GetLeafInput(leafInputId); + var body = $"Your LeafWeb analysis job, {leafInput.Identifier}, encountered the following errors." + Environment.NewLine + "You will need to correct your input and resubmit." + Environment.NewLine + Environment.NewLine + errorMessage; - var message = new MailMessage(_emaialFromAddress, file.LeafInput.Email, "LeafWeb processing error", body); + var message = new MailMessage(_emaialFromAddress, leafInput.Email, "LeafWeb processing error", body); SendMessage(message); } - public void SendLeafWebSuccess(int leafWebFileId) + public void SendLeafWebSuccess(int leafInputId) { - var file = _dataService.GetLeafInputFile(leafWebFileId); - var body = $"Your LeafWeb analysis job, {file.LeafInput.Identifier} (with filename {file.Filename}), has completed." + Environment.NewLine; - var message = new MailMessage(_emaialFromAddress, file.LeafInput.Email, "LeafWeb results", body); + var leafInput = _dataService.GetLeafInput(leafInputId); + var body = $"Your LeafWeb analysis job, {leafInput.Identifier}, has completed." + Environment.NewLine; + var message = new MailMessage(_emaialFromAddress, leafInput.Email, "LeafWeb results", body); var fileStreams = (from outputFile in - file.LeafOutputFiles + leafInput.OutputFiles select Tuple.Create(outputFile, new MemoryStream(outputFile.Contents))).ToList(); try diff --git a/Web/Services/PiscalQueueManager.cs b/Web/Services/PiscalQueueManager.cs index 9e09dd7..fd546ba 100644 --- a/Web/Services/PiscalQueueManager.cs +++ b/Web/Services/PiscalQueueManager.cs @@ -55,39 +55,39 @@ namespace LeafWeb.Web.Services private void ProcessQueue(ILogger logger) { - var runningLeafInputFiles = _dataService.GetLeafInputFiles(LeafInputStatusType.Running).ToList(); - if (runningLeafInputFiles.Any()) + var runningLeafInputs = _dataService.GetLeafInputs(LeafInputStatusType.Running).ToList(); + if (runningLeafInputs.Any()) return; - var queuedFile = + var pending = _dataService - .GetLeafInputFiles(LeafInputStatusType.Queued) + .GetLeafInputs(LeafInputStatusType.Pending) .OrderBy(l => l.StatusHistory.Min(sh => sh.DateTime)) .FirstOrDefault(); - if (queuedFile == null) + if (pending == null) return; - logger.Info("LeafInputFile: {0}, Start", queuedFile.Id); + logger.Info("LeafInputFile: {0}, Start", pending.Id); try { - _piscalService.Run(queuedFile); + _piscalService.Run(pending); } catch (PiscalClientException ex) { - logger.Error("LeafInputFile: {0}, ProcessQueue Exception: {1}", queuedFile.Id, ex.Message); - _dataService.SetLeafInputFileStatus(queuedFile, LeafInputStatusType.Error, "Error occurred submitting LeafInput"); + logger.Error("LeafInputFile: {0}, ProcessQueue Exception: {1}", pending.Id, ex.Message); + _dataService.SetLeafInputStatus(pending, LeafInputStatusType.Error, "Error occurred submitting LeafInput"); // TODO: re-queue //_dataService.SetLeafInputFileStatus(queuedFile, LeafInputStatusType.Queued, "Re-queuing LeafInput"); } - _dataService.SetLeafInputFileStatus(queuedFile, LeafInputStatusType.Running); + _dataService.SetLeafInputStatus(pending, LeafInputStatusType.Running); } private void ProcessRunning(ILogger logger) { - var runningLeafInputFiles = _dataService.GetLeafInputFiles(LeafInputStatusType.Running).ToList(); - foreach (var file in runningLeafInputFiles) + var running = _dataService.GetLeafInputs(LeafInputStatusType.Running).ToList(); + foreach (var file in running) { var status = _piscalService.GetStatus(file); try @@ -110,7 +110,7 @@ namespace LeafWeb.Web.Services string.Join(", ", leafOutputFiles.Select(o => o.Filename))); // update db - _dataService.SetLeafInputFileStatus(file, LeafInputStatusType.Complete); + _dataService.SetLeafInputStatus(file, LeafInputStatusType.Complete); BackgroundJob.Enqueue(() => _emailService.SendLeafWebSuccess(file.Id)); @@ -122,7 +122,7 @@ namespace LeafWeb.Web.Services case PiscalStatus.NotStarted: logger.Warn("LeafInputFile: {0}, Not Started, re-queueing", file.Id); // if it's not started, try to requeue the process - unusual state - _dataService.SetLeafInputFileStatus(file, LeafInputStatusType.Queued); + _dataService.SetLeafInputStatus(file, LeafInputStatusType.Pending); break; case PiscalStatus.Error: @@ -131,7 +131,7 @@ namespace LeafWeb.Web.Services var errorMessage = _piscalService.GetErrorMessage(file); logger.Info("LeafInputFile: {0}, Error Message: {1}", file.Id, errorMessage); - _dataService.SetLeafInputFileStatus(file, LeafInputStatusType.Error, errorMessage); + _dataService.SetLeafInputStatus(file, LeafInputStatusType.Error, errorMessage); BackgroundJob.Enqueue(() => _emailService.SendLeafWebError(file.Id, errorMessage)); @@ -144,7 +144,7 @@ namespace LeafWeb.Web.Services catch (PiscalClientException ex) { logger.Error("LeafInputFile: {0}, ProcessRunning Exception: {1}", file.Id, ex.Message); - _dataService.SetLeafInputFileStatus(file, LeafInputStatusType.Error, "Error occurred processing LeafInput"); + _dataService.SetLeafInputStatus(file, LeafInputStatusType.Error, "Error occurred processing LeafInput"); // TODO: re-queue } diff --git a/Web/Services/PiscalService.cs b/Web/Services/PiscalService.cs index 145262b..1a83154 100644 --- a/Web/Services/PiscalService.cs +++ b/Web/Services/PiscalService.cs @@ -18,40 +18,40 @@ namespace LeafWeb.Web.Services { } - public void Run(LeafInputFile leafInputFile) + public void Run(LeafInput leafInput) { - var inputFile = new PiscalLeafInputFile(leafInputFile); - _piscalClient.RunLeafInputFile(inputFile); + var inputFile = new PiscalLeafInput(leafInput); + _piscalClient.RunLeafInput(inputFile); } - public PiscalStatus GetStatus(LeafInputFile leafInputFile) + public PiscalStatus GetStatus(LeafInput leafInput) { - var inputFile = new PiscalLeafInputFile(leafInputFile); - return _piscalClient.GetLeafInputFileStatus(inputFile); + var inputFile = new PiscalLeafInput(leafInput); + return _piscalClient.GetLeafInputStatus(inputFile); } - public IEnumerable RetrieveOutputFiles(LeafInputFile leafInputFile) + public IEnumerable RetrieveOutputFiles(LeafInput leafInput) { - var inputFile = new PiscalLeafInputFile(leafInputFile); - var piscalLeafOutputFiles = _piscalClient.RetrieveLeafOutput(inputFile); + var input = new PiscalLeafInput(leafInput); + var piscalLeafOutputFiles = _piscalClient.RetrieveLeafOutput(input); foreach (var file in piscalLeafOutputFiles) { var leafOutputFile = file.GetLeafOutputFile(); - leafOutputFile.LeafInputFile = leafInputFile; + leafOutputFile.LeafInput = leafInput; yield return leafOutputFile; } } - public string GetErrorMessage(LeafInputFile leafInputFile) + public string GetErrorMessage(LeafInput leafInput) { - var inputFile = new PiscalLeafInputFile(leafInputFile); + var inputFile = new PiscalLeafInput(leafInput); return _piscalClient.GetErrorMessage(inputFile); } - public void Cleanup(LeafInputFile leafInputFile) + public void Cleanup(LeafInput leafInput) { - var inputFile = new PiscalLeafInputFile(leafInputFile); - _piscalClient.CleanupLeafProcess(inputFile); + var input = new PiscalLeafInput(leafInput); + _piscalClient.CleanupLeafProcess(input); } } } \ No newline at end of file diff --git a/Web/ViewModels/LeafOutput/LeafOutputViewModel.cs b/Web/ViewModels/ResultStatus/ResultStatusViewModel.cs similarity index 65% rename from Web/ViewModels/LeafOutput/LeafOutputViewModel.cs rename to Web/ViewModels/ResultStatus/ResultStatusViewModel.cs index 92eae6a..06fff69 100644 --- a/Web/ViewModels/LeafOutput/LeafOutputViewModel.cs +++ b/Web/ViewModels/ResultStatus/ResultStatusViewModel.cs @@ -2,45 +2,43 @@ using AutoMapper; using LeafWeb.Core.Entities; -namespace LeafWeb.Web.ViewModels.LeafOutput +namespace LeafWeb.Web.ViewModels.ResultStatus { - public class LeafOutputViewModel + public class ResultStatusViewModel { private static readonly IMapper Mapper; - public int LeafInputFileId { get; set; } - public string LeafInputFilename { get; set; } - public string CurrentStatus { get; set; } - public string[] ErrorMessages { get; set; } - public string[] LeafOutputFilenames { get; set; } - public bool HasLeafChartOutputFile { get; set; } + 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 string CurrentStatus { get; set; } + public string[] ErrorMessages { get; set; } + public string[] LeafOutputFilenames { get; set; } + public bool HasLeafChartOutputFile { get; set; } - static LeafOutputViewModel() + static ResultStatusViewModel() { var config = new MapperConfiguration(cfg => { - cfg.CreateMap() - .ForMember(dest => dest.LeafInputFileId, opt => opt.MapFrom(src => src.Id)) - .ForMember(dest => dest.LeafInputFilename, opt => opt.MapFrom(src => src.Filename)) + cfg.CreateMap() + .ForMember(dest => dest.LeafInputId, opt => opt.MapFrom(src => src.Id)) .ForMember(dest => dest.LeafOutputFilenames, opt => opt.ResolveUsing( - file => - file.LeafOutputFiles? + src => + src.OutputFiles? .Select(o => o.Filename) .ToArray() ?? new string[] {})) .ForMember(dest => dest.HasLeafChartOutputFile, opt => opt.ResolveUsing( - file => file.LeafOutputFiles?.Any(o => o.IsLeafChartFile))) - .ForMember(dest => dest.LeafInputName, opt => opt.MapFrom(src => src.LeafInput.Name)) - .ForMember(dest => dest.LeafInputIdentifier, opt => opt.MapFrom(src => src.LeafInput.Identifier)) - .ForMember(dest => dest.LeafInputSiteId, opt => opt.MapFrom(src => src.LeafInput.SiteId)) - .ForMember(dest => dest.LeafInputPhotosynthesisType, opt => opt.MapFrom(src => src.LeafInput.PhotosynthesisType.Name)) + 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 => @@ -53,7 +51,7 @@ namespace LeafWeb.Web.ViewModels.LeafOutput Mapper = config.CreateMapper(); } - public LeafOutputViewModel(LeafInputFile leafInput) + public ResultStatusViewModel(Core.Entities.LeafInput leafInput) { Mapper.Map(leafInput, this); } diff --git a/Web/Views/LeafInput/Index.cshtml b/Web/Views/LeafInput/Index.cshtml index 5d5844d..8d644fd 100644 --- a/Web/Views/LeafInput/Index.cshtml +++ b/Web/Views/LeafInput/Index.cshtml @@ -4,13 +4,15 @@ @Styles.Render("~/backload/blueimp/bootstrap/BasicPlusUI/css") } +@Html.Partial("_StatusMessage") +

Submitting Data and Retrieving EDO Results

- There is no limit on the number of files you may submit for analysis. Keep selecting files and hitting the Add button until all of the files you need to upload are shown in the list. Then enter an identifier for this set of data and click the Upload button. + There is no limit on the number of files you may submit for analysis. + Keep selecting files and hitting the Add button until all of the files you need to upload are shown in the list. + Then enter an identifier for this set of data and click the Upload button.

-@Html.Partial("_StatusMessage") -
@Html.Partial("_ValidationSummary") diff --git a/Web/Views/LeafOutput/Index.cshtml b/Web/Views/ResultStatus/Index.cshtml similarity index 56% rename from Web/Views/LeafOutput/Index.cshtml rename to Web/Views/ResultStatus/Index.cshtml index baabab8..2640d3d 100644 --- a/Web/Views/LeafOutput/Index.cshtml +++ b/Web/Views/ResultStatus/Index.cshtml @@ -1,4 +1,4 @@ -@model IEnumerable +@model IEnumerable @{ ViewBag.Title = "Results"; @@ -11,10 +11,8 @@ grid.Columns( grid.Column("LeafInputIdentifier", "Identifier"), grid.Column("LeafInputSiteId", "Site Id"), - grid.Column("LeafInputFilename", "Filename"), grid.Column("LeafInputName", "Submitted By"), - grid.Column("CurrentStatus", "Status"), - grid.Column("Chart", "Chart", item => @Html.ActionLink("Chart", "Index", "LeafCharter", new { leafInputFileId = item.LeafInputFileId}, new {})) + grid.Column("CurrentStatus", "Status") ), htmlAttributes: new { @class = "table table-striped table-bordered table-hover table-condensed" } ) diff --git a/Web/Views/Shared/_Layout.cshtml b/Web/Views/Shared/_Layout.cshtml index a38b24e..97d5ef6 100644 --- a/Web/Views/Shared/_Layout.cshtml +++ b/Web/Views/Shared/_Layout.cshtml @@ -31,7 +31,7 @@
  • diff --git a/Web/Web.csproj b/Web/Web.csproj index ea6e3d2..7ce0051 100644 --- a/Web/Web.csproj +++ b/Web/Web.csproj @@ -524,6 +524,7 @@ Designer + @@ -926,7 +927,7 @@ - + Global.asax @@ -941,7 +942,7 @@ - + @@ -974,7 +975,7 @@ - + Web.config