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

feat: navigate to submissions page when clicked on the file icon in qr popup #1300

Closed
wants to merge 8 commits into from
59 changes: 28 additions & 31 deletions src/backend/app/central/central_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,45 +357,42 @@ def download_submissions(
return fixed.splitlines()


async def test_form_validity(xform_content: str, form_type: str):
async def test_form_validity(xform_content: bytes, form_type: str):
"""Validate an XForm.

Args:
xform_content (str): form to be tested
form_type (str): type of form (xls or xlsx).
"""
try:
xlsform_path = f"/tmp/validate_form.{form_type}"
outfile = "/tmp/outfile.xml"

with open(xlsform_path, "wb") as f:
f.write(xform_content)

xls2xform_convert(xlsform_path=xlsform_path, xform_path=outfile, validate=False)

namespaces = {
"h": "http://www.w3.org/1999/xhtml",
"odk": "http://www.opendatakit.org/xforms",
"xforms": "http://www.w3.org/2002/xforms",
}

with open(outfile, "r") as xml:
data = xml.read()

root = ElementTree.fromstring(data)
instances = root.findall(".//xforms:instance[@src]", namespaces)

geojson_list = []
for inst in instances:
try:
if "src" in inst.attrib:
if (inst.attrib["src"].split("."))[1] == "geojson":
parts = (inst.attrib["src"].split("."))[0].split("/")
geojson_name = parts[-1]
geojson_list.append(geojson_name)
except Exception:
continue
if form_type != "xml":
# Write xform_content to a temporary file
with open(f"/tmp/xform_temp.{form_type}", "wb") as f:
f.write(xform_content)
else:
with open(f"/tmp/xlsform.{form_type}", "wb") as f:
f.write(xform_content)
# Convert XLSForm to XForm
xls2xform_convert(
xlsform_path="/tmp/xlsform.xls",
xform_path="/tmp/xform_temp.xml",
validate=False,
)

# Parse XForm
namespaces = {"xforms": "http://www.w3.org/2002/xforms"}
tree = ElementTree.parse("/tmp/xform_temp.xml")
root = tree.getroot()

# Extract geojson filenames
geojson_list = [
os.path.splitext(inst.attrib["src"].split("/")[-1])[0]
for inst in root.findall(".//xforms:instance[@src]", namespaces)
if inst.attrib.get("src", "").endswith(".geojson")
]

return {"required media": geojson_list, "message": "Your form is valid"}

except Exception as e:
return JSONResponse(
content={"message": "Your form is invalid", "possible_reason": str(e)},
Expand Down
1 change: 1 addition & 0 deletions src/backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@

# Add sentry tracing only in prod
if not settings.DEBUG:
log.info("Adding Sentry tracing")
sentry_sdk.init(
dsn=settings.SENTRY_DSN,
traces_sample_rate=0.1,
Expand Down
12 changes: 7 additions & 5 deletions src/backend/app/projects/project_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -588,19 +588,21 @@ async def edit_project_boundary(
}


@router.post("/validate_form")
@router.post("/validate-form")
async def validate_form(form: UploadFile):
"""Tests the validity of the xls form uploaded.

Parameters:
- form: The xls form to validate
"""
file_name = os.path.splitext(form.filename)
file_ext = file_name[1]
file = Path(form.filename)
file_ext = file.suffix

allowed_extensions = [".xls", ".xlsx"]
allowed_extensions = [".xls", ".xlsx", "xml"]
if file_ext not in allowed_extensions:
raise HTTPException(status_code=400, detail="Provide a valid .xls file")
raise HTTPException(
status_code=400, detail="Provide a valid .xls,.xlsx,.xml file"
)

contents = await form.read()
return await central_crud.test_form_validity(contents, file_ext[1:])
Expand Down
10 changes: 8 additions & 2 deletions src/backend/app/submissions/submission_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,12 @@ def get_all_submissions_json(db: Session, project_id):

get_task_id_list_sync = async_to_sync(tasks_crud.get_task_id_list)
task_list = get_task_id_list_sync(db, project_id)
submissions = project.getAllSubmissions(project_info.odkid, task_list)
xform_list = [
f"{project_info.project_name_prefix}_{task}_{project_info.xform_title}"
for task in task_list
]

submissions = project.getAllSubmissions(project_info.odkid, xform_list)
return submissions


Expand Down Expand Up @@ -802,7 +807,8 @@ async def get_submission_by_task(
odk_credentials = await project_deps.get_odk_credentials(db, project.id)

xform = get_odk_form(odk_credentials)
data = xform.listSubmissions(project.odkid, str(task_id), filters)
xform_name = f"{project.project_name_prefix}_{task_id}_{project.xform_title}"
data = xform.listSubmissions(project.odkid, xform_name, filters)
submissions = data.get("value", [])
count = data.get("@odata.count", 0)

Expand Down
15 changes: 12 additions & 3 deletions src/frontend/src/components/ProjectDetailsV2/TaskSectionPopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ProjectActions } from '@/store/slices/ProjectSlice';
import environment from '@/environment';
import { ProjectFilesById } from '@/api/Files';
import QrcodeComponent from '@/components/QrcodeComponent';
import { useNavigate } from 'react-router-dom';

type TaskSectionPopupPropType = {
taskId: number | null;
Expand All @@ -14,10 +15,16 @@ type TaskSectionPopupPropType = {

const TaskSectionPopup = ({ taskId, body, feature }: TaskSectionPopupPropType) => {
const dispatch = CoreModules.useAppDispatch();
const [task_status, set_task_status] = useState('READY');
const taskModalStatus = CoreModules.useAppSelector((state) => state.project.taskModalStatus);
const navigate = useNavigate();
const params = CoreModules.useParams();
const encodedProjectId = params.id;
const currentProjectId = environment.decode(params.id);

console.log(encodedProjectId);

const [task_status, set_task_status] = useState('READY');

const taskModalStatus = CoreModules.useAppSelector((state) => state.project.taskModalStatus);
const projectData = CoreModules.useAppSelector((state) => state.project.projectTaskBoundries);
const projectIndex = projectData.findIndex((project) => project.id == currentProjectId);

Expand Down Expand Up @@ -74,7 +81,9 @@ const TaskSectionPopup = ({ taskId, body, feature }: TaskSectionPopupPropType) =
<AssetModules.DescriptionOutlinedIcon
style={{ width: '20px' }}
className="hover:fmtm-text-primaryRed"
onClick={() => {}}
onClick={() => {
navigate(`/project-submissions/${encodedProjectId}?tab=table&task_id=${taskId}`);
}}
/>
<AssetModules.CloseIcon
style={{ width: '20px' }}
Expand Down
100 changes: 14 additions & 86 deletions src/frontend/src/components/createnewproject/DataExtract.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,42 +19,17 @@ const dataExtractOptions = [
{ name: 'data_extract', value: 'custom_data_extract', label: 'Upload custom data extract' },
];

const osmFeatureTypeOptions = [
{ name: 'osm_feature_type', value: 'centroid', label: 'Point/Centroid' },
{ name: 'osm_feature_type', value: 'line', label: 'Line' },
{ name: 'osm_feature_type', value: 'polygon', label: 'Polygon' },
];

enum FeatureTypeName {
centroid = 'Point/Centroid',
line = 'Line',
polygon = 'Polygon',
}

const DataExtract = ({ flag, customLineUpload, setCustomLineUpload, customPolygonUpload, setCustomPolygonUpload }) => {
const DataExtract = ({ flag, customDataExtractUpload, setCustomDataExtractUpload }) => {
const dispatch = useDispatch();
const navigate = useNavigate();

console.log(customDataExtractUpload, 'customDataExtractUpload');
const [extractWays, setExtractWays] = useState('');
const [featureType, setFeatureType] = useState('');
const projectDetails: any = useAppSelector((state) => state.createproject.projectDetails);
const projectAoiGeojson = useAppSelector((state) => state.createproject.drawnGeojson);
const dataExtractGeojson = useAppSelector((state) => state.createproject.dataExtractGeojson);
const isFgbFetching = useAppSelector((state) => state.createproject.isFgbFetching);

const submission = () => {
if (featureType !== formValues?.dataExtractFeatureType && formValues.dataExtractWays === 'osm_data_extract') {
dispatch(
CommonActions.SetSnackBar({
open: true,
message: `Please generate data extract for ${FeatureTypeName[featureType]}`,
variant: 'warning',
duration: 2000,
}),
);
return;
}

dispatch(CreateProjectActions.SetIndividualProjectDetailsData(formValues));
dispatch(CommonActions.SetCurrentStepFormStep({ flag: flag, step: 5 }));

Expand Down Expand Up @@ -107,12 +82,9 @@ const DataExtract = ({ flag, customLineUpload, setCustomLineUpload, customPolygo
dispatch(
CreateProjectActions.SetIndividualProjectDetailsData({
...formValues,
data_extract_type: featureType,
data_extract_url: fgbUrl,
dataExtractWays: extractWays,
dataExtractFeatureType: featureType,
customLineUpload: null,
customPolygonUpload: null,
customDataExtractUpload: null,
}),
);

Expand Down Expand Up @@ -143,18 +115,14 @@ const DataExtract = ({ flag, customLineUpload, setCustomLineUpload, customPolygo
if (formValues?.dataExtractWays) {
setExtractWays(formValues?.dataExtractWays);
}
if (formValues?.dataExtractFeatureType) {
setFeatureType(formValues?.dataExtractFeatureType);
}
}, [formValues?.dataExtractWays, formValues?.dataExtractFeatureType]);
}, [formValues?.dataExtractWays]);

const toggleStep = (step, url) => {
if (url === '/select-category') {
dispatch(
CreateProjectActions.SetIndividualProjectDetailsData({
...formValues,
dataExtractWays: extractWays,
dataExtractFeatureType: featureType,
}),
);
}
Expand Down Expand Up @@ -260,72 +228,32 @@ const DataExtract = ({ flag, customLineUpload, setCustomLineUpload, customPolygo
btnText="Generate Data Extract"
btnType="primary"
onClick={() => {
resetFile(setCustomPolygonUpload);
resetFile(setCustomLineUpload);
resetFile(setCustomDataExtractUpload);
generateDataExtract();
}}
className="fmtm-mt-6"
isLoading={isFgbFetching}
loadingText="Data extracting..."
disabled={dataExtractGeojson && !customPolygonUpload && !customLineUpload ? true : false}
disabled={dataExtractGeojson && customDataExtractUpload ? true : false}
/>
)}
{extractWays === 'custom_data_extract' && (
<>
{/* TODO add option for point upload */}
{/* Set dataExtractFeatureType = 'centroid' */}
{/* <FileInputComponent
onChange={(e) => {
changeFileHandler(e, setCustomPolygonUpload);
handleCustomChange('customPolygonUpload', e.target.files[0]);
handleCustomChange('dataExtractFeatureType', 'polygon');
setFeatureType('');
}}
onResetFile={() => {
resetFile(setCustomPolygonUpload);
handleCustomChange('customPolygonUpload', null);
}}
customFile={customPolygonUpload}
btnText="Upload Polygons"
accept=".geojson,.json,.fgb"
fileDescription="*The supported file formats are .geojson, .json, .fgb"
errorMsg={errors.customPolygonUpload}
/> */}
<FileInputComponent
onChange={(e) => {
changeFileHandler(e, setCustomPolygonUpload);
handleCustomChange('customPolygonUpload', e.target.files[0]);
handleCustomChange('dataExtractFeatureType', 'polygon');
handleCustomChange('data_extract_type', 'line');
setFeatureType('polygon');
}}
onResetFile={() => {
resetFile(setCustomPolygonUpload);
handleCustomChange('customPolygonUpload', null);
}}
customFile={customPolygonUpload}
btnText="Upload Polygons"
accept=".geojson,.json,.fgb"
fileDescription="*The supported file formats are .geojson, .json, .fgb"
errorMsg={errors.customPolygonUpload}
/>
<FileInputComponent
onChange={(e) => {
changeFileHandler(e, setCustomLineUpload);
handleCustomChange('customLineUpload', e.target.files[0]);
handleCustomChange('dataExtractFeatureType', 'line');
handleCustomChange('data_extract_type', 'line');
setFeatureType('line');
changeFileHandler(e, setCustomDataExtractUpload);
handleCustomChange('customDataExtractUpload', e.target.files[0]);
}}
onResetFile={() => {
resetFile(setCustomLineUpload);
handleCustomChange('customLineUpload', null);
resetFile(setCustomDataExtractUpload);
handleCustomChange('customDataExtractUpload', null);
dispatch(CreateProjectActions.setDataExtractGeojson(null));
}}
customFile={customLineUpload}
btnText="Upload Lines"
customFile={customDataExtractUpload}
btnText="Upload Data Extract"
accept=".geojson,.json,.fgb"
fileDescription="*The supported file formats are .geojson, .json, .fgb"
errorMsg={errors.customLineUpload}
errorMsg={errors.customDataExtractUpload}
/>
</>
)}
Expand Down
6 changes: 3 additions & 3 deletions src/frontend/src/components/createnewproject/SelectForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ const SelectForm = ({ flag, geojsonFile, customFormFile, setCustomFormFile }) =>
};
useEffect(() => {
if (customFormFile && !customFileValidity) {
dispatch(ValidateCustomForm(`${import.meta.env.VITE_API_URL}/projects/validate_form`, customFormFile));
dispatch(ValidateCustomForm(`${import.meta.env.VITE_API_URL}/projects/validate-form`, customFormFile));
}
}, [customFormFile]);
return (
Expand Down Expand Up @@ -153,8 +153,8 @@ const SelectForm = ({ flag, geojsonFile, customFormFile, setCustomFormFile }) =>
onResetFile={resetFile}
customFile={customFormFile}
btnText="Select a Form"
accept=".xls,.xlsx"
fileDescription="*The supported file formats are .xlsx, .xls"
accept=".xls,.xlsx,.xml"
fileDescription="*The supported file formats are .xlsx, .xls, .xml"
errorMsg={errors.customFormUpload}
/>
) : null}
Expand Down
Loading
Loading