Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Created check additional requirements page #254

Merged
merged 14 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/actions/run-unit-tests/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ runs:
path: code-coverage-results.md
message: |
Code coverage report

- name: Create Html coverage report
uses: danielpalme/[email protected]
if: github.actor != 'dependabot[bot]' && github.event_name == 'pull_request'
with:
reports: 'coverage/**/coverage.cobertura.xml'
targetdir: 'CoverageReports'
reporttypes: 'Html'

- name: Upload coverage reports
uses: actions/upload-artifact@v4
if: github.actor != 'dependabot[bot]' && github.event_name == 'pull_request'
with:
name: CoverageReports
path: CoverageReports

- name: Write to Job Summary
shell: bash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ public static class ContentTypes
public const string DropdownQuestionPage = "dropdownQuestionPage";
public const string QualificationListPage = "qualificationListPage";
public const string ConfirmQualificationPage = "confirmQualificationPage";
public const string CheckAdditionalRequirementsPage = "checkAdditionalRequirementsPage";
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ public class AdditionalRequirementQuestion
public string ConfirmationStatement { get; set; } = string.Empty;

public bool AnswerToBeFullAndRelevant { get; set; }

public List<Option> Answers { get; set; } = [];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Dfe.EarlyYearsQualification.Content.Entities;

public class CheckAdditionalRequirementsPage
{
public string Heading { get; init; } = string.Empty;

public NavigationLink? BackButton { get; init; }

public string QualificationLabel { get; init; } = string.Empty;

public string QualificationLevelLabel { get; init; } = string.Empty;

public string AwardingOrganisationLabel { get; init; } = string.Empty;

public string InformationMessage { get; init; } = string.Empty;

public string CtaButtonText { get; init; } = string.Empty;

public string QuestionSectionHeading { get; init; } = string.Empty;

public string ErrorMessage { get; init; } = string.Empty;
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ private readonly Dictionary<object, string> _contentTypes
{ typeof(DateQuestionPage), ContentTypes.DateQuestionPage },
{ typeof(DropdownQuestionPage), ContentTypes.DropdownQuestionPage },
{ typeof(QualificationListPage), ContentTypes.QualificationListPage },
{ typeof(ConfirmQualificationPage), ContentTypes.ConfirmQualificationPage }
{ typeof(ConfirmQualificationPage), ContentTypes.ConfirmQualificationPage },
{ typeof(CheckAdditionalRequirementsPage), ContentTypes.CheckAdditionalRequirementsPage }
};

public async Task<StartPage?> GetStartPage()
Expand Down Expand Up @@ -101,6 +102,7 @@ public async Task<List<NavigationLink>> GetNavigationLinks()
public async Task<Qualification?> GetQualificationById(string qualificationId)
{
var queryBuilder = new QueryBuilder<Qualification>().ContentTypeIs(_contentTypes[typeof(Qualification)])
.Include(2)
.FieldEquals("fields.qualificationId",
qualificationId.ToUpper());
var qualifications = await GetEntriesByType(queryBuilder);
Expand Down Expand Up @@ -194,6 +196,18 @@ public async Task<List<Qualification>> GetQualifications()
return confirmQualificationEntities.First();
}

public async Task<CheckAdditionalRequirementsPage?> GetCheckAdditionalRequirementsPage()
{
var checkAdditionalRequirementsPageEntities = await GetEntriesByType<CheckAdditionalRequirementsPage>();
if (checkAdditionalRequirementsPageEntities is null || !checkAdditionalRequirementsPageEntities.Any())
{
logger.LogWarning("No CheckAdditionalRequirementsPage entry returned");
return default;
}

return checkAdditionalRequirementsPageEntities.First();
}

public async Task<QualificationListPage?> GetQualificationListPage()
{
var qualificationListPageEntities = await GetEntriesByType<QualificationListPage>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ public interface IContentService
Task<QualificationListPage?> GetQualificationListPage();

Task<ConfirmQualificationPage?> GetConfirmQualificationPage();

Task<CheckAdditionalRequirementsPage?> GetCheckAdditionalRequirementsPage();
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public Task<List<Qualification>> GetFilteredQualifications(int? level, int? star
{
CreateQualification("EYQ-100", "CACHE", 2, null, "Aug-19"),
CreateQualification("EYQ-101", "NCFE", 2, "Sep-14", "Aug-19"),
CreateQualification("EYQ-102", "Pearson", 3, "Sep-14", "Aug-19"),
CreateQualification("EYQ-240", "Pearson", 3, "Sep-14", "Aug-19"),
CreateQualification("EYQ-103", "NCFE", 3, "Sep-14", "Aug-19"),
CreateQualification("EYQ-104", "City & Guilds", 4, "Sep-14", "Aug-19"),
CreateQualification("EYQ-105", "Montessori Centre International", 4, "Sep-14", "Aug-19"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,36 @@ public async Task<List<NavigationLink>> GetNavigationLinks()
"2021",
"603/5829/4",
"The course must be assessed within the EYFS in an Early Years setting in England. Please note that the name of this qualification changed in February 2023. Qualifications achieved under either name are full and relevant provided that the start date for the qualification aligns with the date of the name change.",
null,
null
new List<AdditionalRequirementQuestion>
{
new()
{
Question = "Test question",
HintText = "This is the hint text",
DetailsHeading = "This is the details heading",
DetailsContent = ContentfulContentHelper.Paragraph("This is the details content"),
Answers =
[
new Option
{
Label = "Yes",
Value = "yes"
},

new Option
{
Label = "No",
Value = "no"
}
],
ConfirmationStatement = "This is the confirmation statement",
AnswerToBeFullAndRelevant = true
}
},
new List<RatioRequirement>()
{
new() { RatioRequirementName = "Level 2 ratio requirements" }
}
));
}

Expand Down Expand Up @@ -281,6 +309,27 @@ public Task<List<Qualification>> GetQualifications()
});
}

