Skip to content

Notifications 6/6: notification history page (optional) #225

Description

@LarsLaskowski

Final, optional stage of the notification feature set. Depends on #220 (ledger entity) and benefits from #224 (dead-letter state).

Full design: notification concept

Context

Once notifications are dispatched in the background, the only evidence a user has that something was sent is the receiving system (webhook endpoint, ntfy topic). When a delivery dead-letters, the reason lives in NotificationRecord.LastError and is invisible without database access. A minimal read-only history page closes that gap and makes the subsystem debuggable from the UI.

Scope

New view data

src/DockerUpdateGuard/UI/NotificationRecordViewData.cs following the existing *ViewData conventions (compare ScanHistoryItemData.cs, VulnerabilityFindingViewData.cs):

Property Purpose
Id Record identifier
Kind NotificationKind for icon/label selection
ChannelName Which channel the record belongs to
Status NotificationRecordStatus for the status chip
Subject Denormalized subject text
Details Optional detail line
AttemptCount Delivery attempts made
LastError Error text for dead-lettered records
CreatedAtUtc / SentAtUtc / NextAttemptAtUtc Timestamps

View service

Extend src/DockerUpdateGuard/UI/IApplicationViewService.cs and ApplicationViewService.cs with

Task<IReadOnlyList<NotificationRecordViewData>> GetNotificationHistoryAsync(int maxItems, CancellationToken cancellationToken);
  • Ordered by CreatedAtUtc descending, capped (default 200), AsNoTracking, projected directly into the view data (no entity leakage into the UI layer — the established pattern for every page).
  • Suppressed baseline records are excluded by default so the first-run baseline does not bury real history; expose them behind a filter toggle only if it stays trivial.

Page

src/DockerUpdateGuard/Components/Pages/NotificationHistory.razor + .razor.cs code-behind, route /notification-history:

  • MudBlazor table listing subject, kind, channel, status chip, attempt count, created/sent timestamps; LastError shown in an expandable detail area for dead-lettered rows (compare the collapsible detail pattern introduced in Collapse severity and delta chips behind an expandable detail area #219).
  • Uses PersistentComponentState and a skeleton loader (TableSkeleton) exactly like the existing pages.
  • Empty state distinguishes "notifications disabled" from "no notifications yet", so the page explains itself when the feature is off.
  • Follows the docs/ui-concept design tokens (--dug-*) and mobile breakpoints.

Navigation

src/DockerUpdateGuard/Components/Layout/NavMenu.razor: new MudNavLink Href="notification-history" with a bell icon (Icons.Material.Filled.Notifications), rendered only when Notifications:Enabled is true — the same conditional pattern already used for the "My Images" and "Base Images" entries.

Out of scope

  • A settings UI for the notification configuration — configuration stays file/environment based like every other option section.
  • Manual re-send or dead-letter requeue actions.
  • A dashboard tile for notifications.

Testing

  • src/Tests/DockerUpdateGuard.Tests/NotificationHistoryPageTests.cs — bunit render tests via BlazorTestContextFactory: rows rendered per record, status chips, dead-letter error visible, both empty states.
  • NotificationHistoryPersistentStateTests.cs mirroring the existing *PersistentStateTests files.
  • ApplicationViewServiceTests extension: ordering, cap, Suppressed exclusion.

Acceptance criteria

  • /notification-history lists notification records with status, channel, attempt count, and timestamps
  • Dead-lettered records surface LastError in the UI
  • The nav entry appears only when notifications are enabled
  • The page never touches DockerUpdateGuardDbContext directly — all data flows through IApplicationViewService
  • Skeleton loader and PersistentComponentState behave like on the existing pages
  • Render and persistent-state tests pass

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions