Skip to content

rn-1810(datatrak): Datatrak survey drafts 1#6657

Closed
tcaiger wants to merge 10 commits intodevfrom
rn-1810-datatrak-survey-drafts-1
Closed

rn-1810(datatrak): Datatrak survey drafts 1#6657
tcaiger wants to merge 10 commits intodevfrom
rn-1810-datatrak-survey-drafts-1

Conversation

@tcaiger
Copy link
Copy Markdown
Contributor

@tcaiger tcaiger commented Mar 3, 2026

Issue #:

Changes:

  • Example

Screenshots:

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant new feature that allows users to save their progress on surveys as drafts. This enhances the user experience by providing flexibility and preventing data loss for long or complex surveys. The changes span across the database, backend API, and frontend application, establishing a complete system for managing survey response drafts from creation to submission or deletion.

Highlights

  • Survey Drafts Feature: Introduced the ability for users to save in-progress survey responses as drafts, allowing them to resume filling out surveys at a later time.
  • Database Schema Update: Added a new survey_response_draft table to the database to store draft survey data, including form data, screen number, and user information.
  • API Endpoints for Drafts: Implemented new API routes and associated logic for creating, retrieving, updating, and deleting survey response drafts.
  • Frontend Integration: Integrated 'Save & exit' functionality into the survey UI, allowing users to save their progress. Drafts are now displayed on the landing page and can be resumed or deleted.
  • Draft Management on Submission: Ensured that any active draft is automatically deleted when the corresponding survey response is successfully submitted.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • packages/database/src/core/migrations/20260303000001-addSurveyResponseDraftTable-modifies-schema.js
    • Added a new migration script to create the survey_response_draft table with fields for ID, survey ID, user ID, country code, entity ID, start time, form data (JSONB), screen number, and updated timestamp.
  • packages/database/src/core/modelClasses/SurveyResponseDraft.js
    • Added SurveyResponseDraftRecord and SurveyResponseDraftModel classes to represent the new survey draft entity in the database layer.
  • packages/database/src/core/modelClasses/index.js
    • Updated to import and export the new SurveyResponseDraftModel.
  • packages/database/src/core/records.js
    • Added SURVEY_RESPONSE_DRAFT to the RECORDS constant for database record identification.
  • packages/database/src/core/sync/initSyncComponents.js
    • Added survey_response_draft to the list of tables included in synchronization processes.
  • packages/datatrak-web-server/src/app/createApp.ts
    • Imported and registered new API routes for GET, POST, PUT, and DELETE operations on survey response drafts.
  • packages/datatrak-web-server/src/routes/EntitiesRoute.ts
    • Updated entity query to remove updated_at_sync_tick from selected columns and added a default value for it in the mapping.
  • packages/datatrak-web-server/src/routes/SurveyResponseDraft/DeleteSurveyResponseDraftRoute.ts
    • Added a new route for handling the deletion of a specific survey response draft, including permission checks.
  • packages/datatrak-web-server/src/routes/SurveyResponseDraft/GetSurveyResponseDraftsRoute.ts
    • Added a new route for retrieving a user's survey response drafts, enriching them with survey and entity details.
  • packages/datatrak-web-server/src/routes/SurveyResponseDraft/SaveSurveyResponseDraftRoute.ts
    • Added a new route for creating and saving a new survey response draft.
  • packages/datatrak-web-server/src/routes/SurveyResponseDraft/UpdateSurveyResponseDraftRoute.ts
    • Added a new route for updating an existing survey response draft, including permission checks.
  • packages/datatrak-web-server/src/routes/SurveyResponseDraft/index.ts
    • Exported all new survey response draft routes for modular organization.
  • packages/datatrak-web-server/src/routes/index.ts
    • Exported the new survey response draft routes from the main routes index.
  • packages/datatrak-web-server/src/types.ts
    • Imported SurveyResponseDraftModel and added it to the DatatrakWebServerModelRegistry.
  • packages/datatrak-web/src/api/mutations/index.ts
    • Exported new React Query mutation hooks for saving, updating, and deleting survey response drafts.
  • packages/datatrak-web/src/api/mutations/useDeleteSurveyResponseDraft.ts
    • Added a new React Query mutation hook for deleting survey response drafts, supporting both local and remote operations.
  • packages/datatrak-web/src/api/mutations/useSaveSurveyResponseDraft.ts
    • Added a new React Query mutation hook for saving survey response drafts, supporting both local and remote operations.
  • packages/datatrak-web/src/api/mutations/useSubmitSurveyResponse.ts
    • Modified the useSubmitSurveyResponse hook to delete an associated draft upon successful survey submission.
  • packages/datatrak-web/src/api/mutations/useUpdateSurveyResponseDraft.ts
    • Added a new React Query mutation hook for updating survey response drafts, supporting both local and remote operations.
  • packages/datatrak-web/src/api/queries/index.ts
    • Exported ContextualMutationFunctionContext type and the new useSurveyResponseDrafts query hook.
  • packages/datatrak-web/src/api/queries/useSurveyResponseDrafts.ts
    • Added a new React Query hook for fetching a user's survey response drafts, supporting both local and remote data sources.
  • packages/datatrak-web/src/database/index.ts
    • Exported all survey response draft database operations.
  • packages/datatrak-web/src/database/surveyResponseDraft/createSurveyResponseDraft.ts
    • Added a local database function to create a new survey response draft.
  • packages/datatrak-web/src/database/surveyResponseDraft/deleteSurveyResponseDraft.ts
    • Added a local database function to delete a survey response draft by ID.
  • packages/datatrak-web/src/database/surveyResponseDraft/index.ts
    • Exported local database functions for creating, updating, and deleting survey response drafts.
  • packages/datatrak-web/src/database/surveyResponseDraft/updateSurveyResponseDraft.ts
    • Added a local database function to update an existing survey response draft by ID.
  • packages/datatrak-web/src/features/Survey/Components/MobileSurveyMenu.tsx
    • Added a 'Save & exit' button to the mobile survey menu, conditionally displayed based on the survey state.
  • packages/datatrak-web/src/features/Survey/Components/SurveyPaginator.tsx
    • Added a 'Save & exit' button to the survey paginator, conditionally displayed and integrated with draft saving logic.
  • packages/datatrak-web/src/features/Survey/SurveyContext/SurveyContext.tsx
    • Updated the SurveyContext to manage draftId and isDraft states, and to load draft data when resuming a survey from a draft.
  • packages/datatrak-web/src/features/Survey/SurveyContext/reducer.ts
    • Updated SurveyFormContextType to include draftId and isDraft properties.
  • packages/datatrak-web/src/features/Survey/hooks/useSaveAsDraft.ts
    • Added a new hook useSaveAsDraft to encapsulate the logic for saving or updating survey responses as drafts.
  • packages/datatrak-web/src/types/model.ts
    • Imported SurveyResponseDraftModel and added it to the DatatrakWebModelRegistry.
  • packages/datatrak-web/src/views/LandingPage/DraftSurveysSection.tsx
    • Added a new DraftSurveysSection component to display a list of saved survey drafts on the landing page.
  • packages/datatrak-web/src/views/LandingPage/LandingPage.tsx
    • Integrated the DraftSurveysSection into the LandingPage layout, adjusting the grid based on the presence of drafts.
  • packages/server-boilerplate/src/index.ts
    • Exported SurveyResponseDraftModel and SurveyResponseDraftRecord from the server boilerplate.
  • packages/tsmodels/src/models/SurveyResponseDraft.ts
    • Added new TypeScript interfaces for SurveyResponseDraftRecord and SurveyResponseDraftModel.
  • packages/tsmodels/src/models/index.ts
    • Exported the new SurveyResponseDraftModel and SurveyResponseDraftRecord.
  • packages/types/src/types/models.ts
    • Defined new TypeScript interfaces for SurveyResponseDraft, SurveyResponseDraftCreate, and SurveyResponseDraftUpdate.
  • packages/types/src/types/requests/datatrak-web-server/DeleteSurveyResponseDraftRequest.ts
    • Defined TypeScript types for the delete survey response draft API request.
  • packages/types/src/types/requests/datatrak-web-server/SaveSurveyResponseDraftRequest.ts
    • Defined TypeScript types for the save survey response draft API request.
  • packages/types/src/types/requests/datatrak-web-server/SurveyResponseDraftsRequest.ts
    • Defined TypeScript types for the get survey response drafts API request and response structure.
  • packages/types/src/types/requests/datatrak-web-server/UpdateSurveyResponseDraftRequest.ts
    • Defined TypeScript types for the update survey response draft API request.
  • packages/types/src/types/requests/datatrak-web-server/index.ts
    • Exported all new survey response draft request types.
  • packages/types/src/types/requests/index.ts
    • Exported the new survey response draft request types from the main requests index.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the functionality for survey response drafts, a significant feature addition. The implementation spans the backend and frontend, including database changes, new API endpoints, and UI components for managing drafts. The code is generally well-structured. My feedback focuses on improving database performance by addressing N+1 query patterns, removing leftover debugging code, and enhancing data type consistency in the database schema.

