Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
150ec05
Remove HealthStatus defaulting to healthy when there is no value from…
Oct 9, 2024
c26e8dd
Show unhealthy only if health status is not null
Oct 14, 2024
57f11be
Add null assert to status in ResourceNotificationTests
Oct 14, 2024
c66b778
Add test for StateColumnDisplay static functions
Oct 14, 2024
7fb3fa0
Merge branch 'main' into dev/adamint/remove-default-healthy-status
Oct 14, 2024
a2df9ed
Update with new expectations
Oct 14, 2024
049dcee
Create initial health reports if we have not received from server
Oct 15, 2024
951e1f9
update public api
Oct 15, 2024
19bb010
Plumb nullable health status / reports through, create fake snapshots…
Oct 15, 2024
613784c
Make resource healthy if it is running with no health checks
Oct 15, 2024
2e10709
Update comment
Oct 15, 2024
f138e46
Clean up
Oct 15, 2024
c726d5a
Add initial test, move health status update logic elsewhere in notifi…
Oct 15, 2024
54c32fd
Add two more tests
Oct 16, 2024
78178d8
Fix flaky test
Oct 16, 2024
65cd571
Simplify test
Oct 16, 2024
caf163e
Merge branch 'refs/heads/main' into dev/adamint/remove-default-health…
Oct 16, 2024
a28f89d
Apply PR suggestions
Oct 17, 2024
254b88d
Merge branch 'main' into dev/adamint/remove-default-healthy-status
Oct 17, 2024
6535aae
Add RuntimeUnhealthy state to known states, as it is affected in Stat…
Oct 17, 2024
c37db7d
Refactor state column display and its tests
JamesNK Oct 17, 2024
749c0b6
Runtime unhealthy improvements
JamesNK Oct 17, 2024
e168dc9
Merge
JamesNK Oct 17, 2024
ac14849
Fix merge
JamesNK Oct 17, 2024
0297102
Fix state not changing
JamesNK Oct 17, 2024
ead2b13
check at beginning of update health status if health status is not al…
Oct 17, 2024
3eacb74
Set state to running (unhealthy) on empty health status
Oct 17, 2024
3f23964
Update test expectations
Oct 17, 2024
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
113 changes: 55 additions & 58 deletions src/Aspire.Dashboard/Components/Controls/ResourceDetails.razor
Original file line number Diff line number Diff line change
Expand Up @@ -117,64 +117,61 @@
@FilteredHealthReports.Count()
</FluentBadge>
</div>
@if (Resource.HealthStatus is null)
{
<div style="margin:10px">
<FluentIcon Icon="Icons.Regular.Size16.HourglassHalf" Color="Color.Info" Class="severity-icon" />
@Loc[nameof(Resources.WaitingForHealthDataMessage)]
</div>
}
else
{
<FluentDataGrid ResizeLabel="@AspireFluentDataGridHeaderCell.GetResizeLabel(ControlStringsLoc)"
ResizeType="DataGridResizeType.Discrete"
Items="@FilteredHealthReports"
ItemKey="r => r.Name"
ResizableColumns="true"
Style="width:100%"
GenerateHeader="GenerateHeaderOption.Sticky"
GridTemplateColumns="1fr 1fr 1.5fr"
ShowHover="true">
<AspireTemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.NameColumnHeader)]" Class="nameColumn">
<GridValue ValueDescription="@ControlStringsLoc[nameof(ControlsStrings.NameColumnHeader)]"
Value="@context.Name"
EnableHighlighting="@(!string.IsNullOrEmpty(_filter))"
HighlightText="@_filter" />
</AspireTemplateColumn>
<AspireTemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.StateColumnHeader)]" Class="stateColumn">
<GridValue ValueDescription="@ControlStringsLoc[nameof(ControlsStrings.StateColumnHeader)]"
Value="@context.HealthStatus.Humanize()"
EnableHighlighting="@(!string.IsNullOrEmpty(_filter))"
HighlightText="@_filter"
TextVisualizerTitle="@context.Name">
<ContentBeforeValue>
@{
// Browse the icon library at: https://aka.ms/fluentui-system-icons
(Icon icon, Color color) = context.HealthStatus switch
{
HealthStatus.Healthy => ((Icon)new Icons.Filled.Size16.Heart(), Color.Success),
HealthStatus.Degraded => (new Icons.Filled.Size16.HeartBroken(), Color.Warning),
HealthStatus.Unhealthy => (new Icons.Filled.Size16.HeartBroken(), Color.Error),
_ => (new Icons.Regular.Size16.CircleHint(), Color.Info)
};
}
<FluentIcon Value="@icon" Color="@color" Class="severity-icon" />
</ContentBeforeValue>
</GridValue>
</AspireTemplateColumn>
<AspireTemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.DetailsColumnHeader)]" Class="detailsColumn">
<GridValue ValueDescription="@ControlStringsLoc[nameof(ControlsStrings.DetailsColumnHeader)]"
Value="@context.DisplayedDescription"
EnableHighlighting="@(!string.IsNullOrEmpty(_filter))"
HighlightText="@_filter"
TextVisualizerTitle="@context.Name">
<ContentBeforeValue>
<ExceptionDetails ExceptionText="@context.ExceptionText" />
</ContentBeforeValue>
</GridValue>
</AspireTemplateColumn>
</FluentDataGrid>
}

