Skip to content

Commit 8e2aadc

Browse files
authored
feat: TT-326 Get recent projects feature added (#319)
1 parent db77699 commit 8e2aadc

File tree

8 files changed

+190
-3
lines changed

8 files changed

+190
-3
lines changed

cosmosdb_emulator/time_tracker_cli/factories/project_factory.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class Project(NamedTuple):
1414
project_type_id: int
1515
customer_id: str
1616
tenant_id: str
17+
status: str
1718

1819

1920
class ProjectFactory(Factory):
@@ -28,3 +29,4 @@ def __init__(self, project_type_id, customer_id):
2829
name = Faker('name')
2930
description = Faker('sentence', nb_words=10)
3031
tenant_id = get_time_tracker_tenant_id()
32+
status = 'active'

cosmosdb_emulator/time_tracker_cli/utils/project.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,6 @@ def get_project_json(project_factory: ProjectFactory) -> dict:
3232
'customer_id': project_factory.customer_id,
3333
'project_type_id': project_factory.project_type_id,
3434
'tenant_id': project_factory.tenant_id,
35+
'status': project_factory.status,
3536
}
3637
return project

tests/time_tracker_api/projects/projects_model_test.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,18 @@
1313
ProjectCosmosDBRepository,
1414
ProjectCosmosDBModel,
1515
create_dao,
16+
ProjectCosmosDBDao,
1617
)
1718
from faker import Faker
1819

20+
from time_tracker_api.time_entries.time_entries_dao import (
21+
TimeEntriesCosmosDBDao,
22+
)
23+
from time_tracker_api.time_entries.time_entries_model import (
24+
TimeEntryCosmosDBModel,
25+
)
26+
from utils.enums.status import Status
27+
1928
fake = Faker()
2029

2130

