From 61673ef2739bc0546f039ff1efb77a9b41283140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Wed, 10 Jan 2018 17:07:21 +0100 Subject: [PATCH] Search: implemented pagination - Got rid of the nasty off-by-one logic in the JavaScript. - Implemented pagination at the API. --- pillar/api/search/queries.py | 14 ++++++++++--- pillar/api/search/routes.py | 33 ++++++++++++++++++++--------- src/scripts/elasticsearch.js | 38 +++++++++++++++++++++------------- src/styles/_search.sass | 3 ++- src/templates/nodes/search.pug | 6 +++--- src/templates/users/index.pug | 6 +++--- 6 files changed, 66 insertions(+), 34 deletions(-) diff --git a/pillar/api/search/queries.py b/pillar/api/search/queries.py index 1f3b1922..ac519ed4 100644 --- a/pillar/api/search/queries.py +++ b/pillar/api/search/queries.py @@ -12,6 +12,7 @@ log = logging.getLogger(__name__) NODE_AGG_TERMS = ['node_type', 'media', 'tags', 'is_free'] USER_AGG_TERMS = ['roles', ] +ITEMS_PER_PAGE = 10 # Will be set in setup_app() client: Elasticsearch = None @@ -54,7 +55,7 @@ def nested_bool(must: list, should: list, terms: dict, *, index_alias: str) -> S return search -def do_node_search(query: str, terms: dict) -> dict: +def do_node_search(query: str, terms: dict, page: int) -> dict: """ Given user query input and term refinements search for public published nodes @@ -82,6 +83,7 @@ def do_node_search(query: str, terms: dict) -> dict: if not query: search = search.sort('-created_at') add_aggs_to_search(search, NODE_AGG_TERMS) + search = paginate(search, page) if log.isEnabledFor(logging.DEBUG): log.debug(json.dumps(search.to_dict(), indent=4)) @@ -94,12 +96,13 @@ def do_node_search(query: str, terms: dict) -> dict: return response.to_dict() -def do_user_search(query: str, terms: dict) -> dict: +def do_user_search(query: str, terms: dict, page: int) -> dict: """ return user objects represented in elasicsearch result dict""" must, should = _common_user_search(query) search = nested_bool(must, should, terms, index_alias='USER') add_aggs_to_search(search, USER_AGG_TERMS) + search = paginate(search, page) if log.isEnabledFor(logging.DEBUG): log.debug(json.dumps(search.to_dict(), indent=4)) @@ -130,7 +133,7 @@ def _common_user_search(query: str) -> (typing.List[Query], typing.List[Query]): return [], should -def do_user_search_admin(query: str, terms: dict) -> dict: +def do_user_search_admin(query: str, terms: dict, page: int) -> dict: """ return users seach result dict object search all user fields and provide aggregation information @@ -150,6 +153,7 @@ def do_user_search_admin(query: str, terms: dict) -> dict: search = nested_bool(must, should, terms, index_alias='USER') add_aggs_to_search(search, USER_AGG_TERMS) + search = paginate(search, page) if log.isEnabledFor(logging.DEBUG): log.debug(json.dumps(search.to_dict(), indent=4)) @@ -162,6 +166,10 @@ def do_user_search_admin(query: str, terms: dict) -> dict: return response.to_dict() +def paginate(search: Search, page_idx: int) -> Search: + return search[page_idx * ITEMS_PER_PAGE:(page_idx + 1) * ITEMS_PER_PAGE] + + def setup_app(app): global client diff --git a/pillar/api/search/routes.py b/pillar/api/search/routes.py index 5cc783c9..ff902ebe 100644 --- a/pillar/api/search/routes.py +++ b/pillar/api/search/routes.py @@ -10,7 +10,6 @@ log = logging.getLogger(__name__) blueprint_search = Blueprint('elksearch', __name__) - TERMS = [ 'node_type', 'media', 'tags', 'is_free', 'projectname', @@ -35,25 +34,38 @@ def _term_filters() -> dict: return {term: request.args.get(term, '') for term in TERMS} +def _page_index() -> int: + """Return the page index from the query string.""" + try: + page_idx = int(request.args.get('page') or '0') + except TypeError: + log.info('invalid page number %r received', request.args.get('page')) + raise wz_exceptions.BadRequest() + return page_idx + + @blueprint_search.route('/') def search_nodes(): searchword = _valid_search() terms = _term_filters() - data = queries.do_node_search(searchword, terms) - return jsonify(data) + page_idx = _page_index() + + result = queries.do_node_search(searchword, terms, page_idx) + return jsonify(result) @blueprint_search.route('/user') def search_user(): searchword = _valid_search() terms = _term_filters() - # data is the raw elasticseach output. + page_idx = _page_index() + # result is the raw elasticseach output. # we need to filter fields in case of user objects. - data = queries.do_user_search(searchword, terms) + result = queries.do_user_search(searchword, terms, page_idx) # filter sensitive stuff # we only need. objectID, full_name, username - hits = data.get('hits') + hits = result.get('hits', {}) new_hits = [] @@ -70,9 +82,9 @@ def search_user(): new_hits.append(single_hit) # replace search result with safe subset - data['hits']['hits'] = new_hits + result['hits']['hits'] = new_hits - return jsonify(data) + return jsonify(result) @blueprint_search.route('/admin/user') @@ -84,6 +96,7 @@ def search_user_admin(): searchword = _valid_search() terms = _term_filters() - data = queries.do_user_search_admin(searchword, terms) + page_idx = _page_index() + result = queries.do_user_search_admin(searchword, terms, page_idx) - return jsonify(data) + return jsonify(result) diff --git a/src/scripts/elasticsearch.js b/src/scripts/elasticsearch.js index 449b0a79..2d814cef 100644 --- a/src/scripts/elasticsearch.js +++ b/src/scripts/elasticsearch.js @@ -1,5 +1,5 @@ $(document).ready(function() { - var HITS_PER_PAGE = 25; + var HITS_PER_PAGE = 10; var MAX_VALUES_PER_FACET = 30; // DOM binding @@ -174,41 +174,48 @@ $(document).ready(function() { return; } - var maxPages = 2; - var nbPages = content.count / HITS_PER_PAGE; + var maxPages = 3; + var nbPages = Math.floor(content.count / HITS_PER_PAGE); // Process pagination var pages = []; if (content.page > maxPages) { pages.push({ current: false, - number: 1 + number: 0, + shownr: 1 }); - // They don't really add much... - // pages.push({ current: false, number: '...', disabled: true }); } for (var p = content.page - maxPages; p < content.page + maxPages; ++p) { - if (p < 0 || p >= nbPages) { + if (p < 0 || p > nbPages) { continue; } pages.push({ current: content.page === p, - number: (p + 1) + number: p, + shownr: p+1 }); } if (content.page + maxPages < nbPages) { - // They don't really add much... - // pages.push({ current: false, number: '...', disabled: true }); pages.push({ current: false, - number: nbPages + number: nbPages-1, + shownr: nbPages }); } + console.log('showing page', content.page); var pagination = { pages: pages, - prev_page: (content.page > 0 ? content.page : false), - next_page: (content.page + 1 < nbPages ? content.page + 2 : false) }; + if (content.page > 0) { + pagination.prev_page = {page: content.page - 1}; + } + if (content.page < nbPages) { + pagination.next_page = {page: content.page + 1}; + } + console.log('next page', pagination.next_page); + console.log('prev page', pagination.prev_page); + console.log('nbPages', nbPages); // Display pagination $pagination.html(paginationTemplate.render(pagination)); } @@ -230,7 +237,10 @@ $(document).ready(function() { }); $(document).on('click', '.gotoPage', function() { - //helper.setCurrentPage(+$(this).data('page') - 1).search(); + const page_idx = $(this).data('page'); + search.setCurrentPage(page_idx); + search.execute(); + $("html, body").animate({ scrollTop: 0 }, '500', 'swing'); diff --git a/src/styles/_search.sass b/src/styles/_search.sass index 75ff1563..7637ad7b 100644 --- a/src/styles/_search.sass +++ b/src/styles/_search.sass @@ -248,7 +248,8 @@ $search-hit-width_grid: 100px opacity: .6 &.active a - color: white + color: $color-text-dark-primary + font-weight: bold #search-list width: 40% diff --git a/src/templates/nodes/search.pug b/src/templates/nodes/search.pug index 4cb112d9..5de2e1bd 100644 --- a/src/templates/nodes/search.pug +++ b/src/templates/nodes/search.pug @@ -155,11 +155,11 @@ script(type="text/template", id="hit-template") // Pagination template script(type="text/template", id="pagination-template") ul.search-pagination. -
  • +
  • {{#pages}} -
  • {{ number }}
  • +
  • {{ shownr }}
  • {{/pages}} -
  • +
  • // Stats template script(type="text/template", id="stats-template") diff --git a/src/templates/users/index.pug b/src/templates/users/index.pug index a1bd163b..1823a862 100644 --- a/src/templates/users/index.pug +++ b/src/templates/users/index.pug @@ -78,11 +78,11 @@ script(type="text/template", id="hit-template") // Pagination template script(type="text/template", id="pagination-template") ul.search-pagination. -
  • +
  • {{#pages}} -
  • {{ number }}
  • +
  • {{ shownr }}
  • {{/pages}} -
  • +
  • // Stats template script(type="text/template", id="stats-template")