Skip to content

feat: Emit effects for assignments to program rule variables backed by a field [DHIS2-21730]#186

Open
enricocolasante wants to merge 3 commits into
masterfrom
DHIS2-21730
Open

feat: Emit effects for assignments to program rule variables backed by a field [DHIS2-21730]#186
enricocolasante wants to merge 3 commits into
masterfrom
DHIS2-21730

Conversation

@enricocolasante

@enricocolasante enricocolasante commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

DHIS2-21730: Emit effects for assignments to program rule variables backed by a field

Summary

ASSIGN actions targeting a program rule variable (content = #{variableName} or A{variableName}) previously only mutated the in-memory value map, so the assigned value was never propagated back to the data element or tracked entity attribute the variable is backed by. This PR makes the engine emit a RuleEffect targeting the backing field for such assignments, and hardens validation and error reporting around ASSIGN targets.

Changes

ASSIGN target resolution (RuleAction.assignTarget() / AssignTarget)

  • New sealed class AssignTarget with three cases:
    • Field — assign to the data element/attribute named by the action's field; emits an effect for the caller to apply.
    • Variable — assign to a program rule variable referenced in content; updates the evaluation state so subsequent rules see the new value. Both #{variableName} and A{variableName} notations are accepted.
    • Invalid — the action defines neither field nor content, or content is not a variable reference; carries a human-readable reason.
  • RuleAction gains ASSIGN/ERROR type constants and an internal withAssignBacking(field, attributeType) helper.

Effect emission for variable assignments (RuleConditionEvaluator)

  • When the assigned variable is backed by a data element or tracked entity attribute, the engine now also emits a RuleEffect carrying a copy of the action with the backing field and attributeType injected, making the effect self-contained for consumers.
  • Each assignment is emitted exactly once across evaluation passes:
    • data-element backed variables → event pass;
    • attribute backed variables → enrollment pass when the execution includes one (hasEnrollmentPass), otherwise the event pass emits them (single-event evaluation, executions without an enrollment, rules scoped to a program stage), so assignments are never silently dropped.
  • Calculated values have no backing field and emit no effect.
  • Assigning to a name that is not a defined program rule variable still updates the value map but logs a warning (possible typo) and emits no effect.

Validation & error reporting

  • New internal RuleConfigurationException: malformed ASSIGN targets (AssignTarget.Invalid) are reported as error effects, like expression errors, instead of surfacing as unexpected exceptions.
  • Error-message construction in addRuleErrorResult deduplicated.
  • Removed the public unwrapVariableName utility (superseded by assignTarget()); currentDate() is now internal.

Logger

  • Logger gains a warning level, wired up on JVM (Level.WARNING), JS (console.warn) and native.

enricocolasante and others added 2 commits July 13, 2026 16:46
…y a field [DHIS2-21730]

ASSIGN actions targeting a program rule variable now emit a RuleEffect
when the variable is backed by a data element or tracked entity
attribute, with the backing field and attributeType injected into the
effect action so consumers can apply it like a direct field assignment.
Emission is matched to the evaluation context (data elements in the
event pass, attributes in the enrollment pass) so evaluateAll emits
each assignment exactly once. Calculated values keep their silent
value-map-only behavior, and rule chaining is unchanged.

The two ASSIGN modes are now dispatched through the new sealed
AssignTarget type (RuleAction.assignTarget()), which accepts both
#{variableName} and A{variableName} content notations and reports
malformed actions with actionable error messages instead of
"unexpected exception: null".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@enricocolasante enricocolasante marked this pull request as draft July 13, 2026 15:36
@sonarqubecloud

Copy link
Copy Markdown

@enricocolasante enricocolasante marked this pull request as ready for review July 14, 2026 12:23
* `content` value, or when `content` is not a program rule variable reference like
* `#{variableName}`.
*/
fun assignTarget(): AssignTarget? {

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.

This method is public in RuleAction and it is a sealed class. If this object is expected to be managed by the client, I would check how the sealed class is managed from Java code in the backend. I think the interoperability for sealed classes is good enough in Java. I wonder how it will look like from JS code, maybe you can publish a beta and ask Tony to check it.
If this method is not supposed to be public, let's make it internal.

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.

2 participants