fix(centralui): re-apply Audit Log query-string filters on same-page drill-in
The drilldown drawer's 'View this/parent execution' actions call
NavigationManager.NavigateTo('/audit/log?executionId=...') while the
user is already on the routed AuditLogPage. Blazor treats this as a
same-component navigation, so OnInitialized does not re-run and
ApplyQueryStringFilters() (which was wired only to OnInitialized) never
re-parsed the new query string: _currentFilter stayed stale and the
results grid never reloaded to the drill-in target.
AuditLogPage now subscribes to NavigationManager.LocationChanged,
re-applies the query-string filters on every location change (closing
the drawer and calling StateHasChanged), and unsubscribes via
IDisposable. The 'View parent execution' drill-in now genuinely lands
on /audit/log?executionId={parentId} with the grid reloaded.
Also corrects the Playwright test wait: a same-page query-string Blazor
navigation pushes history.pushState over the SignalR circuit rather
than triggering a document load, so WaitForLoadState(NetworkIdle)
returned before the URL settled. Switched to WaitForURLAsync, the
correct primitive for SPA/pushState navigations.
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Components.Routing;
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
using ScadaLink.Commons.Entities.Audit;
|
using ScadaLink.Commons.Entities.Audit;
|
||||||
using ScadaLink.Commons.Types.Audit;
|
using ScadaLink.Commons.Types.Audit;
|
||||||
@@ -31,8 +32,20 @@ namespace ScadaLink.CentralUI.Components.Pages.Audit;
|
|||||||
/// without the user clicking Apply. Unknown values (e.g. an invalid enum name)
|
/// without the user clicking Apply. Unknown values (e.g. an invalid enum name)
|
||||||
/// are silently dropped — the page still renders, just without that constraint.
|
/// are silently dropped — the page still renders, just without that constraint.
|
||||||
/// </para>
|
/// </para>
|
||||||
|
///
|
||||||
|
/// <para>
|
||||||
|
/// Query-string filters are re-applied on every <see cref="NavigationManager.LocationChanged"/>,
|
||||||
|
/// not just on init. The drilldown drawer's "View this/parent execution" actions
|
||||||
|
/// navigate to <c>/audit/log?executionId=…</c> while the user is ALREADY on this
|
||||||
|
/// routed page — Blazor treats that as a same-component navigation, so
|
||||||
|
/// <see cref="OnInitialized"/> does not re-run. Without the
|
||||||
|
/// <see cref="NavigationManager.LocationChanged"/> subscription the URL would
|
||||||
|
/// change but <see cref="_currentFilter"/> would stay stale and the grid would
|
||||||
|
/// never reload to the new drill-in. The subscription is disposed via
|
||||||
|
/// <see cref="IDisposable"/>.
|
||||||
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class AuditLogPage
|
public partial class AuditLogPage : IDisposable
|
||||||
{
|
{
|
||||||
[Inject] private NavigationManager Navigation { get; set; } = null!;
|
[Inject] private NavigationManager Navigation { get; set; } = null!;
|
||||||
|
|
||||||
@@ -44,6 +57,28 @@ public partial class AuditLogPage
|
|||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
ApplyQueryStringFilters();
|
ApplyQueryStringFilters();
|
||||||
|
Navigation.LocationChanged += HandleLocationChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Re-applies the query-string drill-in filters when the URL changes while
|
||||||
|
/// this page stays routed (e.g. the drawer's "View parent execution" action
|
||||||
|
/// navigates to <c>/audit/log?executionId=…</c>). Reassigning
|
||||||
|
/// <see cref="_currentFilter"/> to a fresh instance is what kicks the results
|
||||||
|
/// grid into reloading; we also close the drawer so the operator sees the
|
||||||
|
/// newly filtered grid, and call <see cref="ComponentBase.StateHasChanged"/>
|
||||||
|
/// because this fires outside the normal render lifecycle.
|
||||||
|
/// </summary>
|
||||||
|
private void HandleLocationChanged(object? sender, LocationChangedEventArgs e)
|
||||||
|
{
|
||||||
|
ApplyQueryStringFilters();
|
||||||
|
_drawerOpen = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Navigation.LocationChanged -= HandleLocationChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplyQueryStringFilters()
|
private void ApplyQueryStringFilters()
|
||||||
|
|||||||
@@ -415,7 +415,12 @@ public class AuditLogPageTests
|
|||||||
var viewParent = page.Locator("[data-test='view-parent-execution']");
|
var viewParent = page.Locator("[data-test='view-parent-execution']");
|
||||||
await Assertions.Expect(viewParent).ToBeVisibleAsync();
|
await Assertions.Expect(viewParent).ToBeVisibleAsync();
|
||||||
await viewParent.ClickAsync();
|
await viewParent.ClickAsync();
|
||||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
// The drawer's NavigateTo is a same-page (query-string-only) Blazor
|
||||||
|
// navigation: it pushes history.pushState over the SignalR circuit
|
||||||
|
// rather than triggering a document load, so WaitForLoadState would
|
||||||
|
// return before the URL settles. WaitForURLAsync is the correct wait
|
||||||
|
// primitive for SPA/pushState navigations.
|
||||||
|
await page.WaitForURLAsync($"**/audit/log?executionId={parentExecutionId}");
|
||||||
|
|
||||||
// The drill-in lands on ?executionId={parentExecutionId} and auto-loads
|
// The drill-in lands on ?executionId={parentExecutionId} and auto-loads
|
||||||
// the spawner's own row.
|
// the spawner's own row.
|
||||||
|
|||||||
Reference in New Issue
Block a user