Skip to content

Commit e1b0df8

Browse files
authored
Merge pull request #214 from mapswipe/feature/test-validate-aoi
Add queries to test AOI for validate project
2 parents bd312a9 + 6d7ec41 commit e1b0df8

File tree

8 files changed

+354
-3
lines changed

8 files changed

+354
-3
lines changed

apps/project/graphql/queries.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import logging
2+
13
import strawberry
24
import strawberry_django
35
from django.db.models import QuerySet
@@ -7,7 +9,14 @@
79

810
from apps.project.custom_options import get_custom_options
911
from apps.project.graphql.inputs.inputs import ProjectNameInput
12+
from apps.project.graphql.types.project_types.validate import (
13+
TestValidateAoiObjectsResponse,
14+
TestValidateTaskingManagerProjectResponse,
15+
)
1016
from apps.project.models import Organization, Project, ProjectAsset, ProjectTypeEnum
17+
from project_types.base.project import ValidationException
18+
from project_types.validate.project import ValidateProject
19+
from utils import fields
1120
from utils.geo.raster_tile_server.config import RasterConfig, RasterTileServerNameEnum, RasterTileServerNameEnumWithoutCustom
1221
from utils.geo.vector_tile_server.config import VectorConfig, VectorTileServerNameEnum, VectorTileServerNameEnumWithoutCustom
1322

@@ -25,6 +34,8 @@
2534
ProjectType,
2635
)
2736

37+
logger = logging.getLogger(__name__)
38+
2839

2940
def get_tile_servers() -> RasterTileServersType:
3041
def _get_raster_tile_server_type(enum: RasterTileServerNameEnumWithoutCustom):
@@ -79,6 +90,73 @@ def default_custom_options(self, project_type: ProjectTypeEnum) -> list[CustomOp
7990
for item in custom_options
8091
]
8192

93+
@strawberry.field(extensions=[IsAuthenticated()])
94+
def test_aoi_objects(
95+
self,
96+
project_id: strawberry.ID | None,
97+
asset_id: strawberry.ID | None,
98+
ohsome_filter: str | None,
99+
) -> TestValidateAoiObjectsResponse:
100+
response = TestValidateAoiObjectsResponse(
101+
project_id=project_id,
102+
asset_id=asset_id,
103+
ohsome_filter=ohsome_filter,
104+
)
105+
106+
if project_id is None:
107+
return response.generate_error("project_id is required to test aoi elements")
108+
109+
if asset_id is None:
110+
return response.generate_error("asset_id is required to test aoi elements")
111+
112+
if ohsome_filter is None:
113+
return response.generate_error("ohsome_filter is required to test aoi elements")
114+
115+
try:
116+
object_count = ValidateProject.test_ohsome_objects_from_aoi_asset(
117+
project_id,
118+
asset_id,
119+
ohsome_filter,
120+
)
121+
122+
response.object_count = object_count
123+
return response
124+
except ValidationException as e:
125+
return response.generate_error(str(e))
126+
except Exception as e:
127+
raise GraphQLError(str(e)) from e
128+
129+
@strawberry.field(extensions=[IsAuthenticated()])
130+
def test_tasking_manager_project(
131+
self,
132+
hot_tm_id: fields.PydanticId | None,
133+
ohsome_filter: str | None,
134+
) -> TestValidateTaskingManagerProjectResponse:
135+
response = TestValidateTaskingManagerProjectResponse(
136+
hot_tm_id=hot_tm_id,
137+
ohsome_filter=ohsome_filter,
138+
)
139+
140+
if hot_tm_id is None:
141+
return response.generate_error("hot_tm_id is required to test HOT project aoi elements")
142+
143+
if ohsome_filter is None:
144+
return response.generate_error("ohsome_filter is required to test HOT project aoi elements")
145+
146+
try:
147+
object_count = ValidateProject.test_tasking_manager_project(
148+
hot_tm_id,
149+
ohsome_filter,
150+
)
151+
152+
response.object_count = object_count
153+
154+
return response
155+
except ValidationException as e:
156+
return response.generate_error(str(e))
157+
except Exception as e:
158+
raise GraphQLError(str(e)) from e
159+
82160
tile_servers: RasterTileServersType = strawberry.field(resolver=get_tile_servers, extensions=[IsAuthenticated()])
83161

84162
# Private --------------------

apps/project/graphql/types/project_types/validate.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import strawberry
22

33
from project_types.validate import project as validate_project
4+
from utils import fields
45

56

67
@strawberry.experimental.pydantic.type(model=validate_project.ValidateObjectSourceConfig, all_fields=True)
@@ -9,3 +10,30 @@ class ValidateObjectSourceConfig: ...
910

