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 System.Globalization;
using Aspire.Dashboard.Model;
using Aspire.Shared;
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 static readonly Icon s_historyIcon = new Icons.Regular.Size24.History();
 
    // The button is icon-only: a dot shows whether the live view or a recording is displayed, and the header pill
    // names the recording. The tooltip and accessible label state what is being viewed.
    private string RunSelectTitle => SelectedRunIsCurrent
        ? Loc[nameof(LayoutResources.DashboardRunSelectTitle)]
        : FormatRunDate(SelectedRunStartedAtUtc);
    private string RunSelectAccessibleLabel => Loc[nameof(LayoutResources.DashboardRunSelectAccessibleLabel), SelectedRunText];
    private string SelectedRunText => SelectedRunIsCurrent
        ? Loc[nameof(LayoutResources.DashboardRunSelectCurrent)]
        : FormatRunDate(SelectedRunStartedAtUtc);
 
    [Parameter, EditorRequired]
    public required string ButtonId { get; set; }
 
    [Parameter]
    public bool SelectedRunIsCurrent { get; set; }
 
    [Parameter]
    public DateTimeOffset SelectedRunStartedAtUtc { get; set; }
 
    [Parameter]
    public EventCallback OnClick { get; set; }
 
    [Inject]
    public required IStringLocalizer<LayoutResources> Loc { get; init; }
 
    [Inject]
    public required BrowserTimeProvider TimeProvider { get; init; }
 
    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 string FormatRunDate(DateTimeOffset startedAtUtc) => FormatHelpers.FormatDateTime(TimeProvider, startedAtUtc.UtcDateTime);
 
    internal static string FormatRunAge(DateTimeOffset startedAtUtc, DateTimeOffset utcNow, IStringLocalizer<LayoutResources> loc)
    {
        // Clamp so a start time slightly in the future (clock skew between processes) reads as "a moment ago".
        var age = utcNow - startedAtUtc;
        if (age < TimeSpan.Zero)
        {
            age = TimeSpan.Zero;
        }
 
        return age switch
        {
            { TotalMinutes: < 1 } => loc[nameof(LayoutResources.DashboardRunSelectMomentAgo)],
            { TotalMinutes: < 2 } => loc[nameof(LayoutResources.DashboardRunSelectMinuteAgo)],
            { TotalHours: < 1 } => loc[nameof(LayoutResources.DashboardRunSelectMinutesAgo), (int)age.TotalMinutes],
            { TotalDays: < 1 } => loc[nameof(LayoutResources.DashboardRunSelectHoursAgo), (int)age.TotalHours],
            { TotalDays: < 2 } => loc[nameof(LayoutResources.DashboardRunSelectDayAgo)],
            _ => loc[nameof(LayoutResources.DashboardRunSelectDaysAgo), (int)age.TotalDays]
        };
    }
 
    internal static string? FormatRunDuration(DashboardRunDescriptor run)
    {
        // Runs that didn't shut down cleanly have no end time, so their duration is unknown.
        if (run.IsCurrent || run.EndedAtUtc is not { } endedAtUtc || endedAtUtc < run.StartedAtUtc)
        {
            return null;
        }
 
        // Round to whole seconds: sub-second precision is noise for a run's length.
        var duration = TimeSpan.FromSeconds(Math.Round((endedAtUtc - run.StartedAtUtc).TotalSeconds));
        return $"({DurationFormatter.FormatDuration(duration, CultureInfo.CurrentCulture)})";
    }
}