Skip to content

Commit

Permalink
RN-1274: Reworked survey response resubmission in datatrak to use the…
Browse files Browse the repository at this point in the history
… new backend routes
  • Loading branch information
rohan-bes committed Jul 4, 2024
1 parent 3a8b4b5 commit 2e95776
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 152 deletions.
104 changes: 10 additions & 94 deletions packages/datatrak-web/src/api/mutations/useResubmitSurveyResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,112 +5,28 @@

import { useMutation } from 'react-query';
import { generatePath, useNavigate, useParams } from 'react-router';
import { QuestionType } from '@tupaia/types';
import { getUniqueSurveyQuestionFileName } from '@tupaia/utils';
import { post } from '../api';
import { getAllSurveyComponents, useSurveyForm } from '../../features';
import { SurveyScreenComponent } from '../../types';
import { useSurveyForm } from '../../features';
import { ROUTES } from '../../constants';
import { AnswersT, isFileUploadAnswer } from './useSubmitSurveyResponse';

const processAnswers = (
answers: AnswersT,
questionsById: Record<string, SurveyScreenComponent>,
) => {
const files: File[] = [];
let entityId = null as string | null;
let dataTime = null as string | null;
const formattedAnswers = Object.entries(answers).reduce((acc, [questionId, answer]) => {
const { code, type } = questionsById[questionId];
if (!code) return acc;

if (type === QuestionType.PrimaryEntity && answer) {
entityId = answer as string;
return acc;
}

if (type === QuestionType.File) {
if (isFileUploadAnswer(answer) && answer.value instanceof File) {
// Create a new file with a unique name, and add it to the files array, so we can add to the FormData, as this is what the central server expects
const uniqueFileName = getUniqueSurveyQuestionFileName(answer.name);
files.push(
new File([answer.value as Blob], uniqueFileName, {
type: answer.value.type,
}),
);
return {
...acc,
[code]: uniqueFileName,
};
}
if (answer && typeof answer === 'object' && 'name' in answer) {
return {
...acc,
[code]: answer.name,
};
}
}

if (type === QuestionType.DateOfData || type === QuestionType.SubmissionDate) {
const date = new Date(answer as string);
dataTime = date.toISOString();
return acc;
}
return {
...acc,
[code]: answer,
};
}, {});

return {
answers: formattedAnswers,
files,
entityId,
dataTime,
};
};
import { AnswersT, useSurveyResponseData } from './useSubmitSurveyResponse';

