using System.Text.Json;
using System.Text.Json.Nodes;
namespace ZB.MOM.WW.ScadaBridge.CLI.Commands;
///
/// Table projection for deploy status (deferred-work register rows 37/87).
///
///
/// The handler now returns a DeploymentQueryResult envelope —
/// { "deployments": [ … ], "totalCount": N, "page": P, "pageSize": S } — where
/// it used to return a bare row array. Handed to the generic renderer unprojected,
/// an object takes the Property/Value branch and the whole deployments array
/// lands in a single unreadable cell. This unwraps the envelope to the row array the
/// renderer expects, exactly as does for
/// audit-config query.
///
///
///
/// The envelope's counts would be lost by a plain unwrap, and losing THOSE is the one
/// thing this projection must not do: the page size is bounded server-side now, so a
/// full page is no longer proof that there are no more rows. When the page is a strict
/// subset a single trailing row carries a note cell saying how many of how many
/// are shown and how to get the rest. When the page IS the whole result set the column
/// is not emitted at all.
///
///
///
/// JSON output is never touched — this runs only on the table path.
///
///
internal static class DeploymentStatusTableProjection
{
///
/// Projects a DeploymentQueryResult envelope (or a bare row array) to the
/// 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 apply.
///
/// The raw success JSON body from the management API.
/// JSON suitable for table rendering.
internal static string ProjectRows(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 command still renders usefully against
// an older server that returns the pre-envelope shape.
if (root.ValueKind == JsonValueKind.Array)
return BuildRows(root, totalCount: null).ToJsonString();
if (root.ValueKind == JsonValueKind.Object
&& TryGetPropertyCI(root, "deployments", out var rows)
&& rows.ValueKind == JsonValueKind.Array)
{
int? total = TryGetPropertyCI(root, "totalCount", out var tc)
&& tc.ValueKind == JsonValueKind.Number
&& tc.TryGetInt32(out var n)
? n
: null;
return BuildRows(rows, total).ToJsonString();
}
// Some other object (e.g. an error envelope) — leave it to the renderer.
return json;
}
}
private static JsonArray BuildRows(JsonElement rows, int? totalCount)
{
var arr = new JsonArray();
var shown = 0;
foreach (var item in rows.EnumerateArray())
{
arr.Add(item.ValueKind == JsonValueKind.Object
? JsonNode.Parse(item.GetRawText())!
: JsonValue.Create(item.ToString())!);
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;
}
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;
}
}