Skip to content

Commit

Permalink
Prevent project generation if api failure pt2 (#1635)
Browse files Browse the repository at this point in the history
* fix(hotfix): download of basemaps in ui, max zoom level 22 used for tms

* ci: update all gh-workflows to latest v1.6.0

* build: upgrade rclone --> v1 pin to avoid CVE-2024-24790

* build: remove unnecessary tables and field from db schema (#1623)

* build: remove unnecessary tables via migration & simplify schema

* refactor: remove references to removed tables in sqlalchemy models

* build: add IF EXISTS to DROP COLUMN in migration

* build: remove default columns from previous migration

* fix(backend): minor fixes to HTTPException on endpoints

* fix(backend): addded created date on the project response, set expiry of access token to 1 hour (#1633)

* docs: add placeholder for axiom repo activity

* fix(createProjectSlice): set generateProject & drawToggle status to default

* feat(commonUtils): isStatusSuccess function add

* test: get the detailed task history for a project (#1626)

* build(backend): add async-lru dep, remove cpuinfo dep

* fix: replace lru_cache with async for getting odk creds

* fix(splitTasks): dependency add to useEffect

* fix(createProjectService): halt project creation if api failure

---------

Co-authored-by: spwoodcock <sam.woodcock@protonmail.com>
Co-authored-by: Sam <78538841+spwoodcock@users.noreply.github.com>
Co-authored-by: Sujan Adhikari <109404840+Sujanadh@users.noreply.github.com>
Co-authored-by: Azhar Ismagulova <31756707+azharcodeit@users.noreply.github.com>
  • Loading branch information
5 people committed Jul 8, 2024
1 parent e309eb7 commit fceafd9
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 21 deletions.
72 changes: 52 additions & 20 deletions src/frontend/src/api/CreateProjectService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from '@/models/createproject/createProjectModel';
import { CommonActions } from '@/store/slices/CommonSlice';
import { ValidateCustomFormResponse } from '@/store/types/ICreateProject';
import { isStatusSuccess } from '@/utilfunctions/commonUtils';

const CreateProjectService: Function = (
url: string,
Expand All @@ -21,31 +22,42 @@ const CreateProjectService: Function = (
dispatch(CreateProjectActions.CreateProjectLoading(true));
dispatch(CommonActions.SetLoading(true));

let projectId: null | number = null;
try {
// Create project
// halt project creation if any api call fails
let hasAPISuccess = false;

const postNewProjectDetails = await API.post(url, projectData);
hasAPISuccess = isStatusSuccess(postNewProjectDetails.status);

const projectCreateResp: ProjectDetailsModel = postNewProjectDetails.data;
await dispatch(CreateProjectActions.PostProjectDetails(projectCreateResp));

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

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

if (!hasAPISuccess) {
throw new Error(`Request failed`);
}

// Upload data extract
let extractResponse
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}`,
`${import.meta.env.VITE_API_URL}/projects/data-extract-url/?project_id=${projectId}&url=${
projectData.data_extract_url
}`,
);
} else if (dataExtractFile) {
// Custom data extract from user
Expand All @@ -56,26 +68,37 @@ const CreateProjectService: Function = (
dataExtractFormData,
);
}
if (extractResponse.status >= 300) {
hasAPISuccess = isStatusSuccess(extractResponse.status);

if (!hasAPISuccess) {
throw new Error(`Request failed with status ${extractResponse.status}`);
}

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

dispatch(CreateProjectActions.CreateProjectLoading(false));
hasAPISuccess = generateProjectFile;
if (!hasAPISuccess) {
throw new Error(`Request failed`);
}
dispatch(CreateProjectActions.GenerateProjectError(false));
// dispatch(CreateProjectActions.CreateProjectLoading(false));
} catch (error: any) {
if (projectId) {
await dispatch(DeleteProjectService(`${import.meta.env.VITE_API_URL}/projects/${projectId}`, false));
}

await dispatch(CreateProjectActions.GenerateProjectError(true));
dispatch(
CommonActions.SetSnackBar({
open: true,
message: JSON.stringify(error?.response?.data?.detail) || 'Something went wrong.',
message: JSON.stringify(error?.response?.data?.detail) || 'Something went wrong. Please try again.',
variant: 'error',
duration: 2000,
}),
Expand Down Expand Up @@ -109,6 +132,7 @@ const UploadTaskAreasService: Function = (url: string, filePayload: any, project
return async (dispatch) => {
dispatch(CreateProjectActions.UploadAreaLoading(true));
const postUploadArea = async (url, filePayload) => {
let isAPISuccess = true;
try {
const areaFormData = new FormData();
areaFormData.append('task_geojson', filePayload);
Expand All @@ -117,14 +141,16 @@ const UploadTaskAreasService: Function = (url: string, filePayload: any, project
'Content-Type': 'multipart/form-data',
},
});
isAPISuccess = isStatusSuccess(postNewProjectDetails.status);

if (postNewProjectDetails.status >= 200 && postNewProjectDetails.status < 300) {
if (isAPISuccess) {
await dispatch(CreateProjectActions.UploadAreaLoading(false));
await dispatch(CreateProjectActions.PostUploadAreaSuccess(postNewProjectDetails.data));
} else {
throw new Error(`Request failed with status ${postNewProjectDetails.status}`);
}
} catch (error: any) {
isAPISuccess = false;
await dispatch(CreateProjectActions.GenerateProjectError(true));
dispatch(
CommonActions.SetSnackBar({
Expand All @@ -136,9 +162,10 @@ const UploadTaskAreasService: Function = (url: string, filePayload: any, project
);
dispatch(CreateProjectActions.UploadAreaLoading(false));
}
return isAPISuccess;
};

await postUploadArea(url, filePayload);
return await postUploadArea(url, filePayload);
};
};

Expand All @@ -148,6 +175,7 @@ const GenerateProjectFilesService: Function = (url: string, projectData: any, fo
dispatch(CommonActions.SetLoading(true));

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

Expand All @@ -163,8 +191,8 @@ const GenerateProjectFilesService: Function = (url: string, projectData: any, fo
} else {
response = await axios.post(url, {});
}

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

Expand All @@ -173,6 +201,7 @@ const GenerateProjectFilesService: Function = (url: string, projectData: any, fo
// Trigger the watcher and redirect after success
await dispatch(CreateProjectActions.GenerateProjectSuccess(true));
} catch (error: any) {
isAPISuccess = false;
dispatch(CommonActions.SetLoading(false));
await dispatch(CreateProjectActions.GenerateProjectError(true));
dispatch(
Expand All @@ -185,9 +214,10 @@ const GenerateProjectFilesService: Function = (url: string, projectData: any, fo
);
dispatch(CreateProjectActions.GenerateProjectLoading(false));
}
return isAPISuccess;
};

await postUploadArea(url, projectData, formUpload);
return await postUploadArea(url, projectData, formUpload);
};
};

Expand Down Expand Up @@ -462,23 +492,25 @@ const ValidateCustomForm: Function = (url: string, formUpload: any) => {
};
};

const DeleteProjectService: Function = (url: string) => {
const DeleteProjectService: Function = (url: string, hasRedirect: boolean = true) => {
return async (dispatch) => {
const deleteProject = async (url: string) => {
try {
await API.delete(url);
dispatch(
CommonActions.SetSnackBar({
open: true,
message: 'Project deleted. Redirecting...',
message: `Project deleted. ${hasRedirect && 'Redirecting...'}`,
variant: 'success',
duration: 2000,
}),
);
// Redirect to homepage
setTimeout(() => {
window.location.href = '/';
}, 2000);
if (hasRedirect) {
setTimeout(() => {
window.location.href = '/';
}, 2000);
}
} catch (error) {
if (error.response.status === 404) {
dispatch(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ const SplitTasks = ({ flag, geojsonFile, setGeojsonFile, customDataExtractUpload
};

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

const renderTraceback = (errorText: string) => {
if (!errorText) {
Expand Down
3 changes: 3 additions & 0 deletions src/frontend/src/store/slices/CreateProjectSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ const CreateProject = createSlice({
state.uploadAreaSelection = '';
state.dividedTaskGeojson = null;
state.dividedTaskLoading = false;
state.generateProjectSuccess = false;
state.generateProjectError = false;
state.drawToggle = false;
},
UploadAreaLoading(state, action) {
state.projectAreaLoading = action.payload;
Expand Down
7 changes: 7 additions & 0 deletions src/frontend/src/utilfunctions/commonUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,10 @@ export const isInputEmpty = (text: string): boolean => {
export const camelToFlat = (word: string): string => (
(word = word.replace(/[A-Z]/g, ' $&')), word[0].toUpperCase() + word.slice(1)
);

export const isStatusSuccess = (status: number) => {
if (status < 300) {
return true;
}
return false;
};

0 comments on commit fceafd9

Please sign in to comment.