feat(central-ui): Flow Debugger drill-ins from Audit Log + Execution Chain; TraceAllCalls checkbox on the method form
'Open in Flow Debugger' (data-test open-flow-debugger) beside 'View execution chain' in AuditEventDetail (rendered only with an ExecutionId) and beside 'View this execution in the Audit Log' on the Execution Chain page — both target /deployment/flow-debugger?executionId=… which replays on first render. ApiMethodForm gains a 'Trace all calls (Flow Debugger)' form-check under Timeout, loaded from and saved to ApiMethod.TraceAllCalls on both the update and create paths, with the ~7-rows-per-call guidance as help text. Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
This commit is contained in:
@@ -164,5 +164,10 @@
|
||||
@onclick="ViewExecutionChain">
|
||||
View execution chain
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary btn-sm"
|
||||
data-test="open-flow-debugger"
|
||||
@onclick="OpenInFlowDebugger">
|
||||
Open in Flow Debugger
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -295,6 +295,16 @@ public partial class AuditEventDetail
|
||||
Navigation.NavigateTo(uri);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flow Debugger replay drill-in (design 2026-09-05 §4): rebuilds this execution's
|
||||
/// whole flow — inbound row, route call, traced site steps — as a swimlane.
|
||||
/// </summary>
|
||||
private void OpenInFlowDebugger()
|
||||
{
|
||||
if (Event.ExecutionId is not { } exec) return;
|
||||
Navigation.NavigateTo($"/deployment/flow-debugger?executionId={exec}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build a cURL command from an audit event. The URL comes from
|
||||
/// <c>Target</c>; when the RequestSummary parses as
|
||||
|
||||
@@ -39,12 +39,17 @@
|
||||
}
|
||||
else if (_nodes is { Count: > 0 })
|
||||
{
|
||||
<div class="mb-2">
|
||||
<div class="mb-2 d-flex flex-wrap gap-2">
|
||||
<a class="btn btn-outline-secondary btn-sm"
|
||||
data-test="execution-tree-back-to-log"
|
||||
href="@($"/audit/log?executionId={_executionId}")">
|
||||
View this execution in the Audit Log
|
||||
</a>
|
||||
<a class="btn btn-outline-secondary btn-sm"
|
||||
data-test="open-flow-debugger"
|
||||
href="@($"/deployment/flow-debugger?executionId={_executionId}")">
|
||||
Open in Flow Debugger
|
||||
</a>
|
||||
</div>
|
||||
<ExecutionTree Nodes="_nodes" ArrivedFromExecutionId="_executionId.Value"
|
||||
OnNodeActivated="HandleNodeActivated" />
|
||||
|
||||
@@ -35,10 +35,22 @@
|
||||
<label class="form-label">Name</label>
|
||||
<input type="text" class="form-control" @bind="_name" disabled="@Id.HasValue" />
|
||||
</div>
|
||||
<div class="mb-0">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Timeout (seconds)</label>
|
||||
<input type="number" class="form-control" @bind="_timeoutSeconds" min="1" />
|
||||
</div>
|
||||
<div class="form-check mb-0">
|
||||
<input class="form-check-input" type="checkbox" id="api-method-trace-all-calls"
|
||||
data-test="api-method-trace-all-calls" @bind="_traceAllCalls" />
|
||||
<label class="form-check-label" for="api-method-trace-all-calls">
|
||||
Trace all calls (Flow Debugger)
|
||||
</label>
|
||||
<div class="form-text">
|
||||
Every call to this method records its routed site steps as audit rows the
|
||||
Flow Debugger can replay. Adds ~7 audit rows per call; for low-volume
|
||||
integration methods.
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="mb-4">
|
||||
@@ -211,6 +223,7 @@
|
||||
private bool _loading = true;
|
||||
private string _name = "", _script = "";
|
||||
private int _timeoutSeconds = 30;
|
||||
private bool _traceAllCalls;
|
||||
private string? _params, _returns;
|
||||
private string? _formError;
|
||||
private MonacoEditor? _editor;
|
||||
@@ -253,6 +266,7 @@
|
||||
_name = _existing.Name;
|
||||
_script = _existing.Script;
|
||||
_timeoutSeconds = _existing.TimeoutSeconds;
|
||||
_traceAllCalls = _existing.TraceAllCalls;
|
||||
_params = _existing.ParameterDefinitions;
|
||||
_returns = _existing.ReturnDefinition;
|
||||
// Seed approved keys from the seam: which keys' scopes contain this method.
|
||||
@@ -291,6 +305,7 @@
|
||||
{
|
||||
_existing.Script = _script;
|
||||
_existing.TimeoutSeconds = _timeoutSeconds;
|
||||
_existing.TraceAllCalls = _traceAllCalls;
|
||||
_existing.ParameterDefinitions = _params?.Trim();
|
||||
_existing.ReturnDefinition = _returns?.Trim();
|
||||
await InboundApiRepository.UpdateApiMethodAsync(_existing);
|
||||
@@ -301,6 +316,7 @@
|
||||
var m = new ApiMethod(_name.Trim(), _script)
|
||||
{
|
||||
TimeoutSeconds = _timeoutSeconds,
|
||||
TraceAllCalls = _traceAllCalls,
|
||||
ParameterDefinitions = _params?.Trim(),
|
||||
ReturnDefinition = _returns?.Trim()
|
||||
};
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
using Bunit;
|
||||
using Bunit.TestDoubles;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.CentralUI.Services;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Audit;
|
||||
|
||||
/// <summary>
|
||||
/// Flow Debugger drill-ins (design 2026-09-05 §4, plan Task 21): the audit drawer's
|
||||
/// detail body and the Execution Chain page both offer <em>Open in Flow Debugger</em>,
|
||||
/// and the inbound-method form carries the <c>TraceAllCalls</c> checkbox wired to both
|
||||
/// save paths.
|
||||
/// </summary>
|
||||
public class FlowDebuggerDrillinTests : BunitContext
|
||||
{
|
||||
public FlowDebuggerDrillinTests()
|
||||
{
|
||||
JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
}
|
||||
|
||||
private static AuditEventView MakeEvent(Guid? executionId) => new()
|
||||
{
|
||||
EventId = Guid.Parse("11111111-2222-3333-4444-555555555555"),
|
||||
OccurredAtUtc = new DateTime(2026, 9, 5, 12, 30, 45, DateTimeKind.Utc),
|
||||
IngestedAtUtc = new DateTime(2026, 9, 5, 12, 30, 46, DateTimeKind.Utc),
|
||||
Channel = AuditChannel.ApiInbound,
|
||||
Kind = AuditKind.InboundRequest,
|
||||
ExecutionId = executionId,
|
||||
Actor = "tester",
|
||||
Target = "POST /api/DelmiaRecipeDownload",
|
||||
Status = AuditStatus.Delivered,
|
||||
DurationMs = 42,
|
||||
};
|
||||
|
||||
private static string SrcRoot
|
||||
{
|
||||
get
|
||||
{
|
||||
var dir = AppContext.BaseDirectory;
|
||||
for (var i = 0; i < 6 && dir is not null; i++)
|
||||
dir = Directory.GetParent(dir)?.FullName;
|
||||
return Path.Combine(dir!, "src", "ZB.MOM.WW.ScadaBridge.CentralUI", "Components");
|
||||
}
|
||||
}
|
||||
|
||||
private static string Read(params string[] relative)
|
||||
=> File.ReadAllText(Path.Combine([SrcRoot, .. relative]));
|
||||
|
||||
[Fact]
|
||||
public void AuditEventDetail_WithExecutionId_OffersOpenInFlowDebugger_AndNavigatesToReplay()
|
||||
{
|
||||
var exec = Guid.Parse("12345678-aaaa-bbbb-cccc-1234567890ab");
|
||||
var cut = Render<AuditEventDetail>(p => p.Add(c => c.Event, MakeEvent(exec)));
|
||||
|
||||
cut.Find("[data-test=\"open-flow-debugger\"]").Click();
|
||||
|
||||
var nav = (BunitNavigationManager)Services.GetRequiredService<NavigationManager>();
|
||||
Assert.Contains($"/deployment/flow-debugger?executionId={exec}", nav.Uri);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuditEventDetail_WithoutExecutionId_HidesTheButton()
|
||||
{
|
||||
var cut = Render<AuditEventDetail>(p => p.Add(c => c.Event, MakeEvent(null)));
|
||||
|
||||
Assert.DoesNotContain("data-test=\"open-flow-debugger\"", cut.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExecutionTreePage_LinksToTheFlowDebuggerReplay_BesideTheAuditLogLink()
|
||||
{
|
||||
var markup = Read("Pages", "Audit", "ExecutionTreePage.razor");
|
||||
|
||||
Assert.Contains("data-test=\"open-flow-debugger\"", markup);
|
||||
Assert.Contains("/deployment/flow-debugger?executionId={_executionId}", markup);
|
||||
// Same block as the existing back-to-log link, so both actions sit together.
|
||||
Assert.True(markup.IndexOf("execution-tree-back-to-log", StringComparison.Ordinal)
|
||||
< markup.IndexOf("open-flow-debugger", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApiMethodForm_HasTraceAllCallsCheckbox_WiredToBothSavePaths()
|
||||
{
|
||||
var markup = Read("Pages", "Design", "ApiMethodForm.razor");
|
||||
|
||||
Assert.Contains("data-test=\"api-method-trace-all-calls\"", markup);
|
||||
Assert.Contains("Trace all calls (Flow Debugger)", markup);
|
||||
Assert.Contains("~7 audit rows per call", markup);
|
||||
// Load, update and create paths all carry the flag.
|
||||
Assert.Contains("_traceAllCalls = _existing.TraceAllCalls;", markup);
|
||||
Assert.Contains("_existing.TraceAllCalls = _traceAllCalls;", markup);
|
||||
Assert.Contains("TraceAllCalls = _traceAllCalls,", markup);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user