Skip to content

Commit 2312891

Browse files
committed
Allow collapsing operations in the trace view
1 parent f7bd7c2 commit 2312891

4 files changed

Lines changed: 67 additions & 19 deletions

File tree

src/Aspire.Dashboard/Components/Pages/TraceDetail.razor

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,21 +56,29 @@
5656
<FluentDataGrid Class="trace-view-grid" ResizableColumns="true" ItemsProvider="@GetData" TGridItem="SpanWaterfallViewModel" RowClass="@GetRowClass" GridTemplateColumns="2fr 6fr">
5757
<TemplateColumn Title="Name">
5858
<div class="col-long-content" title="@context.GetTooltip()" @onclick="() => OnShowProperties(context)">
59-
<span class="span-name-container" style="@($"margin-left: {(context.Depth - 1) * 15}px; border-left-color: {ColorGenerator.Instance.GetColorHexByKey(GetResourceName(context.Span.Source))};")">
60-
@if (context.IsError)
61-
{
62-
<FluentIcon Icon="Icons.Filled.Size12.ErrorCircle" Color="Color.Error" Class="trace-tag-icon" />
63-
}
64-
@GetResourceName(context.Span.Source)
65-
@if (context.HasUninstrumentedPeer)
66-
{
67-
<span class="uninstrumented-peer">
68-
<FluentIcon Style="@($"fill: {ColorGenerator.Instance.GetColorHexByKey(context.UninstrumentedPeer)};")" Icon="Icons.Filled.Size16.ArrowCircleRight" Class="uninstrumented-peer-icon" />
69-
@context.UninstrumentedPeer
70-
</span>
71-
}
72-
<span class="span-row-name">@context.GetDisplaySummary()</span>
73-
</span>
59+
<div style="@($"margin-left: {(context.Depth - 1) * 15}px;")">
60+
<span class="span-collapse-symbol" @onclick="() => OnToggleCollapse(context)" @onclick:stopPropagation="true">
61+
@if (context.Children.Count > 0)
62+
{
63+
@(context.IsCollapsed ? '+' : '-')
64+
}
65+
</span>
66+
<span class="span-name-container" style="@($"border-left-color: {ColorGenerator.Instance.GetColorHexByKey(GetResourceName(context.Span.Source))};")">
67+
@if (context.IsError)
68+
{
69+
<FluentIcon Icon="Icons.Filled.Size12.ErrorCircle" Color="Color.Error" Class="trace-tag-icon" />
70+
}
71+
@GetResourceName(context.Span.Source)
72+
@if (context.HasUninstrumentedPeer)
73+
{
74+
<span class="uninstrumented-peer">
75+
<FluentIcon Style="@($"fill: {ColorGenerator.Instance.GetColorHexByKey(context.UninstrumentedPeer)};")" Icon="Icons.Filled.Size16.ArrowCircleRight" Class="uninstrumented-peer-icon" />
76+
@context.UninstrumentedPeer
77+
</span>
78+
}
79+
<span class="span-row-name">@context.GetDisplaySummary()</span>
80+
</span>
81+
</div>
7482
</div>
7583
</TemplateColumn>
7684
<TemplateColumn>

