Missing Attachment Management and Report Submission
This document provides a deep technical overview of how report submissions work in the 2.0 App application, with a specific focus on attachment handling, error management, and recovery mechanisms for missing attachments.
1. Overview
The report submission process in Nimbly involves capturing and uploading various attachments (photos, signatures, selfies) as part of questionnaires. The system uses a multi-stage approach to validate, upload, and submit these attachments along with questionnaire data. The core challenge lies in maintaining data integrity across unstable networks, limited device storage, and various environmental factors that can lead to missing attachments.
2. Libraries and Dependencies
The attachment management process leverages several key libraries:
- React & React Native: Core framework for component rendering and native functionality
- @tanstack/react-query: For data mutation management in the submission process
- jotai: State management library used for storing attachment and questionnaire state
- apisauce: API client for making HTTP requests
- dayjs: Date utility for timestamps in logs and submissions
- Sentry (observabilitySentry): For error tracking and performance monitoring
- react-hook-form: For form state management and validation
- @nimbly-technologies/nimbly-common: Common types and utilities
3. Report Submission Flow Trace
3.1 Starting Point: Questionnaire Summary Controller
- Controller:
useSubmitQuestionnaireControllerinpackages/app/features/questionnaire/questionnaire-summary/controller/submit-questionnaire-controller.ts - Entry Function:
handleSubmitQuestionnaire - This controller serves as the entry point for submitting questionnaires with attachments
3.2 Submission Process Steps
-
Validation Phase:
- Validates Firebase Report ID
- Checks questionnaire data completeness
- Validates check-in/check-out times
- Pre-validates attachments using
prevalidateReportAttachment
-
Attachment Upload Phase:
-
Shows upload progress UI via
setIsQuestionnaireUploadProgressOpen(true) -
Generates attachment upload promises:
const questionPromises = generateAttachmentsPromise( questionnaireQuestions ); const signaturePromises = generateSignaturePromise( questionnaire.signatures ); const selfieSignaturePromises = generateSelfieSignaturePromise( questionnaire.selfieSignatures ); -
Uploads signatures and selfie signatures in parallel
-
Uploads question attachments after signatures complete
-
-
Final Submission Phase:
- Creates payload with all uploaded attachments, metadata, and timestamps
- Submits via
submitReportQuestionnairefunction which callsmutateAsync - Routes to success page on completion
4. Attachment Management
4.1 Pre-validation
Before starting the upload process, the system validates all attachments:
if (!forceSubmit && prevalidateReportAttachment) {
await validateReportAttachmentsUseCase.execute({
questions: questionnaire.questions as TrimmedQuestion[],
signatures: questionnaire.signatures || [],
selfieSignatures: questionnaire.selfieSignatures || [],
});
}This validation ensures attachments exist locally before attempting uploads, addressing one of the key issues that caused missing attachments.
4.2 Attachment Upload Process
Attachment uploading is handled by useUploadQuestionnaireAttachments in upload-questionnaire-attachment.ts, which provides three main functions:
generateAttachmentsPromise: Uploads question attachmentsgenerateSignaturePromise: Uploads signature attachmentsgenerateSelfieSignaturePromise: Uploads selfie signature attachments
The upload process:
- Iterates through each attachment
- Checks if the file exists locally
- Uploads the file to remote storage
- Creates metadata and updates progress indicators
4.3 Handling Missing Attachments
Missing attachments are detected in two scenarios:
-
Missing Local Files (Pre-upload):
-
Detected during pre-validation
-
Triggers
ValidateReportAttachmentsErrorwhich contains:questionAttachmentErrorsByIndexsignatureAttachmentErrorsByIndexselfieSignatureAttachmentErrorsByIndex
-
Handled in
handleAttachmentErrorswhich displays error modals:if (Object.keys(error?.questionAttachmentErrorsByIndex ?? {})?.length) { setQuestionsAttachmentErrorList( error.questionAttachmentErrorsByIndex || {} ); } // Similar for signature and selfie errors setOpen(true);
-
-
Failed Uploads:
-
Previously, upload failures were silently handled, causing data loss
-
Now, upload failures are captured and managed:
.catch((error) => { observabilitySentry.setSpanError(uploadSpan, error); setSubmitPromise({ questions: [], signatures, selfieSignature }); setAttachmentUploadErrorOpen(true); throw error; }) -
The
setSubmitPromisepreserves successful uploads for retry attempts
-
4.4 Upload Error Recovery
When uploads fail, the system provides recovery mechanisms:
-
Upload Error Modal:
- Shows detailed error information
- Controlled by
isAttachmentUploadErrorOpenAtom
-
Retry Mechanism:
-
handleSubmitReportQuestionnairefunction allows retrying uploads -
Uses cached successful uploads from
submitPromiseatom:const handleSubmitReportQuestionnaire = () => { const { questions, signatures, selfieSignature } = submitPromise; if (!questionnaireFuncLocal) { toast.show(LL.questionnaire.error.submitFailed(), { preset: "error", message: LL.questionnaire.error.submitFailedMsg(), }); return; } return submitReportQuestionnaire({ questions, questionnaire: questionnaireFuncLocal, signatures, selfieSignature, }); };
-
-
Sync Functionality:
- The
syncReportfunction allows syncing questionnaire data without attachment uploads - Helps preserve partial progress when full submission fails
- The
5. Key Implementation Components
5.1 Attachment Validation
The attachment validation is implemented in validateReportAttachmentsUseCase which:
- Checks each question for required attachments
- Verifies if files exist locally on the device
- Builds detailed error maps by attachment index
- Throws
ValidateReportAttachmentsErrorwith detailed information
5.2 Progress Tracking
Upload progress is tracked and displayed to users:
const setIsQuestionnaireUploadProgressOpen = useSetAtom(
isQuestionnaireUploadProgressOpenAtom
);
const setQuestionnaireUploadProgress = useSetAtom(
questionnaireUploadProgressAtom
);
const questionnaireTotalAttachments = useAtomValue(
questionnaireTotalAttachmentsAtom
);This allows users to see real-time upload progress and prevents premature cancellation of uploads.
5.3 Error Handling and Reporting
Errors are carefully tracked and reported:
observabilitySentry.logEvent("Submit report failed", errorInfo, "error");
observabilitySentry.logError(error, { report: errorInfo });
observabilitySentry.setSpanError(transaction, error);Each error is categorized:
- Missing attachment errors
- Upload failure errors
- Validation errors
- Network errors
5.4 Force Submit Option
For cases where attachments cannot be recovered, a forceSubmit parameter allows bypassing attachment validation:
if (!forceSubmit && prevalidateReportAttachment) {
await validateReportAttachmentsUseCase.execute({
questions: questionnaire.questions as TrimmedQuestion[],
signatures: questionnaire.signatures || [],
selfieSignatures: questionnaire.selfieSignatures || [],
});
}6. Common Issues and Solutions
6.1 Silent Upload Failures
Previous Issue: In upload-questionnaire-attachment.ts, upload errors would silently return empty arrays, causing data loss:
// Previous problematic code
uploadQuestionAttachments().catch(() => {
return []; // Silent failure losing all attachments
});Solution:
- Error bubbling to UI for visibility
- Error state persistence for retries
- Detailed error tracking in Sentry
6.2 Upload State Persistence
Previous Issue: No tracking of upload progress/state between attempts in submit-questionnaire-controller.ts
Solution:
- Added
submitPromiseAtomto store successful uploads - Implemented
handleSubmitReportQuestionnairefor retry with preserved state - Progress tracking for user awareness
6.3 Pre-upload Storage Validation
Previous Issue: No storage space check before starting uploads
Solution:
- Added pre-validation phase
- File existence checks before upload attempts
- Storage space validation (implemented/planned)
7. File Paths and Code References
Key file paths associated with attachment management:
-
Submission Controller:
packages/app/features/questionnaire/questionnaire-summary/controller/submit-questionnaire-controller.ts- Contains core submission logic
- Handles attachment validation and uploads
- Manages error states and recovery
-
Attachment Upload:
packages/app/features/questionnaire/questionnaire-summary/controller/upload-questionnaire-attachment.ts- Handles the actual file uploads
- Creates attachment metadata
- Reports upload progress
-
Attachment Validation:
packages/app/shared/domain/usecase/report-usecase/validate-report-attachments-usecase.ts- Validates attachment existence
- Creates detailed error maps
- Defines
ValidateReportAttachmentsErrortype
-
Error Handling Controllers:
packages/app/features/questionnaire/questionnaire-summary/controller/attachment-error-list-controller.tspackages/app/features/questionnaire/questionnaire-summary/controller/prevalidate-report-attachment-controller.ts
-
State Management:
packages/app/shared/state/questionnaire/questionnaire.tspackages/app/shared/state/attachment-metadata/attachment-metadata.ts
8. Summary
The report submission process in Nimbly has been significantly enhanced to address missing attachment issues through:
- Pre-validation: Checking file existence before upload attempts
- Error visibility: Replacing silent failures with user-facing errors
- State persistence: Preserving successful uploads for retry attempts
- Detailed monitoring: Comprehensive error tracking and performance monitoring
- Graceful degradation: Allowing force submission when attachments cannot be recovered
These improvements ensure more reliable questionnaire submissions, reducing data loss and improving the user experience even in challenging network and storage conditions.