Skip to content

feat(#10509): support uploading attachments in contact forms#10570

Merged
dianabarsan merged 52 commits intomedic:masterfrom
cliftonmcintosh:feature/10509-contact-form-attachments
Feb 20, 2026
Merged

feat(#10509): support uploading attachments in contact forms#10570
dianabarsan merged 52 commits intomedic:masterfrom
cliftonmcintosh:feature/10509-contact-form-attachments

Conversation

@cliftonmcintosh
Copy link
Copy Markdown
Contributor

@cliftonmcintosh cliftonmcintosh commented Jan 16, 2026

Description

This PR adds attachment support to contact forms, enabling file uploads and binary field attachments similar to what already exists for report forms.

What's Implemented

Following discussion in #10509, the implementation was simplified to match report form behavior: all attachments attach to the main contact document only, regardless of form structure.

Backend Changes

  1. File Widget Attachments: Files uploaded through Enketo file widgets are extracted from FileManager and attached to the contact document
  2. Binary Field Attachments: Base64-encoded binary fields (signatures, drawings) are extracted from XML and attached with XPath-based naming
  3. Attachment Naming: Uses consistent patterns matching report forms:
    • File widgets: user-file-{filename}
    • Binary fields: user-file/{form-id}/{xpath}
  4. Edit Preservation: Attachments automatically persist when editing contacts via _defaults() merge

Implementation Details

  • Location: ContactSaveService.prepareSubmittedDocsForSave() (following @jkuester's guidance)
  • Code Added: ~30 lines in processAllAttachments() method
  • Services Reused: AttachmentService, FileManager, Xpath utility
  • Pattern: Mirrors EnketoService.xmlToDocs() for report forms

Test Coverage

Test Type Count Location
Unit Tests 8 webapp/tests/karma/ts/services/contact-save.service.spec.ts
cht-form Integration 2 tests/integration/cht-form/default/contact-with-attachments.wdio-spec.js
E2E Tests 5 tests/e2e/default/contacts/contact-attachments.wdio-spec.js

Unit tests are intended to verify:

  • File widget extraction and attachment to main document
  • Binary field extraction with XPath-based naming
  • Multiple attachments (mixed types) all attach to main document

E2E tests are intended to verify:

  • Create contact with single image attachment
  • Create contact with multiple attachments (image + document)
  • Edit contact preserves existing attachments

All added tests passing ✅

Manual Testing Performed

  • Created contacts with photo attachments via person-create form
  • Verified attachments saved to CouchDB with correct naming (user-file-{filename})
  • Verified edit form displays existing attachment filename
  • Confirmed multiple contacts can have attachments independently
Details about manual testing

Testing Photo Upload on Contact Create

Step 1: Modified the Contact Form

Please note that form modifications have not been committed. The file was modified locally.

File: config/default/forms/contact/person-create.xml

1a. Added field to instance data (~line 922)

<notes/>
<photo/>
<user_for_contact>

1b. Added binding (~line 1062)

<bind nodeset="/data/person/notes" type="string"/>
<bind nodeset="/data/person/photo" type="binary"/>
<bind nodeset="/data/person/user_for_contact/create" ...

1c. Added upload widget to body (~line 1159)

<input ref="/data/person/notes" appearance="multiline">
  <label ref="jr:itext('/data/person/notes:label')"/>
</input>
<upload ref="/data/person/photo" mediatype="image/*">
  <label>Photo</label>
  <hint>Take a photo or select from gallery</hint>
</upload>
<group ref="/data/person/user_for_contact"/>

Step 2: Compiled and Uploaded the Form

2a. Compiled the form

./scripts/build/build-config.sh

2b. Uploaded to CouchDB

Deleted the existing form and posted the new version:

# Got current revision and deleted
REV=$(curl -s http://medic:password@localhost:5984/medic/form%3Acontact%3Aperson%3Acreate | python3 -c 'import sys,json; print(json.load(sys.stdin)["_rev"])')
curl -X DELETE "http://medic:password@localhost:5984/medic/form%3Acontact%3Aperson%3Acreate?rev=$REV"

# Uploaded new version
curl -X POST "http://medic:password@localhost:5984/medic" \
  -H "Content-Type: application/json" \
  -d @api/build/default-docs/form-contact-person-create.doc.json

2c. Refreshed the browser

Hard refresh with Cmd+Shift+R.

Step 3: Tested the Upload

  1. Navigated to a health facility in CHT
  2. Clicked "New person" to create a contact
  3. Filled out required fields (name, DOB, sex)
  4. Scrolled to the "Photo" field and uploaded an image
  5. Submitted the form

Step 4: Verified the Attachment Was Saved

Queried CouchDB directly:

curl -s "http://medic:password@localhost:5984/medic/_find" \
  -H "Content-Type: application/json" \
  -d '{"selector": {"type": "person"}, "limit": 5}' | python3 -m json.tool | head -50

Looked for:

  • "photo": "filename.png" - the field value
  • "_attachments": { "user-file-filename.png": {...} } - the stored attachment

Test Result

Contact created: "Photo Contact"

Attachment saved:

"photo": "Screenshot 2026-01-26 at 09.06.22-9_30_5.png"

"_attachments": {
  "user-file-Screenshot 2026-01-26 at 09.06.22-9_30_5.png": {
    "content_type": "image/png",
    "length": 413623
  }
}

Testing the Edit Form (Display Existing Attachment)

Step 1: Modified the Edit Form

Please note that form modifications have not been committed. The file was modified locally.

File: config/default/forms/contact/person-edit.xml

1a. Added field to instance data (~line 1013)

<notes/>
<photo/>
<meta tag="hidden">

1b. Added binding (~line 1166)

<bind nodeset="/data/person/notes" type="string"/>
<bind nodeset="/data/person/photo" type="string" readonly="true()" relevant="../photo != ''"/>
<bind nodeset="/data/person/meta/created_by" type="string" ...

Note: The binding used:

  • type="string" (not binary) because we were displaying the filename, not uploading
  • readonly="true()" to prevent editing
  • relevant="../photo != ''" to only show when a photo exists

1c. Added read-only input to body (~line 1284)

</input>
<input ref="/data/person/photo">
  <label>Current Photo</label>
  <hint>Existing attachment (read-only)</hint>
</input>
<group ref="/data/person/user_for_contact"/>

Step 2: Compiled and Uploaded the Edit Form

2a. Compiled the form

./scripts/build/build-config.sh

2b. Uploaded to CouchDB

Deleted the existing form and posted the new version:

# Got current revision and deleted
REV=$(curl -s http://medic:password@localhost:5984/medic/form%3Acontact%3Aperson%3Aedit | python3 -c 'import sys,json; print(json.load(sys.stdin)["_rev"])')
curl -X DELETE "http://medic:password@localhost:5984/medic/form%3Acontact%3Aperson%3Aedit?rev=$REV"

# Uploaded new version
curl -X POST "http://medic:password@localhost:5984/medic" \
  -H "Content-Type: application/json" \
  -d @api/build/default-docs/form-contact-person-edit.doc.json

2c. Refreshed the browser

Hard refresh with Cmd+Shift+R.

Step 3: Tested Viewing the Existing Attachment

  1. Navigated to "Photo Contact" (the contact created in Part 1)
  2. Clicked "Edit" to open the edit form
  3. Scrolled to the "Current Photo" field
  4. Verified the filename was displayed

Test Result

Contact edited: "Photo Contact"

Photo field displayed: ✅ Yes - filename shown as read-only

Filename shown: Screenshot 2026-01-26 at 09.06.22-9_30_5.png

Status: ✅ Existing attachment filename correctly displayed in edit form

Screenshots

Empty person create form with photo field

create-form-empty-with-photo-field

Person create form with photo added

create-form-with-added-photo

Person edit form with photo shown

edit-form-with-photo-attached

Not in Scope for V1

Per discussion in #10509, these are deferred:

  • UI Preview on Edit: Attachments persist when editing, but users cannot see previews of existing attachments (can be added in V2) preview on edit is included as per @dianabarsan's request in feat(#10509): support uploading attachments in contact forms #10570
  • Multi-Document Routing: Attachments in sibling/repeat sections all attach to main document (matches report behavior)
  • Contact-Summary Integration: Per issue description, this is out of scope

AI Disclosure

AI Assistance: This PR was developed using Claude Code.

How AI was used:

  • Analysis of existing codebase patterns (EnketoService, AttachmentService) for consistency
  • Code generation for the processAllAttachments() method
  • Test writing following TDD methodology
  • Documentation and planning

Human oversight:

  • I reviewed all generated code for correctness, security, and consistency with project patterns
  • I ran all tests manually to verify functionality
  • I made architecture decisions (V1 simplified approach based on reviewer feedback)
  • I iterated on implementation based on code review feedback
  • All commits include co-author attribution

Closes #10509

Code review checklist

  • UI/UX backwards compatible: No UI changes in this PR. Existing contact forms continue to work unchanged.
  • Readable: Concise, well named, follows the style guide, documented if necessary.
  • Documented: Configuration and user documentation on cht-docs - Form authors will need guidance on adding upload fields
  • Tested: Unit tests, cht-form integration tests, and E2E tests all passing
  • Internationalised: No user-facing text added
  • Backwards compatible: Works with existing data and configuration. Existing contact forms without upload fields are unaffected.

License

The software is provided under AGPL-3.0. Contributions to this project are accepted under the same license.

@cliftonmcintosh
Copy link
Copy Markdown
Contributor Author

@dianabarsan I've created a draft PR with some initial work so that I can get feedback and see if this is the right direction to go.

@cliftonmcintosh cliftonmcintosh changed the title feat(#10509): Support uploading attachments in contact forms feat(#10509): support uploading attachments in contact forms Jan 16, 2026
@cliftonmcintosh cliftonmcintosh force-pushed the feature/10509-contact-form-attachments branch from 23b480b to 64ee2be Compare January 16, 2026 22:36
Comment thread webapp/src/ts/services/contact-save.service.ts Outdated
Comment thread webapp/tests/karma/ts/services/contact-save.service.spec.ts Outdated
Comment thread webapp/tests/karma/ts/services/contact-save.service.spec.ts Outdated
Comment thread webapp/tests/karma/ts/services/contact-save.service.spec.ts Outdated
Comment thread webapp/tests/karma/ts/services/contact-save.service.spec.ts Outdated
Comment thread webapp/tests/karma/ts/services/contact-save.service.spec.ts Outdated
Comment thread webapp/tests/karma/ts/services/contact-save.service.spec.ts Outdated
@cliftonmcintosh cliftonmcintosh force-pushed the feature/10509-contact-form-attachments branch from 5e544cf to 7d0c41d Compare January 17, 2026 18:30
Comment thread webapp/tests/karma/ts/services/contact-save.service.spec.ts Outdated
Comment thread webapp/tests/karma/ts/services/contact-save.service.spec.ts Outdated
@cliftonmcintosh cliftonmcintosh force-pushed the feature/10509-contact-form-attachments branch 2 times, most recently from bd060a7 to ff04047 Compare January 17, 2026 19:16
@andrablaj andrablaj requested a review from dianabarsan January 18, 2026 13:27
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.

Nice quick work! Thanks for adding the AI disclosure, that's super helpful when trying to make sense of some complexities that AI tends to add.
I added a few comments below, mainly as a path to simplify things.
Thanks a lot for the quick turn on this!

Comment thread webapp/src/ts/services/contact-save.service.ts Outdated
});
}

private buildDocumentBoundaries(
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.

Wow! this function!

I think the existence of this function is because the code is built to allow attachments for any of the documents the contact form creates. However, for reports, we only attach binary files for the main document. I think that following this model should be sufficient for the first iteration of this work: following the report format and only allowing attachments for the main document.

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.

This attempt to allow attachments for any of the documents the contact form creates us because in a comment on the issue, @jkuester said

The tricky part is going to be handling the fact that contact forms can create multiple documents (primary doc, sibling docs, repeated child docs), and ensuring that attachments are mapped to the correct document. I think, for each file attachment we will need to look up where in the XML structure it came from, and then map that to the corresponding document. Because of this, I am not sure if there is anything we can easily reuse from xmlToDocs directly, but we can at least borrow the logic for processing FileManager files and binary fields to use in our logic that can be added to ContactSaveService.prepareSubmittedDocsForSave.

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.

What I think would be helpful is guidance on

  1. What the XML looks like for contact forms. What elements do they have? Are there a limited set of objects that can show up as the primary, the sibling or the repeated child docs? Which ones can have attachments?
  2. Which documents we should support attachments for. Are we limiting it to the primary like @dianabarsan is suggesting we do? Or do we need or want to support attachments on the sibling and child docs that @jkuester mentions are possible?

Copy link
Copy Markdown
Member

@dianabarsan dianabarsan Jan 20, 2026

Choose a reason for hiding this comment

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

I asked Josh to clarify this comment about attachments to child docs, we are in a bit of a disagreement and my preference would be to keep things simple.

For sample contact forms, you can always look at the default config contact forms, though these would not have child documents. I will search for a real-world example that does have child documents.

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.

I asked Josh to clarify this comment about attachments to child docs, we are in a bit of a disagreement and my preference would be to keep things simple.

Sounds good. I'm happy to take whichever path the team decides is best.

For sample contact forms, you can always look at the default config contact forms, though these would not have child documents.

Thanks for the tip!

I will search for a real-world example that does have child documents.

Thank you!

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.

Based on Josh's reply to your questions, it looks like you two are in agreement about starting with a simple approach. I will be happy to update this PR so that we only support attachments on the main document.

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.

For sample contact forms, you can always look at the default config contact forms, though these would not have child documents. I will search for a real-world example that does have child documents.

Thanks again for pointing me to that directory, @dianabarsan. I have a couple of follow-up questions

  • If I am trying to write realistic tests, are there particular contact forms that it might be good to choose for my tests? For example, person-create or person-edit?
  • What is a realistic example of having an attachment to the main document for one of those forms? For example, in person-create xml, would we see an attachment at something like /data/person/photo or /data/person/notes/attachment or data/person/ephemeral_dob/birth_certificate? I'd like to be able write tests that are realistic.

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.

What is a realistic example of having an attachment to the main document

Because this feature has never existed, I do not believe we have one. I have heard of deployments wanting to upload PDFs, others to upload signatures (?).

As for a report that has child documents, I found a few examples in private configs and all of them follow this model: https://github.com/medic/cht-core/blob/master/config/default/forms/contact/clinic-create.xml where the main contact gets created along with a primary contact.
There might be some more elaborate examples out there, but I think for the tests we're good to go with this

Comment thread webapp/src/ts/services/contact-save.service.ts Outdated
cliftonmcintosh and others added 11 commits January 19, 2026 18:07
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implement XPath-based attachment routing to correctly map binary fields
to sibling documents in multi-document contact forms. Binary fields are
now attached to the document they belong to based on their XPath location
in the XML structure.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implement XPath-based attachment routing for repeated child documents
in contact forms. Binary fields in repeat groups are now correctly
mapped to their corresponding child documents based on repeat indices
in the XPath.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Handles cases where contact forms generate multiple document types:
- prepareRepeatedDocs now supports any repeat group name (not just
  child_data) by flattening all repeat groups from the repeats object
- buildDocumentBoundaries now correctly maps siblings that appear
  before repeats in XML but after repeats in preparedDocs array using
  two-pass algorithm (count repeats, then assign indices)

This ensures attachments route correctly when forms have both siblings
and repeats, regardless of their order in the XML structure.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Tests complex scenario combining all document types (main, siblings,
repeats) with attachments to verify XPath-based routing works correctly
when all features are used together.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
AttachmentService is never reassigned after construction, so marking
it as readonly satisfies code quality checks.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
AttachmentService is never reassigned after construction, so marking
it as readonly satisfies code quality checks.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
AttachmentService is never reassigned after construction, so marking
it as readonly satisfies code quality checks.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@cliftonmcintosh cliftonmcintosh force-pushed the feature/10509-contact-form-attachments branch from ff04047 to 0ae7f03 Compare January 20, 2026 00:07
@cliftonmcintosh
Copy link
Copy Markdown
Contributor Author

@dianabarsan

I have a question about process. If a PR is still in draft and the branch becomes out-of-date with the base branch, is it okay if I rebase or does your team prefer a merge commit instead?

I'm inclined to rebase until the PR is out of draft status because it keeps the commit history looking cleaner without a merge commit, but I'm happy to follow whatever convention your team prefers.

@dianabarsan
Copy link
Copy Markdown
Member

If a PR is still in draft and the branch becomes out-of-date with the base branch, is it okay if I rebase or does your team prefer a merge commit instead?

We general preference is to merge, because rebase loses all intermediary review comments which are very valuable to understand code progression. So please, avoid rebasing. We only rebase when the branch has additional commits (commits accidentally cherry picked) which makes reviewing very difficult.

@dianabarsan
Copy link
Copy Markdown
Member

because it keeps the commit history looking cleaner without a merge commit,

we ALWAYS squash and merge, so the commit history gets lost anwyay.

@dianabarsan
Copy link
Copy Markdown
Member

There is more info about process in the docs: https://docs.communityhealthtoolkit.org/community/contributing/code/workflow/#opening-pull-requests

cliftonmcintosh and others added 4 commits January 21, 2026 10:02
Changes:
- Remove complex XPath-based document routing logic
- Simplify processAllAttachments to attach everything to main doc
- Revert prepareRepeatedDocs to original simpler implementation
- Remove tests for sibling/repeat attachment routing (not needed in v1)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add integration tests to verify file upload widgets work in contact forms
and attachments are correctly included in form submission.

- Test single file attachment in contact form
- Test multiple attachments (image + document)
- Add test form XML with photo and document upload fields

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@cliftonmcintosh
Copy link
Copy Markdown
Contributor Author

I've run through manual testing again to

  • create a person with an attachment
  • edit the person (and be able to see the attachment)
  • remove an attachment during edit
  • add an attachment during edit
  • replace an attachment during edit
  • verify that the DB data is correct (no orphan attachments)
  • verify that attachments with special characters in their names are sanitized and can be viewed in edit

Here's a recording of that

Screen.Recording.2026-02-09.at.09.16.52.mov

cliftonmcintosh and others added 2 commits February 9, 2026 09:25
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
@cliftonmcintosh
Copy link
Copy Markdown
Contributor Author

It looks like the ci-integration-all-all flaked out. When this happens, should I post asking for a re-run? Or just know that eventually someone will re-run it if needed?

@dianabarsan
Copy link
Copy Markdown
Member

I re-ran the test and it passed. I didn't have time today to do a full review, just took a quick look at the latest commits and I'm really liking the new traversals! Thanks!
I'll do the full review tomorrow.

@mrjones-plip
Copy link
Copy Markdown
Contributor

When this happens, should I post asking for a re-run?

Yeah - here or in slack!

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.

So awesome!

* @returns The sanitized file name with special characters removed
*/
private sanitizeFileName(fileName: string): string {
return fileName.replace(/[^a-zA-Z0-9_.-]/g, ''); // NOSONAR
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.

@dianabarsan
Is this character limitation a problem for places that use other writing systems? Will the files always be named with Latin characters? Do we need to adjust the sanitization?

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.

Good point. I am nooooot sure. I suppose we could ask @binokaryg or @bhishankc about how files are named in Nepali. The safer option would be to only strip out special characters?

Copy link
Copy Markdown
Contributor

@mrjones-plip mrjones-plip Feb 12, 2026

Choose a reason for hiding this comment

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

I propose we keep this as is. In order of relevance, here's why I think this:

  1. we have many examples of using a-zA-Z as "safe"
  2. While Nepali likely is an alphabet we should support, this feature is actually initially being deployed in East Africa. As well, what about Arabic? What about Cyrillic?
  3. There's a larger idea called universal acceptance (UA) that we should consider.

I think the approach should be, at a later date, do an audit of our code for UA and address this concern then.

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.

After thought: Maybe there's a defensive code happy path where if a file is named تصدير and the result of sanitizeFileName() returns an empty string in which case we give the filename of a UUIDv7 instead?

Right now a file is uploaded called تصدير , we'd return a file name called user-file-, if I'm following the code correctly. Maybe worth an update 🤷 (oh maybe files without suffixes (eg .png) aren't allowed by enketo/browser?)

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.

User-generated files can sometimes have Devanagari script in their names. Devanagari script is common across Nepali, Hindi, and other regional languages.

For example, a user could capture an image and rename it to "घर.jpg" in Nepali for easier identification later.

It would be good to support UTF-8 characters if not too difficult.

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.

Thank you @binokaryg !

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.

Following up here to see if there is any action needed right now. Are we going to stick with things the way they are for this PR and adjust later or do we want an adjustment like the one that @mrjones-plip suggested:

Maybe there's a defensive code happy path where if a file is named تصدير and the result of sanitizeFileName() returns an empty string in which case we give the filename of a UUIDv7 instead?

@cliftonmcintosh
Copy link
Copy Markdown
Contributor Author

Please let me know if there are any actions I need to take on this PR. I can't do any merging, so I assume someone else will merge if this is still considered to be approved.

@dianabarsan
Copy link
Copy Markdown
Member

I was expecting a follow-up on the conversation about non-latin characters in file names. We would at least add a unit test showing what happens when you have a file containing only non-latin characters in its name.

@cliftonmcintosh
Copy link
Copy Markdown
Contributor Author

I was expecting a follow-up on the conversation about non-latin characters in file names. We would at least add a unit test showing what happens when you have a file containing only non-latin characters in its name.

Thanks for clarifying the expectations. I've updated the sanitization function so that it uses a UUID when there are no characters that match the regex. Unit test added as well. Please let me know if we want anything else.

@mrjones-plip
Copy link
Copy Markdown
Contributor

Thanks for keeping this PR moving Cliff and Diana!

I think the unit test is great and the UUID handling is icing on the cake. I defer to Diana to finalize the review on the last changes.

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.

Awesome! One small request inline.

const hasExtension = lastDotIndex > 0;

if (!hasExtension) {
return fileName.replace(/[^a-zA-Z0-9_.-]/g, '') || uuidV4(); // NOSONAR
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.

One request about storing the regex in a variable, so it's absolutely clear we're sanitizing with the same rules.

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.

Yes. Of course. Done!

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.

Awesome!

@cliftonmcintosh
Copy link
Copy Markdown
Contributor Author

I'm unfamiliar with the CHT process for merging approved PRs. Does the PR stay unmerged until the target version of CHT is almost ready to be released?

Let me know if there is anything I need to be doing or if there is anything that needs updating before merging.

@mrjones-plip
Copy link
Copy Markdown
Contributor

Hey @cliftonmcintosh ! Thanks for reaching out. For external contributors, the process is that the person who reviewed the PR is the one who merges it. If you have no more pending feedback to tend to and the PR has been approved, you're good to go! (which both are true here ; )

I've kicked off CI again in hopes it was a fluke that it didn't pass before 🤷

@dianabarsan
Copy link
Copy Markdown
Member

I can merge when we believe it's ready, but @cliftonmcintosh comes up with the best questions! I think we would need an answer for these questions, the worst that can happen is that we ungracefully crash if a sub-document has an image attachment.

@cliftonmcintosh
Copy link
Copy Markdown
Contributor Author

I can merge when we believe it's ready, but @cliftonmcintosh comes up with the best questions! I think we would need an answer for these questions, the worst that can happen is that we ungracefully crash if a sub-document has an image attachment.

@dianabarsan I'll be happy to explore this. I will dig in to find examples of sub-documents. If you have pointers for where I might find some to test with in the default config, please let me know. That might save us a little time.

I am traveling today through Monday so likely won't get back to this until Tuesday.

Diana code reviewing

one-more-thing

@dianabarsan
Copy link
Copy Markdown
Member

Nevermind. I tested this myself. The attachment gets added to the main doc.

@dianabarsan dianabarsan merged commit d09d656 into medic:master Feb 20, 2026
64 of 69 checks passed
@cliftonmcintosh
Copy link
Copy Markdown
Contributor Author

Nevermind. I tested this myself. The attachment gets added to the main doc.

Great! Thanks. I was just investigating to try to figure that out.

@mrjones-plip
Copy link
Copy Markdown
Contributor

while the issue is closed, for next time @cliftonmcintosh, instead of running the whole build config as you mentioned in Step 2: Compiled and Uploaded the Form, if you install CHT Conf which can do any/all of the build calls, enables a much more targeted upload of a specific form:

cht  --local upload-contact-forms -- person-create

As this JUST calls upload-contact-forms for JUST the one form, it will be much faster than the 10 calls in the build conf script processing ALL the contact and app forms in default config.

Right now I'm hoping to test a branch build of your work, and when coupled with Docker Helper, I can be up and running very quickly! (though, admittedly, I can't find images for your branch so I may have to build them 🤷 ). Docker helper is of course not good for core dev work like in this branch - but ideal for ad hoc testing.

@cliftonmcintosh
Copy link
Copy Markdown
Contributor Author

if you install CHT Conf which can do any/all of the build calls, enables a much more targeted upload of a specific form:

cht  --local upload-contact-forms -- person-create

Thanks for the tip!

dianabarsan pushed a commit to medic/cht-docs that referenced this pull request Feb 23, 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.

Support uploading attachments in contact forms

4 participants