From 4a1e00b352f0e65978286badb50ad8d091a4da1e Mon Sep 17 00:00:00 2001 From: Andrew Horth Date: Wed, 4 Oct 2023 10:33:41 +0100 Subject: [PATCH 1/3] CRM query + UI changes excluding detail link --- .../Dqt/Queries/UpdateSanctionStateQuery.cs | 3 + .../UpdateSanctionStateHandler.cs | 22 +++++++ .../Pages/Alerts/Alert/Index.cshtml | 61 ++++++++++++++++- .../Pages/Alerts/Alert/Index.cshtml.cs | 66 ++++++++++++++++++- .../Pages/Common/AlertInfo.cs | 14 ++++ .../Pages/Common/AlertStatus.cs | 11 ++++ .../Pages/Persons/PersonDetail/Alerts.cshtml | 2 +- .../Persons/PersonDetail/Alerts.cshtml.cs | 19 +----- 8 files changed, 177 insertions(+), 21 deletions(-) create mode 100644 TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Queries/UpdateSanctionStateQuery.cs create mode 100644 TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/QueryHandlers/UpdateSanctionStateHandler.cs create mode 100644 TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Common/AlertInfo.cs create mode 100644 TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Common/AlertStatus.cs diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Queries/UpdateSanctionStateQuery.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Queries/UpdateSanctionStateQuery.cs new file mode 100644 index 000000000..12ce5c3e6 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Queries/UpdateSanctionStateQuery.cs @@ -0,0 +1,3 @@ +namespace TeachingRecordSystem.Core.Dqt.Queries; + +public record UpdateSanctionStateQuery(Guid SanctionId, dfeta_sanctionState State) : ICrmQuery; diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/QueryHandlers/UpdateSanctionStateHandler.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/QueryHandlers/UpdateSanctionStateHandler.cs new file mode 100644 index 000000000..5c9a56bf5 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/QueryHandlers/UpdateSanctionStateHandler.cs @@ -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 +{ + public async Task Execute(UpdateSanctionStateQuery query, IOrganizationServiceAsync organizationService) + { + await organizationService.ExecuteAsync(new UpdateRequest() + { + Target = new dfeta_sanction() + { + Id = query.SanctionId, + StateCode = query.State + } + }); + + return true; + } +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml index 0813e6657..72a9c985a 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml @@ -1,7 +1,66 @@ @page "/alerts/{alertId}" @model IndexModel @{ - ViewBag.Title = "Alert"; + ViewBag.Title = Model.Alert!.Description; +} + +@section BeforeContent { + Back }

@ViewBag.Title

+ +
+
+ + + + + + + + + + + + + + + +
Start dateEnd dateStatus
@(Model.Alert.StartDate.HasValue ? Model.Alert.StartDate.Value.ToString("dd/MM/yyyy") : string.Empty)@(Model.Alert.EndDate.HasValue ? Model.Alert.EndDate.Value.ToString("dd/MM/yyyy") : string.Empty)@Model.Alert.Status
+
+
+ +
+ +
+
+

Details

+ @if (!string.IsNullOrEmpty(@Model.Alert.Details)) + { +

+ @{var lines = @Model.Alert.Details.Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); + @for (int i = 0; i < lines.Length; i++) + { + @lines[i] + @if (i < lines.Length - 1) + { +
+ } + } + } +

+ } + See full case details (opens in new tab) +
+ @if (Model.IsActive) + { + Mark alert as inactive + } + else + { + Remove inactive status + } +
+
+
diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml.cs index 163e6f653..ae93a2c88 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml.cs @@ -1,10 +1,74 @@ +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 static TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail.AlertsModel; 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 OnPost() { + await _crmQueryDispatcher.ExecuteQuery(new UpdateSanctionStateQuery(AlertId, IsActive ? dfeta_sanctionState.Inactive : dfeta_sanctionState.Active)); + + IsActive = !IsActive; + TempData.SetFlashSuccess($"{(IsActive ? "Inactive status removed" : "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 + }; } } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Common/AlertInfo.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Common/AlertInfo.cs new file mode 100644 index 000000000..1d6cb45b0 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Common/AlertInfo.cs @@ -0,0 +1,14 @@ +namespace TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail; + +public partial class AlertsModel +{ + 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; } + } +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Common/AlertStatus.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Common/AlertStatus.cs new file mode 100644 index 000000000..3b8635278 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Common/AlertStatus.cs @@ -0,0 +1,11 @@ +namespace TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail; + +public partial class AlertsModel +{ + public enum AlertStatus + { + Active, + Inactive, + Closed + } +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Alerts.cshtml b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Alerts.cshtml index 7d02fdcc9..25fc5187e 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Alerts.cshtml +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Alerts.cshtml @@ -45,7 +45,7 @@ else
} } - } + } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Alerts.cshtml.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Alerts.cshtml.cs index fefd94bcd..99a3208b8 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Alerts.cshtml.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Alerts.cshtml.cs @@ -6,7 +6,7 @@ namespace TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail; -public class AlertsModel : PageModel +public partial class AlertsModel : PageModel { private readonly ICrmQueryDispatcher _crmQueryDispatcher; @@ -81,21 +81,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 - } } From 733078d5a189be6158ed43dd7d73dd25d30e6f45 Mon Sep 17 00:00:00 2001 From: Andrew Horth Date: Wed, 4 Oct 2023 16:32:50 +0100 Subject: [PATCH 2/3] Added tests for alert details page and supporting CRM queries --- .../Pages/Alerts/Alert/Index.cshtml | 17 +++-- .../Pages/Alerts/Alert/Index.cshtml.cs | 2 +- .../Pages/Common/AlertInfo.cs | 19 +++-- .../Pages/Common/AlertStatus.cs | 13 ++-- .../Pages/Persons/PersonDetail/Alerts.cshtml | 2 +- .../Persons/PersonDetail/Alerts.cshtml.cs | 1 + .../QueryTests/CloseSanctionTests.cs | 40 +++++++++++ .../QueryTests/UpdateSanctionStateTests.cs | 38 ++++++++++ .../AlertTests.cs | 36 ++++++++++ .../PageExtensions.cs | 16 +++++ .../PageTests/Alerts/Alert/IndexTests.cs | 70 +++++++++++++++++++ .../Alerts/CloseAlert/ConfirmTests.cs | 6 +- .../CrmTestData.CreatePerson.cs | 22 ++++-- 13 files changed, 245 insertions(+), 37 deletions(-) create mode 100644 TeachingRecordSystem/tests/TeachingRecordSystem.Core.Dqt.Tests/QueryTests/CloseSanctionTests.cs create mode 100644 TeachingRecordSystem/tests/TeachingRecordSystem.Core.Dqt.Tests/QueryTests/UpdateSanctionStateTests.cs create mode 100644 TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Alerts/Alert/IndexTests.cs diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml index 72a9c985a..c75f45fd2 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml @@ -8,7 +8,7 @@ Back } -

