Search: implemented pagination
- Got rid of the nasty off-by-one logic in the JavaScript. - Implemented pagination at the API.
This commit is contained in:
@@ -12,6 +12,7 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
NODE_AGG_TERMS = ['node_type', 'media', 'tags', 'is_free']
|
NODE_AGG_TERMS = ['node_type', 'media', 'tags', 'is_free']
|
||||||
USER_AGG_TERMS = ['roles', ]
|
USER_AGG_TERMS = ['roles', ]
|
||||||
|
ITEMS_PER_PAGE = 10
|
||||||
|
|
||||||
# Will be set in setup_app()
|
# Will be set in setup_app()
|
||||||
client: Elasticsearch = None
|
client: Elasticsearch = None
|
||||||
@@ -54,7 +55,7 @@ def nested_bool(must: list, should: list, terms: dict, *, index_alias: str) -> S
|
|||||||
return search
|
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
|
Given user query input and term refinements
|
||||||
search for public published nodes
|
search for public published nodes
|
||||||
@@ -82,6 +83,7 @@ def do_node_search(query: str, terms: dict) -> dict:
|
|||||||
if not query:
|
if not query:
|
||||||
search = search.sort('-created_at')
|
search = search.sort('-created_at')
|
||||||
add_aggs_to_search(search, NODE_AGG_TERMS)
|
add_aggs_to_search(search, NODE_AGG_TERMS)
|
||||||
|
search = paginate(search, page)
|
||||||
|
|
||||||
if log.isEnabledFor(logging.DEBUG):
|
if log.isEnabledFor(logging.DEBUG):
|
||||||
log.debug(json.dumps(search.to_dict(), indent=4))
|
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()
|
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"""
|
""" return user objects represented in elasicsearch result dict"""
|
||||||
|
|
||||||
must, should = _common_user_search(query)
|
must, should = _common_user_search(query)
|
||||||
search = nested_bool(must, should, terms, index_alias='USER')
|
search = nested_bool(must, should, terms, index_alias='USER')
|
||||||
add_aggs_to_search(search, USER_AGG_TERMS)
|
add_aggs_to_search(search, USER_AGG_TERMS)
|
||||||
|
search = paginate(search, page)
|
||||||
|
|
||||||
if log.isEnabledFor(logging.DEBUG):
|
if log.isEnabledFor(logging.DEBUG):
|
||||||
log.debug(json.dumps(search.to_dict(), indent=4))
|
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
|
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
|
return users seach result dict object
|
||||||
search all user fields and provide aggregation information
|
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')
|
search = nested_bool(must, should, terms, index_alias='USER')
|
||||||
add_aggs_to_search(search, USER_AGG_TERMS)
|
add_aggs_to_search(search, USER_AGG_TERMS)
|
||||||
|
search = paginate(search, page)
|
||||||
|
|
||||||
if log.isEnabledFor(logging.DEBUG):
|
if log.isEnabledFor(logging.DEBUG):
|
||||||
log.debug(json.dumps(search.to_dict(), indent=4))
|
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()
|
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):
|
def setup_app(app):
|
||||||
global client
|
global client
|
||||||
|
|
||||||
|
@@ -10,7 +10,6 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
blueprint_search = Blueprint('elksearch', __name__)
|
blueprint_search = Blueprint('elksearch', __name__)
|
||||||
|
|
||||||
|
|
||||||
TERMS = [
|
TERMS = [
|
||||||
'node_type', 'media',
|
'node_type', 'media',
|
||||||
'tags', 'is_free', 'projectname',
|
'tags', 'is_free', 'projectname',
|
||||||
@@ -35,25 +34,38 @@ def _term_filters() -> dict:
|
|||||||
return {term: request.args.get(term, '') for term in TERMS}
|
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('/')
|
@blueprint_search.route('/')
|
||||||
def search_nodes():
|
def search_nodes():
|
||||||
searchword = _valid_search()
|
searchword = _valid_search()
|
||||||
terms = _term_filters()
|
terms = _term_filters()
|
||||||
data = queries.do_node_search(searchword, terms)
|
page_idx = _page_index()
|
||||||
return jsonify(data)
|
|
||||||
|
result = queries.do_node_search(searchword, terms, page_idx)
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
@blueprint_search.route('/user')
|
@blueprint_search.route('/user')
|
||||||
def search_user():
|
def search_user():
|
||||||
searchword = _valid_search()
|
searchword = _valid_search()
|
||||||
terms = _term_filters()
|
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.
|
# 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
|
# filter sensitive stuff
|
||||||
# we only need. objectID, full_name, username
|
# we only need. objectID, full_name, username
|
||||||
hits = data.get('hits')
|
hits = result.get('hits', {})
|
||||||
|
|
||||||
new_hits = []
|
new_hits = []
|
||||||
|
|
||||||
@@ -70,9 +82,9 @@ def search_user():
|
|||||||
new_hits.append(single_hit)
|
new_hits.append(single_hit)
|
||||||
|
|
||||||
# replace search result with safe subset
|
# 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')
|
@blueprint_search.route('/admin/user')
|
||||||
@@ -84,6 +96,7 @@ def search_user_admin():
|
|||||||
|
|
||||||
searchword = _valid_search()
|
searchword = _valid_search()
|
||||||
terms = _term_filters()
|
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)
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
var HITS_PER_PAGE = 25;
|
var HITS_PER_PAGE = 10;
|
||||||
var MAX_VALUES_PER_FACET = 30;
|
var MAX_VALUES_PER_FACET = 30;
|
||||||
|
|
||||||
// DOM binding
|
// DOM binding
|
||||||
@@ -174,41 +174,48 @@ $(document).ready(function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var maxPages = 2;
|
var maxPages = 3;
|
||||||
var nbPages = content.count / HITS_PER_PAGE;
|
var nbPages = Math.floor(content.count / HITS_PER_PAGE);
|
||||||
|
|
||||||
// Process pagination
|
// Process pagination
|
||||||
var pages = [];
|
var pages = [];
|
||||||
if (content.page > maxPages) {
|
if (content.page > maxPages) {
|
||||||
pages.push({
|
pages.push({
|
||||||
current: false,
|
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) {
|
for (var p = content.page - maxPages; p < content.page + maxPages; ++p) {
|
||||||
if (p < 0 || p >= nbPages) {
|
if (p < 0 || p > nbPages) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
pages.push({
|
pages.push({
|
||||||
current: content.page === p,
|
current: content.page === p,
|
||||||
number: (p + 1)
|
number: p,
|
||||||
|
shownr: p+1
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (content.page + maxPages < nbPages) {
|
if (content.page + maxPages < nbPages) {
|
||||||
// They don't really add much...
|
|
||||||
// pages.push({ current: false, number: '...', disabled: true });
|
|
||||||
pages.push({
|
pages.push({
|
||||||
current: false,
|
current: false,
|
||||||
number: nbPages
|
number: nbPages-1,
|
||||||
|
shownr: nbPages
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
console.log('showing page', content.page);
|
||||||
var pagination = {
|
var pagination = {
|
||||||
pages: pages,
|
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
|
// Display pagination
|
||||||
$pagination.html(paginationTemplate.render(pagination));
|
$pagination.html(paginationTemplate.render(pagination));
|
||||||
}
|
}
|
||||||
@@ -230,7 +237,10 @@ $(document).ready(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('click', '.gotoPage', 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({
|
$("html, body").animate({
|
||||||
scrollTop: 0
|
scrollTop: 0
|
||||||
}, '500', 'swing');
|
}, '500', 'swing');
|
||||||
|
@@ -248,7 +248,8 @@ $search-hit-width_grid: 100px
|
|||||||
opacity: .6
|
opacity: .6
|
||||||
|
|
||||||
&.active a
|
&.active a
|
||||||
color: white
|
color: $color-text-dark-primary
|
||||||
|
font-weight: bold
|
||||||
|
|
||||||
#search-list
|
#search-list
|
||||||
width: 40%
|
width: 40%
|
||||||
|
@@ -155,11 +155,11 @@ script(type="text/template", id="hit-template")
|
|||||||
// Pagination template
|
// Pagination template
|
||||||
script(type="text/template", id="pagination-template")
|
script(type="text/template", id="pagination-template")
|
||||||
ul.search-pagination.
|
ul.search-pagination.
|
||||||
<li {{^prev_page}}class="disabled"{{/prev_page}}><a href="#" {{#prev_page}} class="gotoPage" data-page="{{ prev_page }}" {{/prev_page}}><i class="pi-angle-left"></i></a></li>
|
<li {{^prev_page}}class="disabled"{{/prev_page}}><a href="#" {{#prev_page}} class="gotoPage" data-page="{{ prev_page.page }}" {{/prev_page}}><i class="pi-angle-left"></i></a></li>
|
||||||
{{#pages}}
|
{{#pages}}
|
||||||
<li class="{{#current}}active{{/current}}{{#disabled}}disabled{{/disabled}}"><a href="#" {{^disabled}} class="gotoPage" data-page="{{ number }}" {{/disabled}}>{{ number }}</a></li>
|
<li class="{{#current}}active{{/current}}{{#disabled}}disabled{{/disabled}}"><a href="#" {{^disabled}} class="gotoPage" data-page="{{ number }}" {{/disabled}}>{{ shownr }}</a></li>
|
||||||
{{/pages}}
|
{{/pages}}
|
||||||
<li {{^next_page}}class="disabled"{{/next_page}}><a href="#" {{#next_page}} class="gotoPage" data-page="{{ next_page }}" {{/next_page}}><i class="pi-angle-right"></i></a></li>
|
<li {{^next_page}}class="disabled"{{/next_page}}><a href="#" {{#next_page}} class="gotoPage" data-page="{{ page }}" {{/next_page}}><i class="pi-angle-right"></i></a></li>
|
||||||
|
|
||||||
// Stats template
|
// Stats template
|
||||||
script(type="text/template", id="stats-template")
|
script(type="text/template", id="stats-template")
|
||||||
|
@@ -78,11 +78,11 @@ script(type="text/template", id="hit-template")
|
|||||||
// Pagination template
|
// Pagination template
|
||||||
script(type="text/template", id="pagination-template")
|
script(type="text/template", id="pagination-template")
|
||||||
ul.search-pagination.
|
ul.search-pagination.
|
||||||
<li {{^prev_page}}class="disabled"{{/prev_page}}><a href="#" {{#prev_page}} class="gotoPage" data-page="{{ prev_page }}" {{/prev_page}}><i class="pi-angle-left"></i></a></li>
|
<li {{^prev_page}}class="disabled"{{/prev_page}}><a href="#" {{#prev_page}} class="gotoPage" data-page="{{ page }}" {{/prev_page}}><i class="pi-angle-left"></i></a></li>
|
||||||
{{#pages}}
|
{{#pages}}
|
||||||
<li class="{{#current}}active{{/current}}{{#disabled}}disabled{{/disabled}}"><a href="#" {{^disabled}} class="gotoPage" data-page="{{ number }}" {{/disabled}}>{{ number }}</a></li>
|
<li class="{{#current}}active{{/current}}{{#disabled}}disabled{{/disabled}}"><a href="#" {{^disabled}} class="gotoPage" data-page="{{ number }}" {{/disabled}}>{{ shownr }}</a></li>
|
||||||
{{/pages}}
|
{{/pages}}
|
||||||
<li {{^next_page}}class="disabled"{{/next_page}}><a href="#" {{#next_page}} class="gotoPage" data-page="{{ next_page }}" {{/next_page}}><i class="pi-angle-right"></i></a></li>
|
<li {{^next_page}}class="disabled"{{/next_page}}><a href="#" {{#next_page}} class="gotoPage" data-page="{{ page }}" {{/next_page}}><i class="pi-angle-right"></i></a></li>
|
||||||
|
|
||||||
// Stats template
|
// Stats template
|
||||||
script(type="text/template", id="stats-template")
|
script(type="text/template", id="stats-template")
|
||||||
|
Reference in New Issue
Block a user