server/api: let users control page size
This commit is contained in:
parent
fba239855c
commit
d97a332d23
5 changed files with 64 additions and 50 deletions
2
API.md
2
API.md
|
@ -77,7 +77,7 @@ data.
|
|||
|
||||
|
||||
### Listing users
|
||||
Request: `GET /users/?page=<page>&query=<query>`
|
||||
Request: `GET /users/?page=<page>&pageSize=<page-size>&query=<query>`
|
||||
Output:
|
||||
```json5
|
||||
{
|
||||
|
|
|
@ -38,11 +38,13 @@ class UserListApi(BaseApi):
|
|||
auth.verify_privilege(context.user, 'users:list')
|
||||
query = context.get_param_as_string('query')
|
||||
page = context.get_param_as_int('page', 1)
|
||||
count, user_list = self._search_executor.execute(context.session, query, page)
|
||||
page_size = min(100, context.get_param_as_int('pageSize', required=False) or 100)
|
||||
count, user_list = self._search_executor.execute(
|
||||
context.session, query, page, page_size)
|
||||
return {
|
||||
'query': query,
|
||||
'page': page,
|
||||
'pageSize': self._search_executor.page_size,
|
||||
'pageSize': page_size,
|
||||
'total': count,
|
||||
'users': [_serialize_user(context.user, user) for user in user_list],
|
||||
}
|
||||
|
|
|
@ -13,17 +13,16 @@ class SearchExecutor(object):
|
|||
ORDER_ASC = 2
|
||||
|
||||
def __init__(self, search_config):
|
||||
self.page_size = 100
|
||||
self._search_config = search_config
|
||||
|
||||
def execute(self, session, query_text, page):
|
||||
def execute(self, session, query_text, page, page_size):
|
||||
'''
|
||||
Parse input and return tuple containing total record count and filtered
|
||||
entities.
|
||||
'''
|
||||
filter_query = self._prepare(session, query_text)
|
||||
entities = filter_query \
|
||||
.offset((page - 1) * self.page_size).limit(self.page_size).all()
|
||||
.offset((page - 1) * page_size).limit(page_size).all()
|
||||
count_query = filter_query.statement \
|
||||
.with_only_columns([sqlalchemy.func.count()]).order_by(None)
|
||||
count = filter_query.session.execute(count_query).scalar()
|
||||
|
|
|
@ -25,12 +25,16 @@ def mock_context(parent):
|
|||
parent.context = context
|
||||
|
||||
def mock_params(context, params):
|
||||
def get_param_as_string(key, default=None):
|
||||
def get_param_as_string(key, default=None, required=False):
|
||||
if key not in params:
|
||||
if required:
|
||||
raise RuntimeError('Param is missing!')
|
||||
return default
|
||||
return params[key]
|
||||
def get_param_as_int(key, default=None):
|
||||
def get_param_as_int(key, default=None, required=False):
|
||||
if key not in params:
|
||||
if required:
|
||||
raise RuntimeError('Param is missing!')
|
||||
return default
|
||||
return int(params[key])
|
||||
context.get_param_as_string = get_param_as_string
|
||||
|
|
|
@ -9,11 +9,20 @@ class TestUserSearchExecutor(DatabaseTestCase):
|
|||
self.search_config = search.UserSearchConfig()
|
||||
self.executor = search.SearchExecutor(self.search_config)
|
||||
|
||||
def _test(self, query, page, expected_count, expected_user_names):
|
||||
count, users = self.executor.execute(self.session, query, page)
|
||||
def _test(self, query, page, page_size, expected_count, expected_user_names):
|
||||
count, users = self.executor.execute(self.session, query, page, page_size)
|
||||
self.assertEqual(count, expected_count)
|
||||
self.assertEqual([u.name for u in users], expected_user_names)
|
||||
|
||||
def _test_raises(self, query, page, page_size):
|
||||
self.assertRaises(
|
||||
errors.SearchError,
|
||||
self.executor.execute,
|
||||
self.session,
|
||||
query,
|
||||
page,
|
||||
page_size)
|
||||
|
||||
def test_filter_by_creation_time(self):
|
||||
user1 = util.mock_user('u1')
|
||||
user2 = util.mock_user('u2')
|
||||
|
@ -21,7 +30,7 @@ class TestUserSearchExecutor(DatabaseTestCase):
|
|||
user2.creation_time = datetime(2015, 1, 1)
|
||||
self.session.add_all([user1, user2])
|
||||
for alias in ['creation-time', 'creation-date']:
|
||||
self._test('%s:2014' % alias, 1, 1, ['u1'])
|
||||
self._test('%s:2014' % alias, 1, 100, 1, ['u1'])
|
||||
|
||||
def test_filter_by_negated_creation_time(self):
|
||||
user1 = util.mock_user('u1')
|
||||
|
@ -30,7 +39,7 @@ class TestUserSearchExecutor(DatabaseTestCase):
|
|||
user2.creation_time = datetime(2015, 1, 1)
|
||||
self.session.add_all([user1, user2])
|
||||
for alias in ['creation-time', 'creation-date']:
|
||||
self._test('-%s:2014' % alias, 1, 1, ['u2'])
|
||||
self._test('-%s:2014' % alias, 1, 100, 1, ['u2'])
|
||||
|
||||
def test_filter_by_ranged_creation_time(self):
|
||||
user1 = util.mock_user('u1')
|
||||
|
@ -41,12 +50,11 @@ class TestUserSearchExecutor(DatabaseTestCase):
|
|||
user3.creation_time = datetime(2015, 1, 1)
|
||||
self.session.add_all([user1, user2, user3])
|
||||
for alias in ['creation-time', 'creation-date']:
|
||||
self._test('%s:2014..2014-06' % alias, 1, 2, ['u1', 'u2'])
|
||||
self._test('%s:2014-06..2015-01-01' % alias, 1, 2, ['u2', 'u3'])
|
||||
self._test('%s:2014-06..' % alias, 1, 2, ['u2', 'u3'])
|
||||
self._test('%s:..2014-06' % alias, 1, 2, ['u1', 'u2'])
|
||||
self.assertRaises(
|
||||
errors.SearchError, self.executor.execute, self.session, '%s:..', 1)
|
||||
self._test('%s:2014..2014-06' % alias, 1, 100, 2, ['u1', 'u2'])
|
||||
self._test('%s:2014-06..2015-01-01' % alias, 1, 100, 2, ['u2', 'u3'])
|
||||
self._test('%s:2014-06..' % alias, 1, 100, 2, ['u2', 'u3'])
|
||||
self._test('%s:..2014-06' % alias, 1, 100, 2, ['u1', 'u2'])
|
||||
self._test_raises('%s:..' % alias, 1, 100)
|
||||
|
||||
def test_filter_by_negated_ranged_creation_time(self):
|
||||
user1 = util.mock_user('u1')
|
||||
|
@ -57,8 +65,8 @@ class TestUserSearchExecutor(DatabaseTestCase):
|
|||
user3.creation_time = datetime(2015, 1, 1)
|
||||
self.session.add_all([user1, user2, user3])
|
||||
for alias in ['creation-time', 'creation-date']:
|
||||
self._test('-%s:2014..2014-06' % alias, 1, 1, ['u3'])
|
||||
self._test('-%s:2014-06..2015-01-01' % alias, 1, 1, ['u1'])
|
||||
self._test('-%s:2014..2014-06' % alias, 1, 100, 1, ['u3'])
|
||||
self._test('-%s:2014-06..2015-01-01' % alias, 1, 100, 1, ['u1'])
|
||||
|
||||
def test_filter_by_composite_creation_time(self):
|
||||
user1 = util.mock_user('u1')
|
||||
|
@ -69,7 +77,7 @@ class TestUserSearchExecutor(DatabaseTestCase):
|
|||
user3.creation_time = datetime(2015, 1, 1)
|
||||
self.session.add_all([user1, user2, user3])
|
||||
for alias in ['creation-time', 'creation-date']:
|
||||
self._test('%s:2014-01,2015' % alias, 1, 2, ['u1', 'u3'])
|
||||
self._test('%s:2014-01,2015' % alias, 1, 100, 2, ['u1', 'u3'])
|
||||
|
||||
def test_filter_by_negated_composite_creation_time(self):
|
||||
user1 = util.mock_user('u1')
|
||||
|
@ -80,52 +88,50 @@ class TestUserSearchExecutor(DatabaseTestCase):
|
|||
user3.creation_time = datetime(2015, 1, 1)
|
||||
self.session.add_all([user1, user2, user3])
|
||||
for alias in ['creation-time', 'creation-date']:
|
||||
self._test('-%s:2014-01,2015' % alias, 1, 1, ['u2'])
|
||||
self._test('-%s:2014-01,2015' % alias, 1, 100, 1, ['u2'])
|
||||
|
||||
def test_filter_by_name(self):
|
||||
self.session.add(util.mock_user('u1'))
|
||||
self.session.add(util.mock_user('u2'))
|
||||
self._test('name:u1', 1, 1, ['u1'])
|
||||
self._test('name:u2', 1, 1, ['u2'])
|
||||
self._test('name:u1', 1, 100, 1, ['u1'])
|
||||
self._test('name:u2', 1, 100, 1, ['u2'])
|
||||
|
||||
def test_filter_by_negated_name(self):
|
||||
self.session.add(util.mock_user('u1'))
|
||||
self.session.add(util.mock_user('u2'))
|
||||
self._test('-name:u1', 1, 1, ['u2'])
|
||||
self._test('-name:u2', 1, 1, ['u1'])
|
||||
self._test('-name:u1', 1, 100, 1, ['u2'])
|
||||
self._test('-name:u2', 1, 100, 1, ['u1'])
|
||||
|
||||
def test_filter_by_composite_name(self):
|
||||
self.session.add(util.mock_user('u1'))
|
||||
self.session.add(util.mock_user('u2'))
|
||||
self.session.add(util.mock_user('u3'))
|
||||
self._test('name:u1,u2', 1, 2, ['u1', 'u2'])
|
||||
self._test('name:u1,u2', 1, 100, 2, ['u1', 'u2'])
|
||||
|
||||
def test_filter_by_negated_composite_name(self):
|
||||
self.session.add(util.mock_user('u1'))
|
||||
self.session.add(util.mock_user('u2'))
|
||||
self.session.add(util.mock_user('u3'))
|
||||
self._test('-name:u1,u3', 1, 1, ['u2'])
|
||||
self._test('-name:u1,u3', 1, 100, 1, ['u2'])
|
||||
|
||||
def test_filter_by_ranged_name(self):
|
||||
self.assertRaises(
|
||||
errors.SearchError, self.executor.execute, self.session, 'name:u1..u2', 1)
|
||||
self._test_raises('name:u1..u2', 1, 100)
|
||||
|
||||
def test_paging(self):
|
||||
self.executor.page_size = 1
|
||||
self.session.add(util.mock_user('u1'))
|
||||
self.session.add(util.mock_user('u2'))
|
||||
self._test('', 1, 2, ['u1'])
|
||||
self._test('', 2, 2, ['u2'])
|
||||
self._test('', 1, 1, 2, ['u1'])
|
||||
self._test('', 2, 1, 2, ['u2'])
|
||||
|
||||
def test_order_by_name(self):
|
||||
self.session.add(util.mock_user('u1'))
|
||||
self.session.add(util.mock_user('u2'))
|
||||
self._test('order:name', 1, 2, ['u1', 'u2'])
|
||||
self._test('-order:name', 1, 2, ['u2', 'u1'])
|
||||
self._test('order:name,asc', 1, 2, ['u1', 'u2'])
|
||||
self._test('order:name,desc', 1, 2, ['u2', 'u1'])
|
||||
self._test('-order:name,asc', 1, 2, ['u2', 'u1'])
|
||||
self._test('-order:name,desc', 1, 2, ['u1', 'u2'])
|
||||
self._test('order:name', 1, 100, 2, ['u1', 'u2'])
|
||||
self._test('-order:name', 1, 100, 2, ['u2', 'u1'])
|
||||
self._test('order:name,asc', 1, 100, 2, ['u1', 'u2'])
|
||||
self._test('order:name,desc', 1, 100, 2, ['u2', 'u1'])
|
||||
self._test('-order:name,asc', 1, 100, 2, ['u2', 'u1'])
|
||||
self._test('-order:name,desc', 1, 100, 2, ['u1', 'u2'])
|
||||
|
||||
def test_invalid_tokens(self):
|
||||
for query in [
|
||||
|
@ -135,20 +141,24 @@ class TestUserSearchExecutor(DatabaseTestCase):
|
|||
'order:name,asc,desc',
|
||||
'bad:x',
|
||||
'special:unsupported']:
|
||||
self.assertRaises(
|
||||
errors.SearchError, self.executor.execute, self.session, query, 1)
|
||||
self._test_raises(query, 1, 100)
|
||||
|
||||
def test_anonymous(self):
|
||||
self.session.add(util.mock_user('u1'))
|
||||
self.session.add(util.mock_user('u2'))
|
||||
self._test('u1', 1, 1, ['u1'])
|
||||
self._test('u2', 1, 1, ['u2'])
|
||||
self._test('u1', 1, 100, 1, ['u1'])
|
||||
self._test('u2', 1, 100, 1, ['u2'])
|
||||
|
||||
def test_empty_search(self):
|
||||
self.session.add(util.mock_user('u1'))
|
||||
self.session.add(util.mock_user('u2'))
|
||||
self._test('', 1, 100, 2, ['u1', 'u2'])
|
||||
|
||||
def test_negated_anonymous(self):
|
||||
self.session.add(util.mock_user('u1'))
|
||||
self.session.add(util.mock_user('u2'))
|
||||
self._test('-u1', 1, 1, ['u2'])
|
||||
self._test('-u2', 1, 1, ['u1'])
|
||||
self._test('-u1', 1, 100, 1, ['u2'])
|
||||
self._test('-u2', 1, 100, 1, ['u1'])
|
||||
|
||||
def test_combining(self):
|
||||
user1 = util.mock_user('u1')
|
||||
|
@ -158,10 +168,9 @@ class TestUserSearchExecutor(DatabaseTestCase):
|
|||
user2.creation_time = datetime(2014, 6, 1)
|
||||
user3.creation_time = datetime(2015, 1, 1)
|
||||
self.session.add_all([user1, user2, user3])
|
||||
self._test('creation-time:2014 u1', 1, 1, ['u1'])
|
||||
self._test('creation-time:2014 u2', 1, 1, ['u2'])
|
||||
self._test('creation-time:2016 u2', 1, 0, [])
|
||||
self._test('creation-time:2014 u1', 1, 100, 1, ['u1'])
|
||||
self._test('creation-time:2014 u2', 1, 100, 1, ['u2'])
|
||||
self._test('creation-time:2016 u2', 1, 100, 0, [])
|
||||
|
||||
def test_special(self):
|
||||
self.assertRaises(
|
||||
errors.SearchError, self.executor.execute, self.session, 'special:-', 1)
|
||||
self._test_raises('special:-', 1, 100)
|
||||
|
|
Loading…
Reference in a new issue