Skip to content

Commit 26dce9b

Browse files
authored
Only display trace menu option for uninstrumented peer resources (#9030)
* Only display trace menu option for uninstrumented peer resources * Tests
1 parent 957514c commit 26dce9b

File tree

4 files changed

+238
-64
lines changed

4 files changed

+238
-64
lines changed

src/Aspire.Dashboard/Model/ResourceMenuItems.cs

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -56,21 +56,26 @@ public static void AddMenuItems(
5656
}
5757

5858
// Show telemetry menu items if there is telemetry for the resource.
59-
var hasTelemetryApplication = telemetryRepository.GetApplicationByCompositeName(resource.Name) != null;
60-
if (hasTelemetryApplication)
59+
var telemetryApplication = telemetryRepository.GetApplicationByCompositeName(resource.Name);
60+
if (telemetryApplication != null)
6161
{
6262
menuItems.Add(new MenuButtonItem { IsDivider = true });
63-
menuItems.Add(new MenuButtonItem
63+
64+
if (!telemetryApplication.UninstrumentedPeer)
6465
{
65-
Text = loc[nameof(Resources.Resources.ResourceActionStructuredLogsText)],
66-
Tooltip = loc[nameof(Resources.Resources.ResourceActionStructuredLogsText)],
67-
Icon = s_structuredLogsIcon,
68-
OnClick = () =>
66+
menuItems.Add(new MenuButtonItem
6967
{
70-
navigationManager.NavigateTo(DashboardUrls.StructuredLogsUrl(resource: getResourceName(resource)));
71-
return Task.CompletedTask;
72-
}
73-
});
68+
Text = loc[nameof(Resources.Resources.ResourceActionStructuredLogsText)],
69+
Tooltip = loc[nameof(Resources.Resources.ResourceActionStructuredLogsText)],
70+
Icon = s_structuredLogsIcon,
71+
OnClick = () =>
72+
{
73+
navigationManager.NavigateTo(DashboardUrls.StructuredLogsUrl(resource: getResourceName(resource)));
74+
return Task.CompletedTask;
75+
}
76+
});
77+
}
78+
7479
menuItems.Add(new MenuButtonItem
7580
{
7681
Text = loc[nameof(Resources.Resources.ResourceActionTracesText)],
@@ -82,17 +87,21 @@ public static void AddMenuItems(
8287
return Task.CompletedTask;
8388
}
8489
});
85-
menuItems.Add(new MenuButtonItem
90+
91+
if (!telemetryApplication.UninstrumentedPeer)
8692
{
87-
Text = loc[nameof(Resources.Resources.ResourceActionMetricsText)],
88-
Tooltip = loc[nameof(Resources.Resources.ResourceActionMetricsText)],
89-
Icon = s_metricsIcon,
90-
OnClick = () =>
93+
menuItems.Add(new MenuButtonItem
9194
{
92-
navigationManager.NavigateTo(DashboardUrls.MetricsUrl(resource: getResourceName(resource)));
93-
return Task.CompletedTask;
94-
}
95-
});
95+
Text = loc[nameof(Resources.Resources.ResourceActionMetricsText)],
96+
Tooltip = loc[nameof(Resources.Resources.ResourceActionMetricsText)],
97+
Icon = s_metricsIcon,
98+
OnClick = () =>
99+
{
100+
navigationManager.NavigateTo(DashboardUrls.MetricsUrl(resource: getResourceName(resource)));
101+
return Task.CompletedTask;
102+
}
103+
});
104+
}
96105
}
97106

