diff --git a/src/backend/app/db/db_models.py b/src/backend/app/db/db_models.py index 5c5b648891..e4f22d9bd3 100644 --- a/src/backend/app/db/db_models.py +++ b/src/backend/app/db/db_models.py @@ -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" ) @@ -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)) diff --git a/src/backend/app/tasks/tasks_crud.py b/src/backend/app/tasks/tasks_crud.py index 654a0d33f0..a442b70eb3 100644 --- a/src/backend/app/tasks/tasks_crud.py +++ b/src/backend/app/tasks/tasks_crud.py @@ -341,7 +341,7 @@ 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, @@ -349,10 +349,10 @@ def get_task_history( """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. diff --git a/src/backend/app/tasks/tasks_routes.py b/src/backend/app/tasks/tasks_routes.py index 4c239a8cb9..b174049b08 100644 --- a/src/backend/app/tasks/tasks_routes.py +++ b/src/backend/app/tasks/tasks_routes.py @@ -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) diff --git a/src/backend/app/tasks/tasks_schemas.py b/src/backend/app/tasks/tasks_schemas.py index ace91f1857..175a9ebded 100644 --- a/src/backend/app/tasks/tasks_schemas.py +++ b/src/backend/app/tasks/tasks_schemas.py @@ -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 @@ -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 = { @@ -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 "" @@ -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