Skip to content

Commit

Permalink
refactor: further wip updates for task events
Browse files Browse the repository at this point in the history
  • Loading branch information
spwoodcock committed Oct 26, 2024
1 parent 7e19cdc commit 7f30af2
Show file tree
Hide file tree
Showing 12 changed files with 140 additions and 142 deletions.
6 changes: 5 additions & 1 deletion src/backend/app/db/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions src/backend/app/db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())

Expand Down
70 changes: 34 additions & 36 deletions src/backend/app/tasks/task_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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()
48 changes: 24 additions & 24 deletions src/backend/app/tasks/task_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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."""
Expand All @@ -84,7 +84,7 @@ async def add_new_task_event(
db,
db_task.id,
user_id,
new_status,
new_event,
)


Expand Down Expand Up @@ -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])
Expand Down
2 changes: 1 addition & 1 deletion src/backend/tests/test_task_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/e2e/02-mapper-flow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/frontend/src/components/DialogTaskActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
1 change: 0 additions & 1 deletion src/frontend/src/components/ProjectDetailsV2/Comments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down
30 changes: 17 additions & 13 deletions src/frontend/src/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down
52 changes: 21 additions & 31 deletions src/frontend/src/hooks/MapStyles.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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);
}, []);
Expand Down
12 changes: 12 additions & 0 deletions src/frontend/src/types/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading

0 comments on commit 7f30af2

Please sign in to comment.