Skip to content

Commit 8bfb772

Browse files
committed
Add custom field names input validation.
1 parent 8eb4d57 commit 8bfb772

File tree

7 files changed

+70
-7
lines changed

7 files changed

+70
-7
lines changed

src/superannotate/lib/core/entities/filters.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ class ProjectFilters(BaseFilters):
3636
status__ne: Literal["NotStarted", "InProgress", "Completed", "OnHold"]
3737
status__in: List[Literal["NotStarted", "InProgress", "Completed", "OnHold"]]
3838
status__notin: List[Literal["NotStarted", "InProgress", "Completed", "OnHold"]]
39-
custom_field: Optional[str] # dummy field to pass first level validation
4039

4140

4241
class UserFilters(TypedDict, total=False):
@@ -51,4 +50,3 @@ class UserFilters(TypedDict, total=False):
5150
state__in: Optional[List[Literal["CONFIRMED", "PENDING"]]]
5251
role: Optional[Literal["admin", "contributor"]]
5352
role__in: Optional[List[Literal["admin", "contributor"]]]
54-
custom_field: Optional[str] # dummy field to pass first level validation

src/superannotate/lib/core/jsx_conditions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class OperatorEnum(str, Enum):
1414
NE = "$ne"
1515
GT = "$gt"
1616
LT = "$lt"
17+
IS = "$is"
1718
CONTAINS = "$cont"
1819
STARTS = "$starts"
1920
ENDS = "$ends"

src/superannotate/lib/infrastructure/controller.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
from lib.core.response import Response
4949
from lib.core.service_types import PROJECT_TYPE_RESPONSE_MAP
5050
from lib.core.usecases import serialize_item_entity
51+
from lib.infrastructure.custom_entities import generate_schema
5152
from lib.infrastructure.helpers import timed_lru_cache
5253
from lib.infrastructure.query_builder import FieldValidationHandler
5354
from lib.infrastructure.query_builder import IncludeHandler
@@ -360,9 +361,15 @@ def list_projects(
360361
include: List[str] = None,
361362
**filters: Unpack[ProjectFilters],
362363
) -> List[ProjectEntity]:
364+
valid_fields = generate_schema(
365+
ProjectFilters.__annotations__,
366+
self.service_provider.get_custom_fields_templates(
367+
CustomFieldEntityEnum.PROJECT
368+
),
369+
)
363370
chain = QueryBuilderChain(
364371
[
365-
FieldValidationHandler(ProjectFilters.__annotations__.keys()),
372+
FieldValidationHandler(valid_fields.keys()),
366373
ProjectFilterHandler(
367374
self.service_provider, entity=CustomFieldEntityEnum.PROJECT
368375
),
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from lib.core.enums import CustomFieldType
2+
from lib.core.jsx_conditions import OperatorEnum
3+
from typing_extensions import Any
4+
5+
6+
FIELD_TYPE_SUPPORTED_OPERATIONS_MAPPING = {
7+
CustomFieldType.Text: [
8+
OperatorEnum.EQ,
9+
OperatorEnum.IN,
10+
OperatorEnum.NOTIN,
11+
OperatorEnum.CONTAINS,
12+
],
13+
CustomFieldType.MULTI_SELECT: [OperatorEnum.EQ, OperatorEnum.NOTIN],
14+
CustomFieldType.SINGLE_SELECT: [
15+
OperatorEnum.EQ,
16+
OperatorEnum.IN,
17+
OperatorEnum.NOTIN,
18+
OperatorEnum.CONTAINS,
19+
],
20+
CustomFieldType.DATE_PICKER: [OperatorEnum.GT, OperatorEnum.LT, OperatorEnum.EQ],
21+
CustomFieldType.NUMERIC: [
22+
OperatorEnum.GT,
23+
OperatorEnum.LT,
24+
OperatorEnum.EQ,
25+
OperatorEnum.IN,
26+
OperatorEnum.NOTIN,
27+
],
28+
}
29+
30+
# todo implement
31+
FIELD_TYPE_BED_OPERATIONS_MAPPING = {
32+
CustomFieldType.MULTI_SELECT: {OperatorEnum.EQ: OperatorEnum.IN},
33+
CustomFieldType.DATE_PICKER: {OperatorEnum.EQ: OperatorEnum.IS},
34+
}
35+
36+
37+
def generate_schema(base_schema: dict, custom_field_templates) -> dict:
38+
annotations = base_schema
39+
for custom_field_template in custom_field_templates:
40+
for operator in FIELD_TYPE_SUPPORTED_OPERATIONS_MAPPING[
41+
CustomFieldType(custom_field_template["component_id"])
42+
]:
43+
condition = f"custom_field__{custom_field_template['name']}"
44+
if operator != OperatorEnum.EQ:
45+
condition = condition + f"__{operator.name.lower()}"
46+
annotations[condition] = Any
47+
return annotations

src/superannotate/lib/infrastructure/query_builder.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,7 @@ def __init__(self, valid_fields: Iterable[str]):
7575

7676
def handle(self, filters: Dict[str, Any], query: Query = None) -> Query:
7777
for param in filters.keys():
78-
if (
79-
not param.startswith("custom_field__")
80-
and param not in self._valid_fields
81-
):
78+
if param not in self._valid_fields:
8279
raise AppException("Invalid filter param provided.")
8380
return super().handle(filters, query)
8481

src/superannotate/lib/infrastructure/serviceprovider.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ def __init__(self, client: HttpClient):
6969
5, self.work_management
7070
)
7171

72+
def get_custom_fields_templates(self, entity: CustomFieldEntityEnum):
73+
return self._cached_work_management_repository.list_templates(
74+
self.client.team_id, entity=entity
75+
)
76+
7277
def list_custom_field_names(self, entity: CustomFieldEntityEnum) -> List[str]:
7378
return self._cached_work_management_repository.list_custom_field_names(
7479
self.client.team_id, entity=entity

src/superannotate/lib/infrastructure/utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ def sync(self, team_id):
138138
"custom_fields_name_id_map": custom_fields_name_id_map,
139139
"custom_fields_id_name_map": custom_fields_id_name_map,
140140
"custom_fields_id_component_id_map": custom_fields_id_component_id_map,
141+
"templates": response.data["data"],
141142
}
142143
self._update_cache_timestamp(team_id)
143144

@@ -228,3 +229,10 @@ def list_custom_field_names(
228229
else:
229230
custom_field_data = self._user_custom_field_cache.get(team_id)
230231
return list(custom_field_data["custom_fields_name_id_map"].keys())
232+
233+
def list_templates(self, team_id: int, entity: CustomFieldEntityEnum):
234+
if entity == CustomFieldEntityEnum.PROJECT:
235+
custom_field_data = self._project_custom_field_cache.get(team_id)
236+
else:
237+
custom_field_data = self._user_custom_field_cache.get(team_id)
238+
return custom_field_data["templates"]

0 commit comments

Comments
 (0)