Skip to content

Commit

Permalink
feat: endpoints for task comments (#1171)
Browse files Browse the repository at this point in the history
* feat: taskcomment model added

* feat: crud task comment

* feat: task comment crud api route added

* feat: task comment schema added

* fix: removed use of dictionary to object

* fix: change row name by id with task_comment_id

* fix: renamed comment_task_id_to_id

* feat: migration add-task-comment

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* renamed task_comment index

* fix: removed new table migration for taskcomment

* feat: used task_history table to get task_comment and add task_comment
Using task_history table maintained action row as COMMENT and added comment

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix: linting issue

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* refactor: improve sql, use query params, fix linting errors

* refactor: minor code cleanup bundled in merge

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: spwoodcock <sam.woodcock@protonmail.com>
  • Loading branch information
3 people authored Feb 14, 2024
1 parent c88355e commit af1b9ee
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 6 deletions.
26 changes: 26 additions & 0 deletions src/backend/app/db/db_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,32 @@ class DbTaskHistory(Base):
)


class TaskComment(Base):
"""Represents a comment associated with a task."""

__tablename__ = "task_comment"

id = Column(Integer, primary_key=True)
task_id = Column(Integer, nullable=False)
project_id = Column(Integer, ForeignKey("projects.id"), index=True)
comment_text = Column(String)
commented_by = Column(
BigInteger,
ForeignKey("users.id", name="fk_users"),
index=True,
nullable=False,
)
created_at = Column(DateTime, nullable=False, default=timestamp)

__table_args__ = (
ForeignKeyConstraint(
[task_id, project_id], ["tasks.id", "tasks.project_id"], name="fk_tasks"
),
Index("idx_task_comment_composite", "task_id", "project_id"),
{},
)


class DbTask(Base):
"""Describes an individual mapping Task."""

Expand Down
3 changes: 1 addition & 2 deletions src/backend/app/db/postgis_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,6 @@ def parse_and_filter_geojson(
geojson_str: str, filter: bool = True
) -> Optional[geojson.FeatureCollection]:
"""Parse geojson string and filter out incomaptible geometries."""
log.debug("Parsing geojson string")
geojson_parsed = geojson.loads(geojson_str)
if isinstance(geojson_parsed, geojson.FeatureCollection):
log.debug("Already in FeatureCollection format, skipping reparse")
Expand All @@ -255,7 +254,7 @@ def parse_and_filter_geojson(
log.debug("Converting Feature to FeatureCollection")
featcol = geojson.FeatureCollection(features=[geojson_parsed])
else:
log.debug("Converting geometry to FeatureCollection")
log.debug("Converting Geometry to FeatureCollection")
featcol = geojson.FeatureCollection(
features=[geojson.Feature(geometry=geojson_parsed)]
)
Expand Down
3 changes: 0 additions & 3 deletions src/backend/app/projects/project_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,6 @@ def remove_z_dimension(coord):
meters=meters,
)
for index, poly in enumerate(tasks["features"]):
log.debug(poly)
db_task = db_models.DbTask(
project_id=project_id,
outline=wkblib.dumps(shape(poly["geometry"]), hex=True),
Expand All @@ -607,8 +606,6 @@ def remove_z_dimension(coord):
)
db.add(db_task)
db.commit()

# FIXME: write to tasks table
return True


Expand Down
98 changes: 98 additions & 0 deletions src/backend/app/tasks/tasks_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from sqlalchemy.orm import Session
from sqlalchemy.sql import text

