feat(centralui): execution-chain tree view on the Audit Log page

This commit is contained in:
Joseph Doherty
2026-05-21 18:49:13 -04:00
parent 0b5723b777
commit 34a4356625
14 changed files with 1224 additions and 0 deletions

View File

@@ -429,6 +429,88 @@ public class AuditLogPageTests
}
}
[Fact]
public async Task DrillInToExecutionChain_RendersTree_AndNodeClickFiltersGrid()
{
// Audit Log ParentExecutionId feature, Task 10: the drawer's "View
// execution chain" action opens /audit/execution-tree?executionId={id}.
// We seed a spawner row + a child row, open the child's drawer, click
// "View execution chain", assert the tree renders BOTH executions, then
// click the spawner node and assert the Audit Log grid filters to it.
if (!await AuditDataSeeder.IsAvailableAsync())
{
throw new InvalidOperationException("MSSQL unavailable; see FilterNarrowing test for setup instructions.");
}
var runId = Guid.NewGuid().ToString("N");
var targetPrefix = $"playwright-test/exec-chain-tree/{runId}/";
var parentExecutionId = Guid.NewGuid();
var childExecutionId = Guid.NewGuid();
var spawnerEventId = Guid.NewGuid();
var childEventId = Guid.NewGuid();
var now = DateTime.UtcNow;
try
{
// Spawner execution's own row.
await AuditDataSeeder.InsertAuditEventAsync(
eventId: spawnerEventId,
occurredAtUtc: now,
channel: "ApiInbound",
kind: "InboundRequest",
status: "Delivered",
target: targetPrefix + "spawner",
executionId: parentExecutionId,
httpStatus: 200,
durationMs: 7);
// Child (spawned) row — links to the spawner via ParentExecutionId.
await AuditDataSeeder.InsertAuditEventAsync(
eventId: childEventId,
occurredAtUtc: now,
channel: "ApiOutbound",
kind: "ApiCall",
status: "Delivered",
target: targetPrefix + "child",
executionId: childExecutionId,
parentExecutionId: parentExecutionId,
httpStatus: 200,
durationMs: 13);
var page = await _fixture.NewAuthenticatedPageAsync();
// Open the child row's drawer via its ExecutionId filter.
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/audit/log?executionId={childExecutionId}");
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
var childRow = page.Locator($"[data-test='grid-row-{childEventId}']");
await Assertions.Expect(childRow).ToBeVisibleAsync();
await childRow.ClickAsync();
// "View execution chain" opens the tree view.
var viewChain = page.Locator("[data-test='view-execution-chain']");
await Assertions.Expect(viewChain).ToBeVisibleAsync();
await viewChain.ClickAsync();
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
// The tree page rendered both executions as nodes.
Assert.Contains($"executionId={childExecutionId}", page.Url);
await Assertions.Expect(page.Locator($"[data-test='tree-node-{parentExecutionId}']")).ToBeVisibleAsync();
await Assertions.Expect(page.Locator($"[data-test='tree-node-{childExecutionId}']")).ToBeVisibleAsync();
// Clicking the spawner node's link filters the Audit Log to its rows.
await page.Locator($"[data-test='tree-node-link-{parentExecutionId}']").ClickAsync();
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
Assert.Contains($"executionId={parentExecutionId}", page.Url);
await Assertions.Expect(page.Locator($"[data-test='grid-row-{spawnerEventId}']")).ToBeVisibleAsync();
}
finally
{
await AuditDataSeeder.DeleteByTargetPrefixAsync(targetPrefix);
}
}
[Fact]
public async Task NotificationsPage_RendersAuditDrillInLinkPattern()
{