From 2389217087e621fc270d572c30ff6f91f45d4bfe Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Wed, 23 Sep 2026 16:21:10 +0100 Subject: [PATCH 1/2] Limit service timeline to the latest 100 observations --- .../Reporting/DashboardModelsTests.cs | 18 ++++++++++++++++++ .../Components/Pages/ServiceDetail.razor | 2 +- .../Reporting/TimelineObservationSelector.cs | 13 +++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 HealthMonitoring/HealthMonitoring/Reporting/TimelineObservationSelector.cs diff --git a/HealthMonitoring/HealthMonitoring.Tests/Reporting/DashboardModelsTests.cs b/HealthMonitoring/HealthMonitoring.Tests/Reporting/DashboardModelsTests.cs index 2c1f769..635a44d 100644 --- a/HealthMonitoring/HealthMonitoring.Tests/Reporting/DashboardModelsTests.cs +++ b/HealthMonitoring/HealthMonitoring.Tests/Reporting/DashboardModelsTests.cs @@ -21,4 +21,22 @@ public void ServiceDashboardRow_preserves_registered_version() Assert.Equal("2.4.1", row.Version); } + + [Fact] + public void TimelineObservationSelector_returns_latest_100_in_display_order() + { + var observations = Enumerable.Range(0, 101) + .Select(index => new HealthObservation + { + ObservedAtUtc = DateTimeOffset.UtcNow.AddMinutes(index), + Status = HealthStatus.Healthy + }) + .ToArray(); + + var selected = TimelineObservationSelector.Select(observations).ToArray(); + + Assert.Equal(100, selected.Length); + Assert.Equal(observations[1].ObservedAtUtc, selected[0].ObservedAtUtc); + Assert.Equal(observations[100].ObservedAtUtc, selected[^1].ObservedAtUtc); + } } diff --git a/HealthMonitoring/HealthMonitoring/Components/Pages/ServiceDetail.razor b/HealthMonitoring/HealthMonitoring/Components/Pages/ServiceDetail.razor index 78213e1..56cd831 100644 --- a/HealthMonitoring/HealthMonitoring/Components/Pages/ServiceDetail.razor +++ b/HealthMonitoring/HealthMonitoring/Components/Pages/ServiceDetail.razor @@ -11,7 +11,7 @@ else

SERVICE DETAIL · @_model.Service.Environment

@_model.Service.Name

@MonitorDescription

Version: @(string.IsNullOrWhiteSpace(_model.Service.Version) ? "not reported" : _model.Service.Version)

Manage dependency links Back to overview
Last checked @( _model.Summary.LastChecked?.ToLocalTime().ToString("dd MMM, HH:mm:ss") ?? "never")
-

Status timeline

@foreach (var observation in _model.Observations.OrderBy(item => item.ObservedAtUtc)) { }
+

Status timeline

@foreach (var observation in TimelineObservationSelector.Select(_model.Observations)) { }

Dependency details

@if (_model.Checks.Count == 0) {

No dependency details have been returned.

} else {
CheckStatusDurationDetails
@foreach (var check in LatestChecks) { var link = _model.DependencyLinks.FirstOrDefault(item => string.Equals(item.DependencyName.Trim(), check.Result.Name.Trim(), StringComparison.OrdinalIgnoreCase));
@if (link is null) { @check.Result.Name } else { @check.Result.Name ↗ }
@check.Result.Duration.TotalMilliseconds.ToString("0") ms
@(check.Result.Description ?? check.Result.Exception ?? "No detail")
}
}
Latest raw health response
@(_model.Observations.FirstOrDefault()?.RawResponseJson ?? "No response recorded.")
} diff --git a/HealthMonitoring/HealthMonitoring/Reporting/TimelineObservationSelector.cs b/HealthMonitoring/HealthMonitoring/Reporting/TimelineObservationSelector.cs new file mode 100644 index 0000000..2f6ef9b --- /dev/null +++ b/HealthMonitoring/HealthMonitoring/Reporting/TimelineObservationSelector.cs @@ -0,0 +1,13 @@ +using HealthMonitoring.Domain; + +namespace HealthMonitoring.Reporting; + +public static class TimelineObservationSelector +{ + public const int MaximumSegments = 100; + + public static IEnumerable Select(IEnumerable observations) => observations + .OrderByDescending(observation => observation.ObservedAtUtc) + .Take(MaximumSegments) + .OrderBy(observation => observation.ObservedAtUtc); +} From 0121a7f21ae3dbf1bf82950cd57da0032c0458b4 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Wed, 23 Sep 2026 16:56:07 +0100 Subject: [PATCH 2/2] fix: address Codacy timeline property finding --- .../Reporting/DashboardModelsTests.cs | 10 ++++++++++ .../Reporting/TimelineObservationSelector.cs | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/HealthMonitoring/HealthMonitoring.Tests/Reporting/DashboardModelsTests.cs b/HealthMonitoring/HealthMonitoring.Tests/Reporting/DashboardModelsTests.cs index 635a44d..afcc66b 100644 --- a/HealthMonitoring/HealthMonitoring.Tests/Reporting/DashboardModelsTests.cs +++ b/HealthMonitoring/HealthMonitoring.Tests/Reporting/DashboardModelsTests.cs @@ -39,4 +39,14 @@ public void TimelineObservationSelector_returns_latest_100_in_display_order() Assert.Equal(observations[1].ObservedAtUtc, selected[0].ObservedAtUtc); Assert.Equal(observations[100].ObservedAtUtc, selected[^1].ObservedAtUtc); } + + [Fact] + public void TimelineObservationSelector_exposes_maximum_segments_as_read_only_property() + { + var property = typeof(TimelineObservationSelector).GetProperty(nameof(TimelineObservationSelector.MaximumSegments)); + + Assert.NotNull(property); + Assert.False(property!.CanWrite); + Assert.Equal(100, property.GetValue(null)); + } } diff --git a/HealthMonitoring/HealthMonitoring/Reporting/TimelineObservationSelector.cs b/HealthMonitoring/HealthMonitoring/Reporting/TimelineObservationSelector.cs index 2f6ef9b..0e5b018 100644 --- a/HealthMonitoring/HealthMonitoring/Reporting/TimelineObservationSelector.cs +++ b/HealthMonitoring/HealthMonitoring/Reporting/TimelineObservationSelector.cs @@ -4,7 +4,7 @@ namespace HealthMonitoring.Reporting; public static class TimelineObservationSelector { - public const int MaximumSegments = 100; + public static int MaximumSegments => 100; public static IEnumerable Select(IEnumerable observations) => observations .OrderByDescending(observation => observation.ObservedAtUtc)