from app.auth.osm import AuthUser
from app.central import central_crud
from app.db import database, db_models
from app.models.enums import (
Expand Down Expand Up @@ -315,6 +316,103 @@ async def edit_task_boundary(db: Session, task_id: int, boundary: str):
return True


async def get_task_comments(db: Session, project_id: int, task_id: int):
"""Get a list of tasks id for a project."""
query = text(
"""
SELECT
task_history.id, task_history.task_id, users.username,
task_history.action_text, task_history.action_date
FROM
task_history
LEFT JOIN
users ON task_history.user_id = users.id
WHERE
project_id = :project_id
AND task_id = :task_id
AND action = 'COMMENT'
"""
)

params = {"project_id": project_id, "task_id": task_id}

result = db.execute(query, params)

# Convert the result to a list of dictionaries
result_dict_list = [
{
"id": row[0],
"task_id": row[1],
"commented_by": row[2],
"comment": row[3],
"created_at": row[4],
}
for row in result.fetchall()
]

return result_dict_list


async def add_task_comments(
db: Session, comment: tasks_schemas.TaskCommentBase, user_data: AuthUser
):
"""Add a comment to a task.
Parameters:
- db: SQLAlchemy database session
- comment: TaskCommentBase instance containing the comment details
- user_data: AuthUser instance containing the user details
Returns:
- Dictionary with the details of the added comment
"""
currentdate = datetime.now()
# Construct the query to insert the comment and retrieve inserted comment details
query = text(
"""
INSERT INTO task_history (
project_id, task_id, action, action_text,
action_date, user_id
)
VALUES (
:project_id, :task_id, 'COMMENT', :comment_text,
:current_date, :user_id
)
RETURNING
task_history.id,
task_history.task_id,
(SELECT username FROM users WHERE id = task_history.user_id) AS user_id,
task_history.action_text,
task_history.action_date;
"""
)

# Define a dictionary with the parameter values
params = {
"project_id": comment.project_id,
"task_id": comment.task_id,
"comment_text": comment.comment,
"current_date": currentdate,
"user_id": user_data.id,
}

# Execute the query with the named parameters and commit the transaction
result = db.execute(query, params)
db.commit()

# Fetch the first row of the query result
row = result.fetchone()

# Return the details of the added comment as a dictionary
return {
"id": row[0],
"task_id": row[1],
"commented_by": row[2],
"comment": row[3],
"created_at": row[4],
}


async def update_task_history(
tasks: List[tasks_schemas.Task], db: Session = Depends(database.get_db)
):
Expand Down
43 changes: 42 additions & 1 deletion src/backend/app/tasks/tasks_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from sqlalchemy.orm import Session
from sqlalchemy.sql import text

from app.auth.osm import AuthUser
from app.auth.osm import AuthUser, login_required
from app.auth.roles import get_uid, mapper, project_admin
from app.central import central_crud
from app.db import database
Expand Down Expand Up @@ -196,6 +196,47 @@ async def task_features_count(
return data


@router.get("/task-comments/", response_model=list[tasks_schemas.TaskCommentResponse])
async def task_comments(
project_id: int,
task_id: int,
db: Session = Depends(database.get_db),
):
"""Retrieve a list of task comments for a specific project and task.
Args:
project_id (int): The ID of the project.
task_id (int): The ID of the task.
db (Session, optional): The database session.
Returns:
List[tasks_schemas.TaskCommentResponse]: A list of task comments.
"""
task_comment_list = await tasks_crud.get_task_comments(db, project_id, task_id)

return task_comment_list


@router.post("/task-comments/", response_model=tasks_schemas.TaskCommentResponse)
async def add_task_comments(
comment: tasks_schemas.TaskCommentRequest,
db: Session = Depends(database.get_db),
user_data: AuthUser = Depends(login_required),
):
"""Create a new task comment.
Parameters:
comment (TaskCommentRequest): The task comment to be created.
db (Session): The database session.
user_data (AuthUser): The authenticated user.
Returns:
TaskCommentResponse: The created task comment.
"""
task_comment_list = await tasks_crud.add_task_comments(db, comment, user_data)
return task_comment_list


@router.get("/activity/", response_model=List[tasks_schemas.TaskHistoryCount])
async def task_activity(
project_id: int, days: int = 10, db: Session = Depends(database.get_db)
Expand Down
26 changes: 26 additions & 0 deletions src/backend/app/tasks/tasks_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,32 @@ def decrypt_password(self, value: str) -> Optional[str]:
return decrypt_value(value)


class TaskCommentResponse(BaseModel):
"""Task mapping history."""

id: int
task_id: int
comment: Optional[str] = None
commented_by: str
created_at: datetime


class TaskCommentBase(BaseModel):
"""Task mapping history."""

comment: str
commented_by: str
created_at: datetime


class TaskCommentRequest(BaseModel):
"""Task mapping history."""

task_id: int
project_id: int
comment: str


class ReadTask(Task):
"""Task details plus updated task history."""

Expand Down

0 comments on commit af1b9ee

Please sign in to comment.