Skip to content

Commit ebb06d1

Browse files
adamintAdam RatzmanJamesNK
authored
Remove HealthStatus defaulting to healthy when there is no value from a health check (#6209)
* Remove HealthStatus defaulting to healthy when there is no value from a health check * Show unhealthy only if health status is not null * Add null assert to status in ResourceNotificationTests * Add test for StateColumnDisplay static functions * Update with new expectations * Create initial health reports if we have not received from server * update public api * Plumb nullable health status / reports through, create fake snapshots if necessary, add waiting UI * Make resource healthy if it is running with no health checks * Update comment * Clean up * Add initial test, move health status update logic elsewhere in notification service * Add two more tests * Fix flaky test * Simplify test * Apply PR suggestions * Add RuntimeUnhealthy state to known states, as it is affected in StateColumnDisplay * Refactor state column display and its tests * Runtime unhealthy improvements * Fix merge * Fix state not changing * check at beginning of update health status if health status is not already running * Set state to running (unhealthy) on empty health status * Update test expectations --------- Co-authored-by: Adam Ratzman <[email protected]> Co-authored-by: James Newton-King <[email protected]>
1 parent 55e3aea commit ebb06d1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+773
-343
lines changed

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

Lines changed: 55 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -117,64 +117,61 @@
117117
@FilteredHealthReports.Count()
118118
</FluentBadge>
119119
</div>
120-
@if (Resource.HealthStatus is null)
121-
{
122-
<div style="margin:10px">
123-
<FluentIcon Icon="Icons.Regular.Size16.HourglassHalf" Color="Color.Info" Class="severity-icon" />
124-
@Loc[nameof(Resources.WaitingForHealthDataMessage)]
125-
</div>
126-
}
127-
else
128-
{
129-
<FluentDataGrid ResizeLabel="@AspireFluentDataGridHeaderCell.GetResizeLabel(ControlStringsLoc)"
130-
ResizeType="DataGridResizeType.Discrete"
131-
Items="@FilteredHealthReports"
132-
ItemKey="r => r.Name"
133-
ResizableColumns="true"
134-
Style="width:100%"
135-
GenerateHeader="GenerateHeaderOption.Sticky"
136-
GridTemplateColumns="1fr 1fr 1.5fr"
137-
ShowHover="true">
138-
<AspireTemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.NameColumnHeader)]" Class="nameColumn">
139-
<GridValue ValueDescription="@ControlStringsLoc[nameof(ControlsStrings.NameColumnHeader)]"
140-
Value="@context.Name"
141-
EnableHighlighting="@(!string.IsNullOrEmpty(_filter))"
142-
HighlightText="@_filter" />
143-
</AspireTemplateColumn>
144-
<AspireTemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.StateColumnHeader)]" Class="stateColumn">
145-
<GridValue ValueDescription="@ControlStringsLoc[nameof(ControlsStrings.StateColumnHeader)]"
146-
Value="@context.HealthStatus.Humanize()"
147-
EnableHighlighting="@(!string.IsNullOrEmpty(_filter))"
148-
HighlightText="@_filter"
149-
TextVisualizerTitle="@context.Name">
150-
<ContentBeforeValue>
151-
@{
152-
// Browse the icon library at: https://aka.ms/fluentui-system-icons
153-
(Icon icon, Color color) = context.HealthStatus switch
154-
{
155-
HealthStatus.Healthy => ((Icon)new Icons.Filled.Size16.Heart(), Color.Success),
156-
HealthStatus.Degraded => (new Icons.Filled.Size16.HeartBroken(), Color.Warning),
157-
HealthStatus.Unhealthy => (new Icons.Filled.Size16.HeartBroken(), Color.Error),
158-
_ => (new Icons.Regular.Size16.CircleHint(), Color.Info)
159-
};
160-
}
161-
<FluentIcon Value="@icon" Color="@color" Class="severity-icon" />
162-
</ContentBeforeValue>
163-
</GridValue>
164-
</AspireTemplateColumn>
165-
<AspireTemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.DetailsColumnHeader)]" Class="detailsColumn">
166-
<GridValue ValueDescription="@ControlStringsLoc[nameof(ControlsStrings.DetailsColumnHeader)]"
167-
Value="@context.DisplayedDescription"
168-
EnableHighlighting="@(!string.IsNullOrEmpty(_filter))"
169-
HighlightText="@_filter"
170-
TextVisualizerTitle="@context.Name">
171-
<ContentBeforeValue>
172-
<ExceptionDetails ExceptionText="@context.ExceptionText" />
173-
</ContentBeforeValue>
174-
</GridValue>
175-
</AspireTemplateColumn>
176-
</FluentDataGrid>
177-
}
120+
121+
<FluentDataGrid ResizeLabel="@AspireFluentDataGridHeaderCell.GetResizeLabel(ControlStringsLoc)"
122+
ResizeType="DataGridResizeType.Discrete"
123+
Items="@FilteredHealthReports"
124+
ItemKey="r => r.Name"
125+
ResizableColumns="true"
126+
Style="width:100%"
127+
GenerateHeader="GenerateHeaderOption.Sticky"
128+
GridTemplateColumns="1fr 1fr 1.5fr"
129+
ShowHover="true">
130+
<AspireTemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.NameColumnHeader)]" Class="nameColumn">
131+
<GridValue ValueDescription="@ControlStringsLoc[nameof(ControlsStrings.NameColumnHeader)]"
132+
Value="@context.Name"
133+
EnableHighlighting="@(!string.IsNullOrEmpty(_filter))"
134+
HighlightText="@_filter" />
135+
</AspireTemplateColumn>
136+
<AspireTemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.StateColumnHeader)]" Class="stateColumn">
137+
<GridValue ValueDescription="@ControlStringsLoc[nameof(ControlsStrings.StateColumnHeader)]"
138+
Value="@(context.HealthStatus?.Humanize() ?? Loc[nameof(Resources.WaitingHealthDataStatusMessage)])"
139+
EnableHighlighting="@(!string.IsNullOrEmpty(_filter))"
140+
HighlightText="@_filter"
141+
TextVisualizerTitle="@context.Name">
142+
<ContentBeforeValue>
143+
@if (context.HealthStatus is null)
144+
{
145+
<FluentProgressRing Width="16px" Style="display: inline-block;" Class="severity-icon" />
146+
}
147+
else
148+
{
149+
// Browse the icon library at: https://aka.ms/fluentui-system-icons
150+
(Icon? icon, Color color) = context.HealthStatus switch
151+
{
152+
HealthStatus.Healthy => ((Icon)new Icons.Filled.Size16.Heart(), Color.Success),
153+
HealthStatus.Degraded => (new Icons.Filled.Size16.HeartBroken(), Color.Warning),
154+
HealthStatus.Unhealthy => (new Icons.Filled.Size16.HeartBroken(), Color.Error),
155+
_ => (new Icons.Regular.Size16.CircleHint(), Color.Info)
156+
};
157+
158+
<FluentIcon Value="@icon" Color="@color" Class="severity-icon"/>
159+
}
160+
</ContentBeforeValue>
161+
</GridValue>
162+
</AspireTemplateColumn>
163+
<AspireTemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.DetailsColumnHeader)]" Class="detailsColumn">
164+
<GridValue ValueDescription="@ControlStringsLoc[nameof(ControlsStrings.DetailsColumnHeader)]"
165+
Value="@(context.HealthStatus is null ? Loc[nameof(Resources.WaitingForHealthDataMessage)] : context.DisplayedDescription)"
166+
EnableHighlighting="@(!string.IsNullOrEmpty(_filter))"
167+
HighlightText="@_filter"
168+
TextVisualizerTitle="@context.Name">
169+
<ContentBeforeValue>
170+
<ExceptionDetails ExceptionText="@context.ExceptionText" />
171+
</ContentBeforeValue>
172+
</GridValue>
173+
</AspireTemplateColumn>
174+
</FluentDataGrid>
178175
</FluentAccordionItem>
179176
</FluentAccordion>
180177
</div>

