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

Alert detail page #849

Merged
merged 3 commits into from
Oct 5, 2023
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace TeachingRecordSystem.Core.Dqt.Queries;

public record UpdateSanctionStateQuery(Guid SanctionId, dfeta_sanctionState State) : ICrmQuery<bool>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Microsoft.PowerPlatform.Dataverse.Client;
using Microsoft.Xrm.Sdk.Messages;
using TeachingRecordSystem.Core.Dqt.Queries;

namespace TeachingRecordSystem.Core.Dqt.QueryHandlers;

public class UpdateSanctionStateHandler : ICrmQueryHandler<UpdateSanctionStateQuery, bool>
{
public async Task<bool> Execute(UpdateSanctionStateQuery query, IOrganizationServiceAsync organizationService)
{
await organizationService.ExecuteAsync(new UpdateRequest()
{
Target = new dfeta_sanction()
{
Id = query.SanctionId,
StateCode = query.State
}
});

return true;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,56 @@
@page "/alerts/{alertId}"
@model IndexModel
@{
ViewBag.Title = "Alert";
ViewBag.Title = Model.Alert!.Description;
}

<h1 class="govuk-heading-l">@ViewBag.Title</h1>
@section BeforeContent {
<govuk-back-link href="@LinkGenerator.PersonAlerts(Model.PersonId!.Value)">Back</govuk-back-link>
}

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

<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<table class="govuk-table trs-table--no-border govuk-!-margin-bottom-1">
<thead class="govuk-table__head">
<tr class="govuk-table__row">
<th scope="col" class="govuk-table__header trs-table__header--no-border">Start date</th>
<th scope="col" class="govuk-table__header trs-table__header--no-border">End date</th>
<th scope="col" class="govuk-table__header trs-table__header--no-border">Status</th>
</tr>
</thead>
<tbody class="govuk-table__body">
<tr class="govuk-table__row" data-testid="alert-header">
<td class="govuk-table__cell trs-table__cell--no-border" data-testid="start-date" use-empty-fallback>@(Model.Alert.StartDate.HasValue ? Model.Alert.StartDate.Value.ToString("dd/MM/yyyy") : string.Empty)</td>
<td class="govuk-table__cell trs-table__cell--no-border" data-testid="end-date" use-empty-fallback>@(Model.Alert.EndDate.HasValue ? Model.Alert.EndDate.Value.ToString("dd/MM/yyyy") : string.Empty)</td>
<td class="govuk-table__cell trs-table__cell--no-border" data-testid="status">@Model.Alert.Status</td>
</tr>
</tbody>
</table>
</div>
</div>

<hr class="govuk-section-break govuk-section-break--l govuk-section-break--visible">

<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<h3 class="govuk-heading-s">Details</h3>
@if (!string.IsNullOrEmpty(@Model.Alert.Details))
{
<multi-line-text data-testid="alert-details" text="@Model.Alert.Details"/>
}
@if (Model.IsActive)
{
<form asp-page-handler="SetInactive" method="post">
<govuk-button type="submit" class="govuk-!-margin-top-9" data-testid="deactivate-button">Mark alert as inactive</govuk-button>
</form>
}
else
{
<form asp-page-handler="SetActive" method="post">
<govuk-button type="submit" class="govuk-button--warning govuk-!-margin-top-9" data-testid="reactivate-button">Remove inactive status</govuk-button>
</form>
}
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,10 +1,84 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
using TeachingRecordSystem.Core.Dqt.Models;
using TeachingRecordSystem.Core.Dqt.Queries;
using TeachingRecordSystem.SupportUi.Pages.Common;

namespace TeachingRecordSystem.SupportUi.Pages.Alerts.Alert;

public class IndexModel : PageModel
{
public void OnGet()
private readonly TrsLinkGenerator _linkGenerator;
private readonly ICrmQueryDispatcher _crmQueryDispatcher;

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

[FromRoute]
public Guid AlertId { get; set; }

public AlertInfo? Alert { get; set; }

public bool IsActive { get; set; }

public Guid? PersonId { get; set; }

public async Task<IActionResult> OnPostSetActive()
{
await _crmQueryDispatcher.ExecuteQuery(new UpdateSanctionStateQuery(AlertId, dfeta_sanctionState.Active));

IsActive = true;
TempData.SetFlashSuccess("Inactive status removed");

return Redirect(_linkGenerator.Alert(AlertId));
}

public async Task<IActionResult> OnPostSetInactive()
{
await _crmQueryDispatcher.ExecuteQuery(new UpdateSanctionStateQuery(AlertId, dfeta_sanctionState.Inactive));

IsActive = false;
TempData.SetFlashSuccess("Status changed to inactive");

return Redirect(_linkGenerator.Alert(AlertId));
}

public override async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
var sanction = await _crmQueryDispatcher.ExecuteQuery(new GetSanctionDetailsBySanctionIdQuery(AlertId));
if (sanction is null)
{
context.Result = NotFound();
return;
}

Alert = MapSanction(sanction);
IsActive = sanction.Sanction.StateCode == dfeta_sanctionState.Active;
PersonId = sanction.Sanction.dfeta_PersonId.Id;

await next();
}

private AlertInfo MapSanction(SanctionDetailResult sanction)
{
var alertStatus = sanction.Sanction.StateCode == dfeta_sanctionState.Inactive ? AlertStatus.Inactive :
sanction.Sanction.dfeta_EndDate is null ? AlertStatus.Active :
AlertStatus.Closed;

return new AlertInfo()
{
AlertId = sanction.Sanction.Id,
Description = sanction.Description,
Details = sanction.Sanction.dfeta_SanctionDetails,
StartDate = sanction.Sanction.dfeta_StartDate.ToDateOnlyWithDqtBstFix(isLocalTime: true),
EndDate = sanction.Sanction.dfeta_EndDate.ToDateOnlyWithDqtBstFix(isLocalTime: true),
Status = alertStatus
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace TeachingRecordSystem.SupportUi.Pages.Common;

public record AlertInfo
{
public required Guid AlertId { get; init; }
public required string Description { get; init; }
public required string Details { get; init; }
public required DateOnly? StartDate { get; init; }
public required DateOnly? EndDate { get; init; }
public required AlertStatus Status { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace TeachingRecordSystem.SupportUi.Pages.Common;

public enum AlertStatus
{
Active,
Inactive,
Closed
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,8 @@ else
<govuk-summary-list-row-value data-testid="[email protected]">
@if (!string.IsNullOrEmpty(@alert.Details))
{
var lines = @alert.Details.Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
@for (int i = 0; i < lines.Length; i++)
{
<span>@lines[i]</span>
@if (i < lines.Length - 1)
{
<br />
}
}
}
<multi-line-text text="@alert.Details" />
}
</govuk-summary-list-row-value>
</govuk-summary-list-row>
</govuk-summary-list>
Expand All @@ -71,7 +63,7 @@ else
@foreach (var alert in Model.PreviousAlerts)
{
<tr class="govuk-table__row" data-testid="[email protected]">
<td class="govuk-table__cell" data-testid="[email protected]"><a href="@LinkGenerator.Alert(alert.AlertId)" class="govuk-link">@alert.Description</a></td>
<td class="govuk-table__cell" data-testid="[email protected]"><a href="@LinkGenerator.Alert(alert.AlertId)" class="govuk-link" data-testid="[email protected]">@alert.Description</a></td>
<td class="govuk-table__cell" data-testid="[email protected]" use-empty-fallback>@(alert.StartDate.HasValue ? alert.StartDate.Value.ToString("dd/MM/yyyy") : string.Empty)</td>
<td class="govuk-table__cell" data-testid="[email protected]" use-empty-fallback>@(alert.EndDate.HasValue ? alert.EndDate.Value.ToString("dd/MM/yyyy") : string.Empty)</td>
<td class="govuk-table__cell" data-testid="[email protected]">@alert.Status</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
using Microsoft.Xrm.Sdk.Query;
using TeachingRecordSystem.Core.Dqt.Models;
using TeachingRecordSystem.Core.Dqt.Queries;
using TeachingRecordSystem.SupportUi.Pages.Common;

namespace TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail;

public class AlertsModel : PageModel
public partial class AlertsModel : PageModel
{
private readonly ICrmQueryDispatcher _crmQueryDispatcher;

Expand Down Expand Up @@ -81,21 +82,4 @@ private AlertInfo MapSanction(SanctionDetailResult sanction)
Status = alertStatus
};
}

public record AlertInfo
{
public required Guid AlertId { get; init; }
public required string Description { get; init; }
public required string Details { get; init; }
public required DateOnly? StartDate { get; init; }
public required DateOnly? EndDate { get; init; }
public required AlertStatus Status { get; init; }
}

public enum AlertStatus
{
Active,
Inactive,
Closed
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace TeachingRecordSystem.SupportUi.TagHelpers;

public class MultiLineTextTagHelper : TagHelper
{
public string Text { get; set; } = string.Empty;

public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "p";
output.AddClass("govuk-body", HtmlEncoder.Default);

if (!string.IsNullOrWhiteSpace(Text))
{
var lines = Text.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
for (var i = 0; i < lines.Length; i++)
{
output.Content.Append(lines[i]);
if (i < lines.Length - 1)
{
output.Content.AppendHtml("<br />");
}
}
}

output.TagMode = TagMode.StartTagAndEndTag;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
namespace TeachingRecordSystem.Core.Dqt.Tests.QueryTests;

public class CloseSanctionTests : IAsyncLifetime
{
private readonly CrmClientFixture.TestDataScope _dataScope;
private readonly CrmQueryDispatcher _crmQueryDispatcher;

public CloseSanctionTests(CrmClientFixture crmClientFixture)
{
_dataScope = crmClientFixture.CreateTestDataScope();
_crmQueryDispatcher = crmClientFixture.CreateQueryDispatcher();
}

public Task InitializeAsync() => Task.CompletedTask;

public async Task DisposeAsync() => await _dataScope.DisposeAsync();

[Fact]
public async Task QueryExecutesSuccessfully()
{
// Arrange
var sanctionCode = "G1";
var startDate = new DateOnly(2020, 01, 01);
var endDate = new DateOnly(2021, 03, 09);
var createPersonResult = await _dataScope.TestData.CreatePerson(x => x.WithSanction(sanctionCode, startDate: startDate));
var sanction = createPersonResult.Sanctions.Single();

// Act
_ = await _crmQueryDispatcher.ExecuteQuery(new CloseSanctionQuery(sanction.SanctionId, endDate));

// Assert
using var ctx = new DqtCrmServiceContext(_dataScope.OrganizationService);

var closedSanction = ctx.dfeta_sanctionSet.SingleOrDefault(s => s.GetAttributeValue<Guid>(dfeta_sanction.PrimaryIdAttribute) == sanction.SanctionId);
Assert.NotNull(closedSanction);
Assert.Equal(dfeta_sanctionState.Active, closedSanction.StateCode);
Assert.Equal(endDate.FromDateOnlyWithDqtBstFix(isLocalTime: true), closedSanction.dfeta_EndDate);
Assert.True(closedSanction.dfeta_Spent);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
namespace TeachingRecordSystem.Core.Dqt.Tests.QueryTests;

public class UpdateSanctionStateTests : IAsyncLifetime
{
private readonly CrmClientFixture.TestDataScope _dataScope;
private readonly CrmQueryDispatcher _crmQueryDispatcher;

public UpdateSanctionStateTests(CrmClientFixture crmClientFixture)
{
_dataScope = crmClientFixture.CreateTestDataScope();
_crmQueryDispatcher = crmClientFixture.CreateQueryDispatcher();
}

public Task InitializeAsync() => Task.CompletedTask;

public async Task DisposeAsync() => await _dataScope.DisposeAsync();

[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task QueryExecutesSuccessfully(bool setActive)
{
// Arrange
var sanctionCode = "G1";
var startDate = new DateOnly(2020, 01, 01);
var createPersonResult = await _dataScope.TestData.CreatePerson(x => x.WithSanction(sanctionCode, startDate: startDate, isActive: !setActive));
var sanction = createPersonResult.Sanctions.Single();

// Act
_ = await _crmQueryDispatcher.ExecuteQuery(new UpdateSanctionStateQuery(sanction.SanctionId, setActive ? dfeta_sanctionState.Active : dfeta_sanctionState.Inactive));

// Assert
using var ctx = new DqtCrmServiceContext(_dataScope.OrganizationService);
var updatedSanction = ctx.dfeta_sanctionSet.SingleOrDefault(s => s.GetAttributeValue<Guid>(dfeta_sanction.PrimaryIdAttribute) == sanction.SanctionId);
Assert.NotNull(updatedSanction);
Assert.Equal(setActive ? dfeta_sanctionState.Active : dfeta_sanctionState.Inactive, updatedSanction.StateCode);
}
}
Loading