Skip to content

Commit 272932f

Browse files
Copilotdavidfowl
andcommitted
Restore health check timestamp functionality with LastRun property
Co-authored-by: davidfowl <[email protected]>
1 parent d413179 commit 272932f

File tree

10 files changed

+40
-13
lines changed

10 files changed

+40
-13
lines changed

src/Aspire.Dashboard/Components/Controls/ResourceDetails.razor

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,8 @@
233233
Value="@(context.HealthStatus?.Humanize() ?? Loc[nameof(Resources.WaitingHealthDataStatusMessage)])"
234234
EnableHighlighting="@(!string.IsNullOrEmpty(_filter))"
235235
HighlightText="@_filter"
236-
TextVisualizerTitle="@context.Name">
236+
TextVisualizerTitle="@context.Name"
237+
ToolTip="@GetHealthCheckTooltip(context)">
237238
<ContentBeforeValue>
238239
@if (context.HealthStatus is null)
239240
{
@@ -329,4 +330,15 @@
329330
return @<a href="@vm.Url" title="@vm.Url" target="_blank">@vm.DisplayName</a>;
330331
}
331332
}
333+
334+
private string? GetHealthCheckTooltip(HealthReportViewModel healthReport)
335+
{
336+
if (healthReport.LastRun.HasValue)
337+
{
338+
// Convert UTC to local time zone
339+
var lastRunLocal = TimeZoneInfo.ConvertTimeFromUtc(healthReport.LastRun.Value, TimeZoneInfo.Local);
340+
return $"Last check: {lastRunLocal:yyyy-MM-dd HH:mm:ss}";
341+
}
342+
return null;
343+
}
332344
}

src/Aspire.Dashboard/Model/ResourceViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ public bool MatchesFilter(string filter) =>
388388
Target?.Contains(filter, StringComparison.CurrentCultureIgnoreCase) == true;
389389
}
390390

391-
public sealed record class HealthReportViewModel(string Name, HealthStatus? HealthStatus, string? Description, string? ExceptionText)
391+
public sealed record class HealthReportViewModel(string Name, HealthStatus? HealthStatus, string? Description, string? ExceptionText, DateTime? LastRun)
392392
{
393393
private readonly string? _humanizedHealthStatus = HealthStatus?.Humanize();
394394

src/Aspire.Dashboard/ResourceService/Partials.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ public ResourceViewModel ToViewModel(IKnownPropertyLookup knownPropertyLookup, I
5050

5151
HealthReportViewModel ToHealthReportViewModel(HealthReport healthReport)
5252
{
53-
return new HealthReportViewModel(healthReport.Key, healthReport.HasStatus ? MapHealthStatus(healthReport.Status) : null, healthReport.Description, healthReport.Exception);
53+
DateTime? lastRun = healthReport.LastRun?.ToDateTime();
54+
return new HealthReportViewModel(healthReport.Key, healthReport.HasStatus ? MapHealthStatus(healthReport.Status) : null, healthReport.Description, healthReport.Exception, lastRun);
5455
}
5556

5657
Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus MapHealthStatus(HealthStatus healthStatus)

src/Aspire.Hosting/ApplicationModel/CustomResourceSnapshot.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,13 @@ public sealed record ResourceCommandSnapshot(string Name, ResourceCommandState S
292292
/// <param name="Description">An optional description of the report, for display.</param>
293293
/// <param name="ExceptionText">An optional string containing exception details.</param>
294294
[DebuggerDisplay("{Status}", Name = "{Name}")]
295-
public sealed record HealthReportSnapshot(string Name, HealthStatus? Status, string? Description, string? ExceptionText);
295+
public sealed record HealthReportSnapshot(string Name, HealthStatus? Status, string? Description, string? ExceptionText)
296+
{
297+
/// <summary>
298+
/// The timestamp when this health check was last executed, or <see langword="null"/> if it has never run.
299+
/// </summary>
300+
public DateTime? LastRun { get; init; }
301+
}
296302

297303
/// <summary>
298304
/// The state of a resource command.

src/Aspire.Hosting/Dashboard/proto/Partials.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ public static Resource FromSnapshot(ResourceSnapshot snapshot)
9999
healthReport.Status = MapHealthStatus(report.Status.Value);
100100
}
101101

102+
if (report.LastRun.HasValue)
103+
{
104+
healthReport.LastRun = Timestamp.FromDateTime(report.LastRun.Value.ToUniversalTime());
105+
}
106+
102107
resource.HealthReports.Add(healthReport);
103108
}
104109

src/Aspire.Hosting/Dashboard/proto/resource_service.proto

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ message HealthReport {
166166
string description = 3;
167167
// Any exception details.
168168
string exception = 4;
169+
// The timestamp when this health check was last executed.
170+
optional google.protobuf.Timestamp last_run = 5;
169171
}
170172

171173
enum HealthStatus {

src/Aspire.Hosting/Health/ResourceHealthCheckService.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,10 +272,11 @@ await resourceNotificationService.PublishUpdateAsync(resource, s => s with
272272
private static ImmutableArray<HealthReportSnapshot> MergeHealthReports(ImmutableArray<HealthReportSnapshot> healthReports, HealthReport report)
273273
{
274274
var builder = healthReports.ToBuilder();
275+
var now = DateTime.UtcNow;
275276

276277
foreach (var (key, entry) in report.Entries)
277278
{
278-
var snapshot = new HealthReportSnapshot(key, entry.Status, entry.Description, entry.Exception?.ToString());
279+
var snapshot = new HealthReportSnapshot(key, entry.Status, entry.Description, entry.Exception?.ToString()) { LastRun = now };
279280

280281
var found = false;
281282
for (var i = 0; i < builder.Count; i++)

tests/Aspire.Dashboard.Components.Tests/Pages/ResourcesTests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public void UpdateResources_FiltersUpdated()
3131
"Resource1",
3232
"Type1",
3333
"Running",
34-
ImmutableArray.Create(new HealthReportViewModel("Null", null, "Description1", null))),
34+
ImmutableArray.Create(new HealthReportViewModel("Null", null, "Description1", null, null))),
3535
};
3636
var channel = Channel.CreateUnbounded<IReadOnlyList<ResourceViewModelChange>>();
3737
var dashboardClient = new TestDashboardClient(isEnabled: true, initialResources: initialResources, resourceChannelProvider: () => channel);
@@ -73,7 +73,7 @@ public void UpdateResources_FiltersUpdated()
7373
"Resource2",
7474
"Type2",
7575
"Running",
76-
ImmutableArray.Create(new HealthReportViewModel("Healthy", HealthStatus.Healthy, "Description2", null))))
76+
ImmutableArray.Create(new HealthReportViewModel("Healthy", HealthStatus.Healthy, "Description2", null, null))))
7777
]);
7878