export const useResubmitSurveyResponse = () => {
const navigate = useNavigate();
const params = useParams();
const { surveyResponseId } = params;
const { surveyScreens, resetForm } = useSurveyForm();
const allScreenComponents = getAllSurveyComponents(surveyScreens);
const questionsById = allScreenComponents.reduce((acc, component) => {
return {
...acc,
[component.questionId]: component,
};
}, {});

const { resetForm } = useSurveyForm();

const surveyResponseData = useSurveyResponseData();

return useMutation<any, Error, AnswersT, unknown>(
async (surveyAnswers: AnswersT) => {
if (!surveyAnswers) {
async (answers: AnswersT) => {
if (!answers) {
return;
}
const { answers, files, entityId, dataTime } = processAnswers(surveyAnswers, questionsById);

const formData = new FormData();
const formDataToSubmit = { answers } as {
answers: Record<string, string | number | boolean>;
entity_id?: string;
data_time?: string;
};
if (entityId) {
formDataToSubmit.entity_id = entityId;
}
if (dataTime) {
formDataToSubmit.data_time = dataTime;
}
formData.append('payload', JSON.stringify(formDataToSubmit));
files.forEach(file => {
formData.append(file.name, file);
});

return post(`surveyResponse/${surveyResponseId}/resubmit`, {
data: formData,
headers: {
'Content-Type': 'multipart/form-data',
},
data: { ...surveyResponseData, answers },
});
},
{
Expand Down
58 changes: 2 additions & 56 deletions packages/datatrak-web/src/api/mutations/useSubmitSurveyResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,14 @@
import { useMutation, useQueryClient } from 'react-query';
import { generatePath, useNavigate, useParams } from 'react-router';
import { getBrowserTimeZone } from '@tupaia/utils';
import { QuestionType } from '@tupaia/types';
import { Coconut } from '../../components';
import { post, useCurrentUserContext, useEntityByCode } from '..';
import { ROUTES } from '../../constants';
import { getAllSurveyComponents, useSurveyForm } from '../../features';
import { useSurvey } from '../queries';
import { gaEvent, successToast } from '../../utils';

type Base64 = string | null | ArrayBuffer;

type FileAnswerT = {
name: string;
value?: Base64 | File;
};

type Answer = string | number | boolean | null | undefined | FileAnswerT;
type Answer = string | number | boolean | null | undefined;

export type AnswersT = Record<string, Answer>;

Expand All @@ -43,46 +35,6 @@ export const useSurveyResponseData = () => {
};
};

export const isFileUploadAnswer = (answer: Answer): answer is FileAnswerT => {
if (!answer || typeof answer !== 'object') return false;
return 'value' in answer;
};

const createEncodedFile = (fileObject?: File): Promise<Base64> => {
if (!fileObject) {
return Promise.resolve(null);
}
return new Promise((resolve, reject) => {
const reader = new FileReader();

reader.onload = () => {
resolve(reader.result);
};

reader.onerror = reject;

reader.readAsDataURL(fileObject);
});
};

const processAnswers = async (answers: AnswersT, questionsById) => {
const formattedAnswers = { ...answers };
for (const [questionId, answer] of Object.entries(answers)) {
const question = questionsById[questionId];
if (!question) continue;
if (question.type === QuestionType.File && isFileUploadAnswer(answer)) {
// convert to an object with an encoded file so that it can be handled in the backend and uploaded to s3
const encodedFile = await createEncodedFile(answer.value as File);

formattedAnswers[questionId] = {
name: answer.name,
value: encodedFile,
};
}
}
return formattedAnswers;
};

export const useSubmitSurveyResponse = () => {
const queryClient = useQueryClient();
const navigate = useNavigate();
Expand All @@ -93,20 +45,14 @@ export const useSubmitSurveyResponse = () => {

const surveyResponseData = useSurveyResponseData();

const questionsById = surveyResponseData.questions.reduce((acc, question) => {
acc[question.questionId] = question;
return acc;
}, {});

return useMutation<any, Error, AnswersT, unknown>(
async (answers: AnswersT) => {
if (!answers) {
return;
}
const formattedAnswers = await processAnswers(answers, questionsById);

return post('submitSurveyResponse', {
data: { ...surveyResponseData, answers: formattedAnswers },
data: { ...surveyResponseData, answers },
});
},
{
Expand Down
22 changes: 20 additions & 2 deletions packages/datatrak-web/src/features/Questions/FileQuestion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,36 @@ const Wrapper = styled.div`
}
`;

type Base64 = string | null | ArrayBuffer;

const createEncodedFile = (fileObject: File): Promise<Base64> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();

reader.onload = () => {
resolve(reader.result);
};

reader.onerror = reject;

reader.readAsDataURL(fileObject);
});
};

export const FileQuestion = ({
label,
required,
detailLabel,
controllerProps: { onChange, value: selectedFile, name },
}: SurveyQuestionInputProps) => {
const handleChange = (_e, _name, files) => {
const handleChange = async (_e, _name, files) => {
if (!files || files.length === 0) return onChange(null);
const file = files[0];
const encodedFile = await createEncodedFile(file);
// convert to an object with an encoded file so that it can be handled in the backend and uploaded to s3
onChange({
name: file.name,
value: file,
value: encodedFile,
});
};

Expand Down

0 comments on commit 2e95776

Please sign in to comment.