Skip to content

Commit

Permalink
Make child resources visible by default, add expand/collapse all chil…
Browse files Browse the repository at this point in the history
…dren toggle, move resources filters to menu (#7404)

* Expand child resources by default, persist

* Add resources page settings button

* Edit collapse/expand all children text

* Change hide/show to collapse/expand

* Change hide/show to collapse/expand

* Use collapsed resource hash set instead of dictionary

* Add filter button back, hide settings button when there are no children

* Changes and fixes

* Fix build

---------

Co-authored-by: Adam Ratzman <[email protected]>
Co-authored-by: James Newton-King <[email protected]>
  • Loading branch information
3 people authored Feb 10, 2025
1 parent 2e3da91 commit d51f233
Show file tree
Hide file tree
Showing 22 changed files with 318 additions and 44 deletions.
24 changes: 22 additions & 2 deletions src/Aspire.Dashboard/Components/Pages/Resources.razor
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
}

<div class="page-content-container">
<AspirePageContentLayout IsSummaryDetailsViewOpen="@showDetailsView">
<AspirePageContentLayout IsSummaryDetailsViewOpen="@showDetailsView" @ref="@_contentLayout">
<PageTitleSection>
<h1 class="page-header">@Loc[nameof(Dashboard.Resources.Resources.ResourcesHeader)]</h1>
</PageTitleSection>
Expand All @@ -30,6 +30,7 @@
Label="@(ViewportInformation.IsDesktop ? null : ControlsStringsLoc[nameof(ControlsStrings.FilterPlaceholder)].Value)"
@bind-Value:after="HandleSearchFilterChangedAsync" />
<FluentDivider slot="end" Role="DividerRole.Presentation" Orientation="Orientation.Vertical" />

@if (ViewportInformation.IsDesktop)
{
<FluentButton id="resourceFilterButton" slot="end"
Expand All @@ -38,16 +39,35 @@
@onclick="() => _isFilterPopupVisible = !_isFilterPopupVisible"
Title="@(NoFiltersSet ? Loc[nameof(Dashboard.Resources.Resources.ResourcesNotFiltered)] : Loc[nameof(Dashboard.Resources.Resources.ResourcesFiltered)])"
aria-label="@(NoFiltersSet ? Loc[nameof(Dashboard.Resources.Resources.ResourcesNotFiltered)] : Loc[nameof(Dashboard.Resources.Resources.ResourcesFiltered)])" />

@if (HasAnyChildResources())
{
<AspireMenuButton ButtonAppearance="Appearance.Stealth"
Icon="@(new Icons.Regular.Size20.Settings())"
Items="@_resourcesMenuItems"
Title="@Loc[nameof(Dashboard.Resources.ConsoleLogs.ConsoleLogsSettings)]"
slot="end" />
}
}
else
{
@if (HasAnyChildResources())
{
var showExpandAllButton = HasCollapsedResources();

<FluentButton IconStart="@(showExpandAllButton ? new Icons.Regular.Size16.Eye() : new Icons.Regular.Size16.EyeOff())"
OnClick="@(async () => { await OnToggleCollapseAll(); _contentLayout?.CloseMobileToolbarAsync(); })">
@(showExpandAllButton ? Loc[nameof(Dashboard.Resources.Resources.ResourceExpandAllChildren)] : Loc[nameof(Dashboard.Resources.Resources.ResourceCollapseAllChildren)])
</FluentButton>
}

<div>
<ResourceFilters
ResourceStates="@ResourceStatesToVisibility"
ResourceTypes="@ResourceTypesToVisibility"
ResourceHealthStates="@ResourceHealthStatusesToVisibility"
OnAllFilterVisibilityCheckedChangedAsync="@OnAllFilterVisibilityCheckedChangedAsync"
OnResourceFilterVisibilityChangedAsync="@OnResourceFilterVisibilityChangedAsync" />
OnResourceFilterVisibilityChangedAsync="@OnResourceFilterVisibilityChangedAsync"/>
</div>
}
</ToolbarSection>
Expand Down
100 changes: 93 additions & 7 deletions src/Aspire.Dashboard/Components/Pages/Resources.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Diagnostics;
using System.Globalization;
using System.Text;
using Aspire.Dashboard.Components.Layout;
using Aspire.Dashboard.Extensions;
using Aspire.Dashboard.Model;
using Aspire.Dashboard.Otlp.Storage;
Expand All @@ -13,6 +14,7 @@
using Microsoft.AspNetCore.Components;
using Microsoft.FluentUI.AspNetCore.Components;
using Microsoft.JSInterop;
using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons;

namespace Aspire.Dashboard.Components.Pages;