7979
cut.WaitForState(() => cut.Instance.GetFilteredResources().Count() == 2);
@@ -120,17 +120,17 @@ public void FilterResources()
120120
"Resource1",
121121
"Type1",
122122
"Running",
123-
ImmutableArray.Create(new HealthReportViewModel("Null", null, "Description1", null))),
123+
ImmutableArray.Create(new HealthReportViewModel("Null", null, "Description1", null, null))),
124124
CreateResource(
125125
"Resource2",
126126
"Type2",
127127
"Running",
128-
ImmutableArray.Create(new HealthReportViewModel("Healthy", HealthStatus.Healthy, "Description2", null))),
128+
ImmutableArray.Create(new HealthReportViewModel("Healthy", HealthStatus.Healthy, "Description2", null, null))),
129129
CreateResource(
130130
"Resource3",
131131
"Type3",
132132
"Stopping",
133-
ImmutableArray.Create(new HealthReportViewModel("Degraded", HealthStatus.Degraded, "Description3", null))),
133+
ImmutableArray.Create(new HealthReportViewModel("Degraded", HealthStatus.Degraded, "Description3", null, null))),
134134
};
135135
var dashboardClient = new TestDashboardClient(isEnabled: true, initialResources: initialResources, resourceChannelProvider: Channel.CreateUnbounded<IReadOnlyList<ResourceViewModelChange>>);
136136
ResourceSetupHelpers.SetupResourcesPage(
@@ -182,7 +182,7 @@ public void ResourceGraph_MultipleRenders_InitializeOnce()
182182
"Resource1",
183183
"Type1",
184184
"Running",
185-
ImmutableArray.Create(new HealthReportViewModel("Null", null, "Description1", null))),
185+
ImmutableArray.Create(new HealthReportViewModel("Null", null, "Description1", null, null))),
186186
};
187187
var dashboardClient = new TestDashboardClient(isEnabled: true, initialResources: initialResources, resourceChannelProvider: Channel.CreateUnbounded<IReadOnlyList<ResourceViewModelChange>>);
188188
ResourceSetupHelpers.SetupResourcesPage(

tests/Aspire.Dashboard.Tests/Model/ResourceViewModelTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public sealed class ResourceViewModelTests
2626
[InlineData(KnownResourceState.Running, DiagnosticsHealthStatus.Degraded, new string?[] {"Healthy", "Degraded"})]
2727
public void Resource_WithHealthReportAndState_ReturnsCorrectHealthStatus(KnownResourceState? state, DiagnosticsHealthStatus? expectedStatus, string?[]? healthStatusStrings)
2828
{
29-
var reports = healthStatusStrings?.Select<string?, HealthReportViewModel>((h, i) => new HealthReportViewModel(i.ToString(), h is null ? null : System.Enum.Parse<DiagnosticsHealthStatus>(h), null, null)).ToImmutableArray() ?? [];
29+
var reports = healthStatusStrings?.Select<string?, HealthReportViewModel>((h, i) => new HealthReportViewModel(i.ToString(), h is null ? null : System.Enum.Parse<DiagnosticsHealthStatus>(h), null, null, null)).ToImmutableArray() ?? [];
3030
var actualStatus = ResourceViewModel.ComputeHealthStatus(reports, state);
3131
Assert.Equal(expectedStatus, actualStatus);
3232
}

tests/Shared/DashboardModel/ModelTestHelpers.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public static ResourceViewModel CreateResource(
4040
State = state?.ToString(),
4141
KnownState = state,
4242
StateStyle = stateStyle,
43-
HealthReports = reportHealthStatus is null && !createNullHealthReport ? [] : [new HealthReportViewModel("healthcheck", reportHealthStatus, null, null)],
43+
HealthReports = reportHealthStatus is null && !createNullHealthReport ? [] : [new HealthReportViewModel("healthcheck", reportHealthStatus, null, null, null)],
4444
Commands = commands ?? [],
4545
Relationships = relationships ?? [],
4646
IsHidden = hidden

0 commit comments

Comments
 (0)