public async Task<CheckAdditionalRequirementsPage?> GetCheckAdditionalRequirementsPage()
{
return await Task.FromResult(new CheckAdditionalRequirementsPage
{
Heading = "Check the additional requirements",
BackButton = new NavigationLink
{
DisplayText = "Back",
Href = "/",
OpenInNewTab = false
},
CtaButtonText = "Get result",
AwardingOrganisationLabel = "Awarding organisation",
QualificationLabel = "Qualification",
QualificationLevelLabel = "Qualification level",
InformationMessage = "Your result is dependent on the accuracy of the answers you have provided",
ErrorMessage = "This is a test error message",
QuestionSectionHeading = "This is the question section heading"
});
}

public async Task<StartPage?> GetStartPage()
{
var preCtaButtonContent =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.ComponentModel.DataAnnotations;

namespace Dfe.EarlyYearsQualification.Web.Attributes;

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class AnswerValidationAttribute : ValidationAttribute
{
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
if (value is not Dictionary<string, string> dictionary)
{
return new ValidationResult("Object is not of type Dictionary<string, string>()");
}

if (dictionary.Count == 0)
{
return new ValidationResult("Answers are required");
}

foreach (var keyValuePair in dictionary.Where(keyValuePair => string.IsNullOrEmpty(keyValuePair.Value)))
{
return new ValidationResult($"Value is required for key: {keyValuePair.Key}");
}

return ValidationResult.Success;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using Dfe.EarlyYearsQualification.Content.Entities;
using Dfe.EarlyYearsQualification.Content.Renderers.Entities;
using Dfe.EarlyYearsQualification.Content.Services;
using Dfe.EarlyYearsQualification.Web.Controllers.Base;
using Dfe.EarlyYearsQualification.Web.Models.Content;
using Dfe.EarlyYearsQualification.Web.Models.Content.QuestionModels;
using Dfe.EarlyYearsQualification.Web.Services.UserJourneyCookieService;
using Microsoft.AspNetCore.Mvc;

namespace Dfe.EarlyYearsQualification.Web.Controllers;

[Route("qualifications/check-additional-questions")]
public class CheckAdditionalRequirementsController(
ILogger<CheckAdditionalRequirementsController> logger,
IContentService contentService,
IHtmlRenderer htmlRenderer,
IUserJourneyCookieService userJourneyCookieService)
: ServiceController
{
[HttpGet("{qualificationId}")]
public async Task<IActionResult> Index(string qualificationId)
{
if (ModelState.IsValid) return await GetResponse(qualificationId);

logger.LogError("No qualificationId passed in");
return RedirectToAction("Index", "Error");

}

[HttpPost]
public async Task<IActionResult> Post([FromForm]CheckAdditionalRequirementsPageModel model)
{
if (ModelState.IsValid)
{
userJourneyCookieService.SetAdditionalQuestionsAnswers(model.Answers);
return RedirectToAction("Index", "QualificationDetails",
new { model.QualificationId });
}

model.HasErrors = true;
return await GetResponse(model.QualificationId, model);
}

private async Task<IActionResult> GetResponse(string qualificationId,
CheckAdditionalRequirementsPageModel? model = null)
{
var qualification = await contentService.GetQualificationById(qualificationId);
if (qualification is null)
{
var loggedQualificationId = qualificationId.Replace(Environment.NewLine, "");
logger.LogError("Could not find details for qualification with ID: {QualificationId}",
loggedQualificationId);

return RedirectToAction("Index", "Error");
}

if (qualification.AdditionalRequirementQuestions is null || qualification.AdditionalRequirementQuestions.Count == 0)
{
var loggedQualificationId = qualificationId.Replace(Environment.NewLine, "");
logger.LogInformation("QualificationId has no additional requirement questions: {QualificationId}",
loggedQualificationId);
return RedirectToAction("Index", "QualificationDetails",
new { qualificationId });
}

var content = await contentService.GetCheckAdditionalRequirementsPage();
if (content is null)
{
logger.LogError("No content for the check additional requirements page");
return RedirectToAction("Index", "Error");
}

var mappedModel = await MapModel(content, qualification, model);

return View("Index", mappedModel);
}

private async Task<CheckAdditionalRequirementsPageModel> MapModel(CheckAdditionalRequirementsPage content, Qualification qualification,
CheckAdditionalRequirementsPageModel? model = null)
{
var mappedModel = model ?? new CheckAdditionalRequirementsPageModel();
mappedModel.QualificationId = qualification.QualificationId;
mappedModel.AwardingOrganisation = qualification.AwardingOrganisationTitle;
mappedModel.AwardingOrganisationLabel = content.AwardingOrganisationLabel;
mappedModel.CtaButtonText = content.CtaButtonText;
mappedModel.QualificationLevel = qualification.QualificationLevel;
mappedModel.QualificationLabel = content.QualificationLabel;
mappedModel.QualificationName = qualification.QualificationName;
mappedModel.Heading = content.Heading;
mappedModel.InformationMessage = content.InformationMessage;
mappedModel.QualificationLevelLabel = content.QualificationLevelLabel;
mappedModel.QuestionSectionHeading = content.QuestionSectionHeading;
mappedModel.BackButton = content.BackButton;
mappedModel.AdditionalRequirementQuestions =
await MapAdditionalRequirementQuestions(qualification.AdditionalRequirementQuestions!);
mappedModel.Answers = MapQuestionsToDictionary(qualification.AdditionalRequirementQuestions!);
mappedModel.ErrorMessage = content.ErrorMessage;
return mappedModel;
}

private async Task<List<AdditionalRequirementQuestionModel>> MapAdditionalRequirementQuestions(List<AdditionalRequirementQuestion> additionalRequirementQuestions)
{
var results = new List<AdditionalRequirementQuestionModel>();

foreach (var additionalRequirementQuestion in additionalRequirementQuestions)
{
results.Add(new AdditionalRequirementQuestionModel
{
Question = additionalRequirementQuestion.Question,
HintText = additionalRequirementQuestion.HintText,
DetailsHeading = additionalRequirementQuestion.DetailsHeading,
DetailsContent = await htmlRenderer.ToHtml(additionalRequirementQuestion.DetailsContent),
Options = MapOptions(additionalRequirementQuestion.Answers)
});
}

return results;
}

private static List<OptionModel> MapOptions(List<Option> options)
{
return options.Select(option => new OptionModel { Label = option.Label, Value = option.Value }).ToList();
}

private static Dictionary<string, string> MapQuestionsToDictionary(List<AdditionalRequirementQuestion> additionalRequirementQuestions)
{
return additionalRequirementQuestions.ToDictionary(additionalRequirementQuestion => additionalRequirementQuestion.Question, additionalRequirementQuestion => string.Empty);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,28 +48,12 @@ public async Task<IActionResult> Index(string qualificationId)
[HttpPost]
public async Task<IActionResult> Confirm(ConfirmQualificationPageModel model)
{
if (ModelState.IsValid)
{
return model.ConfirmQualificationAnswer == "yes"
? RedirectToAction("Index", "QualificationDetails",
new { qualificationId = model.QualificationId })
: RedirectToAction("Get", "QualificationDetails");
}

var content = await contentService.GetConfirmQualificationPage();

if (content is null)
{
logger.LogError("No content for the cookies page");
return RedirectToAction("Index", "Error");
}

if (string.IsNullOrEmpty(model.QualificationId))
{
logger.LogError("No qualification id provided");
return RedirectToAction("Index", "Error");
}

var qualification = await contentService.GetQualificationById(model.QualificationId);
if (qualification is null)
{
Expand All @@ -79,6 +63,27 @@ public async Task<IActionResult> Confirm(ConfirmQualificationPageModel model)

return RedirectToAction("Index", "Error");
}

if (ModelState.IsValid)
{
var hasAdditionalQuestions = qualification.AdditionalRequirementQuestions is not null &&
qualification.AdditionalRequirementQuestions.Count > 0;
return model.ConfirmQualificationAnswer == "yes" && hasAdditionalQuestions
? RedirectToAction("Index", "CheckAdditionalRequirements",
new { qualificationId = model.QualificationId })
: model.ConfirmQualificationAnswer == "yes"
? RedirectToAction("Index", "QualificationDetails",
new { qualificationId = model.QualificationId })
: RedirectToAction("Get", "QualificationDetails");
}

var content = await contentService.GetConfirmQualificationPage();

if (content is null)
{
logger.LogError("No content for the cookies page");
return RedirectToAction("Index", "Error");
}

model = Map(content, qualification);
model.HasErrors = true;
Expand Down
Loading
Loading