<FluentDataGrid ResizeLabel="@AspireFluentDataGridHeaderCell.GetResizeLabel(ControlStringsLoc)"
ResizeType="DataGridResizeType.Discrete"
Items="@FilteredHealthReports"
ItemKey="r => r.Name"
ResizableColumns="true"
Style="width:100%"
GenerateHeader="GenerateHeaderOption.Sticky"
GridTemplateColumns="1fr 1fr 1.5fr"
ShowHover="true">
<AspireTemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.NameColumnHeader)]" Class="nameColumn">
<GridValue ValueDescription="@ControlStringsLoc[nameof(ControlsStrings.NameColumnHeader)]"
Value="@context.Name"
EnableHighlighting="@(!string.IsNullOrEmpty(_filter))"
HighlightText="@_filter" />
</AspireTemplateColumn>
<AspireTemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.StateColumnHeader)]" Class="stateColumn">
<GridValue ValueDescription="@ControlStringsLoc[nameof(ControlsStrings.StateColumnHeader)]"
Value="@(context.HealthStatus?.Humanize() ?? Loc[nameof(Resources.WaitingHealthDataStatusMessage)])"
EnableHighlighting="@(!string.IsNullOrEmpty(_filter))"
HighlightText="@_filter"
TextVisualizerTitle="@context.Name">
<ContentBeforeValue>
@if (context.HealthStatus is null)
{
<FluentProgressRing Width="16px" Style="display: inline-block;" Class="severity-icon" />
}
else
{
// Browse the icon library at: https://aka.ms/fluentui-system-icons
(Icon? icon, Color color) = context.HealthStatus switch
{
HealthStatus.Healthy => ((Icon)new Icons.Filled.Size16.Heart(), Color.Success),
HealthStatus.Degraded => (new Icons.Filled.Size16.HeartBroken(), Color.Warning),
HealthStatus.Unhealthy => (new Icons.Filled.Size16.HeartBroken(), Color.Error),
_ => (new Icons.Regular.Size16.CircleHint(), Color.Info)
};

<FluentIcon Value="@icon" Color="@color" Class="severity-icon"/>
}
</ContentBeforeValue>
</GridValue>
</AspireTemplateColumn>
<AspireTemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.DetailsColumnHeader)]" Class="detailsColumn">
<GridValue ValueDescription="@ControlStringsLoc[nameof(ControlsStrings.DetailsColumnHeader)]"
Value="@(context.HealthStatus is null ? Loc[nameof(Resources.WaitingForHealthDataMessage)] : context.DisplayedDescription)"
EnableHighlighting="@(!string.IsNullOrEmpty(_filter))"
HighlightText="@_filter"
TextVisualizerTitle="@context.Name">
<ContentBeforeValue>
<ExceptionDetails ExceptionText="@context.ExceptionText" />
</ContentBeforeValue>
</GridValue>
</AspireTemplateColumn>
</FluentDataGrid>
</FluentAccordionItem>
</FluentAccordion>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/Components/Pages/Resources.razor
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
<AspireTemplateColumn ColumnId="@NameColumn" ColumnManager="@_manager" Title="@ControlsStringsLoc[nameof(ControlsStrings.NameColumnHeader)]" Sortable="true" SortBy="@_nameSort" Tooltip="true" TooltipText="@(c => GetResourceName(c))">
<ResourceNameDisplay Resource="context" FilterText="@_filter" FormatName="GetResourceName" />
</AspireTemplateColumn>
<AspireTemplateColumn ColumnId="@StateColumn" ColumnManager="@_manager" Title="@Loc[nameof(Dashboard.Resources.Resources.ResourcesStateColumnHeader)]" Sortable="true" SortBy="@_stateSort" Tooltip="true" TooltipText="@(c => StateColumnDisplay.GetResourceStateTooltip(c, ColumnsLoc))">
<AspireTemplateColumn ColumnId="@StateColumn" ColumnManager="@_manager" Title="@Loc[nameof(Dashboard.Resources.Resources.ResourcesStateColumnHeader)]" Sortable="true" SortBy="@_stateSort" Tooltip="true" TooltipText="@(c => ResourceStateViewModel.GetResourceStateTooltip(c, ColumnsLoc))">
<StateColumnDisplay Resource="@context" UnviewedErrorCounts="@_applicationUnviewedErrorCounts" />
</AspireTemplateColumn>
<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">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
@using Aspire.Dashboard.Model
@using Aspire.Dashboard.Resources

@{
var vm = GetStateViewModel();
// Create view model every render. The resource state may have changed.
var vm = ResourceStateViewModel.GetStateViewModel(Resource, Loc);
}

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

<FluentIcon
Value="@vm.Icon"
Color="@vm.Color"
Class="severity-icon" />
<FluentIcon Value="@vm.Icon"
Color="@vm.Color"
Class="severity-icon" />

