From 150ec05ed96619127f791075ffbc027169c4930c Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Wed, 9 Oct 2024 13:27:38 -0400 Subject: [PATCH 01/24] Remove HealthStatus defaulting to healthy when there is no value from a health check --- .../ApplicationModel/CustomResourceSnapshot.cs | 9 +++------ .../ApplicationModel/ResourceNotificationService.cs | 4 +--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/Aspire.Hosting/ApplicationModel/CustomResourceSnapshot.cs b/src/Aspire.Hosting/ApplicationModel/CustomResourceSnapshot.cs index 00548ab8b93..0ec779960ac 100644 --- a/src/Aspire.Hosting/ApplicationModel/CustomResourceSnapshot.cs +++ b/src/Aspire.Hosting/ApplicationModel/CustomResourceSnapshot.cs @@ -52,14 +52,11 @@ public sealed record CustomResourceSnapshot /// /// /// - /// This value is derived from . However, if a resource - /// is known to have a health check and no reports exist, then this value is . - /// - /// - /// Defaults to . + /// This value is derived from . If a resource is known to have a health check + /// and no reports exist, or if a resource does not have a health check, then this value is . /// /// - public HealthStatus? HealthStatus { get; init; } = Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Healthy; + public HealthStatus? HealthStatus { get; init; } /// /// The health reports for this resource. diff --git a/src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs b/src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs index 557a1a1b31a..53de1dceb21 100644 --- a/src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs +++ b/src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs @@ -493,9 +493,7 @@ private static CustomResourceSnapshot GetCurrentSnapshot(IResource resource, Res { ResourceType = resource.GetType().Name, Properties = [], - HealthStatus = resource.TryGetAnnotationsIncludingAncestorsOfType(out _) - ? null // Indicates that the resource has health check annotations but the health status is unknown. - : HealthStatus.Healthy + HealthStatus = null }; } From c26e8dd56f8726fdec3a067b12a8deb3c0150938 Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Mon, 14 Oct 2024 12:03:35 -0400 Subject: [PATCH 02/24] Show unhealthy only if health status is not null --- .../ResourcesGridColumns/StateColumnDisplay.razor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor.cs b/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor.cs index 9d6ce3a3c32..04c819727d1 100644 --- a/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor.cs +++ b/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor.cs @@ -96,7 +96,7 @@ private ResourceStateViewModel GetStateViewModel() icon = new Icons.Filled.Size16.Circle(); color = Color.Neutral; } - else if (Resource.HealthStatus is not HealthStatus.Healthy) + else if (Resource.HealthStatus is not HealthStatus.Healthy and not null) { icon = new Icons.Filled.Size16.CheckmarkCircleWarning(); color = Color.Neutral; @@ -121,7 +121,7 @@ private ResourceStateViewModel GetStateViewModel() var text = Resource switch { { State: null or "" } => Loc[Columns.UnknownStateLabel], - { KnownState: KnownResourceState.Running, HealthStatus: not HealthStatus.Healthy } => $"{Resource.State.Humanize()} ({(Resource.HealthStatus ?? HealthStatus.Unhealthy).Humanize()})", + { KnownState: KnownResourceState.Running, HealthStatus: not HealthStatus.Healthy and not null } => $"{Resource.State.Humanize()} ({(Resource.HealthStatus ?? HealthStatus.Unhealthy).Humanize()})", _ => Resource.State.Humanize() }; From 57f11be0e65108906554261a7e17ca4967aa3668 Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Mon, 14 Oct 2024 12:24:35 -0400 Subject: [PATCH 03/24] Add null assert to status in ResourceNotificationTests --- tests/Aspire.Hosting.Tests/Dashboard/ResourcePublisherTests.cs | 3 +-- tests/Aspire.Hosting.Tests/ResourceNotificationTests.cs | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/Aspire.Hosting.Tests/Dashboard/ResourcePublisherTests.cs b/tests/Aspire.Hosting.Tests/Dashboard/ResourcePublisherTests.cs index 6f6fe8929a9..1564034fa7d 100644 --- a/tests/Aspire.Hosting.Tests/Dashboard/ResourcePublisherTests.cs +++ b/tests/Aspire.Hosting.Tests/Dashboard/ResourcePublisherTests.cs @@ -3,7 +3,6 @@ using Aspire.Dashboard.Model; using Aspire.Hosting.Dashboard; -using Microsoft.Extensions.Diagnostics.HealthChecks; using Xunit; namespace Aspire.Hosting.Tests.Dashboard; @@ -200,7 +199,7 @@ private static GenericResourceSnapshot CreateResourceSnapshot(string name) Urls = [], Volumes = [], Environment = [], - HealthStatus = HealthStatus.Healthy, + HealthStatus = null, HealthReports = [], Commands = [] }; diff --git a/tests/Aspire.Hosting.Tests/ResourceNotificationTests.cs b/tests/Aspire.Hosting.Tests/ResourceNotificationTests.cs index 76401d873fb..36919ad875c 100644 --- a/tests/Aspire.Hosting.Tests/ResourceNotificationTests.cs +++ b/tests/Aspire.Hosting.Tests/ResourceNotificationTests.cs @@ -80,6 +80,7 @@ async Task> GetValuesAsync(CancellationToken cancellationTok Assert.Equal("myResource", c.ResourceId); Assert.Equal("CustomResource", c.Snapshot.ResourceType); Assert.Equal("value", c.Snapshot.Properties.Single(p => p.Name == "A").Value); + Assert.Null(c.Snapshot.HealthStatus); }, c => { @@ -87,6 +88,7 @@ async Task> GetValuesAsync(CancellationToken cancellationTok Assert.Equal("myResource", c.ResourceId); Assert.Equal("CustomResource", c.Snapshot.ResourceType); Assert.Equal("value", c.Snapshot.Properties.Single(p => p.Name == "B").Value); + Assert.Null(c.Snapshot.HealthStatus); }); } From c66b77874405b4d78fcb45e5010481f90f3e9023 Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Mon, 14 Oct 2024 13:20:06 -0400 Subject: [PATCH 04/24] Add test for StateColumnDisplay static functions --- .../StateColumnDisplay.razor | 4 +- .../StateColumnDisplay.razor.cs | 47 +++++--- .../Controls/StateColumnDisplayTests.cs | 112 ++++++++++++++++++ 3 files changed, 142 insertions(+), 21 deletions(-) create mode 100644 tests/Aspire.Dashboard.Components.Tests/Controls/StateColumnDisplayTests.cs diff --git a/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor b/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor index 42016ce4119..fd7c1c6fbc7 100644 --- a/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor +++ b/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor @@ -1,7 +1,7 @@ -@using Aspire.Dashboard.Model +@using Aspire.Dashboard.Resources @{ - var vm = GetStateViewModel(); + var vm = GetStateViewModel(Resource, Loc[Columns.UnknownStateLabel]); }
diff --git a/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor.cs b/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor.cs index 04c819727d1..720dfc3f150 100644 --- a/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor.cs +++ b/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor.cs @@ -25,31 +25,40 @@ public partial class StateColumnDisplay [Inject] public required IStringLocalizer Loc { get; init; } + internal static string? GetResourceStateTooltip(ResourceViewModel resource, IStringLocalizer loc) + { + return GetResourceStateTooltip( + resource, + loc[Columns.StateColumnResourceExitedUnexpectedly].Value, + loc[Columns.StateColumnResourceExited].Value, + loc[nameof(Columns.RunningAndUnhealthyResourceStateToolTip)]); + } + /// /// Gets the tooltip for a cell in the state column of the resource grid. /// /// /// This is a static method so it can be called at the level of the parent column. /// - public static string? GetResourceStateTooltip(ResourceViewModel resource, IStringLocalizer Loc) + internal static string? GetResourceStateTooltip(ResourceViewModel resource, string exitedUnexpectedlyTooltip, string exitedTooltip, string runningAndUnhealthyTooltip) { 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[Columns.StateColumnResourceExitedUnexpectedly], resource.ResourceType, exitCode); + return string.Format(CultureInfo.CurrentCulture, exitedUnexpectedlyTooltip, resource.ResourceType, exitCode); } else { // Process completed, which may not have been unexpected. - return string.Format(CultureInfo.CurrentCulture, Loc[Columns.StateColumnResourceExited], resource.ResourceType); + return string.Format(CultureInfo.CurrentCulture, exitedTooltip, resource.ResourceType); } } - else if (resource.KnownState is KnownResourceState.Running && resource.HealthStatus is not HealthStatus.Healthy) + else if (resource is { KnownState: KnownResourceState.Running, HealthStatus: not HealthStatus.Healthy and not null }) { // Resource is running but not healthy (initializing). - return Loc[nameof(Columns.RunningAndUnhealthyResourceStateToolTip)]; + return runningAndUnhealthyTooltip; } return null; @@ -58,22 +67,22 @@ public partial class StateColumnDisplay /// /// Gets data needed to populate the content of the state column. /// - private ResourceStateViewModel GetStateViewModel() + internal static ResourceStateViewModel GetStateViewModel(ResourceViewModel resource, string unknownStateLabel) { // Browse the icon library at: https://aka.ms/fluentui-system-icons Icon icon; Color color; - if (Resource.IsStopped()) + if (resource.IsStopped()) { - if (Resource.TryGetExitCode(out var exitCode) && exitCode is not 0) + 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()) + else if (resource.IsFinishedState()) { // Process completed successfully. icon = new Icons.Filled.Size16.CheckmarkUnderlineCircle(); @@ -86,24 +95,24 @@ private ResourceStateViewModel GetStateViewModel() color = Color.Warning; } } - else if (Resource.IsUnusableTransitoryState() || Resource.IsUnknownState()) + else if (resource.IsUnusableTransitoryState() || resource.IsUnknownState()) { icon = new Icons.Filled.Size16.CircleHint(); // A dashed, hollow circle. color = Color.Info; } - else if (Resource.HasNoState()) + else if (resource.HasNoState()) { icon = new Icons.Filled.Size16.Circle(); color = Color.Neutral; } - else if (Resource.HealthStatus is not HealthStatus.Healthy and not null) + else if (resource.HealthStatus is not HealthStatus.Healthy and not null) { icon = new Icons.Filled.Size16.CheckmarkCircleWarning(); color = Color.Neutral; } - else if (!string.IsNullOrEmpty(Resource.StateStyle)) + else if (!string.IsNullOrEmpty(resource.StateStyle)) { - (icon, color) = Resource.StateStyle switch + (icon, color) = resource.StateStyle switch { "warning" => ((Icon)new Icons.Filled.Size16.Warning(), Color.Warning), "error" => (new Icons.Filled.Size16.ErrorCircle(), Color.Error), @@ -118,15 +127,15 @@ private ResourceStateViewModel GetStateViewModel() color = Color.Success; } - var text = Resource switch + var text = resource switch { - { State: null or "" } => Loc[Columns.UnknownStateLabel], - { KnownState: KnownResourceState.Running, HealthStatus: not HealthStatus.Healthy and not null } => $"{Resource.State.Humanize()} ({(Resource.HealthStatus ?? HealthStatus.Unhealthy).Humanize()})", - _ => Resource.State.Humanize() + { State: null or "" } => unknownStateLabel, + { KnownState: KnownResourceState.Running, HealthStatus: not HealthStatus.Healthy and not null } => $"{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); + internal record class ResourceStateViewModel(string Text, Icon Icon, Color Color); } diff --git a/tests/Aspire.Dashboard.Components.Tests/Controls/StateColumnDisplayTests.cs b/tests/Aspire.Dashboard.Components.Tests/Controls/StateColumnDisplayTests.cs new file mode 100644 index 00000000000..189d2e96a2f --- /dev/null +++ b/tests/Aspire.Dashboard.Components.Tests/Controls/StateColumnDisplayTests.cs @@ -0,0 +1,112 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Frozen; +using Aspire.Dashboard.Components.ResourcesGridColumns; +using Aspire.Dashboard.Model; +using Google.Protobuf.WellKnownTypes; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.FluentUI.AspNetCore.Components; +using Xunit; +using Enum = System.Enum; + +namespace Aspire.Dashboard.Components.Tests.Controls; + +public class StateColumnDisplayTests +{ + private const string ResourceType = "TestResourceType"; + private const string ExitedUnexpectedlyTooltip = "Exited Unexpectedly {0} {1}"; + private const string ExitedTooltip = "Exited {0}"; + private const string RunningAndUnhealthyTooltip = "Running and Unhealthy"; + private const string UnknownStateLabel = "Unknown"; + + [Theory] + // Resource is no longer running + [InlineData( + /* state */ KnownResourceState.Exited, null, null,null, + /* expected output */ "Exited TestResourceType", "Warning", Color.Warning, "Exited")] + [InlineData( + /* state */ KnownResourceState.Exited, 3, null, null, + /* expected output */ "Exited Unexpectedly TestResourceType 3", "ErrorCircle", Color.Error, "Exited")] + [InlineData( + /* state */ KnownResourceState.Finished, 0, null, null, + /* expected output */ "Exited TestResourceType", "CheckmarkUnderlineCircle", Color.Success, "Finished")] + [InlineData( + /* state */ KnownResourceState.Unknown, null, null, null, + /* expected output */ null, "CircleHint", Color.Info, "Unknown")] + // Health checks + [InlineData( + /* state */ KnownResourceState.Running, null, "Healthy", null, + /* expected output */ null, "CheckmarkCircle", Color.Success, "Running")] + [InlineData( + /* state */ KnownResourceState.Running, null, null, null, + /* expected output */ null, "CheckmarkCircle", Color.Success, "Running")] + [InlineData( + /* state */ KnownResourceState.Running, null, "Unhealthy", null, + /* expected output */ RunningAndUnhealthyTooltip, "CheckmarkCircleWarning", Color.Neutral, "Running (Unhealthy)")] + [InlineData( + /* state */ KnownResourceState.Running, null, null, "warning", + /* expected output */ null, "Warning", Color.Warning, "Running")] + [InlineData( + /* state */ KnownResourceState.Running, null, null, "NOT_A_VALID_STATE_STYLE", + /* expected output */ null, "Circle", Color.Neutral, "Running")] + public void ResourceViewModel_ReturnsCorrectIconAndTooltip( + KnownResourceState state, + int? exitCode, + string? healthStatusString, + string? stateStyle, + string? expectedTooltip, + string expectedIconName, + Color expectedColor, + string expectedText) + { + // Arrange + HealthStatus? healthStatus = healthStatusString is null ? null : Enum.Parse(healthStatusString); + var propertiesDictionary = new Dictionary(); + if (exitCode is not null) + { + propertiesDictionary.TryAdd(KnownProperties.Resource.ExitCode, new ResourcePropertyViewModel(KnownProperties.Resource.ExitCode, Value.ForNumber((double)exitCode), false, null, 0, new BrowserTimeProvider(new NullLoggerFactory()))); + } + + var resource = new ResourceViewModel + { + // these are the properties that affect this column + State = state.ToString(), + KnownState = state, + HealthStatus = healthStatus, + StateStyle = stateStyle, + Properties = propertiesDictionary.ToFrozenDictionary(), + + // these properties don't matter + Name = string.Empty, + ResourceType = ResourceType, + DisplayName = string.Empty, + Uid = string.Empty, + CreationTimeStamp = null, + StartTimeStamp = null, + StopTimeStamp = null, + Environment = default, + Urls = default, + Volumes = default, + Commands = default, + HealthReports = default + }; + + if (exitCode is not null) + { + resource.Properties.TryAdd(KnownProperties.Resource.ExitCode, new ResourcePropertyViewModel(KnownProperties.Resource.ExitCode, Value.ForNumber((double)exitCode), false, null, 0, new BrowserTimeProvider(new NullLoggerFactory()))); + } + + // Act + var tooltip = StateColumnDisplay.GetResourceStateTooltip(resource, ExitedUnexpectedlyTooltip, ExitedTooltip, RunningAndUnhealthyTooltip); + var vm = StateColumnDisplay.GetStateViewModel(resource, UnknownStateLabel); + + // Assert + Assert.Equal(expectedTooltip, tooltip); + + Assert.Equal(expectedIconName, vm.Icon.Name); + Assert.Equal(expectedColor, vm.Color); + Assert.Equal(expectedText, vm.Text); + } +} From a2df9edba10d086f0c9974ebec5c45819c7c3181 Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Mon, 14 Oct 2024 16:07:39 -0400 Subject: [PATCH 05/24] Update with new expectations --- .../Controls/StateColumnDisplayTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Aspire.Dashboard.Components.Tests/Controls/StateColumnDisplayTests.cs b/tests/Aspire.Dashboard.Components.Tests/Controls/StateColumnDisplayTests.cs index 189d2e96a2f..e97eda62b3e 100644 --- a/tests/Aspire.Dashboard.Components.Tests/Controls/StateColumnDisplayTests.cs +++ b/tests/Aspire.Dashboard.Components.Tests/Controls/StateColumnDisplayTests.cs @@ -31,7 +31,7 @@ public class StateColumnDisplayTests /* expected output */ "Exited Unexpectedly TestResourceType 3", "ErrorCircle", Color.Error, "Exited")] [InlineData( /* state */ KnownResourceState.Finished, 0, null, null, - /* expected output */ "Exited TestResourceType", "CheckmarkUnderlineCircle", Color.Success, "Finished")] + /* expected output */ "Exited TestResourceType", "RecordStop", Color.Info, "Finished")] [InlineData( /* state */ KnownResourceState.Unknown, null, null, null, /* expected output */ null, "CircleHint", Color.Info, "Unknown")] From 049dceef65263a2788f75492ba7292044e8552ea Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Tue, 15 Oct 2024 12:31:46 -0400 Subject: [PATCH 06/24] Create initial health reports if we have not received from server --- .../CustomResourceSnapshot.cs | 2 +- .../Dashboard/DashboardServiceData.cs | 23 ++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Aspire.Hosting/ApplicationModel/CustomResourceSnapshot.cs b/src/Aspire.Hosting/ApplicationModel/CustomResourceSnapshot.cs index 0ec779960ac..42f413d4833 100644 --- a/src/Aspire.Hosting/ApplicationModel/CustomResourceSnapshot.cs +++ b/src/Aspire.Hosting/ApplicationModel/CustomResourceSnapshot.cs @@ -181,7 +181,7 @@ public sealed record ResourceCommandSnapshot(string Type, ResourceCommandState S /// The state of the resource, according to the report. /// An optional description of the report, for display. /// An optional string containing exception details. -public sealed record HealthReportSnapshot(string Name, HealthStatus Status, string? Description, string? ExceptionText); +public sealed record HealthReportSnapshot(string Name, HealthStatus? Status, string? Description, string? ExceptionText); /// /// The state of a resource command. diff --git a/src/Aspire.Hosting/Dashboard/DashboardServiceData.cs b/src/Aspire.Hosting/Dashboard/DashboardServiceData.cs index 16f702a4037..c01e64fd0fe 100644 --- a/src/Aspire.Hosting/Dashboard/DashboardServiceData.cs +++ b/src/Aspire.Hosting/Dashboard/DashboardServiceData.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Immutable; using System.Runtime.CompilerServices; using Aspire.Hosting.ApplicationModel; using Microsoft.Extensions.Logging; @@ -49,9 +50,29 @@ static GenericResourceSnapshot CreateResourceSnapshot(IResource resource, string State = snapshot.State?.Text, StateStyle = snapshot.State?.Style, HealthStatus = snapshot.HealthStatus, - HealthReports = snapshot.HealthReports, + HealthReports = GetOrCreateHealthReports(), Commands = snapshot.Commands }; + + ImmutableArray GetOrCreateHealthReports() + { + if (resource.TryGetAnnotationsIncludingAncestorsOfType(out var annotations)) + { + var enumeratedAnnotations = annotations.ToList(); + if (snapshot.HealthReports.Length == enumeratedAnnotations.Count) + { + return snapshot.HealthReports; + } + + var reportsByKey = snapshot.HealthReports.ToDictionary(report => report.Name); + foreach (var healthCheckAnnotation in enumeratedAnnotations.Where(annotation => !reportsByKey.ContainsKey(annotation.Key))) + { + reportsByKey.Add(healthCheckAnnotation.Key, new HealthReportSnapshot(healthCheckAnnotation.Key, null, "Waiting for initial health check results", null)); + } + } + + return snapshot.HealthReports; + } } var timestamp = DateTime.UtcNow; From 951e1f9f4e9ff82ca81efce38603d7e7f216dd89 Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Tue, 15 Oct 2024 12:32:43 -0400 Subject: [PATCH 07/24] update public api --- src/Aspire.Hosting/PublicAPI.Unshipped.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Aspire.Hosting/PublicAPI.Unshipped.txt b/src/Aspire.Hosting/PublicAPI.Unshipped.txt index c27a4aceb82..649da4b3562 100644 --- a/src/Aspire.Hosting/PublicAPI.Unshipped.txt +++ b/src/Aspire.Hosting/PublicAPI.Unshipped.txt @@ -7,10 +7,10 @@ Aspire.Hosting.ApplicationModel.HealthReportSnapshot.Description.get -> string? Aspire.Hosting.ApplicationModel.HealthReportSnapshot.Description.init -> void Aspire.Hosting.ApplicationModel.HealthReportSnapshot.ExceptionText.get -> string? Aspire.Hosting.ApplicationModel.HealthReportSnapshot.ExceptionText.init -> void -Aspire.Hosting.ApplicationModel.HealthReportSnapshot.HealthReportSnapshot(string! Name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus Status, string? Description, string? ExceptionText) -> void +Aspire.Hosting.ApplicationModel.HealthReportSnapshot.HealthReportSnapshot(string! Name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? Status, string? Description, string? ExceptionText) -> void Aspire.Hosting.ApplicationModel.HealthReportSnapshot.Name.get -> string! Aspire.Hosting.ApplicationModel.HealthReportSnapshot.Name.init -> void -Aspire.Hosting.ApplicationModel.HealthReportSnapshot.Status.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus +Aspire.Hosting.ApplicationModel.HealthReportSnapshot.Status.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? Aspire.Hosting.ApplicationModel.HealthReportSnapshot.Status.init -> void Aspire.Hosting.ApplicationModel.IModelNameParameter Aspire.Hosting.ApplicationModel.AfterEndpointsAllocatedEvent From 19bb010c74e84f6cdefc95e9d9073fbeb82c1302 Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Tue, 15 Oct 2024 13:27:26 -0400 Subject: [PATCH 08/24] Plumb nullable health status / reports through, create fake snapshots if necessary, add waiting UI --- .../Components/Controls/ResourceDetails.razor | 113 +++++++++--------- .../Model/ResourceViewModel.cs | 6 +- .../ResourceService/Partials.cs | 4 +- .../Resources/Resources.Designer.cs | 10 +- src/Aspire.Dashboard/Resources/Resources.resx | 60 +++++----- .../Resources/xlf/Resources.cs.xlf | 7 +- .../Resources/xlf/Resources.de.xlf | 7 +- .../Resources/xlf/Resources.es.xlf | 7 +- .../Resources/xlf/Resources.fr.xlf | 7 +- .../Resources/xlf/Resources.it.xlf | 7 +- .../Resources/xlf/Resources.ja.xlf | 7 +- .../Resources/xlf/Resources.ko.xlf | 7 +- .../Resources/xlf/Resources.pl.xlf | 7 +- .../Resources/xlf/Resources.pt-BR.xlf | 7 +- .../Resources/xlf/Resources.ru.xlf | 7 +- .../Resources/xlf/Resources.tr.xlf | 7 +- .../Resources/xlf/Resources.zh-Hans.xlf | 7 +- .../Resources/xlf/Resources.zh-Hant.xlf | 7 +- .../Dashboard/DashboardServiceData.cs | 28 +++-- .../Dashboard/proto/Partials.cs | 16 ++- .../Dashboard/proto/resource_service.proto | 4 +- 21 files changed, 208 insertions(+), 124 deletions(-) diff --git a/src/Aspire.Dashboard/Components/Controls/ResourceDetails.razor b/src/Aspire.Dashboard/Components/Controls/ResourceDetails.razor index 4de6fc9f6a8..7096417d6e8 100644 --- a/src/Aspire.Dashboard/Components/Controls/ResourceDetails.razor +++ b/src/Aspire.Dashboard/Components/Controls/ResourceDetails.razor @@ -117,64 +117,61 @@ @FilteredHealthReports.Count()
- @if (Resource.HealthStatus is null) - { -
- - @Loc[nameof(Resources.WaitingForHealthDataMessage)] -
- } - 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) - }; - } - - - - - - - - - - - - - } + + + + + + + + + @if (context.HealthStatus is null) + { + + } + 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) + }; + + + } + + + + + + + + + + + diff --git a/src/Aspire.Dashboard/Model/ResourceViewModel.cs b/src/Aspire.Dashboard/Model/ResourceViewModel.cs index a9ecdb5581a..e7b5f854aa0 100644 --- a/src/Aspire.Dashboard/Model/ResourceViewModel.cs +++ b/src/Aspire.Dashboard/Model/ResourceViewModel.cs @@ -292,9 +292,9 @@ public bool MatchesFilter(string filter) => Target?.Contains(filter, StringComparison.CurrentCultureIgnoreCase) == true; } -public sealed record class HealthReportViewModel(string Name, HealthStatus HealthStatus, string? Description, string? ExceptionText) +public sealed record class HealthReportViewModel(string Name, HealthStatus? HealthStatus, string? Description, string? ExceptionText) { - private readonly string _humanizedHealthStatus = HealthStatus.Humanize(); + private readonly string? _humanizedHealthStatus = HealthStatus?.Humanize(); public string? DisplayedDescription { @@ -320,6 +320,6 @@ public bool MatchesFilter(string filter) return Name?.Contains(filter, StringComparison.CurrentCultureIgnoreCase) == true || Description?.Contains(filter, StringComparison.CurrentCultureIgnoreCase) == true || - _humanizedHealthStatus.Contains(filter, StringComparison.OrdinalIgnoreCase); + _humanizedHealthStatus?.Contains(filter, StringComparison.OrdinalIgnoreCase) is true; } } diff --git a/src/Aspire.Dashboard/ResourceService/Partials.cs b/src/Aspire.Dashboard/ResourceService/Partials.cs index 395fd16f21d..e951ffd09d7 100644 --- a/src/Aspire.Dashboard/ResourceService/Partials.cs +++ b/src/Aspire.Dashboard/ResourceService/Partials.cs @@ -60,10 +60,10 @@ public ResourceViewModel ToViewModel(BrowserTimeProvider timeProvider, IKnownPro HealthReportViewModel ToHealthReportViewModel(HealthReport healthReport) { - return new HealthReportViewModel(healthReport.Key, MapHealthStatus(healthReport.Status, healthReport.Exception), healthReport.Description, healthReport.Exception); + return new HealthReportViewModel(healthReport.Key, healthReport.HasStatus ? MapHealthStatus(healthReport.Status) : null, healthReport.Description, healthReport.Exception); } - Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus MapHealthStatus(HealthStatus healthStatus, string? exceptionText = null) + Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus MapHealthStatus(HealthStatus healthStatus) { return healthStatus switch { diff --git a/src/Aspire.Dashboard/Resources/Resources.Designer.cs b/src/Aspire.Dashboard/Resources/Resources.Designer.cs index 1ba7da0f2a4..0de775f02c5 100644 --- a/src/Aspire.Dashboard/Resources/Resources.Designer.cs +++ b/src/Aspire.Dashboard/Resources/Resources.Designer.cs @@ -1,7 +1,6 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -455,5 +454,14 @@ public static string WaitingForHealthDataMessage { return ResourceManager.GetString("WaitingForHealthDataMessage", resourceCulture); } } + + /// + /// Looks up a localized string similar to Waiting.... + /// + public static string WaitingHealthDataStatusMessage { + get { + return ResourceManager.GetString("WaitingHealthDataStatusMessage", resourceCulture); + } + } } } diff --git a/src/Aspire.Dashboard/Resources/Resources.resx b/src/Aspire.Dashboard/Resources/Resources.resx index b0bd0c06a79..1647d5ac7bd 100644 --- a/src/Aspire.Dashboard/Resources/Resources.resx +++ b/src/Aspire.Dashboard/Resources/Resources.resx @@ -1,17 +1,17 @@ - @@ -253,6 +253,10 @@ Waiting for health data... - A message shown in the UI as a placeholder to indicate that health checks are expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + + + Waiting... + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.cs.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.cs.xlf index 9c2bbeb1d25..9c957f776a7 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Resources.cs.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Resources.cs.xlf @@ -220,7 +220,12 @@ Waiting for health data... Waiting for health data... - A message shown in the UI as a placeholder to indicate that health checks are expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + + + Waiting... + Waiting... + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.de.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.de.xlf index 925cadd27cb..58c1cb5c987 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Resources.de.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Resources.de.xlf @@ -220,7 +220,12 @@ Waiting for health data... Waiting for health data... - A message shown in the UI as a placeholder to indicate that health checks are expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + + + Waiting... + Waiting... + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.es.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.es.xlf index 135fb6751a4..5f6d5a5b51a 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Resources.es.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Resources.es.xlf @@ -220,7 +220,12 @@ Waiting for health data... Waiting for health data... - A message shown in the UI as a placeholder to indicate that health checks are expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + + + Waiting... + Waiting... + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.fr.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.fr.xlf index b488da7d6e7..12ac2122a29 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Resources.fr.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Resources.fr.xlf @@ -220,7 +220,12 @@ Waiting for health data... Waiting for health data... - A message shown in the UI as a placeholder to indicate that health checks are expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + + + Waiting... + Waiting... + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.it.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.it.xlf index 5e9a2a47299..4500551cc5b 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Resources.it.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Resources.it.xlf @@ -220,7 +220,12 @@ Waiting for health data... Waiting for health data... - A message shown in the UI as a placeholder to indicate that health checks are expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + + + Waiting... + Waiting... + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.ja.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.ja.xlf index 427004083c0..64bcbde295a 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Resources.ja.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Resources.ja.xlf @@ -220,7 +220,12 @@ Waiting for health data... Waiting for health data... - A message shown in the UI as a placeholder to indicate that health checks are expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + + + Waiting... + Waiting... + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.ko.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.ko.xlf index 5dceaa9a948..d5e484641e2 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Resources.ko.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Resources.ko.xlf @@ -220,7 +220,12 @@ Waiting for health data... Waiting for health data... - A message shown in the UI as a placeholder to indicate that health checks are expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + + + Waiting... + Waiting... + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.pl.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.pl.xlf index 8fd992baf0f..074cd730bf4 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Resources.pl.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Resources.pl.xlf @@ -220,7 +220,12 @@ Waiting for health data... Waiting for health data... - A message shown in the UI as a placeholder to indicate that health checks are expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + + + Waiting... + Waiting... + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.pt-BR.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.pt-BR.xlf index 57abf4b223a..786e110b36c 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Resources.pt-BR.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Resources.pt-BR.xlf @@ -220,7 +220,12 @@ Waiting for health data... Waiting for health data... - A message shown in the UI as a placeholder to indicate that health checks are expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + + + Waiting... + Waiting... + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.ru.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.ru.xlf index dfac693f186..7104da589cb 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Resources.ru.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Resources.ru.xlf @@ -220,7 +220,12 @@ Waiting for health data... Waiting for health data... - A message shown in the UI as a placeholder to indicate that health checks are expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + + + Waiting... + Waiting... + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.tr.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.tr.xlf index 9926bcad575..d5844ab96a9 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Resources.tr.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Resources.tr.xlf @@ -220,7 +220,12 @@ Waiting for health data... Waiting for health data... - A message shown in the UI as a placeholder to indicate that health checks are expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + + + Waiting... + Waiting... + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hans.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hans.xlf index d3bc008437d..f761e2cfa6f 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hans.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hans.xlf @@ -220,7 +220,12 @@ Waiting for health data... Waiting for health data... - A message shown in the UI as a placeholder to indicate that health checks are expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + + + Waiting... + Waiting... + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hant.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hant.xlf index 6f0147c4ee3..8e5e498cc98 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hant.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hant.xlf @@ -220,7 +220,12 @@ Waiting for health data... Waiting for health data... - A message shown in the UI as a placeholder to indicate that health checks are expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. + + + Waiting... + Waiting... + A message shown in the UI as a placeholder to indicate that a health check is expected to produce health report data soon. Once it arrives, this message will be hidden and the data shown in its place. diff --git a/src/Aspire.Hosting/Dashboard/DashboardServiceData.cs b/src/Aspire.Hosting/Dashboard/DashboardServiceData.cs index c01e64fd0fe..3b8e6fd102d 100644 --- a/src/Aspire.Hosting/Dashboard/DashboardServiceData.cs +++ b/src/Aspire.Hosting/Dashboard/DashboardServiceData.cs @@ -56,22 +56,24 @@ static GenericResourceSnapshot CreateResourceSnapshot(IResource resource, string ImmutableArray GetOrCreateHealthReports() { - if (resource.TryGetAnnotationsIncludingAncestorsOfType(out var annotations)) + if (!resource.TryGetAnnotationsIncludingAncestorsOfType(out var annotations)) { - var enumeratedAnnotations = annotations.ToList(); - if (snapshot.HealthReports.Length == enumeratedAnnotations.Count) - { - return snapshot.HealthReports; - } - - var reportsByKey = snapshot.HealthReports.ToDictionary(report => report.Name); - foreach (var healthCheckAnnotation in enumeratedAnnotations.Where(annotation => !reportsByKey.ContainsKey(annotation.Key))) - { - reportsByKey.Add(healthCheckAnnotation.Key, new HealthReportSnapshot(healthCheckAnnotation.Key, null, "Waiting for initial health check results", null)); - } + return snapshot.HealthReports; } - return snapshot.HealthReports; + var enumeratedAnnotations = annotations.ToList(); + if (snapshot.HealthReports.Length == enumeratedAnnotations.Count) + { + return snapshot.HealthReports; + } + + var reportsByKey = snapshot.HealthReports.ToDictionary(report => report.Name); + foreach (var healthCheckAnnotation in enumeratedAnnotations.Where(annotation => !reportsByKey.ContainsKey(annotation.Key))) + { + reportsByKey.Add(healthCheckAnnotation.Key, new HealthReportSnapshot(healthCheckAnnotation.Key, null, null, null)); + } + + return [..reportsByKey.Values]; } } diff --git a/src/Aspire.Hosting/Dashboard/proto/Partials.cs b/src/Aspire.Hosting/Dashboard/proto/Partials.cs index 9d036046167..5941a2a4ac6 100644 --- a/src/Aspire.Hosting/Dashboard/proto/Partials.cs +++ b/src/Aspire.Hosting/Dashboard/proto/Partials.cs @@ -66,22 +66,30 @@ public static Resource FromSnapshot(ResourceSnapshot snapshot) resource.Commands.Add(new ResourceCommand { CommandType = command.Type, DisplayName = command.DisplayName, DisplayDescription = command.DisplayDescription ?? string.Empty, Parameter = ResourceSnapshot.ConvertToValue(command.Parameter), ConfirmationMessage = command.ConfirmationMessage ?? string.Empty, IconName = command.IconName ?? string.Empty, IconVariant = MapIconVariant(command.IconVariant), IsHighlighted = command.IsHighlighted, State = MapCommandState(command.State) }); } - if (snapshot.HealthStatus is not null) + if (MapHealthStatus(snapshot.HealthStatus) is { } healthStatus) { - resource.HealthStatus = MapHealthStatus(snapshot.HealthStatus.Value); + resource.HealthStatus = healthStatus; } foreach (var report in snapshot.HealthReports) { - resource.HealthReports.Add(new HealthReport { Key = report.Name, Description = report.Description ?? "", Status = MapHealthStatus(report.Status), Exception = report.ExceptionText ?? "" }); + var healthReport = new HealthReport { Key = report.Name, Description = report.Description ?? "", Exception = report.ExceptionText ?? "" }; + + if (MapHealthStatus(report.Status) is { } reportStatus) + { + healthReport.Status = reportStatus; + } + + resource.HealthReports.Add(healthReport); } return resource; - static HealthStatus MapHealthStatus(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus healthStatus) + static HealthStatus? MapHealthStatus(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? healthStatus) { return healthStatus switch { + null => null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Healthy => HealthStatus.Healthy, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Degraded => HealthStatus.Degraded, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Unhealthy => HealthStatus.Unhealthy, diff --git a/src/Aspire.Hosting/Dashboard/proto/resource_service.proto b/src/Aspire.Hosting/Dashboard/proto/resource_service.proto index ce0848f19b6..6fac6d820e4 100644 --- a/src/Aspire.Hosting/Dashboard/proto/resource_service.proto +++ b/src/Aspire.Hosting/Dashboard/proto/resource_service.proto @@ -146,8 +146,8 @@ message Volume { } message HealthReport { - // The health status of the resource. - HealthStatus status = 1; + // The health status of the resource. If null, we have not yet received an initial report from the check. + optional HealthStatus status = 1; // Identifies the health check that produced this report. string key = 2; // The reason for the health status. From 613784cbb817c938f2eae9636e0b720546807522 Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Tue, 15 Oct 2024 13:49:55 -0400 Subject: [PATCH 09/24] Make resource healthy if it is running with no health checks --- .../ApplicationModel/ResourceNotificationService.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs b/src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs index 53de1dceb21..1c5c24eebbf 100644 --- a/src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs +++ b/src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs @@ -492,11 +492,16 @@ private static CustomResourceSnapshot GetCurrentSnapshot(IResource resource, Res previousState ??= new CustomResourceSnapshot() { ResourceType = resource.GetType().Name, - Properties = [], - HealthStatus = null + Properties = [] }; } + // A resource is also healthy if it has no health check annotations and is in the running state. + if (previousState.HealthStatus is null && !resource.TryGetAnnotationsIncludingAncestorsOfType(out _) && previousState.State?.Text == KnownResourceStates.Running) + { + previousState = previousState with { HealthStatus = HealthStatus.Healthy }; + } + return previousState; } From 2e1070924a47de43f371ceaf5aa560c770b11a3c Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Tue, 15 Oct 2024 13:57:44 -0400 Subject: [PATCH 10/24] Update comment --- src/Aspire.Hosting/ApplicationModel/CustomResourceSnapshot.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Aspire.Hosting/ApplicationModel/CustomResourceSnapshot.cs b/src/Aspire.Hosting/ApplicationModel/CustomResourceSnapshot.cs index 42f413d4833..57837428047 100644 --- a/src/Aspire.Hosting/ApplicationModel/CustomResourceSnapshot.cs +++ b/src/Aspire.Hosting/ApplicationModel/CustomResourceSnapshot.cs @@ -178,7 +178,7 @@ public sealed record ResourceCommandSnapshot(string Type, ResourceCommandState S /// A report produced by a health check about a resource. ///
/// The name of the health check that produced this report. -/// The state of the resource, according to the report. +/// The state of the resource, according to the report, or null if a health check has not yet been completed. /// An optional description of the report, for display. /// An optional string containing exception details. public sealed record HealthReportSnapshot(string Name, HealthStatus? Status, string? Description, string? ExceptionText); From f138e461aa6543e92e219cbceef782204ad387d5 Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Tue, 15 Oct 2024 14:00:56 -0400 Subject: [PATCH 11/24] Clean up --- src/Aspire.Hosting/Dashboard/proto/Partials.cs | 11 +++++------ .../Dashboard/proto/resource_service.proto | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Aspire.Hosting/Dashboard/proto/Partials.cs b/src/Aspire.Hosting/Dashboard/proto/Partials.cs index 5941a2a4ac6..2d784cff8d7 100644 --- a/src/Aspire.Hosting/Dashboard/proto/Partials.cs +++ b/src/Aspire.Hosting/Dashboard/proto/Partials.cs @@ -66,18 +66,18 @@ public static Resource FromSnapshot(ResourceSnapshot snapshot) resource.Commands.Add(new ResourceCommand { CommandType = command.Type, DisplayName = command.DisplayName, DisplayDescription = command.DisplayDescription ?? string.Empty, Parameter = ResourceSnapshot.ConvertToValue(command.Parameter), ConfirmationMessage = command.ConfirmationMessage ?? string.Empty, IconName = command.IconName ?? string.Empty, IconVariant = MapIconVariant(command.IconVariant), IsHighlighted = command.IsHighlighted, State = MapCommandState(command.State) }); } - if (MapHealthStatus(snapshot.HealthStatus) is { } healthStatus) + if (snapshot.HealthStatus is not null) { - resource.HealthStatus = healthStatus; + resource.HealthStatus = MapHealthStatus(snapshot.HealthStatus.Value); } foreach (var report in snapshot.HealthReports) { var healthReport = new HealthReport { Key = report.Name, Description = report.Description ?? "", Exception = report.ExceptionText ?? "" }; - if (MapHealthStatus(report.Status) is { } reportStatus) + if (report.Status is not null) { - healthReport.Status = reportStatus; + healthReport.Status = MapHealthStatus(report.Status.Value); } resource.HealthReports.Add(healthReport); @@ -85,11 +85,10 @@ public static Resource FromSnapshot(ResourceSnapshot snapshot) return resource; - static HealthStatus? MapHealthStatus(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? healthStatus) + static HealthStatus MapHealthStatus(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus healthStatus) { return healthStatus switch { - null => null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Healthy => HealthStatus.Healthy, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Degraded => HealthStatus.Degraded, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Unhealthy => HealthStatus.Unhealthy, diff --git a/src/Aspire.Hosting/Dashboard/proto/resource_service.proto b/src/Aspire.Hosting/Dashboard/proto/resource_service.proto index 6fac6d820e4..b325b0e856c 100644 --- a/src/Aspire.Hosting/Dashboard/proto/resource_service.proto +++ b/src/Aspire.Hosting/Dashboard/proto/resource_service.proto @@ -146,7 +146,7 @@ message Volume { } message HealthReport { - // The health status of the resource. If null, we have not yet received an initial report from the check. + // The health status of the resource. Not provided if we have not yet received an initial report from the check. optional HealthStatus status = 1; // Identifies the health check that produced this report. string key = 2; From c726d5ae0dde72e8d8c4f6663e37ce34207b92b6 Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Tue, 15 Oct 2024 15:30:53 -0400 Subject: [PATCH 12/24] Add initial test, move health status update logic elsewhere in notification service --- .../ResourceNotificationService.cs | 21 ++++++++++---- .../Health/ResourceHealthCheckServiceTests.cs | 29 +++++++++++++++++++ 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs b/src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs index 1c5c24eebbf..968c4b96277 100644 --- a/src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs +++ b/src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs @@ -352,6 +352,7 @@ public Task PublishUpdateAsync(IResource resource, string resourceId, Func + /// Update resource snapshot health status if the resource is running with no health checks. + /// + private static CustomResourceSnapshot UpdateHealthStatus(IResource resource, CustomResourceSnapshot previousState) + { + // A resource is also healthy if it has no health check annotations and is in the running state. + if (!resource.TryGetAnnotationsIncludingAncestorsOfType(out _) && previousState.State?.Text == KnownResourceStates.Running) + { + return previousState with { HealthStatus = HealthStatus.Healthy }; + } + + return previousState; + } + /// /// Use command annotations to update resource snapshot. /// @@ -496,12 +511,6 @@ private static CustomResourceSnapshot GetCurrentSnapshot(IResource resource, Res }; } - // A resource is also healthy if it has no health check annotations and is in the running state. - if (previousState.HealthStatus is null && !resource.TryGetAnnotationsIncludingAncestorsOfType(out _) && previousState.State?.Text == KnownResourceStates.Running) - { - previousState = previousState with { HealthStatus = HealthStatus.Healthy }; - } - return previousState; } diff --git a/tests/Aspire.Hosting.Tests/Health/ResourceHealthCheckServiceTests.cs b/tests/Aspire.Hosting.Tests/Health/ResourceHealthCheckServiceTests.cs index 53a18c44c65..51ed5390545 100644 --- a/tests/Aspire.Hosting.Tests/Health/ResourceHealthCheckServiceTests.cs +++ b/tests/Aspire.Hosting.Tests/Health/ResourceHealthCheckServiceTests.cs @@ -11,6 +11,35 @@ namespace Aspire.Hosting.Tests.Health; public class ResourceHealthCheckServiceTests(ITestOutputHelper testOutputHelper) { + [Fact] + public async Task ResourcesWithoutHealthCheck_HealthyWhenRunning() + { + using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); + var resource = builder.AddResource(new ParentResource("resource")); + + await using var app = await builder.BuildAsync(); + var rns = app.Services.GetRequiredService(); + + await rns.PublishUpdateAsync(resource.Resource, s => s with + { + State = new ResourceStateSnapshot(KnownResourceStates.Starting, null) + }); + + var startingEvent = await rns.WaitForResourceAsync("resource", e => e.Snapshot.State?.Text == KnownResourceStates.Starting); + Assert.Null(startingEvent.Snapshot.HealthStatus); + + await rns.PublishUpdateAsync(resource.Resource, s => s with + { + State = new ResourceStateSnapshot(KnownResourceStates.Running, null) + }); + + var runningEvent = await rns.WaitForResourceAsync("resource", e => e.Snapshot.State?.Text == KnownResourceStates.Running); + Assert.Equal(HealthStatus.Healthy, runningEvent.Snapshot.HealthStatus); + + await app.StopAsync(); + + } + [Fact] public async Task ResourcesWithoutHealthCheckAnnotationsGetReadyEventFired() { From 54c32fd9b89f4c3fc6aa40c4e9c3158ce1fcb8a0 Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Wed, 16 Oct 2024 14:16:29 -0400 Subject: [PATCH 13/24] Add two more tests --- .../Dashboard/DashboardServiceTests.cs | 96 +++++++++++++++++-- .../Health/ResourceHealthCheckServiceTests.cs | 41 ++++++++ 2 files changed, 130 insertions(+), 7 deletions(-) diff --git a/tests/Aspire.Hosting.Tests/Dashboard/DashboardServiceTests.cs b/tests/Aspire.Hosting.Tests/Dashboard/DashboardServiceTests.cs index acc302204a2..9de80f706f7 100644 --- a/tests/Aspire.Hosting.Tests/Dashboard/DashboardServiceTests.cs +++ b/tests/Aspire.Hosting.Tests/Dashboard/DashboardServiceTests.cs @@ -8,12 +8,15 @@ using Aspire.ResourceService.Proto.V1; using Google.Protobuf.WellKnownTypes; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Xunit; using DashboardService = Aspire.Hosting.Dashboard.DashboardService; +using HealthStatus = Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus; +using ProtoHealthStatus = Aspire.ResourceService.Proto.V1.HealthStatus; using Resource = Aspire.Hosting.ApplicationModel.Resource; namespace Aspire.Hosting.Tests.Dashboard; @@ -124,16 +127,81 @@ await resourceNotificationService.WaitForResourceAsync(testResource.Name, r => Assert.Equal(ResourceService.Proto.V1.IconVariant.Filled, commandData.IconVariant); Assert.True(commandData.IsHighlighted); - cts.Cancel(); + await CancelTokenAndAwaitTask(cts, task); + } - try + [Fact] + public async Task CreateResource_NoChild_WithHealthChecks_ResourceImmediatelyReturnsFakeHealthReports_ThenUpdates() + { + // Arrange + var resourceLoggerService = new ResourceLoggerService(); + var resourceNotificationService = new ResourceNotificationService(NullLogger.Instance, new TestHostApplicationLifetime(), new ServiceCollection().BuildServiceProvider(), resourceLoggerService); + await using var dashboardServiceData = new DashboardServiceData(resourceNotificationService, resourceLoggerService, NullLogger.Instance, new DashboardCommandExecutor(new ServiceCollection().BuildServiceProvider())); + var dashboardService = new DashboardService(dashboardServiceData, new TestHostEnvironment(), new TestHostApplicationLifetime(), NullLogger.Instance); + + var testResource = new TestResource("test-resource"); + using var builder = TestDistributedApplicationBuilder.Create(); + builder.Services.AddHealthChecks() + .AddCheck("Check1", () => HealthCheckResult.Healthy()) + .AddCheck("Check2", () => HealthCheckResult.Healthy()); + + builder.AddResource(testResource) + .WithHealthCheck("Check1") + .WithHealthCheck("Check2"); + + await resourceNotificationService.PublishUpdateAsync(testResource, s => { - await task; - } - catch (OperationCanceledException) + return s with { State = new ResourceStateSnapshot("Starting", null) }; + }); + + var cts = new CancellationTokenSource(); + var context = TestServerCallContext.Create(cancellationToken: cts.Token); + var writer = new TestServerStreamWriter(context); + + // Act + var task = dashboardService.WatchResources( + new WatchResourcesRequest(), + writer, + context); + + // Assert + var initialUpdate = await writer.ReadNextAsync(); + var resource = Assert.Single(initialUpdate.InitialData.Resources); + Assert.False(resource.HasHealthStatus); + Assert.Collection(resource.HealthReports, + r => + { + Assert.Equal("Check1", r.Key); + Assert.False(r.HasStatus); + }, + r => + { + Assert.Equal("Check2", r.Key); + Assert.False(r.HasStatus); + }); + + await resourceNotificationService.PublishUpdateAsync(testResource, s => { - // Ok if this error is thrown. - } + // simulate only having received health check report from one of the checks + return s with { HealthReports = [new HealthReportSnapshot("Check1", HealthStatus.Healthy, null, null)] }; + }); + + var updateAfterCheck = await writer.ReadNextAsync(); + var upsert = Assert.Single(updateAfterCheck.Changes.Value).Upsert; + + Assert.Collection(upsert.HealthReports, + r => + { + Assert.Equal("Check1", r.Key); + Assert.Equal(ProtoHealthStatus.Healthy, r.Status); + }, + r => + { + Assert.Equal("Check2", r.Key); + Assert.False(r.HasStatus); + }); + + await CancelTokenAndAwaitTask(cts, task); } private sealed class TestHostEnvironment : IHostEnvironment @@ -147,4 +215,18 @@ private sealed class TestHostEnvironment : IHostEnvironment private sealed class TestResource(string name) : Resource(name) { } + + private static async Task CancelTokenAndAwaitTask(CancellationTokenSource cts, Task task) + { + await cts.CancelAsync(); + + try + { + await task; + } + catch (OperationCanceledException) + { + // Ok if this error is thrown. + } + } } diff --git a/tests/Aspire.Hosting.Tests/Health/ResourceHealthCheckServiceTests.cs b/tests/Aspire.Hosting.Tests/Health/ResourceHealthCheckServiceTests.cs index 51ed5390545..b489e07546c 100644 --- a/tests/Aspire.Hosting.Tests/Health/ResourceHealthCheckServiceTests.cs +++ b/tests/Aspire.Hosting.Tests/Health/ResourceHealthCheckServiceTests.cs @@ -20,6 +20,8 @@ public async Task ResourcesWithoutHealthCheck_HealthyWhenRunning() await using var app = await builder.BuildAsync(); var rns = app.Services.GetRequiredService(); + await app.StartAsync(); + await rns.PublishUpdateAsync(resource.Resource, s => s with { State = new ResourceStateSnapshot(KnownResourceStates.Starting, null) @@ -37,7 +39,46 @@ await rns.PublishUpdateAsync(resource.Resource, s => s with Assert.Equal(HealthStatus.Healthy, runningEvent.Snapshot.HealthStatus); await app.StopAsync(); + } + + [Fact] + public async Task ResourcesWithHealthCheck_NotHealthyUntilCheckSucceeds() + { + using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); + builder.Services.AddHealthChecks().AddAsyncCheck("healthcheck_a", async () => + { + await Task.Delay(50000); + return HealthCheckResult.Healthy(); + }); + + var resource = builder.AddResource(new ParentResource("resource")) + .WithHealthCheck("healthcheck_a"); + + await using var app = await builder.BuildAsync(); + var rns = app.Services.GetRequiredService(); + + await app.StartAsync(); + + await rns.PublishUpdateAsync(resource.Resource, s => s with + { + State = new ResourceStateSnapshot(KnownResourceStates.Starting, null) + }); + + var startingEvent = await rns.WaitForResourceAsync("resource", e => e.Snapshot.State?.Text == KnownResourceStates.Starting); + Assert.Null(startingEvent.Snapshot.HealthStatus); + + await rns.PublishUpdateAsync(resource.Resource, s => s with + { + State = new ResourceStateSnapshot(KnownResourceStates.Running, null) + }); + var runningEvent = await rns.WaitForResourceAsync("resource", e => e.Snapshot.State?.Text == KnownResourceStates.Running); + Assert.Null(runningEvent.Snapshot.HealthStatus); + + var hasHealthReportsEvent = await rns.WaitForResourceAsync("resource", e => e.Snapshot.HealthReports.Length > 0); + Assert.Equal(HealthStatus.Healthy, hasHealthReportsEvent.Snapshot.HealthStatus); + + await app.StopAsync(); } [Fact] From 78178d87c8b86c0cba3fe7cb4cd63b6590f9b1b7 Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Wed, 16 Oct 2024 14:19:55 -0400 Subject: [PATCH 14/24] Fix flaky test --- .../Dashboard/DashboardServiceTests.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/Aspire.Hosting.Tests/Dashboard/DashboardServiceTests.cs b/tests/Aspire.Hosting.Tests/Dashboard/DashboardServiceTests.cs index 9de80f706f7..0ed00edec56 100644 --- a/tests/Aspire.Hosting.Tests/Dashboard/DashboardServiceTests.cs +++ b/tests/Aspire.Hosting.Tests/Dashboard/DashboardServiceTests.cs @@ -149,11 +149,6 @@ public async Task CreateResource_NoChild_WithHealthChecks_ResourceImmediatelyRet .WithHealthCheck("Check1") .WithHealthCheck("Check2"); - await resourceNotificationService.PublishUpdateAsync(testResource, s => - { - return s with { State = new ResourceStateSnapshot("Starting", null) }; - }); - var cts = new CancellationTokenSource(); var context = TestServerCallContext.Create(cancellationToken: cts.Token); var writer = new TestServerStreamWriter(context); @@ -165,8 +160,13 @@ await resourceNotificationService.PublishUpdateAsync(testResource, s => context); // Assert - var initialUpdate = await writer.ReadNextAsync(); - var resource = Assert.Single(initialUpdate.InitialData.Resources); + await writer.ReadNextAsync(); + await resourceNotificationService.PublishUpdateAsync(testResource, s => + { + return s with { State = new ResourceStateSnapshot("Starting", null) }; + }); + + var resource = Assert.Single((await writer.ReadNextAsync()).Changes.Value).Upsert; Assert.False(resource.HasHealthStatus); Assert.Collection(resource.HealthReports, r => From 65cd571a673da7decaea7988fbd390cf531cd8c4 Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Wed, 16 Oct 2024 14:23:38 -0400 Subject: [PATCH 15/24] Simplify test --- .../Health/ResourceHealthCheckServiceTests.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/Aspire.Hosting.Tests/Health/ResourceHealthCheckServiceTests.cs b/tests/Aspire.Hosting.Tests/Health/ResourceHealthCheckServiceTests.cs index b489e07546c..3672f9a3fb3 100644 --- a/tests/Aspire.Hosting.Tests/Health/ResourceHealthCheckServiceTests.cs +++ b/tests/Aspire.Hosting.Tests/Health/ResourceHealthCheckServiceTests.cs @@ -45,11 +45,7 @@ await rns.PublishUpdateAsync(resource.Resource, s => s with public async Task ResourcesWithHealthCheck_NotHealthyUntilCheckSucceeds() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); - builder.Services.AddHealthChecks().AddAsyncCheck("healthcheck_a", async () => - { - await Task.Delay(50000); - return HealthCheckResult.Healthy(); - }); + builder.Services.AddHealthChecks().AddCheck("healthcheck_a", () => HealthCheckResult.Healthy()); var resource = builder.AddResource(new ParentResource("resource")) .WithHealthCheck("healthcheck_a"); From a28f89db1979f80020ab05bd8cd4c77c29a0ca0a Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Wed, 16 Oct 2024 23:30:32 -0400 Subject: [PATCH 16/24] Apply PR suggestions --- .../StateColumnDisplay.razor | 2 +- .../StateColumnDisplay.razor.cs | 17 +++-------- .../CustomResourceSnapshot.cs | 2 +- .../Controls/StateColumnDisplayTests.cs | 30 ++++++++++++++++++- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor b/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor index fd7c1c6fbc7..f7923a0d799 100644 --- a/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor +++ b/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor @@ -1,7 +1,7 @@ @using Aspire.Dashboard.Resources @{ - var vm = GetStateViewModel(Resource, Loc[Columns.UnknownStateLabel]); + var vm = GetStateViewModel(Resource, Loc[nameof(Columns.UnknownStateLabel)]); }
diff --git a/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor.cs b/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor.cs index c20cbe1c443..d99cf1ea9cc 100644 --- a/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor.cs +++ b/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor.cs @@ -25,40 +25,31 @@ public partial class StateColumnDisplay [Inject] public required IStringLocalizer Loc { get; init; } - internal static string? GetResourceStateTooltip(ResourceViewModel resource, IStringLocalizer loc) - { - return GetResourceStateTooltip( - resource, - loc[Columns.StateColumnResourceExitedUnexpectedly].Value, - loc[Columns.StateColumnResourceExited].Value, - loc[nameof(Columns.RunningAndUnhealthyResourceStateToolTip)]); - } - /// /// Gets the tooltip for a cell in the state column of the resource grid. /// /// /// This is a static method so it can be called at the level of the parent column. /// - internal static string? GetResourceStateTooltip(ResourceViewModel resource, string exitedUnexpectedlyTooltip, string exitedTooltip, string runningAndUnhealthyTooltip) + internal static string? GetResourceStateTooltip(ResourceViewModel resource, IStringLocalizer 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, exitedUnexpectedlyTooltip, resource.ResourceType, exitCode); + return string.Format(CultureInfo.CurrentCulture, loc[nameof(Columns.StateColumnResourceExitedUnexpectedly)].Value, resource.ResourceType, exitCode); } else { // Process completed, which may not have been unexpected. - return string.Format(CultureInfo.CurrentCulture, exitedTooltip, resource.ResourceType); + return string.Format(CultureInfo.CurrentCulture, loc[nameof(Columns.StateColumnResourceExited)].Value, resource.ResourceType); } } else if (resource is { KnownState: KnownResourceState.Running, HealthStatus: not HealthStatus.Healthy and not null }) { // Resource is running but not healthy (initializing). - return runningAndUnhealthyTooltip; + return loc[nameof(Columns.RunningAndUnhealthyResourceStateToolTip)]; } return null; diff --git a/src/Aspire.Hosting/ApplicationModel/CustomResourceSnapshot.cs b/src/Aspire.Hosting/ApplicationModel/CustomResourceSnapshot.cs index 57837428047..ed1e4a38a0a 100644 --- a/src/Aspire.Hosting/ApplicationModel/CustomResourceSnapshot.cs +++ b/src/Aspire.Hosting/ApplicationModel/CustomResourceSnapshot.cs @@ -178,7 +178,7 @@ public sealed record ResourceCommandSnapshot(string Type, ResourceCommandState S /// A report produced by a health check about a resource. /// /// The name of the health check that produced this report. -/// The state of the resource, according to the report, or null if a health check has not yet been completed. +/// The state of the resource, according to the report, or if a health report has not yet been received for this health check. /// An optional description of the report, for display. /// An optional string containing exception details. public sealed record HealthReportSnapshot(string Name, HealthStatus? Status, string? Description, string? ExceptionText); diff --git a/tests/Aspire.Dashboard.Components.Tests/Controls/StateColumnDisplayTests.cs b/tests/Aspire.Dashboard.Components.Tests/Controls/StateColumnDisplayTests.cs index e97eda62b3e..4b50cec1aac 100644 --- a/tests/Aspire.Dashboard.Components.Tests/Controls/StateColumnDisplayTests.cs +++ b/tests/Aspire.Dashboard.Components.Tests/Controls/StateColumnDisplayTests.cs @@ -4,8 +4,10 @@ using System.Collections.Frozen; using Aspire.Dashboard.Components.ResourcesGridColumns; using Aspire.Dashboard.Model; +using Aspire.Dashboard.Resources; using Google.Protobuf.WellKnownTypes; using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.FluentUI.AspNetCore.Components; using Xunit; @@ -98,8 +100,10 @@ public void ResourceViewModel_ReturnsCorrectIconAndTooltip( resource.Properties.TryAdd(KnownProperties.Resource.ExitCode, new ResourcePropertyViewModel(KnownProperties.Resource.ExitCode, Value.ForNumber((double)exitCode), false, null, 0, new BrowserTimeProvider(new NullLoggerFactory()))); } + var localizer = new TestColumnLocalizer(); + // Act - var tooltip = StateColumnDisplay.GetResourceStateTooltip(resource, ExitedUnexpectedlyTooltip, ExitedTooltip, RunningAndUnhealthyTooltip); + var tooltip = StateColumnDisplay.GetResourceStateTooltip(resource, localizer); var vm = StateColumnDisplay.GetStateViewModel(resource, UnknownStateLabel); // Assert @@ -109,4 +113,28 @@ public void ResourceViewModel_ReturnsCorrectIconAndTooltip( Assert.Equal(expectedColor, vm.Color); Assert.Equal(expectedText, vm.Text); } + + private sealed class TestColumnLocalizer : IStringLocalizer + { + public IEnumerable GetAllStrings(bool includeParentCultures) + { + throw new NotImplementedException(); + } + + public LocalizedString this[string name] => name switch + { + nameof(Columns.StateColumnResourceExitedUnexpectedly) => new LocalizedString(nameof(Columns.StateColumnResourceExitedUnexpectedly), ExitedUnexpectedlyTooltip), + nameof(Columns.StateColumnResourceExited) => new LocalizedString(nameof(Columns.StateColumnResourceExited), ExitedTooltip), + nameof(Columns.RunningAndUnhealthyResourceStateToolTip) => new LocalizedString(nameof(Columns.RunningAndUnhealthyResourceStateToolTip), RunningAndUnhealthyTooltip), + _ => throw new ArgumentOutOfRangeException(nameof(name), name, null) + }; + + public LocalizedString this[string name, params object[] arguments] => name switch + { + nameof(Columns.StateColumnResourceExitedUnexpectedly) => new LocalizedString(string.Format(nameof(Columns.StateColumnResourceExitedUnexpectedly), arguments), ExitedUnexpectedlyTooltip), + nameof(Columns.StateColumnResourceExited) => new LocalizedString(string.Format(nameof(Columns.StateColumnResourceExited), arguments), ExitedTooltip), + nameof(Columns.RunningAndUnhealthyResourceStateToolTip) => new LocalizedString(string.Format(nameof(Columns.RunningAndUnhealthyResourceStateToolTip), arguments), RunningAndUnhealthyTooltip), + _ => throw new ArgumentOutOfRangeException(nameof(name), name, null) + }; + } } From 6535aae9fe15de5c8edae7577e1baff7aa0f6218 Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Thu, 17 Oct 2024 00:44:16 -0400 Subject: [PATCH 17/24] Add RuntimeUnhealthy state to known states, as it is affected in StateColumnDisplay --- src/Aspire.Dashboard/Extensions/ResourceViewModelExtensions.cs | 2 +- src/Aspire.Dashboard/Model/KnownResourceState.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Aspire.Dashboard/Extensions/ResourceViewModelExtensions.cs b/src/Aspire.Dashboard/Extensions/ResourceViewModelExtensions.cs index 30fb1750a98..dd65b8f5477 100644 --- a/src/Aspire.Dashboard/Extensions/ResourceViewModelExtensions.cs +++ b/src/Aspire.Dashboard/Extensions/ResourceViewModelExtensions.cs @@ -24,7 +24,7 @@ public static bool IsFinishedState(this ResourceViewModel resource) public static bool IsStopped(this ResourceViewModel resource) { - return resource.KnownState is KnownResourceState.Exited or KnownResourceState.Finished or KnownResourceState.FailedToStart; + return resource.KnownState is KnownResourceState.Exited or KnownResourceState.Finished or KnownResourceState.FailedToStart or KnownResourceState.RuntimeUnhealthy; } public static bool IsUnusableTransitoryState(this ResourceViewModel resource) diff --git a/src/Aspire.Dashboard/Model/KnownResourceState.cs b/src/Aspire.Dashboard/Model/KnownResourceState.cs index fa9a67ae833..a4ff987a569 100644 --- a/src/Aspire.Dashboard/Model/KnownResourceState.cs +++ b/src/Aspire.Dashboard/Model/KnownResourceState.cs @@ -14,5 +14,6 @@ public enum KnownResourceState Hidden, Waiting, Stopping, - Unknown + Unknown, + RuntimeUnhealthy } From c37db7d42e1835249a7dc1845337be3e3164aa2f Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 17 Oct 2024 21:33:59 +0800 Subject: [PATCH 18/24] Refactor state column display and its tests --- .../Components/Pages/Resources.razor | 2 +- .../StateColumnDisplay.razor | 16 +- .../StateColumnDisplay.razor.cs | 109 +------------- .../Extensions/ResourceViewModelExtensions.cs | 5 + .../Model/ResourceStateViewModel.cs | 127 ++++++++++++++++ .../Controls/StateColumnDisplayTests.cs | 140 ------------------ .../DefaultInstrumentUnitResolverTests.cs | 9 -- .../Model/StateColumnDisplayTests.cs | 93 ++++++++++++ .../Model/TestStringLocalizer.cs | 14 ++ .../Shared/DashboardModel/ModelTestHelpers.cs | 8 +- 10 files changed, 254 insertions(+), 269 deletions(-) create mode 100644 src/Aspire.Dashboard/Model/ResourceStateViewModel.cs delete mode 100644 tests/Aspire.Dashboard.Components.Tests/Controls/StateColumnDisplayTests.cs create mode 100644 tests/Aspire.Dashboard.Tests/Model/StateColumnDisplayTests.cs create mode 100644 tests/Aspire.Dashboard.Tests/Model/TestStringLocalizer.cs diff --git a/src/Aspire.Dashboard/Components/Pages/Resources.razor b/src/Aspire.Dashboard/Components/Pages/Resources.razor index 112c7f0ea6b..78ab323fcc7 100644 --- a/src/Aspire.Dashboard/Components/Pages/Resources.razor +++ b/src/Aspire.Dashboard/Components/Pages/Resources.razor @@ -96,7 +96,7 @@ - + diff --git a/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor b/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor index f7923a0d799..b45de172605 100644 --- a/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor +++ b/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor @@ -1,17 +1,13 @@ -@using Aspire.Dashboard.Resources - -@{ - var vm = GetStateViewModel(Resource, Loc[nameof(Columns.UnknownStateLabel)]); -} +@using Aspire.Dashboard.Model +@using Aspire.Dashboard.Resources
- + - @vm.Text + @_viewModel.Text Loc { get; init; } - /// - /// Gets the tooltip for a cell in the state column of the resource grid. - /// - /// - /// This is a static method so it can be called at the level of the parent column. - /// - internal static string? GetResourceStateTooltip(ResourceViewModel resource, IStringLocalizer 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)].Value, resource.ResourceType, exitCode); - } - else - { - // Process completed, which may not have been unexpected. - return string.Format(CultureInfo.CurrentCulture, loc[nameof(Columns.StateColumnResourceExited)].Value, resource.ResourceType); - } - } - else if (resource is { KnownState: KnownResourceState.Running, HealthStatus: not HealthStatus.Healthy and not null }) - { - // Resource is running but not healthy (initializing). - return loc[nameof(Columns.RunningAndUnhealthyResourceStateToolTip)]; - } - - return null; - } + private ResourceStateViewModel _viewModel = default!; - /// - /// Gets data needed to populate the content of the state column. - /// - internal static ResourceStateViewModel GetStateViewModel(ResourceViewModel resource, string unknownStateLabel) + protected override void OnInitialized() { - // 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 and not null) - { - 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 "" } => unknownStateLabel, - { KnownState: KnownResourceState.Running, HealthStatus: not HealthStatus.Healthy and not null } => $"{resource.State.Humanize()} ({(resource.HealthStatus ?? HealthStatus.Unhealthy).Humanize()})", - _ => resource.State.Humanize() - }; - - return new ResourceStateViewModel(text, icon, color); + _viewModel = ResourceStateViewModel.GetStateViewModel(Resource, Loc); } - - internal record class ResourceStateViewModel(string Text, Icon Icon, Color Color); } diff --git a/src/Aspire.Dashboard/Extensions/ResourceViewModelExtensions.cs b/src/Aspire.Dashboard/Extensions/ResourceViewModelExtensions.cs index dd65b8f5477..9f933f65fb0 100644 --- a/src/Aspire.Dashboard/Extensions/ResourceViewModelExtensions.cs +++ b/src/Aspire.Dashboard/Extensions/ResourceViewModelExtensions.cs @@ -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); diff --git a/src/Aspire.Dashboard/Model/ResourceStateViewModel.cs b/src/Aspire.Dashboard/Model/ResourceStateViewModel.cs new file mode 100644 index 00000000000..3f61b16bbdf --- /dev/null +++ b/src/Aspire.Dashboard/Model/ResourceStateViewModel.cs @@ -0,0 +1,127 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Dashboard.Extensions; +using Aspire.Dashboard.Resources; +using Humanizer; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Localization; +using Microsoft.FluentUI.AspNetCore.Components; + +namespace Aspire.Dashboard.Model; + +internal class ResourceStateViewModel(string text, Icon icon, Color color) +{ + public string Text { get; } = text; + public Icon Icon { get; } = icon; + public Color Color { get; } = color; + + /// + /// Gets data needed to populate the content of the state column. + /// + internal static ResourceStateViewModel GetStateViewModel(ResourceViewModel resource, IStringLocalizer loc) + { + // 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 and not null) + { + 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 = GetStateText(resource, loc); + + return new ResourceStateViewModel(text, icon, color); + } + + /// + /// Gets the tooltip for a cell in the state column of the resource grid. + /// + /// + /// This is a static method so it can be called at the level of the parent column. + /// + internal static string? GetResourceStateTooltip(ResourceViewModel resource, IStringLocalizer 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 loc.GetString(nameof(Columns.StateColumnResourceExitedUnexpectedly), resource.ResourceType, exitCode); + } + else + { + // Process completed, which may not have been unexpected. + return loc.GetString(nameof(Columns.StateColumnResourceExited), resource.ResourceType); + } + } + else if (resource is { KnownState: KnownResourceState.Running, HealthStatus: not HealthStatus.Healthy and not null }) + { + // Resource is running but not healthy (initializing). + return loc[nameof(Columns.RunningAndUnhealthyResourceStateToolTip)]; + } + + // Fallback to text displayed in column. + return GetStateText(resource, loc); + } + + private static string GetStateText(ResourceViewModel resource, IStringLocalizer loc) + { + return resource switch + { + { State: null or "" } => loc[Columns.UnknownStateLabel], + { KnownState: KnownResourceState.Running, HealthStatus: not HealthStatus.Healthy and not null } => $"{resource.State.Humanize()} ({(resource.HealthStatus ?? HealthStatus.Unhealthy).Humanize()})", + _ => resource.State.Humanize() + }; + } +} diff --git a/tests/Aspire.Dashboard.Components.Tests/Controls/StateColumnDisplayTests.cs b/tests/Aspire.Dashboard.Components.Tests/Controls/StateColumnDisplayTests.cs deleted file mode 100644 index 4b50cec1aac..00000000000 --- a/tests/Aspire.Dashboard.Components.Tests/Controls/StateColumnDisplayTests.cs +++ /dev/null @@ -1,140 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Frozen; -using Aspire.Dashboard.Components.ResourcesGridColumns; -using Aspire.Dashboard.Model; -using Aspire.Dashboard.Resources; -using Google.Protobuf.WellKnownTypes; -using Microsoft.Extensions.Diagnostics.HealthChecks; -using Microsoft.Extensions.Localization; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.FluentUI.AspNetCore.Components; -using Xunit; -using Enum = System.Enum; - -namespace Aspire.Dashboard.Components.Tests.Controls; - -public class StateColumnDisplayTests -{ - private const string ResourceType = "TestResourceType"; - private const string ExitedUnexpectedlyTooltip = "Exited Unexpectedly {0} {1}"; - private const string ExitedTooltip = "Exited {0}"; - private const string RunningAndUnhealthyTooltip = "Running and Unhealthy"; - private const string UnknownStateLabel = "Unknown"; - - [Theory] - // Resource is no longer running - [InlineData( - /* state */ KnownResourceState.Exited, null, null,null, - /* expected output */ "Exited TestResourceType", "Warning", Color.Warning, "Exited")] - [InlineData( - /* state */ KnownResourceState.Exited, 3, null, null, - /* expected output */ "Exited Unexpectedly TestResourceType 3", "ErrorCircle", Color.Error, "Exited")] - [InlineData( - /* state */ KnownResourceState.Finished, 0, null, null, - /* expected output */ "Exited TestResourceType", "RecordStop", Color.Info, "Finished")] - [InlineData( - /* state */ KnownResourceState.Unknown, null, null, null, - /* expected output */ null, "CircleHint", Color.Info, "Unknown")] - // Health checks - [InlineData( - /* state */ KnownResourceState.Running, null, "Healthy", null, - /* expected output */ null, "CheckmarkCircle", Color.Success, "Running")] - [InlineData( - /* state */ KnownResourceState.Running, null, null, null, - /* expected output */ null, "CheckmarkCircle", Color.Success, "Running")] - [InlineData( - /* state */ KnownResourceState.Running, null, "Unhealthy", null, - /* expected output */ RunningAndUnhealthyTooltip, "CheckmarkCircleWarning", Color.Neutral, "Running (Unhealthy)")] - [InlineData( - /* state */ KnownResourceState.Running, null, null, "warning", - /* expected output */ null, "Warning", Color.Warning, "Running")] - [InlineData( - /* state */ KnownResourceState.Running, null, null, "NOT_A_VALID_STATE_STYLE", - /* expected output */ null, "Circle", Color.Neutral, "Running")] - public void ResourceViewModel_ReturnsCorrectIconAndTooltip( - KnownResourceState state, - int? exitCode, - string? healthStatusString, - string? stateStyle, - string? expectedTooltip, - string expectedIconName, - Color expectedColor, - string expectedText) - { - // Arrange - HealthStatus? healthStatus = healthStatusString is null ? null : Enum.Parse(healthStatusString); - var propertiesDictionary = new Dictionary(); - if (exitCode is not null) - { - propertiesDictionary.TryAdd(KnownProperties.Resource.ExitCode, new ResourcePropertyViewModel(KnownProperties.Resource.ExitCode, Value.ForNumber((double)exitCode), false, null, 0, new BrowserTimeProvider(new NullLoggerFactory()))); - } - - var resource = new ResourceViewModel - { - // these are the properties that affect this column - State = state.ToString(), - KnownState = state, - HealthStatus = healthStatus, - StateStyle = stateStyle, - Properties = propertiesDictionary.ToFrozenDictionary(), - - // these properties don't matter - Name = string.Empty, - ResourceType = ResourceType, - DisplayName = string.Empty, - Uid = string.Empty, - CreationTimeStamp = null, - StartTimeStamp = null, - StopTimeStamp = null, - Environment = default, - Urls = default, - Volumes = default, - Commands = default, - HealthReports = default - }; - - if (exitCode is not null) - { - resource.Properties.TryAdd(KnownProperties.Resource.ExitCode, new ResourcePropertyViewModel(KnownProperties.Resource.ExitCode, Value.ForNumber((double)exitCode), false, null, 0, new BrowserTimeProvider(new NullLoggerFactory()))); - } - - var localizer = new TestColumnLocalizer(); - - // Act - var tooltip = StateColumnDisplay.GetResourceStateTooltip(resource, localizer); - var vm = StateColumnDisplay.GetStateViewModel(resource, UnknownStateLabel); - - // Assert - Assert.Equal(expectedTooltip, tooltip); - - Assert.Equal(expectedIconName, vm.Icon.Name); - Assert.Equal(expectedColor, vm.Color); - Assert.Equal(expectedText, vm.Text); - } - - private sealed class TestColumnLocalizer : IStringLocalizer - { - public IEnumerable GetAllStrings(bool includeParentCultures) - { - throw new NotImplementedException(); - } - - public LocalizedString this[string name] => name switch - { - nameof(Columns.StateColumnResourceExitedUnexpectedly) => new LocalizedString(nameof(Columns.StateColumnResourceExitedUnexpectedly), ExitedUnexpectedlyTooltip), - nameof(Columns.StateColumnResourceExited) => new LocalizedString(nameof(Columns.StateColumnResourceExited), ExitedTooltip), - nameof(Columns.RunningAndUnhealthyResourceStateToolTip) => new LocalizedString(nameof(Columns.RunningAndUnhealthyResourceStateToolTip), RunningAndUnhealthyTooltip), - _ => throw new ArgumentOutOfRangeException(nameof(name), name, null) - }; - - public LocalizedString this[string name, params object[] arguments] => name switch - { - nameof(Columns.StateColumnResourceExitedUnexpectedly) => new LocalizedString(string.Format(nameof(Columns.StateColumnResourceExitedUnexpectedly), arguments), ExitedUnexpectedlyTooltip), - nameof(Columns.StateColumnResourceExited) => new LocalizedString(string.Format(nameof(Columns.StateColumnResourceExited), arguments), ExitedTooltip), - nameof(Columns.RunningAndUnhealthyResourceStateToolTip) => new LocalizedString(string.Format(nameof(Columns.RunningAndUnhealthyResourceStateToolTip), arguments), RunningAndUnhealthyTooltip), - _ => throw new ArgumentOutOfRangeException(nameof(name), name, null) - }; - } -} diff --git a/tests/Aspire.Dashboard.Tests/Model/DefaultInstrumentUnitResolverTests.cs b/tests/Aspire.Dashboard.Tests/Model/DefaultInstrumentUnitResolverTests.cs index 0fede0e7aeb..73ac32f5129 100644 --- a/tests/Aspire.Dashboard.Tests/Model/DefaultInstrumentUnitResolverTests.cs +++ b/tests/Aspire.Dashboard.Tests/Model/DefaultInstrumentUnitResolverTests.cs @@ -5,7 +5,6 @@ using Aspire.Dashboard.Otlp.Model; using Aspire.Dashboard.Resources; using Aspire.Tests.Shared.Telemetry; -using Microsoft.Extensions.Localization; using OpenTelemetry.Proto.Common.V1; using Xunit; @@ -41,12 +40,4 @@ public void ResolveDisplayedUnit(string unit, string name, string expected) // Assert Assert.Equal(expected, result); } - - private sealed class TestStringLocalizer : IStringLocalizer - { - public LocalizedString this[string name] => new LocalizedString(name, $"Localized:{name}"); - public LocalizedString this[string name, params object[] arguments] => new LocalizedString(name, $"Localized:{name}"); - - public IEnumerable GetAllStrings(bool includeParentCultures) => []; - } } diff --git a/tests/Aspire.Dashboard.Tests/Model/StateColumnDisplayTests.cs b/tests/Aspire.Dashboard.Tests/Model/StateColumnDisplayTests.cs new file mode 100644 index 00000000000..bf1e93aa193 --- /dev/null +++ b/tests/Aspire.Dashboard.Tests/Model/StateColumnDisplayTests.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Dashboard.Model; +using Aspire.Dashboard.Resources; +using Aspire.Tests.Shared.DashboardModel; +using Google.Protobuf.WellKnownTypes; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.FluentUI.AspNetCore.Components; +using Xunit; +using Enum = System.Enum; + +namespace Aspire.Dashboard.Tests.Model; + +public class StateColumnDisplayTests +{ + private const string ResourceType = "TestResourceType"; + + [Theory] + // Resource is no longer running + [InlineData( + /* state */ KnownResourceState.Exited, null, null,null, + /* expected output */ $"Localized:{nameof(Columns.StateColumnResourceExited)}:{ResourceType}", "Warning", Color.Warning, "Exited")] + [InlineData( + /* state */ KnownResourceState.Exited, 3, null, null, + /* expected output */ $"Localized:{nameof(Columns.StateColumnResourceExitedUnexpectedly)}:{ResourceType}+3", "ErrorCircle", Color.Error, "Exited")] + [InlineData( + /* state */ KnownResourceState.Finished, 0, null, null, + /* expected output */ $"Localized:{nameof(Columns.StateColumnResourceExited)}:{ResourceType}", "RecordStop", Color.Info, "Finished")] + [InlineData( + /* state */ KnownResourceState.Unknown, null, null, null, + /* expected output */ "Unknown", "CircleHint", Color.Info, "Unknown")] + // Health checks + [InlineData( + /* state */ KnownResourceState.Running, null, "Healthy", null, + /* expected output */ "Running", "CheckmarkCircle", Color.Success, "Running")] + [InlineData( + /* state */ KnownResourceState.Running, null, null, null, + /* expected output */ "Running", "CheckmarkCircle", Color.Success, "Running")] + [InlineData( + /* state */ KnownResourceState.Running, null, "Unhealthy", null, + /* expected output */ $"Localized:{nameof(Columns.RunningAndUnhealthyResourceStateToolTip)}", "CheckmarkCircleWarning", Color.Neutral, "Running (Unhealthy)")] + [InlineData( + /* state */ KnownResourceState.Running, null, null, "warning", + /* expected output */ "Running", "Warning", Color.Warning, "Running")] + [InlineData( + /* state */ KnownResourceState.Running, null, null, "NOT_A_VALID_STATE_STYLE", + /* expected output */ "Running", "Circle", Color.Neutral, "Running")] + public void ResourceViewModel_ReturnsCorrectIconAndTooltip( + KnownResourceState state, + int? exitCode, + string? healthStatusString, + string? stateStyle, + string? expectedTooltip, + string expectedIconName, + Color expectedColor, + string expectedText) + { + // Arrange + HealthStatus? healthStatus = healthStatusString is null ? null : Enum.Parse(healthStatusString); + var propertiesDictionary = new Dictionary(); + if (exitCode is not null) + { + propertiesDictionary.TryAdd(KnownProperties.Resource.ExitCode, new ResourcePropertyViewModel(KnownProperties.Resource.ExitCode, Value.ForNumber((double)exitCode), false, null, 0, new BrowserTimeProvider(new NullLoggerFactory()))); + } + + var resource = ModelTestHelpers.CreateResource( + state: state, + healthStatus: healthStatus, + stateStyle: stateStyle, + resourceType: ResourceType, + properties: propertiesDictionary); + + if (exitCode is not null) + { + resource.Properties.TryAdd(KnownProperties.Resource.ExitCode, new ResourcePropertyViewModel(KnownProperties.Resource.ExitCode, Value.ForNumber((double)exitCode), false, null, 0, new BrowserTimeProvider(new NullLoggerFactory()))); + } + + var localizer = new TestStringLocalizer(); + + // Act + var tooltip = ResourceStateViewModel.GetResourceStateTooltip(resource, localizer); + var vm = ResourceStateViewModel.GetStateViewModel(resource, localizer); + + // Assert + Assert.Equal(expectedTooltip, tooltip); + + Assert.Equal(expectedIconName, vm.Icon.Name); + Assert.Equal(expectedColor, vm.Color); + Assert.Equal(expectedText, vm.Text); + } +} diff --git a/tests/Aspire.Dashboard.Tests/Model/TestStringLocalizer.cs b/tests/Aspire.Dashboard.Tests/Model/TestStringLocalizer.cs new file mode 100644 index 00000000000..5a458381682 --- /dev/null +++ b/tests/Aspire.Dashboard.Tests/Model/TestStringLocalizer.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Extensions.Localization; + +namespace Aspire.Dashboard.Tests.Model; + +public sealed class TestStringLocalizer : IStringLocalizer +{ + public LocalizedString this[string name] => new LocalizedString(name, $"Localized:{name}"); + public LocalizedString this[string name, params object[] arguments] => new LocalizedString(name, $"Localized:{name}:" + string.Join("+", arguments)); + + public IEnumerable GetAllStrings(bool includeParentCultures) => []; +} diff --git a/tests/Shared/DashboardModel/ModelTestHelpers.cs b/tests/Shared/DashboardModel/ModelTestHelpers.cs index 93dfec4a64d..6af2635a31d 100644 --- a/tests/Shared/DashboardModel/ModelTestHelpers.cs +++ b/tests/Shared/DashboardModel/ModelTestHelpers.cs @@ -16,7 +16,9 @@ public static ResourceViewModel CreateResource( string? displayName = null, ImmutableArray? urls = null, Dictionary? properties = null, - string? resourceType = null) + string? resourceType = null, + string? stateStyle = null, + HealthStatus? healthStatus = null) { return new ResourceViewModel { @@ -33,8 +35,8 @@ public static ResourceViewModel CreateResource( Properties = properties?.ToFrozenDictionary() ?? FrozenDictionary.Empty, State = state?.ToString(), KnownState = state, - StateStyle = null, - HealthStatus = HealthStatus.Healthy, + StateStyle = stateStyle, + HealthStatus = healthStatus, HealthReports = [], Commands = [] }; From 749c0b6c048fa731ec15b01e8315b3ac5d2e136a Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 17 Oct 2024 21:55:19 +0800 Subject: [PATCH 19/24] Runtime unhealthy improvements --- .../Extensions/ResourceViewModelExtensions.cs | 2 +- .../Model/ResourceStateViewModel.cs | 10 +++ .../Resources/Columns.Designer.cs | 10 +++ src/Aspire.Dashboard/Resources/Columns.resx | 7 +- .../Resources/Logs.Designer.cs | 81 ------------------- .../Resources/xlf/Columns.cs.xlf | 7 ++ .../Resources/xlf/Columns.de.xlf | 7 ++ .../Resources/xlf/Columns.es.xlf | 7 ++ .../Resources/xlf/Columns.fr.xlf | 7 ++ .../Resources/xlf/Columns.it.xlf | 7 ++ .../Resources/xlf/Columns.ja.xlf | 7 ++ .../Resources/xlf/Columns.ko.xlf | 7 ++ .../Resources/xlf/Columns.pl.xlf | 7 ++ .../Resources/xlf/Columns.pt-BR.xlf | 7 ++ .../Resources/xlf/Columns.ru.xlf | 7 ++ .../Resources/xlf/Columns.tr.xlf | 7 ++ .../Resources/xlf/Columns.zh-Hans.xlf | 7 ++ .../Resources/xlf/Columns.zh-Hant.xlf | 7 ++ ...ests.cs => ResourceStateViewModelTests.cs} | 7 +- 19 files changed, 123 insertions(+), 85 deletions(-) delete mode 100644 src/Aspire.Dashboard/Resources/Logs.Designer.cs rename tests/Aspire.Dashboard.Tests/Model/{StateColumnDisplayTests.cs => ResourceStateViewModelTests.cs} (92%) diff --git a/src/Aspire.Dashboard/Extensions/ResourceViewModelExtensions.cs b/src/Aspire.Dashboard/Extensions/ResourceViewModelExtensions.cs index 9f933f65fb0..230e1f65825 100644 --- a/src/Aspire.Dashboard/Extensions/ResourceViewModelExtensions.cs +++ b/src/Aspire.Dashboard/Extensions/ResourceViewModelExtensions.cs @@ -24,7 +24,7 @@ public static bool IsFinishedState(this ResourceViewModel resource) public static bool IsStopped(this ResourceViewModel resource) { - return resource.KnownState is KnownResourceState.Exited or KnownResourceState.Finished or KnownResourceState.FailedToStart or KnownResourceState.RuntimeUnhealthy; + return resource.KnownState is KnownResourceState.Exited or KnownResourceState.Finished or KnownResourceState.FailedToStart; } public static bool IsUnusableTransitoryState(this ResourceViewModel resource) diff --git a/src/Aspire.Dashboard/Model/ResourceStateViewModel.cs b/src/Aspire.Dashboard/Model/ResourceStateViewModel.cs index 3f61b16bbdf..40a7c3fa9ed 100644 --- a/src/Aspire.Dashboard/Model/ResourceStateViewModel.cs +++ b/src/Aspire.Dashboard/Model/ResourceStateViewModel.cs @@ -52,6 +52,11 @@ internal static ResourceStateViewModel GetStateViewModel(ResourceViewModel resou icon = new Icons.Filled.Size16.CircleHint(); // A dashed, hollow circle. color = Color.Info; } + else if (resource.IsRuntimeUnhealthy()) + { + icon = new Icons.Filled.Size16.Warning(); + color = Color.Warning; + } else if (resource.HasNoState()) { icon = new Icons.Filled.Size16.Circle(); @@ -110,6 +115,11 @@ internal static ResourceStateViewModel GetStateViewModel(ResourceViewModel resou // Resource is running but not healthy (initializing). return loc[nameof(Columns.RunningAndUnhealthyResourceStateToolTip)]; } + else if (resource.IsRuntimeUnhealthy() && resource.IsContainer()) + { + // DCP reports the container runtime is unhealthy. Most likely the container runtime (e.g. Docker) isn't running. + return loc[nameof(Columns.StateColumnResourceContainerRuntimeUnhealthy)]; + } // Fallback to text displayed in column. return GetStateText(resource, loc); diff --git a/src/Aspire.Dashboard/Resources/Columns.Designer.cs b/src/Aspire.Dashboard/Resources/Columns.Designer.cs index 1993c1800a7..bb6a9ffb5a3 100644 --- a/src/Aspire.Dashboard/Resources/Columns.Designer.cs +++ b/src/Aspire.Dashboard/Resources/Columns.Designer.cs @@ -204,6 +204,16 @@ public static string SourceColumnSourceCopyFullPathToClipboard { } } + /// + /// Looks up a localized string similar to Container runtime was found but appears to be unhealthy. Ensure that it is running. + ///For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy. + /// + public static string StateColumnResourceContainerRuntimeUnhealthy { + get { + return ResourceManager.GetString("StateColumnResourceContainerRuntimeUnhealthy", resourceCulture); + } + } + /// /// Looks up a localized string similar to {0} is no longer running. /// diff --git a/src/Aspire.Dashboard/Resources/Columns.resx b/src/Aspire.Dashboard/Resources/Columns.resx index da6fbab6e86..75a8036965a 100644 --- a/src/Aspire.Dashboard/Resources/Columns.resx +++ b/src/Aspire.Dashboard/Resources/Columns.resx @@ -190,4 +190,9 @@ This container is persistent and won't be stopped when the app host is shut down. - + + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + Contains a new line + + \ No newline at end of file diff --git a/src/Aspire.Dashboard/Resources/Logs.Designer.cs b/src/Aspire.Dashboard/Resources/Logs.Designer.cs deleted file mode 100644 index 0e3cd707ba0..00000000000 --- a/src/Aspire.Dashboard/Resources/Logs.Designer.cs +++ /dev/null @@ -1,81 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Aspire.Dashboard.Resources { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - public class Logs { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Logs() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Aspire.Dashboard.Resources.Logs", typeof(Logs).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to contains. - /// - public static string LogContains { - get { - return ResourceManager.GetString("LogContains", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to not contains. - /// - public static string LogNotContains { - get { - return ResourceManager.GetString("LogNotContains", resourceCulture); - } - } - } -} diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.cs.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.cs.xlf index 5b5385fa06e..404847bd4b2 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.cs.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.cs.xlf @@ -82,6 +82,13 @@ Zkopírovat cestu k souboru do schránky + + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + \r\n in the text starts a new line + {0} is no longer running {0} už neběží. diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.de.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.de.xlf index e9acb525ce2..d093f0b2347 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.de.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.de.xlf @@ -82,6 +82,13 @@ Vollständigen Dateipfad in Zwischenablage kopieren + + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + \r\n in the text starts a new line + {0} is no longer running {0} wird nicht mehr ausgeführt. diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.es.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.es.xlf index 09f4afdaca9..e97af81d169 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.es.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.es.xlf @@ -82,6 +82,13 @@ Copiar la ruta de acceso del archivo al Portapapeles + + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + \r\n in the text starts a new line + {0} is no longer running {0} ya no se está ejecutando diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.fr.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.fr.xlf index ce6387aa728..830d691a8df 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.fr.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.fr.xlf @@ -82,6 +82,13 @@ Copier le chemin d’accès du fichier dans le Presse-papiers + + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + \r\n in the text starts a new line + {0} is no longer running {0} n’est plus en cours d’exécution. diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.it.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.it.xlf index 40939295d54..6d78dec99bd 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.it.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.it.xlf @@ -82,6 +82,13 @@ Copia percorso file negli Appunti + + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + \r\n in the text starts a new line + {0} is no longer running {0} non è più in esecuzione diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.ja.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.ja.xlf index 172fbd8e5ce..c4d9052bea5 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.ja.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.ja.xlf @@ -82,6 +82,13 @@ ファイル パスをクリップボードにコピーする + + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + \r\n in the text starts a new line + {0} is no longer running {0} が実行されなくなりました diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.ko.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.ko.xlf index 78bb9bd1622..97aa25be679 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.ko.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.ko.xlf @@ -82,6 +82,13 @@ 파일 경로를 클립보드에 복사 + + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + \r\n in the text starts a new line + {0} is no longer running {0}(이)가 더 이상 실행되고 있지 않습니다. diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.pl.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.pl.xlf index 9b53d008343..2a19dae1751 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.pl.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.pl.xlf @@ -82,6 +82,13 @@ Kopiuj ścieżkę pliku do schowka + + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + \r\n in the text starts a new line + {0} is no longer running Wątek {0} już nie działa diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.pt-BR.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.pt-BR.xlf index 099cbdf8958..9cca94f2a5c 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.pt-BR.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.pt-BR.xlf @@ -82,6 +82,13 @@ Copiar caminho do arquivo para a área de transferência + + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + \r\n in the text starts a new line + {0} is no longer running {0} não está mais em execução diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.ru.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.ru.xlf index 9cb5edb3113..12acaba8edf 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.ru.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.ru.xlf @@ -82,6 +82,13 @@ Копировать путь файла в буфер обмена + + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + \r\n in the text starts a new line + {0} is no longer running {0} больше не выполняется. diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.tr.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.tr.xlf index 6bf2f26aba8..a406f0460ea 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.tr.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.tr.xlf @@ -82,6 +82,13 @@ Dosya yolunu panoya kopyala + + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + \r\n in the text starts a new line + {0} is no longer running {0} artık çalışmıyor diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.zh-Hans.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.zh-Hans.xlf index 432c1e25615..55646fd8e2a 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.zh-Hans.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.zh-Hans.xlf @@ -82,6 +82,13 @@ 将文件路径复制到剪贴板 + + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + \r\n in the text starts a new line + {0} is no longer running {0} 已不再运行 diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.zh-Hant.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.zh-Hant.xlf index 00c3870d9da..aad496612fb 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.zh-Hant.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.zh-Hant.xlf @@ -82,6 +82,13 @@ 將檔案路徑複製至剪貼簿 + + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + Container runtime was found but appears to be unhealthy. Ensure that it is running. +For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy + \r\n in the text starts a new line + {0} is no longer running {0} 不再執行 diff --git a/tests/Aspire.Dashboard.Tests/Model/StateColumnDisplayTests.cs b/tests/Aspire.Dashboard.Tests/Model/ResourceStateViewModelTests.cs similarity index 92% rename from tests/Aspire.Dashboard.Tests/Model/StateColumnDisplayTests.cs rename to tests/Aspire.Dashboard.Tests/Model/ResourceStateViewModelTests.cs index bf1e93aa193..07d15596a85 100644 --- a/tests/Aspire.Dashboard.Tests/Model/StateColumnDisplayTests.cs +++ b/tests/Aspire.Dashboard.Tests/Model/ResourceStateViewModelTests.cs @@ -13,9 +13,9 @@ namespace Aspire.Dashboard.Tests.Model; -public class StateColumnDisplayTests +public class ResourceStateViewModelTests { - private const string ResourceType = "TestResourceType"; + private const string ResourceType = "Container"; [Theory] // Resource is no longer running @@ -47,6 +47,9 @@ public class StateColumnDisplayTests [InlineData( /* state */ KnownResourceState.Running, null, null, "NOT_A_VALID_STATE_STYLE", /* expected output */ "Running", "Circle", Color.Neutral, "Running")] + [InlineData( + /* state */ KnownResourceState.RuntimeUnhealthy, null, null, null, + /* expected output */ $"Localized:{nameof(Columns.StateColumnResourceContainerRuntimeUnhealthy)}", "Warning", Color.Warning, "Runtime unhealthy")] public void ResourceViewModel_ReturnsCorrectIconAndTooltip( KnownResourceState state, int? exitCode, From ac14849939c06a1320484b89830d14979fff538d Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 17 Oct 2024 21:58:58 +0800 Subject: [PATCH 20/24] Fix merge --- .../Components/ResourcesGridColumns/StateColumnDisplay.razor.cs | 1 - src/Aspire.Dashboard/Resources/xlf/Columns.cs.xlf | 2 +- src/Aspire.Dashboard/Resources/xlf/Columns.de.xlf | 2 +- src/Aspire.Dashboard/Resources/xlf/Columns.es.xlf | 2 +- src/Aspire.Dashboard/Resources/xlf/Columns.fr.xlf | 2 +- src/Aspire.Dashboard/Resources/xlf/Columns.it.xlf | 2 +- src/Aspire.Dashboard/Resources/xlf/Columns.ja.xlf | 2 +- src/Aspire.Dashboard/Resources/xlf/Columns.ko.xlf | 2 +- src/Aspire.Dashboard/Resources/xlf/Columns.pl.xlf | 2 +- src/Aspire.Dashboard/Resources/xlf/Columns.pt-BR.xlf | 2 +- src/Aspire.Dashboard/Resources/xlf/Columns.ru.xlf | 2 +- src/Aspire.Dashboard/Resources/xlf/Columns.tr.xlf | 2 +- src/Aspire.Dashboard/Resources/xlf/Columns.zh-Hans.xlf | 2 +- src/Aspire.Dashboard/Resources/xlf/Columns.zh-Hant.xlf | 2 +- 14 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor.cs b/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor.cs index 53feb819fcd..111c5185a36 100644 --- a/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor.cs +++ b/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor.cs @@ -24,7 +24,6 @@ public partial class StateColumnDisplay protected override void OnInitialized() { - { State: null or "" } => Loc[Columns.UnknownStateLabel], _viewModel = ResourceStateViewModel.GetStateViewModel(Resource, Loc); } } diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.cs.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.cs.xlf index 404847bd4b2..3983238cb5d 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.cs.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.cs.xlf @@ -87,7 +87,7 @@ For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy Container runtime was found but appears to be unhealthy. Ensure that it is running. For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy - \r\n in the text starts a new line + Contains a new line {0} is no longer running diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.de.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.de.xlf index d093f0b2347..8f67261b1ae 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.de.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.de.xlf @@ -87,7 +87,7 @@ For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy Container runtime was found but appears to be unhealthy. Ensure that it is running. For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy - \r\n in the text starts a new line + Contains a new line {0} is no longer running diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.es.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.es.xlf index e97af81d169..c00645d401b 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.es.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.es.xlf @@ -87,7 +87,7 @@ For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy Container runtime was found but appears to be unhealthy. Ensure that it is running. For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy - \r\n in the text starts a new line + Contains a new line {0} is no longer running diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.fr.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.fr.xlf index 830d691a8df..7f7daa47466 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.fr.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.fr.xlf @@ -87,7 +87,7 @@ For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy Container runtime was found but appears to be unhealthy. Ensure that it is running. For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy - \r\n in the text starts a new line + Contains a new line {0} is no longer running diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.it.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.it.xlf index 6d78dec99bd..cb46016b426 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.it.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.it.xlf @@ -87,7 +87,7 @@ For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy Container runtime was found but appears to be unhealthy. Ensure that it is running. For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy - \r\n in the text starts a new line + Contains a new line {0} is no longer running diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.ja.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.ja.xlf index c4d9052bea5..794d8d51858 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.ja.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.ja.xlf @@ -87,7 +87,7 @@ For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy Container runtime was found but appears to be unhealthy. Ensure that it is running. For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy - \r\n in the text starts a new line + Contains a new line {0} is no longer running diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.ko.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.ko.xlf index 97aa25be679..2bc3b023b3e 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.ko.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.ko.xlf @@ -87,7 +87,7 @@ For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy Container runtime was found but appears to be unhealthy. Ensure that it is running. For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy - \r\n in the text starts a new line + Contains a new line {0} is no longer running diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.pl.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.pl.xlf index 2a19dae1751..271bc286e73 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.pl.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.pl.xlf @@ -87,7 +87,7 @@ For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy Container runtime was found but appears to be unhealthy. Ensure that it is running. For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy - \r\n in the text starts a new line + Contains a new line {0} is no longer running diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.pt-BR.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.pt-BR.xlf index 9cca94f2a5c..ffa594b4f6b 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.pt-BR.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.pt-BR.xlf @@ -87,7 +87,7 @@ For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy Container runtime was found but appears to be unhealthy. Ensure that it is running. For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy - \r\n in the text starts a new line + Contains a new line {0} is no longer running diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.ru.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.ru.xlf index 12acaba8edf..5c1de5b3fb8 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.ru.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.ru.xlf @@ -87,7 +87,7 @@ For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy Container runtime was found but appears to be unhealthy. Ensure that it is running. For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy - \r\n in the text starts a new line + Contains a new line {0} is no longer running diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.tr.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.tr.xlf index a406f0460ea..5aa6439927f 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.tr.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.tr.xlf @@ -87,7 +87,7 @@ For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy Container runtime was found but appears to be unhealthy. Ensure that it is running. For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy - \r\n in the text starts a new line + Contains a new line {0} is no longer running diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.zh-Hans.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.zh-Hans.xlf index 55646fd8e2a..7d50eea77ba 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.zh-Hans.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.zh-Hans.xlf @@ -87,7 +87,7 @@ For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy Container runtime was found but appears to be unhealthy. Ensure that it is running. For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy - \r\n in the text starts a new line + Contains a new line {0} is no longer running diff --git a/src/Aspire.Dashboard/Resources/xlf/Columns.zh-Hant.xlf b/src/Aspire.Dashboard/Resources/xlf/Columns.zh-Hant.xlf index aad496612fb..ec30e3a3845 100644 --- a/src/Aspire.Dashboard/Resources/xlf/Columns.zh-Hant.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/Columns.zh-Hant.xlf @@ -87,7 +87,7 @@ For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy Container runtime was found but appears to be unhealthy. Ensure that it is running. For more information, see https://aka.ms/dotnet/aspire/container-runtime-unhealthy - \r\n in the text starts a new line + Contains a new line {0} is no longer running From 02971026aecf5a35d3828247d31efb81a1cb3a27 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 17 Oct 2024 22:15:01 +0800 Subject: [PATCH 21/24] Fix state not changing --- .../ResourcesGridColumns/StateColumnDisplay.razor | 11 ++++++++--- .../ResourcesGridColumns/StateColumnDisplay.razor.cs | 7 ------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor b/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor index b45de172605..9f2fec653bc 100644 --- a/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor +++ b/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor @@ -1,13 +1,18 @@ @using Aspire.Dashboard.Model @using Aspire.Dashboard.Resources +@{ + // Create view model every render. The resource state may have changed. + var vm = ResourceStateViewModel.GetStateViewModel(Resource, Loc); +} +
- - @_viewModel.Text + @vm.Text Loc { get; init; } - - private ResourceStateViewModel _viewModel = default!; - - protected override void OnInitialized() - { - _viewModel = ResourceStateViewModel.GetStateViewModel(Resource, Loc); - } } From ead2b132ccac0c985b9847e56bf9d4f96c7b6a2a Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Thu, 17 Oct 2024 10:57:39 -0400 Subject: [PATCH 22/24] check at beginning of update health status if health status is not already running --- .../ApplicationModel/ResourceNotificationService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs b/src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs index 968c4b96277..9732b0ee3a2 100644 --- a/src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs +++ b/src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs @@ -396,7 +396,7 @@ public Task PublishUpdateAsync(IResource resource, string resourceId, Func(out _) && previousState.State?.Text == KnownResourceStates.Running) + if (previousState.HealthStatus is not HealthStatus.Healthy && !resource.TryGetAnnotationsIncludingAncestorsOfType(out _) && previousState.State?.Text == KnownResourceStates.Running) { return previousState with { HealthStatus = HealthStatus.Healthy }; } From 3eacb74300f1a6e84b706e937d65130ea6a3ed77 Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Thu, 17 Oct 2024 11:44:30 -0400 Subject: [PATCH 23/24] Set state to running (unhealthy) on empty health status --- .../Model/ResourceStateViewModel.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Aspire.Dashboard/Model/ResourceStateViewModel.cs b/src/Aspire.Dashboard/Model/ResourceStateViewModel.cs index 40a7c3fa9ed..0508a2c49fc 100644 --- a/src/Aspire.Dashboard/Model/ResourceStateViewModel.cs +++ b/src/Aspire.Dashboard/Model/ResourceStateViewModel.cs @@ -62,10 +62,16 @@ internal static ResourceStateViewModel GetStateViewModel(ResourceViewModel resou icon = new Icons.Filled.Size16.Circle(); color = Color.Info; } - else if (resource.HealthStatus is not HealthStatus.Healthy and not null) + else if (resource.HealthStatus is null) { + // If we are waiting for a health check, show a progress bar and consider the resource unhealthy icon = new Icons.Filled.Size16.CheckmarkCircleWarning(); - color = Color.Neutral; + color = Color.Warning; + } + else if (resource.HealthStatus is not HealthStatus.Healthy) + { + icon = new Icons.Filled.Size16.CheckmarkCircleWarning(); + color = Color.Warning; } else if (!string.IsNullOrEmpty(resource.StateStyle)) { @@ -95,7 +101,7 @@ internal static ResourceStateViewModel GetStateViewModel(ResourceViewModel resou /// /// This is a static method so it can be called at the level of the parent column. /// - internal static string? GetResourceStateTooltip(ResourceViewModel resource, IStringLocalizer loc) + internal static string GetResourceStateTooltip(ResourceViewModel resource, IStringLocalizer loc) { if (resource.IsStopped()) { @@ -110,7 +116,7 @@ internal static ResourceStateViewModel GetStateViewModel(ResourceViewModel resou return loc.GetString(nameof(Columns.StateColumnResourceExited), resource.ResourceType); } } - else if (resource is { KnownState: KnownResourceState.Running, HealthStatus: not HealthStatus.Healthy and not null }) + else if (resource is { KnownState: KnownResourceState.Running, HealthStatus: not HealthStatus.Healthy }) { // Resource is running but not healthy (initializing). return loc[nameof(Columns.RunningAndUnhealthyResourceStateToolTip)]; @@ -130,7 +136,7 @@ private static string GetStateText(ResourceViewModel resource, IStringLocalizer< return resource switch { { State: null or "" } => loc[Columns.UnknownStateLabel], - { KnownState: KnownResourceState.Running, HealthStatus: not HealthStatus.Healthy and not null } => $"{resource.State.Humanize()} ({(resource.HealthStatus ?? HealthStatus.Unhealthy).Humanize()})", + { KnownState: KnownResourceState.Running, HealthStatus: not HealthStatus.Healthy } => $"{resource.State.Humanize()} ({(resource.HealthStatus ?? HealthStatus.Unhealthy).Humanize()})", _ => resource.State.Humanize() }; } From 3f2396470b05698ca3c0d15cd634bf157bb8d34e Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Thu, 17 Oct 2024 12:07:09 -0400 Subject: [PATCH 24/24] Update test expectations --- .../Model/ResourceStateViewModelTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Aspire.Dashboard.Tests/Model/ResourceStateViewModelTests.cs b/tests/Aspire.Dashboard.Tests/Model/ResourceStateViewModelTests.cs index 07d15596a85..57a616f7f8b 100644 --- a/tests/Aspire.Dashboard.Tests/Model/ResourceStateViewModelTests.cs +++ b/tests/Aspire.Dashboard.Tests/Model/ResourceStateViewModelTests.cs @@ -37,15 +37,15 @@ public class ResourceStateViewModelTests /* expected output */ "Running", "CheckmarkCircle", Color.Success, "Running")] [InlineData( /* state */ KnownResourceState.Running, null, null, null, - /* expected output */ "Running", "CheckmarkCircle", Color.Success, "Running")] + /* expected output */ $"Localized:{nameof(Columns.RunningAndUnhealthyResourceStateToolTip)}", "CheckmarkCircleWarning", Color.Warning, "Running (Unhealthy)")] [InlineData( /* state */ KnownResourceState.Running, null, "Unhealthy", null, - /* expected output */ $"Localized:{nameof(Columns.RunningAndUnhealthyResourceStateToolTip)}", "CheckmarkCircleWarning", Color.Neutral, "Running (Unhealthy)")] + /* expected output */ $"Localized:{nameof(Columns.RunningAndUnhealthyResourceStateToolTip)}", "CheckmarkCircleWarning", Color.Warning, "Running (Unhealthy)")] [InlineData( - /* state */ KnownResourceState.Running, null, null, "warning", + /* state */ KnownResourceState.Running, null, "Healthy", "warning", /* expected output */ "Running", "Warning", Color.Warning, "Running")] [InlineData( - /* state */ KnownResourceState.Running, null, null, "NOT_A_VALID_STATE_STYLE", + /* state */ KnownResourceState.Running, null, "Healthy", "NOT_A_VALID_STATE_STYLE", /* expected output */ "Running", "Circle", Color.Neutral, "Running")] [InlineData( /* state */ KnownResourceState.RuntimeUnhealthy, null, null, null,