feat(centralui): ExecutionId column, filter and drill-in on the Audit Log page
This commit is contained in:
@@ -65,6 +65,7 @@ internal static class AuditDataSeeder
|
||||
string? target = null,
|
||||
string? actor = null,
|
||||
Guid? correlationId = null,
|
||||
Guid? executionId = null,
|
||||
int? httpStatus = null,
|
||||
int? durationMs = null,
|
||||
string? errorMessage = null,
|
||||
@@ -76,13 +77,13 @@ internal static class AuditDataSeeder
|
||||
const string sql = @"
|
||||
INSERT INTO [AuditLog]
|
||||
([EventId], [OccurredAtUtc], [IngestedAtUtc], [Channel], [Kind], [CorrelationId],
|
||||
[SourceSiteId], [SourceInstanceId], [SourceScript], [Actor], [Target], [Status],
|
||||
[HttpStatus], [DurationMs], [ErrorMessage], [ErrorDetail], [RequestSummary],
|
||||
[ExecutionId], [SourceSiteId], [SourceInstanceId], [SourceScript], [Actor], [Target],
|
||||
[Status], [HttpStatus], [DurationMs], [ErrorMessage], [ErrorDetail], [RequestSummary],
|
||||
[ResponseSummary], [PayloadTruncated], [Extra], [ForwardState])
|
||||
VALUES
|
||||
(@eventId, @occurredAtUtc, SYSUTCDATETIME(), @channel, @kind, @correlationId,
|
||||
@sourceSiteId, NULL, NULL, @actor, @target, @status,
|
||||
@httpStatus, @durationMs, @errorMessage, NULL, @requestSummary,
|
||||
@executionId, @sourceSiteId, NULL, NULL, @actor, @target,
|
||||
@status, @httpStatus, @durationMs, @errorMessage, NULL, @requestSummary,
|
||||
@responseSummary, 0, @extra, NULL);";
|
||||
|
||||
await using var connection = new SqlConnection(ConnectionString);
|
||||
@@ -94,6 +95,7 @@ VALUES
|
||||
cmd.Parameters.AddWithValue("@channel", channel);
|
||||
cmd.Parameters.AddWithValue("@kind", kind);
|
||||
cmd.Parameters.AddWithValue("@correlationId", (object?)correlationId ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@executionId", (object?)executionId ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@sourceSiteId", (object?)sourceSiteId ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@actor", (object?)actor ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@target", (object?)target ?? DBNull.Value);
|
||||
|
||||
@@ -24,6 +24,9 @@ namespace ScadaLink.CentralUI.PlaywrightTests.Audit;
|
||||
/// link relies on; verified by reproducing the link target directly because
|
||||
/// seeding a notification visible to the report page requires the Akka query
|
||||
/// path, not just an INSERT).</item>
|
||||
/// <item><c>DrillInFromExecutionId_LandsOnAuditLogWithFilterContext</c> — the
|
||||
/// <c>?executionId=</c> drill-in (the drawer's "View this execution" action)
|
||||
/// auto-loads the grid filtered by ExecutionId.</item>
|
||||
/// <item><c>NotificationsPage_HasViewAuditHistoryLink_WhenNotificationsExist</c> —
|
||||
/// the report page wires drill-in links when notifications are present.</item>
|
||||
/// <item><c>ExportCsv_LinkIsVisibleAndDownloads</c> — Export CSV button gated on
|
||||
@@ -289,6 +292,64 @@ public class AuditLogPageTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DrillInFromExecutionId_LandsOnAuditLogWithFilterContext()
|
||||
{
|
||||
// Mirrors the correlationId drill-in: the "View this execution" drawer
|
||||
// action navigates to /audit/log?executionId={ExecutionId}. We seed a row
|
||||
// carrying that ExecutionId, hit the deep link directly, and assert the
|
||||
// page deserializes the param and auto-loads the seeded row.
|
||||
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-drill-in/{runId}/";
|
||||
var executionId = Guid.NewGuid();
|
||||
var eventId = Guid.NewGuid();
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
try
|
||||
{
|
||||
await AuditDataSeeder.InsertAuditEventAsync(
|
||||
eventId: eventId,
|
||||
occurredAtUtc: now,
|
||||
channel: "ApiOutbound",
|
||||
kind: "ApiCall",
|
||||
status: "Delivered",
|
||||
target: targetPrefix + "endpoint",
|
||||
executionId: executionId,
|
||||
httpStatus: 200,
|
||||
durationMs: 11);
|
||||
|
||||
var page = await _fixture.NewAuthenticatedPageAsync();
|
||||
|
||||
// The exact URL the drawer's "View this execution" button produces.
|
||||
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/audit/log?executionId={executionId}");
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
|
||||
Assert.Contains($"executionId={executionId}", page.Url);
|
||||
await Assertions.Expect(page.Locator("h1:has-text('Audit Log')")).ToBeVisibleAsync();
|
||||
await Assertions.Expect(page.Locator("[data-test='audit-filter-bar']")).ToBeVisibleAsync();
|
||||
await Assertions.Expect(page.Locator("[data-test='audit-results-grid']")).ToBeVisibleAsync();
|
||||
|
||||
// Auto-load: the query-string drill-in resolves the ?executionId=
|
||||
// filter on OnInitialized and the seeded row appears without an
|
||||
// Apply click.
|
||||
var seededRow = page.Locator($"[data-test='grid-row-{eventId}']");
|
||||
await Assertions.Expect(seededRow).ToBeVisibleAsync();
|
||||
|
||||
// The ExecutionId column renders the row's short-form value.
|
||||
var execCell = page.Locator($"[data-test='execution-id-{eventId}']");
|
||||
await Assertions.Expect(execCell).ToBeVisibleAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
await AuditDataSeeder.DeleteByTargetPrefixAsync(targetPrefix);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NotificationsPage_RendersAuditDrillInLinkPattern()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user