@ViewBag.Title

+

@ViewBag.Title

@@ -22,9 +22,9 @@ - @(Model.Alert.StartDate.HasValue ? Model.Alert.StartDate.Value.ToString("dd/MM/yyyy") : string.Empty) - @(Model.Alert.EndDate.HasValue ? Model.Alert.EndDate.Value.ToString("dd/MM/yyyy") : string.Empty) - @Model.Alert.Status + @(Model.Alert.StartDate.HasValue ? Model.Alert.StartDate.Value.ToString("dd/MM/yyyy") : string.Empty) + @(Model.Alert.EndDate.HasValue ? Model.Alert.EndDate.Value.ToString("dd/MM/yyyy") : string.Empty) + @Model.Alert.Status @@ -38,7 +38,7 @@

Details

@if (!string.IsNullOrEmpty(@Model.Alert.Details)) { -

+

@{var lines = @Model.Alert.Details.Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); @for (int i = 0; i < lines.Length; i++) { @@ -50,16 +50,15 @@ } }

- } - See full case details (opens in new tab) + }
@if (Model.IsActive) { - Mark alert as inactive + Mark alert as inactive } else { - Remove inactive status + Remove inactive status }
diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml.cs index ae93a2c88..b4f5d90c8 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using TeachingRecordSystem.Core.Dqt.Models; using TeachingRecordSystem.Core.Dqt.Queries; -using static TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail.AlertsModel; +using TeachingRecordSystem.SupportUi.Pages.Common; namespace TeachingRecordSystem.SupportUi.Pages.Alerts.Alert; diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Common/AlertInfo.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Common/AlertInfo.cs index 1d6cb45b0..482b37ca9 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Common/AlertInfo.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Common/AlertInfo.cs @@ -1,14 +1,11 @@ -namespace TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail; +namespace TeachingRecordSystem.SupportUi.Pages.Common; -public partial class AlertsModel +public record AlertInfo { - 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 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; } } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Common/AlertStatus.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Common/AlertStatus.cs index 3b8635278..5061b7bfe 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Common/AlertStatus.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Common/AlertStatus.cs @@ -1,11 +1,8 @@ -namespace TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail; +namespace TeachingRecordSystem.SupportUi.Pages.Common; -public partial class AlertsModel +public enum AlertStatus { - public enum AlertStatus - { - Active, - Inactive, - Closed - } + Active, + Inactive, + Closed } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Alerts.cshtml b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Alerts.cshtml index 25fc5187e..0ecc4adb5 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Alerts.cshtml +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Alerts.cshtml @@ -71,7 +71,7 @@ else @foreach (var alert in Model.PreviousAlerts) { - @alert.Description + @alert.Description @(alert.StartDate.HasValue ? alert.StartDate.Value.ToString("dd/MM/yyyy") : string.Empty) @(alert.EndDate.HasValue ? alert.EndDate.Value.ToString("dd/MM/yyyy") : string.Empty) @alert.Status diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Alerts.cshtml.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Alerts.cshtml.cs index 99a3208b8..fab0b5a10 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Alerts.cshtml.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Alerts.cshtml.cs @@ -3,6 +3,7 @@ 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; diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.Core.Dqt.Tests/QueryTests/CloseSanctionTests.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.Core.Dqt.Tests/QueryTests/CloseSanctionTests.cs new file mode 100644 index 000000000..f26b4f881 --- /dev/null +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.Core.Dqt.Tests/QueryTests/CloseSanctionTests.cs @@ -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(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); + } +} diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.Core.Dqt.Tests/QueryTests/UpdateSanctionStateTests.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.Core.Dqt.Tests/QueryTests/UpdateSanctionStateTests.cs new file mode 100644 index 000000000..f8a2ba12c --- /dev/null +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.Core.Dqt.Tests/QueryTests/UpdateSanctionStateTests.cs @@ -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(dfeta_sanction.PrimaryIdAttribute) == sanction.SanctionId); + Assert.NotNull(updatedSanction); + Assert.Equal(setActive ? dfeta_sanctionState.Active : dfeta_sanctionState.Inactive, updatedSanction.StateCode); + } +} diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.EndToEndTests/AlertTests.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.EndToEndTests/AlertTests.cs index fe1e1f2d6..bbc965074 100644 --- a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.EndToEndTests/AlertTests.cs +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.EndToEndTests/AlertTests.cs @@ -39,4 +39,40 @@ public async Task CloseAlert() await page.AssertFlashMessage("Alert closed"); } + + [Theory] + [InlineData(true, "Status changed to inactive")] + [InlineData(false, "Inactive status removed")] + public async Task ViewAlert(bool isActive, string expectedFlashMessage) + { + var startDate = new DateOnly(2021, 10, 01); + var endDate = new DateOnly(2023, 08, 02); + var createPersonResult = await TestData.CreatePerson(b => b.WithSanction("G1", startDate: startDate, endDate: endDate, spent: true, isActive: isActive)); + var personId = createPersonResult.ContactId; + var alertId = createPersonResult.Sanctions.Single().SanctionId; + + await using var context = await HostFixture.CreateBrowserContext(); + var page = await context.NewPageAsync(); + + await page.GoToPersonAlertsPage(personId); + + await page.AssertOnPersonAlertsPage(personId); + + await page.ClickViewAlertPersonAlertsPage(alertId); + + await page.AssertOnAlertDetailPage(alertId); + + if (isActive) + { + await page.ClickDeactivateButton(); + } + else + { + await page.ClickReactivateButton(); + } + + await page.AssertOnAlertDetailPage(alertId); + + await page.AssertFlashMessage(expectedFlashMessage); + } } diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.EndToEndTests/PageExtensions.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.EndToEndTests/PageExtensions.cs index dae1bd07f..dd2dcab95 100644 --- a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.EndToEndTests/PageExtensions.cs +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.EndToEndTests/PageExtensions.cs @@ -26,6 +26,11 @@ public static async Task ClickCloseAlertPersonAlertsPage(this IPage page, Guid a await page.GetByTestId($"close-{alertId}").ClickAsync(); } + public static async Task ClickViewAlertPersonAlertsPage(this IPage page, Guid alertId) + { + await page.GetByTestId($"view-alert-link-{alertId}").ClickAsync(); + } + public static async Task ClickOpenCasesLinkInNavigationBar(this IPage page) { await page.ClickAsync("a:text-is('Open cases')"); @@ -61,6 +66,11 @@ public static async Task AssertOnPersonAlertsPage(this IPage page, Guid personId await page.WaitForUrlPathAsync($"/persons/{personId}/alerts"); } + public static async Task AssertOnAlertDetailPage(this IPage page, Guid alertId) + { + await page.WaitForUrlPathAsync($"/alerts/{alertId}"); + } + public static async Task AssertOnCloseAlertPage(this IPage page, Guid alertId) { await page.WaitForUrlPathAsync($"/alerts/{alertId}/close"); @@ -95,6 +105,12 @@ public static Task ClickConfirmButton(this IPage page) public static Task ClickContinueButton(this IPage page) => ClickButton(page, "Continue"); + public static Task ClickDeactivateButton(this IPage page) + => ClickButton(page, "Mark alert as inactive"); + + public static Task ClickReactivateButton(this IPage page) + => ClickButton(page, "Remove inactive status"); + private static Task ClickButton(this IPage page, string text) => page.ClickAsync($".govuk-button:text-is('{text}')"); } diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Alerts/Alert/IndexTests.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Alerts/Alert/IndexTests.cs new file mode 100644 index 000000000..25a069acc --- /dev/null +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Alerts/Alert/IndexTests.cs @@ -0,0 +1,70 @@ +using TeachingRecordSystem.SupportUi.Pages.Common; + +namespace TeachingRecordSystem.SupportUi.Tests.PageTests.Alerts.Alert; + +public class IndexTests : TestBase +{ + public IndexTests(HostFixture hostFixture) + : base(hostFixture) + { + } + + [Fact] + public async Task Get_WithAlertIdForNonExistentAlert_ReturnsNotFound() + { + // Arrange + var nonExistentAlertId = Guid.NewGuid().ToString(); + + var request = new HttpRequestMessage(HttpMethod.Get, $"/alerts/{nonExistentAlertId}"); + + // Act + var response = await HttpClient.SendAsync(request); + + // Assert + Assert.Equal(StatusCodes.Status404NotFound, (int)response.StatusCode); + } + + [Theory] + [InlineData("2021-01-01", "2022-03-05", true, true, AlertStatus.Closed)] + [InlineData("2021-01-01", null, false, true, AlertStatus.Active)] + [InlineData("2021-01-01", null, false, false, AlertStatus.Inactive)] + [InlineData("2021-01-01", "2022-03-05", true, false, AlertStatus.Inactive)] + public async Task Get_ValidRequest_RendersExpectedContent(string startDateString, string? endDateString, bool isSpent, bool isActive, AlertStatus expectedAlertStatus) + { + // Arrange + var sanctionCode = "G1"; + var sanctionCodeName = (await TestData.ReferenceDataCache.GetSanctionCodeByValue(sanctionCode)).dfeta_name; + var startDate = DateOnly.Parse(startDateString); + DateOnly? endDate = endDateString is not null ? DateOnly.Parse(endDateString) : null; + var person = await TestData.CreatePerson(x => x.WithSanction(sanctionCode, startDate: startDate, endDate: endDate, spent: isSpent, isActive: isActive)); + + var request = new HttpRequestMessage(HttpMethod.Get, $"/alerts/{person.Sanctions.Single().SanctionId}"); + + // Act + var response = await HttpClient.SendAsync(request); + + // Assert + Assert.Equal(StatusCodes.Status200OK, (int)response.StatusCode); + + var doc = await response.GetDocument(); + + Assert.Equal(sanctionCodeName, doc.GetElementByTestId("title")!.TextContent); + + var alertHeader = doc.GetElementByTestId("alert-header"); + Assert.NotNull(alertHeader); + Assert.Equal(startDate.ToString("dd/MM/yyyy"), alertHeader.GetElementByTestId("start-date")!.TextContent); + Assert.Equal(endDate is not null ? endDate.Value.ToString("dd/MM/yyyy") : "-", alertHeader.GetElementByTestId("end-date")!.TextContent); + Assert.Equal(expectedAlertStatus.ToString(), alertHeader.GetElementByTestId("status")!.TextContent); + Assert.NotNull(doc.GetElementByTestId("alert-details")); + if (isActive) + { + Assert.NotNull(doc.GetElementByTestId("deactivate-button")); + Assert.Null(doc.GetElementByTestId("reactivate-button")); + } + else + { + Assert.Null(doc.GetElementByTestId("deactivate-button")); + Assert.NotNull(doc.GetElementByTestId("reactivate-button")); + } + } +} diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Alerts/CloseAlert/ConfirmTests.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Alerts/CloseAlert/ConfirmTests.cs index 0e8d2d692..07dbc2326 100644 --- a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Alerts/CloseAlert/ConfirmTests.cs +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Alerts/CloseAlert/ConfirmTests.cs @@ -11,9 +11,9 @@ public ConfirmTests(HostFixture hostFixture) public async Task Get_WithAlertIdForNonExistentAlert_ReturnsNotFound() { // Arrange - var nonExistentActivityId = Guid.NewGuid().ToString(); + var nonExistentAlertId = Guid.NewGuid().ToString(); - var request = new HttpRequestMessage(HttpMethod.Get, $"/alerts/{nonExistentActivityId}/close/confirm"); + var request = new HttpRequestMessage(HttpMethod.Get, $"/alerts/{nonExistentAlertId}/close/confirm"); // Act var response = await HttpClient.SendAsync(request); @@ -29,7 +29,7 @@ public async Task Get_ValidRequest_RendersExpectedContent() var sanctionCode = "G1"; var sanctionCodeName = (await TestData.ReferenceDataCache.GetSanctionCodeByValue(sanctionCode)).dfeta_name; var startDate = new DateOnly(2021, 01, 01); - var endDate = new DateOnly(2020, 01, 01); + var endDate = new DateOnly(2022, 03, 05); var person = await TestData.CreatePerson(x => x.WithSanction(sanctionCode, startDate: startDate)); var request = new HttpRequestMessage(HttpMethod.Get, $"/alerts/{person.Sanctions.Single().SanctionId}/close/confirm?endDate={endDate:yyyy-MM-dd}"); diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.TestCommon/CrmTestData.CreatePerson.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.TestCommon/CrmTestData.CreatePerson.cs index a0773b7da..81dcc8579 100644 --- a/TeachingRecordSystem/tests/TeachingRecordSystem.TestCommon/CrmTestData.CreatePerson.cs +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.TestCommon/CrmTestData.CreatePerson.cs @@ -99,9 +99,10 @@ public CreatePersonBuilder WithSanction( DateOnly? endDate = null, DateOnly? reviewDate = null, bool spent = false, - string details = "lorem ipsum") + string details = "lorem ipsum", + bool isActive = true) { - _sanctions.Add(new(Guid.NewGuid(), sanctionCode, startDate, endDate, reviewDate, spent, details)); + _sanctions.Add(new(Guid.NewGuid(), sanctionCode, startDate, endDate, reviewDate, spent, details, isActive)); return this; } @@ -194,9 +195,22 @@ public async Task Execute(CrmTestData testData) dfeta_StartDate = sanction.StartDate?.FromDateOnlyWithDqtBstFix(isLocalTime: true), dfeta_EndDate = sanction.EndDate?.FromDateOnlyWithDqtBstFix(isLocalTime: true), dfeta_NoReAppuntildate = sanction.ReviewDate?.FromDateOnlyWithDqtBstFix(isLocalTime: true), - dfeta_Spent = sanction.Spent + dfeta_Spent = sanction.Spent, + dfeta_SanctionDetails = sanction.Details } }); + + if (!sanction.IsActive) + { + txnRequestBuilder.AddRequest(new UpdateRequest() + { + Target = new dfeta_sanction() + { + Id = sanction.SanctionId, + StateCode = dfeta_sanctionState.Inactive + } + }); + } } await txnRequestBuilder.Execute(); @@ -251,5 +265,5 @@ public record CreatePersonResult }; } - public record Sanction(Guid SanctionId, string SanctionCode, DateOnly? StartDate, DateOnly? EndDate, DateOnly? ReviewDate, bool Spent, string Details); + public record Sanction(Guid SanctionId, string SanctionCode, DateOnly? StartDate, DateOnly? EndDate, DateOnly? ReviewDate, bool Spent, string Details, bool IsActive); } From e7df96b202543dd6a871f5606d1ee547fbb7102b Mon Sep 17 00:00:00 2001 From: Andrew Horth Date: Thu, 5 Oct 2023 12:38:18 +0100 Subject: [PATCH 3/3] Amendments after PR comments --- .../Pages/Alerts/Alert/Index.cshtml | 27 ++++++---------- .../Pages/Alerts/Alert/Index.cshtml.cs | 18 ++++++++--- .../Pages/Persons/PersonDetail/Alerts.cshtml | 10 +----- .../TagHelpers/MultiLineTextTagHelper.cs | 31 +++++++++++++++++++ 4 files changed, 55 insertions(+), 31 deletions(-) create mode 100644 TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/TagHelpers/MultiLineTextTagHelper.cs diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml index c75f45fd2..967856b01 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml @@ -38,28 +38,19 @@

Details

@if (!string.IsNullOrEmpty(@Model.Alert.Details)) { -

- @{var lines = @Model.Alert.Details.Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); - @for (int i = 0; i < lines.Length; i++) - { - @lines[i] - @if (i < lines.Length - 1) - { -
- } - } - } -

- } -
+ + } @if (Model.IsActive) { - Mark alert as inactive + + Mark alert as inactive + } else { - Remove inactive status - } - +
+ Remove inactive status +
+ }
diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml.cs index b4f5d90c8..6deb53da0 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Alerts/Alert/Index.cshtml.cs @@ -29,12 +29,22 @@ public IndexModel( public Guid? PersonId { get; set; } - public async Task OnPost() + public async Task OnPostSetActive() { - await _crmQueryDispatcher.ExecuteQuery(new UpdateSanctionStateQuery(AlertId, IsActive ? dfeta_sanctionState.Inactive : dfeta_sanctionState.Active)); + await _crmQueryDispatcher.ExecuteQuery(new UpdateSanctionStateQuery(AlertId, dfeta_sanctionState.Active)); - IsActive = !IsActive; - TempData.SetFlashSuccess($"{(IsActive ? "Inactive status removed" : "Status changed to inactive")}"); + IsActive = true; + TempData.SetFlashSuccess("Inactive status removed"); + + return Redirect(_linkGenerator.Alert(AlertId)); + } + + public async Task OnPostSetInactive() + { + await _crmQueryDispatcher.ExecuteQuery(new UpdateSanctionStateQuery(AlertId, dfeta_sanctionState.Inactive)); + + IsActive = false; + TempData.SetFlashSuccess("Status changed to inactive"); return Redirect(_linkGenerator.Alert(AlertId)); } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Alerts.cshtml b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Alerts.cshtml index 0ecc4adb5..638412ff2 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Alerts.cshtml +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Alerts.cshtml @@ -36,15 +36,7 @@ else @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++) - { - @lines[i] - @if (i < lines.Length - 1) - { -
- } - } + }
diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/TagHelpers/MultiLineTextTagHelper.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/TagHelpers/MultiLineTextTagHelper.cs new file mode 100644 index 000000000..ac8af2c35 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/TagHelpers/MultiLineTextTagHelper.cs @@ -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("
"); + } + } + } + + output.TagMode = TagMode.StartTagAndEndTag; + } +}