From 2e9f30b5097f0a8bfcc6434204ecdf076c2880bb Mon Sep 17 00:00:00 2001 From: marc Date: Fri, 3 Nov 2023 10:01:48 +0100 Subject: [PATCH] fix: Return all visible events when no program specified [TECH-1663] (#15549) * fix: Return all visible events when no program specified [TECH-1663] * fix: Return search scope and capture scope in old api [TECH-1663] * fix: Return all visible events when mode selected [TECH-1663] * fix: Return all visible events when mode selected [TECH-1663] * fix: Return all visible events when mode selected [TECH-1663] * fix: Add capture scope org unit to integration test [TECH-1633] --- .../tracker/event/JdbcEventStore.java | 99 ++++-- .../tracker/export/event/JdbcEventStore.java | 113 ++++-- .../tracker/AclEventExporterTest.java | 62 ++-- .../RegistrationMultiEventsServiceTest.java | 3 +- .../OrderAndPaginationExporterTest.java | 12 +- .../export/event/AclEventExporterTest.java | 115 ++++-- .../tracker/event_and_enrollment.json | 198 ++++++++++- .../resources/tracker/simple_metadata.json | 335 +++++++++++++++++- 8 files changed, 795 insertions(+), 142 deletions(-) diff --git a/dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/deprecated/tracker/event/JdbcEventStore.java b/dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/deprecated/tracker/event/JdbcEventStore.java index 4212002f3e70..a25bc0f2c7f9 100644 --- a/dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/deprecated/tracker/event/JdbcEventStore.java +++ b/dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/deprecated/tracker/event/JdbcEventStore.java @@ -185,6 +185,16 @@ public class JdbcEventStore implements EventStore { private static final String AND = " AND "; + private static final String COLUMN_USER_UID = "u_uid"; + + private static final String COLUMN_ORG_UNIT_PATH = "ou_path"; + + private static final String USER_SCOPE_ORG_UNIT_PATH_LIKE_MATCH_QUERY = + " ou.path like CONCAT(orgunit.path, '%') "; + + private static final String CUSTOM_ORG_UNIT_PATH_LIKE_MATCH_QUERY = + " ou.path like CONCAT(:" + COLUMN_ORG_UNIT_PATH + ", '%' ) "; + private static final Map QUERY_PARAM_COL_MAP = ImmutableMap.builder() .put(EVENT_ID, "psi_uid") @@ -296,10 +306,6 @@ public class JdbcEventStore implements EventStore { private static final String UPDATE_EVENT_SQL; - private static final String COLUMN_USER_UID = "u_uid"; - - private static final String COLUMN_ORG_UNIT_PATH = "ou_path"; - private static final String PERCENTAGE_SIGN = ", '%' "; /** @@ -2140,13 +2146,7 @@ private String createAccessibleSql( } mapSqlParameterSource.addValue(COLUMN_USER_UID, user.getUid()); - return " EXISTS(SELECT ss.organisationunitid " - + " FROM userteisearchorgunits ss " - + " JOIN organisationunit orgunit ON orgunit.organisationunitid = ss.organisationunitid " - + " JOIN userinfo u ON u.userinfoid = ss.userinfoid " - + " WHERE u.uid = :" - + COLUMN_USER_UID - + " AND ou.path like CONCAT(orgunit.path, '%')) "; + return getSearchAndCaptureScopeOrgUnitPathMatchQuery(USER_SCOPE_ORG_UNIT_PATH_LIKE_MATCH_QUERY); } private String createDescendantsSql( @@ -2155,54 +2155,54 @@ private String createDescendantsSql( if (isProgramRestricted(params.getProgram())) { return createCaptureScopeQuery( - user, - mapSqlParameterSource, - " AND ou.path like CONCAT(:" + COLUMN_ORG_UNIT_PATH + PERCENTAGE_SIGN + ")"); + user, mapSqlParameterSource, AND + CUSTOM_ORG_UNIT_PATH_LIKE_MATCH_QUERY); } - return " ou.path like CONCAT(:" + COLUMN_ORG_UNIT_PATH + PERCENTAGE_SIGN + ") "; + mapSqlParameterSource.addValue(COLUMN_USER_UID, user.getUid()); + return getSearchAndCaptureScopeOrgUnitPathMatchQuery(CUSTOM_ORG_UNIT_PATH_LIKE_MATCH_QUERY); } private String createChildrenSql( User user, EventSearchParams params, MapSqlParameterSource mapSqlParameterSource) { mapSqlParameterSource.addValue(COLUMN_ORG_UNIT_PATH, params.getOrgUnit().getPath()); + String customChildrenQuery = + " AND (ou.hierarchylevel = " + + params.getOrgUnit().getHierarchyLevel() + + " OR ou.hierarchylevel = " + + (params.getOrgUnit().getHierarchyLevel() + 1) + + " ) "; + if (isProgramRestricted(params.getProgram())) { - String childrenSqlClause = - " AND ou.path like CONCAT(:" - + COLUMN_ORG_UNIT_PATH - + PERCENTAGE_SIGN - + ") " - + " AND (ou.hierarchylevel = " - + params.getOrgUnit().getHierarchyLevel() - + " OR ou.hierarchylevel = " - + (params.getOrgUnit().getHierarchyLevel() + 1) - + " )"; - - return createCaptureScopeQuery(user, mapSqlParameterSource, childrenSqlClause); - } - - return " ou.path like CONCAT(:" - + COLUMN_ORG_UNIT_PATH - + PERCENTAGE_SIGN - + ") " - + " AND (ou.hierarchylevel = " - + params.getOrgUnit().getHierarchyLevel() - + " OR ou.hierarchylevel = " - + (params.getOrgUnit().getHierarchyLevel() + 1) - + " ) "; + return createCaptureScopeQuery( + user, + mapSqlParameterSource, + AND + CUSTOM_ORG_UNIT_PATH_LIKE_MATCH_QUERY + customChildrenQuery); + } + + mapSqlParameterSource.addValue(COLUMN_USER_UID, user.getUid()); + return getSearchAndCaptureScopeOrgUnitPathMatchQuery( + CUSTOM_ORG_UNIT_PATH_LIKE_MATCH_QUERY + customChildrenQuery); } private String createSelectedSql( User user, EventSearchParams params, MapSqlParameterSource mapSqlParameterSource) { mapSqlParameterSource.addValue(COLUMN_ORG_UNIT_PATH, params.getOrgUnit().getPath()); + String orgUnitPathEqualsMatchQuery = + " ou.path = :" + + COLUMN_ORG_UNIT_PATH + + " " + + AND + + USER_SCOPE_ORG_UNIT_PATH_LIKE_MATCH_QUERY; + if (isProgramRestricted(params.getProgram())) { String customSelectedClause = " AND ou.path = :" + COLUMN_ORG_UNIT_PATH + " "; return createCaptureScopeQuery(user, mapSqlParameterSource, customSelectedClause); } - return " ou.path = :" + COLUMN_ORG_UNIT_PATH + " "; + mapSqlParameterSource.addValue(COLUMN_USER_UID, user.getUid()); + return getSearchAndCaptureScopeOrgUnitPathMatchQuery(orgUnitPathEqualsMatchQuery); } private boolean isProgramRestricted(Program program) { @@ -2227,4 +2227,25 @@ private String createCaptureScopeQuery( + customClause + ") "; } + + private static String getSearchAndCaptureScopeOrgUnitPathMatchQuery(String orgUnitMatcher) { + return " (EXISTS(SELECT ss.organisationunitid " + + " FROM userteisearchorgunits ss " + + " JOIN userinfo u ON u.userinfoid = ss.userinfoid " + + " JOIN organisationunit orgunit ON orgunit.organisationunitid = ss.organisationunitid " + + " WHERE u.uid = :" + + COLUMN_USER_UID + + AND + + orgUnitMatcher + + " AND p.accesslevel in ('OPEN', 'AUDITED')) " + + " OR EXISTS(SELECT cs.organisationunitid " + + " FROM usermembership cs " + + " JOIN userinfo u ON u.userinfoid = cs.userinfoid " + + " JOIN organisationunit orgunit ON orgunit.organisationunitid = cs.organisationunitid " + + " WHERE u.uid = :" + + COLUMN_USER_UID + + AND + + orgUnitMatcher + + " )) "; + } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/JdbcEventStore.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/JdbcEventStore.java index 30678a7b5f00..4874c2eae089 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/JdbcEventStore.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/JdbcEventStore.java @@ -179,7 +179,9 @@ class JdbcEventStore implements EventStore { private static final String COLUMN_USER_UID = "u_uid"; private static final String COLUMN_ORG_UNIT_PATH = "ou_path"; private static final String DEFAULT_ORDER = COLUMN_EVENT_ID + " desc"; - private static final String ORG_UNIT_PATH_LIKE_MATCH_QUERY = + private static final String USER_SCOPE_ORG_UNIT_PATH_LIKE_MATCH_QUERY = + " ou.path like CONCAT(orgunit.path, '%') "; + private static final String CUSTOM_ORG_UNIT_PATH_LIKE_MATCH_QUERY = " ou.path like CONCAT(:" + COLUMN_ORG_UNIT_PATH + ", '%' ) "; /** @@ -1097,14 +1099,7 @@ private String createAccessibleSql( } mapSqlParameterSource.addValue(COLUMN_USER_UID, user.getUid()); - return " EXISTS(SELECT ss.organisationunitid " - + " FROM userteisearchorgunits ss " - + " JOIN organisationunit orgunit ON orgunit.organisationunitid = ss.organisationunitid " - + " JOIN userinfo u ON u.userinfoid = ss.userinfoid " - + " WHERE u.uid = :" - + COLUMN_USER_UID - + " AND ou.path like CONCAT(orgunit.path, '%') " - + ") "; + return getSearchAndCaptureScopeOrgUnitPathMatchQuery(USER_SCOPE_ORG_UNIT_PATH_LIKE_MATCH_QUERY); } private String createDescendantsSql( @@ -1113,60 +1108,64 @@ private String createDescendantsSql( if (isProgramRestricted(params.getProgram())) { return createCaptureScopeQuery( - user, mapSqlParameterSource, AND + ORG_UNIT_PATH_LIKE_MATCH_QUERY); + user, mapSqlParameterSource, AND + CUSTOM_ORG_UNIT_PATH_LIKE_MATCH_QUERY); } - return ORG_UNIT_PATH_LIKE_MATCH_QUERY; + mapSqlParameterSource.addValue(COLUMN_USER_UID, user.getUid()); + return getSearchAndCaptureScopeOrgUnitPathMatchQuery(CUSTOM_ORG_UNIT_PATH_LIKE_MATCH_QUERY); } private String createChildrenSql( User user, EventQueryParams params, MapSqlParameterSource mapSqlParameterSource) { mapSqlParameterSource.addValue(COLUMN_ORG_UNIT_PATH, params.getOrgUnit().getPath()); + String customChildrenQuery = + " AND (ou.hierarchylevel = " + + params.getOrgUnit().getHierarchyLevel() + + " OR ou.hierarchylevel = " + + (params.getOrgUnit().getHierarchyLevel() + 1) + + " ) "; + if (isProgramRestricted(params.getProgram())) { - String childrenSqlClause = - AND - + ORG_UNIT_PATH_LIKE_MATCH_QUERY - + " AND (ou.hierarchylevel = " - + params.getOrgUnit().getHierarchyLevel() - + " OR ou.hierarchylevel = " - + (params.getOrgUnit().getHierarchyLevel() + 1) - + " )"; - - return createCaptureScopeQuery(user, mapSqlParameterSource, childrenSqlClause); + return createCaptureScopeQuery( + user, + mapSqlParameterSource, + AND + CUSTOM_ORG_UNIT_PATH_LIKE_MATCH_QUERY + customChildrenQuery); } - return ORG_UNIT_PATH_LIKE_MATCH_QUERY - + " AND (ou.hierarchylevel = " - + params.getOrgUnit().getHierarchyLevel() - + " OR ou.hierarchylevel = " - + (params.getOrgUnit().getHierarchyLevel() + 1) - + " ) "; + mapSqlParameterSource.addValue(COLUMN_USER_UID, user.getUid()); + return getSearchAndCaptureScopeOrgUnitPathMatchQuery( + CUSTOM_ORG_UNIT_PATH_LIKE_MATCH_QUERY + customChildrenQuery); } private String createSelectedSql( User user, EventQueryParams params, MapSqlParameterSource mapSqlParameterSource) { mapSqlParameterSource.addValue(COLUMN_ORG_UNIT_PATH, params.getOrgUnit().getPath()); - String orgUnitPathEqualsMatchQuery = " ou.path = :" + COLUMN_ORG_UNIT_PATH + " "; + String orgUnitPathEqualsMatchQuery = + " ou.path = :" + + COLUMN_ORG_UNIT_PATH + + " " + + AND + + USER_SCOPE_ORG_UNIT_PATH_LIKE_MATCH_QUERY; + if (isProgramRestricted(params.getProgram())) { String customSelectedClause = AND + orgUnitPathEqualsMatchQuery; return createCaptureScopeQuery(user, mapSqlParameterSource, customSelectedClause); } - return orgUnitPathEqualsMatchQuery; - } - - private boolean isProgramRestricted(Program program) { - return program != null && (program.isProtected() || program.isClosed()); - } - - private boolean isUserSearchScopeNotSet(User user) { - return user.getTeiSearchOrganisationUnits().isEmpty(); + mapSqlParameterSource.addValue(COLUMN_USER_UID, user.getUid()); + return getSearchAndCaptureScopeOrgUnitPathMatchQuery(orgUnitPathEqualsMatchQuery); } + /** + * Generates a sql to match the org unit event to the org unit(s) in the user's capture scope + * + * @param orgUnitMatcher specific condition to add depending on the ou mode + * @return a sql clause to add to the main query + */ private String createCaptureScopeQuery( - User user, MapSqlParameterSource mapSqlParameterSource, String customClause) { + User user, MapSqlParameterSource mapSqlParameterSource, String orgUnitMatcher) { mapSqlParameterSource.addValue(COLUMN_USER_UID, user.getUid()); return " EXISTS(SELECT cs.organisationunitid " @@ -1176,10 +1175,46 @@ private String createCaptureScopeQuery( + " WHERE u.uid = :" + COLUMN_USER_UID + " AND ou.path like CONCAT(orgunit.path, '%') " - + customClause + + orgUnitMatcher + ") "; } + /** + * Generates a sql to match the org unit event to the org unit(s) in the user's search and capture + * scope + * + * @param orgUnitMatcher specific condition to add depending on the ou mode + * @return a sql clause to add to the main query + */ + private static String getSearchAndCaptureScopeOrgUnitPathMatchQuery(String orgUnitMatcher) { + return " (EXISTS(SELECT ss.organisationunitid " + + " FROM userteisearchorgunits ss " + + " JOIN userinfo u ON u.userinfoid = ss.userinfoid " + + " JOIN organisationunit orgunit ON orgunit.organisationunitid = ss.organisationunitid " + + " WHERE u.uid = :" + + COLUMN_USER_UID + + AND + + orgUnitMatcher + + " AND p.accesslevel in ('OPEN', 'AUDITED')) " + + " OR EXISTS(SELECT cs.organisationunitid " + + " FROM usermembership cs " + + " JOIN userinfo u ON u.userinfoid = cs.userinfoid " + + " JOIN organisationunit orgunit ON orgunit.organisationunitid = cs.organisationunitid " + + " WHERE u.uid = :" + + COLUMN_USER_UID + + AND + + orgUnitMatcher + + " )) "; + } + + private boolean isProgramRestricted(Program program) { + return program != null && (program.isProtected() || program.isClosed()); + } + + private boolean isUserSearchScopeNotSet(User user) { + return user.getTeiSearchOrganisationUnits().isEmpty(); + } + /** * For dataElement params, restriction is set in inner join. For query params, restriction is set * in where clause. diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/dxf2/deprecated/tracker/AclEventExporterTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/dxf2/deprecated/tracker/AclEventExporterTest.java index dd504ac2d152..7d9e651125c2 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/dxf2/deprecated/tracker/AclEventExporterTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/dxf2/deprecated/tracker/AclEventExporterTest.java @@ -121,7 +121,7 @@ void shouldReturnEventsWhenProgramClosedOuModeDescendantsAndOrgUnitInCaptureScop } @Test - void shouldReturnEventsWhenNoProgramSpecifiedOuModeDescendantsAndOrgUnitInSearchScope() { + void shouldReturnEventsWhenNoProgramSpecifiedOuModeDescendantsAndRootOrgUnitRequested() { injectSecurityContext(userService.getUser("FIgVWzUCkpw")); EventSearchParams params = new EventSearchParams(); params.setOrgUnit(get(OrganisationUnit.class, orgUnit.getUid())); @@ -133,12 +133,19 @@ void shouldReturnEventsWhenNoProgramSpecifiedOuModeDescendantsAndOrgUnitInSearch events.isEmpty(), "Expected to find events when no program specified, ou mode descendants and org units in search scope"); assertContainsOnly( - events.stream().map(Event::getOrgUnit).collect(Collectors.toSet()), - List.of("uoNW0E3xXUy", "h4w96yEMlzO", "tSsGrtfRzjY")); + events.stream().map(Event::getUid).collect(Collectors.toSet()), + List.of( + "YKmfzHdjUDL", + "jxgFyJEMUPf", + "D9PbzJY8bJM", + "pTzf9KYMk72", + "JaRDIvcEcEx", + "SbUJzkxKYAG", + "gvULMgNiAfM")); } @Test - void shouldReturnEventsWhenNoProgramSpecifiedOuModeDescenadantsAndOrgUnitInSearchScope() { + void shouldReturnEventsWhenNoProgramSpecifiedOuModeDescendantsAndOrgUnitInSearchScope() { injectSecurityContext(userService.getUser("FIgVWzUCkpw")); EventSearchParams params = new EventSearchParams(); params.setOrgUnit(get(OrganisationUnit.class, "uoNW0E3xXUy")); @@ -150,8 +157,8 @@ void shouldReturnEventsWhenNoProgramSpecifiedOuModeDescenadantsAndOrgUnitInSearc events.isEmpty(), "Expected to find events when no program specified, ou mode descendants and org units in search scope"); assertContainsOnly( - events.stream().map(Event::getOrgUnit).collect(Collectors.toSet()), - List.of("uoNW0E3xXUy", "tSsGrtfRzjY")); + events.stream().map(Event::getUid).collect(Collectors.toSet()), + List.of("jxgFyJEMUPf", "JaRDIvcEcEx", "SbUJzkxKYAG", "gvULMgNiAfM")); } @Test @@ -190,8 +197,8 @@ void shouldReturnEventsWhenNoProgramSpecifiedOuModeChildrenAndOrgUnitInSearchSco events.isEmpty(), "Expected to find events when no program specified, ou mode children and org units in search scope"); assertContainsOnly( - events.stream().map(Event::getOrgUnit).collect(Collectors.toSet()), - List.of("uoNW0E3xXUy", "h4w96yEMlzO")); + List.of("YKmfzHdjUDL", "jxgFyJEMUPf", "JaRDIvcEcEx", "D9PbzJY8bJM", "pTzf9KYMk72"), + events.stream().map(Event::getUid).collect(Collectors.toSet())); } @Test @@ -219,10 +226,10 @@ void shouldReturnEventsWhenProgramClosedOuModeSelectedAndOrgUnitInCaptureScope() @Test void shouldReturnEventsWhenNoProgramSpecifiedOuModeSelectedAndOrgUnitInSearchScope() { - injectSecurityContext(userService.getUser("FIgVWzUCkpw")); + injectSecurityContext(userService.getUser("nIidJVYpQQK")); EventSearchParams params = new EventSearchParams(); - params.setOrgUnit(get(OrganisationUnit.class, orgUnit.getUid())); + params.setOrgUnit(get(OrganisationUnit.class, "DiszpKrYNg8")); params.setOrgUnitSelectionMode(SELECTED); List events = eventService.getEvents(params).getEvents(); @@ -231,14 +238,9 @@ void shouldReturnEventsWhenNoProgramSpecifiedOuModeSelectedAndOrgUnitInSearchSco events.isEmpty(), "Expected to find events when no program specified, ou mode descendants and org units in search scope"); - events.forEach( - e -> - assertEquals( - "h4w96yEMlzO", - e.getOrgUnit(), - "Expected to find selected org unit h4w96yEMlzO, but found " - + e.getOrgUnit() - + " instead")); + assertContainsOnly( + List.of("ck7DzdxqLqA", "OTmjvJDn0Fu", "kWjSezkXHVp"), + events.stream().map(Event::getUid).collect(Collectors.toList())); } @Test @@ -334,7 +336,13 @@ void shouldReturnAllEventsWhenOrgUnitModeAllAndNoOrgUnitProvided() { events.isEmpty(), "Expected to find events when ou mode ALL no program specified and no org unit provided"); assertContainsOnly( - List.of("h4w96yEMlzO", "uoNW0E3xXUy", "DiszpKrYNg8", "tSsGrtfRzjY"), + List.of( + "lbDXJBlvtZe", + "uoNW0E3xXUy", + "RojfDTBhoGC", + "tSsGrtfRzjY", + "h4w96yEMlzO", + "DiszpKrYNg8"), events.stream().map(Event::getOrgUnit).collect(Collectors.toSet())); } @@ -352,7 +360,13 @@ void shouldIgnoreRequestedOrgUnitAndReturnAllEventsWhenOrgUnitModeAllAndOrgUnitP events.isEmpty(), "Expected to find events when ou mode ALL no program specified and org unit provided"); assertContainsOnly( - List.of("h4w96yEMlzO", "uoNW0E3xXUy", "DiszpKrYNg8", "tSsGrtfRzjY"), + List.of( + "lbDXJBlvtZe", + "uoNW0E3xXUy", + "RojfDTBhoGC", + "tSsGrtfRzjY", + "h4w96yEMlzO", + "DiszpKrYNg8"), events.stream().map(Event::getOrgUnit).collect(Collectors.toSet())); } @@ -369,7 +383,13 @@ void shouldReturnAllEventsWhenOrgUnitModeAllAndNoOrgUnitProvidedAndUserNull() { events.isEmpty(), "Expected to find events when ou mode ALL no program specified and no org unit provided"); assertContainsOnly( - List.of("h4w96yEMlzO", "uoNW0E3xXUy", "DiszpKrYNg8", "tSsGrtfRzjY"), + List.of( + "lbDXJBlvtZe", + "uoNW0E3xXUy", + "RojfDTBhoGC", + "tSsGrtfRzjY", + "h4w96yEMlzO", + "DiszpKrYNg8"), events.stream().map(Event::getOrgUnit).collect(Collectors.toSet())); } diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/dxf2/deprecated/tracker/RegistrationMultiEventsServiceTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/dxf2/deprecated/tracker/RegistrationMultiEventsServiceTest.java index b6a53beb8abe..1b91fa8dc92c 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/dxf2/deprecated/tracker/RegistrationMultiEventsServiceTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/dxf2/deprecated/tracker/RegistrationMultiEventsServiceTest.java @@ -35,6 +35,7 @@ import com.google.common.collect.Lists; import java.util.Date; import java.util.HashSet; +import java.util.Set; import org.hamcrest.CoreMatchers; import org.hisp.dhis.common.CodeGenerator; import org.hisp.dhis.common.IdentifiableObjectManager; @@ -166,7 +167,7 @@ protected void setUpTest() throws Exception { manager.update(programStageA); manager.update(programStageB); manager.update(programA); - createUserAndInjectSecurityContext(true); + createUserAndInjectSecurityContext(Set.of(organisationUnitA, organisationUnitB), true); } @Test diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/OrderAndPaginationExporterTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/OrderAndPaginationExporterTest.java index 1f2ddc8fe58b..7be8f923fa6b 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/OrderAndPaginationExporterTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/OrderAndPaginationExporterTest.java @@ -157,7 +157,9 @@ void shouldReturnPaginatedTrackedEntitiesGivenNonDefaultPageSize() assertAll( "second (last) page", () -> assertPager(2, 3, secondPage), - () -> assertEquals(List.of("QesgJkTyTCk", "guVNoAerxWo"), uids(secondPage.getItems()))); + () -> + assertEquals( + List.of("QesgJkTyTCk", "woitxQbWYNq", "guVNoAerxWo"), uids(secondPage.getItems()))); assertIsEmpty( trackedEntityService.getTrackedEntities(params, new PageParams(3, 3, false)).getItems()); @@ -180,7 +182,7 @@ void shouldReturnPaginatedTrackedEntitiesGivenNonDefaultPageSizeAndTotalPages() assertAll( "first page", - () -> assertPager(1, 3, 5, firstPage.getPager()), + () -> assertPager(1, 3, 6, firstPage.getPager()), () -> assertEquals( List.of("dUE514NMOlo", "mHWCacsGYYn", "QS6w44flWAf"), uids(firstPage.getItems()))); @@ -190,8 +192,10 @@ void shouldReturnPaginatedTrackedEntitiesGivenNonDefaultPageSizeAndTotalPages() assertAll( "second (last) page", - () -> assertPager(2, 3, 5, secondPage.getPager()), - () -> assertEquals(List.of("QesgJkTyTCk", "guVNoAerxWo"), uids(secondPage.getItems()))); + () -> assertPager(2, 3, 6, secondPage.getPager()), + () -> + assertEquals( + List.of("QesgJkTyTCk", "woitxQbWYNq", "guVNoAerxWo"), uids(secondPage.getItems()))); assertIsEmpty( trackedEntityService.getTrackedEntities(params, new PageParams(3, 3, true)).getItems()); diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/event/AclEventExporterTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/event/AclEventExporterTest.java index 91f5a96e42a8..e9f6311b80e3 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/event/AclEventExporterTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/event/AclEventExporterTest.java @@ -49,6 +49,7 @@ import java.util.Set; import java.util.stream.Collectors; import org.hisp.dhis.category.CategoryOption; +import org.hisp.dhis.common.BaseIdentifiableObject; import org.hisp.dhis.common.IdentifiableObject; import org.hisp.dhis.common.IdentifiableObjectManager; import org.hisp.dhis.feedback.BadRequestException; @@ -145,8 +146,15 @@ void shouldReturnEventsWhenNoProgramSpecifiedOuModeDescendantsAndOrgUnitInSearch events.isEmpty(), "Expected to find events when no program specified, ou mode descendants and org units in search scope"); assertContainsOnly( - List.of("uoNW0E3xXUy", "h4w96yEMlzO", "tSsGrtfRzjY"), - events.stream().map(e -> e.getOrganisationUnit().getUid()).collect(Collectors.toSet())); + List.of( + "YKmfzHdjUDL", + "jxgFyJEMUPf", + "D9PbzJY8bJM", + "pTzf9KYMk72", + "JaRDIvcEcEx", + "SbUJzkxKYAG", + "gvULMgNiAfM"), + events.stream().map(BaseIdentifiableObject::getUid).collect(Collectors.toSet())); } @Test @@ -188,8 +196,8 @@ void shouldReturnEventsWhenNoProgramSpecifiedOuModeChildrenAndOrgUnitInSearchSco events.isEmpty(), "Expected to find events when no program specified, ou mode children and org units in search scope"); assertContainsOnly( - List.of("uoNW0E3xXUy", "h4w96yEMlzO"), - events.stream().map(e -> e.getOrganisationUnit().getUid()).collect(Collectors.toSet())); + List.of("YKmfzHdjUDL", "jxgFyJEMUPf", "JaRDIvcEcEx", "D9PbzJY8bJM", "pTzf9KYMk72"), + events.stream().map(BaseIdentifiableObject::getUid).collect(Collectors.toSet())); } @Test @@ -253,24 +261,37 @@ void shouldReturnEventsWhenProgramClosedOuModeSelectedAndOrgUnitInCaptureScope() @Test void shouldReturnEventsWhenNoProgramSpecifiedOuModeSelectedAndOrgUnitInSearchScope() throws ForbiddenException, BadRequestException { + injectSecurityContext(userService.getUser("nIidJVYpQQK")); + EventOperationParams params = + EventOperationParams.builder().orgUnitUid("DiszpKrYNg8").orgUnitMode(SELECTED).build(); + + List events = eventService.getEvents(params); + + assertFalse( + events.isEmpty(), + "Expected to find events when no program specified, ou mode selected and org units in search scope"); + + assertContainsOnly( + List.of("ck7DzdxqLqA", "OTmjvJDn0Fu", "kWjSezkXHVp"), + events.stream().map(BaseIdentifiableObject::getUid).collect(Collectors.toSet())); + } + + @Test + void shouldReturnEventsWhenNoProgramSpecifiedOuModeSelectedAndOrgUnitInCaptureScope() + throws ForbiddenException, BadRequestException { injectSecurityContext(userService.getUser("FIgVWzUCkpw")); EventOperationParams params = - EventOperationParams.builder().orgUnitUid(orgUnit.getUid()).orgUnitMode(SELECTED).build(); + EventOperationParams.builder().orgUnitUid("RojfDTBhoGC").orgUnitMode(SELECTED).build(); List events = eventService.getEvents(params); assertFalse( events.isEmpty(), - "Expected to find events when no program specified, ou mode descendants and org units in search scope"); + "Expected to find events when no program specified, ou mode selected and org units in capture scope"); - events.forEach( - e -> - assertEquals( - "h4w96yEMlzO", - e.getOrganisationUnit().getUid(), - "Expected to find selected org unit h4w96yEMlzO, but found " - + e.getOrganisationUnit().getUid() - + " instead")); + assertContainsOnly( + List.of("SbUJzkxKYAG"), + events.stream().map(BaseIdentifiableObject::getUid).collect(Collectors.toSet())); } @Test @@ -352,23 +373,6 @@ void shouldReturnEventsWhenProgramClosedOuModeCapture() + " instead")); } - @Test - void shouldReturnEventsWhenNoProgramAndOuModeAccessible() - throws ForbiddenException, BadRequestException { - injectSecurityContext(userService.getUser("nIidJVYpQQK")); - EventOperationParams params = EventOperationParams.builder().orgUnitMode(ACCESSIBLE).build(); - - List events = eventService.getEvents(params); - - assertFalse( - events.isEmpty(), - "Expected to find events when ou mode accessible and no program specified"); - - assertContainsOnly( - List.of("uoNW0E3xXUy", "tSsGrtfRzjY"), - events.stream().map(e -> e.getOrganisationUnit().getUid()).collect(Collectors.toSet())); - } - @Test void shouldReturnAccessibleOrgUnitEventsWhenNoOrgUnitSpecified() throws ForbiddenException, BadRequestException { @@ -457,7 +461,13 @@ void shouldReturnAllEventsWhenOrgUnitModeAllAndNoOrgUnitProvided() events.isEmpty(), "Expected to find events when ou mode ALL no program specified and no org unit provided"); assertContainsOnly( - List.of("h4w96yEMlzO", "uoNW0E3xXUy", "DiszpKrYNg8", "tSsGrtfRzjY"), + List.of( + "lbDXJBlvtZe", + "uoNW0E3xXUy", + "RojfDTBhoGC", + "tSsGrtfRzjY", + "h4w96yEMlzO", + "DiszpKrYNg8"), events.stream().map(e -> e.getOrganisationUnit().getUid()).collect(Collectors.toSet())); } @@ -474,7 +484,13 @@ void shouldReturnAllEventsWhenOrgUnitModeAllAndNoOrgUnitProvidedAndUserNull() events.isEmpty(), "Expected to find events when ou mode ALL no program specified and no org unit provided"); assertContainsOnly( - List.of("h4w96yEMlzO", "uoNW0E3xXUy", "DiszpKrYNg8", "tSsGrtfRzjY"), + List.of( + "lbDXJBlvtZe", + "uoNW0E3xXUy", + "RojfDTBhoGC", + "tSsGrtfRzjY", + "h4w96yEMlzO", + "DiszpKrYNg8"), events.stream().map(e -> e.getOrganisationUnit().getUid()).collect(Collectors.toSet())); } @@ -492,10 +508,41 @@ void shouldIgnoreRequestedOrgUnitAndReturnAllEventsWhenOrgUnitModeAllAndOrgUnitP events.isEmpty(), "Expected to find events when ou mode ALL no program specified and org unit provided"); assertContainsOnly( - List.of("h4w96yEMlzO", "uoNW0E3xXUy", "DiszpKrYNg8", "tSsGrtfRzjY"), + List.of( + "lbDXJBlvtZe", + "uoNW0E3xXUy", + "RojfDTBhoGC", + "tSsGrtfRzjY", + "h4w96yEMlzO", + "DiszpKrYNg8"), events.stream().map(e -> e.getOrganisationUnit().getUid()).collect(Collectors.toSet())); } + @Test + void + shouldReturnOnlyVisibleEventsInSearchAndCaptureScopeWhenNoProgramPresentOrgUnitModeAccessible() + throws ForbiddenException, BadRequestException { + injectSecurityContext(userService.getUser("nIidJVYpQQK")); + + EventOperationParams params = EventOperationParams.builder().orgUnitMode(ACCESSIBLE).build(); + + List events = eventService.getEvents(params); + + assertFalse( + events.isEmpty(), "Expected to find events when ou mode ACCESSIBLE and events visible"); + assertContainsOnly( + List.of( + "ck7DzdxqLqA", + "OTmjvJDn0Fu", + "kWjSezkXHVp", + "jxgFyJEMUPf", + "JaRDIvcEcEx", + "YKmfzHdjUDL", + "SbUJzkxKYAG", + "gvULMgNiAfM"), + events.stream().map(BaseIdentifiableObject::getUid).collect(Collectors.toSet())); + } + private T get(Class type, String uid) { T t = manager.get(type, uid); assertNotNull(t, () -> String.format("metadata with uid '%s' should have been created", uid)); diff --git a/dhis-2/dhis-test-integration/src/test/resources/tracker/event_and_enrollment.json b/dhis-2/dhis-test-integration/src/test/resources/tracker/event_and_enrollment.json index 31fe9146c4a4..4c8d09ca4b2a 100644 --- a/dhis-2/dhis-test-integration/src/test/resources/tracker/event_and_enrollment.json +++ b/dhis-2/dhis-test-integration/src/test/resources/tracker/event_and_enrollment.json @@ -223,6 +223,48 @@ "deleted": false, "potentialDuplicate": false, "relationships": [], + "attributes": [ + { + "valueType": "TEXT", + "attribute": { + "idScheme": "UID", + "identifier": "toDelete000" + }, + "value": "just day" + }, + { + "valueType": "TEXT", + "attribute": { + "idScheme": "UID", + "identifier": "toUpdate000" + }, + "value": "summer day" + }, + { + "valueType": "INTEGER", + "attribute": { + "idScheme": "UID", + "identifier": "numericAttr" + }, + "value": 91 + } + ], + "enrollments": [] + }, + { + "trackedEntity": "woitxQbWYNq", + "trackedEntityType": { + "idScheme": "UID", + "identifier": "ja8NY4PW7Xm" + }, + "orgUnit": { + "idScheme": "UID", + "identifier": "RojfDTBhoGC" + }, + "inactive": false, + "deleted": false, + "potentialDuplicate": false, + "relationships": [], "attributes": [ { "valueType": "TEXT", @@ -372,6 +414,78 @@ "relationships": [], "attributes": [], "notes": [] + }, + { + "enrollment": "qxOSXoEZkOA", + "createdAtClient": "2017-01-26T13:48:13.363", + "trackedEntity": "woitxQbWYNq", + "program": { + "idScheme": "UID", + "identifier": "YlUmbgnKWkd" + }, + "status": "ACTIVE", + "orgUnit": { + "idScheme": "UID", + "identifier": "RojfDTBhoGC" + }, + "orgUnitName": "test-orgunit-3", + "enrolledAt": "2021-02-28T12:05:00.000", + "occurredAt": "2021-02-28T12:05:00.000", + "dueDate": "2021-02-28T12:05:00.000", + "followUp": false, + "deleted": false, + "events": [], + "relationships": [], + "attributes": [], + "notes": [] + }, + { + "enrollment": "HDWTYSYkICe", + "createdAtClient": "2017-01-26T13:48:13.363", + "trackedEntity": "woitxQbWYNq", + "program": { + "idScheme": "UID", + "identifier": "SeeUNWLQmZk" + }, + "status": "ACTIVE", + "orgUnit": { + "idScheme": "UID", + "identifier": "DiszpKrYNg8" + }, + "orgUnitName": "test-orgunit-3", + "enrolledAt": "2021-02-28T12:05:00.000", + "occurredAt": "2021-02-28T12:05:00.000", + "dueDate": "2021-02-28T12:05:00.000", + "followUp": false, + "deleted": false, + "events": [], + "relationships": [], + "attributes": [], + "notes": [] + }, + { + "enrollment": "FXWSSZunTLk", + "createdAtClient": "2017-01-26T13:48:13.363", + "trackedEntity": "woitxQbWYNq", + "program": { + "idScheme": "UID", + "identifier": "sLngICFQjvH" + }, + "status": "ACTIVE", + "orgUnit": { + "idScheme": "UID", + "identifier": "lbDXJBlvtZe" + }, + "orgUnitName": "test-orgunit-3", + "enrolledAt": "2021-02-28T12:05:00.000", + "occurredAt": "2021-02-28T12:05:00.000", + "dueDate": "2021-02-28T12:05:00.000", + "followUp": false, + "deleted": false, + "events": [], + "relationships": [], + "attributes": [], + "notes": [] } ], "events": [ @@ -893,7 +1007,89 @@ "idScheme": "UID", "identifier": "HllvX50cXC0" }, - + "notes": [] + }, + { + "event": "SbUJzkxKYAG", + "status": "COMPLETED", + "program": { + "idScheme": "UID", + "identifier": "YlUmbgnKWkd" + }, + "programStage": { + "idScheme": "UID", + "identifier": "uLQthmAPTPq" + }, + "enrollment": "qxOSXoEZkOA", + "orgUnit": { + "idScheme": "UID", + "identifier": "RojfDTBhoGC" + }, + "relationships": [], + "occurredAt": "2019-01-28T00:00:00.000", + "scheduledAt": "2019-01-28T12:10:38.100", + "storedBy": "admin", + "followUp": true, + "deleted": false, + "attributeOptionCombo": { + "idScheme": "UID", + "identifier": "HllvX50cXC0" + } + }, + { + "event": "LCSfHnurnNB", + "status": "COMPLETED", + "program": { + "idScheme": "UID", + "identifier": "SeeUNWLQmZk" + }, + "programStage": { + "idScheme": "UID", + "identifier": "GmxBvezOlGA" + }, + "enrollment": "HDWTYSYkICe", + "orgUnit": { + "idScheme": "UID", + "identifier": "DiszpKrYNg8" + }, + "relationships": [], + "occurredAt": "2019-01-28T00:00:00.000", + "scheduledAt": "2019-01-28T12:10:38.100", + "storedBy": "admin", + "followUp": true, + "deleted": false, + "attributeOptionCombo": { + "idScheme": "UID", + "identifier": "HllvX50cXC0" + }, + "notes": [] + }, + { + "event": "YKmfzHdjUDL", + "status": "COMPLETED", + "program": { + "idScheme": "UID", + "identifier": "sLngICFQjvH" + }, + "programStage": { + "idScheme": "UID", + "identifier": "zydGjigJcJb" + }, + "enrollment": "FXWSSZunTLk", + "orgUnit": { + "idScheme": "UID", + "identifier": "lbDXJBlvtZe" + }, + "relationships": [], + "occurredAt": "2019-01-28T00:00:00.000", + "scheduledAt": "2019-01-28T12:10:38.100", + "storedBy": "admin", + "followUp": true, + "deleted": false, + "attributeOptionCombo": { + "idScheme": "UID", + "identifier": "HllvX50cXC0" + }, "notes": [] } ], diff --git a/dhis-2/dhis-test-integration/src/test/resources/tracker/simple_metadata.json b/dhis-2/dhis-test-integration/src/test/resources/tracker/simple_metadata.json index 0123bc92777d..ebe7a300b8ae 100644 --- a/dhis-2/dhis-test-integration/src/test/resources/tracker/simple_metadata.json +++ b/dhis-2/dhis-test-integration/src/test/resources/tracker/simple_metadata.json @@ -1243,6 +1243,132 @@ "userAccesses": [], "programStageSections": [], "programStageDataElements": [] + }, + { + "lastUpdated": "2020-05-31T11:41:22.426", + "id": "uLQthmAPTPq", + "created": "2020-05-31T09:02:52.687", + "name": "test-program-stage-low-level-org-unit", + "allowGenerateNextVisit": false, + "preGenerateUID": false, + "publicAccess": "rwrw----", + "description": "test-program-stage-low-level-org-unit", + "openAfterEnrollment": false, + "repeatable": true, + "remindCompleted": false, + "displayGenerateEventBox": true, + "generatedByEnrollmentDate": false, + "validationStrategy": "ON_COMPLETE", + "autoGenerateEvent": true, + "sortOrder": 1, + "hideDueDate": false, + "blockEntryForm": false, + "enableUserAssignment": false, + "minDaysFromStart": 0, + "program": { + "id": "YlUmbgnKWkd" + }, + "lastUpdatedBy": { + "id": "FIgVWzUCkpw" + }, + "user": { + "id": "FIgVWzUCkpw" + }, + "notificationTemplates": [ + { + "id": "FdIeUL4gyoB" + } + ], + "translations": [], + "userGroupAccesses": [], + "attributeValues": [], + "userAccesses": [], + "programStageSections": [], + "programStageDataElements": [] + }, + { + "lastUpdated": "2020-05-31T11:41:22.426", + "id": "GmxBvezOlGA", + "created": "2020-05-31T09:02:52.687", + "name": "test-program-stage-low-level-org-unit", + "allowGenerateNextVisit": false, + "preGenerateUID": false, + "publicAccess": "rwrw----", + "description": "test-program-stage-low-level-org-unit", + "openAfterEnrollment": false, + "repeatable": true, + "remindCompleted": false, + "displayGenerateEventBox": true, + "generatedByEnrollmentDate": false, + "validationStrategy": "ON_COMPLETE", + "autoGenerateEvent": true, + "sortOrder": 1, + "hideDueDate": false, + "blockEntryForm": false, + "enableUserAssignment": false, + "minDaysFromStart": 0, + "program": { + "id": "SeeUNWLQmZk" + }, + "lastUpdatedBy": { + "id": "FIgVWzUCkpw" + }, + "user": { + "id": "FIgVWzUCkpw" + }, + "notificationTemplates": [ + { + "id": "FdIeUL4gyoB" + } + ], + "translations": [], + "userGroupAccesses": [], + "attributeValues": [], + "userAccesses": [], + "programStageSections": [], + "programStageDataElements": [] + }, + { + "lastUpdated": "2020-05-31T11:41:22.426", + "id": "zydGjigJcJb", + "created": "2020-05-31T09:02:52.687", + "name": "test-program-stage-low-level-org-unit", + "allowGenerateNextVisit": false, + "preGenerateUID": false, + "publicAccess": "rwrw----", + "description": "test-program-stage-low-level-org-unit", + "openAfterEnrollment": false, + "repeatable": true, + "remindCompleted": false, + "displayGenerateEventBox": true, + "generatedByEnrollmentDate": false, + "validationStrategy": "ON_COMPLETE", + "autoGenerateEvent": true, + "sortOrder": 1, + "hideDueDate": false, + "blockEntryForm": false, + "enableUserAssignment": false, + "minDaysFromStart": 0, + "program": { + "id": "SeeUNWLQmZk" + }, + "lastUpdatedBy": { + "id": "FIgVWzUCkpw" + }, + "user": { + "id": "FIgVWzUCkpw" + }, + "notificationTemplates": [ + { + "id": "FdIeUL4gyoB" + } + ], + "translations": [], + "userGroupAccesses": [], + "attributeValues": [], + "userAccesses": [], + "programStageSections": [], + "programStageDataElements": [] } ], "users": [ @@ -1554,12 +1680,15 @@ "userAccesses": [], "teiSearchOrganisationUnits": [ { - "id": "uoNW0E3xXUy" + "id": "DiszpKrYNg8" } ], "organisationUnits": [ { "id": "uoNW0E3xXUy" + }, + { + "id": "lbDXJBlvtZe" } ], "dataViewOrganisationUnits": [ @@ -1613,13 +1742,35 @@ "translations": [] }, { - "id": "tSsGrtfRzjY", + "id": "lbDXJBlvtZe", + "name": "test-orgunit-2", + "level": 2, + "created": "2020-05-31T09:05:34.570", + "lastUpdated": "2020-05-31T11:41:22.385", + "shortName": "test-program-rule", + "path": "/h4w96yEMlzO/lbDXJBlvtZe", + "closedDate": "2020-12-27T00:00:00.000", + "openingDate": "2020-05-31T00:00:00.000", + "parent": { + "id": "h4w96yEMlzO" + }, + "lastUpdatedBy": { + "id": "M5zQapPyTZI" + }, + "user": { + "id": "M5zQapPyTZI" + }, + "attributeValues": [], + "translations": [] + }, + { + "id": "RojfDTBhoGC", "name": "test-orgunit-3", "level": 3, "created": "2020-05-31T09:05:34.570", "lastUpdated": "2020-05-31T11:41:22.385", "shortName": "test-program-rule", - "path": "/h4w96yEMlzO/uoNW0E3xXUy/tSsGrtfRzjY", + "path": "/h4w96yEMlzO/uoNW0E3xXUy/RojfDTBhoGC", "closedDate": "2020-12-27T00:00:00.000", "openingDate": "2020-05-31T00:00:00.000", "parent": { @@ -1634,6 +1785,28 @@ "attributeValues": [], "translations": [] }, + { + "id": "tSsGrtfRzjY", + "name": "test-orgunit-4", + "level": 4, + "created": "2020-05-31T09:05:34.570", + "lastUpdated": "2020-05-31T11:41:22.385", + "shortName": "test-program-rule", + "path": "/h4w96yEMlzO/uoNW0E3xXUy/RojfDTBhoGC/tSsGrtfRzjY", + "closedDate": "2020-12-27T00:00:00.000", + "openingDate": "2020-05-31T00:00:00.000", + "parent": { + "id": "RojfDTBhoGC" + }, + "lastUpdatedBy": { + "id": "M5zQapPyTZI" + }, + "user": { + "id": "M5zQapPyTZI" + }, + "attributeValues": [], + "translations": [] + }, { "id": "DiszpKrYNg8", "name": "test-orgunit-3", @@ -1971,6 +2144,162 @@ } ], "userAccesses": [] + }, + { + "id": "YlUmbgnKWkd", + "name": "test-program-low-level-org-unit", + "shortName": "test-program-low-level-org-unit", + "lastUpdated": "2020-05-31T11:41:22.438", + "created": "2020-05-31T09:02:52.718", + "publicAccess": "rwrw----", + "completeEventsExpiryDays": 0, + "ignoreOverdueEvents": false, + "skipOffline": false, + "minAttributesRequiredToSearch": 1, + "displayFrontPageList": false, + "onlyEnrollOnce": false, + "programType": "WITH_REGISTRATION", + "accessLevel": "CLOSED", + "version": 2, + "maxTeiCountToReturn": 0, + "selectIncidentDatesInFuture": false, + "displayIncidentDate": true, + "selectEnrollmentDatesInFuture": false, + "expiryDays": 0, + "useFirstStageDuringRegistration": false, + "categoryCombo": { + "id": "bjDvmb4bfuf" + }, + "lastUpdatedBy": { + "id": "FIgVWzUCkpw" + }, + "trackedEntityType": { + "id": "ja8NY4PW7Xm" + }, + "user": { + "id": "FIgVWzUCkpw" + }, + "programTrackedEntityAttributes": [], + "notificationTemplates": [], + "translations": [], + "organisationUnits": [ + { + "id": "RojfDTBhoGC" + } + ], + "userGroupAccesses": [], + "programSections": [], + "attributeValues": [], + "programStages": [ + { + "id": "uLQthmAPTPq" + } + ], + "userAccesses": [] + }, + { + "id": "SeeUNWLQmZk", + "name": "test-program-low-level-org-unit", + "shortName": "test-program-low-level-org-unit", + "lastUpdated": "2020-05-31T11:41:22.438", + "created": "2020-05-31T09:02:52.718", + "publicAccess": "rwrw----", + "completeEventsExpiryDays": 0, + "ignoreOverdueEvents": false, + "skipOffline": false, + "minAttributesRequiredToSearch": 1, + "displayFrontPageList": false, + "onlyEnrollOnce": false, + "programType": "WITH_REGISTRATION", + "accessLevel": "CLOSED", + "version": 2, + "maxTeiCountToReturn": 0, + "selectIncidentDatesInFuture": false, + "displayIncidentDate": true, + "selectEnrollmentDatesInFuture": false, + "expiryDays": 0, + "useFirstStageDuringRegistration": false, + "categoryCombo": { + "id": "bjDvmb4bfuf" + }, + "lastUpdatedBy": { + "id": "FIgVWzUCkpw" + }, + "trackedEntityType": { + "id": "ja8NY4PW7Xm" + }, + "user": { + "id": "FIgVWzUCkpw" + }, + "programTrackedEntityAttributes": [], + "notificationTemplates": [], + "translations": [], + "organisationUnits": [ + { + "id": "DiszpKrYNg8" + } + ], + "userGroupAccesses": [], + "programSections": [], + "attributeValues": [], + "programStages": [ + { + "id": "GmxBvezOlGA" + } + ], + "userAccesses": [] + }, + { + "id": "sLngICFQjvH", + "name": "test-program-low-level-org-unit", + "shortName": "test-program-low-level-org-unit", + "lastUpdated": "2020-05-31T11:41:22.438", + "created": "2020-05-31T09:02:52.718", + "publicAccess": "rwrw----", + "completeEventsExpiryDays": 0, + "ignoreOverdueEvents": false, + "skipOffline": false, + "minAttributesRequiredToSearch": 1, + "displayFrontPageList": false, + "onlyEnrollOnce": false, + "programType": "WITH_REGISTRATION", + "accessLevel": "OPEN", + "version": 2, + "maxTeiCountToReturn": 0, + "selectIncidentDatesInFuture": false, + "displayIncidentDate": true, + "selectEnrollmentDatesInFuture": false, + "expiryDays": 0, + "useFirstStageDuringRegistration": false, + "categoryCombo": { + "id": "bjDvmb4bfuf" + }, + "lastUpdatedBy": { + "id": "FIgVWzUCkpw" + }, + "trackedEntityType": { + "id": "ja8NY4PW7Xm" + }, + "user": { + "id": "FIgVWzUCkpw" + }, + "programTrackedEntityAttributes": [], + "notificationTemplates": [], + "translations": [], + "organisationUnits": [ + { + "id": "lbDXJBlvtZe" + } + ], + "userGroupAccesses": [], + "programSections": [], + "attributeValues": [], + "programStages": [ + { + "id": "zydGjigJcJb" + } + ], + "userAccesses": [] } ], "userRoles": [