feat(#10904): route file attachments to correct sub-docs in report fo… - #11312
Draft
jkuester wants to merge 61 commits into
Draft
feat(#10904): route file attachments to correct sub-docs in report fo…#11312jkuester wants to merge 61 commits into
jkuester wants to merge 61 commits into
Conversation
…rt forms (medic#10922) Co-authored-by: Bernard K. <kagondubernard81@gmail.com>
…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
processAllAttachments now walks the parsed XML to determine which prepared doc owns each [type=binary] element (main / sibling / repeat child) and attaches files accordingly, instead of dumping every upload on preparedDocs[0]. Field-value sanitization and orphan cleanup also run per-doc. Adds two private helpers: - resolveContactOwnerDoc: DOM-walk from any element to its section root, then to the owning prepared doc (with mainDoc fallback). - findContactOwnerForFilename: locates the [type=binary] node whose text matches a FileManager filename and resolves its owner. No public API changes; no new service dependencies.
saveContact now uses validateAttachments(preparedDocs.preparedDocs)
New 'attachment routing to sub-contacts' describe block exercising: - file uploaded inside a sibling section -> sibling doc - file uploaded inside a repeat child -> i-th repeat doc - mixed uploads across main / sibling / repeat -> each owner - FileManager file with no matching binary node -> main doc fallback - inline binary (draw widget) inside sibling -> sibling doc - per-doc field-value sanitization (sibling field rewritten, main untouched) - main-doc orphan cleanup on edit path with per-doc loop
- main-doc & sub-doc oversize attachment fails saveContact - normal-sized attachment passes validation
Enketo's setVal rewrites uploaded binary nodes to type="file" the moment a value is set.
add sub-contact attachment routing form fixtures & testes
Co-authored-by: Joshua Kuestersteffen <jkuester@kuester7.com>
…t attachments Inline-binary form fields (`<foo type="binary">…base64…</foo>` populated by form `<instance>` defaults, `calculate` expressions, or external `instanceData` injection) previously had attachments stored under `user-file/<xpath>` while the saved field was blanked, requiring downstream binary fields attachment name reconstruction from `label` at render time. That reconstruction was broken for sub-docs, where the parent-form prefix in the attachment name is not derivable from the sub-doc alone. With this change, a media field's value is the name of its attachment, regardless of how the attachment was produced. Inline-binary fields keep their stable, xml based name (`user-file/<xpath>`), which is also written back into the field value so the renderer can resolve the image by value alone, consistent with file-widget uploads. - `nodesToJs` no longer sets `[type=binary]` to ''. - `bindJsonToXml` skips binding into `[type=binary]` for empty & attachmentment name reference. inline base64 still binds. - `xmlToDocs` attaches `[type=binary]` blob under the xpath-derived name and rewrites the element text to that name. - `getImagePath` gets the value directly when it starts with `user-file/` and preserves the legacy fetch path Existing data behavior: - Pre-existing docs with `<binary>=""` and a `user-file/<xpath>` attachment, gets overwrite-in-place on edit-re-save as the base64 is supplied for re-attachment under the same xpath-derived key. - Un-edited legacy main-doc records continue to render via the legacy `user-file/<label-path>` fallback in `getImagePath`. - Sub-doc image rendering, previously broken because the parent-form prefix was unreachable from sub-doc field paths, now works once the field has been rewrittento the literal attachment name. - `calculate`-derived per-doc base64 is preserved across edits via re-computation at Enketo's form-init
The report path was searching [type=binary] for file-widget filenames, so modern [type=file] widgets in sub-docs fell back to the main report doc. Narrow both report and contact paths to [type=file] (inline binaries are handled by their own loops).
A media field's value is now uniformly its attachment name minus the
USER_FILE_PREFIX, for both file-widget uploads and inline-binary fields.
- export USER_FILE_PREFIX from enketo.service; use it for both file and
inline-binary attachment names (binary -> user-file-<formId>/<xpath>/<field>)
- store the bare reference (<formId>/<xpath>/<field>) as the binary field value
- add EnketoTranslationService.isAttachmentRef (path-pattern regex) and use it
in the three former startsWith('user-file/') guards
- remove the dedicated user-file/ branch from getImagePath; the bare value
resolves through the unified user-file- + value branch
- reduce processAllAttachments cognitive complexity (extract helpers)
- reduce bindJsonToXml cognitive complexity (extract shouldSkipBinaryBind,
bindArrayToXml, bindObjectToXml)
- namespace report sub-doc binaries by ownerDoc.type
- update unit specs; fix stale type="binary" file-widget fixtures to type="file"
Make the sub-contact badge assertions robust to the exact xpath: verify the inline-binary attachment exists and the field value is that name minus the user-file- prefix, and that the value is preserved unchanged on edit.
Sub-docs carry their own form, so revert attachInlineBinary to `ownerDoc.form || doc.form`. Update the db-doc-with-binary fixture so its sub-docs include a <form> element, matching real sub-report shape.
- fill unit-coverage gaps: binary-in-repeat, empty binary, file dedup - gitignore tmep folder - tests hygiene: variable renaming and comments clean up
…contract Make the data-attachment-ref sidecar the single mechanism that preserves an untouched inline-binary field across a report edit. - remove recoverEmptyBinaryReference and its helpers (getOwnerFields, asAttachmentRef) plus the now-unused lodash-es import from enketo.service.ts; the empty-binary branch restores from the sidecar only - replace the obsolete owner-doc-fields recovery spec with a "no sidecar -> left empty" contract test - add attachment-ref-roundtrip.spec.ts pinning that enketo-core/mergexml keeps the foreign data-attachment-ref through FormModel merge + getStr - e2e: seed a referenced-binary report and assert the attachment survives an untouched-field edit byte-for-byte
…edit via sidecar Consume the data-attachment-ref sidecar in ContactSaveService so an untouched inline-binary field is restored on edit when the form has no default/calculate to re-supply the base64.
findRepeatDoc counted all element children of <repeat>, so a calculated counter node (e.g. child_count) serialized before the <child> nodes shifted every child's index: each photo routed to the next child and the last child fell back to the parent doc. Filter section.children to <child> nodes in findRepeatDoc and findFieldContainerElement so the index aligns 1:1 with preparedDocs' repeat slots (built from child_data). Adds a regression test.
Add AttachmentRoutingService (shared file/binary/finalize loop) plus a per-pipeline strategy. Reports adopt objectPath write-back, dropping the XML-rewrite/reparse pass, and gain filename sanitization and orphan cleanup.
…ve xpath Replace the form-id-prefixed xpath naming for inline [type=binary] attachments with the field's xpath relative to its owner doc's container: top-level -> `user-file-badge`, nested -> `group/photo`, repeat instance -> `my_repeat[1]/photo`. Binary binds are skipped unconditionally and any prior value is stashed to the data-attachment-ref sidecar, which is used to preserve an untouched field on edit.
… binaries restoreUntouchedBinary resolves owner/field-path generically, but only the main-doc case had a positive sidecar-restore test; the sibling case only existed as the negative no-sidecar test. Add matching positive cases for a sibling and a repeat <child> so the restore path is pinned across all owner types, not just main.
findUploadNodeByFilename only matched [type=file], so a draw/signature widget whose model node still reads type=binary fell back to the main doc, splitting the blob and field value across docs. Match [type=binary] too when locating the upload node by filename.
Enketo's filename postfix is only second-resolution, so two files with the same name uploaded in the same second collide. With per-doc routing both resolved to the first node, landing on one doc while the other sub-doc's field pointed at a missing attachment. Track consumed nodes so each file claims a distinct node.
…an cleanup Add a format-data-record case that resolves a nested owner-relative reference (group/photo -> user-file-group/photo), the sub-report render path. Add a routing-pipeline case asserting a replaced sub-doc upload drops the sub-doc's stale user-file-<old> while the main doc's referenced attachment is left untouched (orphan cleanup runs per prepared doc).
Replace generic .to.equal(null|undefined) and .length).to.equal(n) with .to.be.null/.to.be.undefined/.to.have.lengthOf(n) on the lines SonarCloud flagged across the attachment-routing test suite.
Relocating attachment-routing.spec.ts to tests/karma/ts/providers/ left its enketo-xml fixture require pointing at a nonexistent sibling dir. The fixtures are shared with enketo.service.spec.ts, so point at the canonical tests/karma/ts/services/enketo-xml/ copy.
Port the sub-doc attachment-routing feature (medic#10903/medic#10904) onto master's restructured Enketo form-save code (medic#11256). - Re-home report + contact attachment routing onto EnketoService.saveReport / saveContact via AttachmentRoutingService strategies that resolve each file / inline-binary owner by nearest owning DOM element (EnketoReportFormData / EnketoContactFormData), replacing processFormAttachments. - Move the data-attachment-ref sidecar + node-name findCurrentElement into EnketoPrepopulationDataService.bindJsonToXml (ex-EnketoTranslationService home). - Add validateAttachments on the contact save path in FormService. - Restore AttachmentService (the routing engine's doc-mutation API). - Add EnketoContactFormData.getMainData(). - Route on minified contact docs to avoid the parent/contact reference cycle; drop empty _attachments maps. - Delete the obsolete ContactSaveService / EnketoTranslationService and their specs; update enketo.service.spec attachment assertions to owner-relative user-file-<ref> naming. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…rms (#10922)
Co-authored-by: Bernard K. kagondubernard81@gmail.com<!--
Please use semantic PR titles that respect this format:
(#):
Quick example:
feat(#1234): add hat wobble
^--^(#^--^): ^------------^
| | |
| | + - > subject
| |
| + -------- > issue number
|
https://docs.communityhealthtoolkit.org/contribute/code/workflow/#commit-message-format
-->
Description
Code review checklist
can_view_old_navigationpermission to see the old design. Test it has appropriate design for RTL languages.License
The software is provided under AGPL-3.0. Contributions to this project are accepted under the same license.