Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,32 @@ 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);
}

[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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ else
<div class="page-heading"><div><p class="eyebrow">SERVICE DETAIL · @_model.Service.Environment</p><h1>@_model.Service.Name</h1><p class="muted">@MonitorDescription</p><p class="service-version-detail">Version: <strong>@(string.IsNullOrWhiteSpace(_model.Service.Version) ? "not reported" : _model.Service.Version)</strong></p></div><div><a class="button secondary" href="@($"/services/{ServiceId}/dependencies")">Manage dependency links</a> <a class="button secondary" href="/">Back to overview</a></div></div>
<div class="detail-status"><StatusBadge Status="@_model.Summary.Status" /> <span>Last checked @( _model.Summary.LastChecked?.ToLocalTime().ToString("dd MMM, HH:mm:ss") ?? "never")</span></div>
<div class="stat-grid three"><StatCard Label="Uptime (24 hours)" Value="@UptimeText" Class="healthy" /><StatCard Label="Observations" Value="@_model.Observations.Count" Class="neutral" /><StatCard Label="Response" Value="@ResponseText" Class="neutral" /></div>
<section class="panel"><h2>Status timeline</h2><div class="timeline">@foreach (var observation in _model.Observations.OrderBy(item => item.ObservedAtUtc)) { <span class="timeline-segment @observation.Status.ToString().ToLowerInvariant()" title="@observation.ObservedAtUtc.ToLocalTime()"></span> }</div></section>
<section class="panel"><h2>Status timeline</h2><div class="timeline">@foreach (var observation in TimelineObservationSelector.Select(_model.Observations)) { <span class="timeline-segment @observation.Status.ToString().ToLowerInvariant()" title="@observation.ObservedAtUtc.ToLocalTime()"></span> }</div></section>
<section class="panel"><h2>Dependency details</h2>@if (_model.Checks.Count == 0) { <p class="muted">No dependency details have been returned.</p> } else { <div class="check-table"><div class="table-heading"><span>Check</span><span>Status</span><span>Duration</span><span>Details</span></div>@foreach (var check in LatestChecks) { var link = _model.DependencyLinks.FirstOrDefault(item => string.Equals(item.DependencyName.Trim(), check.Result.Name.Trim(), StringComparison.OrdinalIgnoreCase)); <div class="service-row"><div>@if (link is null) { <strong>@check.Result.Name</strong> } else { <a class="dependency-link" href="@($"/services/{link.TargetServiceId}")"><strong>@check.Result.Name</strong> ↗</a> }</div><div><StatusBadge Status="@check.Result.Status" /></div><div>@check.Result.Duration.TotalMilliseconds.ToString("0") ms</div><div>@(check.Result.Description ?? check.Result.Exception ?? "No detail")</div></div>}</div> }</section>
<details class="panel raw"><summary>Latest raw health response</summary><pre>@(_model.Observations.FirstOrDefault()?.RawResponseJson ?? "No response recorded.")</pre></details>
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using HealthMonitoring.Domain;

namespace HealthMonitoring.Reporting;

public static class TimelineObservationSelector
{
public static int MaximumSegments => 100;

public static IEnumerable<HealthObservation> Select(IEnumerable<HealthObservation> observations) => observations
.OrderByDescending(observation => observation.ObservedAtUtc)
.Take(MaximumSegments)
.OrderBy(observation => observation.ObservedAtUtc);
}
Loading