Skip to content

Commit

Permalink
Merge branch 'main' into feature/prod-deployment
Browse files Browse the repository at this point in the history
  • Loading branch information
RobertGHippo authored Jul 24, 2024
2 parents 7bd3d1a + dacea08 commit b2e62f5
Show file tree
Hide file tree
Showing 32 changed files with 1,191 additions and 112 deletions.
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

0 comments on commit b2e62f5

Please sign in to comment.