feat(ui): server-side streaming CSV export of Audit Log (#23 M7)

This commit is contained in:
Joseph Doherty
2026-05-20 20:57:01 -04:00
parent 943c2ced39
commit 8744630adb
10 changed files with 1163 additions and 0 deletions

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.WebUtilities;
using ScadaLink.Commons.Entities.Audit;
@@ -158,4 +159,70 @@ public partial class AuditLogPage
// grid) shows the same row instantly without a re-render flicker.
_drawerOpen = false;
}
/// <summary>
/// Bundle F (M7-T14): URL the Export-CSV link points at. Renders the most
/// recently applied filter as query-string params so the server-side
/// streaming endpoint reproduces the user's current view. With no filter
/// applied yet, returns the bare endpoint — i.e. an unconstrained export.
/// </summary>
/// <remarks>
/// Built here rather than in markup so the per-row test coverage can
/// exercise the URL composition without booting the full Blazor renderer.
/// </remarks>
internal string ExportUrl => BuildExportUrl(_currentFilter);
internal static string BuildExportUrl(AuditLogQueryFilter? filter)
{
const string basePath = "/api/centralui/audit/export";
if (filter is null)
{
return basePath;
}
var parts = new List<KeyValuePair<string, string?>>(9);
if (filter.Channel is { } ch)
{
parts.Add(new("channel", ch.ToString()));
}
if (filter.Kind is { } kind)
{
parts.Add(new("kind", kind.ToString()));
}
if (filter.Status is { } status)
{
parts.Add(new("status", status.ToString()));
}
if (!string.IsNullOrWhiteSpace(filter.SourceSiteId))
{
parts.Add(new("site", filter.SourceSiteId));
}
if (!string.IsNullOrWhiteSpace(filter.Target))
{
parts.Add(new("target", filter.Target));
}
if (!string.IsNullOrWhiteSpace(filter.Actor))
{
parts.Add(new("actor", filter.Actor));
}
if (filter.CorrelationId is { } corr)
{
parts.Add(new("correlationId", corr.ToString()));
}
if (filter.FromUtc is { } from)
{
parts.Add(new("from", from.ToString("O", CultureInfo.InvariantCulture)));
}
if (filter.ToUtc is { } to)
{
parts.Add(new("to", to.ToString("O", CultureInfo.InvariantCulture)));
}
if (parts.Count == 0)
{
return basePath;
}
return QueryHelpers.AddQueryString(basePath, parts);
}
}