diff --git a/src/DynamoCoreWpf/Properties/Resources.Designer.cs b/src/DynamoCoreWpf/Properties/Resources.Designer.cs index 2f115a7510b..6eab0be7f13 100644 --- a/src/DynamoCoreWpf/Properties/Resources.Designer.cs +++ b/src/DynamoCoreWpf/Properties/Resources.Designer.cs @@ -5556,6 +5556,24 @@ public static string OpenDynamoDefinitionDialogTitle { } } + /// + /// Looks up a localized string similar to Compatible. + /// + public static string PackageCompatible { + get { + return ResourceManager.GetString("PackageCompatible", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Versions compatible with the current setup. + /// + public static string PackageCompatibleFilterTooltip { + get { + return ResourceManager.GetString("PackageCompatibleFilterTooltip", resourceCulture); + } + } + /// /// Looks up a localized string similar to This package contains custom nodes that are in use. These custom nodes need to be deleted or the graph needs to be closed before the package can be deleted.. /// @@ -5934,6 +5952,15 @@ public static string PackageFilter_Name_All { } } + /// + /// Looks up a localized string similar to VERSION COMPATIBILITY. + /// + public static string PackageFilterByCompatibility { + get { + return ResourceManager.GetString("PackageFilterByCompatibility", resourceCulture); + } + } + /// /// Looks up a localized string similar to DEPENDENCY. /// @@ -6043,6 +6070,24 @@ public static string PackageHostDependencyTooltip { } } + /// + /// Looks up a localized string similar to Incompatible. + /// + public static string PackageIncompatible { + get { + return ResourceManager.GetString("PackageIncompatible", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Versions maybe incompatible with the current setup. + /// + public static string PackageIncompatibleFilterTooltip { + get { + return ResourceManager.GetString("PackageIncompatibleFilterTooltip", resourceCulture); + } + } + /// /// Looks up a localized string similar to Clear all. /// @@ -7302,6 +7347,24 @@ public static string PackageTypeShortString { } } + /// + /// Looks up a localized string similar to Unknown compatibility. + /// + public static string PackageUnknownCompatibility { + get { + return ResourceManager.GetString("PackageUnknownCompatibility", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Compatibility of package version cannot be verified. + /// + public static string PackageUnknownCompatibilityFilterTooltip { + get { + return ResourceManager.GetString("PackageUnknownCompatibilityFilterTooltip", resourceCulture); + } + } + /// /// Looks up a localized string similar to None. /// diff --git a/src/DynamoCoreWpf/Properties/Resources.en-US.resx b/src/DynamoCoreWpf/Properties/Resources.en-US.resx index f0ad09bd3b1..bc3e5c110ce 100644 --- a/src/DynamoCoreWpf/Properties/Resources.en-US.resx +++ b/src/DynamoCoreWpf/Properties/Resources.en-US.resx @@ -4075,4 +4075,25 @@ To make this file into a new template, save it to a different folder, then move Incompatible version + + VERSION COMPATIBILITY + + + Compatible + + + Versions compatible with the current setup + + + Unknown compatibility + + + Compatibility of package version cannot be verified + + + Incompatible + + + Versions maybe incompatible with the current setup + diff --git a/src/DynamoCoreWpf/Properties/Resources.resx b/src/DynamoCoreWpf/Properties/Resources.resx index 2677838dd7f..6fd4ac719f6 100644 --- a/src/DynamoCoreWpf/Properties/Resources.resx +++ b/src/DynamoCoreWpf/Properties/Resources.resx @@ -4062,4 +4062,25 @@ To make this file into a new template, save it to a different folder, then move Incompatible version + + VERSION COMPATIBILITY + + + Compatible + + + Versions compatible with the current setup + + + Unknown compatibility + + + Compatibility of package version cannot be verified + + + Incompatible + + + Versions maybe incompatible with the current setup + diff --git a/src/DynamoCoreWpf/PublicAPI.Unshipped.txt b/src/DynamoCoreWpf/PublicAPI.Unshipped.txt index bbbc8a21c76..cab36cb3875 100644 --- a/src/DynamoCoreWpf/PublicAPI.Unshipped.txt +++ b/src/DynamoCoreWpf/PublicAPI.Unshipped.txt @@ -857,6 +857,8 @@ Dynamo.PackageManager.PackageManagerSearchViewModel.ClearSearchTextBoxCommand.se Dynamo.PackageManager.PackageManagerSearchViewModel.ClearToastNotification(object obj) -> void Dynamo.PackageManager.PackageManagerSearchViewModel.ClearToastNotificationCommand.get -> Prism.Commands.DelegateCommand Dynamo.PackageManager.PackageManagerSearchViewModel.ClearToastNotificationCommand.set -> void +Dynamo.PackageManager.PackageManagerSearchViewModel.CompatibilityFilter.get -> System.Collections.Generic.List +Dynamo.PackageManager.PackageManagerSearchViewModel.CompatibilityFilter.set -> void Dynamo.PackageManager.PackageManagerSearchViewModel.DisableSearchTextBox() -> void Dynamo.PackageManager.PackageManagerSearchViewModel.Downloads.get -> System.Collections.ObjectModel.ObservableCollection Dynamo.PackageManager.PackageManagerSearchViewModel.ExecuteSelected() -> void @@ -4989,6 +4991,8 @@ static Dynamo.Wpf.Properties.Resources.OnboardingWorkspaceTitle.get -> string static Dynamo.Wpf.Properties.Resources.OneAssemblyWasLoadedSeveralTimesErrorMessage.get -> string static Dynamo.Wpf.Properties.Resources.OnlyTitle.get -> string static Dynamo.Wpf.Properties.Resources.OpenDynamoDefinitionDialogTitle.get -> string +static Dynamo.Wpf.Properties.Resources.PackageCompatible.get -> string +static Dynamo.Wpf.Properties.Resources.PackageCompatibleFilterTooltip.get -> string static Dynamo.Wpf.Properties.Resources.PackageContextMenuDeletePackageCustomNodesInUseTooltip.get -> string static Dynamo.Wpf.Properties.Resources.PackageContextMenuDeletePackageText.get -> string static Dynamo.Wpf.Properties.Resources.PackageContextMenuDeletePackageTooltip.get -> string @@ -5030,6 +5034,7 @@ static Dynamo.Wpf.Properties.Resources.PackageDownloadStateInstalling.get -> str static Dynamo.Wpf.Properties.Resources.PackageDownloadStateStarting.get -> string static Dynamo.Wpf.Properties.Resources.PackageDuplicateAssemblyWarning.get -> string static Dynamo.Wpf.Properties.Resources.PackageDuplicateAssemblyWarningTitle.get -> string +static Dynamo.Wpf.Properties.Resources.PackageFilterByCompatibility.get -> string static Dynamo.Wpf.Properties.Resources.PackageFilterByDependency.get -> string static Dynamo.Wpf.Properties.Resources.PackageFilterByHost.get -> string static Dynamo.Wpf.Properties.Resources.PackageFilterByStatus.get -> string @@ -5043,6 +5048,8 @@ static Dynamo.Wpf.Properties.Resources.PackageFolderNotAccessible.get -> string static Dynamo.Wpf.Properties.Resources.PackageHostDependencyFilter.get -> string static Dynamo.Wpf.Properties.Resources.PackageHostDependencyFilterContextItem.get -> string static Dynamo.Wpf.Properties.Resources.PackageHostDependencyTooltip.get -> string +static Dynamo.Wpf.Properties.Resources.PackageIncompatible.get -> string +static Dynamo.Wpf.Properties.Resources.PackageIncompatibleFilterTooltip.get -> string static Dynamo.Wpf.Properties.Resources.PackageManagerClearAllButtonText.get -> string static Dynamo.Wpf.Properties.Resources.PackageManagerFinishedPackageFilesPublishedMessage.get -> string static Dynamo.Wpf.Properties.Resources.PackageManagerFinishedPackageFilesUploadedMessage.get -> string @@ -5182,6 +5189,8 @@ static Dynamo.Wpf.Properties.Resources.PackageStateUnknown.get -> string static Dynamo.Wpf.Properties.Resources.PackageStateUnloaded.get -> string static Dynamo.Wpf.Properties.Resources.PackageStateUnloadedTooltip.get -> string static Dynamo.Wpf.Properties.Resources.PackageTypeShortString.get -> string +static Dynamo.Wpf.Properties.Resources.PackageUnknownCompatibility.get -> string +static Dynamo.Wpf.Properties.Resources.PackageUnknownCompatibilityFilterTooltip.get -> string static Dynamo.Wpf.Properties.Resources.PackageUploadNoDependency.get -> string static Dynamo.Wpf.Properties.Resources.PackageUploadStateCompressing.get -> string static Dynamo.Wpf.Properties.Resources.PackageUploadStateCopying.get -> string diff --git a/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerSearchViewModel.cs b/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerSearchViewModel.cs index e5c793885f9..df9131abb7d 100644 --- a/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerSearchViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerSearchViewModel.cs @@ -202,7 +202,7 @@ public PackageSortingKey SortingKey set { _sortingKey = value; - RaisePropertyChanged("SortingKey"); + RaisePropertyChanged(nameof(SortingKey)); } } @@ -218,11 +218,10 @@ public List HostFilter set { hostFilter = value; - RaisePropertyChanged("HostFilter"); + RaisePropertyChanged(nameof(HostFilter)); } } - private List nonHostFilter; /// @@ -235,7 +234,22 @@ public List NonHostFilter set { nonHostFilter = value; - RaisePropertyChanged("NonHostFilter"); + RaisePropertyChanged(nameof(NonHostFilter)); + } + } + + private List compatibilityFilter; + + /// + /// Compatibility filters (Compatible, Non-compatible, Unknown) + /// + public List CompatibilityFilter + { + get { return compatibilityFilter; } + set + { + compatibilityFilter = value; + RaisePropertyChanged(nameof(CompatibilityFilter)); } } @@ -252,7 +266,7 @@ public PackageSortingDirection SortingDirection set { _sortingDirection = value; - RaisePropertyChanged("SortingDirection"); + RaisePropertyChanged(nameof(SortingDirection)); } } @@ -308,7 +322,7 @@ public string SearchText { _SearchText = value; SearchAndUpdateResults(); - RaisePropertyChanged("SearchText"); + RaisePropertyChanged(nameof(SearchText)); } } } @@ -329,7 +343,7 @@ public int SelectedIndex if (_selectedIndex != value) { _selectedIndex = value; - RaisePropertyChanged("SelectedIndex"); + RaisePropertyChanged(nameof(SelectedIndex)); } } } @@ -395,7 +409,7 @@ public bool IsAnyFilterOn private void SetFilterChange() { - IsAnyFilterOn = HostFilter.Any(f => f.OnChecked) || NonHostFilter.Any(f => f.OnChecked); + IsAnyFilterOn = HostFilter.Any(f => f.OnChecked) || NonHostFilter.Any(f => f.OnChecked) || CompatibilityFilter.Any(f => f.OnChecked); } /// @@ -579,9 +593,12 @@ internal PackageManagerSearchViewModel() SortingDirection = PackageSortingDirection.Descending; HostFilter = new List(); NonHostFilter = new List(); + CompatibilityFilter = new List(); SelectedHosts = new List(); } + + /// /// Add package information to Lucene index /// @@ -616,6 +633,7 @@ public PackageManagerSearchViewModel(PackageManagerClientViewModel client) : thi PackageManagerClientViewModel = client; HostFilter = InitializeHostFilter(); NonHostFilter = InitializeNonHostFilter(); + CompatibilityFilter = InitializeCompatibilityFilter(); InitializeLuceneForPackageManager(); } @@ -765,7 +783,18 @@ private List InitializeNonHostFilter() nonHostFilter.ForEach(f => f.PropertyChanged += filter_PropertyChanged); return nonHostFilter; + } + + private List InitializeCompatibilityFilter() + { + var compatibilityFilter = new List() { new FilterEntry(Resources.PackageCompatible, Resources.PackageFilterByCompatibility, Resources.PackageCompatibleFilterTooltip, this), + new FilterEntry(Resources.PackageUnknownCompatibility, Resources.PackageFilterByCompatibility, Resources.PackageUnknownCompatibilityFilterTooltip, this), + new FilterEntry(Resources.PackageIncompatible, Resources.PackageFilterByCompatibility, Resources.PackageIncompatibleFilterTooltip, this) + }; + compatibilityFilter.ForEach(f => f.PropertyChanged += filter_PropertyChanged); + + return compatibilityFilter; } /// @@ -932,6 +961,15 @@ private void ClearAllFilters(object obj) } } + foreach (var filter in CompatibilityFilter) + { + if (filter.OnChecked) + { + filter.OnChecked = false; + filter.FilterCommand.Execute(filter.FilterName); + } + } + RaisePropertyChanged(nameof(IsAnyFilterOn)); } @@ -1266,6 +1304,7 @@ internal void SearchAndUpdateResults(string query) results = Search(query, true); results = ApplyNonHostFilters(results); results = ApplyHostFilters(results); + results = ApplyCompatibilityFilters(results); } this.ClearSearchResults(); @@ -1362,6 +1401,7 @@ internal IEnumerable GetAllPackages() var initialResults = LastSync?.Select(x => GetSearchElementViewModel(x)); list = ApplyNonHostFilters(initialResults); list = ApplyHostFilters(list).ToList(); + list = ApplyCompatibilityFilters(list).ToList(); Sort(list, this.SortingKey); @@ -1391,6 +1431,34 @@ private List ApplyNonHostFilters(IEnumerab .ToList(); } + /// + /// Applies the compatibility filters - works on the package versions rather than the packages themselves + /// + /// + /// + internal IEnumerable ApplyCompatibilityFilters(IEnumerable list) + { + // No need to filter by host if nothing selected + if (!CompatibilityFilter.Any(f => f.OnChecked)) + { + return list; + } + + // Filter the list by checking if the selected version's compatibility matches the filters + IEnumerable filteredList = list.Where(x => + // Apply filter for Compatible (IsSelectedVersionCompatible == true) + CompatibilityFilter.FirstOrDefault(f => f.FilterName.Equals(Wpf.Properties.Resources.PackageCompatible))?.OnChecked == true && x.IsSelectedVersionCompatible == true || + + // Apply filter for Incompatible (IsSelectedVersionCompatible == false) + CompatibilityFilter.FirstOrDefault(f => f.FilterName.Equals(Wpf.Properties.Resources.PackageIncompatible))?.OnChecked == true && x.IsSelectedVersionCompatible == false || + + // Apply filter for Unknown compatibility (IsSelectedVersionCompatible == null) + CompatibilityFilter.FirstOrDefault(f => f.FilterName.Equals(Wpf.Properties.Resources.PackageUnknownCompatibility))?.OnChecked == true && x.IsSelectedVersionCompatible == null + ).ToList(); + + return filteredList; + } + /// /// Checks if a package has any dependencies (will always have at least itself as 1 dependency) /// @@ -1640,6 +1708,9 @@ internal void Dispose() nonHostFilter?.ForEach(f => f.PropertyChanged -= filter_PropertyChanged); nonHostFilter.Clear(); + compatibilityFilter?.ForEach(f => f.PropertyChanged -= filter_PropertyChanged); + compatibilityFilter.Clear(); + if (aTimer != null) { aTimer.Stop(); diff --git a/src/DynamoCoreWpf/Views/PackageManager/Controls/PackageManagerSearchControl.xaml b/src/DynamoCoreWpf/Views/PackageManager/Controls/PackageManagerSearchControl.xaml index 6e493d7fcf7..de24db4ee82 100644 --- a/src/DynamoCoreWpf/Views/PackageManager/Controls/PackageManagerSearchControl.xaml +++ b/src/DynamoCoreWpf/Views/PackageManager/Controls/PackageManagerSearchControl.xaml @@ -268,7 +268,10 @@ - + + @@ -276,19 +279,33 @@ Filter="DependencyItem_OnFilter" Source="{Binding NonHostFilter}" /> + + + + + + + + + + + + + + - + @@ -339,6 +356,9 @@ + @@ -348,6 +368,7 @@ + diff --git a/src/DynamoCoreWpf/Views/PackageManager/Controls/PackageManagerSearchControl.xaml.cs b/src/DynamoCoreWpf/Views/PackageManager/Controls/PackageManagerSearchControl.xaml.cs index 4d72227c049..76018787ab9 100644 --- a/src/DynamoCoreWpf/Views/PackageManager/Controls/PackageManagerSearchControl.xaml.cs +++ b/src/DynamoCoreWpf/Views/PackageManager/Controls/PackageManagerSearchControl.xaml.cs @@ -84,6 +84,12 @@ private void ViewDetailsButton_OnClick(object sender, RoutedEventArgs e) PkgSearchVM.ViewPackageDetailsCommand.Execute(packageManagerSearchElementViewModel.SearchElementModel); } + private void CompatibilityItem_OnFilter(object sender, FilterEventArgs e) + { + var item = e.Item as PackageManagerSearchViewModel.FilterEntry; + e.Accepted = item.GroupName.Equals(Wpf.Properties.Resources.PackageFilterByCompatibility); + } + private void StatusItem_OnFilter(object sender, FilterEventArgs e) { var item = e.Item as PackageManagerSearchViewModel.FilterEntry; diff --git a/test/DynamoCoreWpfTests/PackageManager/PackageManagerSearchElementViewModelTests.cs b/test/DynamoCoreWpfTests/PackageManager/PackageManagerSearchElementViewModelTests.cs index 65cc914ad21..07ce45ac280 100644 --- a/test/DynamoCoreWpfTests/PackageManager/PackageManagerSearchElementViewModelTests.cs +++ b/test/DynamoCoreWpfTests/PackageManager/PackageManagerSearchElementViewModelTests.cs @@ -558,6 +558,121 @@ public void PackageSearchDialogSearchTestDependencyFilters() Assert.IsFalse(packageManagerSearchViewModel.NonHostFilter[4].OnChecked); } + /// + /// Validates that after setting compatibility filters in the package search, + /// we get an intersection of results based on selected compatibility options. + /// + [Test] + public void PackageSearchDialogSearchTestCompatibilityFilters() + { + // Arrange + int numberOfPackages = 6; + string packageId = "c5ecd20a-d41c-4e0c-8e11-8ddfb953d77f"; + string packageVersionNumber = "1.0.0.0"; + string compatibleFilterName = Dynamo.Wpf.Properties.Resources.PackageCompatible; + string incompatibleFilterName = Dynamo.Wpf.Properties.Resources.PackageIncompatible; + string unknownCompatibilityFilterName = Dynamo.Wpf.Properties.Resources.PackageUnknownCompatibility; + + // Compatible, Incompatible, and Unknown Compatibility Packages + List compatiblePackagesName = new List { "DynamoIronPython2.7", "dynamo", "Celery for Dynamo 2.5" }; + List incompatiblePackagesName = new List { "IncompatPackage1", "IncompatPackage2" }; + List unknownCompatibilityPackagesName = new List { "DynamoXCompatPackage" }; + + var mockGreg = new Mock(); + var clientmock = new Mock(mockGreg.Object, MockMaker.Empty(), string.Empty); + var pmCVM = new Mock(ViewModel, clientmock.Object) { CallBase = true }; + + var packageManagerSearchViewModel = new PackageManagerSearchViewModel(pmCVM.Object); + packageManagerSearchViewModel.RegisterTransientHandlers(); + + // Adding Compatibility filters + packageManagerSearchViewModel.CompatibilityFilter = new List + { + new FilterEntry(compatibleFilterName, "Filter by compatibility", "Compatible Packages", packageManagerSearchViewModel) { OnChecked = false }, + new FilterEntry(incompatibleFilterName, "Filter by compatibility", "Incompatible Packages", packageManagerSearchViewModel) { OnChecked = false }, + new FilterEntry(unknownCompatibilityFilterName, "Filter by compatibility", "Unknown Compatibility Packages", packageManagerSearchViewModel) { OnChecked = false }, + }; + + // Adding compatible packages + foreach (var package in compatiblePackagesName) + { + var tmpPackageVersion = new PackageVersion { version = packageVersionNumber, created = DateTime.Now.ToString() }; + var tmpPackage = new PackageManagerSearchElementViewModel(new PackageManagerSearchElement(new PackageHeader() + { + _id = packageId, + name = package, + versions = new List { tmpPackageVersion }, + }), false); + tmpPackage.IsSelectedVersionCompatible = true; + packageManagerSearchViewModel.AddToSearchResults(tmpPackage); + } + + // Adding incompatible packages + foreach (var package in incompatiblePackagesName) + { + var tmpPackageVersion = new PackageVersion { version = packageVersionNumber, created = DateTime.Now.ToString() }; + var tmpPackage = new PackageManagerSearchElementViewModel(new PackageManagerSearchElement(new PackageHeader() + { + _id = packageId, + name = package, + versions = new List { tmpPackageVersion }, + }), false); + tmpPackage.IsSelectedVersionCompatible = false; + packageManagerSearchViewModel.AddToSearchResults(tmpPackage); + } + + // Adding unknown compatibility packages + foreach (var package in unknownCompatibilityPackagesName) + { + var tmpPackageVersion = new PackageVersion { version = packageVersionNumber, created = DateTime.Now.ToString() }; + var tmpPackage = new PackageManagerSearchElementViewModel(new PackageManagerSearchElement(new PackageHeader() + { + _id = packageId, + name = package, + versions = new List { tmpPackageVersion }, + }), false); + tmpPackage.IsSelectedVersionCompatible = null; + packageManagerSearchViewModel.AddToSearchResults(tmpPackage); + } + + //We need to add the PackageManagerSearchElementViewModel because fitlers act on the LastSync results + packageManagerSearchViewModel.LastSync = new List(); + foreach (var result in packageManagerSearchViewModel.SearchResults) + { + var sem = result.SearchElementModel; + sem.SelectedVersion.IsCompatible = result.IsSelectedVersionCompatible; + packageManagerSearchViewModel.LastSync.Add(sem); + } + + // Validate the total added packages match + Assert.That(packageManagerSearchViewModel.SearchResults.Count == numberOfPackages); + + // Act & Assert + // Check the Compatible filter + packageManagerSearchViewModel.CompatibilityFilter[0].OnChecked = true; + packageManagerSearchViewModel.CompatibilityFilter[0].FilterCommand.Execute(string.Empty); + Assert.IsNotNull(packageManagerSearchViewModel.SearchResults, "No results found"); + Assert.That(packageManagerSearchViewModel.SearchResults.Count == compatiblePackagesName.Count, "Filtered results do not match the compatible packages"); + + // Reset (Filters are not mutually exclusive) + packageManagerSearchViewModel.CompatibilityFilter[0].OnChecked = false; + + // Check the Incompatible filter + packageManagerSearchViewModel.CompatibilityFilter[1].OnChecked = true; + packageManagerSearchViewModel.CompatibilityFilter[1].FilterCommand.Execute(string.Empty); + Assert.IsNotNull(packageManagerSearchViewModel.SearchResults, "No results found"); + Assert.That(packageManagerSearchViewModel.SearchResults.Count == incompatiblePackagesName.Count, "Filtered results do not match the incompatible packages"); + + // Reset (Filters are not mutually exclusive) + packageManagerSearchViewModel.CompatibilityFilter[1].OnChecked = false; + + // Check the Unknown Compatibility filter + packageManagerSearchViewModel.CompatibilityFilter[2].OnChecked = true; + packageManagerSearchViewModel.CompatibilityFilter[2].FilterCommand.Execute(string.Empty); + Assert.IsNotNull(packageManagerSearchViewModel.SearchResults, "No results found"); + Assert.That(packageManagerSearchViewModel.SearchResults.Count == unknownCompatibilityPackagesName.Count, "Filtered results do not match the unknown compatibility packages"); + } + /// /// This unit test will validate that we can search packages in different languages and they will be found. ///