Skip to content

Commit d8415c3

Browse files
authored
Merge pull request #195 from ioet/feature/paginated-endpoint#192
💥 Paginated endpoint with offset & limit
2 parents b5857c6 + ce01303 commit d8415c3

File tree

5 files changed

+142
-6
lines changed

5 files changed

+142
-6
lines changed

tests/conftest.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,15 @@ def time_entry_repository(app: Flask) -> TimeEntryCosmosDBRepository:
181181
return TimeEntryCosmosDBRepository()
182182

183183

184+
@pytest.fixture
185+
def time_entries_dao():
186+
from time_tracker_api.time_entries.time_entries_namespace import (
187+
time_entries_dao,
188+
)
189+
190+
return time_entries_dao
191+
192+
184193
@pytest.yield_fixture(scope="module")
185194
def running_time_entry(
186195
time_entry_repository: TimeEntryCosmosDBRepository,

tests/time_tracker_api/time_entries/time_entries_namespace_test.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,3 +639,45 @@ def test_summary_is_called_with_date_range_from_worked_time_module(
639639
repository_find_all_mock.assert_called_once_with(
640640
ANY, conditions=conditions, date_range=date_range
641641
)
642+
643+
644+
def test_paginated_fails_with_no_params(
645+
client: FlaskClient, valid_header: dict,
646+
):
647+
response = client.get('/time-entries/paginated', headers=valid_header)
648+
assert HTTPStatus.BAD_REQUEST == response.status_code
649+
650+
651+
def test_paginated_succeeds_with_valid_params(
652+
client: FlaskClient, valid_header: dict,
653+
):
654+
response = client.get(
655+
'/time-entries/paginated?start=10&length=10', headers=valid_header
656+
)
657+
assert HTTPStatus.OK == response.status_code
658+
659+
660+
def test_paginated_response_contains_expected_props(
661+
client: FlaskClient, valid_header: dict,
662+
):
663+
response = client.get(
664+
'/time-entries/paginated?start=10&length=10', headers=valid_header
665+
)
666+
assert 'data' in json.loads(response.data)
667+
assert 'records_total' in json.loads(response.data)
668+
669+
670+
def test_paginated_sends_max_count_and_offset_on_call_to_repository(
671+
client: FlaskClient, valid_header: dict, time_entries_dao
672+
):
673+
time_entries_dao.repository.find_all = Mock(return_value=[])
674+
675+
response = client.get(
676+
'/time-entries/paginated?start=10&length=10', headers=valid_header
677+
)
678+
679+
time_entries_dao.repository.find_all.assert_called_once()
680+
681+
args, kwargs = time_entries_dao.repository.find_all.call_args
682+
assert 'max_count' in kwargs and kwargs['max_count'] is not None
683+
assert 'offset' in kwargs and kwargs['offset'] is not None

time_tracker_api/projects/projects_model.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ def get_all(self, conditions: dict = None, **kwargs) -> list:
7777
"""
7878
event_ctx = self.create_event_context("read-many")
7979
customer_dao = customers_create_dao()
80-
customers = customer_dao.get_all()
80+
customers = customer_dao.get_all(
81+
max_count=kwargs.get('max_count', None)
82+
)
8183

8284
customers_id = [customer.id for customer in customers]
8385
conditions = conditions if conditions else {}

time_tracker_api/time_entries/time_entries_model.py

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ def find_all(
211211
custom_sql_conditions=custom_sql_conditions,
212212
custom_params=custom_params,
213213
max_count=kwargs.get("max_count", None),
214+
offset=kwargs.get("offset", 0),
214215
)
215216

216217
if time_entries:
@@ -221,7 +222,9 @@ def find_all(
221222

222223
project_dao = projects_model.create_dao()
223224
projects = project_dao.get_all(
224-
custom_sql_conditions=[custom_conditions], visible_only=False
225+
custom_sql_conditions=[custom_conditions],
226+
visible_only=False,
227+
max_count=kwargs.get("max_count", None),
225228
)
226229

227230
add_project_info_to_time_entries(time_entries, projects)
@@ -230,6 +233,7 @@ def find_all(
230233
activities = activity_dao.get_all(
231234
custom_sql_conditions=[custom_conditions_activity],
232235
visible_only=False,
236+
max_count=kwargs.get("max_count", None),
233237
)
234238
add_activity_name_to_time_entries(time_entries, activities)
235239

@@ -398,12 +402,10 @@ def stop_time_entry_if_was_left_running(
398402
)
399403
raise CosmosResourceNotFoundError()
400404

401-
def get_all(self, conditions: dict = None, **kwargs) -> list:
402-
event_ctx = self.create_event_context("read-many")
403-
conditions.update({"owner_id": event_ctx.user_id})
405+
def build_custom_query(self, is_admin: bool, conditions: dict = None):
404406
custom_query = []
405407
if "user_id" in conditions:
406-
if event_ctx.is_admin:
408+
if is_admin:
407409
conditions.pop("owner_id")
408410
custom_query = (
409411
[]
@@ -419,6 +421,16 @@ def get_all(self, conditions: dict = None, **kwargs) -> list:
419421
abort(
420422
HTTPStatus.FORBIDDEN, "You don't have enough permissions."
421423
)
424+
return custom_query
425+
426+
def get_all(self, conditions: dict = None, **kwargs) -> list:
427+
event_ctx = self.create_event_context("read-many")
428+
conditions.update({"owner_id": event_ctx.user_id})
429+
430+
custom_query = self.build_custom_query(
431+
is_admin=event_ctx.is_admin, conditions=conditions,
432+
)
433+
422434
date_range = self.handle_date_filter_args(args=conditions)
423435
limit = conditions.get("limit", None)
424436
conditions.pop("limit", None)
@@ -430,6 +442,36 @@ def get_all(self, conditions: dict = None, **kwargs) -> list:
430442
max_count=limit,
431443
)
432444

445+
def get_all_paginated(self, conditions: dict = None, **kwargs) -> list:
446+
event_ctx = self.create_event_context("read-many")
447+
conditions.update({"owner_id": event_ctx.user_id})
448+
449+
custom_query = self.build_custom_query(
450+
is_admin=event_ctx.is_admin, conditions=conditions,
451+
)
452+
453+
date_range = self.handle_date_filter_args(args=conditions)
454+
455+
length = conditions.get("length", None)
456+
conditions.pop("length", None)
457+
458+
start = conditions.get("start", None)
459+
conditions.pop("start", None)
460+
461+
time_entries = self.repository.find_all(
462+
event_ctx,
463+
conditions=conditions,
464+
custom_sql_conditions=custom_query,
465+
date_range=date_range,
466+
max_count=length,
467+
offset=start,
468+
)
469+
470+
return {
471+
'records_total': len(time_entries),
472+
'data': time_entries,
473+
}
474+
433475
def get(self, id):
434476
event_ctx = self.create_event_context("read")
435477

time_tracker_api/time_entries/time_entries_namespace.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,3 +345,44 @@ def get(self):
345345
"""Find the summary of worked time"""
346346
conditions = summary_attribs_parser.parse_args()
347347
return time_entries_dao.get_worked_time(conditions)
348+
349+
350+
time_entry_paginated = ns.model(
351+
'TimeEntryPaginated',
352+
{
353+
'records_total': fields.Integer(
354+
title='Records total', description='Total number of entries.',
355+
),
356+
'data': fields.List(fields.Nested(time_entry)),
357+
},
358+
)
359+
360+
paginated_attribs_parser = ns.parser()
361+
paginated_attribs_parser.add_argument(
362+
'length',
363+
required=True,
364+
type=int,
365+
help="(Filter) The number of rows the endpoint should return.",
366+
location='args',
367+
)
368+
369+
paginated_attribs_parser.add_argument(
370+
'start',
371+
required=True,
372+
type=int,
373+
help="(Filter) The number of rows to be removed from the query. (aka offset)",
374+
location='args',
375+
)
376+
377+
378+
@ns.route('/paginated')
379+
@ns.response(HTTPStatus.OK, 'Time Entries paginated')
380+
@ns.response(HTTPStatus.NOT_FOUND, 'Time entry not found')
381+
class PaginatedTimeEntry(Resource):
382+
@ns.expect(paginated_attribs_parser)
383+
@ns.doc('list_time_entries_paginated')
384+
@ns.marshal_list_with(time_entry_paginated)
385+
def get(self):
386+
"""List all time entries paginated"""
387+
conditions = paginated_attribs_parser.parse_args()
388+
return time_entries_dao.get_all_paginated(conditions)

0 commit comments

Comments
 (0)