98107
var menuCommands = resource.Commands
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Aspire.Dashboard.Model;
5+
using Aspire.Dashboard.Otlp.Model;
6+
using Aspire.Dashboard.Tests.TelemetryRepositoryTests;
7+
using Aspire.Tests.Shared.DashboardModel;
8+
using Aspire.Tests.Shared.Telemetry;
9+
using Google.Protobuf.Collections;
10+
using Microsoft.AspNetCore.Components;
11+
using OpenTelemetry.Proto.Trace.V1;
12+
using Xunit;
13+
14+
namespace Aspire.Dashboard.Tests.Model;
15+
16+
public sealed class ResourceMenuItemsTests
17+
{
18+
private static readonly DateTime s_testTime = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
19+
20+
[Fact]
21+
public void AddMenuItems_NoTelemetry_NoTelemetryItems()
22+
{
23+
// Arrange
24+
var resource = ModelTestHelpers.CreateResource();
25+
var telemetryRespository = TelemetryTestHelpers.CreateRepository();
26+
27+
// Act
28+
var menuItems = new List<MenuButtonItem>();
29+
ResourceMenuItems.AddMenuItems(
30+
menuItems,
31+
openingMenuButtonId: null,
32+
resource,
33+
new TestNavigationManager(),
34+
telemetryRespository,
35+
r => r.Name,
36+
new TestStringLocalizer<Resources.ControlsStrings>(),
37+
new TestStringLocalizer<Resources.Resources>(),
38+
_ => Task.CompletedTask,
39+
_ => Task.CompletedTask,
40+
(_, _) => false,
41+
true,
42+
true);
43+
44+
// Assert
45+
Assert.Collection(menuItems,
46+
e => Assert.Equal("Localized:ActionViewDetailsText", e.Text),
47+
e => Assert.Equal("Localized:ResourceActionConsoleLogsText", e.Text));
48+
}
49+
50+
[Fact]
51+
public void AddMenuItems_UninstrumentedPeer_TraceItem()
52+
{
53+
// Arrange
54+
var resource = ModelTestHelpers.CreateResource(appName: "test-abc");
55+
var outgoingPeerResolver = new TestOutgoingPeerResolver(onResolve: attributes => (resource.Name, resource));
56+
var repository = TelemetryTestHelpers.CreateRepository(outgoingPeerResolvers: [outgoingPeerResolver]);
57+
var addContext = new AddContext();
58+
repository.AddTraces(addContext, new RepeatedField<ResourceSpans>()
59+
{
60+
new ResourceSpans
61+
{
62+
Resource = TelemetryTestHelpers.CreateResource(name: "source", instanceId: "abc"),
63+
ScopeSpans =
64+
{
65+
new ScopeSpans
66+
{
67+
Scope = TelemetryTestHelpers.CreateScope(),
68+
Spans =
69+
{
70+
TelemetryTestHelpers.CreateSpan(traceId: "1", spanId: "1-1", startTime: s_testTime.AddMinutes(1), endTime: s_testTime.AddMinutes(10), attributes: [KeyValuePair.Create(OtlpSpan.PeerServiceAttributeKey, "value-1")], kind: Span.Types.SpanKind.Client),
71+
TelemetryTestHelpers.CreateSpan(traceId: "1", spanId: "1-2", startTime: s_testTime.AddMinutes(5), endTime: s_testTime.AddMinutes(10), parentSpanId: "1-1", attributes: [KeyValuePair.Create(OtlpSpan.PeerServiceAttributeKey, "value-2")], kind: Span.Types.SpanKind.Client)
72+
}
73+
}
74+
}
75+
}
76+
});
77+
78+
// Act
79+
var menuItems = new List<MenuButtonItem>();
80+
ResourceMenuItems.AddMenuItems(
81+
menuItems,
82+
openingMenuButtonId: null,
83+
resource,
84+
new TestNavigationManager(),
85+
repository,
86+
r => r.Name,
87+
new TestStringLocalizer<Resources.ControlsStrings>(),
88+
new TestStringLocalizer<Resources.Resources>(),
89+
_ => Task.CompletedTask,
90+
_ => Task.CompletedTask,
91+
(_, _) => false,
92+
true,
93+
true);
94+
95+
// Assert
96+
Assert.Collection(menuItems,
97+
e => Assert.Equal("Localized:ActionViewDetailsText", e.Text),
98+
e => Assert.Equal("Localized:ResourceActionConsoleLogsText", e.Text),
99+
e => Assert.True(e.IsDivider),
100+
e => Assert.Equal("Localized:ResourceActionTracesText", e.Text));
101+
}
102+
103+
[Fact]
104+
public void AddMenuItems_HasTelemetry_TelemetryItems()
105+
{
106+
// Arrange
107+
var resource = ModelTestHelpers.CreateResource(appName: "test-abc");
108+
var repository = TelemetryTestHelpers.CreateRepository();
109+
var addContext = new AddContext();
110+
repository.AddTraces(addContext, new RepeatedField<ResourceSpans>()
111+
{
112+
new ResourceSpans
113+
{
114+
Resource = TelemetryTestHelpers.CreateResource(name: "test", instanceId: "abc"),
115+
ScopeSpans =
116+
{
117+
new ScopeSpans
118+
{
119+
Scope = TelemetryTestHelpers.CreateScope(),
120+
Spans =
121+
{
122+
TelemetryTestHelpers.CreateSpan(traceId: "1", spanId: "1-1", startTime: s_testTime.AddMinutes(1), endTime: s_testTime.AddMinutes(10))
123+
}
124+
}
125+
}
126+
}
127+
});
128+
129+
// Act
130+
var menuItems = new List<MenuButtonItem>();
131+
ResourceMenuItems.AddMenuItems(
132+
menuItems,
133+
openingMenuButtonId: null,
134+
resource,
135+
new TestNavigationManager(),
136+
repository,
137+
r => r.Name,
138+
new TestStringLocalizer<Resources.ControlsStrings>(),
139+
new TestStringLocalizer<Resources.Resources>(),
140+
_ => Task.CompletedTask,
141+
_ => Task.CompletedTask,
142+
(_, _) => false,
143+
true,
144+
true);
145+
146+
// Assert
147+
Assert.Collection(menuItems,
148+
e => Assert.Equal("Localized:ActionViewDetailsText", e.Text),
149+
e => Assert.Equal("Localized:ResourceActionConsoleLogsText", e.Text),
150+
e => Assert.True(e.IsDivider),
151+
e => Assert.Equal("Localized:ResourceActionStructuredLogsText", e.Text),
152+
e => Assert.Equal("Localized:ResourceActionTracesText", e.Text),
153+
e => Assert.Equal("Localized:ResourceActionMetricsText", e.Text));
154+
}
155+
156+
private sealed class TestNavigationManager : NavigationManager
157+
{
158+
}
159+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Aspire.Dashboard.Model;
5+
using Aspire.Tests.Shared.DashboardModel;
6+
7+
namespace Aspire.Dashboard.Tests.TelemetryRepositoryTests;
8+
9+
public sealed class TestOutgoingPeerResolver : IOutgoingPeerResolver, IDisposable
10+
{
11+
private readonly Func<KeyValuePair<string, string>[], (string? Name, ResourceViewModel? Resource)>? _onResolve;
12+
private readonly List<Func<Task>> _callbacks;
13+
14+
public TestOutgoingPeerResolver(Func<KeyValuePair<string, string>[], (string? Name, ResourceViewModel? Resource)>? onResolve = null)
15+
{
16+
_onResolve = onResolve;
17+
_callbacks = new();
18+
}
19+
20+
public void Dispose()
21+
{
22+
}
23+
24+
public IDisposable OnPeerChanges(Func<Task> callback)
25+
{
26+
_callbacks.Add(callback);
27+
return this;
28+
}
29+
30+
public async Task InvokePeerChanges()
31+
{
32+
foreach (var callback in _callbacks)
33+
{
34+
await callback();
35+
}
36+
}
37+
38+
public bool TryResolvePeer(KeyValuePair<string, string>[] attributes, out string? name, out ResourceViewModel? matchedResourced)
39+
{
40+
if (_onResolve != null)
41+
{
42+
(name, matchedResourced) = _onResolve(attributes);
43+
return (name != null);
44+
}
45+
46+
name = "TestPeer";
47+
matchedResourced = ModelTestHelpers.CreateResource(appName: "TestPeer");
48+
return true;
49+
}
50+
}

tests/Aspire.Dashboard.Tests/TelemetryRepositoryTests/TraceTests.cs

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System.Globalization;
55
using System.Text;
6-
using Aspire.Dashboard.Model;
76
using Aspire.Dashboard.Model.Otlp;
87
using Aspire.Dashboard.Otlp.Model;
98
using Aspire.Dashboard.Otlp.Storage;
@@ -1929,49 +1928,6 @@ public void RemoveTraces_SelectedResource_SpansFromDifferentTrace()
19291928
});
19301929
}
19311930

