From 5500d31659058447304d5ebc9224d1dcf305875c Mon Sep 17 00:00:00 2001 From: "g.verdier" Date: Tue, 1 Oct 2024 18:13:46 -0400 Subject: [PATCH] Fix filters when two fields have the same name --- .../Components/QueryBuilder.razor | 59 ++++++++++++++----- DotnetEventsViewer/Querying/Field.cs | 16 +++-- 2 files changed, 55 insertions(+), 20 deletions(-) diff --git a/DotnetEventsViewer/Components/QueryBuilder.razor b/DotnetEventsViewer/Components/QueryBuilder.razor index ef44576..5cba58b 100644 --- a/DotnetEventsViewer/Components/QueryBuilder.razor +++ b/DotnetEventsViewer/Components/QueryBuilder.razor @@ -147,7 +147,7 @@ protected override void OnInitialized() { _allFields = Field.StaticEventField - .Concat(EnumerateDynamicFieldSelectors(State.Trace!, State.Query!.Filters)) + .Concat(EnumerateDynamicFields(State.Trace!, State.Query!.Filters)) .OrderBy(s => s.Name) .ToArray(); Query = State.Query!; @@ -158,18 +158,46 @@ _messageStore = new ValidationMessageStore(_editContext); } - private IEnumerable EnumerateDynamicFieldSelectors(Trace trace, Filter[] filters) + private IEnumerable EnumerateDynamicFields(Trace trace, Filter[] filters) { - return trace.EventMetadata - .SelectMany(m => m.FieldDefinitions.Select(d => + // Some fields can have the same name. In that case, the field should be displayed once in the combobox, + // but it should work on all events that have that field. It's assumed in this method, that if two fields + // have the same name, they have a similar FieldDefinition. + Dictionary> eventMetadataByFieldName = []; + foreach (var metadata in trace.EventMetadata) + { + foreach (var fieldDefinition in metadata.FieldDefinitions) { - // FluentCombobox.SelectedOption is expected to exist in FluentCombobox.Items but if the field is - // created dynamically, the reference comparison will fail and the filter will be set to null when - // changing page. The hack here, is to reuse existing fields before creating a new instance. - var filter = filters.FirstOrDefault(x => x.Field.Name == d.Name); - return filter?.Field ?? Field.FromPayloadFieldDefinition(d, m); - })) - .DistinctBy(s => s.Name); + if (!eventMetadataByFieldName.TryGetValue(fieldDefinition.Name, out var eventMetadataForFieldName)) + { + eventMetadataForFieldName = []; + eventMetadataByFieldName[fieldDefinition.Name] = eventMetadataForFieldName; + } + + eventMetadataForFieldName.Add(metadata); + } + } + + List fields = new(eventMetadataByFieldName.Count); + foreach (var eventMetadataForFieldName in eventMetadataByFieldName) + { + // FluentCombobox.SelectedOption is expected to exist in FluentCombobox.Items but if the field is + // created dynamically, the reference comparison will fail and the filter will be set to null when + // changing page. The hack here, is to reuse existing fields before creating a new instance. + var filter = filters.FirstOrDefault(x => x.Field.Name == eventMetadataForFieldName.Key); + if (filter?.Field != null) + { + fields.Add(filter.Field); + continue; + } + + var fieldDefinition = eventMetadataForFieldName.Value[0].FieldDefinitions.First(d => d.Name == eventMetadataForFieldName.Key); + var field = Field.FromNameAndType(eventMetadataForFieldName.Key, fieldDefinition.TypeCode, eventMetadataForFieldName.Value.ToArray()); + fields.Add(field); + } + + + return fields; } private void OnAddFilter() @@ -202,8 +230,11 @@ return _allFields; } - return _allFields.Where(f => f.AssociatedEventMetadata == null - || Query.EventKeys.Any(k => k.Matches(f.AssociatedEventMetadata))).ToArray(); + return _allFields + .Where(f => + f.AssociatedEventMetadata == null + || Query.EventKeys.Any(k => f.AssociatedEventMetadata.Any(k.Matches))) + .ToArray(); } private void OnColumnFieldSearch(OptionsSearchEventArgs e) @@ -215,7 +246,7 @@ else { e.Items = _allFields.Where(f => - (f.AssociatedEventMetadata == null || Query.EventKeys.Any(k => k.Matches(f.AssociatedEventMetadata))) + (f.AssociatedEventMetadata == null || Query.EventKeys.Any(k => f.AssociatedEventMetadata.Any(k.Matches))) && f.Name.Contains(e.Text, StringComparison.OrdinalIgnoreCase)); } } diff --git a/DotnetEventsViewer/Querying/Field.cs b/DotnetEventsViewer/Querying/Field.cs index e28b206..3122f46 100644 --- a/DotnetEventsViewer/Querying/Field.cs +++ b/DotnetEventsViewer/Querying/Field.cs @@ -2,7 +2,7 @@ namespace DotnetEventsViewer.Querying; -public class Field(string name, TypeCode type, Func selector, EventMetadata? associatedEventMetadata = null) +public class Field(string name, TypeCode type, Func selector, EventMetadata[]? associatedEventMetadata = null) { public static readonly Field CaptureThreadIdField = new(nameof(Event.CaptureThreadId), TypeCode.Int64, e => e.CaptureThreadId); public static readonly Field ThreadIdField = new(nameof(Event.ThreadId), TypeCode.Int64, e => e.ThreadId); @@ -23,17 +23,21 @@ public class Field(string name, TypeCode type, Func selector, Ev EventNameField, ]; - public static Field FromPayloadFieldDefinition(EventFieldDefinition fieldDefinition, EventMetadata eventMetadata) + public static Field FromNameAndType(string name, TypeCode typeCode, EventMetadata[] eventMetadata) { return new Field( - fieldDefinition.Name, - fieldDefinition.TypeCode, - e => e.Payload.GetValueOrDefault(fieldDefinition.Name), + name, + typeCode, + e => e.Payload.GetValueOrDefault(name), eventMetadata); } public string Name { get; } = name; public TypeCode Type { get; } = type; public Func Selector { get; } = selector; - public EventMetadata? AssociatedEventMetadata { get; } = associatedEventMetadata; + + /// + /// The event the field is associated to. Can contain several events if the same name is used for different events. + /// + public EventMetadata[]? AssociatedEventMetadata { get; } = associatedEventMetadata; }