Skip to content

Commit

Permalink
Merge branch 'development' of github.com:hotosm/fmtm into feat/new-ge…
Browse files Browse the repository at this point in the history
…om-table
  • Loading branch information
Sujanadh committed Jan 2, 2025
2 parents 741f688 + d05d4c3 commit 0922f4c
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 84 deletions.
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ repos:
# Lint / autoformat: Python code
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: "v0.8.3"
rev: "v0.8.4"
hooks:
# Run the linter
- id: ruff
Expand All @@ -21,15 +21,15 @@ repos:

# Deps: ensure Python uv lockfile is up to date
- repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.5.9
rev: 0.5.13
hooks:
- id: uv-lock
files: src/backend/pyproject.toml
args: [--project, src/backend]

# Upgrade: upgrade Python syntax
- repo: https://github.com/asottile/pyupgrade
rev: v3.19.0
rev: v3.19.1
hooks:
- id: pyupgrade

Expand Down
23 changes: 21 additions & 2 deletions src/backend/app/projects/project_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,7 @@ async def add_additional_entity_list(
async def generate_files(
db: Annotated[Connection, Depends(db_conn)],
project_user_dict: Annotated[ProjectUserDict, Depends(project_manager)],
background_tasks: BackgroundTasks,
xlsform_upload: Annotated[
Optional[BytesIO], Depends(central_deps.read_optional_xlsform)
],
Expand All @@ -906,6 +907,7 @@ async def generate_files(
created (i.e. the project form references multiple geometries).
db (Connection): The database connection.
project_user_dict (ProjectUserDict): Project admin role.
background_tasks (BackgroundTasks): FastAPI background tasks.
Returns:
json (JSONResponse): A success message containing the project ID.
Expand Down Expand Up @@ -981,6 +983,13 @@ async def generate_files(
},
)

if project.custom_tms_url:
basemap_in = project_schemas.BasemapGenerate(
tile_source="custom", file_format="pmtiles", tms_url=project.custom_tms_url
)
org_id = project.organisation_id
await generate_basemap(project_id, org_id, basemap_in, db, background_tasks)

return JSONResponse(
status_code=HTTPStatus.OK,
content={"message": "success"},
Expand All @@ -1000,7 +1009,19 @@ async def generate_project_basemap(
project_id = project_user.get("project").id
org_id = project_user.get("project").organisation_id

await generate_basemap(project_id, org_id, basemap_in, db, background_tasks)
# Create task in db and return uuid
return {"Message": "Tile generation started"}


async def generate_basemap(
project_id: int,
org_id: int,
basemap_in: project_schemas.BasemapGenerate,
db: Connection,
background_tasks: BackgroundTasks,
):
"""Generate basemap tiles for a project."""
log.debug(
"Creating generate_project_basemap background task "
f"for project ID: {project_id}"
Expand All @@ -1024,8 +1045,6 @@ async def generate_project_basemap(
basemap_in.tms_url,
)

return {"Message": "Tile generation started"}


@router.patch("/{project_id}", response_model=project_schemas.ProjectOut)
async def update_project(
Expand Down
4 changes: 3 additions & 1 deletion src/backend/app/projects/project_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,9 @@ class ProjectUserContributions(BaseModel):
class BasemapGenerate(BaseModel):
"""Params to generate a new basemap."""

tile_source: Annotated[Literal["esri", "bing", "google"], Field(default="esri")]
tile_source: Annotated[
Literal["esri", "bing", "google", "custom"], Field(default="esri")
]
file_format: Annotated[
Literal["mbtiles", "sqlitedb", "pmtiles"],
Field(default="mbtiles"),
Expand Down
14 changes: 7 additions & 7 deletions src/frontend/src/components/GenerateBasemap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ const GenerateBasemap = ({ projectInfo }: { projectInfo: Partial<projectInfoType

useEffect(() => {
if (projectInfo?.custom_tms_url) {
setSelectedTileSource('tms');
setSelectedTileSource('custom');
setTmsUrl(projectInfo?.custom_tms_url);
}
}, [projectInfo]);

const handleTileSourceChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSelectedTileSource(e.target.value);
// If 'tms' is selected, clear the TMS URL
if (e.target.value !== 'tms') {
// If 'custom' is selected, clear the TMS URL
if (e.target.value !== 'custom') {
setTmsUrl('');
}
};
Expand All @@ -70,7 +70,7 @@ const GenerateBasemap = ({ projectInfo }: { projectInfo: Partial<projectInfoType
if (!selectedOutputFormat) {
currentError.push('selectedOutputFormat');
}
if (!tmsUrl && selectedTileSource === 'tms') {
if (!tmsUrl && selectedTileSource === 'custom') {
currentError.push('tmsUrl');
}
setError(currentError);
Expand Down Expand Up @@ -150,7 +150,7 @@ const GenerateBasemap = ({ projectInfo }: { projectInfo: Partial<projectInfoType
<p className="fmtm-text-sm fmtm-text-red-500">Tile Source is Required.</p>
)}
</CoreModules.Grid>
{selectedTileSource === 'tms' && (
{selectedTileSource === 'custom' && (
<CoreModules.Grid item xs={12} sm={6} md={4}>
<CoreModules.FormControl fullWidth>
<CoreModules.TextField
Expand Down Expand Up @@ -219,8 +219,8 @@ const GenerateBasemap = ({ projectInfo }: { projectInfo: Partial<projectInfoType
<CoreModules.Grid
item
xs={12}
sm={selectedTileSource === 'tms' ? 6 : 12}
md={selectedTileSource === 'tms' ? 12 : 4}
sm={selectedTileSource === 'custom' ? 6 : 12}
md={selectedTileSource === 'custom' ? 12 : 4}
>
<div className="fmtm-w-full fmtm-flex fmtm-items-center fmtm-justify-center sm:fmtm-justify-end fmtm-mr-4 fmtm-gap-4 fmtm-h-full">
{/* Generate Button */}
Expand Down
133 changes: 72 additions & 61 deletions src/frontend/src/components/ProjectSubmissions/SubmissionsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
import React, { useEffect, useState } from 'react';
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { format } from 'date-fns';
import { Tooltip } from '@mui/material';
import { Loader2 } from 'lucide-react';

import AssetModules from '@/shared/AssetModules.js';
import { CustomSelect } from '@/components/common/Select.js';
import CoreModules from '@/shared/CoreModules.js';
import windowDimention from '@/hooks/WindowDimension';

import Button from '@/components/common/Button';
import { Modal } from '@/components/common/Modal';
import { CustomSelect } from '@/components/common/Select.js';
import CustomDatePicker from '@/components/common/CustomDatePicker';
import Table, { TableHeader } from '@/components/common/CustomTable';
import { SubmissionFormFieldsService, SubmissionTableService } from '@/api/SubmissionService';
import CoreModules from '@/shared/CoreModules.js';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/common/Dropdown';
import { SubmissionsTableSkeletonLoader } from '@/components/ProjectSubmissions/ProjectSubmissionsSkeletonLoader.js';
import { Loader2 } from 'lucide-react';
import { SubmissionActions } from '@/store/slices/SubmissionSlice';
import UpdateReviewStatusModal from '@/components/ProjectSubmissions/UpdateReviewStatusModal';
import { reviewStateData } from '@/constants/projectSubmissionsConstants';
import CustomDatePicker from '@/components/common/CustomDatePicker';
import { format } from 'date-fns';
import Button from '@/components/common/Button';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/common/Dropdown';

import { useAppSelector } from '@/types/reduxTypes';
import { task_state, task_event, entity_state } from '@/types/enums';
import { filterType } from '@/store/types/ISubmissions';
import { SubmissionActions } from '@/store/slices/SubmissionSlice';

import { CreateTaskEvent } from '@/api/TaskEvent';
import { ConvertXMLToJOSM, getDownloadProjectSubmission } from '@/api/task';
import { Modal } from '@/components/common/Modal';
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { SubmissionFormFieldsService, SubmissionTableService } from '@/api/SubmissionService';

import filterParams from '@/utilfunctions/filterParams';
import UpdateReviewStatusModal from '@/components/ProjectSubmissions/UpdateReviewStatusModal';
import { useAppSelector } from '@/types/reduxTypes';
import { camelToFlat } from '@/utilfunctions/commonUtils';
import useDocumentTitle from '@/utilfunctions/useDocumentTitle';
import { CreateTaskEvent } from '@/api/TaskEvent';
import { filterType } from '@/store/types/ISubmissions';
import { task_state, task_event } from '@/types/enums';

const SubmissionsTable = ({ toggleView }) => {
useDocumentTitle('Submission Table');
Expand Down Expand Up @@ -79,27 +85,24 @@ const SubmissionsTable = ({ toggleView }) => {
setNumberOfFilters(count);
}, [filter]);

useEffect(() => {
let count = 0;
const filters = Object.keys(filter);
filters?.map((fltr) => {
if (filter[fltr]) {
count = count + 1;
const updatedSubmissionFormFields = submissionFormFields
//filter necessary fields only
?.filter(
(formField) =>
formField?.path.startsWith('/survey_questions') ||
['/start', '/end', '/username', '/task_id', '/status'].includes(formField?.path),
)
// convert path to dot notation & update name
?.map((formField) => {
if (formField.type !== 'structure') {
return {
...formField,
path: formField?.path.slice(1).replace(/\//g, '.'),
name: formField?.name.charAt(0).toUpperCase() + formField?.name.slice(1).replace(/_/g, ' '),
};
}
return null;
});
setNumberOfFilters(count);
}, [filter]);

const updatedSubmissionFormFields = submissionFormFields?.map((formField) => {
if (formField.type !== 'structure') {
return {
...formField,
path: formField?.path.slice(1).replace(/\//g, '.'),
name: formField?.name.charAt(0).toUpperCase() + formField?.name.slice(1).replace(/_/g, ' '),
};
}
return null;
});

useEffect(() => {
dispatch(
Expand Down Expand Up @@ -169,6 +172,8 @@ const SubmissionsTable = ({ toggleView }) => {
if (path === 'start' || path === 'end') {
// start & end date is static
value = `${value[item]?.split('T')[0]} ${value[item]?.split('T')[1]}`;
} else if (path === 'status') {
value = entity_state[value[item]].replaceAll('_', ' ');
} else if (
value &&
value[item] &&
Expand All @@ -185,7 +190,7 @@ const SubmissionsTable = ({ toggleView }) => {
value = value?.[item];
}
});
return value ? (typeof value === 'object' ? '-' : value) : '';
return value ? (typeof value === 'object' ? '-' : value) : '-';
}

const uploadToJOSM = () => {
Expand Down Expand Up @@ -247,7 +252,7 @@ const SubmissionsTable = ({ toggleView }) => {
</div>
}
open={!!josmEditorError}
onOpenChange={(value) => {
onOpenChange={() => {
dispatch(CoreModules.TaskActions.SetJosmEditorError(null));
}}
/>
Expand Down Expand Up @@ -446,14 +451,16 @@ const SubmissionsTable = ({ toggleView }) => {
dataField={field?.name}
headerClassName="codeHeader"
rowClassName="codeRow"
dataFormat={(row) => (
<div
className="fmtm-w-[7rem] fmtm-overflow-hidden fmtm-truncate"
title={getValueByPath(row, field?.path)}
>
<span className="fmtm-text-[15px]">{getValueByPath(row, field?.path)}</span>
</div>
)}
dataFormat={(row) => {
const value = getValueByPath(row, field?.path);
return (
<Tooltip arrow placement="bottom-start" title={value}>
<div className="fmtm-w-[7rem] fmtm-overflow-hidden fmtm-truncate">
<span className="fmtm-text-[15px]">{value}</span>
</div>
</Tooltip>
);
}}
/>
);
}
Expand All @@ -468,24 +475,28 @@ const SubmissionsTable = ({ toggleView }) => {
return (
<div className="fmtm-w-[5rem] fmtm-overflow-hidden fmtm-truncate fmtm-text-center">
<Link to={`/project-submissions/${projectId}/tasks/${taskUid}/submission/${row?.meta?.instanceID}`}>
<AssetModules.VisibilityOutlinedIcon className="fmtm-text-[#545454] hover:fmtm-text-primaryRed" />
<Tooltip arrow placement="bottom" title="Validate Submission">
<AssetModules.VisibilityOutlinedIcon className="fmtm-text-[#545454] hover:fmtm-text-primaryRed" />
</Tooltip>
</Link>
<span className="fmtm-text-primaryRed fmtm-border-[1px] fmtm-border-primaryRed fmtm-mx-1"></span>{' '}
<AssetModules.CheckOutlinedIcon
className="fmtm-text-[#545454] hover:fmtm-text-primaryRed"
onClick={() => {
dispatch(
SubmissionActions.SetUpdateReviewStatusModal({
toggleModalStatus: true,
instanceId: row?.meta?.instanceID,
taskId: row?.task_id,
projectId: projectId,
reviewState: row?.__system?.reviewState,
taskUid: taskUid,
}),
);
}}
/>
<Tooltip arrow placement="bottom" title="Update Review Status">
<AssetModules.CheckOutlinedIcon
className="fmtm-text-[#545454] hover:fmtm-text-primaryRed"
onClick={() => {
dispatch(
SubmissionActions.SetUpdateReviewStatusModal({
toggleModalStatus: true,
instanceId: row?.meta?.instanceID,
taskId: row?.task_id,
projectId: projectId,
reviewState: row?.__system?.reviewState,
taskUid: taskUid,
}),
);
}}
/>
</Tooltip>
</div>
);
}}
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/src/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ export default {
{ id: 1, label: 'ESRI', value: 'esri' },
{ id: 2, label: 'Bing', value: 'bing' },
{ id: 3, label: 'Google', value: 'google' },
{ id: 5, label: 'Custom TMS', value: 'tms' },
{ id: 5, label: 'Custom TMS', value: 'custom' },
],
tileOutputFormats: [
{ id: 1, label: 'MBTiles', value: 'mbtiles' },
{ id: 2, label: 'OSMAnd', value: 'sqlite3' },
{ id: 2, label: 'OSMAnd', value: 'sqlitedb' },
{ id: 3, label: 'PMTiles', value: 'pmtiles' },
],
statusColors: {
Expand Down
12 changes: 10 additions & 2 deletions src/mapper/src/lib/components/dialog-entities-actions.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<script lang="ts">
import type { ProjectData } from '$lib/types';
import { TaskStatusEnum, type ProjectData } from '$lib/types';
import { getEntitiesStatusStore } from '$store/entities.svelte.ts';
import { getAlertStore } from '$store/common.svelte.ts';
import { getTaskStore } from '$store/tasks.svelte.ts';
import { mapTask } from '$lib/db/events';
type Props = {
isTaskActionModalOpen: boolean;
Expand All @@ -14,6 +16,7 @@
const entitiesStore = getEntitiesStatusStore();
const alertStore = getAlertStore();
const taskStore = getTaskStore();
const selectedEntityOsmId = $derived(entitiesStore.selectedEntity);
const selectedEntity = $derived(
Expand All @@ -38,6 +41,10 @@
status: 1,
label: `Task ${selectedEntity?.task_id} Feature ${selectedEntity?.osmid}`,
});
if (taskStore.selectedTaskId && taskStore.selectedTaskState === TaskStatusEnum['UNLOCKED_TO_MAP']) {
mapTask(projectData?.id, taskStore.selectedTaskId);
}
}
// Load entity in ODK Collect by intent
document.location.href = `odkcollect://form/${xformId}?feature=${entityUuid}`;
Expand All @@ -48,7 +55,8 @@
const navigateToEntity = () => {
if (!entitiesStore.toggleGeolocation) {
entitiesStore.setToggleGeolocation(true);
alertStore.setAlert({ message: 'Please enable geolocation to navigate to the entity.', variant: 'warning' });
return;
}
entitiesStore.setEntityToNavigate(selectedEntityCoordinate);
};
Expand Down
Loading

0 comments on commit 0922f4c

Please sign in to comment.