Skip to content

Commit

Permalink
Added support for showing MQ deleted events in change log (#1060)
Browse files Browse the repository at this point in the history
  • Loading branch information
hortha authored Jan 8, 2024
1 parent 8043957 commit 0a2cf43
Show file tree
Hide file tree
Showing 12 changed files with 404 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Text.Json;
using TeachingRecordSystem.Core.Events;

namespace TeachingRecordSystem.Core.DataStore.Postgres.Models;
Expand All @@ -15,7 +14,7 @@ public class Event
public static Event FromEventBase(EventBase @event, DateTime? inserted)
{
var eventName = @event.GetEventName();
var payload = JsonSerializer.Serialize(@event, inputType: @event.GetType(), EventBase.JsonSerializerOptions);
var payload = @event.Serialize();

return new Event()
{
Expand All @@ -29,10 +28,6 @@ public static Event FromEventBase(EventBase @event, DateTime? inserted)

public EventBase ToEventBase()
{
var eventTypeName = $"{typeof(EventBase).Namespace}.{EventName}";
var eventType = Type.GetType(eventTypeName) ??
throw new Exception($"Could not find event type '{eventTypeName}'.");

return (EventBase)JsonSerializer.Deserialize(Payload, eventType, EventBase.JsonSerializerOptions)!;
return EventBase.Deserialize(Payload, EventName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,15 @@ public abstract record EventBase
public required RaisedByUserInfo RaisedBy { get; init; }

public string GetEventName() => GetType().Name;

public string Serialize() => JsonSerializer.Serialize(this, inputType: GetType(), JsonSerializerOptions);

public static EventBase Deserialize(string payload, string eventName)
{
var eventTypeName = $"{typeof(EventBase).Namespace}.{eventName}";
var eventType = Type.GetType(eventTypeName) ??
throw new Exception($"Could not find event type '{eventTypeName}'.");

return (EventBase)JsonSerializer.Deserialize(payload, eventType, JsonSerializerOptions)!;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<td class="govuk-table__cell" data-testid="[email protected]"><a href="@LinkGenerator.EditChangeRequest(changeRequestInfo.RequestReference)" class="govuk-link">@changeRequestInfo.RequestReference</a></td>
<td class="govuk-table__cell" data-testid="[email protected]">@changeRequestInfo.Customer</td>
<td class="govuk-table__cell" data-testid="[email protected]">@changeRequestInfo.ChangeType</td>
<td class="govuk-table__cell" data-testid="[email protected]">@changeRequestInfo.CreatedOn.ToString("dd/MM/yyyy")</td>
<td class="govuk-table__cell" data-testid="[email protected]">@changeRequestInfo.CreatedOn.ToString("d MMMM yyyy")</td>
</tr>
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@page "/persons/{personId}/changelog"
@using TeachingRecordSystem.Core.Events
@using TeachingRecordSystem.SupportUi.Pages.Common;
@using TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail.Timeline.Events
@model TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail.ChangeLogModel
@{
Layout = "Layout";
Expand All @@ -23,7 +24,7 @@ else
{
var viewName = timelineItem.ItemType switch
{
TimelineItemType.Event => $"./Timeline/Events/{((EventBase)timelineItem.ItemModel).GetEventName()}.cshtml",
TimelineItemType.Event => $"./Timeline/Events/{((TimelineEvent)timelineItem.ItemModel).Event.GetEventName()}.cshtml",
_ => $"./Timeline/{timelineItem.ItemType}.cshtml"
};

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

namespace TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail;

public class ChangeLogModel(ICrmQueryDispatcher crmQueryDispatcher) : PageModel
public class ChangeLogModel(ICrmQueryDispatcher crmQueryDispatcher, IDbContextFactory<TrsDbContext> dbContextFactory) : PageModel
{
[FromRoute]
public Guid PersonId { get; set; }
Expand Down Expand Up @@ -40,6 +45,44 @@ public async Task<IActionResult> OnGet()

var notesResult = await crmQueryDispatcher.ExecuteQuery(new GetNotesByContactIdQuery(PersonId));

using var dbContext = await dbContextFactory.CreateDbContextAsync();
var personIdString = PersonId.ToString();
var eventsWithUser = await dbContext.Database
.SqlQuery<EventWithUser>($"""
SELECT
e.event_name,
e.payload as event_payload,
u.user_id as trs_user_user_id,
u.active as trs_user_active,
u.user_type as trs_user_user_type,
u.name as trs_user_name,
u.email as trs_user_email,
u.azure_ad_user_id as trs_user_azure_ad_user_id,
u.roles as trs_user_roles,
u.dqt_user_id as trs_user_dqt_user_id,
CASE
WHEN e.payload #>> Array['RaisedBy','DqtUserId'] is not null THEN
(e.payload #>> Array['RaisedBy','DqtUserId'])::uuid
ELSE
null
END as dqt_user_id,
e.payload #>> Array['RaisedBy','DqtUserName'] as dqt_user_name
FROM
events as e
LEFT JOIN
users as u ON
CASE
WHEN e.payload #>> Array['RaisedBy','DqtUserId'] is null THEN
(e.payload ->> 'RaisedBy')::uuid
ELSE
null
END = u.user_id
WHERE
e.payload ->> 'PersonId' = {personIdString}
AND e.event_name = 'MandatoryQualificationDeletedEvent'
""")
.ToListAsync();

TimelineItems = notesResult
.Annotations.Select(n => (TimelineItem)new TimelineItem<Annotation>(
TimelineItemType.Annotation,
Expand All @@ -53,9 +96,70 @@ public async Task<IActionResult> OnGet()
TimelineItemType.Task,
t.ModifiedOn.WithDqtBstFix(isLocalTime: true)!.Value,
t)))
.Concat(eventsWithUser.Select(MapTimelineEvent))
.OrderByDescending(i => i.Timestamp)
.ToArray();

return Page();
}

private TimelineItem MapTimelineEvent(EventWithUser eventWithUser)
{
var @event = EventBase.Deserialize(eventWithUser.EventPayload, eventWithUser.EventName);
User? user = null;
if (eventWithUser.TrsUserUserId is not null)
{
user = new User
{
UserId = eventWithUser.TrsUserUserId.Value,
Active = eventWithUser.TrsUserActive!.Value,
UserType = eventWithUser.TrsUserUserType!.Value,
Name = eventWithUser.TrsUserName!,
Email = eventWithUser.TrsUserEmail,
AzureAdUserId = eventWithUser.TrsUserAzureAdUserId,
Roles = eventWithUser.TrsUserRoles!,
DqtUserId = eventWithUser.TrsUserDqtUserId
};
}

DqtUser? dqtUser = null;
if (eventWithUser.DqtUserId is not null)
{
dqtUser = new DqtUser
{
UserId = eventWithUser.DqtUserId.Value,
Name = eventWithUser.DqtUserName!
};
}

RaisedByUser raiseByUser = new()
{
User = user,
DqtUser = dqtUser
};

var timelineEventType = typeof(TimelineEvent<>).MakeGenericType(@event.GetType()!);
var timelineEvent = (TimelineEvent)Activator.CreateInstance(timelineEventType, @event, raiseByUser)!;
var timelineItemType = typeof(TimelineItem<>).MakeGenericType(timelineEventType);
return (TimelineItem)Activator.CreateInstance(timelineItemType, TimelineItemType.Event, timelineEvent.Event.CreatedUtc, timelineEvent)!;
}

/// <summary>
/// Flattened out record to allow Event, TRS User and DQT User to be returned in a single SQL query
/// </summary>
private record EventWithUser
{
public required string EventName { get; init; }
public required string EventPayload { get; init; }
public required Guid? TrsUserUserId { get; init; }
public required bool? TrsUserActive { get; set; }
public required UserType? TrsUserUserType { get; init; }
public required string? TrsUserName { get; set; }
public required string? TrsUserEmail { get; set; }
public required string? TrsUserAzureAdUserId { get; set; }
public required string[]? TrsUserRoles { get; set; }
public required Guid? TrsUserDqtUserId { get; set; }
public required Guid? DqtUserId { get; set; }
public required string? DqtUserName { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail.Timeline.Events;

public record DqtUser
{
public required Guid? UserId { get; set; }
public required string? Name { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
@using TeachingRecordSystem.Core.Events
@using TeachingRecordSystem.Core.Services.Files
@inject IFileService FileService
@model TimelineItem<TimelineEvent<MandatoryQualificationDeletedEvent>>
@{
var deletedEvent = Model.ItemModel.Event;
var mandatoryQualification = deletedEvent.MandatoryQualification;
var evidenceFileUrl = deletedEvent.EvidenceFile is not null ? await FileService.GetFileUrl(deletedEvent.EvidenceFile!.FileId, TimeSpan.FromMinutes(15)) : null;
var raisedByUser = Model.ItemModel.RaisedByUser;
var raisedBy = deletedEvent.RaisedBy.IsDqtUser ? (raisedByUser.DqtUser?.Name ?? "Unknown") : (raisedByUser.User?.Name) ?? "Unknown";
}

<div class="moj-timeline__item govuk-!-padding-bottom-2" data-testid="timeline-item">
<div class="moj-timeline__header">
<h2 class="moj-timeline__title">Mandatory qualification deleted</h2>
</div>
<p class="moj-timeline__date">
<span data-testid="raised-by">By @raisedBy on</span>
<time datetime="@deletedEvent.CreatedUtc.ToString("O")" data-testid="timeline-item-time">
@deletedEvent.CreatedUtc.ToString("dd MMMMM yyyy 'at' h:mm tt")
</time>
</p>
<div class="moj-timeline__description">
<govuk-summary-list>
<govuk-summary-list-row>
<govuk-summary-list-row-key>Reason for deleting</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="deletion-reason">@(!string.IsNullOrEmpty(deletedEvent.DeletionReason) ? deletedEvent.DeletionReason : "None")</govuk-summary-list-row-value>
</govuk-summary-list-row>
<govuk-summary-list-row>
<govuk-summary-list-row-key>More detail about the reason for deleting</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="deletion-reason-detail"><multi-line-text text="@(!string.IsNullOrEmpty(deletedEvent.DeletionReasonDetail) ? deletedEvent.DeletionReasonDetail : "None")" /></govuk-summary-list-row-value>
</govuk-summary-list-row>
<govuk-summary-list-row>
<govuk-summary-list-row-key>Training provider</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="provider">@(mandatoryQualification.Provider is not null ? mandatoryQualification.Provider.Name : "None" )</govuk-summary-list-row-value>
</govuk-summary-list-row>
<govuk-summary-list-row>
<govuk-summary-list-row-key>Specialism</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="specialism">@(mandatoryQualification.Specialism.HasValue ? mandatoryQualification.Specialism.Value.GetTitle() : "None")</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">@(mandatoryQualification.StartDate.HasValue ? mandatoryQualification.StartDate.Value.ToString("d MMMM yyyy") : "None")</govuk-summary-list-row-value>
</govuk-summary-list-row>
<govuk-summary-list-row>
<govuk-summary-list-row-key>Status</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="status">@(mandatoryQualification.Status.HasValue ? mandatoryQualification.Status.Value.GetTitle() : "None")</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">@(mandatoryQualification.EndDate.HasValue ? mandatoryQualification.EndDate.Value.ToString("d MMMM yyyy") : "None")</govuk-summary-list-row-value>
</govuk-summary-list-row>
</govuk-summary-list>

@if (evidenceFileUrl is not null)
{
<p class="govuk-body">
<a href="@evidenceFileUrl" class="govuk-link" rel="noreferrer noopener" target="_blank" data-testid="uploaded-evidence-link">@($"{deletedEvent.EvidenceFile!.Name} (opens in new tab)")</a>
</p>
}
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using TeachingRecordSystem.Core.DataStore.Postgres.Models;

namespace TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail.Timeline.Events;

public record RaisedByUser
{
public required User? User { get; set; }
public required DqtUser? DqtUser { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using TeachingRecordSystem.Core.Events;

namespace TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail.Timeline.Events;

public record TimelineEvent(EventBase Event, RaisedByUser RaisedByUser);

public record TimelineEvent<TEvent>(TEvent Event, RaisedByUser RaisedByUser) : TimelineEvent(Event, RaisedByUser) where TEvent : EventBase
{
public new TEvent Event => (TEvent)base.Event;
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ public async Task Get_ValidRequest_RendersExpectedContent()
Assert.Equal(createIncident1Result.TicketNumber, tableRow1.GetElementByTestId($"request-reference-{createIncident1Result.TicketNumber}")!.TextContent);
Assert.Equal($"{createPerson1Result.FirstName} {createPerson1Result.LastName}", tableRow1.GetElementByTestId($"name-{createIncident1Result.TicketNumber}")!.TextContent);
Assert.Equal(createIncident1Result.SubjectTitle, tableRow1.GetElementByTestId($"change-type-{createIncident1Result.TicketNumber}")!.TextContent);
Assert.Equal(createIncident1Result.CreatedOn.ToString("dd/MM/yyyy"), tableRow1.GetElementByTestId($"created-on-{createIncident1Result.TicketNumber}")!.TextContent);
Assert.Equal(createIncident1Result.CreatedOn.ToString("d MMMM yyyy"), tableRow1.GetElementByTestId($"created-on-{createIncident1Result.TicketNumber}")!.TextContent);
var tableRow2 = doc.GetElementByTestId($"change-request-{createIncident2Result.TicketNumber}");
Assert.NotNull(tableRow2);
Assert.Equal(createIncident2Result.TicketNumber, tableRow2.GetElementByTestId($"request-reference-{createIncident2Result.TicketNumber}")!.TextContent);
Assert.Equal($"{createPerson2Result.FirstName} {createPerson2Result.LastName}", tableRow2.GetElementByTestId($"name-{createIncident2Result.TicketNumber}")!.TextContent);
Assert.Equal(createIncident2Result.SubjectTitle, tableRow2.GetElementByTestId($"change-type-{createIncident2Result.TicketNumber}")!.TextContent);
Assert.Equal(createIncident2Result.CreatedOn.ToString("dd/MM/yyyy"), tableRow2.GetElementByTestId($"created-on-{createIncident2Result.TicketNumber}")!.TextContent);
Assert.Equal(createIncident2Result.CreatedOn.ToString("d MMMM yyyy"), tableRow2.GetElementByTestId($"created-on-{createIncident2Result.TicketNumber}")!.TextContent);
}

[Fact]
Expand Down
Loading

0 comments on commit 0a2cf43

Please sign in to comment.