Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

@inject IStringLocalizer<Metrics> Loc

@if (_instruments is null)
@if (PageViewModel.Instruments is null)
{
return;
}
Expand All @@ -14,7 +14,7 @@

<FluentTreeView Id="metric-selector" Class="metrics-tree" @bind-CurrentSelected="@PageViewModel.SelectedTreeItem" @bind-CurrentSelected:after="HandleSelectedTreeItemChangedAsync">
<ChildContent>
@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))
{
<FluentTreeItem @key="meterGroup.Key" Text="@meterGroup.Key.MeterName" Data="@meterGroup.Key" title="@meterGroup.Key.MeterName" InitiallyExpanded="true" InitiallySelected="@(PageViewModel.SelectedInstrument == null && meterGroup.Key.MeterName == PageViewModel.SelectedMeter?.MeterName)">
@foreach (var instrument in meterGroup.OrderBy(i => i.Name))
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -21,27 +20,4 @@ public partial class TreeMetricSelector

[Inject]
public required TelemetryRepository TelemetryRepository { get; init; }

private List<OtlpInstrumentSummary>? _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();
}
}
1 change: 0 additions & 1 deletion src/Aspire.Dashboard/Components/Pages/Metrics.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,6 @@ private async Task HandleSelectedApplicationChangedAsync()
}

await this.AfterViewModelChangedAsync(_contentLayout, waitToApplyMobileChange: true);
_treeMetricSelector?.OnResourceChanged();
}

private bool ShouldClearSelectedMetrics(List<OtlpInstrumentSummary> instruments)
Expand Down
89 changes: 89 additions & 0 deletions tests/Aspire.Dashboard.Components.Tests/Pages/MetricsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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>();
telemetryRepository.AddMetrics(new AddContext(), new RepeatedField<ResourceMetrics>
{
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<Metrics>(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<FluentTreeView>();
var items1 = tree1.FindComponents<FluentTreeItem>();

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<ResourceMetrics>
{
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<FluentTreeView>();
var items2 = tree2.FindComponents<FluentTreeItem>();

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
Expand Down