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:
@@ -39,6 +39,16 @@
|
|||||||
@onclick="() => SetWindowAsync(168)" disabled="@_trendsLoading">
|
@onclick="() => SetWindowAsync(168)" disabled="@_trendsLoading">
|
||||||
7d
|
7d
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn @(_windowHours == 720 ? "btn-secondary" : "btn-outline-secondary")"
|
||||||
|
@onclick="() => SetWindowAsync(720)" disabled="@_trendsLoading">
|
||||||
|
30d
|
||||||
|
</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn @(_windowHours == 2160 ? "btn-secondary" : "btn-outline-secondary")"
|
||||||
|
@onclick="() => SetWindowAsync(2160)" disabled="@_trendsLoading">
|
||||||
|
90d
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -109,6 +109,12 @@
|
|||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn @(_trendWindowHours == 168 ? "btn-primary" : "btn-outline-secondary")"
|
class="btn @(_trendWindowHours == 168 ? "btn-primary" : "btn-outline-secondary")"
|
||||||
@onclick="() => SetTrendWindowAsync(168)" disabled="@_trendsLoading">7d</button>
|
@onclick="() => SetTrendWindowAsync(168)" disabled="@_trendsLoading">7d</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn @(_trendWindowHours == 720 ? "btn-primary" : "btn-outline-secondary")"
|
||||||
|
@onclick="() => SetTrendWindowAsync(720)" disabled="@_trendsLoading">30d</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn @(_trendWindowHours == 2160 ? "btn-primary" : "btn-outline-secondary")"
|
||||||
|
@onclick="() => SetTrendWindowAsync(2160)" disabled="@_trendsLoading">90d</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-3">
|
<div class="card-body p-3">
|
||||||
|
|||||||
+6
@@ -172,6 +172,12 @@
|
|||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn @(_windowHours == 168 ? "btn-primary" : "btn-outline-secondary")"
|
class="btn @(_windowHours == 168 ? "btn-primary" : "btn-outline-secondary")"
|
||||||
@onclick="() => SetWindowAsync(168)" disabled="@_trendsLoading">7d</button>
|
@onclick="() => SetWindowAsync(168)" disabled="@_trendsLoading">7d</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn @(_windowHours == 720 ? "btn-primary" : "btn-outline-secondary")"
|
||||||
|
@onclick="() => SetWindowAsync(720)" disabled="@_trendsLoading">30d</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn @(_windowHours == 2160 ? "btn-primary" : "btn-outline-secondary")"
|
||||||
|
@onclick="() => SetWindowAsync(2160)" disabled="@_trendsLoading">90d</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
|
|||||||
@@ -248,6 +248,12 @@
|
|||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn @(_windowHours == 168 ? "btn-primary" : "btn-outline-secondary")"
|
class="btn @(_windowHours == 168 ? "btn-primary" : "btn-outline-secondary")"
|
||||||
@onclick="() => SetWindowAsync(168)" disabled="@_trendsLoading">7d</button>
|
@onclick="() => SetWindowAsync(168)" disabled="@_trendsLoading">7d</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn @(_windowHours == 720 ? "btn-primary" : "btn-outline-secondary")"
|
||||||
|
@onclick="() => SetWindowAsync(720)" disabled="@_trendsLoading">30d</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn @(_windowHours == 2160 ? "btn-primary" : "btn-outline-secondary")"
|
||||||
|
@onclick="() => SetWindowAsync(2160)" disabled="@_trendsLoading">90d</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -117,6 +117,44 @@ public class AuditLogPageTrendTests : BunitContext
|
|||||||
Arg.Any<DateTime>(), Arg.Any<DateTime>(), Arg.Any<int?>(), Arg.Any<CancellationToken>());
|
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]
|
[Fact]
|
||||||
public void TrendsPanel_WhenQueryServiceThrows_PageStillRenders()
|
public void TrendsPanel_WhenQueryServiceThrows_PageStillRenders()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -590,6 +590,31 @@ public class SiteCallsReportPageTests : BunitContext
|
|||||||
Assert.True(_kpiHistory.ReceivedCalls().Count() > afterExpand));
|
Assert.True(_kpiHistory.ReceivedCalls().Count() > afterExpand));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TrendsSection_LongRangeWindows_RenderAndReloadSeries()
|
||||||
|
{
|
||||||
|
// The 30d/90d buttons (plan #22 T7) render alongside 24h/7d and clicking
|
||||||
|
// one re-loads the series — the query service then routes the long window
|
||||||
|
// to hourly rollups transparently.
|
||||||
|
var cut = Render<SiteCallsReportPage>();
|
||||||
|
cut.WaitForState(() => cut.Markup.Contains("ERP.GetOrder"));
|
||||||
|
|
||||||
|
cut.Find("[data-test='site-calls-trends-toggle']").Click();
|
||||||
|
cut.WaitForAssertion(() => Assert.NotEmpty(cut.FindAll("[data-test^='kpi-trend-']")));
|
||||||
|
|
||||||
|
// Both long-range buttons are present in the toggle group.
|
||||||
|
Assert.Contains(cut.FindAll("button"), b => b.TextContent.Trim() == "30d");
|
||||||
|
Assert.Contains(cut.FindAll("button"), b => b.TextContent.Trim() == "90d");
|
||||||
|
|
||||||
|
var afterExpand = _kpiHistory.ReceivedCalls().Count();
|
||||||
|
|
||||||
|
// Switch the window to 90d — the series re-load (another batch of calls).
|
||||||
|
cut.FindAll("button").First(b => b.TextContent.Trim() == "90d").Click();
|
||||||
|
|
||||||
|
cut.WaitForAssertion(() =>
|
||||||
|
Assert.True(_kpiHistory.ReceivedCalls().Count() > afterExpand));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TrendsSection_WhenKpiHistoryThrows_PageStillRenders()
|
public void TrendsSection_WhenKpiHistoryThrows_PageStillRenders()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user