Files
ScadaBridge/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/FlowTraceList.razor.cs
T
Joseph Doherty 0ce6633270 feat(flow-debugger): vertical trace list view alongside the swimlane (ScadaBridge#53)
Direction B: the same FlowGraph steps as a filterable list (lane column, lane filter,
failures-only), toggled from the flow panel toolbar. The mockup canvas is not in the repo,
so this is the minimal faithful version.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-09 15:33:31 -05:00

73 lines
2.8 KiB
C#

using System.Globalization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Debugging;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared;
/// <summary>
/// Vertical trace list over a <see cref="FlowGraph"/> (Flow Debugger mockup Direction B, Gitea #53): the
/// steps in display order with the lane as a column, filterable by lane and to failures only.
/// </summary>
public partial class FlowTraceList : ComponentBase
{
/// <summary>The real graph (from a run or a replay); null before a run.</summary>
[Parameter] public FlowGraph? Graph { get; set; }
/// <summary>The selected step's event id.</summary>
[Parameter] public Guid? SelectedEventId { get; set; }
/// <summary>Raised when a row is clicked or activated with Enter/Space.</summary>
[Parameter] public EventCallback<FlowStep> OnSelect { get; set; }
private string _laneFilter = "";
private bool _failedOnly;
private IReadOnlyList<(FlowStep Step, int Index)> Rows()
{
if (Graph == null) return Array.Empty<(FlowStep, int)>();
// The ordinal is the step's position in the whole flow, so it stays stable while filtering.
IEnumerable<(FlowStep, int)> q = Graph.Steps.Select((s, i) => (s, i));
if (Enum.TryParse<FlowLane>(_laneFilter, out var lane)) q = q.Where(t => t.Item1.Lane == lane);
if (_failedOnly) q = q.Where(t => IsFailure(t.Item1));
return q.ToList();
}
internal static bool IsFailure(FlowStep s) => s.Outcome is FlowStepOutcome.Failed or FlowStepOutcome.TimedOut;
private string Offset(FlowStep s)
{
var first = Graph!.Steps[0].OccurredAtUtc;
var ms = (s.OccurredAtUtc - first).TotalMilliseconds;
return "+" + ms.ToString("0", CultureInfo.InvariantCulture) + " ms";
}
private async Task OnKeyAsync(KeyboardEventArgs e, FlowStep s)
{
if (e.Key is "Enter" or " ") await OnSelect.InvokeAsync(s);
}
private static string PillClass(FlowStepOutcome outcome) => outcome switch
{
FlowStepOutcome.Ok => "ft-pill ok",
FlowStepOutcome.Pending => "ft-pill pending",
FlowStepOutcome.Parked => "ft-pill parked",
FlowStepOutcome.Failed or FlowStepOutcome.TimedOut => "ft-pill failed",
_ => "ft-pill",
};
internal static string KindLabel(FlowStepKind kind) => kind switch
{
FlowStepKind.InboundRequest => "Request",
FlowStepKind.RouteCall => "Route call",
FlowStepKind.ScriptRun => "Script run",
FlowStepKind.AttributeWrite => "Write",
FlowStepKind.AttributeWait => "Wait",
FlowStepKind.Response => "Response",
FlowStepKind.ApiCall => "API call",
FlowStepKind.DbCall => "Database",
FlowStepKind.Notify => "Notify",
_ => "Step",
};
}