File: Components\Controls\DashboardRunSelect.razor.cs
Web Access
Project: src\src\Aspire.Dashboard\Aspire.Dashboard.csproj (Aspire.Dashboard)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Dashboard.Model;
using Aspire.Dashboard.Utils;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using Microsoft.FluentUI.AspNetCore.Components;
using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons;
using LayoutResources = Aspire.Dashboard.Resources.Layout;
 
namespace Aspire.Dashboard.Components.Controls;
 
public partial class DashboardRunSelect : ComponentBase
{
    private const string DashboardRunsHelpUrl = "https://aka.ms/aspire/run-persistence";
 
    private static readonly Icon s_checkmarkIcon = new Icons.Regular.Size16.Checkmark();
    private static readonly Icon s_helpIcon = new Icons.Regular.Size16.QuestionCircle();
    private static readonly Icon s_pinIcon = new Icons.Regular.Size16.Pin();
    private static readonly Icon s_pinnedIcon = new Icons.Filled.Size16.Pin();
    private readonly string _runMenuItemIdPrefix = $"dashboard-run-{Guid.NewGuid():N}";
 
    private string RunSelectTitle => Loc[nameof(LayoutResources.DashboardRunSelectTitle)];
    private string RunSelectAccessibleLabel => Loc[nameof(LayoutResources.DashboardRunSelectAccessibleLabel), SelectedRunText];
    private string SelectedRunText => SelectedRunIsCurrent
        ? Loc[nameof(LayoutResources.DashboardRunSelectCurrent)]
        : FormatHelpers.FormatTimeWithOptionalDate(TimeProvider, SelectedRunStartedAtUtc.UtcDateTime);
 
    [Parameter, EditorRequired]
    public required string SelectedRunId { get; set; }
 
    [Parameter]
    public bool SelectedRunIsCurrent { get; set; }
 
    [Parameter]
    public DateTimeOffset SelectedRunStartedAtUtc { get; set; }
 
    [Parameter]
    public EventCallback<string?> SelectedRunIdChanged { get; set; }
 
    [Inject]
    public required IStringLocalizer<LayoutResources> Loc { get; init; }
 
    [Inject]
    public required BrowserTimeProvider TimeProvider { get; init; }
 
    [Inject]
    public required IDashboardRunStore RunStore { get; init; }
 
    [Inject]
    public required ILogger<DashboardRunSelect> Logger { get; init; }
 
    private IList<MenuButtonItem> LoadRuns()
    {
        var runs = GetSortedRuns(RunStore.GetRuns());
 
        var menuItems = new List<MenuButtonItem>();
        foreach (var run in runs)
        {
            var isCompatible = run.IsCompatible;
            var menuItem = new MenuButtonItem
            {
                Id = $"{_runMenuItemIdPrefix}-{Uri.EscapeDataString(run.RunId)}",
                RenderKey = run.RunId,
                Text = FormatRunOption(run),
                Role = MenuItemRole.Radio,
                Checked = string.Equals(run.RunId, SelectedRunId, StringComparison.Ordinal),
                Icon = s_checkmarkIcon,
                IsDisabled = !isCompatible,
                Tooltip = isCompatible ? null : Loc[nameof(LayoutResources.DashboardRunSelectIncompatibleTooltip)].Value,
                SecondaryActionIcon = run.IsPinned ? s_pinnedIcon : s_pinIcon,
                SecondaryActionAriaLabel = Loc[run.IsPinned
                    ? nameof(LayoutResources.DashboardRunSelectUnpin)
                    : nameof(LayoutResources.DashboardRunSelectPin)],
                IsSecondaryActionSelected = run.IsPinned,
                OnSecondaryActionClick = () =>
                {
                    SetRunPinned(run, !run.IsPinned);
                    return Task.CompletedTask;
                },
                OnClick = () => SelectedRunIdChanged.InvokeAsync(run.IsCurrent ? null : run.RunId)
            };
            menuItems.Add(menuItem);
 
            if (run.IsCurrent && runs.Count > 1)
            {
                menuItems.Add(new MenuButtonItem { IsDivider = true });
            }
        }
 
        menuItems.Add(new MenuButtonItem { IsDivider = true });
        menuItems.Add(MenuButtonItem.CreateExternalLink(
            Loc[nameof(LayoutResources.DashboardRunSelectHelp)],
            DashboardRunsHelpUrl,
            s_helpIcon,
            tooltip: Loc[nameof(LayoutResources.DashboardRunSelectHelpTooltip)]));
 
        return menuItems;
    }
 
    internal static List<DashboardRunDescriptor> GetSortedRuns(IReadOnlyList<DashboardRunDescriptor> storedRuns)
    {
        var runs = new List<DashboardRunDescriptor>(storedRuns.Count);
        foreach (var run in storedRuns)
        {
            if (!run.IsPruned && (run.IsSelectable || !run.IsCompatible))
            {
                runs.Add(run);
            }
        }
        runs.Sort(CompareRuns);
 
        return runs;
    }
 
    private static int CompareRuns(DashboardRunDescriptor left, DashboardRunDescriptor right)
    {
        var result = right.IsCurrent.CompareTo(left.IsCurrent);
        if (result == 0)
        {
            result = right.IsPinned.CompareTo(left.IsPinned);
        }
        if (result == 0)
        {
            result = right.StartedAtUtc.CompareTo(left.StartedAtUtc);
        }
        if (result == 0)
        {
            result = string.Compare(left.RunId, right.RunId, StringComparison.Ordinal);
        }
 
        return result;
    }
 
    private void SetRunPinned(DashboardRunDescriptor run, bool isPinned)
    {
        try
        {
            RunStore.SetRunPinned(run, isPinned);
        }
        catch (Exception exception)
        {
            Logger.LogError(exception, "Failed to update the pinned state of dashboard run '{RunId}'.", run.RunId);
        }
    }
 
    private string FormatRunOption(DashboardRunDescriptor run)
    {
        if (run.IsCurrent)
        {
            return Loc[nameof(LayoutResources.DashboardRunSelectCurrent)];
        }
 
        return FormatHelpers.FormatTimeWithOptionalDate(TimeProvider, run.StartedAtUtc.UtcDateTime);
    }
}