chore(theme): bump ZB.MOM.WW.Theme 0.3.0 -> 0.3.1 (interactive-render nav fix)

This commit is contained in:
Joseph Doherty
2026-06-05 07:19:11 -04:00
parent 2515c9db2d
commit 0783547a2d
5 changed files with 174 additions and 70 deletions
@@ -1,3 +1,5 @@
using System.Globalization;
using System.Text.Json;
using Microsoft.Data.SqlClient;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Audit;
@@ -51,9 +53,14 @@ internal static class AuditDataSeeder
}
/// <summary>
/// Inserts a single audit row into <c>AuditLog</c>. All optional fields are
/// nullable so individual tests can shape the row to whatever payload they
/// need for their drawer/grid assertions.
/// Inserts a single audit row into the canonical <c>AuditLog</c> table. After the
/// <c>CollapseAuditLogToCanonical</c> migration the typed audit fields live inside
/// <c>DetailsJson</c> (camelCase), and <c>Kind</c>/<c>Status</c>/<c>SourceSiteId</c>/
/// <c>ExecutionId</c>/<c>ParentExecutionId</c>/<c>IngestedAtUtc</c> are computed columns
/// derived from it — so this seeder writes only the 10 stored columns plus a
/// <c>DetailsJson</c> bag matching the production codec, and lets the computed columns
/// derive automatically. All optional fields are nullable so individual tests can shape
/// the row to whatever payload they need for their drawer/grid assertions.
/// </summary>
public static async Task InsertAuditEventAsync(
Guid eventId,
@@ -75,17 +82,47 @@ internal static class AuditDataSeeder
string? extra = null,
CancellationToken ct = default)
{
// Typed audit fields ride inside DetailsJson (camelCase, nulls omitted) exactly as
// the production AuditDetailsCodec writes them; the computed columns read these JSON
// paths ($.kind, $.status, $.sourceSiteId, $.executionId, $.parentExecutionId,
// $.ingestedAtUtc). Property order is irrelevant — the readers look up by name.
var details = new Dictionary<string, object?>
{
["channel"] = channel,
["kind"] = kind,
["status"] = status,
};
if (executionId is { } ex) details["executionId"] = ex;
if (parentExecutionId is { } pex) details["parentExecutionId"] = pex;
if (sourceSiteId is not null) details["sourceSiteId"] = sourceSiteId;
if (httpStatus is { } hs) details["httpStatus"] = hs;
if (durationMs is { } dm) details["durationMs"] = dm;
if (errorMessage is not null) details["errorMessage"] = errorMessage;
if (requestSummary is not null) details["requestSummary"] = requestSummary;
if (responseSummary is not null) details["responseSummary"] = responseSummary;
if (extra is not null) details["extra"] = extra;
details["payloadTruncated"] = false;
details["ingestedAtUtc"] = DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture);
var detailsJson = JsonSerializer.Serialize(details);
// Action/Outcome/Category derive from (channel, kind, status) exactly as the
// production canonical factory and the migration's SQL projection do.
var action = $"{channel}.{kind}";
var category = channel;
var outcome =
kind == "InboundAuthFailure" ? "Denied"
: status == "Delivered" ? "Success"
: status is "Failed" or "Parked" or "Discarded" ? "Failure"
: "Success";
const string sql = @"
INSERT INTO [AuditLog]
([EventId], [OccurredAtUtc], [IngestedAtUtc], [Channel], [Kind], [CorrelationId],
[ExecutionId], [ParentExecutionId], [SourceSiteId], [SourceInstanceId], [SourceScript], [Actor], [Target],
[Status], [HttpStatus], [DurationMs], [ErrorMessage], [ErrorDetail], [RequestSummary],
[ResponseSummary], [PayloadTruncated], [Extra], [ForwardState])
([EventId], [OccurredAtUtc], [Actor], [Action], [Outcome], [Category],
[Target], [SourceNode], [CorrelationId], [DetailsJson])
VALUES
(@eventId, @occurredAtUtc, SYSUTCDATETIME(), @channel, @kind, @correlationId,
@executionId, @parentExecutionId, @sourceSiteId, NULL, NULL, @actor, @target,
@status, @httpStatus, @durationMs, @errorMessage, NULL, @requestSummary,
@responseSummary, 0, @extra, NULL);";
(@eventId, @occurredAtUtc, @actor, @action, @outcome, @category,
@target, NULL, @correlationId, @detailsJson);";
await using var connection = new SqlConnection(ConnectionString);
await connection.OpenAsync(ct);
@@ -93,21 +130,13 @@ VALUES
cmd.CommandText = sql;
cmd.Parameters.AddWithValue("@eventId", eventId);
cmd.Parameters.AddWithValue("@occurredAtUtc", occurredAtUtc);
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("@parentExecutionId", (object?)parentExecutionId ?? DBNull.Value);
cmd.Parameters.AddWithValue("@sourceSiteId", (object?)sourceSiteId ?? DBNull.Value);
cmd.Parameters.AddWithValue("@actor", (object?)actor ?? DBNull.Value);
cmd.Parameters.AddWithValue("@action", action);
cmd.Parameters.AddWithValue("@outcome", outcome);
cmd.Parameters.AddWithValue("@category", category);
cmd.Parameters.AddWithValue("@target", (object?)target ?? DBNull.Value);
cmd.Parameters.AddWithValue("@status", status);
cmd.Parameters.AddWithValue("@httpStatus", (object?)httpStatus ?? DBNull.Value);
cmd.Parameters.AddWithValue("@durationMs", (object?)durationMs ?? DBNull.Value);
cmd.Parameters.AddWithValue("@errorMessage", (object?)errorMessage ?? DBNull.Value);
cmd.Parameters.AddWithValue("@requestSummary", (object?)requestSummary ?? DBNull.Value);
cmd.Parameters.AddWithValue("@responseSummary", (object?)responseSummary ?? DBNull.Value);
cmd.Parameters.AddWithValue("@extra", (object?)extra ?? DBNull.Value);
cmd.Parameters.AddWithValue("@correlationId", (object?)correlationId ?? DBNull.Value);
cmd.Parameters.AddWithValue("@detailsJson", detailsJson);
await cmd.ExecuteNonQueryAsync(ct);
}