diff --git a/config.py b/config.py index f91637992..7398259ae 100644 --- a/config.py +++ b/config.py @@ -23,7 +23,8 @@ CLIENT_WHITELIST = ['SERVER_HOSTNAME', 'PREFERRED_URL_SCHEME', 'MIXPANEL_KEY', 'CONTACT_INFO', 'AVATAR_KIND', 'LOCAL_OAUTH_HANDLER', 'DOCUMENTATION_LOCATION', 'DOCUMENTATION_METADATA', 'SETUP_COMPLETE', 'DEBUG', 'MARKETO_MUNCHKIN_ID', 'STATIC_SITE_BUCKET', 'RECAPTCHA_SITE_KEY', 'CHANNEL_COLORS', - 'TAG_EXPIRATION_OPTIONS', 'INTERNAL_OIDC_SERVICE_ID'] + 'TAG_EXPIRATION_OPTIONS', 'INTERNAL_OIDC_SERVICE_ID', + 'SEARCH_RESULTS_PER_PAGE', 'SEARCH_MAX_RESULT_PAGE_COUNT'] def frontend_visible_config(config_dict): @@ -531,3 +532,9 @@ class DefaultConfig(ImmutableConfig): # Defines the delay required (in seconds) before the last_accessed field of a user/robot or access # token will be updated after the previous update. LAST_ACCESSED_UPDATE_THRESHOLD_S = 60 + + # Defines the number of results per page used to show search results + SEARCH_RESULTS_PER_PAGE = 10 + + # Defines the maximum number of pages the user can paginate before they are limited + SEARCH_MAX_RESULT_PAGE_COUNT = 10 diff --git a/endpoints/api/search.py b/endpoints/api/search.py index 54abdf4f5..41d8748a6 100644 --- a/endpoints/api/search.py +++ b/endpoints/api/search.py @@ -12,7 +12,7 @@ from auth.permissions import (OrganizationMemberPermission, ReadRepositoryPermis ReadRepositoryPermission) from auth.auth_context import get_authenticated_user from auth import scopes -from app import avatar, authentication +from app import app, avatar, authentication from flask import abort from operator import itemgetter from stringscore import liquidmetal @@ -335,7 +335,8 @@ class ConductSearch(ApiResource): return {'results': sorted(results, key=itemgetter('score'), reverse=True)} -MAX_PER_PAGE = 10 +MAX_PER_PAGE = app.config.get('SEARCH_RESULTS_PER_PAGE', 10) +MAX_RESULT_PAGE_COUNT = app.config.get('SEARCH_MAX_RESULT_PAGE_COUNT', 10) @resource('/v1/find/repositories') class ConductRepositorySearch(ApiResource): @@ -347,7 +348,7 @@ class ConductRepositorySearch(ApiResource): def get(self, parsed_args): """ Get a list of apps and repositories that match the specified query. """ query = parsed_args['query'] - page = min(max(1, parsed_args['page']), 10) + page = min(max(1, parsed_args['page']), MAX_RESULT_PAGE_COUNT) offset = (page - 1) * MAX_PER_PAGE limit = offset + MAX_PER_PAGE + 1 diff --git a/static/css/pages/search.css b/static/css/pages/search.css index 6bb8f77bf..478549759 100644 --- a/static/css/pages/search.css +++ b/static/css/pages/search.css @@ -85,6 +85,20 @@ padding: 6px; } + +.search .search-max-results-info { + padding-left: 5px; +} + +.search .page-navigation-wrapper { + display: flex; + align-items: center; +} + +.search .page-navigation-wrapper :first-child { + margin-right: 5px; +} + @media screen and (max-width: 767px) { .search { padding: 0px; diff --git a/static/js/pages/search.js b/static/js/pages/search.js index 0a01b7d5c..6529ea447 100644 --- a/static/js/pages/search.js +++ b/static/js/pages/search.js @@ -8,7 +8,7 @@ }); }]); - function SearchCtrl($scope, ApiService, $routeParams, $location) { + function SearchCtrl($scope, ApiService, $routeParams, $location, Config) { var refreshResults = function() { $scope.currentPage = ($routeParams['page'] || '1') * 1; @@ -17,10 +17,16 @@ 'page': $scope.currentPage }; + var MAX_PAGE_RESULTS = Config['SEARCH_MAX_RESULT_PAGE_COUNT']; + var page = $routeParams['page'] || 1; + $scope.maxPopularity = 0; $scope.resultsResource = ApiService.conductRepoSearchAsResource(params).get(function(resp) { $scope.results = resp['results']; - $scope.hasAdditional = resp['has_additional']; + // Only show "Next Page" if we have more results, and we aren't on the max page + $scope.showNextButton = page < MAX_PAGE_RESULTS && resp['has_additional']; + // Show some help text if we're on the last page, making them specify the search more + $scope.showMaxResultsHelpText = page >= MAX_PAGE_RESULTS; $scope.startIndex = resp['start_index']; resp['results'].forEach(function(result) { $scope.maxPopularity = Math.max($scope.maxPopularity, result['popularity']); @@ -45,5 +51,5 @@ }); } - SearchCtrl.$inject = ['$scope', 'ApiService', '$routeParams', '$location']; + SearchCtrl.$inject = ['$scope', 'ApiService', '$routeParams', '$location', 'Config']; })(); \ No newline at end of file diff --git a/static/partials/search.html b/static/partials/search.html index 2b4ecc666..119c143fb 100644 --- a/static/partials/search.html +++ b/static/partials/search.html @@ -40,8 +40,11 @@ - Previous Page - Next Page +
\ No newline at end of file diff --git a/test/data/test.db b/test/data/test.db index 201814669..4b5cbd65f 100644 Binary files a/test/data/test.db and b/test/data/test.db differ diff --git a/util/config/schema.py b/util/config/schema.py index f37f0e766..276a3de56 100644 --- a/util/config/schema.py +++ b/util/config/schema.py @@ -166,6 +166,16 @@ CONFIG_SCHEMA = { }, ], }, + 'SEARCH_RESULTS_PER_PAGE' : { + 'type': 'number', + 'description': 'Number of results returned per page by search page. Defaults to 10', + 'x-example': 10, + }, + 'SEARCH_MAX_RESULT_PAGE_COUNT' : { + 'type': 'number', + 'description': 'Maximum number of pages the user can paginate in search before they are limited. Defaults to 10', + 'x-example': 10, + }, # E-mail. 'FEATURE_MAILING': {