Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix mapper frontend task display and event POSTs #1842

Merged
merged 5 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ services:
# AUTH_JWT_KEY: ${ENCRYPTION_KEY}
# AUTH_JWT_AUD: ${FMTM_DOMAIN}
ports:
- "7055:7055"
- "7055:3000"
networks:
- fmtm-net
restart: "unless-stopped"
Expand Down
76 changes: 75 additions & 1 deletion src/backend/app/db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from app.db.enums import (
BackgroundTaskStatus,
CommunityType,
EntityState,
HTTPStatus,
MappingLevel,
MappingState,
Expand Down Expand Up @@ -842,7 +843,7 @@ async def create(

Args:
db (Connection): The database connection.
project_id (int): The organisation ID.
project_id (int): The project ID.
tasks (geojson.FeatureCollection): FeatureCollection of task areas.

Returns:
Expand Down Expand Up @@ -1296,6 +1297,79 @@ async def delete(cls, db: Connection, project_id: int) -> bool:
)


class DbOdkEntities(BaseModel):
"""Table odk_entities.

Mirror tracking the status of Entities in ODK.
"""

entity_id: UUID
status: EntityState
project_id: int
task_id: int

@classmethod
async def upsert(
cls,
db: Connection,
project_id: int,
entities: list[Self],
) -> bool:
"""Update or insert Entity data, with statuses.

Args:
db (Connection): The database connection.
project_id (int): The project ID.
entities (list[Self]): List of DbOdkEntities objects.

Returns:
bool: Success or failure.
"""
log.info(
f"Updating FMTM database Entities for project {project_id} "
f"with ({len(entities)}) features"
)

sql = """
INSERT INTO public.odk_entities
(entity_id, status, project_id, task_id)
VALUES
"""

# Prepare data for bulk insert
values = []
data = {}
for index, entity in enumerate(entities):
entity_index = f"entity_{index}"
values.append(
f"(%({entity_index}_entity_id)s, "
f"%({entity_index}_status)s, "
f"%({entity_index}_project_id)s, "
f"%({entity_index}_task_id)s)"
)
data[f"{entity_index}_entity_id"] = entity["id"]
data[f"{entity_index}_status"] = EntityState(int(entity["status"])).name
data[f"{entity_index}_project_id"] = project_id
task_id = entity["task_id"]
data[f"{entity_index}_task_id"] = int(task_id) if task_id else None

sql += (
", ".join(values)
+ """
ON CONFLICT (entity_id) DO UPDATE SET
status = EXCLUDED.status,
task_id = EXCLUDED.task_id
RETURNING True;
"""
)

async with db.cursor() as cur:
await cur.execute(sql, data)
result = await cur.fetchall()

return bool(result)


class DbBackgroundTask(BaseModel):
"""Table background_tasks.

Expand Down
4 changes: 2 additions & 2 deletions src/backend/app/projects/project_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,8 +532,8 @@ async def generate_project_files(

# Split extract by task area
log.debug("Splitting data extract per task area")
# TODO in future this splitting could be removed as the task_id is
# no longer used in the XLSForm
# TODO in future this splitting could be removed if the task_id is
# no longer used in the XLSForm for the map filter
task_extract_dict = await split_geojson_by_task_areas(
db, feature_collection, project_id
)
Expand Down
17 changes: 15 additions & 2 deletions src/backend/app/projects/project_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,14 @@
ProjectRole,
XLSFormType,
)
from app.db.models import DbBackgroundTask, DbBasemap, DbProject, DbTask, DbUserRole
from app.db.models import (
DbBackgroundTask,
DbBasemap,
DbOdkEntities,
DbProject,
DbTask,
DbUserRole,
)
from app.db.postgis_utils import (
check_crs,
featcol_keep_single_geom_type,
Expand Down Expand Up @@ -174,14 +181,20 @@ async def get_odk_entities_geojson(
response_model=list[central_schemas.EntityMappingStatus],
)
async def get_odk_entities_mapping_statuses(
project_id: int,
project: Annotated[DbProject, Depends(project_deps.get_project)],
db: Annotated[Connection, Depends(db_conn)],
):
"""Get the ODK entities mapping statuses, i.e. in progress or complete."""
return await central_crud.get_entities_data(
entities = await central_crud.get_entities_data(
project.odk_credentials,
project.odkid,
)
# First update the Entity statuses in the db
# FIXME this is a hack and in the long run should be replaced
# https://github.com/hotosm/fmtm/issues/1841
await DbOdkEntities.upsert(db, project_id, entities)
return entities


@router.get(
Expand Down
36 changes: 36 additions & 0 deletions src/backend/migrations/009-entity-table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
-- ## Migration to:
-- * Add a new table that syncs the ODK Entity status to FMTM.
-- * Add a primary key for entity_id field.
-- * Add two indexes on entity_id + project_id / task_id


-- Start a transaction
BEGIN;

CREATE TABLE IF NOT EXISTS public.odk_entities (
entity_id UUID NOT NULL,
status entitystate NOT NULL,
project_id integer NOT NULL,
task_id integer
);
ALTER TABLE public.odk_entities OWNER TO fmtm;

DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'odk_entities_pkey') THEN
ALTER TABLE ONLY public.odk_entities
ADD CONSTRAINT odk_entities_pkey PRIMARY KEY (entity_id);
END IF;
END $$;

CREATE INDEX IF NOT EXISTS idx_entities_project_id
ON public.odk_entities USING btree (
entity_id, project_id
);
CREATE INDEX IF NOT EXISTS idx_entities_task_id
ON public.odk_entities USING btree (
entity_id, task_id
);

-- Commit the transaction
COMMIT;
37 changes: 28 additions & 9 deletions src/backend/migrations/init/fmtm_base_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ SET default_table_access_method = heap;

-- Tables

CREATE TABLE IF NOT EXISTS public._migrations (
CREATE TABLE public._migrations (
script_name text,
date_executed timestamp with time zone
);
Expand Down Expand Up @@ -339,6 +339,14 @@ CREATE TABLE public.users (
);
ALTER TABLE public.users OWNER TO fmtm;

CREATE TABLE public.odk_entities (
entity_id UUID NOT NULL,
status entitystate NOT NULL,
project_id integer NOT NULL,
task_id integer
);
ALTER TABLE public.odk_entities OWNER TO fmtm;

CREATE TABLE public.xlsforms (
id integer NOT NULL,
title character varying,
Expand Down Expand Up @@ -435,6 +443,9 @@ ADD CONSTRAINT users_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.users
ADD CONSTRAINT users_username_key UNIQUE (username);

ALTER TABLE ONLY public.odk_entities
ADD CONSTRAINT odk_entities_pkey PRIMARY KEY (entity_id);

ALTER TABLE ONLY public.xlsforms
ADD CONSTRAINT xlsforms_pkey PRIMARY KEY (id);

Expand All @@ -447,16 +458,16 @@ ADD CONSTRAINT submission_photos_pkey PRIMARY KEY (id);
-- Indexing

CREATE INDEX idx_projects_outline ON public.projects USING gist (outline);
CREATE INDEX IF NOT EXISTS idx_projects_mapper_level
CREATE INDEX idx_projects_mapper_level
ON public.projects USING btree (
mapper_level
);
CREATE INDEX IF NOT EXISTS idx_projects_organisation_id
CREATE INDEX idx_projects_organisation_id
ON public.projects USING btree (
organisation_id
);
CREATE INDEX idx_tasks_outline ON public.tasks USING gist (outline);
CREATE INDEX IF NOT EXISTS idx_tasks_composite
CREATE INDEX idx_tasks_composite
ON public.tasks USING btree (
id, project_id
);
Expand All @@ -466,26 +477,34 @@ CREATE INDEX idx_user_roles ON public.user_roles USING btree (
CREATE INDEX idx_org_managers ON public.organisation_managers USING btree (
user_id, organisation_id
);
CREATE INDEX IF NOT EXISTS idx_task_event_composite
CREATE INDEX idx_task_event_composite
ON public.task_events USING btree (
task_id, project_id
);
CREATE INDEX IF NOT EXISTS idx_task_event_project_user
CREATE INDEX idx_task_event_project_user
ON public.task_events USING btree (
user_id, project_id
);
CREATE INDEX IF NOT EXISTS idx_task_event_project_id
CREATE INDEX idx_task_event_project_id
ON public.task_events USING btree (
task_id, project_id
);
CREATE INDEX IF NOT EXISTS idx_task_event_user_id
CREATE INDEX idx_task_event_user_id
ON public.task_events USING btree (
task_id, user_id
);
CREATE INDEX IF NOT EXISTS idx_task_history_date
CREATE INDEX idx_task_history_date
ON public.task_history USING btree (
task_id, created_at
);
CREATE INDEX idx_entities_project_id
ON public.odk_entities USING btree (
entity_id, project_id
);
CREATE INDEX idx_entities_task_id
ON public.odk_entities USING btree (
entity_id, task_id
);

-- Foreign keys

Expand Down
2 changes: 1 addition & 1 deletion src/mapper/src/lib/components/page/layer-switcher.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script>
import { clickOutside } from '../../../utilFunctions/clickOutside.ts';
import { clickOutside } from '$lib/utils/clickOutside.ts';

let isOpen = false;
</script>
Expand Down
6 changes: 3 additions & 3 deletions src/mapper/src/lib/components/page/legend.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import { clickOutside } from '../../../utilFunctions/clickOutside.ts';
import LockImg from '../../../assets/images/black-lock.png';
import '../../../styles/page.css';
import { clickOutside } from '$lib/utils/clickOutside.ts';
import LockImg from '$assets/images/black-lock.png';
import '$styles/page.css';

type taskStatusesType = { status: string; color?: string; icon?: string };

Expand Down
Loading
Loading