Expand Down Expand Up @@ -42,6 +44,8 @@ public partial class Resources : ComponentBase, IAsyncDisposable
public required BrowserTimeProvider TimeProvider { get; init; }
[Inject]
public required IJSRuntime JS { get; init; }
[Inject]
public required ISessionStorage SessionStorage { get; init; }

[CascadingParameter]
public required ViewportInformation ViewportInformation { get; set; }
Expand All @@ -66,7 +70,7 @@ public partial class Resources : ComponentBase, IAsyncDisposable

private readonly CancellationTokenSource _watchTaskCancellationTokenSource = new();
private readonly ConcurrentDictionary<string, ResourceViewModel> _resourceByName = new(StringComparers.ResourceName);
private readonly HashSet<string> _expandedResourceNames = [];
private readonly HashSet<string> _collapsedResourceNames = new(StringComparers.ResourceName);
private string _filter = "";
private bool _isFilterPopupVisible;
private Task? _resourceSubscriptionTask;
Expand All @@ -75,6 +79,8 @@ public partial class Resources : ComponentBase, IAsyncDisposable
private FluentDataGrid<ResourceGridViewModel> _dataGrid = null!;
private GridColumnManager _manager = null!;
private int _maxHighlightedCount;
private readonly List<MenuButtonItem> _resourcesMenuItems = new();
private AspirePageContentLayout? _contentLayout;

private ColumnResizeLabels _resizeLabels = ColumnResizeLabels.Default;
private ColumnSortLabels _sortLabels = ColumnSortLabels.Default;
Expand All @@ -100,12 +106,14 @@ private async Task OnAllFilterVisibilityCheckedChangedAsync()
{
await ClearSelectedResourceAsync();
await _dataGrid.SafeRefreshDataAsync();
UpdateMenuButtons();
}

private async Task OnResourceFilterVisibilityChangedAsync(string resourceType, bool isVisible)
{
await ClearSelectedResourceAsync();
await _dataGrid.SafeRefreshDataAsync();
UpdateMenuButtons();
}

private async Task HandleSearchFilterChangedAsync()
Expand Down Expand Up @@ -138,10 +146,21 @@ protected override async Task OnInitializedAsync()
new GridColumn(Name: EndpointsColumn, DesktopWidth: "2.25fr", MobileWidth: "2fr"),
new GridColumn(Name: ActionsColumn, DesktopWidth: "minmax(150px, 1.5fr)", MobileWidth: "1fr")
];

_applicationUnviewedErrorCounts = TelemetryRepository.GetApplicationUnviewedErrorLogsCount();
UpdateMenuButtons();

if (DashboardClient.IsEnabled)
{
var collapsedResult = await SessionStorage.GetAsync<List<string>>(BrowserStorageKeys.ResourcesCollapsedResourceNames);
if (collapsedResult.Success)
{
foreach (var resourceName in collapsedResult.Value)
{
_collapsedResourceNames.Add(resourceName);
}
}

await SubscribeResourcesAsync();
}

