From db901470bf9292fe05756606d135828308e50b61 Mon Sep 17 00:00:00 2001 From: David May-Miller Date: Fri, 18 Jun 2021 11:17:53 +0100 Subject: [PATCH 01/16] HEEDLS-374 Set up basic form for editing centre details --- .../CentreConfigurationController.cs | 14 +++ .../EditCentreDetailsViewModel.cs | 38 ++++++++ .../EditCentreDetails.cshtml | 88 +++++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CentreConfiguration/EditCentreDetailsViewModel.cs create mode 100644 DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs index 60302a3ca3..c7d5ef6d9b 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs @@ -1,6 +1,7 @@ namespace DigitalLearningSolutions.Web.Controllers.TrackingSystem.CentreConfiguration { using DigitalLearningSolutions.Data.DataServices; + using DigitalLearningSolutions.Data.Services; using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.Helpers.ExternalApis; using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.CentreConfiguration; @@ -131,5 +132,18 @@ public IActionResult EditCentreWebsiteDetails(EditCentreWebsiteDetailsViewModel return RedirectToAction("Index"); } + + [HttpGet] + [Route("EditCentreDetails")] + public IActionResult EditCentreDetails() + { + var centreId = User.GetCentreId(); + + var centreDetails = centresDataService.GetCentreDetailsById(centreId); + + var model = new EditCentreDetailsViewModel(centreDetails); + + return View(model); + } } } diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CentreConfiguration/EditCentreDetailsViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CentreConfiguration/EditCentreDetailsViewModel.cs new file mode 100644 index 0000000000..da3045c15c --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CentreConfiguration/EditCentreDetailsViewModel.cs @@ -0,0 +1,38 @@ +namespace DigitalLearningSolutions.Web.ViewModels.TrackingSystem.CentreConfiguration +{ + using System.ComponentModel.DataAnnotations; + using DigitalLearningSolutions.Data.Models; + using DigitalLearningSolutions.Web.Attributes; + using Microsoft.AspNetCore.Http; + + public class EditCentreDetailsViewModel + { + public EditCentreDetailsViewModel(Centre centre) + { + NotifyEmail = centre.NotifyEmail; + BannerText = centre.BannerText; + CentreSignature = centre.SignatureImage; + CentreLogo = centre.CentreLogo; + } + + [Required(ErrorMessage = "Enter an email address")] + [MaxLength(250, ErrorMessage = "Email address must be 250 characters or fewer")] + [EmailAddress(ErrorMessage = "Enter an email address in the correct format, like name@example.com")] + [NoWhitespace("Email address must not contain any whitespace characters")] + public string? NotifyEmail { get; set; } + + [Required(ErrorMessage = "Enter the centre support details")] + [MaxLength(250, ErrorMessage = "Centre support details must be 250 characters or fewer")] + public string? BannerText { get; set; } + + public byte[]? CentreSignature { get; set; } + + [AllowedExtensions(new []{".png",".tiff",".jpg",".jpeg",".bmp",".gif"})] + public IFormFile? CentreSignatureFile { get; set; } + + public byte[]? CentreLogo { get; set; } + + [AllowedExtensions(new []{".png",".tiff",".jpg",".jpeg",".bmp",".gif"})] + public IFormFile? CentreLogoFile { get; set; } + } +} diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml new file mode 100644 index 0000000000..9952dc6afa --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml @@ -0,0 +1,88 @@ +@inject IConfiguration Configuration +@using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.CentreConfiguration +@using Microsoft.Extensions.Configuration +@model EditCentreDetailsViewModel + +@{ + var errorHasOccurred = !ViewData.ModelState.IsValid; + ViewData["Title"] = errorHasOccurred ? "Error: Edit Centre Details" : "Edit Centre Details"; + ViewData["Application"] = "Tracking System"; + ViewData["HeaderPath"] = $"{Configuration["AppRootPath"]}/TrackingSystem/Centre/Dashboard"; + ViewData["HeaderPathName"] = "Tracking System"; +} + +@section NavMenuItems { + +} + +
+
+ @if (errorHasOccurred) { + + } + +

Edit centre details

