From d630e2646bc7a4077efcffc252699b5dd0a336a9 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 23 May 2026 19:01:48 -0400 Subject: [PATCH] feat(ui): show SourceNode under SourceSiteId in audit log detail popup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The audit log drilldown drawer (and the execution-tree node-detail modal, which shares this component) now renders the SourceNode field directly under SourceSiteId so provenance reads 'site → node → instance → script' in declared order. Two focused tests pin the field's presence in both populated and null cases plus the inter-field ordering. --- .../Components/Audit/AuditEventDetail.razor | 3 ++ .../Components/Audit/AuditEventDetailTests.cs | 38 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/ScadaLink.CentralUI/Components/Audit/AuditEventDetail.razor b/src/ScadaLink.CentralUI/Components/Audit/AuditEventDetail.razor index cb7a2a2..6bbce9d 100644 --- a/src/ScadaLink.CentralUI/Components/Audit/AuditEventDetail.razor +++ b/src/ScadaLink.CentralUI/Components/Audit/AuditEventDetail.razor @@ -28,6 +28,9 @@
SourceSiteId
@(Event.SourceSiteId ?? "—")
+
SourceNode
+
@(Event.SourceNode ?? "—")
+
SourceInstanceId
@(Event.SourceInstanceId ?? "—")
diff --git a/tests/ScadaLink.CentralUI.Tests/Components/Audit/AuditEventDetailTests.cs b/tests/ScadaLink.CentralUI.Tests/Components/Audit/AuditEventDetailTests.cs index f4b48e9..9316c3e 100644 --- a/tests/ScadaLink.CentralUI.Tests/Components/Audit/AuditEventDetailTests.cs +++ b/tests/ScadaLink.CentralUI.Tests/Components/Audit/AuditEventDetailTests.cs @@ -81,6 +81,44 @@ public class AuditEventDetailTests : BunitContext Assert.Contains("2026-05-20T12:30:45", cut.Markup); } + [Fact] + public void RendersSourceNodeField_BetweenSiteAndInstance() + { + // SourceNode is rendered as a sibling row directly under SourceSiteId + // so the popup reads "site → node → instance → script" in provenance + // order. Populated case. + var ev = MakeEvent() with { SourceNode = "node-a" }; + + var cut = Render(p => p.Add(c => c.Event, ev)); + + Assert.Contains("data-test=\"field-SourceNode\"", cut.Markup); + Assert.Contains("node-a", cut.Markup); + + // Ordering: SourceSiteId appears before SourceNode, which appears + // before SourceInstanceId. + var siteIdx = cut.Markup.IndexOf("data-test=\"field-SourceSiteId\"", StringComparison.Ordinal); + var nodeIdx = cut.Markup.IndexOf("data-test=\"field-SourceNode\"", StringComparison.Ordinal); + var instanceIdx = cut.Markup.IndexOf("data-test=\"field-SourceInstanceId\"", StringComparison.Ordinal); + Assert.True(siteIdx > 0 && nodeIdx > siteIdx && instanceIdx > nodeIdx, + $"Expected SourceSiteId < SourceNode < SourceInstanceId; got {siteIdx}, {nodeIdx}, {instanceIdx}"); + } + + [Fact] + public void RendersSourceNodeField_AsDashWhenNull() + { + // Null SourceNode (e.g. central direct-write row pre-feature, or a + // reconciled row from a retired node) renders as the em-dash, same + // convention as the sibling provenance fields. + var ev = MakeEvent(); // SourceNode left at default null + Assert.Null(ev.SourceNode); + + var cut = Render(p => p.Add(c => c.Event, ev)); + + Assert.Contains("data-test=\"field-SourceNode\"", cut.Markup); + // The field is present and renders the em-dash placeholder. + Assert.Contains(">—<", cut.Markup); + } + [Fact] public void ErrorSection_RendersWhenErrorPresent() {