Skip to content

feat(#10904): route file attachments to correct sub-docs in report forms#10922

Merged
jkuester merged 6 commits into
medic:10700-photo-capture-in-sub-contacts-and-reportsfrom
YASHSHARMAOFFICIALLY:10904-sub-contact-photo-reports-routing
May 19, 2026
Merged

feat(#10904): route file attachments to correct sub-docs in report forms#10922
jkuester merged 6 commits into
medic:10700-photo-capture-in-sub-contacts-and-reportsfrom
YASHSHARMAOFFICIALLY:10904-sub-contact-photo-reports-routing

Conversation

@YASHSHARMAOFFICIALLY
Copy link
Copy Markdown
Contributor

@YASHSHARMAOFFICIALLY YASHSHARMAOFFICIALLY commented Apr 24, 2026

Summary

Closes #10904

When a report form contains [db-doc="true"] sub-documents with binary/file fields, attachments are now routed to the owning sub-document instead of always attaching to the main report doc.

Changes

  • resolveOwnerDoc() — walks up the XML tree from a binary node to find the nearest [db-doc="true"] ancestor, returning the corresponding prepared sub-doc (or the main report doc as fallback)
  • findBinaryNodeByFilename() — locates the [type=binary] XML node matching a FileManager filename so its position in the tree can determine the owner
  • FileManager file routing — uploaded files are now attached to the correct owner doc based on their position in the XML form
  • Inline binary blob routing — base64-encoded binary fields inside sub-docs are attached to those sub-docs

Test plan

  • Unit tests pass (2652 passing)
  • Lint passes
  • Binary field in main doc attaches to main report doc
  • Binary field inside [db-doc="true"] attaches to that sub-doc
  • FileManager file upload inside sub-doc routes correctly
  • Existing report forms without sub-docs are unaffected

@benkags
Copy link
Copy Markdown
Contributor

benkags commented Apr 24, 2026

@YASHSHARMAOFFICIALLY let's use the branch medic:10700-photo-capture-in-sub-contacts-and-reports as the base branch.

@YASHSHARMAOFFICIALLY YASHSHARMAOFFICIALLY force-pushed the 10904-sub-contact-photo-reports-routing branch from 8928897 to bfb5ec2 Compare April 25, 2026 15:22
@YASHSHARMAOFFICIALLY YASHSHARMAOFFICIALLY changed the base branch from master to 10700-photo-capture-in-sub-contacts-and-reports April 25, 2026 15:23
@YASHSHARMAOFFICIALLY
Copy link
Copy Markdown
Contributor Author

YASHSHARMAOFFICIALLY commented Apr 25, 2026

Done, updated the base branch to 10700-photo-capture-in-sub-contacts-and-reports. Ready for review! @benkags @jkuester @dianabarsan

Copy link
Copy Markdown
Contributor

@benkags benkags left a comment

Choose a reason for hiding this comment

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

Thank you @YASHSHARMAOFFICIALLY. I've left a few review comments.

private findBinaryNodeByFilename($record, filename: string) {
let match = null;
$record
.find('[type=binary]')
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.

You want to ensure the you check for type=file. When testing #10923, I realized Enketo sets type to file; so uploads would fail silently. Did

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — updated the selector from [type=binary] to [type=file] in 900c9550a. Enketo's Nodeset.setVal rewrites file-widget nodes to type="file" after upload, so this is the correct selector for FileManager files. Inline-binary blobs (draw/signature widgets) still use type="binary" and are handled via the separate xpath-based path.

const attachLegacyFile = (elem, file, type, alreadyEncoded) => {
const ownerDoc = resolveOwnerDoc(elem);
const xpath = Xpath.getElementXPath(elem);
const formId = ownerDoc === doc ? doc.form : ownerDoc.type;
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.

doc.form and doc.type are two different types. And type is not always unique. Let's just use doc.form. What's the rationale for using type here if may ask?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You're right — switched to doc.form for the xpath root segment in 900c9550a. Using doc.type was incorrect since it's not guaranteed to be unique and serves a different purpose. The xpath-based filename is now always rooted at the main form's internalId regardless of which sub-doc owns the attachment.

expect(actual.length).to.equal(1);
expect(AddAttachment.callCount).to.equal(1);
expect(AddAttachment.args[0][0]._id).to.equal(actual[0]._id);
});
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.

We should add 2 more unit tests: 1) test repeats 2) test for clean up of attachments removed during edit

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added both in 5da5cc194:

  1. Repeats — unit test verifies files inside db-doc blocks nested in repeat sections route to the corresponding sub-docs, with no files landing on the main doc.
  2. Attachment cleanup on edit — unit test verifies that when a file field is cleared during edit, no attachment is created on the sub-doc.

