Skip to content

Commit 2b57549

Browse files
committed
refactor: TT-185 create SQLBuilder, TimeEntryQueryBuilder and find_all_v2 method in time entries repository
1 parent 7914fdf commit 2b57549

File tree

4 files changed

+210
-0
lines changed

4 files changed

+210
-0
lines changed

tests/time_tracker_api/time_entries/time_entries_model_test.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
)
99
from time_tracker_api.time_entries.time_entries_repository import (
1010
TimeEntryCosmosDBRepository,
11+
TimeEntryCosmosDBModel,
1112
)
1213

1314

@@ -266,3 +267,57 @@ def test_updated_item_without_deleted_key_should_call_validate_data(
266267
time_entry_repository.on_update({}, event_context)
267268
on_update_mock.assert_called_once()
268269
time_entry_repository.validate_data.assert_called_once()
270+
271+
272+
@pytest.mark.parametrize(
273+
'owner_list,expected_result',
274+
[
275+
([], ""),
276+
(None, ""),
277+
(["id"], "AND c.owner_id IN ('id')"),
278+
(["id1", "id2"], "AND c.owner_id IN ('id1', 'id2')"),
279+
],
280+
)
281+
def test_create_owner_condition(
282+
owner_list,
283+
expected_result,
284+
event_context: EventContext,
285+
time_entry_repository: TimeEntryCosmosDBRepository,
286+
):
287+
result = time_entry_repository.create_owner_condition(owner_list)
288+
assert result == expected_result
289+
290+
291+
def test_find_all_v2(
292+
event_context: EventContext,
293+
time_entry_repository: TimeEntryCosmosDBRepository,
294+
):
295+
expected_item = {
296+
'id': 'id',
297+
'start_date': '2021-03-22T10:00:00.000Z',
298+
'end_date': "2021-03-22T11:00:00.000Z",
299+
'description': 'do some testing',
300+
'tenant_id': 'tenant_id',
301+
'project_id': 'project_id',
302+
'activity_id': 'activity_id',
303+
'technologies': ['python'],
304+
}
305+
query_items_mock = Mock(return_value=[expected_item])
306+
time_entry_repository.container = Mock()
307+
time_entry_repository.container.query_items = query_items_mock
308+
309+
result = time_entry_repository.find_all_v2(
310+
event_context,
311+
['owner_id'],
312+
{
313+
'start_date': "2021-03-22T10:00:00.000Z",
314+
'end_date': "2021-03-22T11:00:00.000Z",
315+
},
316+
)
317+
318+
print(result)
319+
assert len(result) == 1
320+
time_entry = result[0]
321+
print(time_entry.__dict__)
322+
assert isinstance(time_entry, TimeEntryCosmosDBModel)
323+
assert time_entry.__dict__ == expected_item
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from utils.query_builder import CosmosDBQueryBuilder
2+
3+
4+
class TimeEntryQueryBuilder(CosmosDBQueryBuilder):
5+
def __init__(self):
6+
super().__init__()
7+
8+
def add_sql_date_range_condition(self, dates: tuple = None):
9+
if dates and len(dates) == 2:
10+
start_date, end_date = dates
11+
condition = """
12+
((c.start_date BETWEEN @start_date AND @end_date) OR
13+
(c.end_date BETWEEN @start_date AND @end_date))
14+
"""
15+
self.where_conditions.append(condition)
16+
self.parameters.extend(
17+
[
18+
{'name': '@start_date', 'value': start_date},
19+
{'name': '@end_date', 'value': end_date},
20+
]
21+
)
22+
return self

time_tracker_api/time_entries/time_entries_repository.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
from commons.data_access_layer.database import EventContext
2727
from typing import List, Callable
2828
from time_tracker_api.projects import projects_model
29+
from time_tracker_api.time_entries.time_entries_query_builder import (
30+
TimeEntryQueryBuilder,
31+
)
2932

3033

3134
class TimeEntryCosmosDBRepository(CosmosDBRepository):
@@ -196,6 +199,40 @@ def find_all(
196199
abort(HTTPStatus.NOT_FOUND, "Time entry not found")
197200
return time_entries
198201

202+
def find_all_v2(
203+
self,
204+
event_context: EventContext,
205+
owner_id_list: List[str],
206+
date_range: tuple = None,
207+
fields: dict = None,
208+
limit: int = None,
209+
offset: int = 0,
210+
visible_only=True,
211+
mapper: Callable = None,
212+
):
213+
limit = self.get_page_size_or(limit)
214+
partition_key_value = self.find_partition_key_value(event_context)
215+
query_builder = (
216+
TimeEntryQueryBuilder()
217+
.add_sql_date_range_condition(date_range)
218+
.add_sql_in_condition(owner_id_list)
219+
.add_sql_where_equal_condition(fields)
220+
.add_sql_limit_condition(limit)
221+
.add_sql_offset_condition(offset)
222+
.build()
223+
)
224+
225+
query_str = query_builder.get_query()
226+
params = query_builder.get_parameters()
227+
228+
result = self.container.query_items(
229+
query=query_str,
230+
parameters=params,
231+
partition_key=partition_key_value,
232+
)
233+
function_mapper = self.get_mapper_or_dict(mapper)
234+
return list(map(function_mapper, result))
235+
199236
def on_create(self, new_item_data: dict, event_context: EventContext):
200237
CosmosDBRepository.on_create(self, new_item_data, event_context)
201238

utils/query_builder.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
from typing import List
2+
from utils.repository import convert_list_to_tuple_string
3+
4+
5+
class CosmosDBQueryBuilder:
6+
query: str
7+
8+
def __init__(self):
9+
super().__init__()
10+
self.query = ""
11+
self.parameters = []
12+
self.select_conditions = []
13+
self.where_conditions = []
14+
self.limit = None
15+
self.offset = None
16+
17+
def add_select_conditions(self, column: List[str] = None):
18+
column = column if column else ["*"]
19+
self.select_conditions.extend(column)
20+
return self
21+
22+
def add_sql_in_condition(
23+
self, attribute: str = None, ids_list: List[str] = None
24+
):
25+
if ids_list and attribute and len(ids_list) > 0:
26+
ids_values = convert_list_to_tuple_string(ids_list)
27+
self.where_conditions.append(f"c.{attribute} IN {ids_values}")
28+
return self
29+
30+
def add_sql_where_equal_condition(self, data: dict = None):
31+
if data:
32+
for k, v in data.items():
33+
condition = f"c.{k} = @{k}"
34+
self.where_conditions.append(condition)
35+
self.parameters.append({'name': f'@{k}', 'value': v})
36+
return self
37+
38+
def add_sql_visibility_condition(self, visible_only: bool):
39+
if visible_only:
40+
self.where_conditions.append('NOT IS_DEFINED(c.deleted)')
41+
return self
42+
43+
def add_sql_limit_condition(self, limit):
44+
if limit and isinstance(limit, int):
45+
self.limit = limit
46+
return self
47+
48+
def add_sql_offset_condition(self, offset):
49+
if offset and isinstance(offset, int):
50+
self.offset = offset
51+
return self
52+
53+
def build_select(self):
54+
if len(self.select_conditions) < 1:
55+
self.select_conditions.append("*")
56+
return ",".join(self.select_conditions)
57+
58+
def build_where(self):
59+
if len(self.where_conditions) > 0:
60+
return "WHERE " + " AND ".join(self.where_conditions)
61+
else:
62+
return ""
63+
64+
def build_offset(self):
65+
if self.offset:
66+
self.parameters.append({'name': '@offset', 'value': self.offset})
67+
return "OFFSET @offset"
68+
else:
69+
return ""
70+
71+
def build_limit(self):
72+
if self.limit:
73+
self.parameters.append({'name': '@limit', 'value': self.limit})
74+
return "LIMIT @limit"
75+
else:
76+
return ""
77+
78+
def build(self):
79+
self.query = """
80+
SELECT {select_conditions} FROM c
81+
{where_conditions}
82+
{offset_condition}
83+
{limit_condition}
84+
""".format(
85+
select_conditions=self.build_select(),
86+
where_conditions=self.build_where(),
87+
offset_condition=self.build_offset(),
88+
limit_condition=self.build_limit(),
89+
)
90+
return self
91+
92+
def get_query(self):
93+
return self.query
94+
95+
def get_parameters(self):
96+
return self.parameters

0 commit comments

Comments
 (0)