feat(kpihistory): add 30d/90d trend windows to Audit/SiteCalls/Notifications/Health (plan #22 T7)

Adds 30d (720h) and 90d (2160h) toggle buttons alongside the existing
24h/7d controls on all four KPI trend surfaces. No fetch-logic changes:
the setters already recompute fromUtc and re-fetch, and the query service
(T5) transparently routes windows > RollupThresholdHours to hourly rollups
— so these windows are what actually exercise the rollup path. bUnit tests
extend the Audit and SiteCalls trend harnesses to cover the new buttons.

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
Joseph Doherty
2026-07-10 12:12:17 -04:00
parent bedf3c55f0
commit bbfc1b3278
6 changed files with 91 additions and 0 deletions
@@ -117,6 +117,44 @@ public class AuditLogPageTrendTests : BunitContext
Arg.Any<DateTime>(), Arg.Any<DateTime>(), Arg.Any<int?>(), Arg.Any<CancellationToken>());
}
[Fact]
public void TrendsPanel_LongRangeWindows_RenderAndRefetchWithMatchingSpan()
{
// The 30d (720h) / 90d (2160h) buttons added for the hourly-rollup path
// (plan #22 T7) must render alongside 24h/7d and, when clicked, re-fetch
// every series over the corresponding range — the query service then
// transparently routes the long windows to hourly rollups.
var ranges = new List<(DateTime From, DateTime To)>();
var kpi = Substitute.For<IKpiHistoryQueryService>();
kpi.GetSeriesAsync(
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string?>(),
Arg.Any<DateTime>(), Arg.Any<DateTime>(), Arg.Any<int?>(), Arg.Any<CancellationToken>())
.Returns(ci =>
{
ranges.Add((ci.ArgAt<DateTime>(4), ci.ArgAt<DateTime>(5)));
return Task.FromResult(ThreePoints());
});
var cut = RenderAuditLogPage(kpi);
// Both new window buttons render in the toggle group.
cut.WaitForAssertion(() =>
{
Assert.Contains(cut.FindAll("button"), b => b.TextContent.Trim() == "30d");
Assert.Contains(cut.FindAll("button"), b => b.TextContent.Trim() == "90d");
});
// Click 30d — every series re-fetches over a ~720h span.
cut.FindAll("button").First(b => b.TextContent.Trim() == "30d").Click();
cut.WaitForAssertion(() =>
Assert.Contains(ranges, r => Math.Abs((r.To - r.From).TotalHours - 720) < 0.5));
// Click 90d — every series re-fetches over a ~2160h span.
cut.FindAll("button").First(b => b.TextContent.Trim() == "90d").Click();
cut.WaitForAssertion(() =>
Assert.Contains(ranges, r => Math.Abs((r.To - r.From).TotalHours - 2160) < 0.5));
}
[Fact]
public void TrendsPanel_WhenQueryServiceThrows_PageStillRenders()
{