Repository: dotnet/aspire
Pull request #8123: Move filters into a menu, add a remove all filters button
Author: login: adamint
Labels: name: area-dashboard
Created: 2025-03-17T20:42:51Z | Merged: 2025-03-20T01:56:35Z

## Description

This PR removes the current horizontal filter view, moving the filters into a fluent menu with a counter badge to hint at the number of filters set.

<img width="1106" alt="image" src="https://github.com/user-attachments/assets/59f1ac81-a628-4800-9914-963b0f274b63" />

Inside the menu, set filters are presented first, then a divider and a clear all filters item. Maybe we could think of icons to set for each of the filter types? 
<img width="272" alt="image" src="https://github.com/user-attachments/assets/17caeef7-8916-45b5-b2dc-11b4155b5459" />
 
If no filters are present, "No filters" continues to be shown.
<img width="618" alt="image" src="https://github.com/user-attachments/assets/21716352-9762-4c16-b5d9-16484ec4eeb2" />


Fixes #5673

## Checklist

- Is this feature complete?
  - [X] Yes. Ready to ship.
  - [ ] No. Follow-up changes expected.
- Are you including unit tests for the changes and scenario tests if relevant?
  - [ ] Yes
  - [X] No
- Did you add public API?
  - [ ] Yes
    - If yes, did you have an API Review for it?
      - [ ] Yes
      - [ ] No
    - Did you add `<remarks />` and `<code />` elements on your triple slash comments?
      - [ ] Yes
      - [ ] No
  - [X] No
- Does the change make any security assumptions or guarantees?
  - [ ] Yes
    - If yes, have you done a threat model and had a security review?
      - [ ] Yes
      - [ ] No
  - [X] No
