using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.WebUtilities; using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit; namespace ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Audit; /// /// Code-behind for the execution-chain tree page (Audit Log ParentExecutionId /// feature, Task 10). Route /audit/execution-tree, reached via the Audit /// Log drilldown drawer's "View execution chain" action with /// ?executionId={guid}. /// /// /// On initialization the page parses ?executionId= (lax-parsed, matching /// the Audit Log page's drill-in contract — an absent or unparseable value /// leaves the page in a guidance state and issues NO service call), then asks /// /// for the whole chain. The flat list is handed /// to the recursive ExecutionTree component, which assembles + renders /// the tree. /// /// /// /// The data path mirrors the Audit Log results grid: the page talks ONLY to the /// CentralUI IAuditLogQueryService facade, never IAuditLogRepository /// directly, so the page can be unit-tested with a substituted service. /// /// public partial class ExecutionTreePage { [Inject] private NavigationManager Navigation { get; set; } = null!; // The parsed ?executionId= value, or null when absent / unparseable. private Guid? _executionId; // The flat chain returned by the query service; null until the load // completes (or when no id was supplied). private IReadOnlyList? _nodes; private bool _loading; private string? _error; // Execution-Tree Node Detail Modal feature (Task 4) — state backing the // . A double-click on a tree node sets // _modalExecutionId + flips _modalOpen true; the modal loads that // execution's audit rows on the closed → open transition. _modalOpen is the // visibility gate — _modalExecutionId is left intact across a close (it is // harmless while the modal is hidden and avoids a flicker if reopened). private Guid? _modalExecutionId; private bool _modalOpen; /// protected override async Task OnInitializedAsync() { _executionId = ParseExecutionId(); if (_executionId is null) { // No id — render guidance, do not touch the service. return; } await LoadChainAsync(_executionId.Value); } /// /// Lax-parses ?executionId=. Returns null when the param is absent or /// is not a valid — the page then shows guidance instead /// of an error, consistent with the Audit Log page's drill-in handling. /// private Guid? ParseExecutionId() { var uri = Navigation.ToAbsoluteUri(Navigation.Uri); var query = QueryHelpers.ParseQuery(uri.Query); if (query.TryGetValue("executionId", out var values) && Guid.TryParse(values.ToString(), out var parsed)) { return parsed; } return null; } private async Task LoadChainAsync(Guid executionId) { _loading = true; _error = null; try { _nodes = await AuditLogQueryService.GetExecutionTreeAsync(executionId); } catch (Exception ex) { // A transient DB outage degrades this page to an error banner // rather than killing the circuit — the same defensive posture the // Audit Log grid takes around its query. _error = $"Could not load the execution chain: {ex.Message}"; _nodes = null; } finally { _loading = false; } } /// /// Raised by ExecutionTree (bubbled up from a node double-click) with /// the activated node's ExecutionId. Opens the /// ExecutionDetailModal for that execution — the modal loads its /// audit rows on the closed → open transition. /// private void HandleNodeActivated(Guid executionId) { _modalExecutionId = executionId; _modalOpen = true; } /// /// Raised by ExecutionDetailModal when the user dismisses it. Flips /// the visibility gate closed; is left as-is. /// private void HandleModalClose() => _modalOpen = false; }