From 70b3d8374616ee5f4aa8830bd16a34c72017e40b Mon Sep 17 00:00:00 2001 From: Branden Bonaby <105318831+bbonaby@users.noreply.github.com> Date: Thu, 3 Oct 2024 10:08:30 -0700 Subject: [PATCH] Fix multi user extension login for setup environment and create environment flows (#3907) * Fix multi user login for setup target and create environment flows * update based on comments. Removes Add/Remove event handlers and instead used the weak event listener --- .../DevHomeChoiceSetWithDynamicRefresh.cs | 26 +-- .../Assets/DarkHostConfig.json | 2 +- .../Assets/LightHostConfig.json | 2 +- .../CreationStateKindToVisibilityConverter.cs | 57 +++++++ .../Strings/en-us/Resources.resw | 8 + .../Utilities/DevDriveUtil.cs | 4 +- .../ComputeSystemCardViewModel.cs | 7 +- .../ComputeSystemsListViewModel.cs | 21 ++- .../EnvironmentCreationOptionsViewModel.cs | 156 ++++++++++++------ .../ViewModels/SetupTargetViewModel.cs | 82 ++++----- .../EnvironmentCreationOptionsView.xaml | 67 +++++++- 11 files changed, 304 insertions(+), 128 deletions(-) create mode 100644 tools/SetupFlow/DevHome.SetupFlow/Converters/CreationStateKindToVisibilityConverter.cs diff --git a/common/Renderers/DevHomeChoiceSetWithDynamicRefresh.cs b/common/Renderers/DevHomeChoiceSetWithDynamicRefresh.cs index f320e84802..e0b52398b6 100644 --- a/common/Renderers/DevHomeChoiceSetWithDynamicRefresh.cs +++ b/common/Renderers/DevHomeChoiceSetWithDynamicRefresh.cs @@ -5,6 +5,7 @@ using System.Text.Json; using AdaptiveCards.ObjectModel.WinUI3; using AdaptiveCards.Rendering.WinUI3; +using CommunityToolkit.WinUI.Helpers; using DevHome.Common.DevHomeAdaptiveCards.CardModels; using DevHome.Common.DevHomeAdaptiveCards.InputValues; using Microsoft.UI.Xaml; @@ -85,9 +86,14 @@ private Grid SetupParentComboBoxForDynamicRefresh(AdaptiveChoiceSetInput choiceS comboBox.ItemsSource = listOfComboBoxItems; comboBox.SelectedIndex = int.TryParse(choiceSet.Value, out var selectedIndex) ? selectedIndex : UnSelectedIndex; - // Setup event handlers - comboBox.SelectionChanged += RefreshCardOnSelectionChanged; - comboBox.Unloaded += RemoveEventHandler; + // Setup selection changed weak event handler + var selectionChangedWeakRef = new WeakEventListener(comboBox) + { + OnEventAction = (instance, source, args) => RefreshCardOnSelectionChanged(instance, args), + OnDetachAction = (weakEventListener) => comboBox.SelectionChanged -= weakEventListener.OnEvent, + }; + + comboBox.SelectionChanged += selectionChangedWeakRef.OnEvent; // Use the choiceSets Id as the name of the combo box. comboBox.Name = choiceSet.Id; @@ -224,20 +230,6 @@ private void RefreshCardOnSelectionChanged(object sender, SelectionChangedEventA } } - private void RemoveEventHandler(object sender, object args) - { - if (sender is ComboBox parentComboBox) - { - parentComboBox.SelectionChanged -= RefreshCardOnSelectionChanged; - parentComboBox.Unloaded -= RemoveEventHandler; - _childChoiceSetDataForOnParentSelectionChanged.Remove(parentComboBox); - if (_choiceSetParentIdToChildIdMap.TryGetValue(parentComboBox.Name, out var childComboBoxId)) - { - _choiceSetIdToUIElementMap.Remove(childComboBoxId); - } - } - } - private void UpdateComboBoxWithDynamicData(ComboBox parentComboBox, ComboBox childComboBox) { _childChoiceSetDataForOnParentSelectionChanged.TryGetValue(parentComboBox, out var newDataForChildComboBox); diff --git a/settings/DevHome.Settings/Assets/DarkHostConfig.json b/settings/DevHome.Settings/Assets/DarkHostConfig.json index a3a18d1067..a5c39a22f4 100644 --- a/settings/DevHome.Settings/Assets/DarkHostConfig.json +++ b/settings/DevHome.Settings/Assets/DarkHostConfig.json @@ -4,7 +4,7 @@ "fontFamily": "'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif", "fontSizes": { "small": 12, - "default": 12, + "default": 14, "medium": 14, "large": 18, "extraLarge": 26 diff --git a/settings/DevHome.Settings/Assets/LightHostConfig.json b/settings/DevHome.Settings/Assets/LightHostConfig.json index 28841b433b..5f01cdfe86 100644 --- a/settings/DevHome.Settings/Assets/LightHostConfig.json +++ b/settings/DevHome.Settings/Assets/LightHostConfig.json @@ -4,7 +4,7 @@ "fontFamily": "'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif", "fontSizes": { "small": 12, - "default": 12, + "default": 14, "medium": 14, "large": 18, "extraLarge": 26 diff --git a/tools/SetupFlow/DevHome.SetupFlow/Converters/CreationStateKindToVisibilityConverter.cs b/tools/SetupFlow/DevHome.SetupFlow/Converters/CreationStateKindToVisibilityConverter.cs new file mode 100644 index 0000000000..c0d3bb52b5 --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow/Converters/CreationStateKindToVisibilityConverter.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DevHome.Common.Environments.Models; +using DevHome.SetupFlow.ViewModels.Environments; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Media; + +namespace DevHome.SetupFlow.Converters; + +/// +/// Converts the state of the EnvironmentCreationOptions page to a visibility enum. +/// +public class CreationStateKindToVisibilityConverter : IValueConverter +{ + private const string ProgressRingGridName = "ProgressRingGrid"; + + private const string AdaptiveCardGridName = "AdaptiveCardGrid"; + + public object Convert(object value, Type targetType, object parameter, string language) + { + var parameterString = (string)parameter; + var creationStateKind = (CreationPageStateKind)value; + + if (parameterString.Equals(AdaptiveCardGridName, StringComparison.Ordinal)) + { + return creationStateKind switch + { + CreationPageStateKind.InitialPageAdaptiveCardLoaded => Visibility.Visible, + _ => Visibility.Collapsed, + }; + } + + if (parameterString.Equals(ProgressRingGridName, StringComparison.Ordinal)) + { + return creationStateKind switch + { + CreationPageStateKind.InitialPageAdaptiveCardLoading => Visibility.Visible, + CreationPageStateKind.OtherPageAdaptiveCardLoading => Visibility.Visible, + _ => Visibility.Collapsed, + }; + } + + return Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } +} diff --git a/tools/SetupFlow/DevHome.SetupFlow/Strings/en-us/Resources.resw b/tools/SetupFlow/DevHome.SetupFlow/Strings/en-us/Resources.resw index 1cf7dcdc5b..c841bfdcff 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Strings/en-us/Resources.resw +++ b/tools/SetupFlow/DevHome.SetupFlow/Strings/en-us/Resources.resw @@ -1820,6 +1820,10 @@ There was an error loading the data required to create {0} environments Locked={"{0}"} Error text display when we are unable to retrieve the creation data for a Dev Home provider. {0} is the name of the provider + + There was an error loading {0} environments for '{1}'. + Locked={"{0}", "{1}"} Error text to display when a Dev Home provider returns an error when returning environments for a specific user. {0} is the name of the provider and {1} is the users logon Id. + Environment Title for create environment review tab @@ -2035,4 +2039,8 @@ Please check your network settings and try again. Text displayed when there is no internet connection + + Select an account + Header text for combo box where the user can select an account + \ No newline at end of file diff --git a/tools/SetupFlow/DevHome.SetupFlow/Utilities/DevDriveUtil.cs b/tools/SetupFlow/DevHome.SetupFlow/Utilities/DevDriveUtil.cs index 474a5c1c5c..da9265f684 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Utilities/DevDriveUtil.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Utilities/DevDriveUtil.cs @@ -6,11 +6,11 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; -using CommunityToolkit.Common; using Serilog; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.UI.Shell; +using static CommunityToolkit.Common.Converters; namespace DevHome.SetupFlow.Utilities; @@ -269,7 +269,7 @@ public static string ConvertBytesToString(ulong sizeInBytes) if (result != 0) { // fallback to using community toolkit which shows this unlocalized. In the form of 50 GB, 40 TB etc. - return Converters.ToFileSizeString((long)sizeInBytes); + return ToFileSizeString((long)sizeInBytes); } else { diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/ComputeSystemCardViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/ComputeSystemCardViewModel.cs index 82eaf9ba3c..9784a8c87b 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/ComputeSystemCardViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/ComputeSystemCardViewModel.cs @@ -72,7 +72,12 @@ public ComputeSystemCardViewModel(ComputeSystemCache computeSystem, IComputeSyst _dispatcherQueue = dispatcherQueue; _computeSystemManager = manager; ComputeSystemTitle = computeSystem.DisplayName.Value; - ComputeSystemAlternativeTitle = computeSystem.SupplementalDisplayName.Value; + + if (!string.IsNullOrEmpty(computeSystem.SupplementalDisplayName.Value)) + { + ComputeSystemAlternativeTitle = $"({computeSystem.SupplementalDisplayName.Value})"; + } + ComputeSystem = computeSystem; ComputeSystem.StateChanged += _computeSystemManager.OnComputeSystemStateChanged; _computeSystemManager.ComputeSystemStateChanged += OnComputeSystemStateChanged; diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/ComputeSystemsListViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/ComputeSystemsListViewModel.cs index 33d26bda41..4877db56c5 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/ComputeSystemsListViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/ComputeSystemsListViewModel.cs @@ -48,8 +48,6 @@ public partial class ComputeSystemsListViewModel : ObservableObject public AdvancedCollectionView ComputeSystemCardAdvancedCollectionView { get; private set; } - public Dictionary DevIdToComputeSystemMap { get; set; } - public ComputeSystemProvider Provider { get; set; } public DeveloperIdWrapper CurrentDeveloperId { get; set; } @@ -101,21 +99,22 @@ public override string ToString() return AccessibilityName; } - public ComputeSystemsListViewModel(ComputeSystemsLoadedData loadedData) + public ComputeSystemsListViewModel( + ComputeSystemProviderDetails providerDetails, + KeyValuePair devIdComputeSystemPair) { - Provider = loadedData.ProviderDetails.ComputeSystemProvider; - DevIdToComputeSystemMap = loadedData.DevIdToComputeSystemMap; - - // Get the first developerId and compute system result. - var devIdToResultKeyValuePair = DevIdToComputeSystemMap.FirstOrDefault(); - CurrentResult = devIdToResultKeyValuePair.Value; - CurrentDeveloperId = devIdToResultKeyValuePair.Key; + Provider = providerDetails.ComputeSystemProvider; + CurrentResult = devIdComputeSystemPair.Value; + CurrentDeveloperId = devIdComputeSystemPair.Key; DisplayName = Provider.DisplayName; if ((CurrentResult != null) && (CurrentResult.ComputeSystems != null)) { - ComputeSystems = CurrentResult.ComputeSystems.Select(computeSystem => new ComputeSystemCache(computeSystem)).ToList(); + // Filter out compute systems that do not support configuration. + ComputeSystems = CurrentResult.ComputeSystems + .Where(computeSystem => computeSystem.SupportedOperations.HasFlag(ComputeSystemOperations.ApplyConfiguration)) + .Select(computeSystem => new ComputeSystemCache(computeSystem)).ToList(); } // Create a new AdvancedCollectionView for the ComputeSystemCards collection. diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/EnvironmentCreationOptionsViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/EnvironmentCreationOptionsViewModel.cs index 8091d14162..c0b8f63395 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/EnvironmentCreationOptionsViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/EnvironmentCreationOptionsViewModel.cs @@ -2,6 +2,8 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; using AdaptiveCards.ObjectModel.WinUI3; @@ -19,11 +21,22 @@ using DevHome.SetupFlow.Models.Environments; using DevHome.SetupFlow.Services; using Microsoft.UI.Dispatching; +using Microsoft.UI.Xaml; using Microsoft.Windows.DevHome.SDK; using Serilog; namespace DevHome.SetupFlow.ViewModels.Environments; +public enum CreationPageStateKind +{ + AccountSelection, + InitialPageAdaptiveCardError, + InitialPageAdaptiveCardLoaded, + InitialPageAdaptiveCardLoading, + OtherPageAdaptiveCardLoading, + OtherPageAdaptiveCardLoaded, +} + /// /// View model for the Configure Environment page in the setup flow. This page will display an adaptive card that is provided by the selected /// compute system provider. The adaptive card will be display in the middle of the page and will contain compute system provider specific UI @@ -43,7 +56,7 @@ public partial class EnvironmentCreationOptionsViewModel : SetupPageViewModelBas private readonly SetupFlowViewModel _setupFlowViewModel; - private readonly IStringResource _stringResource; + private readonly IStringResource _stringResource; private ComputeSystemProviderDetails _curProviderDetails; @@ -59,17 +72,22 @@ public partial class EnvironmentCreationOptionsViewModel : SetupPageViewModelBas private AdaptiveInputs _userInputsFromAdaptiveCard; - private bool _isFirstTimeLoadingNewFlow; + [ObservableProperty] + private string _sessionErrorMessage; + [ObservableProperty] + private string _sessionErrorTitle; + + [NotifyPropertyChangedFor(nameof(DeveloperIdWrappers))] [NotifyCanExecuteChangedFor(nameof(GoToNextPageCommand))] [ObservableProperty] - private bool _isAdaptiveCardSessionLoaded; + private CreationPageStateKind _creationPageState; [ObservableProperty] - private string _sessionErrorMessage; + private ObservableCollection _developerIdWrappers = new(); [ObservableProperty] - private string _sessionErrorTitle; + private DeveloperIdWrapper _selectedDeveloperId; [ObservableProperty] private string _adaptiveCardLoadingMessage; @@ -105,7 +123,6 @@ public EnvironmentCreationOptionsViewModel( _elementRegistration.Set(DevHomeContentDialogContent.AdaptiveElementType, new DevHomeContentDialogContentParser()); _adaptiveCardRenderingService = renderingService; Orchestrator.CurrentSetupFlowKind = SetupFlowKind.CreateEnvironment; - _isFirstTimeLoadingNewFlow = true; } /// @@ -139,11 +156,13 @@ protected async override Task OnFirstNavigateToAsync() /// protected async override Task OnEachNavigateToAsync() { + ResetErrorUI(); + // Don't get a new adaptive card session if we went from review page back to // configure environment creation page. if (Orchestrator.IsNavigatingBackward) - { - ResetErrorUI(); + { + CreationPageState = CreationPageStateKind.InitialPageAdaptiveCardLoading; Orchestrator.AdaptiveCardFlowNavigator.GoToPreviousPage(); return; } @@ -153,21 +172,45 @@ protected async override Task OnEachNavigateToAsync() CanGoToNextPage = false; var curSelectedProviderId = _curProviderDetails?.ComputeSystemProvider?.Id ?? string.Empty; var upcomingSelectedProviderId = _upcomingProviderDetails?.ComputeSystemProvider?.Id; - _isFirstTimeLoadingNewFlow = true; // Selected compute system provider changed so we need to update the adaptive card in the UI // with a new adaptive card from the new provider. _curProviderDetails = _upcomingProviderDetails; - - IsAdaptiveCardSessionLoaded = false; + SelectedDeveloperId = new DeveloperIdWrapper(new EmptyDeveloperId()); AdaptiveCardLoadingMessage = StringResource.GetLocalized(StringResourceKey.EnvironmentCreationUILoadingMessage, _curProviderDetails.ComputeSystemProvider.DisplayName); + DeveloperIdWrappers.Clear(); + CreationPageState = CreationPageStateKind.AccountSelection; + + // The DeveloperId count will always be at least one for every provider. If there is only one, + // then the select account combo box will not be shown and the adaptive card for this provider will be shown. + if (_curProviderDetails.DeveloperIds.Count == 1) + { + PopulateAdaptiveCardSectionAsync(_curProviderDetails.DeveloperIds[0].DeveloperId); + return; + } + + // If execution ends here, the view will only show a combo box with a list of developerIds. + DeveloperIdWrappers = new(_curProviderDetails.DeveloperIds.OrderBy(wrapper => wrapper.LoginId)); + } + + [RelayCommand] + public void DeveloperIdSelected(DeveloperIdWrapper wrapper) + { + PopulateAdaptiveCardSectionAsync(wrapper.DeveloperId); + } + + private void PopulateAdaptiveCardSectionAsync(IDeveloperId developerId) + { + CreationPageState = CreationPageStateKind.InitialPageAdaptiveCardLoading; // Its possible that an extension could take a long time to load the adaptive card session. // So we run this on a background thread to prevent the UI from freezing. _ = Task.Run(() => { - var developerIdWrapper = _curProviderDetails.DeveloperIds.First(); - var result = _curProviderDetails.ComputeSystemProvider.CreateAdaptiveCardSessionForDeveloperId(developerIdWrapper.DeveloperId, ComputeSystemAdaptiveCardKind.CreateComputeSystem); + var result = _curProviderDetails.ComputeSystemProvider.CreateAdaptiveCardSessionForDeveloperId( + developerId, + ComputeSystemAdaptiveCardKind.CreateComputeSystem); + UpdateExtensionAdaptiveCard(result); }); } @@ -207,10 +250,11 @@ public void UpdateExtensionAdaptiveCard(ComputeSystemAdaptiveCardResult adaptive var result = _extensionAdaptiveCardSession.Initialize(_extensionAdaptiveCard); if (result.Status == ProviderOperationStatus.Failure) { - _log.Error(result.ExtendedError, $"Extension failed to generate adaptive card. DisplayMsg: {result.DisplayMessage}, DiagnosticMsg: {result.DiagnosticText}"); + _log.Error(result.ExtendedError, $"Extension failed to generate adaptive card. DisplayMessage: {result.DisplayMessage}, DiagnosticMessage: {result.DiagnosticText}"); SessionErrorTitle = _stringResource.GetLocalized("ErrorLoadingCreationUITitle", _curProviderDetails.ComputeSystemProvider.DisplayName); SessionErrorMessage = result.DisplayMessage; CanGoToNextPage = false; + CreationPageState = CreationPageStateKind.InitialPageAdaptiveCardError; } else { @@ -220,8 +264,10 @@ public void UpdateExtensionAdaptiveCard(ComputeSystemAdaptiveCardResult adaptive catch (Exception ex) { _log.Error(ex, $"Failed to get creation options adaptive card from provider {_curProviderDetails.ComputeSystemProvider.Id}."); - SessionErrorTitle = _stringResource.GetLocalized("ErrorLoadingCreationUITitle", _curProviderDetails.ComputeSystemProvider.DisplayName); + var displayName = _curProviderDetails.ComputeSystemProvider.DisplayName; + SessionErrorTitle = _stringResource.GetLocalized("ErrorLoadingCreationUIForDeveloperId", displayName, SelectedDeveloperId.LoginId); SessionErrorMessage = ex.Message; + CreationPageState = CreationPageStateKind.InitialPageAdaptiveCardError; } }); } @@ -239,17 +285,16 @@ public void OnAdaptiveCardUpdated(object sender, AdaptiveCard adaptiveCard) _renderedAdaptiveCard = _adaptiveCardRenderer.RenderAdaptiveCard(adaptiveCard); _renderedAdaptiveCard.Action += OnRenderedAdaptiveCardAction; _userInputsFromAdaptiveCard = _renderedAdaptiveCard.UserInputs; - - // Only send the adaptive card to the creation options view when the user moves - // from the extension selection page to the environment creation options page. - // All other times after that will happen when the user clicks the "Previous" - // button in the setup flow, in which case sending the adaptive card to the creation - // options view will be done in the OnRenderedAdaptiveCardAction method below. - if (_isFirstTimeLoadingNewFlow) + + if (ShouldAdaptiveCardAppearOnInitialPage()) + { + SendAdaptiveCardToCurrentView(); + CreationPageState = CreationPageStateKind.InitialPageAdaptiveCardLoaded; + CanGoToNextPage = true; + } + else { - SendAdaptiveCardToCurrentView(); - StopShowingLoadingUI(); - _isFirstTimeLoadingNewFlow = false; + CreationPageState = CreationPageStateKind.OtherPageAdaptiveCardLoaded; } }); } @@ -265,31 +310,38 @@ private void OnRenderedAdaptiveCardAction(object sender, AdaptiveActionEventArgs { _dispatcherQueue.TryEnqueue(async () => { - IsAdaptiveCardSessionLoaded = false; ResetErrorUI(); AdaptiveCardLoadingMessage = StringResource.GetLocalized(StringResourceKey.EnvironmentCreationUILoadingMessage, _curProviderDetails.ComputeSystemProvider.DisplayName); // Send the inputs and actions that the user entered back to the extension. var result = await _extensionAdaptiveCardSession.OnAction(args.Action.ToJson().Stringify(), args.Inputs.AsJson().Stringify()); - var actionFailed = result.Status == ProviderOperationStatus.Failure; - - if (actionFailed) - { - _log.Error(result.ExtendedError, $"Extension failed to generate adaptive card. DisplayMsg: {result.DisplayMessage}, DiagnosticMsg: {result.DiagnosticText}"); + var actionFailed = result.Status == ProviderOperationStatus.Failure; + + if (actionFailed) + { + _log.Error(result.ExtendedError, $"Extension failed to generate adaptive card. DisplayMsg: {result.DisplayMessage}, DiagnosticMsg: {result.DiagnosticText}"); SessionErrorMessage = result.DisplayMessage; - } - if (IsCurrentPage && !actionFailed) - { - // Move Setup flow forward if the adaptive card Id that was clicked was the next button. - if (Orchestrator.AdaptiveCardFlowNavigator.IsActionButtonIdNextButtonId(args.Action.Id)) + if (IsCurrentPage) { - await Orchestrator.GoToNextPage(); - } + // Make the current adaptive card in the session visible again since there was an error. + CreationPageState = CreationPageStateKind.InitialPageAdaptiveCardLoaded; + } + } + + if (IsCurrentPage && !actionFailed) + { + // Move Setup flow forward if the adaptive card Id that was clicked was the next button. + if (Orchestrator.AdaptiveCardFlowNavigator.IsActionButtonIdNextButtonId(args.Action.Id)) + { + await Orchestrator.GoToNextPage(); + } } - SendAdaptiveCardToCurrentView(SessionErrorMessage); - StopShowingLoadingUI(); + if (!ShouldAdaptiveCardAppearOnInitialPage()) + { + SendAdaptiveCardToCurrentView(SessionErrorMessage); + } }); } @@ -334,7 +386,8 @@ private void OnReviewPageViewRequest(EnvironmentCreationOptionsViewModel recipie { // Only send the adaptive card if the session has loaded. If the session hasn't loaded yet, we'll send an empty response. The review page should be sent the adaptive card // once the session has loaded in the OnAdaptiveCardUpdated method. - if (!IsAdaptiveCardSessionLoaded && Orchestrator?.CurrentPageViewModel is not SummaryViewModel) + var otherPageAdaptiveCardLoaded = CreationPageState == CreationPageStateKind.OtherPageAdaptiveCardLoaded; + if (!otherPageAdaptiveCardLoaded && Orchestrator?.CurrentPageViewModel is not SummaryViewModel) { return; } @@ -378,13 +431,12 @@ private async Task GetAdaptiveCardRenderer() public void GoToNextPage() { ResetErrorUI(); - Orchestrator.AdaptiveCardFlowNavigator.GoToNextPageWithValidation(_userInputsFromAdaptiveCard); - } + var validationSuccessful = Orchestrator.AdaptiveCardFlowNavigator.GoToNextPageWithValidation(_userInputsFromAdaptiveCard); - private void StopShowingLoadingUI() - { - CanGoToNextPage = true; - IsAdaptiveCardSessionLoaded = true; + if (validationSuccessful) + { + CreationPageState = CreationPageStateKind.OtherPageAdaptiveCardLoading; + } } private void ResetErrorUI() @@ -398,4 +450,14 @@ private void SendAdaptiveCardToCurrentView(string errorMessage = null) WeakReferenceMessenger.Default.Send(new NewAdaptiveCardAvailableMessage( new RenderedAdaptiveCardData(Orchestrator.CurrentPageViewModel, _renderedAdaptiveCard, errorMessage))); } + + private bool ShouldAdaptiveCardAppearOnInitialPage() + { + return CreationPageState switch + { + CreationPageStateKind.OtherPageAdaptiveCardLoaded => false, + CreationPageStateKind.OtherPageAdaptiveCardLoading => false, + _ => true, + }; + } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupTargetViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupTargetViewModel.cs index 41cbeb1191..ed84d633b8 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupTargetViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupTargetViewModel.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; @@ -13,6 +14,7 @@ using DevHome.Common.Environments.Helpers; using DevHome.Common.Environments.Models; using DevHome.Common.Environments.Services; +using DevHome.Common.Models; using DevHome.Common.Services; using DevHome.SetupFlow.Models.Environments; using DevHome.SetupFlow.Services; @@ -395,63 +397,65 @@ public async Task UpdateListViewModelList(ComputeSystemsLoadedData data) { _notificationsHelper?.DisplayComputeSystemEnumerationErrors(data); - // Remove the mappings that failed to load. - // The errors are already handled by the notification helper. - foreach (var mapping in data.DevIdToComputeSystemMap.Where(kvp => - kvp.Value.Result.Status == ProviderOperationStatus.Failure)) + var computeSystemListViewModels = new List(); + var allComputeSystems = new List(); + foreach (var devIdToComputeSystemResultPair in data.DevIdToComputeSystemMap) { - data.DevIdToComputeSystemMap.Remove(mapping.Key); - } + // Remove the mappings that failed to load. + // The errors are already handled by the notification helper. + if (devIdToComputeSystemResultPair.Value.Result.Status == ProviderOperationStatus.Failure) + { + continue; + } + + var listViewModel = new ComputeSystemsListViewModel(data.ProviderDetails, devIdToComputeSystemResultPair); - var curListViewModel = new ComputeSystemsListViewModel(data); + if (listViewModel.ComputeSystems.Count > 0) + { + computeSystemListViewModels.Add(listViewModel); + allComputeSystems.AddRange(listViewModel.ComputeSystems); + } + } // Fetch data for all compute systems that support the ApplyConfiguration flag in parallel // on thread pool to avoid calling expensive OOP operations on the UI thread. - await Parallel.ForEachAsync(curListViewModel.ComputeSystems, async (computeSystem, token) => + await Parallel.ForEachAsync(allComputeSystems, async (computeSystem, token) => { - if (computeSystem.SupportedOperations.Value.HasFlag(ComputeSystemOperations.ApplyConfiguration)) - { - await computeSystem.FetchDataAsync(); - } + await computeSystem.FetchDataAsync(); }); await _dispatcherQueue.EnqueueAsync(async () => { - foreach (var computeSystem in curListViewModel.ComputeSystems) + foreach (var listViewModel in computeSystemListViewModels) { - // Remove any cards that don't support the ApplyConfiguration flag. - if (!computeSystem.SupportedOperations.Value.HasFlag(ComputeSystemOperations.ApplyConfiguration)) + foreach (var computeSystem in listViewModel.ComputeSystems) { - continue; + var packageFullName = data.ProviderDetails.ExtensionWrapper.PackageFullName; + var card = await _computeSystemViewModelFactory.CreateCardViewModelAsync( + ComputeSystemManagerObj, + computeSystem, + data.ProviderDetails.ComputeSystemProvider, + packageFullName, + _dispatcherQueue); + + // Don't show environments that aren't in a state to configure + if (!ShouldShowCard(card.CardState)) + { + _log.Information($"{computeSystem.DisplayName} not in valid state." + + $" Current state: {card.CardState}"); + continue; + } + + listViewModel.ComputeSystemCardCollection.Add(card); } - var packageFullName = data.ProviderDetails.ExtensionWrapper.PackageFullName; - var card = await _computeSystemViewModelFactory.CreateCardViewModelAsync( - ComputeSystemManagerObj, - computeSystem, - curListViewModel.Provider, - packageFullName, - _dispatcherQueue); - - // Don't show environments that aren't in a state to configure - if (!ShouldShowCard(card.CardState)) + if (listViewModel.ComputeSystemCardCollection.Count > 0) { - continue; + AddListViewModelToList(listViewModel); + listViewModel.CardSelectionChanged += OnListSelectionChanged; } - - curListViewModel.ComputeSystemCardCollection.Add(card); - curListViewModel.CardSelectionChanged += OnListSelectionChanged; - } - - // Don't add view model to list if it doesn't contain any cards. - if (curListViewModel.ComputeSystemCardCollection.Count == 0) - { - _log.Information($"The {data.ProviderDetails.ComputeSystemProvider.DisplayName} was found but does not contain environments that support configuration"); - UpdateProviderNames(curListViewModel); - return; } - AddListViewModelToList(curListViewModel); ShouldShowShimmerBelowList = true; }); } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml index 10a9dd6b4a..c0fec3878b 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml @@ -9,12 +9,16 @@ xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors" xmlns:commonBehaviors="using:DevHome.Common.Behaviors" xmlns:setupFlowBehaviors="using:DevHome.SetupFlow.Behaviors" + xmlns:ic="using:Microsoft.Xaml.Interactions.Core" + xmlns:setupFlowConverters="using:DevHome.SetupFlow.Converters" Unloaded="ViewUnloaded" Loaded="ViewLoaded"> + + @@ -52,8 +56,9 @@ Margin="{ThemeResource ContentPageMargin}" Grid.Row="1"> - - + + + + HorizontalAlignment="Stretch"> + + + + + + + + + + + + + + + + + + + Padding="-15" + HorizontalAlignment="Left"> + Visibility="{x:Bind ViewModel.CreationPageState, Mode=OneWay, Converter={StaticResource CreationStateKindToVisibilityConverter}, ConverterParameter='ProgressRingGrid'}"> @@ -96,7 +145,7 @@ IsActive="True" Width="20" Height="20"/> -