From 81da8f7fd8f1701b47f53e9565177f7ece8b8950 Mon Sep 17 00:00:00 2001 From: Pradip-p Date: Wed, 7 Aug 2024 21:17:32 +0545 Subject: [PATCH 1/8] feat: added task ongoing, completed & requests count --- src/backend/app/tasks/task_routes.py | 48 +++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/src/backend/app/tasks/task_routes.py b/src/backend/app/tasks/task_routes.py index 27b6a860..2bf2f298 100644 --- a/src/backend/app/tasks/task_routes.py +++ b/src/backend/app/tasks/task_routes.py @@ -1,7 +1,7 @@ import uuid from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException from app.config import settings -from app.models.enums import EventType, State, UserRole +from app.models.enums import EventType, HTTPStatus, State, UserRole from app.tasks import task_schemas, task_crud from app.users.user_deps import login_required from app.users.user_schemas import AuthUser @@ -18,7 +18,53 @@ responses={404: {"description": "Not found"}}, ) +@router.get("/task/stats") +async def get_task_stats( + db: Database = Depends(database.get_db), + user_data: AuthUser = Depends(login_required), +): + user_id = user_data.id + query = """SELECT role FROM user_profile WHERE user_id = :user_id""" + records = await db.fetch_all(query, {"user_id": user_id}) + if not records: + raise HTTPException(status_code=404, detail="User profile not found") + + roles = [record["role"] for record in records] + + if UserRole.PROJECT_CREATOR.name in roles: + raw_sql = """ + SELECT + (SELECT COUNT(*) + FROM tasks t + LEFT JOIN task_events te ON t.id = te.task_id + WHERE t.project_id IN (SELECT id FROM projects WHERE author_id = :user_id) + AND te.state = 'LOCKED_FOR_MAPPING') AS ongoing_tasks, + (SELECT COUNT(*) + FROM tasks t + LEFT JOIN task_events te ON t.id = te.task_id + WHERE t.project_id IN (SELECT id FROM projects WHERE author_id = :user_id) + AND te.state = 'REQUEST_FOR_MAPPING') AS request_logs, + (SELECT COUNT(*) + FROM tasks t + LEFT JOIN task_events te ON t.id = te.task_id + WHERE t.project_id IN (SELECT id FROM projects WHERE author_id = :user_id) + AND te.state = 'UNLOCKED_DONE') AS completed_tasks + """ + values = {"user_id": user_id} + try: + db_counts = await db.fetch_one(query=raw_sql, values=values) + except Exception as e: + raise HTTPException( + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, + detail=f"Failed to fetch task counts. {e}", + ) + + return db_counts + # if UserRole.DRONE_PILOT.name in roles: + # pass + + @router.get("/", response_model=list[task_schemas.UserTasksStatsOut]) async def list_tasks( db: Database = Depends(database.get_db), From b22efde4f4cb4879a125824acb0820d6c40dab06 Mon Sep 17 00:00:00 2001 From: Pradip-p Date: Thu, 8 Aug 2024 09:35:32 +0545 Subject: [PATCH 2/8] feat: added task route --- src/backend/app/tasks/task_routes.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/backend/app/tasks/task_routes.py b/src/backend/app/tasks/task_routes.py index 2bf2f298..e06124cd 100644 --- a/src/backend/app/tasks/task_routes.py +++ b/src/backend/app/tasks/task_routes.py @@ -18,7 +18,8 @@ responses={404: {"description": "Not found"}}, ) -@router.get("/task/stats") + +@router.get("/stats") async def get_task_stats( db: Database = Depends(database.get_db), user_data: AuthUser = Depends(login_required), @@ -30,11 +31,11 @@ async def get_task_stats( raise HTTPException(status_code=404, detail="User profile not found") roles = [record["role"] for record in records] - + if UserRole.PROJECT_CREATOR.name in roles: raw_sql = """ SELECT - (SELECT COUNT(*) + (SELECT COUNT(*) FROM tasks t LEFT JOIN task_events te ON t.id = te.task_id WHERE t.project_id IN (SELECT id FROM projects WHERE author_id = :user_id) @@ -63,8 +64,8 @@ async def get_task_stats( # if UserRole.DRONE_PILOT.name in roles: # pass - - + + @router.get("/", response_model=list[task_schemas.UserTasksStatsOut]) async def list_tasks( db: Database = Depends(database.get_db), From 853e1798c2b3f8c10beebbaf58a47781a9316dd9 Mon Sep 17 00:00:00 2001 From: Pradip-p Date: Thu, 8 Aug 2024 14:05:44 +0545 Subject: [PATCH 3/8] feat: Add statistics endpoint of task --- .../app/migrations/versions/2b92f8a9bbec_.py | 56 +++++++ src/backend/app/models/enums.py | 2 + src/backend/app/tasks/task_routes.py | 157 +++++++++++++----- src/backend/app/tasks/task_schemas.py | 4 + 4 files changed, 179 insertions(+), 40 deletions(-) create mode 100644 src/backend/app/migrations/versions/2b92f8a9bbec_.py diff --git a/src/backend/app/migrations/versions/2b92f8a9bbec_.py b/src/backend/app/migrations/versions/2b92f8a9bbec_.py new file mode 100644 index 00000000..d0d975a7 --- /dev/null +++ b/src/backend/app/migrations/versions/2b92f8a9bbec_.py @@ -0,0 +1,56 @@ +""" + +Revision ID: 2b92f8a9bbec +Revises: d862bfa31c36 +Create Date: 2024-08-08 08:10:11.056119 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + +# Define the new enum type +new_state_enum = sa.Enum( + "UNLOCKED_TO_MAP", + "LOCKED_FOR_MAPPING", + "UNLOCKED_TO_VALIDATE", + "LOCKED_FOR_VALIDATION", + "UNLOCKED_DONE", + "REQUEST_FOR_MAPPING", + "UNFLYABLE_TASK", + name="state", +) + +old_state_enum = sa.Enum( + "UNLOCKED_TO_MAP", + "LOCKED_FOR_MAPPING", + "UNLOCKED_TO_VALIDATE", + "LOCKED_FOR_VALIDATION", + "UNLOCKED_DONE", + "REQUEST_FOR_MAPPING", + name="state", +) + +# revision identifiers, used by Alembic. +revision: str = '2b92f8a9bbec' +down_revision: Union[str, None] = 'd862bfa31c36' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.execute("ALTER TYPE state ADD VALUE 'UNFLYABLE_TASK'") + + +def downgrade() -> None: + op.execute("ALTER TYPE state RENAME TO state_old") + old_state_enum.create(op.get_bind(), checkfirst=False) + op.execute( + ( + "ALTER TABLE task_events " + "ALTER COLUMN state TYPE state USING state::text::state" + ) + ) + op.execute("DROP TYPE state_old") + diff --git a/src/backend/app/models/enums.py b/src/backend/app/models/enums.py index 750ce60c..2a3d3ddd 100644 --- a/src/backend/app/models/enums.py +++ b/src/backend/app/models/enums.py @@ -132,6 +132,7 @@ class State(int, Enum): - ``unlocked to validate`` - ``locked for validation`` - ``unlocked done`` + - ``Unflyable task`` """ REQUEST_FOR_MAPPING = -1 @@ -140,6 +141,7 @@ class State(int, Enum): UNLOCKED_TO_VALIDATE = 2 LOCKED_FOR_VALIDATION = 3 UNLOCKED_DONE = 4 + UNFLYABLE_TASK = 5 class EventType(str, Enum): diff --git a/src/backend/app/tasks/task_routes.py b/src/backend/app/tasks/task_routes.py index e06124cd..bd74f5bb 100644 --- a/src/backend/app/tasks/task_routes.py +++ b/src/backend/app/tasks/task_routes.py @@ -19,51 +19,128 @@ ) -@router.get("/stats") +@router.post("/{task_id}") +async def create_task_comment( + input: task_schemas.TaskComment, + task_id: uuid.UUID, + db: Database = Depends(database.get_db), + user_data: AuthUser = Depends(login_required), +): + """ + Create or update a comment for a specific task. + + Args: + comment (task_schemas.TaskComment): The comment data. + task_id (uuid.UUID): The unique identifier of the task. + db (Database): The database session dependency. + user_data (AuthUser): The authenticated user data. + + Returns: + dict: A message indicating the success of the operation. + + Raises: + HTTPException: If updating the task comment fails. + """ + try: + raw_sql = """ + UPDATE task_events + SET state = :state, comment = :comment + WHERE task_id = :task_id + """ + await db.execute( + raw_sql, + {"task_id": task_id, "state": "UNFLYABLE_TASK", "comment": input.comment}, + ) + return {"detail": "Successfully created the task comment."} + + except Exception as e: + raise HTTPException( + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, + detail=f"Failed to update task comment. {e}", + ) + + +@router.get("/{task_id}") +async def read_task( + task_id: uuid.UUID, + db: Database = Depends(database.get_db), + user_data: AuthUser = Depends(login_required), +): + "Retrieve details of a specific task by its ID." + try: + query = """ + SELECT + ST_Area(ST_Transform(tasks.outline, 4326)) / 1000000 AS task_area, + task_events.created_at, + projects.name AS project_name, + project_task_index, + projects.front_overlap AS front_overlap, + projects.side_overlap AS side_overlap, + projects.gsd_cm_px AS gsd_cm_px, + projects.gimble_angles_degrees AS gimble_angles_degrees + FROM + task_events + JOIN + tasks ON task_events.task_id = tasks.id + JOIN + projects ON task_events.project_id = projects.id + WHERE + task_events.task_id = :task_id + """ + records = await db.fetch_one(query, values={"task_id": task_id}) + return records + except Exception as e: + raise HTTPException( + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, + detail=f"Failed to fetch tasks. {e}", + ) + + +@router.get("/statistics/") async def get_task_stats( db: Database = Depends(database.get_db), user_data: AuthUser = Depends(login_required), ): + "Retrieve statistics related to tasks for the authenticated user." user_id = user_data.id query = """SELECT role FROM user_profile WHERE user_id = :user_id""" records = await db.fetch_all(query, {"user_id": user_id}) + if not records: raise HTTPException(status_code=404, detail="User profile not found") - roles = [record["role"] for record in records] + raw_sql = """ + SELECT + (SELECT COUNT(*) + FROM tasks t + LEFT JOIN task_events te ON t.id = te.task_id + WHERE t.project_id IN (SELECT id FROM projects WHERE author_id = :user_id) + AND te.state = 'LOCKED_FOR_MAPPING') AS ongoing_tasks, + (SELECT COUNT(*) + FROM tasks t + LEFT JOIN task_events te ON t.id = te.task_id + WHERE t.project_id IN (SELECT id FROM projects WHERE author_id = :user_id) + AND te.state = 'REQUEST_FOR_MAPPING') AS request_logs, + (SELECT COUNT(*) + FROM tasks t + LEFT JOIN task_events te ON t.id = te.task_id + WHERE t.project_id IN (SELECT id FROM projects WHERE author_id = :user_id) + AND te.state = 'UNLOCKED_DONE') AS completed_tasks, + (SELECT COUNT(*) + FROM tasks t + LEFT JOIN task_events te ON t.id = te.task_id + WHERE t.project_id IN (SELECT id FROM projects WHERE author_id = :user_id) + AND te.state = 'UNFLYABLE_TASK') AS unflyable_tasks + """ - if UserRole.PROJECT_CREATOR.name in roles: - raw_sql = """ - SELECT - (SELECT COUNT(*) - FROM tasks t - LEFT JOIN task_events te ON t.id = te.task_id - WHERE t.project_id IN (SELECT id FROM projects WHERE author_id = :user_id) - AND te.state = 'LOCKED_FOR_MAPPING') AS ongoing_tasks, - (SELECT COUNT(*) - FROM tasks t - LEFT JOIN task_events te ON t.id = te.task_id - WHERE t.project_id IN (SELECT id FROM projects WHERE author_id = :user_id) - AND te.state = 'REQUEST_FOR_MAPPING') AS request_logs, - (SELECT COUNT(*) - FROM tasks t - LEFT JOIN task_events te ON t.id = te.task_id - WHERE t.project_id IN (SELECT id FROM projects WHERE author_id = :user_id) - AND te.state = 'UNLOCKED_DONE') AS completed_tasks - """ - values = {"user_id": user_id} - try: - db_counts = await db.fetch_one(query=raw_sql, values=values) - except Exception as e: - raise HTTPException( - status_code=HTTPStatus.INTERNAL_SERVER_ERROR, - detail=f"Failed to fetch task counts. {e}", - ) - - return db_counts - - # if UserRole.DRONE_PILOT.name in roles: - # pass + try: + db_counts = await db.fetch_one(query=raw_sql, values={"user_id": user_id}) + except Exception as e: + raise HTTPException( + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, + detail=f"Failed to fetch task counts. {e}", + ) + return db_counts @router.get("/", response_model=list[task_schemas.UserTasksStatsOut]) @@ -99,7 +176,7 @@ async def new_event( case EventType.REQUESTS: # TODO: Combine the logic of `update_or_create_task_state` and `request_mapping` functions into a single function if possible. Will do later. project = await get_project_by_id(db, project_id) - if project["requires_approval_from_manager_for_locking"] == "true": + if project["requires_approval_from_manager_for_locking"] is False: data = await task_crud.update_or_create_task_state( db, project_id, @@ -214,7 +291,7 @@ async def new_event( html_content, ) - return await task_crud.update_task_state( + return await task_crud.update_or_create_task_state( db, project_id, task_id, @@ -224,7 +301,7 @@ async def new_event( State.UNLOCKED_TO_MAP, ) case EventType.FINISH: - return await task_crud.update_task_state( + return await task_crud.update_or_create_task_state( db, project_id, task_id, @@ -234,7 +311,7 @@ async def new_event( State.UNLOCKED_TO_VALIDATE, ) case EventType.VALIDATE: - return await task_crud.update_task_state( + return await task_crud.update_or_create_task_state( db, project_id, task_id, @@ -244,7 +321,7 @@ async def new_event( State.LOCKED_FOR_VALIDATION, ) case EventType.GOOD: - return await task_crud.update_task_state( + return await task_crud.update_or_create_task_state( db, project_id, task_id, @@ -255,7 +332,7 @@ async def new_event( ) case EventType.BAD: - return await task_crud.update_task_state( + return await task_crud.update_or_create_task_state( db, project_id, task_id, diff --git a/src/backend/app/tasks/task_schemas.py b/src/backend/app/tasks/task_schemas.py index 3f60671a..5249a832 100644 --- a/src/backend/app/tasks/task_schemas.py +++ b/src/backend/app/tasks/task_schemas.py @@ -13,3 +13,7 @@ class UserTasksStatsOut(BaseModel): task_area: float created_at: datetime state: str + + +class TaskComment(BaseModel): + comment: str From f434de9edbc9c62ac525d86dd924bd88eee949c3 Mon Sep 17 00:00:00 2001 From: Pradip-p Date: Thu, 8 Aug 2024 14:06:51 +0545 Subject: [PATCH 4/8] feat:the endpoint retrieves counts of ongoing tasks, request logs, completed tasks, and unflyable tasks. --- src/backend/app/migrations/versions/2b92f8a9bbec_.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/app/migrations/versions/2b92f8a9bbec_.py b/src/backend/app/migrations/versions/2b92f8a9bbec_.py index d0d975a7..8c75f306 100644 --- a/src/backend/app/migrations/versions/2b92f8a9bbec_.py +++ b/src/backend/app/migrations/versions/2b92f8a9bbec_.py @@ -5,6 +5,7 @@ Create Date: 2024-08-08 08:10:11.056119 """ + from typing import Sequence, Union from alembic import op @@ -33,8 +34,8 @@ ) # revision identifiers, used by Alembic. -revision: str = '2b92f8a9bbec' -down_revision: Union[str, None] = 'd862bfa31c36' +revision: str = "2b92f8a9bbec" +down_revision: Union[str, None] = "d862bfa31c36" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -53,4 +54,3 @@ def downgrade() -> None: ) ) op.execute("DROP TYPE state_old") - From 3404da7e3ef967a14093d5b890db23a2da310361 Mon Sep 17 00:00:00 2001 From: Pradip-p Date: Thu, 8 Aug 2024 15:08:49 +0545 Subject: [PATCH 5/8] fix: sql raw query & task approve status --- .../app/migrations/versions/2b92f8a9bbec_.py | 13 +++--- src/backend/app/tasks/task_crud.py | 43 +++++++++++++++++++ src/backend/app/tasks/task_routes.py | 40 ++++++----------- 3 files changed, 62 insertions(+), 34 deletions(-) diff --git a/src/backend/app/migrations/versions/2b92f8a9bbec_.py b/src/backend/app/migrations/versions/2b92f8a9bbec_.py index 8c75f306..88836952 100644 --- a/src/backend/app/migrations/versions/2b92f8a9bbec_.py +++ b/src/backend/app/migrations/versions/2b92f8a9bbec_.py @@ -35,7 +35,7 @@ # revision identifiers, used by Alembic. revision: str = "2b92f8a9bbec" -down_revision: Union[str, None] = "d862bfa31c36" +down_revision: Union[str, None] = "5d38e368b3d2" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -46,11 +46,10 @@ def upgrade() -> None: def downgrade() -> None: op.execute("ALTER TYPE state RENAME TO state_old") + old_state_enum.create(op.get_bind(), checkfirst=False) - op.execute( - ( - "ALTER TABLE task_events " - "ALTER COLUMN state TYPE state USING state::text::state" - ) - ) + op.execute("ALTER TABLE task_events ALTER COLUMN state TYPE text USING state::text") + # op.execute( + # "ALTER TABLE task_events ALTER COLUMN state TYPE state USING state::text::state" + # ) op.execute("DROP TYPE state_old") diff --git a/src/backend/app/tasks/task_crud.py b/src/backend/app/tasks/task_crud.py index dcad65ea..3f73a85f 100644 --- a/src/backend/app/tasks/task_crud.py +++ b/src/backend/app/tasks/task_crud.py @@ -168,6 +168,49 @@ async def request_mapping( return {"project_id": project_id, "task_id": task_id, "comment": comment} +async def update_task_state( + db: Database, + project_id: uuid.UUID, + task_id: uuid.UUID, + user_id: str, + comment: str, + initial_state: State, + final_state: State, +): + query = """ + WITH last AS ( + SELECT * + FROM task_events + WHERE project_id = :project_id AND task_id = :task_id + ORDER BY created_at DESC + LIMIT 1 + ), + locked AS ( + SELECT * + FROM last + WHERE user_id = :user_id AND state = :initial_state + ) + INSERT INTO task_events(event_id, project_id, task_id, user_id, state, comment, created_at) + SELECT gen_random_uuid(), project_id, task_id, user_id, :final_state, :comment, now() + FROM last + WHERE user_id = :user_id + RETURNING project_id, task_id, user_id, state; + """ + + values = { + "project_id": str(project_id), + "task_id": str(task_id), + "user_id": str(user_id), + "comment": comment, + "initial_state": initial_state.name, + "final_state": final_state.name, + } + + await db.fetch_one(query, values) + + return {"project_id": project_id, "task_id": task_id, "comment": comment} + + async def update_or_create_task_state( db: Database, project_id: uuid.UUID, diff --git a/src/backend/app/tasks/task_routes.py b/src/backend/app/tasks/task_routes.py index bd74f5bb..9e4d014a 100644 --- a/src/backend/app/tasks/task_routes.py +++ b/src/backend/app/tasks/task_routes.py @@ -108,30 +108,16 @@ async def get_task_stats( if not records: raise HTTPException(status_code=404, detail="User profile not found") - raw_sql = """ SELECT - (SELECT COUNT(*) - FROM tasks t - LEFT JOIN task_events te ON t.id = te.task_id - WHERE t.project_id IN (SELECT id FROM projects WHERE author_id = :user_id) - AND te.state = 'LOCKED_FOR_MAPPING') AS ongoing_tasks, - (SELECT COUNT(*) - FROM tasks t - LEFT JOIN task_events te ON t.id = te.task_id - WHERE t.project_id IN (SELECT id FROM projects WHERE author_id = :user_id) - AND te.state = 'REQUEST_FOR_MAPPING') AS request_logs, - (SELECT COUNT(*) - FROM tasks t - LEFT JOIN task_events te ON t.id = te.task_id - WHERE t.project_id IN (SELECT id FROM projects WHERE author_id = :user_id) - AND te.state = 'UNLOCKED_DONE') AS completed_tasks, - (SELECT COUNT(*) - FROM tasks t - LEFT JOIN task_events te ON t.id = te.task_id - WHERE t.project_id IN (SELECT id FROM projects WHERE author_id = :user_id) - AND te.state = 'UNFLYABLE_TASK') AS unflyable_tasks - """ + COUNT(CASE WHEN te.state = 'LOCKED_FOR_MAPPING' THEN 1 END) AS ongoing_tasks, + COUNT(CASE WHEN te.state = 'REQUEST_FOR_MAPPING' THEN 1 END) AS request_logs, + COUNT(CASE WHEN te.state = 'UNLOCKED_DONE' THEN 1 END) AS completed_tasks, + COUNT(CASE WHEN te.state = 'UNFLYABLE_TASK' THEN 1 END) AS unflyable_tasks + FROM tasks t + LEFT JOIN task_events te ON t.id = te.task_id + WHERE t.project_id IN (SELECT id FROM projects WHERE author_id = :user_id); + """ try: db_counts = await db.fetch_one(query=raw_sql, values={"user_id": user_id}) @@ -291,7 +277,7 @@ async def new_event( html_content, ) - return await task_crud.update_or_create_task_state( + return await task_crud.update_task_state( db, project_id, task_id, @@ -301,7 +287,7 @@ async def new_event( State.UNLOCKED_TO_MAP, ) case EventType.FINISH: - return await task_crud.update_or_create_task_state( + return await task_crud.update_task_state( db, project_id, task_id, @@ -311,7 +297,7 @@ async def new_event( State.UNLOCKED_TO_VALIDATE, ) case EventType.VALIDATE: - return await task_crud.update_or_create_task_state( + return await task_crud.update_task_state( db, project_id, task_id, @@ -321,7 +307,7 @@ async def new_event( State.LOCKED_FOR_VALIDATION, ) case EventType.GOOD: - return await task_crud.update_or_create_task_state( + return await task_crud.update_task_state( db, project_id, task_id, @@ -332,7 +318,7 @@ async def new_event( ) case EventType.BAD: - return await task_crud.update_or_create_task_state( + return await task_crud.update_task_state( db, project_id, task_id, From 94b019d88be614d26f3477b60a9dacc4ae6427f2 Mon Sep 17 00:00:00 2001 From: Pradip-p Date: Thu, 8 Aug 2024 16:59:30 +0545 Subject: [PATCH 6/8] refactor: consolidate task comment creation into task events update endpoint --- src/backend/app/tasks/task_routes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/app/tasks/task_routes.py b/src/backend/app/tasks/task_routes.py index 9e4d014a..f2397c82 100644 --- a/src/backend/app/tasks/task_routes.py +++ b/src/backend/app/tasks/task_routes.py @@ -20,14 +20,14 @@ @router.post("/{task_id}") -async def create_task_comment( +async def update_task_event( input: task_schemas.TaskComment, task_id: uuid.UUID, db: Database = Depends(database.get_db), user_data: AuthUser = Depends(login_required), ): """ - Create or update a comment for a specific task. + update a specific task event. Args: comment (task_schemas.TaskComment): The comment data. @@ -51,7 +51,7 @@ async def create_task_comment( raw_sql, {"task_id": task_id, "state": "UNFLYABLE_TASK", "comment": input.comment}, ) - return {"detail": "Successfully created the task comment."} + return {"detail": "Successfully updated the task event."} except Exception as e: raise HTTPException( From d9b15c2cfdbd2563d83dbf1ccb3feb0cfaa0bc6c Mon Sep 17 00:00:00 2001 From: Pradip-p Date: Thu, 8 Aug 2024 20:15:02 +0545 Subject: [PATCH 7/8] feat: add comment to task event update and render project ID when listing all tasks. --- src/backend/app/tasks/task_crud.py | 83 +++++++++++++++++++-------- src/backend/app/tasks/task_routes.py | 51 ++++------------ src/backend/app/tasks/task_schemas.py | 7 +-- 3 files changed, 71 insertions(+), 70 deletions(-) diff --git a/src/backend/app/tasks/task_crud.py b/src/backend/app/tasks/task_crud.py index 3f73a85f..adca0346 100644 --- a/src/backend/app/tasks/task_crud.py +++ b/src/backend/app/tasks/task_crud.py @@ -36,31 +36,64 @@ async def get_task_geojson(db: Database, task_id: uuid.UUID): async def get_tasks_by_user(user_id: str, db: Database): try: - query = """WITH task_details AS ( - SELECT - tasks.id AS task_id, - ST_Area(ST_Transform(tasks.outline, 4326)) / 1000000 AS task_area, - task_events.created_at, - task_events.state - FROM - task_events - JOIN - tasks ON task_events.task_id = tasks.id - WHERE - task_events.user_id = :user_id - ) - SELECT - task_details.task_id, - task_details.task_area, - task_details.created_at, - CASE - WHEN task_details.state = 'REQUEST_FOR_MAPPING' THEN 'ongoing' - WHEN task_details.state = 'UNLOCKED_DONE' THEN 'completed' - WHEN task_details.state IN ('UNLOCKED_TO_VALIDATE', 'LOCKED_FOR_VALIDATION') THEN 'mapped' - ELSE 'unknown' - END AS state - FROM task_details - """ + query = """ + WITH task_details AS ( + SELECT + tasks.id AS task_id, + tasks.project_id AS project_id, + ST_Area(ST_Transform(tasks.outline, 4326)) / 1000000 AS task_area, + task_events.created_at, + task_events.state + FROM + tasks + LEFT JOIN + task_events ON tasks.id = task_events.task_id AND tasks.project_id = task_events.project_id + WHERE + task_events.user_id = :user_id + ) + SELECT + task_details.task_id, + task_details.project_id, + task_details.task_area, + task_details.created_at, + CASE + WHEN task_details.state = 'REQUEST_FOR_MAPPING' THEN 'ongoing' + WHEN task_details.state = 'UNLOCKED_DONE' THEN 'completed' + WHEN task_details.state IN ('UNLOCKED_TO_VALIDATE', 'LOCKED_FOR_VALIDATION') THEN 'mapped' + ELSE 'unknown' + END AS state + FROM task_details + """ + + query = """ + WITH task_details AS ( + SELECT + tasks.id AS task_id, + task_events.project_id AS project_id, + ST_Area(ST_Transform(tasks.outline, 4326)) / 1000000 AS task_area, + task_events.created_at, + task_events.state + FROM + task_events + JOIN + tasks ON task_events.task_id = tasks.id + WHERE + task_events.user_id = :user_id + ) + SELECT + task_details.task_id, + task_details.project_id, + task_details.task_area, + task_details.created_at, + CASE + WHEN task_details.state = 'REQUEST_FOR_MAPPING' THEN 'ongoing' + WHEN task_details.state = 'UNLOCKED_DONE' THEN 'completed' + WHEN task_details.state IN ('UNLOCKED_TO_VALIDATE', 'LOCKED_FOR_VALIDATION') THEN 'mapped' + ELSE 'unknown' + END AS state + FROM task_details + """ + records = await db.fetch_all(query, values={"user_id": user_id}) return records diff --git a/src/backend/app/tasks/task_routes.py b/src/backend/app/tasks/task_routes.py index f2397c82..9907b27c 100644 --- a/src/backend/app/tasks/task_routes.py +++ b/src/backend/app/tasks/task_routes.py @@ -19,47 +19,6 @@ ) -@router.post("/{task_id}") -async def update_task_event( - input: task_schemas.TaskComment, - task_id: uuid.UUID, - db: Database = Depends(database.get_db), - user_data: AuthUser = Depends(login_required), -): - """ - update a specific task event. - - Args: - comment (task_schemas.TaskComment): The comment data. - task_id (uuid.UUID): The unique identifier of the task. - db (Database): The database session dependency. - user_data (AuthUser): The authenticated user data. - - Returns: - dict: A message indicating the success of the operation. - - Raises: - HTTPException: If updating the task comment fails. - """ - try: - raw_sql = """ - UPDATE task_events - SET state = :state, comment = :comment - WHERE task_id = :task_id - """ - await db.execute( - raw_sql, - {"task_id": task_id, "state": "UNFLYABLE_TASK", "comment": input.comment}, - ) - return {"detail": "Successfully updated the task event."} - - except Exception as e: - raise HTTPException( - status_code=HTTPStatus.INTERNAL_SERVER_ERROR, - detail=f"Failed to update task comment. {e}", - ) - - @router.get("/{task_id}") async def read_task( task_id: uuid.UUID, @@ -327,6 +286,16 @@ async def new_event( State.LOCKED_FOR_VALIDATION, State.UNLOCKED_TO_MAP, ) + case EventType.COMMENT: + return await task_crud.update_task_state( + db, + project_id, + task_id, + user_id, + detail.comment, + State.LOCKED_FOR_MAPPING, + State.UNFLYABLE_TASK, + ) return True diff --git a/src/backend/app/tasks/task_schemas.py b/src/backend/app/tasks/task_schemas.py index 5249a832..18912b02 100644 --- a/src/backend/app/tasks/task_schemas.py +++ b/src/backend/app/tasks/task_schemas.py @@ -2,10 +2,12 @@ from app.models.enums import EventType import uuid from datetime import datetime +from typing import Optional class NewEvent(BaseModel): event: EventType + comment: Optional[str] = None class UserTasksStatsOut(BaseModel): @@ -13,7 +15,4 @@ class UserTasksStatsOut(BaseModel): task_area: float created_at: datetime state: str - - -class TaskComment(BaseModel): - comment: str + project_id: uuid.UUID From 95133cdcf0d5c8722ac022796b590fbbe320474a Mon Sep 17 00:00:00 2001 From: Pradip-p Date: Thu, 8 Aug 2024 20:17:16 +0545 Subject: [PATCH 8/8] fix: remove unused raw sql query from get_tasks_by_user --- src/backend/app/tasks/task_crud.py | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/src/backend/app/tasks/task_crud.py b/src/backend/app/tasks/task_crud.py index adca0346..3a021318 100644 --- a/src/backend/app/tasks/task_crud.py +++ b/src/backend/app/tasks/task_crud.py @@ -36,35 +36,6 @@ async def get_task_geojson(db: Database, task_id: uuid.UUID): async def get_tasks_by_user(user_id: str, db: Database): try: - query = """ - WITH task_details AS ( - SELECT - tasks.id AS task_id, - tasks.project_id AS project_id, - ST_Area(ST_Transform(tasks.outline, 4326)) / 1000000 AS task_area, - task_events.created_at, - task_events.state - FROM - tasks - LEFT JOIN - task_events ON tasks.id = task_events.task_id AND tasks.project_id = task_events.project_id - WHERE - task_events.user_id = :user_id - ) - SELECT - task_details.task_id, - task_details.project_id, - task_details.task_area, - task_details.created_at, - CASE - WHEN task_details.state = 'REQUEST_FOR_MAPPING' THEN 'ongoing' - WHEN task_details.state = 'UNLOCKED_DONE' THEN 'completed' - WHEN task_details.state IN ('UNLOCKED_TO_VALIDATE', 'LOCKED_FOR_VALIDATION') THEN 'mapped' - ELSE 'unknown' - END AS state - FROM task_details - """ - query = """ WITH task_details AS ( SELECT