src/Aspire.Dashboard/Components/Pages/Resources.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696
<AspireTemplateColumn ColumnId="@NameColumn" ColumnManager="@_manager" Title="@ControlsStringsLoc[nameof(ControlsStrings.NameColumnHeader)]" Sortable="true" SortBy="@_nameSort" Tooltip="true" TooltipText="@(c => GetResourceName(c))">
9797
<ResourceNameDisplay Resource="context" FilterText="@_filter" FormatName="GetResourceName" />
9898
</AspireTemplateColumn>
99-
<AspireTemplateColumn ColumnId="@StateColumn" ColumnManager="@_manager" Title="@Loc[nameof(Dashboard.Resources.Resources.ResourcesStateColumnHeader)]" Sortable="true" SortBy="@_stateSort" Tooltip="true" TooltipText="@(c => StateColumnDisplay.GetResourceStateTooltip(c, ColumnsLoc))">
99+
<AspireTemplateColumn ColumnId="@StateColumn" ColumnManager="@_manager" Title="@Loc[nameof(Dashboard.Resources.Resources.ResourcesStateColumnHeader)]" Sortable="true" SortBy="@_stateSort" Tooltip="true" TooltipText="@(c => ResourceStateViewModel.GetResourceStateTooltip(c, ColumnsLoc))">
100100
<StateColumnDisplay Resource="@context" UnviewedErrorCounts="@_applicationUnviewedErrorCounts" />
101101
</AspireTemplateColumn>
102102
<AspireTemplateColumn ColumnId="@StartTimeColumn" ColumnManager="@_manager" Title="@Loc[nameof(Dashboard.Resources.Resources.ResourcesStartTimeColumnHeader)]" Sortable="true" SortBy="@_startTimeSort" TooltipText="@(context => context.CreationTimeStamp != null ? FormatHelpers.FormatDateTime(TimeProvider, context.CreationTimeStamp.Value, MillisecondsDisplay.None, CultureInfo.CurrentCulture) : null)" Tooltip="true">