1011
@strawberry.experimental.pydantic.type(model=validate_project.ValidateProjectProperty, all_fields=True)
1112
class ValidateProjectPropertyType: ...
13+
14+
15+
DEFAULT_TEST_RESPONSE_ERROR_MESSAGE: str = "Something unexpected has occurred. Please contact an admin to fix this issue."
16+
17+
18+
@strawberry.type
19+
class ValidateTestAoiResponse:
20+
ok: bool = True
21+
error: str | None = None
22+
object_count: int | None = None
23+
ohsome_filter: str | None = None
24+
25+
def generate_error(self, message: str = DEFAULT_TEST_RESPONSE_ERROR_MESSAGE):
26+
self.ok = False
27+
self.error = message
28+
return self
29+
30+
31+
@strawberry.type
32+
class TestValidateAoiObjectsResponse(ValidateTestAoiResponse):
33+
project_id: strawberry.ID | None = None
34+
asset_id: strawberry.ID | None = None
35+
36+
37+
@strawberry.type
38+
class TestValidateTaskingManagerProjectResponse(ValidateTestAoiResponse):
39+
hot_tm_id: fields.PydanticId | None = None

apps/project/tests/e2e_create_validate_project_test.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,32 @@ class Mutation:
294294
}
295295
"""
296296

297+
class Query:
298+
TEST_AOI_OBJECTS = """
299+
query TestAoiObjects($assetId: ID, $projectId: ID, $ohsomeFilter: String) {
300+
testAoiObjects(assetId: $assetId, projectId: $projectId, ohsomeFilter: $ohsomeFilter) {
301+
ok
302+
error
303+
objectCount
304+
assetId
305+
projectId
306+
ohsomeFilter
307+
}
308+
}
309+
"""
310+
311+
TEST_TASKING_MANAGER_PROJECT = """
312+
query TestTaskingManagerProject($hotTmId: String, $ohsomeFilter: String) {
313+
testTaskingManagerProject(hotTmId: $hotTmId, ohsomeFilter: $ohsomeFilter) {
314+
ok
315+
error
316+
objectCount
317+
hotTmId
318+
ohsomeFilter
319+
}
320+
}
321+
"""
322+
297323
@pytest.mark.vcr("assets/tests/projects/validate/cassette")
298324
def test_validate_project_e2e(self):
299325
# TODO(susilnem): Add more test with filters
@@ -398,6 +424,15 @@ def _test_project(self, filename: str):
398424
assert aoi_response["ok"]
399425
aoi_id = aoi_response["result"]["id"]
400426

427+
# Test AOI objects
428+
ohsomeFilter = "building=* and geometry:polygon"
429+
test_aoi_objects_content = self.query_check(
430+
self.Query.TEST_AOI_OBJECTS,
431+
variables={"assetId": aoi_id, "projectId": project_id, "ohsomeFilter": ohsomeFilter},
432+
)
433+
test_aoi_objects_response = test_aoi_objects_content["data"]["testAoiObjects"]
434+
assert test_aoi_objects_response["ok"]
435+
401436
# Update project
402437
update_project_data = test_data["update_project"]
403438
update_project_data["image"] = image_id

project_types/validate/api_calls.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from main.config import Config
1010
from main.logging import log_extra_response
11+
from utils.fields import PydanticLongText
1112

1213
logger = logging.getLogger(__name__)
1314

@@ -175,6 +176,40 @@ def remove_noise_and_add_user_info(json: dict[str, Any]) -> dict[str, Any]:
175176
return json
176177

177178

179+
# fixme(frozenhelium): merge this function with `ohsome` and also add appropriate messages to raised exceptions
180+
def get_object_count_from_ohsome(area: str, ohsome_filter: PydanticLongText) -> int | None:
181+
url = Config.OHSOME_API_LINK + "elements/count"
182+
data = {"bpolys": area, "filter": ohsome_filter}
183+
184+
logger.info("Target: %s", url)
185+
logger.info("Filter: %s", ohsome_filter)
186+
187+
# fixme(frozenhelium): use httpx for proper timeout
188+
response = requests.post(url, data=data, timeout=100)
189+
if response.status_code != 200:
190+
logger.warning(
191+
"ohsome element count request failed: check for errors in filter or geometries",
192+
extra=log_extra_response(response=response),
193+
)
194+
raise ValidateApiCallError
195+
logger.info("Query successful.")
196+
197+
response_json = response.json()
198+
results = response_json.get("result", None)
199+
if results is None:
200+
return None
201+
202+
first_result = results[0]
203+
if first_result is None:
204+
return None
205+
206+
value = first_result.get("value", None)
207+
if value is None:
208+
return None
209+
210+
return int(value)
211+
212+
178213
def ohsome(request: dict[str, Any], area: str, properties: str | None = None) -> dict[str, Any]:
179214
"""Request data from Ohsome API."""
180215
url = Config.OHSOME_API_LINK + request["endpoint"]

0 commit comments

Comments
 (0)