src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,10 @@ private ValueTask<GridItemsProviderResult<SpanWaterfallViewModel>> GetData(GridI
4949
{
5050
Debug.Assert(_spanWaterfallViewModels != null);
5151

52+
var visibleSpanWaterfallViewModels = _spanWaterfallViewModels.Where(viewModel => !viewModel.IsHidden).ToList();
5253
return ValueTask.FromResult(new GridItemsProviderResult<SpanWaterfallViewModel>
5354
{
54-
Items = _spanWaterfallViewModels,
55+
Items = visibleSpanWaterfallViewModels,
5556
TotalItemCount = _spanWaterfallViewModels.Count
5657
});
5758
}
@@ -72,15 +73,19 @@ private static List<SpanWaterfallViewModel> CreateSpanWaterfallViewModels(OtlpTr
7273

7374
return orderedSpans;
7475

75-
static void AddSelfAndChildren(List<SpanWaterfallViewModel> orderedSpans, OtlpSpan span, int depth, IEnumerable<IOutgoingPeerResolver> outgoingPeerResolvers, Func<OtlpSpan, int, IEnumerable<IOutgoingPeerResolver>, SpanWaterfallViewModel> createViewModel)
76+
static SpanWaterfallViewModel AddSelfAndChildren(List<SpanWaterfallViewModel> orderedSpans, OtlpSpan span, int depth, IEnumerable<IOutgoingPeerResolver> outgoingPeerResolvers, Func<OtlpSpan, int, IEnumerable<IOutgoingPeerResolver>, SpanWaterfallViewModel> createViewModel)
7677
{
77-
orderedSpans.Add(createViewModel(span, depth, outgoingPeerResolvers));
78+
var viewModel = createViewModel(span, depth, outgoingPeerResolvers);
79+
orderedSpans.Add(viewModel);
7880
depth++;
7981

8082
foreach (var child in span.GetChildSpans().OrderBy(s => s.StartTime))
8183
{
82-
AddSelfAndChildren(orderedSpans, child, depth, outgoingPeerResolvers, createViewModel);
84+
var childViewModel = AddSelfAndChildren(orderedSpans, child, depth, outgoingPeerResolvers, createViewModel);
85+
viewModel.Children.Add(childViewModel);
8386
}
87+
88+
return viewModel;
8489
}
8590

8691
static SpanWaterfallViewModel CreateViewModel(OtlpSpan span, int depth, IEnumerable<IOutgoingPeerResolver> outgoingPeerResolvers)
@@ -103,6 +108,7 @@ static SpanWaterfallViewModel CreateViewModel(OtlpSpan span, int depth, IEnumera
103108

104109
var viewModel = new SpanWaterfallViewModel
105110
{
111+
Children = [],
106112
Span = span,
107113
LeftOffset = leftOffset,
108114
Width = width,
@@ -181,6 +187,11 @@ private string GetRowClass(SpanWaterfallViewModel viewModel)
181187

182188
public SpanDetailsViewModel? SelectedSpan { get; set; }
183189

190+
private static void OnToggleCollapse(SpanWaterfallViewModel viewModel)
191+
{
192+
viewModel.IsCollapsed = !viewModel.IsCollapsed;
193+
}
194+
184195
private void OnShowProperties(SpanWaterfallViewModel viewModel)
185196
{
186197
if (SelectedSpan?.Span == viewModel.Span)

src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,19 @@
8686
grid-row: 1;
8787
}
8888

89+
::deep .span-collapse-symbol {
90+
display: inline-block;
91+
width: 20px;
92+
text-align: center;
93+
padding: 0px 5px;
94+
line-height: 28px;
95+
user-select: none;
96+
}
97+
8998
::deep .span-name-container {
9099
border-left-width: 5px;
91100
border-left-style: solid;
101+
margin-left: -3px;
92102
padding-left: 5px;
93103
line-height: 28px;
94104
}

src/Aspire.Dashboard/Model/Otlp/SpanWaterfallViewModel.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,29 @@ namespace Aspire.Dashboard.Model.Otlp;
99

1010
public sealed class SpanWaterfallViewModel
1111
{
12+
public required List<SpanWaterfallViewModel> Children { get; init; }
1213
public required OtlpSpan Span { get; init; }
1314
public required double LeftOffset { get; init; }
1415
public required double Width { get; init; }
1516
public required int Depth { get; init; }
1617
public required bool LabelIsRight { get; init; }
1718
public required string? UninstrumentedPeer { get; init; }
19+
public bool IsHidden { get; private set; }
1820
[MemberNotNullWhen(true, nameof(UninstrumentedPeer))]
1921
public bool HasUninstrumentedPeer => !string.IsNullOrEmpty(UninstrumentedPeer);
2022
public bool IsError => Span.Status == OtlpSpanStatusCode.Error;
2123

24+
private bool _isCollapsed;
25+
public bool IsCollapsed
26+
{
27+
get => _isCollapsed;
28+
set
29+
{
30+
_isCollapsed = value;
31+
UpdateHidden();
32+
}
33+
}
34+
2235
public string GetTooltip()
2336
{
2437
var tooltip = $"{Span.Source.ApplicationName}: {GetDisplaySummary()}";
@@ -86,4 +99,10 @@ public string GetDisplaySummary()
8699

87100
return Span.Name;
88101
}
102+
103+
private void UpdateHidden(bool isParentCollapsed = false)
104+
{
105+
IsHidden = isParentCollapsed;
106+
Children.ForEach(child => child.UpdateHidden(isParentCollapsed || IsCollapsed));
107+
}
89108
}

0 commit comments

Comments
 (0)