diff --git a/src/Aspire.Hosting.Azure.Kusto/AzureKustoBuilderExtensions.cs b/src/Aspire.Hosting.Azure.Kusto/AzureKustoBuilderExtensions.cs index 02ccb63f5de..2776afad215 100644 --- a/src/Aspire.Hosting.Azure.Kusto/AzureKustoBuilderExtensions.cs +++ b/src/Aspire.Hosting.Azure.Kusto/AzureKustoBuilderExtensions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. #pragma warning disable AZPROVISION001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +#pragma warning disable ASPIREINTERACTION001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Azure; @@ -11,7 +12,9 @@ using Kusto.Data; using Kusto.Data.Common; using Kusto.Data.Net.Client; +using Kusto.Data.Utils; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; namespace Aspire.Hosting; @@ -36,7 +39,7 @@ public static class AzureKustoBuilderExtensions /// /// /// By default references to the Azure Data Explorer database resources will be assigned the following roles: - /// + /// /// - /// /// @@ -88,6 +91,7 @@ public static IResourceBuilder AddAzureKustoCluster(t var resourceBuilder = builder.AddResource(resource); AddKustoHealthChecksAndLifecycleManagement(resourceBuilder); + AddKustoCustomCommands(resourceBuilder); return resourceBuilder .WithAnnotation(new DefaultRoleAssignmentsAnnotation(new HashSet())); @@ -307,4 +311,100 @@ private static KustoConnectionStringBuilder GetConnectionStringBuilder(AzureKust return builder; } + + private static void AddKustoCustomCommands(IResourceBuilder resourceBuilder) + { + resourceBuilder.WithCommand( + name: "open-kusto-explorer-desktop", + displayName: "Open in Kusto Explorer (Desktop)", + executeCommand: context => OnOpenInKustoExplorerDesktop(resourceBuilder, context), + commandOptions: new CommandOptions + { + UpdateState = UpdateStateDesktop, + IconName = "DatabaseSearch" + }); + + resourceBuilder.WithCommand( + name: "open-kusto-explorer-web", + displayName: "Open in Kusto Explorer (Web)", + executeCommand: context => OnOpenInKustoExplorerWeb(resourceBuilder, context), + commandOptions: new CommandOptions + { + UpdateState = context => UpdateStateWeb(resourceBuilder, context), + IconName = "DatabaseSearch" + }); + + static ResourceCommandState UpdateStateDesktop(UpdateCommandStateContext context) + { + // The Desktop Kusto.Explorer is only available on Windows, so don't show the command on other platforms. + if (!OperatingSystem.IsWindows()) + { + return ResourceCommandState.Hidden; + } + + return context.ResourceSnapshot.HealthStatus is HealthStatus.Healthy ? ResourceCommandState.Enabled : ResourceCommandState.Disabled; + } + + static ResourceCommandState UpdateStateWeb(IResourceBuilder resourceBuilder, UpdateCommandStateContext context) + { + // The web explorer will only auto-connect to allowed domains, so do not show the command when running as an emulator. + if (resourceBuilder.Resource.IsEmulator) + { + return ResourceCommandState.Hidden; + } + + return context.ResourceSnapshot.HealthStatus is HealthStatus.Healthy ? ResourceCommandState.Enabled : ResourceCommandState.Disabled; + } + + static async Task OnOpenInKustoExplorerDesktop(IResourceBuilder resourceBuilder, ExecuteCommandContext context) + { + var connectionString = await resourceBuilder + .Resource + .ConnectionStringExpression + .GetValueAsync(context.CancellationToken) + .ConfigureAwait(false) ?? + throw new DistributedApplicationException($"Connection string for Kusto resource '{resourceBuilder.Resource.Name}' is not set."); + + var launcher = new KustoClientToolLauncher(); + var result = launcher.TryLaunchKustoExplorer(title: "", resourceBuilder.Resource.Name, connectionString, requestText: ""); + + return result ? CommandResults.Success() : CommandResults.Failure("Failed to launch Kusto Explorer"); + } + + static async Task OnOpenInKustoExplorerWeb(IResourceBuilder resourceBuilder, ExecuteCommandContext context) + { + var connectionString = await resourceBuilder + .Resource + .ConnectionStringExpression + .GetValueAsync(context.CancellationToken) + .ConfigureAwait(false) ?? + throw new DistributedApplicationException($"Connection string for Kusto resource '{resourceBuilder.Resource.Name}' is not set."); + + var launcher = new KustoClientToolLauncher(); + var result = launcher.TryLaunchKustoWebExplorer(title: "", resourceBuilder.Resource.Name, connectionString, requestText: ""); + + if (!result) + { + // If the launcher fails (which may mean we're in a remote session or can't detect a browser), + // show a notification with a clickable link to the Kusto Web Explorer + var interactionService = context.ServiceProvider.GetRequiredService(); + if (interactionService.IsAvailable) + { + _ = await interactionService.PromptMessageBoxAsync( + title: "Kusto Web Explorer", + message: $"Could not automatically open Kusto Web Explorer for resource '{resourceBuilder.Resource.Name}'. Click [{connectionString}]({connectionString}) to manually open the Web Explorer.", + new MessageBoxInteractionOptions + { + Intent = MessageIntent.Information, + EnableMessageMarkdown = true, + PrimaryButtonText = "Dismiss", + ShowSecondaryButton = false, + }, + context.CancellationToken).ConfigureAwait(false); + } + } + + return CommandResults.Success(); + } + } }