feat(flow-debugger): FlowStep carries the audit CorrelationId; CLI table adds a correlation column
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
This commit is contained in:
@@ -18,7 +18,7 @@ namespace ZB.MOM.WW.ScadaBridge.CLI.Commands;
|
|||||||
/// to the generic renderer unprojected, the object takes the Property/Value branch and the
|
/// to the generic renderer unprojected, the object takes the Property/Value branch and the
|
||||||
/// whole event array lands in one cell as raw JSON. This projects the events through the same
|
/// whole event array lands in one cell as raw JSON. This projects the events through the same
|
||||||
/// <see cref="FlowGraphBuilder"/> the Flow Debugger page uses and prints one row per STEP —
|
/// <see cref="FlowGraphBuilder"/> the Flow Debugger page uses and prints one row per STEP —
|
||||||
/// <c>occurred | kind | lane | target | durationMs | status | lifecycle | error</c> — in the
|
/// <c>occurred | kind | lane | target | durationMs | status | lifecycle | correlation | error</c> — in the
|
||||||
/// order the page draws them, so a terminal user can read the swimlane top-to-bottom. An
|
/// order the page draws them, so a terminal user can read the swimlane top-to-bottom. An
|
||||||
/// outbound operation's rows (a cached call's Submit → Forwarded → Attempted → Resolve, a
|
/// outbound operation's rows (a cached call's Submit → Forwarded → Attempted → Resolve, a
|
||||||
/// notification's Send → Deliver) fold into ONE row whose <c>lifecycle</c> column counts them
|
/// notification's Send → Deliver) fold into ONE row whose <c>lifecycle</c> column counts them
|
||||||
@@ -146,6 +146,7 @@ internal static class FlowDebugTableProjection
|
|||||||
["durationMs"] = step.DurationMs,
|
["durationMs"] = step.DurationMs,
|
||||||
["status"] = status,
|
["status"] = status,
|
||||||
["lifecycle"] = lifecycle is { Count: > 0 } ? $"{lifecycle.Count} {(lifecycle.Count == 1 ? "row" : "rows")}" : null,
|
["lifecycle"] = lifecycle is { Count: > 0 } ? $"{lifecycle.Count} {(lifecycle.Count == 1 ? "row" : "rows")}" : null,
|
||||||
|
["correlation"] = step.CorrelationId?.ToString("D"),
|
||||||
["error"] = step.ErrorMessage,
|
["error"] = step.ErrorMessage,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2289,7 +2289,7 @@ Trace steps are ordinary audit rows: a run leaves them in the central `AuditLog`
|
|||||||
chain after the fact.
|
chain after the fact.
|
||||||
|
|
||||||
Table output prints one row per step in chronological order —
|
Table output prints one row per step in chronological order —
|
||||||
`occurred | kind | lane | target | durationMs | status | lifecycle | error` — the same
|
`occurred | kind | lane | target | durationMs | status | lifecycle | correlation | error` — the same
|
||||||
projection the Flow Debugger page draws (`FlowGraphBuilder`): an outbound `ExternalSystem.*`,
|
projection the Flow Debugger page draws (`FlowGraphBuilder`): an outbound `ExternalSystem.*`,
|
||||||
`Database.*` or `Notify` call is ONE row on the `External` lane (kinds `ApiCall` / `DbCall` /
|
`Database.*` or `Notify` call is ONE row on the `External` lane (kinds `ApiCall` / `DbCall` /
|
||||||
`Notify`), and a cached call's or notification's later rows (Forwarded → Attempted → Delivered /
|
`Notify`), and a cached call's or notification's later rows (Forwarded → Attempted → Delivered /
|
||||||
|
|||||||
@@ -81,6 +81,11 @@ public sealed record FlowLifecycleRow(
|
|||||||
/// (<see cref="Lifecycle"/>; outbound-calls plan 2026-09-06 D3). Never serialized to the wire;
|
/// (<see cref="Lifecycle"/>; outbound-calls plan 2026-09-06 D3). Never serialized to the wire;
|
||||||
/// the wire carries the audit events themselves (design §5.4).
|
/// the wire carries the audit events themselves (design §5.4).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <c>CorrelationId</c> is the audit row's own: the <c>TrackedOperationId</c> of a cached call or the
|
||||||
|
/// <c>NotificationId</c> of a notification — the key the Site Calls / Notification report pages accept as
|
||||||
|
/// <c>?correlationId=</c> (follow-ups plan 2026-09-06 D1/D2). Null for sync one-shots and trace rows.
|
||||||
|
/// </remarks>
|
||||||
public sealed record FlowStep(
|
public sealed record FlowStep(
|
||||||
Guid EventId,
|
Guid EventId,
|
||||||
Guid? ExecutionId,
|
Guid? ExecutionId,
|
||||||
@@ -100,4 +105,5 @@ public sealed record FlowStep(
|
|||||||
string? Instance,
|
string? Instance,
|
||||||
string? Target,
|
string? Target,
|
||||||
ScriptTraceExtra? Extra,
|
ScriptTraceExtra? Extra,
|
||||||
IReadOnlyList<FlowLifecycleRow>? Lifecycle = null);
|
IReadOnlyList<FlowLifecycleRow>? Lifecycle = null,
|
||||||
|
Guid? CorrelationId = null);
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ public static class FlowStepProjector
|
|||||||
Node: evt.SourceNode ?? extra?.Node,
|
Node: evt.SourceNode ?? extra?.Node,
|
||||||
Instance: d.SourceInstanceId,
|
Instance: d.SourceInstanceId,
|
||||||
Target: evt.Target,
|
Target: evt.Target,
|
||||||
Extra: extra);
|
Extra: extra,
|
||||||
|
CorrelationId: evt.CorrelationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Projects one outbound audit row onto its lifecycle entry (<see cref="FlowStep.Lifecycle"/>).</summary>
|
/// <summary>Projects one outbound audit row onto its lifecycle entry (<see cref="FlowStep.Lifecycle"/>).</summary>
|
||||||
|
|||||||
@@ -229,11 +229,13 @@ public class FlowDebugCommandsTests
|
|||||||
Assert.Equal("External", rows[3].GetProperty("lane").GetString());
|
Assert.Equal("External", rows[3].GetProperty("lane").GetString());
|
||||||
Assert.Equal("RigDb", rows[3].GetProperty("target").GetString());
|
Assert.Equal("RigDb", rows[3].GetProperty("target").GetString());
|
||||||
Assert.Equal("3 rows", rows[3].GetProperty("lifecycle").GetString());
|
Assert.Equal("3 rows", rows[3].GetProperty("lifecycle").GetString());
|
||||||
|
Assert.Equal(corr.ToString("D"), rows[3].GetProperty("correlation").GetString());
|
||||||
Assert.Equal("Delivered", rows[3].GetProperty("status").GetString());
|
Assert.Equal("Delivered", rows[3].GetProperty("status").GetString());
|
||||||
Assert.Equal(50, rows[3].GetProperty("durationMs").GetInt32());
|
Assert.Equal(50, rows[3].GetProperty("durationMs").GetInt32());
|
||||||
Assert.Equal("09:00:00.070", rows[3].GetProperty("occurred").GetString());
|
Assert.Equal("09:00:00.070", rows[3].GetProperty("occurred").GetString());
|
||||||
|
|
||||||
Assert.Equal("DelmiaSim01.OutboundDemo", rows[1].GetProperty("target").GetString());
|
Assert.Equal("DelmiaSim01.OutboundDemo", rows[1].GetProperty("target").GetString());
|
||||||
Assert.Equal(JsonValueKind.Null, rows[1].GetProperty("lifecycle").ValueKind);
|
Assert.Equal(JsonValueKind.Null, rows[1].GetProperty("lifecycle").ValueKind);
|
||||||
|
Assert.Equal(JsonValueKind.Null, rows[1].GetProperty("correlation").ValueKind);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -232,6 +232,7 @@ public class FlowGraphBuilderTests
|
|||||||
Assert.Equal(T0.AddMilliseconds(35), api.OccurredAtUtc.UtcDateTime);
|
Assert.Equal(T0.AddMilliseconds(35), api.OccurredAtUtc.UtcDateTime);
|
||||||
Assert.Equal(4, api.Lifecycle!.Count);
|
Assert.Equal(4, api.Lifecycle!.Count);
|
||||||
Assert.Equal(new[] { "CachedSubmit", "ApiCallCached", "ApiCallCached", "CachedResolve" }, api.Lifecycle.Select(r => r.Kind));
|
Assert.Equal(new[] { "CachedSubmit", "ApiCallCached", "ApiCallCached", "CachedResolve" }, api.Lifecycle.Select(r => r.Kind));
|
||||||
|
Assert.Equal(corr, api.CorrelationId);
|
||||||
Assert.Equal("503", api.Lifecycle[2].ErrorMessage);
|
Assert.Equal("503", api.Lifecycle[2].ErrorMessage);
|
||||||
Assert.Equal(FlowStepOutcome.Ok, api.Outcome);
|
Assert.Equal(FlowStepOutcome.Ok, api.Outcome);
|
||||||
Assert.Equal(4100, api.DurationMs);
|
Assert.Equal(4100, api.DurationMs);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using ZB.MOM.WW.Audit;
|
||||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
||||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Debugging;
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Debugging;
|
||||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||||
@@ -161,4 +162,23 @@ public class FlowStepProjectorTests
|
|||||||
occurredAtUtc: T0.UtcDateTime, target: "MesDb", extra: extra);
|
occurredAtUtc: T0.UtcDateTime, target: "MesDb", extra: extra);
|
||||||
Assert.Equal(expected, FlowStepProjector.Project(evt).Subtitle);
|
Assert.Equal(expected, FlowStepProjector.Project(evt).Subtitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Project_CarriesTheAuditRowsCorrelationId()
|
||||||
|
{
|
||||||
|
var corr = Guid.NewGuid();
|
||||||
|
var evt = ScadaBridgeAuditEventFactory.Create(AuditChannel.ApiOutbound, AuditKind.CachedSubmit, AuditStatus.Submitted,
|
||||||
|
occurredAtUtc: T0.UtcDateTime, target: "Delmia.Ack", executionId: Guid.NewGuid(), correlationId: corr, sourceSiteId: "site-a");
|
||||||
|
|
||||||
|
Assert.Equal(corr, FlowStepProjector.Project(evt).CorrelationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Project_NullCorrelation_LeavesCorrelationIdNull()
|
||||||
|
{
|
||||||
|
var evt = ScadaBridgeAuditEventFactory.Create(AuditChannel.ApiOutbound, AuditKind.ApiCall, AuditStatus.Delivered,
|
||||||
|
occurredAtUtc: T0.UtcDateTime, target: "Delmia.Ack", executionId: Guid.NewGuid(), sourceSiteId: "site-a");
|
||||||
|
|
||||||
|
Assert.Null(FlowStepProjector.Project(evt).CorrelationId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user