Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate task history end point #1071

Merged
merged 4 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved all the logic to the pydantic model.

Note we can use @computed_field decorators to generated computed values.
We can also use this to generate values based on database relationships.
To do this we need the model_config from_attributes=True:

    model_config = ConfigDict(
        from_attributes=True,
    )

@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
Loading