feat(audit): multi-value filters across ManagementService, CLI and Central UI
This commit is contained in:
@@ -15,20 +15,20 @@ namespace ScadaLink.CentralUI.Components.Audit;
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// The repository filter contract (<see cref="AuditLogQueryFilter"/>) is single-value
|
||||
/// per dimension today; the chip multi-selects therefore collapse to the FIRST
|
||||
/// selected chip when the model is published via <see cref="ToFilter"/>. That is a
|
||||
/// deliberate Bundle B scope decision — the chip UI is preserved so a follow-up can
|
||||
/// either repeat the query per chip or widen the filter contract without rewriting
|
||||
/// the form. Instance and Script free-text are also UI-only today: the underlying
|
||||
/// filter has no matching columns, so they are dropped during collapse.
|
||||
/// The repository filter contract (<see cref="AuditLogQueryFilter"/>) is multi-value
|
||||
/// per dimension: the chip multi-selects map straight through to the
|
||||
/// <c>Channels</c> / <c>Kinds</c> / <c>Statuses</c> / <c>SourceSiteIds</c> filter
|
||||
/// lists when the model is published via <see cref="ToFilter"/> — an empty set means
|
||||
/// "do not constrain". Instance and Script free-text remain UI-only: the underlying
|
||||
/// filter has no matching columns, so they are dropped when the model is published.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// The Errors-only toggle is a convenience: when true AND no explicit Status chips
|
||||
/// are selected, the collapsed filter pins <see cref="AuditStatus.Failed"/> (the
|
||||
/// first of {Failed, Parked, Discarded}). When Status chips ARE selected the toggle
|
||||
/// is a no-op — the explicit Status filter wins.
|
||||
/// are selected, <see cref="ToFilter"/> targets the full error-status set
|
||||
/// {<see cref="AuditStatus.Failed"/>, <see cref="AuditStatus.Parked"/>,
|
||||
/// <see cref="AuditStatus.Discarded"/>}. When Status chips ARE selected the toggle
|
||||
/// is a no-op — the explicit Status chips win.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class AuditQueryModel
|
||||
@@ -104,20 +104,21 @@ public sealed class AuditQueryModel
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collapses this UI model to the repository's single-value filter.
|
||||
/// See class doc for the multi-select → single-value contract.
|
||||
/// Publishes this UI model as the repository's multi-value filter: each chip
|
||||
/// multi-select maps straight through to its filter list (an empty set yields
|
||||
/// <c>null</c> — "do not constrain"). See class doc for the Errors-only rule.
|
||||
/// </summary>
|
||||
public AuditLogQueryFilter ToFilter(DateTime utcNow)
|
||||
{
|
||||
var status = ResolveStatus();
|
||||
var statuses = ResolveStatuses();
|
||||
|
||||
var (fromUtc, toUtc) = ResolveTimeWindow(utcNow);
|
||||
|
||||
return new AuditLogQueryFilter(
|
||||
Channels: Channels.Count > 0 ? new[] { Channels.First() } : null,
|
||||
Kinds: Kinds.Count > 0 ? new[] { Kinds.First() } : null,
|
||||
Statuses: status is { } s ? new[] { s } : null,
|
||||
SourceSiteIds: SiteIdentifiers.Count > 0 ? new[] { SiteIdentifiers.First() } : null,
|
||||
Channels: Channels.Count > 0 ? Channels.ToArray() : null,
|
||||
Kinds: Kinds.Count > 0 ? Kinds.ToArray() : null,
|
||||
Statuses: statuses,
|
||||
SourceSiteIds: SiteIdentifiers.Count > 0 ? SiteIdentifiers.ToArray() : null,
|
||||
Target: string.IsNullOrWhiteSpace(TargetSearch) ? null : TargetSearch.Trim(),
|
||||
Actor: string.IsNullOrWhiteSpace(ActorSearch) ? null : ActorSearch.Trim(),
|
||||
CorrelationId: null,
|
||||
@@ -125,20 +126,22 @@ public sealed class AuditQueryModel
|
||||
ToUtc: toUtc);
|
||||
}
|
||||
|
||||
private AuditStatus? ResolveStatus()
|
||||
/// <summary>The non-success statuses targeted by the Errors-only toggle.</summary>
|
||||
private static readonly AuditStatus[] ErrorStatuses =
|
||||
{ AuditStatus.Failed, AuditStatus.Parked, AuditStatus.Discarded };
|
||||
|
||||
private IReadOnlyList<AuditStatus>? ResolveStatuses()
|
||||
{
|
||||
if (Statuses.Count > 0)
|
||||
{
|
||||
// Explicit chips win — Errors-only is a no-op.
|
||||
return Statuses.First();
|
||||
return Statuses.ToArray();
|
||||
}
|
||||
|
||||
if (ErrorsOnly)
|
||||
{
|
||||
// Single-value filter contract: Failed is the lead non-success status.
|
||||
// When the filter widens to multi-value the full {Failed, Parked, Discarded}
|
||||
// set will flow through.
|
||||
return AuditStatus.Failed;
|
||||
// Multi-value filter: Errors-only targets the full non-success set.
|
||||
return ErrorStatuses;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user