Skip to content

Commit

Permalink
Added support for showing MQ deleted events in changelog UI + Tests
Browse files Browse the repository at this point in the history
Removed tabs

Added filter to only get MQ deleted events for the moment

Amendments following PR comments

Removed unused properties
  • Loading branch information
hortha committed Jan 5, 2024
1 parent 8043957 commit 9cf425a
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 9cf425a

Please sign in to comment.