diff --git a/src/Aspire.Dashboard/Components/Controls/TreeMetricSelector.razor b/src/Aspire.Dashboard/Components/Controls/TreeMetricSelector.razor index b2536c63946..8d7ef9f191d 100644 --- a/src/Aspire.Dashboard/Components/Controls/TreeMetricSelector.razor +++ b/src/Aspire.Dashboard/Components/Controls/TreeMetricSelector.razor @@ -2,7 +2,7 @@ @inject IStringLocalizer Loc -@if (_instruments is null) +@if (PageViewModel.Instruments is null) { return; } @@ -14,7 +14,7 @@ - @foreach (var meterGroup in _instruments.GroupBy(i => i.Parent).OrderBy(g => g.Key.MeterName)) + @foreach (var meterGroup in PageViewModel.Instruments.GroupBy(i => i.Parent).OrderBy(g => g.Key.MeterName)) { @foreach (var instrument in meterGroup.OrderBy(i => i.Name)) diff --git a/src/Aspire.Dashboard/Components/Controls/TreeMetricSelector.razor.cs b/src/Aspire.Dashboard/Components/Controls/TreeMetricSelector.razor.cs index 3430f23352b..a3bb494873e 100644 --- a/src/Aspire.Dashboard/Components/Controls/TreeMetricSelector.razor.cs +++ b/src/Aspire.Dashboard/Components/Controls/TreeMetricSelector.razor.cs @@ -1,8 +1,7 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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.Components.Pages; -using Aspire.Dashboard.Otlp.Model; using Aspire.Dashboard.Otlp.Storage; using Microsoft.AspNetCore.Components; @@ -21,27 +20,4 @@ public partial class TreeMetricSelector [Inject] public required TelemetryRepository TelemetryRepository { get; init; } - - private List? _instruments; - - protected override void OnInitialized() - { - OnResourceChanged(); - } - - public void OnResourceChanged() - { - // instruments may be out of sync if we have updated the application but not yet closed the filter panel - // this is because we have not updated the URL, which would close the details panel - // because of this, we should always compute the instruments from the repository - var selectedInstance = PageViewModel.SelectedApplication.Id?.GetApplicationKey(); - - if (selectedInstance is null) - { - return; - } - - _instruments = TelemetryRepository.GetInstrumentsSummaries(selectedInstance.Value); - StateHasChanged(); - } } diff --git a/src/Aspire.Dashboard/Components/Pages/Metrics.razor.cs b/src/Aspire.Dashboard/Components/Pages/Metrics.razor.cs index ce75c3a7b8d..a4fb31a0215 100644 --- a/src/Aspire.Dashboard/Components/Pages/Metrics.razor.cs +++ b/src/Aspire.Dashboard/Components/Pages/Metrics.razor.cs @@ -170,7 +170,6 @@ private async Task HandleSelectedApplicationChangedAsync() } await this.AfterViewModelChangedAsync(_contentLayout, waitToApplyMobileChange: true); - _treeMetricSelector?.OnResourceChanged(); } private bool ShouldClearSelectedMetrics(List instruments) diff --git a/tests/Aspire.Dashboard.Components.Tests/Pages/MetricsTests.cs b/tests/Aspire.Dashboard.Components.Tests/Pages/MetricsTests.cs index 027d90a19f8..5e3c0d1cc80 100644 --- a/tests/Aspire.Dashboard.Components.Tests/Pages/MetricsTests.cs +++ b/tests/Aspire.Dashboard.Components.Tests/Pages/MetricsTests.cs @@ -13,6 +13,7 @@ using Google.Protobuf.Collections; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; +using Microsoft.FluentUI.AspNetCore.Components; using OpenTelemetry.Proto.Metrics.V1; using Xunit; using static Aspire.Dashboard.Components.Pages.Metrics; @@ -119,6 +120,94 @@ public void InitialLoad_HasSessionState_RedirectUsingState() Assert.Equal(MetricViewKind.Table.ToString(), query["view"]); } + [Fact] + public void MetricsTree_MetricsAdded_TreeUpdated() + { + // Arrange + MetricsSetupHelpers.SetupMetricsPage(this); + + var telemetryRepository = Services.GetRequiredService(); + telemetryRepository.AddMetrics(new AddContext(), new RepeatedField + { + new ResourceMetrics + { + Resource = CreateResource(name: "TestApp"), + ScopeMetrics = + { + new ScopeMetrics + { + Scope = CreateScope(name: "test-meter1"), + Metrics = + { + CreateSumMetric(metricName: "test-instrument1-1", startTime: s_testTime.AddMinutes(1)) + } + } + } + } + }); + + // Act 1 + // Initial page load + var cut = RenderComponent(builder => + { + builder.AddCascadingValue(new ViewportInformation(IsDesktop: true, IsUltraLowHeight: false, IsUltraLowWidth: false)); + builder.Add(m => m.ApplicationName, "TestApp"); + }); + + // Assert 2 + cut.WaitForState(() => cut.Instance.PageViewModel.Instruments?.Count == 1); + + var tree1 = cut.FindComponent(); + var items1 = tree1.FindComponents(); + + foreach (var instrument in cut.Instance.PageViewModel.Instruments!) + { + Assert.Single(items1.Where(i => i.Instance.Data as OtlpInstrumentSummary == instrument)); + Assert.Single(items1.Where(i => i.Instance.Data as OtlpMeter == instrument.Parent)); + } + + // Act 2 + // New instruments added + telemetryRepository.AddMetrics(new AddContext(), new RepeatedField + { + new ResourceMetrics + { + Resource = CreateResource(name: "TestApp"), + ScopeMetrics = + { + new ScopeMetrics + { + Scope = CreateScope(name: "test-meter1"), + Metrics = + { + CreateSumMetric(metricName: "test-instrument1-2", startTime: s_testTime.AddMinutes(1)) + } + }, + new ScopeMetrics + { + Scope = CreateScope(name: "test-meter2"), + Metrics = + { + CreateSumMetric(metricName: "test-instrument2-1", startTime: s_testTime.AddMinutes(1)) + } + } + } + } + }); + + // Assert 2 + cut.WaitForState(() => cut.Instance.PageViewModel.Instruments?.Count == 3); + + var tree2 = cut.FindComponent(); + var items2 = tree2.FindComponents(); + + foreach (var instrument in cut.Instance.PageViewModel.Instruments!) + { + Assert.Single(items2.Where(i => i.Instance.Data as OtlpInstrumentSummary == instrument)); + Assert.Single(items2.Where(i => i.Instance.Data as OtlpMeter == instrument.Parent)); + } + } + private void ChangeResourceAndAssertInstrument(string app1InstrumentName, string app2InstrumentName, string? expectedMeterNameAfterChange, string? expectedInstrumentNameAfterChange) { // Arrange