Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/Aspire.Dashboard/Components/Layout/MainLayout.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Aspire.Dashboard.Model;
using Aspire.Dashboard.Utils;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using Microsoft.FluentUI.AspNetCore.Components;
Expand Down Expand Up @@ -65,11 +66,16 @@ public partial class MainLayout : IGlobalKeydownListener, IAsyncDisposable
[Inject]
public required ILocalStorage LocalStorage { get; init; }

[Inject]
public required NavigationService NavigationService { get; init; }

[CascadingParameter]
public required ViewportInformation ViewportInformation { get; set; }

protected override async Task OnInitializedAsync()
{
NavigationManager.LocationChanged += OnLocationChanged;

// Theme change can be triggered from the settings dialog. This logic applies the new theme to the browser window.
// Note that this event could be raised from a settings dialog opened in a different browser window.
_themeChangedSubscription = ThemeManager.OnThemeChanged(async () =>
Expand Down Expand Up @@ -274,8 +280,14 @@ private void CloseMobileNavMenu()
StateHasChanged();
}

private void OnLocationChanged(object? sender, LocationChangedEventArgs args)
{
NavigationService.IsFirstPageLoad = false;
}

public async ValueTask DisposeAsync()
{
NavigationManager.LocationChanged -= OnLocationChanged;
_shortcutManagerReference?.Dispose();
_layoutReference?.Dispose();
_themeChangedSubscription?.Dispose();
Expand Down
3 changes: 3 additions & 0 deletions src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ private sealed class ConsoleLogsSubscription
[Inject]
public required NavigationManager NavigationManager { get; init; }

[Inject]
public required NavigationService NavigationService { get; init; }

[Inject]
public required ILogger<ConsoleLogs> Logger { get; init; }

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.Components.Layout;
using Aspire.Dashboard.Model;
using Microsoft.AspNetCore.Components;

namespace Aspire.Dashboard.Components.Pages;
Expand All @@ -27,6 +28,7 @@ public interface IPageWithSessionAndUrlState<TViewModel, TSerializableViewModel>

public NavigationManager NavigationManager { get; }
public ISessionStorage SessionStorage { get; }
public NavigationService NavigationService { get; }

/// <summary>
/// The view model containing live state, to be instantiated in OnInitialized.
Expand Down Expand Up @@ -97,16 +99,23 @@ public static async Task<bool> InitializeViewModelAsync<TViewModel, TSerializabl
var result = await page.SessionStorage.GetAsync<TSerializableViewModel>(page.SessionStorageKey).ConfigureAwait(false);
if (result is { Success: true, Value: not null })
{
var newUrl = page.GetUrlFromSerializableViewModel(result.Value).ToString();

// Don't navigate if the URL redirects to itself.
if (newUrl != "/" + page.BasePath)
if (page.NavigationService.IsFirstPageLoad)
{
await page.SessionStorage.DeleteAsync(page.SessionStorageKey).ConfigureAwait(false);
}
else
{
// Replace the initial address with this navigation.
// We do this because the visit to "/{BasePath}" then redirect to the final address is automatic from the user perspective.
// Replacing the visit to "/{BasePath}" is good because we want to take the user back to where they started, not an intermediary address.
page.NavigationManager.NavigateTo(newUrl, new NavigationOptions { ReplaceHistoryEntry = true });
return true;
var newUrl = page.GetUrlFromSerializableViewModel(result.Value).ToString();

// Don't navigate if the URL redirects to itself.
if (newUrl != "/" + page.BasePath)
{
// Replace the initial address with this navigation.
// We do this because the visit to "/{BasePath}" then redirect to the final address is automatic from the user perspective.
// Replacing the visit to "/{BasePath}" is good because we want to take the user back to where they started, not an intermediary address.
page.NavigationManager.NavigateTo(newUrl, new NavigationOptions { ReplaceHistoryEntry = true });
return true;
}
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/Aspire.Dashboard/Components/Pages/Metrics.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ public partial class Metrics : IDisposable, IPageWithSessionAndUrlState<Metrics.
[Inject]
public required NavigationManager NavigationManager { get; init; }

[Inject]
public required NavigationService NavigationService { get; init; }

[Inject]
public required ISessionStorage SessionStorage { get; init; }

Expand Down
2 changes: 2 additions & 0 deletions src/Aspire.Dashboard/Components/Pages/Resources.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public partial class Resources : ComponentBase, IAsyncDisposable, IPageWithSessi
[Inject]
public required NavigationManager NavigationManager { get; init; }
[Inject]
public required NavigationService NavigationService { get; init; }
[Inject]
public required DashboardCommandExecutor DashboardCommandExecutor { get; init; }
[Inject]
public required BrowserTimeProvider TimeProvider { get; init; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ public partial class StructuredLogs : IPageWithSessionAndUrlState<StructuredLogs
[Inject]
public required NavigationManager NavigationManager { get; init; }

[Inject]
public required NavigationService NavigationService { get; init; }

[Inject]
public required BrowserTimeProvider TimeProvider { get; init; }

Expand Down
3 changes: 3 additions & 0 deletions src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ public partial class TraceDetail : ComponentBase, IDisposable
[Inject]
public required NavigationManager NavigationManager { get; init; }

[Inject]
public required NavigationService NavigationService { get; init; }

protected override void OnInitialized()
{
_gridColumns = [
Expand Down
3 changes: 3 additions & 0 deletions src/Aspire.Dashboard/Components/Pages/Traces.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ public partial class Traces : IPageWithSessionAndUrlState<Traces.TracesPageViewM
[Inject]
public required NavigationManager NavigationManager { get; set; }

[Inject]
public required NavigationService NavigationService { get; init; }

[Inject]
public required ISessionStorage SessionStorage { get; set; }

Expand Down
1 change: 1 addition & 0 deletions src/Aspire.Dashboard/DashboardWebApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ public DashboardWebApplication(
builder.Services.AddScoped<ShortcutManager>();
builder.Services.AddScoped<ConsoleLogsManager>();
builder.Services.AddSingleton<IInstrumentUnitResolver, DefaultInstrumentUnitResolver>();
builder.Services.AddScoped<NavigationService>();

// Time zone is set by the browser.
builder.Services.AddScoped<BrowserTimeProvider>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Aspire.Dashboard.Model.BrowserStorage;

public abstract class BrowserStorageBase : IBrowserStorage
{
private readonly ProtectedBrowserStorage _protectedBrowserStorage;
protected readonly ProtectedBrowserStorage _protectedBrowserStorage;

protected BrowserStorageBase(ProtectedBrowserStorage protectedBrowserStorage, ILogger logger)
{
Expand Down Expand Up @@ -43,4 +43,6 @@ public async Task SetAsync<TValue>(string key, TValue value)
{
await _protectedBrowserStorage.SetAsync(key, value!).ConfigureAwait(false);
}

public abstract Task DeleteAsync(string key);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ public interface IBrowserStorage
{
Task<StorageResult<TValue>> GetAsync<TValue>(string key);
Task SetAsync<TValue>(string key, TValue value);
Task DeleteAsync(string key);
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,7 @@ private ValueTask SetJsonAsync(string key, string json)

private ValueTask<string?> GetJsonAsync(string key)
=> _jsRuntime.InvokeAsync<string?>("localStorage.getItem", key);

public override async Task DeleteAsync(string key)
=> await _jsRuntime.InvokeVoidAsync("localStorage.removeItem", key).ConfigureAwait(false);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,9 @@ public class SessionBrowserStorage : BrowserStorageBase, ISessionStorage
public SessionBrowserStorage(ProtectedSessionStorage protectedSessionStorage, ILogger<SessionBrowserStorage> logger) : base(protectedSessionStorage, logger)
{
}

public override async Task DeleteAsync(string key)
{
await _protectedBrowserStorage.DeleteAsync(key).ConfigureAwait(false);
}
}
9 changes: 9 additions & 0 deletions src/Aspire.Dashboard/Model/NavigationService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// 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;

public sealed class NavigationService
{
public bool IsFirstPageLoad { get; set; } = true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ private void SetupMainLayoutServices(TestLocalStorage? localStorage = null, Mess
Services.AddSingleton<IToastService, ToastService>();
Services.AddSingleton<GlobalState>();
Services.Configure<DashboardOptions>(o => o.Otlp.AuthMode = OtlpAuthMode.Unsecured);
Services.AddSingleton<NavigationService>();

var version = typeof(FluentMain).Assembly.GetName().Version!;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,7 @@ private void SetupConsoleLogsServices(TestDashboardClient? dashboardClient = nul
Services.AddSingleton<DashboardCommandExecutor>();
Services.AddSingleton<ConsoleLogsManager>();
Services.AddSingleton<PauseManager>();
Services.AddSingleton<NavigationService>();
}

private static string GetFluentFile(string filePath, Version version)
Expand Down
32 changes: 22 additions & 10 deletions tests/Aspire.Dashboard.Components.Tests/Pages/MetricsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Aspire.Dashboard.Components.Pages;
using Aspire.Dashboard.Components.Resize;
using Aspire.Dashboard.Components.Tests.Shared;
using Aspire.Dashboard.Model;
using Aspire.Dashboard.Otlp.Model;
using Aspire.Dashboard.Otlp.Storage;
using Aspire.Dashboard.Utils;
Expand Down Expand Up @@ -46,8 +47,10 @@ public void ChangeResource_MeterAndInstrumentNotOnNewResources_InstrumentCleared
expectedInstrumentNameAfterChange: null);
}

[Fact]
public void InitialLoad_HasSessionState_RedirectUsingState()
[Theory]
[InlineData(false, true)]
[InlineData(true, false)] // first page load doesn't redirect
public void HasSessionState_RedirectsCorrectly(bool isFirstPageLoad, bool expectRedirect)
{
// Arrange
var testSessionStorage = new TestSessionStorage
Expand All @@ -74,6 +77,9 @@ public void InitialLoad_HasSessionState_RedirectUsingState()
};
MetricsSetupHelpers.SetupMetricsPage(this, sessionStorage: testSessionStorage);

var navigationService = Services.GetRequiredService<NavigationService>();
navigationService.IsFirstPageLoad = isFirstPageLoad;

var navigationManager = Services.GetRequiredService<NavigationManager>();
navigationManager.NavigateTo(DashboardUrls.MetricsUrl());

Expand Down Expand Up @@ -110,14 +116,20 @@ public void InitialLoad_HasSessionState_RedirectUsingState()
});

// Assert
Assert.NotNull(loadRedirect);
Assert.Equal("/metrics/resource/TestApp", loadRedirect.AbsolutePath);

var query = HttpUtility.ParseQueryString(loadRedirect.Query);
Assert.Equal("test-meter", query["meter"]);
Assert.Equal("test-instrument", query["instrument"]);
Assert.Equal("720", query["duration"]);
Assert.Equal(MetricViewKind.Table.ToString(), query["view"]);
if (expectRedirect)
{
Assert.NotNull(loadRedirect);
Assert.Equal("/metrics/resource/TestApp", loadRedirect.AbsolutePath);
var query = HttpUtility.ParseQueryString(loadRedirect.Query);
Assert.Equal("test-meter", query["meter"]);
Assert.Equal("test-instrument", query["instrument"]);
Assert.Equal("720", query["duration"]);
Assert.Equal(nameof(MetricViewKind.Table), query["view"]);
}
else
{
Assert.Null(loadRedirect);
}
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ private void SetupStructureLogsServices()
Services.AddSingleton<LibraryConfiguration>();
Services.AddSingleton<IKeyCodeService, KeyCodeService>();
Services.AddSingleton<GlobalState>();
Services.AddSingleton<NavigationService>();
}

private static string GetFluentFile(string filePath, Version version)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ private void SetupTraceDetailsServices()
Services.AddSingleton<ShortcutManager>();
Services.AddSingleton<LibraryConfiguration>();
Services.AddSingleton<IKeyCodeService, KeyCodeService>();
Services.AddSingleton<NavigationService>();
}

private static string GetFluentFile(string filePath, Version version)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ internal static void SetupMetricsPage(TestContext context, ISessionStorage? sess
context.Services.AddSingleton<IKeyCodeService, KeyCodeService>();
context.Services.AddSingleton<IThemeResolver, TestThemeResolver>();
context.Services.AddSingleton<ThemeManager>();
context.Services.AddSingleton<NavigationService>();
}

private static string GetFluentFile(string filePath, Version version)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ public static void SetupResourcesPage(TestContext context, ViewportInformation v
context.Services.AddFluentUIComponents();
context.Services.AddScoped<DashboardCommandExecutor, DashboardCommandExecutor>();
context.Services.AddSingleton<IDashboardClient>(dashboardClient ?? new TestDashboardClient(isEnabled: true, initialResources: [], resourceChannelProvider: Channel.CreateUnbounded<IReadOnlyList<ResourceViewModelChange>>));
context.Services.AddSingleton<NavigationService>();

var dimensionManager = context.Services.GetRequiredService<DimensionManager>();
dimensionManager.InvokeOnViewportInformationChanged(viewport);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,9 @@ public Task SetUnprotectedAsync<T>(string key, T value)
}
return Task.CompletedTask;
}

public Task DeleteAsync(string key)
{
return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,9 @@ public Task SetAsync<T>(string key, T value)

return Task.CompletedTask;
}

public Task DeleteAsync(string key)
{
return Task.CompletedTask;
}
}
Loading