File: Model\SpanMenuBuilder.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.Components.CustomIcons;
using Aspire.Dashboard.Components.Dialogs;
using Aspire.Dashboard.Model.Assistant;
using Aspire.Dashboard.Model.Assistant.Prompts;
using Aspire.Dashboard.Model.GenAI;
using Aspire.Dashboard.Otlp.Model;
using Aspire.Dashboard.Otlp.Storage;
using Aspire.Dashboard.Resources;
using Aspire.Dashboard.Utils;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using Microsoft.FluentUI.AspNetCore.Components;
using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons;
 
namespace Aspire.Dashboard.Model;
 
/// <summary>
/// Builds menu items for span context menus and action buttons.
/// </summary>
public sealed class SpanMenuBuilder
{
    private static readonly Icon s_viewDetailsIcon = new Icons.Regular.Size16.Info();
    private static readonly Icon s_structuredLogsIcon = new Icons.Regular.Size16.SlideTextSparkle();
    private static readonly Icon s_gitHubCopilotIcon = new AspireIcons.Size16.GitHubCopilot();
    private static readonly Icon s_genAIIcon = new Icons.Regular.Size16.Sparkle();
    private static readonly Icon s_bracesIcon = new Icons.Regular.Size16.Braces();
 
    private readonly IStringLocalizer<ControlsStrings> _controlsLoc;
    private readonly IStringLocalizer<AIAssistant> _aiAssistantLoc;
    private readonly IStringLocalizer<AIPrompts> _aiPromptsLoc;
    private readonly NavigationManager _navigationManager;
    private readonly IAIContextProvider _aiContextProvider;
    private readonly DashboardDialogService _dialogService;
    private readonly TelemetryRepository _telemetryRepository;
 
    /// <summary>
    /// Initializes a new instance of the <see cref="SpanMenuBuilder"/> class.
    /// </summary>
    public SpanMenuBuilder(
        IStringLocalizer<ControlsStrings> controlsLoc,
        IStringLocalizer<AIAssistant> aiAssistantLoc,
        IStringLocalizer<AIPrompts> aiPromptsLoc,
        NavigationManager navigationManager,
        IAIContextProvider aiContextProvider,
        DashboardDialogService dialogService,
        TelemetryRepository telemetryRepository)
    {
        _controlsLoc = controlsLoc;
        _aiAssistantLoc = aiAssistantLoc;
        _aiPromptsLoc = aiPromptsLoc;
        _navigationManager = navigationManager;
        _aiContextProvider = aiContextProvider;
        _dialogService = dialogService;
        _telemetryRepository = telemetryRepository;
    }
 
    /// <summary>
    /// Adds menu items for a span to the provided list.
    /// </summary>
    /// <param name="menuItems">The list to add menu items to.</param>
    /// <param name="span">The span to create menu items for.</param>
    /// <param name="onViewDetails">Callback when View Details is clicked. Ignored when <paramref name="showViewDetails"/> is <c>false</c>.</param>
    /// <param name="onLaunchGenAI">Callback for launching GenAI details. Only shown when span has GenAI attributes.</param>
    /// <param name="showViewDetails">Whether to include the View Details menu item. Defaults to <c>true</c>.</param>
    public void AddMenuItems(
        List<MenuButtonItem> menuItems,
        OtlpSpan span,
        EventCallback onViewDetails,
        EventCallback<OtlpSpan> onLaunchGenAI,
        bool showViewDetails = true)
    {
        if (showViewDetails)
        {
            menuItems.Add(new MenuButtonItem
            {
                Text = _controlsLoc[nameof(ControlsStrings.ActionViewDetailsText)],
                Icon = s_viewDetailsIcon,
                OnClick = onViewDetails.InvokeAsync
            });
        }
 
        menuItems.Add(new MenuButtonItem
        {
            Text = _controlsLoc[nameof(ControlsStrings.ActionStructuredLogsText)],
            Icon = s_structuredLogsIcon,
            OnClick = () =>
            {
                _navigationManager.NavigateTo(DashboardUrls.StructuredLogsUrl(spanId: span.SpanId));
                return Task.CompletedTask;
            }
        });
 
        if (GenAIHelpers.HasGenAIAttribute(span.Attributes))
        {
            menuItems.Add(new MenuButtonItem
            {
                Text = _controlsLoc[nameof(ControlsStrings.GenAIDetailsTitle)],
                Icon = s_genAIIcon,
                OnClick = () => onLaunchGenAI.InvokeAsync(span)
            });
        }
 
        menuItems.Add(new MenuButtonItem
        {
            Text = _controlsLoc[nameof(ControlsStrings.ExportJson)],
            Icon = s_bracesIcon,
            OnClick = async () =>
            {
                var result = ExportHelpers.GetSpanAsJson(span, _telemetryRepository);
                await TextVisualizerDialog.OpenDialogAsync(new OpenTextVisualizerDialogOptions
                {
                    DialogService = _dialogService,
                    ValueDescription = result.FileName,
                    Value = result.Content,
                    DownloadFileName = result.FileName
                }).ConfigureAwait(false);
            }
        });
 
        if (_aiContextProvider.Enabled)
        {
            menuItems.Add(new MenuButtonItem
            {
                Text = _aiAssistantLoc[nameof(AIAssistant.MenuTextAskGitHubCopilot)],
                Icon = s_gitHubCopilotIcon,
                OnClick = async () =>
                {
                    await _aiContextProvider.LaunchAssistantSidebarAsync(
                        promptContext => PromptContextsBuilder.AnalyzeSpan(
                            promptContext,
                            _aiPromptsLoc.GetString(nameof(AIPrompts.PromptAnalyzeSpan), OtlpHelpers.ToShortenedId(span.SpanId)),
                            span)).ConfigureAwait(false);
                }
            });
        }
    }
}