Skip to content

Commit 20c3ac2

Browse files
authored
Add sorting and filtering UI to Gallery (#8105)
* Add sorting and filtering in gallery (without UI) * Remove extra lines and comments * Add feature flight * Fix PR comments * Applied PR comments * Sync gallery fabric with Content fabric * Add AdvancedSearch UI * Add test for LuceneSearchService * Got rid of static package types in the Gallery backend * Put static package type values in UI * Got rid of static package types in the Gallery backend * Fix PR comments * Fix telemetry * Fix accessibility issues * Move CSS to Bootstap's less file * Compacted the LESS file * Nit space * Changed to isDefault * Use variable instead of hardcoded value * Fix case-sensitivity and remove UI guid * Change Dict --> IReadOnlyDict * Nit: spacing * Fix telemetry for null packageType * Remove unnecessary ToJson * Remove unnecessary for in label * Fix CSS * Fix const naming * Change manual URL construction to form * Fix prerel checkbox * Fix paging * Renaming of constants * Add flag to tell if AdvancedSearch is supported by the SearchService * Format * Apply PR comments * Nest CSS * Fix PR comments * Nested the nestable * Updated narrator labels
1 parent 3df003b commit 20c3ac2

12 files changed

Lines changed: 387 additions & 91 deletions

File tree

src/Bootstrap/dist/css/bootstrap-theme.css

Lines changed: 63 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Bootstrap/less/theme/page-list-packages.less

Lines changed: 74 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,85 @@
33
margin-bottom: 33px;
44
}
55

