feat(central-ui): Notification report accepts ?correlationId= and opens the notification's detail
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
This commit is contained in:
+70
-1
@@ -5,7 +5,9 @@
|
||||
@using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories
|
||||
@using ZB.MOM.WW.ScadaBridge.Commons.Messages.Notification
|
||||
@using ZB.MOM.WW.ScadaBridge.Communication
|
||||
@using Microsoft.AspNetCore.WebUtilities
|
||||
@inject CommunicationService CommunicationService
|
||||
@inject NavigationManager Navigation
|
||||
@inject ISiteRepository SiteRepository
|
||||
@inject SiteScopeService SiteScope
|
||||
@inject IDialogService Dialog
|
||||
@@ -88,6 +90,20 @@
|
||||
<label class="form-check-label small" for="no-stuck-only">Stuck only</label>
|
||||
</div>
|
||||
</div>
|
||||
@if (_notificationIdFilter is { } notifFilter)
|
||||
{
|
||||
@* Register row 111 (D2): read-only exact-match pill seeded from the
|
||||
?correlationId= deep link (Flow Debugger / Audit Log). Cleared by
|
||||
its × (re-queries) or by Clear. *@
|
||||
<div class="col-auto">
|
||||
<span class="badge text-bg-secondary" data-test="notif-filter-notification"
|
||||
title="@notifFilter">Notification @ShortId(notifFilter)</span>
|
||||
<button type="button" class="btn btn-link btn-sm p-0 ms-1 text-decoration-none"
|
||||
data-test="notif-filter-notification-clear"
|
||||
aria-label="Clear notification filter" title="Clear notification filter"
|
||||
@onclick="ClearNotificationFilter">×</button>
|
||||
</div>
|
||||
}
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-outline-secondary btn-sm" @onclick="ClearFilters"
|
||||
disabled="@(!HasActiveFilters)">Clear</button>
|
||||
@@ -439,6 +455,11 @@
|
||||
private DateTime? _fromFilter;
|
||||
private DateTime? _toFilter;
|
||||
|
||||
// Register row 111 (D2): exact-match NotificationId seeded from ?correlationId=.
|
||||
// Not bound to a control — surfaced as a read-only pill in the filter card.
|
||||
private string? _notificationIdFilter;
|
||||
private const int MaxNotificationIdFilterLength = 64;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
@@ -457,7 +478,52 @@
|
||||
Logger.LogWarning(ex, "Failed to load sites for the report source-site filter.");
|
||||
}
|
||||
|
||||
// Seed the ?correlationId= filter BEFORE the first fetch so the initial grid
|
||||
// load is already narrowed to the linked notification.
|
||||
ApplyQueryStringFilters();
|
||||
|
||||
await RefreshAll();
|
||||
|
||||
// Deep link landed on exactly the row it named — open its detail so the
|
||||
// operator does not have to find and double-click it.
|
||||
if (_notificationIdFilter is { } wanted
|
||||
&& _notifications is { Count: 1 }
|
||||
&& _notifications[0].NotificationId == wanted)
|
||||
{
|
||||
await ShowDetail(_notifications[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pre-apply the <c>?correlationId=</c> deep-link filter from the URL query
|
||||
/// string (Flow Debugger External-lane node / Audit Log convention). The value
|
||||
/// is the notification's own id (a string, not a GUID) — accepted trimmed when
|
||||
/// non-blank and at most <see cref="MaxNotificationIdFilterLength"/> chars;
|
||||
/// anything else is silently dropped, leaving the page unfiltered.
|
||||
/// </summary>
|
||||
private void ApplyQueryStringFilters()
|
||||
{
|
||||
var uri = Navigation.ToAbsoluteUri(Navigation.Uri);
|
||||
var query = QueryHelpers.ParseQuery(uri.Query);
|
||||
if (query.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (query.TryGetValue("correlationId", out var values))
|
||||
{
|
||||
var candidate = values.ToString()?.Trim();
|
||||
if (!string.IsNullOrEmpty(candidate) && candidate.Length <= MaxNotificationIdFilterLength)
|
||||
{
|
||||
_notificationIdFilter = candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ClearNotificationFilter()
|
||||
{
|
||||
_notificationIdFilter = null;
|
||||
await Search();
|
||||
}
|
||||
|
||||
private async Task RefreshAll()
|
||||
@@ -491,7 +557,8 @@
|
||||
To: ToUtc(_toFilter),
|
||||
PageNumber: _pageNumber,
|
||||
PageSize: _pageSize,
|
||||
SourceNodeFilter: NullIfEmpty(_nodeFilter));
|
||||
SourceNodeFilter: NullIfEmpty(_nodeFilter),
|
||||
NotificationIdFilter: _notificationIdFilter);
|
||||
|
||||
var response = await CommunicationService.QueryNotificationOutboxAsync(request);
|
||||
if (response.Success)
|
||||
@@ -693,9 +760,11 @@
|
||||
_stuckOnly = false;
|
||||
_fromFilter = null;
|
||||
_toFilter = null;
|
||||
_notificationIdFilter = null;
|
||||
}
|
||||
|
||||
private bool HasActiveFilters =>
|
||||
_notificationIdFilter != null ||
|
||||
!string.IsNullOrEmpty(_statusFilter) ||
|
||||
!string.IsNullOrEmpty(_typeFilter) ||
|
||||
!string.IsNullOrEmpty(_siteFilter) ||
|
||||
|
||||
Reference in New Issue
Block a user