Comment on lines +32 to +56
const enrichedDrafts = await Promise.all(
drafts.map(async draft => {
const survey = draft.survey_id
? await models.survey.findById(draft.survey_id as string)
: null;

const entity = draft.entity_id
? await models.entity.findById(draft.entity_id as string)
: null;

return {
id: draft.id,
surveyId: draft.survey_id,
surveyCode: survey?.code ?? null,
surveyName: survey?.name ?? null,
countryCode: (draft.country_code as string) ?? entity?.country_code ?? null,
entityId: draft.entity_id ?? null,
entityName: entity?.name ?? null,
startTime: draft.start_time ?? null,
formData: draft.form_data,
screenNumber: draft.screen_number,
updatedAt: draft.updated_at,
};
}),
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation fetches details for each draft individually within a Promise.all, which can lead to an N+1 query problem and cause performance issues if a user has many drafts. To optimize this, you can collect all survey_ids and entity_ids and fetch them in two batch queries. This will significantly reduce the number of database calls.

    const surveyIds = drafts.map(draft => draft.survey_id).filter(Boolean);
    const entityIds = drafts.map(draft => draft.entity_id).filter(Boolean);

    const [surveys, entities] = await Promise.all([
      models.survey.findManyById(surveyIds as string[]),
      models.entity.findManyById(entityIds as string[]),
    ]);

    const surveysById = new Map(surveys.map(s => [s.id, s]));
    const entitiesById = new Map(entities.map(e => [e.id, e]));

    const enrichedDrafts = drafts.map(draft => {
      const survey = draft.survey_id ? surveysById.get(draft.survey_id as string) : null;
      const entity = draft.entity_id ? entitiesById.get(draft.entity_id as string) : null;

      return {
        id: draft.id,
        surveyId: draft.survey_id,
        surveyCode: survey?.code ?? null,
        surveyName: survey?.name ?? null,
        countryCode: (draft.country_code as string) ?? entity?.country_code ?? null,
        entityId: draft.entity_id ?? null,
        entityName: entity?.name ?? null,
        startTime: draft.start_time ?? null,
        formData: draft.form_data,
        screenNumber: draft.screen_number,
        updatedAt: draft.updated_at,
      };
    });

Comment on lines +19 to +38
return Promise.all(
drafts.map(async draft => {
const survey = draft.survey_id ? await models.survey.findById(draft.survey_id) : null;
const entity = draft.entity_id ? await models.entity.findById(draft.entity_id) : null;

return {
id: draft.id,
surveyId: draft.survey_id,
surveyCode: survey?.code ?? null,
surveyName: survey?.name ?? null,
countryCode: draft.country_code ?? entity?.country_code ?? null,
entityId: draft.entity_id ?? null,
entityName: entity?.name ?? null,
startTime: draft.start_time ?? null,
formData: draft.form_data,
screenNumber: draft.screen_number,
updatedAt: draft.updated_at,
};
}),
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This logic for fetching local drafts introduces an N+1 query problem by fetching survey and entity details for each draft individually. This can be inefficient, especially in an offline-first scenario on devices with limited resources. It's better to batch these queries by first collecting all necessary IDs and then fetching them in a minimal number of queries.

    const surveyIds = drafts.map(draft => draft.survey_id).filter(Boolean);
    const entityIds = drafts.map(draft => draft.entity_id).filter(Boolean);

    const [surveys, entities] = await Promise.all([
      models.survey.findManyById(surveyIds),
      models.entity.findManyById(entityIds),
    ]);

    const surveysById = new Map(surveys.map(s => [s.id, s]));
    const entitiesById = new Map(entities.map(e => [e.id, e]));

    return drafts.map(draft => {
      const survey = draft.survey_id ? surveysById.get(draft.survey_id) : null;
      const entity = draft.entity_id ? entitiesById.get(draft.entity_id) : null;

      return {
        id: draft.id,
        surveyId: draft.survey_id,
        surveyCode: survey?.code ?? null,
        surveyName: survey?.name ?? null,
        countryCode: draft.country_code ?? entity?.country_code ?? null,
        entityId: draft.entity_id ?? null,
        entityName: entity?.name ?? null,
        startTime: draft.start_time ?? null,
        formData: draft.form_data,
        screenNumber: draft.screen_number,
        updatedAt: draft.updated_at,
      };
    });

user_id TEXT NOT NULL,
country_code TEXT,
entity_id TEXT,
start_time TEXT,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The start_time column is defined as TEXT. For consistency with the updated_at column and for better data integrity, it would be more appropriate to use a TIMESTAMP or TIMESTAMPTZ data type for storing date/time values.

Suggested change
start_time TEXT,
start_time TIMESTAMP,

@tcaiger
Copy link
Copy Markdown
Contributor Author

tcaiger commented Mar 10, 2026

Superseded by a stack of smaller PRs for easier review:

@tcaiger tcaiger closed this Mar 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant