From ef025a325d89d629a72fe6492efbad21b9a1cd9b Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sun, 24 May 2026 05:44:21 -0400 Subject: [PATCH] feat(centralui): Bundle Import filter on ConfigurationAuditLog page --- .../Pages/Audit/ConfigurationAuditLog.razor | 51 +++++++++++++++++++ .../Repositories/ICentralUiRepository.cs | 1 + .../Repositories/CentralUiRepository.cs | 4 ++ .../RepositoryTests.cs | 23 +++++++++ 4 files changed, 79 insertions(+) diff --git a/src/ScadaLink.CentralUI/Components/Pages/Audit/ConfigurationAuditLog.razor b/src/ScadaLink.CentralUI/Components/Pages/Audit/ConfigurationAuditLog.razor index e7d76f4..2e19de4 100644 --- a/src/ScadaLink.CentralUI/Components/Pages/Audit/ConfigurationAuditLog.razor +++ b/src/ScadaLink.CentralUI/Components/Pages/Audit/ConfigurationAuditLog.razor @@ -5,6 +5,7 @@ @using ScadaLink.Commons.Interfaces.Repositories @attribute [Authorize(Policy = AuthorizationPolicies.OperationalAudit)] @inject ICentralUiRepository CentralUiRepository +@inject NavigationManager Nav @inject IJSRuntime JS
@@ -12,6 +13,23 @@ + @* Bundle Import filter chip (T24). Set via ?bundleImportId={guid} query + string so drill-ins from the Import wizard / other pages can scope this + page to a single import run. Cleared via the × button, which navigates + back to the page without the query param so the user sees all rows. *@ + @if (BundleImportId is Guid bundleId) + { +
+ + Filtered by Bundle Import: @bundleId.ToString()[..8] + + +
+ } +
@@ -190,6 +208,13 @@
@code { + /// + /// T24 (Transport). When non-null, scopes the page to a single bundle + /// import run. Set via the ?bundleImportId= query string from + /// drill-ins (Import wizard summary, future BundleImported row links). + /// + [SupplyParameterFromQuery, Parameter] public Guid? BundleImportId { get; set; } + private string? _filterUser; private string? _filterEntityType; private string? _filterAction; @@ -216,6 +241,10 @@ private int TotalPages => _pageSize > 0 ? Math.Max(1, (_totalCount + _pageSize - 1) / _pageSize) : 1; private bool HasMore => _page * _pageSize < _totalCount; + // Tracks the BundleImportId we last fetched against so a re-render with the + // same query param doesn't re-run the query on every parameter set. + private Guid? _lastFetchedBundleImportId; + protected override async Task OnAfterRenderAsync(bool firstRender) { if (!firstRender) return; @@ -233,6 +262,27 @@ } } + protected override async Task OnParametersSetAsync() + { + // T24: when the BundleImportId query param is set (or cleared), refetch + // automatically so the user lands on a pre-filtered page from a drill-in + // link without having to click Search. + if (BundleImportId != _lastFetchedBundleImportId) + { + _lastFetchedBundleImportId = BundleImportId; + _page = 1; + await FetchPage(); + } + } + + private void ClearBundleImportFilter() + { + // Strip the query param by navigating to the bare page route. The + // resulting OnParametersSetAsync run will refetch with BundleImportId + // back to null. + Nav.NavigateTo("/audit/configuration"); + } + private async Task Search() { _page = 1; @@ -265,6 +315,7 @@ action: string.IsNullOrWhiteSpace(_filterAction) ? null : _filterAction.Trim(), from: BrowserTime.LocalInputToUtc(_filterFrom, _browserUtcOffsetMinutes), to: BrowserTime.LocalInputToUtc(_filterTo, _browserUtcOffsetMinutes), + bundleImportId: BundleImportId, page: _page, pageSize: _pageSize); diff --git a/src/ScadaLink.Commons/Interfaces/Repositories/ICentralUiRepository.cs b/src/ScadaLink.Commons/Interfaces/Repositories/ICentralUiRepository.cs index ff012ad..8fad6b7 100644 --- a/src/ScadaLink.Commons/Interfaces/Repositories/ICentralUiRepository.cs +++ b/src/ScadaLink.Commons/Interfaces/Repositories/ICentralUiRepository.cs @@ -25,6 +25,7 @@ public interface ICentralUiRepository DateTimeOffset? to = null, string? entityId = null, string? entityName = null, + Guid? bundleImportId = null, int page = 1, int pageSize = 50, CancellationToken cancellationToken = default); diff --git a/src/ScadaLink.ConfigurationDatabase/Repositories/CentralUiRepository.cs b/src/ScadaLink.ConfigurationDatabase/Repositories/CentralUiRepository.cs index 7973cef..2261334 100644 --- a/src/ScadaLink.ConfigurationDatabase/Repositories/CentralUiRepository.cs +++ b/src/ScadaLink.ConfigurationDatabase/Repositories/CentralUiRepository.cs @@ -104,6 +104,7 @@ public class CentralUiRepository : ICentralUiRepository DateTimeOffset? to = null, string? entityId = null, string? entityName = null, + Guid? bundleImportId = null, int page = 1, int pageSize = 50, CancellationToken cancellationToken = default) @@ -131,6 +132,9 @@ public class CentralUiRepository : ICentralUiRepository if (!string.IsNullOrWhiteSpace(entityName)) query = query.Where(a => a.EntityName.Contains(entityName)); + if (bundleImportId is Guid bundleId) + query = query.Where(a => a.BundleImportId == bundleId); + var totalCount = await query.CountAsync(cancellationToken); var entries = await query diff --git a/tests/ScadaLink.ConfigurationDatabase.Tests/RepositoryTests.cs b/tests/ScadaLink.ConfigurationDatabase.Tests/RepositoryTests.cs index aa6ba9c..a8d25af 100644 --- a/tests/ScadaLink.ConfigurationDatabase.Tests/RepositoryTests.cs +++ b/tests/ScadaLink.ConfigurationDatabase.Tests/RepositoryTests.cs @@ -353,6 +353,29 @@ public class CentralUiRepositoryTests : IDisposable Assert.Single(entries); } + [Fact] + public async Task GetAuditLogEntries_FiltersByBundleImportId() + { + // T24 — Bundle Import filter on the Configuration Audit Log page is + // backed by the new optional bundleImportId arg on the repo query. + // Only rows stamped with the given id should come back. + var importA = Guid.NewGuid(); + var importB = Guid.NewGuid(); + _context.AuditLogEntries.AddRange( + new AuditLogEntry("admin", "Create", "Template", "1", "T1") + { Timestamp = DateTimeOffset.UtcNow, BundleImportId = importA }, + new AuditLogEntry("admin", "Create", "Template", "2", "T2") + { Timestamp = DateTimeOffset.UtcNow, BundleImportId = importB }, + new AuditLogEntry("admin", "Update", "Template", "3", "T3") + { Timestamp = DateTimeOffset.UtcNow }); + await _context.SaveChangesAsync(); + + var (entries, total) = await _repository.GetAuditLogEntriesAsync(bundleImportId: importA); + Assert.Single(entries); + Assert.Equal(1, total); + Assert.Equal(importA, entries[0].BundleImportId); + } + [Fact] public async Task GetAuditLogEntries_ReverseChronologicalWithPagination() {