diff --git a/src/backend/app/db/enums.py b/src/backend/app/db/enums.py index f4883ca6df..8f1de72880 100644 --- a/src/backend/app/db/enums.py +++ b/src/backend/app/db/enums.py @@ -140,7 +140,11 @@ class TaskEvent(StrEnum, Enum): class MappingState(StrEnum, Enum): - """State options for tasks in FMTM.""" + """State options for tasks in FMTM. + + NOTE We no longer have states invalidated / bad, and instead rely on the + EntityState.MARKED_BAD buildings to display red on the map. + """ UNLOCKED_TO_MAP = "UNLOCKED_TO_MAP" LOCKED_FOR_MAPPING = "LOCKED_FOR_MAPPING" diff --git a/src/backend/app/db/models.py b/src/backend/app/db/models.py index 69ca517175..25a2a7113a 100644 --- a/src/backend/app/db/models.py +++ b/src/backend/app/db/models.py @@ -678,10 +678,10 @@ async def all( async def create( cls, db: Connection, - task_in: "TaskEventIn", + event_in: "TaskEventIn", ) -> Self: """Create a new task event.""" - model_dump = dump_and_check_model(task_in) + model_dump = dump_and_check_model(event_in) columns = ", ".join(model_dump.keys()) value_placeholders = ", ".join(f"%({key})s" for key in model_dump.keys()) diff --git a/src/backend/app/tasks/task_crud.py b/src/backend/app/tasks/task_crud.py index 581abb5681..8032d745ba 100644 --- a/src/backend/app/tasks/task_crud.py +++ b/src/backend/app/tasks/task_crud.py @@ -17,12 +17,9 @@ # """Logic for FMTM tasks.""" -from datetime import datetime, timedelta - from fastapi import HTTPException from loguru import logger as log from psycopg import Connection -from psycopg.rows import class_row from app.db.enums import ( HTTPStatus, @@ -61,40 +58,41 @@ async def new_task_event( return created_task_event -async def get_project_task_activity( - db: Connection, - project_id: int, - days: int, -) -> task_schemas.TaskEventCount: - """Get number of tasks mapped and validated for project. +# FIXME the endpoint that calls this isn't used? +# async def get_project_task_activity( +# db: Connection, +# project_id: int, +# days: int, +# ) -> task_schemas.TaskEventCount: +# """Get number of tasks mapped and validated for 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 (Connection): The database connection. +# Args: +# project_id (int): The ID of the project. +# days (int): The number of days to consider for the +# task activity (default: 10). +# db (Connection): The database connection. - Returns: - list[task_schemas.TaskEventCount]: A list of task event counts. - """ - end_date = datetime.now() - timedelta(days=days) +# Returns: +# list[task_schemas.TaskEventCount]: A list of task event counts. +# """ +# end_date = datetime.now() - timedelta(days=days) - sql = """ - SELECT - to_char(created_at::date, 'dd/mm/yyyy') as date, - COUNT(*) FILTER (WHERE state = 'UNLOCKED_DONE') AS validated, - COUNT(*) FILTER (WHERE state = 'UNLOCKED_TO_VALIDATE') AS mapped - FROM - task_events - WHERE - project_id = %(project_id)s - AND created_at >= %(end_date)s - GROUP BY - created_at::date - ORDER BY - created_at::date; - """ +# sql = """ +# SELECT +# to_char(created_at::date, 'dd/mm/yyyy') as date, +# COUNT(*) FILTER (WHERE state = 'UNLOCKED_DONE') AS validated, +# COUNT(*) FILTER (WHERE state = 'UNLOCKED_TO_VALIDATE') AS mapped +# FROM +# task_events +# WHERE +# project_id = %(project_id)s +# AND created_at >= %(end_date)s +# GROUP BY +# created_at::date +# ORDER BY +# created_at::date; +# """ - async with db.cursor(row_factory=class_row(task_schemas.TaskEventCount)) as cur: - await cur.execute(sql, {"project_id": project_id, "end_date": end_date}) - return await cur.fetchall() +# async with db.cursor(row_factory=class_row(task_schemas.TaskEventCount)) as cur: +# await cur.execute(sql, {"project_id": project_id, "end_date": end_date}) +# return await cur.fetchall() diff --git a/src/backend/app/tasks/task_routes.py b/src/backend/app/tasks/task_routes.py index 40cfe6b0ac..0a0996e28e 100644 --- a/src/backend/app/tasks/task_routes.py +++ b/src/backend/app/tasks/task_routes.py @@ -25,7 +25,7 @@ from app.auth.auth_schemas import ProjectUserDict from app.auth.roles import get_uid, mapper from app.db.database import db_conn -from app.db.enums import HTTPStatus, MappingState, TaskEvent +from app.db.enums import HTTPStatus, TaskEvent from app.db.models import DbTask, DbTaskEvent from app.tasks import task_crud, task_schemas from app.tasks.task_deps import get_task @@ -70,12 +70,12 @@ async def get_specific_task( # TODO update this to be POST /project/{pid}/events ? @router.post( - "/{task_id}/new-status/{new_status}", response_model=task_schemas.TaskEventOut + "/{task_id}/new-status/{new_event}", response_model=task_schemas.TaskEventOut ) async def add_new_task_event( db_task: Annotated[DbTask, Depends(get_task)], project_user: Annotated[ProjectUserDict, Depends(mapper)], - new_status: MappingState, + new_event: TaskEvent, db: Annotated[Connection, Depends(db_conn)], ): """Add a new event to the events table / update task status.""" @@ -84,7 +84,7 @@ async def add_new_task_event( db, db_task.id, user_id, - new_status, + new_event, ) @@ -117,26 +117,26 @@ async def add_task_comment( return await DbTaskEvent.create(db, new_comment) -# NOTE this endpoint isn't used? -@router.get("/activity/", response_model=list[task_schemas.TaskEventCount]) -async def task_activity( - project_id: int, - db: Annotated[Connection, Depends(db_conn)], - project_user: Annotated[ProjectUserDict, Depends(mapper)], - days: int = 10, -): - """Get the number of mapped or validated tasks on each day. - - Return format: - [ - { - date: DD/MM/YYYY, - validated: int, - mapped: int, - } - ] - """ - return await task_crud.get_project_task_activity(db, project_id, days) +# FIXME this endpoint isn't used? +# @router.get("/activity/", response_model=list[task_schemas.TaskEventCount]) +# async def task_activity( +# project_id: int, +# db: Annotated[Connection, Depends(db_conn)], +# project_user: Annotated[ProjectUserDict, Depends(mapper)], +# days: int = 10, +# ): +# """Get the number of mapped or validated tasks on each day. + +# Return format: +# [ +# { +# date: DD/MM/YYYY, +# validated: int, +# mapped: int, +# } +# ] +# """ +# return await task_crud.get_project_task_activity(db, project_id, days) @router.get("/{task_id}/history/", response_model=list[task_schemas.TaskEventOut]) diff --git a/src/backend/tests/test_task_routes.py b/src/backend/tests/test_task_routes.py index dec5ee51ab..e446d3a807 100644 --- a/src/backend/tests/test_task_routes.py +++ b/src/backend/tests/test_task_routes.py @@ -41,7 +41,7 @@ async def test_read_task_history(client, task_event): assert data["username"] == task_event.username assert data["profile_img"] == task_event.profile_img assert data["comment"] == task_event.comment - assert data["status"] == MappingState.READY + assert data["status"] == MappingState.UNLOCKED_TO_MAP async def test_update_task_status(client, tasks): diff --git a/src/frontend/e2e/02-mapper-flow.spec.ts b/src/frontend/e2e/02-mapper-flow.spec.ts index b6cc32a7e7..75961f2e34 100644 --- a/src/frontend/e2e/02-mapper-flow.spec.ts +++ b/src/frontend/e2e/02-mapper-flow.spec.ts @@ -22,7 +22,7 @@ test.describe('mapper flow', () => { y: 95, }, }); - await expect(page.getByText('Status: READY')).toBeVisible(); + await expect(page.getByText('Status: UNLOCKED_TO_MAP')).toBeVisible(); await page.getByRole('alert').waitFor({ state: 'hidden' }); await page.getByTitle('Close').getByTestId('CloseIcon').click(); // Use maxDiffPixelRatio to avoid issues with OSM tile loading delay @@ -119,7 +119,7 @@ test.describe('mapper flow', () => { y: 220, }, }); - await expect(page.getByText('Status: READY')).toBeVisible(); + await expect(page.getByText('Status: UNLOCKED_TO_MAP')).toBeVisible(); await expect(page.getByRole('button', { name: 'START MAPPING' })).toBeVisible(); // 2. Click on a specific feature / Entity within a task diff --git a/src/frontend/src/components/DialogTaskActions.tsx b/src/frontend/src/components/DialogTaskActions.tsx index 6545a619a8..379a045519 100755 --- a/src/frontend/src/components/DialogTaskActions.tsx +++ b/src/frontend/src/components/DialogTaskActions.tsx @@ -70,6 +70,7 @@ export default function Dialog({ taskId, feature }: dialogPropType) { } }, [taskId, taskInfo]); + // TODO need to set the TaskEvent here, not MappingStatus? useEffect(() => { if (projectIndex != -1) { const currentStatus = projectTaskActivityList.length > 0 ? projectTaskActivityList[0].state : 'UNLOCKED_TO_MAP'; diff --git a/src/frontend/src/components/ProjectDetailsV2/Comments.tsx b/src/frontend/src/components/ProjectDetailsV2/Comments.tsx index 2d693b31e9..7a4d547345 100644 --- a/src/frontend/src/components/ProjectDetailsV2/Comments.tsx +++ b/src/frontend/src/components/ProjectDetailsV2/Comments.tsx @@ -34,7 +34,6 @@ const Comments = () => { ); useEffect(() => { - console.log(currentStatus); dispatch( GetProjectComments( `${import.meta.env.VITE_API_URL}/tasks/${currentStatus?.id}/history/?project_id=${projectId}&comment=true`, diff --git a/src/frontend/src/environment.ts b/src/frontend/src/environment.ts index d3a4d18e5d..ed07b586e6 100755 --- a/src/frontend/src/environment.ts +++ b/src/frontend/src/environment.ts @@ -5,41 +5,45 @@ export default { return parseInt(binary, 2); }, encode: (dec) => { - const desimaal = (dec >>> 0).toString(2); - return window.btoa(desimaal); + const decimal = (dec >>> 0).toString(2); + return window.btoa(decimal); }, matomoTrackingId: '28', tasksStatus: [ { label: 'UNLOCKED_TO_MAP', - action: [{ key: 'Start Mapping', value: 'LOCKED_FOR_MAPPING', btnBG: 'red' }], + action: [{ key: 'Start Mapping', value: 'MAP', btnBG: 'red' }], btnBG: 'red', }, { label: 'LOCKED_FOR_MAPPING', action: [ - { key: 'Mark as fully mapped', value: 'UNLOCKED_TO_VALIDATE', btnBG: 'gray' }, - { key: 'Stop Mapping', value: 'UNLOCKED_TO_MAP', btnBG: 'transparent' }, + { key: 'Mark as fully mapped', value: 'FINISH', btnBG: 'gray' }, + { key: 'Stop Mapping', value: 'BAD', btnBG: 'transparent' }, ], }, { label: 'UNLOCKED_TO_VALIDATE', action: [ - { key: 'Start Validation', value: 'LOCKED_FOR_VALIDATION', btnBG: 'gray' }, - { key: 'Reset', value: 'UNLOCKED_TO_MAP' }, + { key: 'Start Validation', value: 'VALIDATE', btnBG: 'gray' }, + { key: 'Reset', value: 'BAD' }, ], }, { label: 'LOCKED_FOR_VALIDATION', action: [ - { key: 'Mark As Validated', value: 'UNLOCKED_DONE', btnBG: 'gray' }, - { key: 'Mapping Needed', value: 'UNLOCKED_TO_MAP', btnBG: 'transparent' }, + { key: 'Mark As Validated', value: 'GOOD', btnBG: 'gray' }, + { key: 'Mapping Needed', value: 'BAD', btnBG: 'transparent' }, ], }, - { label: 'UNLOCKED_DONE', action: [{ key: 'Reset', value: 'UNLOCKED_TO_MAP', btnBG: 'gray' }] }, - // { label: 'VALIDATED', action: [{ key: 'Merge data with OSM', value: 'MERGE_WITH_OSM', btnBG: 'gray' }] }, - // "SPLIT", - // "ARCHIVED", + { + label: 'UNLOCKED_DONE', + action: [ + { key: 'Reset', value: 'BAD', btnBG: 'gray' }, + // { key: 'Merge data with OSM', value: 'CONFLATE', btnBG: 'gray' }, + ], + }, + // TODO add SPLIT, MERGE, ASSIGN ], baseMapProviders: [ { id: 1, label: 'ESRI', value: 'esri' }, diff --git a/src/frontend/src/hooks/MapStyles.js b/src/frontend/src/hooks/MapStyles.js index 84881ee1d8..6a288f5c28 100755 --- a/src/frontend/src/hooks/MapStyles.js +++ b/src/frontend/src/hooks/MapStyles.js @@ -54,7 +54,7 @@ export default function MapStyles() { const redIconStyle = createIconStyle(AssetModules.RedLockPng); const geojsonStyles = { - READY: new Style({ + UNLOCKED_TO_MAP: new Style({ stroke: new Stroke({ color: strokeColor, width: 3, @@ -64,7 +64,7 @@ export default function MapStyles() { }), }), LOCKED_FOR_MAPPING: [lockedPolygonStyle, iconStyle], - MAPPED: new Style({ + UNLOCKED_TO_VALIDATE: new Style({ stroke: new Stroke({ color: strokeColor, width: 3, @@ -74,8 +74,7 @@ export default function MapStyles() { }), }), LOCKED_FOR_VALIDATION: [lockedValidationStyle, redIconStyle], - - VALIDATED: new Style({ + UNLOCKED_DONE: new Style({ stroke: new Stroke({ color: strokeColor, width: 3, @@ -84,33 +83,24 @@ export default function MapStyles() { color: mapTheme.palette.mapFeatureColors.validated_rgb, }), }), - INVALIDATED: new Style({ - stroke: new Stroke({ - color: strokeColor, - width: 3, - }), - fill: new Fill({ - color: mapTheme.palette.mapFeatureColors.invalidated_rgb, - }), - }), - BAD: new Style({ - stroke: new Stroke({ - color: strokeColor, - width: 3, - }), - fill: new Fill({ - color: mapTheme.palette.mapFeatureColors.bad_rgb, - }), - }), - SPLIT: new Style({ - stroke: new Stroke({ - color: strokeColor, - width: 3, - }), - fill: new Fill({ - color: mapTheme.palette.mapFeatureColors.split_rgb, - }), - }), + // INVALIDATED: new Style({ + // stroke: new Stroke({ + // color: strokeColor, + // width: 3, + // }), + // fill: new Fill({ + // color: mapTheme.palette.mapFeatureColors.invalidated_rgb, + // }), + // }), + // BAD: new Style({ + // stroke: new Stroke({ + // color: strokeColor, + // width: 3, + // }), + // fill: new Fill({ + // color: mapTheme.palette.mapFeatureColors.bad_rgb, + // }), + // }), }; setStyle(geojsonStyles); }, []); diff --git a/src/frontend/src/types/enums.ts b/src/frontend/src/types/enums.ts index 75fe227f59..9447a4ad58 100644 --- a/src/frontend/src/types/enums.ts +++ b/src/frontend/src/types/enums.ts @@ -4,6 +4,18 @@ export enum task_split_type { task_splitting_algorithm = 'TASK_SPLITTING_ALGORITHM', } +export enum task_actions { + MAP = 'MAP', + FINISH = 'FINISH', + VALIDATE = 'VALIDATE', + GOOD = 'GOOD', + BAD = 'BAD', + SPLIT = 'SPLIT', + MERGE = 'MERGE', + ASSIGN = 'ASSIGN', + COMMENT = 'COMMENT', +} + export enum task_state { UNLOCKED_TO_MAP = 'UNLOCKED_TO_MAP', LOCKED_FOR_MAPPING = 'LOCKED_FOR_MAPPING', diff --git a/src/frontend/src/utilfunctions/getTaskStatusStyle.ts b/src/frontend/src/utilfunctions/getTaskStatusStyle.ts index 05216a9c6a..efed48d590 100644 --- a/src/frontend/src/utilfunctions/getTaskStatusStyle.ts +++ b/src/frontend/src/utilfunctions/getTaskStatusStyle.ts @@ -124,7 +124,7 @@ export const getFeatureStatusStyle = (osmId: string, mapTheme: Record