feat(ui): drill-in from Notifications to Audit Log (#23 M7)
This commit is contained in:
@@ -163,6 +163,14 @@
|
|||||||
<td><TimestampDisplay Value="@n.CreatedAt" Format="yyyy-MM-dd HH:mm" /></td>
|
<td><TimestampDisplay Value="@n.CreatedAt" Format="yyyy-MM-dd HH:mm" /></td>
|
||||||
<td><TimestampDisplay Value="@n.DeliveredAt" Format="yyyy-MM-dd HH:mm" NullText="—" /></td>
|
<td><TimestampDisplay Value="@n.DeliveredAt" Format="yyyy-MM-dd HH:mm" NullText="—" /></td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
|
@* Bundle D (#23 M7-T10) drill-in: NotificationId is the audit
|
||||||
|
CorrelationId, so the link deep-links into the central Audit
|
||||||
|
Log pre-filtered to this notification's lifecycle events. *@
|
||||||
|
<a class="btn btn-outline-secondary btn-sm me-1"
|
||||||
|
href="/audit/log?correlationId=@n.NotificationId"
|
||||||
|
data-test="audit-link-@n.NotificationId">
|
||||||
|
View audit history
|
||||||
|
</a>
|
||||||
@if (n.Status == "Parked")
|
@if (n.Status == "Parked")
|
||||||
{
|
{
|
||||||
<button class="btn btn-outline-success btn-sm me-1"
|
<button class="btn btn-outline-success btn-sm me-1"
|
||||||
@@ -174,10 +182,6 @@
|
|||||||
Discard
|
Discard
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
<span class="text-muted small">—</span>
|
|
||||||
}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,6 +177,62 @@ public class NotificationReportPageTests : BunitContext
|
|||||||
Assert.Contains("outbox query backend unavailable", cut.Markup));
|
Assert.Contains("outbox query backend unavailable", cut.Markup));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// Bundle D drill-in (#23 M7-T10) — every notification row carries a
|
||||||
|
// "View audit history" link to /audit/log?correlationId={NotificationId}.
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NotificationRow_ViewAuditHistory_Link_HasCorrectHref()
|
||||||
|
{
|
||||||
|
var cut = Render<NotificationReportPage>();
|
||||||
|
|
||||||
|
cut.WaitForAssertion(() =>
|
||||||
|
{
|
||||||
|
// Both rows (Parked + Delivered) must surface the link — the drill-in
|
||||||
|
// is row-scope, not status-scope. We pin the parked row's href to the
|
||||||
|
// canonical correlationId-deep-link shape.
|
||||||
|
var parkedRow = cut.FindAll("tbody tr")
|
||||||
|
.First(r => r.TextContent.Contains("Pump fault at Plant-A"));
|
||||||
|
var link = parkedRow.QuerySelector("a[data-test^=\"audit-link-\"]");
|
||||||
|
Assert.NotNull(link);
|
||||||
|
Assert.Equal(
|
||||||
|
"/audit/log?correlationId=notif-aaaaaaaa-1111",
|
||||||
|
link!.GetAttribute("href"));
|
||||||
|
Assert.Contains("View audit history", link.TextContent);
|
||||||
|
|
||||||
|
var deliveredRow = cut.FindAll("tbody tr")
|
||||||
|
.First(r => r.TextContent.Contains("Daily summary"));
|
||||||
|
var deliveredLink = deliveredRow.QuerySelector("a[data-test^=\"audit-link-\"]");
|
||||||
|
Assert.NotNull(deliveredLink);
|
||||||
|
Assert.Equal(
|
||||||
|
"/audit/log?correlationId=notif-bbbbbbbb-2222",
|
||||||
|
deliveredLink!.GetAttribute("href"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Click_NavigatesTo_AuditLog_WithCorrelationId()
|
||||||
|
{
|
||||||
|
// The drill-in is a plain <a href> — browser-native navigation, not a
|
||||||
|
// Blazor onclick handler — so this test verifies the rendered anchor's
|
||||||
|
// attributes are exactly what a browser would follow: href, role, and
|
||||||
|
// human-visible text. (Triggering bUnit's .Click() on a bare anchor
|
||||||
|
// raises MissingEventHandlerException because there is no onclick
|
||||||
|
// handler to invoke; the navigation contract lives in the <a> markup.)
|
||||||
|
var cut = Render<NotificationReportPage>();
|
||||||
|
|
||||||
|
cut.WaitForState(() => cut.Markup.Contains("Pump fault at Plant-A"));
|
||||||
|
|
||||||
|
var parkedRow = cut.FindAll("tbody tr")
|
||||||
|
.First(r => r.TextContent.Contains("Pump fault at Plant-A"));
|
||||||
|
var link = parkedRow.QuerySelector("a[data-test^=\"audit-link-\"]")!;
|
||||||
|
|
||||||
|
Assert.Equal("a", link.TagName, ignoreCase: true);
|
||||||
|
Assert.Equal("/audit/log?correlationId=notif-aaaaaaaa-1111", link.GetAttribute("href"));
|
||||||
|
Assert.Contains("View audit history", link.TextContent);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
|
|||||||
Reference in New Issue
Block a user