Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,15 @@ def time_entry_repository(app: Flask) -> TimeEntryCosmosDBRepository:
return TimeEntryCosmosDBRepository()


@pytest.fixture
def time_entries_dao():
from time_tracker_api.time_entries.time_entries_namespace import (
time_entries_dao,
)

return time_entries_dao


@pytest.yield_fixture(scope="module")
def running_time_entry(
time_entry_repository: TimeEntryCosmosDBRepository,
Expand Down
42 changes: 42 additions & 0 deletions tests/time_tracker_api/time_entries/time_entries_namespace_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -639,3 +639,45 @@ def test_summary_is_called_with_date_range_from_worked_time_module(
repository_find_all_mock.assert_called_once_with(
ANY, conditions=conditions, date_range=date_range
)


def test_paginated_fails_with_no_params(
client: FlaskClient, valid_header: dict,
):
response = client.get('/time-entries/paginated', headers=valid_header)
assert HTTPStatus.BAD_REQUEST == response.status_code


def test_paginated_succeeds_with_valid_params(
client: FlaskClient, valid_header: dict,
):
response = client.get(
'/time-entries/paginated?start=10&length=10', headers=valid_header
)
assert HTTPStatus.OK == response.status_code


def test_paginated_response_contains_expected_props(
client: FlaskClient, valid_header: dict,
):
response = client.get(
'/time-entries/paginated?start=10&length=10', headers=valid_header
)
assert 'data' in json.loads(response.data)
assert 'records_total' in json.loads(response.data)


def test_paginated_sends_max_count_and_offset_on_call_to_repository(
client: FlaskClient, valid_header: dict, time_entries_dao
):
time_entries_dao.repository.find_all = Mock(return_value=[])

response = client.get(
'/time-entries/paginated?start=10&length=10', headers=valid_header
)

time_entries_dao.repository.find_all.assert_called_once()

args, kwargs = time_entries_dao.repository.find_all.call_args
assert 'max_count' in kwargs and kwargs['max_count'] is not None
assert 'offset' in kwargs and kwargs['offset'] is not None
4 changes: 3 additions & 1 deletion time_tracker_api/projects/projects_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ def get_all(self, conditions: dict = None, **kwargs) -> list:
"""
event_ctx = self.create_event_context("read-many")
customer_dao = customers_create_dao()
customers = customer_dao.get_all()
customers = customer_dao.get_all(
max_count=kwargs.get('max_count', None)
)

customers_id = [customer.id for customer in customers]
conditions = conditions if conditions else {}
Expand Down
52 changes: 47 additions & 5 deletions time_tracker_api/time_entries/time_entries_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ def find_all(
custom_sql_conditions=custom_sql_conditions,
custom_params=custom_params,
max_count=kwargs.get("max_count", None),
offset=kwargs.get("offset", 0),
)

if time_entries:
Expand All @@ -221,7 +222,9 @@ def find_all(

project_dao = projects_model.create_dao()
projects = project_dao.get_all(
custom_sql_conditions=[custom_conditions], visible_only=False
custom_sql_conditions=[custom_conditions],
visible_only=False,
max_count=kwargs.get("max_count", None),
)

add_project_info_to_time_entries(time_entries, projects)
Expand All @@ -230,6 +233,7 @@ def find_all(
activities = activity_dao.get_all(
custom_sql_conditions=[custom_conditions_activity],
visible_only=False,
max_count=kwargs.get("max_count", None),
)
add_activity_name_to_time_entries(time_entries, activities)

Expand Down Expand Up @@ -398,12 +402,10 @@ def stop_time_entry_if_was_left_running(
)
raise CosmosResourceNotFoundError()

def get_all(self, conditions: dict = None, **kwargs) -> list:
event_ctx = self.create_event_context("read-many")
conditions.update({"owner_id": event_ctx.user_id})
def build_custom_query(self, is_admin: bool, conditions: dict = None):
custom_query = []
if "user_id" in conditions:
if event_ctx.is_admin:
if is_admin:
conditions.pop("owner_id")
custom_query = (
[]
Expand All @@ -419,6 +421,16 @@ def get_all(self, conditions: dict = None, **kwargs) -> list:
abort(
HTTPStatus.FORBIDDEN, "You don't have enough permissions."
)
return custom_query

def get_all(self, conditions: dict = None, **kwargs) -> list:
event_ctx = self.create_event_context("read-many")
conditions.update({"owner_id": event_ctx.user_id})

custom_query = self.build_custom_query(
is_admin=event_ctx.is_admin, conditions=conditions,
)

date_range = self.handle_date_filter_args(args=conditions)
limit = conditions.get("limit", None)
conditions.pop("limit", None)
Expand All @@ -430,6 +442,36 @@ def get_all(self, conditions: dict = None, **kwargs) -> list:
max_count=limit,
)

def get_all_paginated(self, conditions: dict = None, **kwargs) -> list:
event_ctx = self.create_event_context("read-many")
conditions.update({"owner_id": event_ctx.user_id})

custom_query = self.build_custom_query(
is_admin=event_ctx.is_admin, conditions=conditions,
)

date_range = self.handle_date_filter_args(args=conditions)

length = conditions.get("length", None)
conditions.pop("length", None)

start = conditions.get("start", None)
conditions.pop("start", None)

time_entries = self.repository.find_all(
event_ctx,
conditions=conditions,
custom_sql_conditions=custom_query,
date_range=date_range,
max_count=length,
offset=start,
)

return {
'records_total': len(time_entries),
'data': time_entries,
}

def get(self, id):
event_ctx = self.create_event_context("read")

Expand Down
41 changes: 41 additions & 0 deletions time_tracker_api/time_entries/time_entries_namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,3 +345,44 @@ def get(self):
"""Find the summary of worked time"""
conditions = summary_attribs_parser.parse_args()
return time_entries_dao.get_worked_time(conditions)


time_entry_paginated = ns.model(
'TimeEntryPaginated',
{
'records_total': fields.Integer(
title='Records total', description='Total number of entries.',
),
'data': fields.List(fields.Nested(time_entry)),
},
)

paginated_attribs_parser = ns.parser()
paginated_attribs_parser.add_argument(
'length',
required=True,
type=int,
help="(Filter) The number of rows the endpoint should return.",
location='args',
)

paginated_attribs_parser.add_argument(
'start',
required=True,
type=int,
help="(Filter) The number of rows to be removed from the query. (aka offset)",
location='args',
)


@ns.route('/paginated')
@ns.response(HTTPStatus.OK, 'Time Entries paginated')
@ns.response(HTTPStatus.NOT_FOUND, 'Time entry not found')
class PaginatedTimeEntry(Resource):
@ns.expect(paginated_attribs_parser)
@ns.doc('list_time_entries_paginated')
@ns.marshal_list_with(time_entry_paginated)
def get(self):
"""List all time entries paginated"""
conditions = paginated_attribs_parser.parse_args()
return time_entries_dao.get_all_paginated(conditions)