Skip to content

Commit

Permalink
fix(frontend): prevent project generation if api failure
Browse files Browse the repository at this point in the history
  • Loading branch information
spwoodcock committed Jul 8, 2024
1 parent 33b86a7 commit e309eb7
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 101 deletions.
160 changes: 85 additions & 75 deletions src/frontend/src/api/CreateProjectService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,72 +21,69 @@ const CreateProjectService: Function = (
dispatch(CreateProjectActions.CreateProjectLoading(true));
dispatch(CommonActions.SetLoading(true));

const postCreateProjectDetails = async (url, projectData, taskAreaGeojson, formUpload) => {
try {
// Create project
const postNewProjectDetails = await axios.post(url, projectData);
const resp: ProjectDetailsModel = postNewProjectDetails.data;
await dispatch(CreateProjectActions.PostProjectDetails(resp));

// Submit task boundaries
await dispatch(
UploadTaskAreasService(
`${import.meta.env.VITE_API_URL}/projects/${resp.id}/upload-task-boundaries`,
taskAreaGeojson,
),
);

dispatch(
CommonActions.SetSnackBar({
open: true,
message: 'Project Successfully Created Now Generating QR For Project',
variant: 'success',
duration: 2000,
}),
try {
// Create project
const postNewProjectDetails = await API.post(url, projectData);
const projectCreateResp: ProjectDetailsModel = postNewProjectDetails.data;
await dispatch(CreateProjectActions.PostProjectDetails(projectCreateResp));

if (projectCreateResp.status >= 300) {
throw new Error(`Request failed with status ${projectCreateResp.status}`);
}
const projectId = projectCreateResp.id;

// Submit task boundaries
await dispatch(
UploadTaskAreasService(
`${import.meta.env.VITE_API_URL}/projects/${projectId}/upload-task-boundaries`,
taskAreaGeojson,
),
);

// Upload data extract
let extractResponse
if (isOsmExtract) {
// Generated extract from raw-data-api
extractResponse = await API.get(
`${import.meta.env.VITE_API_URL}/projects/data-extract-url/?project_id=${projectId}&url=${projectData.data_extract_url}`,
);

if (isOsmExtract) {
// Upload data extract generated from raw-data-api
const response = await axios.get(
`${import.meta.env.VITE_API_URL}/projects/data-extract-url/?project_id=${resp.id}&url=${
projectData.data_extract_url
}`,
);
} else if (dataExtractFile) {
// Upload custom data extract from user
const dataExtractFormData = new FormData();
dataExtractFormData.append('custom_extract_file', dataExtractFile);
const response = await axios.post(
`${import.meta.env.VITE_API_URL}/projects/upload-custom-extract/?project_id=${resp.id}`,
dataExtractFormData,
);
}

// Generate QR codes
await dispatch(
GenerateProjectQRService(
`${import.meta.env.VITE_API_URL}/projects/${resp.id}/generate-project-data`,
projectData,
formUpload,
),
} else if (dataExtractFile) {
// Custom data extract from user
const dataExtractFormData = new FormData();
dataExtractFormData.append('custom_extract_file', dataExtractFile);
extractResponse = await API.post(
`${import.meta.env.VITE_API_URL}/projects/upload-custom-extract/?project_id=${projectId}`,
dataExtractFormData,
);
} catch (error: any) {
// Added Snackbar toast for error message
dispatch(
CommonActions.SetSnackBar({
open: true,
message: JSON.stringify(error?.response?.data?.detail) || 'Something went wrong.',
variant: 'error',
duration: 2000,
}),
);
dispatch(CreateProjectActions.CreateProjectLoading(false));
} finally {
dispatch(CommonActions.SetLoading(false));
}
};
if (extractResponse.status >= 300) {
throw new Error(`Request failed with status ${extractResponse.status}`);
}

await postCreateProjectDetails(url, projectData, taskAreaGeojson, formUpload);
// Generate project files
await dispatch(
GenerateProjectFilesService(
`${import.meta.env.VITE_API_URL}/projects/${projectId}/generate-project-data`,
projectData,
formUpload,
),
);

dispatch(CreateProjectActions.CreateProjectLoading(false));
} catch (error: any) {
await dispatch(CreateProjectActions.GenerateProjectError(true));
dispatch(
CommonActions.SetSnackBar({
open: true,
message: JSON.stringify(error?.response?.data?.detail) || 'Something went wrong.',
variant: 'error',
duration: 2000,
}),
);
dispatch(CreateProjectActions.CreateProjectLoading(false));
} finally {
dispatch(CommonActions.SetLoading(false));
}
};
};

Expand All @@ -107,6 +104,7 @@ const FormCategoryService: Function = (url: string) => {
await getFormCategoryList(url);
};
};

const UploadTaskAreasService: Function = (url: string, filePayload: any, projectData: any) => {
return async (dispatch) => {
dispatch(CreateProjectActions.UploadAreaLoading(true));
Expand All @@ -119,10 +117,15 @@ const UploadTaskAreasService: Function = (url: string, filePayload: any, project
'Content-Type': 'multipart/form-data',
},
});
// const resp: UploadAreaDetailsModel = postNewProjectDetails.data;
await dispatch(CreateProjectActions.UploadAreaLoading(false));
await dispatch(CreateProjectActions.PostUploadAreaSuccess(postNewProjectDetails.data));

if (postNewProjectDetails.status >= 200 && postNewProjectDetails.status < 300) {
await dispatch(CreateProjectActions.UploadAreaLoading(false));
await dispatch(CreateProjectActions.PostUploadAreaSuccess(postNewProjectDetails.data));
} else {
throw new Error(`Request failed with status ${postNewProjectDetails.status}`);
}
} catch (error: any) {
await dispatch(CreateProjectActions.GenerateProjectError(true));
dispatch(
CommonActions.SetSnackBar({
open: true,
Expand All @@ -138,34 +141,40 @@ const UploadTaskAreasService: Function = (url: string, filePayload: any, project
await postUploadArea(url, filePayload);
};
};
const GenerateProjectQRService: Function = (url: string, projectData: any, formUpload: any) => {

const GenerateProjectFilesService: Function = (url: string, projectData: any, formUpload: any) => {
return async (dispatch) => {
dispatch(CreateProjectActions.GenerateProjectQRLoading(true));
dispatch(CreateProjectActions.GenerateProjectLoading(true));
dispatch(CommonActions.SetLoading(true));

const postUploadArea = async (url, projectData: any, formUpload) => {
try {
let postNewProjectDetails;
let response;

if (projectData.form_ways === 'custom_form') {
// TODO move form upload to a separate service / endpoint?
const generateApiFormData = new FormData();
generateApiFormData.append('xls_form_upload', formUpload);
postNewProjectDetails = await axios.post(url, generateApiFormData, {
response = await axios.post(url, generateApiFormData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
} else {
postNewProjectDetails = await axios.post(url, {});
response = await axios.post(url, {});
}

if (response.status > 300) {
throw new Error(`Request failed with status ${response.status}`);
}

const resp: string = postNewProjectDetails.data;
await dispatch(CreateProjectActions.GenerateProjectQRLoading(false));
await dispatch(CreateProjectActions.GenerateProjectLoading(false));
dispatch(CommonActions.SetLoading(false));
await dispatch(CreateProjectActions.GenerateProjectQRSuccess(resp));
// Trigger the watcher and redirect after success
await dispatch(CreateProjectActions.GenerateProjectSuccess(true));
} catch (error: any) {
dispatch(CommonActions.SetLoading(false));
await dispatch(CreateProjectActions.GenerateProjectError(true));
dispatch(
CommonActions.SetSnackBar({
open: true,
Expand All @@ -174,7 +183,7 @@ const GenerateProjectQRService: Function = (url: string, projectData: any, formU
duration: 2000,
}),
);
dispatch(CreateProjectActions.GenerateProjectQRLoading(false));
dispatch(CreateProjectActions.GenerateProjectLoading(false));
}
};

Expand Down Expand Up @@ -333,6 +342,7 @@ const PatchProjectDetails: Function = (url: string, projectData: any) => {
await patchProjectDetails(url, projectData);
};
};

const PostFormUpdate: Function = (url: string, projectData: any) => {
return async (dispatch) => {
dispatch(CreateProjectActions.SetPostFormUpdateLoading(true));
Expand Down Expand Up @@ -492,7 +502,7 @@ export {
UploadTaskAreasService,
CreateProjectService,
FormCategoryService,
GenerateProjectQRService,
GenerateProjectFilesService,
OrganisationService,
GetDividedTaskFromGeojson,
TaskSplittingPreviewService,
Expand Down
10 changes: 5 additions & 5 deletions src/frontend/src/components/createnewproject/SplitTasks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ const SplitTasks = ({ flag, geojsonFile, setGeojsonFile, customDataExtractUpload
const projectDetails = useAppSelector((state) => state.createproject.projectDetails);
const dataExtractGeojson = useAppSelector((state) => state.createproject.dataExtractGeojson);

const generateQrSuccess = useAppSelector((state) => state.createproject.generateQrSuccess);
const generateProjectSuccess = useAppSelector((state) => state.createproject.generateProjectSuccess);
const generateProjectError = useAppSelector((state) => state.createproject.generateProjectError);
const projectDetailsResponse = useAppSelector((state) => state.createproject.projectDetailsResponse);
const dividedTaskGeojson = useAppSelector((state) => state.createproject.dividedTaskGeojson);
const projectDetailsLoading = useAppSelector((state) => state.createproject.projectDetailsLoading);
Expand Down Expand Up @@ -191,12 +192,12 @@ const SplitTasks = ({ flag, geojsonFile, setGeojsonFile, customDataExtractUpload
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const handleQRGeneration = async () => {
if (generateQrSuccess) {
if (!generateProjectError && generateProjectSuccess) {
const projectId = projectDetailsResponse?.id;
dispatch(
CommonActions.SetSnackBar({
open: true,
message: 'QR Generation Completed. Redirecting...',
message: 'Project Generation Completed. Redirecting...',
variant: 'success',
duration: 2000,
}),
Expand All @@ -205,15 +206,14 @@ const SplitTasks = ({ flag, geojsonFile, setGeojsonFile, customDataExtractUpload
// Add 5-second delay to allow backend Entity generation to catch up
await delay(5000);
dispatch(CreateProjectActions.CreateProjectLoading(false));
dispatch(CreateProjectActions.SetGenerateProjectQRSuccess(null));
navigate(`/project/${projectId}`);
dispatch(CreateProjectActions.ClearCreateProjectFormData());
dispatch(CreateProjectActions.SetCanSwitchCreateProjectSteps(false));
}
};

handleQRGeneration();
}, [generateQrSuccess]);
}, [generateProjectSuccess]);

const renderTraceback = (errorText: string) => {
if (!errorText) {
Expand Down
25 changes: 11 additions & 14 deletions src/frontend/src/store/slices/CreateProjectSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ export const initialState: CreateProjectStateTypes = {
projectAreaLoading: false,
formCategoryList: [],
formCategoryLoading: false,
generateQrLoading: false,
generateProjectLoading: false,
generateProjectSuccess: false,
generateProjectError: false,
organisationList: [],
organisationListLoading: false,
generateQrSuccess: null,
createProjectStep: 1,
dividedTaskLoading: false,
dividedTaskGeojson: null,
Expand Down Expand Up @@ -110,25 +111,21 @@ const CreateProject = createSlice({
SetIndividualProjectDetailsData(state, action) {
state.projectDetails = action.payload;
},
GenerateProjectQRLoading(state, action) {
state.generateQrLoading = action.payload;
GenerateProjectLoading(state, action) {
state.generateProjectLoading = action.payload;
},
GenerateProjectSuccess(state, action) {
state.generateProjectSuccess = action.payload;
},
GenerateProjectError(state, action) {
state.generateProjectError = action.payload;
},
GetOrganisationList(state, action) {
state.organisationList = action.payload;
},
GetOrganisationListLoading(state, action) {
state.organisationListLoading = action.payload;
},
GenerateProjectQRSuccess(state, action) {
if (action.payload.status === 'SUCCESS') {
state.generateQrSuccess = null;
} else {
state.generateQrSuccess = action.payload;
}
},
SetGenerateProjectQRSuccess(state, action) {
state.generateQrSuccess = action.payload;
},
SetCreateProjectFormStep(state, action) {
state.createProjectStep = action.payload;
},
Expand Down
10 changes: 3 additions & 7 deletions src/frontend/src/store/types/ICreateProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ export type CreateProjectStateTypes = {
projectAreaLoading: boolean;
formCategoryList: FormCategoryListTypes[] | [];
formCategoryLoading: boolean;
generateQrLoading: boolean;
generateProjectLoading: boolean;
generateProjectSuccess: boolean;
generateProjectError: boolean;
organisationList: OrganisationListTypes[];
organisationListLoading: boolean;
generateQrSuccess: GenerateQrSuccessTypes | null;
createProjectStep: number;
dividedTaskLoading: boolean;
dividedTaskGeojson: null | GeoJSONFeatureTypes;
Expand Down Expand Up @@ -129,11 +130,6 @@ export type FormCategoryListTypes = {
title: string;
};

export type GenerateQrSuccessTypes = {
Message: string;
task_id: string;
};

export type OrganisationListTypes = {
logo: string;
id: number;
Expand Down

0 comments on commit e309eb7

Please sign in to comment.