diff --git a/tools/Environments/DevHome.Environments/Helpers/DataExtractor.cs b/tools/Environments/DevHome.Environments/Helpers/DataExtractor.cs index 4704f599b..698866c28 100644 --- a/tools/Environments/DevHome.Environments/Helpers/DataExtractor.cs +++ b/tools/Environments/DevHome.Environments/Helpers/DataExtractor.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; using System.Collections.Generic; using System.Threading.Tasks; using DevHome.Common.Environments.Models; @@ -98,7 +99,7 @@ public static async Task> FillDotButtonPinOperationsAsync /// Returns the list of operations to be added to the launch button. /// // Compute system used to fill OperationsViewModel's callback function. - public static List FillLaunchButtonOperations(ComputeSystemCache computeSystem) + public static List FillLaunchButtonOperations(ComputeSystemProvider provider, ComputeSystemCache computeSystem, Action? configurationCallback) { var operations = new List(); var supportedOperations = computeSystem.SupportedOperations.Value; @@ -151,6 +152,12 @@ public static List FillLaunchButtonOperations(ComputeSystem _stringResource.GetLocalized("Operations_Terminate"), "\uEE95", computeSystem.TerminateAsync, ComputeSystemOperations.Terminate)); } + if (supportedOperations.HasFlag(ComputeSystemOperations.ApplyConfiguration) && configurationCallback is not null) + { + operations.Add(new OperationsViewModel( + _stringResource.GetLocalized("Operations_ApplyConfiguration"), "\uE835", configurationCallback, provider, computeSystem)); + } + return operations; } } diff --git a/tools/Environments/DevHome.Environments/Strings/en-us/Resources.resw b/tools/Environments/DevHome.Environments/Strings/en-us/Resources.resw index d820b1ecb..4c8051d61 100644 --- a/tools/Environments/DevHome.Environments/Strings/en-us/Resources.resw +++ b/tools/Environments/DevHome.Environments/Strings/en-us/Resources.resw @@ -359,4 +359,8 @@ More options Name of the button that brings up the "More options" menu + + Set up + Value to be shown in the button option to start the configuration flow + \ No newline at end of file diff --git a/tools/Environments/DevHome.Environments/ViewModels/ComputeSystemViewModel.cs b/tools/Environments/DevHome.Environments/ViewModels/ComputeSystemViewModel.cs index 8dfe018f2..9d296e476 100644 --- a/tools/Environments/DevHome.Environments/ViewModels/ComputeSystemViewModel.cs +++ b/tools/Environments/DevHome.Environments/ViewModels/ComputeSystemViewModel.cs @@ -58,6 +58,8 @@ public partial class ComputeSystemViewModel : ComputeSystemCardBase, IRecipient< private bool _disposedValue; + private Action? _configurationAction; + /// /// Initializes a new instance of the class. /// This class requires a 3-step initialization: @@ -73,6 +75,7 @@ public ComputeSystemViewModel( IComputeSystem system, ComputeSystemProvider provider, Func removalAction, + Action? configurationAction, string packageFullName, Window window) { @@ -83,6 +86,7 @@ public ComputeSystemViewModel( ComputeSystem = new(system); PackageFullName = packageFullName; _removalAction = removalAction; + _configurationAction = configurationAction; _stringResource = new StringResource("DevHome.Environments.pri", "DevHome.Environments/Resources"); } @@ -133,7 +137,7 @@ private async Task InitializeOperationDataAsync() ShouldShowDotOperations = false; ShouldShowSplitButton = false; - RegisterForAllOperationMessages(DataExtractor.FillDotButtonOperations(ComputeSystem, _mainWindow), DataExtractor.FillLaunchButtonOperations(ComputeSystem)); + RegisterForAllOperationMessages(DataExtractor.FillDotButtonOperations(ComputeSystem, _mainWindow), DataExtractor.FillLaunchButtonOperations(_provider, ComputeSystem, _configurationAction)); _ = Task.Run(async () => { diff --git a/tools/Environments/DevHome.Environments/ViewModels/CreateComputeSystemOperationViewModel.cs b/tools/Environments/DevHome.Environments/ViewModels/CreateComputeSystemOperationViewModel.cs index b64cf11db..d988677e6 100644 --- a/tools/Environments/DevHome.Environments/ViewModels/CreateComputeSystemOperationViewModel.cs +++ b/tools/Environments/DevHome.Environments/ViewModels/CreateComputeSystemOperationViewModel.cs @@ -160,6 +160,7 @@ private async void AddComputeSystemToUI(CreateComputeSystemResult result) result.ComputeSystem, Operation.ProviderDetails.ComputeSystemProvider, _removalAction, + null, Operation.ProviderDetails.ExtensionWrapper.PackageFullName, _mainWindow); diff --git a/tools/Environments/DevHome.Environments/ViewModels/LandingPageViewModel.cs b/tools/Environments/DevHome.Environments/ViewModels/LandingPageViewModel.cs index 5fa802206..87ca6b37f 100644 --- a/tools/Environments/DevHome.Environments/ViewModels/LandingPageViewModel.cs +++ b/tools/Environments/DevHome.Environments/ViewModels/LandingPageViewModel.cs @@ -152,6 +152,18 @@ public void CallToActionInvokeButton() _navigationService.NavigateTo(KnownPageKeys.SetupFlow, "startCreationFlow;EnvironmentsLandingPage"); } + public void ConfigureComputeSystem(ComputeSystemReviewItem item) + { + _log.Information("User clicked on the setup button. Navigating to the Setup an Environment page in Setup flow"); + object[] parameters = { "StartConfigurationFlow", "EnvironmentsLandingPage", item }; + + // Run on the UI thread + _mainWindow.DispatcherQueue.EnqueueAsync(() => + { + _navigationService.NavigateTo(KnownPageKeys.SetupFlow, parameters); + }); + } + // Updates the last sync time on the UI thread after set delay private async Task UpdateLastSyncTimeUI(string time, TimeSpan delay, CancellationToken token) { @@ -337,6 +349,7 @@ private async Task AddAllComputeSystemsFromAProvider(ComputeSystemsLoadedData da computeSystem, provider, RemoveComputeSystemCard, + ConfigureComputeSystem, packageFullName, _mainWindow); diff --git a/tools/Environments/DevHome.Environments/ViewModels/OperationsViewModel.cs b/tools/Environments/DevHome.Environments/ViewModels/OperationsViewModel.cs index 7c25a61a1..6bf6a2920 100644 --- a/tools/Environments/DevHome.Environments/ViewModels/OperationsViewModel.cs +++ b/tools/Environments/DevHome.Environments/ViewModels/OperationsViewModel.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; +using DevHome.Common.Environments.Models; using DevHome.Common.Services; using DevHome.Environments.Models; using Microsoft.UI.Xaml; @@ -37,6 +38,10 @@ public partial class OperationsViewModel : IEquatable private readonly string _additionalContext = string.Empty; + private readonly Window? _mainWindow; + + private readonly StringResource _stringResource = new("DevHome.Environments.pri", "DevHome.Environments/Resources"); + public string Name { get; } public ComputeSystemOperations ComputeSystemOperation { get; } @@ -47,9 +52,9 @@ public partial class OperationsViewModel : IEquatable private Action? DevHomeAction { get; } - private readonly Window? _mainWindow; + private Action? DevHomeActionWithReviewItem { get; } - private readonly StringResource _stringResource = new("DevHome.Environments.pri", "DevHome.Environments/Resources"); + private ComputeSystemReviewItem? _item; public OperationsViewModel( string name, @@ -89,6 +94,20 @@ public OperationsViewModel(string name, string icon, Action command) DevHomeAction = command; } + public OperationsViewModel( + string name, + string icon, + Action? command, + ComputeSystemProvider provider, + ComputeSystemCache cache) + { + _operationKind = OperationKind.DevHomeAction; + Name = name; + IconGlyph = icon; + DevHomeActionWithReviewItem = command; + _item = new(cache, provider); + } + private void RunAction() { // To Do: Need to disable the card UI while the operation is in progress and handle failures. @@ -96,7 +115,15 @@ private void RunAction() { if (_operationKind == OperationKind.DevHomeAction) { - DevHomeAction!(); + if (DevHomeAction != null) + { + DevHomeAction(); + } + else if (DevHomeActionWithReviewItem != null && _item != null) + { + DevHomeActionWithReviewItem(_item); + } + return; } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/ConfigureTargetTask.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/ConfigureTargetTask.cs index 2e7f3ec3f..bb9fbf0cf 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Models/ConfigureTargetTask.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/ConfigureTargetTask.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using CommunityToolkit.WinUI; +using DevHome.Common.Environments.Models; using DevHome.Common.Environments.Services; using DevHome.Common.Extensions; using DevHome.Common.Services; diff --git a/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/SetupTargetTaskGroup.cs b/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/SetupTargetTaskGroup.cs index 7bef0ace6..fc492807d 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/SetupTargetTaskGroup.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/SetupTargetTaskGroup.cs @@ -13,6 +13,7 @@ namespace DevHome.SetupFlow.TaskGroups; public class SetupTargetTaskGroup : ISetupTaskGroup { private readonly SetupTargetViewModel _setupTargetViewModel; + private readonly SetupTargetReviewViewModel _setupTargetReviewViewModel; private readonly ConfigureTargetTask _setupTargetTaskGroup; diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/MainPageViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/MainPageViewModel.cs index 0c1e49042..e204853b6 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/MainPageViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/MainPageViewModel.cs @@ -7,6 +7,8 @@ using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using DevHome.Common.Environments.Models; +using DevHome.Common.Environments.Services; using DevHome.Common.Extensions; using DevHome.Common.Models; using DevHome.Common.Services; @@ -42,6 +44,7 @@ public partial class MainPageViewModel : SetupPageViewModelBase, IDisposable private readonly IWinGet _winget; private readonly IDSC _dsc; private readonly IExperimentationService _experimentationService; + private readonly IComputeSystemManager _computeSystemManager; public MainPageBannerViewModel BannerViewModel { get; } @@ -80,13 +83,15 @@ public MainPageViewModel( IDSC dsc, IHost host, MainPageBannerViewModel bannerViewModel, - IExperimentationService experimentationService) + IExperimentationService experimentationService, + IComputeSystemManager computeSystemManager) : base(stringResource, orchestrator) { _host = host; _winget = winget; _dsc = dsc; _experimentationService = experimentationService; + _computeSystemManager = computeSystemManager; IsNavigationBarVisible = false; IsStepPage = false; @@ -226,6 +231,38 @@ private void StartSetupForTargetEnvironment(string flowTitle) _host.GetService()); } + /// + /// Starts the setup target flow from the environments page. + /// + public void StartSetupForTargetEnvironmentWithTelemetry(string flowTitle, string navigationAction, string originPage, ComputeSystemReviewItem item) + { + var setupTask = _host.GetService(); + + _log.Information("Starting setup for target environment from the Environments page"); + StartSetupFlowForTaskGroups( + flowTitle, + "SetupTargetEnvironment", + setupTask, + _host.GetService(), + _host.GetService()); + + TelemetryFactory.Get().Log( + "Setup_Environment_button_Clicked", + LogLevel.Measure, + new EnvironmentRedirectionUserEvent(navigationAction: navigationAction, originPage), + relatedActivityId: Orchestrator.ActivityId); + + Orchestrator.GoToNextPage().GetAwaiter().GetResult(); + + // We add the target environment to the setup task after because the constructor flow + // of the setup task group sets the target environment to null on the main thread. + // We move it to a background thread so that there isn't a race condition with the main thread. + Task.Run(() => + { + _computeSystemManager.ComputeSystemSetupItem = item; + }); + } + /// /// Starts a setup flow that only includes repo config. /// diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupFlowViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupFlowViewModel.cs index ac5b00063..57c548d2d 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupFlowViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupFlowViewModel.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using DevHome.Common.Environments.Models; using DevHome.Common.Extensions; using DevHome.Common.Services; using DevHome.Common.TelemetryEvents.SetupFlow; @@ -29,6 +30,8 @@ public partial class SetupFlowViewModel : ObservableObject private readonly string _creationFlowNavigationParameter = "StartCreationFlow"; + private readonly string _configurationFlowNavigationParameter = "StartConfigurationFlow"; + public SetupFlowOrchestrator Orchestrator { get; } public event EventHandler EndSetupFlow = (s, e) => { }; @@ -123,7 +126,7 @@ public async Task StartFileActivationFlowAsync(StorageFile file) await _mainPageViewModel.StartConfigurationFileAsync(file); } - public void StartCreationFlowAsync(string originPage) + public void StartCreationFlow(string originPage) { Orchestrator.FlowPages = [_mainPageViewModel]; @@ -131,9 +134,17 @@ public void StartCreationFlowAsync(string originPage) _mainPageViewModel.StartCreateEnvironmentWithTelemetry(string.Empty, _creationFlowNavigationParameter, originPage); } + public void StartSetupFlow(string originPage, ComputeSystemReviewItem item) + { + Orchestrator.FlowPages = [_mainPageViewModel]; + + // This method is only called when the user clicks a button that redirects them to 'Setup' flow in the Environments page. + _mainPageViewModel.StartSetupForTargetEnvironmentWithTelemetry(string.Empty, _configurationFlowNavigationParameter, originPage, item); + } + public void OnNavigatedTo(NavigationEventArgs args) { - // The setup flow isn't setup to support using the navigation service to navigate to specific + // The setup flow isn't set up to support using the navigation service to navigate to specific // pages. Instead we need to navigate to the main page and then start the creation flow template manually. var parameter = args.Parameter?.ToString(); @@ -146,7 +157,19 @@ public void OnNavigatedTo(NavigationEventArgs args) // and the second value being the page name that redirection came from for telemetry purposes. var parameters = parameter.Split(';'); Cancel(); - StartCreationFlowAsync(originPage: parameters[1]); + StartCreationFlow(originPage: parameters[1]); + } + else if (args.Parameter is object[] configObjs && configObjs.Length == 3) + { + if (configObjs[0] is string configObj && configObj.Equals(_configurationFlowNavigationParameter, StringComparison.OrdinalIgnoreCase)) + { + // We expect that when navigating from anywhere in Dev Home to the create environment page + // that the arg.Parameter variable be an object array with the the first value being 'StartCreationFlow', + // the second value being the page name that redirection came from for telemetry purposes, and + // the third value being the ComputeSystemReviewItem to setup. + Cancel(); + StartSetupFlow(originPage: configObjs[1] as string, item: configObjs[2] as ComputeSystemReviewItem); + } } }