search: added per-backend sorting fields.

- unified naming to be more user friendly
- only sort on supported fields that each backend exposes
- fixed few cases of sort errors and added additional tests
This commit is contained in:
Daniel Dourvaris 2019-10-02 17:23:34 +02:00
parent cdb5181fd2
commit e9018c74bd
7 changed files with 74 additions and 21 deletions

View file

@ -27,6 +27,16 @@ from rhodecode.api.tests.utils import (
@pytest.mark.usefixtures("testuser_api", "app")
class TestApiSearch(object):
@pytest.mark.parametrize("sort_dir", [
"asc",
"desc",
])
@pytest.mark.parametrize("sort", [
"xxx",
"author_email",
"date",
"message",
])
@pytest.mark.parametrize("query, expected_hits, expected_paths", [
('todo', 23, [
'vcs/backends/hg/inmemory.py',
@ -55,10 +65,11 @@ class TestApiSearch(object):
'vcs/tests/test_cli.py']),
('owner:michał test', 0, []),
])
def test_search_content_results(self, query, expected_hits, expected_paths):
def test_search_content_results(self, sort_dir, sort, query, expected_hits, expected_paths):
id_, params = build_data(
self.apikey_regular, 'search',
search_query=query,
search_sort='{}:{}'.format(sort_dir, sort),
search_type='content')
response = api_call(self.app, params)
@ -70,6 +81,16 @@ class TestApiSearch(object):
for expected_path in expected_paths:
assert expected_path in paths
@pytest.mark.parametrize("sort_dir", [
"asc",
"desc",
])
@pytest.mark.parametrize("sort", [
"xxx",
"date",
"file",
"size",
])
@pytest.mark.parametrize("query, expected_hits, expected_paths", [
('readme.rst', 3, []),
('test*', 75, []),
@ -77,10 +98,11 @@ class TestApiSearch(object):
('extension:rst', 48, []),
('extension:rst api', 24, []),
])
def test_search_file_paths(self, query, expected_hits, expected_paths):
def test_search_file_paths(self, sort_dir, sort, query, expected_hits, expected_paths):
id_, params = build_data(
self.apikey_regular, 'search',
search_query=query,
search_sort='{}:{}'.format(sort_dir, sort),
search_type='path')
response = api_call(self.app, params)

View file

@ -110,9 +110,13 @@ def perform_search(request, tmpl_context, repo_name=None, repo_group_name=None):
c.search_tags = search_tags
direction, sort_field = searcher.get_sort(search_type, search_sort)
c.sort = '{}:{}'.format(direction, sort_field)
c.sort_tag = sort_field
sort_definition = searcher.sort_def(search_type, direction, sort_field)
c.sort = ''
c.sort_tag = None
c.sort_tag_dir = direction
if sort_definition:
c.sort = '{}:{}'.format(direction, sort_field)
c.sort_tag = sort_field
class SearchView(BaseAppView):

View file

@ -89,6 +89,15 @@ class BaseSearcher(object):
"""
return val
def sort_def(self, search_type, direction, sort_field):
"""
Defines sorting for search. This function should decide if for given
search_type, sorting can be done with sort_field.
It also should translate common sort fields into backend specific. e.g elasticsearch
"""
raise NotImplementedError()
@staticmethod
def get_sort(search_type, search_val):
"""

View file

@ -99,6 +99,29 @@ class WhooshSearcher(BaseSearcher):
query = u'(%s) OR %s' % (query, hashes_or_query)
return query
def sort_def(self, search_type, direction, sort_field):
if search_type == 'commit':
field_defs = {
'message': 'message',
'date': 'date',
'author_email': 'author',
}
elif search_type == 'path':
field_defs = {
'file': 'path',
'size': 'size',
'lines': 'lines',
}
elif search_type == 'content':
# NOTE(dan): content doesn't support any sorting
field_defs = {}
else:
return ''
if sort_field in field_defs:
return field_defs[sort_field]
def search(self, query, document_type, search_user,
repo_name=None, repo_group_name=None,
requested_page=1, page_limit=10, sort=None, raise_on_exc=True):
@ -124,21 +147,16 @@ class WhooshSearcher(BaseSearcher):
query = qp.parse(safe_unicode(query))
log.debug('query: %s (%s)', query, repr(query))
def sort_def(_direction, _sort_field):
field2whoosh = {
'message.raw': 'message',
'author.email.raw': 'author',
}
return field2whoosh.get(_sort_field) or _sort_field
reverse, sorted_by = False, None
direction, sort_field = self.get_sort(search_type, sort)
if sort_field:
if direction == Searcher.DIRECTION_DESC:
reverse = True
if direction == Searcher.DIRECTION_ASC:
reverse = False
sorted_by = sort_def(direction, sort_field)
sort_definition = self.sort_def(search_type, direction, sort_field)
if sort_definition:
sorted_by = sort_definition
if direction == Searcher.DIRECTION_DESC:
reverse = True
if direction == Searcher.DIRECTION_ASC:
reverse = False
whoosh_results = self.searcher.search(
query, filter=allowed_repos_filter, limit=None,

View file

@ -69,7 +69,7 @@
elif c.sort.startswith('desc:'):
return c.url_generator(sort='asc:{}'.format(field_name))
return 'asc:{}'.format(field_name)
return c.url_generator(sort='asc:{}'.format(field_name))
%>
</%def>

View file

@ -9,13 +9,13 @@
<th>${_('Commit')}</th>
<th></th>
<th>
<a href="${search.field_sort('message.raw')}">${_('Commit message')}</a>
<a href="${search.field_sort('message')}">${_('Commit message')}</a>
</th>
<th>
<a href="${search.field_sort('date')}">${_('Age')}</a>
<a href="${search.field_sort('date')}">${_('Commit date')}</a>
</th>
<th>
<a href="${search.field_sort('author.email.raw')}">${_('Author')}</a>
<a href="${search.field_sort('author_email')}">${_('Author')}</a>
</th>
</tr>
%for entry in c.formatted_results:

View file

@ -6,7 +6,7 @@
<tr>
<th>${_('Repository')}</th>
<th>
<a href="${search.field_sort('file.raw')}">${_('File')}</a>
<a href="${search.field_sort('file')}">${_('File')}</a>
</th>
<th>
<a href="${search.field_sort('size')}">${_('Size')}</a>