6-
.row-heading {
7-
.cell-heading {
8-
h1 {
9-
.break-word;
10-
}
6+
.no-padding {
7+
padding: 0 !important;
8+
}
9+
10+
.no-margin {
11+
margin: 0 !important;
12+
}
13+
14+
.btn-command {
15+
margin-top: 33px;
16+
margin-bottom: 33px;
17+
width: 90px;
18+
height: 45px;
19+
background-color: transparent;
20+
border: none;
21+
}
22+
23+
@media (max-width: @screen-md) {
24+
.btn-command {
25+
text-align: left;
26+
margin-top: 0px;
27+
margin-bottom: 5px;
1128
}
29+
}
1230

13-
@media (min-width: @screen-md-min) {
14-
display: table;
15-
width: 100%;
31+
.prerel-filter {
32+
white-space: nowrap;
33+
}
1634

17-
.cell-heading {
18-
text-align: left;
19-
display: table-cell;
20-
}
35+
@media (min-width: @screen-md) {
36+
.prerel-filter {
37+
display: inline-block;
38+
margin-top: 54px;
39+
}
40+
}
2141

22-
.cell-controls {
23-
margin-top: 42px;
24-
display: table-cell;
25-
text-align: right;
42+
.btn-command:hover {
43+
background-color: #f4f4f4;
44+
}
45+
46+
.advanced-search-panel {
47+
background-color: @alert-info-bg;
48+
border-color: @alert-info-border;
49+
margin-right: 0px;
50+
margin-left: 0;
51+
margin-bottom: 15px;
52+
53+
fieldset {
54+
margin: 20px;
55+
56+
label {
57+
margin-left: 3px;
58+
font-weight: 400;
59+
font-size: small;
2660
white-space: nowrap;
2761
}
62+
63+
input {
64+
margin-right: 3px;
65+
position: relative;
66+
top: 2px;
67+
}
68+
}
69+
70+
legend {
71+
margin-bottom: 5px;
72+
font-weight: 500;
73+
font-size: medium;
74+
white-space: nowrap;
75+
}
76+
77+
.btn {
78+
margin-left: 20px;
79+
margin-bottom: 10px;
80+
}
81+
82+
.warning-panel {
83+
margin-left: 10px;
84+
margin-right: 10px;
2885
}
2986
}
30-
}
87+
}

src/NuGetGallery/Controllers/PackagesController.cs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,15 +1118,6 @@ public virtual async Task<ActionResult> ListPackages(PackageListSearchViewModel
11181118
page = 1;
11191119
}
11201120

1121-
var isAdvancedSearchFlightEnabled = _featureFlagService.IsAdvancedSearchEnabled(GetCurrentUser());
1122-
1123-
// If advanced search is disabled, use the default experience
1124-
if (!isAdvancedSearchFlightEnabled)
1125-
{
1126-
searchAndListModel.SortBy = GalleryConstants.SearchSortNames.Relevance;
1127-
searchAndListModel.PackageType = string.Empty;
1128-
}
1129-
11301121
q = (q ?? string.Empty).Trim();
11311122

11321123
// We are not going to SQL here anyway, but our request logs do show some attempts to SQL injection.
@@ -1143,6 +1134,14 @@ public virtual async Task<ActionResult> ListPackages(PackageListSearchViewModel
11431134

11441135
var isPreviewSearchEnabled = _abTestService.IsPreviewSearchEnabled(GetCurrentUser());
11451136
var searchService = isPreviewSearchEnabled ? _searchServiceFactory.GetPreviewService() : _searchServiceFactory.GetService();
1137+
var isAdvancedSearchFlightEnabled = _featureFlagService.IsAdvancedSearchEnabled(GetCurrentUser());
1138+
1139+
// If advanced search is disabled, use the default experience
1140+
if (!isAdvancedSearchFlightEnabled || !searchService.SupportsAdvancedSearch)
1141+
{
1142+
searchAndListModel.SortBy = GalleryConstants.SearchSortNames.Relevance;
1143+
searchAndListModel.PackageType = string.Empty;
1144+
}
11461145

11471146
if (!IsSupportedSortBy(searchAndListModel.SortBy))
11481147
{
@@ -1228,8 +1227,8 @@ public virtual async Task<ActionResult> ListPackages(PackageListSearchViewModel
12281227
searchAndListModel.SortBy);
12291228

12301229
// If the experience hasn't been cached, it means it's not the default experienced, therefore, show the panel
1231-
viewModel.IsAdvancedSearchFlightEnabled = isAdvancedSearchFlightEnabled;
1232-
viewModel.ShouldDisplayAdvancedSearchPanel = !shouldCacheAdvancedSearch || !includePrerelease;
1230+
viewModel.IsAdvancedSearchFlightEnabled = searchService.SupportsAdvancedSearch && isAdvancedSearchFlightEnabled;
1231+
viewModel.ShouldDisplayAdvancedSearchPanel = !shouldCacheAdvancedSearch || !includePrerelease;
12331232

12341233
ViewBag.SearchTerm = q;
12351234

src/NuGetGallery/Infrastructure/Lucene/ExternalSearchService.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -34,7 +34,9 @@ public bool IsLocal
3434
get { return false; }
3535
}
3636

37-
public bool ContainsAllVersions { get { return true; } }
37+
public bool ContainsAllVersions => true;
38+
39+
public bool SupportsAdvancedSearch => true;
3840

3941
public ExternalSearchService(IDiagnosticsService diagnostics, ISearchClient searchClient)
4042
{

src/NuGetGallery/Infrastructure/Lucene/LuceneSearchService.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -25,7 +25,9 @@ public class LuceneSearchService : ISearchService
2525
private static readonly string[] FieldAliases = new[] { "Id", "Title", "Tag", "Tags", "Description", "Author", "Authors", "Owner", "Owners" };
2626
private static readonly string[] Fields = new[] { "Id", "Title", "Tags", "Description", "Authors", "Owners" };
2727

28-
public bool ContainsAllVersions { get { return false; } }
28+
public bool ContainsAllVersions => false;
29+
30+
public bool SupportsAdvancedSearch => false;
2931

3032
public LuceneSearchService(Lucene.Net.Store.Directory directory)
3133
{

src/NuGetGallery/OData/SearchService/SearchAdaptor.cs

Lines changed: 17 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,19 @@ public static class SearchAdaptor
2525
/// Determines the maximum number of packages returned in a single page of an OData result.
2626
/// </summary>
2727
internal const int MaxPageSize = 100;
28+
private static readonly IReadOnlyDictionary<string, SortOrder> SortOrders = new Dictionary<string, SortOrder>(StringComparer.OrdinalIgnoreCase)
29+
{
30+
{ GalleryConstants.AlphabeticSortOrder, SortOrder.TitleAscending },
31+
{ GalleryConstants.SearchSortNames.TitleAsc, SortOrder.TitleAscending },
32+
{ GalleryConstants.SearchSortNames.TitleDesc, SortOrder.TitleDescending },
33+
{ GalleryConstants.RecentSortOrder, SortOrder.Published },
34+
{ GalleryConstants.SearchSortNames.Published, SortOrder.Published },
35+
{ GalleryConstants.SearchSortNames.LastEdited, SortOrder.LastEdited },
36+
{ GalleryConstants.SearchSortNames.CreatedAsc, SortOrder.CreatedAscending },
37+
{ GalleryConstants.SearchSortNames.CreatedDesc, SortOrder.CreatedDescending },
38+
{ GalleryConstants.SearchSortNames.TotalDownloadsAsc, SortOrder.TotalDownloadsAscending },
39+
{ GalleryConstants.SearchSortNames.TotalDownloadsDesc, SortOrder.TotalDownloadsDescending },
40+
};
2841

2942
public static SearchFilter GetSearchFilter(string q, int page, bool includePrerelease, string packageType, string sortOrder, string context, string semVerLevel)
3043
{
@@ -41,43 +54,13 @@ public static SearchFilter GetSearchFilter(string q, int page, bool includePrere
4154
PackageType = packageType,
4255
};
4356

44-
switch (sortOrder)
57+
if (sortOrder == null || !SortOrders.TryGetValue(sortOrder, out var sortOrderValue))
4558
{
46-
case GalleryConstants.AlphabeticSortOrder:
47-
case GalleryConstants.SearchSortNames.TitleAsc:
48-
searchFilter.SortOrder = SortOrder.TitleAscending;
49-
break;
50-
51-
case GalleryConstants.SearchSortNames.TitleDesc:
52-
searchFilter.SortOrder = SortOrder.TitleDescending;
53-
break;
54-
55-
case GalleryConstants.RecentSortOrder:
56-
case GalleryConstants.SearchSortNames.Published:
57-
searchFilter.SortOrder = SortOrder.Published;
58-
break;
59-
60-
case GalleryConstants.SearchSortNames.LastEdited:
61-
searchFilter.SortOrder = SortOrder.LastEdited;
62-
break;
63-
64-
case GalleryConstants.SearchSortNames.CreatedAsc:
65-
searchFilter.SortOrder = SortOrder.CreatedAscending;
66-
break;
67-
case GalleryConstants.SearchSortNames.CreatedDesc:
68-
searchFilter.SortOrder = SortOrder.CreatedDescending;
69-
break;
70-
case GalleryConstants.SearchSortNames.TotalDownloadsAsc:
71-
searchFilter.SortOrder = SortOrder.TotalDownloadsAscending;
72-
break;
73-
case GalleryConstants.SearchSortNames.TotalDownloadsDesc:
74-
searchFilter.SortOrder = SortOrder.TotalDownloadsDescending;
75-
break;
76-
default: // popularity
77-
searchFilter.SortOrder = SortOrder.Relevance;
78-
break;
59+
sortOrderValue = SortOrder.Relevance;
7960
}
8061

62+
searchFilter.SortOrder = sortOrderValue;
63+
8164
return searchFilter;
8265
}
8366

src/NuGetGallery/Services/ISearchService.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,10 @@ public interface ISearchService
1818
/// Gets a boolean indicating if all versions of each package are stored in the index
1919
/// </summary>
2020
bool ContainsAllVersions { get; }
21+
22+
/// <summary>
23+
/// Gets a boolean indicating if the search service supports Advanced Search.
24+
/// </summary>
25+
bool SupportsAdvancedSearch { get; }
2126
}
2227
}

src/NuGetGallery/UrlHelperExtensions.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ public static string PackageList(
148148
int page,
149149
string q,
150150
bool includePrerelease,
151+
string packageType,
152+
string sortBy,
151153
bool relativeUrl = true)
152154
{
153155
var routeValues = new RouteValueDictionary();
@@ -167,6 +169,16 @@ public static string PackageList(
167169
routeValues["prerel"] = "false";
168170
}
169171

172+
if (!string.IsNullOrWhiteSpace(packageType))
173+
{
174+
routeValues["packageType"] = packageType;
175+
}
176+
177+
if (!string.IsNullOrWhiteSpace(sortBy))
178+
{
179+
routeValues["sortBy"] = sortBy;
180+
}
181+
170182
return GetActionLink(
171183
url,
172184
"ListPackages",

src/NuGetGallery/ViewModels/PackageListViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public PackageListViewModel(
3535
packageViewModels,
3636
PageIndex,
3737
pageCount,
38-
page => url.PackageList(page, searchTerm, includePrerelease));
38+
page => url.PackageList(page, searchTerm, includePrerelease, packageType, sortBy));
3939
Items = pager.Items;
4040
Pager = pager;
4141
IncludePrerelease = includePrerelease;

0 commit comments

Comments
 (0)