using System.Text.Json; using System.Text.Json.Nodes; namespace ZB.MOM.WW.ScadaBridge.CLI.Commands; /// /// Compact table projection for audit-config query (deferred-work register row 57). /// /// /// The handler returns an AuditLogQueryResult — { "entries": [ … ], /// "totalCount": N }. Handed to the generic renderer unprojected, that object takes /// the Property/Value branch and the whole entries array lands in a single cell as /// raw JSON, so --format table was unreadable. This unwraps the envelope to the /// row array the renderer expects and drops afterStateJson, the per-entry snapshot /// of the entire changed entity — the same "one fat field makes the table useless" problem /// solves by dropping child collections. /// /// /// /// totalCount would be lost by a plain unwrap, so when the page is a strict subset /// of the matches a single trailing row carries a note cell saying how many of how /// many are shown. Every real entry leaves that cell empty, and when the page IS the whole /// result set the column is not emitted at all — so the common small-query table stays /// exactly as narrow as its data. /// /// /// /// JSON output is never touched (this runs only on the table path), and the command's /// --detail flag skips the projection to render the raw payload as-is — that is /// the way to see afterStateJson in table form. /// /// internal static class AuditConfigTableProjection { /// /// Projects an AuditLogQueryResult envelope (or a bare entry array) to the /// compact row array the table renderer consumes. Returns the input unchanged when it /// is not JSON or not one of those shapes, so the renderer's own fallbacks still apply. /// /// The raw success JSON body from the management API. /// Compact JSON suitable for table rendering. internal static string ProjectEntries(string json) { JsonDocument doc; try { doc = JsonDocument.Parse(json); } catch (JsonException) { // Not JSON (e.g. a proxy error page) — let the renderer print it verbatim. return json; } using (doc) { var root = doc.RootElement; // A bare array is accepted so the projection still works if the payload is // ever unwrapped upstream, and so the deprecated `audit-log` alias against an // older server does not render worse than it does today. if (root.ValueKind == JsonValueKind.Array) { return BuildRows(root, totalCount: null).ToJsonString(); } if (root.ValueKind == JsonValueKind.Object && TryGetPropertyCI(root, "entries", out var entries) && entries.ValueKind == JsonValueKind.Array) { int? total = TryGetPropertyCI(root, "totalCount", out var tc) && tc.ValueKind == JsonValueKind.Number && tc.TryGetInt32(out var n) ? n : null; return BuildRows(entries, total).ToJsonString(); } // Some other object (e.g. an error envelope) — leave it to the renderer. return json; } } private static JsonArray BuildRows(JsonElement entries, int? totalCount) { var arr = new JsonArray(); var shown = 0; foreach (var item in entries.EnumerateArray()) { arr.Add(ProjectElement(item)); shown++; } // Only when the page is a strict subset — otherwise the count is just the row // count the operator can already see, and the extra column would be noise. if (totalCount is int total && total > shown) { arr.Add(new JsonObject { ["note"] = JsonValue.Create( $"showing {shown} of {total} — use --page / --page-size for the rest"), }); } return arr; } /// Projects a single audit entry to its compact row node. private static JsonNode ProjectElement(JsonElement element) { if (element.ValueKind != JsonValueKind.Object) return JsonValue.Create(element.ToString())!; // JsonObject preserves insertion order, which fixes the table's column order. // afterStateJson (the full post-change entity snapshot) and bundleImportId are // deliberately omitted — `--detail`, or --format json, for those. return new JsonObject { ["id"] = Int(element, "id"), ["timestamp"] = Str(element, "timestamp"), ["user"] = Str(element, "user"), ["action"] = Str(element, "action"), ["entityType"] = Str(element, "entityType"), ["entityName"] = Str(element, "entityName"), ["entityId"] = Str(element, "entityId"), }; } private static bool TryGetPropertyCI(JsonElement obj, string name, out JsonElement value) { foreach (var prop in obj.EnumerateObject()) { if (string.Equals(prop.Name, name, StringComparison.OrdinalIgnoreCase)) { value = prop.Value; return true; } } value = default; return false; } private static JsonNode? Str(JsonElement obj, string name) => TryGetPropertyCI(obj, name, out var v) && v.ValueKind == JsonValueKind.String ? JsonValue.Create(v.GetString()) : null; private static JsonNode? Int(JsonElement obj, string name) => TryGetPropertyCI(obj, name, out var v) && v.ValueKind == JsonValueKind.Number && v.TryGetInt32(out var n) ? JsonValue.Create(n) : null; }