- Does the change require an update in our Aspire docs?
  - [ ] Yes
    - Is this introducing a breaking change?
      - [ ] Yes
        - Link to aspire-docs issue (please use this [`breaking-change` template](https://github.com/dotnet/docs-aspire/issues/new?template=04-breaking-change.yml)):
      - [ ] No
        - Link to aspire-docs issue (please use this [`doc-idea` template](https://github.com/dotnet/docs-aspire/issues/new?template=02-docs-request.yml)):
  - [X] No


Linked issue #5673: [AzureTools][Aspire][Suggestion] It would be better if there is a "Remove all filters" option instead of having to click on the filters individually in order to remove all of it
**Clean machine:** Win11 x64 23h2 ENU
**VS Version:** VS 17.11.3 from [35309.138.d17.11]
**Aspire Version:** 9.0.0-preview.4.24459.11
Apply NuGet Feeds
Install Docker Desktop/Podman Desktop

**REPRO STEPS:**

1. Create a new Aspire starter project -> F5 the project.
2. Under "Structured" in the Dashboard, click "Add filter" -> Apply filter.
	        Parameter: "Message"
	        Condition: "contains"
	        Value: "listening"
3. Repeat step 2 with **different values** until there are more than 2 filters.<img width="953" alt="image" src="https://github.com/user-attachments/assets/f37b4de5-c5ff-45ad-837c-876851dc5d2c">
4. Check whether there is a "Remove all filters" option.
	

**Actual:**
The filters must be clicked individually in order to be removed.
![Suggest3](https://github.com/user-attachments/assets/740de5d0-c25b-49a1-a2d3-615b899df0b0)

**Suggestion:**
It would be better if there is a "Remove all filters" option instead of having to click on the filters individually in order to remove all of it.

**More Info:**
1. The filters are not removed when refreshing "Structured" page.
![Suggest2](https://github.com/user-attachments/assets/291f6133-f7fc-4b06-bc20-69e3aba781de)
2. The filters are not removed when switching to different page such as "Resources", "Structured", "Traces", "Metrics" and ".NET Aspire" logo. 
![Suggest1](https://github.com/user-attachments/assets/661727a6-c0f9-4fc6-947f-b8b708389e50)
Linked issue comments: user: login: Coleen17
created_at: 2025-04-01T05:15:20Z
body: I verified this issue using SDK 9.0.202 + Latest Aspire 9.2.0-preview.1.25181.6 and it doesn't reproduce. So, closing this bug.
author_association: NONE

Diff:
diff --git a/src/Aspire.Dashboard/Components/Controls/AspireMenuButton.razor b/src/Aspire.Dashboard/Components/Controls/AspireMenuButton.razor
index 7c4881dda7a..b60f7831436 100644
--- a/src/Aspire.Dashboard/Components/Controls/AspireMenuButton.razor
+++ b/src/Aspire.Dashboard/Components/Controls/AspireMenuButton.razor
@@ -21,7 +21,11 @@
     else
     {
         @Text
-        <FluentIcon Value="@_icon" Color="@IconColor" Slot="end" />
+
+        @if (!HideIcon)
+        {
+            <FluentIcon Value="@_icon" Color="@IconColor" Slot="end" />
+        }
     }
 </FluentButton>
 
@@ -36,10 +40,10 @@
         {
             var additionalMenuItemAttributes = new Dictionary<string, object>(item.AdditionalAttributes ?? ImmutableDictionary<string, object>.Empty)
             {
-                { "title", item.Tooltip ?? string.Empty }
+                { "title", item.Tooltip ?? item.Text ?? string.Empty }
             };
 
-            <FluentMenuItem Id="@item.Id" OnClick="() => HandleItemClicked(item)" Disabled="@item.IsDisabled" AdditionalAttributes="@additionalMenuItemAttributes">
+            <FluentMenuItem Id="@item.Id" Class="@item.Class" OnClick="() => HandleItemClicked(item)" Disabled="@item.IsDisabled" AdditionalAttributes="@additionalMenuItemAttributes">
                 @item.Text
                 @if (item.Icon != null)
                 {
diff --git a/src/Aspire.Dashboard/Components/Controls/AspireMenuButton.razor.cs b/src/Aspire.Dashboard/Components/Controls/AspireMenuButton.razor.cs
index 3463d32d5a7..54c063c8467 100644
--- a/src/Aspire.Dashboard/Components/Controls/AspireMenuButton.razor.cs
+++ b/src/Aspire.Dashboard/Components/Controls/AspireMenuButton.razor.cs
@@ -43,6 +43,9 @@ public partial class AspireMenuButton : FluentComponentBase
     [Parameter]
     public string MenuButtonId { get; set; } = Identifier.NewId();
 
+    [Parameter]
+    public bool HideIcon { get; set; }
+
     protected override void OnParametersSet()
     {
         _icon = Icon ?? s_defaultIcon;
diff --git a/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor b/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor
index 2c129c76319..83709331680 100644
--- a/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor
+++ b/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor
@@ -6,6 +6,7 @@
 @using Aspire.Dashboard.Resources
 @using System.Globalization
 @using Aspire.Dashboard.Components.Controls.Grid
+@using Aspire.Dashboard.Model
 @inject IJSRuntime JS
 @implements IDisposable
 
@@ -68,22 +69,17 @@
                                 HandleSelectedLogLevelChangedAsync="@HandleSelectedLogLevelChangedAsync" />
             }
             <FluentDivider slot="end" Role="DividerRole.Presentation" Orientation="Orientation.Vertical" />
-            <FluentLabel slot="end">@FilterLoc[nameof(StructuredFiltering.Filters)]</FluentLabel>
             @if (ViewModel.Filters.Count == 0)
             {
                 <span slot="end">@FilterLoc[nameof(StructuredFiltering.NoFilters)]</span>
             }
             else
             {
-                for (var i = 0; i < ViewModel.Filters.Count; i++)
-                {
-                    var filter = ViewModel.Filters[i];
-                    if (i != 0)
-                    {
-                        <FluentDivider slot="end" Role="DividerRole.Presentation" Orientation="Orientation.Vertical" />
-                    }
-                    <FluentButton slot="end" Appearance="Appearance.Outline" OnClick="() => OpenFilterAsync(filter)" class="telemetry-filter-button" title="@filter.GetDisplayText(FilterLoc)">@filter.GetDisplayText(FilterLoc)</FluentButton>
-                }
+                <div slot="end">
+                    <FluentCounterBadge HorizontalPosition="70" Count="@(ViewModel.Filters.Count)" Appearance="Appearance.Accent">
+                        <AspireMenuButton HideIcon="true" Text="@FilterLoc[nameof(StructuredFiltering.Filters)]" Items="@GetFilterMenuItems()" />
+                    </FluentCounterBadge>
+                </div>
             }
             <FluentDivider slot="end" Role="DividerRole.Presentation" Orientation="Orientation.Vertical" />
             @if (ViewportInformation.IsDesktop)
diff --git a/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs b/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs
index 6a914209112..aca17fd1365 100644
--- a/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs
+++ b/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs
@@ -349,6 +349,39 @@ private string GetRowClass(OtlpLogEntry entry)
         }
     }
 
+    private List<MenuButtonItem> GetFilterMenuItems()
+    {
+        var filterMenuItems = new List<MenuButtonItem>();
+
+        foreach (var filter in ViewModel.Filters)
+        {
+            filterMenuItems.Add(new MenuButtonItem
+            {
+                OnClick = () => OpenFilterAsync(filter),
+                Text = filter.GetDisplayText(FilterLoc),
+                Class = "filter-menu-item",
+            });
+        }
+
+        filterMenuItems.Add(new MenuButtonItem
+        {
+            IsDivider = true
+        });
+
+        filterMenuItems.Add(new MenuButtonItem
+        {
+            Text = DialogsLoc[nameof(Dashboard.Resources.Dialogs.SettingsRemoveAllButtonText)],
+            Icon = new Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size16.Delete(),
+            OnClick = () =>
+            {
+                ViewModel.ClearFilters();
+                return this.AfterViewModelChangedAsync(_contentLayout, waitToApplyMobileChange: false);
+            }
+        });
+
+        return filterMenuItems;
+    }
+
     protected override async Task OnAfterRenderAsync(bool firstRender)
     {
         if (_applicationChanged)
diff --git a/src/Aspire.Dashboard/Components/Pages/Traces.razor b/src/Aspire.Dashboard/Components/Pages/Traces.razor
index 2c76c270189..7984a0c462d 100644
--- a/src/Aspire.Dashboard/Components/Pages/Traces.razor
+++ b/src/Aspire.Dashboard/Components/Pages/Traces.razor
@@ -47,22 +47,17 @@
                           title="@Loc[nameof(Dashboard.Resources.Traces.TracesNameFilter)]"
                           slot="end" />
             <FluentDivider slot="end" Role="DividerRole.Presentation" Orientation="Orientation.Vertical" />
-            <FluentLabel slot="end">@FilterLoc[nameof(StructuredFiltering.Filters)]</FluentLabel>
             @if (TracesViewModel.Filters.Count == 0)
             {
                 <span slot="end">@FilterLoc[nameof(StructuredFiltering.NoFilters)]</span>
             }
             else
             {
-                for (var i = 0; i < TracesViewModel.Filters.Count; i++)
-                {
-                    var filter = TracesViewModel.Filters[i];
-                    if (i != 0)
-                    {
-                        <FluentDivider slot="end" Role="DividerRole.Presentation" Orientation="Orientation.Vertical" />
-                    }
-                    <FluentButton slot="end" Appearance="Appearance.Outline" OnClick="() => OpenFilterAsync(filter)" class="telemetry-filter-button" title="@filter.GetDisplayText(FilterLoc)">@filter.GetDisplayText(FilterLoc)</FluentButton>
-                }
+                <div slot="end">
+                    <FluentCounterBadge HorizontalPosition="70" Count="@(TracesViewModel.Filters.Count)" Appearance="Appearance.Accent">
+                        <AspireMenuButton HideIcon="true" Text="@FilterLoc[nameof(StructuredFiltering.Filters)]" Items="@GetFilterMenuItems()" />
+                    </FluentCounterBadge>
+                </div>
             }
             <FluentDivider slot="end" Role="DividerRole.Presentation" Orientation="Orientation.Vertical" />
             @if (ViewportInformation.IsDesktop)
diff --git a/src/Aspire.Dashboard/Components/Pages/Traces.razor.cs b/src/Aspire.Dashboard/Components/Pages/Traces.razor.cs
index 9619c8a9faf..d7ab137de73 100644
--- a/src/Aspire.Dashboard/Components/Pages/Traces.razor.cs
+++ b/src/Aspire.Dashboard/Components/Pages/Traces.razor.cs
@@ -343,6 +343,39 @@ private Task ClearTraces(ApplicationKey? key)
         return Task.CompletedTask;
     }
 
+    private List<MenuButtonItem> GetFilterMenuItems()
+    {
+        var filterMenuItems = new List<MenuButtonItem>();
+
+        foreach (var filter in TracesViewModel.Filters)
+        {
+            filterMenuItems.Add(new MenuButtonItem
+            {
+                OnClick = () => OpenFilterAsync(filter),
+                Text = filter.GetDisplayText(FilterLoc),
+                Class = "filter-menu-item",
+            });
+        }
+
+        filterMenuItems.Add(new MenuButtonItem
+        {
+            IsDivider = true
+        });
+
+        filterMenuItems.Add(new MenuButtonItem
+        {
+            Text = DialogsLoc[nameof(Dashboard.Resources.Dialogs.SettingsRemoveAllButtonText)],
+            Icon = new Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size16.Delete(),
+            OnClick = () =>
+            {
+                TracesViewModel.ClearFilters();
+                return this.AfterViewModelChangedAsync(_contentLayout, waitToApplyMobileChange: false);
+            }
+        });
+
+        return filterMenuItems;
+    }
+
     public class TracesPageViewModel
     {
         public required SelectViewModel<ResourceTypeDetails> SelectedApplication { get; set; }
diff --git a/src/Aspire.Dashboard/Model/MenuButtonItem.cs b/src/Aspire.Dashboard/Model/MenuButtonItem.cs
index 174e836636b..57b254733af 100644
--- a/src/Aspire.Dashboard/Model/MenuButtonItem.cs
+++ b/src/Aspire.Dashboard/Model/MenuButtonItem.cs
@@ -14,5 +14,6 @@ public class MenuButtonItem
     public Func<Task>? OnClick { get; set; }
     public bool IsDisabled { get; set; }
     public string Id { get; set; } = Identifier.NewId();
+    public string? Class { get; set; }
     public IReadOnlyDictionary<string, object>? AdditionalAttributes { get; set; }
 }
diff --git a/src/Aspire.Dashboard/wwwroot/css/app.css b/src/Aspire.Dashboard/wwwroot/css/app.css
index 500db7a7ed4..35bcf66b4a8 100644
--- a/src/Aspire.Dashboard/wwwroot/css/app.css
+++ b/src/Aspire.Dashboard/wwwroot/css/app.css
@@ -535,12 +535,6 @@ fluent-switch.table-switch::part(label) {
     padding: calc(((var(--design-unit) * 0.5) - var(--stroke-width)) * 1px) calc((var(--design-unit) - var(--stroke-width)) * 1px);
 }
 
-.telemetry-filter-button::part(content) {
-    max-width: 350px;
-    overflow: hidden;
-    text-overflow: ellipsis
-}
-
 .mobile-toolbar {
     width: 100%;
     height: max(5vh, 30px);
@@ -752,3 +746,7 @@ fluent-switch.table-switch::part(label) {
     max-height: 400px;
     overflow-y: auto;
 }
+
+.filter-menu-item::part(content) {
+    max-width: 300px;
+}
diff --git a/tests/Aspire.Dashboard.Components.Tests/Pages/StructuredLogsTests.cs b/tests/Aspire.Dashboard.Components.Tests/Pages/StructuredLogsTests.cs
index 99777f0bf64..d273a4e8e03 100644
--- a/tests/Aspire.Dashboard.Components.Tests/Pages/StructuredLogsTests.cs
+++ b/tests/Aspire.Dashboard.Components.Tests/Pages/StructuredLogsTests.cs
@@ -189,6 +189,7 @@ private void SetupStructureLogsServices()
         Services.AddSingleton<ShortcutManager>();
         Services.AddSingleton<LibraryConfiguration>();
         Services.AddSingleton<IKeyCodeService, KeyCodeService>();
+        Services.AddSingleton<GlobalState>();
     }
 
     private static string GetFluentFile(string filePath, Version version)
diff --git a/tests/Shared/AsyncTestHelpers.cs b/tests/Shared/AsyncTestHelpers.cs
index 785a9efdc22..62640fc0048 100644
--- a/tests/Shared/AsyncTestHelpers.cs
+++ b/tests/Shared/AsyncTestHelpers.cs
@@ -182,7 +182,7 @@ public static async Task AssertIsTrueRetryAsync(Func<Task<bool>> assert, string
         {
             if (i > 0)
             {
-                await Task.Delay((i + 1) * (i + 1) * 10);
+                await Task.Delay((i + 1) * (i + 1) * 10 * 5);
             }
 
             if (await assert())


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR refactors the filter UI on the Structured Logs page by moving filters into a Fluent menu and adding a “remove all filters” option.  
- Introduces a new helper method (GetFilterMenuItems) to generate menu items for existing filters and the clear-all option.  
- Adds a new DisableIcon parameter to the AspireMenuButton to allow conditional rendering of the icon.  
- Updates the StructuredLogs view to wrap the new menu button in a counter badge indicating the number of active filters.

### Reviewed Changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated no comments.

| File | Description |
| ---- | ----------- |
| src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs | Added GetFilterMenuItems method to generate filter menu items, including a clear-all option. |
| src/Aspire.Dashboard/Components/Controls/AspireMenuButton.razor.cs | Introduced DisableIcon parameter to offer control over icon rendering. |
| src/Aspire.Dashboard/Components/Controls/AspireMenuButton.razor | Updated rendering logic to conditionally include the icon based on DisableIcon. |
| src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor | Replaced inline filtering buttons with a counter badge wrapping a menu button. |

<details>
<summary>Files not reviewed (1)</summary>

* **src/Aspire.Dashboard/wwwroot/css/app.css**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor:80**
* Consider adding unit tests to cover the new filter menu functionality and its interactions, ensuring that the filtering and clear-all behavior work as expected.
```
<AspireMenuButton DisableIcon="true" Text="@FilterLoc[nameof(StructuredFiltering.Filters)]" Items="@GetFilterMenuItems()" />
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T20:43:24Z
user: login: adamint
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T20:43:29Z
user: login: JamesNK
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T03:17:23Z
user: login: adamint
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T15:54:40Z
user: login: adamint
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T16:05:47Z
user: login: JamesNK
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T01:23:57Z
user: login: JamesNK
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-20T01:56:23Z

Review comments:
diff_hunk: @@ -68,22 +69,17 @@
                                 HandleSelectedLogLevelChangedAsync="@HandleSelectedLogLevelChangedAsync" />
             }
             <FluentDivider slot="end" Role="DividerRole.Presentation" Orientation="Orientation.Vertical" />
-            <FluentLabel slot="end">@FilterLoc[nameof(StructuredFiltering.Filters)]</FluentLabel>
             @if (ViewModel.Filters.Count == 0)
             {
                 <span slot="end">@FilterLoc[nameof(StructuredFiltering.NoFilters)]</span>
             }
             else
             {
-                for (var i = 0; i < ViewModel.Filters.Count; i++)
-                {
-                    var filter = ViewModel.Filters[i];
-                    if (i != 0)
-                    {
-                        <FluentDivider slot="end" Role="DividerRole.Presentation" Orientation="Orientation.Vertical" />
-                    }
-                    <FluentButton slot="end" Appearance="Appearance.Outline" OnClick="() => OpenFilterAsync(filter)" class="telemetry-filter-button" title="@filter.GetDisplayText(FilterLoc)">@filter.GetDisplayText(FilterLoc)</FluentButton>
-                }
+                <div slot="end">
+                    <FluentCounterBadge HorizontalPosition="70" Count="@(ViewModel.Filters.Count)" Appearance="Appearance.Accent">
path: src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor
user: login: adamint
body: setting slot=end onto counter badge doesn't work, maybe a bug in the component? I can check
created_at: 2025-03-17T20:43:28Z
author_association: MEMBER
diff_hunk: @@ -43,6 +43,9 @@ public partial class AspireMenuButton : FluentComponentBase
     [Parameter]
     public string MenuButtonId { get; set; } = Identifier.NewId();
 
+    [Parameter]
+    public bool DisableIcon { get; set; }
path: src/Aspire.Dashboard/Components/Controls/AspireMenuButton.razor.cs
user: login: JamesNK
body: Is `HideIcon` a better name? Disable usually means something is still visible but it's in a disabled state.
created_at: 2025-03-18T03:17:23Z
author_association: MEMBER
diff_hunk: @@ -68,22 +69,17 @@
                                 HandleSelectedLogLevelChangedAsync="@HandleSelectedLogLevelChangedAsync" />
             }
             <FluentDivider slot="end" Role="DividerRole.Presentation" Orientation="Orientation.Vertical" />
-            <FluentLabel slot="end">@FilterLoc[nameof(StructuredFiltering.Filters)]</FluentLabel>
             @if (ViewModel.Filters.Count == 0)
             {
                 <span slot="end">@FilterLoc[nameof(StructuredFiltering.NoFilters)]</span>
             }
             else
             {
-                for (var i = 0; i < ViewModel.Filters.Count; i++)
-                {
-                    var filter = ViewModel.Filters[i];
-                    if (i != 0)
-                    {
-                        <FluentDivider slot="end" Role="DividerRole.Presentation" Orientation="Orientation.Vertical" />
-                    }
-                    <FluentButton slot="end" Appearance="Appearance.Outline" OnClick="() => OpenFilterAsync(filter)" class="telemetry-filter-button" title="@filter.GetDisplayText(FilterLoc)">@filter.GetDisplayText(FilterLoc)</FluentButton>
-                }
+                <div slot="end">
+                    <FluentCounterBadge HorizontalPosition="70" Count="@(ViewModel.Filters.Count)" Appearance="Appearance.Accent">
path: src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor
user: login: adamint
body: Yeah, this is a bug in that component
created_at: 2025-03-19T15:54:40Z
author_association: MEMBER
diff_hunk: @@ -68,22 +69,17 @@
                                 HandleSelectedLogLevelChangedAsync="@HandleSelectedLogLevelChangedAsync" />
             }
             <FluentDivider slot="end" Role="DividerRole.Presentation" Orientation="Orientation.Vertical" />
-            <FluentLabel slot="end">@FilterLoc[nameof(StructuredFiltering.Filters)]</FluentLabel>
             @if (ViewModel.Filters.Count == 0)
             {
                 <span slot="end">@FilterLoc[nameof(StructuredFiltering.NoFilters)]</span>
             }
             else
             {
-                for (var i = 0; i < ViewModel.Filters.Count; i++)
-                {
-                    var filter = ViewModel.Filters[i];
-                    if (i != 0)
-                    {
-                        <FluentDivider slot="end" Role="DividerRole.Presentation" Orientation="Orientation.Vertical" />
-                    }
-                    <FluentButton slot="end" Appearance="Appearance.Outline" OnClick="() => OpenFilterAsync(filter)" class="telemetry-filter-button" title="@filter.GetDisplayText(FilterLoc)">@filter.GetDisplayText(FilterLoc)</FluentButton>
-                }
+                <div slot="end">
+                    <FluentCounterBadge HorizontalPosition="70" Count="@(ViewModel.Filters.Count)" Appearance="Appearance.Accent">
path: src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor
user: login: adamint
body: https://github.com/microsoft/fluentui-blazor/pull/3542
created_at: 2025-03-19T16:05:41Z
author_association: MEMBER
diff_hunk: @@ -182,7 +182,7 @@ public static async Task AssertIsTrueRetryAsync(Func<Task<bool>> assert, string
         {
             if (i > 0)
             {
-                await Task.Delay((i + 1) * (i + 1) * 10);
+                await Task.Delay((i + 1) * (i + 1) * 10 * 5);
path: tests/Shared/AsyncTestHelpers.cs
user: login: JamesNK
body: What caused this to timeout?
created_at: 2025-03-20T01:23:57Z
author_association: MEMBER

PR comments:
user: login: mitchdenny
created_at: 2025-03-17T22:28:59Z
body: The placement of _Filters_ and _No filters_ seems strange to me. I think maybe its the fact that it changes from Filters to No filters that is putting me off. I wonder if when no filters are set it still says Filters, but doesn't have the badge.

I'm no UX expert though.
author_association: MEMBER
user: login: mitchdenny
created_at: 2025-03-17T22:29:45Z
body: Actually, the fact that a badge is being used is also a bit odd I think. Perhaps changing the colour of the button itself?
author_association: MEMBER
user: login: JamesNK
created_at: 2025-03-18T03:09:49Z
body: A long name or value overflows the menu item. A very long name here should be trimmed with `...`.

![image](https://github.com/user-attachments/assets/1ae29f5c-b20a-4b8d-b377-73695f770eeb)


author_association: MEMBER
user: login: JamesNK
created_at: 2025-03-18T03:13:26Z
body: I'm guessing there's no limit on the height or the menu:

![image](https://github.com/user-attachments/assets/c118cfde-23d6-4368-8ab5-771c5145a946)

It might be a problem, but realistically there aren't going to be that many filters added so lets not worry right now.
author_association: MEMBER
user: login: JamesNK
created_at: 2025-03-18T03:15:34Z
body: Refreshing the page while there is a filter causes the badge number to move.

Add a new filter:
![image](https://github.com/user-attachments/assets/eff08475-e6ec-466d-bb1e-2137b6535650)

Refresh the page:
![image](https://github.com/user-attachments/assets/e7aeae86-5782-4509-b36d-c15cd7017679)

author_association: MEMBER
user: login: JamesNK
created_at: 2025-03-18T03:16:29Z
body: Traces page has the same filters. They're not updated.
author_association: MEMBER
user: login: JamesNK
created_at: 2025-03-18T03:20:58Z
body: Remove all just updates the filter UI. It doesn't remove the filters from the query string or update the page:

![filters-remove-all](https://github.com/user-attachments/assets/5584caa2-2693-49c9-8491-579a6ac857eb)

author_association: MEMBER
user: login: JamesNK
created_at: 2025-03-18T03:52:37Z
body: > Actually, the fact that a badge is being used is also a bit odd I think. Perhaps changing the colour of the button itself?

I think it's ok. Lets you know how many filters there are.
author_association: MEMBER
user: login: adamint
created_at: 2025-03-19T15:37:13Z
body: > Refreshing the page while there is a filter causes the badge number to move.
> 
> Add a new filter: ![image](https://private-user-images.githubusercontent.com/303201/423771444-eff08475-e6ec-466d-bb1e-2137b6535650.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDIzOTYwNzEsIm5iZiI6MTc0MjM5NTc3MSwicGF0aCI6Ii8zMDMyMDEvNDIzNzcxNDQ0LWVmZjA4NDc1LWU2ZWMtNDY2ZC1iYjFlLTIxMzdiNjUzNTY1MC5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUwMzE5JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MDMxOVQxNDQ5MzFaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT0zNjVhNTJmODE3YTg3MTZiYWRhN2VjYWVjNDY1NDcwNzY0NDc0MmFmMzMxNmQ3MTIwMjI4Y2FjMjJlZjhiNDFiJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.UIofwwvE8_uJIiyyWX-xT0ld0IKTRkchc0pj-5JUvD8)
> 
> Refresh the page: ![image](https://private-user-images.githubusercontent.com/303201/423771477-e7aeae86-5782-4509-b36d-c15cd7017679.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDIzOTYwNzEsIm5iZiI6MTc0MjM5NTc3MSwicGF0aCI6Ii8zMDMyMDEvNDIzNzcxNDc3LWU3YWVhZTg2LTU3ODItNDUwOS1iMzZkLWMxNWNkNzAxNzY3OS5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUwMzE5JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MDMxOVQxNDQ5MzFaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT0wYTViNWE2NjM0ZjBiNmI3M2YxNmZhOTFjNzMxYWZmZTcyZGJmYTU2MDNlNTdkY2M4N2I5YjQ3MzgxODU1OTRlJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.OghewQpu_3Ii_-L0DPyqlWgmenYQ_giSLY1c-wD0VAY)

This is due to a fluentui-blazor bug that was fixed on 3/10 and will be included in the next release. The 'true' positioning is the former (what you see after adding a filter)
author_association: MEMBER
user: login: adamint
created_at: 2025-03-19T15:46:21Z
body: > The placement of _Filters_ and _No filters_ seems strange to me. I think maybe its the fact that it changes from Filters to No filters that is putting me off. I wonder if when no filters are set it still says Filters, but doesn't have the badge.
> 
> I'm no UX expert though.

@JamesNK?
author_association: MEMBER
user: login: JamesNK
created_at: 2025-03-20T01:54:56Z
body: > > The placement of _Filters_ and _No filters_ seems strange to me. I think maybe its the fact that it changes from Filters to No filters that is putting me off. I wonder if when no filters are set it still says Filters, but doesn't have the badge.
> > I'm no UX expert though.
> 
> @JamesNK?

I think it is Good Enough(tm) to merge. Nothing stops it changing later. People can use and give feedback.
author_association: MEMBER