@benkags
Copy link
Copy Markdown
Contributor

benkags commented May 5, 2026

@YASHSHARMAOFFICIALLY take a look at YASHSHARMAOFFICIALLY#1

@dianabarsan dianabarsan self-requested a review May 6, 2026 06:10
@benkags
Copy link
Copy Markdown
Contributor

benkags commented May 7, 2026

@YASHSHARMAOFFICIALLY let me know if you are still working on this

@YASHSHARMAOFFICIALLY
Copy link
Copy Markdown
Contributor Author

@benkags will raised a pr by today night working on these

@benkags
Copy link
Copy Markdown
Contributor

benkags commented May 7, 2026

@YASHSHARMAOFFICIALLY I added the fixes in the linked PR, please take a look, add an e2e test so that it is ready for review.

YASHSHARMAOFFICIALLY and others added 3 commits May 8, 2026 00:04
…rt forms

When a report form contains [db-doc="true"] sub-documents with binary/file
fields, attachments are now routed to the owning sub-document instead of
always attaching to the main report doc.

- Add resolveOwnerDoc() to walk up XML tree to nearest db-doc ancestor
- Route FileManager file uploads to correct owner doc
- Route inline binary blobs to correct owner doc
- Fall back to main report doc when element is not inside a sub-doc
…oc attachments

- findFileNodeByFilename now selects [type=file], matching Enketo's
  runtime XML state
- use doc.form for the xpath-rooted filename
- additional tests: orphan file fallback, db-doc nested in repeats, and filename shape for sub-doc binaries.
… sub-documents

Verifies that when a form has a db-doc sub-document with a file upload
field, the uploaded file is attached to the sub-doc and not the main
report document.
@YASHSHARMAOFFICIALLY YASHSHARMAOFFICIALLY force-pushed the 10904-sub-contact-photo-reports-routing branch from bfb5ec2 to cb6c753 Compare May 7, 2026 18:35
@YASHSHARMAOFFICIALLY
Copy link
Copy Markdown
Contributor Author

@benkags Do check and let me know if any improvment needed further

…removed during edit

Verify that when a file is cleared from a sub-doc during editing,
no attachment lands on the sub-doc while the main doc still
receives its own file correctly.
@YASHSHARMAOFFICIALLY
Copy link
Copy Markdown
Contributor Author

YASHSHARMAOFFICIALLY commented May 12, 2026

@benkags @dianabarsan @andrablaj Thank you for the fixes and the review guidance. Addressed the remaining items:

Unit tests added:

  1. Repeats (db-doc-in-repeat-with-files): Verifies each repeat instance's file routes to the corresponding sub-doc, with nothing landing on the main doc.
  2. Attachment cleanup on edit (db-doc-with-file-field-cleared): Verifies that when a file is cleared from a sub-doc during editing, no attachment lands on the sub-doc while the main doc still receives its own file correctly.

Both use dedicated XML fixtures to isolate the scenario.

The e2e test (submit-db-doc-file-upload.wdio-spec.js) from the previous commit covers the full end-to-end flow: uploading files to both the main doc and a sub-doc, then verifying each attachment ended up on the correct document in the database.

Ready for re-review.

Copy link
Copy Markdown
Member

@dianabarsan dianabarsan left a comment

Choose a reason for hiding this comment

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

Code looks solid, and makes sense on a quick read. Appreciate that.
I need to request that we add an e2e test for this feature, that involves having a form with multiple sub-docs and test that attachments are saved correctly. We should also cover sub-docs in repeats.

Appreciate this contribution!

…-docs with file attachments

Cover the two scenarios requested in review: a form with multiple
sub-documents each owning separate file attachments, and sub-documents
created inside repeat groups with per-instance file routing.
@YASHSHARMAOFFICIALLY
Copy link
Copy Markdown
Contributor Author

YASHSHARMAOFFICIALLY commented May 12, 2026

@dianabarsan @andrablaj Thank you for the review. I've added the requested e2e coverage in e0229410f:

  1. Multiple sub-docs — new form (db-doc-multi-file-upload) with two separate db-doc="true" sub-documents, each with its own file upload. The test verifies each sub-doc receives only its own attachment and checks for cross-contamination between all three documents (main, sub-doc A, sub-doc B).

  2. Sub-docs in repeats — new form (db-doc-repeat-file-upload) with a repeat group containing a db-doc="true" block with a file upload. The test creates two repeat instances with different files and verifies each repeat's sub-doc gets the correct attachment, with no files leaking to the main doc or between repeat sub-docs.

