From cc84b64b2badbaad6498ae2e735a7d1726e4a96b Mon Sep 17 00:00:00 2001 From: Huzaifa Danish Date: Thu, 29 Aug 2024 10:02:48 -0700 Subject: [PATCH] [Environments] Auto reload after Extensions page visit; selectable error text (#3589) * Environments reload after extensions refresh * Added message file * Added comment * Fixing UAC prompt * Added selectable text * Added AltName to MachineConfig Flow * Remove empty properties * Remove empty properties II * Added ExtensionToggle event * Resolved comments I * Removed unused headers --------- Co-authored-by: Huzaifa Danish --- .../Helpers/ComputeSystemHelpers.cs | 9 +++- .../Environments/Scripts/HyperVSetupScript.cs | 15 +----- .../StackedNotificationsBehaviorExtensions.cs | 41 ++++++++++----- common/Services/IExtensionService.cs | 3 ++ src/Services/ExtensionService.cs | 13 +++-- .../ViewModels/ComputeSystemViewModel.cs | 1 + .../ViewModels/LandingPageViewModel.cs | 18 ++++++- .../Views/LandingPage.xaml.cs | 2 +- .../ComputeSystemCardViewModel.cs | 9 +++- .../Views/SetupTargetView.xaml | 51 ++++++++++--------- 10 files changed, 101 insertions(+), 61 deletions(-) diff --git a/common/Environments/Helpers/ComputeSystemHelpers.cs b/common/Environments/Helpers/ComputeSystemHelpers.cs index db644b54fa..1ef53b4034 100644 --- a/common/Environments/Helpers/ComputeSystemHelpers.cs +++ b/common/Environments/Helpers/ComputeSystemHelpers.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; +using System.Linq; using System.Runtime.InteropServices.WindowsRuntime; using System.Threading.Tasks; using DevHome.Common.Environments.Models; @@ -88,7 +89,13 @@ public static async Task> GetComputeSystemCardPropertiesAsync try { var currentProperties = await computeSystem.GetComputeSystemPropertiesAsync(string.Empty); - return GetComputeSystemCardProperties(currentProperties, packageFullName); + + // Remove properties with empty values + var filteredProperties = currentProperties + .Where(property => property?.Value != null && !string.IsNullOrEmpty(property.Value.ToString())) + .ToList(); + + return GetComputeSystemCardProperties(filteredProperties, packageFullName); } catch (Exception ex) { diff --git a/common/Environments/Scripts/HyperVSetupScript.cs b/common/Environments/Scripts/HyperVSetupScript.cs index e0fb5529d6..443e6c1010 100644 --- a/common/Environments/Scripts/HyperVSetupScript.cs +++ b/common/Environments/Scripts/HyperVSetupScript.cs @@ -37,21 +37,10 @@ 6. The user is already in the Hyper-V Admin group and the Hyper-V Feature is alr $adminGroupResult = [OperationStatus]::OperationNotRun $hyperVGroupSid = 'S-1-5-32-578' - # Check the security token the user logged on with contains the Hyper-V Administrators group SID (S-1-5-32-578). This can only be updated, - # once the user logs off and on again. Even if we add the user to the group later on in the script. - $foundSecurityTokenString = [System.Security.Principal.WindowsIdentity]::GetCurrent().Groups.Value | Where-Object { $_ -eq $hyperVGroupSid } - $doesUserSecurityTokenContainHyperAdminGroup = $foundSecurityTokenString -eq $hyperVGroupSid - # Check if the Hyper-V feature is enabled $featureState = Get-WindowsOptionalFeature -FeatureName 'Microsoft-Hyper-V' -Online | Select-Object -ExpandProperty State $featureEnabled = $featureState -eq 'Enabled' - if ($doesUserSecurityTokenContainHyperAdminGroup -and $featureEnabled) - { - # User already in Admin group and feature already enabled - exit 6 - } - # Enable the Hyper-V feature if it is not already enabled if (-not $featureEnabled) { @@ -119,8 +108,8 @@ exit 5 } # If both operations have not been run at this point, then user is already in the Hyper-V admin group and the Hyper-V feature is enabled. # This could happen if the script runs the first time without the user being in the group, while Hyper-V is enabled but the user doesn't - # log off/on again or reboot. The second time we run the script there would be no work to be done. Since the actual token of the user - # doesn't update until they log off, the $doesUserSecurityTokenContainHyperAdminGroup variable above will still remain false, which is + # log off/on again or reboot. The second time we run the script there would be no work to be done. Since the actual token of the user + # doesn't update until they log off, the $doesUserSecurityTokenContainHyperAdminGroup variable above will still remain false, which is # how we ended up here. elseif ($featureEnablementResult -eq [OperationStatus]::OperationNotRun -and $adminGroupResult -eq [OperationStatus]::OperationNotRun) { diff --git a/common/Extensions/StackedNotificationsBehaviorExtensions.cs b/common/Extensions/StackedNotificationsBehaviorExtensions.cs index c93bbec88b..0fe5edd6b0 100644 --- a/common/Extensions/StackedNotificationsBehaviorExtensions.cs +++ b/common/Extensions/StackedNotificationsBehaviorExtensions.cs @@ -23,27 +23,42 @@ public static void ShowWithWindowExtension( var dispatcherQueue = Application.Current.GetService(); dispatcherQueue?.EnqueueAsync(() => - { + { var notificationToShow = new Notification { Title = title, - Message = message, Severity = severity, - }; + }; + + // Create a stack panel to hold the message and button + // A custom control is needed to allow text selection in the message + var stackPanel = new StackPanel + { + Orientation = Orientation.Vertical, + Margin = new Thickness(0, -15, 0, 20), + Spacing = 10, + }; + + stackPanel.Children.Add(new TextBlock + { + Text = message, + TextWrapping = TextWrapping.WrapWholeWords, + IsTextSelectionEnabled = true, + }); if (command != null) - { - notificationToShow.ActionButton = new Button - { - Content = buttonContent, - Command = command, - }; - + { // Make the command parameter the notification so RelayCommands can reference the notification in case they need // to close it within the command. - notificationToShow.ActionButton.CommandParameter = notificationToShow; - } - + stackPanel.Children.Add(new Button + { + Content = buttonContent, + Command = command, + CommandParameter = notificationToShow, + }); + } + + notificationToShow.Content = stackPanel; behavior.Show(notificationToShow); }); } diff --git a/common/Services/IExtensionService.cs b/common/Services/IExtensionService.cs index 941e61e583..298c19fe71 100644 --- a/common/Services/IExtensionService.cs +++ b/common/Services/IExtensionService.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Windows.Foundation; namespace DevHome.Common.Services; @@ -21,6 +22,8 @@ public interface IExtensionService public event EventHandler OnExtensionsChanged; + public event TypedEventHandler ExtensionToggled; + public void EnableExtension(string extensionUniqueId); public void DisableExtension(string extensionUniqueId); diff --git a/src/Services/ExtensionService.cs b/src/Services/ExtensionService.cs index e09d7c6ea5..80fa7101ad 100644 --- a/src/Services/ExtensionService.cs +++ b/src/Services/ExtensionService.cs @@ -12,6 +12,7 @@ using Serilog; using Windows.ApplicationModel; using Windows.ApplicationModel.AppExtensions; +using Windows.Foundation; using Windows.Foundation.Collections; using static DevHome.Common.Helpers.ManagementInfrastructureHelper; @@ -23,6 +24,8 @@ public class ExtensionService : IExtensionService, IDisposable public event EventHandler OnExtensionsChanged = (_, _) => { }; + public event TypedEventHandler ExtensionToggled = (_, _) => { }; + private static readonly PackageCatalog _catalog = PackageCatalog.OpenForCurrentUser(); private static readonly object _lock = new(); private readonly SemaphoreSlim _getInstalledExtensionsLock = new(1, 1); @@ -369,14 +372,16 @@ private List GetCreateInstanceList(IPropertySet activationPropSet) public void EnableExtension(string extensionUniqueId) { - var extension = _installedExtensions.Where(extension => extension.ExtensionUniqueId.Equals(extensionUniqueId, StringComparison.Ordinal)); - _enabledExtensions.Add(extension.First()); + var extension = _installedExtensions.Where(extension => extension.ExtensionUniqueId.Equals(extensionUniqueId, StringComparison.Ordinal)).First(); + ExtensionToggled.Invoke(this, extension); + _enabledExtensions.Add(extension); } public void DisableExtension(string extensionUniqueId) { - var extension = _enabledExtensions.Where(extension => extension.ExtensionUniqueId.Equals(extensionUniqueId, StringComparison.Ordinal)); - _enabledExtensions.Remove(extension.First()); + var extension = _enabledExtensions.Where(extension => extension.ExtensionUniqueId.Equals(extensionUniqueId, StringComparison.Ordinal)).First(); + ExtensionToggled.Invoke(this, extension); + _enabledExtensions.Remove(extension); } /// diff --git a/tools/Environments/DevHome.Environments/ViewModels/ComputeSystemViewModel.cs b/tools/Environments/DevHome.Environments/ViewModels/ComputeSystemViewModel.cs index 9d296e476d..901fe2489c 100644 --- a/tools/Environments/DevHome.Environments/ViewModels/ComputeSystemViewModel.cs +++ b/tools/Environments/DevHome.Environments/ViewModels/ComputeSystemViewModel.cs @@ -221,6 +221,7 @@ private async void SetPropertiesAsync() } var properties = await ComputeSystemHelpers.GetComputeSystemCardPropertiesAsync(ComputeSystem!, PackageFullName); + if (!ComputeSystemHelpers.RemoveAllItemsAndReplace(Properties, properties)) { Properties = new(properties); diff --git a/tools/Environments/DevHome.Environments/ViewModels/LandingPageViewModel.cs b/tools/Environments/DevHome.Environments/ViewModels/LandingPageViewModel.cs index 26d8b682b3..0786b1eba9 100644 --- a/tools/Environments/DevHome.Environments/ViewModels/LandingPageViewModel.cs +++ b/tools/Environments/DevHome.Environments/ViewModels/LandingPageViewModel.cs @@ -48,6 +48,8 @@ public partial class LandingPageViewModel : ObservableObject, IDisposable private bool _wasSyncButtonClicked; + private bool _extensionsToggled; + private string _selectedProvider = string.Empty; public bool IsLoading { get; set; } @@ -98,6 +100,7 @@ private enum SortOptions public LandingPageViewModel( INavigationService navigationService, IComputeSystemManager manager, + IExtensionService extensionService, Window mainWindow) { _computeSystemManager = manager; @@ -111,6 +114,15 @@ public LandingPageViewModel( _lastSyncTime = _stringResource.GetLocalized("MomentsAgo"); ComputeSystemCardsView = new AdvancedCollectionView(ComputeSystemCards); ComputeSystemCardsView.SortDescriptions.Add(new SortDescription("IsCardCreating", SortDirection.Descending)); + extensionService.ExtensionToggled += OnExtensionToggled; + } + + private void OnExtensionToggled(IExtensionService sender, IExtensionWrapper extension) + { + if (extension.HasProviderType(ProviderType.ComputeSystem)) + { + _extensionsToggled = true; + } } public void Initialize(StackedNotificationsBehavior notificationQueue) @@ -216,7 +228,7 @@ private async Task RunSyncTimmer() /// /// Main entry point for loading the view model. /// - public async Task LoadModelAsync(bool useDebugValues = false) + public async Task LoadModelAsync() { lock (_lock) { @@ -228,8 +240,9 @@ public async Task LoadModelAsync(bool useDebugValues = false) // If the page has already loaded once, then we don't need to re-load the compute systems as that can take a while. // The user can click the sync button to refresh the compute systems. However, there may be new operations that have started // since the last time the page was loaded. So we need to add those to the view model quickly. + // But if the user toggled extensions, we need to reload the page to show refreshed data. SetupCreateComputeSystemOperationForUI(); - if (HasPageLoadedForTheFirstTime && !_wasSyncButtonClicked) + if (HasPageLoadedForTheFirstTime && !_wasSyncButtonClicked && !_extensionsToggled) { return; } @@ -269,6 +282,7 @@ public async Task LoadModelAsync(bool useDebugValues = false) { IsLoading = false; HasPageLoadedForTheFirstTime = true; + _extensionsToggled = false; } } diff --git a/tools/Environments/DevHome.Environments/Views/LandingPage.xaml.cs b/tools/Environments/DevHome.Environments/Views/LandingPage.xaml.cs index 21c8d7af68..249f342bdd 100644 --- a/tools/Environments/DevHome.Environments/Views/LandingPage.xaml.cs +++ b/tools/Environments/DevHome.Environments/Views/LandingPage.xaml.cs @@ -24,6 +24,6 @@ public LandingPage() private async void OnLoaded(object sender, RoutedEventArgs e) { - await ViewModel.LoadModelAsync(false); + await ViewModel.LoadModelAsync(); } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/ComputeSystemCardViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/ComputeSystemCardViewModel.cs index 106158a06c..82eaf9ba3c 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/ComputeSystemCardViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/ComputeSystemCardViewModel.cs @@ -48,7 +48,10 @@ public partial class ComputeSystemCardViewModel : ObservableObject private bool _isSelected; [ObservableProperty] - private string _computeSystemTitle; + private string _computeSystemTitle; + + [ObservableProperty] + private string _computeSystemAlternativeTitle; [ObservableProperty] private string _computeSystemProviderDisplayName; @@ -68,7 +71,8 @@ public ComputeSystemCardViewModel(ComputeSystemCache computeSystem, IComputeSyst { _dispatcherQueue = dispatcherQueue; _computeSystemManager = manager; - ComputeSystemTitle = computeSystem.DisplayName.Value; + ComputeSystemTitle = computeSystem.DisplayName.Value; + ComputeSystemAlternativeTitle = computeSystem.SupplementalDisplayName.Value; ComputeSystem = computeSystem; ComputeSystem.StateChanged += _computeSystemManager.OnComputeSystemStateChanged; _computeSystemManager.ComputeSystemStateChanged += OnComputeSystemStateChanged; @@ -100,6 +104,7 @@ private async Task RefreshOperationDataAsync() private async Task UpdatePropertiesAsync() { var properties = await ComputeSystemHelpers.GetComputeSystemCardPropertiesAsync(ComputeSystem, _packageFullName); + lock (_lock) { if (!ComputeSystemHelpers.RemoveAllItemsAndReplace(ComputeSystemProperties, properties)) diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/SetupTargetView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/SetupTargetView.xaml index 84d21d9977..108f539906 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/SetupTargetView.xaml +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/SetupTargetView.xaml @@ -8,8 +8,8 @@ xmlns:setupControls="using:DevHome.SetupFlow.Controls" xmlns:devEnvModels="using:DevHome.SetupFlow.Models.Environments" xmlns:devViewModels="using:DevHome.SetupFlow.ViewModels.Environments" - xmlns:devEnvCustomControls="using:DevHome.Common.Environments.CustomControls" - xmlns:devCommonModels="using:DevHome.Common.Environments.Models" + xmlns:devEnvCustomControls="using:DevHome.Common.Environments.CustomControls" + xmlns:devCommonModels="using:DevHome.Common.Environments.Models" xmlns:converters="using:CommunityToolkit.WinUI.Converters" xmlns:ic="using:Microsoft.Xaml.Interactions.Core" xmlns:i="using:Microsoft.Xaml.Interactivity" @@ -56,6 +56,7 @@ - - - @@ -221,7 +222,7 @@ Text="{x:Bind ViewModel.ComputeSystemFilterText, Mode=TwoWay}"> - @@ -240,7 +241,7 @@ - @@ -254,14 +255,14 @@ SelectedValue="{x:Bind ViewModel.SelectedComputeSystemProviderComboBoxName, Mode=TwoWay}"> - - @@ -277,8 +278,8 @@ SelectedIndex="{x:Bind ViewModel.SelectedComputeSystemSortComboBoxIndex, Mode=TwoWay}"> - @@ -295,12 +296,12 @@ - - - @@ -354,7 +355,7 @@ Grid.Row="1" Visibility="{x:Bind ViewModel.ShouldShowCollectionView, Mode=OneWay, Converter={StaticResource CollapsedWhenTrueBoolToVisibilityConverter}}"> - - - -