src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
@using Aspire.Dashboard.Model
2+
@using Aspire.Dashboard.Resources
23

34
@{
4-
var vm = GetStateViewModel();
5+
// Create view model every render. The resource state may have changed.
6+
var vm = ResourceStateViewModel.GetStateViewModel(Resource, Loc);
57
}
68

79
<div class="state-column-cell">
810

9-
<FluentIcon
10-
Value="@vm.Icon"
11-
Color="@vm.Color"
12-
Class="severity-icon" />
11+
<FluentIcon Value="@vm.Icon"
12+
Color="@vm.Color"
13+
Class="severity-icon" />
1314

1415
<span>@vm.Text</span>
1516

Lines changed: 0 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System.Globalization;
5-
using Aspire.Dashboard.Extensions;
64
using Aspire.Dashboard.Model;
75
using Aspire.Dashboard.Otlp.Storage;
86
using Aspire.Dashboard.Resources;
9-
using Humanizer;
107
using Microsoft.AspNetCore.Components;
11-
using Microsoft.Extensions.Diagnostics.HealthChecks;
128
using Microsoft.Extensions.Localization;
13-
using Microsoft.FluentUI.AspNetCore.Components;
149

1510
namespace Aspire.Dashboard.Components.ResourcesGridColumns;
1611

@@ -24,109 +19,4 @@ public partial class StateColumnDisplay
2419

