4.9 KiB
Execution-Tree Node Detail Modal (Design)
Date: 2026-05-22 Status: Validated — ready for implementation planning.
Problem
On the Central UI execution-chain tree page (/audit/execution-tree, the
ParentExecutionId feature's Task 10), each node represents one execution and
shows a small inline summary. The only interaction is the short-id link, which
navigates away to /audit/log?executionId=…. There is no way to inspect an
execution's actual audit rows without leaving the tree.
Decision
Double-clicking a tree node opens a modal showing that execution's audit
rows. The modal mirrors the /audit/log detail experience: a list of the
execution's rows, and clicking a row reveals that row's full field/payload
detail — the exact content the Audit Log drilldown drawer shows.
Resolved during brainstorming:
- Modal content — the execution's audit rows, with per-row full detail.
- Multi-row executions — list the rows; clicking one shows its detail. A single-row execution opens straight to the detail view.
- Trigger — double-click anywhere on the node. The short-id link keeps its single-click navigation to the Audit Log grid (unchanged).
Considered and rejected
- Reuse
AuditDrilldownDrawerdirectly. The drawer renders oneAuditEventby design; bending it into a list-or-detail hybrid is more invasive to a well-tested component than a purpose-built modal. - Inline expansion under the node. The user asked for a modal, and an inline panel inside the recursive tree fights the existing expand/collapse toggle and is visually messy.
Components
| Component | Change |
|---|---|
AuditEventDetail.razor |
New. The single-AuditEvent field/payload/drill-in-button block, extracted verbatim from AuditDrilldownDrawer's body. |
AuditDrilldownDrawer.razor |
Modified. Keeps its offcanvas chrome + close button; its body becomes <AuditEventDetail Event="Event" />. The one refactor with regression risk — existing drawer bUnit + Playwright tests guard it. |
ExecutionDetailModal.razor (+ .razor.cs + .razor.css) |
New. A custom Bootstrap modal — hand-rolled modal / modal-backdrop markup, Blazor-toggled, no component framework (the same way AuditDrilldownDrawer hand-rolls offcanvas). |
ExecutionTree.razor / .razor.cs |
Modified. @ondblclick on the node body invokes a new OnNodeActivated EventCallback<Guid>; recursive child instances re-raise it upward so the event bubbles to the root. |
ExecutionTreePage.razor / .razor.cs |
Modified. Hosts one ExecutionDetailModal; wires the tree's OnNodeActivated to open it. |
No database, repository, or service changes — purely Central UI. The
IAuditLogQueryService.QueryAsync method already filters by ExecutionId; the
modal reuses it (no new service method).
Data flow
- Double-click a node →
ExecutionTreeinvokesOnNodeActivated(node.ExecutionId). - The event bubbles up the recursive
ExecutionTreeinstances toExecutionTreePage. - The page opens
ExecutionDetailModalwith theExecutionId. - The modal calls
IAuditLogQueryService.QueryAsync(new AuditLogQueryFilter(ExecutionId: id), new AuditLogPaging(PageSize: 100))→IReadOnlyList<AuditEvent>. - Render by row count:
- ≥ 2 rows — a compact row list (kind / status / target / time, each row a button); clicking a row swaps to its
<AuditEventDetail>with a "← Back to rows" control. - 1 row — opens straight to the detail view.
- 0 rows — a stub execution; a friendly empty state.
- ≥ 2 rows — a compact row list (kind / status / target / time, each row a button); clicking a row swaps to its
- Close via the X button, the backdrop, or Esc.
The list rows are full AuditEvent objects (that is what QueryAsync returns),
so the list→detail transition needs no second fetch.
Error handling
- A
QueryAsyncfailure surfaces an inline error inside the modal ("Couldn't load this execution's rows") and never tears down the SignalR circuit — mirroring the tree page's existingtry/catchdegrade-gracefully pattern. - An empty result renders the friendly empty state, not an error.
Testing
- bUnit —
ExecutionTreeraisesOnNodeActivatedon@ondblclickand bubbles it through a nested instance;ExecutionDetailModallist renders from a fake query service, row click → detail, 1-row jump-straight, 0-row empty state, close;AuditEventDetailrenders the field block; the existingAuditDrilldownDrawertests stay green after the body extraction. - Playwright — on
/audit/execution-tree, double-click a node → modal opens → (multi-row) row list → click a row → detail → close. Uses a seeded chain. frontend-designskill for the modal markup/CSS — clean corporate aesthetic, custom Blazor + Bootstrap, no component frameworks.
Constraints
- Central UI only — no DB / repository / service-contract changes.
- Custom Blazor + Bootstrap; no component frameworks.
- The short-id link's single-click navigation to
/audit/log?executionId=…is unchanged.