+
+ + + + +
+
+ + +
+
+ @if (Model.CentreSignature != null) { + Centre signature + } else { + Default centre signature + } +
+
+ + +
+
+ +
+
+ + +
+
+ @if (Model.CentreLogo != null) { + Centre logo + } else { + Default centre logo + } +
+
+ + +
+
+ + + +
+
+ +
+
+
+
From adda2299e8d868328e5804d94f11606cde925b9b Mon Sep 17 00:00:00 2001 From: David May-Miller Date: Fri, 18 Jun 2021 15:24:06 +0100 Subject: [PATCH 02/16] HEEDLS-374 Enabled saving centre details --- .../DataServices/CentresDataService.cs | 34 +++++ .../CentreConfigurationController.cs | 123 +++++++++++++++++- .../EditCentreDetailsViewModel.cs | 2 + .../EditCentreDetails.cshtml | 75 ++++++----- 4 files changed, 204 insertions(+), 30 deletions(-) diff --git a/DigitalLearningSolutions.Data/DataServices/CentresDataService.cs b/DigitalLearningSolutions.Data/DataServices/CentresDataService.cs index 403b3e5940..1e372fed2b 100644 --- a/DigitalLearningSolutions.Data/DataServices/CentresDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/CentresDataService.cs @@ -39,6 +39,14 @@ void UpdateCentreWebsiteDetails string? otherInformation ); + void UpdateCentreDetails( + int centreId, + string notifyEmail, + string bannerText, + byte[]? centreSignature, + byte[]? centreLogo + ); + (string firstName, string lastName, string email) GetCentreManagerDetails(int centreId); string[] GetCentreIpPrefixes(int centreId); } @@ -221,6 +229,32 @@ public void UpdateCentreWebsiteDetails ); } + public void UpdateCentreDetails( + int centreId, + string notifyEmail, + string bannerText, + byte[]? centreSignature, + byte[]? centreLogo + ) + { + connection.Execute( + @"UPDATE Centres SET + NotifyEmail = @notifyEmail, + BannerText = @bannerText, + SignatureImage = @centreSignature, + CentreLogo = @centreLogo + WHERE CentreId = @centreId", + new + { + notifyEmail, + bannerText, + centreSignature, + centreLogo, + centreId + } + ); + } + public (string firstName, string lastName, string email) GetCentreManagerDetails(int centreId) { var info = connection.QueryFirstOrDefault<(string, string, string)> diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs index c7d5ef6d9b..370c050b10 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs @@ -1,5 +1,6 @@ namespace DigitalLearningSolutions.Web.Controllers.TrackingSystem.CentreConfiguration { + using System.Linq; using DigitalLearningSolutions.Data.DataServices; using DigitalLearningSolutions.Data.Services; using DigitalLearningSolutions.Web.Helpers; @@ -8,6 +9,7 @@ namespace DigitalLearningSolutions.Web.Controllers.TrackingSystem.CentreConfigur using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; + using Microsoft.AspNetCore.Mvc.ModelBinding; [Authorize(Policy = CustomPolicies.UserCentreAdmin)] [Route("/TrackingSystem/CentreConfiguration")] @@ -16,16 +18,19 @@ public class CentreConfigurationController : Controller private readonly ICentresDataService centresDataService; private readonly ILogger logger; private readonly IMapsApiHelper mapsApiHelper; + private readonly IImageResizeService imageResizeService; public CentreConfigurationController( ICentresDataService centresDataService, IMapsApiHelper mapsApiHelper, - ILogger logger + ILogger logger, + IImageResizeService imageResizeService ) { this.centresDataService = centresDataService; this.mapsApiHelper = mapsApiHelper; this.logger = logger; + this.imageResizeService = imageResizeService; } public IActionResult Index() @@ -145,5 +150,121 @@ public IActionResult EditCentreDetails() return View(model); } + + [HttpPost] + [Route("EditCentreDetails")] + public IActionResult EditCentreDetails(EditCentreDetailsViewModel model, string action) + { + return action switch + { + "save" => EditCentreDetailsPostSave(model), + "previewSignature" => EditCentreDetailsPostPreviewSignature(model), + "removeSignature" => EditCentreDetailsPostRemoveSignature(model), + "previewLogo" => EditCentreDetailsPostPreviewLogo(model), + "removeLogo" => EditCentreDetailsPostRemoveLogo(model), + _ => RedirectToAction("Error", "LearningSolutions") + }; + } + + private IActionResult EditCentreDetailsPostSave(EditCentreDetailsViewModel model) + { + + if (model.CentreSignatureFile != null) + { + ModelState.AddModelError(nameof(EditCentreDetailsViewModel.CentreSignatureFile), + "Preview your new centre signature before saving"); + } + + if (model.CentreLogoFile != null) + { + ModelState.AddModelError(nameof(EditCentreDetailsViewModel.CentreLogoFile), + "Preview your new centre logo before saving"); + } + + if (!ModelState.IsValid) + { + return View(model); + } + + var centreId = User.GetCentreId(); + + centresDataService.UpdateCentreDetails( + centreId, + model.NotifyEmail!, + model.BannerText!, + model.CentreSignature, + model.CentreLogo + ); + + return RedirectToAction("Index"); + } + + private IActionResult EditCentreDetailsPostPreviewSignature(EditCentreDetailsViewModel model) + { + ClearErrorsForAllFieldsExcept(nameof(EditCentreDetailsViewModel.CentreSignatureFile)); + + if (!ModelState.IsValid) + { + return View(model); + } + + if (model.CentreSignatureFile != null) + { + ModelState.Remove(nameof(EditCentreDetailsViewModel.CentreSignature)); + model.CentreSignature = imageResizeService.ResizeProfilePicture(model.CentreSignatureFile); + } + + return View(model); + } + + private IActionResult EditCentreDetailsPostRemoveSignature(EditCentreDetailsViewModel model) + { + ClearErrorsForAllFields(); + + ModelState.Remove(nameof(EditCentreDetailsViewModel.CentreSignature)); + model.CentreSignature = null; + return View(model); + } + + private IActionResult EditCentreDetailsPostPreviewLogo(EditCentreDetailsViewModel model) + { + ClearErrorsForAllFieldsExcept(nameof(EditCentreDetailsViewModel.CentreLogoFile)); + + if (!ModelState.IsValid) + { + return View(model); + } + + if (model.CentreLogoFile != null) + { + ModelState.Remove(nameof(EditCentreDetailsViewModel.CentreLogo)); + model.CentreLogo = imageResizeService.ResizeProfilePicture(model.CentreLogoFile); + } + + return View(model); + } + + private IActionResult EditCentreDetailsPostRemoveLogo(EditCentreDetailsViewModel model) + { + ClearErrorsForAllFields(); + + ModelState.Remove(nameof(EditCentreDetailsViewModel.CentreLogo)); + model.CentreLogo = null; + return View(model); + } + + private void ClearErrorsForAllFields() + { + ClearErrorsForAllFieldsExcept(null); + } + + private void ClearErrorsForAllFieldsExcept(string? fieldName) + { + foreach (var key in ModelState.Keys.Where(k => k != fieldName)) + { + ModelState[key].Errors.Clear(); + ModelState[key].ValidationState = ModelValidationState.Valid; + } + } } } diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CentreConfiguration/EditCentreDetailsViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CentreConfiguration/EditCentreDetailsViewModel.cs index da3045c15c..50a87ca6ed 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CentreConfiguration/EditCentreDetailsViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CentreConfiguration/EditCentreDetailsViewModel.cs @@ -7,6 +7,8 @@ public class EditCentreDetailsViewModel { + public EditCentreDetailsViewModel() { } + public EditCentreDetailsViewModel(Centre centre) { NotifyEmail = centre.NotifyEmail; diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml index 9952dc6afa..cead2add40 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml @@ -3,12 +3,17 @@ @using Microsoft.Extensions.Configuration @model EditCentreDetailsViewModel + + @{ var errorHasOccurred = !ViewData.ModelState.IsValid; ViewData["Title"] = errorHasOccurred ? "Error: Edit Centre Details" : "Edit Centre Details"; ViewData["Application"] = "Tracking System"; ViewData["HeaderPath"] = $"{Configuration["AppRootPath"]}/TrackingSystem/Centre/Dashboard"; ViewData["HeaderPathName"] = "Tracking System"; + var hintText = @"To change your {0}, select a new image and click the Preview button to preview it. + To remove your {0} click the remove button. + Changes will not be made until the Save button below is clicked."; } @section NavMenuItems { @@ -22,37 +27,46 @@ }

Edit centre details

-
- + +
+ +
+ +
+ - + +
-
- - +
+ +
-
+
@if (Model.CentreSignature != null) { - Centre signature + Centre Signature Picture } else { - Default centre signature + Placeholder signature image }
+
+ +
@@ -60,17 +74,20 @@
-
- - +
+ +
-
+
@if (Model.CentreLogo != null) { - Centre logo + Centre Logo Picture } else { - Default centre logo + Placeholder logo image }
+
+ +
From 6fc3d7d5e6cb6290dbc26166487d0055d94fd722 Mon Sep 17 00:00:00 2001 From: David May-Miller Date: Fri, 18 Jun 2021 16:53:53 +0100 Subject: [PATCH 03/16] HEEDLS-374 Added resize methods for centre images --- .../Services/ImageResizeService.cs | 42 ++++++++++++++++++- .../CentreConfigurationController.cs | 4 +- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/DigitalLearningSolutions.Data/Services/ImageResizeService.cs b/DigitalLearningSolutions.Data/Services/ImageResizeService.cs index 7df2d49595..ede9d6e177 100644 --- a/DigitalLearningSolutions.Data/Services/ImageResizeService.cs +++ b/DigitalLearningSolutions.Data/Services/ImageResizeService.cs @@ -10,6 +10,8 @@ public interface IImageResizeService { public byte[] ResizeProfilePicture(IFormFile formProfileImage); + + public byte[] ResizeCentreImage(IFormFile formCentreImage); } public class ImageResizeService : IImageResizeService @@ -22,6 +24,14 @@ public byte[] ResizeProfilePicture(IFormFile formProfileImage) return SquareImageFromMemoryStream(memoryStream, 300); } + public byte[] ResizeCentreImage(IFormFile formCentreImage) + { + using var memoryStream = new MemoryStream(); + formCentreImage.CopyTo(memoryStream); + + return ResizedImageFromMemoryStream(memoryStream, 500); + } + private byte[] SquareImageFromMemoryStream(MemoryStream memoryStream, int targetSideLengthPx) { using var image = Image.FromStream(memoryStream); @@ -35,6 +45,17 @@ private byte[] SquareImageFromMemoryStream(MemoryStream memoryStream, int target return result.ToArray(); } + private byte[] ResizedImageFromMemoryStream(MemoryStream memoryStream, int maxSideLengthPx) + { + using var image = Image.FromStream(memoryStream); + + using var resizedImage = ResizeImageByMaxSideLength(image, maxSideLengthPx); + + using var result = new MemoryStream(); + resizedImage.Save(result, ImageFormat.Jpeg); + return result.ToArray(); + } + private Image CropImageToCentredSquare(Image image) { var minSideLength = Math.Min(image.Height, image.Width); @@ -67,8 +88,25 @@ private Image CropImageToCentredSquare(Image image) private Image ResizeSquareImage(Image image, int sideLengthPx) { - var destRect = new Rectangle(0, 0, sideLengthPx, sideLengthPx); - var returnImage = new Bitmap(sideLengthPx, sideLengthPx); + return ResizeImageToDimensions(image, sideLengthPx, sideLengthPx); + } + + private Image ResizeImageByMaxSideLength(Image image, int maxSideLengthPx) + { + var ratioX = Math.Min((float)maxSideLengthPx / (float)image.Width, 1); + var ratioY = Math.Min((float)maxSideLengthPx / (float)image.Height, 1); + var ratio = Math.Min(ratioX, ratioY); + + var newWidth = (int)(image.Width * ratio); + var newHeight = (int)(image.Height * ratio); + + return ResizeImageToDimensions(image, newWidth, newHeight); + } + + private Image ResizeImageToDimensions(Image image, int widthPx, int heightPx) + { + var destRect = new Rectangle(0, 0, widthPx, heightPx); + var returnImage = new Bitmap(widthPx, heightPx); returnImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs index 370c050b10..fd789d3a77 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs @@ -211,7 +211,7 @@ private IActionResult EditCentreDetailsPostPreviewSignature(EditCentreDetailsVie if (model.CentreSignatureFile != null) { ModelState.Remove(nameof(EditCentreDetailsViewModel.CentreSignature)); - model.CentreSignature = imageResizeService.ResizeProfilePicture(model.CentreSignatureFile); + model.CentreSignature = imageResizeService.ResizeCentreImage(model.CentreSignatureFile); } return View(model); @@ -238,7 +238,7 @@ private IActionResult EditCentreDetailsPostPreviewLogo(EditCentreDetailsViewMode if (model.CentreLogoFile != null) { ModelState.Remove(nameof(EditCentreDetailsViewModel.CentreLogo)); - model.CentreLogo = imageResizeService.ResizeProfilePicture(model.CentreLogoFile); + model.CentreLogo = imageResizeService.ResizeCentreImage(model.CentreLogoFile); } return View(model); From 5632144b422ad06bb91e42f3dcb9050f3e0a1704 Mon Sep 17 00:00:00 2001 From: David May-Miller Date: Fri, 18 Jun 2021 17:14:40 +0100 Subject: [PATCH 04/16] HEEDLS-374 Fix minor display issues --- .../CentreConfigurationController.cs | 2 +- .../EditCentreDetails.cshtml | 38 ++++++++++--------- .../_CentreConfigurationCentreDetails.cshtml | 6 ++- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs index fd789d3a77..b277c6316f 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs @@ -144,7 +144,7 @@ public IActionResult EditCentreDetails() { var centreId = User.GetCentreId(); - var centreDetails = centresDataService.GetCentreDetailsById(centreId); + var centreDetails = centresDataService.GetCentreDetailsById(centreId)!; var model = new EditCentreDetailsViewModel(centreDetails); diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml index cead2add40..a645c370fb 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml @@ -33,23 +33,25 @@
- +
+ - + +
@@ -67,7 +69,7 @@
-
+
@@ -88,7 +90,7 @@
-
+
diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/_CentreConfigurationCentreDetails.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/_CentreConfigurationCentreDetails.cshtml index 09142480a6..1419237c55 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/_CentreConfigurationCentreDetails.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/_CentreConfigurationCentreDetails.cshtml @@ -22,7 +22,7 @@
- Banner text + Centre support details
@Model.BannerText @@ -56,6 +56,10 @@
+ + Edit + + Preview From e2feb0298e566ba4f08f670dc17508d983ddfa97 Mon Sep 17 00:00:00 2001 From: David May-Miller Date: Fri, 18 Jun 2021 17:26:59 +0100 Subject: [PATCH 05/16] HEEDLS-374 Fix CentreConfigurationController tests --- .../CentreConfiguration/CentreConfigurationControllerTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationControllerTests.cs b/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationControllerTests.cs index 332f9aa3d9..f8df9ac63c 100644 --- a/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationControllerTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationControllerTests.cs @@ -3,6 +3,7 @@ using System.Globalization; using DigitalLearningSolutions.Data.DataServices; using DigitalLearningSolutions.Data.Models.External.Maps; + using DigitalLearningSolutions.Data.Services; using DigitalLearningSolutions.Web.Controllers.TrackingSystem.CentreConfiguration; using DigitalLearningSolutions.Web.Helpers.ExternalApis; using DigitalLearningSolutions.Web.Tests.ControllerHelpers; @@ -18,13 +19,14 @@ public class CentreConfigurationControllerTests private readonly IMapsApiHelper mapsApiHelper = A.Fake(); private readonly ILogger logger = A.Fake>(); + private readonly IImageResizeService imageResizeService = A.Fake(); private CentreConfigurationController controller = null!; [SetUp] public void Setup() { controller = - new CentreConfigurationController(centresDataService, mapsApiHelper, logger) + new CentreConfigurationController(centresDataService, mapsApiHelper, logger, imageResizeService) .WithDefaultContext() .WithMockUser(true); From 8d3f7be311b7a1c17e01846010b8644dac875c33 Mon Sep 17 00:00:00 2001 From: David May-Miller Date: Thu, 24 Jun 2021 16:01:38 +0100 Subject: [PATCH 06/16] HEEDLS-374 Added tests for CentreConfigurationController --- .../CentreConfigurationControllerTests.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationControllerTests.cs b/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationControllerTests.cs index f8df9ac63c..857b17d984 100644 --- a/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationControllerTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationControllerTests.cs @@ -9,7 +9,11 @@ using DigitalLearningSolutions.Web.Tests.ControllerHelpers; using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.CentreConfiguration; using FakeItEasy; + using FluentAssertions; using FluentAssertions.AspNetCore.Mvc; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Logging; using NUnit.Framework; @@ -84,6 +88,64 @@ public void EditCentreWebsiteDetails_should_redirect_to_error_page_when_API_issu result.Should().BeRedirectToActionResult().WithActionName("Error").WithControllerName("LearningSolutions"); } + [Test] + public void EditCentreDetailsPostSave_with_signature_image_fails_validation() + { + // Given + var model = new EditCentreDetailsViewModel + { + NotifyEmail = "email@test.com", + BannerText = "Banner text", + CentreSignatureFile = A.Fake() + }; + + // When + var result = controller.EditCentreDetails(model, "save"); + + // Then + A.CallTo(() => centresDataService.UpdateCentreDetails(A._, A._, A._, A._, A._)) + .MustNotHaveHappened(); + result.As().Model.Should().BeEquivalentTo(model); + controller.ModelState[nameof(EditCentreDetailsViewModel.CentreSignatureFile)].ValidationState.Should() + .Be(ModelValidationState.Invalid); + } + + [Test] + public void EditCentreDetailsPostSave_with_logo_image_fails_validation() + { + // Given + var model = new EditCentreDetailsViewModel + { + NotifyEmail = "email@test.com", + BannerText = "Banner text", + CentreLogoFile = A.Fake() + }; + + // When + var result = controller.EditCentreDetails(model, "save"); + + // Then + A.CallTo(() => centresDataService.UpdateCentreDetails(A._, A._, A._, A._, A._)) + .MustNotHaveHappened(); + result.As().Model.Should().BeEquivalentTo(model); + controller.ModelState[nameof(EditCentreDetailsViewModel.CentreLogoFile)].ValidationState.Should() + .Be(ModelValidationState.Invalid); + } + + [Test] + public void EditCentreDetailsPost_returns_error_with_unexpected_action() + { + // Given + const string action = "unexpectedString"; + var model = new EditCentreDetailsViewModel(); + + // When + var result = controller.EditCentreDetails(model, action); + + // Then + result.Should().BeRedirectToActionResult().WithControllerName("LearningSolutions").WithActionName("Error"); + } + [Test] public void EditCentreWebsiteDetails_should_show_save_coordinates_when_postcode_is_valid() { From bf41651eb3ccc9b45601540d2793dcf10dfd8579 Mon Sep 17 00:00:00 2001 From: David May-Miller Date: Wed, 30 Jun 2021 16:25:01 +0100 Subject: [PATCH 07/16] HEEDLS-374 Added and updated tests and made NotifyEmail optional --- .../DataServices/CentresDataServiceTests.cs | 27 +++++++++++++++++++ .../DataServices/CentresDataService.cs | 4 +-- .../BasicAccessibilityTests.cs | 1 + .../MyAccount/MyAccountControllerTests.cs | 2 +- .../CentreConfigurationControllerTests.cs | 4 +-- .../CentreConfigurationController.cs | 3 +-- .../EditCentreDetailsViewModel.cs | 1 - .../EditCentreDetails.cshtml | 6 ++++- 8 files changed, 39 insertions(+), 9 deletions(-) diff --git a/DigitalLearningSolutions.Data.Tests/DataServices/CentresDataServiceTests.cs b/DigitalLearningSolutions.Data.Tests/DataServices/CentresDataServiceTests.cs index ed898f39d5..c533819746 100644 --- a/DigitalLearningSolutions.Data.Tests/DataServices/CentresDataServiceTests.cs +++ b/DigitalLearningSolutions.Data.Tests/DataServices/CentresDataServiceTests.cs @@ -230,5 +230,32 @@ public void UpdateCentreWebsiteDetails_updates_centre() transaction.Dispose(); } } + + [Test] + public void UpdateCentreDetails_updates_centre() + { + using var transaction = new TransactionScope(); + try + { + // Given + const string notifyEmail = "test@centre.com"; + const string bannerText = "Test banner text"; + + // When + centresDataService.UpdateCentreDetails(2, notifyEmail, bannerText, null, null); + var updatedCentre = centresDataService.GetCentreDetailsById(2)!; + + // Then + using (new AssertionScope()) + { + updatedCentre.NotifyEmail.Should().BeEquivalentTo(notifyEmail); + updatedCentre.BannerText.Should().BeEquivalentTo(bannerText); + } + } + finally + { + transaction.Dispose(); + } + } } } diff --git a/DigitalLearningSolutions.Data/DataServices/CentresDataService.cs b/DigitalLearningSolutions.Data/DataServices/CentresDataService.cs index 1e372fed2b..db8c7f2cef 100644 --- a/DigitalLearningSolutions.Data/DataServices/CentresDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/CentresDataService.cs @@ -41,7 +41,7 @@ void UpdateCentreWebsiteDetails void UpdateCentreDetails( int centreId, - string notifyEmail, + string? notifyEmail, string bannerText, byte[]? centreSignature, byte[]? centreLogo @@ -231,7 +231,7 @@ public void UpdateCentreWebsiteDetails public void UpdateCentreDetails( int centreId, - string notifyEmail, + string? notifyEmail, string bannerText, byte[]? centreSignature, byte[]? centreLogo diff --git a/DigitalLearningSolutions.Web.AutomatedUiTests/AccessibilityTests/BasicAccessibilityTests.cs b/DigitalLearningSolutions.Web.AutomatedUiTests/AccessibilityTests/BasicAccessibilityTests.cs index dfd86cc2c3..85b2a0edd1 100644 --- a/DigitalLearningSolutions.Web.AutomatedUiTests/AccessibilityTests/BasicAccessibilityTests.cs +++ b/DigitalLearningSolutions.Web.AutomatedUiTests/AccessibilityTests/BasicAccessibilityTests.cs @@ -32,6 +32,7 @@ public void Page_has_no_accessibility_errors(string url, string pageTitle) [InlineData("/TrackingSystem/Centre/Administrators", "Centre administrators")] [InlineData("/TrackingSystem/Centre/Dashboard", "Centre dashboard")] [InlineData("/TrackingSystem/CentreConfiguration", "Centre configuration")] + [InlineData("/TrackingSystem/CentreConfiguration/EditCentreDetails", "Edit centre details")] [InlineData("/TrackingSystem/CentreConfiguration/EditCentreManagerDetails", "Edit centre manager details")] [InlineData("/TrackingSystem/CentreConfiguration/EditCentreWebsiteDetails", "Edit centre content on DLS website")] [InlineData("/TrackingSystem/CentreConfiguration/RegistrationPrompts", "Manage delegate registration prompts")] diff --git a/DigitalLearningSolutions.Web.Tests/Controllers/MyAccount/MyAccountControllerTests.cs b/DigitalLearningSolutions.Web.Tests/Controllers/MyAccount/MyAccountControllerTests.cs index 118bc5b2c5..764459ca22 100644 --- a/DigitalLearningSolutions.Web.Tests/Controllers/MyAccount/MyAccountControllerTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Controllers/MyAccount/MyAccountControllerTests.cs @@ -105,7 +105,7 @@ public void EditDetailsPostSave_for_admin_user_with_missing_delegate_answers_doe } [Test] - public void EditDetailsPostSave_with_profile_image_fails_validation() + public void EditDetailsPostSave_without_previewing_profile_image_fails_validation() { // Given var myAccountController = new MyAccountController diff --git a/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationControllerTests.cs b/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationControllerTests.cs index 857b17d984..6234d12716 100644 --- a/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationControllerTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationControllerTests.cs @@ -89,7 +89,7 @@ public void EditCentreWebsiteDetails_should_redirect_to_error_page_when_API_issu } [Test] - public void EditCentreDetailsPostSave_with_signature_image_fails_validation() + public void EditCentreDetailsPostSave_without_previewing_signature_image_fails_validation() { // Given var model = new EditCentreDetailsViewModel @@ -111,7 +111,7 @@ public void EditCentreDetailsPostSave_with_signature_image_fails_validation() } [Test] - public void EditCentreDetailsPostSave_with_logo_image_fails_validation() + public void EditCentreDetailsPostSave_without_previewing_logo_image_fails_validation() { // Given var model = new EditCentreDetailsViewModel diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs index b277c6316f..8ac86e5461 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs @@ -168,7 +168,6 @@ public IActionResult EditCentreDetails(EditCentreDetailsViewModel model, string private IActionResult EditCentreDetailsPostSave(EditCentreDetailsViewModel model) { - if (model.CentreSignatureFile != null) { ModelState.AddModelError(nameof(EditCentreDetailsViewModel.CentreSignatureFile), @@ -190,7 +189,7 @@ private IActionResult EditCentreDetailsPostSave(EditCentreDetailsViewModel model centresDataService.UpdateCentreDetails( centreId, - model.NotifyEmail!, + model.NotifyEmail, model.BannerText!, model.CentreSignature, model.CentreLogo diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CentreConfiguration/EditCentreDetailsViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CentreConfiguration/EditCentreDetailsViewModel.cs index 50a87ca6ed..2d8b8dae1e 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CentreConfiguration/EditCentreDetailsViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CentreConfiguration/EditCentreDetailsViewModel.cs @@ -17,7 +17,6 @@ public EditCentreDetailsViewModel(Centre centre) CentreLogo = centre.CentreLogo; } - [Required(ErrorMessage = "Enter an email address")] [MaxLength(250, ErrorMessage = "Email address must be 250 characters or fewer")] [EmailAddress(ErrorMessage = "Enter an email address in the correct format, like name@example.com")] [NoWhitespace("Email address must not contain any whitespace characters")] diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml index a645c370fb..517529e6e6 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml @@ -23,7 +23,11 @@
@if (errorHasOccurred) { - + }

Edit centre details

From 47eb9a094cdb0e93619cf53cbc23f5dfb880200e Mon Sep 17 00:00:00 2001 From: David May-Miller Date: Thu, 15 Jul 2021 09:50:09 +0100 Subject: [PATCH 08/16] HEEDLS-374 Cleaned up Centre Configuration page html and matched spacing to othger similar forms --- .../EditCentreDetails.cshtml | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml index 517529e6e6..81d5ae95fa 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml @@ -11,27 +11,23 @@ ViewData["Application"] = "Tracking System"; ViewData["HeaderPath"] = $"{Configuration["AppRootPath"]}/TrackingSystem/Centre/Dashboard"; ViewData["HeaderPathName"] = "Tracking System"; - var hintText = @"To change your {0}, select a new image and click the Preview button to preview it. + const string hintText = @"To change your {0}, select a new image and click the Preview button to preview it. To remove your {0} click the remove button. Changes will not be made until the Save button below is clicked."; } @section NavMenuItems { - + }
@if (errorHasOccurred) { - + }

Edit centre details

- +
@@ -61,11 +57,11 @@
- +
@if (Model.CentreSignature != null) { - Centre Signature Picture + Centre signature picture } else { Placeholder signature image } @@ -82,7 +78,7 @@
- +
@if (Model.CentreLogo != null) { @@ -102,10 +98,8 @@ -
-
- -
-
+ + +
From 312e08a50ce005d654a5ac1c8a2d566528781b5d Mon Sep 17 00:00:00 2001 From: David May-Miller Date: Thu, 15 Jul 2021 10:02:05 +0100 Subject: [PATCH 09/16] HEEDLS-374 Added extension methods to ModelState --- .../Controllers/MyAccountController.cs | 13 ++--- .../CentreConfigurationController.cs | 25 +++------- .../RegistrationPromptsController.cs | 15 ++---- .../Extensions/ModelStateExtensions.cs | 49 +++++++++++++------ 4 files changed, 46 insertions(+), 56 deletions(-) diff --git a/DigitalLearningSolutions.Web/Controllers/MyAccountController.cs b/DigitalLearningSolutions.Web/Controllers/MyAccountController.cs index 528606cc11..1cbcd355a1 100644 --- a/DigitalLearningSolutions.Web/Controllers/MyAccountController.cs +++ b/DigitalLearningSolutions.Web/Controllers/MyAccountController.cs @@ -5,6 +5,7 @@ using DigitalLearningSolutions.Data.DataServices; using DigitalLearningSolutions.Data.Models.User; using DigitalLearningSolutions.Data.Services; + using DigitalLearningSolutions.Web.Extensions; using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.ViewModels.Common; using DigitalLearningSolutions.Web.ViewModels.MyAccount; @@ -130,11 +131,7 @@ private IActionResult EditDetailsPostSave(EditDetailsViewModel model) private IActionResult EditDetailsPostPreviewImage(EditDetailsViewModel model) { // We don't want to display validation errors on other fields in this case - foreach (var key in ModelState.Keys.Where(k => k != nameof(EditDetailsViewModel.ProfileImageFile))) - { - ModelState[key].Errors.Clear(); - ModelState[key].ValidationState = ModelValidationState.Valid; - } + ModelState.ClearErrorsForAllFieldsExcept(nameof(EditDetailsViewModel.ProfileImageFile)); if (!ModelState.IsValid) { @@ -153,11 +150,7 @@ private IActionResult EditDetailsPostPreviewImage(EditDetailsViewModel model) private IActionResult EditDetailsPostRemoveImage(EditDetailsViewModel model) { // We don't want to display validation errors on other fields in this case - foreach (var key in ModelState.Keys) - { - ModelState[key].Errors.Clear(); - ModelState[key].ValidationState = ModelValidationState.Valid; - } + ModelState.ClearErrorsForAllFields(); ModelState.Remove(nameof(EditDetailsViewModel.ProfileImage)); model.ProfileImage = null; diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs index 937dbdab8d..387d3041b8 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs @@ -3,6 +3,7 @@ namespace DigitalLearningSolutions.Web.Controllers.TrackingSystem.CentreConfigur using System.Linq; using DigitalLearningSolutions.Data.DataServices; using DigitalLearningSolutions.Data.Services; + using DigitalLearningSolutions.Web.Extensions; using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.Helpers.ExternalApis; using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.CentreConfiguration; @@ -162,7 +163,7 @@ public IActionResult EditCentreDetails(EditCentreDetailsViewModel model, string "removeSignature" => EditCentreDetailsPostRemoveSignature(model), "previewLogo" => EditCentreDetailsPostPreviewLogo(model), "removeLogo" => EditCentreDetailsPostRemoveLogo(model), - _ => RedirectToAction("Error", "LearningSolutions") + _ => new StatusCodeResult(500) }; } @@ -200,7 +201,7 @@ private IActionResult EditCentreDetailsPostSave(EditCentreDetailsViewModel model private IActionResult EditCentreDetailsPostPreviewSignature(EditCentreDetailsViewModel model) { - ClearErrorsForAllFieldsExcept(nameof(EditCentreDetailsViewModel.CentreSignatureFile)); + ModelState.ClearErrorsForAllFieldsExcept(nameof(EditCentreDetailsViewModel.CentreSignatureFile)); if (!ModelState.IsValid) { @@ -218,7 +219,7 @@ private IActionResult EditCentreDetailsPostPreviewSignature(EditCentreDetailsVie private IActionResult EditCentreDetailsPostRemoveSignature(EditCentreDetailsViewModel model) { - ClearErrorsForAllFields(); + ModelState.ClearErrorsForAllFields(); ModelState.Remove(nameof(EditCentreDetailsViewModel.CentreSignature)); model.CentreSignature = null; @@ -227,7 +228,7 @@ private IActionResult EditCentreDetailsPostRemoveSignature(EditCentreDetailsView private IActionResult EditCentreDetailsPostPreviewLogo(EditCentreDetailsViewModel model) { - ClearErrorsForAllFieldsExcept(nameof(EditCentreDetailsViewModel.CentreLogoFile)); + ModelState.ClearErrorsForAllFieldsExcept(nameof(EditCentreDetailsViewModel.CentreLogoFile)); if (!ModelState.IsValid) { @@ -245,25 +246,11 @@ private IActionResult EditCentreDetailsPostPreviewLogo(EditCentreDetailsViewMode private IActionResult EditCentreDetailsPostRemoveLogo(EditCentreDetailsViewModel model) { - ClearErrorsForAllFields(); + ModelState.ClearErrorsForAllFields(); ModelState.Remove(nameof(EditCentreDetailsViewModel.CentreLogo)); model.CentreLogo = null; return View(model); } - - private void ClearErrorsForAllFields() - { - ClearErrorsForAllFieldsExcept(null); - } - - private void ClearErrorsForAllFieldsExcept(string? fieldName) - { - foreach (var key in ModelState.Keys.Where(k => k != fieldName)) - { - ModelState[key].Errors.Clear(); - ModelState[key].ValidationState = ModelValidationState.Valid; - } - } } } diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/RegistrationPromptsController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/RegistrationPromptsController.cs index b4536e4d78..23c222c4ad 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/RegistrationPromptsController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/RegistrationPromptsController.cs @@ -315,7 +315,7 @@ public IActionResult RemoveRegistrationPrompt(int promptNumber, RemoveRegistrati private IActionResult EditRegistrationPromptPostSave(EditRegistrationPromptViewModel model) { - IgnoreAddNewAnswerValidation(); + ModelState.ClearErrorsForAllFields(); if (!ModelState.IsValid) { @@ -367,7 +367,7 @@ private IActionResult RegistrationPromptAnswersPostRemovePrompt( bool saveToTempData = false ) { - IgnoreAddNewAnswerValidation(); + ModelState.ClearErrorsForAllFields(); var optionsString = NewlineSeparatedStringListHelper.RemoveStringFromNewlineSeparatedList(model.OptionsString!, index); @@ -384,7 +384,7 @@ private IActionResult RegistrationPromptAnswersPostRemovePrompt( private IActionResult AddRegistrationPromptConfigureAnswersPostNext(RegistrationPromptAnswersViewModel model) { - IgnoreAddNewAnswerValidation(); + ModelState.ClearErrorsForAllFields(); if (!ModelState.IsValid) { @@ -457,15 +457,6 @@ private void SetTotalAnswersLengthTooLongError(RegistrationPromptAnswersViewMode ); } - private void IgnoreAddNewAnswerValidation() - { - foreach (var key in ModelState.Keys) - { - ModelState[key].Errors.Clear(); - ModelState[key].ValidationState = ModelValidationState.Valid; - } - } - private static bool TryGetAnswerIndexFromDeleteAction(string action, out int index) { return int.TryParse(action.Remove(0, DeleteAction.Length), out index); diff --git a/DigitalLearningSolutions.Web/Extensions/ModelStateExtensions.cs b/DigitalLearningSolutions.Web/Extensions/ModelStateExtensions.cs index bf65f4d30c..42e1586114 100644 --- a/DigitalLearningSolutions.Web/Extensions/ModelStateExtensions.cs +++ b/DigitalLearningSolutions.Web/Extensions/ModelStateExtensions.cs @@ -1,15 +1,34 @@ -using System.Linq; -using Microsoft.AspNetCore.Mvc.ModelBinding; - -namespace DigitalLearningSolutions.Web.Extensions -{ - internal static class ModelStateDictionaryExtensions - { - internal static bool HasError(this ModelStateDictionary modelStateDictionary, string fieldName) - { - return !string.IsNullOrWhiteSpace(fieldName) - && modelStateDictionary.TryGetValue(fieldName, out var entry) - && (entry.Errors?.Any() ?? false); - } - } -} +using System.Linq; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace DigitalLearningSolutions.Web.Extensions +{ + using Microsoft.CodeAnalysis.CSharp.Syntax; + + internal static class ModelStateDictionaryExtensions + { + internal static bool HasError(this ModelStateDictionary modelStateDictionary, string fieldName) + { + return !string.IsNullOrWhiteSpace(fieldName) + && modelStateDictionary.TryGetValue(fieldName, out var entry) + && (entry.Errors?.Any() ?? false); + } + + internal static void ClearErrorsForAllFields(this ModelStateDictionary modelStateDictionary) + { + ClearErrorsForAllFieldsExcept(modelStateDictionary, null); + } + + internal static void ClearErrorsForAllFieldsExcept( + this ModelStateDictionary modelStateDictionary, + string? fieldName + ) + { + foreach (var key in modelStateDictionary.Keys.Where(k => k != fieldName)) + { + modelStateDictionary[key].Errors.Clear(); + modelStateDictionary[key].ValidationState = ModelValidationState.Valid; + } + } + } +} From f52e4dffa00fbe7ad99fdf70c49ce1b258bacdeb Mon Sep 17 00:00:00 2001 From: David May-Miller Date: Thu, 15 Jul 2021 10:02:16 +0100 Subject: [PATCH 10/16] HEEDLS-374 Made ImageResizeService more intuitive --- .../Services/ImageResizeService.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/DigitalLearningSolutions.Data/Services/ImageResizeService.cs b/DigitalLearningSolutions.Data/Services/ImageResizeService.cs index ede9d6e177..ccb33204a4 100644 --- a/DigitalLearningSolutions.Data/Services/ImageResizeService.cs +++ b/DigitalLearningSolutions.Data/Services/ImageResizeService.cs @@ -6,6 +6,7 @@ using System.Drawing.Imaging; using System.IO; using Microsoft.AspNetCore.Http; + using Org.BouncyCastle.Crypto.Tls; public interface IImageResizeService { @@ -93,9 +94,9 @@ private Image ResizeSquareImage(Image image, int sideLengthPx) private Image ResizeImageByMaxSideLength(Image image, int maxSideLengthPx) { - var ratioX = Math.Min((float)maxSideLengthPx / (float)image.Width, 1); - var ratioY = Math.Min((float)maxSideLengthPx / (float)image.Height, 1); - var ratio = Math.Min(ratioX, ratioY); + var longestSideLengthPx = Math.Max(image.Width, image.Height); + // No need to resize if image is smaller than the max size + var ratio = Math.Min((float)maxSideLengthPx / (float)longestSideLengthPx, 1); var newWidth = (int)(image.Width * ratio); var newHeight = (int)(image.Height * ratio); From 977bd7b652ab45c09f6a96a1209aae1f2dc90bc7 Mon Sep 17 00:00:00 2001 From: David May-Miller Date: Thu, 15 Jul 2021 10:03:05 +0100 Subject: [PATCH 11/16] HEEDLS-374 Renamed method --- .../Controllers/MyAccountController.cs | 2 +- .../CentreConfiguration/CentreConfigurationController.cs | 4 ++-- .../CentreConfiguration/RegistrationPromptsController.cs | 6 +++--- .../Extensions/ModelStateExtensions.cs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/DigitalLearningSolutions.Web/Controllers/MyAccountController.cs b/DigitalLearningSolutions.Web/Controllers/MyAccountController.cs index 1cbcd355a1..4776cd7beb 100644 --- a/DigitalLearningSolutions.Web/Controllers/MyAccountController.cs +++ b/DigitalLearningSolutions.Web/Controllers/MyAccountController.cs @@ -150,7 +150,7 @@ private IActionResult EditDetailsPostPreviewImage(EditDetailsViewModel model) private IActionResult EditDetailsPostRemoveImage(EditDetailsViewModel model) { // We don't want to display validation errors on other fields in this case - ModelState.ClearErrorsForAllFields(); + ModelState.ClearAllErrors(); ModelState.Remove(nameof(EditDetailsViewModel.ProfileImage)); model.ProfileImage = null; diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs index 387d3041b8..852d03ed1c 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationController.cs @@ -219,7 +219,7 @@ private IActionResult EditCentreDetailsPostPreviewSignature(EditCentreDetailsVie private IActionResult EditCentreDetailsPostRemoveSignature(EditCentreDetailsViewModel model) { - ModelState.ClearErrorsForAllFields(); + ModelState.ClearAllErrors(); ModelState.Remove(nameof(EditCentreDetailsViewModel.CentreSignature)); model.CentreSignature = null; @@ -246,7 +246,7 @@ private IActionResult EditCentreDetailsPostPreviewLogo(EditCentreDetailsViewMode private IActionResult EditCentreDetailsPostRemoveLogo(EditCentreDetailsViewModel model) { - ModelState.ClearErrorsForAllFields(); + ModelState.ClearAllErrors(); ModelState.Remove(nameof(EditCentreDetailsViewModel.CentreLogo)); model.CentreLogo = null; diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/RegistrationPromptsController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/RegistrationPromptsController.cs index 23c222c4ad..c3a603b793 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/RegistrationPromptsController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/RegistrationPromptsController.cs @@ -315,7 +315,7 @@ public IActionResult RemoveRegistrationPrompt(int promptNumber, RemoveRegistrati private IActionResult EditRegistrationPromptPostSave(EditRegistrationPromptViewModel model) { - ModelState.ClearErrorsForAllFields(); + ModelState.ClearAllErrors(); if (!ModelState.IsValid) { @@ -367,7 +367,7 @@ private IActionResult RegistrationPromptAnswersPostRemovePrompt( bool saveToTempData = false ) { - ModelState.ClearErrorsForAllFields(); + ModelState.ClearAllErrors(); var optionsString = NewlineSeparatedStringListHelper.RemoveStringFromNewlineSeparatedList(model.OptionsString!, index); @@ -384,7 +384,7 @@ private IActionResult RegistrationPromptAnswersPostRemovePrompt( private IActionResult AddRegistrationPromptConfigureAnswersPostNext(RegistrationPromptAnswersViewModel model) { - ModelState.ClearErrorsForAllFields(); + ModelState.ClearAllErrors(); if (!ModelState.IsValid) { diff --git a/DigitalLearningSolutions.Web/Extensions/ModelStateExtensions.cs b/DigitalLearningSolutions.Web/Extensions/ModelStateExtensions.cs index 42e1586114..0940eb9d55 100644 --- a/DigitalLearningSolutions.Web/Extensions/ModelStateExtensions.cs +++ b/DigitalLearningSolutions.Web/Extensions/ModelStateExtensions.cs @@ -14,7 +14,7 @@ internal static bool HasError(this ModelStateDictionary modelStateDictionary, st && (entry.Errors?.Any() ?? false); } - internal static void ClearErrorsForAllFields(this ModelStateDictionary modelStateDictionary) + internal static void ClearAllErrors(this ModelStateDictionary modelStateDictionary) { ClearErrorsForAllFieldsExcept(modelStateDictionary, null); } From bbe27e0fea2bbb56cf8002ffa6c2240691570313 Mon Sep 17 00:00:00 2001 From: David May-Miller Date: Thu, 15 Jul 2021 12:40:46 +0100 Subject: [PATCH 12/16] HEEDLS-374 Added tests for all actions on EditCentreDetails --- .../CentreConfigurationControllerTests.cs | 140 +++++++++++++++++- 1 file changed, 137 insertions(+), 3 deletions(-) diff --git a/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationControllerTests.cs b/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationControllerTests.cs index 6234d12716..c3327ea799 100644 --- a/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationControllerTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/CentreConfiguration/CentreConfigurationControllerTests.cs @@ -21,8 +21,10 @@ public class CentreConfigurationControllerTests { private readonly ICentresDataService centresDataService = A.Fake(); private readonly IMapsApiHelper mapsApiHelper = A.Fake(); + private readonly ILogger logger = A.Fake>(); + private readonly IImageResizeService imageResizeService = A.Fake(); private CentreConfigurationController controller = null!; @@ -52,6 +54,15 @@ public void Setup() ).DoesNothing(); } + [TearDown] + public void Cleanup() + { + Fake.ClearRecordedCalls(centresDataService); + Fake.ClearRecordedCalls(mapsApiHelper); + Fake.ClearRecordedCalls(logger); + Fake.ClearRecordedCalls(imageResizeService); + } + [Test] public void EditCentreWebsiteDetails_should_show_validation_error_to_user_when_given_invalid_postcode() { @@ -103,7 +114,15 @@ public void EditCentreDetailsPostSave_without_previewing_signature_image_fails_v var result = controller.EditCentreDetails(model, "save"); // Then - A.CallTo(() => centresDataService.UpdateCentreDetails(A._, A._, A._, A._, A._)) + A.CallTo( + () => centresDataService.UpdateCentreDetails( + A._, + A._, + A._, + A._, + A._ + ) + ) .MustNotHaveHappened(); result.As().Model.Should().BeEquivalentTo(model); controller.ModelState[nameof(EditCentreDetailsViewModel.CentreSignatureFile)].ValidationState.Should() @@ -125,7 +144,15 @@ public void EditCentreDetailsPostSave_without_previewing_logo_image_fails_valida var result = controller.EditCentreDetails(model, "save"); // Then - A.CallTo(() => centresDataService.UpdateCentreDetails(A._, A._, A._, A._, A._)) + A.CallTo( + () => centresDataService.UpdateCentreDetails( + A._, + A._, + A._, + A._, + A._ + ) + ) .MustNotHaveHappened(); result.As().Model.Should().BeEquivalentTo(model); controller.ModelState[nameof(EditCentreDetailsViewModel.CentreLogoFile)].ValidationState.Should() @@ -143,7 +170,114 @@ public void EditCentreDetailsPost_returns_error_with_unexpected_action() var result = controller.EditCentreDetails(model, action); // Then - result.Should().BeRedirectToActionResult().WithControllerName("LearningSolutions").WithActionName("Error"); + result.Should().BeStatusCodeResult().WithStatusCode(500); + } + + [Test] + public void EditCentreDetailsPost_updates_centre_and_redirects_with_successful_save() + { + // Given + const string action = "save"; + var model = new EditCentreDetailsViewModel + { + BannerText = "Test banner text" + }; + + // When + var result = controller.EditCentreDetails(model, action); + + // Then + result.Should().BeRedirectToActionResult().WithActionName("Index"); + A.CallTo(() => centresDataService.UpdateCentreDetails(2, null, model.BannerText, null, null)) + .MustHaveHappenedOnceExactly(); + } + + [Test] + public void EditCentreDetailsPost_previewSignature_calls_imageResizeService() + { + // Given + const string action = "previewSignature"; + var model = new EditCentreDetailsViewModel + { + BannerText = "Test banner text", + CentreSignature = new byte[100], + CentreSignatureFile = A.Fake() + }; + var newImage = new byte [200]; + A.CallTo(() => imageResizeService.ResizeCentreImage(A._)).Returns(newImage); + + // When + var result = controller.EditCentreDetails(model, action); + + // Then + result.Should().BeViewResult(); + A.CallTo(() => imageResizeService.ResizeCentreImage(A._)).MustHaveHappenedOnceExactly(); + var returnModel = (result as ViewResult)!.Model as EditCentreDetailsViewModel; + returnModel!.CentreSignature.Should().BeEquivalentTo(newImage); + } + + [Test] + public void EditCentreDetailsPost_previewLogo_calls_imageResizeService() + { + // Given + const string action = "previewLogo"; + var model = new EditCentreDetailsViewModel + { + BannerText = "Test banner text", + CentreLogo = new byte[100], + CentreLogoFile = A.Fake() + }; + var newImage = new byte [200]; + A.CallTo(() => imageResizeService.ResizeCentreImage(A._)).Returns(newImage); + + // When + var result = controller.EditCentreDetails(model, action); + + // Then + result.Should().BeViewResult(); + A.CallTo(() => imageResizeService.ResizeCentreImage(A._)).MustHaveHappenedOnceExactly(); + var returnModel = (result as ViewResult)!.Model as EditCentreDetailsViewModel; + returnModel!.CentreLogo.Should().BeEquivalentTo(newImage); + } + + [Test] + public void EditCentreDetailsPost_removeSignature_removes_signature() + { + // Given + const string action = "removeSignature"; + var model = new EditCentreDetailsViewModel + { + BannerText = "Test banner text", + CentreSignature = new byte[100] + }; + + // When + var result = controller.EditCentreDetails(model, action); + + // Then + result.Should().BeViewResult(); + var returnModel = (result as ViewResult)!.Model as EditCentreDetailsViewModel; + returnModel!.CentreSignature.Should().BeNull(); + } + + [Test] + public void EditCentreDetailsPost_removeLogo_removes_logo() + { + // Given + const string action = "removeLogo"; + var model = new EditCentreDetailsViewModel + { + BannerText = "Test banner text", + CentreLogo = new byte[100] + }; + + // When + var result = controller.EditCentreDetails(model, action); + + // Then + result.Should().BeViewResult(); + var returnModel = (result as ViewResult)!.Model as EditCentreDetailsViewModel; + returnModel!.CentreLogo.Should().BeNull(); } [Test] From 6827427dfc5b9dbbdfc410a91ea02ba88beafea4 Mon Sep 17 00:00:00 2001 From: David May-Miller Date: Thu, 15 Jul 2021 12:44:42 +0100 Subject: [PATCH 13/16] HEEDLS-374 Test that UpdateCentreDetails saves CentreLogo and Signature correctly --- .../DataServices/CentresDataServiceTests.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Data.Tests/DataServices/CentresDataServiceTests.cs b/DigitalLearningSolutions.Data.Tests/DataServices/CentresDataServiceTests.cs index 1d58f99a2b..56400f6527 100644 --- a/DigitalLearningSolutions.Data.Tests/DataServices/CentresDataServiceTests.cs +++ b/DigitalLearningSolutions.Data.Tests/DataServices/CentresDataServiceTests.cs @@ -240,9 +240,12 @@ public void UpdateCentreDetails_updates_centre() // Given const string notifyEmail = "test@centre.com"; const string bannerText = "Test banner text"; + var signature = new byte[100]; + var logo = new byte[200]; + // When - centresDataService.UpdateCentreDetails(2, notifyEmail, bannerText, null, null); + centresDataService.UpdateCentreDetails(2, notifyEmail, bannerText, signature, logo); var updatedCentre = centresDataService.GetCentreDetailsById(2)!; // Then @@ -250,6 +253,8 @@ public void UpdateCentreDetails_updates_centre() { updatedCentre.NotifyEmail.Should().BeEquivalentTo(notifyEmail); updatedCentre.BannerText.Should().BeEquivalentTo(bannerText); + updatedCentre.SignatureImage.Should().BeEquivalentTo(signature); + updatedCentre.CentreLogo.Should().BeEquivalentTo(logo); } } finally From e7a7cf4cc49435592d104f5433b650c91aff5b7e Mon Sep 17 00:00:00 2001 From: Daniel Bloxham Date: Thu, 15 Jul 2021 14:15:47 +0100 Subject: [PATCH 14/16] HEEDLS-374 - made centre logo alt text capitalisation consistent --- .../TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml index 81d5ae95fa..009ed13162 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml @@ -82,7 +82,7 @@
@if (Model.CentreLogo != null) { - Centre Logo Picture + Centre logo picture } else { Placeholder logo image } From ceda538bd15b8d155cc6a263b1cc7405d30e7b2b Mon Sep 17 00:00:00 2001 From: Daniel Bloxham Date: Thu, 15 Jul 2021 14:24:27 +0100 Subject: [PATCH 15/16] HEEDLS-374 - fix spacing on EditCentreDetails h1 --- .../TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml index 009ed13162..e8aa0dc7f5 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/CentreConfiguration/EditCentreDetails.cshtml @@ -26,7 +26,7 @@ } -

Edit centre details

+

Edit centre details

From 0894170a0f14d1d5e699efcaef22d07f771e6e72 Mon Sep 17 00:00:00 2001 From: Daniel Bloxham Date: Thu, 15 Jul 2021 14:44:45 +0100 Subject: [PATCH 16/16] HEEDLS-374 - remove redundant model validation checks in registration prompt posts --- .../RegistrationPromptsController.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/RegistrationPromptsController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/RegistrationPromptsController.cs index 7b29ebbacb..d7e5c3c727 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/RegistrationPromptsController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CentreConfiguration/RegistrationPromptsController.cs @@ -320,11 +320,6 @@ private IActionResult EditRegistrationPromptPostSave(EditRegistrationPromptViewM { ModelState.ClearAllErrors(); - if (!ModelState.IsValid) - { - return View(model); - } - customPromptsService.UpdateCustomPromptForCentre( User.GetCentreId(), model.PromptNumber, @@ -389,11 +384,6 @@ private IActionResult AddRegistrationPromptConfigureAnswersPostNext(Registration { ModelState.ClearAllErrors(); - if (!ModelState.IsValid) - { - return View(model); - } - UpdateTempDataWithAnswersModelValues(model); return RedirectToAction("AddRegistrationPromptSummary");