1932-
private sealed class TestOutgoingPeerResolver : IOutgoingPeerResolver, IDisposable
1933-
{
1934-
private readonly Func<KeyValuePair<string, string>[], (string? Name, ResourceViewModel? Resource)>? _onResolve;
1935-
private readonly List<Func<Task>> _callbacks;
1936-
1937-
public TestOutgoingPeerResolver(Func<KeyValuePair<string, string>[], (string? Name, ResourceViewModel? Resource)>? onResolve = null)
1938-
{
1939-
_onResolve = onResolve;
1940-
_callbacks = new();
1941-
}
1942-
1943-
public void Dispose()
1944-
{
1945-
}
1946-
1947-
public IDisposable OnPeerChanges(Func<Task> callback)
1948-
{
1949-
_callbacks.Add(callback);
1950-
return this;
1951-
}
1952-
1953-
public async Task InvokePeerChanges()
1954-
{
1955-
foreach (var callback in _callbacks)
1956-
{
1957-
await callback();
1958-
}
1959-
}
1960-
1961-
public bool TryResolvePeer(KeyValuePair<string, string>[] attributes, out string? name, out ResourceViewModel? matchedResourced)
1962-
{
1963-
if (_onResolve != null)
1964-
{
1965-
(name, matchedResourced) = _onResolve(attributes);
1966-
return (name != null);
1967-
}
1968-
1969-
name = "TestPeer";
1970-
matchedResourced = ModelTestHelpers.CreateResource(appName: "TestPeer");
1971-
return true;
1972-
}
1973-
}
1974-
19751931
[Fact]
19761932
public void AddTraces_HaveUninstrumentedPeers()
19771933
{

0 commit comments

Comments
 (0)