Visitar URL original
[Enumerators] Propagate the backfilled initial question to other programs that use the list set by chelseakr-exygy · Pull Request #14344 · civiform/civiform · GitHub
Skip to content

[Enumerators] Propagate the backfilled initial question to other programs that use the list set - #14344

Open
chelseakr-exygy wants to merge 1 commit into
mainfrom
chelseakr-exygy/13419-propagate-initial-question
Open

chelseakr-exygy wants to merge 1 commit into
mainfrom
chelseakr-exygy/13419-propagate-initial-question

Conversation

@chelseakr-exygy

@chelseakr-exygy chelseakr-exygy commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Description

Step 4 of the backfill flow. When an admin saves an initial question on a legacy enumerator (#14328), the question is now also added to every other draft program block that uses the same enumerator. Saving already makes drafts of the published programs that use it, so this covers drafts and published programs alike. The initial question is shared between them, not copied again. The staged card's warning also says how many other programs will get it ("This list set is also used in 2 other programs, which will get the initial question too.").

TDD Step 4 says to run the save and propagation in one transaction so a failure rolls everything back. I tried that, and it breaks the save: addQuestionsToBlock and the program lookups read on other threads, which can't see uncommitted changes, so adding the initial question to the current block is refused. The TDD allows for this and suggests reverting what was done instead, so that is what this does. If anything fails after the initial question is linked, it removes the question from the blocks it was added to and unlinks it from the enumerator, then returns the error and leaves the bank open so the admin can retry. The copied initial question stays in the question bank, as the TDD notes, and so do any drafts made for published programs. Each undo step is logged if it fails.

For review: is the revert approach OK in place of the transaction? The alternative is passing the transaction through the async lookups, which touches a lot of code.

This change was AI-drafted (Claude Code) under my direction.

Checklist

General

  • Added the correct label (see docs for more info): < feature | enhancement | bug | under-development | dependencies | infrastructure | ignore-for-release | database >
  • Assigned to a specific person, civiform/developers, or a more specific round-robin list
  • Added an additional reviewer from civiform/eng-admin as FYI (if this PR includes functionality changes and neither you nor the primary reviewer is an admin)
  • Added an additional reviewer as required if changes potentially affect the security of the application.
  • Removed the release notes section if the title is sufficient for the release notes description, or put more details in that section.
  • Created unit and/or browser tests which fail without the change (if possible)
  • Performed manual testing (Chrome and Firefox if it includes front-end changes) (Chromium through the browser test; Firefox through a session driven by Claude Code; Safari by hand by me, see below)
  • Extended the README / documentation, if necessary. For user-facing features, consider updating the user docs. For "under-the-hood" changes or things more relevant to developers, consider updating the dev wiki.
  • Ensured PII wasn't added to any new logs, unless it was guarded by isDevOrStaging
  • Noted in the PR description which, if any, code in this PR was generated by AI.

Instructions for manual testing

  1. Turn on ENUMERATOR_IMPROVEMENTS_ENABLED.
  2. Seed sample questions and programs at /dev/seed and log in as an admin. The comprehensive sample program already uses the "Sample Enumerator Question", which has no initial question.
  3. Open the minimal sample program, click "Add list set", choose "Choose existing", and add "Sample Enumerator Question".
  4. Click "Add initial question" and pick a question. The warning says the list set is also used in 1 other program.
  5. Click "Save initial question". Open the comprehensive sample program's draft: the enumerator has the same initial question. The program's JSON export (/admin/export?programId=<id>) shows it on the enumerator block's questions.

I ran the full end_to_end_enumerators.test.ts in Chromium against a local app on this branch (25 passed, 7 already skipped), including a new test for the count. In Firefox, a session driven by Claude Code ran the steps above and checked that both programs' enumerator blocks have the initial question. I also clicked through this by hand in Safari: picked the initial question from the bank (using the search so only one question was listed), checked that the card showed the pick and the warning with "also used in 1 other program", checked that leaving the screen discarded the pick, saved it, and checked the initial question on the edit forms of the enumerator in both programs. This also covers the backfill flow from #14328. The unit tests for the touched classes pass (272). The tests that fail without the change are hxBackfillInitialQuestion_propagatesInitialQuestionToOtherDraftProgramsUsingEnumerator, hxBackfillInitialQuestion_propagatesInitialQuestionToOtherActiveProgramsUsingEnumerator, hxBackfillInitialQuestion_whenPropagationFails_savesNothing, and hxSelectInitialQuestion_onLegacyEnumeratorBlock_warningSaysHowManyOtherProgramsUseIt. Not covered here: the browser test only checks the count, and propagation is checked by the unit tests and the Claude Code-driven Firefox session. Accessibility of the longer warning text was not checked.

Issue(s) this completes

Fixes #13419

@chelseakr-exygy chelseakr-exygy added the under-development Features that are hidden behind a feature flag and not yet ready for staging or production testing label Oct 22, 2026
@chelseakr-exygy chelseakr-exygy self-assigned this Oct 22, 2026
@github-actions github-actions Bot added the i18n Related to internationalization label Oct 22, 2026
@codecov

codecov Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.36232% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.59%. Comparing base (231fc0c) to head (013dca2).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
...rs/admin/AdminProgramBlockQuestionsController.java 73.68% 10 Missing and 5 partials ⚠️
server/app/services/program/ProgramService.java 80.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main   #14344      +/-   ##
============================================
- Coverage     82.60%   82.59%   -0.01%     
- Complexity     5767     5776       +9     
============================================
  Files           617      617              
  Lines         26350    26416      +66     
  Branches       2123     2132       +9     
============================================
+ Hits          21766    21818      +52     
- Misses         3851     3859       +8     
- Partials        733      739       +6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@chelseakr-exygy
chelseakr-exygy force-pushed the chelseakr-exygy/13419-propagate-initial-question branch from 1cfeddc to 24958a7 Compare October 22, 2026 22:02
@chelseakr-exygy
chelseakr-exygy force-pushed the chelseakr-exygy/13420-save-initial-question-backfill branch from 38f1b69 to ccf5fce Compare October 22, 2026 22:11
@chelseakr-exygy
chelseakr-exygy force-pushed the chelseakr-exygy/13419-propagate-initial-question branch from 24958a7 to 9f92ca9 Compare October 22, 2026 22:13
Base automatically changed from chelseakr-exygy/13420-save-initial-question-backfill to main October 26, 2026 15:33
@chelseakr-exygy
chelseakr-exygy force-pushed the chelseakr-exygy/13419-propagate-initial-question branch from 9f92ca9 to 013dca2 Compare October 26, 2026 15:41
@chelseakr-exygy
chelseakr-exygy marked this pull request as ready for review October 26, 2026 21:30
Comment thread server/conf/i18n/messages
# Warning shown on the list set screen after an admin picks an initial question for a list set that does not have one yet, before they save it
alert.repeatedSet.backfillSaveWarning=The initial question will be applied to any uses of this repeated set across programs. You will not be able to change the initial question after you save it.
# Added after the save warning when exactly one other program also uses this list set
alert.repeatedSet.backfillSaveWarningOneProgram=This list set is also used in 1 other program, which will get the initial question too.

@melanie-exygy melanie-exygy Oct 28, 2026 •

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.

issue (blocking): I don't think this wording was reviewed by UX or Product since I don't see it in the mocks. I just sent the wording to UX for review. Here's is Hannan's recommendation: First sentence visibility depends if programs > 0:

"This will also update X program(s) [bolded] that use(s) this list set. You will not be able to remove the initial question after you save it."

Comment thread server/conf/i18n/messages
@@ -1259,6 +1259,10 @@ description.repeatedSet.initialQuestionAdminId=This is the question that the app
description.repeatedSet.backfillInitialQuestion=Add an initial question that accompanies the enumerator selected above. For example if your enumerator asks users to list members of their household, you might choose a name question.
# Warning shown on the list set screen after an admin picks an initial question for a list set that does not have one yet, before they save it
alert.repeatedSet.backfillSaveWarning=The initial question will be applied to any uses of this repeated set across programs. You will not be able to change the initial question after you save it.

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.

suggestion (non-blocking): Sorry I didn't catch this last time, but the wording here should be "list set" rather than "repeated set". The term was updated since the mocks were created and it seems the mocks weren't updated.

)
})

