Skip to content

Commit

Permalink
fix: merge conflict
Browse files Browse the repository at this point in the history
  • Loading branch information
nrjadkry committed Sep 13, 2024
2 parents c73c7d4 + e542f2d commit a2b016d
Show file tree
Hide file tree
Showing 23 changed files with 532 additions and 119 deletions.
2 changes: 2 additions & 0 deletions src/backend/app/models/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ class EventType(str, Enum):
- ``split`` -- Set the state *unlocked done* then generate additional subdivided task areas.
- ``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.
Note that ``task_id`` must be specified in the endpoint too.
"""
Expand All @@ -175,3 +176,4 @@ class EventType(str, Enum):
SPLIT = "split"
ASSIGN = "assign"
COMMENT = "comment"
UNLOCK = "unlock"
37 changes: 29 additions & 8 deletions src/backend/app/projects/project_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,18 +283,38 @@ async def all(
limit: int = 100,
):
"""
Get all projects. Optionally filter by the project creator (user).
Get all projects, count total tasks and task states (ongoing, completed, etc.).
Optionally filter by the project creator (user).
"""
async with db.cursor(row_factory=dict_row) as cur:
await cur.execute(
"""
SELECT
id, slug, name, description, per_task_instructions,
ST_AsGeoJSON(outline)::jsonb AS outline,
requires_approval_from_manager_for_locking
FROM projects
WHERE (author_id = COALESCE(%(user_id)s, author_id))
ORDER BY created_at DESC
p.id, p.slug, p.name, p.description, p.per_task_instructions,
ST_AsGeoJSON(p.outline)::jsonb AS outline,
p.requires_approval_from_manager_for_locking,
-- Count total tasks for each project
COUNT(t.id) AS total_task_count,
-- Count based on the latest state of tasks
COUNT(CASE WHEN te.state = 'LOCKED_FOR_MAPPING' THEN 1 END) AS ongoing_task_count
FROM projects p
LEFT JOIN tasks t ON t.project_id = p.id
LEFT JOIN (
-- Get the latest event per task
SELECT DISTINCT ON (te.task_id)
te.task_id,
te.state,
te.created_at
FROM task_events te
ORDER BY te.task_id, te.created_at DESC
) AS te ON te.task_id = t.id
WHERE (p.author_id = COALESCE(%(user_id)s, p.author_id))
GROUP BY p.id
ORDER BY p.created_at DESC
OFFSET %(skip)s
LIMIT %(limit)s
""",
Expand Down Expand Up @@ -417,9 +437,10 @@ class ProjectOut(BaseModel):
outline: Optional[Polygon | Feature | FeatureCollection]
no_fly_zones: Optional[Polygon | Feature | FeatureCollection | MultiPolygon] = None
requires_approval_from_manager_for_locking: bool
task_count: int = 0
total_task_count: int = 0
tasks: Optional[list[TaskOut]] = []
image_url: Optional[str] = None
ongoing_task_count: Optional[int] = 0

@model_validator(mode="after")
def set_image_url(cls, values):
Expand Down
38 changes: 38 additions & 0 deletions src/backend/app/tasks/task_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,41 @@ async def request_mapping(
)
result = await cur.fetchone()
return result


async def get_task_state(
db: Connection, project_id: uuid.UUID, task_id: uuid.UUID
) -> dict:
"""
Retrieve the latest state of a task by querying the task_events table.
Args:
db (Connection): The database connection.
project_id (uuid.UUID): The project ID.
task_id (uuid.UUID): The task ID.
Returns:
dict: A dictionary containing the task's state and associated metadata.
"""
try:
async with db.cursor(row_factory=dict_row) as cur:
await cur.execute(
"""
SELECT state, user_id, created_at, comment
FROM task_events
WHERE project_id = %(project_id)s AND task_id = %(task_id)s
ORDER BY created_at DESC
LIMIT 1;
""",
{
"project_id": str(project_id),
"task_id": str(task_id),
},
)
result = await cur.fetchone()
return result
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"An error occurred while retrieving the task state: {str(e)}",
)
32 changes: 32 additions & 0 deletions src/backend/app/tasks/task_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,4 +368,36 @@ async def new_event(
State.UNFLYABLE_TASK,
)

case EventType.UNLOCK:
# Fetch the task state
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 unlock operation.",
)
if user_id != locked_user_id:
raise HTTPException(
status_code=403,
detail="You cannot unlock this task as it is locked by another user.",
)

# Proceed with unlocking the task
return await task_logic.update_task_state(
db,
project_id,
task_id,
user_id,
f"Task has been unlock by user {user_data.name}.",
State.LOCKED_FOR_MAPPING,
State.UNLOCKED_TO_MAP,
)

return True
1 change: 0 additions & 1 deletion src/backend/app/waypoints/waypoint_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ async def get_task_waypoint(
if download:
outfile = outfile = f"/tmp/{uuid.uuid4()}"
kmz_file = wpml.create_wpml(placemarks, outfile)

return FileResponse(
kmz_file, media_type="application/zip", filename="flight_plan.kmz"
)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
import { convertGeojsonToFile } from '@Utils/convertLayerUtils';
import prepareFormData from '@Utils/prepareFormData';
import hasErrorBoundary from '@Utils/hasErrorBoundary';
import { getFrontOverlap, getSideOverlap, gsdToAltitude } from '@Utils/index';

/**
* This function looks up the provided map of components to find and return
Expand Down Expand Up @@ -86,6 +87,9 @@ const CreateprojectLayout = () => {
const capturedProjectMap = useTypedSelector(
state => state.createproject.capturedProjectMap,
);
const imageMergeType = useTypedSelector(
state => state.createproject.imageMergeType,
);

const initialState: FieldValues = {
name: '',
Expand Down Expand Up @@ -214,13 +218,29 @@ const CreateprojectLayout = () => {
return;
}

// get altitude
const agl =
measurementType === 'gsd'
? gsdToAltitude(data?.gsd_cm_px)
: data?.altitude_from_ground;

const refactoredData = {
...data,
is_terrain_follow: isTerrainFollow,
requires_approval_from_manager_for_locking:
requireApprovalFromManagerForLocking === 'required',
deadline_at: data?.deadline_at ? data?.deadline_at : null,
front_overlap:
imageMergeType === 'spacing'
? getFrontOverlap(agl, data?.forward_spacing)
: data?.front_overlap,
side_overlap:
imageMergeType === 'spacing'
? getSideOverlap(agl, data?.side_spacing)
: data?.side_overlap,
};
delete refactoredData?.forward_spacing;
delete refactoredData?.side_spacing;

// remove key
if (isNoflyzonePresent === 'no')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { taskGenerationGuidelines } from '@Constants/createProject';

export default function GenerateTask() {
return (
<div className="naxatw-px-10 naxatw-py-5">
Expand All @@ -6,6 +8,19 @@ export default function GenerateTask() {
Split the task into smaller chunks based on the given dimensions to
ensure more efficient and precise data collection and analysis.
</p>
<br />
<div>
<p className="naxatw-mt-2 naxatw-text-body-md">
{taskGenerationGuidelines?.title}
</p>
<ol className="naxatw-flex naxatw-list-decimal naxatw-flex-col naxatw-gap-1 naxatw-px-2 naxatw-py-2">
{taskGenerationGuidelines?.guidelines?.map(item => (
<li key={item} className="naxatw-text-left naxatw-text-body-md">
{item}
</li>
))}
</ol>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { useMutation } from '@tanstack/react-query';
import { useState } from 'react';
import ErrorMessage from '@Components/common/ErrorMessage';
import { useTypedDispatch, useTypedSelector } from '@Store/hooks';
import { FormControl, Label, Input } from '@Components/common/FormUI';
import { Button } from '@Components/RadixComponents/Button';
Expand All @@ -11,6 +13,7 @@ import MapSection from './MapSection';

export default function GenerateTask({ formProps }: { formProps: any }) {
const dispatch = useTypedDispatch();
const [error, setError] = useState('');

const { register, watch } = formProps;
const dimension = watch('task_split_dimension');
Expand Down Expand Up @@ -53,11 +56,15 @@ export default function GenerateTask({ formProps }: { formProps: any }) {
type="number"
className="naxatw-mt-1"
value={dimension}
min={50}
max={700}
{...register('task_split_dimension', {
required: 'Required',
valueAsNumber: true,
})}
onFocus={() => setError('')}
/>
{error && <ErrorMessage message={error} />}
</FormControl>
<Button
withLoader
Expand All @@ -66,14 +73,16 @@ export default function GenerateTask({ formProps }: { formProps: any }) {
disabled={!dimension}
className="naxatw-mt-4 naxatw-bg-red"
onClick={() => {
if (!projectArea) return;
if (!projectArea) return () => {};
if (dimension < 50 || dimension > 700)
return setError('Dimension must in between 50-700');
dispatch(
setCreateProjectState({
splitGeojson: null,
capturedProjectMap: false,
}),
);
mutate(payload);
return mutate(payload);
}}
>
Generate Task
Expand Down
Loading

0 comments on commit a2b016d

Please sign in to comment.