Improve Create Section: add open case condition and dedicated fields#1225
Improve Create Section: add open case condition and dedicated fields#1225jingcheng16 merged 10 commits intojc/save-to-case-basefrom
Conversation
Add four new dedicated xPath properties to the Create section: - openCaseCondition - caseName - ownerId - ownerIdCondition Each field has standard parse (parseBindElement) and generate (getBindList, dataChildFilter, getCaseSaveData) support.
Per-property relevant on case_type/case_name causes runtime errors on mobile. Promote it to the case-level openCaseCondition both at parse time and generation time so it can never appear as a per-property attribute in the output. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Now that case_name and owner_id have dedicated fields, repurpose createProperty to accept arbitrary property names while rejecting reserved ones (case_type, case_name, owner_id). Extra create properties emit under <update> in the XML since CommCare's <create> block only supports case_type, case_name, and owner_id. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a parsed form has both <create> and <update> sections, merge the update properties into createProperty and clear useUpdate so they appear in the Create section. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add validateXPath helper that uses form.xpath.parse() to catch invalid XPath expressions. Applied to openCaseCondition and ownerIdCondition via validationFunc. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
mergeUpdatePropertiesIntoCreateAfterParse
millerdev
left a comment
There was a problem hiding this comment.
I ran out of time and quickly reviewed the final tests commit, so should spend a bit more time on that, but otherwise looks good.
| ownerIdRelevant = mug.p._ownerIdRelevant; | ||
| delete mug.p._caseTypeRelevant; | ||
| delete mug.p._caseNameRelevant; | ||
| delete mug.p._ownerIdRelevant; |
There was a problem hiding this comment.
I don't see this delete mug.p.property pattern elsewhere in Vellum. Have you confirmed that it works? Might be better to use mug.p.set('property'), which deletes 'property' from the mug's properties.
There was a problem hiding this comment.
ahhh, sorry, this is confusing. _caseTypeRelevant, _caseNameRelevant, _ownerIdRelevant technically are not spec properties on mug, they're just plain JS property on MugProperties object. mug.p.set('attr') won't work because it deletes from __data. But I can see it is confusing because the pattern is mug.p.attr... I've now moved them to mug._stashedCreateBindRelevants to avoid confusion. 60a076b
millerdev
left a comment
There was a problem hiding this comment.
Responses to all comments can be implemented in a follow-up PR if you agree with them but would prefer merge this PR ASAP.
| mug.p.createProperty = { | ||
| case_name: { calculate: "/data/name" }, | ||
| }; | ||
| assert.notEqual(mug.spec.createProperty.validationFunc(mug), "pass"); |
There was a problem hiding this comment.
How hard would it be to check for the specific expected error here rather than "not pass"?
Example:
Line 99 in 5c0c6ed
There is at least one other assertion in this file that might also benefit from that technique.
There was a problem hiding this comment.
With my changes in #1227, now each field carry its own validation, the property level will never return the reserved-name error, it will only return an errorSummary which is an generic string: “One or more properties above have errors. Fix the highlighted fields.” . And I find checking against the generic string is no different than checking pass.
| assert.deepEqual(_.without(_.keys(mug.p.createProperty), ""), ["p1", "p2"]); | ||
| }); | ||
|
|
||
| it("should preserve user-provided ownerIdCondition even when openCaseCondition has same condition", function () { |
There was a problem hiding this comment.
Given this behavior, why not always move owner_id relevant to ownerIdCondition rather than merging the two as outlined in the spec?
If
case_name+owner_idorcase_type+owner_idshare the same relevant → move to Open Case Condition. XML changes.
Seems like that could simplify the implementation and maybe not be quite as surprising for users who (for example) accidentally set the both case_name + owner_id relevants to the same expression when they intended to only set owner_id relevant?
There was a problem hiding this comment.
Yes it will simplify the implementation. But I believe the intention for user to leave same relevant for both case_name and owner_id or case_type and owner_id is to open the case conditionally and I want the new UI will accurately reflect that.
maybe not be quite as surprising for users who (for example) accidentally set the both case_name + owner_id relevants to the same expression when they intended to only set owner_id relevant?
This is possible. I wonder how well our user remember the value they typed in all mugs...For users who intentionally set both relevant the same expression and intend to make them just open case condition, if we always move owner_id relevant to ownerIdCondition will we surprise user by open case condition and owner id condition be the same?
| }); | ||
|
|
||
| describe("openCaseCondition promotion", function () { | ||
| it("should preserve user-provided ownerIdCondition even when openCaseCondition has same condition", function () { |
There was a problem hiding this comment.
How is this test different from the one above on line 635?
it("should preserve user-provided ownerIdCondition even when openCaseCondition has same condition", ...
There was a problem hiding this comment.
nice catch. I'll address in a follow up PR.
Product Description
The old Create section required users to manually enter case_name, case_type, and owner_id as generic key-value properties, each with a Property Name, Calculation, and an optional Relevant (update condition) field. We’ve already reconciled case_type with a top-level dropdown. This PR is to:
Before

After

Backward compatibility
Legacy forms that used per-property
relevantoncase_type,case_name, orowner_idcreate binds are automatically migrated:case_typeandcase_namerelevants are promoted to Open Case Condition (combined withandif they differ).owner_idrelevant is absorbed if it matches the promoted condition, otherwise kept as Owner ID Condition.<create>and<update>sections merge update properties into the Create section.See the design decision document for the full rationale on how
relevantfields are handled.Technical Summary
Ticket: https://dimagi.atlassian.net/browse/SAAS-19457
Why
aec64e3(XPath validation) is neededThe xPath widget's built-in logic reference tracking (via the logic manager) validates expressions during interactive editing — but it does NOT validate on form load. When a legacy form with an invalid XPath in a promoted
relevantis loaded, the logic manager silently fails to track references without producing an error. ThevalidationFuncinaec64e3catches this by runningform.xpath.parse()duringmug.validate(), which fires when the mug is displayed. The message text matches the logic manager's message ("Invalid XPath expression."with period) sogetMessagesdeduplicates them during interactive editing.Feature Flag
VELLUM_SAVE_TO_CASESafety Assurance
Safety story
Tested locally multiple times. I also test on production forms that has per-property relevant to make sure them will load as expected so user's workflow won't be disrupted.
Automated test coverage
New test suites:
QA Plan
Rollback instructions
Labels & Review