Skip to content

Commit 06ee4ae

Browse files
authored
Prevent duplicate filters on structured logs page (#5524)
* Prevent duplicate filters on structured logs page * Remove trim
1 parent 8662a41 commit 06ee4ae

File tree

4 files changed

+81
-8
lines changed

4 files changed

+81
-8
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,10 @@ public void UpdateViewModelFromQuery(StructuredLogsPageViewModel viewModel)
434434
if (filters.Count > 0)
435435
{
436436
ViewModel.ClearFilters();
437-
ViewModel.AddFilters(filters);
437+
foreach (var filter in filters)
438+
{
439+
ViewModel.AddFilter(filter);
440+
}
438441
}
439442
}
440443

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
namespace Aspire.Dashboard.Model.Otlp;
1111

1212
[DebuggerDisplay("{FilterText,nq}")]
13-
public class LogFilter
13+
public class LogFilter : IEquatable<LogFilter>
1414
{
1515
public const string KnownMessageField = "log.message";
1616
public const string KnownCategoryField = "log.category";
@@ -142,4 +142,29 @@ public IEnumerable<OtlpLogEntry> Apply(IEnumerable<OtlpLogEntry> input)
142142
}
143143
}
144144
}
145+
146+
public bool Equals(LogFilter? other)
147+
{
148+
if (other == null)
149+
{
150+
return false;
151+
}
152+
153+
if (Field != other.Field)
154+
{
155+
return false;
156+
}
157+
158+
if (Condition != other.Condition)
159+
{
160+
return false;
161+
}
162+
163+
if (!string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase))
164+
{
165+
return false;
166+
}
167+
168+
return true;
169+
}
145170
}

src/Aspire.Dashboard/Model/StructuredLogsViewModel.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,21 @@ public void ClearFilters()
3434
_logs = null;
3535
}
3636

37-
public void AddFilters(IEnumerable<LogFilter> filters)
38-
{
39-
_filters.AddRange(filters);
40-
_logs = null;
41-
}
42-
4337
public void AddFilter(LogFilter filter)
4438
{
39+
// Don't add duplicate filters.
40+
foreach (var existingFilter in _filters)
41+
{
42+
if (existingFilter.Equals(filter))
43+
{
44+
return;
45+
}
46+
}
47+
4548
_filters.Add(filter);
4649
_logs = null;
4750
}
51+
4852
public bool RemoveFilter(LogFilter filter)
4953
{
5054
if (_filters.Remove(filter))
@@ -54,6 +58,7 @@ public bool RemoveFilter(LogFilter filter)
5458
}
5559
return false;
5660
}
61+
5762
public int StartIndex { get => _logsStartIndex; set => SetValue(ref _logsStartIndex, value); }
5863
public int? Count { get => _logsCount; set => SetValue(ref _logsCount, value); }
5964
public LogLevel? LogLevel { get => _logLevel; set => SetValue(ref _logLevel, value); }

tests/Aspire.Dashboard.Components.Tests/Pages/StructuredLogsTests.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Aspire.Dashboard.Components.Resize;
66
using Aspire.Dashboard.Components.Tests.Shared;
77
using Aspire.Dashboard.Configuration;
8+
using Aspire.Dashboard.Extensions;
89
using Aspire.Dashboard.Model;
910
using Aspire.Dashboard.Model.BrowserStorage;
1011
using Aspire.Dashboard.Model.Otlp;
@@ -64,6 +65,45 @@ public void Render_TraceIdAndSpanId_FilterAdded()
6465
});
6566
}
6667

68+
[Fact]
69+
public void Render_DuplicateFilters_SingleFilterAdded()
70+
{
71+
// Arrange
72+
SetupStructureLogsServices();
73+
74+
var logFilter = new LogFilter { Field = "TestField", Condition = FilterCondition.Contains, Value = "TestValue" };
75+
var serializedFilter = LogFilterFormatter.SerializeLogFiltersToString([logFilter]);
76+
77+
var navigationManager = Services.GetRequiredService<NavigationManager>();
78+
var uri = navigationManager.GetUriWithQueryParameters(new Dictionary<string, object?>
79+
{
80+
["filters"] = serializedFilter + "+" + serializedFilter,
81+
});
82+
navigationManager.NavigateTo(uri);
83+
84+
var viewport = new ViewportInformation(IsDesktop: true, IsUltraLowHeight: false, IsUltraLowWidth: false);
85+
86+
var dimensionManager = Services.GetRequiredService<DimensionManager>();
87+
dimensionManager.InvokeOnBrowserDimensionsChanged(viewport);
88+
89+
// Act
90+
var cut = RenderComponent<StructuredLogs>(builder =>
91+
{
92+
builder.Add(p => p.ViewportInformation, viewport);
93+
});
94+
95+
// Assert
96+
var viewModel = Services.GetRequiredService<StructuredLogsViewModel>();
97+
98+
Assert.Collection(viewModel.Filters,
99+
f =>
100+
{
101+
Assert.Equal(logFilter.Field, f.Field);
102+
Assert.Equal(logFilter.Condition, f.Condition);
103+
Assert.Equal(logFilter.Value, f.Value);
104+
});
105+
}
106+
67107
private void SetupStructureLogsServices()
68108
{
69109
var version = typeof(FluentMain).Assembly.GetName().Version!;

0 commit comments

Comments
 (0)