From 257e549c3597285ee664ac63e1faa71397a381b9 Mon Sep 17 00:00:00 2001 From: Pradip-p Date: Tue, 22 Oct 2024 10:11:51 +0545 Subject: [PATCH 01/33] feat: update the status & project creation time on project lists --- src/backend/app/projects/project_schemas.py | 15 ++++----------- src/backend/app/tasks/task_schemas.py | 9 +-------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/backend/app/projects/project_schemas.py b/src/backend/app/projects/project_schemas.py index d05ee799..1be1d66b 100644 --- a/src/backend/app/projects/project_schemas.py +++ b/src/backend/app/projects/project_schemas.py @@ -258,15 +258,7 @@ async def one(db: Connection, project_id: uuid.UUID): SELECT DISTINCT ON (te.task_id) te.task_id, te.user_id, - CASE - WHEN te.state = 'REQUEST_FOR_MAPPING' THEN 'request logs' - WHEN te.state = 'LOCKED_FOR_MAPPING' OR te.state = 'IMAGE_UPLOADED' THEN 'ongoing' - WHEN te.state = 'IMAGE_PROCESSED' THEN 'completed' - WHEN te.state = 'UNFLYABLE_TASK' THEN 'unflyable task' - WHEN te.state = 'IMAGE_PROCESSING_FAILED' THEN 'task failed' - - ELSE '' - END AS calculated_state + te.state FROM task_events te ORDER BY @@ -283,7 +275,7 @@ async def one(db: Connection, project_id: uuid.UUID): ST_YMin(ST_Envelope(t.outline)) AS ymin, ST_XMax(ST_Envelope(t.outline)) AS xmax, ST_YMax(ST_Envelope(t.outline)) AS ymax, - COALESCE(tsc.calculated_state) AS state, + tsc.state AS state, tsc.user_id, u.name, ST_Area(ST_Transform(t.outline, 3857)) / 1000000 AS task_area @@ -343,7 +335,7 @@ async def all( await cur.execute( """ SELECT - p.id, p.slug, p.name, p.description, p.per_task_instructions, + p.id, p.slug, p.name, p.description, p.per_task_instructions, p.created_at, ST_AsGeoJSON(p.outline)::jsonb AS outline, p.requires_approval_from_manager_for_locking, @@ -541,6 +533,7 @@ class ProjectInfo(BaseModel): ongoing_task_count: Optional[int] = 0 completed_task_count: Optional[int] = 0 status: Optional[str] = "not-started" + created_at: datetime @model_validator(mode="after") def set_image_url(cls, values): diff --git a/src/backend/app/tasks/task_schemas.py b/src/backend/app/tasks/task_schemas.py index 14155290..28b2fbfd 100644 --- a/src/backend/app/tasks/task_schemas.py +++ b/src/backend/app/tasks/task_schemas.py @@ -168,14 +168,7 @@ async def get_tasks_by_user( ST_Area(ST_Transform(tasks.outline, 3857)) / 1000000 AS task_area, task_events.created_at, task_events.updated_at, - CASE - WHEN task_events.state = 'REQUEST_FOR_MAPPING' THEN 'request logs' - WHEN task_events.state IN ('LOCKED_FOR_MAPPING', 'IMAGE_UPLOADED') THEN 'ongoing' - WHEN task_events.state = 'IMAGE_PROCESSED' THEN 'completed' - WHEN task_events.state = 'UNFLYABLE_TASK' THEN 'unflyable task' - WHEN task_events.state = 'IMAGE_PROCESSING_FAILED' THEN 'task failed' - ELSE '' - END AS state + task_events.state FROM task_events LEFT JOIN From 636aaca40ab34aca1a5fac4908a7f309eee83581 Mon Sep 17 00:00:00 2001 From: Pradip-p Date: Tue, 22 Oct 2024 10:37:17 +0545 Subject: [PATCH 02/33] feat: update task events endpoint for the task image uploaded --- src/backend/app/models/enums.py | 2 ++ src/backend/app/projects/project_routes.py | 15 +---------- src/backend/app/projects/project_schemas.py | 1 + src/backend/app/tasks/task_logic.py | 20 -------------- src/backend/app/tasks/task_routes.py | 30 +++++++++++++++++++++ 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/backend/app/models/enums.py b/src/backend/app/models/enums.py index 409af322..efa7e5cc 100644 --- a/src/backend/app/models/enums.py +++ b/src/backend/app/models/enums.py @@ -168,6 +168,7 @@ class EventType(str, Enum): - ``assign`` -- For a requester user to assign a task to another user. Set the state *locked for mapping* passing in the required user id. - ``comment`` -- Keep the state the same, but simply add a comment. - ``unlock`` -- Unlock a task state by unlocking it if it's locked. + - ``image_upload`` -- Set the state to *image uploaded* when the task image is uploaded. Note that ``task_id`` must be specified in the endpoint too. """ @@ -183,3 +184,4 @@ class EventType(str, Enum): ASSIGN = "assign" COMMENT = "comment" UNLOCK = "unlock" + IMAGE_UPLOAD = "image_upload" diff --git a/src/backend/app/projects/project_routes.py b/src/backend/app/projects/project_routes.py index bfed3700..cb6751f6 100644 --- a/src/backend/app/projects/project_routes.py +++ b/src/backend/app/projects/project_routes.py @@ -395,17 +395,6 @@ async def process_imagery( db: Annotated[Connection, Depends(database.get_db)], ): user_id = user_data.id - # TODO: Update task state to reflect completion of image uploads. - await task_logic.update_task_state( - db, - project.id, - task_id, - user_id, - "Task images upload completed.", - State.LOCKED_FOR_MAPPING, - State.IMAGE_UPLOADED, - timestamp(), - ) background_tasks.add_task( project_logic.process_drone_images, project.id, task_id, user_id, db ) @@ -493,9 +482,7 @@ async def odm_webhook( "Task completed.", ) elif status["code"] == 30: - current_state = await task_logic.get_current_state( - db, dtm_project_id, dtm_task_id - ) + current_state = await task_logic.get_task_state(db, dtm_project_id, dtm_task_id) # If the current state is not already IMAGE_PROCESSING_FAILED, update it if current_state != State.IMAGE_PROCESSING_FAILED: await task_logic.update_task_state( diff --git a/src/backend/app/projects/project_schemas.py b/src/backend/app/projects/project_schemas.py index 1be1d66b..c10a3b68 100644 --- a/src/backend/app/projects/project_schemas.py +++ b/src/backend/app/projects/project_schemas.py @@ -197,6 +197,7 @@ class DbProject(BaseModel): altitude_from_ground: Optional[float] = None is_terrain_follow: bool = False image_url: Optional[str] = None + created_at: datetime async def one(db: Connection, project_id: uuid.UUID): """Get a single project & all associated tasks by ID.""" diff --git a/src/backend/app/tasks/task_logic.py b/src/backend/app/tasks/task_logic.py index dff7d597..398a67b2 100644 --- a/src/backend/app/tasks/task_logic.py +++ b/src/backend/app/tasks/task_logic.py @@ -7,26 +7,6 @@ from datetime import datetime -async def get_current_state(db, project_id, task_id): - try: - async with db.cursor() as cur: - await cur.execute( - """ - SELECT DISTINCT ON (state) state - FROM task_events - WHERE task_id = %(task_id)s AND project_id = %(project_id)s - ORDER BY state, created_at DESC - """, - {"task_id": task_id, "project_id": project_id}, - ) - return await cur.fetchone() - - except Exception as err: - raise HTTPException( - status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(err) - ) - - async def update_take_off_point_in_db( db: Connection, task_id: uuid.UUID, take_off_point: str ): diff --git a/src/backend/app/tasks/task_routes.py b/src/backend/app/tasks/task_routes.py index f5a05a4a..0608a09e 100644 --- a/src/backend/app/tasks/task_routes.py +++ b/src/backend/app/tasks/task_routes.py @@ -401,4 +401,34 @@ async def new_event( detail.updated_at, ) + case EventType.IMAGE_UPLOAD: + current_task_state = await task_logic.get_task_state( + db, project_id, task_id + ) + state = current_task_state.get("state") + locked_user_id = current_task_state.get("user_id") + + # Determine error conditions + if state != State.LOCKED_FOR_MAPPING.name: + raise HTTPException( + status_code=400, + detail="Task state does not match expected state for image upload.", + ) + if user_id != locked_user_id: + raise HTTPException( + status_code=403, + detail="You cannot upload an image for this task as it is locked by another user.", + ) + + return await task_logic.update_task_state( + db, + project_id, + task_id, + user_id, + f"Task image uploaded by user {user_data.name}.", + State.LOCKED_FOR_MAPPING, + State.IMAGE_UPLOADED, + detail.updated_at, + ) + return True From b7db9831c5581b4e20a2c8a2cdeaa50c5634630a Mon Sep 17 00:00:00 2001 From: Pradip-p Date: Tue, 22 Oct 2024 11:00:30 +0545 Subject: [PATCH 03/33] feat: added the new case for re-run the image task processing... --- src/backend/app/projects/project_routes.py | 53 +++++++++++++++++----- src/backend/app/tasks/task_routes.py | 5 +- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/src/backend/app/projects/project_routes.py b/src/backend/app/projects/project_routes.py index cb6751f6..3dea1b19 100644 --- a/src/backend/app/projects/project_routes.py +++ b/src/backend/app/projects/project_routes.py @@ -469,18 +469,47 @@ async def odm_webhook( if status["code"] == 40: log.info(f"Task ID: {task_id}, Status: going for download......") - # Call function to download assets from ODM and upload to S3 - background_tasks.add_task( - image_processing.download_and_upload_assets_from_odm_to_s3, - db, - settings.NODE_ODM_URL, - task_id, - dtm_project_id, - dtm_task_id, - dtm_user_id, - State.IMAGE_UPLOADED, - "Task completed.", - ) + current_state = await task_logic.get_task_state(db, dtm_project_id, dtm_task_id) + match current_state: + case State.IMAGE_UPLOADED: + log.info( + f"Task ID: {task_id}, Status: already IMAGE_UPLOADED - no update needed." + ) + # Call function to download assets from ODM and upload to S3 + background_tasks.add_task( + image_processing.download_and_upload_assets_from_odm_to_s3, + db, + settings.NODE_ODM_URL, + task_id, + dtm_project_id, + dtm_task_id, + dtm_user_id, + State.IMAGE_UPLOADED, + "Task completed.", + ) + + case State.IMAGE_PROCESSING_FAILED: + log.warning( + f"Task ID: {task_id}, Status: previously failed, updating to IMAGE_UPLOADED" + ) + # Call function to download assets from ODM and upload to S3 + background_tasks.add_task( + image_processing.download_and_upload_assets_from_odm_to_s3, + db, + settings.NODE_ODM_URL, + task_id, + dtm_project_id, + dtm_task_id, + dtm_user_id, + State.IMAGE_UPLOADED, + "Task completed.", + ) + + case _: + log.info( + f"Task ID: {task_id}, Status: updating to IMAGE_UPLOADED from {current_state}" + ) + elif status["code"] == 30: current_state = await task_logic.get_task_state(db, dtm_project_id, dtm_task_id) # If the current state is not already IMAGE_PROCESSING_FAILED, update it diff --git a/src/backend/app/tasks/task_routes.py b/src/backend/app/tasks/task_routes.py index 0608a09e..fedfb3ad 100644 --- a/src/backend/app/tasks/task_routes.py +++ b/src/backend/app/tasks/task_routes.py @@ -409,7 +409,10 @@ async def new_event( locked_user_id = current_task_state.get("user_id") # Determine error conditions - if state != State.LOCKED_FOR_MAPPING.name: + if ( + state != State.LOCKED_FOR_MAPPING.name + or State.IMAGE_PROCESSING_FAILED.name + ): raise HTTPException( status_code=400, detail="Task state does not match expected state for image upload.", From 0b35e341346c81bc76b4f0166d1c41eeaffad35c Mon Sep 17 00:00:00 2001 From: Pradip-p Date: Tue, 22 Oct 2024 12:35:21 +0545 Subject: [PATCH 04/33] feat: updated status of task on get_assets_info --- src/backend/app/projects/project_routes.py | 5 ++++- src/backend/app/projects/project_schemas.py | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/backend/app/projects/project_routes.py b/src/backend/app/projects/project_routes.py index 3dea1b19..efca9491 100644 --- a/src/backend/app/projects/project_routes.py +++ b/src/backend/app/projects/project_routes.py @@ -432,7 +432,10 @@ async def get_assets_info( return results else: - return project_logic.get_project_info_from_s3(project.id, task_id) + current_state = await task_logic.get_task_state(db, project.id, task_id) + project_info = project_logic.get_project_info_from_s3(project.id, task_id) + project_info.state = current_state.get("state") + return project_info @router.post( diff --git a/src/backend/app/projects/project_schemas.py b/src/backend/app/projects/project_schemas.py index c10a3b68..bd394db6 100644 --- a/src/backend/app/projects/project_schemas.py +++ b/src/backend/app/projects/project_schemas.py @@ -12,7 +12,7 @@ from psycopg import Connection from psycopg.rows import class_row from slugify import slugify -from app.models.enums import FinalOutput, ProjectVisibility +from app.models.enums import FinalOutput, ProjectVisibility, UserRole from app.models.enums import ( IntEnum, ProjectStatus, @@ -58,6 +58,7 @@ class AssetsInfo(BaseModel): task_id: str image_count: int assets_url: Optional[str] + state: Optional[UserRole] = None def validate_geojson( From 9a913e386b8282353535f20fea501ed846822cfa Mon Sep 17 00:00:00 2001 From: Sujit Date: Tue, 22 Oct 2024 13:21:41 +0545 Subject: [PATCH 05/33] feat: add static cumulative task status values --- src/frontend/src/constants/index.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/frontend/src/constants/index.ts b/src/frontend/src/constants/index.ts index 561da55c..4d236725 100644 --- a/src/frontend/src/constants/index.ts +++ b/src/frontend/src/constants/index.ts @@ -86,3 +86,10 @@ export const rowsPerPageOptions = [ { label: '24', value: 24 }, { label: '30', value: 30 }, ]; + +export const taskStatusObj = { + request_logs: ['REQUEST_FOR_MAPPING'], + ongoing: ['LOCKED_FOR_MAPPING', 'IMAGE_UPLOADED', 'IMAGE_PROCESSING_FAILED'], + completed: ['IMAGE_PROCESSED'], + unflyable: ['UNFLYABLE_TASK'], +}; From 6b3de0382af1e4884577c015e14c700cf0a373b8 Mon Sep 17 00:00:00 2001 From: Sujit Date: Tue, 22 Oct 2024 13:28:52 +0545 Subject: [PATCH 06/33] feat(dashboard): Show task list as per active status card and show detailed status on status column on list --- .../components/Dashboard/RequestLogs/index.tsx | 7 ++++--- .../components/Dashboard/TaskLogs/index.tsx | 18 +++++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/frontend/src/components/Dashboard/RequestLogs/index.tsx b/src/frontend/src/components/Dashboard/RequestLogs/index.tsx index ccdde506..4322acac 100644 --- a/src/frontend/src/components/Dashboard/RequestLogs/index.tsx +++ b/src/frontend/src/components/Dashboard/RequestLogs/index.tsx @@ -1,19 +1,20 @@ import { useGetTaskListQuery } from '@Api/dashboard'; import { FlexColumn } from '@Components/common/Layouts'; import { Button } from '@Components/RadixComponents/Button'; +import { taskStatusObj } from '@Constants/index'; import { postTaskStatus } from '@Services/project'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import hasErrorBoundary from '@Utils/hasErrorBoundary'; import { toast } from 'react-toastify'; const RequestLogs = () => { + const queryClient = useQueryClient(); const { data: requestedTasks }: any = useGetTaskListQuery({ select: (data: any) => - data?.data?.filter( - (task: Record) => task?.state === 'request logs', + data?.data?.filter((task: Record) => + taskStatusObj.request_logs.includes(task?.state), ), }); - const queryClient = useQueryClient(); const { mutate: respondToRequest } = useMutation({ mutationFn: postTaskStatus, diff --git a/src/frontend/src/components/Dashboard/TaskLogs/index.tsx b/src/frontend/src/components/Dashboard/TaskLogs/index.tsx index 864626af..edbab2b7 100644 --- a/src/frontend/src/components/Dashboard/TaskLogs/index.tsx +++ b/src/frontend/src/components/Dashboard/TaskLogs/index.tsx @@ -1,19 +1,19 @@ import { useMemo } from 'react'; import { useGetTaskListQuery } from '@Api/dashboard'; import hasErrorBoundary from '@Utils/hasErrorBoundary'; +import { taskStatusObj } from '@Constants/index'; import TaskLogsTable from './TaskLogsTable'; interface TaskLogsProps { title: string; } -const getStatusByTitle = (title: string): string => { - if (title === 'Ongoing Tasks') return 'ongoing'; - if (title === 'Request Logs') return 'request logs'; - if (title === 'Unflyable Tasks') return 'unflyable task'; - if (title === 'Completed Tasks') return 'completed'; - - return ''; +const getStatusListByTitle = (title: string): string[] => { + if (title === 'Ongoing Tasks') return taskStatusObj.ongoing; + if (title === 'Request Logs') return taskStatusObj.request_logs; + if (title === 'Unflyable Tasks') return taskStatusObj.unflyable; + if (title === 'Completed Tasks') return taskStatusObj.completed; + return []; }; const TaskLogs = ({ title }: TaskLogsProps) => { @@ -21,8 +21,8 @@ const TaskLogs = ({ title }: TaskLogsProps) => { const filteredData = useMemo( () => - taskList?.filter( - (task: Record) => task?.state === getStatusByTitle(title), + taskList?.filter((task: Record) => + getStatusListByTitle(title)?.includes(task?.state), ), [title, taskList], ); From 65574324d23d39f031ea8416c8124c8cafe89cd6 Mon Sep 17 00:00:00 2001 From: Sujit Date: Tue, 22 Oct 2024 13:35:54 +0545 Subject: [PATCH 07/33] feat(project-details): Replace old Breadcrumb with dynamic one --- .../src/views/IndividualProject/index.tsx | 31 ++++++------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/src/frontend/src/views/IndividualProject/index.tsx b/src/frontend/src/views/IndividualProject/index.tsx index cfe2d170..e1abca72 100644 --- a/src/frontend/src/views/IndividualProject/index.tsx +++ b/src/frontend/src/views/IndividualProject/index.tsx @@ -1,6 +1,7 @@ /* eslint-disable jsx-a11y/interactive-supports-focus */ /* eslint-disable jsx-a11y/click-events-have-key-events */ import { useGetProjectsDetailQuery } from '@Api/projects'; +import BreadCrumb from '@Components/common/Breadcrumb'; import Tab from '@Components/common/Tabs'; import { Contributions, @@ -13,7 +14,7 @@ import { projectOptions } from '@Constants/index'; import { setProjectState } from '@Store/actions/project'; import { useTypedDispatch, useTypedSelector } from '@Store/hooks'; import hasErrorBoundary from '@Utils/hasErrorBoundary'; -import { useNavigate, useParams } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; // function to render the content based on active tab const getActiveTabContent = ( @@ -37,13 +38,12 @@ const getActiveTabContent = ( const IndividualProject = () => { const { id } = useParams(); const dispatch = useTypedDispatch(); - const navigate = useNavigate(); const individualProjectActiveTab = useTypedSelector( state => state.project.individualProjectActiveTab, ); - const { data: projectData, isFetching: isProjectDataFetching } = + const { data: projectData, isFetching: isProjectDataFetching }: any = useGetProjectsDetailQuery(id as string, { onSuccess: (res: any) => { dispatch( @@ -68,25 +68,12 @@ const IndividualProject = () => { return (
- {/* <----------- temporary breadcrumb -----------> */} -
- { - navigate('/projects'); - }} - > - Project / - - - { - // @ts-ignore - projectData?.name || '--' - } - - {/* <----------- temporary breadcrumb -----------> */} -
+
Date: Tue, 22 Oct 2024 13:41:52 +0545 Subject: [PATCH 08/33] feat(project-details): Show Unflyable task on map --- .../src/components/IndividualProject/MapSection/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/components/IndividualProject/MapSection/index.tsx b/src/frontend/src/components/IndividualProject/MapSection/index.tsx index 76ccfaf0..1d69991b 100644 --- a/src/frontend/src/components/IndividualProject/MapSection/index.tsx +++ b/src/frontend/src/components/IndividualProject/MapSection/index.tsx @@ -271,11 +271,11 @@ const MapSection = ({ projectData }: { projectData: Record }) => { 'fill-opacity': 0.5, }, } - : taskStatusObj?.[`${task?.id}`] === 'IMAGE_PROCESSED' + : taskStatusObj?.[`${task?.id}`] === 'UNFLYABLE_TASK' ? { type: 'fill', paint: { - 'fill-color': '#ACD2C4', + 'fill-color': '#9EA5AD', 'fill-outline-color': '#484848', 'fill-opacity': 0.7, }, From 58460c19ec43688778c932cabcdff33f1bac0835 Mon Sep 17 00:00:00 2001 From: Sujit Date: Tue, 22 Oct 2024 13:44:00 +0545 Subject: [PATCH 09/33] feat(main-dashboard): add unit of task area on table column header --- .../src/components/Dashboard/TaskLogs/TaskLogsTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/components/Dashboard/TaskLogs/TaskLogsTable.tsx b/src/frontend/src/components/Dashboard/TaskLogs/TaskLogsTable.tsx index 0095b9d6..d54574f2 100644 --- a/src/frontend/src/components/Dashboard/TaskLogs/TaskLogsTable.tsx +++ b/src/frontend/src/components/Dashboard/TaskLogs/TaskLogsTable.tsx @@ -19,7 +19,7 @@ const TaskLogsTable = ({ data: taskList }: ITaskLogsTableProps) => { Project Name - Total task area + Total task area in kmĀ² {/* Est.flight time From 03bab0b2ea110bc9302a4dad5219ef73ce8afac4 Mon Sep 17 00:00:00 2001 From: Pradip-p Date: Tue, 22 Oct 2024 13:52:43 +0545 Subject: [PATCH 10/33] fix: updating image after image processing... --- src/backend/app/projects/project_routes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend/app/projects/project_routes.py b/src/backend/app/projects/project_routes.py index efca9491..d8532f51 100644 --- a/src/backend/app/projects/project_routes.py +++ b/src/backend/app/projects/project_routes.py @@ -473,7 +473,8 @@ async def odm_webhook( log.info(f"Task ID: {task_id}, Status: going for download......") current_state = await task_logic.get_task_state(db, dtm_project_id, dtm_task_id) - match current_state: + current_state_value = State[current_state.get("state")] + match current_state_value: case State.IMAGE_UPLOADED: log.info( f"Task ID: {task_id}, Status: already IMAGE_UPLOADED - no update needed." From f8d031bf3028890d36659f05643a6ede1159663c Mon Sep 17 00:00:00 2001 From: Sujit Date: Tue, 22 Oct 2024 13:55:04 +0545 Subject: [PATCH 11/33] feat(task-description): remove padding --- src/frontend/src/views/TaskDescription/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/views/TaskDescription/index.tsx b/src/frontend/src/views/TaskDescription/index.tsx index 9dcd94e5..19113860 100644 --- a/src/frontend/src/views/TaskDescription/index.tsx +++ b/src/frontend/src/views/TaskDescription/index.tsx @@ -9,7 +9,7 @@ const TaskDescription = () => { return ( <>
-
+
From 60f852883a86285ea2db152c9e49ea8f2d04ffd5 Mon Sep 17 00:00:00 2001 From: Sujit Date: Tue, 22 Oct 2024 14:15:30 +0545 Subject: [PATCH 12/33] feat(task-description): update breadcrumb --- .../DroneOperatorTask/Header/index.tsx | 38 +++++++++---------- .../src/views/TaskDescription/index.tsx | 2 +- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/frontend/src/components/DroneOperatorTask/Header/index.tsx b/src/frontend/src/components/DroneOperatorTask/Header/index.tsx index 648d91cf..01bf47fb 100644 --- a/src/frontend/src/components/DroneOperatorTask/Header/index.tsx +++ b/src/frontend/src/components/DroneOperatorTask/Header/index.tsx @@ -1,32 +1,30 @@ import { useGetIndividualTaskQuery } from '@Api/tasks'; -import { useNavigate, useParams } from 'react-router-dom'; +import BreadCrumb from '@Components/common/Breadcrumb'; +import { useParams } from 'react-router-dom'; const DroneOperatorTaskHeader = () => { - const navigate = useNavigate(); - const { taskId } = useParams(); + const { taskId, projectId } = useParams(); const { data: taskDescription }: Record = useGetIndividualTaskQuery(taskId as string); return ( <> -
-
-

navigate('/projects')} - > - Projects -

-

- / -

-

- {taskDescription?.project_name || '-'} -

-
-
+ 8 ? '...' : ''}` || + '--', + navLink: `/projects/${projectId}`, + }, + { + name: `#${taskDescription?.project_task_index}` || '--', + navLink: '', + }, + ]} + /> ); }; diff --git a/src/frontend/src/views/TaskDescription/index.tsx b/src/frontend/src/views/TaskDescription/index.tsx index 19113860..6d2d68d1 100644 --- a/src/frontend/src/views/TaskDescription/index.tsx +++ b/src/frontend/src/views/TaskDescription/index.tsx @@ -10,7 +10,7 @@ const TaskDescription = () => { <>
-
+
From 4aecf7d7b976e2f336a681eb07434a326c4ffac7 Mon Sep 17 00:00:00 2001 From: Pradip-p Date: Tue, 22 Oct 2024 16:20:12 +0545 Subject: [PATCH 13/33] fix: comment the task restrictions --- src/backend/app/tasks/task_routes.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/backend/app/tasks/task_routes.py b/src/backend/app/tasks/task_routes.py index fedfb3ad..a031e4ae 100644 --- a/src/backend/app/tasks/task_routes.py +++ b/src/backend/app/tasks/task_routes.py @@ -408,15 +408,15 @@ async def new_event( state = current_task_state.get("state") locked_user_id = current_task_state.get("user_id") - # Determine error conditions - if ( - state != State.LOCKED_FOR_MAPPING.name - or State.IMAGE_PROCESSING_FAILED.name - ): - raise HTTPException( - status_code=400, - detail="Task state does not match expected state for image upload.", - ) + # # Determine error conditions + # if ( + # state != State.LOCKED_FOR_MAPPING.name + # or State.IMAGE_PROCESSING_FAILED.name + # ): + # raise HTTPException( + # status_code=400, + # detail="Task state does not match expected state for image upload.", + # ) if user_id != locked_user_id: raise HTTPException( status_code=403, From 2d0a82284af8a8c4555b07030030ba173b897916 Mon Sep 17 00:00:00 2001 From: Sujit Date: Tue, 22 Oct 2024 16:41:27 +0545 Subject: [PATCH 14/33] feat(task-description): remove unnecessary animation code --- .../DescriptionSection/index.tsx | 58 ++----------------- 1 file changed, 4 insertions(+), 54 deletions(-) diff --git a/src/frontend/src/components/DroneOperatorTask/DescriptionSection/index.tsx b/src/frontend/src/components/DroneOperatorTask/DescriptionSection/index.tsx index 8eca20cd..a0aff2a3 100644 --- a/src/frontend/src/components/DroneOperatorTask/DescriptionSection/index.tsx +++ b/src/frontend/src/components/DroneOperatorTask/DescriptionSection/index.tsx @@ -2,23 +2,17 @@ import { useGetIndividualTaskQuery, useGetTaskWaypointQuery } from '@Api/tasks'; import { Button } from '@Components/RadixComponents/Button'; import useWindowDimensions from '@Hooks/useWindowDimensions'; -import { useTypedSelector } from '@Store/hooks'; import hasErrorBoundary from '@Utils/hasErrorBoundary'; -import { motion } from 'framer-motion'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { useParams } from 'react-router-dom'; import { toast } from 'react-toastify'; import MapSection from '../MapSection'; import DescriptionBox from './DescriptionBox'; -import UploadsBox from './UploadsBox'; const { BASE_URL } = process.env; const DroneOperatorDescriptionBox = () => { const { taskId, projectId } = useParams(); - const secondPageStates = useTypedSelector(state => state.droneOperatorTask); - const { secondPageState, secondPage } = secondPageStates; - const [animated, setAnimated] = useState(false); const [showDownloadOptions, setShowDownloadOptions] = useState(false); const { width } = useWindowDimensions(); @@ -37,52 +31,6 @@ const DroneOperatorDescriptionBox = () => { }, ); - useEffect(() => { - setAnimated(true); - setTimeout(() => { - setAnimated(false); - }, 100); - }, [secondPageState, secondPage]); - const variants = { - open: { opacity: 1, y: 0 }, - closed: { opacity: 0, y: '50%' }, - }; - - const renderComponent = (role: string) => { - switch (role) { - case 'description': - return ( - - - - ); - case 'uploads': - return ( - - - - ); - default: - return ( - - - - ); - } - }; - const downloadFlightPlanKmz = () => { fetch( `${BASE_URL}/waypoint/task/${taskId}/?project_id=${projectId}&download=true`, @@ -256,7 +204,9 @@ const DroneOperatorDescriptionBox = () => {
{width < 640 && } - {renderComponent(secondPageState)} +
+ +
); From b099919869cabf99899d1f9c81849cc325176b50 Mon Sep 17 00:00:00 2001 From: Sujit Date: Tue, 22 Oct 2024 16:42:04 +0545 Subject: [PATCH 15/33] feat(task-description): show image processing status show re-upload and re-run `processing button in case of failure --- .../DescriptionBox/index.tsx | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/frontend/src/components/DroneOperatorTask/DescriptionSection/DescriptionBox/index.tsx b/src/frontend/src/components/DroneOperatorTask/DescriptionSection/DescriptionBox/index.tsx index c6c20130..bb1aa78a 100644 --- a/src/frontend/src/components/DroneOperatorTask/DescriptionSection/DescriptionBox/index.tsx +++ b/src/frontend/src/components/DroneOperatorTask/DescriptionSection/DescriptionBox/index.tsx @@ -10,6 +10,7 @@ import { Button } from '@Components/RadixComponents/Button'; import DescriptionBoxComponent from './DescriptionComponent'; import QuestionBox from '../QuestionBox'; import UploadsInformation from '../UploadsInformation'; +import UploadsBox from '../UploadsBox'; const DescriptionBox = () => { const [flyable, setFlyable] = useState('yes'); @@ -170,8 +171,10 @@ const DescriptionBox = () => { name: 'Orthophoto available', value: taskAssetsInformation?.assets_url ? 'Yes' : 'No', }, + { name: 'Image Status', value: taskAssetsInformation?.state }, ]} /> + {taskAssetsInformation?.assets_url && (
)} + {taskAssetsInformation?.state === 'IMAGE_PROCESSING_FAILED' && ( +
+ +
+ )} + {taskAssetsInformation?.state === 'IMAGE_PROCESSING_FAILED' && ( +
+ +
+ )}
)} From d8ce4fe434e87c94e3d14de3a63f7d2685d33708 Mon Sep 17 00:00:00 2001 From: Sujit Date: Tue, 22 Oct 2024 16:45:27 +0545 Subject: [PATCH 16/33] feat: upload image component UI and functionality --- .../DescriptionSection/UploadsBox/index.tsx | 41 ++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/frontend/src/components/DroneOperatorTask/DescriptionSection/UploadsBox/index.tsx b/src/frontend/src/components/DroneOperatorTask/DescriptionSection/UploadsBox/index.tsx index 4df0ea89..c88c0ae0 100644 --- a/src/frontend/src/components/DroneOperatorTask/DescriptionSection/UploadsBox/index.tsx +++ b/src/frontend/src/components/DroneOperatorTask/DescriptionSection/UploadsBox/index.tsx @@ -6,7 +6,7 @@ import { toggleModal } from '@Store/actions/common'; import { setFiles } from '@Store/actions/droneOperatorTask'; import { useTypedDispatch, useTypedSelector } from '@Store/hooks'; -const UploadsBox = () => { +const UploadsBox = ({ label = 'Upload Raw Image' }: { label?: string }) => { const dispatch = useTypedDispatch(); const files = useTypedSelector(state => state.droneOperatorTask.files); const handleFileChange = (event: any) => { @@ -22,17 +22,38 @@ const UploadsBox = () => {

- Upload Raw Image + {label}

- + + {files.length > 0 && (
)} - {taskAssetsInformation?.state === 'IMAGE_PROCESSING_FAILED' && ( -
- -
- )} + {/* {taskAssetsInformation?.state === 'IMAGE_PROCESSING_FAILED' && ( */} +
+ +
+ {/* )} */} {taskAssetsInformation?.state === 'IMAGE_PROCESSING_FAILED' && (
From 3d9b278f6b86f4bc66ad8f7c61cd2323693181b8 Mon Sep 17 00:00:00 2001 From: Sujit Date: Tue, 22 Oct 2024 17:00:16 +0545 Subject: [PATCH 20/33] fix(task-description): make rerun button visible only on imnage process failure --- .../DescriptionBox/index.tsx | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/frontend/src/components/DroneOperatorTask/DescriptionSection/DescriptionBox/index.tsx b/src/frontend/src/components/DroneOperatorTask/DescriptionSection/DescriptionBox/index.tsx index c20e3268..ef2f4157 100644 --- a/src/frontend/src/components/DroneOperatorTask/DescriptionSection/DescriptionBox/index.tsx +++ b/src/frontend/src/components/DroneOperatorTask/DescriptionSection/DescriptionBox/index.tsx @@ -197,19 +197,19 @@ const DescriptionBox = () => {
)} - {/* {taskAssetsInformation?.state === 'IMAGE_PROCESSING_FAILED' && ( */} -
- -
- {/* )} */} + {taskAssetsInformation?.state === 'IMAGE_PROCESSING_FAILED' && ( +
+ +
+ )} {taskAssetsInformation?.state === 'IMAGE_PROCESSING_FAILED' && (
From 4f154d82665f3da8965806ad821b04f37bba7d76 Mon Sep 17 00:00:00 2001 From: Sujit Date: Tue, 22 Oct 2024 17:00:16 +0545 Subject: [PATCH 21/33] fix(task-description): make rerun button visible only on image process failure --- .../DescriptionBox/index.tsx | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/frontend/src/components/DroneOperatorTask/DescriptionSection/DescriptionBox/index.tsx b/src/frontend/src/components/DroneOperatorTask/DescriptionSection/DescriptionBox/index.tsx index c20e3268..ef2f4157 100644 --- a/src/frontend/src/components/DroneOperatorTask/DescriptionSection/DescriptionBox/index.tsx +++ b/src/frontend/src/components/DroneOperatorTask/DescriptionSection/DescriptionBox/index.tsx @@ -197,19 +197,19 @@ const DescriptionBox = () => {
)} - {/* {taskAssetsInformation?.state === 'IMAGE_PROCESSING_FAILED' && ( */} -
- -
- {/* )} */} + {taskAssetsInformation?.state === 'IMAGE_PROCESSING_FAILED' && ( +
+ +
+ )} {taskAssetsInformation?.state === 'IMAGE_PROCESSING_FAILED' && (
From d72a76f543f091f752538fc615c82e2d1fcee9b8 Mon Sep 17 00:00:00 2001 From: Sujit Date: Tue, 22 Oct 2024 17:24:28 +0545 Subject: [PATCH 22/33] feat: create utill function to format the string with the help of regex remove underscore make first character uppercase and rest of the character lowercase --- src/frontend/src/utils/index.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/frontend/src/utils/index.ts b/src/frontend/src/utils/index.ts index a413c1a8..b55fc37a 100644 --- a/src/frontend/src/utils/index.ts +++ b/src/frontend/src/utils/index.ts @@ -76,3 +76,12 @@ export const getFrontOverlap = (agl: number, forwardSpacing: number) => { const frontOverlap = (frontOverlapDistance * 100) / frontPhotoHeight; return frontOverlap.toFixed(2); }; + +// remove underscore and capitalize the word +export const formatString = (value: string) => { + if (!value) return ''; + return value + .replace(/_/g, ' ') + .toLowerCase() + .replace(/^\w/, char => char.toUpperCase()); +}; From 2ba818646c8506dc91373d9fd2afe438e1076a7e Mon Sep 17 00:00:00 2001 From: Sujit Date: Tue, 22 Oct 2024 17:26:50 +0545 Subject: [PATCH 23/33] feat: convert the task state into readable text --- .../src/components/Dashboard/TaskLogs/TaskLogsTable.tsx | 3 ++- .../DescriptionSection/DescriptionBox/index.tsx | 6 +++++- .../IndividualProject/Contributions/TableSection/index.tsx | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/frontend/src/components/Dashboard/TaskLogs/TaskLogsTable.tsx b/src/frontend/src/components/Dashboard/TaskLogs/TaskLogsTable.tsx index d54574f2..a46a31f2 100644 --- a/src/frontend/src/components/Dashboard/TaskLogs/TaskLogsTable.tsx +++ b/src/frontend/src/components/Dashboard/TaskLogs/TaskLogsTable.tsx @@ -1,3 +1,4 @@ +import { formatString } from '@Utils/index'; import { format } from 'date-fns'; import { useNavigate } from 'react-router-dom'; @@ -47,7 +48,7 @@ const TaskLogsTable = ({ data: taskList }: ITaskLogsTableProps) => { {format(new Date(task.created_at), 'yyyy-MM-dd')} - {task.state} + {formatString(task.state)}
{ name: 'Orthophoto available', value: taskAssetsInformation?.assets_url ? 'Yes' : 'No', }, - { name: 'Image Status', value: taskAssetsInformation?.state }, + { + name: 'Image Status', + value: formatString(taskAssetsInformation?.state), + }, ]} /> diff --git a/src/frontend/src/components/IndividualProject/Contributions/TableSection/index.tsx b/src/frontend/src/components/IndividualProject/Contributions/TableSection/index.tsx index f0834341..9f09fb73 100644 --- a/src/frontend/src/components/IndividualProject/Contributions/TableSection/index.tsx +++ b/src/frontend/src/components/IndividualProject/Contributions/TableSection/index.tsx @@ -2,6 +2,7 @@ import { useGetAllAssetsUrlQuery } from '@Api/projects'; import DataTable from '@Components/common/DataTable'; import Icon from '@Components/common/Icon'; import { useTypedSelector } from '@Store/hooks'; +import { formatString } from '@Utils/index'; import { useMemo } from 'react'; import { useParams } from 'react-router-dom'; import { toast } from 'react-toastify'; @@ -79,7 +80,7 @@ export default function TableSection({ isFetching }: { isFetching: boolean }) { { user: curr?.name || '-', task_mapped: `Task# ${curr?.project_task_index}`, - task_state: curr?.state, + task_state: formatString(curr?.state), assets_url: selectedAssetsDetails?.assets_url, image_count: selectedAssetsDetails?.image_count, }, From 408b88a5216719cf3af15cf7c9b9d0963d9cf5b2 Mon Sep 17 00:00:00 2001 From: Pradip-p Date: Sat, 26 Oct 2024 13:08:57 +0545 Subject: [PATCH 24/33] fix: update state of task after image processes --- src/backend/app/projects/image_processing.py | 25 +++++++++++--------- src/backend/app/projects/project_routes.py | 3 --- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/backend/app/projects/image_processing.py b/src/backend/app/projects/image_processing.py index 018b05dc..9f4c3d0b 100644 --- a/src/backend/app/projects/image_processing.py +++ b/src/backend/app/projects/image_processing.py @@ -6,6 +6,7 @@ from app.tasks import task_logic from app.models.enums import State from app.utils import timestamp +from app.db import database from pyodm import Node from app.s3 import get_file_from_bucket, list_objects_from_bucket, add_file_to_bucket from loguru import logger as log @@ -193,7 +194,6 @@ def process_images_from_s3(self, bucket_name, name=None, options=[], webhook=Non async def download_and_upload_assets_from_odm_to_s3( - db: Connection, node_odm_url: str, task_id: str, dtm_project_id: uuid.UUID, @@ -236,16 +236,19 @@ async def download_and_upload_assets_from_odm_to_s3( log.info(f"Assets for task {task_id} successfully uploaded to S3.") # Update background task status to COMPLETED - await task_logic.update_task_state( - db, - dtm_project_id, - dtm_task_id, - user_id, - comment, - current_state, - State.IMAGE_PROCESSED, - timestamp(), - ) + pool = await database.get_db_connection_pool() + + async with pool.connection() as conn: + await task_logic.update_task_state( + db=conn, + project_id=dtm_project_id, + task_id=dtm_task_id, + user_id=user_id, + comment="Task completed.", + initial_state=current_state, + final_state=State.IMAGE_PROCESSED, + updated_at=timestamp(), + ) except Exception as e: log.error(f"Error downloading or uploading assets for task {task_id}: {e}") diff --git a/src/backend/app/projects/project_routes.py b/src/backend/app/projects/project_routes.py index d8532f51..ec3e8bb2 100644 --- a/src/backend/app/projects/project_routes.py +++ b/src/backend/app/projects/project_routes.py @@ -482,7 +482,6 @@ async def odm_webhook( # Call function to download assets from ODM and upload to S3 background_tasks.add_task( image_processing.download_and_upload_assets_from_odm_to_s3, - db, settings.NODE_ODM_URL, task_id, dtm_project_id, @@ -499,7 +498,6 @@ async def odm_webhook( # Call function to download assets from ODM and upload to S3 background_tasks.add_task( image_processing.download_and_upload_assets_from_odm_to_s3, - db, settings.NODE_ODM_URL, task_id, dtm_project_id, @@ -531,7 +529,6 @@ async def odm_webhook( background_tasks.add_task( image_processing.download_and_upload_assets_from_odm_to_s3, - db, settings.NODE_ODM_URL, task_id, dtm_project_id, From f3145aa7e65e703c266b90ca74cba4ee9640b1aa Mon Sep 17 00:00:00 2001 From: Pradip-p Date: Mon, 28 Oct 2024 11:02:23 +0545 Subject: [PATCH 25/33] feat: re-upload the images from pre-signed urls --- src/backend/app/projects/project_routes.py | 57 ++++++++++++++++++---- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/src/backend/app/projects/project_routes.py b/src/backend/app/projects/project_routes.py index 44c378cd..b2ec8316 100644 --- a/src/backend/app/projects/project_routes.py +++ b/src/backend/app/projects/project_routes.py @@ -34,6 +34,7 @@ from app.tasks import task_schemas from app.utils import geojson_to_kml, timestamp from app.users import user_schemas +from minio.deleteobjects import DeleteObject router = APIRouter( @@ -287,8 +288,9 @@ async def preview_split_by_square( @router.post("/generate-presigned-url/", tags=["Image Upload"]) async def generate_presigned_url( - data: project_schemas.PresignedUrlRequest, user: Annotated[AuthUser, Depends(login_required)], + data: project_schemas.PresignedUrlRequest, + replace_existing: bool = False, ): """ Generate a pre-signed URL for uploading an image to S3 Bucket. @@ -297,21 +299,57 @@ async def generate_presigned_url( an S3 bucket. The URL expires after a specified duration. Args: - - image_name: The name of the image you want to upload - expiry : Expiry time in hours + image_name: The name of the image(s) you want to upload. + expiry : Expiry time in hours. + replace_existing: A boolean flag to indicate if the image should be replaced. Returns: - - str: The pre-signed URL to upload the image + list: A list of dictionaries with the image name and the pre-signed URL to upload. """ try: - # Generate a pre-signed URL for an object + # Initialize the S3 client client = s3_client() urls = [] - for image in data.image_name: - image_path = f"projects/{data.project_id}/{data.task_id}/images/{image}" + # Process each image in the request + for image in data.image_name: + # Construct the image path + image_path = ( + f"projects/{data.project_id}/{data.task_id}/images/" + if replace_existing + else f"projects/{data.project_id}/{data.task_id}/images/{image}" + ) + # If replace_existing is True, delete the image first + if replace_existing: + try: + # Prepare the list of objects to delete (recursively if necessary) + delete_object_list = map( + lambda x: DeleteObject(x.object_name), + client.list_objects( + settings.S3_BUCKET_NAME, image_path, recursive=True + ), + ) + + # Remove the objects (images) + errors = client.remove_objects( + settings.S3_BUCKET_NAME, delete_object_list + ) + + # Handle deletion errors, if any + for error in errors: + log.error("Error occurred when deleting object", error) + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail=f"Failed to delete existing image: {error}", + ) + + except Exception as e: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail=f"Failed to delete existing image. {e}", + ) + + # Generate a new pre-signed URL for the image upload url = client.get_presigned_url( "PUT", settings.S3_BUCKET_NAME, @@ -321,6 +359,7 @@ async def generate_presigned_url( urls.append({"image_name": image, "url": url}) return urls + except Exception as e: raise HTTPException( status_code=HTTPStatus.BAD_REQUEST, From 6e2ae417fe674f4e7b46d17a3d103d7e4b402696 Mon Sep 17 00:00:00 2001 From: Sujit Date: Mon, 28 Oct 2024 11:08:15 +0545 Subject: [PATCH 26/33] feat(individual-project): show image-processed/task-completed on map --- .../IndividualProject/MapSection/index.tsx | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/frontend/src/components/IndividualProject/MapSection/index.tsx b/src/frontend/src/components/IndividualProject/MapSection/index.tsx index 1d69991b..dc6a7591 100644 --- a/src/frontend/src/components/IndividualProject/MapSection/index.tsx +++ b/src/frontend/src/components/IndividualProject/MapSection/index.tsx @@ -262,32 +262,41 @@ const MapSection = ({ projectData }: { projectData: Record }) => { 'fill-opacity': 0.5, }, } - : taskStatusObj?.[`${task?.id}`] === 'IMAGE_UPLOADED' + : taskStatusObj?.[`${task?.id}`] === 'IMAGE_PROCESSED' ? { type: 'fill', paint: { - 'fill-color': '#9C77B2', + 'fill-color': '#ACD2C4', 'fill-outline-color': '#484848', - 'fill-opacity': 0.5, + 'fill-opacity': 0.7, }, } - : taskStatusObj?.[`${task?.id}`] === 'UNFLYABLE_TASK' + : taskStatusObj?.[`${task?.id}`] === 'IMAGE_UPLOADED' ? { type: 'fill', paint: { - 'fill-color': '#9EA5AD', - 'fill-outline-color': '#484848', - 'fill-opacity': 0.7, - }, - } - : { - type: 'fill', - paint: { - 'fill-color': '#ffffff', + 'fill-color': '#9C77B2', 'fill-outline-color': '#484848', 'fill-opacity': 0.5, }, } + : taskStatusObj?.[`${task?.id}`] === 'UNFLYABLE_TASK' + ? { + type: 'fill', + paint: { + 'fill-color': '#9EA5AD', + 'fill-outline-color': '#484848', + 'fill-opacity': 0.7, + }, + } + : { + type: 'fill', + paint: { + 'fill-color': '#ffffff', + 'fill-outline-color': '#484848', + 'fill-opacity': 0.5, + }, + } } hasImage={ taskStatusObj?.[`${task?.id}`] === 'LOCKED_FOR_MAPPING' || false From f125093ce483fd9e42aafa388990707577f09cb3 Mon Sep 17 00:00:00 2001 From: Pradip-p Date: Mon, 28 Oct 2024 11:53:47 +0545 Subject: [PATCH 27/33] feat: handle state event for image re-upload --- src/backend/app/projects/project_routes.py | 16 +++++++++++++ src/backend/app/tasks/task_routes.py | 26 +++++++++++++--------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/backend/app/projects/project_routes.py b/src/backend/app/projects/project_routes.py index b2ec8316..b05c9a4b 100644 --- a/src/backend/app/projects/project_routes.py +++ b/src/backend/app/projects/project_routes.py @@ -343,6 +343,22 @@ async def generate_presigned_url( detail=f"Failed to delete existing image: {error}", ) + # # Update task as images uploaded + # pool = await database.get_db_connection_pool() + # current_task_state = await task_logic.get_task_state( + # conn, data.project_id, data.task_id + # ) + # async with pool.connection() as conn: + # await task_logic.update_task_state( + # conn, + # data.project_id, + # data.task_id, + # data.user_id, + # "Image re-upload", + # current_task_state, + # final_state=State.IMAGE_UPLOADED, + # updated_at=timestamp(), + # ) except Exception as e: raise HTTPException( status_code=HTTPStatus.BAD_REQUEST, diff --git a/src/backend/app/tasks/task_routes.py b/src/backend/app/tasks/task_routes.py index a031e4ae..c4cdd2b4 100644 --- a/src/backend/app/tasks/task_routes.py +++ b/src/backend/app/tasks/task_routes.py @@ -405,18 +405,24 @@ async def new_event( current_task_state = await task_logic.get_task_state( db, project_id, task_id ) + if not current_task_state: + raise HTTPException( + status_code=400, detail="Task is not ready for image upload." + ) state = current_task_state.get("state") locked_user_id = current_task_state.get("user_id") - # # Determine error conditions - # if ( - # state != State.LOCKED_FOR_MAPPING.name - # or State.IMAGE_PROCESSING_FAILED.name - # ): - # raise HTTPException( - # status_code=400, - # detail="Task state does not match expected state for image upload.", - # ) + # Determine error conditions: Current State must be IMAGE_UPLOADED or IMAGE_PROCESSING_FAILED or lokec for mapping. + if state not in ( + State.IMAGE_UPLOADED.name, + State.IMAGE_PROCESSING_FAILED.name, + State.LOCKED_FOR_MAPPING.name, + ): + raise HTTPException( + status_code=400, + detail="Task state does not match expected state for image upload.", + ) + if user_id != locked_user_id: raise HTTPException( status_code=403, @@ -429,7 +435,7 @@ async def new_event( task_id, user_id, f"Task image uploaded by user {user_data.name}.", - State.LOCKED_FOR_MAPPING, + State[state], State.IMAGE_UPLOADED, detail.updated_at, ) From 800bf61a80da822386d859e3ce00bb102e2de781 Mon Sep 17 00:00:00 2001 From: Sujit Date: Mon, 28 Oct 2024 12:12:50 +0545 Subject: [PATCH 28/33] feat(project-description): add `image processing failed` state of map task on map --- .../IndividualProject/MapSection/Legend.tsx | 6 ++- .../IndividualProject/MapSection/index.tsx | 37 +++++++++++++------ 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/frontend/src/components/IndividualProject/MapSection/Legend.tsx b/src/frontend/src/components/IndividualProject/MapSection/Legend.tsx index 21cc42fb..f3b3417e 100644 --- a/src/frontend/src/components/IndividualProject/MapSection/Legend.tsx +++ b/src/frontend/src/components/IndividualProject/MapSection/Legend.tsx @@ -5,7 +5,7 @@ import { useState } from 'react'; const Legend = () => { const [showLegendItems, setShowLegendItems] = useState(true); return ( -
+
Legend
{
Image Processing
+
+
+
Image Processing Failed
+
Requested Tasks
diff --git a/src/frontend/src/components/IndividualProject/MapSection/index.tsx b/src/frontend/src/components/IndividualProject/MapSection/index.tsx index dc6a7591..52cb242c 100644 --- a/src/frontend/src/components/IndividualProject/MapSection/index.tsx +++ b/src/frontend/src/components/IndividualProject/MapSection/index.tsx @@ -143,7 +143,8 @@ const MapSection = ({ projectData }: { projectData: Record }) => { return `This task's Images has been uploaded ${properties.locked_user_name ? `by ${userDetails?.id === properties?.locked_user_id ? 'you' : properties?.locked_user_name}` : ''}`; case 'IMAGE_PROCESSED': return `This task is completed ${properties.locked_user_name ? `by ${userDetails?.id === properties?.locked_user_id ? 'you' : properties?.locked_user_name}` : ''}`; - + case 'IMAGE_PROCESSING_FAILED': + return `This task's image processing is failed started ${properties.locked_user_name ? `by ${userDetails?.id === properties?.locked_user_id ? 'you' : properties?.locked_user_name}` : ''}`; default: return ''; } @@ -280,23 +281,34 @@ const MapSection = ({ projectData }: { projectData: Record }) => { 'fill-opacity': 0.5, }, } - : taskStatusObj?.[`${task?.id}`] === 'UNFLYABLE_TASK' + : taskStatusObj?.[`${task?.id}`] === + 'IMAGE_PROCESSING_FAILED' ? { type: 'fill', paint: { - 'fill-color': '#9EA5AD', - 'fill-outline-color': '#484848', - 'fill-opacity': 0.7, - }, - } - : { - type: 'fill', - paint: { - 'fill-color': '#ffffff', + 'fill-color': '#f00000', 'fill-outline-color': '#484848', 'fill-opacity': 0.5, }, } + : taskStatusObj?.[`${task?.id}`] === + 'UNFLYABLE_TASK' + ? { + type: 'fill', + paint: { + 'fill-color': '#9EA5AD', + 'fill-outline-color': '#484848', + 'fill-opacity': 0.7, + }, + } + : { + type: 'fill', + paint: { + 'fill-color': '#ffffff', + 'fill-outline-color': '#484848', + 'fill-opacity': 0.5, + }, + } } hasImage={ taskStatusObj?.[`${task?.id}`] === 'LOCKED_FOR_MAPPING' || false @@ -327,7 +339,8 @@ const MapSection = ({ projectData }: { projectData: Record }) => { (taskStatusObj?.[selectedTaskId] === 'LOCKED_FOR_MAPPING' && lockedUser?.id === userDetails?.id) || taskStatusObj?.[selectedTaskId] === 'IMAGE_UPLOADED' || - taskStatusObj?.[selectedTaskId] === 'IMAGE_PROCESSED' + taskStatusObj?.[selectedTaskId] === 'IMAGE_PROCESSED' || + taskStatusObj?.[selectedTaskId] === 'IMAGE_PROCESSING_FAILED' ) } buttonText={ From d313f067d66cccbead1aa77e7ebc78899cca80eb Mon Sep 17 00:00:00 2001 From: Sujit Date: Tue, 29 Oct 2024 09:38:24 +0545 Subject: [PATCH 29/33] feat(task-description): save uploading image (replace/add) on redux store --- src/frontend/src/store/actions/droneOperatorTask.ts | 1 + src/frontend/src/store/slices/droneOperartorTask.ts | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/frontend/src/store/actions/droneOperatorTask.ts b/src/frontend/src/store/actions/droneOperatorTask.ts index 19e24a36..eb6d031b 100644 --- a/src/frontend/src/store/actions/droneOperatorTask.ts +++ b/src/frontend/src/store/actions/droneOperatorTask.ts @@ -12,4 +12,5 @@ export const { setFiles, setSelectedTakeOffPointOption, setSelectedTakeOffPoint, + setUploadedImagesType, } = droneOperatorTaskSlice.actions; diff --git a/src/frontend/src/store/slices/droneOperartorTask.ts b/src/frontend/src/store/slices/droneOperartorTask.ts index 85f33d4a..5f942271 100644 --- a/src/frontend/src/store/slices/droneOperartorTask.ts +++ b/src/frontend/src/store/slices/droneOperartorTask.ts @@ -10,6 +10,7 @@ export interface IDroneOperatorTaskState { files: any[]; selectedTakeOffPointOption: string; selectedTakeOffPoint: any[] | string | null; + uploadedImagesType: 'add' | 'replace'; } const initialState: IDroneOperatorTaskState = { @@ -21,6 +22,7 @@ const initialState: IDroneOperatorTaskState = { files: [], selectedTakeOffPointOption: 'current_location', selectedTakeOffPoint: null, + uploadedImagesType: 'add', }; export const droneOperatorTaskSlice = createSlice({ @@ -65,6 +67,10 @@ export const droneOperatorTaskSlice = createSlice({ setSelectedTakeOffPoint: (state, action) => { state.selectedTakeOffPoint = action.payload; }, + + setUploadedImagesType: (state, action) => { + state.uploadedImagesType = action.payload; + }, }, }); From b6be4e77dd1f41a64b492056016020e8351fa593 Mon Sep 17 00:00:00 2001 From: Sujit Date: Tue, 29 Oct 2024 09:39:45 +0545 Subject: [PATCH 30/33] feat(task-description): add options to choose want to replace the image or add on existing imags while uploading images --- .../DescriptionBox/index.tsx | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/components/DroneOperatorTask/DescriptionSection/DescriptionBox/index.tsx b/src/frontend/src/components/DroneOperatorTask/DescriptionSection/DescriptionBox/index.tsx index 8b357d3a..f2dc2eba 100644 --- a/src/frontend/src/components/DroneOperatorTask/DescriptionSection/DescriptionBox/index.tsx +++ b/src/frontend/src/components/DroneOperatorTask/DescriptionSection/DescriptionBox/index.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; import { useParams } from 'react-router-dom'; +import { useDispatch } from 'react-redux'; import { toast } from 'react-toastify'; import { useGetIndividualTaskQuery, @@ -10,14 +11,22 @@ import { useMutation } from '@tanstack/react-query'; import { postProcessImagery } from '@Services/tasks'; import { formatString } from '@Utils/index'; import { Button } from '@Components/RadixComponents/Button'; +import { Label } from '@Components/common/FormUI'; +import SwitchTab from '@Components/common/SwitchTab'; +import { setUploadedImagesType } from '@Store/actions/droneOperatorTask'; +import { useTypedSelector } from '@Store/hooks'; import DescriptionBoxComponent from './DescriptionComponent'; import QuestionBox from '../QuestionBox'; import UploadsInformation from '../UploadsInformation'; import UploadsBox from '../UploadsBox'; const DescriptionBox = () => { + const dispatch = useDispatch(); const [flyable, setFlyable] = useState('yes'); const { taskId, projectId } = useParams(); + const uploadedImageType = useTypedSelector( + state => state.droneOperatorTask.uploadedImagesType, + ); const { data: taskWayPoints }: any = useGetTaskWaypointQuery( projectId as string, @@ -215,8 +224,39 @@ const DescriptionBox = () => {
)} {taskAssetsInformation?.state === 'IMAGE_PROCESSING_FAILED' && ( -
- +
+ + ) => { + dispatch(setUploadedImagesType(selected.value)); + }} + /> +

+ Note:{' '} + {uploadedImageType === 'add' + ? 'Uploaded images will be added with the existing images.' + : 'Uploaded images will be replaced with all the existing images and starts processing.'} +

+
)}
From 0716d370dd562980552fd934454a6ff9eb6c9124 Mon Sep 17 00:00:00 2001 From: Sujit Date: Tue, 29 Oct 2024 09:41:41 +0545 Subject: [PATCH 31/33] feat(task-description): get presigned url as per the type(replace the images/ add to existing images) --- .../PopoverBox/ImageBox/index.tsx | 9 +++++++-- src/frontend/src/services/droneOperator.ts | 14 +++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/frontend/src/components/DroneOperatorTask/DescriptionSection/PopoverBox/ImageBox/index.tsx b/src/frontend/src/components/DroneOperatorTask/DescriptionSection/PopoverBox/ImageBox/index.tsx index 863f0eca..789b3f58 100644 --- a/src/frontend/src/components/DroneOperatorTask/DescriptionSection/PopoverBox/ImageBox/index.tsx +++ b/src/frontend/src/components/DroneOperatorTask/DescriptionSection/PopoverBox/ImageBox/index.tsx @@ -23,7 +23,6 @@ import PreviewImage from './PreviewImage'; const ImageBoxPopOver = () => { const dispatch = useTypedDispatch(); - const pathname = window.location.pathname?.split('/'); const projectId = pathname?.[2]; const taskId = pathname?.[4]; @@ -41,6 +40,9 @@ const ImageBoxPopOver = () => { const checkedImages = useTypedSelector( state => state.droneOperatorTask.checkedImages, ); + const uploadedImageType = useTypedSelector( + state => state.droneOperatorTask.uploadedImagesType, + ); const { mutate: updateStatus } = useMutation({ mutationFn: postTaskStatus, @@ -64,7 +66,10 @@ const ImageBoxPopOver = () => { // function that gets the signed urls for the images and again puts them in chunks of 4 const { mutate } = useMutation({ mutationFn: async (data: any) => { - const urlsData = await getImageUploadLink(data); + const urlsData = await getImageUploadLink( + uploadedImageType === 'replace', + data, + ); // urls fromm array of objects is retrieved and stored in value const urls = urlsData.data.map((url: any) => url.url); diff --git a/src/frontend/src/services/droneOperator.ts b/src/frontend/src/services/droneOperator.ts index fb4f0a32..fae23836 100644 --- a/src/frontend/src/services/droneOperator.ts +++ b/src/frontend/src/services/droneOperator.ts @@ -16,9 +16,13 @@ export const postUnflyableComment = ({ }, }); -export const getImageUploadLink = (data: any) => - authenticated(api).post(`/projects/generate-presigned-url/`, data, { - headers: { - 'Content-Type': 'application/json', +export const getImageUploadLink = (replaceExistingImages: boolean, data: any) => + authenticated(api).post( + `/projects/generate-presigned-url/?replace_existing=${replaceExistingImages}`, + data, + { + headers: { + 'Content-Type': 'application/json', + }, }, - }); + ); From fcc2a20ce8faca38581d22893232c2ef738496be Mon Sep 17 00:00:00 2001 From: Sujit Date: Tue, 29 Oct 2024 09:53:41 +0545 Subject: [PATCH 32/33] fix: update status text on formating `Image Processed` to `Completed` --- src/frontend/src/utils/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/frontend/src/utils/index.ts b/src/frontend/src/utils/index.ts index b55fc37a..82620b4a 100644 --- a/src/frontend/src/utils/index.ts +++ b/src/frontend/src/utils/index.ts @@ -80,6 +80,7 @@ export const getFrontOverlap = (agl: number, forwardSpacing: number) => { // remove underscore and capitalize the word export const formatString = (value: string) => { if (!value) return ''; + if (value === 'IMAGE_PROCESSED') return 'Completed'; return value .replace(/_/g, ' ') .toLowerCase() From 0ca557d6e1a962547fbb0e48e79b4371132f52e4 Mon Sep 17 00:00:00 2001 From: Pradip-p Date: Tue, 29 Oct 2024 10:12:49 +0545 Subject: [PATCH 33/33] fix: issues slove on updated imgaes delete on s3 buckets --- src/backend/app/projects/project_routes.py | 27 ++++------------------ 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/src/backend/app/projects/project_routes.py b/src/backend/app/projects/project_routes.py index b05c9a4b..eddc5c22 100644 --- a/src/backend/app/projects/project_routes.py +++ b/src/backend/app/projects/project_routes.py @@ -313,20 +313,17 @@ async def generate_presigned_url( # Process each image in the request for image in data.image_name: - # Construct the image path - image_path = ( - f"projects/{data.project_id}/{data.task_id}/images/" - if replace_existing - else f"projects/{data.project_id}/{data.task_id}/images/{image}" - ) + image_path = f"projects/{data.project_id}/{data.task_id}/images/{image}" + # If replace_existing is True, delete the image first if replace_existing: + image_dir = f"projects/{data.project_id}/{data.task_id}/images/" try: # Prepare the list of objects to delete (recursively if necessary) delete_object_list = map( lambda x: DeleteObject(x.object_name), client.list_objects( - settings.S3_BUCKET_NAME, image_path, recursive=True + settings.S3_BUCKET_NAME, image_dir, recursive=True ), ) @@ -343,22 +340,6 @@ async def generate_presigned_url( detail=f"Failed to delete existing image: {error}", ) - # # Update task as images uploaded - # pool = await database.get_db_connection_pool() - # current_task_state = await task_logic.get_task_state( - # conn, data.project_id, data.task_id - # ) - # async with pool.connection() as conn: - # await task_logic.update_task_state( - # conn, - # data.project_id, - # data.task_id, - # data.user_id, - # "Image re-upload", - # current_task_state, - # final_state=State.IMAGE_UPLOADED, - # updated_at=timestamp(), - # ) except Exception as e: raise HTTPException( status_code=HTTPStatus.BAD_REQUEST,