Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Allow evaluate in progress events [DHIS2-18631] #145

Merged
merged 11 commits into from
Jan 27, 2025
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ repositories {
maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") }
}

version = "3.3.1-SNAPSHOT"
version = "3.3.2-SNAPSHOT"
group = "org.hisp.dhis.rules"

if (project.hasProperty("removeSnapshotSuffix")) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -133,39 +135,35 @@ internal class RuleVariableValueMapBuilder {
currentDate: LocalDate,
): Map<String, RuleVariableValue> {
val valueMap: MutableMap<String, RuleVariableValue> = HashMap()
val eventDate =
ruleEvent.eventDate
.toLocalDateTime(TimeZone.currentSystemDefault())
.date
.toString()
valueMap[RuleEngineUtils.ENV_VAR_EVENT_DATE] =
RuleVariableValue(
RuleValueType.DATE,
eventDate,
listOf(eventDate),
currentDate.toString(),
)
if (ruleEvent.dueDate != null) {
val dueDate = ruleEvent.dueDate
valueMap[RuleEngineUtils.ENV_VAR_DUE_DATE] =
RuleVariableValue(
RuleValueType.DATE,
dueDate.toString(),
listOf(dueDate.toString()),
currentDate.toString(),
)
}
if (ruleEvent.completedDate != null) {
val completedDate = ruleEvent.completedDate
valueMap[RuleEngineUtils.ENV_VAR_COMPLETED_DATE] =
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.DATE,
completedDate.toString(),
listOf(completedDate.toString()),
eventDate,
eventDate?.let { listOf(it) } ?: emptyList(),
currentDate.toString(),
)
}

valueMap[RuleEngineUtils.ENV_VAR_DUE_DATE] =
RuleVariableValue(
RuleValueType.DATE,
ruleEvent.dueDate?.toString(),
ruleEvent.dueDate?.let { listOf(it.toString()) } ?: emptyList(),
currentDate.toString(),
)
valueMap[RuleEngineUtils.ENV_VAR_COMPLETED_DATE] =
RuleVariableValue(
RuleValueType.DATE,
ruleEvent.completedDate?.toString(),
ruleEvent.completedDate?.let { listOf(it.toString()) } ?: emptyList(),
currentDate.toString(),
)
val eventCount = ruleEvents.size.toString()
valueMap[RuleEngineUtils.ENV_VAR_EVENT_COUNT] =
RuleVariableValue(
Expand All @@ -179,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] =
Expand Down Expand Up @@ -212,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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -25,7 +26,10 @@ class RuleVariableNewestEvent(
fieldType,
optionValue,
ruleDataValues.map { it.value },
getLastUpdateDate(ruleDataValues),
value.eventDate
.toLocalDateTime(TimeZone.currentSystemDefault())
.date
.toString(),
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -27,7 +28,10 @@ class RuleVariableNewestStageEvent(
fieldType,
optionValue,
stageRuleDataValues.map { it.value },
getLastUpdateDate(stageRuleDataValues),
value.eventDate
.toLocalDateTime(TimeZone.currentSystemDefault())
.date
.toString(),
)
}
}
Expand Down
33 changes: 0 additions & 33 deletions src/commonMain/kotlin/org/hisp/dhis/rules/utils/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<RuleDataValueHistory>,
ruleEvent: RuleEvent,
): String {
val dates: MutableList<Instant> = 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<RuleDataValueHistory>): String {
val dates: MutableList<Instant> = 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)
Expand Down
152 changes: 151 additions & 1 deletion src/commonTest/kotlin/org/hisp/dhis/rules/RuleEngineFunctionTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -158,14 +196,80 @@ class RuleEngineFunctionTest {
"test_data_element_one",
"test_value",
),
),
)
)
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 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")
Expand Down Expand Up @@ -3017,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
Expand Down
Loading
Loading