Skip to content

Commit

Permalink
feat: task history end point with associated user (#1071)
Browse files Browse the repository at this point in the history
* feat: separate task history end point

* fix: add user relationship backref to task history

* fix: add user details to task_history endpoint

* refactor: revert to sqlalchemy backref names

---------

Co-authored-by: sujanadh <sujanadh07@gmail.com>
Co-authored-by: spwoodcock <sam.woodcock@protonmail.com>
  • Loading branch information
3 people authored Feb 6, 2024
1 parent 9432337 commit 3a5d3d1
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 15 deletions.
7 changes: 7 additions & 0 deletions src/backend/app/db/db_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ class DbTaskHistory(Base):
)

# Define relationships
user = relationship(DbUser, uselist=False, backref="task_history_user")
invalidation_history = relationship(
DbTaskInvalidationHistory, lazy="dynamic", cascade="all"
)
Expand Down Expand Up @@ -464,6 +465,12 @@ class DbProject(Base):
author = relationship(DbUser, uselist=False, backref="user")
created = cast(datetime, Column(DateTime, default=timestamp, nullable=False))

task_split_type = Column(Enum(TaskSplitType), nullable=True)
# split_strategy = Column(Integer)
# grid_meters = Column(Integer)
# task_type = Column(Integer)
# target_number_of_features = Column(Integer)

# PROJECT DETAILS
project_name_prefix = cast(str, Column(String))
task_type_prefix = cast(str, Column(String))
Expand Down
10 changes: 5 additions & 5 deletions src/backend/app/tasks/tasks_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,18 +341,18 @@ def process_history_entry(history_entry):
return tasks


def get_task_history(
async def get_project_task_history(
project_id: int,
end_date: Optional[datetime],
db: Session,
) -> list[db_models.DbTaskHistory]:
"""Retrieves the task history records for a specific project.
Args:
project_id: The ID of the project.
end_date: The end date of the task history
records to retrieve (optional).
db: The database session.
project_id (int): The ID of the project.
end_date (datetime, optional): The end date of the task history
records to retrieve.
db (Session): The database session.
Returns:
A list of task history records for the specified project.
Expand Down
29 changes: 24 additions & 5 deletions src/backend/app/tasks/tasks_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,19 +206,38 @@ async def task_activity(
"""Retrieves the validate and mapped task count for a specific project.
Args:
project_id: The ID of the project.
days: The number of days to consider for the
task activity (default: 10).
db: The database session.
project_id (int): The ID of the project.
days (int): The number of days to consider for the
task activity (default: 10).
db (Session): The database session.
Returns:
list[TaskHistoryCount]: A list of task history counts.
"""
end_date = datetime.now() - timedelta(days=days)
task_history = tasks_crud.get_task_history(project_id, end_date, db)
task_history = await tasks_crud.get_project_task_history(project_id, end_date, db)

return await tasks_crud.count_validated_and_mapped_tasks(
task_history,
end_date,
)


@router.get("/task_history/", response_model=List[tasks_schemas.TaskHistory])
async def task_history(
project_id: int, days: int = 10, db: Session = Depends(database.get_db)
):
"""Get the detailed task history for a project.
Args:
project_id (int): The ID of the project.
days (int): The number of days to consider for the
task activity (default: 10).
db (Session): The database session.
Returns:
List[TaskHistory]: A list of task history.
"""
end_date = datetime.now() - timedelta(days=days)
return await tasks_crud.get_project_task_history(project_id, end_date, db)
55 changes: 50 additions & 5 deletions src/backend/app/tasks/tasks_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

from geojson_pydantic import Feature as GeojsonFeature
from loguru import logger as log
from pydantic import BaseModel, ConfigDict, Field, ValidationInfo
from pydantic import BaseModel, ConfigDict, Field, ValidationInfo, computed_field
from pydantic.functional_serializers import field_serializer
from pydantic.functional_validators import field_validator

Expand Down Expand Up @@ -96,7 +96,9 @@ def get_geojson_from_outline(cls, value: Any, info: ValidationInfo) -> str:

@field_validator("outline_centroid", mode="before")
@classmethod
def get_centroid_from_outline(cls, value: Any, info: ValidationInfo) -> str:
def get_centroid_from_outline(
cls, value: Any, info: ValidationInfo
) -> Optional[str]:
"""Get outline_centroid from Shapely geom."""
if outline := info.data.get("outline"):
properties = {
Expand All @@ -109,21 +111,21 @@ def get_centroid_from_outline(cls, value: Any, info: ValidationInfo) -> str:
return None

@field_serializer("locked_by_uid")
def get_locked_by_uid(self, value: str) -> str:
def get_locked_by_uid(self, value: str) -> Optional[str]:
"""Get lock uid from lock_holder details."""
if self.lock_holder:
return self.lock_holder.id
return None

@field_serializer("locked_by_username")
def get_locked_by_username(self, value: str) -> str:
def get_locked_by_username(self, value: str) -> Optional[str]:
"""Get lock username from lock_holder details."""
if self.lock_holder:
return self.lock_holder.username
return None

@field_serializer("odk_token")
def decrypt_password(self, value: str) -> str:
def decrypt_password(self, value: str) -> Optional[str]:
"""Decrypt the ODK Token extracted from the db."""
if not value:
return ""
Expand All @@ -135,3 +137,46 @@ class ReadTask(Task):
"""Task details plus updated task history."""

task_history: Optional[List[TaskHistoryOut]] = None


class TaskHistory(BaseModel):
"""Task history details."""

model_config = ConfigDict(
from_attributes=True,
)

# Excluded
user: Any = Field(exclude=True)

task_id: int
action_text: str
action_date: datetime

@computed_field
@property
def username(self) -> Optional[str]:
"""Get username from user db obj."""
if self.user:
return self.user.username
return None

@computed_field
@property
def profile_img(self) -> Optional[str]:
"""Get profile_img from user db obj."""
if self.user:
return self.user.profile_img
return None

@computed_field
@property
def status(self) -> Optional[str]:
"""Extract status from standard format action_text."""
if self.action_text:
split_text = self.action_text.split()
if len(split_text) > 5:
return split_text[5]
else:
return self.action_text
return None

0 comments on commit 3a5d3d1

Please sign in to comment.