From 2cc126587f72887bbc32c063426e34fd86cb14df Mon Sep 17 00:00:00 2001 From: Enrico Date: Fri, 13 Dec 2024 11:30:39 -0300 Subject: [PATCH 1/9] fix: Allow evaluate in progress events [DHIS2-18631] --- .../org/hisp/dhis/rules/RuleEngineJs.kt | 33 ++++++++++++++++--- .../hisp/dhis/rules/RuleEventInProgressJs.kt | 21 ++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 src/jsMain/kotlin/org/hisp/dhis/rules/RuleEventInProgressJs.kt diff --git a/src/jsMain/kotlin/org/hisp/dhis/rules/RuleEngineJs.kt b/src/jsMain/kotlin/org/hisp/dhis/rules/RuleEngineJs.kt index 3d63722d..f3da0948 100644 --- a/src/jsMain/kotlin/org/hisp/dhis/rules/RuleEngineJs.kt +++ b/src/jsMain/kotlin/org/hisp/dhis/rules/RuleEngineJs.kt @@ -8,6 +8,7 @@ import org.hisp.dhis.rules.api.DataItem import org.hisp.dhis.rules.api.RuleEngine import org.hisp.dhis.rules.api.RuleEngineContext import org.hisp.dhis.rules.models.* +import kotlin.time.Duration @JsExport class RuleEngineJs(verbose: Boolean = false) { @@ -21,9 +22,13 @@ class RuleEngineJs(verbose: Boolean = false) { fun validateDataFieldExpression(expression: String, dataItemStore: JsMap): RuleValidationResult{ return RuleEngine.getInstance().validateDataFieldExpression(expression, toMap(dataItemStore, {it}, ::toDataItemJava)) } - fun evaluateAll(enrollmentTarget: RuleEnrollmentJs?, eventsTarget: Array, executionContext: RuleEngineContextJs): Array{ + fun evaluateAll(enrollmentTarget: RuleEnrollmentJs?, eventsInProgressTarget: Array, eventsTarget: Array, executionContext: RuleEngineContextJs): Array{ + val lastEventDate = eventsTarget + .map { Instant.fromEpochMilliseconds(it.eventDate.toEpochMilli().toLong())} + .maxWith(compareBy { it }) + .plus(Duration.parse("1D")) return toRuleEffectsJsList(RuleEngine.getInstance().evaluateAll(toEnrollmentJava(enrollmentTarget), - eventsTarget.map(::toEventJava).toList(), + eventsTarget.map(::toEventJava).plus(eventsInProgressTarget.map{ e -> this.toEventJava(e, lastEventDate)}), toRuleEngineContextJava(executionContext))) } @@ -33,8 +38,12 @@ class RuleEngineJs(verbose: Boolean = false) { toRuleEngineContextJava(executionContext)) .map(::toRuleEffectJs).toTypedArray() } - fun evaluateEvent(target: RuleEventJs, ruleEnrollment: RuleEnrollmentJs?, ruleEvents: Array, executionContext: RuleEngineContextJs): Array{ - return RuleEngine.getInstance().evaluate(toEventJava(target), + fun evaluateEvent(target: RuleEventInProgressJs, ruleEnrollment: RuleEnrollmentJs?, ruleEvents: Array, executionContext: RuleEngineContextJs): Array{ + val lastEventDate = ruleEvents + .map { Instant.fromEpochMilliseconds(it.eventDate.toEpochMilli().toLong())} + .maxWith(compareBy { it }) + .plus(Duration.parse("1D")) + return RuleEngine.getInstance().evaluate(toEventJava(target, lastEventDate), toEnrollmentJava(ruleEnrollment), ruleEvents.map(::toEventJava).toList(), toRuleEngineContextJava(executionContext)) @@ -85,6 +94,22 @@ class RuleEngineJs(verbose: Boolean = false) { ) } + private fun toEventJava(event: RuleEventInProgressJs, eventDate: Instant): RuleEvent { + return RuleEvent( + event = event.event, + programStage = event.programStage, + programStageName = event.programStageName, + status = event.status, + eventDate = eventDate, + createdDate = Instant.fromEpochMilliseconds(event.createdDate.toEpochMilli().toLong()), + dueDate = toLocalDate(event.dueDate), + completedDate = toLocalDate(event.completedDate), + organisationUnit = event.organisationUnit, + organisationUnitCode = event.organisationUnitCode, + dataValues = event.dataValues.toList() + ) + } + private fun toRuleJava(rule: RuleJs): Rule { return Rule( condition = rule.condition, diff --git a/src/jsMain/kotlin/org/hisp/dhis/rules/RuleEventInProgressJs.kt b/src/jsMain/kotlin/org/hisp/dhis/rules/RuleEventInProgressJs.kt new file mode 100644 index 00000000..9379fa8f --- /dev/null +++ b/src/jsMain/kotlin/org/hisp/dhis/rules/RuleEventInProgressJs.kt @@ -0,0 +1,21 @@ +package org.hisp.dhis.rules + +import kotlinx.datetime.internal.JSJoda.Instant +import kotlinx.datetime.internal.JSJoda.LocalDate +import org.hisp.dhis.rules.models.RuleDataValue +import org.hisp.dhis.rules.models.RuleEventStatus + +@JsExport +data class RuleEventInProgressJs( + val event: String, + val programStage: String, + val programStageName: String, + val status: RuleEventStatus, + val eventDate: Instant?, + val createdDate: Instant, + val dueDate: LocalDate?, + val completedDate: LocalDate?, + val organisationUnit: String, + val organisationUnitCode: String?, + val dataValues: Array +) \ No newline at end of file From 0a324eb285a1bcdcb099dd8384b86e8137fb80c8 Mon Sep 17 00:00:00 2001 From: Enrico Date: Fri, 13 Dec 2024 11:32:46 -0300 Subject: [PATCH 2/9] Bump version --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index f76aff8b..43dcfae2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ repositories { maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } } -version = "3.2.0-SNAPSHOT" +version = "3.3.0-SNAPSHOT" group = "org.hisp.dhis.rules" if (project.hasProperty("removeSnapshotSuffix")) { From 26b10155c1946f48dfb8a9acf421f7541cb5907b Mon Sep 17 00:00:00 2001 From: Enrico Date: Mon, 16 Dec 2024 14:19:04 -0300 Subject: [PATCH 3/9] fix: Assing distant future date to events without date [DHIS2-18631] --- .../engine/RuleVariableValueMapBuilder.kt | 28 +++++++------ .../models/RuleVariableValueMapBuilderTest.kt | 38 ++++++++++++++++++ .../org/hisp/dhis/rules/RuleEngineJs.kt | 39 ++++++------------- .../hisp/dhis/rules/RuleEventInProgressJs.kt | 21 ---------- .../kotlin/org/hisp/dhis/rules/RuleEventJs.kt | 2 +- 5 files changed, 66 insertions(+), 62 deletions(-) delete mode 100644 src/jsMain/kotlin/org/hisp/dhis/rules/RuleEventInProgressJs.kt diff --git a/src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleVariableValueMapBuilder.kt b/src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleVariableValueMapBuilder.kt index b9756264..e0be3d21 100644 --- a/src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleVariableValueMapBuilder.kt +++ b/src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleVariableValueMapBuilder.kt @@ -1,8 +1,10 @@ package org.hisp.dhis.rules.engine +import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime +import org.hisp.dhis.rules.api.RuleEngine import org.hisp.dhis.rules.models.* import org.hisp.dhis.rules.utils.RuleEngineUtils import org.hisp.dhis.rules.utils.currentDate @@ -133,18 +135,20 @@ internal class RuleVariableValueMapBuilder { currentDate: LocalDate, ): Map { val valueMap: MutableMap = HashMap() - val eventDate = - ruleEvent.eventDate - .toLocalDateTime(TimeZone.currentSystemDefault()) - .date - .toString() - valueMap[RuleEngineUtils.ENV_VAR_EVENT_DATE] = - RuleVariableValue( - RuleValueType.TEXT, - eventDate, - listOf(eventDate), - currentDate.toString(), - ) + if (ruleEvent.eventDate < Instant.DISTANT_FUTURE ){ + val eventDate = + ruleEvent.eventDate + .toLocalDateTime(TimeZone.currentSystemDefault()) + .date + .toString() + valueMap[RuleEngineUtils.ENV_VAR_EVENT_DATE] = + RuleVariableValue( + RuleValueType.TEXT, + eventDate, + listOf(eventDate), + currentDate.toString(), + ) + } if (ruleEvent.dueDate != null) { val dueDate = ruleEvent.dueDate valueMap[RuleEngineUtils.ENV_VAR_DUE_DATE] = diff --git a/src/commonTest/kotlin/org/hisp/dhis/rules/models/RuleVariableValueMapBuilderTest.kt b/src/commonTest/kotlin/org/hisp/dhis/rules/models/RuleVariableValueMapBuilderTest.kt index fd7a7c35..a9706491 100644 --- a/src/commonTest/kotlin/org/hisp/dhis/rules/models/RuleVariableValueMapBuilderTest.kt +++ b/src/commonTest/kotlin/org/hisp/dhis/rules/models/RuleVariableValueMapBuilderTest.kt @@ -212,6 +212,44 @@ class RuleVariableValueMapBuilderTest { .hasCandidates("test_value_two") } + @Test + fun currentEventVariableShouldContainValuesFromCurrentEventWhenEventDateIsDistantFuture() { + val eventInstant = Instant.parse("2015-01-01T01:00:00Z") + val eventDate = LocalDate.parse("2015-01-01") + val dueDate = LocalDate.parse("2016-01-01") + + // values from context events should be ignored + val contextEventOne = + RuleEvent( + "test_context_event_one", + "test_program_stage", + "", + RuleEventStatus.ACTIVE, + Instant.DISTANT_FUTURE, + Clock.System.now(), + LocalDate.currentDate(), + null, + "", + null, + listOf( + RuleDataValue( + "test_dataelement_one", + "test_context_value_one", + ), + RuleDataValue( + "test_dataelement_two", + "test_context_value_two", + ), + ), + ) + + val valueMap = + RuleVariableValueMapBuilder() + .build(emptyMap(), listOf(), setOf(contextEventOne), null, null) + + assertNull(valueMap["event_date"]) + } + @Test fun newestEventProgramVariableShouldContainValueFromNewestContextEvent() { val ruleVariableOne: RuleVariable = diff --git a/src/jsMain/kotlin/org/hisp/dhis/rules/RuleEngineJs.kt b/src/jsMain/kotlin/org/hisp/dhis/rules/RuleEngineJs.kt index f3da0948..c218a50d 100644 --- a/src/jsMain/kotlin/org/hisp/dhis/rules/RuleEngineJs.kt +++ b/src/jsMain/kotlin/org/hisp/dhis/rules/RuleEngineJs.kt @@ -2,6 +2,7 @@ package org.hisp.dhis.rules import js.array.tupleOf import js.collections.JsMap +import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate import org.hisp.dhis.rules.api.DataItem @@ -22,13 +23,9 @@ class RuleEngineJs(verbose: Boolean = false) { fun validateDataFieldExpression(expression: String, dataItemStore: JsMap): RuleValidationResult{ return RuleEngine.getInstance().validateDataFieldExpression(expression, toMap(dataItemStore, {it}, ::toDataItemJava)) } - fun evaluateAll(enrollmentTarget: RuleEnrollmentJs?, eventsInProgressTarget: Array, eventsTarget: Array, executionContext: RuleEngineContextJs): Array{ - val lastEventDate = eventsTarget - .map { Instant.fromEpochMilliseconds(it.eventDate.toEpochMilli().toLong())} - .maxWith(compareBy { it }) - .plus(Duration.parse("1D")) + fun evaluateAll(enrollmentTarget: RuleEnrollmentJs?, eventsTarget: Array, executionContext: RuleEngineContextJs): Array{ return toRuleEffectsJsList(RuleEngine.getInstance().evaluateAll(toEnrollmentJava(enrollmentTarget), - eventsTarget.map(::toEventJava).plus(eventsInProgressTarget.map{ e -> this.toEventJava(e, lastEventDate)}), + eventsTarget.map(::toEventJava), toRuleEngineContextJava(executionContext))) } @@ -38,12 +35,8 @@ class RuleEngineJs(verbose: Boolean = false) { toRuleEngineContextJava(executionContext)) .map(::toRuleEffectJs).toTypedArray() } - fun evaluateEvent(target: RuleEventInProgressJs, ruleEnrollment: RuleEnrollmentJs?, ruleEvents: Array, executionContext: RuleEngineContextJs): Array{ - val lastEventDate = ruleEvents - .map { Instant.fromEpochMilliseconds(it.eventDate.toEpochMilli().toLong())} - .maxWith(compareBy { it }) - .plus(Duration.parse("1D")) - return RuleEngine.getInstance().evaluate(toEventJava(target, lastEventDate), + fun evaluateEvent(target: RuleEventJs, ruleEnrollment: RuleEnrollmentJs?, ruleEvents: Array, executionContext: RuleEngineContextJs): Array{ + return RuleEngine.getInstance().evaluate(toEventJava(target), toEnrollmentJava(ruleEnrollment), ruleEvents.map(::toEventJava).toList(), toRuleEngineContextJava(executionContext)) @@ -84,7 +77,10 @@ class RuleEngineJs(verbose: Boolean = false) { programStage = event.programStage, programStageName = event.programStageName, status = event.status, - eventDate = Instant.fromEpochMilliseconds(event.eventDate.toEpochMilli().toLong()), + eventDate = if(event.eventDate != null) + Instant.fromEpochMilliseconds(event.eventDate.toEpochMilli().toLong()) + else + Instant.DISTANT_FUTURE, createdDate = Instant.fromEpochMilliseconds(event.createdDate.toEpochMilli().toLong()), dueDate = toLocalDate(event.dueDate), completedDate = toLocalDate(event.completedDate), @@ -94,21 +90,7 @@ class RuleEngineJs(verbose: Boolean = false) { ) } - private fun toEventJava(event: RuleEventInProgressJs, eventDate: Instant): RuleEvent { - return RuleEvent( - event = event.event, - programStage = event.programStage, - programStageName = event.programStageName, - status = event.status, - eventDate = eventDate, - createdDate = Instant.fromEpochMilliseconds(event.createdDate.toEpochMilli().toLong()), - dueDate = toLocalDate(event.dueDate), - completedDate = toLocalDate(event.completedDate), - organisationUnit = event.organisationUnit, - organisationUnitCode = event.organisationUnitCode, - dataValues = event.dataValues.toList() - ) - } + private fun toRuleJava(rule: RuleJs): Rule { return Rule( @@ -222,5 +204,6 @@ class RuleEngineJs(verbose: Boolean = false) { internal companion object { var verbose: Boolean = false + var lastDate: Instant = Clock.System.now() } } \ No newline at end of file diff --git a/src/jsMain/kotlin/org/hisp/dhis/rules/RuleEventInProgressJs.kt b/src/jsMain/kotlin/org/hisp/dhis/rules/RuleEventInProgressJs.kt deleted file mode 100644 index 9379fa8f..00000000 --- a/src/jsMain/kotlin/org/hisp/dhis/rules/RuleEventInProgressJs.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.hisp.dhis.rules - -import kotlinx.datetime.internal.JSJoda.Instant -import kotlinx.datetime.internal.JSJoda.LocalDate -import org.hisp.dhis.rules.models.RuleDataValue -import org.hisp.dhis.rules.models.RuleEventStatus - -@JsExport -data class RuleEventInProgressJs( - val event: String, - val programStage: String, - val programStageName: String, - val status: RuleEventStatus, - val eventDate: Instant?, - val createdDate: Instant, - val dueDate: LocalDate?, - val completedDate: LocalDate?, - val organisationUnit: String, - val organisationUnitCode: String?, - val dataValues: Array -) \ No newline at end of file diff --git a/src/jsMain/kotlin/org/hisp/dhis/rules/RuleEventJs.kt b/src/jsMain/kotlin/org/hisp/dhis/rules/RuleEventJs.kt index 50c75ba4..38c2ab98 100644 --- a/src/jsMain/kotlin/org/hisp/dhis/rules/RuleEventJs.kt +++ b/src/jsMain/kotlin/org/hisp/dhis/rules/RuleEventJs.kt @@ -11,7 +11,7 @@ data class RuleEventJs( val programStage: String, val programStageName: String, val status: RuleEventStatus, - val eventDate: Instant, + val eventDate: Instant?, val createdDate: Instant, val dueDate: LocalDate?, val completedDate: LocalDate?, From 593491c4071c35361147dc2022e2fbf0cd5d2734 Mon Sep 17 00:00:00 2001 From: Enrico Date: Wed, 18 Dec 2024 10:21:14 -0300 Subject: [PATCH 4/9] Add null environment variables to map --- .../engine/RuleVariableValueMapBuilder.kt | 8 +++ .../hisp/dhis/rules/RuleEngineFunctionTest.kt | 66 +++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleVariableValueMapBuilder.kt b/src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleVariableValueMapBuilder.kt index e0be3d21..416bc109 100644 --- a/src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleVariableValueMapBuilder.kt +++ b/src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleVariableValueMapBuilder.kt @@ -148,6 +148,14 @@ internal class RuleVariableValueMapBuilder { listOf(eventDate), currentDate.toString(), ) + } else { + valueMap[RuleEngineUtils.ENV_VAR_EVENT_DATE] = + RuleVariableValue( + RuleValueType.TEXT, + null, + listOf(), + currentDate.toString(), + ); } if (ruleEvent.dueDate != null) { val dueDate = ruleEvent.dueDate diff --git a/src/commonTest/kotlin/org/hisp/dhis/rules/RuleEngineFunctionTest.kt b/src/commonTest/kotlin/org/hisp/dhis/rules/RuleEngineFunctionTest.kt index bac378f3..cc3f3908 100644 --- a/src/commonTest/kotlin/org/hisp/dhis/rules/RuleEngineFunctionTest.kt +++ b/src/commonTest/kotlin/org/hisp/dhis/rules/RuleEngineFunctionTest.kt @@ -166,6 +166,72 @@ class RuleEngineFunctionTest { assertEquals(ruleAction, ruleEffects[0].ruleAction) } + @Test + fun evaluateHasValueFunctionMustReturnFalseIfValueNotSpecified() { + val ruleAction = + RuleAction( + "d2:hasValue(V{event_date})", + "DISPLAYTEXT", + mapOf(Pair("content", "test_action_content"), Pair("location", "feedback")), + ) + val rule = Rule("d2:hasValue(V{event_date})", listOf(ruleAction), "", "") + val ruleEngineContext = RuleEngineTestUtils.getRuleEngineContext(rule, listOf()) + val ruleEvent = + RuleEvent( + "test_event", + "test_program_stage", + "", + RuleEventStatus.ACTIVE, + Instant.DISTANT_FUTURE, + Clock.System.now(), + LocalDate.currentDate(), + null, + "", + null, + listOf( + RuleDataValue( + "test_data_element_one", + "test_value", + ), + ), + ) + val ruleEffects = RuleEngine.getInstance().evaluate(ruleEvent, null, emptyList(), ruleEngineContext) + assertEquals(0, ruleEffects.size) + } + + @Test + fun evaluateNotHasValueFunctionMustReturnTrueIfValueNotSpecified() { + val ruleAction = + RuleAction( + "true", + "DISPLAYTEXT", + mapOf(Pair("content", "test_action_content"), Pair("location", "feedback")), + ) + val rule = Rule("!d2:hasValue(V{event_date})", listOf(ruleAction), "", "") + val ruleEngineContext = RuleEngineTestUtils.getRuleEngineContext(rule, listOf()) + val ruleEvent = + RuleEvent( + "test_event", + "test_program_stage", + "", + RuleEventStatus.ACTIVE, + Instant.DISTANT_FUTURE, + Clock.System.now(), + LocalDate.currentDate(), + null, + "", + null, + listOf( + RuleDataValue( + "test_data_element_one", + "test_value", + ), + ), + ) + val ruleEffects = RuleEngine.getInstance().evaluate(ruleEvent, null, emptyList(), ruleEngineContext) + assertEquals(1, ruleEffects.size) + } + @Test fun optionSetNameShouldBeUsed() { val option1 = Option("name1", "code1") From 3df73fbcffcf678b8e797b86321987a5d64d507d Mon Sep 17 00:00:00 2001 From: Enrico Date: Wed, 18 Dec 2024 10:37:08 -0300 Subject: [PATCH 5/9] Add null environment variables to map --- .../engine/RuleVariableValueMapBuilder.kt | 48 +++++++------------ 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleVariableValueMapBuilder.kt b/src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleVariableValueMapBuilder.kt index 416bc109..1f887a6d 100644 --- a/src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleVariableValueMapBuilder.kt +++ b/src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleVariableValueMapBuilder.kt @@ -135,49 +135,35 @@ internal class RuleVariableValueMapBuilder { currentDate: LocalDate, ): Map { val valueMap: MutableMap = HashMap() - if (ruleEvent.eventDate < Instant.DISTANT_FUTURE ){ val eventDate = + if (ruleEvent.eventDate < Instant.DISTANT_FUTURE ) ruleEvent.eventDate .toLocalDateTime(TimeZone.currentSystemDefault()) .date .toString() + else null valueMap[RuleEngineUtils.ENV_VAR_EVENT_DATE] = RuleVariableValue( RuleValueType.TEXT, eventDate, - listOf(eventDate), + if(eventDate == null) listOf() else listOf(eventDate), currentDate.toString(), ) - } else { - valueMap[RuleEngineUtils.ENV_VAR_EVENT_DATE] = - RuleVariableValue( - RuleValueType.TEXT, - null, - listOf(), - currentDate.toString(), - ); - } - if (ruleEvent.dueDate != null) { - val dueDate = ruleEvent.dueDate - valueMap[RuleEngineUtils.ENV_VAR_DUE_DATE] = - RuleVariableValue( - RuleValueType.TEXT, - dueDate.toString(), - listOf(dueDate.toString()), - currentDate.toString(), - ) - } - if (ruleEvent.completedDate != null) { - val completedDate = ruleEvent.completedDate - valueMap[RuleEngineUtils.ENV_VAR_COMPLETED_DATE] = - RuleVariableValue( - RuleValueType.TEXT, - completedDate.toString(), - listOf(completedDate.toString()), - currentDate.toString(), - ) - } + valueMap[RuleEngineUtils.ENV_VAR_DUE_DATE] = + RuleVariableValue( + RuleValueType.TEXT, + ruleEvent.dueDate?.toString(), + if(ruleEvent.dueDate == null) listOf() else listOf(ruleEvent.dueDate.toString()), + currentDate.toString(), + ) + valueMap[RuleEngineUtils.ENV_VAR_COMPLETED_DATE] = + RuleVariableValue( + RuleValueType.TEXT, + ruleEvent.completedDate?.toString(), + if(ruleEvent.completedDate == null) listOf() else listOf(ruleEvent.completedDate.toString()), + currentDate.toString(), + ) val eventCount = ruleEvents.size.toString() valueMap[RuleEngineUtils.ENV_VAR_EVENT_COUNT] = RuleVariableValue( From c611da6aaa7d63af1fdc0b19970a02884638f072 Mon Sep 17 00:00:00 2001 From: Enrico Date: Wed, 18 Dec 2024 10:47:31 -0300 Subject: [PATCH 6/9] Fix tests --- .../models/RuleVariableValueMapBuilderTest.kt | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/commonTest/kotlin/org/hisp/dhis/rules/models/RuleVariableValueMapBuilderTest.kt b/src/commonTest/kotlin/org/hisp/dhis/rules/models/RuleVariableValueMapBuilderTest.kt index a9706491..94d718c8 100644 --- a/src/commonTest/kotlin/org/hisp/dhis/rules/models/RuleVariableValueMapBuilderTest.kt +++ b/src/commonTest/kotlin/org/hisp/dhis/rules/models/RuleVariableValueMapBuilderTest.kt @@ -164,7 +164,7 @@ class RuleVariableValueMapBuilderTest { val valueMap = RuleVariableValueMapBuilder() .build(emptyMap(), listOf(ruleVariableOne, ruleVariableTwo), setOf(contextEventOne, contextEventTwo), null, currentEvent) - assertEquals(13, valueMap.size.toLong()) + assertEquals(14, valueMap.size.toLong()) RuleVariableValueAssert .assertThatVariable(valueMap["current_date"]!!) .hasValue( @@ -345,7 +345,7 @@ class RuleVariableValueMapBuilderTest { val valueMap = RuleVariableValueMapBuilder() .build(emptyMap(), listOf(ruleVariableOne, ruleVariableTwo), setOf(oldestRuleEvent, newestRuleEvent), null, currentEvent) - assertEquals(valueMap.size.toLong(), 12) + assertEquals(14, valueMap.size.toLong() ) RuleVariableValueAssert .assertThatVariable(valueMap["current_date"]!!) .hasValue( @@ -367,7 +367,11 @@ class RuleVariableValueMapBuilderTest { .hasValue("test_event_uid_current") .isTypeOf(RuleValueType.TEXT) .hasCandidates("test_event_uid_current") - assertNull(valueMap["due_date"]) + RuleVariableValueAssert + .assertThatVariable(valueMap["due_date"]!!) + .hasValue(null) + .isTypeOf(RuleValueType.TEXT) + .hasCandidates() RuleVariableValueAssert .assertThatVariable(valueMap["test_variable_one"]!!) .hasValue("test_value_one_newest") @@ -483,7 +487,7 @@ class RuleVariableValueMapBuilderTest { val valueMap = RuleVariableValueMapBuilder() .build(emptyMap(), listOf(ruleVariableOne, ruleVariableTwo), setOf(firstRuleEvent, secondRuleEvent), null, currentEvent) - assertEquals(13, valueMap.size.toLong()) + assertEquals(14, valueMap.size.toLong()) RuleVariableValueAssert .assertThatVariable(valueMap["current_date"]!!) .hasValue( @@ -626,7 +630,7 @@ class RuleVariableValueMapBuilderTest { val valueMap = RuleVariableValueMapBuilder() .build(emptyMap(), listOf(ruleVariable), setOf(eventOne, eventTwo, eventThree), null, eventCurrent) - assertEquals(12, valueMap.size.toLong()) + assertEquals(13, valueMap.size.toLong()) RuleVariableValueAssert .assertThatVariable(valueMap["current_date"]!!) .hasValue( @@ -716,7 +720,7 @@ class RuleVariableValueMapBuilderTest { val valueMap = RuleVariableValueMapBuilder() .build(emptyMap(), listOf(ruleVariable), setOf(ruleEventOne), null, ruleEventTwo) - assertEquals(12, valueMap.size.toLong()) + assertEquals(13, valueMap.size.toLong()) RuleVariableValueAssert .assertThatVariable(valueMap["current_date"]!!) .hasValue( @@ -839,7 +843,7 @@ class RuleVariableValueMapBuilderTest { val valueMap = RuleVariableValueMapBuilder() .build(emptyMap(), listOf(ruleVariable), setOf(ruleEventOne, ruleEventTwo, ruleEventThree), null, ruleEventCurrent) - assertEquals(12, valueMap.size.toLong()) + assertEquals(13, valueMap.size.toLong()) RuleVariableValueAssert .assertThatVariable(valueMap["current_date"]!!) .hasValue( @@ -960,7 +964,7 @@ class RuleVariableValueMapBuilderTest { val valueMap = RuleVariableValueMapBuilder() .build(emptyMap(), listOf(ruleVariable), setOf(ruleEventOne, ruleEventTwo, ruleEventThree), null, ruleEventCurrent) - assertEquals(12, valueMap.size.toLong()) + assertEquals(13, valueMap.size.toLong()) RuleVariableValueAssert .assertThatVariable(valueMap["current_date"]!!) .hasValue( @@ -1079,7 +1083,7 @@ class RuleVariableValueMapBuilderTest { val valueMap = RuleVariableValueMapBuilder() .build(emptyMap(), listOf(ruleVariableOne, ruleVariableTwo), setOf(contextEvent), ruleEnrollment, currentEvent) - assertEquals(20, valueMap.size.toLong()) + assertEquals(21, valueMap.size.toLong()) RuleVariableValueAssert .assertThatVariable(valueMap["current_date"]!!) .hasValue( From eaad7aec71840e99cafda620b0270b271d38b220 Mon Sep 17 00:00:00 2001 From: Enrico Date: Thu, 19 Dec 2024 12:00:57 -0300 Subject: [PATCH 7/9] Add default value test --- .../hisp/dhis/rules/RuleEngineFunctionTest.kt | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/commonTest/kotlin/org/hisp/dhis/rules/RuleEngineFunctionTest.kt b/src/commonTest/kotlin/org/hisp/dhis/rules/RuleEngineFunctionTest.kt index cc3f3908..5ce8c437 100644 --- a/src/commonTest/kotlin/org/hisp/dhis/rules/RuleEngineFunctionTest.kt +++ b/src/commonTest/kotlin/org/hisp/dhis/rules/RuleEngineFunctionTest.kt @@ -123,6 +123,44 @@ class RuleEngineFunctionTest { return null } + @Test + fun actionConditionShouldEvaluateToTrueUsingVariableDefaultValueIfVariableValueIsNull() { + val ruleAction = + RuleAction( + "#{test_variable} > -1", + "DISPLAYTEXT", + mapOf(Pair("content", "test_action_content"), Pair("location", "feedback")), + ) + val ruleVariable: RuleVariable = + RuleVariableCurrentEvent( + "test_variable", + true, + emptyList(), + "test_data_element_one", + RuleValueType.NUMERIC, + ) + val rule = Rule("true", listOf(ruleAction), "", "") + val ruleEngineContext = RuleEngineTestUtils.getRuleEngineContext(rule, listOf(ruleVariable)) + val ruleEvent = + RuleEvent( + "test_event", + "test_program_stage", + "", + RuleEventStatus.ACTIVE, + Clock.System.now(), + Clock.System.now(), + LocalDate.currentDate(), + null, + "", + null, + listOf(), + ) + val ruleEffects = RuleEngine.getInstance().evaluate(ruleEvent, null, emptyList(), ruleEngineContext) + assertEquals(1, ruleEffects.size) + assertEquals("true", ruleEffects[0].data) + assertEquals(ruleAction, ruleEffects[0].ruleAction) + } + @Test fun evaluateHasValueFunctionMustReturnTrueIfValueSpecified() { val ruleAction = @@ -158,7 +196,7 @@ class RuleEngineFunctionTest { "test_data_element_one", "test_value", ), - ), + ) ) val ruleEffects = RuleEngine.getInstance().evaluate(ruleEvent, null, emptyList(), ruleEngineContext) assertEquals(1, ruleEffects.size) From 2f4c056d2ad79fafcc3616d27fc8177c73a3c328 Mon Sep 17 00:00:00 2001 From: Enrico Date: Fri, 24 Jan 2025 10:58:49 -0300 Subject: [PATCH 8/9] fix: Use eventDate as fallback for event_id variable --- build.gradle.kts | 2 +- .../engine/RuleVariableValueMapBuilder.kt | 7 ++- .../rules/models/RuleVariableNewestEvent.kt | 8 +++- .../models/RuleVariableNewestStageEvent.kt | 8 +++- .../kotlin/org/hisp/dhis/rules/utils/Utils.kt | 33 ------------- .../hisp/dhis/rules/RuleEngineFunctionTest.kt | 46 +++++++++++++++++++ .../models/RuleVariableValueMapBuilderTest.kt | 2 +- 7 files changed, 65 insertions(+), 41 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 70510229..b2f3ff89 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ repositories { maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } } -version = "3.3.3-SNAPSHOT" +version = "3.3.2-SNAPSHOT" group = "org.hisp.dhis.rules" if (project.hasProperty("removeSnapshotSuffix")) { diff --git a/src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleVariableValueMapBuilder.kt b/src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleVariableValueMapBuilder.kt index 7e340f8b..032aba31 100644 --- a/src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleVariableValueMapBuilder.kt +++ b/src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleVariableValueMapBuilder.kt @@ -177,7 +177,10 @@ internal class RuleVariableValueMapBuilder { RuleValueType.TEXT, ruleEvent.event, listOf(ruleEvent.event), - currentDate.toString(), + ruleEvent.eventDate + .toLocalDateTime(TimeZone.currentSystemDefault()) + .date + .toString(), ) val status = ruleEvent.status.toString() valueMap[RuleEngineUtils.ENV_VAR_EVENT_STATUS] = @@ -210,7 +213,7 @@ internal class RuleVariableValueMapBuilder { RuleValueType.TEXT, ruleEnrollment.enrollment, listOf(ruleEnrollment.enrollment), - currentDate.toString(), + ruleEnrollment.enrollmentDate.toString(), ) valueMap[RuleEngineUtils.ENV_VAR_ENROLLMENT_COUNT] = RuleVariableValue( diff --git a/src/commonMain/kotlin/org/hisp/dhis/rules/models/RuleVariableNewestEvent.kt b/src/commonMain/kotlin/org/hisp/dhis/rules/models/RuleVariableNewestEvent.kt index 09a41b6e..63762952 100644 --- a/src/commonMain/kotlin/org/hisp/dhis/rules/models/RuleVariableNewestEvent.kt +++ b/src/commonMain/kotlin/org/hisp/dhis/rules/models/RuleVariableNewestEvent.kt @@ -1,7 +1,8 @@ package org.hisp.dhis.rules.models +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime import org.hisp.dhis.rules.engine.RuleVariableValue -import org.hisp.dhis.rules.utils.getLastUpdateDate class RuleVariableNewestEvent( override val name: String, @@ -25,7 +26,10 @@ class RuleVariableNewestEvent( fieldType, optionValue, ruleDataValues.map { it.value }, - getLastUpdateDate(ruleDataValues), + value.eventDate + .toLocalDateTime(TimeZone.currentSystemDefault()) + .date + .toString(), ) } } diff --git a/src/commonMain/kotlin/org/hisp/dhis/rules/models/RuleVariableNewestStageEvent.kt b/src/commonMain/kotlin/org/hisp/dhis/rules/models/RuleVariableNewestStageEvent.kt index 221dfa74..8f02fc17 100644 --- a/src/commonMain/kotlin/org/hisp/dhis/rules/models/RuleVariableNewestStageEvent.kt +++ b/src/commonMain/kotlin/org/hisp/dhis/rules/models/RuleVariableNewestStageEvent.kt @@ -1,7 +1,8 @@ package org.hisp.dhis.rules.models +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime import org.hisp.dhis.rules.engine.RuleVariableValue -import org.hisp.dhis.rules.utils.getLastUpdateDate class RuleVariableNewestStageEvent( override val name: String, @@ -27,7 +28,10 @@ class RuleVariableNewestStageEvent( fieldType, optionValue, stageRuleDataValues.map { it.value }, - getLastUpdateDate(stageRuleDataValues), + value.eventDate + .toLocalDateTime(TimeZone.currentSystemDefault()) + .date + .toString(), ) } } diff --git a/src/commonMain/kotlin/org/hisp/dhis/rules/utils/Utils.kt b/src/commonMain/kotlin/org/hisp/dhis/rules/utils/Utils.kt index ca7df17e..e50cea39 100644 --- a/src/commonMain/kotlin/org/hisp/dhis/rules/utils/Utils.kt +++ b/src/commonMain/kotlin/org/hisp/dhis/rules/utils/Utils.kt @@ -4,39 +4,6 @@ import kotlinx.datetime.* import org.hisp.dhis.rules.models.RuleDataValueHistory import org.hisp.dhis.rules.models.RuleEvent -fun getLastUpdateDateForPrevious( - ruleDataValues: List, - ruleEvent: RuleEvent, -): String { - val dates: MutableList = ArrayList() - for (date in ruleDataValues) { - val d = date.eventDate - if (d < ruleEvent.eventDate || - (ruleEvent.eventDate == d && ruleEvent.createdDate > date.createdDate) - ) { - dates.add(d) - } - } - return dates - .max() - .toLocalDateTime(TimeZone.currentSystemDefault()) - .date - .toString() -} - -fun getLastUpdateDate(ruleDataValues: List): String { - val dates: MutableList = ArrayList() - for (date in ruleDataValues) { - val d = date.eventDate - dates.add(d) - } - return dates - .max() - .toLocalDateTime(TimeZone.currentSystemDefault()) - .date - .toString() -} - fun unwrapVariableName(variable: String): String { if (variable.startsWith("#{") && variable.endsWith("}")) { return variable.substring(2, variable.length - 1) diff --git a/src/commonTest/kotlin/org/hisp/dhis/rules/RuleEngineFunctionTest.kt b/src/commonTest/kotlin/org/hisp/dhis/rules/RuleEngineFunctionTest.kt index 5ce8c437..479e8a1c 100644 --- a/src/commonTest/kotlin/org/hisp/dhis/rules/RuleEngineFunctionTest.kt +++ b/src/commonTest/kotlin/org/hisp/dhis/rules/RuleEngineFunctionTest.kt @@ -3121,6 +3121,52 @@ class RuleEngineFunctionTest { assertEquals(dayAfterTomorrow.toString(), ruleEffects[0].data) } + @Test + fun evaluateLastEventDateForEventIdVariable() { + val dayBeforeYesterday = + LocalDate.Companion + .currentDate() + .minus( + 2, + DateTimeUnit.DAY, + ).atStartOfDayIn(TimeZone.currentSystemDefault()) + val ruleAction = + RuleAction( + "d2:lastEventDate(V{event_id})", + "DISPLAYTEXT", + mapOf(Pair("content", "test_action_content"), Pair("location", "feedback")), + ) + + val rule = Rule("true", listOf(ruleAction), "test_rule", "") + val ruleEngineContext = RuleEngineTestUtils.getRuleEngineContext(rule, listOf()) + val ruleEvent1 = + RuleEvent( + "test_event1", + "test_program_stage1", + "", + RuleEventStatus.ACTIVE, + dayBeforeYesterday, + dayBeforeYesterday, + LocalDate.currentDate(), + null, + "", + null, + listOf( + RuleDataValue( + "test_data_element_one", + "value1", + ), + ), + ) + val ruleEffects = RuleEngine.getInstance().evaluate(ruleEvent1, null, listOf(), ruleEngineContext) + assertEquals(1, ruleEffects.size) + assertEquals(ruleAction, ruleEffects[0].ruleAction) + val expectedDate = dayBeforeYesterday.toLocalDateTime(TimeZone.currentSystemDefault()) + .date + .toString() + assertEquals(expectedDate, ruleEffects[0].data) + } + companion object { private const val DATE_PATTERN = "yyyy-MM-dd" private const val USE_CODE_FOR_OPTION_SET = true diff --git a/src/commonTest/kotlin/org/hisp/dhis/rules/models/RuleVariableValueMapBuilderTest.kt b/src/commonTest/kotlin/org/hisp/dhis/rules/models/RuleVariableValueMapBuilderTest.kt index 217d42d4..ef73a020 100644 --- a/src/commonTest/kotlin/org/hisp/dhis/rules/models/RuleVariableValueMapBuilderTest.kt +++ b/src/commonTest/kotlin/org/hisp/dhis/rules/models/RuleVariableValueMapBuilderTest.kt @@ -370,7 +370,7 @@ class RuleVariableValueMapBuilderTest { RuleVariableValueAssert .assertThatVariable(valueMap["due_date"]!!) .hasValue(null) - .isTypeOf(RuleValueType.TEXT) + .isTypeOf(RuleValueType.DATE) .hasCandidates() RuleVariableValueAssert .assertThatVariable(valueMap["test_variable_one"]!!) From bf8014cd3ac2e1ec5b91b6f0ee73d67d7aeb5909 Mon Sep 17 00:00:00 2001 From: Enrico Date: Mon, 27 Jan 2025 07:49:42 -0300 Subject: [PATCH 9/9] Fix review comments --- .../hisp/dhis/rules/engine/RuleVariableValueMapBuilder.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleVariableValueMapBuilder.kt b/src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleVariableValueMapBuilder.kt index 032aba31..2b8abaff 100644 --- a/src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleVariableValueMapBuilder.kt +++ b/src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleVariableValueMapBuilder.kt @@ -146,7 +146,7 @@ internal class RuleVariableValueMapBuilder { RuleVariableValue( RuleValueType.DATE, eventDate, - if(eventDate == null) listOf() else listOf(eventDate), + eventDate?.let { listOf(it) } ?: emptyList(), currentDate.toString(), ) @@ -154,14 +154,14 @@ internal class RuleVariableValueMapBuilder { RuleVariableValue( RuleValueType.DATE, ruleEvent.dueDate?.toString(), - if(ruleEvent.dueDate == null) listOf() else listOf(ruleEvent.dueDate.toString()), + ruleEvent.dueDate?.let { listOf(it.toString()) } ?: emptyList(), currentDate.toString(), ) valueMap[RuleEngineUtils.ENV_VAR_COMPLETED_DATE] = RuleVariableValue( RuleValueType.DATE, ruleEvent.completedDate?.toString(), - if(ruleEvent.completedDate == null) listOf() else listOf(ruleEvent.completedDate.toString()), + ruleEvent.completedDate?.let { listOf(it.toString()) } ?: emptyList(), currentDate.toString(), ) val eventCount = ruleEvents.size.toString()