From 6deadb496a980a8019a5c1af3567896c90f41ff9 Mon Sep 17 00:00:00 2001 From: Daniel Clarke Date: Thu, 20 Jun 2024 10:26:26 +0100 Subject: [PATCH 1/8] Added new awarding organisation page, along with new dropdown page type in contentful --- .../Constants/QuestionPages.cs | 7 ++ .../Entities/DropdownQuestionPage.cs | 16 +++++ .../Services/ContentfulContentService.cs | 22 ++++-- .../Services/IContentService.cs | 4 ++ .../Content/MockContentfulService.cs | 10 +++ .../Controllers/QuestionsController.cs | 70 +++++++++++++++++++ .../QuestionModels/BaseQuestionModel.cs | 2 - .../QuestionModels/DropdownQuestionModel.cs | 20 ++++++ .../Security/SecureHeaderConfiguration.cs | 7 ++ .../Views/Questions/Dropdown.cshtml | 64 +++++++++++++++++ 10 files changed, 215 insertions(+), 7 deletions(-) create mode 100644 src/Dfe.EarlyYearsQualification.Content/Entities/DropdownQuestionPage.cs create mode 100644 src/Dfe.EarlyYearsQualification.Web/Models/Content/QuestionModels/DropdownQuestionModel.cs create mode 100644 src/Dfe.EarlyYearsQualification.Web/Views/Questions/Dropdown.cshtml diff --git a/src/Dfe.EarlyYearsQualification.Content/Constants/QuestionPages.cs b/src/Dfe.EarlyYearsQualification.Content/Constants/QuestionPages.cs index 5024a7ff..9078d65c 100644 --- a/src/Dfe.EarlyYearsQualification.Content/Constants/QuestionPages.cs +++ b/src/Dfe.EarlyYearsQualification.Content/Constants/QuestionPages.cs @@ -21,4 +21,11 @@ public static class QuestionPages /// Entry ID for the "When was the qualification started" question page. /// public const string WhenWasTheQualificationStarted = "2o331MBr0R6nsZNBem4yvk"; + + //// Dropdown Pages + + /// + /// Entry ID for the "What is the awarding organisation" question page. + /// + public const string WhatIsTheAwardingOrganisation = "7A9txhwSGFrgh3Ye9k5PJ9"; } \ No newline at end of file diff --git a/src/Dfe.EarlyYearsQualification.Content/Entities/DropdownQuestionPage.cs b/src/Dfe.EarlyYearsQualification.Content/Entities/DropdownQuestionPage.cs new file mode 100644 index 00000000..68373534 --- /dev/null +++ b/src/Dfe.EarlyYearsQualification.Content/Entities/DropdownQuestionPage.cs @@ -0,0 +1,16 @@ +namespace Dfe.EarlyYearsQualification.Content.Entities; + +public class DropdownQuestionPage +{ + public string Question { get; init; } = string.Empty; + + public string CtaButtonText { get; init; } = string.Empty; + + public string ErrorMessage { get; init; } = string.Empty; + + public string DropdownHeading { get; init; } = string.Empty; + + public string NotInListText { get; init; } = string.Empty; + + public string DefaultText { get; init; } = string.Empty; +} \ No newline at end of file diff --git a/src/Dfe.EarlyYearsQualification.Content/Services/ContentfulContentService.cs b/src/Dfe.EarlyYearsQualification.Content/Services/ContentfulContentService.cs index 2514019a..7c5cc700 100644 --- a/src/Dfe.EarlyYearsQualification.Content/Services/ContentfulContentService.cs +++ b/src/Dfe.EarlyYearsQualification.Content/Services/ContentfulContentService.cs @@ -25,7 +25,8 @@ private readonly Dictionary _contentTypes { typeof(CookiesPage), "cookiesPage" }, { typeof(PhaseBanner), "phaseBanner" }, { typeof(CookiesBanner), "cookiesBanner" }, - { typeof(DateQuestionPage), "dateQuestionPage" } + { typeof(DateQuestionPage), "dateQuestionPage" }, + { typeof(DropdownQuestionPage), "dropdownQuestionPage" } }; public async Task GetStartPage() @@ -126,10 +127,15 @@ private readonly Dictionary _contentTypes return await GetEntryById(entryId); } - public async Task GetDateQuestionPage(string entryId) - { - return await GetEntryById(entryId); - } + public async Task GetDateQuestionPage(string entryId) + { + return await GetEntryById(entryId); + } + + public async Task GetDropdownQuestionPage(string entryId) + { + return await GetEntryById(entryId); + } public async Task GetPhaseBannerContent() { @@ -155,6 +161,12 @@ private readonly Dictionary _contentTypes return cookiesBannerEntry.First(); } + public async Task> GetQualifications() + { + var qualifications = await GetEntriesByType(); + return qualifications!.ToList(); + } + private async Task GetEntryById(string entryId) { try diff --git a/src/Dfe.EarlyYearsQualification.Content/Services/IContentService.cs b/src/Dfe.EarlyYearsQualification.Content/Services/IContentService.cs index b4d4cf70..92bb3ce6 100644 --- a/src/Dfe.EarlyYearsQualification.Content/Services/IContentService.cs +++ b/src/Dfe.EarlyYearsQualification.Content/Services/IContentService.cs @@ -25,4 +25,8 @@ public interface IContentService Task GetCookiesBannerContent(); Task GetDateQuestionPage(string entryId); + + Task GetDropdownQuestionPage(string entryId); + + Task> GetQualifications(); } \ No newline at end of file diff --git a/src/Dfe.EarlyYearsQualification.Mock/Content/MockContentfulService.cs b/src/Dfe.EarlyYearsQualification.Mock/Content/MockContentfulService.cs index 080bda7e..7a9263a2 100644 --- a/src/Dfe.EarlyYearsQualification.Mock/Content/MockContentfulService.cs +++ b/src/Dfe.EarlyYearsQualification.Mock/Content/MockContentfulService.cs @@ -149,6 +149,16 @@ await Task.FromResult(CreateDateQuestionPage()), }; } + public Task GetDropdownQuestionPage(string entryId) + { + throw new NotImplementedException(); + } + + public Task> GetQualifications() + { + throw new NotImplementedException(); + } + public async Task GetStartPage() { var preCtaButtonContent = diff --git a/src/Dfe.EarlyYearsQualification.Web/Controllers/QuestionsController.cs b/src/Dfe.EarlyYearsQualification.Web/Controllers/QuestionsController.cs index ce7d50b3..14e6355e 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Controllers/QuestionsController.cs +++ b/src/Dfe.EarlyYearsQualification.Web/Controllers/QuestionsController.cs @@ -7,6 +7,7 @@ using Dfe.EarlyYearsQualification.Web.Models.Content.QuestionModels; using Dfe.EarlyYearsQualification.Web.Services.UserJourneyCookieService; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; namespace Dfe.EarlyYearsQualification.Web.Controllers; @@ -113,6 +114,44 @@ public async Task WhatLevelIsTheQualification(RadioQuestionModel userJourneyCookieService.SetLevelOfQualification(model.Option!); + return RedirectToAction(nameof(this.WhatIsTheAwardingOrganisation)); + } + + [HttpGet("what-is-the-awarding-organisation")] + public async Task WhatIsTheAwardingOrganisation() + { + var questionPage = await contentService.GetDropdownQuestionPage(QuestionPages.WhatIsTheAwardingOrganisation); + if (questionPage is null) + { + logger.LogError("No content for the question page"); + return RedirectToAction("Index", "Error"); + } + + var qualifications = await contentService.GetQualifications(); + + var model = MapDropdownModel(new DropdownQuestionModel(), questionPage, qualifications, nameof(this.WhatIsTheAwardingOrganisation), + Questions); + return View("Dropdown", model); + } + + [HttpPost("what-is-the-awarding-organisation")] + public async Task WhatIsTheAwardingOrganisation(DropdownQuestionModel model) + { + if (!ModelState.IsValid || (string.IsNullOrEmpty(model.SelectedValue) && !model.NotInTheList)) + { + var questionPage = await contentService.GetDropdownQuestionPage(QuestionPages.WhatIsTheAwardingOrganisation); + if (questionPage is not null) + { + var qualifications = await contentService.GetQualifications(); + + model = MapDropdownModel(model, questionPage, qualifications, nameof(this.WhatIsTheAwardingOrganisation), + Questions); + model.HasErrors = true; + } + + return View("Dropdown", model); + } + return RedirectToAction("Get", "QualificationDetails"); } @@ -157,4 +196,35 @@ private static DateQuestionModel MapDateModel(DateQuestionModel model, DateQuest model.YearLabel = question.YearLabel; return model; } + + private static DropdownQuestionModel MapDropdownModel(DropdownQuestionModel model, DropdownQuestionPage question, List qualifications, string actionName, + string controllerName) + { + var uniqueAwardingOrganisations = qualifications.Select(x => x.AwardingOrganisationTitle).Distinct().Order().ToList(); + + model.ActionName = actionName; + model.ControllerName = controllerName; + model.CtaButtonText = question.CtaButtonText; + model.ErrorMessage = question.ErrorMessage; + model.Question = question.Question; + model.DropdownHeading = question.DropdownHeading; + model.NotInListText = question.NotInListText; + + model.Values.Add(new SelectListItem() + { + Text = question.DefaultText, + Value = "" + }); + + foreach (var awardingOrg in uniqueAwardingOrganisations) + { + model.Values.Add(new SelectListItem() + { + Value = awardingOrg, + Text = awardingOrg + }); + } + + return model; + } } \ No newline at end of file diff --git a/src/Dfe.EarlyYearsQualification.Web/Models/Content/QuestionModels/BaseQuestionModel.cs b/src/Dfe.EarlyYearsQualification.Web/Models/Content/QuestionModels/BaseQuestionModel.cs index ed49e5d4..5836baad 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Models/Content/QuestionModels/BaseQuestionModel.cs +++ b/src/Dfe.EarlyYearsQualification.Web/Models/Content/QuestionModels/BaseQuestionModel.cs @@ -13,6 +13,4 @@ public abstract class BaseQuestionModel public string ErrorMessage { get; set; } = string.Empty; public bool HasErrors { get; set; } - - } \ No newline at end of file diff --git a/src/Dfe.EarlyYearsQualification.Web/Models/Content/QuestionModels/DropdownQuestionModel.cs b/src/Dfe.EarlyYearsQualification.Web/Models/Content/QuestionModels/DropdownQuestionModel.cs new file mode 100644 index 00000000..3a2c4f2d --- /dev/null +++ b/src/Dfe.EarlyYearsQualification.Web/Models/Content/QuestionModels/DropdownQuestionModel.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace Dfe.EarlyYearsQualification.Web.Models.Content.QuestionModels; + +public class DropdownQuestionModel : BaseQuestionModel +{ + public string SelectedValue { get; set; } = string.Empty; + + public List Values { get; set; } = []; + + public string DropdownHeading { get; set; } = string.Empty; + + public string NotInListText { get; set; } = string.Empty; + + public string DropdownId { get; init; } = "awarding-organisation-select"; + + public string CheckboxId { get; init; } = "awarding-organisation-not-in-list"; + + public bool NotInTheList { get; set; } +} \ No newline at end of file diff --git a/src/Dfe.EarlyYearsQualification.Web/Security/SecureHeaderConfiguration.cs b/src/Dfe.EarlyYearsQualification.Web/Security/SecureHeaderConfiguration.cs index c09d5424..7ba4d05b 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Security/SecureHeaderConfiguration.cs +++ b/src/Dfe.EarlyYearsQualification.Web/Security/SecureHeaderConfiguration.cs @@ -40,6 +40,12 @@ public static SecureHeadersMiddlewareConfiguration CustomConfiguration() CommandType = CspCommandType.Directive, DirectiveOrUri = "sha256-Om9RNNoMrdmIZzT4Oo7KaozVNUg6zYxVQuq3CPld2Ms=" }; + + var dropdownPageCheckbox = new ContentSecurityPolicyElement + { + CommandType = CspCommandType.Directive, + DirectiveOrUri = "sha256-pwrEcsLN2o+4gQQDR/0sGCITSf0nhhLAzP4h73+5foc=" + }; var unsafeHashesElement = new ContentSecurityPolicyElement { CommandType = CspCommandType.Directive, DirectiveOrUri = "unsafe-hashes" }; @@ -49,6 +55,7 @@ public static SecureHeadersMiddlewareConfiguration CustomConfiguration() configuration.ContentSecurityPolicyConfiguration.ScriptSrc.Add(cookiesPageShaCspElement); configuration.ContentSecurityPolicyConfiguration.ScriptSrc.Add(windowLocationShaCspElement); configuration.ContentSecurityPolicyConfiguration.ScriptSrc.Add(unsafeHashesElement); + configuration.ContentSecurityPolicyConfiguration.ScriptSrc.Add(dropdownPageCheckbox); configuration.ContentSecurityPolicyConfiguration.FrameAncestors.Add(contentfulCspElement); return configuration; } diff --git a/src/Dfe.EarlyYearsQualification.Web/Views/Questions/Dropdown.cshtml b/src/Dfe.EarlyYearsQualification.Web/Views/Questions/Dropdown.cshtml new file mode 100644 index 00000000..c192e69b --- /dev/null +++ b/src/Dfe.EarlyYearsQualification.Web/Views/Questions/Dropdown.cshtml @@ -0,0 +1,64 @@ +@model Dfe.EarlyYearsQualification.Web.Models.Content.QuestionModels.DropdownQuestionModel + +@{ + ViewData["Title"] = "Questions"; +} + + + +Back +
+
+
+ @using (Html.BeginForm(Model.ActionName, Model.ControllerName, FormMethod.Post)) + { +
+ +

+ @Model.Question +

+
+ + @if (Model.HasErrors) + { +

+ Error: @Model.ErrorMessage +

+ } + +
+ +

+ @Html.LabelFor(x => x.DropdownId, Model.DropdownHeading, new { @class = "govuk-label govuk-fieldset__legend--m" }) +

+ +
+ @Html.DropDownListFor(x => x.SelectedValue, Model.Values, new { @class = "govuk-select", id = Model.DropdownId }) +
+
+
+ + +
+
+ +
+ +
+ } +
+
+
\ No newline at end of file From ef78444be5a79717cd68645f20ac2a58ffc4ed86 Mon Sep 17 00:00:00 2001 From: Daniel Clarke Date: Thu, 20 Jun 2024 14:31:14 +0100 Subject: [PATCH 2/8] finished implementation of new awarding organisation page - now saving selection to user journey and displaying on qualification list page --- .../Controllers/QuestionsController.cs | 3 +++ .../Content/QuestionModels/DropdownQuestionModel.cs | 8 ++++---- .../Models/UserJourneyModel.cs | 1 + .../IUserJourneyCookieService.cs | 1 + .../UserJourneyCookieService.cs | 9 +++++++++ .../Views/QualificationDetails/Get.cshtml | 5 +++++ .../Views/Questions/Dropdown.cshtml | 13 ++++++------- .../Controllers/QuestionsControllerTests.cs | 3 +-- 8 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/Dfe.EarlyYearsQualification.Web/Controllers/QuestionsController.cs b/src/Dfe.EarlyYearsQualification.Web/Controllers/QuestionsController.cs index 14e6355e..585c3c1e 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Controllers/QuestionsController.cs +++ b/src/Dfe.EarlyYearsQualification.Web/Controllers/QuestionsController.cs @@ -131,6 +131,7 @@ public async Task WhatIsTheAwardingOrganisation() var model = MapDropdownModel(new DropdownQuestionModel(), questionPage, qualifications, nameof(this.WhatIsTheAwardingOrganisation), Questions); + return View("Dropdown", model); } @@ -152,6 +153,8 @@ public async Task WhatIsTheAwardingOrganisation(DropdownQuestionM return View("Dropdown", model); } + userJourneyCookieService.SetAwardingOrganisation(model.NotInTheList ? string.Empty : model.SelectedValue); + return RedirectToAction("Get", "QualificationDetails"); } diff --git a/src/Dfe.EarlyYearsQualification.Web/Models/Content/QuestionModels/DropdownQuestionModel.cs b/src/Dfe.EarlyYearsQualification.Web/Models/Content/QuestionModels/DropdownQuestionModel.cs index 3a2c4f2d..180e5ae7 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Models/Content/QuestionModels/DropdownQuestionModel.cs +++ b/src/Dfe.EarlyYearsQualification.Web/Models/Content/QuestionModels/DropdownQuestionModel.cs @@ -4,17 +4,17 @@ namespace Dfe.EarlyYearsQualification.Web.Models.Content.QuestionModels; public class DropdownQuestionModel : BaseQuestionModel { - public string SelectedValue { get; set; } = string.Empty; + public string SelectedValue { get; init; } = string.Empty; - public List Values { get; set; } = []; + public List Values { get; init; } = []; public string DropdownHeading { get; set; } = string.Empty; public string NotInListText { get; set; } = string.Empty; - + public string DropdownId { get; init; } = "awarding-organisation-select"; public string CheckboxId { get; init; } = "awarding-organisation-not-in-list"; - public bool NotInTheList { get; set; } + public bool NotInTheList { get; init; } } \ No newline at end of file diff --git a/src/Dfe.EarlyYearsQualification.Web/Models/UserJourneyModel.cs b/src/Dfe.EarlyYearsQualification.Web/Models/UserJourneyModel.cs index 4dc4e818..9a9a9c3d 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Models/UserJourneyModel.cs +++ b/src/Dfe.EarlyYearsQualification.Web/Models/UserJourneyModel.cs @@ -5,4 +5,5 @@ public class UserJourneyModel public string WhereWasQualificationAwarded { get; set; } = string.Empty; public string WhenWasQualificationAwarded { get; set; } = string.Empty; public string LevelOfQualification { get; set; } = string.Empty; + public string WhatIsTheAwardingOrganisation { get; set; } = string.Empty; } \ No newline at end of file diff --git a/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/IUserJourneyCookieService.cs b/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/IUserJourneyCookieService.cs index efdfb3d7..fe04f9e0 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/IUserJourneyCookieService.cs +++ b/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/IUserJourneyCookieService.cs @@ -7,6 +7,7 @@ public interface IUserJourneyCookieService public void SetWhereWasQualificationAwarded(string location); public void SetWhenWasQualificationAwarded(string date); public void SetLevelOfQualification(string level); + public void SetAwardingOrganisation(string awardingOrganisation); public UserJourneyModel GetUserJourneyModelFromCookie(); public void ResetUserJourneyCookie(); } diff --git a/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/UserJourneyCookieService.cs b/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/UserJourneyCookieService.cs index 88b711be..b221477f 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/UserJourneyCookieService.cs +++ b/src/Dfe.EarlyYearsQualification.Web/Services/UserJourneyCookieService/UserJourneyCookieService.cs @@ -40,6 +40,15 @@ public void SetLevelOfQualification(string level) SetJourneyCookie(model); } + public void SetAwardingOrganisation(string awardingOrganisation) + { + var model = GetUserJourneyModelFromCookie(); + + model.WhatIsTheAwardingOrganisation = awardingOrganisation; + + SetJourneyCookie(model); + } + public UserJourneyModel GetUserJourneyModelFromCookie() { var cookie = context.HttpContext?.Request.Cookies[CookieKeyNames.UserJourneyKey]; diff --git a/src/Dfe.EarlyYearsQualification.Web/Views/QualificationDetails/Get.cshtml b/src/Dfe.EarlyYearsQualification.Web/Views/QualificationDetails/Get.cshtml index 04c02f32..a7d14f19 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Views/QualificationDetails/Get.cshtml +++ b/src/Dfe.EarlyYearsQualification.Web/Views/QualificationDetails/Get.cshtml @@ -26,6 +26,11 @@ {
  • @Model.LevelOfQualification
  • } + + @if (!string.IsNullOrEmpty(Model.WhatIsTheAwardingOrganisation)) + { +
  • @Model.WhatIsTheAwardingOrganisation
  • + }

    diff --git a/src/Dfe.EarlyYearsQualification.Web/Views/Questions/Dropdown.cshtml b/src/Dfe.EarlyYearsQualification.Web/Views/Questions/Dropdown.cshtml index c192e69b..cdf10d02 100644 --- a/src/Dfe.EarlyYearsQualification.Web/Views/Questions/Dropdown.cshtml +++ b/src/Dfe.EarlyYearsQualification.Web/Views/Questions/Dropdown.cshtml @@ -6,7 +6,7 @@