@@ -138,3 +147,55 @@ def test_get_all_projects_with_customers(
138147
assert isinstance(projects[0], ProjectCosmosDBModel)
139148
assert projects[0].__dict__['customer_name'] == customer_data['name']
140149
assert len(projects) == 1
150+
151+
152+
def test_get_recent_projects_get_all_method_should_have_been_called_with_specific_arguments(
153+
mocker,
154+
):
155+
projects_amount = 5
156+
expected_conditions = {'status': Status.ACTIVE.value}
157+
expected_projects_ids = list(
158+
set([fake.uuid4() for i in range(projects_amount)])
159+
)
160+
user_time_entries = []
161+
162+
for project_id in expected_projects_ids:
163+
current_entry = TimeEntryCosmosDBModel(
164+
{'project_id': project_id, 'id': fake.uuid4()}
165+
)
166+
user_time_entries.append(current_entry)
167+
168+
mocker.patch.object(
169+
TimeEntriesCosmosDBDao,
170+
'get_latest_entries',
171+
return_value=user_time_entries,
172+
)
173+
project_cosmos_db_dao_get_all_mock = mocker.patch.object(
174+
ProjectCosmosDBDao, 'get_all'
175+
)
176+
projects_dao = create_dao()
177+
178+
projects_dao.get_recent_projects()
179+
180+
project_cosmos_db_dao_get_all_mock.assert_called_once_with(
181+
conditions=expected_conditions,
182+
project_ids=expected_projects_ids,
183+
customer_status=Status.ACTIVE.value,
184+
)
185+
186+
187+
def test_get_recent_projects_should_return_an_empty_array_if_the_user_has_no_entries(
188+
mocker,
189+
):
190+
user_time_entries = []
191+
mocker.patch.object(
192+
TimeEntriesCosmosDBDao,
193+
'get_latest_entries',
194+
return_value=user_time_entries,
195+
)
196+
197+
projects_dao = create_dao()
198+
199+
recent_projects = projects_dao.get_recent_projects()
200+
201+
assert len(recent_projects) == 0

tests/time_tracker_api/projects/projects_namespace_test.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,3 +309,20 @@ def test_delete_project_should_return_unprocessable_entity_for_invalid_id_format
309309
repository_remove_mock.assert_called_once_with(
310310
str(invalid_id), {'status': Status.INACTIVE.value}, ANY
311311
)
312+
313+
314+
def test_get_recent_projects_should_call_method_get_recent_projects_from_project_dao(
315+
client: FlaskClient, mocker: MockFixture, valid_header: dict
316+
):
317+
project_dao_get_recent_projects_mock = mocker.patch.object(
318+
ProjectCosmosDBDao, 'get_recent_projects', return_value=[]
319+
)
320+
321+
response = client.get(
322+
"/projects/recent",
323+
headers=valid_header,
324+
follow_redirects=True,
325+
)
326+
327+
assert response.status_code == HTTPStatus.OK
328+
project_dao_get_recent_projects_mock.assert_called_once()
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from unittest.mock import ANY
2+
3+
from time_tracker_api.database import APICosmosDBDao
4+
from time_tracker_api.time_entries.time_entries_repository import (
5+
TimeEntryCosmosDBRepository,
6+
)
7+
8+
9+
def test_get_latest_entries_must_be_called_with_default_amount_of_entries(
10+
mocker, time_entries_dao
11+
):
12+
expected_conditions = {'owner_id': ANY}
13+
14+
expected_entries_amount = 20
15+
16+
time_entries_repository_find_all_mock = mocker.patch.object(
17+
TimeEntryCosmosDBRepository, 'find_all'
18+
)
19+
mocker.patch.object(APICosmosDBDao, 'create_event_context')
20+
21+
time_entries_dao.get_latest_entries()
22+
23+
time_entries_repository_find_all_mock.assert_called_with(
24+
conditions=expected_conditions,
25+
max_count=expected_entries_amount,
26+
event_context=ANY,
27+
)
28+
29+
30+
def test_get_latest_entries_must_be_called_with_amount_of_entries_passed_in_condition(
31+
mocker, time_entries_dao
32+
):
33+
time_entries_repository_find_all_mock = mocker.patch.object(
34+
TimeEntryCosmosDBRepository, 'find_all'
35+
)
36+
mocker.patch.object(APICosmosDBDao, 'create_event_context')
37+
38+
expected_entries_amount = 40
39+
conditions = {'limit': expected_entries_amount}
40+
41+
time_entries_dao.get_latest_entries(conditions=conditions)
42+
43+
conditions.update({'owner_id': ANY})
44+
45+
time_entries_repository_find_all_mock.assert_called_with(
46+
conditions=conditions,
47+
max_count=expected_entries_amount,
48+
event_context=ANY,
49+
)

time_tracker_api/projects/projects_model.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
create_dao as project_types_create_dao,
1616
)
1717
from time_tracker_api.customers.customers_model import CustomerCosmosDBModel
18+
from utils.enums.status import Status
1819
from utils.query_builder import CosmosDBQueryBuilder
1920
from utils.extend_model import (
2021
add_customer_name_to_projects,
@@ -158,8 +159,37 @@ def get_all(
158159
add_customer_name_to_projects(projects, customers)
159160
return projects
160161

162+
def get_recent_projects(self):
163+
"""
164+
Gets the last projects in which the person has generated entries.
165+
The import had to be carried out within the method to avoid circular dependency.
166+
"""
167+
from time_tracker_api.time_entries.time_entries_dao import (
168+
create_dao as create_entries_dao,
169+
)
170+
171+
recent_projects = []
172+
time_entries_dao = create_entries_dao()
173+
last_time_entries = time_entries_dao.get_latest_entries()
174+
175+
last_time_entries_amount = len(last_time_entries)
176+
177+
if last_time_entries_amount == 0:
178+
return recent_projects
179+
180+
project_ids = list(
181+
set([entry.project_id for entry in last_time_entries])
182+
)
183+
conditions = {'status': Status.ACTIVE.value}
184+
recent_projects = self.get_all(
185+
conditions=conditions,
186+
project_ids=project_ids,
187+
customer_status=Status.ACTIVE.value,
188+
)
189+
190+
return recent_projects
191+
161192

162193
def create_dao() -> ProjectDao:
163194
repository = ProjectCosmosDBRepository()
164-
165195
return ProjectCosmosDBDao(repository)

time_tracker_api/projects/projects_namespace.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,11 @@ def delete(self, id):
193193
"""Delete a project"""
194194
project_dao.update(id, {'status': Status.INACTIVE.value})
195195
return None, HTTPStatus.NO_CONTENT
196+
197+
198+
@ns.route('/recent')
199+
class RecentProjects(Resource):
200+
@ns.doc('list_recent_projects')
201+
@ns.marshal_list_with(project)
202+
def get(self):
203+
return project_dao.get_recent_projects()

time_tracker_api/time_entries/time_entries_dao.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@
22
from commons.data_access_layer.cosmos_db import (
33
CosmosDBDao,
44
CustomError,
5-
CosmosDBRepository,
65
)
76
from utils.extend_model import (
87
add_project_info_to_time_entries,
98
add_activity_name_to_time_entries,
10-
create_custom_query_from_str,
119
create_list_from_str,
1210
)
1311
from utils.time import (
@@ -121,6 +119,27 @@ def get_all(self, conditions: dict = None, **kwargs) -> list:
121119

122120
return time_entries_list
123121

122+
def get_latest_entries(self, conditions: dict = None):
123+
"""
124+
Get the latest entries without taking into account a data range.
125+
It would only be necessary to pass the number of last entries that
126+
you need, this parameter must be passed by the conditions.
127+
The default value for the entries amount is 20.
128+
"""
129+
conditions = conditions if conditions else {}
130+
131+
default_entries_amount = 20
132+
event_context = self.create_event_context('read_many')
133+
conditions.update({'owner_id': event_context.user_id})
134+
entries_amount = conditions.pop("limit", default_entries_amount)
135+
time_entries = self.repository.find_all(
136+
conditions=conditions,
137+
max_count=entries_amount,
138+
event_context=event_context,
139+
)
140+
141+
return time_entries
142+
124143
def get_lastest_entries_by_project(
125144
self, conditions: dict = None, **kwargs
126145
) -> list:

0 commit comments

Comments
 (0)