Happy to contribute more

@benkags
Copy link
Copy Markdown
Contributor

benkags commented May 14, 2026

@dianabarsan please take another look and let us know if there is additional changes needed. Thanks.

@dianabarsan dianabarsan self-requested a review May 14, 2026 18:01
Copy link
Copy Markdown
Member

@dianabarsan dianabarsan left a comment

Choose a reason for hiding this comment

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

Thanks for the followup!

Edge case worth covering: if two sub-docs end up with files of the same name (e.g., default-named camera captures, or any form where the user picks files with overlapping names from disk), findFileNodeByFilename returns the first match and both files route to the same sub-doc — the other sub-doc gets no attachment, and the user-file-<name>
FileManager attachment key collides on top of that.

The e2e tests use distinct filenames (icon.png, layers.png, photo-for-upload-form.png), so this isn't exercised. Could you add a case with two sub-docs whose files have the same name and decide what the desired behavior is? At minimum I think we want to detect and warn rather than silently drop one file.

Could we also get an e2e test covering a draw/signature widget inside a db-doc="true" sub-doc? In production the draw widget puts its value in FileManager with a generated filename (drawing-HH_MM_SS.pngwebapp/src/js/enketo/widgets/draw.js:674), so it goes through the same findFileNodeByFilename routing the PR
rewires. That path is only tested with upload widgets right now.
Can we also have an e2e test covering binary uploads in subdocs?

While you're there: those generated filenames are second-resolution, so a user signing two sub-doc sections within the same second produces colliding filenames — which is the same routing/collision concern I raised on the upload path, just more likely to hit because the user doesn't choose the filename.

// actual[0] = main doc, actual[1] = first repeat doc, actual[2] = second repeat doc
expect(actual.length).to.equal(3);

const file1Call = AddAttachment.args.find(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this test asserts that the files were attached to repeat document, but not to which document.

@benkags
Copy link
Copy Markdown
Contributor

benkags commented May 16, 2026

Some of the work done here has leaked into #10923 when trying to handle the binary type input - should we track one PR instead?

I know you have done a lot of work here already @YASHSHARMAOFFICIALLY but at this point it is hard to separate out the reports logic in #10923's review. I suggest you you target my branch here benkags:10903-sub-contact-routing so that we track all the work you have already done here from there cc @dianabarsan @jkuester.

The <group> element wrapping the repeat caused Enketo to render it
as a separate page. The test never navigated to that page, so the
"Repeat photo" labels were not found. Removing the group keeps
everything on a single page, matching the other test forms.
@YASHSHARMAOFFICIALLY
Copy link
Copy Markdown
Contributor Author

YASHSHARMAOFFICIALLY commented May 18, 2026

@dianabarsan @benkags Pushed a fix for the failing e2e test should route file attachments to sub-docs inside repeat groups.

Root cause: The db-doc-repeat-file-upload.xml form had a <group> element wrapping the <repeat>, which caused Enketo to render the repeat section on a separate page. The test never navigated to that page, so $$(label*=Repeat photo) found 0 elements → Index out of bounds.

Fix: Removed the <group> wrapper so the repeat renders on the same page as the name field, matching the pattern of the other two test forms (db-doc-file-upload.xml, db-doc-multi-file-upload.xml).

@jkuester jkuester merged commit cc34e08 into medic:10700-photo-capture-in-sub-contacts-and-reports May 19, 2026
26 of 28 checks passed
@jkuester
Copy link
Copy Markdown
Contributor

@YASHSHARMAOFFICIALLY thank you for the changes here!

@dianabarsan, sorry for hijacking the PR and merging prematurely! 😬 🥷 I was on a call with @benkags and we were trying to find the most simple way to sync all of this with #10923 and get it shipped. The current plan is that @benkags is going to update his branch with the latest from https://github.com/medic/cht-core/tree/10700-photo-capture-in-sub-contacts-and-reports and then target master with github.com//pull/10923.

So, the complete unified changes will be available to review in that PR. 👍

benkags added a commit to benkags/cht-core that referenced this pull request May 21, 2026
…rt forms (medic#10922)

Co-authored-by: Bernard K. <kagondubernard81@gmail.com>
benkags added a commit to benkags/cht-core that referenced this pull request May 27, 2026
…rt forms (medic#10922)

Co-authored-by: Bernard K. <kagondubernard81@gmail.com>
benkags added a commit to benkags/cht-core that referenced this pull request May 27, 2026
…rt forms (medic#10922)

Co-authored-by: Bernard K. <kagondubernard81@gmail.com>
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.

sub-contact photo capture: Reports core routing + unit tests

4 participants