From 4c3cd0ae167bfdd4a358b8120e2d3b94340cd04a Mon Sep 17 00:00:00 2001 From: Anuj-Gupta4 Date: Tue, 10 Dec 2024 17:57:27 +0545 Subject: [PATCH] feat: auto unlock locked tasks after 3 days --- src/backend/app/db/models.py | 16 +++-- src/backend/app/tasks/task_routes.py | 65 +++++++++++++++++++ .../migrations/011-add-date-to-entity.sql | 22 +++++++ 3 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 src/backend/migrations/011-add-date-to-entity.sql diff --git a/src/backend/app/db/models.py b/src/backend/app/db/models.py index 61a951ba1d..3f8879b6d4 100644 --- a/src/backend/app/db/models.py +++ b/src/backend/app/db/models.py @@ -22,7 +22,7 @@ """ import json -from datetime import timedelta +from datetime import datetime, timedelta from io import BytesIO from re import sub from typing import TYPE_CHECKING, Annotated, Optional, Self @@ -1366,6 +1366,7 @@ class DbOdkEntities(BaseModel): status: EntityState project_id: int task_id: int + updated_at: Optional[AwareDatetime] = None @classmethod async def upsert( @@ -1391,7 +1392,7 @@ async def upsert( sql = """ INSERT INTO public.odk_entities - (entity_id, status, project_id, task_id) + (entity_id, status, project_id, task_id, updated_at) VALUES """ @@ -1404,20 +1405,27 @@ async def upsert( f"(%({entity_index}_entity_id)s, " f"%({entity_index}_status)s, " f"%({entity_index}_project_id)s, " - f"%({entity_index}_task_id)s)" + f"%({entity_index}_task_id)s, " + f"%({entity_index}_updated_at)s)" ) data[f"{entity_index}_entity_id"] = entity["id"] data[f"{entity_index}_status"] = EntityState(int(entity["status"])).name data[f"{entity_index}_project_id"] = project_id task_id = entity["task_id"] data[f"{entity_index}_task_id"] = int(task_id) if task_id else None + data[f"{entity_index}_updated_at"] = ( + datetime.fromisoformat(entity["updatedAt"].replace("Z", "+00:00")) + if entity["updatedAt"] + else None + ) sql += ( ", ".join(values) + """ ON CONFLICT (entity_id) DO UPDATE SET status = EXCLUDED.status, - task_id = EXCLUDED.task_id + task_id = EXCLUDED.task_id, + updated_at = EXCLUDED.updated_at RETURNING True; """ ) diff --git a/src/backend/app/tasks/task_routes.py b/src/backend/app/tasks/task_routes.py index 3529b838e0..7a4ba420d2 100644 --- a/src/backend/app/tasks/task_routes.py +++ b/src/backend/app/tasks/task_routes.py @@ -115,3 +115,68 @@ async def get_task_event_history( ): """Get the detailed history for a task.""" return await DbTaskEvent.all(db, task_id=task_id, days=days, comments=comments) + + +@router.post("/unlock-tasks") +async def trigger_unlock_tasks(db: Annotated[Connection, Depends(db_conn)]): + """Endpoint to trigger unlock_old_locked_tasks manually.""" + await unlock_old_locked_tasks(db) + return {"message": "Old locked tasks unlocked successfully."} + + +async def unlock_old_locked_tasks(db): + """Unlock tasks locked for more than 3 days.""" + unlock_query = """ + WITH svc_user AS ( + SELECT id AS svc_user_id, username AS svc_username + FROM users + WHERE username = 'svcfmtm' + ), + recent_events AS ( + SELECT DISTINCT ON (t.id, t.project_id) + t.id AS task_id, + t.project_id, + MAX(the.created_at) AS last_event_time, + oe.status AS entity_status, + oe.updated_at + FROM tasks t + JOIN task_events the ON t.id = the.task_id + LEFT JOIN odk_entities oe ON t.id = oe.task_id + WHERE the.event IN ('ASSIGN', 'MAP') + GROUP BY t.id, t.project_id, oe.entity_id, oe.status, oe.updated_at + ) + INSERT INTO task_events ( + event_id, + task_id, + project_id, + event, + user_id, + state, + created_at, + username + ) + SELECT + gen_random_uuid(), + re.task_id, + re.project_id, + 'MAP'::taskevent, + svc.svc_user_id, + 'UNLOCKED_TO_MAP'::mappingstate, + NOW(), + svc.svc_username + FROM recent_events re + CROSS JOIN svc_user svc + WHERE re.last_event_time < NOW() - INTERVAL '3 days' + AND re.updated_at::timestamp < NOW() - INTERVAL '3 days'; + """ + + async with db.cursor() as cur: + await cur.execute("BEGIN;") + await cur.execute( + "ALTER TABLE task_events DISABLE TRIGGER task_event_state_trigger;" + ) + await cur.execute(unlock_query) + await cur.execute( + "ALTER TABLE task_events ENABLE TRIGGER task_event_state_trigger;" + ) + await cur.execute("COMMIT;") diff --git a/src/backend/migrations/011-add-date-to-entity.sql b/src/backend/migrations/011-add-date-to-entity.sql new file mode 100644 index 0000000000..c16e4c87e6 --- /dev/null +++ b/src/backend/migrations/011-add-date-to-entity.sql @@ -0,0 +1,22 @@ +-- ## Migration to: +-- * track the last time an entity was updated + +-- Start a transaction +BEGIN; + +-- Add column 'updated_at' to 'odk_entities' table +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_name='odk_entities' AND column_name='updated_at' + ) THEN + ALTER TABLE public.odk_entities + ADD COLUMN updated_at VARCHAR; + END IF; +END $$; + + +-- Commit the transaction +COMMIT;