feat(auditlog): GetExecutionTreeAsync recursive execution-chain query
This commit is contained in:
@@ -134,4 +134,45 @@ public interface IAuditLogRepository
|
||||
TimeSpan window,
|
||||
DateTime? nowUtc = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Audit Log ParentExecutionId feature (Task 8) — given any
|
||||
/// <paramref name="executionId"/> in an execution chain, returns the whole
|
||||
/// chain rooted at the topmost ancestor: one <see cref="ExecutionTreeNode"/>
|
||||
/// per distinct execution, summarising its <c>AuditLog</c> rows. The Central
|
||||
/// UI renders the result as a tree.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The input id may be any node in the chain — a leaf, the root, or a middle
|
||||
/// node. The implementation first walks <em>up</em> via
|
||||
/// <c>ParentExecutionId</c> to find the root, then walks <em>down</em> from
|
||||
/// the root via a recursive CTE, so the full chain is returned regardless of
|
||||
/// entry point.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The <c>ParentExecutionId</c> graph is a tree (acyclic by construction —
|
||||
/// each execution is minted fresh and its parent always pre-exists). Both
|
||||
/// the upward walk and the downward CTE are nonetheless bounded at 32 levels
|
||||
/// as a guard against corrupt/pathological data: a depth that exceeds the
|
||||
/// guard raises an error rather than hanging the server. Chains are shallow
|
||||
/// (1-2 levels typical) so the guard is never reached in practice.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// A "stub" node — an execution that emitted no rows of its own yet is
|
||||
/// referenced by a child via <c>ParentExecutionId</c>, or whose rows have
|
||||
/// been purged — still appears, with <see cref="ExecutionTreeNode.RowCount"/>
|
||||
/// = 0. A purged/missing parent simply ends the upward walk.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When no <c>AuditLog</c> row carries <paramref name="executionId"/> in
|
||||
/// either <c>ExecutionId</c> or <c>ParentExecutionId</c>, the result is a
|
||||
/// single stub node for <paramref name="executionId"/> itself
|
||||
/// (<see cref="ExecutionTreeNode.RowCount"/> = 0) — consistent with the
|
||||
/// stub-node treatment of any other row-less execution.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
Task<IReadOnlyList<ExecutionTreeNode>> GetExecutionTreeAsync(
|
||||
Guid executionId,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
|
||||
71
src/ScadaLink.Commons/Types/Audit/ExecutionTreeNode.cs
Normal file
71
src/ScadaLink.Commons/Types/Audit/ExecutionTreeNode.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
namespace ScadaLink.Commons.Types.Audit;
|
||||
|
||||
/// <summary>
|
||||
/// One execution within an execution chain returned by
|
||||
/// <see cref="ScadaLink.Commons.Interfaces.Repositories.IAuditLogRepository.GetExecutionTreeAsync"/>.
|
||||
/// Each node summarises the <c>AuditLog</c> rows sharing a single
|
||||
/// <see cref="ExecutionId"/>; the Central UI renders the set as a tree by
|
||||
/// joining <see cref="ParentExecutionId"/> to a parent node's
|
||||
/// <see cref="ExecutionId"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>Stub nodes.</b> An execution that performed a trust-boundary action but
|
||||
/// crossed it without emitting any audit row — or whose own rows have been
|
||||
/// purged — still appears as a node when a child references it via
|
||||
/// <see cref="ParentExecutionId"/>. Such a stub node has <see cref="RowCount"/>
|
||||
/// = 0, empty <see cref="Channels"/>/<see cref="Statuses"/>, null
|
||||
/// <see cref="SourceSiteId"/>/<see cref="SourceInstanceId"/>, null timestamps,
|
||||
/// and a null <see cref="ParentExecutionId"/> (a purged/ghost parent leaves no
|
||||
/// row from which its own parent could be read — the upward walk ends there).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="Channels"/> and <see cref="Statuses"/> are the distinct sets of
|
||||
/// the corresponding enum names present across the execution's rows, modelled
|
||||
/// as <see cref="IReadOnlyList{T}"/> of string to mirror how the repository's
|
||||
/// query filters already pass small bounded sets around.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="ExecutionId">The execution this node summarises.</param>
|
||||
/// <param name="ParentExecutionId">
|
||||
/// The <see cref="ExecutionId"/> of the spawning execution, or null for the
|
||||
/// root (and for stub nodes, whose own parent is unknowable).
|
||||
/// </param>
|
||||
/// <param name="RowCount">
|
||||
/// Number of <c>AuditLog</c> rows carrying this <see cref="ExecutionId"/>; 0 for
|
||||
/// a stub node.
|
||||
/// </param>
|
||||
/// <param name="Channels">
|
||||
/// Distinct <see cref="ScadaLink.Commons.Types.Enums.AuditChannel"/> names
|
||||
/// present across this execution's rows; empty for a stub node.
|
||||
/// </param>
|
||||
/// <param name="Statuses">
|
||||
/// Distinct <see cref="ScadaLink.Commons.Types.Enums.AuditStatus"/> names
|
||||
/// present across this execution's rows; empty for a stub node.
|
||||
/// </param>
|
||||
/// <param name="SourceSiteId">
|
||||
/// Source site of the execution's rows when consistent; null for a stub node
|
||||
/// (or when the rows carry no site).
|
||||
/// </param>
|
||||
/// <param name="SourceInstanceId">
|
||||
/// Source instance of the execution's rows when consistent; null for a stub
|
||||
/// node (or when the rows carry no instance).
|
||||
/// </param>
|
||||
/// <param name="FirstOccurredAtUtc">
|
||||
/// Earliest <c>OccurredAtUtc</c> across this execution's rows; null for a stub
|
||||
/// node.
|
||||
/// </param>
|
||||
/// <param name="LastOccurredAtUtc">
|
||||
/// Latest <c>OccurredAtUtc</c> across this execution's rows; null for a stub
|
||||
/// node.
|
||||
/// </param>
|
||||
public sealed record ExecutionTreeNode(
|
||||
Guid ExecutionId,
|
||||
Guid? ParentExecutionId,
|
||||
int RowCount,
|
||||
IReadOnlyList<string> Channels,
|
||||
IReadOnlyList<string> Statuses,
|
||||
string? SourceSiteId,
|
||||
string? SourceInstanceId,
|
||||
DateTime? FirstOccurredAtUtc,
|
||||
DateTime? LastOccurredAtUtc);
|
||||
Reference in New Issue
Block a user