Skip to content

Commit

Permalink
Fix: Prioritise payload/bundled events over database events for enrol…
Browse files Browse the repository at this point in the history
…ment[DHIS2-17806] (#19330)
  • Loading branch information
zubaira authored Dec 3, 2024
1 parent 325b90f commit e5823c0
Show file tree
Hide file tree
Showing 5 changed files with 352 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.ListUtils;
import org.hisp.dhis.dxf2.events.event.EventQueryParams;
Expand Down Expand Up @@ -207,19 +206,34 @@ private List<TrackedEntityAttributeValue> getAttributes(
// if they are present in both places
private Set<ProgramStageInstance> getEventsFromEnrollment(
String enrollmentUid, TrackerBundle bundle, TrackerPreheat preheat) {

// Fetch events linked to the enrollment from the database
EventQueryParams eventQueryParams = new EventQueryParams();
eventQueryParams.setProgramInstances(Set.of(enrollmentUid));
List<org.hisp.dhis.dxf2.events.event.Event> events =
List<org.hisp.dhis.dxf2.events.event.Event> dbEvents =
eventService.getEvents(eventQueryParams).getEvents();

Stream<ProgramStageInstance> programStageInstances =
events.stream().map(e -> programStageInstanceService.getProgramStageInstance(e.getUid()));
// Convert DB events to ProgramStageInstances
Map<String, ProgramStageInstance> dbProgramStageInstances =
dbEvents.stream()
.collect(
Collectors.toMap(
org.hisp.dhis.dxf2.events.event.Event::getUid,
e -> programStageInstanceService.getProgramStageInstance(e.getUid())));

Stream<ProgramStageInstance> bundleEvents =
// Fetch events from the payload for the given enrollment
Map<String, ProgramStageInstance> payloadProgramStageInstances =
bundle.getEvents().stream()
.filter(e -> e.getEnrollment().equals(enrollmentUid))
.map(event -> eventTrackerConverterService.fromForRuleEngine(preheat, event));
.filter(event -> event.getEnrollment().equals(enrollmentUid))
.collect(
Collectors.toMap(
Event::getUid,
event -> eventTrackerConverterService.fromForRuleEngine(preheat, event)));

// Merge payload events (prioritized) with DB events
dbProgramStageInstances.putAll(payloadProgramStageInstances);

return Stream.concat(programStageInstances, bundleEvents).collect(Collectors.toSet());
// Return a Set of unique ProgramStageInstances
return new HashSet<>(dbProgramStageInstances.values());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,20 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.hisp.dhis.common.AuditType;
import org.hisp.dhis.dataelement.DataElement;
import org.hisp.dhis.trackedentitydatavalue.TrackedEntityDataValueAudit;
import org.hisp.dhis.tracker.job.TrackerSideEffectDataBundle;
import org.hisp.dhis.tracker.report.ImportReport;
import org.hisp.dhis.tracker.report.Status;
import org.hisp.dhis.tracker.report.TrackerTypeReport;
import org.hisp.dhis.tracker.report.ValidationReport;
import org.hisp.dhis.tracker.sideeffect.TrackerRuleEngineSideEffect;
import org.hisp.dhis.tracker.sideeffect.TrackerSendMessageSideEffect;
import org.hisp.dhis.tracker.validation.ValidationCode;
import org.hisp.dhis.util.DateUtils;
import org.joda.time.format.DateTimeFormat;
Expand Down Expand Up @@ -274,6 +280,52 @@ public static void assertNoErrorsAndNoWarnings(ImportReport report) {
+ report.getValidationReport().getWarnings()));
}

public static void assertHasNoNotificationSideEffects(ImportReport report) {
assertNotNull(report, "The ImportReport should not be null.");

TrackerTypeReport typeReport =
report.getPersistenceReport().getTypeReportMap().get(TrackerType.EVENT);

assertNotNull(typeReport, "The TrackerTypeReport for EVENT should not be null.");
assertFalse(
typeReport.getSideEffectDataBundles().isEmpty(),
"Expected side effect data bundles but none were found.");

TrackerSideEffectDataBundle sideEffectDataBundle = typeReport.getSideEffectDataBundles().get(0);

List<TrackerRuleEngineSideEffect> ruleEngineSideEffects =
sideEffectDataBundle.getEventRuleEffects().values().stream()
.flatMap(List::stream)
.collect(Collectors.toList());

assertTrue(
ruleEngineSideEffects.stream().noneMatch(TrackerSendMessageSideEffect.class::isInstance),
"Unexpected notification side effect (TrackerSendMessageSideEffect) found.");
}

public static void assertHasNotificationSideEffects(ImportReport report) {
assertNotNull(report, "The ImportReport should not be null.");

TrackerTypeReport typeReport =
report.getPersistenceReport().getTypeReportMap().get(TrackerType.EVENT);

assertNotNull(typeReport, "The TrackerTypeReport for EVENT should not be null.");
assertFalse(
typeReport.getSideEffectDataBundles().isEmpty(),
"Expected side effect data bundles but none were found.");

TrackerSideEffectDataBundle sideEffectDataBundle = typeReport.getSideEffectDataBundles().get(0);

List<TrackerRuleEngineSideEffect> ruleEngineSideEffects =
sideEffectDataBundle.getEventRuleEffects().values().stream()
.flatMap(List::stream) // Flatten the list of lists into a single stream
.collect(Collectors.toList()); // Collect into a single list

assertTrue(
ruleEngineSideEffects.stream().anyMatch(TrackerSendMessageSideEffect.class::isInstance),
"Expected notification side effect (TrackerSendMessageSideEffect) but none were found.");
}

public static void assertNoErrors(ImportReport report) {
assertNotNull(report);
assertEquals(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import static org.hisp.dhis.programrule.ProgramRuleActionType.SHOWERROR;
import static org.hisp.dhis.programrule.ProgramRuleActionType.SHOWWARNING;
import static org.hisp.dhis.tracker.Assertions.assertHasError;
import static org.hisp.dhis.tracker.Assertions.assertHasNoNotificationSideEffects;
import static org.hisp.dhis.tracker.Assertions.assertHasNotificationSideEffects;
import static org.hisp.dhis.tracker.Assertions.assertHasOnlyErrors;
import static org.hisp.dhis.tracker.Assertions.assertHasOnlyWarnings;
import static org.hisp.dhis.tracker.Assertions.assertNoErrors;
Expand All @@ -54,6 +56,7 @@
import org.hisp.dhis.programrule.ProgramRuleVariableService;
import org.hisp.dhis.tracker.TrackerImportParams;
import org.hisp.dhis.tracker.TrackerImportService;
import org.hisp.dhis.tracker.TrackerImportStrategy;
import org.hisp.dhis.tracker.TrackerTest;
import org.hisp.dhis.tracker.report.ImportReport;
import org.hisp.dhis.tracker.validation.ValidationCode;
Expand All @@ -64,6 +67,7 @@ class ProgramRuleTest extends TrackerTest {
private static final String ENROLLMENT_UID = "TvctPPhpD8u";

private static final String EVENT_UID = "D9PbzJY8bJO";
private static final String TEMPLATE_UID = "D9PbzJY8bNH";

private static final String PROGRAM_EVENT_UID = "PEVENT12345";

Expand All @@ -77,11 +81,12 @@ class ProgramRuleTest extends TrackerTest {

@Autowired private ConstantService constantService;

private Program program;
private Program programWithRegistration;

private Program programWithoutRegistration;

private DataElement dataElement1;
private DataElement dataElement6;

private ProgramStage programStageOnInsert;

Expand All @@ -90,19 +95,27 @@ class ProgramRuleTest extends TrackerTest {
@Override
public void initTest() throws IOException {
ObjectBundle bundle = setUpMetadata("tracker/simple_metadata.json");
program = bundle.getPreheat().get(PreheatIdentifier.UID, Program.class, "BFcipDERJnf");
programWithRegistration =
bundle.getPreheat().get(PreheatIdentifier.UID, Program.class, "BFcipDERJnf");
programWithoutRegistration =
bundle.getPreheat().get(PreheatIdentifier.UID, Program.class, "BFcipDERJne");
dataElement1 = bundle.getPreheat().get(PreheatIdentifier.UID, DataElement.class, "DATAEL00001");
DataElement dataElement2 =
bundle.getPreheat().get(PreheatIdentifier.UID, DataElement.class, "DATAEL00002");

dataElement6 = bundle.getPreheat().get(PreheatIdentifier.UID, DataElement.class, "DATAEL00006");
programStageOnInsert =
bundle.getPreheat().get(PreheatIdentifier.UID, ProgramStage.class, "NpsdDv6kKSO");
programStageOnComplete =
bundle.getPreheat().get(PreheatIdentifier.UID, ProgramStage.class, "NpsdDv6kKS2");
ProgramRuleVariable programRuleVariable =
createProgramRuleVariableWithDataElement('A', program, dataElement2);
createProgramRuleVariableWithDataElement('A', programWithRegistration, dataElement2);

ProgramRuleVariable programRuleVariableDE6 =
createProgramRuleVariableWithDataElement('D', programWithRegistration, dataElement6);
programRuleVariableDE6.setName("integer_prv_de6");
programRuleVariableService.addProgramRuleVariable(programRuleVariable);
programRuleVariableService.addProgramRuleVariable(programRuleVariableDE6);
constantService.saveConstant(constant());

injectAdminUser();
Expand Down Expand Up @@ -171,6 +184,27 @@ void shouldImportEventWithNoWarningsWhenThereAreNoProgramRules() throws IOExcept
assertNoErrorsAndNoWarnings(report);
}

@Test
void shouldImportEventWithWarningsWhenPayloadEventDataIsPrioritized() throws IOException {
storeNotificationProgramRule(
'I',
programWithRegistration,
programStageOnInsert,
TEMPLATE_UID,
"#{integer_prv_de6} > 10");
ImportReport report =
trackerImportService.importTracker(
fromJson("tracker/programrule/tei_enrollment_with_event_and_no_datavalues.json"));
assertHasNoNotificationSideEffects(report);

TrackerImportParams importParams =
fromJson("tracker/programrule/event_updated_datavalues.json");
importParams.setImportStrategy(TrackerImportStrategy.UPDATE);
report = trackerImportService.importTracker(importParams);

assertHasNotificationSideEffects(report);
}

@Test
void shouldImportEventWithWarningsWhenAWarningIsTriggered() throws IOException {
ImportReport report =
Expand Down Expand Up @@ -352,15 +386,15 @@ void shouldImportWithNoWarningsWhenDataElementHasNullValue() throws IOException
}

private void alwaysTrueErrorProgramRule() {
storeProgramRule('A', program, ProgramRuleActionType.SHOWERROR);
storeProgramRule('A', programWithRegistration, ProgramRuleActionType.SHOWERROR);
}

private void onCompleteErrorProgramRule() {
storeProgramRule('B', program, ProgramRuleActionType.ERRORONCOMPLETE);
storeProgramRule('B', programWithRegistration, ProgramRuleActionType.ERRORONCOMPLETE);
}

private void alwaysTrueWarningProgramRule() {
storeProgramRule('C', program, SHOWWARNING);
storeProgramRule('C', programWithRegistration, SHOWWARNING);
}

private void alwaysTrueWarningProgramEventProgramRule() {
Expand All @@ -372,11 +406,11 @@ private void alwaysTrueErrorProgramEventProgramRule() {
}

private void programStageWarningRule() {
storeProgramRule('G', program, programStageOnInsert, SHOWWARNING);
storeProgramRule('G', programWithRegistration, programStageOnInsert, SHOWWARNING, "true");
}

private void syntaxErrorRule() {
ProgramRule programRule = createProgramRule('H', program, null, "SYNTAX ERROR");
ProgramRule programRule = createProgramRule('H', programWithRegistration, null, "SYNTAX ERROR");
programRuleService.addProgramRule(programRule);
ProgramRuleAction programRuleAction =
createProgramRuleAction(programRule, SHOWERROR, null, null);
Expand All @@ -386,11 +420,12 @@ private void syntaxErrorRule() {
}

private void programStage2WarningRule() {
storeProgramRule('I', program, programStageOnComplete, SHOWWARNING);
storeProgramRule('I', programWithRegistration, programStageOnComplete, SHOWWARNING, "true");
}

private void programStage2WrongDataElementWarningRule() {
ProgramRule programRule = createProgramRule('J', program, programStageOnComplete, "true");
ProgramRule programRule =
createProgramRule('J', programWithRegistration, programStageOnComplete, "true");
programRuleService.addProgramRule(programRule);
ProgramRuleAction programRuleAction =
createProgramRuleAction(programRule, SHOWWARNING, dataElement1, null);
Expand All @@ -402,7 +437,10 @@ private void programStage2WrongDataElementWarningRule() {
private void showErrorWhenVariableHasValueRule() {
ProgramRule programRule =
createProgramRule(
'K', program, programStageOnInsert, "d2:hasValue(#{ProgramRuleVariableA})");
'K',
programWithRegistration,
programStageOnInsert,
"d2:hasValue(#{ProgramRuleVariableA})");
programRuleService.addProgramRule(programRule);
ProgramRuleAction programRuleAction =
createProgramRuleAction(programRule, SHOWERROR, null, null);
Expand All @@ -424,15 +462,16 @@ private void conditionWithConstantEvaluatesToTrue() {

private void storeProgramRule(
char uniqueCharacter, Program program, ProgramRuleActionType actionType) {
storeProgramRule(uniqueCharacter, program, null, actionType);
storeProgramRule(uniqueCharacter, program, null, actionType, "true");
}

private void storeProgramRule(
char uniqueCharacter,
Program program,
ProgramStage programStage,
ProgramRuleActionType actionType) {
ProgramRule programRule = createProgramRule(uniqueCharacter, program, programStage, "true");
ProgramRuleActionType actionType,
String condition) {
ProgramRule programRule = createProgramRule(uniqueCharacter, program, programStage, condition);
programRuleService.addProgramRule(programRule);
ProgramRuleAction programRuleAction =
createProgramRuleAction(programRule, actionType, null, null);
Expand All @@ -441,6 +480,22 @@ private void storeProgramRule(
programRuleService.updateProgramRule(programRule);
}

private void storeNotificationProgramRule(
char uniqueCharacter,
Program program,
ProgramStage programStage,
String notification,
String condition) {
ProgramRule programRule = createProgramRule(uniqueCharacter, program, programStage, condition);
programRuleService.addProgramRule(programRule);
ProgramRuleAction sendMessageProgramRuleAction =
createProgramRuleAction(programRule, ProgramRuleActionType.SENDMESSAGE, null, null);
sendMessageProgramRuleAction.setTemplateUid(notification);
programRuleActionService.addProgramRuleAction(sendMessageProgramRuleAction);
programRule.getProgramRuleActions().add(sendMessageProgramRuleAction);
programRuleService.updateProgramRule(programRule);
}

private ProgramRule createProgramRule(
char uniqueCharacter, Program program, ProgramStage programStage, String condition) {
ProgramRule programRule = createProgramRule(uniqueCharacter, program);
Expand Down
Loading

0 comments on commit e5823c0

Please sign in to comment.