Skip to content

Commit

Permalink
Include alert events in Change history (#1624)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrew Horth <[email protected]>
  • Loading branch information
gunndabad and hortha authored Oct 31, 2024
1 parent ac184b9 commit 24c9317
Show file tree
Hide file tree
Showing 11 changed files with 607 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace TeachingRecordSystem.Core;

public static class TaskEnumerableExtensions
{
public static async IAsyncEnumerable<T> ToAsyncEnumerable<T>(this Task<T[]> task)
{
var result = await task;

foreach (var r in result)
{
yield return r;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public async Task OnGet()
OpenAlerts = authorizedAlerts.Where(a => a.Alert.IsOpen).OrderBy(a => a.Alert.StartDate).ThenBy(a => a.Alert.AlertType.Name).ToArray();
ClosedAlerts = authorizedAlerts.Where(a => !a.Alert.IsOpen).OrderBy(a => a.Alert.StartDate).ThenBy(a => a.Alert.EndDate).ThenBy(a => a.Alert.AlertType.Name).ToArray();

CanAddAlert = await (await referenceDataCache.GetAlertTypes(activeOnly: true))
CanAddAlert = await referenceDataCache.GetAlertTypes(activeOnly: true)
.ToAsyncEnumerable()
.AnyAwaitAsync(async at => (await authorizationService.AuthorizeForAlertTypeAsync(User, at.AlertTypeId, Permissions.Alerts.Write)) is { Succeeded: true });

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using TeachingRecordSystem.Core.DataStore.Postgres;
using TeachingRecordSystem.Core.Dqt.Models;
using TeachingRecordSystem.Core.Dqt.Queries;
using TeachingRecordSystem.SupportUi.Infrastructure.Security;
using TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail.Timeline.Events;

namespace TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail;

public class ChangeHistoryModel(ICrmQueryDispatcher crmQueryDispatcher, TrsDbContext dbContext, TrsLinkGenerator linkGenerator) : PageModel
public class ChangeHistoryModel(
ICrmQueryDispatcher crmQueryDispatcher,
TrsDbContext dbContext,
ReferenceDataCache referenceDataCache,
IAuthorizationService authorizationService,
TrsLinkGenerator linkGenerator) : PageModel
{
private const int PageSize = 10;

Expand Down Expand Up @@ -50,8 +57,32 @@ public async Task<IActionResult> OnGet()
nameof(MandatoryQualificationCreatedEvent),
nameof(MandatoryQualificationDqtImportedEvent),
nameof(MandatoryQualificationMigratedEvent),
nameof(AlertCreatedEvent),
nameof(AlertUpdatedEvent),
nameof(AlertDeletedEvent),
nameof(AlertMigratedEvent),
nameof(AlertDqtDeactivatedEvent),
nameof(AlertDqtImportedEvent),
nameof(AlertDqtReactivatedEvent),
};

var alertEventTypes = eventTypes.Where(et => et.StartsWith("Alert")).ToArray();

var alertTypesWithReadPermission = await referenceDataCache.GetAlertTypes(activeOnly: false)
.ToAsyncEnumerable()
.SelectAwait(async at => (
AlertType: at,
CanRead: (await authorizationService.AuthorizeForAlertTypeAsync(User, at.AlertTypeId, Permissions.Alerts.Read)) is { Succeeded: true }))
.Where(t => t.CanRead)
.ToArrayAsync();

var alertTypeIdsWithReadPermission = alertTypesWithReadPermission.Select(at => at.AlertType.AlertTypeId).ToArray();

var dqtSanctionCodesWithReadPermission = alertTypesWithReadPermission
.Select(at => at.AlertType.DqtSanctionCode)
.Where(sc => sc is not null)
.ToArray();

var eventsWithUser = await dbContext.Database
.SqlQuery<EventWithUser>($"""
SELECT
Expand All @@ -72,6 +103,13 @@ users as u ON
WHERE
e.person_id = {PersonId}
AND e.event_name = any ({eventTypes})
-- Only return alerts that have an alert type (or DQT sanction code) that the user is authorized to Read
AND (
NOT (e.event_name = any({alertEventTypes}))
OR (e.payload #>> Array['Alert','AlertTypeId'])::uuid = any({alertTypeIdsWithReadPermission})
OR (e.payload #>> Array['Alert','DqtSanctionCode','Value']) = any({dqtSanctionCodesWithReadPermission})
)
""")
.ToListAsync();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
@using TeachingRecordSystem.Core.Events
@using TeachingRecordSystem.Core.Services.Files
@inject IFileService FileService
@inject ReferenceDataCache ReferenceDataCache
@model TimelineItem<TimelineEvent<AlertCreatedEvent>>
@{
var createdEvent = Model.ItemModel.Event;
var alert = createdEvent.Alert;
var alertType = alert.AlertTypeId is not null ? await ReferenceDataCache.GetAlertTypeById(alert.AlertTypeId.Value) : null;
var evidenceFileUrl = createdEvent.EvidenceFile is not null ? await FileService.GetFileUrl(createdEvent.EvidenceFile!.FileId, TimeSpan.FromMinutes(15)) : null;
}

<div class="moj-timeline__item govuk-!-padding-bottom-2" data-testid="timeline-item-alert-created-event">
<div class="moj-timeline__header">
<h2 class="moj-timeline__title">Alert added</h2>
</div>
<p class="moj-timeline__date">
<span data-testid="raised-by">By @Model.ItemModel.RaisedByUser.Name on</span>
<time datetime="@Model.Timestamp.ToString("O")" data-testid="timeline-item-time">@Model.FormattedTimestamp</time>
</p>
<div class="moj-timeline__description">
<govuk-summary-list>
@if (alertType is not null)
{
<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" use-empty-fallback>@alertType?.Name</govuk-summary-list-row-value>
</govuk-summary-list-row>
}
else
{
<govuk-summary-list-row>
<govuk-summary-list-row-key>DQT sanction code</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="sanction-code" use-empty-fallback>@alert.DqtSanctionCode?.Value</govuk-summary-list-row-value>
</govuk-summary-list-row>
<govuk-summary-list-row>
<govuk-summary-list-row-key>DQT sanction name</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="sanction-name" use-empty-fallback>@alert.DqtSanctionCode?.Name</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">@alert.StartDate?.ToString("d MMMM yyyy")</govuk-summary-list-row-value>
</govuk-summary-list-row>
</govuk-summary-list>

<govuk-details class="govuk-!-margin-bottom-2">
<govuk-details-summary>Reason for adding alert</govuk-details-summary>
<govuk-details-text>
<govuk-summary-list>
<govuk-summary-list-row>
<govuk-summary-list-row-key>Reason</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="reason" use-empty-fallback>@createdEvent.AddReason</govuk-summary-list-row-value>
</govuk-summary-list-row>
<govuk-summary-list-row>
<govuk-summary-list-row-key>Reason details</govuk-summary-list-row-key>
<govuk-summary-list-row-value>
@if (createdEvent.AddReasonDetail is not null)
{
<multi-line-text text="@createdEvent.AddReasonDetail" />
}
else
{
<span use-empty-fallback></span>
}
</govuk-summary-list-row-value>
</govuk-summary-list-row>
<govuk-summary-list-row>
<govuk-summary-list-row-key>Evidence</govuk-summary-list-row-key>
<govuk-summary-list-row-value>
@if (evidenceFileUrl is not null)
{
<a href="@evidenceFileUrl" class="govuk-link" rel="noreferrer noopener" target="_blank" data-testid="uploaded-evidence-link">@($"{createdEvent.EvidenceFile!.Name} (opens in new tab)")</a>
}
else
{
<span use-empty-fallback></span>
}
</govuk-summary-list-row-value>
</govuk-summary-list-row>
</govuk-summary-list>
</govuk-details-text>
</govuk-details>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
@using TeachingRecordSystem.Core.Events
@using TeachingRecordSystem.Core.Services.Files
@inject IFileService FileService
@inject ReferenceDataCache ReferenceDataCache
@model TimelineItem<TimelineEvent<AlertDeletedEvent>>
@{
var deletedEvent = Model.ItemModel.Event;
var alert = deletedEvent.Alert;
var alertType = alert.AlertTypeId is not null ? await ReferenceDataCache.GetAlertTypeById(alert.AlertTypeId.Value) : null;
var evidenceFileUrl = deletedEvent.EvidenceFile is not null ? await FileService.GetFileUrl(deletedEvent.EvidenceFile!.FileId, TimeSpan.FromMinutes(15)) : null;
}

<div class="moj-timeline__item govuk-!-padding-bottom-2" data-testid="timeline-item-alert-deleted-event">
<div class="moj-timeline__header">
<h2 class="moj-timeline__title">Alert deleted</h2>
</div>
<p class="moj-timeline__date">
<span data-testid="raised-by">By @Model.ItemModel.RaisedByUser.Name on</span>
<time datetime="@Model.Timestamp.ToString("O")" data-testid="timeline-item-time">@Model.FormattedTimestamp</time>
</p>
<div class="moj-timeline__description">
<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" use-empty-fallback>@alertType?.Name</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">@alert.StartDate?.ToString("d MMMM yyyy")</govuk-summary-list-row-value>
</govuk-summary-list-row>
</govuk-summary-list>

<govuk-details class="govuk-!-margin-bottom-2">
<govuk-details-summary>Reason for deletion</govuk-details-summary>
<govuk-details-text>
<govuk-summary-list>
<govuk-summary-list-row>
<govuk-summary-list-row-key>Deletion details</govuk-summary-list-row-key>
<govuk-summary-list-row-value>
@if (deletedEvent.DeletionReasonDetail is not null)
{
<multi-line-text text="@deletedEvent.DeletionReasonDetail" />
}
else
{
<span use-empty-fallback></span>
}
</govuk-summary-list-row-value>
</govuk-summary-list-row>
<govuk-summary-list-row>
<govuk-summary-list-row-key>Evidence</govuk-summary-list-row-key>
<govuk-summary-list-row-value>
@if (evidenceFileUrl is not null)
{
<a href="@evidenceFileUrl" class="govuk-link" rel="noreferrer noopener" target="_blank" data-testid="uploaded-evidence-link">@($"{deletedEvent.EvidenceFile!.Name} (opens in new tab)")</a>
}
else
{
<span use-empty-fallback></span>
}
</govuk-summary-list-row-value>
</govuk-summary-list-row>
</govuk-summary-list>
</govuk-details-text>
</govuk-details>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
@using TeachingRecordSystem.Core.Events
@using TeachingRecordSystem.Core.Services.Files
@inject IFileService FileService
@inject ReferenceDataCache ReferenceDataCache
@model TimelineItem<TimelineEvent<AlertDqtDeactivatedEvent>>
@{
var dqtDeactivatedEvent = Model.ItemModel.Event;
var alert = dqtDeactivatedEvent.Alert;
}

<div class="moj-timeline__item govuk-!-padding-bottom-2" data-testid="timeline-item-alert-deleted-event">
<div class="moj-timeline__header">
<h2 class="moj-timeline__title">Alert deactivated</h2>
</div>
<p class="moj-timeline__date">
<span data-testid="raised-by">By @Model.ItemModel.RaisedByUser.Name on</span>
<time datetime="@Model.Timestamp.ToString("O")" data-testid="timeline-item-time">@Model.FormattedTimestamp</time>
</p>
<div class="moj-timeline__description">
<govuk-details class="govuk-!-margin-bottom-2">
<govuk-details-summary>Deactivated data</govuk-details-summary>
<govuk-details-text>
<govuk-summary-list>
<govuk-summary-list-row>
<govuk-summary-list-row-key>DQT sanction code</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="sanction-code" use-empty-fallback>@alert.DqtSanctionCode?.Value</govuk-summary-list-row-value>
</govuk-summary-list-row>
<govuk-summary-list-row>
<govuk-summary-list-row-key>DQT sanction name</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="sanction-name" use-empty-fallback>@alert.DqtSanctionCode?.Name</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" use-empty-fallback>@alert.StartDate?.ToString("d MMMM yyyy")</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" use-empty-fallback>@alert.Details</govuk-summary-list-row-value>
</govuk-summary-list-row>
<govuk-summary-list-row>
<govuk-summary-list-row-key>External link</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="external-link" use-empty-fallback>@alert.ExternalLink</govuk-summary-list-row-value>
</govuk-summary-list-row>
<govuk-summary-list-row>
<govuk-summary-list-row-key>End date</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="end-date" use-empty-fallback>@alert.EndDate?.ToString("d MMMM yyyy")</govuk-summary-list-row-value>
</govuk-summary-list-row>
<govuk-summary-list-row>
<govuk-summary-list-row-key>DQT spent</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="dqt-spent" use-empty-fallback>@alert.DqtSpent</govuk-summary-list-row-value>
</govuk-summary-list-row>
</govuk-summary-list>
</govuk-details-text>
</govuk-details>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@using TeachingRecordSystem.Core.Events
@using TeachingRecordSystem.Core.Services.Files
@inject IFileService FileService
@inject ReferenceDataCache ReferenceDataCache
@model TimelineItem<TimelineEvent<AlertDqtImportedEvent>>
@{
var dqtImportedEvent = Model.ItemModel.Event;
var alert = dqtImportedEvent.Alert;
}

<div class="moj-timeline__item govuk-!-padding-bottom-2" data-testid="timeline-item-alert-deleted-event">
<div class="moj-timeline__header">
<h2 class="moj-timeline__title">Alert imported</h2>
</div>
<p class="moj-timeline__date">
<span data-testid="raised-by">By @Model.ItemModel.RaisedByUser.Name on</span>
<time datetime="@Model.Timestamp.ToString("O")" data-testid="timeline-item-time">@Model.FormattedTimestamp</time>
</p>
<div class="moj-timeline__description">
<govuk-summary-list>
<govuk-summary-list-row>
<govuk-summary-list-row-key>DQT sanction code</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="sanction-code" use-empty-fallback>@alert.DqtSanctionCode?.Value</govuk-summary-list-row-value>
</govuk-summary-list-row>
<govuk-summary-list-row>
<govuk-summary-list-row-key>DQT sanction name</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="sanction-name" use-empty-fallback>@alert.DqtSanctionCode?.Name</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" use-empty-fallback>@alert.StartDate?.ToString("d MMMM yyyy")</govuk-summary-list-row-value>
</govuk-summary-list-row>
</govuk-summary-list>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@using TeachingRecordSystem.Core.Events
@using TeachingRecordSystem.Core.Services.Files
@inject IFileService FileService
@inject ReferenceDataCache ReferenceDataCache
@model TimelineItem<TimelineEvent<AlertDqtReactivatedEvent>>
@{
var dqtReactivatedEvent = Model.ItemModel.Event;
var alert = dqtReactivatedEvent.Alert;
}

<div class="moj-timeline__item govuk-!-padding-bottom-2" data-testid="timeline-item-alert-deleted-event">
<div class="moj-timeline__header">
<h2 class="moj-timeline__title">Alert reactivated</h2>
</div>
<p class="moj-timeline__date">
<span data-testid="raised-by">By @Model.ItemModel.RaisedByUser.Name on</span>
<time datetime="@Model.Timestamp.ToString("O")" data-testid="timeline-item-time">@Model.FormattedTimestamp</time>
</p>
<div class="moj-timeline__description">
<govuk-summary-list>
<govuk-summary-list-row>
<govuk-summary-list-row-key>DQT sanction code</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="sanction-code" use-empty-fallback>@alert.DqtSanctionCode?.Value</govuk-summary-list-row-value>
</govuk-summary-list-row>
<govuk-summary-list-row>
<govuk-summary-list-row-key>DQT sanction name</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="sanction-name" use-empty-fallback>@alert.DqtSanctionCode?.Name</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" use-empty-fallback>@alert.StartDate?.ToString("d MMMM yyyy")</govuk-summary-list-row-value>
</govuk-summary-list-row>
</govuk-summary-list>
</div>
</div>
Loading

0 comments on commit 24c9317

Please sign in to comment.