Expand Down Expand Up @@ -245,6 +264,8 @@ bool UpdateFromResource(ResourceViewModel resource, Func<string, bool> resourceT
ResourceStatesToVisibility.TryAdd(resource.State ?? string.Empty, stateVisible(resource.State ?? string.Empty));
ResourceHealthStatusesToVisibility.TryAdd(resource.HealthStatus?.Humanize() ?? string.Empty, healthStatusVisible(resource.HealthStatus?.Humanize() ?? string.Empty));

UpdateMenuButtons();

return added;
}
}
Expand All @@ -267,7 +288,7 @@ private ValueTask<GridItemsProviderResult<ResourceGridViewModel>> GetData(GridIt
// Rearrange resources based on parent information.
// This must happen after resources are ordered so nested resources are in the right order.
// Collapsed resources are filtered out of results.
var orderedResources = ResourceGridViewModel.OrderNestedResources(filteredResources.ToList(), r => !_expandedResourceNames.Contains(r.Name))
var orderedResources = ResourceGridViewModel.OrderNestedResources(filteredResources.ToList(), r => _collapsedResourceNames.Contains(r.Name))
.Where(r => !r.IsHidden)
.ToList();

Expand All @@ -280,6 +301,37 @@ private ValueTask<GridItemsProviderResult<ResourceGridViewModel>> GetData(GridIt
return ValueTask.FromResult(GridItemsProviderResult.From(query, orderedResources.Count));
}

private void UpdateMenuButtons()
{
_resourcesMenuItems.Clear();

if (HasCollapsedResources())
{
_resourcesMenuItems.Add(new MenuButtonItem
{
IsDisabled = false,
OnClick = OnToggleCollapseAll,
Text = Loc[nameof(Dashboard.Resources.Resources.ResourceExpandAllChildren)],
Icon = new Icons.Regular.Size16.Eye()
});
}
else
{
_resourcesMenuItems.Add(new MenuButtonItem
{
IsDisabled = false,
OnClick = OnToggleCollapseAll,
Text = Loc[nameof(Dashboard.Resources.Resources.ResourceCollapseAllChildren)],
Icon = new Icons.Regular.Size16.EyeOff()
});
}
}

private bool HasCollapsedResources()
{
return _resourceByName.Any(r => !r.Value.IsHiddenState() && _collapsedResourceNames.Contains(r.Key));
}

private void UpdateMaxHighlightedCount()
{
var maxHighlightedCount = 0;
Expand Down Expand Up @@ -353,7 +405,7 @@ private async Task ShowResourceDetailsAsync(ResourceViewModel resource, string?
{
if (_resourceByName.TryGetValue(value, out current))
{
_expandedResourceNames.Add(value);
_collapsedResourceNames.Remove(value);
continue;
}
}
Expand Down Expand Up @@ -441,25 +493,59 @@ private async Task OnToggleCollapse(ResourceGridViewModel viewModel)
{
// View model data is recreated if data updates.
// Persist the collapsed state in a separate list.
viewModel.IsCollapsed = !viewModel.IsCollapsed;

if (viewModel.IsCollapsed)
{
viewModel.IsCollapsed = false;
_expandedResourceNames.Add(viewModel.Resource.Name);
_collapsedResourceNames.Add(viewModel.Resource.Name);
}
else
{
_collapsedResourceNames.Remove(viewModel.Resource.Name);
}

await SessionStorage.SetAsync(BrowserStorageKeys.ResourcesCollapsedResourceNames, _collapsedResourceNames.ToList());
await _dataGrid.SafeRefreshDataAsync();
UpdateMenuButtons();
}

private async Task OnToggleCollapseAll()
{
var resourcesWithChildren = _resourceByName.Values
.Where(r => !r.IsHiddenState())
.Where(r => _resourceByName.Values.Any(nested => nested.GetResourcePropertyValue(KnownProperties.Resource.ParentName) == r.Name))
.ToList();

if (HasCollapsedResources())
{
foreach (var resource in resourcesWithChildren)
{
_collapsedResourceNames.Remove(resource.Name);
}
}
else
{
viewModel.IsCollapsed = true;
_expandedResourceNames.Remove(viewModel.Resource.Name);
foreach (var resource in resourcesWithChildren)
{
_collapsedResourceNames.Add(resource.Name);
}
}

await SessionStorage.SetAsync(BrowserStorageKeys.ResourcesCollapsedResourceNames, _collapsedResourceNames.ToList());
await _dataGrid.SafeRefreshDataAsync();
UpdateMenuButtons();
}

private static List<DisplayedEndpoint> GetDisplayedEndpoints(ResourceViewModel resource)
{
return ResourceEndpointHelpers.GetEndpoints(resource, includeInternalUrls: false);
}

private bool HasAnyChildResources()
{
return _resourceByName.Values.Any(r => !string.IsNullOrEmpty(r.GetResourcePropertyValue(KnownProperties.Resource.ParentName)));
}

public async ValueTask DisposeAsync()
{
_watchTaskCancellationTokenSource.Cancel();
Expand Down
16 changes: 15 additions & 1 deletion src/Aspire.Dashboard/Model/BrowserStorage/StorageResult.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;

namespace Aspire.Dashboard.Model.BrowserStorage;

public readonly record struct StorageResult<TValue>(bool Success, TValue? Value);
public readonly struct StorageResult<TValue>
{
[MemberNotNullWhen(true, nameof(Value))]
public bool Success { get; }

public TValue? Value { get; }

public StorageResult(bool success, TValue? value)
{
Success = success;
Value = value;
}
}
5 changes: 2 additions & 3 deletions src/Aspire.Dashboard/Model/ResourceGridViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,12 @@ public sealed class ResourceGridViewModel
public int Depth { get; set; }
public bool IsHidden { get; set; }

private bool _isCollapsed;
public bool IsCollapsed
{
get => _isCollapsed;
get;
set
{
_isCollapsed = value;
field = value;
UpdateHidden();
}
}
Expand Down
18 changes: 18 additions & 0 deletions src/Aspire.Dashboard/Resources/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/Aspire.Dashboard/Resources/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -268,4 +268,10 @@
<data name="ResourceActionTelemetryTooltip" xml:space="preserve">
<value>No telemetry found for this resource.</value>
</data>
<data name="ResourceCollapseAllChildren" xml:space="preserve">
<value>Collapse child resources</value>
</data>
<data name="ResourceExpandAllChildren" xml:space="preserve">
<value>Expand child resources</value>
</data>
</root>
14 changes: 12 additions & 2 deletions src/Aspire.Dashboard/Resources/xlf/Resources.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit d51f233

Please sign in to comment.