test('warns how many other programs will also get the initial question', async ({

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.

suggestion (non-blocking): This test could be merged into the one above it. Since our browser tests take so long to run and we don't use beforeAll, the eng team decided that a good solution was to merge tests rather than perform the same setup for separate tests.

saved.initialQuestion().getId(),
fileUploadQuestionImprovementsEnabled,
addedTo);
} catch (Exception e) {

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.

suggestion (non-blocking): When propagation fails, the checked exceptions (e.g. CantAddQuestionToBlockException when another program's block already has the question) are turned into a 404 by the outer catch without being logged. That leaves no server-side record of the failure.

Could you log before reverting? Something like:

Suggested change
} catch (Exception e) {
} catch (Exception e) {
logger.error(
"Could not add initial question {} to every block that uses enumerator {}; reverting",
saved.initialQuestion().getId(),
saved.enumeratorQuestion().getId(),
e);

Also, right now the user will not know that there was an error because HTMX swallows errors and doesn't display an error page. We don't need to take care of that in this PR. The user-facing error can be handled in #13587.

blockId);

assertThat(result.status()).isNotEqualTo(OK);
// The bank stays open so the admin can retry.

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.

issue (blocking): The question bank is already closed by the time Save can be clicked (hxSelectInitialQuestion closes it via HX-Trigger-After-Swap, and the success test at line 555 says so), so this comment isn't accurate. What stays on screen is the staged card with its Save button.

The next assertion doesn't distinguish failure from success either: it checks HX-Trigger rather than HX-Trigger-After-Swap, and the success path has no HX-Trigger too. I think these two lines can be removed. The checks below already verify the revert.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

i18n Related to internationalization under-development Features that are hidden behind a feature flag and not yet ready for staging or production testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Enumerator] Propagate the initial question to every other block (in any program) that references the same enumerator

2 participants