diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/DimensionalItemObject.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/DimensionalItemObject.java index c12c4e954df7..4134b7314387 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/DimensionalItemObject.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/DimensionalItemObject.java @@ -87,4 +87,8 @@ public interface DimensionalItemObject extends NameableObject { default int getPeriodOffset() { return (getQueryMods() != null) ? getQueryMods().getPeriodOffset() : 0; } + + default boolean isOfType(DimensionItemType type) { + return type == getDimensionItemType(); + } } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/feedback/ErrorCode.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/feedback/ErrorCode.java index 1c1cf44d0afe..9dcf3008b561 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/feedback/ErrorCode.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/feedback/ErrorCode.java @@ -505,6 +505,9 @@ public enum ErrorCode { E7236("Program stage '{0}' is not associated to program '{0}'"), E7237("Sorting must have a valid dimension and a direction"), E7238("Sorting dimension ‘{0}’ is not a column"), + E7239( + "Tracked Entity Attributes marked as 'confidential' can only be used in aggregate analytics: `{0}`"), + E7240("Data Elements marked as 'skipAnalytics' can only be used in aggregate analytics: `{0}`"), /* TE analytics */ E7250("Dimension is not a fully qualified: `{0}`"), diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/program/Program.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/program/Program.java index d6ca3575963d..8d9b8da95009 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/program/Program.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/program/Program.java @@ -37,11 +37,9 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; import java.util.ArrayList; -import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import org.hisp.dhis.category.CategoryCombo; @@ -262,26 +260,12 @@ public Set getDataElements() { return programStages.stream().flatMap(ps -> ps.getDataElements().stream()).collect(toSet()); } - /** - * Returns all data elements which are part of the stages of this program and is not skipped in - * analytics. - */ - public Set getAnalyticsDataElements() { - return programStages.stream() - .map(ProgramStage::getProgramStageDataElements) - .flatMap(Collection::stream) - .filter(Objects::nonNull) - .filter(psde -> !psde.getSkipAnalytics()) - .map(ProgramStageDataElement::getDataElement) - .collect(toSet()); - } - /** * Returns data elements which are part of the stages of this program which have a legend set and * is of numeric value type. */ - public Set getAnalyticsDataElementsWithLegendSet() { - return getAnalyticsDataElements().stream() + public Set getDataElementsWithLegendSet() { + return getDataElements().stream() .filter(de -> de.hasLegendSet() && de.isNumericType()) .collect(toSet()); } @@ -296,23 +280,13 @@ public List getTrackedEntityAttributes() { .collect(Collectors.toList()); } - /** - * Returns non-confidential TrackedEntityAttributes from ProgramTrackedEntityAttributes. Use - * getAttributes() to access the persisted attribute list. - */ - public List getNonConfidentialTrackedEntityAttributes() { - return getTrackedEntityAttributes().stream() - .filter(a -> !a.isConfidentialBool()) - .collect(Collectors.toList()); - } - /** * Returns TrackedEntityAttributes from ProgramTrackedEntityAttributes which have a legend set and * is of numeric value type. */ - public List getNonConfidentialTrackedEntityAttributesWithLegendSet() { + public List getTrackedEntityAttributesWithLegendSet() { return getTrackedEntityAttributes().stream() - .filter(a -> !a.isConfidentialBool() && a.hasLegendSet() && a.isNumericType()) + .filter(a -> a.hasLegendSet() && a.isNumericType()) .collect(Collectors.toList()); } diff --git a/dhis-2/dhis-api/src/test/java/org/hisp/dhis/program/ProgramTest.java b/dhis-2/dhis-api/src/test/java/org/hisp/dhis/program/ProgramTest.java index 3555686ada7e..7f7cbff8216b 100644 --- a/dhis-2/dhis-api/src/test/java/org/hisp/dhis/program/ProgramTest.java +++ b/dhis-2/dhis-api/src/test/java/org/hisp/dhis/program/ProgramTest.java @@ -85,7 +85,6 @@ void testGetAnalyticsDataElements() { assertEquals(2, prA.getDataElements().size()); assertTrue(prA.getDataElements().contains(deA)); assertTrue(prA.getDataElements().contains(deB)); - assertEquals(1, prA.getAnalyticsDataElements().size()); assertTrue(prA.getDataElements().contains(deA)); } @@ -106,7 +105,6 @@ void testCopyOfWithPropertyValuesSet() { // check equal assertEquals(original.getAccess(), copy.getAccess()); assertEquals(original.getAccessLevel(), copy.getAccessLevel()); - assertEquals(original.getAnalyticsDataElements(), copy.getAnalyticsDataElements()); assertEquals(original.getCategoryCombo(), copy.getCategoryCombo()); assertEquals(original.getCompleteEventsExpiryDays(), copy.getCompleteEventsExpiryDays()); assertEquals(original.getDataElements(), copy.getDataElements()); @@ -180,10 +178,7 @@ void testCopyOfWithNulls() { assertEquals("copynull", copy.getName()); assertEquals(original.getAccessLevel(), copy.getAccessLevel()); assertEquals(original.getDescription(), copy.getDescription()); - assertTrue(copy.getAnalyticsDataElements().isEmpty()); assertTrue(copy.getDataElements().isEmpty()); - assertTrue(copy.getNonConfidentialTrackedEntityAttributes().isEmpty()); - assertTrue(copy.getNonConfidentialTrackedEntityAttributesWithLegendSet().isEmpty()); assertTrue(copy.getNotificationTemplates().isEmpty()); assertTrue(copy.getOrganisationUnits().isEmpty()); assertTrue(copy.getProgramAttributes().isEmpty()); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/EventQueryParams.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/EventQueryParams.java index 4d6f0683f691..7848156e434d 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/EventQueryParams.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/EventQueryParams.java @@ -37,6 +37,7 @@ import static org.hisp.dhis.common.DimensionalObject.PERIOD_DIM_ID; import static org.hisp.dhis.common.DimensionalObjectUtils.asList; import static org.hisp.dhis.common.DimensionalObjectUtils.asTypedList; +import static org.hisp.dhis.common.RequestTypeAware.EndpointAction.AGGREGATE; import static org.hisp.dhis.common.RequestTypeAware.EndpointAction.QUERY; import com.google.common.base.MoreObjects; @@ -998,6 +999,10 @@ public boolean isRowContext() { return rowContext; } + public boolean includeConfidentialOrSkipAnalyticsItems() { + return endpointAction == AGGREGATE; + } + // ------------------------------------------------------------------------- // Builder of immutable instances // ------------------------------------------------------------------------- diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/DefaultEnrollmentAnalyticsDimensionsService.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/DefaultEnrollmentAnalyticsDimensionsService.java index 2894d3771eb6..c13b40d3d4df 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/DefaultEnrollmentAnalyticsDimensionsService.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/DefaultEnrollmentAnalyticsDimensionsService.java @@ -31,22 +31,26 @@ import static org.hisp.dhis.analytics.common.DimensionsServiceCommon.OperationType.QUERY; import static org.hisp.dhis.analytics.common.DimensionsServiceCommon.collectDimensions; import static org.hisp.dhis.analytics.common.DimensionsServiceCommon.filterByValueType; +import static org.hisp.dhis.analytics.event.data.DefaultEventAnalyticsDimensionsService.getTeasIfRegistration; import static org.hisp.dhis.common.PrefixedDimensions.ofItemsWithProgram; import static org.hisp.dhis.common.PrefixedDimensions.ofProgramStageDataElements; import java.util.Collection; import java.util.List; import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.hisp.dhis.analytics.common.DimensionsServiceCommon; +import org.hisp.dhis.analytics.common.DimensionsServiceCommon.OperationType; import org.hisp.dhis.analytics.event.EnrollmentAnalyticsDimensionsService; import org.hisp.dhis.common.PrefixedDimension; import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramService; import org.hisp.dhis.program.ProgramStage; +import org.hisp.dhis.program.ProgramStageDataElement; import org.hisp.dhis.security.acl.AclService; -import org.hisp.dhis.trackedentity.TrackedEntityAttribute; import org.hisp.dhis.user.CurrentUserUtil; import org.hisp.dhis.user.UserDetails; import org.springframework.stereotype.Service; @@ -77,9 +81,7 @@ public List getQueryDimensionsByProgramId(String programId) { .collect(Collectors.toSet())), getProgramStageDataElements(QUERY, program), filterByValueType( - QUERY, - ofItemsWithProgram( - program, getTeasIfRegistrationAndNotConfidential(program)))))) + QUERY, ofItemsWithProgram(program, getTeasIfRegistration(program)))))) .orElse(List.of()); } @@ -87,14 +89,22 @@ private Collection getProgramStageDataElements( DimensionsServiceCommon.OperationType operationType, Program program) { return program.getProgramStages().stream() .map(ProgramStage::getProgramStageDataElements) - .map( - programStageDataElements -> - filterByValueType( - operationType, ofProgramStageDataElements(programStageDataElements))) + .map(psdes -> excludeIfSkipAnalytics(operationType, psdes)) + .map(psdes -> filterByValueType(operationType, ofProgramStageDataElements(psdes))) .flatMap(Collection::stream) .collect(Collectors.toList()); } + private Set excludeIfSkipAnalytics( + OperationType operationType, Set programStageDataElements) { + if (operationType == QUERY) { + return programStageDataElements.stream() + .filter(Predicate.not(ProgramStageDataElement::getSkipAnalytics)) + .collect(Collectors.toSet()); + } + return programStageDataElements; + } + @Override public List getAggregateDimensionsByProgramStageId(String programId) { return Optional.of(programId) @@ -109,19 +119,4 @@ public List getAggregateDimensionsByProgramStageId(String pro ofItemsWithProgram(program, program.getTrackedEntityAttributes()))))) .orElse(List.of()); } - - private Collection getTeasIfRegistrationAndNotConfidential( - Program program) { - return Optional.of(program) - .filter(Program::isRegistration) - .map(Program::getTrackedEntityAttributes) - .orElse(List.of()) - .stream() - .filter(this::isNotConfidential) - .collect(Collectors.toList()); - } - - private boolean isNotConfidential(TrackedEntityAttribute trackedEntityAttribute) { - return !trackedEntityAttribute.isConfidentialBool(); - } } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/DefaultEventAnalyticsDimensionsService.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/DefaultEventAnalyticsDimensionsService.java index 76086a2c4b27..a67b341b61df 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/DefaultEventAnalyticsDimensionsService.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/DefaultEventAnalyticsDimensionsService.java @@ -135,9 +135,7 @@ private List dimensions(ProgramStage programStage) { .filter(pi -> aclService.canRead(currentUserDetails, pi)) .collect(Collectors.toSet())), filterByValueType(QUERY, ofDataElements(programStage)), - filterByValueType( - QUERY, - ofItemsWithProgram(p, getTeasIfRegistrationAndNotConfidential(p))), + filterByValueType(QUERY, ofItemsWithProgram(p, getTeasIfRegistration(p))), ofItemsWithProgram(p, getCategories(p)), ofItemsWithProgram(p, getAttributeCategoryOptionGroupSetsIfNeeded(p))))) .orElse(List.of()); @@ -187,17 +185,10 @@ private List getCategories(Program program) { .orElse(List.of()); } - private List getTeasIfRegistrationAndNotConfidential(Program program) { + static List getTeasIfRegistration(Program program) { return Optional.of(program) .filter(Program::isRegistration) .map(Program::getTrackedEntityAttributes) - .orElse(List.of()) - .stream() - .filter(this::isNotConfidential) - .collect(Collectors.toList()); - } - - private boolean isNotConfidential(TrackedEntityAttribute trackedEntityAttribute) { - return !trackedEntityAttribute.isConfidentialBool(); + .orElse(List.of()); } } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/DefaultEventDataQueryService.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/DefaultEventDataQueryService.java index b08af886985a..3d20c79d5a61 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/DefaultEventDataQueryService.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/DefaultEventDataQueryService.java @@ -36,6 +36,8 @@ import static org.hisp.dhis.analytics.event.data.DefaultEventDataQueryService.SortableItems.translateItemIfNecessary; import static org.hisp.dhis.analytics.util.AnalyticsUtils.illegalQueryExSupplier; import static org.hisp.dhis.analytics.util.AnalyticsUtils.throwIllegalQueryEx; +import static org.hisp.dhis.common.DimensionItemType.DATA_ELEMENT; +import static org.hisp.dhis.common.DimensionItemType.PROGRAM_ATTRIBUTE; import static org.hisp.dhis.common.DimensionalObject.DIMENSION_NAME_SEP; import static org.hisp.dhis.common.DimensionalObject.PERIOD_DIM_ID; import static org.hisp.dhis.common.DimensionalObjectUtils.getDimensionFromParam; @@ -52,6 +54,7 @@ import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; +import java.util.stream.Stream; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; @@ -67,6 +70,7 @@ import org.hisp.dhis.analytics.table.EnrollmentAnalyticsColumnName; import org.hisp.dhis.analytics.table.EventAnalyticsColumnName; import org.hisp.dhis.common.BaseDimensionalItemObject; +import org.hisp.dhis.common.BaseIdentifiableObject; import org.hisp.dhis.common.DimensionalItemObject; import org.hisp.dhis.common.DimensionalObject; import org.hisp.dhis.common.EventAnalyticalObject; @@ -90,6 +94,7 @@ import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramService; import org.hisp.dhis.program.ProgramStage; +import org.hisp.dhis.program.ProgramStageDataElement; import org.hisp.dhis.program.ProgramStageService; import org.hisp.dhis.setting.UserSettings; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; @@ -223,9 +228,73 @@ public EventQueryParams getFromRequest(EventDataQueryRequest request, boolean an eventQueryParams = builder.build(); } + validateQueryParamsForConfidentialAndSkipAnalytics(eventQueryParams); + return eventQueryParams; } + static void validateQueryParamsForConfidentialAndSkipAnalytics( + EventQueryParams eventQueryParams) { + if (eventQueryParams.includeConfidentialOrSkipAnalyticsItems()) { + return; + } + Set confidentialAttributes = + Stream.concat( + eventQueryParams.getItems().stream(), eventQueryParams.getItemFilters().stream()) + .map(QueryItem::getItem) + .filter(Objects::nonNull) + .filter(dimObj -> dimObj.isOfType(PROGRAM_ATTRIBUTE)) + .map(TrackedEntityAttribute.class::cast) + .filter(TrackedEntityAttribute::isConfidentialBool) + .collect(Collectors.toSet()); + + if (!confidentialAttributes.isEmpty()) { + throw new IllegalQueryException( + new ErrorMessage( + ErrorCode.E7239, + confidentialAttributes.stream() + .map(TrackedEntityAttribute::getUid) + .collect(Collectors.joining(", ")))); + } + + Set skipAnalyticsDataElements = + Stream.concat( + eventQueryParams.getItems().stream(), eventQueryParams.getItemFilters().stream()) + .filter(item -> item.getItem().isOfType(DATA_ELEMENT)) + .filter(DefaultEventDataQueryService::isSkipAnalytics) + .map(item -> (DataElement) item.getItem()) + .collect(Collectors.toSet()); + + if (!skipAnalyticsDataElements.isEmpty()) { + throw new IllegalQueryException( + new ErrorMessage( + ErrorCode.E7240, + skipAnalyticsDataElements.stream() + .map(DataElement::getUid) + .collect(Collectors.joining(", ")))); + } + } + + /** + * Checks if the data element is marked as skip analytics by looking at the program stage data + * elements associated with the program stage. If any of the program stage data elements has the + * skip analytics flag set to true, the data element in it is considered to be skipAnalytics. + * + * @param item the query item + * @return true if the data element is marked as skip analytics, false otherwise + */ + static boolean isSkipAnalytics(QueryItem item) { + return Optional.of(item) + .map(QueryItem::getProgramStage) + .map(ProgramStage::getProgramStageDataElements) + .orElse(Set.of()) + .stream() + .filter(ProgramStageDataElement::getSkipAnalytics) + .map(ProgramStageDataElement::getDataElement) + .map(BaseIdentifiableObject::getUid) + .anyMatch(uid -> uid.equals(item.getItem().getUid())); + } + private boolean hasPeriodDimension(EventQueryParams eventQueryParams) { return Objects.nonNull(getPeriodDimension(eventQueryParams)); } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/AbstractEventJdbcTableManager.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/AbstractEventJdbcTableManager.java index 5807a308ae4e..c00a0d97feb0 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/AbstractEventJdbcTableManager.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/AbstractEventJdbcTableManager.java @@ -249,7 +249,7 @@ protected void populateTableInternal(AnalyticsTablePartition partition, String f * Returns a list of columns based on the given attribute. * * @param attribute the {@link TrackedEntityAttribute}. - * @return a list of {@link AnaylyticsTableColumn}. + * @return a list of {@link AnalyticsTableColumn}. */ protected List getColumnForAttribute(TrackedEntityAttribute attribute) { List columns = new ArrayList<>(); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcEnrollmentAnalyticsTableManager.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcEnrollmentAnalyticsTableManager.java index 009b19645afb..b7c0ca957a65 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcEnrollmentAnalyticsTableManager.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcEnrollmentAnalyticsTableManager.java @@ -190,7 +190,7 @@ private List getColumns(Program program) { * @return a list of {@link AnalyticsTableColumn}. */ private List getTrackedEntityAttributeColumns(Program program) { - return program.getNonConfidentialTrackedEntityAttributes().stream() + return program.getTrackedEntityAttributes().stream() .map(this::getColumnForAttribute) .flatMap(Collection::stream) .toList(); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcEventAnalyticsTableManager.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcEventAnalyticsTableManager.java index 8c652952993f..cc6c5496394e 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcEventAnalyticsTableManager.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcEventAnalyticsTableManager.java @@ -463,12 +463,12 @@ private List getAttributeCategoryColumns(Program program) private List getDataElementColumns(Program program) { List columns = new ArrayList<>(); columns.addAll( - program.getAnalyticsDataElements().stream() + program.getDataElements().stream() .map(de -> getColumnForDataElement(de, false)) .flatMap(Collection::stream) .toList()); columns.addAll( - program.getAnalyticsDataElementsWithLegendSet().stream() + program.getDataElementsWithLegendSet().stream() .map(de -> getColumnForDataElement(de, true)) .flatMap(Collection::stream) .toList()); @@ -569,12 +569,12 @@ private List getColumnForOrgUnitDataElement( private List getAttributeColumns(Program program) { List columns = new ArrayList<>(); columns.addAll( - program.getNonConfidentialTrackedEntityAttributes().stream() + program.getTrackedEntityAttributes().stream() .map(this::getColumnForAttribute) .flatMap(Collection::stream) .toList()); columns.addAll( - program.getNonConfidentialTrackedEntityAttributesWithLegendSet().stream() + program.getTrackedEntityAttributesWithLegendSet().stream() .map(this::getColumnForAttributeWithLegendSet) .flatMap(Collection::stream) .toList()); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcTrackedEntityAnalyticsTableManager.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcTrackedEntityAnalyticsTableManager.java index 333602b5adf8..503bd434e512 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcTrackedEntityAnalyticsTableManager.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcTrackedEntityAnalyticsTableManager.java @@ -89,8 +89,7 @@ public class JdbcTrackedEntityAnalyticsTableManager extends AbstractJdbcTableManager { private static final String PROGRAMS_BY_TET_KEY = "programsByTetUid"; - private static final String ALL_NON_CONFIDENTIAL_TET_ATTRIBUTES = - "allNonConfidentialTetAttributes"; + private static final String ALL_TET_ATTRIBUTES = "allTetAttributes"; private final TrackedEntityTypeService trackedEntityTypeService; @@ -203,12 +202,9 @@ private List getColumns( .build())); List trackedEntityAttributes = - getAllTrackedEntityAttributes(trackedEntityType, programsByTetUid) - .filter(tea -> !tea.isConfidentialBool()) - .toList(); + getAllTrackedEntityAttributes(trackedEntityType, programsByTetUid).toList(); - params.addExtraParam( - trackedEntityType.getUid(), ALL_NON_CONFIDENTIAL_TET_ATTRIBUTES, trackedEntityAttributes); + params.addExtraParam(trackedEntityType.getUid(), ALL_TET_ATTRIBUTES, trackedEntityAttributes); columns.addAll( trackedEntityAttributes.stream() @@ -358,7 +354,7 @@ public void populateTable(AnalyticsTableUpdateParams params, AnalyticsTableParti Map.of())); ((List) - params.getExtraParam(trackedEntityType.getUid(), ALL_NON_CONFIDENTIAL_TET_ATTRIBUTES)) + params.getExtraParam(trackedEntityType.getUid(), ALL_TET_ATTRIBUTES)) .forEach( tea -> sql.append( diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/DefaultEventDataQueryServiceTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/DefaultEventDataQueryServiceTest.java new file mode 100644 index 000000000000..0a54a25dc6aa --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/DefaultEventDataQueryServiceTest.java @@ -0,0 +1,151 @@ +/* + * 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.analytics.event.data; + +import static org.hisp.dhis.analytics.event.data.DefaultEventDataQueryService.validateQueryParamsForConfidentialAndSkipAnalytics; +import static org.hisp.dhis.common.DimensionItemType.DATA_ELEMENT; +import static org.hisp.dhis.common.DimensionItemType.PROGRAM_ATTRIBUTE; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import org.hisp.dhis.analytics.event.EventQueryParams; +import org.hisp.dhis.common.DimensionItemType; +import org.hisp.dhis.common.DimensionalItemObject; +import org.hisp.dhis.common.IllegalQueryException; +import org.hisp.dhis.common.QueryItem; +import org.hisp.dhis.dataelement.DataElement; +import org.hisp.dhis.program.ProgramStage; +import org.hisp.dhis.program.ProgramStageDataElement; +import org.hisp.dhis.trackedentity.TrackedEntityAttribute; +import org.junit.jupiter.api.Test; + +class DefaultEventDataQueryServiceTest { + + private EventQueryParams mockEventQueryParams( + boolean includeConfidentialOrSkipAnalyticsItems, List queryItems) { + EventQueryParams mock = mock(EventQueryParams.class); + when(mock.includeConfidentialOrSkipAnalyticsItems()) + .thenReturn(includeConfidentialOrSkipAnalyticsItems); + when(mock.getItems()).thenReturn(queryItems); + when(mock.getItemFilters()).thenReturn(List.of()); + return mock; + } + + private QueryItem mockDimensionalItemObject( + Class clazz, DimensionItemType type, List> behavious) { + T mock = mock(clazz); + when(mock.isOfType(type)).thenReturn(true); + QueryItem queryItem = mock(QueryItem.class); + when(queryItem.getItem()).thenReturn(mock); + behavious.forEach(c -> c.accept(mock)); + return queryItem; + } + + @Test + void testAggregateDontThrowExceptionForConfidential() { + QueryItem queryItem = + mockDimensionalItemObject( + TrackedEntityAttribute.class, + PROGRAM_ATTRIBUTE, + List.of(t -> when(t.isConfidentialBool()).thenReturn(true))); + EventQueryParams eventQueryParams = mockEventQueryParams(true, List.of(queryItem)); + assertDoesNotThrow(() -> validateQueryParamsForConfidentialAndSkipAnalytics(eventQueryParams)); + } + + @Test + void testAggregateDontThrowExceptionForSkipAnalytics() { + final String dataElementUid = "dataElementUid"; + + ProgramStage programStage = mock(ProgramStage.class); + ProgramStageDataElement programStageDataElement = mock(ProgramStageDataElement.class); + + when(programStage.getProgramStageDataElements()).thenReturn(Set.of(programStageDataElement)); + when(programStageDataElement.getSkipAnalytics()).thenReturn(true); + + DataElement dataElement = mock(DataElement.class); + when(dataElement.getUid()).thenReturn(dataElementUid); + when(programStageDataElement.getDataElement()).thenReturn(dataElement); + + QueryItem queryItem = + mockDimensionalItemObject( + DataElement.class, + DATA_ELEMENT, + List.of(t -> when(t.getUid()).thenReturn(dataElementUid))); + when(queryItem.getProgramStage()).thenReturn(programStage); + + EventQueryParams eventQueryParams = mockEventQueryParams(true, List.of(queryItem)); + + assertDoesNotThrow(() -> validateQueryParamsForConfidentialAndSkipAnalytics(eventQueryParams)); + } + + @Test + void testQueryThrowsForConfidential() { + QueryItem queryItem = + mockDimensionalItemObject( + TrackedEntityAttribute.class, + PROGRAM_ATTRIBUTE, + List.of(t -> when(t.isConfidentialBool()).thenReturn(true))); + EventQueryParams eventQueryParams = mockEventQueryParams(false, List.of(queryItem)); + assertThrows( + IllegalQueryException.class, + () -> validateQueryParamsForConfidentialAndSkipAnalytics(eventQueryParams)); + } + + @Test + void testQueryThrowsForSkipAnalytics() { + final String dataElementUid = "dataElementUid"; + + ProgramStage programStage = mock(ProgramStage.class); + ProgramStageDataElement programStageDataElement = mock(ProgramStageDataElement.class); + + when(programStage.getProgramStageDataElements()).thenReturn(Set.of(programStageDataElement)); + when(programStageDataElement.getSkipAnalytics()).thenReturn(true); + + DataElement dataElement = mock(DataElement.class); + when(dataElement.getUid()).thenReturn(dataElementUid); + when(programStageDataElement.getDataElement()).thenReturn(dataElement); + + QueryItem queryItem = + mockDimensionalItemObject( + DataElement.class, + DATA_ELEMENT, + List.of(t -> when(t.getUid()).thenReturn(dataElementUid))); + when(queryItem.getProgramStage()).thenReturn(programStage); + + EventQueryParams eventQueryParams = mockEventQueryParams(false, List.of(queryItem)); + + assertThrows( + IllegalQueryException.class, + () -> validateQueryParamsForConfidentialAndSkipAnalytics(eventQueryParams)); + } +} diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcTrackedEntityAnalyticsTableManagerTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcTrackedEntityAnalyticsTableManagerTest.java index 606d62214bbe..d9f1dc4038a9 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcTrackedEntityAnalyticsTableManagerTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcTrackedEntityAnalyticsTableManagerTest.java @@ -28,7 +28,6 @@ package org.hisp.dhis.analytics.table; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; @@ -143,13 +142,13 @@ void verifyNonConfidentialTeasAreSkipped() { AnalyticsTable analyticsTable = analyticsTables.get(0); assertContainsNonConfidentialTeaColumns(analyticsTable); - assertDoesntContainConfidentialTeaColumns(analyticsTable); + assertContainsConfidentialTeaColumns(analyticsTable); } - private void assertDoesntContainConfidentialTeaColumns(AnalyticsTable analyticsTable) { + private void assertContainsConfidentialTeaColumns(AnalyticsTable analyticsTable) { List columns = analyticsTable.getColumns(); - assertFalse(columns.stream().map(Column::getName).anyMatch("confidentialTeaUid"::equals)); + assertTrue(columns.stream().map(Column::getName).anyMatch("confidentialTeaUid"::equals)); } private void assertContainsNonConfidentialTeaColumns(AnalyticsTable analyticsTable) { diff --git a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/AnalyticsDimensionsTest.java b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/AnalyticsDimensionsTest.java index 08811f890abf..821adf4c0230 100644 --- a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/AnalyticsDimensionsTest.java +++ b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/AnalyticsDimensionsTest.java @@ -183,7 +183,7 @@ public void shouldOnlyReturnConfidentialAttributeInAggregateDimensions() { .query() .getDimensionsByDimensionType(trackerProgram.getUid(), "PROGRAM_ATTRIBUTE") .validate() - .body("dimensions.uid", not(CoreMatchers.hasItem(confidentialAttribute))); + .body("dimensions.uid", CoreMatchers.hasItem(confidentialAttribute)); analyticsEnrollmentsActions .aggregate()