2520
[Inject]
2621
public required IStringLocalizer<Columns> Loc { get; init; }
27-
28-
/// <summary>
29-
/// Gets the tooltip for a cell in the state column of the resource grid.
30-
/// </summary>
31-
/// <remarks>
32-
/// This is a static method so it can be called at the level of the parent column.
33-
/// </remarks>
34-
public static string? GetResourceStateTooltip(ResourceViewModel resource, IStringLocalizer<Columns> Loc)
35-
{
36-
if (resource.IsStopped())
37-
{
38-
if (resource.TryGetExitCode(out var exitCode) && exitCode is not 0)
39-
{
40-
// Process completed unexpectedly, hence the non-zero code. This is almost certainly an error, so warn users.
41-
return string.Format(CultureInfo.CurrentCulture, Loc[nameof(Columns.StateColumnResourceExitedUnexpectedly)], resource.ResourceType, exitCode);
42-
}
43-
else
44-
{
45-
// Process completed, which may not have been unexpected.
46-
return string.Format(CultureInfo.CurrentCulture, Loc[nameof(Columns.StateColumnResourceExited)], resource.ResourceType);
47-
}
48-
}
49-
else if (resource.KnownState is KnownResourceState.Running && resource.HealthStatus is not HealthStatus.Healthy)
50-
{
51-
// Resource is running but not healthy (initializing).
52-
return Loc[nameof(Columns.RunningAndUnhealthyResourceStateToolTip)];
53-
}
54-
55-
return null;
56-
}
57-
58-
/// <summary>
59-
/// Gets data needed to populate the content of the state column.
60-
/// </summary>
61-
private ResourceStateViewModel GetStateViewModel()
62-
{
63-
// Browse the icon library at: https://aka.ms/fluentui-system-icons
64-
65-
Icon icon;
66-
Color color;
67-
68-
if (Resource.IsStopped())
69-
{
70-
if (Resource.TryGetExitCode(out var exitCode) && exitCode is not 0)
71-
{
72-
// Process completed unexpectedly, hence the non-zero code. This is almost certainly an error, so warn users.
73-
icon = new Icons.Filled.Size16.ErrorCircle();
74-
color = Color.Error;
75-
}
76-
else if (Resource.IsFinishedState())
77-
{
78-
// Process completed successfully.
79-
icon = new Icons.Regular.Size16.RecordStop();
80-
color = Color.Info;
81-
}
82-
else
83-
{
84-
// Process completed, which may not have been unexpected.
85-
icon = new Icons.Filled.Size16.Warning();
86-
color = Color.Warning;
87-
}
88-
}
89-
else if (Resource.IsUnusableTransitoryState() || Resource.IsUnknownState())
90-
{
91-
icon = new Icons.Filled.Size16.CircleHint(); // A dashed, hollow circle.
92-
color = Color.Info;
93-
}
94-
else if (Resource.HasNoState())
95-
{
96-
icon = new Icons.Filled.Size16.Circle();
97-
color = Color.Info;
98-
}
99-
else if (Resource.HealthStatus is not HealthStatus.Healthy)
100-
{
101-
icon = new Icons.Filled.Size16.CheckmarkCircleWarning();
102-
color = Color.Neutral;
103-
}
104-
else if (!string.IsNullOrEmpty(Resource.StateStyle))
105-
{
106-
(icon, color) = Resource.StateStyle switch
107-
{
108-
"warning" => ((Icon)new Icons.Filled.Size16.Warning(), Color.Warning),
109-
"error" => (new Icons.Filled.Size16.ErrorCircle(), Color.Error),
110-
"success" => (new Icons.Filled.Size16.CheckmarkCircle(), Color.Success),
111-
"info" => (new Icons.Filled.Size16.Info(), Color.Info),
112-
_ => (new Icons.Filled.Size16.Circle(), Color.Neutral)
113-
};
114-
}
115-
else
116-
{
117-
icon = new Icons.Filled.Size16.CheckmarkCircle();
118-
color = Color.Success;
119-
}
120-
121-
var text = Resource switch
122-
{
123-
{ State: null or "" } => Loc[nameof(Columns.UnknownStateLabel)],
124-
{ KnownState: KnownResourceState.Running, HealthStatus: not HealthStatus.Healthy } => $"{Resource.State.Humanize()} ({(Resource.HealthStatus ?? HealthStatus.Unhealthy).Humanize()})",
125-
_ => Resource.State.Humanize()
126-
};
127-
128-
return new ResourceStateViewModel(text, icon, color);
129-
}
130-
131-
private record class ResourceStateViewModel(string Text, Icon Icon, Color Color);
13222
}

src/Aspire.Dashboard/Extensions/ResourceViewModelExtensions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ public static bool IsUnusableTransitoryState(this ResourceViewModel resource)
3232
return resource.KnownState is KnownResourceState.Starting or KnownResourceState.Building or KnownResourceState.Waiting or KnownResourceState.Stopping;
3333
}
3434

35+
public static bool IsRuntimeUnhealthy(this ResourceViewModel resource)
36+
{
37+
return resource.KnownState is KnownResourceState.RuntimeUnhealthy;
38+
}
39+
3540
public static bool IsUnknownState(this ResourceViewModel resource) => resource.KnownState is KnownResourceState.Unknown;
3641

3742
public static bool HasNoState(this ResourceViewModel resource) => string.IsNullOrEmpty(resource.State);

src/Aspire.Dashboard/Model/KnownResourceState.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ public enum KnownResourceState
1414
Hidden,
1515
Waiting,
1616
Stopping,
17-
Unknown
17+
Unknown,
18+
RuntimeUnhealthy
1819
}

0 commit comments

Comments
 (0)