Skip to content

Commit

Permalink
Ensure grid item keys are unique (#6241)
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesNK authored Oct 16, 2024
1 parent d73a624 commit 7d23b1d
Show file tree
Hide file tree
Showing 27 changed files with 273 additions and 127 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ public interface IPropertyGridItem
/// <summary>
/// Gets the display name of the item.
/// </summary>
string? Name { get; }
string Name { get; }

/// <summary>
/// Gets the key of the item. Must be unique.
/// </summary>
public object Key => Name;

/// <summary>
/// Gets the display value of the item.
Expand Down Expand Up @@ -91,7 +96,7 @@ public partial class PropertyGrid<TItem> where TItem : IPropertyGridItem
public IQueryable<TItem>? Items { get; set; }

[Parameter]
public Func<TItem, object?> ItemKey { get; init; } = static item => item.Name;
public Func<TItem, object?> ItemKey { get; init; } = static item => item.Key;

[Parameter]
public string GridTemplateColumns { get; set; } = "1fr 1fr";
Expand Down
7 changes: 3 additions & 4 deletions src/Aspire.Dashboard/Components/Controls/SpanDetails.razor
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
@FilteredItems.Count()
</FluentBadge>
</div>
<PropertyGrid TItem="SpanPropertyViewModel"
<PropertyGrid TItem="TelemetryPropertyViewModel"
Items="@FilteredItems"
GridTemplateColumns="1fr 2fr"
HighlightText="@_filter" />
Expand All @@ -47,7 +47,7 @@
@FilteredContextItems.Count()
</FluentBadge>
</div>
<PropertyGrid TItem="SpanPropertyViewModel"
<PropertyGrid TItem="TelemetryPropertyViewModel"
Items="@FilteredContextItems"
GridTemplateColumns="1fr 2fr"
HighlightText="@_filter" />
Expand All @@ -58,7 +58,7 @@
@FilteredResourceItems.Count()
</FluentBadge>
</div>
<PropertyGrid TItem="SpanPropertyViewModel"
<PropertyGrid TItem="TelemetryPropertyViewModel"
Items="@FilteredResourceItems"
GridTemplateColumns="1fr 2fr"
HighlightText="@_filter" />
Expand All @@ -71,7 +71,6 @@
</div>
<PropertyGrid TItem="OtlpSpanEvent"
Items="@FilteredSpanEvents"
ItemKey="@((r) => r.InternalId)"
GridTemplateColumns="1fr 2fr"
NameColumnTitle="@(Loc[nameof(ControlsStrings.TimeOffsetColumnHeader)])"
ValueColumnTitle="@(Loc[nameof(ControlsStrings.EventColumnHeader)])"
Expand Down
24 changes: 12 additions & 12 deletions src/Aspire.Dashboard/Components/Controls/SpanDetails.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Dashboard.Model;
using Aspire.Dashboard.Model.Otlp;
using Aspire.Dashboard.Otlp.Model;
using Aspire.Dashboard.Otlp.Storage;
using Aspire.Dashboard.Utils;
Expand All @@ -24,15 +25,14 @@ public partial class SpanDetails : IDisposable
[Inject]
public required TelemetryRepository TelemetryRepository { get; init; }

private IQueryable<SpanPropertyViewModel> FilteredItems =>
private IQueryable<TelemetryPropertyViewModel> FilteredItems =>
ViewModel.Properties.Where(ApplyFilter).AsQueryable();

private IQueryable<SpanPropertyViewModel> FilteredContextItems =>
_contextAttributes.Select(p => new SpanPropertyViewModel { Name = p.Key, Value = p.Value })
.Where(ApplyFilter).AsQueryable();
private IQueryable<TelemetryPropertyViewModel> FilteredContextItems =>
_contextAttributes.Where(ApplyFilter).AsQueryable();

private IQueryable<SpanPropertyViewModel> FilteredResourceItems =>
ViewModel.Span.Source.AllProperties().Select(p => new SpanPropertyViewModel { Name = p.Key, Value = p.Value })
private IQueryable<TelemetryPropertyViewModel> FilteredResourceItems =>
ViewModel.Span.Source.AllProperties().Select(p => new TelemetryPropertyViewModel { Name = p.DisplayName, Key = p.Key, Value = p.Value })
.Where(ApplyFilter).AsQueryable();

private IQueryable<OtlpSpanEvent> FilteredSpanEvents =>
Expand All @@ -49,11 +49,11 @@ public partial class SpanDetails : IDisposable
private bool _isSpanBacklinksExpanded;

private string _filter = "";
private List<KeyValuePair<string, string>> _contextAttributes = null!;
private List<TelemetryPropertyViewModel> _contextAttributes = null!;

private readonly CancellationTokenSource _cts = new();

private bool ApplyFilter(SpanPropertyViewModel vm)
private bool ApplyFilter(TelemetryPropertyViewModel vm)
{
return vm.Name.Contains(_filter, StringComparison.CurrentCultureIgnoreCase) ||
vm.Value?.Contains(_filter, StringComparison.CurrentCultureIgnoreCase) == true;
Expand All @@ -63,19 +63,19 @@ protected override void OnParametersSet()
{
_contextAttributes =
[
new KeyValuePair<string, string>("Source", ViewModel.Span.Scope.ScopeName)
new TelemetryPropertyViewModel { Name = "Source", Key = KnownSourceFields.NameField, Value = ViewModel.Span.Scope.ScopeName }
];
if (!string.IsNullOrEmpty(ViewModel.Span.Scope.Version))
{
_contextAttributes.Add(new KeyValuePair<string, string>("Version", ViewModel.Span.Scope.Version));
_contextAttributes.Add(new TelemetryPropertyViewModel { Name = "Version", Key = KnownSourceFields.VersionField, Value = ViewModel.Span.Scope.ScopeName });
}
if (!string.IsNullOrEmpty(ViewModel.Span.ParentSpanId))
{
_contextAttributes.Add(new KeyValuePair<string, string>("ParentId", ViewModel.Span.ParentSpanId));
_contextAttributes.Add(new TelemetryPropertyViewModel { Name = "ParentId", Key = KnownTraceFields.ParentIdField, Value = ViewModel.Span.ParentSpanId });
}
if (!string.IsNullOrEmpty(ViewModel.Span.TraceId))
{
_contextAttributes.Add(new KeyValuePair<string, string>("TraceId", ViewModel.Span.TraceId));
_contextAttributes.Add(new TelemetryPropertyViewModel { Name = "TraceId", Key = KnownTraceFields.TraceIdField, Value = ViewModel.Span.TraceId });
}

// Collapse details sections when they have no data.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
@FilteredItems.Count()
</FluentBadge>
</div>
<PropertyGrid TItem="LogEntryPropertyViewModel"
<PropertyGrid TItem="TelemetryPropertyViewModel"
Items="@FilteredItems"
GridTemplateColumns="1fr 2fr"
HighlightText="@_filter" />
Expand All @@ -38,7 +38,7 @@
@FilteredContextItems.Count()
</FluentBadge>
</div>
<PropertyGrid TItem="LogEntryPropertyViewModel"
<PropertyGrid TItem="TelemetryPropertyViewModel"
Items="@FilteredContextItems"
GridTemplateColumns="1fr 2fr"
HighlightText="@_filter" />
Expand All @@ -51,7 +51,7 @@
@FilteredExceptionItems.Count()
</FluentBadge>
</div>
<PropertyGrid TItem="LogEntryPropertyViewModel"
<PropertyGrid TItem="TelemetryPropertyViewModel"
Items="@FilteredExceptionItems"
GridTemplateColumns="1fr 2fr"
HighlightText="@_filter" />
Expand All @@ -63,7 +63,7 @@
@FilteredResourceItems.Count()
</FluentBadge>
</div>
<PropertyGrid TItem="LogEntryPropertyViewModel"
<PropertyGrid TItem="TelemetryPropertyViewModel"
Items="@FilteredResourceItems"
GridTemplateColumns="1fr 2fr"
HighlightText="@_filter" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Dashboard.Model;
using Aspire.Dashboard.Model.Otlp;
using Microsoft.AspNetCore.Components;

namespace Aspire.Dashboard.Components.Controls;
Expand All @@ -14,64 +15,63 @@ public partial class StructuredLogDetails
[Inject]
public required BrowserTimeProvider TimeProvider { get; init; }

private IQueryable<LogEntryPropertyViewModel> FilteredItems =>
_logEntryAttributes.Select(p => new LogEntryPropertyViewModel { Name = p.Key, Value = p.Value })
.Where(ApplyFilter).AsQueryable();
internal IQueryable<TelemetryPropertyViewModel> FilteredItems =>
_logEntryAttributes.Where(ApplyFilter).AsQueryable();

private IQueryable<LogEntryPropertyViewModel> FilteredExceptionItems =>
_exceptionAttributes.Select(p => new LogEntryPropertyViewModel { Name = p.Key, Value = p.Value })
.Where(ApplyFilter).AsQueryable();
internal IQueryable<TelemetryPropertyViewModel> FilteredExceptionItems =>
_exceptionAttributes.Where(ApplyFilter).AsQueryable();

private IQueryable<LogEntryPropertyViewModel> FilteredContextItems =>
_contextAttributes.Select(p => new LogEntryPropertyViewModel { Name = p.Key, Value = p.Value })
.Where(ApplyFilter).AsQueryable();
internal IQueryable<TelemetryPropertyViewModel> FilteredContextItems =>
_contextAttributes.Where(ApplyFilter).AsQueryable();

private IQueryable<LogEntryPropertyViewModel> FilteredResourceItems =>
ViewModel.LogEntry.ApplicationView.AllProperties().Select(p => new LogEntryPropertyViewModel { Name = p.Key, Value = p.Value })
internal IQueryable<TelemetryPropertyViewModel> FilteredResourceItems =>
ViewModel.LogEntry.ApplicationView.AllProperties().Select(p => new TelemetryPropertyViewModel { Name = p.DisplayName, Key = p.Key, Value = p.Value })
.Where(ApplyFilter).AsQueryable();

private string _filter = "";

private List<KeyValuePair<string, string>> _logEntryAttributes = null!;
private List<KeyValuePair<string, string>> _contextAttributes = null!;
private List<KeyValuePair<string, string>> _exceptionAttributes = null!;
private List<TelemetryPropertyViewModel> _logEntryAttributes = null!;
private List<TelemetryPropertyViewModel> _contextAttributes = null!;
private List<TelemetryPropertyViewModel> _exceptionAttributes = null!;

protected override void OnParametersSet()
{
// Move some attributes to separate lists, e.g. exception attributes to their own list.
// Remaining attributes are displayed along side the message.
var attributes = ViewModel.LogEntry.Attributes.ToList();
var attributes = ViewModel.LogEntry.Attributes
.Select(a => new TelemetryPropertyViewModel { Name = a.Key, Key = $"unknown-{a.Key}", Value = a.Value })
.ToList();

_contextAttributes =
[
new KeyValuePair<string, string>("Category", ViewModel.LogEntry.Scope.ScopeName)
new TelemetryPropertyViewModel { Name ="Category", Key = KnownStructuredLogFields.CategoryField, Value = ViewModel.LogEntry.Scope.ScopeName }
];
MoveAttributes(attributes, _contextAttributes, a => a.Key is "event.name" or "logrecord.event.id" or "logrecord.event.name");
MoveAttributes(attributes, _contextAttributes, a => a.Name is "event.name" or "logrecord.event.id" or "logrecord.event.name");
if (HasTelemetryBaggage(ViewModel.LogEntry.TraceId))
{
_contextAttributes.Add(new KeyValuePair<string, string>("TraceId", ViewModel.LogEntry.TraceId));
_contextAttributes.Add(new TelemetryPropertyViewModel { Name = "TraceId", Key = KnownStructuredLogFields.TraceIdField, Value = ViewModel.LogEntry.TraceId });
}
if (HasTelemetryBaggage(ViewModel.LogEntry.SpanId))
{
_contextAttributes.Add(new KeyValuePair<string, string>("SpanId", ViewModel.LogEntry.SpanId));
_contextAttributes.Add(new TelemetryPropertyViewModel { Name = "SpanId", Key = KnownStructuredLogFields.SpanIdField, Value = ViewModel.LogEntry.SpanId });
}
if (HasTelemetryBaggage(ViewModel.LogEntry.ParentId))
{
_contextAttributes.Add(new KeyValuePair<string, string>("ParentId", ViewModel.LogEntry.ParentId));
_contextAttributes.Add(new TelemetryPropertyViewModel { Name = "ParentId", Key = KnownStructuredLogFields.ParentIdField, Value = ViewModel.LogEntry.ParentId });
}

_exceptionAttributes = [];
MoveAttributes(attributes, _exceptionAttributes, a => a.Key.StartsWith("exception.", StringComparison.OrdinalIgnoreCase));
MoveAttributes(attributes, _exceptionAttributes, a => a.Name.StartsWith("exception.", StringComparison.OrdinalIgnoreCase));

_logEntryAttributes =
[
new KeyValuePair<string, string>("Level", ViewModel.LogEntry.Severity.ToString()),
new KeyValuePair<string, string>("Message", ViewModel.LogEntry.Message),
new TelemetryPropertyViewModel { Name = "Level", Key = KnownStructuredLogFields.LevelField, Value = ViewModel.LogEntry.Severity.ToString() },
new TelemetryPropertyViewModel { Name = "Message", Key = KnownStructuredLogFields.MessageField, Value = ViewModel.LogEntry.Message },
.. attributes,
];
}

private static void MoveAttributes(List<KeyValuePair<string, string>> source, List<KeyValuePair<string, string>> desintation, Func<KeyValuePair<string, string>, bool> predicate)
private static void MoveAttributes(List<TelemetryPropertyViewModel> source, List<TelemetryPropertyViewModel> desintation, Func<TelemetryPropertyViewModel, bool> predicate)
{
var insertStart = desintation.Count;
for (var i = source.Count - 1; i >= 0; i--)
Expand All @@ -84,7 +84,7 @@ private static void MoveAttributes(List<KeyValuePair<string, string>> source, Li
}
}

private bool ApplyFilter(LogEntryPropertyViewModel vm)
private bool ApplyFilter(TelemetryPropertyViewModel vm)
{
return vm.Name.Contains(_filter, StringComparison.CurrentCultureIgnoreCase) ||
vm.Value?.Contains(_filter, StringComparison.CurrentCultureIgnoreCase) == true;
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ private async Task OnShowPropertiesAsync(SpanWaterfallViewModel viewModel, strin
else
{
var entryProperties = viewModel.Span.AllProperties()
.Select(kvp => new SpanPropertyViewModel { Name = kvp.Key, Value = kvp.Value })
.Select(f => new TelemetryPropertyViewModel { Name = f.DisplayName, Key = f.Key, Value = f.Value })
.ToList();

var traceCache = new Dictionary<string, OtlpTrace>(StringComparer.Ordinal);
Expand Down
10 changes: 10 additions & 0 deletions src/Aspire.Dashboard/Model/Otlp/KnownResourceFields.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Aspire.Dashboard.Model.Otlp;

public static class KnownResourceFields
{
public const string ServiceNameField = "resource.servicename";
public const string ServiceInstanceIdField = "resource.serviceinstanceid";
}
10 changes: 10 additions & 0 deletions src/Aspire.Dashboard/Model/Otlp/KnownSourceFields.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Aspire.Dashboard.Model.Otlp;

public static class KnownSourceFields
{
public const string NameField = "source.name";
public const string VersionField = "source.version";
}
7 changes: 4 additions & 3 deletions src/Aspire.Dashboard/Model/Otlp/KnownStructuredLogFields.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// 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.

namespace Aspire.Dashboard.Model.Otlp;
Expand All @@ -7,15 +7,16 @@ public static class KnownStructuredLogFields
{
public const string MessageField = "log.message";
public const string CategoryField = "log.category";
public const string ApplicationField = "log.application";
public const string TraceIdField = "log.traceid";
public const string SpanIdField = "log.spanid";
public const string ParentIdField = "log.parentid";
public const string LevelField = "log.level";
public const string OriginalFormatField = "log.originalformat";

public static readonly List<string> AllFields = [
MessageField,
CategoryField,
ApplicationField,
KnownResourceFields.ServiceNameField,
TraceIdField,
SpanIdField,
OriginalFormatField
Expand Down
12 changes: 7 additions & 5 deletions src/Aspire.Dashboard/Model/Otlp/KnownTraceFields.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// 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.

namespace Aspire.Dashboard.Model.Otlp;
Expand All @@ -8,18 +8,20 @@ public static class KnownTraceFields
public const string NameField = "trace.name";
public const string KindField = "trace.kind";
public const string StatusField = "trace.status";
public const string ApplicationField = "trace.application";
public const string TraceIdField = "trace.traceid";
public const string SpanIdField = "trace.spanid";
public const string SourceField = "trace.source";

// Not used in search.
public const string StatusMessageField = "trace.statusmessage";
public const string ParentIdField = "trace.parentid";

public static readonly List<string> AllFields = [
NameField,
KindField,
StatusField,
ApplicationField,
KnownResourceFields.ServiceNameField,
TraceIdField,
SpanIdField,
SourceField
KnownSourceFields.NameField
];
}
5 changes: 2 additions & 3 deletions src/Aspire.Dashboard/Model/Otlp/TelemetryFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ public static string ResolveFieldName(string name)
return name switch
{
KnownStructuredLogFields.MessageField => "Message",
KnownStructuredLogFields.ApplicationField => "Application",
KnownStructuredLogFields.TraceIdField => "TraceId",
KnownStructuredLogFields.SpanIdField => "SpanId",
KnownStructuredLogFields.OriginalFormatField => "OriginalFormat",
Expand All @@ -34,9 +33,9 @@ public static string ResolveFieldName(string name)
KnownTraceFields.SpanIdField => "SpanId",
KnownTraceFields.TraceIdField => "TraceId",
KnownTraceFields.KindField => "Kind",
KnownTraceFields.ApplicationField => "Application",
KnownTraceFields.StatusField => "Status",
KnownTraceFields.SourceField => "Source",
KnownSourceFields.NameField => "Source",
KnownResourceFields.ServiceNameField => "Application",
_ => name
};
}
Expand Down
Loading

0 comments on commit 7d23b1d

Please sign in to comment.