Skip to content

Commit

Permalink
refactor: reduce API calls on home page and project details page (#1315)
Browse files Browse the repository at this point in the history
* refactor: remove verbose logs from task pydantic model

* fix(frontend): remove project-summaries call from project details page load

* refactor: optimise homepage to use one endpoint (centroids in schema)

* refactor: reduce number of endpoint calls on project details page
  • Loading branch information
spwoodcock authored Mar 1, 2024
1 parent 2202329 commit 5dcd774
Show file tree
Hide file tree
Showing 15 changed files with 88 additions and 192 deletions.
14 changes: 11 additions & 3 deletions src/backend/app/db/postgis_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,20 @@ def geometry_to_geojson(


def get_centroid(
geometry: WKBElement, properties: Optional[dict] = None, id: Optional[int] = None
):
"""Convert SQLAlchemy geometry to Centroid GeoJSON."""
geometry: WKBElement,
properties: Optional[dict] = None,
id: Optional[int] = None,
) -> Union[list[int], Feature]:
"""Convert SQLAlchemy geometry to Centroid GeoJSON.
If no id or properties fields are passed, returns the coordinate only.
Else returns a Feature GeoJSON.
"""
if geometry:
shape = to_shape(geometry)
point = shape.centroid
if not properties and not id:
return point
geojson = {
"type": "Feature",
"geometry": mapping(point),
Expand Down
2 changes: 1 addition & 1 deletion src/backend/app/projects/project_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -1514,7 +1514,7 @@ async def convert_to_app_project(db_project: db_models.DbProject):
db_project.outline, {"id": db_project.id}, db_project.id
)

app_project.project_tasks = db_project.tasks
app_project.tasks = db_project.tasks

return app_project

Expand Down
3 changes: 2 additions & 1 deletion src/backend/app/projects/project_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ async def read_project_summaries(


@router.get(
"/search_projects", response_model=project_schemas.PaginatedProjectSummaries
"/search-projects", response_model=project_schemas.PaginatedProjectSummaries
)
async def search_project(
search: str,
Expand Down Expand Up @@ -1286,6 +1286,7 @@ async def project_dashboard(
background_task_id = await project_crud.insert_background_task_into_database(
db, "sync_submission", db_project.id
)
# Update submissions in S3
background_tasks.add_task(
submission_crud.update_submission_in_s3, db, db_project.id, background_task_id
)
Expand Down
14 changes: 13 additions & 1 deletion src/backend/app/projects/project_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ class ProjectSummary(BaseModel):
priority: ProjectPriority = ProjectPriority.MEDIUM
priority_str: str = priority.name
title: Optional[str] = None
centroid: list[float]
location_str: Optional[str] = None
description: Optional[str] = None
total_tasks: Optional[int] = None
Expand All @@ -252,11 +253,16 @@ def from_db_project(
) -> "ProjectSummary":
"""Generate model from database obj."""
priority = project.priority
centroid_point = read_wkb(project.centroid)
# NOTE format x,y (lon,lat) required for GeoJSON
centroid_coords = [centroid_point.x, centroid_point.y]

return cls(
id=project.id,
priority=priority,
priority_str=priority.name,
title=project.title,
centroid=centroid_coords,
location_str=project.location_str,
description=project.description,
total_tasks=project.total_tasks,
Expand All @@ -269,6 +275,12 @@ def from_db_project(
organisation_logo=project.organisation_logo,
)

# @field_serializer("centroid")
# def get_coord_from_centroid(self, value):
# """Get the cetroid coordinates from WBKElement."""
# if value is None:
# return None


class PaginationInfo(BaseModel):
"""Pagination JSON return."""
Expand Down Expand Up @@ -319,7 +331,7 @@ def outline_geojson(self) -> Optional[Feature]:
class ProjectWithTasks(ProjectBase):
"""Project plus list of tasks objects."""

project_tasks: Optional[List[tasks_schemas.Task]]
tasks: Optional[List[tasks_schemas.Task]]


class ProjectOut(ProjectWithTasks):
Expand Down
16 changes: 16 additions & 0 deletions src/backend/app/submissions/submission_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from app.central.central_crud import get_odk_form, get_odk_project, list_odk_xforms
from app.config import settings
from app.db import db_models
from app.models.enums import HTTPStatus
from app.projects import project_crud, project_deps
from app.s3 import add_obj_to_bucket, get_obj_from_bucket
from app.tasks import tasks_crud
Expand Down Expand Up @@ -315,6 +316,14 @@ def update_submission_in_s3(
odk_credentials = odk_sync(db, project_id)
odk_forms = list_odk_xforms(project.odkid, odk_credentials, True)

if not odk_forms:
msg = f"No odk forms returned for project ({project_id})"
log.warning(msg)
return HTTPException(
status_code=HTTPStatus.UNPROCESSABLE_ENTITY,
detail=msg,
)

# Get latest submission date
valid_datetimes = [
form["lastSubmission"]
Expand All @@ -329,6 +338,13 @@ def update_submission_in_s3(
if valid_datetimes
else None
)
if not last_submission:
msg = f"Could not identify last submission for project ({project_id})"
log.warning(msg)
return HTTPException(
status_code=HTTPStatus.UNPROCESSABLE_ENTITY,
detail=msg,
)

# Check if the file already exists in s3
s3_project_path = f"/{project.organisation_id}/{project_id}"
Expand Down
51 changes: 27 additions & 24 deletions src/backend/app/tasks/tasks_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,30 +75,33 @@ async def read_tasks(
return tasks


@router.get("/point_on_surface")
async def get_point_on_surface(project_id: int, db: Session = Depends(database.get_db)):
"""Get a point on the surface of the geometry for each task of the project.
Parameters:
project_id (int): The ID of the project.
Returns:
List[Tuple[int, str]]: A list of tuples containing the task ID
and the centroid as a string.
"""
query = text(
f"""
SELECT id,
ARRAY_AGG(ARRAY[ST_X(ST_PointOnSurface(outline)),
ST_Y(ST_PointOnSurface(outline))]) AS point
FROM tasks
WHERE project_id = {project_id}
GROUP BY id; """
)

result = db.execute(query)
result_dict_list = [{"id": row[0], "point": row[1]} for row in result.fetchall()]
return result_dict_list
# TODO remove this? Not used anywhere
# @router.get("/point_on_surface")
# async def get_point_on_surface(project_id: int,
# db: Session = Depends(database.get_db)):
# """Get a point on the surface of the geometry for each task of the project.

# Parameters:
# project_id (int): The ID of the project.

# Returns:
# List[Tuple[int, str]]: A list of tuples containing the task ID
# and the centroid as a string.
# """
# query = text(
# f"""
# SELECT id,
# ARRAY_AGG(ARRAY[ST_X(ST_PointOnSurface(outline)),
# ST_Y(ST_PointOnSurface(outline))]) AS point
# FROM tasks
# WHERE project_id = {project_id}
# GROUP BY id; """
# )

# result = db.execute(query)
# result_dict_list = [
# {"id": row[0], "point": row[1]} for row in result.fetchall()]
# return result_dict_list


@router.post("/near_me", response_model=tasks_schemas.Task)
Expand Down
4 changes: 2 additions & 2 deletions src/backend/tests/test_projects_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ async def test_convert_to_app_project():

assert result.outline_geojson is not None

assert result.project_tasks is not None
assert isinstance(result.project_tasks, list)
assert result.tasks is not None
assert isinstance(result.tasks, list)


async def test_create_project_with_project_info(db, project):
Expand Down
34 changes: 2 additions & 32 deletions src/frontend/src/api/HomeService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import axios from 'axios';
import { HomeActions } from '@/store/slices/HomeSlice';
import { HomeProjectCardModel } from '@/models/home/homeModel';
import environment from '@/environment';

export const HomeSummaryService: Function = (url: string) => {
return async (dispatch) => {
Expand All @@ -10,19 +8,10 @@ export const HomeSummaryService: Function = (url: string) => {
const fetchHomeSummaries = async (url) => {
try {
const fetchHomeData = await axios.get(url);
const resp: any = fetchHomeData.data.results;
const projectSummaries: any = fetchHomeData.data.results;
const paginationResp = fetchHomeData.data.pagination;
dispatch(HomeActions.SetHomeProjectPagination(paginationResp));
const fetchProjectCentroid = await axios.get(`${import.meta.env.VITE_API_URL}/projects/centroid/`);
const projectCentroidResp: any = fetchProjectCentroid.data;
const addedProjectCentroidOnSummary = resp.map((project) => {
const findProjectId = projectCentroidResp.find((payload) => payload.id === project.id);
if (findProjectId) {
return { ...project, centroid: findProjectId.centroid };
}
return project;
});
dispatch(HomeActions.SetHomeProjectSummary(addedProjectCentroidOnSummary));
dispatch(HomeActions.SetHomeProjectSummary(projectSummaries));
dispatch(HomeActions.HomeProjectLoading(false));
} catch (error) {
dispatch(HomeActions.HomeProjectLoading(false));
Expand All @@ -32,22 +21,3 @@ export const HomeSummaryService: Function = (url: string) => {
await fetchHomeSummaries(url);
};
};
export const ProjectCentroidService: Function = (url: string) => {
return async (dispatch) => {
dispatch(HomeActions.SetProjectCentroidLoading(true));

const fetchProjectCentroid = async (url) => {
try {
const fetchHomeData = await axios.get(url);
const resp: HomeProjectCardModel = fetchHomeData.data;

dispatch(HomeActions.SetProjectCentroid(resp));
dispatch(HomeActions.SetProjectCentroidLoading(false));
} catch (error) {
dispatch(HomeActions.SetProjectCentroidLoading(false));
}
};

await fetchProjectCentroid(url);
};
};
22 changes: 3 additions & 19 deletions src/frontend/src/api/Project.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,8 @@ export const ProjectById = (existingProjectList, projectId) => {
try {
dispatch(ProjectActions.SetProjectDetialsLoading(true));
const project = await CoreModules.axios.get(`${import.meta.env.VITE_API_URL}/projects/${projectId}`);
const taskList = await CoreModules.axios.get(
`${import.meta.env.VITE_API_URL}/tasks/task-list?project_id=${projectId}`,
);
const taskBbox = await CoreModules.axios.get(
`${import.meta.env.VITE_API_URL}/tasks/point_on_surface?project_id=${projectId}`,
);
const projectResp = project.data;
const taskListResp = taskList.data;
const persistingValues = taskListResp.map((data) => {
const persistingValues = projectResp.tasks.map((data) => {
return {
id: data.id,
outline_geojson: data.outline_geojson,
Expand All @@ -29,18 +22,9 @@ export const ProjectById = (existingProjectList, projectId) => {
odk_token: data.odk_token,
};
});
// added centroid from another api to projecttaskboundries
// At top level id project id to object
const projectTaskBoundries = [{ id: projectResp.id, taskBoundries: persistingValues }];
const mergedBboxIntoTask = projectTaskBoundries[0].taskBoundries.map((projectTask) => {
const filteredTaskIdCentroid = taskBbox.data.find((task) => task.id === projectTask.id).point[0];
return {
...projectTask,
bbox: filteredTaskIdCentroid,
};
});
dispatch(
ProjectActions.SetProjectTaskBoundries([{ ...projectTaskBoundries[0], taskBoundries: mergedBboxIntoTask }]),
);
dispatch(ProjectActions.SetProjectTaskBoundries([{ ...projectTaskBoundries[0] }]));
dispatch(
ProjectActions.SetProjectInfo({
id: projectResp.id,
Expand Down
96 changes: 0 additions & 96 deletions src/frontend/src/components/TasksLayer.jsx

This file was deleted.

2 changes: 1 addition & 1 deletion src/frontend/src/components/home/ProjectListMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const ProjectListMap = () => {
},
geometry: {
type: 'Point',
coordinates: project.centroid[0],
coordinates: project.centroid,
},
})),
};
Expand Down
Loading

0 comments on commit 5dcd774

Please sign in to comment.