From 2e957765919fa648a6cca9c191e8f905e64e22f1 Mon Sep 17 00:00:00 2001 From: Rohan Port Date: Wed, 15 May 2024 15:46:16 +1000 Subject: [PATCH] RN-1274: Reworked survey response resubmission in datatrak to use the new backend routes --- .../mutations/useResubmitSurveyResponse.ts | 104 ++---------------- .../api/mutations/useSubmitSurveyResponse.ts | 58 +--------- .../src/features/Questions/FileQuestion.tsx | 22 +++- 3 files changed, 32 insertions(+), 152 deletions(-) diff --git a/packages/datatrak-web/src/api/mutations/useResubmitSurveyResponse.ts b/packages/datatrak-web/src/api/mutations/useResubmitSurveyResponse.ts index 76b0b71e43..427c1f9c42 100644 --- a/packages/datatrak-web/src/api/mutations/useResubmitSurveyResponse.ts +++ b/packages/datatrak-web/src/api/mutations/useResubmitSurveyResponse.ts @@ -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, -) => { - 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( - 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; - 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 }, }); }, { diff --git a/packages/datatrak-web/src/api/mutations/useSubmitSurveyResponse.ts b/packages/datatrak-web/src/api/mutations/useSubmitSurveyResponse.ts index 8e3564b12b..72b4b05ee8 100644 --- a/packages/datatrak-web/src/api/mutations/useSubmitSurveyResponse.ts +++ b/packages/datatrak-web/src/api/mutations/useSubmitSurveyResponse.ts @@ -6,7 +6,6 @@ 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'; @@ -14,14 +13,7 @@ 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; @@ -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 => { - 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(); @@ -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( async (answers: AnswersT) => { if (!answers) { return; } - const formattedAnswers = await processAnswers(answers, questionsById); return post('submitSurveyResponse', { - data: { ...surveyResponseData, answers: formattedAnswers }, + data: { ...surveyResponseData, answers }, }); }, { diff --git a/packages/datatrak-web/src/features/Questions/FileQuestion.tsx b/packages/datatrak-web/src/features/Questions/FileQuestion.tsx index 54b25e4e7b..9932bbf169 100644 --- a/packages/datatrak-web/src/features/Questions/FileQuestion.tsx +++ b/packages/datatrak-web/src/features/Questions/FileQuestion.tsx @@ -25,18 +25,36 @@ const Wrapper = styled.div` } `; +type Base64 = string | null | ArrayBuffer; + +const createEncodedFile = (fileObject: File): Promise => { + 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, }); };