Skip to content

Commit

Permalink
Add alert journey (#855)
Browse files Browse the repository at this point in the history
* Started add alert page

* Added basic functionality to add alert page

* Finished off backend and UI to add an alert

* Added CRM and UI tests

* Added E2E test for adding an alert

* Fixed inconsistency

* Fixed issues from PR comments
  • Loading branch information
hortha authored Oct 10, 2023
1 parent 7626faf commit b1f4047
Show file tree
Hide file tree
Showing 23 changed files with 842 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace TeachingRecordSystem.Core.Dqt.Queries;

public record CreateSanctionQuery : ICrmQuery<Guid>
{
public required Guid ContactId { get; init; }
public required Guid SanctionCodeId { get; init; }
public required string Details { get; init; }
public required string? Link { get; init; }
public required DateOnly StartDate { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Microsoft.PowerPlatform.Dataverse.Client;
using TeachingRecordSystem.Core.Dqt.Queries;

namespace TeachingRecordSystem.Core.Dqt.QueryHandlers;

public class CreateSanctionHandler : ICrmQueryHandler<CreateSanctionQuery, Guid>
{
public async Task<Guid> Execute(CreateSanctionQuery query, IOrganizationServiceAsync organizationService)
{
var sanction = new dfeta_sanction()
{
Id = Guid.NewGuid(),
dfeta_PersonId = query.ContactId.ToEntityReference(Contact.EntityLogicalName),
dfeta_SanctionCodeId = query.SanctionCodeId.ToEntityReference(dfeta_sanctioncode.EntityLogicalName),
dfeta_SanctionDetails = query.Details,
dfeta_DetailsLink = query.Link,
dfeta_StartDate = query.StartDate.FromDateOnlyWithDqtBstFix(isLocalTime: true)
};

var sanctionId = await organizationService.CreateAsync(sanction);
return sanctionId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ public async Task<dfeta_sanctioncode> GetSanctionCodeByValue(string value)
return sanctionCodes.First(s => s.dfeta_Value == value);
}

public async Task<dfeta_sanctioncode> GetSanctionCodeById(Guid sanctionCodeId)
{
var sanctionCodes = await EnsureSanctionCodes();
return sanctionCodes.Single(s => s.dfeta_sanctioncodeId == sanctionCodeId);
}

public async Task<dfeta_sanctioncode[]> GetSanctionCodes()
{
var sanctionCodes = await EnsureSanctionCodes();
return sanctionCodes.ToArray();
}

public async Task<Subject> GetSubjectByTitle(string title)
{
var subjects = await EnsureSubjects();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ namespace TeachingRecordSystem.SupportUi;

public static class JourneyNames
{
public const string AddAlert = nameof(AddAlert);
public const string CloseAlert = nameof(CloseAlert);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;

namespace TeachingRecordSystem.SupportUi.Pages.Alerts.AddAlert;

public class AddAlertState
{
public Guid? AlertTypeId { get; set; }

public string? Details { get; set; }

public string? Link { get; set; }

public DateOnly? StartDate { get; set; }

[JsonIgnore]
[MemberNotNullWhen(true, nameof(AlertTypeId), nameof(Details), nameof(StartDate))]
public bool IsComplete => AlertTypeId.HasValue && !string.IsNullOrWhiteSpace(Details) && StartDate.HasValue;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
@page "/alerts/add/confirm"
@model TeachingRecordSystem.SupportUi.Pages.Alerts.AddAlert.ConfirmModel
@{
ViewBag.Title = "Confirm alert information";
}

@section BeforeContent {
<govuk-back-link href="@LinkGenerator.AlertAdd(Model.PersonId, Model.JourneyInstance!.InstanceId)">Back</govuk-back-link>
}

<h1 class="govuk-heading-l">@ViewBag.Title</h1>

<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<form method="post">
<govuk-summary-list>
<govuk-summary-list-row>
<govuk-summary-list-row-key>Alert type</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="alert-type">@Model.AlertType</govuk-summary-list-row-value>
</govuk-summary-list-row>
<govuk-summary-list-row>
<govuk-summary-list-row-key>Details</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="details"><multi-line-text text="@Model.Details" /></govuk-summary-list-row-value>
</govuk-summary-list-row>
@if (!string.IsNullOrWhiteSpace(Model.Link))
{
<govuk-summary-list-row>
<govuk-summary-list-row-key>Link</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="link">@Model.Link</govuk-summary-list-row-value>
</govuk-summary-list-row>
}
<govuk-summary-list-row>
<govuk-summary-list-row-key>Start date</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="start-date">@Model.StartDate!.Value.ToString("dd/MM/yyyy")</govuk-summary-list-row-value>
</govuk-summary-list-row>
</govuk-summary-list>

<govuk-button type="submit">Confirm</govuk-button>
</form>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Xrm.Sdk.Query;
using TeachingRecordSystem.Core.Dqt.Models;
using TeachingRecordSystem.Core.Dqt.Queries;

namespace TeachingRecordSystem.SupportUi.Pages.Alerts.AddAlert;

[Journey(JourneyNames.AddAlert), RequireJourneyInstance]
public class ConfirmModel : PageModel
{
private readonly TrsLinkGenerator _linkGenerator;
private readonly ICrmQueryDispatcher _crmQueryDispatcher;
private readonly ReferenceDataCache _referenceDataCache;

public ConfirmModel(
TrsLinkGenerator linkGenerator,
ICrmQueryDispatcher crmQueryDispatcher,
ReferenceDataCache referenceDataCache)
{
_linkGenerator = linkGenerator;
_crmQueryDispatcher = crmQueryDispatcher;
_referenceDataCache = referenceDataCache;
}

public JourneyInstance<AddAlertState>? JourneyInstance { get; set; }

[FromQuery]
public Guid PersonId { get; set; }

public Guid? AlertTypeId { get; set; }

public string? AlertType { get; set; }

public string? Details { get; set; }

public string? Link { get; set; }

public DateOnly? StartDate { get; set; }

public async Task<IActionResult> OnPost()
{
await _crmQueryDispatcher.ExecuteQuery(
new CreateSanctionQuery()
{
ContactId = PersonId,
SanctionCodeId = AlertTypeId!.Value,
Details = Details!,
Link = Link,
StartDate = StartDate!.Value
});

await JourneyInstance!.CompleteAsync();

TempData.SetFlashSuccess("Alert added");

return Redirect(_linkGenerator.PersonAlerts(PersonId));
}

public override async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
var person = await _crmQueryDispatcher.ExecuteQuery(new GetContactDetailByIdQuery(PersonId, new ColumnSet(Contact.Fields.Id)));
if (person is null)
{
context.Result = NotFound();
return;
}

if (!JourneyInstance!.State.IsComplete)
{
context.Result = Redirect(_linkGenerator.AlertAdd(PersonId, JourneyInstance!.InstanceId));
return;
}

var sanctionCodeId = JourneyInstance!.State.AlertTypeId!.Value;
var sanctionCode = await _referenceDataCache.GetSanctionCodeById(sanctionCodeId);

AlertTypeId = sanctionCodeId;
AlertType = sanctionCode.dfeta_name;
Details = JourneyInstance!.State.Details;
Link = JourneyInstance!.State.Link;
StartDate = JourneyInstance!.State.StartDate;

await next();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
@page "/alerts/add"
@model IndexModel
@addTagHelper *, Joonasw.AspNetCore.SecurityHeaders
@{
ViewBag.Title = "Add an alert";
}

@section Styles {
<link rel="stylesheet" href="~/Styles/Components/accessible-autocomplete.min.css" asp-append-version="true" />
}

@section Scripts {
<script src="~/Scripts/Components/accessible-autocomplete.min.js"></script>
<script asp-add-nonce="true">
window.onload = function () {
accessibleAutocomplete.enhanceSelectElement({
selectElement: document.querySelector('#@nameof(Model.AlertTypeId)')
})
}
</script>
}

@section BeforeContent {
<govuk-back-link href="@LinkGenerator.PersonAlerts(Model.PersonId)">Back</govuk-back-link>
}

<h1 class="govuk-heading-l">@ViewBag.Title</h1>

<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<form method="post">
<govuk-select asp-for="AlertTypeId" label-class="govuk-label--m">
<govuk-select-item value=""></govuk-select-item>
@foreach (var alertType in Model.AlertTypes!)
{
<govuk-select-item value="@alertType.AlertTypeId">@alertType.Name</govuk-select-item>
}
</govuk-select>

<govuk-textarea asp-for="Details" label-class="govuk-label--m"/>

<govuk-input asp-for="Link" label-class="govuk-label--m"/>

<govuk-date-input asp-for="StartDate">
<govuk-date-input-fieldset>
<govuk-date-input-fieldset-legend class="govuk-fieldset__legend--m" />
</govuk-date-input-fieldset>
</govuk-date-input>

<govuk-button type="submit">Continue</govuk-button>
</form>
</div>
</div>

Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Xrm.Sdk.Query;
using TeachingRecordSystem.Core.Dqt.Models;
using TeachingRecordSystem.Core.Dqt.Queries;
using TeachingRecordSystem.SupportUi.Pages.Common;

namespace TeachingRecordSystem.SupportUi.Pages.Alerts.AddAlert;

[Journey(JourneyNames.AddAlert), ActivatesJourney, RequireJourneyInstance]
public class IndexModel : PageModel
{
private readonly TrsLinkGenerator _linkGenerator;
private readonly ICrmQueryDispatcher _crmQueryDispatcher;
private readonly ReferenceDataCache _referenceDataCache;

public IndexModel(
TrsLinkGenerator linkGenerator,
ICrmQueryDispatcher crmQueryDispatcher,
ReferenceDataCache referenceDataCache)
{
_linkGenerator = linkGenerator;
_crmQueryDispatcher = crmQueryDispatcher;
_referenceDataCache = referenceDataCache;
}

public JourneyInstance<AddAlertState>? JourneyInstance { get; set; }

[FromQuery]
public Guid PersonId { get; set; }

[BindProperty]
[Display(Name = "Alert type")]
public Guid? AlertTypeId { get; set; }

[BindProperty]
[Display(Description = "You can enter up to 4000 characters")]
public string? Details { get; set; }

[BindProperty]
[Display(Description = "Optional")]
public string? Link { get; set; }

[BindProperty]
[Display(Name = "Start date")]
public DateOnly? StartDate { get; set; }

public AlertType[]? AlertTypes { get; set; }

public async Task<IActionResult> OnPost()
{
if (AlertTypeId is null)
{
ModelState.AddModelError(nameof(AlertTypeId), "Add an alert type");
}

if (string.IsNullOrWhiteSpace(Details))
{
ModelState.AddModelError(nameof(Details), "Add details");
}

if (!string.IsNullOrEmpty(Link) &&
(!Uri.TryCreate(Link, UriKind.Absolute, out var uri) ||
(uri.Scheme != "http" && uri.Scheme != "https")))
{
ModelState.AddModelError(nameof(Link), "Enter a valid URL");
}

if (StartDate is null)
{
ModelState.AddModelError(nameof(StartDate), "Add a start date");
}

if (!ModelState.IsValid)
{
return this.PageWithErrors();
}

await JourneyInstance!.UpdateStateAsync(s =>
{
s.AlertTypeId = AlertTypeId;
s.Details = Details;
s.Link = Link;
s.StartDate = StartDate;
});

return Redirect(_linkGenerator.AlertAddConfirm(PersonId, JourneyInstance!.InstanceId));
}

public override async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
var person = await _crmQueryDispatcher.ExecuteQuery(new GetContactDetailByIdQuery(PersonId, new ColumnSet(Contact.Fields.Id)));
if (person is null)
{
context.Result = NotFound();
return;
}

var sanctionCodes = await _referenceDataCache.GetSanctionCodes();
AlertTypes = sanctionCodes
.Select(MapSanctionCode)
.OrderBy(a => a.Name)
.ToArray();

AlertTypeId ??= JourneyInstance!.State.AlertTypeId;
Details ??= JourneyInstance!.State.Details;
Link ??= JourneyInstance!.State.Link;
StartDate ??= JourneyInstance!.State.StartDate;

await next();
}

private AlertType MapSanctionCode(dfeta_sanctioncode sanctionCode) =>
new AlertType()
{
AlertTypeId = sanctionCode.dfeta_sanctioncodeId!.Value,
Value = sanctionCode.dfeta_Value,
Name = sanctionCode.dfeta_name
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace TeachingRecordSystem.SupportUi.Pages.Common;

public record AlertType
{
public required Guid AlertTypeId { get; init; }
public required string Name { get; init; }
public required string Value { get; init; }
}
Loading

0 comments on commit b1f4047

Please sign in to comment.