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: useSubmitQuestionnaireController in packages/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

  1. Validation Phase:

    • Validates Firebase Report ID
    • Checks questionnaire data completeness
    • Validates check-in/check-out times
    • Pre-validates attachments using prevalidateReportAttachment
  2. 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

  3. Final Submission Phase:

    • Creates payload with all uploaded attachments, metadata, and timestamps
    • Submits via submitReportQuestionnaire function which calls mutateAsync
    • 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 attachments
  • generateSignaturePromise: Uploads signature attachments
  • generateSelfieSignaturePromise: Uploads selfie signature attachments

The upload process:

  1. Iterates through each attachment
  2. Checks if the file exists locally
  3. Uploads the file to remote storage
  4. Creates metadata and updates progress indicators

4.3 Handling Missing Attachments

Missing attachments are detected in two scenarios:

  1. Missing Local Files (Pre-upload):

    • Detected during pre-validation

    • Triggers ValidateReportAttachmentsError which contains:

      • questionAttachmentErrorsByIndex
      • signatureAttachmentErrorsByIndex
      • selfieSignatureAttachmentErrorsByIndex
    • Handled in handleAttachmentErrors which displays error modals:

      if (Object.keys(error?.questionAttachmentErrorsByIndex ?? {})?.length) {
        setQuestionsAttachmentErrorList(
          error.questionAttachmentErrorsByIndex || {}
        );
      }
      // Similar for signature and selfie errors
      setOpen(true);
  2. 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 setSubmitPromise preserves successful uploads for retry attempts

4.4 Upload Error Recovery

When uploads fail, the system provides recovery mechanisms:

  1. Upload Error Modal:

    • Shows detailed error information
    • Controlled by isAttachmentUploadErrorOpenAtom
  2. Retry Mechanism:

    • handleSubmitReportQuestionnaire function allows retrying uploads

    • Uses cached successful uploads from submitPromise atom:

      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,
        });
      };
  3. Sync Functionality:

    • The syncReport function allows syncing questionnaire data without attachment uploads
    • Helps preserve partial progress when full submission fails

5. Key Implementation Components

5.1 Attachment Validation

The attachment validation is implemented in validateReportAttachmentsUseCase which:

  1. Checks each question for required attachments
  2. Verifies if files exist locally on the device
  3. Builds detailed error maps by attachment index
  4. Throws ValidateReportAttachmentsError with 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 submitPromiseAtom to store successful uploads
  • Implemented handleSubmitReportQuestionnaire for 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 ValidateReportAttachmentsError type
  • Error Handling Controllers:

    • packages/app/features/questionnaire/questionnaire-summary/controller/attachment-error-list-controller.ts
    • packages/app/features/questionnaire/questionnaire-summary/controller/prevalidate-report-attachment-controller.ts
  • State Management:

    • packages/app/shared/state/questionnaire/questionnaire.ts
    • packages/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:

  1. Pre-validation: Checking file existence before upload attempts
  2. Error visibility: Replacing silent failures with user-facing errors
  3. State persistence: Preserving successful uploads for retry attempts
  4. Detailed monitoring: Comprehensive error tracking and performance monitoring
  5. 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.