<span>@vm.Text</span>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Globalization;
using Aspire.Dashboard.Extensions;
using Aspire.Dashboard.Model;
using Aspire.Dashboard.Otlp.Storage;
using Aspire.Dashboard.Resources;
using Humanizer;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Localization;
using Microsoft.FluentUI.AspNetCore.Components;

namespace Aspire.Dashboard.Components.ResourcesGridColumns;

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

[Inject]
public required IStringLocalizer<Columns> Loc { get; init; }

/// <summary>
/// Gets the tooltip for a cell in the state column of the resource grid.
/// </summary>
/// <remarks>
/// This is a static method so it can be called at the level of the parent column.
/// </remarks>
public static string? GetResourceStateTooltip(ResourceViewModel resource, IStringLocalizer<Columns> Loc)
{
if (resource.IsStopped())
{
if (resource.TryGetExitCode(out var exitCode) && exitCode is not 0)
{
// Process completed unexpectedly, hence the non-zero code. This is almost certainly an error, so warn users.
return string.Format(CultureInfo.CurrentCulture, Loc[nameof(Columns.StateColumnResourceExitedUnexpectedly)], resource.ResourceType, exitCode);
}
else
{
// Process completed, which may not have been unexpected.
return string.Format(CultureInfo.CurrentCulture, Loc[nameof(Columns.StateColumnResourceExited)], resource.ResourceType);
}
}
else if (resource.KnownState is KnownResourceState.Running && resource.HealthStatus is not HealthStatus.Healthy)
{
// Resource is running but not healthy (initializing).
return Loc[nameof(Columns.RunningAndUnhealthyResourceStateToolTip)];
}

return null;
}

/// <summary>
/// Gets data needed to populate the content of the state column.
/// </summary>
private ResourceStateViewModel GetStateViewModel()
{
// Browse the icon library at: https://aka.ms/fluentui-system-icons

Icon icon;
Color color;

if (Resource.IsStopped())
{
if (Resource.TryGetExitCode(out var exitCode) && exitCode is not 0)
{
// Process completed unexpectedly, hence the non-zero code. This is almost certainly an error, so warn users.
icon = new Icons.Filled.Size16.ErrorCircle();
color = Color.Error;
}
else if (Resource.IsFinishedState())
{
// Process completed successfully.
icon = new Icons.Regular.Size16.RecordStop();
color = Color.Info;
}
else
{
// Process completed, which may not have been unexpected.
icon = new Icons.Filled.Size16.Warning();
color = Color.Warning;
}
}
else if (Resource.IsUnusableTransitoryState() || Resource.IsUnknownState())
{
icon = new Icons.Filled.Size16.CircleHint(); // A dashed, hollow circle.
color = Color.Info;
}
else if (Resource.HasNoState())
{
icon = new Icons.Filled.Size16.Circle();
color = Color.Info;
}
else if (Resource.HealthStatus is not HealthStatus.Healthy)
{
icon = new Icons.Filled.Size16.CheckmarkCircleWarning();
color = Color.Neutral;
}
else if (!string.IsNullOrEmpty(Resource.StateStyle))
{
(icon, color) = Resource.StateStyle switch
{
"warning" => ((Icon)new Icons.Filled.Size16.Warning(), Color.Warning),
"error" => (new Icons.Filled.Size16.ErrorCircle(), Color.Error),
"success" => (new Icons.Filled.Size16.CheckmarkCircle(), Color.Success),
"info" => (new Icons.Filled.Size16.Info(), Color.Info),
_ => (new Icons.Filled.Size16.Circle(), Color.Neutral)
};
}
else
{
icon = new Icons.Filled.Size16.CheckmarkCircle();
color = Color.Success;
}

var text = Resource switch
{
{ State: null or "" } => Loc[nameof(Columns.UnknownStateLabel)],
{ KnownState: KnownResourceState.Running, HealthStatus: not HealthStatus.Healthy } => $"{Resource.State.Humanize()} ({(Resource.HealthStatus ?? HealthStatus.Unhealthy).Humanize()})",
_ => Resource.State.Humanize()
};

return new ResourceStateViewModel(text, icon, color);
}

private record class ResourceStateViewModel(string Text, Icon Icon, Color Color);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ public static bool IsUnusableTransitoryState(this ResourceViewModel resource)
return resource.KnownState is KnownResourceState.Starting or KnownResourceState.Building or KnownResourceState.Waiting or KnownResourceState.Stopping;
}

public static bool IsRuntimeUnhealthy(this ResourceViewModel resource)
{
return resource.KnownState is KnownResourceState.RuntimeUnhealthy;
}

public static bool IsUnknownState(this ResourceViewModel resource) => resource.KnownState is KnownResourceState.Unknown;

public static bool HasNoState(this ResourceViewModel resource) => string.IsNullOrEmpty(resource.State);
Expand Down
3 changes: 2 additions & 1 deletion src/Aspire.Dashboard/Model/KnownResourceState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ public enum KnownResourceState
Hidden,
Waiting,
Stopping,
Unknown
Unknown,
RuntimeUnhealthy
}
Loading