From 01994b37e36eeb833e508d61f1195845ac5115a5 Mon Sep 17 00:00:00 2001 From: teleivo Date: Fri, 15 Nov 2024 15:30:09 +0100 Subject: [PATCH] feat: support /tracker/event?dataElementIdScheme DHIS2-14968 (#19153) * feat: support /tracker/event?dataElementIdScheme * chore: remove unused * uncomment code * remove comment * Update dhis-2/dhis-test-web-api/src/test/resources/tracker/event_and_enrollment.json Co-authored-by: Enrico Colasante * chore: review suggestion --------- Co-authored-by: Enrico Colasante --- .../export/event/DefaultEventService.java | 101 ++++++-------- .../export/event/EventOperationParams.java | 4 + .../event/EventOperationParamsMapper.java | 9 +- .../export/event/EventQueryParams.java | 8 ++ .../tracker/export/event/EventService.java | 17 ++- .../dhis/tracker/export/event/EventStore.java | 52 ------- .../tracker/export/event/JdbcEventStore.java | 121 ++++++++++++++-- .../event/EventOperationParamsMapperTest.java | 12 +- .../resources/tracker/simple_metadata.json | 36 +++++ .../export/IdSchemeExportControllerTest.java | 131 +++++++++++------- .../tracker/event_and_enrollment.json | 8 ++ .../event/EventRequestParamsMapper.java | 7 +- .../export/event/EventsExportController.java | 38 ++--- .../event/EventRequestParamsMapperTest.java | 84 ++++++----- docker-compose.yml | 3 + 15 files changed, 391 insertions(+), 240 deletions(-) delete mode 100644 dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventStore.java diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/DefaultEventService.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/DefaultEventService.java index 17f1ab850f8f..441a6b36e134 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/DefaultEventService.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/DefaultEventService.java @@ -36,6 +36,9 @@ import javax.annotation.Nonnull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.hisp.dhis.common.IdScheme; +import org.hisp.dhis.common.IdentifiableObjectManager; +import org.hisp.dhis.common.IdentifiableProperty; import org.hisp.dhis.common.OrganisationUnitSelectionMode; import org.hisp.dhis.common.UID; import org.hisp.dhis.dataelement.DataElement; @@ -50,6 +53,9 @@ import org.hisp.dhis.program.Event; import org.hisp.dhis.relationship.Relationship; import org.hisp.dhis.relationship.RelationshipItem; +import org.hisp.dhis.tracker.TrackerIdScheme; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.acl.TrackerAccessManager; import org.hisp.dhis.tracker.export.FileResourceStream; import org.hisp.dhis.tracker.export.Page; @@ -67,7 +73,9 @@ @RequiredArgsConstructor class DefaultEventService implements EventService { - private final EventStore eventStore; + private final JdbcEventStore eventStore; + + private final IdentifiableObjectManager manager; private final TrackerAccessManager trackerAccessManager; @@ -148,17 +156,24 @@ private FileResource getFileResourceMetadata(UID eventUid, UID dataElementUid) @Override public Event getEvent(@Nonnull UID event) throws ForbiddenException, NotFoundException { - return getEvent(event, EventParams.FALSE, getCurrentUserDetails()); + return getEvent( + event, TrackerIdSchemeParams.builder().build(), EventParams.FALSE, getCurrentUserDetails()); } @Override - public Event getEvent(@Nonnull UID event, @Nonnull EventParams eventParams) + public Event getEvent( + @Nonnull UID event, + @Nonnull TrackerIdSchemeParams idSchemeParams, + @Nonnull EventParams eventParams) throws ForbiddenException, NotFoundException { - return getEvent(event, eventParams, getCurrentUserDetails()); + return getEvent(event, idSchemeParams, eventParams, getCurrentUserDetails()); } private Event getEvent( - @Nonnull UID eventUid, @Nonnull EventParams eventParams, @Nonnull UserDetails user) + @Nonnull UID eventUid, + @Nonnull TrackerIdSchemeParams idSchemeParams, + @Nonnull EventParams eventParams, + @Nonnull UserDetails user) throws NotFoundException, ForbiddenException { Page events; try { @@ -167,6 +182,7 @@ private Event getEvent( .orgUnitMode(OrganisationUnitSelectionMode.ACCESSIBLE) .events(Set.of(eventUid)) .eventParams(eventParams) + .idSchemeParams(idSchemeParams) .build(); events = getEvents(operationParams, new PageParams(1, 1, false)); } catch (BadRequestException e) { @@ -179,64 +195,35 @@ private Event getEvent( } Event event = events.getItems().get(0); - return getEvent(event, eventParams, user); - } - - private Event getEvent( - @Nonnull Event event, @Nonnull EventParams eventParams, @Nonnull UserDetails user) { - Event result = new Event(); - result.setId(event.getId()); - result.setUid(event.getUid()); - - result.setStatus(event.getStatus()); - result.setOccurredDate(event.getOccurredDate()); - result.setScheduledDate(event.getScheduledDate()); - result.setStoredBy(event.getStoredBy()); - result.setCompletedBy(event.getCompletedBy()); - result.setCompletedDate(event.getCompletedDate()); - result.setCreated(event.getCreated()); - result.setCreatedByUserInfo(event.getCreatedByUserInfo()); - result.setLastUpdatedByUserInfo(event.getLastUpdatedByUserInfo()); - result.setCreatedAtClient(event.getCreatedAtClient()); - result.setLastUpdated(event.getLastUpdated()); - result.setLastUpdatedAtClient(event.getLastUpdatedAtClient()); - result.setGeometry(event.getGeometry()); - result.setDeleted(event.isDeleted()); - result.setAssignedUser(event.getAssignedUser()); - - result.setEnrollment(event.getEnrollment()); - result.setProgramStage(event.getProgramStage()); - - result.setOrganisationUnit(event.getOrganisationUnit()); - result.setProgramStage(event.getProgramStage()); - - result.setAttributeOptionCombo(event.getAttributeOptionCombo()); - + Set dataValues = new HashSet<>(event.getEventDataValues().size()); for (EventDataValue dataValue : event.getEventDataValues()) { - if (dataElementService.getDataElement(dataValue.getDataElement()) - != null) // check permissions - { - EventDataValue value = new EventDataValue(); - value.setCreated(dataValue.getCreated()); - value.setCreatedByUserInfo(dataValue.getCreatedByUserInfo()); - value.setLastUpdated(dataValue.getLastUpdated()); - value.setLastUpdatedByUserInfo(dataValue.getLastUpdatedByUserInfo()); - value.setDataElement(dataValue.getDataElement()); - value.setValue(dataValue.getValue()); - value.setProvidedElsewhere(dataValue.getProvidedElsewhere()); - value.setStoredBy(dataValue.getStoredBy()); + DataElement dataElement = null; + TrackerIdSchemeParam dataElementIdScheme = idSchemeParams.getDataElementIdScheme(); + if (TrackerIdScheme.UID == dataElementIdScheme.getIdScheme()) { + dataElement = dataElementService.getDataElement(dataValue.getDataElement()); + } else if (TrackerIdScheme.CODE == dataElementIdScheme.getIdScheme()) { + dataElement = manager.getByCode(DataElement.class, dataValue.getDataElement()); + } else if (TrackerIdScheme.NAME == dataElementIdScheme.getIdScheme()) { + dataElement = manager.getByName(DataElement.class, dataValue.getDataElement()); + } else if (TrackerIdScheme.ATTRIBUTE == dataElementIdScheme.getIdScheme()) { + dataElement = + manager.getObject( + DataElement.class, + new IdScheme(IdentifiableProperty.ATTRIBUTE, dataElementIdScheme.getAttributeUid()), + dataValue.getDataElement()); + } - result.getEventDataValues().add(value); + if (dataElement != null) // check permissions + { + dataValues.add(dataValue); } else { log.info("Cannot find data element with UID {}", dataValue.getDataElement()); } } - - result.getNotes().addAll(event.getNotes()); + event.setEventDataValues(dataValues); if (eventParams.isIncludeRelationships()) { Set relationshipItems = new HashSet<>(); - for (RelationshipItem relationshipItem : event.getRelationshipItems()) { Relationship daoRelationship = relationshipItem.getRelationship(); if (trackerAccessManager.canRead(user, daoRelationship).isEmpty() @@ -245,10 +232,10 @@ private Event getEvent( } } - result.setRelationshipItems(relationshipItems); + event.setRelationshipItems(relationshipItems); } - return result; + return event; } @Override @@ -271,7 +258,7 @@ public RelationshipItem getEventInRelationshipItem( @Nonnull UID uid, @Nonnull EventParams eventParams) { Event event; try { - event = getEvent(uid, eventParams); + event = getEvent(uid, TrackerIdSchemeParams.builder().build(), eventParams); } catch (NotFoundException | ForbiddenException e) { // events are not shown in relationships if the user has no access to them return null; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventOperationParams.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventOperationParams.java index 0d24750cb317..a4d9f4c5d7ab 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventOperationParams.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventOperationParams.java @@ -52,6 +52,7 @@ import org.hisp.dhis.program.ProgramStage; import org.hisp.dhis.program.ProgramType; import org.hisp.dhis.trackedentity.TrackedEntity; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.export.Order; @Getter @@ -146,6 +147,9 @@ public class EventOperationParams { private EventParams eventParams; + @Builder.Default + private TrackerIdSchemeParams idSchemeParams = TrackerIdSchemeParams.builder().build(); + public static class EventOperationParamsBuilder { private final List order = new ArrayList<>(); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventOperationParamsMapper.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventOperationParamsMapper.java index 2aa01ab24c18..76da7f7c71b8 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventOperationParamsMapper.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventOperationParamsMapper.java @@ -141,7 +141,8 @@ public EventQueryParams map( .setEvents(operationParams.getEvents()) .setEnrollments(operationParams.getEnrollments()) .setIncludeDeleted(operationParams.isIncludeDeleted()) - .setIncludeRelationships(operationParams.getEventParams().isIncludeRelationships()); + .setIncludeRelationships(operationParams.getEventParams().isIncludeRelationships()) + .setIdSchemeParams(operationParams.getIdSchemeParams()); } private ProgramStage validateProgramStage(String programStageUid, UserDetails user) @@ -261,7 +262,8 @@ private void mapOrderParam(EventQueryParams params, List orders) throw new BadRequestException( "Cannot order by '" + uid.getValue() - + "' as its neither a data element nor a tracked entity attribute. Events can be ordered by event fields, data elements and tracked entity attributes."); + + "' as its neither a data element nor a tracked entity attribute. Events can be" + + " ordered by event fields, data elements and tracked entity attributes."); } params.orderBy(tea, order.getDirection()); @@ -269,7 +271,8 @@ private void mapOrderParam(EventQueryParams params, List orders) throw new IllegalArgumentException( "Cannot order by '" + order.getField() - + "'. Events can be ordered by event fields, data elements and tracked entity attributes."); + + "'. Events can be ordered by event fields, data elements and tracked entity" + + " attributes."); } } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventQueryParams.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventQueryParams.java index 2817ba1e9632..7102c9eb5941 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventQueryParams.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventQueryParams.java @@ -53,6 +53,7 @@ import org.hisp.dhis.program.ProgramType; import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.export.Order; /** @@ -149,6 +150,8 @@ class EventQueryParams { @Getter private AssignedUserQueryParam assignedUserQueryParam = AssignedUserQueryParam.ALL; + @Getter private TrackerIdSchemeParams idSchemeParams = TrackerIdSchemeParams.builder().build(); + public EventQueryParams() {} public boolean hasProgram() { @@ -551,4 +554,9 @@ public boolean isPathOrganisationUnitMode() { && (OrganisationUnitSelectionMode.DESCENDANTS.equals(orgUnitMode) || OrganisationUnitSelectionMode.CHILDREN.equals(orgUnitMode)); } + + public EventQueryParams setIdSchemeParams(TrackerIdSchemeParams idSchemeParams) { + this.idSchemeParams = idSchemeParams; + return this; + } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventService.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventService.java index 120b1071b82c..93fadadff981 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventService.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventService.java @@ -37,6 +37,8 @@ import org.hisp.dhis.fileresource.ImageFileDimension; import org.hisp.dhis.program.Event; import org.hisp.dhis.relationship.RelationshipItem; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.export.FileResourceStream; import org.hisp.dhis.tracker.export.Page; import org.hisp.dhis.tracker.export.PageParams; @@ -61,28 +63,33 @@ FileResourceStream getFileResourceImage(UID event, UID dataElement, ImageFileDim /** * Get event matching given {@code UID} under the privileges of the currently authenticated user. - * Use {@link #getEvent(UID, EventParams)} instead to also get the events relationships. + * Metadata identifiers will use the {@code idScheme} {@link TrackerIdSchemeParam#UID}. Use {@link + * #getEvent(UID, TrackerIdSchemeParams, EventParams)} instead to also get the events + * relationships and specify different {@code idSchemes}. */ Event getEvent(UID uid) throws NotFoundException, ForbiddenException; /** * Get event matching given {@code UID} and params under the privileges of the currently - * authenticated user. + * authenticated user. Metadata identifiers will use the {@code idScheme} defined by {@link + * TrackerIdSchemeParams}. */ - Event getEvent(UID uid, EventParams eventParams) throws NotFoundException, ForbiddenException; + Event getEvent(UID uid, @Nonnull TrackerIdSchemeParams idSchemeParams, EventParams eventParams) + throws NotFoundException, ForbiddenException; /** * Get all events matching given params under the privileges of the currently authenticated user. */ @Nonnull - List getEvents(EventOperationParams params) throws BadRequestException, ForbiddenException; + List getEvents(@Nonnull EventOperationParams params) + throws BadRequestException, ForbiddenException; /** * Get a page of events matching given params under the privileges of the currently authenticated * user. */ @Nonnull - Page getEvents(EventOperationParams params, PageParams pageParams) + Page getEvents(@Nonnull EventOperationParams params, @Nonnull PageParams pageParams) throws BadRequestException, ForbiddenException; RelationshipItem getEventInRelationshipItem(UID uid, EventParams eventParams) diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventStore.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventStore.java deleted file mode 100644 index 1bccaaaeea3c..000000000000 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventStore.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2004-2022, University of Oslo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * Neither the name of the HISP project nor the names of its contributors may - * be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package org.hisp.dhis.tracker.export.event; - -import java.util.List; -import java.util.Set; -import org.hisp.dhis.program.Event; -import org.hisp.dhis.tracker.export.Page; -import org.hisp.dhis.tracker.export.PageParams; - -/** - * @author Morten Olav Hansen - */ -public interface EventStore { - /** Get all events matching given params. */ - List getEvents(EventQueryParams params); - - /** Get a page of events matching given params. */ - Page getEvents(EventQueryParams params, PageParams pageParams); - - /** - * Fields the {@link #getEvents(EventQueryParams)} can order events by. Ordering by fields other - * than these is considered a programmer error. Validation of user provided field names should - * occur before calling {@link #getEvents(EventQueryParams)}. - */ - Set getOrderableFields(); -} 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 03fcbdb7e685..f2d4503fb45d 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 @@ -92,6 +92,8 @@ import org.hisp.dhis.system.util.SqlUtils; import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; +import org.hisp.dhis.tracker.TrackerIdScheme; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; import org.hisp.dhis.tracker.export.Order; import org.hisp.dhis.tracker.export.Page; import org.hisp.dhis.tracker.export.PageParams; @@ -117,7 +119,7 @@ @Slf4j @Repository("org.hisp.dhis.tracker.export.event.EventStore") @RequiredArgsConstructor -class JdbcEventStore implements EventStore { +class JdbcEventStore { private static final String RELATIONSHIP_IDS_QUERY = " left join (select ri.eventid as ri_ev_id, json_agg(ri.relationshipid) as ev_rl from" + " relationshipitem ri group by ri_ev_id) as fgh on fgh.ri_ev_id=event.ev_id "; @@ -176,6 +178,7 @@ class JdbcEventStore implements EventStore { private static final String COLUMN_ENROLLMENT_FOLLOWUP = "en_followup"; private static final String COLUMN_EVENT_STATUS = "ev_status"; private static final String COLUMN_EVENT_SCHEDULED_DATE = "ev_scheduleddate"; + private static final String COLUMN_EVENT_DATAVALUES = "ev_eventdatavalues"; private static final String COLUMN_EVENT_STORED_BY = "ev_storedby"; private static final String COLUMN_EVENT_LAST_UPDATED_BY = "ev_lastupdatedbyuserinfo"; private static final String COLUMN_EVENT_CREATED_BY = "ev_createdbyuserinfo"; @@ -249,12 +252,10 @@ class JdbcEventStore implements EventStore { private final RelationshipStore relationshipStore; - @Override public List getEvents(EventQueryParams queryParams) { return fetchEvents(queryParams, null); } - @Override public Page getEvents(EventQueryParams queryParams, PageParams pageParams) { List events = fetchEvents(queryParams, pageParams); LongSupplier eventCount = () -> getEventCount(queryParams); @@ -279,12 +280,15 @@ private List fetchEvents(EventQueryParams queryParams, PageParams pagePar final MapSqlParameterSource mapSqlParameterSource = new MapSqlParameterSource(); String sql = buildSql(queryParams, pageParams, mapSqlParameterSource, currentUser); + TrackerIdSchemeParam dataElementIdScheme = + queryParams.getIdSchemeParams().getDataElementIdScheme(); return jdbcTemplate.query( sql, mapSqlParameterSource, resultSet -> { Set notes = new HashSet<>(); + Set dataElementUids = new HashSet<>(); while (resultSet.next()) { if (resultSet.getString(COLUMN_EVENT_UID) == null) { @@ -405,11 +409,13 @@ private List fetchEvents(EventQueryParams queryParams, PageParams pagePar event.setAssignedUser(eventUser); } - if (!StringUtils.isEmpty(resultSet.getString("ev_eventdatavalues"))) { - Set eventDataValues = - convertEventDataValueJsonIntoSet(resultSet.getString("ev_eventdatavalues")); - - event.getEventDataValues().addAll(eventDataValues); + if (TrackerIdScheme.UID == dataElementIdScheme.getIdScheme() + && !StringUtils.isEmpty(resultSet.getString(COLUMN_EVENT_DATAVALUES))) { + event + .getEventDataValues() + .addAll( + convertEventDataValueJsonIntoSet( + resultSet.getString(COLUMN_EVENT_DATAVALUES))); } if (queryParams.isIncludeRelationships() && resultSet.getObject("ev_rl") != null) { @@ -425,6 +431,21 @@ private List fetchEvents(EventQueryParams queryParams, PageParams pagePar events.add(event); } + if (TrackerIdScheme.UID != dataElementIdScheme.getIdScheme()) { + // We get one row per eventdatavalue for idSchemes other than UID due to the need to + // join on the dataelement table to get idScheme information. There can only be one + // data value per data element. The same data element can be in the result set + // multiple times if the event also has notes. + String dataElementUid = resultSet.getString("de_uid"); + if (!dataElementUids.contains(dataElementUid)) { + EventDataValue eventDataValue = parseEventDataValue(dataElementIdScheme, resultSet); + if (eventDataValue != null) { + event.getEventDataValues().add(eventDataValue); + dataElementUids.add(dataElementUid); + } + } + } + if (resultSet.getString("note_text") != null && !notes.contains(resultSet.getString("note_id"))) { Note note = new Note(); @@ -472,6 +493,60 @@ private List fetchEvents(EventQueryParams queryParams, PageParams pagePar }); } + private EventDataValue parseEventDataValue( + TrackerIdSchemeParam dataElementIdScheme, ResultSet resultSet) throws SQLException { + String dataValueResult = resultSet.getString("ev_eventdatavalue"); + if (StringUtils.isEmpty(dataValueResult)) { + return null; + } + + EventDataValue eventDataValue = new EventDataValue(); + eventDataValue.setDataElement(getDataElementIdentifier(dataElementIdScheme, resultSet)); + JsonObject dataValueJson = JsonMixed.of(dataValueResult).asObject(); + eventDataValue.setValue(dataValueJson.getString("value").string("")); + eventDataValue.setProvidedElsewhere( + dataValueJson.getBoolean("providedElsewhere").booleanValue(false)); + eventDataValue.setStoredBy(dataValueJson.getString("storedBy").string("")); + + eventDataValue.setCreated(DateUtils.parseDate(dataValueJson.getString("created").string(""))); + if (dataValueJson.has("createdByUserInfo")) { + eventDataValue.setCreatedByUserInfo( + EventUtils.jsonToUserInfo( + dataValueJson.getObject("createdByUserInfo").toJson(), jsonMapper)); + } + + eventDataValue.setLastUpdated( + DateUtils.parseDate(dataValueJson.getString("lastUpdated").string(""))); + if (dataValueJson.has("lastUpdatedByUserInfo")) { + eventDataValue.setLastUpdatedByUserInfo( + EventUtils.jsonToUserInfo( + dataValueJson.getObject("lastUpdatedByUserInfo").toJson(), jsonMapper)); + } + + return eventDataValue; + } + + private String getDataElementIdentifier( + TrackerIdSchemeParam dataElementIdScheme, ResultSet resultSet) throws SQLException { + switch (dataElementIdScheme.getIdScheme()) { + case CODE: + return resultSet.getString("de_code"); + case NAME: + return resultSet.getString("de_name"); + case ATTRIBUTE: + String attributeValuesString = resultSet.getString("de_attributevalues"); + if (StringUtils.isEmpty(attributeValuesString)) { + return ""; + } + JsonObject attributeValuesJson = JsonMixed.of(attributeValuesString).asObject(); + String attributeUid = dataElementIdScheme.getAttributeUid(); + AttributeValues attributeValues = AttributeValues.of(attributeValuesJson.toJson()); + return attributeValues.get(attributeUid); + default: + return resultSet.getString("de_uid"); + } + } + private Page getPage(PageParams pageParams, List events, LongSupplier eventCount) { if (pageParams.isPageTotal()) { return Page.withTotals( @@ -481,7 +556,6 @@ private Page getPage(PageParams pageParams, List events, LongSuppl return Page.withoutTotals(events, pageParams.getPage(), pageParams.getPageSize()); } - @Override public Set getOrderableFields() { return ORDERABLE_FIELDS.keySet(); } @@ -529,7 +603,16 @@ private String buildSql( PageParams pageParams, MapSqlParameterSource mapSqlParameterSource, User user) { - StringBuilder sqlBuilder = new StringBuilder().append("select * from ("); + StringBuilder sqlBuilder = new StringBuilder("select "); + if (TrackerIdScheme.UID + != queryParams.getIdSchemeParams().getDataElementIdScheme().getIdScheme()) { + sqlBuilder.append( + "event.*, cm.*,eventdatavalue.value as ev_eventdatavalue, de.uid as de_uid, de.code as" + + " de_code, de.name as de_name, de.attributevalues as de_attributevalues"); + } else { + sqlBuilder.append("*"); + } + sqlBuilder.append(" from ("); sqlBuilder.append(getEventSelectQuery(queryParams, mapSqlParameterSource, user)); @@ -553,6 +636,20 @@ private String buildSql( sqlBuilder.append(COLUMN_EVENT_ID); sqlBuilder.append("=cm.evn_id "); + if (TrackerIdScheme.UID + != queryParams.getIdSchemeParams().getDataElementIdScheme().getIdScheme()) { + sqlBuilder.append( + """ +left join + lateral jsonb_each( + coalesce(event.ev_eventdatavalues, '{}') + ) as eventdatavalue(dataelement_uid, value) + on true +left join dataelement de on de.uid = eventdatavalue.dataelement_uid +where eventdatavalue.dataelement_uid is not null +"""); + } + if (queryParams.isIncludeRelationships()) { sqlBuilder.append(RELATIONSHIP_IDS_QUERY); } @@ -738,8 +835,10 @@ private String getEventSelectQuery( .append(COLUMN_EVENT_STATUS) .append(", ev.occurreddate as ") .append(COLUMN_EVENT_OCCURRED_DATE) - .append(", ev.eventdatavalues as ev_eventdatavalues, ev.scheduleddate as ") + .append(", ev.scheduleddate as ") .append(COLUMN_EVENT_SCHEDULED_DATE) + .append(", ev.eventdatavalues as ") + .append(COLUMN_EVENT_DATAVALUES) .append(", ev.completedby as ") .append(COLUMN_EVENT_COMPLETED_BY) .append(", ev.storedby as ") diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/export/event/EventOperationParamsMapperTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/export/event/EventOperationParamsMapperTest.java index 8e63c6df02ac..824853977ba6 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/export/event/EventOperationParamsMapperTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/export/event/EventOperationParamsMapperTest.java @@ -154,24 +154,24 @@ public void setUp() { void shouldFailWithForbiddenExceptionWhenUserHasNoAccessToProgramStage() { ProgramStage programStage = new ProgramStage(); programStage.setUid("PlZSBEN7iZd"); - EventOperationParams eventOperationParams = eventBuilder.programStage(programStage).build(); + EventOperationParams operationParams = eventBuilder.programStage(programStage).build(); when(aclService.canDataRead(user, programStage)).thenReturn(false); when(programStageService.getProgramStage("PlZSBEN7iZd")).thenReturn(programStage); Exception exception = - assertThrows(ForbiddenException.class, () -> mapper.map(eventOperationParams, user)); + assertThrows(ForbiddenException.class, () -> mapper.map(operationParams, user)); assertEquals( "User has no access to program stage: " + programStage.getUid(), exception.getMessage()); } @Test void shouldFailWithBadRequestExceptionWhenMappingWithUnknownProgramStage() { - EventOperationParams eventOperationParams = + EventOperationParams operationParams = EventOperationParams.builder().programStage(UID.of("NeU85luyD4w")).build(); Exception exception = - assertThrows(BadRequestException.class, () -> mapper.map(eventOperationParams, user)); + assertThrows(BadRequestException.class, () -> mapper.map(operationParams, user)); assertEquals( "Program stage is specified but does not exist: NeU85luyD4w", exception.getMessage()); } @@ -179,7 +179,7 @@ void shouldFailWithBadRequestExceptionWhenMappingWithUnknownProgramStage() { @Test void shouldFailWithForbiddenExceptionWhenUserHasNoAccessToCategoryComboGivenAttributeCategoryOptions() { - EventOperationParams eventOperationParams = + EventOperationParams operationParams = eventBuilder .attributeCategoryCombo(UID.of("NeU85luyD4w")) .attributeCategoryOptions(UID.of("tqrzUqNMHib", "bT6OSf4qnnk")) @@ -193,7 +193,7 @@ void shouldFailWithBadRequestExceptionWhenMappingWithUnknownProgramStage() { .thenReturn(false); Exception exception = - assertThrows(ForbiddenException.class, () -> mapper.map(eventOperationParams, user)); + assertThrows(ForbiddenException.class, () -> mapper.map(operationParams, user)); assertEquals( "User has no access to attribute category option combo: " + combo.getUid(), diff --git a/dhis-2/dhis-support/dhis-support-test/src/main/resources/tracker/simple_metadata.json b/dhis-2/dhis-support/dhis-support-test/src/main/resources/tracker/simple_metadata.json index 02130842b2ce..595b18bddb35 100644 --- a/dhis-2/dhis-support/dhis-support-test/src/main/resources/tracker/simple_metadata.json +++ b/dhis-2/dhis-support/dhis-support-test/src/main/resources/tracker/simple_metadata.json @@ -492,6 +492,14 @@ }, { "id": "GieVkTxp4HH", + "attributeValues": [ + { + "attribute": { + "id": "j45AR9cBQKc" + }, + "value": "dataElement GieVkTxp4HH" + } + ], "aggregationType": "AVERAGE", "categoryCombo": { "id": "bjDvmb4bfuf" @@ -507,6 +515,14 @@ }, { "id": "GieVkTxp4HG", + "attributeValues": [ + { + "attribute": { + "id": "j45AR9cBQKc" + }, + "value": "dataElement GieVkTxp4HG" + } + ], "aggregationType": "AVERAGE", "code": "DG_240794", "created": "2015-03-31T10:27:46.069", @@ -1066,6 +1082,26 @@ }, "sharing": {}, "sortOrder": 1 + }, + { + "id": "x3612481973", + "access": { + "delete": true, + "manage": true, + "read": true, + "update": true, + "write": true + }, + "created": "2022-04-22T05:57:48.383", + "dataElement": { + "id": "GieVkTxp4HG" + }, + "lastUpdated": "2022-04-22T05:57:59.783", + "programStage": { + "id": "qLZC0lvvxQH" + }, + "sharing": {}, + "sortOrder": 2 } ], "validationStrategy": "ON_UPDATE_AND_INSERT" diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/IdSchemeExportControllerTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/IdSchemeExportControllerTest.java index 97d7d8c0c7bd..5b59faa1096b 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/IdSchemeExportControllerTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/IdSchemeExportControllerTest.java @@ -47,6 +47,7 @@ import org.hisp.dhis.attribute.Attribute; import org.hisp.dhis.common.IdentifiableObject; import org.hisp.dhis.common.IdentifiableObjectManager; +import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.dxf2.metadata.objectbundle.ObjectBundle; import org.hisp.dhis.dxf2.metadata.objectbundle.ObjectBundleMode; import org.hisp.dhis.dxf2.metadata.objectbundle.ObjectBundleParams; @@ -149,71 +150,96 @@ void shouldExportMetadataUsingGivenIdScheme(TrackerIdSchemeParam idSchemeParam) "attributeOptionCombo", "categoryOptionCombo", "attributeCategoryOptions", - "categoryOption"); + "categoryOption", + "dataValues", + "dataElement"); // maps JSON fields to expected metadata identifier in the requested idScheme and form. many // category options are mapped to a single string value in the event.attributeCategoryOptions Map> metadata = Map.of( "orgUnit", actual -> - ((Executable) - () -> - assertIdScheme( - idSchemeParam.getIdentifier(event.getOrganisationUnit()), - actual, - idSchemeParam, - "orgUnit")), + (() -> + assertIdScheme( + idSchemeParam.getIdentifier(event.getOrganisationUnit()), + actual, + idSchemeParam, + "orgUnit")), "program", actual -> - ((Executable) - () -> - assertIdScheme( - idSchemeParam.getIdentifier(event.getProgramStage().getProgram()), - actual, - idSchemeParam, - "program")), + (() -> + assertIdScheme( + idSchemeParam.getIdentifier(event.getProgramStage().getProgram()), + actual, + idSchemeParam, + "program")), "programStage", actual -> - ((Executable) - () -> - assertIdScheme( - idSchemeParam.getIdentifier(event.getProgramStage()), - actual, - idSchemeParam, - "programStage")), + (() -> + assertIdScheme( + idSchemeParam.getIdentifier(event.getProgramStage()), + actual, + idSchemeParam, + "programStage")), "attributeOptionCombo", actual -> - ((Executable) - () -> - assertIdScheme( - idSchemeParam.getIdentifier(event.getAttributeOptionCombo()), - actual, - idSchemeParam, - "attributeOptionCombo")), + (() -> + assertIdScheme( + idSchemeParam.getIdentifier(event.getAttributeOptionCombo()), + actual, + idSchemeParam, + "attributeOptionCombo")), "attributeCategoryOptions", json -> - ((Executable) - () -> { - String field = "attributeCategoryOptions"; - List expected = - event.getAttributeOptionCombo().getCategoryOptions().stream() - .map(co -> idSchemeParam.getIdentifier(co)) - .toList(); - assertNotEmpty( - expected, + (() -> { + String field = "attributeCategoryOptions"; + List expected = + event.getAttributeOptionCombo().getCategoryOptions().stream() + .map(co -> idSchemeParam.getIdentifier(co)) + .toList(); + assertNotEmpty( + expected, + String.format( + "metadata corresponding to field \"%s\" has no value in test data for" + + " idScheme '%s'", + field, idSchemeParam)); + assertTrue( + json.has(field), + () -> String.format( - "metadata corresponding to field \"%s\" has no value in test data for" - + " idScheme '%s'", - field, idSchemeParam)); - assertTrue( - json.has(field), - () -> - String.format( - "field \"%s\" is not in response %s for idScheme '%s'", - field, json, idSchemeParam)); - assertContainsOnly( - expected, Arrays.asList(json.getString(field).string().split(","))); - })); + "field \"%s\" is not in response %s for idScheme '%s'", + field, json, idSchemeParam)); + assertContainsOnly( + expected, Arrays.asList(json.getString(field).string().split(","))); + }), + "dataValues", + json -> + (() -> { + String field = "dataValues"; + List expected = + event.getEventDataValues().stream() + .map( + dv -> + idSchemeParam.getIdentifier( + get(DataElement.class, dv.getDataElement()))) + .toList(); + assertNotEmpty( + expected, + String.format( + "metadata corresponding to field \"%s\" has no value in test data for" + + " idScheme '%s'", + field, idSchemeParam)); + assertTrue( + json.has(field), + () -> + String.format( + "field \"%s\" is not in response %s for idScheme '%s'", + field, json, idSchemeParam)); + List actual = + json.getList(field, JsonObject.class) + .toList(el -> el.getString("dataElement").string("")); + assertContainsOnly(expected, actual); + })); String fields = metadata.keySet().stream().collect(Collectors.joining(",")); String idSchemes = metadata.keySet().stream() @@ -225,7 +251,7 @@ void shouldExportMetadataUsingGivenIdScheme(TrackerIdSchemeParam idSchemeParam) .content(HttpStatus.OK) .as(JsonEvent.class); - assertMetadataIdScheme(metadata, actual, "event"); + assertMetadataIdScheme(metadata, actual, idSchemeParam, "event"); } public static Stream shouldExportMetadataUsingGivenIdSchemeProvider() { @@ -243,10 +269,11 @@ public static Stream shouldExportMetadataUsingGivenIdSchem private void assertMetadataIdScheme( Map> expected, JsonObject actual, + TrackerIdSchemeParam idSchemeParam, String objectName) { List assertions = expected.entrySet().stream().map(e -> e.getValue().apply(actual)).toList(); - assertAll(objectName + " metadata assertions", assertions); + assertAll(objectName + " metadata assertions for idScheme=" + idSchemeParam, assertions); } private static void assertIdScheme( diff --git a/dhis-2/dhis-test-web-api/src/test/resources/tracker/event_and_enrollment.json b/dhis-2/dhis-test-web-api/src/test/resources/tracker/event_and_enrollment.json index 65e456734b90..12a75f2722c6 100644 --- a/dhis-2/dhis-test-web-api/src/test/resources/tracker/event_and_enrollment.json +++ b/dhis-2/dhis-test-web-api/src/test/resources/tracker/event_and_enrollment.json @@ -837,6 +837,14 @@ }, "value": "15", "providedElsewhere": false + }, + { + "dataElement": { + "idScheme": "UID", + "identifier": "GieVkTxp4HG" + }, + "value": "1.5", + "providedElsewhere": false } ], "notes": [], diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventRequestParamsMapper.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventRequestParamsMapper.java index 0130ca287fa8..90c5e4809dd7 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventRequestParamsMapper.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventRequestParamsMapper.java @@ -46,6 +46,7 @@ import org.hisp.dhis.common.collection.CollectionUtils; import org.hisp.dhis.feedback.BadRequestException; import org.hisp.dhis.program.EnrollmentStatus; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.export.event.EventOperationParams; import org.hisp.dhis.tracker.export.event.EventOperationParams.EventOperationParamsBuilder; import org.hisp.dhis.util.DateUtils; @@ -65,7 +66,8 @@ class EventRequestParamsMapper { private final EventFieldsParamMapper eventsMapper; - public EventOperationParams map(EventRequestParams eventRequestParams) + public EventOperationParams map( + EventRequestParams eventRequestParams, TrackerIdSchemeParams idSchemeParams) throws BadRequestException { OrganisationUnitSelectionMode orgUnitMode = validateDeprecatedParameter( @@ -168,7 +170,8 @@ public EventOperationParams map(EventRequestParams eventRequestParams) .events(eventUids) .enrollments(eventRequestParams.getEnrollments()) .includeDeleted(eventRequestParams.isIncludeDeleted()) - .eventParams(eventsMapper.map(eventRequestParams.getFields())); + .eventParams(eventsMapper.map(eventRequestParams.getFields())) + .idSchemeParams(idSchemeParams); mapOrderParam(builder, eventRequestParams.getOrder()); diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventsExportController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventsExportController.java index d5496f6cf98a..4e00b849f771 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventsExportController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventsExportController.java @@ -155,7 +155,8 @@ ResponseEntity> getEvents( EventRequestParams requestParams, TrackerIdSchemeParams idSchemeParams) throws BadRequestException, ForbiddenException, NotFoundException { validatePaginationParameters(requestParams); - EventOperationParams eventOperationParams = eventParamsMapper.map(requestParams); + EventOperationParams eventOperationParams = + eventParamsMapper.map(requestParams, idSchemeParams); if (requestParams.isPaged()) { PageParams pageParams = @@ -188,13 +189,14 @@ ResponseEntity> getEvents( @GetMapping(produces = CONTENT_TYPE_JSON_GZIP) void getEventsAsJsonGzip( - EventRequestParams eventRequestParams, + EventRequestParams requestParams, TrackerIdSchemeParams idSchemeParams, HttpServletResponse response) throws BadRequestException, IOException, ForbiddenException, NotFoundException { - validatePaginationParameters(eventRequestParams); + validatePaginationParameters(requestParams); - EventOperationParams eventOperationParams = eventParamsMapper.map(eventRequestParams); + EventOperationParams eventOperationParams = + eventParamsMapper.map(requestParams, idSchemeParams); List events = eventService.getEvents(eventOperationParams).stream() @@ -206,7 +208,7 @@ void getEventsAsJsonGzip( response.setContentType(CONTENT_TYPE_JSON_GZIP); List objectNodes = - fieldFilterService.toObjectNodes(events, eventRequestParams.getFields()); + fieldFilterService.toObjectNodes(events, requestParams.getFields()); writeGzip( response.getOutputStream(), Page.withoutPager(EVENTS, objectNodes), objectMapper.writer()); @@ -214,13 +216,14 @@ void getEventsAsJsonGzip( @GetMapping(produces = CONTENT_TYPE_JSON_ZIP) void getEventsAsJsonZip( - EventRequestParams eventRequestParams, + EventRequestParams requestParams, TrackerIdSchemeParams idSchemeParams, HttpServletResponse response) throws BadRequestException, ForbiddenException, IOException, NotFoundException { - validatePaginationParameters(eventRequestParams); + validatePaginationParameters(requestParams); - EventOperationParams eventOperationParams = eventParamsMapper.map(eventRequestParams); + EventOperationParams eventOperationParams = + eventParamsMapper.map(requestParams, idSchemeParams); List events = eventService.getEvents(eventOperationParams).stream() @@ -232,7 +235,7 @@ void getEventsAsJsonZip( response.setContentType(CONTENT_TYPE_JSON_ZIP); List objectNodes = - fieldFilterService.toObjectNodes(events, eventRequestParams.getFields()); + fieldFilterService.toObjectNodes(events, requestParams.getFields()); writeZip( response.getOutputStream(), @@ -243,12 +246,13 @@ void getEventsAsJsonZip( @GetMapping(produces = {CONTENT_TYPE_CSV, CONTENT_TYPE_TEXT_CSV}) void getEventsAsCsv( - EventRequestParams eventRequestParams, + EventRequestParams requestParams, TrackerIdSchemeParams idSchemeParams, HttpServletResponse response, @RequestParam(required = false, defaultValue = "false") boolean skipHeader) throws IOException, BadRequestException, ForbiddenException, NotFoundException { - EventOperationParams eventOperationParams = eventParamsMapper.map(eventRequestParams); + EventOperationParams eventOperationParams = + eventParamsMapper.map(requestParams, idSchemeParams); List events = eventService.getEvents(eventOperationParams).stream() @@ -263,12 +267,13 @@ void getEventsAsCsv( @GetMapping(produces = {CONTENT_TYPE_CSV_GZIP}) void getEventsAsCsvGZip( - EventRequestParams eventRequestParams, + EventRequestParams requestParams, TrackerIdSchemeParams idSchemeParams, HttpServletResponse response, @RequestParam(required = false, defaultValue = "false") boolean skipHeader) throws IOException, BadRequestException, ForbiddenException, NotFoundException { - EventOperationParams eventOperationParams = eventParamsMapper.map(eventRequestParams); + EventOperationParams eventOperationParams = + eventParamsMapper.map(requestParams, idSchemeParams); List events = eventService.getEvents(eventOperationParams).stream() @@ -284,12 +289,13 @@ void getEventsAsCsvGZip( @GetMapping(produces = {CONTENT_TYPE_CSV_ZIP}) void getEventsAsCsvZip( - EventRequestParams eventRequestParams, + EventRequestParams requestParams, HttpServletResponse response, @RequestParam(required = false, defaultValue = "false") boolean skipHeader, TrackerIdSchemeParams idSchemeParams) throws IOException, BadRequestException, ForbiddenException, NotFoundException { - EventOperationParams eventOperationParams = eventParamsMapper.map(eventRequestParams); + EventOperationParams eventOperationParams = + eventParamsMapper.map(requestParams, idSchemeParams); List events = eventService.getEvents(eventOperationParams).stream() @@ -313,7 +319,7 @@ ResponseEntity getEventByUid( throws NotFoundException, ForbiddenException { EventParams eventParams = eventsMapper.map(fields); org.hisp.dhis.webapi.controller.tracker.view.Event event = - EVENTS_MAPPER.map(eventService.getEvent(uid, eventParams), idSchemeParams); + EVENTS_MAPPER.map(eventService.getEvent(uid, idSchemeParams, eventParams), idSchemeParams); return ResponseEntity.ok(fieldFilterService.toObjectNode(event, fields)); } diff --git a/dhis-2/dhis-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventRequestParamsMapperTest.java b/dhis-2/dhis-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventRequestParamsMapperTest.java index e6a809242e42..eeb707f33cc8 100644 --- a/dhis-2/dhis-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventRequestParamsMapperTest.java +++ b/dhis-2/dhis-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventRequestParamsMapperTest.java @@ -71,6 +71,7 @@ import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; import org.hisp.dhis.trackedentity.TrackedEntityAttributeService; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.export.Order; import org.hisp.dhis.tracker.export.event.EventOperationParams; import org.hisp.dhis.tracker.export.event.EventParams; @@ -129,6 +130,8 @@ class EventRequestParamsMapperTest { private OrganisationUnit orgUnit; + private TrackerIdSchemeParams idSchemeParams; + @BeforeEach public void setUp() throws ForbiddenException, NotFoundException, BadRequestException { User user = new User(); @@ -165,13 +168,15 @@ public void setUp() throws ForbiddenException, NotFoundException, BadRequestExce DataElement de2 = new DataElement(); de2.setUid(DE_2_UID.getValue()); when(dataElementService.getDataElement(DE_2_UID.getValue())).thenReturn(de2); + + idSchemeParams = TrackerIdSchemeParams.builder().build(); } @Test void testMappingDoesNotFetchOptionalEmptyQueryParametersFromDB() throws BadRequestException { EventRequestParams eventRequestParams = new EventRequestParams(); - mapper.map(eventRequestParams); + mapper.map(eventRequestParams, idSchemeParams); verifyNoInteractions(programService); verifyNoInteractions(programStageService); @@ -184,7 +189,7 @@ void testMappingProgram() throws BadRequestException { EventRequestParams eventRequestParams = new EventRequestParams(); eventRequestParams.setProgram(PROGRAM_UID); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); assertEquals(UID.of(program), params.getProgram()); } @@ -195,7 +200,7 @@ void shouldMapOrgUnitModeGivenOrgUnitModeParam() throws BadRequestException { eventRequestParams.setOrgUnit(UID.of(orgUnit)); eventRequestParams.setOrgUnitMode(SELECTED); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); assertEquals(SELECTED, params.getOrgUnitMode()); } @@ -207,7 +212,8 @@ void shouldFailIfDeprecatedAndNewOrgUnitModeParameterIsSet() { eventRequestParams.setOrgUnitMode(SELECTED); BadRequestException exception = - assertThrows(BadRequestException.class, () -> mapper.map(eventRequestParams)); + assertThrows( + BadRequestException.class, () -> mapper.map(eventRequestParams, idSchemeParams)); assertStartsWith("Only one parameter of 'ouMode' and 'orgUnitMode'", exception.getMessage()); } @@ -219,7 +225,8 @@ void shouldFailIfDeprecatedAndNewEnrollmentStatusParameterIsSet() { eventRequestParams.setEnrollmentStatus(EnrollmentStatus.ACTIVE); BadRequestException exception = - assertThrows(BadRequestException.class, () -> mapper.map(eventRequestParams)); + assertThrows( + BadRequestException.class, () -> mapper.map(eventRequestParams, idSchemeParams)); assertStartsWith( "Only one parameter of 'programStatus' and 'enrollmentStatus'", exception.getMessage()); @@ -230,7 +237,7 @@ void shouldReturnOrgUnitWhenCorrectOrgUnitMapped() throws BadRequestException { EventRequestParams eventRequestParams = new EventRequestParams(); eventRequestParams.setOrgUnit(UID.of(orgUnit)); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); assertEquals(UID.of(orgUnit), params.getOrgUnit()); } @@ -240,7 +247,7 @@ void testMappingTrackedEntity() throws BadRequestException { EventRequestParams eventRequestParams = new EventRequestParams(); eventRequestParams.setTrackedEntity(UID.of("qnR1RK4cTIZ")); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); assertEquals(UID.of("qnR1RK4cTIZ"), params.getTrackedEntity()); } @@ -254,7 +261,7 @@ void testMappingOccurredAfterBefore() throws BadRequestException { EndDateTime occurredBefore = EndDateTime.of("2020-09-12"); eventRequestParams.setOccurredBefore(occurredBefore); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); assertEquals(occurredAfter.toDate(), params.getOccurredAfter()); assertEquals(occurredBefore.toDate(), params.getOccurredBefore()); @@ -269,7 +276,7 @@ void testMappingScheduledAfterBefore() throws BadRequestException { EndDateTime scheduledBefore = EndDateTime.of("2021-09-12"); eventRequestParams.setScheduledBefore(scheduledBefore); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); assertEquals(scheduledAfter.toDate(), params.getScheduledAfter()); assertEquals(scheduledBefore.toDate(), params.getScheduledBefore()); @@ -284,7 +291,7 @@ void shouldMapAfterAndBeforeDatesWhenSupplied() throws BadRequestException { EndDateTime updatedBefore = EndDateTime.of("2022-09-12"); eventRequestParams.setUpdatedBefore(updatedBefore); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); assertEquals(updatedAfter.toDate(), params.getUpdatedAfter()); assertEquals(updatedBefore.toDate(), params.getUpdatedBefore()); @@ -296,7 +303,7 @@ void shouldMapUpdatedWithinDateWhenSupplied() throws BadRequestException { String updatedWithin = "6m"; eventRequestParams.setUpdatedWithin(updatedWithin); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); assertEquals(updatedWithin, params.getUpdatedWithin()); } @@ -313,7 +320,8 @@ void shouldFailWithBadRequestExceptionWhenTryingToMapAllUpdateDatesTogether() { eventRequestParams.setUpdatedWithin(updatedWithin); Exception exception = - assertThrows(BadRequestException.class, () -> mapper.map(eventRequestParams)); + assertThrows( + BadRequestException.class, () -> mapper.map(eventRequestParams, idSchemeParams)); assertEquals( "Last updated from and/or to and last updated duration cannot be specified simultaneously", @@ -329,7 +337,7 @@ void testMappingEnrollmentEnrolledAtDates() throws BadRequestException { StartDateTime enrolledAfter = StartDateTime.of("2022-02-01"); eventRequestParams.setEnrollmentEnrolledAfter(enrolledAfter); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); assertEquals(enrolledBefore.toDate(), params.getEnrollmentEnrolledBefore()); assertEquals(enrolledAfter.toDate(), params.getEnrollmentEnrolledAfter()); @@ -344,7 +352,7 @@ void testMappingEnrollmentOccurredAtDates() throws BadRequestException { StartDateTime enrolledAfter = StartDateTime.of("2022-02-01"); eventRequestParams.setEnrollmentOccurredAfter(enrolledAfter); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); assertEquals(enrolledBefore.toDate(), params.getEnrollmentOccurredBefore()); assertEquals(enrolledAfter.toDate(), params.getEnrollmentOccurredAfter()); @@ -356,7 +364,7 @@ void testMappingEnrollments() throws BadRequestException { eventRequestParams.setEnrollments(Set.of(UID.of("NQnuK2kLm6e"))); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); assertEquals(Set.of(UID.of("NQnuK2kLm6e")), params.getEnrollments()); } @@ -366,7 +374,7 @@ void testMappingEvent() throws BadRequestException { EventRequestParams eventRequestParams = new EventRequestParams(); eventRequestParams.setEvent("XKrcfuM4Hcw;M4pNmLabtXl"); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); assertEquals(UID.of("XKrcfuM4Hcw", "M4pNmLabtXl"), params.getEvents()); } @@ -376,7 +384,7 @@ void testMappingEvents() throws BadRequestException { EventRequestParams eventRequestParams = new EventRequestParams(); eventRequestParams.setEvents(UID.of("XKrcfuM4Hcw", "M4pNmLabtXl")); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); assertEquals(UID.of("XKrcfuM4Hcw", "M4pNmLabtXl"), params.getEvents()); } @@ -385,7 +393,7 @@ void testMappingEvents() throws BadRequestException { void testMappingEventIsNull() throws BadRequestException { EventRequestParams eventRequestParams = new EventRequestParams(); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); assertIsEmpty(params.getEvents()); } @@ -396,7 +404,7 @@ void testMappingAssignedUser() throws BadRequestException { eventRequestParams.setAssignedUser("IsdLBTOBzMi;l5ab8q5skbB"); eventRequestParams.setAssignedUserMode(AssignedUserSelectionMode.PROVIDED); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); assertContainsOnly(UID.of("IsdLBTOBzMi", "l5ab8q5skbB"), params.getAssignedUsers()); assertEquals(AssignedUserSelectionMode.PROVIDED, params.getAssignedUserMode()); @@ -408,7 +416,7 @@ void testMappingAssignedUsers() throws BadRequestException { eventRequestParams.setAssignedUsers(UID.of("IsdLBTOBzMi", "l5ab8q5skbB")); eventRequestParams.setAssignedUserMode(AssignedUserSelectionMode.PROVIDED); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); assertContainsOnly(UID.of("IsdLBTOBzMi", "l5ab8q5skbB"), params.getAssignedUsers()); assertEquals(AssignedUserSelectionMode.PROVIDED, params.getAssignedUserMode()); @@ -421,7 +429,8 @@ void testMutualExclusionOfEventsAndFilter() { eventRequestParams.setEvent(DE_1_UID + ";" + DE_2_UID); Exception exception = - assertThrows(BadRequestException.class, () -> mapper.map(eventRequestParams)); + assertThrows( + BadRequestException.class, () -> mapper.map(eventRequestParams, idSchemeParams)); assertEquals( "Event UIDs and filters can not be specified at the same time", exception.getMessage()); } @@ -431,7 +440,7 @@ void shouldMapDataElementFilters() throws BadRequestException { EventRequestParams eventRequestParams = new EventRequestParams(); eventRequestParams.setFilter(DE_1_UID + ":eq:2," + DE_2_UID + ":like:foo"); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); Map> dataElementFilters = params.getDataElementFilters(); assertNotNull(dataElementFilters); @@ -449,7 +458,7 @@ void shouldMapDataElementFiltersWhenDataElementHasMultipleFilters() throws BadRe EventRequestParams eventRequestParams = new EventRequestParams(); eventRequestParams.setFilter(DE_1_UID + ":gt:10:lt:20"); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); Map> dataElementFilters = params.getDataElementFilters(); assertNotNull(dataElementFilters); @@ -465,7 +474,7 @@ void shouldMapDataElementFiltersWhenDataElementHasMultipleFilters() throws BadRe void shouldMapDataElementFiltersToDefaultIfNoneSet() throws BadRequestException { EventRequestParams eventRequestParams = new EventRequestParams(); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); Map> dataElementFilters = params.getDataElementFilters(); @@ -478,7 +487,7 @@ void shouldMapAttributeFilters() throws BadRequestException { EventRequestParams eventRequestParams = new EventRequestParams(); eventRequestParams.setFilterAttributes(TEA_1_UID + ":eq:2," + TEA_2_UID + ":like:foo"); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); Map> attributeFilters = params.getAttributeFilters(); assertNotNull(attributeFilters); @@ -496,7 +505,7 @@ void shouldMapAttributeFiltersWhenAttributeHasMultipleFilters() throws BadReques EventRequestParams eventRequestParams = new EventRequestParams(); eventRequestParams.setFilterAttributes(TEA_1_UID + ":gt:10:lt:20"); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); Map> attributeFilters = params.getAttributeFilters(); assertNotNull(attributeFilters); @@ -513,7 +522,7 @@ void shouldMapAttributeFiltersWhenOnlyGivenUID() throws BadRequestException { EventRequestParams eventRequestParams = new EventRequestParams(); eventRequestParams.setFilterAttributes(TEA_1_UID.getValue()); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); Map> attributeFilters = params.getAttributeFilters(); assertNotNull(attributeFilters); @@ -525,7 +534,7 @@ void shouldMapAttributeFiltersWhenOnlyGivenUID() throws BadRequestException { void shouldMapAttributeFiltersToDefaultIfNoneSet() throws BadRequestException { EventRequestParams eventRequestParams = new EventRequestParams(); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); Map> attributeFilters = params.getAttributeFilters(); @@ -540,7 +549,7 @@ void shouldMapOrderParameterInGivenOrderWhenFieldsAreOrderable() throws BadReque OrderCriteria.fromOrderString( "createdAt:asc,zGlzbfreTOH,programStage:desc,scheduledAt:asc")); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); assertEquals( List.of( @@ -558,7 +567,8 @@ void shouldFailGivenInvalidOrderFieldName() { OrderCriteria.fromOrderString("unsupportedProperty1:asc,enrolledAt:asc")); Exception exception = - assertThrows(BadRequestException.class, () -> mapper.map(eventRequestParams)); + assertThrows( + BadRequestException.class, () -> mapper.map(eventRequestParams, idSchemeParams)); assertAll( () -> assertStartsWith("order parameter is invalid", exception.getMessage()), () -> assertContains("unsupportedProperty1", exception.getMessage())); @@ -569,7 +579,7 @@ void shouldMapSelectedOrgUnitModeWhenOrgUnitModeNotProvided() throws BadRequestE EventRequestParams eventRequestParams = new EventRequestParams(); eventRequestParams.setOrgUnit(UID.of(orgUnit)); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); assertEquals(SELECTED, params.getOrgUnitMode()); } @@ -579,7 +589,7 @@ void shouldMapAccessibleOrgUnitModeWhenOrgUnitModeNorOrgUnitProvided() throws BadRequestException { EventRequestParams eventRequestParams = new EventRequestParams(); - EventOperationParams params = mapper.map(eventRequestParams); + EventOperationParams params = mapper.map(eventRequestParams, idSchemeParams); assertEquals(ACCESSIBLE, params.getOrgUnitMode()); } @@ -597,7 +607,8 @@ void shouldFailWhenOrgUnitSuppliedAndOrgUnitModeCannotHaveOrgUnit( eventRequestParams.setOrgUnitMode(orgUnitMode); Exception exception = - assertThrows(BadRequestException.class, () -> mapper.map(eventRequestParams)); + assertThrows( + BadRequestException.class, () -> mapper.map(eventRequestParams, idSchemeParams)); assertStartsWith( "orgUnitMode " + orgUnitMode + " cannot be used with orgUnits.", exception.getMessage()); @@ -613,7 +624,8 @@ void shouldFailWhenNoOrgUnitSuppliedAndOrgUnitModeNeedsOrgUnit( eventRequestParams.setOrgUnitMode(orgUnitMode); Exception exception = - assertThrows(BadRequestException.class, () -> mapper.map(eventRequestParams)); + assertThrows( + BadRequestException.class, () -> mapper.map(eventRequestParams, idSchemeParams)); assertStartsWith( "At least one org unit is required for orgUnitMode: " + orgUnitMode, @@ -628,7 +640,7 @@ void shouldMapEventParamsTrueWhenFieldPathIncludeRelationships() throws BadReque eventRequestParams.setFields(fieldPaths); when(eventFieldsParamMapper.map(fieldPaths)).thenReturn(EventParams.TRUE); - EventOperationParams eventOperationParams = mapper.map(eventRequestParams); + EventOperationParams eventOperationParams = mapper.map(eventRequestParams, idSchemeParams); assertEquals(EventParams.TRUE, eventOperationParams.getEventParams()); } @@ -640,7 +652,7 @@ void shouldMapEventParamsFalseWhenFieldPathIncludeRelationships() throws BadRequ eventRequestParams.setFields(fieldPaths); when(eventFieldsParamMapper.map(fieldPaths)).thenReturn(EventParams.FALSE); - EventOperationParams eventOperationParams = mapper.map(eventRequestParams); + EventOperationParams eventOperationParams = mapper.map(eventRequestParams, idSchemeParams); assertEquals(EventParams.FALSE, eventOperationParams.getEventParams()); } } diff --git a/docker-compose.yml b/docker-compose.yml index 6ba2568eb059..08753d0a7fe8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,6 +24,9 @@ services: db: image: ghcr.io/baosystems/postgis:13-3.4 + # uncomment to enable query logging + # command: + # ["postgres", "-c", "log_statement=all", "-c", "log_destination=stderr"] ports: - "127.0.0.1:5432:5432" volumes: