diff --git a/.github/workflows/run-api-tests.yml b/.github/workflows/run-api-tests.yml index 0e58ab299626..c244585a27f2 100644 --- a/.github/workflows/run-api-tests.yml +++ b/.github/workflows/run-api-tests.yml @@ -41,13 +41,13 @@ jobs: # build and publish multi-arch images using Jib. Image is used for api tests in # this workflow and can be pulled from Dockerhub by devs to run locally, ... mvn clean verify --threads 2C --batch-mode --no-transfer-progress \ - -DskipTests --update-snapshots --file dhis-2/pom.xml \ + -DskipTests -Dmdep.analyze.skip --update-snapshots --file dhis-2/pom.xml \ --projects dhis-web-server --also-make --activate-profiles jibBuild \ -Djib.to.image=$CORE_IMAGE_NAME -Djib.container.labels=DHIS2_BUILD_REVISION=${{github.event.pull_request.head.sha}},DHIS2_BUILD_BRANCH=${{github.head_ref}} else # only build image for running api tests in this workflow i.e. master, 2.39, ... mvn clean verify --threads 2C --batch-mode --no-transfer-progress \ - -DskipTests --update-snapshots --file dhis-2/pom.xml \ + -DskipTests -Dmdep.analyze.skip --update-snapshots --file dhis-2/pom.xml \ --projects dhis-web-server --also-make --activate-profiles jibDockerBuild \ -Djib.to.image=$CORE_IMAGE_NAME fi diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 105cf42facc2..4226b4bb46e0 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -24,6 +24,9 @@ jobs: - name: Run unit tests run: mvn clean test --threads 2C --batch-mode --no-transfer-progress --update-snapshots --file ./dhis-2/pom.xml --activate-profiles unit-test timeout-minutes: 30 + - name: Run dependency analysis + run: mvn dependency:analyze --file ./dhis-2/pom.xml + timeout-minutes: 2 - name: Report coverage to codecov uses: codecov/codecov-action@v3 with: diff --git a/dhis-2/dhis-api/pom.xml b/dhis-2/dhis-api/pom.xml index 4a7fa1a4189c..5a864693924f 100644 --- a/dhis-2/dhis-api/pom.xml +++ b/dhis-2/dhis-api/pom.xml @@ -105,10 +105,6 @@ org.springframework.security spring-security-core - - commons-codec - commons-codec - org.locationtech.jts jts-core diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/indicator/IndicatorMergeProcessor.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/analytics/CategoryDimensionStore.java similarity index 68% rename from dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/indicator/IndicatorMergeProcessor.java rename to dhis-2/dhis-api/src/main/java/org/hisp/dhis/analytics/CategoryDimensionStore.java index ee4a7260e80e..cebd91048b8d 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/indicator/IndicatorMergeProcessor.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/analytics/CategoryDimensionStore.java @@ -25,30 +25,24 @@ * (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.merge.indicator; +package org.hisp.dhis.analytics; -import lombok.RequiredArgsConstructor; -import org.hisp.dhis.merge.MergeProcessor; -import org.hisp.dhis.merge.MergeService; -import org.springframework.stereotype.Component; +import java.util.Collection; +import java.util.List; +import org.hisp.dhis.category.CategoryDimension; +import org.hisp.dhis.category.CategoryOption; +import org.hisp.dhis.common.GenericStore; /** - * Implementation of {@link MergeProcessor} that currently only uses its default method. - * * @author david mackessy */ -@Component -@RequiredArgsConstructor -public class IndicatorMergeProcessor implements MergeProcessor { +public interface CategoryDimensionStore extends GenericStore { /** - * Spring injects the correct service based on the variable name (when there are multiple - * implementations to choose from). So The {@link IndicatorMergeService} gets injected here. + * Gets all {@link CategoryDimension}s that reference any of the supplied {@link CategoryOption}s + * + * @param categoryOptions to search for + * @return matching {@link CategoryDimension}s */ - private final MergeService indicatorMergeService; - - @Override - public MergeService getMergeService() { - return indicatorMergeService; - } + List getByCategoryOption(Collection categoryOptions); } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/analytics/QueryKey.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/analytics/QueryKey.java index 2a3b605134f3..a1da5835f529 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/analytics/QueryKey.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/analytics/QueryKey.java @@ -30,8 +30,8 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; -import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; +import org.hisp.dhis.common.HashUtils; /** * @author Lars Helge Overland @@ -110,7 +110,7 @@ public String asPlainKey() { /** Returns a 40-character unique key. The key is a SHA-1 hash of the components of this key. */ public String build() { - return DigestUtils.sha1Hex(asPlainKey()); + return HashUtils.hashSHA1(asPlainKey().getBytes()); } /** Equal to {@link QueryKey#build()}. */ diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/attribute/Attribute.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/attribute/Attribute.java index ee586c751568..8e047fe5c6aa 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/attribute/Attribute.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/attribute/Attribute.java @@ -235,8 +235,8 @@ public boolean isAttribute(ObjectType type) { return objectTypes.contains(type); } - private void setAttribute(ObjectType type, Boolean isAttribute) { - if (isAttribute != null && isAttribute) { + private void setAttribute(ObjectType type, boolean isAttribute) { + if (isAttribute) { objectTypes.add(type); } else { objectTypes.remove(type); @@ -305,7 +305,7 @@ public boolean isDataElementGroupAttribute() { return isAttribute(ObjectType.DATA_ELEMENT_GROUP); } - public void setDataElementGroupAttribute(Boolean dataElementGroupAttribute) { + public void setDataElementGroupAttribute(boolean dataElementGroupAttribute) { setAttribute(ObjectType.DATA_ELEMENT_GROUP, dataElementGroupAttribute); } @@ -327,7 +327,7 @@ public boolean isIndicatorGroupAttribute() { return isAttribute(ObjectType.INDICATOR_GROUP); } - public void setIndicatorGroupAttribute(Boolean indicatorGroupAttribute) { + public void setIndicatorGroupAttribute(boolean indicatorGroupAttribute) { setAttribute(ObjectType.INDICATOR_GROUP, indicatorGroupAttribute); } @@ -338,7 +338,7 @@ public boolean isDataSetAttribute() { return isAttribute(ObjectType.DATA_SET); } - public void setDataSetAttribute(Boolean dataSetAttribute) { + public void setDataSetAttribute(boolean dataSetAttribute) { setAttribute(ObjectType.DATA_SET, dataSetAttribute); } @@ -360,7 +360,7 @@ public boolean isOrganisationUnitGroupAttribute() { return isAttribute(ObjectType.ORGANISATION_UNIT_GROUP); } - public void setOrganisationUnitGroupAttribute(Boolean organisationUnitGroupAttribute) { + public void setOrganisationUnitGroupAttribute(boolean organisationUnitGroupAttribute) { setAttribute(ObjectType.ORGANISATION_UNIT_GROUP, organisationUnitGroupAttribute); } @@ -371,7 +371,7 @@ public boolean isOrganisationUnitGroupSetAttribute() { return isAttribute(ObjectType.ORGANISATION_UNIT_GROUP_SET); } - public void setOrganisationUnitGroupSetAttribute(Boolean organisationUnitGroupSetAttribute) { + public void setOrganisationUnitGroupSetAttribute(boolean organisationUnitGroupSetAttribute) { setAttribute(ObjectType.ORGANISATION_UNIT_GROUP_SET, organisationUnitGroupSetAttribute); } @@ -393,7 +393,7 @@ public boolean isUserGroupAttribute() { return isAttribute(ObjectType.USER_GROUP); } - public void setUserGroupAttribute(Boolean userGroupAttribute) { + public void setUserGroupAttribute(boolean userGroupAttribute) { setAttribute(ObjectType.USER_GROUP, userGroupAttribute); } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/cache/CacheProvider.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/cache/CacheProvider.java index 0c4550aa89da..7e95b8237adb 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/cache/CacheProvider.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/cache/CacheProvider.java @@ -27,8 +27,8 @@ */ package org.hisp.dhis.cache; -import java.time.Duration; import org.hisp.dhis.common.event.ApplicationCacheClearedEvent; +import org.hisp.dhis.common.event.CacheInvalidationEvent; /** * The {@link CacheProvider} has a factory method for each {@link Cache} use case in DHIS2. @@ -41,7 +41,6 @@ * @author Jan Bernitt */ public interface CacheProvider { - Cache createAnalyticsResponseCache(Duration initialExpirationTime); Cache createAnalyticsCache(); @@ -55,12 +54,8 @@ public interface CacheProvider { Cache createInUserOrgUnitHierarchyCache(); - Cache createInUserViewOrgUnitHierarchyCache(); - Cache createInUserSearchOrgUnitHierarchyCache(); - Cache createUserCaptureOrgUnitThresholdCache(); - Cache createPeriodIdCache(); Cache createUserFailedLoginAttemptCache(V defaultValue); @@ -73,8 +68,6 @@ public interface CacheProvider { Cache createProgramTempOwnerCache(); - Cache createUserIdCache(); - Cache createCurrentUserGroupInfoCache(); Cache createAttrOptionComboIdCache(); @@ -89,8 +82,6 @@ public interface CacheProvider { Cache createAnalyticsSqlCache(); - Cache createDataElementCache(); - Cache createPropertyTransformerCache(); Cache createProgramHasRulesCache(); @@ -101,9 +92,7 @@ public interface CacheProvider { void handleApplicationCachesCleared(ApplicationCacheClearedEvent event); - Cache createProgramWebHookNotificationTemplateCache(); - - Cache createProgramStageWebHookNotificationTemplateCache(); + void handleCacheInvalidationEvent(CacheInvalidationEvent event); Cache createProgramOrgUnitAssociationCache(); @@ -113,8 +102,6 @@ public interface CacheProvider { Cache createApiKeyCache(); - Cache createProgramCache(); - Cache createTeAttributesCache(); Cache createProgramTeAttributesCache(); diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/cache/SoftCache.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/cache/SoftCache.java new file mode 100644 index 000000000000..30ca2d876f6e --- /dev/null +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/cache/SoftCache.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2004-2024, 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.cache; + +import java.lang.ref.SoftReference; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Supplier; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; + +/** + * A soft cache uses a concurrent map with {@link java.lang.ref.SoftReference} boxed values. + * + *

This means the values might get GC-ed if the JVM needs memory. Therefore, when accessing a + * value by key, a {@link java.util.function.Supplier} needs to be passed in case the value needs + * re-computing. + * + * @author Jan Bernitt + * @since 2.42 + */ +public final class SoftCache { + + private final ConcurrentMap> cache = new ConcurrentHashMap<>(); + + /** + * Access a cached value by key. + * + * @param key the key to get from the cache, when null the value is computed and not cached + * @param onMiss a function to compute the value on a cache miss; the function must not return + * null + * @return the cached or computed value + */ + @Nonnull + public T get(@CheckForNull String key, @Nonnull Supplier onMiss) { + if (key == null) return onMiss.get(); + Supplier memOnMiss = memorize(onMiss); + T res = + cache + .compute( + key, + (k, v) -> { + if (v == null) return new SoftReference<>(memOnMiss.get()); + if (v.get() != null) return v; + return new SoftReference<>(memOnMiss.get()); + }) + .get(); + // in theory the get() of the SoftReference can become null + // just when it is returned from the map + // in which case the res is taken directly from the supplier + // since this should mean the JVM is low on memory + return res == null ? memOnMiss.get() : res; + } + + /** + * @param onMiss assumed expensive to compute + * @return a supplier that will memorize the result once it has been computed + */ + private Supplier memorize(Supplier onMiss) { + Object[] memorize = new Object[1]; + return () -> { + @SuppressWarnings("unchecked") + T v = (T) memorize[0]; + if (v != null) return v; + v = onMiss.get(); + memorize[0] = v; + return v; + }; + } +} diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/Category.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/Category.java index 12eb663a33e5..b3297a804b5d 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/Category.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/Category.java @@ -34,6 +34,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; import com.google.common.collect.Lists; import java.util.ArrayList; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -92,6 +93,10 @@ public void removeCategoryOption(CategoryOption categoryOption) { categoryOption.getCategories().remove(this); } + public void removeCategoryOptions(Collection categoryOptions) { + categoryOptions.forEach(this::removeCategoryOption); + } + public void removeAllCategoryOptions() { for (CategoryOption categoryOption : categoryOptions) { categoryOption.getCategories().remove(this); diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryDimension.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryDimension.java index b70f0474eded..e5529076ebf7 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryDimension.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryDimension.java @@ -33,7 +33,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.List; +import javax.annotation.Nonnull; import org.hisp.dhis.common.BaseIdentifiableObject; import org.hisp.dhis.common.DimensionalEmbeddedObject; import org.hisp.dhis.common.DxfNamespaces; @@ -83,6 +85,18 @@ public void setItems(List items) { this.items = items; } + public void addItem(CategoryOption item) { + this.getItems().add(item); + } + + public void removeItem(CategoryOption item) { + this.getItems().remove(item); + } + + public void removeItems(@Nonnull Collection items) { + items.forEach(this::removeItem); + } + @Override public String toString() { final StringBuilder sb = new StringBuilder("CategoryDimension{"); diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOption.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOption.java index 465a63b2acec..6b1cca0a2b1e 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOption.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOption.java @@ -32,6 +32,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.Set; @@ -116,11 +117,19 @@ public void addCategoryOptionCombo(CategoryOptionCombo dataElementCategoryOption dataElementCategoryOptionCombo.getCategoryOptions().add(this); } + public void addCategoryOptionCombos(Collection categoryOptionCombos) { + categoryOptionCombos.forEach(this::addCategoryOptionCombo); + } + public void removeCategoryOptionCombo(CategoryOptionCombo dataElementCategoryOptionCombo) { categoryOptionCombos.remove(dataElementCategoryOptionCombo); dataElementCategoryOptionCombo.getCategoryOptions().remove(this); } + public void removeCategoryOptionCombos(Collection categoryOptionCombos) { + categoryOptionCombos.forEach(this::removeCategoryOptionCombo); + } + public void addOrganisationUnit(OrganisationUnit organisationUnit) { organisationUnits.add(organisationUnit); organisationUnit.getCategoryOptions().add(this); diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOptionCombo.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOptionCombo.java index 532afe95cc99..d728fd6b594e 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOptionCombo.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOptionCombo.java @@ -33,6 +33,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -151,6 +152,10 @@ public void removeCategoryOption(CategoryOption dataElementCategoryOption) { dataElementCategoryOption.getCategoryOptionCombos().remove(this); } + public void removeCategoryOptions(Collection categoryOptions) { + categoryOptions.forEach(this::removeCategoryOption); + } + public void removeAllCategoryOptions() { categoryOptions.clear(); } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOptionComboStore.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOptionComboStore.java index a2b494997579..3aa5be77c8c4 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOptionComboStore.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOptionComboStore.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis.category; +import java.util.Collection; import java.util.List; import java.util.Set; import org.hisp.dhis.common.IdentifiableObjectStore; @@ -56,4 +57,13 @@ CategoryOptionCombo getCategoryOptionCombo( * @return a List of {@link CategoryOptionCombo} or empty List */ List getCategoryOptionCombosByGroupUid(String groupId, String dataElementId); + + /** + * Retrieves all CategoryOptionCombos with a ref to any of the CategoryOptions passed in. + * + * @param categoryOptions refs to search for + * @return categoryOptionCombos with refs to categoryOptions + */ + List getCategoryOptionCombosByCategoryOption( + Collection categoryOptions); } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOptionGroup.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOptionGroup.java index f15116a66b59..84c68c08e4d5 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOptionGroup.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOptionGroup.java @@ -32,6 +32,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import java.util.Collection; import java.util.HashSet; import java.util.Set; import org.hisp.dhis.common.BaseDimensionalItemObject; @@ -128,4 +129,8 @@ public void removeCategoryOption(CategoryOption categoryOption) { members.remove(categoryOption); categoryOption.getGroups().remove(this); } + + public void removeCategoryOptions(Collection categoryOptions) { + categoryOptions.forEach(this::removeCategoryOption); + } } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOptionGroupStore.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOptionGroupStore.java index 2979e635c789..78f14b3750fc 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOptionGroupStore.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOptionGroupStore.java @@ -27,9 +27,12 @@ */ package org.hisp.dhis.category; +import java.util.Collection; import java.util.List; import org.hisp.dhis.common.IdentifiableObjectStore; public interface CategoryOptionGroupStore extends IdentifiableObjectStore { List getCategoryOptionGroups(CategoryOptionGroupSet groupSet); + + List getByCategoryOption(Collection categoryOptions); } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryService.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryService.java index 92999fe0417a..f16c298f3fee 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryService.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryService.java @@ -32,6 +32,7 @@ import java.util.Set; import org.apache.commons.collections4.SetValuedMap; import org.hisp.dhis.common.IdScheme; +import org.hisp.dhis.common.UID; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.dataelement.DataElementOperand; import org.hisp.dhis.dataset.DataSet; @@ -145,6 +146,14 @@ public interface CategoryService { */ List getAttributeDataDimensionCategoriesNoAcl(); + /** + * Retrieves all Categories with a ref to any of the CategoryOptions passed in. + * + * @param categoryOptions refs to search for + * @return categories with refs to categoryOptions + */ + List getCategoriesByCategoryOption(Collection categoryOptions); + // ------------------------------------------------------------------------- // CategoryOption // ------------------------------------------------------------------------- @@ -461,6 +470,15 @@ CategoryOptionCombo getCategoryOptionCombo( /** Updates the name property of all category option combinations. */ void updateCategoryOptionComboNames(); + /** + * Retrieves all CategoryOptionCombos with a ref to any of the CategoryOptions passed in. + * + * @param categoryOptions refs to search for + * @return categoryOptionCombos with refs to categoryOptions + */ + List getCategoryOptionCombosByCategoryOption( + Collection categoryOptions); + // ------------------------------------------------------------------------- // DataElementOperand // ------------------------------------------------------------------------- @@ -509,6 +527,8 @@ CategoryOptionCombo getCategoryOptionCombo( List getCategoryOptionGroups(CategoryOptionGroupSet groupSet); + List getCategoryOptionGroupByCategoryOption(Collection categoryOptions); + /** * Returns a set of CategoryOptionGroups that may be seen by the current user, if the current user * has any CategoryOptionGroupSet constraint(s). @@ -541,4 +561,6 @@ CategoryOptionCombo getCategoryOptionCombo( CategoryOption getDefaultCategoryOption(); Category getDefaultCategory(); + + List getCategoryOptionsByUid(List catOptionUids); } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryStore.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryStore.java index 8c83c541a69c..71d6587f2a83 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryStore.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryStore.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis.category; +import java.util.Collection; import java.util.List; import org.hisp.dhis.common.DataDimensionType; import org.hisp.dhis.common.GenericDimensionalObjectStore; @@ -40,4 +41,6 @@ public interface CategoryStore extends GenericDimensionalObjectStore { List getCategories(DataDimensionType dataDimensionType, boolean dataDimension); List getCategoriesNoAcl(DataDimensionType dataDimensionType, boolean dataDimension); + + List getCategoriesByCategoryOption(Collection categoryOptions); } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/HashUtils.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/HashUtils.java index d77a6cd72cef..d21c6b83967f 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/HashUtils.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/HashUtils.java @@ -43,6 +43,26 @@ private HashUtils() { throw new IllegalStateException("Utility class"); } + /** + * Calculates a MD5 hash for the given input string. + * + * @param bytes the input string. + * @return the hash. + */ + public static String hashMD5(@Nonnull byte[] bytes) { + return Hashing.md5().hashBytes(bytes).toString(); + } + + /** + * Calculates a SHA1 hash for the given input string. + * + * @param bytes the input string. + * @return the hash. + */ + public static String hashSHA1(@Nonnull byte[] bytes) { + return Hashing.sha1().hashBytes(bytes).toString(); + } + /** * Calculates a SHA256 hash for the given input string. * diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/IdentifiableObjectManager.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/IdentifiableObjectManager.java index 02a0685f8657..12a493210513 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/IdentifiableObjectManager.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/IdentifiableObjectManager.java @@ -101,6 +101,16 @@ public interface IdentifiableObjectManager { @CheckForNull T get(@Nonnull Class type, @Nonnull String uid); + /** + * Retrieves the object of the given type and UID, or null if no object exists. + * + * @param type the object class type. + * @param uid the UID. + * @return the object with the given UID. + */ + @CheckForNull + T get(@Nonnull Class type, @Nonnull UID uid); + /** * Retrieves the object of the given type and UID, throws exception if no object exists. * @@ -172,9 +182,6 @@ List getByCode( @CheckForNull T search(@Nonnull Class type, @Nonnull String query); - @Nonnull - List filter(@Nonnull Class type, @Nonnull String query); - @Nonnull List getAll(@Nonnull Class type); diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/OpenApi.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/OpenApi.java index 54c3e793959a..538384551a39 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/OpenApi.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/OpenApi.java @@ -217,6 +217,8 @@ enum Access { * @return the class that represents the domain for the annotated controller {@link Class} or * endpoint {@link java.lang.reflect.Method}. */ + // TODO make this metadataEntity() ? => always point out the most related metadata object to + // categorise a controller by? Class entity() default EntityType.class; /** @@ -467,6 +469,7 @@ enum Status { */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) + @Inherited @interface Shared { /** diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/UID.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/UID.java index 35d779963a46..ee5b85334e69 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/UID.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/UID.java @@ -105,4 +105,9 @@ public static Set toValueSet(Collection uids) { public static List toValueList(Collection uids) { return uids.stream().map(UID::getValue).toList(); } + + public static Set toUidValueSet( + @Nonnull Collection elements) { + return elements.stream().map(BaseIdentifiableObject::getUid).collect(toUnmodifiableSet()); + } } 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-api/src/main/java/org/hisp/dhis/common/cache/Region.java similarity index 58% rename from dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventStore.java rename to dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/cache/Region.java index 1bccaaaeea3c..05eb0773b410 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventStore.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/cache/Region.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2022, University of Oslo + * Copyright (c) 2004-2024, University of Oslo * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -25,28 +25,46 @@ * (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; +package org.hisp.dhis.common.cache; /** - * @author Morten Olav Hansen + * Enum is used to make sure we do not use same region twice. Each method should have its own + * constant. */ -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(); +@SuppressWarnings("squid:S115") // allow non enum-ish names +public enum Region { + analyticsResponse, + defaultObjectCache, + isDataApproved, + allConstantsCache, + inUserOrgUnitHierarchy, + inUserSearchOrgUnitHierarchy, + periodIdCache, + userAccountRecoverAttempt, + userFailedLoginAttempt, + twoFaDisableFailedAttempt, + programOwner, + programTempOwner, + currentUserGroupInfoCache, + attrOptionComboIdCache, + googleAccessToken, + dataItemsPagination, + metadataAttributes, + canDataWriteCocCache, + analyticsSql, + propertyTransformerCache, + programHasRulesCache, + userGroupNameCache, + userDisplayNameCache, + pgmOrgUnitAssocCache, + catOptOrgUnitAssocCache, + dataSetOrgUnitAssocCache, + apiTokensCache, + teAttributesCache, + programTeAttributesCache, + userGroupUIDCache, + securityCache, + dataIntegritySummaryCache, + dataIntegrityDetailsCache, + queryAliasCache } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/collection/CollectionUtils.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/collection/CollectionUtils.java index eba00bea3f3c..4dce993d1163 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/collection/CollectionUtils.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/collection/CollectionUtils.java @@ -214,6 +214,7 @@ private static Set union(@Nonnull Set... more) { * @return a string, or null if no matches. */ public static String popStartsWith(Collection collection, String prefix) { + if (collection == null || collection.isEmpty()) return null; Iterator iterator = collection.iterator(); while (iterator.hasNext()) { diff --git a/dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/filter/UserRoleCanIssueFilter.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/event/CacheInvalidationEvent.java similarity index 70% rename from dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/filter/UserRoleCanIssueFilter.java rename to dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/event/CacheInvalidationEvent.java index d9cab6377453..b7ddf01259c9 100644 --- a/dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/filter/UserRoleCanIssueFilter.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/event/CacheInvalidationEvent.java @@ -25,31 +25,31 @@ * (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.system.filter; +package org.hisp.dhis.common.event; -import org.hisp.dhis.commons.filter.Filter; -import org.hisp.dhis.user.User; -import org.hisp.dhis.user.UserRole; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import lombok.Getter; +import org.hisp.dhis.common.cache.Region; +import org.springframework.context.ApplicationEvent; /** - * @author Lars Helge Overland + * @author Ameen Mohamed */ -public class UserRoleCanIssueFilter implements Filter { - private User user; +@Getter +public class CacheInvalidationEvent extends ApplicationEvent { + private final Region region; + private final String key; - private boolean canGrantOwnUserRoles = false; - - protected UserRoleCanIssueFilter() {} - - public UserRoleCanIssueFilter(User user, boolean canGrantOwnUserRoles) { - if (user != null) { - this.user = user; - this.canGrantOwnUserRoles = canGrantOwnUserRoles; - } + public CacheInvalidationEvent(Object source, @Nonnull Region region, @Nullable String key) { + super(source); + this.region = region; + this.key = key; } - @Override - public boolean retain(UserRole group) { - return user != null && user.canIssueUserRole(group, canGrantOwnUserRoles); + public CacheInvalidationEvent(Object source, @Nonnull Region region) { + super(source); + this.region = region; + this.key = null; } } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dashboard/Dashboard.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dashboard/Dashboard.java index b7dbab8bff09..96d0d2e8b7c5 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dashboard/Dashboard.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dashboard/Dashboard.java @@ -43,8 +43,12 @@ import org.hisp.dhis.common.MetadataObject; import org.hisp.dhis.dashboard.design.ItemConfig; import org.hisp.dhis.dashboard.design.Layout; +import org.hisp.dhis.dashboard.embedded.EmbeddedDashboard; /** + * Encapsulates information about an embedded dashboard. An embedded dashboard is typically loaded + * from an external provider. + * * @author Lars Helge Overland */ @NoArgsConstructor @@ -67,6 +71,9 @@ public class Dashboard extends BaseNameableObject implements MetadataObject { /** Allowed filter dimensions (if any) which may be used for the dashboard. */ private List allowedFilters = new ArrayList<>(); + /** Optional, only set if this dashboard is embedded and loaded from an external provider. */ + private EmbeddedDashboard embedded; + // ------------------------------------------------------------------------- // Constructors // ------------------------------------------------------------------------- @@ -146,4 +153,14 @@ public List getAllowedFilters() { public void setAllowedFilters(List allowedFilters) { this.allowedFilters = allowedFilters; } + + @JsonProperty + @JacksonXmlProperty(namespace = DXF_2_0) + public EmbeddedDashboard getEmbedded() { + return embedded; + } + + public void setEmbedded(EmbeddedDashboard embedded) { + this.embedded = embedded; + } } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dashboard/DashboardItemType.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dashboard/DashboardItemType.java index ea804396b2a7..4f1122f41b8d 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dashboard/DashboardItemType.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dashboard/DashboardItemType.java @@ -28,6 +28,8 @@ package org.hisp.dhis.dashboard; /** + * Encapsulates type of dashboard item. + * * @author Lars Helge Overland */ public enum DashboardItemType { diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/DataElementMergeProcessor.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dashboard/embedded/EmbeddedDashboard.java similarity index 66% rename from dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/DataElementMergeProcessor.java rename to dhis-2/dhis-api/src/main/java/org/hisp/dhis/dashboard/embedded/EmbeddedDashboard.java index 57ca57b21ccf..c6f08a84c83a 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/DataElementMergeProcessor.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dashboard/embedded/EmbeddedDashboard.java @@ -25,30 +25,31 @@ * (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.merge.dataelement; +package org.hisp.dhis.dashboard.embedded; -import lombok.RequiredArgsConstructor; -import org.hisp.dhis.merge.MergeProcessor; -import org.hisp.dhis.merge.MergeService; -import org.springframework.stereotype.Component; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; /** - * Implementation of {@link MergeProcessor} that currently only uses its default method. + * Encapsulates metadata for an embedded and externally provided dashboard. * - * @author david mackessy + * @author Lars Helge Overland */ -@Component -@RequiredArgsConstructor -public class DataElementMergeProcessor implements MergeProcessor { +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class EmbeddedDashboard implements Serializable { + /** Provider of embedded dashboards. */ + @JsonProperty private EmbeddedProvider provider; - /** - * Spring injects the correct service based on the variable name (when there are multiple - * implementations to choose from). So The {@link DataElementMergeService} gets injected here. - */ - private final MergeService dataElementMergeService; + /** Identifier for embedded dashboard. */ + @JsonProperty private String id; - @Override - public MergeService getMergeService() { - return dataElementMergeService; - } + /** Customization options for embedded dashboard. */ + @JsonProperty private EmbeddedOptions options; } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dashboard/embedded/EmbeddedOptions.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dashboard/embedded/EmbeddedOptions.java new file mode 100644 index 000000000000..f856b5ba457f --- /dev/null +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dashboard/embedded/EmbeddedOptions.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2004-2024, 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.dashboard.embedded; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Encapsulates customization options for embedded dashboards. + * + * @author Lars Helge Overland + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class EmbeddedOptions implements Serializable { + /** Hide tab. Applies to Superset. */ + @JsonProperty private boolean hideTab; + + /** Hide the chart controls. Applies to Superset. */ + @JsonProperty private boolean hideChartControls; + + /** Filter options. Applies to Superset. */ + @JsonProperty private FilterOptions filters; +} diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dashboard/embedded/EmbeddedProvider.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dashboard/embedded/EmbeddedProvider.java new file mode 100644 index 000000000000..3b7ee0aca75c --- /dev/null +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dashboard/embedded/EmbeddedProvider.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2004-2024, 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.dashboard.embedded; + +/** + * Enumeration of providers for embedded dashboards. + * + * @author Lars Helge Overland + */ +public enum EmbeddedProvider { + SUPERSET; +} diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dashboard/embedded/FilterOptions.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dashboard/embedded/FilterOptions.java new file mode 100644 index 000000000000..90d5edb6da18 --- /dev/null +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dashboard/embedded/FilterOptions.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2004-2024, 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.dashboard.embedded; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Encapsulates embedded dashboard filter options. + * + * @author Lars Helge Overland + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class FilterOptions implements Serializable { + /** Whether filter sidebar should be accessible when opening the dashboard. */ + @JsonProperty private boolean visible; + + /** Whether filter sidebar should be expanded when opening the dashboard. */ + @JsonProperty private boolean expanded; +} diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataset/SectionService.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataset/SectionService.java index d63b12c2393c..4f32edf657ec 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataset/SectionService.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataset/SectionService.java @@ -27,73 +27,63 @@ */ package org.hisp.dhis.dataset; +import java.util.Collection; import java.util.List; +import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.indicator.Indicator; public interface SectionService { - String ID = SectionService.class.getName(); - /** - * Adds a Section. + * Adds a {@link Section}. * - * @param section the Section to add. + * @param section the {@link Section} to add. * @return the generated identifier. */ long addSection(Section section); /** - * Updates a Section. + * Updates a {@link Section}. * - * @param section the Section to update. + * @param section the {@link Section} to update. */ void updateSection(Section section); /** - * Deletes a Section. + * Deletes a {@link Section}. * - * @param section the Section to delete. + * @param section the {@link Section} to delete. */ void deleteSection(Section section); /** - * Retrieves the Section with the given identifier. + * Retrieves the {@link Section} with the given UID. * - * @param id the identifier of the Section to retrieve. - * @return the Section. - */ - Section getSection(long id); - - /** - * Retrieves the Section with the given identifier (uid). - * - * @param uid the identifier of the Section to retrieve. - * @return the Section. + * @param uid the identifier of the {@link Section} to retrieve. + * @return the {@link Section}. */ Section getSection(String uid); /** - * Retrieves the Section with the given name. + * Retrieves sections associated with the data element with the given UID. * - * @param name the name of the Section to retrieve. - * @return the Section. + * @param uid the data element UID. + * @return a list of {@link Section}. */ - Section getSectionByName(String name, Integer dataSetId); + List

getSectionsByDataElement(String uid); /** - * Retrieves all Sections. + * Retrieves sections associated with the given data elements. * - * @return a Collection of Sections. + * @param dataElements the list of {@link DataElement}. + * @return a list of {@link Section}. */ - List
getAllSections(); + List
getSectionsByDataElement(Collection dataElements); /** - * Retrieves all {@link Section}s referencing the provided {@link Indicator}s. + * Retrieves sections associated with the given indicators. * - * @return a Collection of {@link Section}s. + * @param indicators the list of {@link Indicator}. + * @return a list of {@link Section}. */ - List
getSectionsByIndicators(List indicators); - - void removeIndicator(Section s, Indicator i); - - void addIndicator(Section s, Indicator i); + List
getSectionsByIndicators(Collection indicators); } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataset/SectionStore.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataset/SectionStore.java index 7e2d5990e506..c95419ddb794 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataset/SectionStore.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataset/SectionStore.java @@ -34,8 +34,6 @@ import org.hisp.dhis.indicator.Indicator; public interface SectionStore extends IdentifiableObjectStore
{ - String ID = SectionStore.class.getName(); - /** * Retrieves the Section with the given name and the given DataSet. * @@ -44,14 +42,27 @@ public interface SectionStore extends IdentifiableObjectStore
{ */ Section getSectionByName(String name, DataSet dataSet); - List
getSectionsByDataElement(String dataElementUid); + /** + * Retrieves sections associated with the data element with the given UID. + * + * @param uid the data element UID. + * @return a list of {@link Section}. + */ + List
getSectionsByDataElement(String uid); /** - * Retrieves all {@link Section}s referencing the provided {@link Indicator}s. + * Retrieves sections associated with the given indicators. * - * @return a Collection of {@link Section}s. + * @param indicators the list of {@link Indicator}. + * @return a list of {@link Section}. */ - List
getSectionsByIndicators(List indicators); + List
getSectionsByIndicators(Collection indicators); - List
getByDataElement(Collection dataElements); + /** + * Retrieves sections associated with the given data elements. + * + * @param dataElements the list of {@link DataElement}. + * @return a list of {@link Section}. + */ + List
getSectionsByDataElement(Collection dataElements); } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueService.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueService.java index f4aff88d4a1a..7e06490ebdaf 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueService.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueService.java @@ -32,6 +32,7 @@ import org.hisp.dhis.category.CategoryCombo; import org.hisp.dhis.category.CategoryOptionCombo; import org.hisp.dhis.common.IllegalQueryException; +import org.hisp.dhis.common.UID; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.period.Period; @@ -212,4 +213,12 @@ DataValue getDataValue( * @return true, if any value exist, otherwise false */ boolean dataValueExists(CategoryCombo combo); + + /** + * Checks if any data values exist for the provided {@link DataElement} {@link UID}. + * + * @param uid the {@link DataElement} {@link UID} to check + * @return true, if any values exist, otherwise false + */ + boolean dataValueExistsForDataElement(UID uid); } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueStore.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueStore.java index 7991b7420853..61bd3550f9a2 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueStore.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueStore.java @@ -32,6 +32,7 @@ import java.util.concurrent.TimeUnit; import org.hisp.dhis.category.CategoryCombo; import org.hisp.dhis.category.CategoryOptionCombo; +import org.hisp.dhis.common.UID; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.period.Period; @@ -184,4 +185,12 @@ DataValue getDataValue( * @return true, if any value exist, otherwise false */ boolean dataValueExists(CategoryCombo combo); + + /** + * Checks if any data values exist for the provided {@link DataElement} {@link UID}. + * + * @param uid the {@link DataElement} {@link UID} to check + * @return true, if any values exist, otherwise false + */ + boolean dataValueExistsForDataElement(String uid); } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dxf2/webmessage/responses/MergeWebResponse.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dxf2/webmessage/responses/MergeWebResponse.java index 4e00bfb25e8f..17188062cb77 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dxf2/webmessage/responses/MergeWebResponse.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dxf2/webmessage/responses/MergeWebResponse.java @@ -46,8 +46,8 @@ public MergeWebResponse(@Nonnull MergeReport mergeReport) { MergeType mergeType = mergeReport.getMergeType(); this.mergeReport.setMessage( mergeReport.hasErrorMessages() - ? "%s merge has errors".formatted(mergeType) - : "%s merge complete".formatted(mergeType)); + ? "%s merge has errors".formatted(mergeType.getName()) + : "%s merge complete".formatted(mergeType.getName())); } @Nonnull 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 7a68761c5c47..dea0cc65ed49 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 @@ -65,6 +65,7 @@ public enum ErrorCode { "Option set `{0}` of value type multi-text cannot have option codes with the separator character: `{1}`"), E1119("{0} already exists: `{1}`"), E1120("Update cannot be applied as it would make existing data values inaccessible"), + E1121("Data element `{0}` value type cannot be changed as it has associated data values"), /* Org unit merge */ E1500("At least two source orgs unit must be specified"), @@ -88,27 +89,17 @@ public enum ErrorCode { E1522("User `{0}` is not allowed to move organisation `{1}` unit from parent `{2}`"), E1523("User `{0}` is not allowed to move organisation `{1}` unit to parent `{2}`"), - /* Indicator Type merge */ - E1530("At least one source indicator type must be specified"), - E1531("Target indicator type must be specified"), - E1532("Target indicator type cannot be a source indicator type"), - E1533("{0} indicator type does not exist: `{1}`"), - - /* Indicator merge */ - E1540("At least one source indicator must be specified"), - E1541("Target indicator must be specified"), - E1542("Target indicator cannot be a source indicator"), - E1543("{0} indicator does not exist: `{1}`"), + /* Generic merge errors */ + E1530("At least one source {0} must be specified"), + E1531("Target {0} must be specified"), + E1532("Target {0} cannot be a source {1}"), + E1533("{0} {1} does not exist: `{2}`"), + E1534("dataMergeStrategy field must be specified. With value `DISCARD` or `LAST_UPDATED`"), /* DataElement merge */ - E1550("At least one source data element must be specified"), - E1551("Target data element must be specified"), - E1552("Target data element cannot be a source data element"), - E1553("{0} data element does not exist: `{1}`"), - E1554("All source ValueTypes must match target ValueType: `{0}`. Other ValueTypes found: `{1}`"), - E1555( + E1550("All source ValueTypes must match target ValueType: `{0}`. Other ValueTypes found: `{1}`"), + E1551( "All source DataElementDomains must match target DataElementDomain: `{0}`. Other DataElementDomains found: `{1}`"), - E1556("dataMergeStrategy field must be specified. With value `DISCARD` or `LAST_UPDATED`"), /* Data */ E2000("Query parameters cannot be null"), @@ -460,6 +451,7 @@ public enum ErrorCode { "Query failed because a referenced table does not exist. Please ensure analytics job was run"), E7145("Query failed because of a syntax error"), E7146("A {0} date was not specified in periods, dimensions, filters"), + E7147("Query failed because of a missing column: `{0}`"), /* Analytics outliers */ diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/feedback/MergeReport.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/feedback/MergeReport.java index 9d7c6dab1244..3a771fe1f91d 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/feedback/MergeReport.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/feedback/MergeReport.java @@ -54,10 +54,6 @@ public class MergeReport implements ErrorMessageContainer { @JsonProperty private Set sourcesDeleted = new HashSet<>(); @JsonProperty private String message; - public MergeReport(MergeType mergeType) { - this.mergeType = mergeType; - } - @Override public boolean hasErrorMessages() { return !mergeErrors.isEmpty(); diff --git a/dhis-2/dhis-services/dhis-service-node/src/main/java/org/hisp/dhis/fieldfilter/Defaults.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/fieldfilter/Defaults.java similarity index 100% rename from dhis-2/dhis-services/dhis-service-node/src/main/java/org/hisp/dhis/fieldfilter/Defaults.java rename to dhis-2/dhis-api/src/main/java/org/hisp/dhis/fieldfilter/Defaults.java diff --git a/dhis-2/dhis-services/dhis-service-field-filtering/src/main/java/org/hisp/dhis/fieldfiltering/FieldPreset.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/fieldfiltering/FieldPreset.java similarity index 98% rename from dhis-2/dhis-services/dhis-service-field-filtering/src/main/java/org/hisp/dhis/fieldfiltering/FieldPreset.java rename to dhis-2/dhis-api/src/main/java/org/hisp/dhis/fieldfiltering/FieldPreset.java index 8e2dcb7b9099..f257c28c8a0e 100644 --- a/dhis-2/dhis-services/dhis-service-field-filtering/src/main/java/org/hisp/dhis/fieldfiltering/FieldPreset.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/fieldfiltering/FieldPreset.java @@ -52,7 +52,7 @@ public enum FieldPreset { FieldPreset(String name, List fields) { this.name = name; - this.fields = fields; + this.fields = List.copyOf(fields); } public String getName() { diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/interpretation/MentionUtils.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/interpretation/MentionUtils.java index 42110e111df4..c17f9d41be8e 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/interpretation/MentionUtils.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/interpretation/MentionUtils.java @@ -31,7 +31,6 @@ import java.util.Date; import java.util.HashSet; import java.util.List; -import java.util.ListIterator; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -62,17 +61,4 @@ public static Set getMentionedUsers(String text, UserService userService) } return users; } - - public static List removeCustomFilters(List filters) { - List mentions = new ArrayList(); - ListIterator filterIterator = filters.listIterator(); - while (filterIterator.hasNext()) { - String[] filterSplit = filterIterator.next().split(":"); - if (filterSplit[1].equals("in") && filterSplit[0].equals("mentions")) { - mentions.add(filterSplit[2]); - filterIterator.remove(); - } - } - return mentions; - } } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/merge/MergeParams.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/merge/MergeParams.java index d0f9ef9c7734..d3b81b230d3c 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/merge/MergeParams.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/merge/MergeParams.java @@ -50,6 +50,4 @@ public class MergeParams { @JsonProperty private boolean deleteSources; @JsonProperty private DataMergeStrategy dataMergeStrategy; - - private MergeType mergeType; } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/merge/MergeProcessor.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/merge/MergeProcessor.java deleted file mode 100644 index c26c3bcfb7ff..000000000000 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/merge/MergeProcessor.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2004-2023, 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.merge; - -import javax.annotation.Nonnull; -import org.hisp.dhis.common.IdentifiableObject; -import org.hisp.dhis.feedback.ConflictException; -import org.hisp.dhis.feedback.MergeReport; - -/** - * Interface with default method used to process merging of {@link IdentifiableObject}s uniformly, - * in a standardised fashion. It requires an implementation of {@link MergeService}, which will - * process the merging for its required use case.
- * It essentially calls each method of the {@link MergeService} in the following order:
- * - *
    - *
  1. validate - *
  2. merge - *
- */ -public interface MergeProcessor { - - /** - * @return implemented {@link MergeService} to process merge - */ - MergeService getMergeService(); - - /** - * Processes a merge in full, using the implemented {@link MergeService} retrieved. - * - * @param mergeParams {@link MergeParams} to process - * @return updated {@link MergeReport} with any errors - */ - default MergeReport processMerge(@Nonnull MergeParams mergeParams) throws ConflictException { - MergeReport mergeReport = new MergeReport(mergeParams.getMergeType()); - - MergeRequest mergeRequest = getMergeService().validate(mergeParams, mergeReport); - if (mergeReport.hasErrorMessages()) - throw new ConflictException("Merge validation error").setMergeReport(mergeReport); - - MergeReport report = getMergeService().merge(mergeRequest, mergeReport); - if (report.hasErrorMessages()) - throw new ConflictException("Merge error").setMergeReport(mergeReport); - - return report; - } -} diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/merge/MergeService.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/merge/MergeService.java index 913503f4dea1..f99bb188783e 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/merge/MergeService.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/merge/MergeService.java @@ -29,6 +29,7 @@ import javax.annotation.Nonnull; import org.hisp.dhis.common.IdentifiableObject; +import org.hisp.dhis.feedback.ConflictException; import org.hisp.dhis.feedback.MergeReport; import org.springframework.transaction.annotation.Transactional; @@ -39,6 +40,27 @@ */ public interface MergeService { + /** + * Processes a merge in full. + * + * @param mergeParams {@link MergeParams} to process + * @return updated {@link MergeReport} with any errors + */ + @Transactional + default MergeReport processMerge(@Nonnull MergeParams mergeParams) throws ConflictException { + MergeReport mergeReport = new MergeReport(); + + MergeRequest mergeRequest = validate(mergeParams, mergeReport); + if (mergeReport.hasErrorMessages()) + throw new ConflictException("Merge validation error").setMergeReport(mergeReport); + + merge(mergeRequest, mergeReport); + if (mergeReport.hasErrorMessages()) + throw new ConflictException("Merge error").setMergeReport(mergeReport); + + return mergeReport; + } + /** * This method transforms a {@link MergeParams} to a {@link MergeRequest}. If there are any * errors/issues with the params then the {@link MergeReport} should be updated. @@ -57,6 +79,5 @@ public interface MergeService { * @param mergeReport report to be updated if any issues/errors with the {@link MergeRequest} * @return {@link MergeReport} */ - @Transactional MergeReport merge(@Nonnull MergeRequest request, @Nonnull MergeReport mergeReport); } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/merge/MergeType.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/merge/MergeType.java index 56f73e09c965..a19697ace91b 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/merge/MergeType.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/merge/MergeType.java @@ -27,15 +27,38 @@ */ package org.hisp.dhis.merge; +import com.fasterxml.jackson.annotation.JsonValue; +import org.hisp.dhis.category.CategoryOption; +import org.hisp.dhis.common.IdentifiableObject; +import org.hisp.dhis.dataelement.DataElement; +import org.hisp.dhis.indicator.Indicator; +import org.hisp.dhis.indicator.IndicatorType; + /** * Enum for merge type. * * @author david mackessy */ public enum MergeType { - ORG_UNIT, + INDICATOR_TYPE(IndicatorType.class), + INDICATOR(Indicator.class), + DATA_ELEMENT(DataElement.class), + CATEGORY_OPTION(CategoryOption.class); + + private final Class clazz; + private final String name; + + MergeType(Class clazz) { + this.clazz = clazz; + this.name = clazz.getSimpleName(); + } + + public Class getClazz() { + return this.clazz; + } - INDICATOR_TYPE, - INDICATOR, - DATA_ELEMENT + @JsonValue + public String getName() { + return this.name; + } } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/merge/MergeValidator.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/merge/MergeValidator.java index 471ecd7e6143..cc1e3fb5534c 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/merge/MergeValidator.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/merge/MergeValidator.java @@ -28,6 +28,7 @@ package org.hisp.dhis.merge; import java.util.Set; +import javax.annotation.Nonnull; import org.hisp.dhis.common.IdentifiableObject; import org.hisp.dhis.common.UID; import org.hisp.dhis.feedback.MergeReport; @@ -37,18 +38,28 @@ */ public interface MergeValidator { + /** + * Validates source & target {@link UID}s passed in the params. Validation used by all merges. + * + * @param params {@link MergeParams} that contain {@link UID}s to validate + * @param mergeReport {@link MergeReport} to update + * @param mergeType {@link MergeType} + * @return {@link MergeRequest} to process + */ + MergeRequest validateUIDs( + @Nonnull MergeParams params, @Nonnull MergeReport mergeReport, @Nonnull MergeType mergeType); + /** * Verifies whether the source {@link UID}s map to valid {@link IdentifiableObject}s.
* - If they are valid then they are added to verifiedSources param.
* - If any are not valid then the {@link MergeReport} is updated with an error. * * @param paramSources {@link UID}s - * @param verifiedSources set to add verified source {@link UID}s * @param mergeReport to update if any error - * @param clazz {@link IdentifiableObject} type + * @param mergeType {@link MergeType} + * @return verified source {@link UID}s */ - void verifySources( - Set paramSources, Set verifiedSources, MergeReport mergeReport, Class clazz); + Set verifySources(Set paramSources, MergeReport mergeReport, MergeType mergeType); /** * Checks whether the target is referenced in the sources collection
@@ -57,10 +68,10 @@ void verifySources( * @param sources to check * @param target to check if in sources * @param mergeReport to update if any error - * @param clazz {@link IdentifiableObject} type + * @param mergeType {@link MergeType} */ - void checkIsTargetInSources( - Set sources, UID target, MergeReport mergeReport, Class clazz); + void checkIsTargetInSources( + Set sources, UID target, MergeReport mergeReport, MergeType mergeType); /** * Verifies whether the target {@link UID} maps to a valid {@link IdentifiableObject}.
@@ -71,9 +82,9 @@ void checkIsTargetInSources( * @param mergeReport to update if any error * @param sources to return in merge request * @param params merge params with target to verify - * @param clazz {@link IdentifiableObject} type + * @param mergeType {@link MergeType} * @return merge request */ - MergeRequest verifyTarget( - MergeReport mergeReport, Set sources, MergeParams params, Class clazz); + MergeRequest verifyTarget( + MergeReport mergeReport, Set sources, MergeParams params, MergeType mergeType); } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/message/MessageConversation.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/message/MessageConversation.java index 316bbc99e996..c528e5332e9a 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/message/MessageConversation.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/message/MessageConversation.java @@ -41,6 +41,7 @@ import org.apache.commons.lang3.StringUtils; import org.hisp.dhis.common.BaseIdentifiableObject; import org.hisp.dhis.common.DxfNamespaces; +import org.hisp.dhis.common.UID; import org.hisp.dhis.schema.annotation.PropertyRange; import org.hisp.dhis.user.User; @@ -192,12 +193,12 @@ public boolean isRead(User user) { return false; } - public boolean markRead(User user) { - for (UserMessage userMessage : userMessages) { - if (userMessage.getUser() != null && userMessage.getUser().equals(user)) { - boolean read = userMessage.isRead(); + public boolean markRead(UID user) { + for (UserMessage msg : userMessages) { + if (msg.getUser() != null && msg.getUser().getUid().equals(user.getValue())) { + boolean read = msg.isRead(); - userMessage.setRead(true); + msg.setRead(true); return !read; } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnit.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnit.java index 144efee252e2..b64446845041 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnit.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnit.java @@ -292,6 +292,10 @@ public void removeCategoryOption(CategoryOption categoryOption) { categoryOption.getOrganisationUnits().remove(this); } + public void removeCategoryOptions(Collection categoryOptions) { + categoryOptions.forEach(this::removeCategoryOption); + } + public void removeAllUsers() { for (User user : users) { user.getOrganisationUnits().remove(this); diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnitService.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnitService.java index 33fd9263ddb0..f76dcab600e9 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnitService.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnitService.java @@ -33,6 +33,7 @@ import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; +import org.hisp.dhis.common.UID; import org.hisp.dhis.dataset.DataSet; import org.hisp.dhis.hierarchy.HierarchyViolationException; import org.hisp.dhis.program.Program; @@ -132,14 +133,6 @@ public interface OrganisationUnitService extends OrganisationUnitDataIntegrityPr */ List getOrganisationUnitsByUid(@Nonnull Collection uids); - /** - * Returns a list of OrganisationUnits based on the given params. - * - * @param params the params. - * @return a list of OrganisationUnits. - */ - List getOrganisationUnitsByQuery(OrganisationUnitQueryParams params); - /** * Returns an OrganisationUnit with a given name. * @@ -486,4 +479,12 @@ List getOrganisationUnitByCoordinate( * @return */ List getSearchOrganisationUnitsUidsByUser(String username); + + /** + * Returns all OrganisationUnits with refs to any of the CategoryOptions passed in. + * + * @param categoryOptions refs to search for. + * @return OrganisationUnits with refs to any of the CategoryOptions passed in + */ + List getByCategoryOption(Collection categoryOptions); } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnitStore.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnitStore.java index 8fcce43e67b1..f35af4770f09 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnitStore.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnitStore.java @@ -186,4 +186,12 @@ Map> getOrganisationUnitDataSetAssocationMap( List getOrganisationUnitUids(OrganisationUnitQueryParams params); int updateAllOrganisationUnitsGeometryToNull(); + + /** + * Get OrganisationUnits with refs to any of the CategoryOptions passed in + * + * @param categoryOptions categoryOptions refs + * @return OrganisationUnits with refs to any of the CategoryOptions passed in + */ + List getByCategoryOption(Collection categoryOptions); } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/period/PeriodService.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/period/PeriodService.java index 4836010871ae..1bae5b803803 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/period/PeriodService.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/period/PeriodService.java @@ -258,7 +258,7 @@ public interface PeriodService { PeriodType getPeriodType(int id); /** - * Returns a PeriodType with a given name. + * Returns a PeriodType with the given name. * * @param name the name of the PeriodType to return. * @return the PeriodType with the given name, or null if no match. @@ -282,6 +282,16 @@ public interface PeriodService { */ PeriodType reloadPeriodType(PeriodType periodType); + /** + * Returns a PeriodType with the given {@link PeriodTypeEnum} value. + * + * @param periodType the {@link PeriodTypeEnum}. + * @return the PeriodType with the given name, or null if no match. + */ + default PeriodType getPeriodType(PeriodTypeEnum periodType) { + return getPeriodTypeByName(periodType.getName()); + } + // ------------------------------------------------------------------------- // RelativePeriods // ------------------------------------------------------------------------- diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/program/ProgramTrackedEntityAttribute.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/program/ProgramTrackedEntityAttribute.java index f08fd4afad12..2ea9d726b625 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/program/ProgramTrackedEntityAttribute.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/program/ProgramTrackedEntityAttribute.java @@ -68,6 +68,8 @@ public class ProgramTrackedEntityAttribute extends BaseIdentifiableObject private Boolean searchable = false; + private boolean skipIndividualAnalytics; + // ------------------------------------------------------------------------- // Constructors // ------------------------------------------------------------------------- @@ -265,6 +267,16 @@ public DeviceRenderTypeMap getRenderType() { return renderType; } + @JsonProperty + @JacksonXmlProperty(namespace = DxfNamespaces.DXF_2_0) + public boolean isSkipIndividualAnalytics() { + return skipIndividualAnalytics; + } + + public void setSkipIndividualAnalytics(boolean skipIndividualAnalytics) { + this.skipIndividualAnalytics = skipIndividualAnalytics; + } + public void setRenderType(DeviceRenderTypeMap renderType) { this.renderType = renderType; } @@ -295,5 +307,6 @@ private static void setShallowCopyValues( copy.setSharing(original.getSharing()); copy.setSortOrder(original.getSortOrder()); copy.setTranslations(original.getTranslations()); + copy.setSkipIndividualAnalytics(original.isSkipIndividualAnalytics()); } } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/relationship/RelationshipKey.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/relationship/RelationshipKey.java index 98b1259f8108..e258ce7b7bae 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/relationship/RelationshipKey.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/relationship/RelationshipKey.java @@ -30,7 +30,7 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; -import org.apache.commons.lang3.StringUtils; +import org.hisp.dhis.common.UID; @Data @Builder(toBuilder = true) @@ -56,34 +56,34 @@ public RelationshipKey inverseKey() { @Data @Builder public static class RelationshipItemKey { - private final String trackedEntity; + private final UID trackedEntity; - private final String enrollment; + private final UID enrollment; - private final String event; + private final UID event; public String asString() { if (isTrackedEntity()) { - return trackedEntity; + return trackedEntity.getValue(); } else if (isEnrollment()) { - return enrollment; + return enrollment.getValue(); } else if (isEvent()) { - return event; + return event.getValue(); } return "ERROR"; } public boolean isTrackedEntity() { - return StringUtils.isNoneBlank(trackedEntity); + return trackedEntity != null; } public boolean isEnrollment() { - return StringUtils.isNoneBlank(enrollment); + return enrollment != null; } public boolean isEvent() { - return StringUtils.isNoneBlank(event); + return event != null; } } } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/resourcetable/ResourceTableService.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/resourcetable/ResourceTableService.java index 800c3fcae97c..186ada653ada 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/resourcetable/ResourceTableService.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/resourcetable/ResourceTableService.java @@ -36,6 +36,9 @@ public interface ResourceTableService { /** Generates resource tables. */ void generateResourceTables(); + /** Replicates resource tables in the analytics database. */ + void replicateAnalyticsResourceTables(); + /** Generates data approval resource tables. */ void generateDataApprovalResourceTables(); diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/resourcetable/ResourceTableType.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/resourcetable/ResourceTableType.java index 30176f043b4a..1adb7e8fa0ef 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/resourcetable/ResourceTableType.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/resourcetable/ResourceTableType.java @@ -44,6 +44,7 @@ public enum ResourceTableType { ORG_UNIT_GROUP_SET_STRUCTURE, CATEGORY_STRUCTURE, DATA_ELEMENT_STRUCTURE, + DATA_SET, PERIOD_STRUCTURE, DATE_PERIOD_STRUCTURE, DATA_ELEMENT_CATEGORY_OPTION_COMBO, diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/scheduling/JobRunErrors.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/scheduling/JobRunErrors.java index 84bcc3e6f8b0..656f1dcdf2f7 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/scheduling/JobRunErrors.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/scheduling/JobRunErrors.java @@ -74,7 +74,8 @@ default JsonDate finished() { return get("finished", JsonDate.class); } - @OpenApi.Description(""" + @OpenApi.Description( + """ The file resource used to store the job's input """) @OpenApi.Property({UID.class, FileResource.class}) diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/security/Authorities.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/security/Authorities.java index 855d60d28143..27f121e3ae59 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/security/Authorities.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/security/Authorities.java @@ -86,6 +86,7 @@ public enum Authorities { F_INDICATOR_TYPE_MERGE, F_INDICATOR_MERGE, F_DATA_ELEMENT_MERGE, + F_CATEGORY_OPTION_MERGE, F_INSERT_CUSTOM_JS_CSS, F_VIEW_UNAPPROVED_DATA, F_USER_VIEW, diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/setting/SystemSettings.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/setting/SystemSettings.java index e211f4f51aea..26b2a534cd47 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/setting/SystemSettings.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/setting/SystemSettings.java @@ -115,10 +115,7 @@ static boolean isTranslatable(@Nonnull String key) { return LazySettings.isTranslatable(key); } - /* - settings used in core - */ - + /** Settings used in core */ default Locale getUiLocale() { return asLocale("keyUiLocale", LocaleManager.DEFAULT_LOCALE); } @@ -308,6 +305,10 @@ default boolean getIncludeZeroValuesInAnalytics() { return asBoolean("keyIncludeZeroValuesInAnalytics", false); } + default boolean getEmbeddedDashboardsEnabled() { + return asBoolean("keyEmbeddedDashboardsEnabled", false); + } + default int getSqlViewMaxLimit() { return asInt("keySqlViewMaxLimit", -1); } @@ -720,12 +721,14 @@ default String getGlobalShellAppName() { return asString("globalShellAppName", "global-app-shell"); } - /* - - Combinators based on several settings - - */ + /** + * @return true if email verification is enforced for all users. + */ + default boolean getEnforceVerifiedEmail() { + return asBoolean("enforceVerifiedEmail", false); + } + /** Combinators based on several settings. */ default boolean isEmailConfigured() { return !getEmailHostName().isBlank() && !getEmailUsername().isBlank(); } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/SystemUser.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/SystemUser.java index 8aa2f6d2b048..b238adac245f 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/SystemUser.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/SystemUser.java @@ -184,6 +184,11 @@ public boolean isTwoFactorEnabled() { return false; } + @Override + public boolean isEmailVerified() { + return true; + } + @Override public boolean hasAnyRestrictions(Collection restrictions) { return false; diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/User.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/User.java index 12e672f36f8c..1bb2880b6670 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/User.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/User.java @@ -41,6 +41,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @@ -1192,6 +1193,12 @@ public String getVerifiedEmail() { return this.verifiedEmail; } + @JsonProperty + @JacksonXmlProperty(namespace = DxfNamespaces.DXF_2_0) + public boolean isEmailVerified() { + return this.getEmail() != null && Objects.equals(this.getEmail(), this.getVerifiedEmail()); + } + public void setVerifiedEmail(String verifiedEmail) { this.verifiedEmail = verifiedEmail; } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserDetails.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserDetails.java index 9c9db96a73b8..cca0ccb8ede9 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserDetails.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserDetails.java @@ -28,6 +28,7 @@ package org.hisp.dhis.user; import java.util.Collection; +import java.util.List; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -118,6 +119,7 @@ static UserDetails createUserDetails( .password(user.getPassword()) .externalAuth(user.isExternalAuth()) .isTwoFactorEnabled(user.isTwoFactorEnabled()) + .isEmailVerified(user.isEmailVerified()) .code(user.getCode()) .firstName(user.getFirstName()) .surname(user.getSurname()) @@ -243,10 +245,20 @@ static UserDetails createUserDetails( boolean isTwoFactorEnabled(); + boolean isEmailVerified(); + boolean hasAnyRestrictions(Collection restrictions); void setId(Long id); + default boolean canIssueUserRole(UserRole role, boolean canGrantOwnUserRole) { + if (role == null) return false; + if (isSuper()) return true; + if (hasAnyAuthorities(List.of(Authorities.ALL))) return true; + if (!canGrantOwnUserRole && getUserRoleIds().contains(role.getUid())) return false; + return getAllAuthorities().containsAll(role.getAuthorities()); + } + default boolean isInUserHierarchy(String orgUnitPath) { return isInUserHierarchy(orgUnitPath, getUserOrgUnitIds()); } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserDetailsImpl.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserDetailsImpl.java index e29438f00843..522bc1586093 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserDetailsImpl.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserDetailsImpl.java @@ -53,6 +53,7 @@ public class UserDetailsImpl implements UserDetails { private final String password; private final boolean externalAuth; private final boolean isTwoFactorEnabled; + private final boolean isEmailVerified; private final boolean enabled; private final boolean accountNonExpired; private final boolean accountNonLocked; @@ -82,6 +83,11 @@ public boolean canModifyUser(User other) { return auths.containsAll(other.getAllAuthorities()); } + @Override + public boolean isEmailVerified() { + return this.isEmailVerified; + } + @Override public boolean hasAnyRestrictions(Collection restrictions) { return getAllRestrictions().stream().anyMatch(restrictions::contains); diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserOrgUnitProperty.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserOrgUnitProperty.java new file mode 100644 index 000000000000..702adebb2d02 --- /dev/null +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserOrgUnitProperty.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2004-2024, 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.user; + +public enum UserOrgUnitProperty { + ORG_UNITS("organisationUnits"), + DATA_VIEW_ORG_UNITS("dataViewOrganisationUnits"), + TEI_SEARCH_ORG_UNITS("teiSearchOrganisationUnits"); + + private final String value; + + UserOrgUnitProperty(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserQueryParams.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserQueryParams.java index 49752b1f2d03..a4e67fc429fb 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserQueryParams.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserQueryParams.java @@ -75,6 +75,7 @@ public class UserQueryParams { @ToString.Include private UserInvitationStatus invitationStatus; + /** If empty, no matching condition will be generated */ private Set organisationUnits = new HashSet<>(); private Set userGroups = new HashSet<>(); diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserService.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserService.java index 52e24f14c011..a64c41765021 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserService.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserService.java @@ -41,11 +41,14 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.hisp.dhis.common.IdentifiableObject; +import org.hisp.dhis.common.UID; import org.hisp.dhis.dataset.DataSet; +import org.hisp.dhis.feedback.ConflictException; import org.hisp.dhis.feedback.ErrorCode; import org.hisp.dhis.feedback.ErrorReport; import org.hisp.dhis.feedback.ForbiddenException; import org.hisp.dhis.feedback.NotFoundException; +import org.hisp.dhis.organisationunit.OrganisationUnit; /** * @author Chau Thu Tran @@ -233,6 +236,17 @@ static boolean hasTwoFactorSecretForApproval(User user) { */ List getUsers(UserQueryParams params, @Nullable List orders); + /** + * Returns a list of users based on the given query parameters. If the specified list of orders + * are empty, default order of last name and first name will be applied. + * + * @param params the user query parameters. + * @param orders the already validated order strings (e.g. email:asc). + * @return a List of users. + */ + List getUserIds(UserQueryParams params, @Nullable List orders) + throws ConflictException; + /** * Returns the number of users based on the given query parameters. * @@ -406,12 +420,9 @@ static boolean hasTwoFactorSecretForApproval(User user) { int countDataSetUserRoles(DataSet dataSet); /** - * Filters the given collection of user roles based on whether the current user is allowed to - * issue it. - * - * @param userRoles the collection of user roles. + * @return IDs of the roles the current user can issue */ - void canIssueFilter(Collection userRoles); + List getRolesCurrentUserCanIssue(); /** * Validate that the current user are allowed to create or modify properties of the given user. @@ -891,4 +902,14 @@ void validateTwoFactorUpdate(boolean before, boolean after, User userToModify) boolean isEmailVerified(User currentUser); User getUserByVerificationToken(String token); + + /** + * Method that retrieves all {@link User}s that have an entry for the {@link OrganisationUnit} in + * the given table + * + * @param orgUnitProperty {@link UserOrgUnitProperty} used to search + * @param uid {@link OrganisationUnit} {@link UID} to match on + * @return matching {@link User}s + */ + List getUsersWithOrgUnit(@Nonnull UserOrgUnitProperty orgUnitProperty, @Nonnull UID uid); } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserStore.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserStore.java index 4348f74a7c03..f286f26e5850 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserStore.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserStore.java @@ -38,6 +38,8 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.hisp.dhis.common.IdentifiableObjectStore; +import org.hisp.dhis.common.UID; +import org.hisp.dhis.organisationunit.OrganisationUnit; /** * @author Nguyen Hong Duc @@ -63,6 +65,8 @@ public interface UserStore extends IdentifiableObjectStore { */ List getUsers(UserQueryParams params, @Nullable List orders); + List getUserIds(UserQueryParams params, @Nullable List orders); + /** * Returns the number of users based on the given query parameters. * @@ -224,4 +228,14 @@ Map> findNotifiableUsersWithPasswordLastUpdatedBetween( User getUserByVerificationToken(String token); User getUserByVerifiedEmail(String email); + + /** + * Retrieves all {@link User}s that have an entry for the {@link OrganisationUnit} in the given + * table + * + * @param orgUnitProperty {@link UserOrgUnitProperty} used to search + * @param uid {@link OrganisationUnit} {@link UID} to match on + * @return matching {@link User}s + */ + List getUsersWithOrgUnit(@Nonnull UserOrgUnitProperty orgUnitProperty, @Nonnull UID uid); } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/util/DateUtils.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/util/DateUtils.java index 57c2e928c1a2..d51120225b22 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/util/DateUtils.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/util/DateUtils.java @@ -283,6 +283,18 @@ public static Date getDate(int year, int month, int dayOfMonth, int hourOfDay, i return new DateTime(year, month, dayOfMonth, hourOfDay, minuteOfHour).toDate(); } + /** + * Creates a {@link Date} representing the given year, month and day. + * + * @param year the year. + * @param month the month, from 1. + * @param dayOfMonth the day of the month, from 1. + * @return a {@link Date}. + */ + public static Date getDate(int year, int month, int dayOfMonth) { + return new DateTime(year, month, dayOfMonth, 0, 0).toDate(); + } + /** * Formats a Date according to the HTTP specification standard date format. * @@ -798,19 +810,29 @@ private static R convertOrNull(T from, Function converter) { * UTC time zone. * * @param time the LocalDateTime. - * @return a Date. + * @return a {@link Date}. */ public static Date getDate(LocalDateTime time) { Instant instant = time.toInstant(ZoneOffset.UTC); - return Date.from(instant); } + /** + * Truncates the given date to the first day of the month. + * + * @param date the date to truncate. + * @return a {@link Date}. + */ + public static Date dateTruncMonth(Date date) { + LocalDate localDate = new LocalDate(date); + return localDate.withDayOfMonth(1).toDate(); + } + /** * Return the current date minus the duration specified by the given string. * * @param duration the duration string. - * @return a Date. + * @return a {@link Date}. */ public static Date nowMinusDuration(String duration) { Duration dr = DateUtils.getDuration(duration); diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/util/Timer.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/util/Timer.java index 906dc4ce32dc..1d1039943357 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/util/Timer.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/util/Timer.java @@ -28,6 +28,7 @@ package org.hisp.dhis.util; import lombok.extern.slf4j.Slf4j; +import org.slf4j.helpers.MessageFormatter; @Slf4j public class Timer { @@ -49,10 +50,10 @@ public long getSplitTime() { return getSplitTime("Split"); } - public long getSplitTime(String msg) { - long endTime = System.nanoTime(); - - long time = (endTime - startTime) / 1000; + public long getSplitTime(String pattern, Object... args) { + final long endTime = System.nanoTime(); + final long time = (endTime - startTime) / 1000; + final String msg = MessageFormatter.arrayFormat(pattern, args).getMessage(); if (!printDisabled) { log.info("Time: " + time + " micros: " + msg); @@ -68,16 +69,13 @@ public long getTimeInMs() { } public long getTimeInS() { - long endTime = System.nanoTime(); - long time = (endTime - startTime) / 1000000000; - return time; + final long endTime = System.nanoTime(); + return (endTime - startTime) / 1000000000; } public long getTime(String msg) { - long time = getSplitTime(msg); - + final long time = getSplitTime(msg); start(); - return time; } diff --git a/dhis-2/dhis-api/src/test/java/org/hisp/dhis/common/HashUtilsTest.java b/dhis-2/dhis-api/src/test/java/org/hisp/dhis/common/HashUtilsTest.java index f9e8778914da..b1be79ba9ceb 100644 --- a/dhis-2/dhis-api/src/test/java/org/hisp/dhis/common/HashUtilsTest.java +++ b/dhis-2/dhis-api/src/test/java/org/hisp/dhis/common/HashUtilsTest.java @@ -37,6 +37,21 @@ * @author Morten Svanæs */ class HashUtilsTest { + + @Test + void testMd5Hex() { + String value = "10-05-2022T12:55:45"; + assertEquals(32, HashUtils.hashMD5(value.getBytes()).length()); + assertEquals("c149820871470e3ab15eb24d42b3561a", HashUtils.hashMD5(value.getBytes())); + } + + @Test + void testSha1Hex() { + String value = "/api/me"; + assertEquals(40, HashUtils.hashSHA1(value.getBytes()).length()); + assertEquals("4f8cc3f306852ecb642ba4375453be1a4b860e71", HashUtils.hashSHA1(value.getBytes())); + } + @Test void testIsValidSHA256HexFormat() { String validSHA256Hex = "635e253fddd8466788d9580983bda99c258e9dd8c5a60a032623fde6c3a2789d"; diff --git a/dhis-2/dhis-api/src/test/java/org/hisp/dhis/common/IdSchemesTest.java b/dhis-2/dhis-api/src/test/java/org/hisp/dhis/common/IdSchemesTest.java index b26c5064a6d1..f2061ffa3c2e 100644 --- a/dhis-2/dhis-api/src/test/java/org/hisp/dhis/common/IdSchemesTest.java +++ b/dhis-2/dhis-api/src/test/java/org/hisp/dhis/common/IdSchemesTest.java @@ -58,7 +58,8 @@ void testSerializeIdSchemes() throws JsonProcessingException { IdSchemes original = new IdSchemes(); original.setProgramIdScheme("CODE"); // language=JSON - String expected = """ + String expected = + """ {"programIdScheme":{"type":"CODE"}}"""; assertEquals(expected, new ObjectMapper().writeValueAsString(original)); } diff --git a/dhis-2/dhis-api/src/test/java/org/hisp/dhis/program/ProgramTrackedEntityAttributeTest.java b/dhis-2/dhis-api/src/test/java/org/hisp/dhis/program/ProgramTrackedEntityAttributeTest.java index 7ad34ae9e498..52409c166a17 100644 --- a/dhis-2/dhis-api/src/test/java/org/hisp/dhis/program/ProgramTrackedEntityAttributeTest.java +++ b/dhis-2/dhis-api/src/test/java/org/hisp/dhis/program/ProgramTrackedEntityAttributeTest.java @@ -29,6 +29,7 @@ import static org.hisp.dhis.program.ProgramTest.getNewProgram; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotSame; @@ -62,6 +63,27 @@ void testCopyOf() { assertEquals("Copy of Program Name tracked entity attr 1", copy.getName()); } + @Test + void testDefaultSkipAnalyticsValue() { + ProgramTrackedEntityAttribute ptea = new ProgramTrackedEntityAttribute(); + assertFalse( + ptea.isSkipIndividualAnalytics(), + "Default value of skipIndividualAnalytics should be false"); + } + + @Test + void testCopyOfWithSkipAnalytics() { + ProgramTrackedEntityAttribute original = getNewProgramAttribute(getNewProgram()); + original.setSkipIndividualAnalytics(true); // Set to true to check copy behavior + ProgramTrackedEntityAttribute copy = + ProgramTrackedEntityAttribute.copyOf.apply(original, getNewProgram()); + + assertEquals( + original.isSkipIndividualAnalytics(), + copy.isSkipIndividualAnalytics(), + "skipIndividualAnalytics should be copied correctly"); + } + private ProgramTrackedEntityAttribute getNewProgramAttribute(Program program) { ProgramTrackedEntityAttribute ptea = new ProgramTrackedEntityAttribute(); TrackedEntityAttribute tea = new TrackedEntityAttribute(); diff --git a/dhis-2/dhis-api/src/test/java/org/hisp/dhis/relationship/RelationshipKeyTest.java b/dhis-2/dhis-api/src/test/java/org/hisp/dhis/relationship/RelationshipKeyTest.java index 69384538d1bf..89186847499f 100644 --- a/dhis-2/dhis-api/src/test/java/org/hisp/dhis/relationship/RelationshipKeyTest.java +++ b/dhis-2/dhis-api/src/test/java/org/hisp/dhis/relationship/RelationshipKeyTest.java @@ -29,6 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import org.hisp.dhis.common.UID; import org.junit.jupiter.api.Test; class RelationshipKeyTest { @@ -39,8 +40,12 @@ void asStringForRelationshipTeiToTei() { RelationshipKey key = RelationshipKey.of( "dDrh5UyCyvQ", - RelationshipKey.RelationshipItemKey.builder().trackedEntity("Ea0rRdBPAIp").build(), - RelationshipKey.RelationshipItemKey.builder().trackedEntity("G1afLIEKt8A").build()); + RelationshipKey.RelationshipItemKey.builder() + .trackedEntity(UID.of("Ea0rRdBPAIp")) + .build(), + RelationshipKey.RelationshipItemKey.builder() + .trackedEntity(UID.of("G1afLIEKt8A")) + .build()); assertEquals("dDrh5UyCyvQ_Ea0rRdBPAIp_G1afLIEKt8A", key.asString()); } @@ -51,8 +56,12 @@ void asStringForRelationshipTeiToEnrollment() { RelationshipKey key = RelationshipKey.of( "dDrh5UyCyvQ", - RelationshipKey.RelationshipItemKey.builder().trackedEntity("Ea0rRdBPAIp").build(), - RelationshipKey.RelationshipItemKey.builder().enrollment("G1afLIEKt8A").build()); + RelationshipKey.RelationshipItemKey.builder() + .trackedEntity(UID.of("Ea0rRdBPAIp")) + .build(), + RelationshipKey.RelationshipItemKey.builder() + .enrollment(UID.of("G1afLIEKt8A")) + .build()); assertEquals("dDrh5UyCyvQ_Ea0rRdBPAIp_G1afLIEKt8A", key.asString()); } @@ -63,8 +72,10 @@ void asStringForRelationshipTeiToEvent() { RelationshipKey key = RelationshipKey.of( "dDrh5UyCyvQ", - RelationshipKey.RelationshipItemKey.builder().trackedEntity("Ea0rRdBPAIp").build(), - RelationshipKey.RelationshipItemKey.builder().event("G1afLIEKt8A").build()); + RelationshipKey.RelationshipItemKey.builder() + .trackedEntity(UID.of("Ea0rRdBPAIp")) + .build(), + RelationshipKey.RelationshipItemKey.builder().event(UID.of("G1afLIEKt8A")).build()); assertEquals("dDrh5UyCyvQ_Ea0rRdBPAIp_G1afLIEKt8A", key.asString()); } diff --git a/dhis-2/dhis-api/src/test/java/org/hisp/dhis/util/DateUtilsTest.java b/dhis-2/dhis-api/src/test/java/org/hisp/dhis/util/DateUtilsTest.java index 8c441747ba0f..0c7b97ba9709 100644 --- a/dhis-2/dhis-api/src/test/java/org/hisp/dhis/util/DateUtilsTest.java +++ b/dhis-2/dhis-api/src/test/java/org/hisp/dhis/util/DateUtilsTest.java @@ -35,6 +35,7 @@ import static org.hamcrest.Matchers.lessThan; import static org.hisp.dhis.util.DateUtils.dateIsValid; import static org.hisp.dhis.util.DateUtils.dateTimeIsValid; +import static org.hisp.dhis.util.DateUtils.getDate; import static org.hisp.dhis.util.DateUtils.minusOneDay; import static org.hisp.dhis.util.DateUtils.parseDate; import static org.hisp.dhis.util.DateUtils.plusOneDay; @@ -456,6 +457,15 @@ void testSafeParseDateInvalidDateFormats() { assertNull(date); } + @Test + void testDateTruncMonth() { + Date dateA = getDate(2024, 3, 9); + Date dateB = getDate(2024, 10, 20); + + assertEquals(getDate(2024, 3, 1), DateUtils.dateTruncMonth(dateA)); + assertEquals(getDate(2024, 10, 1), DateUtils.dateTruncMonth(dateB)); + } + @Test void testPlusOneDay() { Date aDay = toMediumDate("2021-01-01"); diff --git a/dhis-2/dhis-services/dhis-service-administration/pom.xml b/dhis-2/dhis-services/dhis-service-administration/pom.xml index d9ba0eeca877..b956e76a0f20 100644 --- a/dhis-2/dhis-services/dhis-service-administration/pom.xml +++ b/dhis-2/dhis-services/dhis-service-administration/pom.xml @@ -131,7 +131,7 @@ com.networknt json-schema-validator - 1.5.2 + 1.5.3 org.apache.commons diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/maintenance/DefaultMaintenanceService.java b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/maintenance/DefaultMaintenanceService.java index 6f7df34de1d6..656854538283 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/maintenance/DefaultMaintenanceService.java +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/maintenance/DefaultMaintenanceService.java @@ -78,7 +78,7 @@ public class DefaultMaintenanceService implements MaintenanceService { private final ApplicationEventPublisher eventPublisher; - private final EventChangeLogService trackedEntityDataValueChangelogService; + private final EventChangeLogService eventChangeLogService; // ------------------------------------------------------------------------- // MaintenanceService implementation @@ -169,7 +169,8 @@ public boolean pruneData(DataElement dataElement) { return false; } - trackedEntityDataValueChangelogService.deleteTrackedEntityDataValueChangeLog(dataElement); + eventChangeLogService.deleteTrackedEntityDataValueChangeLog(dataElement); + eventChangeLogService.deleteEventChangeLog(dataElement); dataValueAuditService.deleteDataValueAudits(dataElement); dataValueService.deleteDataValues(dataElement); diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/maintenance/jdbc/JdbcMaintenanceStore.java b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/maintenance/jdbc/JdbcMaintenanceStore.java index c778900de40a..7809f5bd4c64 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/maintenance/jdbc/JdbcMaintenanceStore.java +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/maintenance/jdbc/JdbcMaintenanceStore.java @@ -122,6 +122,7 @@ public int deleteSoftDeletedEvents() { // delete other objects related to events "delete from relationshipitem where eventid in " + eventSelect, "delete from trackedentitydatavalueaudit where eventid in " + eventSelect, + "delete from eventchangelog where eventid in " + eventSelect, "delete from programmessage where eventid in " + eventSelect, // finally delete the events "delete from event where deleted is true" @@ -202,6 +203,7 @@ public int deleteSoftDeletedEnrollments() { // delete other entries linked to events "delete from relationshipitem where eventid in " + eventSelect, "delete from trackedentitydatavalueaudit where eventid in " + eventSelect, + "delete from eventchangelog where eventid in " + eventSelect, "delete from programmessage where eventid in " + eventSelect, // delete other entries linked to enrollments "delete from relationshipitem where enrollmentid in " + enrollmentSelect, @@ -288,6 +290,7 @@ public int deleteSoftDeletedTrackedEntities() { "delete from note where noteid not in (select noteid from event_notes union all select noteid from enrollment_notes)", // delete other objects related to obsolete events "delete from trackedentitydatavalueaudit where eventid in " + eventSelect, + "delete from eventchangelog where eventid in " + eventSelect, // delete other objects related to obsolete enrollments "delete from programmessage where enrollmentid in " + enrollmentSelect, "delete from event where enrollmentid in " + enrollmentSelect, diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/CommonMergeHandler.java b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/CommonMergeHandler.java index 604e4f8d502c..314f6eb58fb1 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/CommonMergeHandler.java +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/CommonMergeHandler.java @@ -36,7 +36,7 @@ import org.hisp.dhis.dataentryform.DataEntryFormService; import org.hisp.dhis.indicator.Indicator; import org.hisp.dhis.indicator.IndicatorService; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; /** * Common Merge handler for metadata entities. The merge operations here are shared by many merge @@ -45,7 +45,7 @@ * * @author david mackessy */ -@Service +@Component @RequiredArgsConstructor public class CommonMergeHandler { diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/DefaultMergeValidator.java b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/DefaultMergeValidator.java index ea0d6ffde1e2..f170b39348e2 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/DefaultMergeValidator.java +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/DefaultMergeValidator.java @@ -27,10 +27,13 @@ */ package org.hisp.dhis.merge; +import java.util.HashSet; import java.util.Optional; import java.util.Set; import java.util.function.Supplier; +import javax.annotation.Nonnull; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.hisp.dhis.common.IdentifiableObject; import org.hisp.dhis.common.IdentifiableObjectManager; import org.hisp.dhis.common.UID; @@ -48,45 +51,57 @@ * * @author david mackessy */ +@Slf4j @Component @RequiredArgsConstructor public class DefaultMergeValidator implements MergeValidator { private final IdentifiableObjectManager manager; - private static final String INDICATOR_TYPE = "IndicatorType"; - private static final String INDICATOR = "Indicator"; - private static final String DATA_ELEMENT = "DataElement"; - private static final String MERGE_ERROR = "Unexpected value retrieving merge error code: "; @Override - public void verifySources( - Set paramSources, Set verifiedSources, MergeReport mergeReport, Class clazz) { + public MergeRequest validateUIDs( + @Nonnull MergeParams params, @Nonnull MergeReport mergeReport, @Nonnull MergeType mergeType) { + log.info("Validating {} merge request", mergeType); + mergeReport.setMergeType(mergeType); + + Set verifiedSources = verifySources(params.getSources(), mergeReport, mergeType); + + checkIsTargetInSources(verifiedSources, params.getTarget(), mergeReport, mergeType); + + return verifyTarget(mergeReport, verifiedSources, params, mergeType); + } + + @Override + public Set verifySources( + Set paramSources, MergeReport mergeReport, MergeType mergeType) { + Set verifiedSources = new HashSet<>(); Optional.ofNullable(paramSources) .filter(CollectionUtils::isNotEmpty) .ifPresentOrElse( - ids -> getSourcesAndVerify(ids, mergeReport, verifiedSources, clazz), + ids -> getSourcesAndVerify(ids, mergeReport, verifiedSources, mergeType), () -> mergeReport.addErrorMessage( - new ErrorMessage(missingSourceErrorCode(clazz.getSimpleName())))); + new ErrorMessage(ErrorCode.E1530, mergeType.getName()))); + return verifiedSources; } @Override - public void checkIsTargetInSources( - Set sources, UID target, MergeReport mergeReport, Class clazz) { + public void checkIsTargetInSources( + Set sources, UID target, MergeReport mergeReport, MergeType mergeType) { if (sources.contains(target)) mergeReport.addErrorMessage( - new ErrorMessage(targetNotSourceErrorCode(clazz.getSimpleName()))); + new ErrorMessage(ErrorCode.E1532, mergeType.getName(), mergeType.getName())); } @Override - public MergeRequest verifyTarget( - MergeReport mergeReport, Set sources, MergeParams params, Class clazz) { - return getAndVerify(params.getTarget(), mergeReport, MergeObjectType.TARGET.name(), clazz) + public MergeRequest verifyTarget( + MergeReport mergeReport, Set sources, MergeParams params, MergeType mergeType) { + return getAndVerify(params.getTarget(), mergeReport, MergeObjectType.TARGET, mergeType) .map( - t -> + uid -> MergeRequest.builder() .sources(sources) - .target(t) + .target(uid) .deleteSources(params.isDeleteSources()) .dataMergeStrategy(params.getDataMergeStrategy()) .build()) @@ -103,19 +118,18 @@ public MergeRequest verifyTarget( * @param uid to verify * @param mergeReport to update * @param mergeObjectType indicating whether this is a source or target - * @param clazz {@link IdentifiableObject} type + * @param mergeType {@link MergeType} * @return optional UID */ - private Optional getAndVerify( - UID uid, MergeReport mergeReport, String mergeObjectType, Class clazz) { + private Optional getAndVerify( + UID uid, MergeReport mergeReport, MergeObjectType mergeObjectType, MergeType mergeType) { if (uid != null) { - return Optional.ofNullable(manager.get(clazz, uid.getValue())) + return Optional.ofNullable(manager.get(mergeType.getClazz(), uid.getValue())) .map(i -> UID.of(i.getUid())) - .or(reportError(uid, mergeReport, mergeObjectType, clazz)); + .or(reportError(uid, mergeReport, mergeObjectType, mergeType)); } else { - mergeReport.addErrorMessage( - new ErrorMessage(noTargetErrorCode(clazz.getSimpleName()), mergeObjectType, uid)); + mergeReport.addErrorMessage(new ErrorMessage(ErrorCode.E1531, mergeType.getName())); return Optional.empty(); } } @@ -128,67 +142,25 @@ private Optional getAndVerify( * @param uids to verify * @param report to update with any error * @param verifiedSources to update with verified uids - * @param clazz {@link IdentifiableObject} type + * @param mergeType {@link MergeType} */ - private void getSourcesAndVerify( - Set uids, MergeReport report, Set verifiedSources, Class clazz) { + private void getSourcesAndVerify( + Set uids, MergeReport report, Set verifiedSources, MergeType mergeType) { uids.forEach( uid -> - getAndVerify(uid, report, MergeObjectType.SOURCE.name(), clazz) + getAndVerify(uid, report, MergeObjectType.SOURCE, mergeType) .ifPresent(verifiedSources::add)); } - private Supplier> reportError( - UID uid, MergeReport mergeReport, String mergeObjectType, Class clazz) { + private Supplier> reportError( + UID uid, MergeReport mergeReport, MergeObjectType mergeObjectType, MergeType mergeType) { return () -> { mergeReport.addErrorMessage( - new ErrorMessage(doesNotExistErrorCode(clazz.getSimpleName()), mergeObjectType, uid)); + new ErrorMessage(ErrorCode.E1533, mergeObjectType.toString(), mergeType.getName(), uid)); return Optional.empty(); }; } - /** - * Methods to get the appropriate error code based on the Object type - * - * @param clazz class name - * @return error code - */ - private ErrorCode missingSourceErrorCode(String clazz) { - return switch (clazz) { - case INDICATOR_TYPE -> ErrorCode.E1530; - case INDICATOR -> ErrorCode.E1540; - case DATA_ELEMENT -> ErrorCode.E1550; - default -> throw new IllegalStateException(MERGE_ERROR + clazz); - }; - } - - private ErrorCode noTargetErrorCode(String clazz) { - return switch (clazz) { - case INDICATOR_TYPE -> ErrorCode.E1531; - case INDICATOR -> ErrorCode.E1541; - case DATA_ELEMENT -> ErrorCode.E1551; - default -> throw new IllegalStateException(MERGE_ERROR + clazz); - }; - } - - private ErrorCode targetNotSourceErrorCode(String clazz) { - return switch (clazz) { - case INDICATOR_TYPE -> ErrorCode.E1532; - case INDICATOR -> ErrorCode.E1542; - case DATA_ELEMENT -> ErrorCode.E1552; - default -> throw new IllegalStateException(MERGE_ERROR + clazz); - }; - } - - private ErrorCode doesNotExistErrorCode(String clazz) { - return switch (clazz) { - case INDICATOR_TYPE -> ErrorCode.E1533; - case INDICATOR -> ErrorCode.E1543; - case DATA_ELEMENT -> ErrorCode.E1553; - default -> throw new IllegalStateException(MERGE_ERROR + clazz); - }; - } - enum MergeObjectType { SOURCE, TARGET diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/category/option/CategoryOptionMergeHandler.java b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/category/option/CategoryOptionMergeHandler.java new file mode 100644 index 000000000000..f62280f70d18 --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/category/option/CategoryOptionMergeHandler.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2004-2024, 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.merge.category.option; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.hisp.dhis.analytics.CategoryDimensionStore; +import org.hisp.dhis.category.Category; +import org.hisp.dhis.category.CategoryDimension; +import org.hisp.dhis.category.CategoryOption; +import org.hisp.dhis.category.CategoryOptionCombo; +import org.hisp.dhis.category.CategoryOptionComboStore; +import org.hisp.dhis.category.CategoryOptionGroup; +import org.hisp.dhis.category.CategoryOptionGroupStore; +import org.hisp.dhis.category.CategoryStore; +import org.hisp.dhis.common.UID; +import org.hisp.dhis.organisationunit.OrganisationUnit; +import org.hisp.dhis.organisationunit.OrganisationUnitStore; +import org.springframework.stereotype.Component; + +/** + * Merge handler for metadata entities. + * + * @author david mackessy + */ +@Component +@RequiredArgsConstructor +public class CategoryOptionMergeHandler { + + private final CategoryStore categoryStore; + private final CategoryOptionComboStore categoryOptionComboStore; + private final CategoryOptionGroupStore categoryOptionGroupStore; + private final OrganisationUnitStore organisationUnitStore; + private final CategoryDimensionStore categoryDimensionStore; + + /** + * Remove sources from {@link Category} and add target to {@link Category} + * + * @param sources to be removed + * @param target to add + */ + public void handleCategories(List sources, CategoryOption target) { + List sourceCategories = + categoryStore.getCategoriesByCategoryOption(UID.toUidValueSet(sources)); + sourceCategories.forEach( + c -> { + c.addCategoryOption(target); + c.removeCategoryOptions(sources); + }); + } + + /** + * Remove sources from {@link CategoryOptionCombo} and add target to {@link CategoryOptionCombo} + * + * @param sources to be removed + * @param target to add + */ + public void handleCategoryOptionCombos(List sources, CategoryOption target) { + List sourceCocs = + categoryOptionComboStore.getCategoryOptionCombosByCategoryOption( + UID.toUidValueSet(sources)); + + sources.forEach(s -> s.removeCategoryOptionCombos(sourceCocs)); + target.addCategoryOptionCombos(sourceCocs); + } + + /** + * Remove sources from {@link OrganisationUnit} and add target to {@link OrganisationUnit} + * + * @param sources to be removed + * @param target to add + */ + public void handleOrganisationUnits(List sources, CategoryOption target) { + List sourceOus = + organisationUnitStore.getByCategoryOption(UID.toUidValueSet(sources)); + + sourceOus.forEach( + ou -> { + ou.addCategoryOption(target); + ou.removeCategoryOptions(sources); + }); + } + + /** + * Remove sources from {@link CategoryOptionGroup} and add target to {@link CategoryOptionGroup} + * + * @param sources to be removed + * @param target to add + */ + public void handleCategoryOptionGroups(List sources, CategoryOption target) { + List sourceCogs = + categoryOptionGroupStore.getByCategoryOption(UID.toUidValueSet(sources)); + + sourceCogs.forEach( + cog -> { + cog.addCategoryOption(target); + cog.removeCategoryOptions(sources); + }); + } + + /** + * Remove sources from {@link CategoryDimension} and add target to {@link CategoryDimension} + * + * @param sources to be removed + * @param target to add + */ + public void handleCategoryDimensions(List sources, CategoryOption target) { + List sourceCds = + categoryDimensionStore.getByCategoryOption(UID.toUidValueSet(sources)); + + sourceCds.forEach( + cd -> { + cd.addItem(target); + cd.removeItems(sources); + }); + } +} diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/category/option/CategoryOptionMergeService.java b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/category/option/CategoryOptionMergeService.java new file mode 100644 index 000000000000..3e2ccf59c655 --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/category/option/CategoryOptionMergeService.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2004-2024, 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.merge.category.option; + +import jakarta.persistence.EntityManager; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.hisp.dhis.category.CategoryOption; +import org.hisp.dhis.category.CategoryService; +import org.hisp.dhis.common.UID; +import org.hisp.dhis.dataelement.DataElement; +import org.hisp.dhis.feedback.MergeReport; +import org.hisp.dhis.merge.MergeParams; +import org.hisp.dhis.merge.MergeRequest; +import org.hisp.dhis.merge.MergeService; +import org.hisp.dhis.merge.MergeType; +import org.hisp.dhis.merge.MergeValidator; +import org.springframework.stereotype.Service; + +/** + * Main class for a {@link CategoryOption} merge. + * + * @author david mackessy + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class CategoryOptionMergeService implements MergeService { + + private final CategoryService categoryService; + private final CategoryOptionMergeHandler categoryOptionMergeHandler; + private final MergeValidator validator; + private final EntityManager entityManager; + private List metadataMergeHandlers; + + @Override + public MergeRequest validate(@Nonnull MergeParams params, @Nonnull MergeReport mergeReport) { + return validator.validateUIDs(params, mergeReport, MergeType.CATEGORY_OPTION); + } + + @Override + public MergeReport merge(@Nonnull MergeRequest request, @Nonnull MergeReport mergeReport) { + log.info("Performing CategoryOption merge"); + + List sources = + categoryService.getCategoryOptionsByUid(UID.toValueList(request.getSources())); + CategoryOption target = categoryService.getCategoryOption(request.getTarget().getValue()); + + // merge metadata + log.info("Handling CategoryOption reference associations and merges"); + metadataMergeHandlers.forEach(h -> h.merge(sources, target)); + + // a flush is required here to bring the system into a consistent state. This is required so + // that the deletion handler hooks, which are usually done using JDBC (non-Hibernate), can + // see the most up-to-date state, including merges done using Hibernate. + entityManager.flush(); + + // handle deletes + if (request.isDeleteSources()) handleDeleteSources(sources, mergeReport); + + return mergeReport; + } + + private void handleDeleteSources(List sources, MergeReport mergeReport) { + log.info("Deleting source CategoryOptions"); + for (CategoryOption source : sources) { + mergeReport.addDeletedSource(source.getUid()); + categoryService.deleteCategoryOption(source); + } + } + + @PostConstruct + private void initMergeHandlers() { + metadataMergeHandlers = + List.of( + categoryOptionMergeHandler::handleCategories, + categoryOptionMergeHandler::handleCategoryOptionCombos, + categoryOptionMergeHandler::handleOrganisationUnits, + categoryOptionMergeHandler::handleCategoryOptionGroups, + categoryOptionMergeHandler::handleCategoryDimensions); + } + + /** + * Functional interface representing a {@link DataElement} data merge operation. + * + * @author david mackessy + */ + @FunctionalInterface + public interface MetadataMergeHandler { + void merge(@Nonnull List sources, @Nonnull CategoryOption target); + } +} diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/DataElementMergeService.java b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/DataElementMergeService.java index 9ab1fd51826f..1e6b29f801d9 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/DataElementMergeService.java +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/DataElementMergeService.java @@ -29,9 +29,7 @@ import com.google.common.collect.ImmutableList; import jakarta.persistence.EntityManager; -import java.util.HashSet; import java.util.List; -import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.PostConstruct; import lombok.RequiredArgsConstructor; @@ -47,13 +45,13 @@ import org.hisp.dhis.merge.MergeParams; import org.hisp.dhis.merge.MergeRequest; import org.hisp.dhis.merge.MergeService; +import org.hisp.dhis.merge.MergeType; import org.hisp.dhis.merge.MergeValidator; import org.hisp.dhis.merge.dataelement.handler.AnalyticalDataElementMergeHandler; import org.hisp.dhis.merge.dataelement.handler.DataDataElementMergeHandler; import org.hisp.dhis.merge.dataelement.handler.MetadataDataElementMergeHandler; import org.hisp.dhis.merge.dataelement.handler.TrackerDataElementMergeHandler; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; /** * Main class for a {@link org.hisp.dhis.dataelement.DataElement} merge. @@ -81,23 +79,15 @@ public class DataElementMergeService implements MergeService { @Override public MergeRequest validate(@Nonnull MergeParams params, @Nonnull MergeReport mergeReport) { - log.info("Validating DataElement merge request"); + MergeRequest request = validator.validateUIDs(params, mergeReport, MergeType.DATA_ELEMENT); + if (mergeReport.hasErrorMessages()) return request; + + // data element-specific validation if (params.getDataMergeStrategy() == null) { - mergeReport.addErrorMessage(new ErrorMessage(ErrorCode.E1556)); - return null; + mergeReport.addErrorMessage(new ErrorMessage(ErrorCode.E1534)); + return request; } - // sources - Set sources = new HashSet<>(); - validator.verifySources(params.getSources(), sources, mergeReport, DataElement.class); - - // target - validator.checkIsTargetInSources(sources, params.getTarget(), mergeReport, DataElement.class); - MergeRequest request = validator.verifyTarget(mergeReport, sources, params, DataElement.class); - - if (mergeReport.hasErrorMessages()) return request; - - // get DEs for further type-specific validation DataElement deTarget = dataElementService.getDataElement(request.getTarget().getValue()); List deSources = dataElementService.getDataElementsByUid( @@ -113,7 +103,6 @@ public MergeRequest validate(@Nonnull MergeParams params, @Nonnull MergeReport m } @Override - @Transactional public MergeReport merge(@Nonnull MergeRequest request, @Nonnull MergeReport mergeReport) { log.info("Performing DataElement merge"); @@ -197,7 +186,7 @@ private void initMergeHandlers() { * @author david mackessy */ @FunctionalInterface - public static interface DataElementAuditMergeHandler { + public interface DataElementAuditMergeHandler { void merge(@Nonnull List sources, @Nonnull MergeRequest mergeRequest); } @@ -207,7 +196,7 @@ public static interface DataElementAuditMergeHandler { * @author david mackessy */ @FunctionalInterface - public static interface DataElementDataMergeHandler { + public interface DataElementDataMergeHandler { void merge( @Nonnull List sources, @Nonnull DataElement target, @@ -220,7 +209,7 @@ void merge( * @author david mackessy */ @FunctionalInterface - public static interface DataElementMergeHandler { + public interface DataElementMergeHandler { void merge(List sources, DataElement target); } } diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/DataElementMergeValidator.java b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/DataElementMergeValidator.java index e8b7ca064852..d48f0e56ab62 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/DataElementMergeValidator.java +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/DataElementMergeValidator.java @@ -27,8 +27,8 @@ */ package org.hisp.dhis.merge.dataelement; -import static org.hisp.dhis.feedback.ErrorCode.E1554; -import static org.hisp.dhis.feedback.ErrorCode.E1555; +import static org.hisp.dhis.feedback.ErrorCode.E1550; +import static org.hisp.dhis.feedback.ErrorCode.E1551; import java.util.List; import javax.annotation.Nonnull; @@ -56,7 +56,7 @@ public MergeReport validateValueType( if (!mismatches.isEmpty()) { report.addErrorMessage( new ErrorMessage( - E1554, + E1550, target.getValueType(), mismatches.stream().map(DataElement::getValueType).distinct().toList())); } @@ -75,7 +75,7 @@ public MergeReport validateDomainType( if (!mismatches.isEmpty()) { report.addErrorMessage( new ErrorMessage( - E1555, + E1551, target.getDomainType(), mismatches.stream().map(DataElement::getDomainType).distinct().toList())); } diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/handler/AnalyticalDataElementMergeHandler.java b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/handler/AnalyticalDataElementMergeHandler.java index b6ee44050e1b..05967b17ad94 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/handler/AnalyticalDataElementMergeHandler.java +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/handler/AnalyticalDataElementMergeHandler.java @@ -38,14 +38,14 @@ import org.hisp.dhis.eventvisualization.EventVisualizationStore; import org.hisp.dhis.mapping.MapView; import org.hisp.dhis.trackedentity.TrackedEntityDataElementDimension; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; /** * Merge handler for analytical entities. * * @author david mackessy */ -@Service +@Component @RequiredArgsConstructor public class AnalyticalDataElementMergeHandler { diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/handler/DataDataElementMergeHandler.java b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/handler/DataDataElementMergeHandler.java index 3fb5e36fa856..28f8c1fc2d92 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/handler/DataDataElementMergeHandler.java +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/handler/DataDataElementMergeHandler.java @@ -45,7 +45,7 @@ import org.hisp.dhis.datavalue.DataValueStore; import org.hisp.dhis.merge.DataMergeStrategy; import org.hisp.dhis.merge.MergeRequest; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; /** * Merge handler for data @@ -53,7 +53,7 @@ * @author david mackessy */ @Slf4j -@Service +@Component @RequiredArgsConstructor public class DataDataElementMergeHandler { diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/handler/MetadataDataElementMergeHandler.java b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/handler/MetadataDataElementMergeHandler.java index c8122f85a52f..af6a15559288 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/handler/MetadataDataElementMergeHandler.java +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/handler/MetadataDataElementMergeHandler.java @@ -47,14 +47,14 @@ import org.hisp.dhis.predictor.PredictorStore; import org.hisp.dhis.sms.command.code.SMSCode; import org.hisp.dhis.sms.command.hibernate.SMSCommandStore; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; /** * Merge handler for metadata entities. * * @author david mackessy */ -@Service +@Component @RequiredArgsConstructor public class MetadataDataElementMergeHandler { @@ -204,7 +204,7 @@ public void handleDataSetElement(List sources, DataElement target) * Section} */ public void handleSection(List sources, DataElement target) { - List
sections = sectionStore.getByDataElement(sources); + List
sections = sectionStore.getSectionsByDataElement(sources); sources.forEach( source -> { sections.forEach(s -> s.getDataElements().remove(source)); diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/handler/TrackerDataElementMergeHandler.java b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/handler/TrackerDataElementMergeHandler.java index 8a359a9d8e85..18561899de4d 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/handler/TrackerDataElementMergeHandler.java +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/dataelement/handler/TrackerDataElementMergeHandler.java @@ -55,9 +55,10 @@ import org.hisp.dhis.programrule.ProgramRuleActionStore; import org.hisp.dhis.programrule.ProgramRuleVariable; import org.hisp.dhis.programrule.ProgramRuleVariableStore; +import org.hisp.dhis.tracker.export.event.EventChangeLog; import org.hisp.dhis.tracker.export.event.EventChangeLogService; import org.hisp.dhis.tracker.export.event.TrackedEntityDataValueChangeLog; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; /** * Merge handler for tracker entities. @@ -65,7 +66,7 @@ * @author david mackessy */ @Slf4j -@Service +@Component @RequiredArgsConstructor public class TrackerDataElementMergeHandler { @@ -76,7 +77,7 @@ public class TrackerDataElementMergeHandler { private final ProgramRuleActionStore programRuleActionStore; private final ProgramIndicatorStore programIndicatorStore; private final EventStore eventStore; - private final EventChangeLogService teDataValueChangeLogService; + private final EventChangeLogService eventChangeLogService; /** * Method retrieving {@link ProgramIndicator}s which have a source {@link DataElement} reference @@ -274,9 +275,9 @@ private void setLastUpdatedAsTargetAndRemoveRemaining( } /** - * Method handling {@link TrackedEntityDataValueChangeLog}s. All {@link - * TrackedEntityDataValueChangeLog}s will either be deleted or left as is, based on whether the - * source {@link DataElement}s are being deleted or not. + * Method handling {@link TrackedEntityDataValueChangeLog}s and {@link EventChangeLog}s. Both of + * them will either be deleted or left as is, based on whether the source {@link DataElement}s are + * being deleted or not. * * @param sources source {@link DataElement}s used to retrieve {@link DataValueAudit}s * @param mergeRequest merge request @@ -286,7 +287,8 @@ public void handleTrackedEntityDataValueChangelog( if (mergeRequest.isDeleteSources()) { log.info( "Deleting source tracked entity data value change log records as source DataElements are being deleted"); - sources.forEach(teDataValueChangeLogService::deleteTrackedEntityDataValueChangeLog); + sources.forEach(eventChangeLogService::deleteTrackedEntityDataValueChangeLog); + sources.forEach(eventChangeLogService::deleteEventChangeLog); } else { log.info( "Leaving source tracked entity data value change log records as is, source DataElements are not being deleted"); diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/indicator/IndicatorMergeService.java b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/indicator/IndicatorMergeService.java index d49ea52abfd6..c46b4eb840e2 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/indicator/IndicatorMergeService.java +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/indicator/IndicatorMergeService.java @@ -28,12 +28,11 @@ package org.hisp.dhis.merge.indicator; import com.google.common.collect.ImmutableList; -import java.util.HashSet; import java.util.List; -import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.PostConstruct; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.hisp.dhis.common.UID; import org.hisp.dhis.feedback.MergeReport; import org.hisp.dhis.indicator.Indicator; @@ -43,16 +42,17 @@ import org.hisp.dhis.merge.MergeParams; import org.hisp.dhis.merge.MergeRequest; import org.hisp.dhis.merge.MergeService; +import org.hisp.dhis.merge.MergeType; import org.hisp.dhis.merge.MergeValidator; import org.hisp.dhis.merge.indicator.handler.IndicatorMergeHandler; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; /** * Main class for indicator merge. * * @author david mackessy */ +@Slf4j @Service @RequiredArgsConstructor public class IndicatorMergeService implements MergeService { @@ -66,18 +66,10 @@ public class IndicatorMergeService implements MergeService { @Override public MergeRequest validate(@Nonnull MergeParams params, @Nonnull MergeReport mergeReport) { - // sources - Set sources = new HashSet<>(); - validator.verifySources(params.getSources(), sources, mergeReport, Indicator.class); - - // target - validator.checkIsTargetInSources(sources, params.getTarget(), mergeReport, Indicator.class); - - return validator.verifyTarget(mergeReport, sources, params, Indicator.class); + return validator.validateUIDs(params, mergeReport, MergeType.INDICATOR); } @Override - @Transactional public MergeReport merge(@Nonnull MergeRequest request, @Nonnull MergeReport mergeReport) { List sources = indicatorService.getIndicatorsByUid(UID.toValueList(request.getSources())); diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/indicator/IndicatorTypeMergeService.java b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/indicator/IndicatorTypeMergeService.java index 52e850994fb6..af1e94c5c0be 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/indicator/IndicatorTypeMergeService.java +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/indicator/IndicatorTypeMergeService.java @@ -27,11 +27,10 @@ */ package org.hisp.dhis.merge.indicator; -import java.util.HashSet; import java.util.List; -import java.util.Set; import javax.annotation.Nonnull; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.hisp.dhis.common.IdentifiableObjectManager; import org.hisp.dhis.common.UID; import org.hisp.dhis.feedback.MergeReport; @@ -41,15 +40,16 @@ import org.hisp.dhis.merge.MergeParams; import org.hisp.dhis.merge.MergeRequest; import org.hisp.dhis.merge.MergeService; +import org.hisp.dhis.merge.MergeType; import org.hisp.dhis.merge.MergeValidator; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; /** * Main class for indicator type merge. * * @author david mackessy */ +@Slf4j @Service @RequiredArgsConstructor public class IndicatorTypeMergeService implements MergeService { @@ -60,18 +60,10 @@ public class IndicatorTypeMergeService implements MergeService { @Override public MergeRequest validate(@Nonnull MergeParams params, @Nonnull MergeReport mergeReport) { - // sources - Set sources = new HashSet<>(); - validator.verifySources(params.getSources(), sources, mergeReport, IndicatorType.class); - - // target - validator.checkIsTargetInSources(sources, params.getTarget(), mergeReport, IndicatorType.class); - - return validator.verifyTarget(mergeReport, sources, params, IndicatorType.class); + return validator.validateUIDs(params, mergeReport, MergeType.INDICATOR_TYPE); } @Override - @Transactional public MergeReport merge(@Nonnull MergeRequest request, @Nonnull MergeReport mergeReport) { List sources = indicatorService.getIndicatorTypesByUid(UID.toValueList(request.getSources())); diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/indicator/handler/IndicatorMergeHandler.java b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/indicator/handler/IndicatorMergeHandler.java index 128ab80025d9..9b79042121f0 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/indicator/handler/IndicatorMergeHandler.java +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/indicator/handler/IndicatorMergeHandler.java @@ -42,14 +42,14 @@ import org.hisp.dhis.indicator.IndicatorGroup; import org.hisp.dhis.visualization.Visualization; import org.hisp.dhis.visualization.VisualizationService; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; /** * Merge handler for metadata entities. * * @author david mackessy */ -@Service +@Component @RequiredArgsConstructor public class IndicatorMergeHandler { diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/orgunit/handler/AnalyticalObjectOrgUnitMergeHandler.java b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/orgunit/handler/AnalyticalObjectOrgUnitMergeHandler.java index ed72c10f2f70..fa44709ce057 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/orgunit/handler/AnalyticalObjectOrgUnitMergeHandler.java +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/orgunit/handler/AnalyticalObjectOrgUnitMergeHandler.java @@ -37,14 +37,14 @@ import org.hisp.dhis.mapping.MappingService; import org.hisp.dhis.merge.orgunit.OrgUnitMergeRequest; import org.hisp.dhis.visualization.VisualizationService; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; /** * Merge handler for analytical object entities. * * @author Lars Helge Overland */ -@Service +@Component @RequiredArgsConstructor public class AnalyticalObjectOrgUnitMergeHandler { private final VisualizationService visualizationService; diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/orgunit/handler/DataOrgUnitMergeHandler.java b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/orgunit/handler/DataOrgUnitMergeHandler.java index 37a639c69de0..2a96fc59db8c 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/orgunit/handler/DataOrgUnitMergeHandler.java +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/orgunit/handler/DataOrgUnitMergeHandler.java @@ -43,7 +43,7 @@ import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.SqlParameterSource; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; /** @@ -51,7 +51,7 @@ * * @author Lars Helge Overland */ -@Service +@Component @Transactional @RequiredArgsConstructor public class DataOrgUnitMergeHandler { diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/orgunit/handler/MetadataOrgUnitMergeHandler.java b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/orgunit/handler/MetadataOrgUnitMergeHandler.java index 59696d7d98f6..71fcc80d7c52 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/orgunit/handler/MetadataOrgUnitMergeHandler.java +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/orgunit/handler/MetadataOrgUnitMergeHandler.java @@ -43,14 +43,14 @@ import org.hisp.dhis.user.User; import org.hisp.dhis.user.UserQueryParams; import org.hisp.dhis.user.UserService; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; /** * Merge handler for metadata entities. * * @author Lars Helge Overland */ -@Service +@Component @RequiredArgsConstructor public class MetadataOrgUnitMergeHandler { private final UserService userService; diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/orgunit/handler/TrackerOrgUnitMergeHandler.java b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/orgunit/handler/TrackerOrgUnitMergeHandler.java index 859a28a1fb24..e1bb3f8c5212 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/orgunit/handler/TrackerOrgUnitMergeHandler.java +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/orgunit/handler/TrackerOrgUnitMergeHandler.java @@ -31,7 +31,7 @@ import lombok.RequiredArgsConstructor; import org.hisp.dhis.common.IdentifiableObjectUtils; import org.hisp.dhis.merge.orgunit.OrgUnitMergeRequest; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; /** @@ -39,7 +39,7 @@ * * @author Lars Helge Overland */ -@Service +@Component @RequiredArgsConstructor public class TrackerOrgUnitMergeHandler { private final EntityManager entityManager; diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/split/orgunit/handler/MetadataOrgUnitSplitHandler.java b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/split/orgunit/handler/MetadataOrgUnitSplitHandler.java index 47af207314c2..ed87641babea 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/split/orgunit/handler/MetadataOrgUnitSplitHandler.java +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/split/orgunit/handler/MetadataOrgUnitSplitHandler.java @@ -29,14 +29,14 @@ import com.google.common.collect.Sets; import java.util.List; -import java.util.Set; import lombok.RequiredArgsConstructor; +import org.hisp.dhis.common.UID; import org.hisp.dhis.configuration.Configuration; import org.hisp.dhis.configuration.ConfigurationService; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.split.orgunit.OrgUnitSplitRequest; import org.hisp.dhis.user.User; -import org.hisp.dhis.user.UserQueryParams; +import org.hisp.dhis.user.UserOrgUnitProperty; import org.hisp.dhis.user.UserService; import org.springframework.stereotype.Service; @@ -92,11 +92,10 @@ public void splitOrganisationUnits(OrgUnitSplitRequest request) { } public void splitUsers(OrgUnitSplitRequest request) { - Set source = Sets.newHashSet(request.getSource()); + UID sourceOrgUnitUid = UID.of(request.getSource().getUid()); List dataCaptureUsers = - userService.getUsers( - new UserQueryParams().setCanSeeOwnRoles(true).setOrganisationUnits(source)); + userService.getUsersWithOrgUnit(UserOrgUnitProperty.ORG_UNITS, sourceOrgUnitUid); dataCaptureUsers.forEach( u -> { @@ -105,8 +104,7 @@ public void splitUsers(OrgUnitSplitRequest request) { }); List dataViewUsers = - userService.getUsers( - new UserQueryParams().setCanSeeOwnRoles(true).setDataViewOrganisationUnits(source)); + userService.getUsersWithOrgUnit(UserOrgUnitProperty.DATA_VIEW_ORG_UNITS, sourceOrgUnitUid); dataViewUsers.forEach( u -> { @@ -115,8 +113,7 @@ public void splitUsers(OrgUnitSplitRequest request) { }); List teiSearchOrgUnits = - userService.getUsers( - new UserQueryParams().setCanSeeOwnRoles(true).setTeiSearchOrganisationUnits(source)); + userService.getUsersWithOrgUnit(UserOrgUnitProperty.TEI_SEARCH_ORG_UNITS, sourceOrgUnitUid); teiSearchOrgUnits.forEach( u -> { diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks.yaml b/dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks.yaml index 7e0297644979..f8b538046fd3 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks.yaml +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks.yaml @@ -7,6 +7,7 @@ checks: - categories/categories_unique_category_combo.yaml - categories/category_combos_unused.yaml - categories/categories_same_category_options.yaml + - categories/category_option_combos_have_duplicates.yaml - categories/category_option_combinations_cardinality.yaml - categories/categories_shared_category_options_in_combo.yaml - categories/category_option_combinations_disjoint.yaml @@ -15,6 +16,8 @@ checks: - categories/category_option_groups_excess_members.yaml - categories/category_option_groups_sets_incomplete.yaml - categories/invalid_category_combos.yaml + - categories/categories_dimensions_no_visualizations.yaml + - categories/category_option_combinations_no_names.yaml - datasets/datasets_empty.yaml - datasets/datasets_not_assigned_to_org_units.yaml - orgunits/compulsory_orgunit_groups.yaml @@ -52,6 +55,8 @@ checks: - data_elements/aggregate_des_datasets_different_period_types.yaml - data_elements/aggregate_des_nodata.yaml - data_elements/aggregate_des_no_datasets.yaml + - data_elements/aggregate_des_cannot_aggregate_operator_not_none.yaml + - data_elements/aggregate_des_can_aggregate_operator_none.yaml - indicators/indicator_duplicate_types.yaml - indicators/indicator_nongrouped.yaml - indicators/indicator_noanalysis.yaml diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/categories/categories_dimensions_no_visualizations.yaml b/dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/categories/categories_dimensions_no_visualizations.yaml new file mode 100644 index 000000000000..6c137c6ea199 --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/categories/categories_dimensions_no_visualizations.yaml @@ -0,0 +1,86 @@ +# 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. +# +--- +name: categories_dimensions_no_visualizations +description: Categories which are enabled as a data dimension but which have no associated visualizations. +section: Categories +summary_sql: >- + select + COUNT(*) as value, + 100 * COUNT(*) / NULLIF( (SELECT COUNT(*) FROM category), 0) as percent + from category where categoryid + in ( + SELECT categoryid + FROM category where datadimension = TRUE + AND categoryid NOT IN ( + SELECT categoryid from + categorydimension where categorydimensionid IN ( + SELECT categorydimensionid + from eventvisualization_categorydimensions + UNION + SELECT categorydimensionid + from mapview_categorydimensions + UNION + SELECT categorydimensionid + FROM visualization_categorydimensions + ))); +details_sql: >- + SELECT uid,name,datadimensiontype as comment + FROM category where datadimension = TRUE + AND categoryid NOT IN ( + SELECT categoryid from + categorydimension where categorydimensionid IN ( + SELECT categorydimensionid + from eventvisualization_categorydimensions + UNION + SELECT categorydimensionid + from mapview_categorydimensions + UNION + SELECT categorydimensionid + FROM visualization_categorydimensions + ) + ); +details_id_type: categories +severity: WARNING +introduction: > + Categories can be enabled as data dimensions. + This is useful for generating sub-totals for category combos + which consist of more than one category. For each category + which is enabled as a dimension, a column in the analytics + tables is created and then indexed. If many categories + are enabled as a dimensions, this may lead to an increase + in the amount of time which is required to export the analytics tables. + If the category is never used for any analytical purpose (map, chart, table, etc) + or to generate sub-totals in a data set report, + then there is little point to enable it as a dimension. +section_order: 17 +recommendation: > + Review the list of categories which have been flagged by this integrity check + and consider whether they are actually needed as dimensions. If they are not + needed, then disable them as dimensions. If they are needed, then create + visualizations which use them. Categories can always be re-enabled as dimensions + if they are needed once again. diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/categories/category_option_combinations_no_names.yaml b/dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/categories/category_option_combinations_no_names.yaml new file mode 100644 index 000000000000..d95e3b0d6c81 --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/categories/category_option_combinations_no_names.yaml @@ -0,0 +1,53 @@ +# 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. +# +--- +name: category_option_combos_no_names +description: Category option combinations with no names. +section: Categories +section_order: 8 +summary_sql: >- + WITH coc_no_names as ( + SELECT uid,name FROM categoryoptioncombo WHERE name IS NULL OR name = '' + ) + SELECT COUNT(*) as value, + 100 * COUNT(*) / NULLIF( (SELECT COUNT(*) FROM categoryoptioncombo), 0) as percent + FROM coc_no_names; +details_sql: >- + SELECT uid,name FROM categoryoptioncombo WHERE name IS NULL OR name = ''; +details_id_type: categoryOptionCombos +severity: SEVERE +introduction: > + This check will find all category option combinations that do not have a name. Category option + combinations should have a name to help identify them. Normally, names for category option combinations + should be automatically generated based on the category options that are associated with the category + but it is possible to manually set the name for a category option combination either via the API + or directly in the database. Category option combinations with no names should be reviewed to + determine if the name should be set or if the category option combination should be removed. +recommendation: > + Review the category option combinations with no names and set the name for the category option + combination. If the category option combination is not needed, it should be removed. Alternatively, + the name can be set to a value which is more meaningful to the user. \ No newline at end of file diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/categories/category_option_combos_have_duplicates.yaml b/dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/categories/category_option_combos_have_duplicates.yaml new file mode 100644 index 000000000000..9605f198178e --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/categories/category_option_combos_have_duplicates.yaml @@ -0,0 +1,125 @@ +# Copyright (c) 2004-2024, 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. +# +--- +name: category_option_combos_have_duplicates +description: Category option combo duplicates, same category options and same category combo +section: Categories +section_order: 6 +summary_sql: >- + WITH coc_long AS ( + SELECT coc.name as coc_name,coc.uid as coc_uid, + cc.uid as cc_uid,cc.name as cc_name, + co.uid as co_uid,co.name as co_name + FROM( + SELECT a.categorycomboid, + b.categoryoptioncomboid, + b.categoryoptionid + FROM categorycombos_optioncombos a + RIGHT OUTER JOIN categoryoptioncombos_categoryoptions b on a.categoryoptioncomboid = b.categoryoptioncomboid + ORDER BY a.categorycomboid,b.categoryoptionid + + ) as x + JOIN categorycombo cc on cc.categorycomboid = x.categorycomboid + JOIN categoryoptioncombo coc on coc.categoryoptioncomboid = x.categoryoptioncomboid + JOIN categoryoption co on co.categoryoptionid = x.categoryoptionid + ORDER BY coc.name,cc.name,co.name + ), + coc_wide AS ( + SELECT coc_name,coc_uid, + array_agg(cc_uid) as cc_uids, + array_agg(cc_name) as cc_names, + array_agg(co_uid) as co_uids, + array_agg(co_name) as co_names + FROM coc_long + GROUP BY coc_name,coc_uid + ), + coc_summary AS( + SELECT cc_uids,cc_names,co_uids,co_names,COUNT(*) as coc_count + FROM coc_wide + GROUP BY cc_uids,cc_names,co_uids,co_names + HAVING COUNT(*) > 1 + ) + SELECT COUNT(*) as value, + 100.0 * COUNT(*) / NULLIF( (SELECT COUNT(*) + FROM categoryoptioncombo),0 ) percent + FROM coc_summary; +details_sql: >- + WITH coc_long AS ( + SELECT coc.name as coc_name,coc.uid as coc_uid, + cc.uid as cc_uid,cc.name as cc_name, + co.uid as co_uid,co.name as co_name + FROM( + SELECT a.categorycomboid, + b.categoryoptioncomboid, + b.categoryoptionid + FROM categorycombos_optioncombos a + RIGHT OUTER JOIN categoryoptioncombos_categoryoptions b on a.categoryoptioncomboid = b.categoryoptioncomboid + ORDER BY a.categorycomboid,b.categoryoptionid + + ) as x + JOIN categorycombo cc on cc.categorycomboid = x.categorycomboid + JOIN categoryoptioncombo coc on coc.categoryoptioncomboid = x.categoryoptioncomboid + JOIN categoryoption co on co.categoryoptionid = x.categoryoptionid + ORDER BY coc.name,cc.name,co.name + ), + coc_wide AS ( + SELECT coc_name,coc_uid, + array_agg(cc_uid) as cc_uids, + array_agg(cc_name) as cc_names, + array_agg(co_uid) as co_uids, + array_agg(co_name) as co_names + FROM coc_long + GROUP BY coc_name,coc_uid + ), + coc_summary AS( + SELECT cc_uids,cc_names,co_uids,co_names,COUNT(*) as coc_count + FROM coc_wide + GROUP BY cc_uids,cc_names,co_uids,co_names + HAVING COUNT(*) > 1 + ) + SELECT coc_wide.coc_uid as uid, + coc_wide.coc_name as name, + array_to_string(coc_wide.cc_names,',') as comment, + coc_wide.cc_names as refs + FROM coc_wide + JOIN coc_summary on coc_wide.cc_uids = coc_summary.cc_uids and coc_wide.co_uids = coc_summary.co_uids +severity: WARNING +introduction: > + Category option combos with the exact same category options should be considered + to be merged when they associated with the same category combination. + Category option combos with the exact same category options + may be easily confused by users in analysis. The details view will + provide a list of category option combos which have the exact same category options along + with the category combo that they are associated with. +details_id_type: categoryOptionCombos +recommendation: | + Duplicate category option combinations can arise from a number of different factors. Metadata + may have been imported which may lead to duplicates in certain situations. Other causes + may include manual creation of these category option combinations through either the database + or the API. Once you have identified the potential duplicates, you should consider merging + these category option combinations. + diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/data_elements/aggregate_des_can_aggregate_operator_none.yaml b/dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/data_elements/aggregate_des_can_aggregate_operator_none.yaml new file mode 100644 index 000000000000..3c7ce338c739 --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/data_elements/aggregate_des_can_aggregate_operator_none.yaml @@ -0,0 +1,58 @@ +# 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. +# +--- + name: data_elements_can_aggregate_with_none_operator + description: Aggregate domain data elements which can be aggregated but have an aggregation operator set to NONE + section: Data elements (aggregate) + section_order: 9 + summary_sql: >- + with des_aggregation_op as ( + SELECT uid,name,valuetype,aggregationtype from dataelement + where valuetype IN ('BOOLEAN', 'TRUE_ONLY', 'NUMBER', 'UNIT_INTERVAL', 'PERCENTAGE', 'INTEGER', 'INTEGER_POSITIVE', + 'INTEGER_NEGATIVE','INTEGER_ZERO_OR_POSITIVE' ) + AND aggregationtype = 'NONE' + AND domaintype = 'AGGREGATE' + ) + SELECT COUNT(*) as value, + 100*COUNT(*) / NULLIF( ( SELECT COUNT(*) from dataelement WHERE domaintype = 'AGGREGATE'), 0 ) as percent + from des_aggregation_op; + details_sql: >- + SELECT uid,name from dataelement + where valuetype IN ('BOOLEAN', 'TRUE_ONLY', 'NUMBER', 'UNIT_INTERVAL', 'PERCENTAGE', 'INTEGER', 'INTEGER_POSITIVE', + 'INTEGER_NEGATIVE','INTEGER_ZERO_OR_POSITIVE' ) + AND aggregationtype = 'NONE' + AND domaintype = 'AGGREGATE'; + severity: WARNING + introduction: > + Aggregate domain data elements which can be aggregated should have their aggregation operator set to some + aggregation operator other than NONE. + If the aggregation operator is set to NONE, this may lead to unexpected results + in the analytics modules. + recommendation: > + Open the affected data elements in the Maintenance App and change their aggregation type + to an appropriate type, such as SUM. Alternatively, these can be altered via the API. + details_id_type: dataElements diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/data_elements/aggregate_des_cannot_aggregate_operator_not_none.yaml b/dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/data_elements/aggregate_des_cannot_aggregate_operator_not_none.yaml new file mode 100644 index 000000000000..82b2a6d98ea3 --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/data_elements/aggregate_des_cannot_aggregate_operator_not_none.yaml @@ -0,0 +1,62 @@ +# 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. +# +--- + name: data_elements_cannot_aggregate_operator_not_none + description: Aggregate data elements which cannot be aggregated but have an aggregation operator set to something + other than NONE + section: Data elements (aggregate) + section_order: 9 + summary_sql: >- + with des_aggregation_op as ( + SELECT uid,name,valuetype,aggregationtype from dataelement + where valuetype IN ('TEXT', 'LONG_TEXT', 'LETTER', + 'PHONE_NUMBER','EMAIL','DATE','DATETIME','TIME','USERNAME', + 'TRACKER_ASSOCIATE','ORGANISATION_UNIT','REFERENCE','AGE','URL', + 'FILE_RESOURCE','IMAGE','GEOJSON','MULTI_TEXT') + AND aggregationtype != 'NONE' + AND domaintype = 'AGGREGATE' + ) + SELECT COUNT(*) as value, + 100*COUNT(*) / NULLIF( ( SELECT COUNT(*) from dataelement WHERE domaintype = 'AGGREGATE'), 0 ) as percent + from des_aggregation_op; + details_sql: >- + SELECT uid,name from dataelement + where valuetype IN ('TEXT', 'LONG_TEXT', 'LETTER', + 'PHONE_NUMBER','EMAIL','DATE','DATETIME','TIME','USERNAME', + 'TRACKER_ASSOCIATE','ORGANISATION_UNIT','REFERENCE','AGE','URL', + 'FILE_RESOURCE','IMAGE','GEOJSON','MULTI_TEXT') + AND aggregationtype != 'NONE' + AND domaintype = 'AGGREGATE'; + severity: WARNING + introduction: > + Aggregate domain data elements which cannot be aggregated should have their aggregation operator set to NONE. + If the aggregation operator is set to something other than NONE, this may lead to unexpected results + in the analytics modules. + recommendation: > + Open the affected data elements in the Maintenance App and change their aggregation type + to NONE. Alternatively, these can be altered via the API. + details_id_type: dataElements diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/data_elements/aggregate_des_inconsistent_agg_operator.yaml b/dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/data_elements/aggregate_des_inconsistent_agg_operator.yaml deleted file mode 100644 index a8fe12d60682..000000000000 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/data_elements/aggregate_des_inconsistent_agg_operator.yaml +++ /dev/null @@ -1,72 +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. -# ---- - name: data_elements_aggregate_aggregation_operator - description: Non-numeric data elements which have an aggregation operator other than NONE. - section: Data elements (aggregate) - section_order: 8 - summary_sql: >- - with des_aggregation_op as ( - SELECT uid,name,valuetype,aggregationtype from dataelement - where valuetype IN ('TEXT', 'LONG_TEXT', 'LETTER', - 'PHONE_NUMBER','EMAIL','DATE','DATETIME','TIME','USERNAME', - 'TRACKER_ASSOCIATE','ORGANISATION_UNIT','REFERENCE','AGE','URL', - 'FILE_RESOURCE','IMAGE','GEOJSON','MULTI_TEXT') - AND aggregationtype != 'NONE' - UNION - SELECT uid,name,valuetype,aggregationtype from dataelement - where valuetype IN ('BOOLEAN', 'TRUE_ONLY', 'NUMBER', 'UNIT_INTERVAL', 'PERCENTAGE', 'INTEGER', 'INTEGER_POSITIVE', - 'INTEGER_NEGATIVE','INTEGER_ZERO_OR_POSITIVE' ) - AND aggregationtype = 'NONE' - - ) - SELECT COUNT(*) as value, - 100*COUNT(*) / NULLIF( ( SELECT COUNT(*) from dataelement), 0 ) as percent - from des_aggregation_op; - details_sql: >- - SELECT uid,name,valuetype || ' (' || aggregationtype || ')' as comment - FROM dataelement - WHERE valuetype IN ('TEXT', 'LONG_TEXT', 'LETTER', - 'PHONE_NUMBER','EMAIL','DATE','DATETIME','TIME','USERNAME', - 'TRACKER_ASSOCIATE','ORGANISATION_UNIT','REFERENCE','AGE','URL', - 'FILE_RESOURCE','IMAGE','GEOJSON','MULTI_TEXT') - AND aggregationtype != 'NONE' - UNION - SELECT uid,name,valuetype || ' (' || aggregationtype || ')' as comment from dataelement - where valuetype IN ('BOOLEAN', 'TRUE_ONLY', 'NUMBER', 'UNIT_INTERVAL', 'PERCENTAGE', 'INTEGER', 'INTEGER_POSITIVE', - 'INTEGER_NEGATIVE','INTEGER_ZERO_OR_POSITIVE' ) - AND aggregationtype = 'NONE' - ORDER BY name; - severity: WARNING - introduction: > - Data elements which are not numeric (text, dates, etc) should have an aggregation - operator set to NONE. Data elements which are able to be aggregated (numbers, integers, etc) - should have an aggregation operator set to something other than NONE, most often SUM. - recommendation: > - Open the affected data elements in the Maintenance App and change their aggregation type - to an appropriate type. Alternatively, these can be altered via the API. - details_id_type: dataElements diff --git a/dhis-2/dhis-services/dhis-service-administration/src/test/java/org/hisp/dhis/dataintegrity/DataIntegrityYamlReaderTest.java b/dhis-2/dhis-services/dhis-service-administration/src/test/java/org/hisp/dhis/dataintegrity/DataIntegrityYamlReaderTest.java index 8e9def56b8c5..45caf579b787 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/test/java/org/hisp/dhis/dataintegrity/DataIntegrityYamlReaderTest.java +++ b/dhis-2/dhis-services/dhis-service-administration/src/test/java/org/hisp/dhis/dataintegrity/DataIntegrityYamlReaderTest.java @@ -38,6 +38,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.ResourceBundle; import java.util.Set; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -56,7 +57,7 @@ void testReadDataIntegrityYaml() { List checks = new ArrayList<>(); readYaml(checks, "data-integrity-checks.yaml", "data-integrity-checks", CLASS_PATH); - assertEquals(79, checks.size()); + assertEquals(83, checks.size()); // Names should be unique List allNames = checks.stream().map(DataIntegrityCheck::getName).toList(); @@ -141,6 +142,29 @@ void testWithInvalidYamlFormat() { assertEquals(0, checks.size()); } + @Test + void testChecksHaveTranslations() { + ResourceBundle resourceBundle = ResourceBundle.getBundle("i18n_global"); + + List translationsSuffix = new ArrayList<>(); + /* Require the name only for now, but we can add the other translations strings later when needed. + e.g. description, introduction, recommendation, section + */ + translationsSuffix.add("name"); + + List checks = new ArrayList<>(); + readYaml(checks, "data-integrity-checks.yaml", "data-integrity-checks", CLASS_PATH); + // Check for names + for (DataIntegrityCheck check : checks) { + for (String suffix : translationsSuffix) { + String translationKey = "data_integrity." + check.getName() + "." + suffix; + assertTrue( + resourceBundle.containsKey(translationKey), + "data integrity check translations should contain " + translationKey); + } + } + } + private void readYaml( List checks, String fileChecks, diff --git a/dhis-2/dhis-services/dhis-service-administration/src/test/java/org/hisp/dhis/merge/dataelement/DataElementMergeValidatorTest.java b/dhis-2/dhis-services/dhis-service-administration/src/test/java/org/hisp/dhis/merge/dataelement/DataElementMergeValidatorTest.java index 50d06ab94408..7610c9c7280d 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/test/java/org/hisp/dhis/merge/dataelement/DataElementMergeValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-administration/src/test/java/org/hisp/dhis/merge/dataelement/DataElementMergeValidatorTest.java @@ -38,7 +38,6 @@ import org.hisp.dhis.dataelement.DataElementDomain; import org.hisp.dhis.feedback.ErrorCode; import org.hisp.dhis.feedback.MergeReport; -import org.hisp.dhis.merge.MergeType; import org.hisp.dhis.test.TestBase; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -58,8 +57,7 @@ void whenAllValueTypesMatchThenNoError() { // when DataElementMergeValidator validator = new DataElementMergeValidator(); MergeReport report = - validator.validateValueType( - target, List.of(source1, source2, source3), new MergeReport(MergeType.DATA_ELEMENT)); + validator.validateValueType(target, List.of(source1, source2, source3), new MergeReport()); // then assertFalse(report.hasErrorMessages()); @@ -78,12 +76,11 @@ void when1ValueTypeDoesNotMatchThenError() { // when DataElementMergeValidator validator = new DataElementMergeValidator(); MergeReport report = - validator.validateValueType( - target, List.of(source1, source2, source3), new MergeReport(MergeType.DATA_ELEMENT)); + validator.validateValueType(target, List.of(source1, source2, source3), new MergeReport()); // then assertTrue(report.hasErrorMessages()); - assertEquals(ErrorCode.E1554, report.getMergeErrors().get(0).getErrorCode()); + assertEquals(ErrorCode.E1550, report.getMergeErrors().get(0).getErrorCode()); assertEquals( "All source ValueTypes must match target ValueType: `TEXT`. Other ValueTypes found: `[NUMBER]`", report.getMergeErrors().get(0).getMessage()); @@ -102,12 +99,11 @@ void whenMultipleValueTypeDoesNotMatchThenError() { // when DataElementMergeValidator validator = new DataElementMergeValidator(); MergeReport report = - validator.validateValueType( - target, List.of(source1, source2, source3), new MergeReport(MergeType.DATA_ELEMENT)); + validator.validateValueType(target, List.of(source1, source2, source3), new MergeReport()); // then assertTrue(report.hasErrorMessages()); - assertEquals(ErrorCode.E1554, report.getMergeErrors().get(0).getErrorCode()); + assertEquals(ErrorCode.E1550, report.getMergeErrors().get(0).getErrorCode()); assertEquals( "All source ValueTypes must match target ValueType: `TEXT`. Other ValueTypes found: `[NUMBER, DATE]`", report.getMergeErrors().get(0).getMessage()); @@ -130,8 +126,7 @@ void whenAllDomainTypesMatchThenNoError() { // when DataElementMergeValidator validator = new DataElementMergeValidator(); MergeReport report = - validator.validateDomainType( - target, List.of(source1, source2, source3), new MergeReport(MergeType.DATA_ELEMENT)); + validator.validateDomainType(target, List.of(source1, source2, source3), new MergeReport()); // then assertFalse(report.hasErrorMessages()); @@ -154,12 +149,11 @@ void whenDomainTypeDoNotMatchThenError() { // when DataElementMergeValidator validator = new DataElementMergeValidator(); MergeReport report = - validator.validateDomainType( - target, List.of(source1, source2, source3), new MergeReport(MergeType.DATA_ELEMENT)); + validator.validateDomainType(target, List.of(source1, source2, source3), new MergeReport()); // then assertTrue(report.hasErrorMessages()); - assertEquals(ErrorCode.E1555, report.getMergeErrors().get(0).getErrorCode()); + assertEquals(ErrorCode.E1551, report.getMergeErrors().get(0).getErrorCode()); assertEquals( "All source DataElementDomains must match target DataElementDomain: `AGGREGATE`. Other DataElementDomains found: `[TRACKER]`", report.getMergeErrors().get(0).getMessage()); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/AnalyticsTableUpdateParams.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/AnalyticsTableUpdateParams.java index 1dd90d192c58..8790cc8e6740 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/AnalyticsTableUpdateParams.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/AnalyticsTableUpdateParams.java @@ -81,13 +81,27 @@ public class AnalyticsTableUpdateParams { /** Current date, only used for testing */ private final Date today; - /** free key-value map for extra parameters. */ + /** Map of arbitrary extra parameters. */ @Builder.Default private final Map extraParameters = new HashMap<>(); + /** + * Adds an extra parameter. + * + * @param prefix the parameter key prefix. + * @param key the parameter key. + * @param value the parameter value. + */ public void addExtraParam(String prefix, String key, Object value) { extraParameters.put(prefix + key, value); } + /** + * Retrieves the extra parameter with the given key. + * + * @param prefix the parameter key prefix. + * @param key the parameter key. + * @return a parameter object. + */ public Object getExtraParam(String prefix, String key) { return extraParameters.get(prefix + key); } @@ -152,6 +166,15 @@ public Date getFromDate() { return earliest; } + /** + * Indicates whether a from date exists. + * + * @return true if a from date exists. + */ + public boolean hasFromDate() { + return getFromDate() != null; + } + public AnalyticsTableUpdateParams withLatestPartition() { return this.toBuilder().lastYears(AnalyticsTablePartition.LATEST_PARTITION).build(); } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/MeasureFilter.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/MeasureFilter.java index fa0a5cf738b5..f23978eda067 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/MeasureFilter.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/MeasureFilter.java @@ -47,19 +47,12 @@ public enum MeasureFilter { * @return true if the constraint/filter is valid when x is compared with y. */ public boolean measureIsValid(Double x, Double y) { - switch (this) { - case EQ: - return Double.compare(x, y) == 0; - case GT: - return Double.compare(x, y) > 0; - case GE: - return Double.compare(x, y) >= 0; - case LT: - return Double.compare(x, y) < 0; - case LE: - return Double.compare(x, y) <= 0; - default: - return false; - } + return switch (this) { + case EQ -> Double.compare(x, y) == 0; + case GT -> Double.compare(x, y) > 0; + case GE -> Double.compare(x, y) >= 0; + case LT -> Double.compare(x, y) < 0; + case LE -> Double.compare(x, y) <= 0; + }; } } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/OrgUnitField.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/OrgUnitField.java index 3cf320319be2..f16cbb92e06f 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/OrgUnitField.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/OrgUnitField.java @@ -43,6 +43,8 @@ import lombok.EqualsAndHashCode; import lombok.Getter; +import org.hisp.dhis.db.sql.PostgreSqlBuilder; +import org.hisp.dhis.db.sql.SqlBuilder; import org.hisp.dhis.program.AnalyticsType; /** @@ -83,6 +85,8 @@ public class OrgUnitField { private final OrgUnitFieldType type; + private SqlBuilder sqlBuilder = new PostgreSqlBuilder(); + public OrgUnitField(String field) { this.field = field; @@ -197,7 +201,7 @@ private String getOuUidColumn(AnalyticsType analyticsType) { private String getTableAndColumn(String tableAlias, String col, boolean noColumnAlias) { if (type.isOwnership()) { return "coalesce(" - + quote(OWNERSHIP_TBL_ALIAS, col) + + sqlBuilder.quote(OWNERSHIP_TBL_ALIAS, col) + "," + ouQuote(tableAlias, col, true) + ")" @@ -215,7 +219,8 @@ private String getTableAndColumn(String tableAlias, String col, boolean noColumn */ private String ouQuote(String tableAlias, String col, boolean noColumnAlias) { if (ORG_UNIT_STRUCT_ALIAS.equals(tableAlias) && DEFAULT_ORG_UNIT_COL.equals(col)) { - return quote(tableAlias, "organisationunituid") + ((noColumnAlias) ? "" : " as " + col); + return sqlBuilder.quote(tableAlias, "organisationunituid") + + ((noColumnAlias) ? "" : " as " + col); } return quote(tableAlias, col); @@ -225,4 +230,9 @@ private String ouQuote(String tableAlias, String col, boolean noColumnAlias) { public String toString() { return "[" + field + "-" + type + "]"; } + + public OrgUnitField withSqlBuilder(SqlBuilder sqlBuilder) { + this.sqlBuilder = sqlBuilder; + return this; + } } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/ValueTypeMapping.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/ValueTypeMapping.java index bac5c446b1f2..3710ae0736cf 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/ValueTypeMapping.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/ValueTypeMapping.java @@ -71,7 +71,7 @@ public enum ValueTypeMapping { @Getter private final UnaryOperator argumentTransformer; @Getter private final String postgresCast; - ValueTypeMapping(Function converter, Class... classes) { + ValueTypeMapping(Function converter, Class... classes) { this.converter = converter; this.valueTypes = fromClasses(classes); this.selectTransformer = UnaryOperator.identity(); @@ -86,7 +86,7 @@ public enum ValueTypeMapping { * @param classes the classes to be converted * @return the respective {@link ValueType} array */ - private ValueType[] fromClasses(Class... classes) { + private ValueType[] fromClasses(Class... classes) { return stream(ValueType.values()) .filter(valueType -> isAssignableFrom(classes, valueType)) .toArray(ValueType[]::new); @@ -99,14 +99,14 @@ private ValueType[] fromClasses(Class... classes) { * @param valueType the {@link ValueType} to be checked * @return true if the {@link ValueType} is assignable from the given classes, false otherwise */ - private static boolean isAssignableFrom(Class[] classes, ValueType valueType) { + private static boolean isAssignableFrom(Class[] classes, ValueType valueType) { return stream(classes).anyMatch(valueType.getJavaClass()::isAssignableFrom); } ValueTypeMapping( Function converter, UnaryOperator selectTransformer, - Class... classes) { + Class... classes) { this.converter = converter; this.valueTypes = fromClasses(classes); this.selectTransformer = s -> Objects.isNull(s) ? null : selectTransformer.apply(s); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/query/Field.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/query/Field.java index 1afc5227b147..859365e94498 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/query/Field.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/query/Field.java @@ -39,19 +39,17 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.With; -import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.hisp.dhis.analytics.common.params.dimension.DimensionIdentifier; import org.hisp.dhis.analytics.common.params.dimension.DimensionParam; import org.springframework.lang.NonNull; /** - * This class represents a {@link Renderable} field. It's mainly used for SQL query rendering and + * This class represents a {@link Renderable} field. It is mainly used for SQL query rendering and * headers display. */ @RequiredArgsConstructor(staticName = "of", access = AccessLevel.PRIVATE) @AllArgsConstructor(access = AccessLevel.PRIVATE) -@Slf4j public class Field extends BaseRenderable { private final String tableAlias; @@ -63,10 +61,10 @@ public class Field extends BaseRenderable { private final Boolean quotingNeeded; - /** a flag to indicate whether the field will be used in the headers */ + /** Indicates whether the field will be used in the headers. */ @With @Getter private final boolean usedInHeaders; - /** virtual fields won't be added to the select clause */ + /** Transient fields which is not added to the select clause. */ @With @Getter private final boolean virtual; // A cached version of the rendered field. diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/query/GroupableCondition.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/query/GroupableCondition.java index bb652ed1b7d8..fc6cd91f4e99 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/query/GroupableCondition.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/query/GroupableCondition.java @@ -30,7 +30,7 @@ import lombok.Data; import lombok.RequiredArgsConstructor; -/** Class to represent a renderable with it's groupId. */ +/** Class to represent a renderable with groupId. */ @Data @RequiredArgsConstructor(staticName = "of") public class GroupableCondition { diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/query/InOrEqConditionRenderer.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/query/InOrEqConditionRenderer.java index 22af5a4b195c..c323722531ed 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/query/InOrEqConditionRenderer.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/query/InOrEqConditionRenderer.java @@ -32,8 +32,8 @@ import lombok.RequiredArgsConstructor; /** - * A condition renderer which renders a condition with an IN or EQ operator, depending on the size - * of values + * A condition renderer which renders a condition with an IN or EQ operator, depending on the number + * of values. */ @RequiredArgsConstructor(staticName = "of") public class InOrEqConditionRenderer extends BaseRenderable { diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/query/IndexedOrder.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/query/IndexedOrder.java index 27f0725d7c4e..7ddf49bac378 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/query/IndexedOrder.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/query/IndexedOrder.java @@ -30,7 +30,7 @@ import lombok.Data; import lombok.RequiredArgsConstructor; -/** Class to represent a renderable with it's index. */ +/** Class to represent a renderable with index. */ @Data @RequiredArgsConstructor(staticName = "of") public class IndexedOrder { diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultAnalyticsService.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultAnalyticsService.java index a28bc342529c..71abe0d413a9 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultAnalyticsService.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultAnalyticsService.java @@ -33,12 +33,19 @@ import static org.hisp.dhis.analytics.util.AnalyticsUtils.getDataValueSetAsGrid; import static org.hisp.dhis.analytics.util.AnalyticsUtils.isTableLayout; import static org.hisp.dhis.commons.collection.ListUtils.removeEmptys; +import static org.hisp.dhis.feedback.ErrorCode.E7147; import static org.hisp.dhis.visualization.Visualization.addListIfEmpty; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.Set; import lombok.RequiredArgsConstructor; +import org.apache.commons.collections4.CollectionUtils; import org.hisp.dhis.analytics.AnalyticsSecurityManager; import org.hisp.dhis.analytics.AnalyticsService; import org.hisp.dhis.analytics.DataQueryParams; @@ -52,6 +59,7 @@ import org.hisp.dhis.common.DimensionalItemObject; import org.hisp.dhis.common.Grid; import org.hisp.dhis.common.IdentifiableObjectUtils; +import org.hisp.dhis.common.IllegalQueryException; import org.hisp.dhis.dxf2.datavalueset.DataValueSet; import org.hisp.dhis.system.grid.ListGrid; import org.hisp.dhis.visualization.Visualization; @@ -64,6 +72,7 @@ @Service("org.hisp.dhis.analytics.AnalyticsService") @RequiredArgsConstructor public class DefaultAnalyticsService implements AnalyticsService { + private final AnalyticsSecurityManager securityManager; private final QueryValidator queryValidator; @@ -220,6 +229,8 @@ private Grid getAggregatedDataValuesTableLayout( List> tableColumns = new ArrayList<>(); List> tableRows = new ArrayList<>(); + Map> columnsDimensionItemsByDimension = new HashMap<>(); + Map> rowsDimensionItemsByDimension = new HashMap<>(); if (columns != null) { for (String dimension : columns) { @@ -227,7 +238,10 @@ private Grid getAggregatedDataValuesTableLayout( dimension, params.getDimension(dimension).getDimensionType()); visualization.getColumnDimensions().add(dimension); - tableColumns.add(params.getDimensionItemsExplodeCoc(dimension)); + List dimensionItemsExplodeCoc = + params.getDimensionItemsExplodeCoc(dimension); + columnsDimensionItemsByDimension.put(dimension, dimensionItemsExplodeCoc); + tableColumns.add(dimensionItemsExplodeCoc); } } @@ -237,14 +251,32 @@ private Grid getAggregatedDataValuesTableLayout( dimension, params.getDimension(dimension).getDimensionType()); visualization.getRowDimensions().add(dimension); - tableRows.add(params.getDimensionItemsExplodeCoc(dimension)); + + List dimensionItemsExplodeCoc = + params.getDimensionItemsExplodeCoc(dimension); + rowsDimensionItemsByDimension.put(dimension, dimensionItemsExplodeCoc); + tableRows.add(dimensionItemsExplodeCoc); } } + List> gridColumns = + Optional.ofNullable(columns) + .map(cols -> getGridItems(grid, columnsDimensionItemsByDimension, cols)) + .filter(CollectionUtils::isNotEmpty) + .map(this::reorderItems) + .orElseGet(() -> CombinationGenerator.newInstance(tableColumns).getCombinations()); + + List> gridRows = + Optional.ofNullable(rows) + .map(rws -> getGridItems(grid, rowsDimensionItemsByDimension, rws)) + .filter(CollectionUtils::isNotEmpty) + .map(this::reorderItems) + .orElseGet(() -> CombinationGenerator.newInstance(tableRows).getCombinations()); + visualization .setGridTitle(IdentifiableObjectUtils.join(params.getFilterItems())) - .setGridColumns(CombinationGenerator.newInstance(tableColumns).getCombinations()) - .setGridRows(CombinationGenerator.newInstance(tableRows).getCombinations()); + .setGridColumns(gridColumns) + .setGridRows(gridRows); addListIfEmpty(visualization.getGridColumns()); addListIfEmpty(visualization.getGridRows()); @@ -261,4 +293,65 @@ private Grid getAggregatedDataValuesTableLayout( params.getDisplayProperty(), false); } + + private List> reorderItems( + List> alternateItems) { + return alternateItems.stream().sorted(this::compareItems).toList(); + } + + private int compareItems(List dio, List other) { + return dio.get(0).getDisplayName().compareTo(other.get(0).getDisplayName()); + } + + /** + * Returns alternative grid dimensional items based on the given grid and all dimension items. + * Alternative grid items are used improve the performance of the grid rendering when the + * combination of dimension items is large. The alternative grid items are a list of lists of + * dimension items where each list represents a possible combination of dimension items that are + * used to render the grid. + * + * @param grid the grid + * @param dimensionItemsByDimension a map of dimension items by dimension + * @param dimensionIds the dimension ids + * @return the alternative grid items + */ + static List> getGridItems( + Grid grid, + Map> dimensionItemsByDimension, + List dimensionIds) { + Set> alternateItems = new HashSet<>(); + + // Last column is the value column. + int metaCount = grid.getWidth() - 1; + + for (List row : grid.getRows()) { + DimensionalItemObject[] alternateItem = new DimensionalItemObject[dimensionIds.size()]; + + for (int i = 0; i < metaCount; i++) { + // Header name is the dimension id. + String headerName = grid.getHeaders().get(i).getName(); + String value = row.get(i).toString(); + if (isDimension(headerName, dimensionItemsByDimension)) { + int indexInColumn = dimensionIds.indexOf(headerName); + alternateItem[indexInColumn] = findValueInDimensionItem(dimensionItemsByDimension, value); + } + } + alternateItems.add(Arrays.stream(alternateItem).toList()); + } + return new ArrayList<>(alternateItems); + } + + private static DimensionalItemObject findValueInDimensionItem( + Map> dimensionItemsByDimension, String value) { + return dimensionItemsByDimension.values().stream() + .flatMap(List::stream) + .filter(dio -> dio.getDimensionItem().equals(value)) + .findFirst() + .orElseThrow(() -> new IllegalQueryException(E7147, value)); + } + + private static boolean isDimension( + String dimensionUid, Map> rowsDimensionItemsByDimension) { + return rowsDimensionItemsByDimension.containsKey(dimensionUid); + } } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultDataQueryService.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultDataQueryService.java index 195fa7f309cc..6ed9ff5c5838 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultDataQueryService.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultDataQueryService.java @@ -63,6 +63,7 @@ import java.util.Locale; import java.util.Objects; import java.util.Optional; +import java.util.Set; import lombok.RequiredArgsConstructor; import org.hisp.dhis.analytics.AnalyticsSecurityManager; import org.hisp.dhis.analytics.DataQueryParams; @@ -92,7 +93,7 @@ @Service("org.hisp.dhis.analytics.DataQueryService") @RequiredArgsConstructor public class DefaultDataQueryService implements DataQueryService { - private final DimensionalObjectProducer dimensionalObjectProducer; + private final DimensionalObjectProvider dimensionalObjectProducer; private final IdentifiableObjectManager idObjectManager; @@ -296,7 +297,7 @@ public List getUserOrgUnits(DataQueryParams params, String use units.addAll(currentUser.getOrganisationUnits().stream().sorted().toList()); break; case DATA_OUTPUT: - units.addAll(currentUser.getDataViewOrganisationUnits().stream().sorted().toList()); + units.addAll(getAnalyticsOrganisationUnitsOrDefault(currentUser)); break; case TEI_SEARCH: units.addAll(currentUser.getTeiSearchOrganisationUnits().stream().sorted().toList()); @@ -309,6 +310,30 @@ public List getUserOrgUnits(DataQueryParams params, String use return units; } + /** + * Retrieve the list of organisation units to which the current user has access rights. If the + * user has analytics organisation units assigned, those will be returned. Otherwise, it returns + * the default ones (data capture organisation units). + * + * @param currentUser {@link User} + * @return a list of {@link OrganisationUnit}. + */ + private List getAnalyticsOrganisationUnitsOrDefault(User currentUser) { + Set organisationUnits = currentUser.getDataViewOrganisationUnits(); + if (organisationUnits != null && !organisationUnits.isEmpty()) { + return organisationUnits.stream().sorted().toList(); + } else { + // If the user has no analytics permissions for any organization unit, + // returns data capture organization units, instead. + Set defaultOrganisationUnits = currentUser.getOrganisationUnits(); + if (defaultOrganisationUnits != null && !defaultOrganisationUnits.isEmpty()) { + return defaultOrganisationUnits.stream().sorted().toList(); + } + } + + return new ArrayList<>(); + } + private List getDimensionalObjects(DataQueryRequest request) { List list = new ArrayList<>(); DataQueryParams params = diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultQueryPlanner.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultQueryPlanner.java index 2d3c8d05864f..d06131f652b7 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultQueryPlanner.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultQueryPlanner.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis.analytics.data; +import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import static org.hisp.dhis.analytics.DataQueryParams.LEVEL_PREFIX; import static org.hisp.dhis.analytics.util.AnalyticsUtils.throwIllegalQueryEx; import static org.hisp.dhis.common.DimensionalObject.DATA_X_DIM_ID; @@ -111,7 +112,7 @@ public DataQueryGroups planQuery(DataQueryParams params, QueryPlannerParams plan .build(); for (Function> grouper : groupers) { - List currentQueries = Lists.newArrayList(queries); + List currentQueries = new ArrayList<>(queries); queries.clear(); currentQueries.forEach(query -> queries.addAll(grouper.apply(query))); @@ -125,7 +126,7 @@ public DataQueryGroups planQuery(DataQueryParams params, QueryPlannerParams plan return queryGroups; } - List splitDimensions = Lists.newArrayList(DATA_X_DIM_ID, ORGUNIT_DIM_ID); + List splitDimensions = List.of(DATA_X_DIM_ID, ORGUNIT_DIM_ID); for (String dim : splitDimensions) { queryGroups = splitByDimension(queryGroups, dim, plannerParams.getOptimalQueries()); @@ -236,7 +237,7 @@ public List groupByPeriodType(DataQueryParams params) { if (params.isSkipPartitioning()) { queries.add(params); - } else if (!params.getPeriods().isEmpty()) { + } else if (isNotEmpty(params.getPeriods())) { ListMap periodTypePeriodMap = PartitionUtils.getPeriodTypePeriodMap(params.getPeriods()); @@ -253,7 +254,7 @@ public List groupByPeriodType(DataQueryParams params) { queries.add(query); } - } else if (!params.getFilterPeriods().isEmpty()) { + } else if (isNotEmpty(params.getFilterPeriods())) { DimensionalObject filter = params.getFilter(PERIOD_DIM_ID); ListMap periodTypePeriodMap = @@ -290,7 +291,7 @@ public List groupByPeriodType(DataQueryParams params) { public List groupByOrgUnitLevel(DataQueryParams params) { List queries = new ArrayList<>(); - if (!params.getOrganisationUnits().isEmpty()) { + if (isNotEmpty(params.getOrganisationUnits())) { ListMap levelOrgUnitMap = QueryPlannerUtils.getLevelOrgUnitMap(params.getOrganisationUnits()); @@ -306,7 +307,7 @@ public List groupByOrgUnitLevel(DataQueryParams params) { queries.add(query); } - } else if (!params.getFilterOrganisationUnits().isEmpty()) { + } else if (isNotEmpty(params.getFilterOrganisationUnits())) { ListMap levelOrgUnitMap = QueryPlannerUtils.getLevelOrgUnitMap(params.getFilterOrganisationUnits()); @@ -340,7 +341,7 @@ public List groupByOrgUnitLevel(DataQueryParams params) { public List groupByStartEndDateRestriction(DataQueryParams params) { List queries = new ArrayList<>(); - if (!params.getPeriods().isEmpty()) { + if (isNotEmpty(params.getPeriods())) { for (DimensionalItemObject item : params.getPeriods()) { Period period = (Period) item; @@ -357,7 +358,7 @@ public List groupByStartEndDateRestriction(DataQueryParams para queries.add(query); } - } else if (!params.getFilterPeriods().isEmpty()) { + } else if (isNotEmpty(params.getFilterPeriods())) { Period period = (Period) params.getFilterPeriods().get(0); DataQueryParams query = @@ -386,7 +387,7 @@ public List groupByStartEndDateRestriction(DataQueryParams para private List groupByDataType(DataQueryParams params) { List queries = new ArrayList<>(); - if (!params.getDataElements().isEmpty()) { + if (isNotEmpty(params.getDataElements())) { ListMap dataTypeDataElementMap = QueryPlannerUtils.getDataTypeDataElementMap(params.getDataElements()); @@ -420,7 +421,7 @@ private List groupByDataType(DataQueryParams params) { private List groupByQueryMods(DataQueryParams params) { List queries = new ArrayList<>(); - if (!params.getDataElements().isEmpty()) { + if (isNotEmpty(params.getDataElements())) { ListMap queryModsElementMap = QueryPlannerUtils.getQueryModsElementMap(params.getDataElements()); @@ -476,7 +477,7 @@ private List groupByQueryMods(DataQueryParams params) { private List groupByAggregationType(DataQueryParams params) { List queries = new ArrayList<>(); - if (!params.getDataElements().isEmpty()) { + if (isNotEmpty(params.getDataElements())) { ListMap aggregationTypeDataElementMap = QueryPlannerUtils.getAggregationTypeDataElementMap( params.getDataElements(), params.getAggregationType(), params.getPeriodType()); @@ -490,13 +491,13 @@ private List groupByAggregationType(DataQueryParams params) { queries.add(query); } - } else if (!params.getDataElementGroupSets().isEmpty()) { + } else if (isNotEmpty(params.getDataElementGroupSets())) { DataElementGroup deg = params.getFirstDataElementGroup(); AnalyticsAggregationType aggregationType = ObjectUtils.firstNonNull(params.getAggregationType(), AnalyticsAggregationType.SUM); - if (deg != null && !deg.getMembers().isEmpty()) { + if (deg != null && isNotEmpty(deg.getMembers())) { PeriodType periodType = PeriodType.getPeriodTypeByName(params.getPeriodType()); AnalyticsAggregationType degAggType = AnalyticsAggregationType.fromAggregationType(deg.getAggregationType()); @@ -634,7 +635,7 @@ private List groupByDaysInPeriod(DataQueryParams params) { private List groupByDataPeriodType(DataQueryParams params) { List queries = new ArrayList<>(); - if (params.isDisaggregation() && !params.getDataElements().isEmpty()) { + if (params.isDisaggregation() && isNotEmpty(params.getDataElements())) { ListMap periodTypeDataElementMap = QueryPlannerUtils.getPeriodTypeDataElementMap(params.getDataElements()); @@ -647,7 +648,7 @@ private List groupByDataPeriodType(DataQueryParams params) { queries.add(query); } - } else if (params.isDisaggregation() && !params.getDataElementGroupSets().isEmpty()) { + } else if (params.isDisaggregation() && isNotEmpty(params.getDataElementGroupSets())) { DataElementGroup deg = params.getFirstDataElementGroup(); PeriodType periodType = deg != null ? deg.getPeriodType() : null; @@ -672,7 +673,7 @@ private List groupByDataPeriodType(DataQueryParams params) { private List groupByPeriod(DataQueryParams params) { List queries = new ArrayList<>(); - if (params.isFirstOrLastOrLastInPeriodAggregationType() && !params.getPeriods().isEmpty()) { + if (params.isFirstOrLastOrLastInPeriodAggregationType() && isNotEmpty(params.getPeriods())) { for (DimensionalItemObject period : params.getPeriods()) { String periodType = ((Period) period).getPeriodType().getName().toLowerCase(); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DimensionalObjectProducer.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DimensionalObjectProvider.java similarity index 99% rename from dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DimensionalObjectProducer.java rename to dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DimensionalObjectProvider.java index c776b35d49f0..c247113b657f 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DimensionalObjectProducer.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DimensionalObjectProvider.java @@ -116,7 +116,7 @@ */ @Component @RequiredArgsConstructor -public class DimensionalObjectProducer { +public class DimensionalObjectProvider { private final IdentifiableObjectManager idObjectManager; private final OrganisationUnitService organisationUnitService; diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/JdbcAnalyticsManager.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/JdbcAnalyticsManager.java index 3e3829dcc77f..89935206684f 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/JdbcAnalyticsManager.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/JdbcAnalyticsManager.java @@ -59,6 +59,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -95,7 +96,6 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.rowset.SqlRowSet; import org.springframework.scheduling.annotation.Async; -import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.stereotype.Service; import org.springframework.util.Assert; @@ -184,14 +184,14 @@ public Future> getAggregatedDataValues( params = getParamsWithOffsetPartitions(params, tableType); } - String sql = getSql(params, tableType); + final String sql = getSql(params, tableType); final DataQueryParams immutableParams = DataQueryParams.newBuilder(params).build(); if (params.analyzeOnly()) { withExceptionHandling( () -> executionPlanStore.addExecutionPlan(immutableParams.getExplainOrderId(), sql)); - return new AsyncResult<>(Maps.newHashMap()); + return CompletableFuture.completedFuture(Maps.newHashMap()); } Map map; @@ -205,12 +205,12 @@ public Future> getAggregatedDataValues( throw ex; } log.warn(ERR_MSG_SILENT_FALLBACK, ex); - return new AsyncResult<>(Maps.newHashMap()); + return CompletableFuture.completedFuture(Maps.newHashMap()); } replaceDataPeriodsWithAggregationPeriods(map, params, dataPeriodAggregationPeriodMap); - return new AsyncResult<>(map); + return CompletableFuture.completedFuture(map); } catch (DataAccessResourceFailureException ex) { throw new QueryRuntimeException(ErrorCode.E7131); } catch (RuntimeException ex) { @@ -313,7 +313,6 @@ private String getSql(DataQueryParams params, AnalyticsTableType tableType) { StringBuilder builder = new StringBuilder(); builder.append(getSelectClause(params)); - builder.append(getFromClause(params, tableType)); // Skip the where clause here if already in sub query @@ -326,7 +325,7 @@ private String getSql(DataQueryParams params, AnalyticsTableType tableType) { if (params.hasMeasureCriteria() && params.isDataType(DataType.NUMERIC) && !params.hasReportingRates()) { - /* Reporting rates applies the measure criteria after the rates calculation phase. It cannot be done at this stage. */ + // Reporting rates applies the measure criteria after the rates calculation phase builder.append(getMeasureCriteriaSql(params)); } @@ -372,7 +371,7 @@ protected String getValueClause(DataQueryParams params) { * @return a SQL numeric value column. */ protected String getAggregateValueColumn(DataQueryParams params) { - String sql; + String sql = null; AnalyticsAggregationType aggType = params.getAggregationType(); @@ -388,8 +387,7 @@ protected String getAggregateValueColumn(DataQueryParams params) { sql = "sum(daysxvalue) / sum(daysno) * 100"; } else if (SIMPLE_AGGREGATION_TYPES.contains(aggType.getAggregationType())) { sql = String.format("%s(%s)", aggType.getAggregationType().getValue(), valueColumn); - } else // SUM and no value - { + } else { // SUM and no value sql = "sum(" + valueColumn + ")"; } @@ -911,16 +909,12 @@ private String getMeasureCriteriaSql(DataQueryParams params) { for (MeasureFilter filter : params.getMeasureCriteria().keySet()) { Double criterion = params.getMeasureCriteria().get(filter); + String sqlFilter = + String.format( + " %s %s %s ", + getAggregateValueColumn(params), OPERATOR_SQL_MAP.get(filter), criterion); - sql += - sqlHelper.havingAnd() - + " " - + getAggregateValueColumn(params) - + " " - + OPERATOR_SQL_MAP.get(filter) - + " " - + criterion - + " "; + sql += sqlHelper.havingAnd() + sqlFilter; } return sql; @@ -955,9 +949,7 @@ private Map getKeyValueMap(DataQueryParams params, String sql, i for (DimensionalObject dim : params.getDimensions()) { String value = dim.isFixed() ? dim.getDimensionName() : rowSet.getString(dim.getDimensionName()); - String queryModsId = params.getQueryModsId(dim); - key.append(value).append(queryModsId).append(DIMENSION_SEP); } @@ -965,12 +957,10 @@ private Map getKeyValueMap(DataQueryParams params, String sql, i if (params.isDataType(TEXT)) { String value = rowSet.getString(VALUE_ID); - map.put(key.toString(), value); } else // NUMERIC { Double value = rowSet.getDouble(VALUE_ID); - map.put(key.toString(), value); } } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/handler/DataHandler.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/handler/DataHandler.java index 0cde7c3a3e1b..998fcb80d55c 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/handler/DataHandler.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/handler/DataHandler.java @@ -122,6 +122,7 @@ import org.hisp.dhis.analytics.AnalyticsTableType; import org.hisp.dhis.analytics.DataQueryGroups; import org.hisp.dhis.analytics.DataQueryParams; +import org.hisp.dhis.analytics.DataQueryParams.Builder; import org.hisp.dhis.analytics.DimensionItem; import org.hisp.dhis.analytics.QueryPlanner; import org.hisp.dhis.analytics.QueryPlannerParams; @@ -134,6 +135,7 @@ import org.hisp.dhis.analytics.util.PeriodOffsetUtils; import org.hisp.dhis.common.BaseDimensionalObject; import org.hisp.dhis.common.DimensionItemObjectValue; +import org.hisp.dhis.common.DimensionType; import org.hisp.dhis.common.DimensionalItemId; import org.hisp.dhis.common.DimensionalItemObject; import org.hisp.dhis.common.DimensionalObject; @@ -780,7 +782,7 @@ private Map getAggregatedCompletenessValueMap(DataQueryParams pa */ private Map getAggregatedCompletenessTargetMap(DataQueryParams params) { List>> queryGroupers = newArrayList(); - queryGroupers.add(q -> queryPlanner.groupByStartEndDateRestriction(q)); + queryGroupers.add(queryPlanner::groupByStartEndDateRestriction); return getDoubleMap(getAggregatedValueMap(params, COMPLETENESS_TARGET, queryGroupers)); } @@ -821,15 +823,15 @@ private void addDataElementOperandValues(DataQueryParams params, Grid grid, Tota } /** - * Delivers a DataQueryParams instance for DataElementOperands. The dimension and filter operands - * are added to the query parameters. + * Based on the list of operands, it adds dimensions and filters into the given {@link + * DataQueryParams}. * * @param params the {@link DataQueryParams}. * @param operands the collection of {@link DataElementOperand}. * @param totalType the {@link TotalType}. * @return mapped DataQueryParams */ - private DataQueryParams getOperandDataQueryParams( + DataQueryParams getOperandDataQueryParams( DataQueryParams params, List operands, TotalType totalType) { List dataElements = newArrayList(getDataElements(operands)); @@ -837,209 +839,228 @@ private DataQueryParams getOperandDataQueryParams( newArrayList(getCategoryOptionCombos(operands)); List attributeOptionCombos = newArrayList(getAttributeOptionCombos(operands)); + List dataElementOperands = params.getDataElementOperands(); + List filterDataElementOperands = params.getFilterDataElementOperands(); DataQueryParams.Builder builder = newBuilder(params).removeDimension(DATA_X_DIM_ID); - addDataElementDimensionToDataQueryParamBuilder(builder, params, dataElements); - addDataElementFilterToDataQueryParamBuilder(builder, params, dataElements); + // Data elements. + handleDataElementOperands( + getDataElementInDataElementOperands(dataElementOperands, dataElements), + getDataElementInDataElementOperands(filterDataElementOperands, dataElements), + builder, + DATA_X_DIM_ID, + DATA_X); + // Category option combos. if (totalType.isCategoryOptionCombo()) { - addCategoryOptionComboDimensionToDataQueryParamBuilder(builder, params, categoryOptionCombos); - addCategoryOptionComboFilterToDataQueryParamBuilder(builder, params, categoryOptionCombos); + handleDataElementOperands( + getCategoryOptionCombosInDataElementOperands(dataElementOperands, categoryOptionCombos), + getCategoryOptionCombosInDataElementOperands( + filterDataElementOperands, categoryOptionCombos), + builder, + CATEGORYOPTIONCOMBO_DIM_ID, + CATEGORY_OPTION_COMBO); } + // Attribute option combos. if (totalType.isAttributeOptionCombo()) { - addAttributeOptionComboDimensionToDataQueryParamBuilder( - builder, params, attributeOptionCombos); - addAttributeOptionComboFilterToDataQueryParamBuilder(builder, params, attributeOptionCombos); + handleDataElementOperands( + getAttributeOptionComboDimensionInDataElementOperands( + dataElementOperands, attributeOptionCombos), + getAttributeOptionComboDimensionInDataElementOperands( + filterDataElementOperands, attributeOptionCombos), + builder, + ATTRIBUTEOPTIONCOMBO_DIM_ID, + ATTRIBUTE_OPTION_COMBO); } return builder.build(); } /** - * Add CategoryOptionCombo dimension to DataQueryParam builder. + * Decides if data element operands should or not be added to the "builder" reference, based on + * the given arguments. Note that the "builder" object might have his state changed. * - * @param builder the {@link DataQueryParams.Builder}. - * @param params the {@link DataQueryParams}. - * @param categoryOptionCombos the collection of the {@link DimensionalItemObject}. + * @param dataElementOperands the list of data element operands ({@link DimensionalItemObject}). + * @param filterDataElementOperands the list of filter data element operands ({@link + * DimensionalItemObject}). + * @param builder the current {@link Builder}. + * @param dimensionUid the dimension uid. + * @param dimensionType the {@link DimensionType}. */ - private void addCategoryOptionComboDimensionToDataQueryParamBuilder( - DataQueryParams.Builder builder, - DataQueryParams params, - List categoryOptionCombos) { - List dimensionCategoryOptionCombos = - categoryOptionCombos.stream() - .filter( - coc -> - params.getDataElementOperands().stream() - .filter(deo -> ((DataElementOperand) deo).getCategoryOptionCombo() != null) - .anyMatch( - deo -> - ((DataElementOperand) deo) - .getCategoryOptionCombo() - .getUid() - .equals(coc.getUid()))) - .toList(); - - if (!dimensionCategoryOptionCombos.isEmpty()) { + private void handleDataElementOperands( + List dataElementOperands, + List filterDataElementOperands, + Builder builder, + String dimensionUid, + DimensionType dimensionType) { + + addDimensionToBuilder(dataElementOperands, builder, dimensionUid, dimensionType); + addFilterToBuilder(filterDataElementOperands, builder, dimensionUid, dimensionType); + } + + /** + * Adds the given list "dimensionalItemObjects" to the filter of the "builder", if the list is not + * empty. Note that the "builder" object might have his state changed. + * + * @param dimensionalItemObjects the list of data element operands ({@link + * DimensionalItemObject}). + * @param builder the current {@link Builder}. + * @param dimensionUid the dimension uid. + * @param dimensionType the {@link DimensionType}. + */ + private static void addFilterToBuilder( + List dimensionalItemObjects, + Builder builder, + String dimensionUid, + DimensionType dimensionType) { + if (!dimensionalItemObjects.isEmpty()) { + builder.addFilter( + new BaseDimensionalObject(dimensionUid, dimensionType, dimensionalItemObjects)); + } + } + + /** + * Adds the given list "dimensionalItemObjects" to the dimension of the "builder", if the list is + * not empty. Note that the "builder" object might have his state changed. + * + * @param dimensionalItemObjects the list of data element operands ({@link + * DimensionalItemObject}). + * @param builder the current {@link Builder}. + * @param dimensionUid the dimension uid. + * @param dimensionType the {@link DimensionType}. + */ + private static void addDimensionToBuilder( + List dimensionalItemObjects, + Builder builder, + String dimensionUid, + DimensionType dimensionType) { + if (!dimensionalItemObjects.isEmpty()) { builder.addDimension( - new BaseDimensionalObject( - CATEGORYOPTIONCOMBO_DIM_ID, CATEGORY_OPTION_COMBO, dimensionCategoryOptionCombos)); + new BaseDimensionalObject(dimensionUid, dimensionType, dimensionalItemObjects)); } } /** - * Add CategoryOptionCombo filter to DataQueryParam builder. + * Returns a list of category option combos found in the given data element operands. * - * @param builder the {@link DataQueryParams.Builder}. - * @param params the {@link DataQueryParams}. - * @param categoryOptionCombos the collection of the {@link DimensionalItemObject}. + * @param dataElementOperands the list of {@link DimensionalItemObject}. + * @param categoryOptionCombos the list of {@link DimensionalItemObject}. + * @return the list of category option combos as {@link DimensionalItemObject}. */ - private void addCategoryOptionComboFilterToDataQueryParamBuilder( - DataQueryParams.Builder builder, - DataQueryParams params, + private List getCategoryOptionCombosInDataElementOperands( + List dataElementOperands, List categoryOptionCombos) { - List filterCategoryOptionCombos = - categoryOptionCombos.stream() - .filter( - coc -> - params.getFilterDataElementOperands().stream() - .filter(deo -> ((DataElementOperand) deo).getCategoryOptionCombo() != null) - .anyMatch( - deo -> - ((DataElementOperand) deo) - .getCategoryOptionCombo() - .getUid() - .equals(coc.getUid()))) - .toList(); - - if (!filterCategoryOptionCombos.isEmpty()) { - builder.addFilter( - new BaseDimensionalObject( - CATEGORYOPTIONCOMBO_DIM_ID, CATEGORY_OPTION_COMBO, filterCategoryOptionCombos)); + List dimensionCategoryOptionCombos = new ArrayList<>(); + + for (DimensionalItemObject coc : categoryOptionCombos) { + if (matchCategoryOptionCombo(dataElementOperands, coc)) { + dimensionCategoryOptionCombos.add(coc); + } } + + return dimensionCategoryOptionCombos; } /** - * Add AttributeOptionCombo dimension to DataQueryParam builder. + * Evaluates the given list of data element operands and returns true if there is match for the + * "coc" provided. * - * @param builder the {@link DataQueryParams.Builder}. - * @param params the {@link DataQueryParams}. - * @param attributeOptionCombos the collection of the {@link DimensionalItemObject}. + * @param dataElementOperands the list of {@link DimensionalItemObject}. + * @param coc the {@DimensionalItemObject} representing a category option combo. + * @return true if there is a match, false otherwise. */ - private void addAttributeOptionComboDimensionToDataQueryParamBuilder( - DataQueryParams.Builder builder, - DataQueryParams params, - List attributeOptionCombos) { - List dimensionAttributeOptionCombos = - attributeOptionCombos.stream() - .filter( - aoc -> - params.getDataElementOperands().stream() - .filter(deo -> ((DataElementOperand) deo).getAttributeOptionCombo() != null) - .anyMatch( - deo -> - ((DataElementOperand) deo) - .getAttributeOptionCombo() - .getUid() - .equals(aoc.getUid()))) - .toList(); - - if (!dimensionAttributeOptionCombos.isEmpty()) { - builder.addDimension( - new BaseDimensionalObject( - ATTRIBUTEOPTIONCOMBO_DIM_ID, ATTRIBUTE_OPTION_COMBO, dimensionAttributeOptionCombos)); + private boolean matchCategoryOptionCombo( + List dataElementOperands, DimensionalItemObject coc) { + for (DimensionalItemObject deo : dataElementOperands) { + if (((DataElementOperand) deo).getCategoryOptionCombo() != null + && (((DataElementOperand) deo).getCategoryOptionCombo().getUid().equals(coc.getUid()))) { + return true; + } } + + return false; } /** - * Add AttributeOptionCombo filter to DataQueryParam builder. + * Returns a list of attribute option combos found in the given data element operands. * - * @param builder the {@link DataQueryParams.Builder}. - * @param params the {@link DataQueryParams}. - * @param attributeOptionCombos the collection of the {@link DimensionalItemObject}. + * @param dataElementOperands the list of {@link DimensionalItemObject}. + * @param attributeOptionCombos the list of {@link DimensionalItemObject}. + * @return the list of attribute option combos as {@link DimensionalItemObject}. */ - private void addAttributeOptionComboFilterToDataQueryParamBuilder( - DataQueryParams.Builder builder, - DataQueryParams params, + private List getAttributeOptionComboDimensionInDataElementOperands( + List dataElementOperands, List attributeOptionCombos) { - List filterAttributeOptionCombos = - attributeOptionCombos.stream() - .filter( - aoc -> - params.getFilterDataElementOperands().stream() - .filter(deo -> ((DataElementOperand) deo).getAttributeOptionCombo() != null) - .anyMatch( - deo -> - ((DataElementOperand) deo) - .getAttributeOptionCombo() - .getUid() - .equals(aoc.getUid()))) - .toList(); - - if (!filterAttributeOptionCombos.isEmpty()) { - builder.addFilter( - new BaseDimensionalObject( - ATTRIBUTEOPTIONCOMBO_DIM_ID, ATTRIBUTE_OPTION_COMBO, filterAttributeOptionCombos)); + List dimensionAttributeOptionCombos = new ArrayList<>(); + for (DimensionalItemObject aoc : attributeOptionCombos) { + if (matchAttributeOptionCombo(dataElementOperands, aoc)) { + dimensionAttributeOptionCombos.add(aoc); + } } + + return dimensionAttributeOptionCombos; } /** - * Add DataElement dimension to DataQueryParam builder. + * Evaluates the given list of data element operands and returns true if there is match for the + * "aoc" provided. * - * @param builder the {@link DataQueryParams.Builder}. - * @param params the {@link DataQueryParams}. - * @param dataElements the collection of the {@link DimensionalItemObject}. + * @param dataElementOperands the list of {@link DimensionalItemObject}. + * @param aoc the {@DimensionalItemObject} representing an attribute option combo. + * @return true if there is a match, false otherwise. */ - private void addDataElementDimensionToDataQueryParamBuilder( - DataQueryParams.Builder builder, - DataQueryParams params, - List dataElements) { - - List dimensionDataElements = - dataElements.stream() - .filter( - de -> - params.getDataElementOperands().stream() - .anyMatch( - deo -> - ((DataElementOperand) deo) - .getDataElement() - .getUid() - .equals(de.getUid()))) - .toList(); - if (!dimensionDataElements.isEmpty()) { - builder.addDimension(new BaseDimensionalObject(DATA_X_DIM_ID, DATA_X, dimensionDataElements)); + private boolean matchAttributeOptionCombo( + List dataElementOperands, DimensionalItemObject aoc) { + for (DimensionalItemObject deo : dataElementOperands) { + if (((DataElementOperand) deo).getAttributeOptionCombo() != null + && (((DataElementOperand) deo).getAttributeOptionCombo().getUid().equals(aoc.getUid()))) { + return true; + } } + + return false; } /** - * Add DataElement filter to DataQueryParam builder. + * Returns a list of data elements found in the given data element operands. * - * @param builder the {@link DataQueryParams.Builder}. - * @param params the {@link DataQueryParams}. - * @param dataElements the collection of the {@link DimensionalItemObject}. + * @param dataElementOperands the list of {@link DimensionalItemObject}. + * @param dataElements the list of {@link DimensionalItemObject}. + * @return the list of data elements as {@link DimensionalItemObject}. */ - private void addDataElementFilterToDataQueryParamBuilder( - DataQueryParams.Builder builder, - DataQueryParams params, - List dataElements) { - List filterDataElements = - dataElements.stream() - .filter( - de -> - params.getFilterDataElementOperands().stream() - .anyMatch( - deo -> - ((DataElementOperand) deo) - .getDataElement() - .getUid() - .equals(de.getUid()))) - .toList(); - if (!filterDataElements.isEmpty()) { - builder.addFilter(new BaseDimensionalObject(DATA_X_DIM_ID, DATA_X, filterDataElements)); + private List getDataElementInDataElementOperands( + List dataElementOperands, List dataElements) { + List dimensionDataElements = new ArrayList<>(); + + for (DimensionalItemObject de : dataElements) { + if (matchDataElement(dataElementOperands, de)) { + dimensionDataElements.add(de); + } + } + + return dimensionDataElements; + } + + /** + * Evaluates the given list of data element operands and returns true if there is match for the + * "de" provided. + * + * @param dataElementOperands the list of {@link DimensionalItemObject}. + * @param de the {@DimensionalItemObject} representing a data element. + * @return true if there is a match, false otherwise. + */ + private boolean matchDataElement( + List dataElementOperands, DimensionalItemObject de) { + for (DimensionalItemObject deo : dataElementOperands) { + if (((DataElementOperand) deo).getDataElement() != null + && (((DataElementOperand) deo).getDataElement().getUid().equals(de.getUid()))) { + return true; + } } + + return false; } /** @@ -1087,10 +1108,10 @@ private boolean satisfiesMeasureCriteria( Double indicatorRoundedValue = getRoundedValue(params, indicator.getDecimals(), value.getValue()).doubleValue(); - return !params.getMeasureCriteria().entrySet().stream() - .anyMatch( + return params.getMeasureCriteria().entrySet().stream() + .allMatch( measureValue -> - !measureValue + measureValue .getKey() .measureIsValid(indicatorRoundedValue, measureValue.getValue())); } @@ -1433,10 +1454,9 @@ private Map getAggregatedValueMap( DataQueryGroups queryGroups = queryPlanner.planQuery(params, plannerParams); timer.getSplitTime( - "Planned analytics query, got: " - + queryGroups.getLargestGroupSize() - + " for optimal: " - + optimalQueries); + "Planned analytics query, got: {} for optimal: {}", + queryGroups.getLargestGroupSize(), + optimalQueries); Map map = new HashMap<>(); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/AbstractJdbcEventAnalyticsManager.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/AbstractJdbcEventAnalyticsManager.java index b7007bb7e703..9f53bfd51add 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/AbstractJdbcEventAnalyticsManager.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/AbstractJdbcEventAnalyticsManager.java @@ -41,12 +41,14 @@ import static org.hisp.dhis.analytics.AggregationType.CUSTOM; import static org.hisp.dhis.analytics.AggregationType.NONE; import static org.hisp.dhis.analytics.AnalyticsConstants.DATE_PERIOD_STRUCT_ALIAS; -import static org.hisp.dhis.analytics.DataQueryParams.LEVEL_PREFIX; import static org.hisp.dhis.analytics.DataQueryParams.NUMERATOR_DENOMINATOR_PROPERTIES_COUNT; import static org.hisp.dhis.analytics.DataType.NUMERIC; import static org.hisp.dhis.analytics.QueryKey.NV; import static org.hisp.dhis.analytics.SortOrder.ASC; import static org.hisp.dhis.analytics.SortOrder.DESC; +import static org.hisp.dhis.analytics.event.data.EnrollmentQueryHelper.getHeaderColumns; +import static org.hisp.dhis.analytics.event.data.EnrollmentQueryHelper.getOrgUnitLevelColumns; +import static org.hisp.dhis.analytics.event.data.EnrollmentQueryHelper.getPeriodColumns; import static org.hisp.dhis.analytics.table.JdbcEventAnalyticsTableManager.OU_GEOMETRY_COL_SUFFIX; import static org.hisp.dhis.analytics.table.JdbcEventAnalyticsTableManager.OU_NAME_COL_SUFFIX; import static org.hisp.dhis.analytics.util.AnalyticsUtils.replaceStringBetween; @@ -55,7 +57,6 @@ import static org.hisp.dhis.common.DimensionItemType.DATA_ELEMENT; import static org.hisp.dhis.common.DimensionItemType.PROGRAM_INDICATOR; import static org.hisp.dhis.common.DimensionalObject.ORGUNIT_DIM_ID; -import static org.hisp.dhis.common.DimensionalObject.PERIOD_DIM_ID; import static org.hisp.dhis.common.DimensionalObjectUtils.COMPOSITE_DIM_OBJECT_PLAIN_SEP; import static org.hisp.dhis.common.QueryOperator.IN; import static org.hisp.dhis.common.RequestTypeAware.EndpointItem.ENROLLMENT; @@ -102,7 +103,6 @@ import org.hisp.dhis.common.IdScheme; import org.hisp.dhis.common.IdentifiableObject; import org.hisp.dhis.common.InQueryFilter; -import org.hisp.dhis.common.OrganisationUnitSelectionMode; import org.hisp.dhis.common.QueryFilter; import org.hisp.dhis.common.QueryItem; import org.hisp.dhis.common.Reference; @@ -114,7 +114,6 @@ import org.hisp.dhis.db.sql.SqlBuilder; import org.hisp.dhis.feedback.ErrorCode; import org.hisp.dhis.option.Option; -import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.period.Period; import org.hisp.dhis.program.AnalyticsType; import org.hisp.dhis.program.ProgramIndicator; @@ -139,9 +138,9 @@ public abstract class AbstractJdbcEventAnalyticsManager { protected static final int LAST_VALUE_YEARS_OFFSET = -10; - private static final String COL_VALUE = "value"; + static final String COL_VALUE = "value"; - private static final String OUTER_SQL_ALIAS = "t1"; + static final String OUTER_SQL_ALIAS = "t1"; private static final String AND = " and "; @@ -338,12 +337,9 @@ private void addDimensionSelectColumns( dimension -> { if (params.isAggregatedEnrollments() && dimension.getDimensionType() == DimensionType.PERIOD) { - dimension - .getItems() - .forEach( - it -> - columns.add( - ((Period) it).getPeriodType().getPeriodTypeEnum().getName())); + for (DimensionalItemObject it : dimension.getItems()) { + columns.add(((Period) it).getPeriodType().getPeriodTypeEnum().getName()); + } return; } @@ -906,7 +902,10 @@ private String getTableAndColumn( if (params.hasTimeField() && DimensionType.PERIOD == dimension.getDimensionType()) { return sqlBuilder.quote(DATE_PERIOD_STRUCT_ALIAS, col); } else if (DimensionType.ORGANISATION_UNIT == dimension.getDimensionType()) { - return params.getOrgUnitField().getOrgUnitStructCol(col, getAnalyticsType(), isGroupByClause); + return params + .getOrgUnitField() + .withSqlBuilder(sqlBuilder) + .getOrgUnitStructCol(col, getAnalyticsType(), isGroupByClause); } else if (DimensionType.ORGANISATION_UNIT_GROUP_SET == dimension.getDimensionType()) { return params .getOrgUnitField() @@ -951,54 +950,9 @@ protected String getAggregatedEnrollmentsSql(List headers, EventQuer sql += getWhereClause(params); - final String tempSql = sql; - - String headerColumns = - headers.stream() - .filter( - header -> - !header.getName().equalsIgnoreCase(COL_VALUE) - && !header.getName().equalsIgnoreCase(PERIOD_DIM_ID) - && !header.getName().equalsIgnoreCase(ORGUNIT_DIM_ID)) - .map( - header -> { - String headerName = header.getName(); - if (tempSql.contains(headerName)) { - return OUTER_SQL_ALIAS + "." + quote(headerName); - } - if (headerName.contains(".")) { - headerName = headerName.split("\\.")[1]; - } - - return OUTER_SQL_ALIAS + "." + quote(headerName); - }) - .collect(joining(",")); - - String orgColumns = EMPTY; - - if (!params.isOrganisationUnitMode(OrganisationUnitSelectionMode.SELECTED) - && !params.isOrganisationUnitMode(OrganisationUnitSelectionMode.CHILDREN)) { - - orgColumns = - params.getDimensionOrFilterItems(ORGUNIT_DIM_ID).stream() - .map(d -> LEVEL_PREFIX + ((OrganisationUnit) d).getLevel()) - .distinct() - .collect(joining(",")); - } - - String periodColumns = - params.getDimensions().stream() - .filter(d -> d.getDimensionType() == DimensionType.PERIOD) - .flatMap( - d -> - d.getItems().stream() - .map( - it -> - OUTER_SQL_ALIAS - + "." - + ((Period) it).getPeriodType().getPeriodTypeEnum().getName()) - .distinct()) - .collect(joining(",")); + String headerColumns = getHeaderColumns(headers, sql).stream().collect(joining(",")); + String orgColumns = getOrgUnitLevelColumns(params).stream().collect(joining(",")); + String periodColumns = getPeriodColumns(params).stream().collect(joining(",")); String columns = (!isBlank(orgColumns) ? orgColumns : "," + ORGUNIT_DIM_ID) 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 42f345e3b7b9..b08af886985a 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 @@ -94,7 +94,6 @@ import org.hisp.dhis.setting.UserSettings; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; import org.hisp.dhis.trackedentity.TrackedEntityAttributeService; -import org.hisp.dhis.user.UserSettingsService; import org.springframework.stereotype.Service; import org.springframework.util.Assert; @@ -119,8 +118,6 @@ public class DefaultEventDataQueryService implements EventDataQueryService { private final DataQueryService dataQueryService; - private final UserSettingsService userSettingsService; - @Override public EventQueryParams getFromRequest(EventDataQueryRequest request) { return getFromRequest(request, false); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EnrollmentAggregateService.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EnrollmentAggregateService.java index cf08987e7753..46cf8945bcef 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EnrollmentAggregateService.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EnrollmentAggregateService.java @@ -142,7 +142,7 @@ private void addData(Grid grid, EventQueryParams params) { for (EventQueryParams queryParams : paramsList) { timer.getSplitTime( - "Planned enrollment query, got partitions: " + queryParams.getPartitions()); + "Planned enrollment query, got partitions: {}", queryParams.getPartitions()); int maxLimit = params.isAggregatedEnrollments() ? UNLIMITED_PAGING : queryValidator.getMaxLimit(); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EnrollmentQueryHelper.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EnrollmentQueryHelper.java new file mode 100644 index 000000000000..7ee323563a9d --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EnrollmentQueryHelper.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2004-2024, 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 lombok.AccessLevel.PRIVATE; +import static org.hisp.dhis.analytics.DataQueryParams.LEVEL_PREFIX; +import static org.hisp.dhis.analytics.event.data.AbstractJdbcEventAnalyticsManager.COL_VALUE; +import static org.hisp.dhis.analytics.event.data.AbstractJdbcEventAnalyticsManager.OUTER_SQL_ALIAS; +import static org.hisp.dhis.common.DimensionType.PERIOD; +import static org.hisp.dhis.common.DimensionalObject.ORGUNIT_DIM_ID; +import static org.hisp.dhis.common.DimensionalObject.PERIOD_DIM_ID; +import static org.hisp.dhis.common.OrganisationUnitSelectionMode.CHILDREN; +import static org.hisp.dhis.common.OrganisationUnitSelectionMode.SELECTED; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import lombok.NoArgsConstructor; +import org.hisp.dhis.analytics.event.EventQueryParams; +import org.hisp.dhis.common.DimensionalItemObject; +import org.hisp.dhis.common.DimensionalObject; +import org.hisp.dhis.common.GridHeader; +import org.hisp.dhis.organisationunit.OrganisationUnit; +import org.hisp.dhis.period.Period; + +/** Helper class to support SQL/query handling for enrollments. */ +@NoArgsConstructor(access = PRIVATE) +public class EnrollmentQueryHelper { + + private static final String QUOTE = "\""; + + private static final String DOT = "."; + + /** + * Based on the given headers, it returns a set of respective database columns. It includes the + * required alias. + * + * @param headers the list of {@link GridHeader}. + * @return the set of database columns. + */ + public static Set getHeaderColumns(List headers, String sql) { + Set headerColumns = new LinkedHashSet<>(); + + for (GridHeader header : headers) { + String headerName = header.getName(); + + if (!headerName.equalsIgnoreCase(COL_VALUE) + && !headerName.equalsIgnoreCase(PERIOD_DIM_ID) + && !headerName.equalsIgnoreCase(ORGUNIT_DIM_ID)) { + + if (sql.contains(headerName)) { + headerName = quote(headerName); + } else if (headerName.contains(DOT)) { + // Gets only the column name from the header in the URL. + // This has to match the column of the analytics table. + // ie.: A03MvHHogjR.a3kGcGDCuk6 -> a3kGcGDCuk6 + headerName = quote(headerName.split("\\.")[1]); + } else { + headerName = quote(headerName); + } + + headerColumns.add(OUTER_SQL_ALIAS + DOT + headerName); + } + } + + return headerColumns; + } + + /** + * Based on the given params, it returns a set of respective database columns representing org. + * unit levels. + * + * @param params the {@link EventQueryParams}. + * @return the set of database columns. + */ + public static Set getOrgUnitLevelColumns(EventQueryParams params) { + if (!params.isOrganisationUnitMode(SELECTED) && !params.isOrganisationUnitMode(CHILDREN)) { + Set levels = new LinkedHashSet<>(); + + for (DimensionalItemObject itemObject : params.getDimensionOrFilterItems(ORGUNIT_DIM_ID)) { + String level = LEVEL_PREFIX + ((OrganisationUnit) itemObject).getLevel(); + levels.add(level); + } + + return levels; + } + + return Set.of(); + } + + /** + * Based on the given params, it returns a set of respective database columns representing + * periods. It includes the required alias. + * + * @param params the list of {@link EventQueryParams}. + * @return the set of database columns. + */ + public static Set getPeriodColumns(EventQueryParams params) { + Set periods = new LinkedHashSet<>(); + + for (DimensionalObject dim : params.getDimensions()) { + if (dim.getDimensionType() == PERIOD) { + for (DimensionalItemObject itemObject : dim.getItems()) { + String column = + OUTER_SQL_ALIAS + + DOT + + ((Period) itemObject).getPeriodType().getPeriodTypeEnum().getName(); + periods.add(column); + } + } + } + + return periods; + } + + /** + * It adds double quotes to the given value. + * + * @param value the value to quote. + * @return a double quoted value. + */ + private static String quote(String value) { + String escaped = value.replace(QUOTE, (QUOTE + QUOTE)); + return QUOTE + escaped + QUOTE; + } +} diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EnrollmentQueryService.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EnrollmentQueryService.java index 376ff813652c..be80b3566ddb 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EnrollmentQueryService.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EnrollmentQueryService.java @@ -215,7 +215,7 @@ private long addData(Grid grid, EventQueryParams params) { long count = 0; - timer.getSplitTime("Planned enrollment query, got partitions: " + queryParams.getPartitions()); + timer.getSplitTime("Planned enrollment query, got partitions: {}", queryParams.getPartitions()); if (queryParams.isTotalPages()) { count += enrollmentAnalyticsManager.getEnrollmentCount(queryParams); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EnrollmentTimeFieldSqlRenderer.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EnrollmentTimeFieldSqlRenderer.java index 6af56d861519..09ec724b96de 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EnrollmentTimeFieldSqlRenderer.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EnrollmentTimeFieldSqlRenderer.java @@ -42,7 +42,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import lombok.Getter; -import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.hisp.dhis.analytics.EventOutputType; import org.hisp.dhis.analytics.TimeField; @@ -57,10 +56,11 @@ import org.springframework.util.Assert; @Component -@RequiredArgsConstructor class EnrollmentTimeFieldSqlRenderer extends TimeFieldSqlRenderer { - private final SqlBuilder sqlBuilder; + public EnrollmentTimeFieldSqlRenderer(SqlBuilder sqlBuilder) { + super(sqlBuilder); + } @Getter private final Set allowedTimeFields = Set.of(TimeField.LAST_UPDATED); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EventAggregateService.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EventAggregateService.java index 39ba417dfbaf..580cedbd69fe 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EventAggregateService.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EventAggregateService.java @@ -69,6 +69,7 @@ import java.util.List; import java.util.Map; import lombok.RequiredArgsConstructor; +import org.hisp.dhis.analytics.AnalyticsMetaDataKey; import org.hisp.dhis.analytics.AnalyticsSecurityManager; import org.hisp.dhis.analytics.EventAnalyticsDimensionalItem; import org.hisp.dhis.analytics.cache.AnalyticsCache; @@ -239,7 +240,7 @@ private void addData(Grid grid, EventQueryParams params, int maxLimit) { List queries = queryPlanner.planAggregateQuery(params); - timer.getSplitTime("Planned event query, got partitions: " + params.getPartitions()); + timer.getSplitTime("Planned event query, got partitions: {}", params.getPartitions()); for (EventQueryParams query : queries) { if (query.hasEnrollmentProgramIndicatorDimension()) { diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EventQueryService.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EventQueryService.java index 24f4741250d9..1bef2394a06a 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EventQueryService.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EventQueryService.java @@ -331,7 +331,7 @@ private long addData(Grid grid, EventQueryParams params) { params = queryPlanner.planEventQuery(params); - timer.getSplitTime("Planned event query, got partitions: " + params.getPartitions()); + timer.getSplitTime("Planned event query, got partitions: {}", params.getPartitions()); long count = 0; EventQueryParams immutableParams = new EventQueryParams.Builder(params).build(); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EventTimeFieldSqlRenderer.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EventTimeFieldSqlRenderer.java index cf140375016f..67ffd0f8490d 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EventTimeFieldSqlRenderer.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EventTimeFieldSqlRenderer.java @@ -43,7 +43,6 @@ import java.util.Set; import java.util.stream.Collectors; import lombok.Getter; -import lombok.RequiredArgsConstructor; import org.hisp.dhis.analytics.EventOutputType; import org.hisp.dhis.analytics.TimeField; import org.hisp.dhis.analytics.event.EventQueryParams; @@ -54,10 +53,11 @@ import org.springframework.stereotype.Component; @Component -@RequiredArgsConstructor class EventTimeFieldSqlRenderer extends TimeFieldSqlRenderer { - private final SqlBuilder sqlBuilder; + public EventTimeFieldSqlRenderer(SqlBuilder sqlBuilder) { + super(sqlBuilder); + } @Getter private final Set allowedTimeFields = Set.of(LAST_UPDATED, SCHEDULED_DATE); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/JdbcEventAnalyticsManager.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/JdbcEventAnalyticsManager.java index 76732d22ea17..667a139a2209 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/JdbcEventAnalyticsManager.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/JdbcEventAnalyticsManager.java @@ -632,7 +632,10 @@ private String getOrgUnitDescendantsClause( .map(object -> (OrganisationUnit) object) .collect( Collectors.groupingBy( - unit -> orgUnitField.getOrgUnitLevelCol(unit.getLevel(), getAnalyticsType()))); + unit -> + orgUnitField + .withSqlBuilder(sqlBuilder) + .getOrgUnitLevelCol(unit.getLevel(), getAnalyticsType()))); return collect.keySet().stream() .map(org -> toInCondition(org, collect.get(org))) diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/TimeFieldSqlRenderer.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/TimeFieldSqlRenderer.java index 5f084617425c..8cccb77fdc90 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/TimeFieldSqlRenderer.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/TimeFieldSqlRenderer.java @@ -32,7 +32,6 @@ import static org.hisp.dhis.analytics.event.data.JdbcEventAnalyticsManager.OPEN_IN; import static org.hisp.dhis.common.IdentifiableObjectUtils.getUids; import static org.hisp.dhis.commons.util.TextUtils.getQuotedCommaDelimitedString; -import static org.hisp.dhis.system.util.SqlUtils.quote; import static org.hisp.dhis.util.DateUtils.toMediumDate; import java.util.ArrayList; @@ -51,6 +50,7 @@ import org.hisp.dhis.analytics.event.EventQueryParams; import org.hisp.dhis.common.DateRange; import org.hisp.dhis.common.DimensionalItemObject; +import org.hisp.dhis.db.sql.SqlBuilder; import org.hisp.dhis.jdbc.PostgreSqlStatementBuilder; import org.hisp.dhis.jdbc.StatementBuilder; import org.hisp.dhis.period.Period; @@ -58,9 +58,13 @@ /** Provides methods targeting the generation of SQL statements for periods and time fields. */ public abstract class TimeFieldSqlRenderer { - + protected final SqlBuilder sqlBuilder; protected final StatementBuilder statementBuilder = new PostgreSqlStatementBuilder(); + protected TimeFieldSqlRenderer(SqlBuilder sqlBuilder) { + this.sqlBuilder = sqlBuilder; + } + /** * Generates a SQL statement for periods or time field based on the given params. * @@ -311,7 +315,7 @@ private String wrapAndJoinWithOrIfNecessary(Collection periodSingleCondi private String toSqlCondition(String alias, Entry> entry) { String columnName = entry.getKey().getName().toLowerCase(); - return quote(alias, columnName) + return sqlBuilder.quote(alias, columnName) + OPEN_IN + getQuotedCommaDelimitedString(getUids(entry.getValue())) + ") "; diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/programindicator/RelationshipTypeJoinGenerator.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/programindicator/RelationshipTypeJoinGenerator.java index 5fd86f689d79..6c12a6b35274 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/programindicator/RelationshipTypeJoinGenerator.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/programindicator/RelationshipTypeJoinGenerator.java @@ -80,31 +80,27 @@ public static String generate( private static String getToJoin(RelationshipEntity relationshipEntity) { String sql = "LEFT JOIN "; - switch (relationshipEntity) { - case TRACKED_ENTITY_INSTANCE: - return sql + "trackedentity te on te.trackedentityid = ri2.trackedentityid"; - case PROGRAM_STAGE_INSTANCE: - return sql + "event ev on ev.eventid = ri2.eventid"; - case PROGRAM_INSTANCE: - return sql + "enrollment en on en.enrollmentid = ri2.enrollmentid"; - default: - throw new IllegalQueryException( - new ErrorMessage(ErrorCode.E7227, relationshipEntity.name())); - } + return switch (relationshipEntity) { + case TRACKED_ENTITY_INSTANCE -> + sql + "trackedentity te on te.trackedentityid = ri2.trackedentityid"; + case PROGRAM_STAGE_INSTANCE -> sql + "event ev on ev.eventid = ri2.eventid"; + case PROGRAM_INSTANCE -> sql + "enrollment en on en.enrollmentid = ri2.enrollmentid"; + default -> + throw new IllegalQueryException( + new ErrorMessage(ErrorCode.E7227, relationshipEntity.name())); + }; } private static String getFromRelationshipEntity( String alias, RelationshipEntity relationshipEntity, AnalyticsType programIndicatorType) { - switch (relationshipEntity) { - case TRACKED_ENTITY_INSTANCE: - return getTei(alias); - case PROGRAM_STAGE_INSTANCE: - case PROGRAM_INSTANCE: - return (programIndicatorType.equals(AnalyticsType.EVENT) - ? getEvent(alias) - : getEnrollment(alias)); - } - throw new IllegalQueryException(new ErrorMessage(ErrorCode.E7227, relationshipEntity.name())); + return switch (relationshipEntity) { + case TRACKED_ENTITY_INSTANCE -> getTei(alias); + case PROGRAM_STAGE_INSTANCE, PROGRAM_INSTANCE -> + programIndicatorType.equals(AnalyticsType.EVENT) ? getEvent(alias) : getEnrollment(alias); + default -> + throw new IllegalQueryException( + new ErrorMessage(ErrorCode.E7227, relationshipEntity.name())); + }; } private static String getTei(String alias) { diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/outlier/data/OutlierQueryParser.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/outlier/data/OutlierQueryParser.java index a64dec95202b..65b350d257c9 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/outlier/data/OutlierQueryParser.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/outlier/data/OutlierQueryParser.java @@ -39,7 +39,7 @@ import java.util.UUID; import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; -import org.hisp.dhis.analytics.data.DimensionalObjectProducer; +import org.hisp.dhis.analytics.data.DimensionalObjectProvider; import org.hisp.dhis.category.CategoryOptionCombo; import org.hisp.dhis.common.BaseDimensionalObject; import org.hisp.dhis.common.DisplayProperty; @@ -60,7 +60,7 @@ @AllArgsConstructor public class OutlierQueryParser { private final IdentifiableObjectManager idObjectManager; - private final DimensionalObjectProducer dimensionalObjectProducer; + private final DimensionalObjectProvider dimensionalObjectProducer; private final UserService userService; /** diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/outlier/service/AnalyticsOutlierService.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/outlier/service/AnalyticsOutlierService.java index 9b7ed5e7a7d1..8dad2acf3de9 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/outlier/service/AnalyticsOutlierService.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/outlier/service/AnalyticsOutlierService.java @@ -63,7 +63,7 @@ import org.hisp.dhis.analytics.cache.OutliersCache; import org.hisp.dhis.analytics.common.ColumnHeader; import org.hisp.dhis.analytics.common.TableInfoReader; -import org.hisp.dhis.analytics.data.DimensionalObjectProducer; +import org.hisp.dhis.analytics.data.DimensionalObjectProvider; import org.hisp.dhis.analytics.outlier.data.Outlier; import org.hisp.dhis.analytics.outlier.data.OutlierRequest; import org.hisp.dhis.category.CategoryOptionCombo; @@ -104,7 +104,7 @@ public class AnalyticsOutlierService { private final IdentifiableObjectManager idObjectManager; - private final DimensionalObjectProducer dimensionalObjectProducer; + private final DimensionalObjectProvider dimensionalObjectProducer; /** * Transform the incoming request into api response (json). 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 db518011e295..0b07dced4214 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 @@ -30,7 +30,6 @@ import static org.hisp.dhis.analytics.table.model.Skip.SKIP; import static org.hisp.dhis.analytics.util.AnalyticsUtils.getClosingParentheses; import static org.hisp.dhis.analytics.util.AnalyticsUtils.getColumnType; -import static org.hisp.dhis.commons.util.TextUtils.replace; import static org.hisp.dhis.db.model.DataType.TEXT; import static org.hisp.dhis.system.util.MathUtils.NUMERIC_LENIENT_REGEXP; @@ -97,47 +96,101 @@ public AbstractEventJdbcTableManager( public static final String OU_NAME_COL_SUFFIX = "_name"; protected final String getNumericClause() { - return " and value ~* '" + NUMERIC_LENIENT_REGEXP + "'"; + return " and " + sqlBuilder.regexpMatch("value", "'" + NUMERIC_LENIENT_REGEXP + "'"); } protected final String getDateClause() { - return " and value ~* '" + DATE_REGEXP + "'"; + return " and " + sqlBuilder.regexpMatch("value", DATE_REGEXP); } + /** + * Indicates whether creating an index should be skipped. + * + * @param valueType the {@link ValueType}. + * @param hasOptionSet whether an option set exists. + * @return a {@link Skip}. + */ protected Skip skipIndex(ValueType valueType, boolean hasOptionSet) { boolean skipIndex = NO_INDEX_VAL_TYPES.contains(valueType) && !hasOptionSet; return skipIndex ? Skip.SKIP : Skip.INCLUDE; } /** - * Returns the select clause, potentially with a cast statement, based on the given value type. + * Returns a select expression for a data element value, handling casting to the appropriate data + * type based on the given value type. + * + * @param valueType the {@link ValueType}. + * @param columnName the column name. + * @return a select expression. + */ + protected String getSelectExpression(ValueType valueType, String columnName) { + return getSelectExpression(valueType, columnName, false); + } + + /** + * Returns a select expression for a tracked entity attribute, handling casting to the appropriate + * data type based on the given value type. + * + * @param valueType the {@link ValueType}. + * @param columnName the column name. + * @return a select expression. + */ + protected String getSelectExpressionForAttribute(ValueType valueType, String columnName) { + return getSelectExpression(valueType, columnName, true); + } + + /** + * Returns a select expression, potentially with a cast statement, based on the given value type. + * Handles data element and tracked entity attribute select expressions. * - * @param valueType the value type to represent as database column type. + * @param valueType the {@link ValueType} to represent as database column type. + * @param columnExpression the expression or name of the column to be selected. + * @param isTea whether the selection is in the context of a tracked entity attribute. When true, + * organisation unit selections will include an additional subquery wrapper. + * @return a select expression appropriate for the given value type and context. */ - protected String getSelectClause(ValueType valueType, String columnName) { + private String getSelectExpression(ValueType valueType, String columnExpression, boolean isTea) { if (valueType.isDecimal()) { - return "cast(" + columnName + " as double precision)"; + return getCastExpression(columnExpression, NUMERIC_REGEXP, sqlBuilder.dataTypeDouble()); } else if (valueType.isInteger()) { - return "cast(" + columnName + " as bigint)"; + return getCastExpression(columnExpression, NUMERIC_REGEXP, sqlBuilder.dataTypeBigInt()); } else if (valueType.isBoolean()) { return "case when " - + columnName + + columnExpression + " = 'true' then 1 when " - + columnName + + columnExpression + " = 'false' then 0 else null end"; } else if (valueType.isDate()) { - return "cast(" + columnName + " as timestamp)"; + return getCastExpression(columnExpression, DATE_REGEXP, sqlBuilder.dataTypeTimestamp()); } else if (valueType.isGeo() && isSpatialSupport()) { return "ST_GeomFromGeoJSON('{\"type\":\"Point\", \"coordinates\":' || (" - + columnName + + columnExpression + ") || ', \"crs\":{\"type\":\"name\", \"properties\":{\"name\":\"EPSG:4326\"}}}')"; } else if (valueType.isOrganisationUnit()) { - return "ou.uid from organisationunit ou where ou.uid = (select " + columnName; + String ouClause = + isTea + ? "ou.uid from ${organisationunit} ou where ou.uid = (select ${columnName}" + : "ou.uid from ${organisationunit} ou where ou.uid = ${columnName}"; + return replaceQualify(ouClause, Map.of("columnName", columnExpression)); } else { - return columnName; + return columnExpression; } } + /** + * Returns a cast expression which includes a value filter for the given value type. + * + * @param columnExpression the column expression. + * @param filterRegex the value type filter regular expression. + * @param dataType the SQL data type. + * @return a cast and validate expression. + */ + String getCastExpression(String columnExpression, String filterRegex, String dataType) { + String filter = sqlBuilder.regexpMatch(columnExpression, filterRegex); + return String.format( + "case when %s then cast(%s as %s) else null end", filter, columnExpression, dataType); + } + @Override public boolean validState() { return tableIsNotEmpty("event"); @@ -182,13 +235,13 @@ protected List getTrackedEntityAttributeColumns(Program pr attribute.isNumericType() ? getNumericClause() : attribute.isDateType() ? getDateClause() : ""; - String select = getSelectClause(attribute.getValueType(), "value"); + String select = getSelectExpressionForAttribute(attribute.getValueType(), "value"); Skip skipIndex = skipIndex(attribute.getValueType(), attribute.hasOptionSet()); String sql = - replace( + replaceQualify( """ - (select ${select} from trackedentityattributevalue \ + (select ${select} from ${trackedentityattributevalue} \ where trackedentityid=en.trackedentityid \ and trackedentityattributeid=${attributeId}\ ${dataClause})${closingParentheses} as ${attributeUid}""", @@ -233,24 +286,23 @@ protected List getTrackedEntityAttributeColumns(Program pr * The select statement used by the table population. * * @param attribute the {@link TrackedEntityAttribute}. - * @param fromType the sql snippet related to "from" part - * @param dataClause the data type related clause like "NUMERIC" - * @return + * @param columnExpression the column expression. + * @param dataClause the data type related clause like "NUMERIC". + * @return a select statement. */ protected String selectForInsert( - TrackedEntityAttribute attribute, String fromType, String dataClause) { - return replace( + TrackedEntityAttribute attribute, String columnExpression, String dataClause) { + return replaceQualify( """ - (select ${fromType} from trackedentityattributevalue \ - where trackedentityid=en.trackedentityid \ - and trackedentityattributeid=${attributeId}\ - ${dataClause})\ - ${closingParentheses} as ${attributeUid}""", + (select ${columnExpression} from ${trackedentityattributevalue} \ + where trackedentityid=en.trackedentityid \ + and trackedentityattributeid=${attributeId}${dataClause})\ + ${closingParentheses} as ${attributeUid}""", Map.of( - "fromType", fromType, + "columnExpression", columnExpression, "dataClause", dataClause, "attributeId", String.valueOf(attribute.getId()), - "closingParentheses", getClosingParentheses(fromType), + "closingParentheses", getClosingParentheses(columnExpression), "attributeUid", quote(attribute.getUid()))); } } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/AbstractJdbcTableManager.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/AbstractJdbcTableManager.java index 95a4a5f55ca1..1d16314a85cf 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/AbstractJdbcTableManager.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/AbstractJdbcTableManager.java @@ -32,7 +32,9 @@ import static org.hisp.dhis.analytics.table.util.PartitionUtils.getStartDate; import static org.hisp.dhis.commons.util.TextUtils.format; import static org.hisp.dhis.db.model.DataType.CHARACTER_11; +import static org.hisp.dhis.db.model.DataType.INTEGER; import static org.hisp.dhis.db.model.DataType.TEXT; +import static org.hisp.dhis.db.model.constraint.Nullable.NOT_NULL; import static org.hisp.dhis.util.DateUtils.toLongDate; import java.util.Collection; @@ -84,6 +86,7 @@ import org.hisp.dhis.setting.SystemSettings; import org.hisp.dhis.setting.SystemSettingsProvider; import org.hisp.dhis.system.database.DatabaseInfoProvider; +import org.hisp.dhis.system.util.MathUtils; import org.hisp.dhis.util.DateUtils; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; @@ -109,7 +112,9 @@ public abstract class AbstractJdbcTableManager implements AnalyticsTableManager * */ protected static final String DATE_REGEXP = - "^\\d{4}-\\d{2}-\\d{2}(\\s|T)?((\\d{2}:)(\\d{2}:)?(\\d{2}))?(|.(\\d{3})|.(\\d{3})Z)?$"; + "'^\\d{4}-\\d{2}-\\d{2}(\\s|T)?((\\d{2}:)(\\d{2}:)?(\\d{2}))?(|.(\\d{3})|.(\\d{3})Z)?$'"; + + protected static final String NUMERIC_REGEXP = "'" + MathUtils.NUMERIC_LENIENT_REGEXP + "'"; protected static final Set NO_INDEX_VAL_TYPES = Set.of(ValueType.TEXT, ValueType.LONG_TEXT); @@ -149,9 +154,11 @@ public abstract class AbstractJdbcTableManager implements AnalyticsTableManager protected Boolean spatialSupport; protected boolean isSpatialSupport() { - if (spatialSupport == null) + if (spatialSupport == null) { spatialSupport = databaseInfoProvider.getDatabaseInfo().isSpatialSupport(); - return spatialSupport; + } + + return spatialSupport && sqlBuilder.supportsGeospatialData(); } /** @@ -354,14 +361,15 @@ protected String getTableName() { protected AnalyticsTable getRegularAnalyticsTable( AnalyticsTableUpdateParams params, List dataYears, - List columns) { + List columns, + List sortKey) { Calendar calendar = PeriodType.getCalendar(); List years = ListUtils.mutableCopy(dataYears); Logged logged = analyticsTableSettings.getTableLogged(); Collections.sort(years); - AnalyticsTable table = new AnalyticsTable(getAnalyticsTableType(), columns, logged); + AnalyticsTable table = new AnalyticsTable(getAnalyticsTableType(), columns, sortKey, logged); for (Integer year : years) { List checks = getPartitionChecks(year, getEndDate(calendar, year)); @@ -396,7 +404,7 @@ protected AnalyticsTable getLatestAnalyticsTable( Date endDate = params.getStartTime(); boolean hasUpdatedData = hasUpdatedLatestData(lastAnyTableUpdate, endDate); - AnalyticsTable table = new AnalyticsTable(getAnalyticsTableType(), columns, logged); + AnalyticsTable table = new AnalyticsTable(getAnalyticsTableType(), columns, List.of(), logged); if (hasUpdatedData) { table.addTablePartition( @@ -666,7 +674,7 @@ protected boolean skipColumn(AnalyticsTableColumn column) { * @return true if the table is not empty. */ protected boolean tableIsNotEmpty(String name) { - String sql = format("select 1 from {} limit 1;", sqlBuilder.quote(name)); + String sql = format("select 1 from {} limit 1;", sqlBuilder.qualifyTable(name)); return jdbcTemplate.queryForRowSet(sql).next(); } @@ -732,6 +740,24 @@ protected String replaceQualify(String template, Map variables) return TextUtils.replace(template, map); } + protected AnalyticsTableColumn getPartitionColumn() { + return AnalyticsTableColumn.builder() + .name("year") + .dataType(INTEGER) + .nullable(NOT_NULL) + // The expression should use sqlBuilder, but the concept of functions (like YEAR) + // is part of the previous PR (https://github.com/dhis2/dhis2-core/pull/19131/files) + .selectExpression( + """ + CASE + WHEN ev.status = 'SCHEDULE' THEN YEAR(ev.scheduleddate) + ELSE YEAR(ev.occurreddate) + END + """) + .skipIndex(Skip.SKIP) + .build(); + } + // ------------------------------------------------------------------------- // Private supportive methods // ------------------------------------------------------------------------- diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/DefaultAnalyticsTableGenerator.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/DefaultAnalyticsTableGenerator.java index 2a2504314e31..85c817b11138 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/DefaultAnalyticsTableGenerator.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/DefaultAnalyticsTableGenerator.java @@ -45,6 +45,7 @@ import org.hisp.dhis.analytics.AnalyticsTableUpdateParams; import org.hisp.dhis.analytics.cache.AnalyticsCache; import org.hisp.dhis.analytics.cache.OutliersCache; +import org.hisp.dhis.analytics.table.setting.AnalyticsTableSettings; import org.hisp.dhis.resourcetable.ResourceTableService; import org.hisp.dhis.scheduling.JobProgress; import org.hisp.dhis.setting.SystemSettingsService; @@ -64,12 +65,12 @@ public class DefaultAnalyticsTableGenerator implements AnalyticsTableGenerator { private final SystemSettingsService settingsService; + private final AnalyticsTableSettings settings; + private final AnalyticsCache analyticsCache; private final OutliersCache outliersCache; - // TODO introduce last successful timestamps per table type - @Override public void generateAnalyticsTables(AnalyticsTableUpdateParams params0, JobProgress progress) { Clock clock = new Clock(log).startClock(); @@ -93,6 +94,11 @@ public void generateAnalyticsTables(AnalyticsTableUpdateParams params0, JobProgr if (!params.isSkipResourceTables() && !params.isLatestUpdate()) { generateResourceTablesInternal(progress); + + if (settings.isAnalyticsDatabaseConfigured()) { + log.info("Replicating resource tables in analytics database"); + resourceTableService.replicateAnalyticsResourceTables(); + } } Set skipTypes = emptyIfNull(params.getSkipTableTypes()); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/DefaultAnalyticsTableService.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/DefaultAnalyticsTableService.java index 55347a9264bd..f2d9807084c7 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/DefaultAnalyticsTableService.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/DefaultAnalyticsTableService.java @@ -108,9 +108,7 @@ public void create(AnalyticsTableUpdateParams params, JobProgress progress) { List tables = tableManager.getAnalyticsTables(params); if (tables.isEmpty()) { - clock.logTime( - "Table update aborted, no table or partitions to be updated: '{}'", - tableType.getTableName()); + clock.logTime("Table update aborted, nothing to update: '{}'", tableType.getTableName()); progress.startingStage("Table updates " + tableType); progress.completedStage("Table updated aborted, no table or partitions to be updated"); return; @@ -369,11 +367,17 @@ List getTablePartitions(List tables) { int getParallelJobs() { SystemSettings settings = settingsProvider.getCurrentSettings(); int parallelJobs = settings.getParallelJobsInAnalyticsTableExport(); - if (parallelJobs > 0) return parallelJobs; + if (parallelJobs > 0) { + return parallelJobs; + } int databaseCpus = settings.getDatabaseServerCpus(); - if (databaseCpus > 0) return databaseCpus; + if (databaseCpus > 0) { + return databaseCpus; + } int serverCpus = SystemUtils.getCpuCores(); - if (serverCpus > 2) return serverCpus - 1; + if (serverCpus > 2) { + return serverCpus - 1; + } return serverCpus; } } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/EnrollmentAnalyticsColumn.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/EnrollmentAnalyticsColumn.java index c0907ead1d0a..1cbca99e8be3 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/EnrollmentAnalyticsColumn.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/EnrollmentAnalyticsColumn.java @@ -38,176 +38,217 @@ import static org.hisp.dhis.db.model.DataType.VARCHAR_50; import static org.hisp.dhis.db.model.constraint.Nullable.NOT_NULL; +import java.util.ArrayList; +import java.util.List; import lombok.NoArgsConstructor; import org.hisp.dhis.analytics.table.model.AnalyticsTableColumn; import org.hisp.dhis.analytics.table.model.Skip; import org.hisp.dhis.db.model.IndexType; +import org.hisp.dhis.db.sql.SqlBuilder; @NoArgsConstructor(access = lombok.AccessLevel.PRIVATE) public final class EnrollmentAnalyticsColumn { - public static final AnalyticsTableColumn ENROLLMENT = + private static final AnalyticsTableColumn ENROLLMENT = AnalyticsTableColumn.builder() .name(EnrollmentAnalyticsColumnName.ENROLLMENT_COLUMN_NAME) .dataType(CHARACTER_11) .nullable(NOT_NULL) .selectExpression("en.uid") .build(); - public static final AnalyticsTableColumn TRACKED_ENTITY = + static final AnalyticsTableColumn TRACKED_ENTITY = AnalyticsTableColumn.builder() .name(EnrollmentAnalyticsColumnName.TRACKED_ENTITY_COLUMN_NAME) .dataType(CHARACTER_11) .selectExpression("te.uid") .build(); - public static final AnalyticsTableColumn ENROLLMENT_DATE = + private static final AnalyticsTableColumn ENROLLMENT_DATE = AnalyticsTableColumn.builder() .name(EnrollmentAnalyticsColumnName.ENROLLMENT_DATE_COLUMN_NAME) .dataType(TIMESTAMP) .selectExpression("en.enrollmentdate") .build(); - public static final AnalyticsTableColumn OCCURRED_DATE = + private static final AnalyticsTableColumn OCCURRED_DATE = AnalyticsTableColumn.builder() .name(EnrollmentAnalyticsColumnName.OCCURRED_DATE_COLUMN_NAME) .dataType(TIMESTAMP) .selectExpression("en.occurreddate") .build(); - public static final AnalyticsTableColumn COMPLETED_DATE = + private static final AnalyticsTableColumn COMPLETED_DATE = AnalyticsTableColumn.builder() .name(EnrollmentAnalyticsColumnName.COMPLETED_DATE_COLUMN_NAME) .dataType(TIMESTAMP) .selectExpression("case en.status when 'COMPLETED' then en.completeddate end") .build(); - public static final AnalyticsTableColumn LAST_UPDATED = + private static final AnalyticsTableColumn LAST_UPDATED = AnalyticsTableColumn.builder() .name(EnrollmentAnalyticsColumnName.LAST_UPDATED_COLUMN_NAME) .dataType(TIMESTAMP) .selectExpression("en.lastupdated") .build(); - public static final AnalyticsTableColumn STORED_BY = + private static final AnalyticsTableColumn STORED_BY = AnalyticsTableColumn.builder() .name(EnrollmentAnalyticsColumnName.STORED_BY_COLUMN_NAME) .dataType(VARCHAR_255) .selectExpression("en.storedby") .build(); - public static final AnalyticsTableColumn CREATED_BY_USERNAME = - AnalyticsTableColumn.builder() - .name(EnrollmentAnalyticsColumnName.CREATED_BY_USERNAME_COLUMN_NAME) - .dataType(VARCHAR_255) - .selectExpression("en.createdbyuserinfo ->> 'username' as createdbyusername") - .build(); - public static final AnalyticsTableColumn CREATED_BY_NAME = - AnalyticsTableColumn.builder() - .name(EnrollmentAnalyticsColumnName.CREATED_BY_NAME_COLUMN_NAME) - .dataType(VARCHAR_255) - .selectExpression("en.createdbyuserinfo ->> 'firstName' as createdbyname") - .skipIndex(Skip.SKIP) - .build(); - public static final AnalyticsTableColumn CREATED_BY_LASTNAME = - AnalyticsTableColumn.builder() - .name(EnrollmentAnalyticsColumnName.CREATED_BY_LASTNAME_COLUMN_NAME) - .dataType(VARCHAR_255) - .selectExpression("en.createdbyuserinfo ->> 'surname' as createdbylastname") - .skipIndex(Skip.SKIP) - .build(); - public static final AnalyticsTableColumn CREATED_BY_DISPLAYNAME = - AnalyticsTableColumn.builder() - .name(EnrollmentAnalyticsColumnName.CREATED_BY_DISPLAY_NAME_COLUMN_NAME) - .dataType(VARCHAR_255) - .selectExpression(getDisplayName("createdbyuserinfo", "en", "createdbydisplayname")) - .skipIndex(Skip.SKIP) - .build(); - public static final AnalyticsTableColumn LAST_UPDATED_BY_USERNAME = - AnalyticsTableColumn.builder() - .name(EnrollmentAnalyticsColumnName.LAST_UPDATED_BY_USERNAME_COLUMN_NAME) - .dataType(VARCHAR_255) - .selectExpression("en.lastupdatedbyuserinfo ->> 'username' as lastupdatedbyusername") - .build(); - public static final AnalyticsTableColumn LAST_UPDATED_BY_NAME = - AnalyticsTableColumn.builder() - .name(EnrollmentAnalyticsColumnName.LAST_UPDATED_BY_NAME_COLUMN_NAME) - .dataType(VARCHAR_255) - .selectExpression("en.lastupdatedbyuserinfo ->> 'firstName' as lastupdatedbyname") - .skipIndex(Skip.SKIP) - .build(); - public static final AnalyticsTableColumn LAST_UPDATED_BY_LASTNAME = - AnalyticsTableColumn.builder() - .name(EnrollmentAnalyticsColumnName.LAST_UPDATED_BY_LASTNAME_COLUMN_NAME) - .dataType(VARCHAR_255) - .selectExpression("en.lastupdatedbyuserinfo ->> 'surname' as lastupdatedbylastname") - .skipIndex(Skip.SKIP) - .build(); - public static final AnalyticsTableColumn LAST_UPDATED_BY_DISPLAYNAME = - AnalyticsTableColumn.builder() - .name(EnrollmentAnalyticsColumnName.LAST_UPDATED_BY_DISPLAY_NAME_COLUMN_NAME) - .dataType(VARCHAR_255) - .selectExpression( - getDisplayName("lastupdatedbyuserinfo", "en", "lastupdatedbydisplayname")) - .skipIndex(Skip.SKIP) - .build(); - public static final AnalyticsTableColumn ENROLLMENT_STATUS = + private static final AnalyticsTableColumn ENROLLMENT_STATUS = AnalyticsTableColumn.builder() .name(EnrollmentAnalyticsColumnName.ENROLLMENT_STATUS_COLUMN_NAME) .dataType(VARCHAR_50) .selectExpression("en.status") .build(); - public static final AnalyticsTableColumn LONGITUDE = + private static final AnalyticsTableColumn LONGITUDE = AnalyticsTableColumn.builder() .name(EnrollmentAnalyticsColumnName.LONGITUDE_COLUMN_NAME) .dataType(DOUBLE) .selectExpression( "CASE WHEN 'POINT' = GeometryType(en.geometry) THEN ST_X(en.geometry) ELSE null END") .build(); - public static final AnalyticsTableColumn LATITUDE = + private static final AnalyticsTableColumn LATITUDE = AnalyticsTableColumn.builder() .name(EnrollmentAnalyticsColumnName.LATITUDE_COLUMN_NAME) .dataType(DOUBLE) .selectExpression( "CASE WHEN 'POINT' = GeometryType(en.geometry) THEN ST_Y(en.geometry) ELSE null END") .build(); - public static final AnalyticsTableColumn OU = + private static final AnalyticsTableColumn OU = AnalyticsTableColumn.builder() .name(EnrollmentAnalyticsColumnName.OU_COLUMN_NAME) .dataType(CHARACTER_11) .nullable(NOT_NULL) .selectExpression("ou.uid") .build(); - public static final AnalyticsTableColumn OU_NAME = + private static final AnalyticsTableColumn OU_NAME = AnalyticsTableColumn.builder() .name(EnrollmentAnalyticsColumnName.OU_NAME_COLUMN_NAME) .dataType(TEXT) .nullable(NOT_NULL) .selectExpression("ou.name") .build(); - public static final AnalyticsTableColumn OU_CODE = + private static final AnalyticsTableColumn OU_CODE = AnalyticsTableColumn.builder() .name(EnrollmentAnalyticsColumnName.OU_CODE_COLUMN_NAME) .dataType(TEXT) .selectExpression("ou.code") .build(); - public static final AnalyticsTableColumn OU_LEVEL = + private static final AnalyticsTableColumn OU_LEVEL = AnalyticsTableColumn.builder() .name(EnrollmentAnalyticsColumnName.OU_LEVEL_COLUMN_NAME) .dataType(INTEGER) .selectExpression("ous.level") .build(); - public static final AnalyticsTableColumn ENROLLMENT_GEOMETRY = + private static final AnalyticsTableColumn ENROLLMENT_GEOMETRY = AnalyticsTableColumn.builder() .name(EnrollmentAnalyticsColumnName.ENROLLMENT_GEOMETRY_COLUMN_NAME) .dataType(GEOMETRY) .selectExpression("en.geometry") .indexType(IndexType.GIST) .build(); - public static final AnalyticsTableColumn REGISTRATION_OU = + private static final AnalyticsTableColumn REGISTRATION_OU = AnalyticsTableColumn.builder() .name(EnrollmentAnalyticsColumnName.REGISTRATION_OU_COLUMN_NAME) .dataType(CHARACTER_11) .nullable(NOT_NULL) .selectExpression("coalesce(registrationou.uid,ou.uid)") .build(); - public static final AnalyticsTableColumn TRACKED_ENTITY_GEOMETRY = + static final AnalyticsTableColumn TRACKED_ENTITY_GEOMETRY = AnalyticsTableColumn.builder() .name(EnrollmentAnalyticsColumnName.TRACKED_ENTITY_GEOMETRY_COLUMN_NAME) .dataType(GEOMETRY) .selectExpression("te.geometry") .build(); + + private static final List COMMON_COLUMNS = + List.of( + ENROLLMENT, + ENROLLMENT_DATE, + OCCURRED_DATE, + COMPLETED_DATE, + LAST_UPDATED, + STORED_BY, + ENROLLMENT_STATUS, + OU, + OU_NAME, + OU_CODE, + OU_LEVEL, + REGISTRATION_OU); + + // Geometry-specific columns + private static final List GEOMETRY_COLUMNS = + List.of(ENROLLMENT_GEOMETRY, LONGITUDE, LATITUDE); + + private static List createJsonColumns(SqlBuilder sqlBuilder) { + + return List.of( + AnalyticsTableColumn.builder() + .name(EnrollmentAnalyticsColumnName.CREATED_BY_USERNAME_COLUMN_NAME) + .dataType(VARCHAR_255) + .selectExpression( + sqlBuilder.jsonExtract("en.createdbyuserinfo", "username") + + " as createdbyusername") + .build(), + AnalyticsTableColumn.builder() + .name(EnrollmentAnalyticsColumnName.CREATED_BY_NAME_COLUMN_NAME) + .dataType(VARCHAR_255) + .selectExpression( + sqlBuilder.jsonExtract("en.createdbyuserinfo", "firstName") + " as createdbyname") + .skipIndex(Skip.SKIP) + .build(), + AnalyticsTableColumn.builder() + .name(EnrollmentAnalyticsColumnName.CREATED_BY_LASTNAME_COLUMN_NAME) + .dataType(VARCHAR_255) + .selectExpression( + sqlBuilder.jsonExtract("en.createdbyuserinfo", "surname") + " as createdbylastname") + .skipIndex(Skip.SKIP) + .build(), + AnalyticsTableColumn.builder() + .name(EnrollmentAnalyticsColumnName.CREATED_BY_DISPLAY_NAME_COLUMN_NAME) + .dataType(VARCHAR_255) + .selectExpression( + getDisplayName("createdbyuserinfo", "en", "createdbydisplayname", sqlBuilder)) + .skipIndex(Skip.SKIP) + .build(), + AnalyticsTableColumn.builder() + .name(EnrollmentAnalyticsColumnName.LAST_UPDATED_BY_USERNAME_COLUMN_NAME) + .dataType(VARCHAR_255) + .selectExpression( + sqlBuilder.jsonExtract("en.lastupdatedbyuserinfo", "username") + + " as lastupdatedbyusername") + .build(), + AnalyticsTableColumn.builder() + .name(EnrollmentAnalyticsColumnName.LAST_UPDATED_BY_NAME_COLUMN_NAME) + .dataType(VARCHAR_255) + .selectExpression( + sqlBuilder.jsonExtract("en.lastupdatedbyuserinfo", "firstName") + + " as lastupdatedbyname") + .skipIndex(Skip.SKIP) + .build(), + AnalyticsTableColumn.builder() + .name(EnrollmentAnalyticsColumnName.LAST_UPDATED_BY_LASTNAME_COLUMN_NAME) + .dataType(VARCHAR_255) + .selectExpression( + sqlBuilder.jsonExtract("en.lastupdatedbyuserinfo", "surname") + + " as lastupdatedbylastname") + .skipIndex(Skip.SKIP) + .build(), + AnalyticsTableColumn.builder() + .name(EnrollmentAnalyticsColumnName.LAST_UPDATED_BY_DISPLAY_NAME_COLUMN_NAME) + .dataType(VARCHAR_255) + .selectExpression( + getDisplayName( + "lastupdatedbyuserinfo", "en", "lastupdatedbydisplayname", sqlBuilder)) + .skipIndex(Skip.SKIP) + .build()); + } + + public static List getColumns(SqlBuilder sqlBuilder) { + List columns = new ArrayList<>(COMMON_COLUMNS); + columns.addAll(createJsonColumns(sqlBuilder)); + // Add database-specific columns based on SqlBuilder capabilities + if (sqlBuilder.supportsGeospatialData()) { + columns.addAll(GEOMETRY_COLUMNS); + } + return columns; + } } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/EventAnalyticsColumn.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/EventAnalyticsColumn.java index a5b0435e6a69..4ca92d43f72a 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/EventAnalyticsColumn.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/EventAnalyticsColumn.java @@ -38,22 +38,27 @@ import static org.hisp.dhis.db.model.DataType.VARCHAR_50; import static org.hisp.dhis.db.model.constraint.Nullable.NOT_NULL; +import java.util.ArrayList; +import java.util.List; import lombok.NoArgsConstructor; import org.hisp.dhis.analytics.table.model.AnalyticsTableColumn; import org.hisp.dhis.analytics.table.model.Skip; import org.hisp.dhis.db.model.IndexType; +import org.hisp.dhis.db.sql.SqlBuilder; @NoArgsConstructor(access = lombok.AccessLevel.PRIVATE) public final class EventAnalyticsColumn { - public static final AnalyticsTableColumn EVENT = + // Common columns that work across all databases + + private static final AnalyticsTableColumn EVENT = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.EVENT_COLUMN_NAME) .dataType(CHARACTER_11) .nullable(NOT_NULL) .selectExpression("ev.uid") .build(); - public static final AnalyticsTableColumn ENROLLMENT = + private static final AnalyticsTableColumn ENROLLMENT = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.ENROLLMENT_COLUMN_NAME) .dataType(CHARACTER_11) @@ -66,45 +71,45 @@ public final class EventAnalyticsColumn { .dataType(CHARACTER_11) .selectExpression("te.uid") .build(); - public static final AnalyticsTableColumn PS = + private static final AnalyticsTableColumn PS = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.PS_COLUMN_NAME) .dataType(CHARACTER_11) .nullable(NOT_NULL) .selectExpression("ps.uid") .build(); - public static final AnalyticsTableColumn AO = + private static final AnalyticsTableColumn AO = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.AO_COLUMN_NAME) .dataType(CHARACTER_11) .nullable(NOT_NULL) .selectExpression("ao.uid") .build(); - public static final AnalyticsTableColumn ENROLLMENT_DATE = + private static final AnalyticsTableColumn ENROLLMENT_DATE = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.ENROLLMENT_DATE_COLUMN_NAME) .dataType(TIMESTAMP) .selectExpression("en.enrollmentdate") .build(); - public static final AnalyticsTableColumn ENROLLMENT_OCCURRED_DATE = + private static final AnalyticsTableColumn ENROLLMENT_OCCURRED_DATE = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.ENROLLMENT_OCCURRED_DATE_COLUMN_NAME) .dataType(TIMESTAMP) .selectExpression("en.occurreddate") .build(); - public static final AnalyticsTableColumn OCCURRED_DATE = + private static final AnalyticsTableColumn OCCURRED_DATE = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.OCCURRED_DATE_COLUMN_NAME) .dataType(TIMESTAMP) .selectExpression("ev.occurreddate") .build(); - public static final AnalyticsTableColumn SCHEDULED_DATE = + private static final AnalyticsTableColumn SCHEDULED_DATE = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.SCHEDULED_DATE_COLUMN_NAME) .dataType(TIMESTAMP) .selectExpression("ev.scheduleddate") .build(); - public static final AnalyticsTableColumn COMPLETED_DATE = + private static final AnalyticsTableColumn COMPLETED_DATE = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.COMPLETED_DATE_COLUMN_NAME) .dataType(TIMESTAMP) @@ -115,92 +120,38 @@ public final class EventAnalyticsColumn { * DHIS2-14981: Use the client-side timestamp if available, otherwise * the server-side timestamp. Applies to both created and lastupdated. */ - public static final AnalyticsTableColumn CREATED = + private static final AnalyticsTableColumn CREATED = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.CREATED_COLUMN_NAME) .dataType(TIMESTAMP) .selectExpression(firstIfNotNullOrElse("ev.createdatclient", "ev.created")) .build(); - public static final AnalyticsTableColumn LAST_UPDATED = + private static final AnalyticsTableColumn LAST_UPDATED = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.LAST_UPDATED_COLUMN_NAME) .dataType(TIMESTAMP) .selectExpression(firstIfNotNullOrElse("ev.lastupdatedatclient", "ev.lastupdated")) .build(); - public static final AnalyticsTableColumn STOREDBY = + private static final AnalyticsTableColumn STOREDBY = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.STORED_BY_COLUMN_NAME) .dataType(VARCHAR_255) .selectExpression("ev.storedby") .build(); - public static final AnalyticsTableColumn CREATED_BY_USERNAME = - AnalyticsTableColumn.builder() - .name(EventAnalyticsColumnName.CREATED_BY_USERNAME_COLUMN_NAME) - .dataType(VARCHAR_255) - .selectExpression("ev.createdbyuserinfo ->> 'username' as createdbyusername") - .build(); - public static final AnalyticsTableColumn CREATED_BY_NAME = - AnalyticsTableColumn.builder() - .name(EventAnalyticsColumnName.CREATED_BY_NAME_COLUMN_NAME) - .dataType(VARCHAR_255) - .selectExpression("ev.createdbyuserinfo ->> 'firstName' as createdbyname") - .skipIndex(Skip.SKIP) - .build(); - public static final AnalyticsTableColumn CREATED_BY_LASTNAME = - AnalyticsTableColumn.builder() - .name(EventAnalyticsColumnName.CREATED_BY_LASTNAME_COLUMN_NAME) - .dataType(VARCHAR_255) - .selectExpression("ev.createdbyuserinfo ->> 'surname' as createdbylastname") - .skipIndex(Skip.SKIP) - .build(); - public static final AnalyticsTableColumn CREATED_BY_DISPLAYNAME = - AnalyticsTableColumn.builder() - .name(EventAnalyticsColumnName.CREATED_BY_DISPLAYNAME_COLUMN_NAME) - .dataType(VARCHAR_255) - .selectExpression(getDisplayName("createdbyuserinfo", "ev", "createdbydisplayname")) - .skipIndex(Skip.SKIP) - .build(); - public static final AnalyticsTableColumn LAST_UPDATED_BY_USERNAME = - AnalyticsTableColumn.builder() - .name(EventAnalyticsColumnName.LAST_UPDATED_BY_USERNAME_COLUMN_NAME) - .dataType(VARCHAR_255) - .selectExpression("ev.lastupdatedbyuserinfo ->> 'username' as lastupdatedbyusername") - .build(); - public static final AnalyticsTableColumn LAST_UPDATED_BY_NAME = - AnalyticsTableColumn.builder() - .name(EventAnalyticsColumnName.LAST_UPDATED_BY_NAME_COLUMN_NAME) - .dataType(VARCHAR_255) - .selectExpression("ev.lastupdatedbyuserinfo ->> 'firstName' as lastupdatedbyname") - .skipIndex(Skip.SKIP) - .build(); - public static final AnalyticsTableColumn LAST_UPDATED_BY_LASTNAME = - AnalyticsTableColumn.builder() - .name(EventAnalyticsColumnName.LAST_UPDATED_BY_LASTNAME_COLUMN_NAME) - .dataType(VARCHAR_255) - .selectExpression("ev.lastupdatedbyuserinfo ->> 'surname' as lastupdatedbylastname") - .skipIndex(Skip.SKIP) - .build(); - public static final AnalyticsTableColumn LAST_UPDATED_BY_DISPLAYNAME = - AnalyticsTableColumn.builder() - .name(EventAnalyticsColumnName.LAST_UPDATED_BY_DISPLAYNAME_COLUMN_NAME) - .dataType(VARCHAR_255) - .selectExpression( - getDisplayName("lastupdatedbyuserinfo", "ev", "lastupdatedbydisplayname")) - .skipIndex(Skip.SKIP) - .build(); - public static final AnalyticsTableColumn EVENT_STATUS = + + private static final AnalyticsTableColumn EVENT_STATUS = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.EVENT_STATUS_COLUMN_NAME) .dataType(VARCHAR_50) .selectExpression("ev.status") .build(); - public static final AnalyticsTableColumn ENROLLMENT_STATUS = + private static final AnalyticsTableColumn ENROLLMENT_STATUS = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.ENROLLMENT_STATUS_COLUMN_NAME) .dataType(VARCHAR_50) .selectExpression("en.status") .build(); - public static final AnalyticsTableColumn EVENT_GEOMETRY = + private static final AnalyticsTableColumn EVENT_GEOMETRY = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.EVENT_GEOMETRY_COLUMN_NAME) .dataType(GEOMETRY) @@ -208,68 +159,68 @@ public final class EventAnalyticsColumn { .indexType(IndexType.GIST) .build(); // TODO latitude and longitude deprecated in 2.30, remove in 2.33 - public static final AnalyticsTableColumn LONGITUDE = + private static final AnalyticsTableColumn LONGITUDE = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.LONGITUDE_COLUMN_NAME) .dataType(DOUBLE) .selectExpression( "CASE WHEN 'POINT' = GeometryType(ev.geometry) THEN ST_X(ev.geometry) ELSE null END") .build(); - public static final AnalyticsTableColumn LATITUDE = + private static final AnalyticsTableColumn LATITUDE = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.LATITUDE_COLUMN_NAME) .dataType(DOUBLE) .selectExpression( "CASE WHEN 'POINT' = GeometryType(ev.geometry) THEN ST_Y(ev.geometry) ELSE null END") .build(); - public static final AnalyticsTableColumn OU = + private static final AnalyticsTableColumn OU = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.OU_COLUMN_NAME) .dataType(CHARACTER_11) .nullable(NOT_NULL) .selectExpression("ou.uid") .build(); - public static final AnalyticsTableColumn OU_NAME = + private static final AnalyticsTableColumn OU_NAME = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.OU_NAME_COLUMN_NAME) .dataType(TEXT) .nullable(NOT_NULL) .selectExpression("ou.name") .build(); - public static final AnalyticsTableColumn OU_CODE = + private static final AnalyticsTableColumn OU_CODE = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.OU_CODE_COLUMN_NAME) .dataType(TEXT) .selectExpression("ou.code") .build(); - public static final AnalyticsTableColumn OU_LEVEL = + private static final AnalyticsTableColumn OU_LEVEL = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.OU_LEVEL_COLUMN_NAME) .dataType(INTEGER) .selectExpression("ous.level") .build(); - public static final AnalyticsTableColumn OU_GEOMETRY = + private static final AnalyticsTableColumn OU_GEOMETRY = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.OU_GEOMETRY_COLUMN_NAME) .dataType(GEOMETRY) .selectExpression("ou.geometry") .indexType(IndexType.GIST) .build(); - public static final AnalyticsTableColumn ENROLLMENT_GEOMETRY = + private static final AnalyticsTableColumn ENROLLMENT_GEOMETRY = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.ENROLLMENT_GEOMETRY_COLUMN_NAME) .dataType(GEOMETRY) .selectExpression("en.geometry") .indexType(IndexType.GIST) .build(); - public static final AnalyticsTableColumn REGISTRATION_OU = + private static final AnalyticsTableColumn REGISTRATION_OU = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.REGISTRATION_OU_COLUMN_NAME) .dataType(CHARACTER_11) .nullable(NOT_NULL) .selectExpression("coalesce(registrationou.uid,ou.uid)") .build(); - public static final AnalyticsTableColumn ENROLLMENT_OU = + private static final AnalyticsTableColumn ENROLLMENT_OU = AnalyticsTableColumn.builder() .name(EventAnalyticsColumnName.ENROLLMENT_OU_COLUMN_NAME) .dataType(CHARACTER_11) @@ -283,6 +234,111 @@ public final class EventAnalyticsColumn { .selectExpression("te.geometry") .build(); + private static final List COMMON_COLUMNS = + List.of( + EVENT, + ENROLLMENT, + PS, + AO, + ENROLLMENT_DATE, + ENROLLMENT_OCCURRED_DATE, + OCCURRED_DATE, + SCHEDULED_DATE, + COMPLETED_DATE, + CREATED, + LAST_UPDATED, + STOREDBY, + EVENT_STATUS, + ENROLLMENT_STATUS, + OU, + OU_NAME, + OU_CODE, + OU_LEVEL, + REGISTRATION_OU, + ENROLLMENT_OU); + + // Geometry-specific columns + private static final List GEOMETRY_COLUMNS = + List.of(EVENT_GEOMETRY, OU_GEOMETRY, ENROLLMENT_GEOMETRY, LONGITUDE, LATITUDE); + + // JSON-specific columns (might vary by database) + private static List createJsonColumns(SqlBuilder sqlBuilder) { + return List.of( + AnalyticsTableColumn.builder() + .name(EventAnalyticsColumnName.CREATED_BY_USERNAME_COLUMN_NAME) + .dataType(VARCHAR_255) + .selectExpression( + sqlBuilder.jsonExtract("ev.createdbyuserinfo", "username") + + " as createdbyusername") + .build(), + AnalyticsTableColumn.builder() + .name(EventAnalyticsColumnName.CREATED_BY_NAME_COLUMN_NAME) + .dataType(VARCHAR_255) + .selectExpression( + sqlBuilder.jsonExtract("ev.createdbyuserinfo", "firstName") + " as createdbyname") + .skipIndex(Skip.SKIP) + .build(), + AnalyticsTableColumn.builder() + .name(EventAnalyticsColumnName.CREATED_BY_LASTNAME_COLUMN_NAME) + .dataType(VARCHAR_255) + .selectExpression( + sqlBuilder.jsonExtract("ev.createdbyuserinfo", "surname") + " as createdbylastname") + .skipIndex(Skip.SKIP) + .build(), + AnalyticsTableColumn.builder() + .name(EventAnalyticsColumnName.CREATED_BY_DISPLAYNAME_COLUMN_NAME) + .dataType(VARCHAR_255) + .selectExpression( + getDisplayName("createdbyuserinfo", "ev", "createdbydisplayname", sqlBuilder)) + .skipIndex(Skip.SKIP) + .build(), + AnalyticsTableColumn.builder() + .name(EventAnalyticsColumnName.LAST_UPDATED_BY_USERNAME_COLUMN_NAME) + .dataType(VARCHAR_255) + .selectExpression( + sqlBuilder.jsonExtract("ev.lastupdatedbyuserinfo", "username") + + " as lastupdatedbyusername") + .build(), + AnalyticsTableColumn.builder() + .name(EventAnalyticsColumnName.LAST_UPDATED_BY_NAME_COLUMN_NAME) + .dataType(VARCHAR_255) + .selectExpression( + sqlBuilder.jsonExtract("ev.lastupdatedbyuserinfo", "firstName") + + " as lastupdatedbyname") + .skipIndex(Skip.SKIP) + .build(), + AnalyticsTableColumn.builder() + .name(EventAnalyticsColumnName.LAST_UPDATED_BY_LASTNAME_COLUMN_NAME) + .dataType(VARCHAR_255) + .selectExpression( + sqlBuilder.jsonExtract("ev.lastupdatedbyuserinfo", "surname") + + " as lastupdatedbylastname") + .skipIndex(Skip.SKIP) + .build(), + AnalyticsTableColumn.builder() + .name(EventAnalyticsColumnName.LAST_UPDATED_BY_DISPLAYNAME_COLUMN_NAME) + .dataType(VARCHAR_255) + .selectExpression( + getDisplayName( + "lastupdatedbyuserinfo", "ev", "lastupdatedbydisplayname", sqlBuilder)) + .skipIndex(Skip.SKIP) + .build()); + } + + /** Returns the appropriate set of columns based on the SqlBuilder type */ + public static List getColumns(SqlBuilder sqlBuilder) { + List columns = new ArrayList<>(COMMON_COLUMNS); + + // Add database-specific columns based on SqlBuilder capabilities + if (sqlBuilder.supportsGeospatialData()) { + columns.addAll(GEOMETRY_COLUMNS); + } + + columns.addAll(createJsonColumns(sqlBuilder)); + + return columns; + } + /** * Returns a SQL expression that returns the first argument if it is not null, otherwise the * second argument. diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcAnalyticsTableManager.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcAnalyticsTableManager.java index 6225d8baa936..48bdf885800c 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcAnalyticsTableManager.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcAnalyticsTableManager.java @@ -29,6 +29,7 @@ import static org.hisp.dhis.analytics.table.model.AnalyticsValueType.FACT; import static org.hisp.dhis.analytics.table.util.PartitionUtils.getLatestTablePartition; +import static org.hisp.dhis.commons.util.TextUtils.emptyIfTrue; import static org.hisp.dhis.commons.util.TextUtils.format; import static org.hisp.dhis.commons.util.TextUtils.replace; import static org.hisp.dhis.db.model.DataType.CHARACTER_11; @@ -77,7 +78,6 @@ import org.hisp.dhis.setting.SystemSettings; import org.hisp.dhis.setting.SystemSettingsProvider; import org.hisp.dhis.system.database.DatabaseInfoProvider; -import org.hisp.dhis.system.util.MathUtils; import org.hisp.dhis.util.DateUtils; import org.hisp.dhis.util.ObjectUtils; import org.springframework.beans.factory.annotation.Qualifier; @@ -160,6 +160,8 @@ public class JdbcAnalyticsTableManager extends AbstractJdbcTableManager { .selectExpression("ous.level as oulevel") .build()); + private static final List SORT_KEY = List.of("dx", "co"); + public JdbcAnalyticsTableManager( IdentifiableObjectManager idObjectManager, OrganisationUnitService organisationUnitService, @@ -205,7 +207,7 @@ public List getAnalyticsTables(AnalyticsTableUpdateParams params AnalyticsTable table = params.isLatestUpdate() ? getLatestAnalyticsTable(params, getColumns(params)) - : getRegularAnalyticsTable(params, getDataYears(params), getColumns(params)); + : getRegularAnalyticsTable(params, getDataYears(params), getColumns(params), SORT_KEY); return table.hasTablePartitions() ? List.of(table) : List.of(); } @@ -219,10 +221,10 @@ public boolean validState() { @Override public boolean hasUpdatedLatestData(Date startDate, Date endDate) { String sql = - replace( + replaceQualify( """ select dv.dataelementid \ - from datavalue dv \ + from ${datavalue} dv \ where dv.lastupdated >= '${startDate}' and dv.lastupdated < '${endDate}' \ limit 1;""", Map.of("startDate", toLongDate(startDate), "endDate", toLongDate(endDate))); @@ -240,20 +242,20 @@ public void preCreateTables(AnalyticsTableUpdateParams params) { public void removeUpdatedData(List tables) { AnalyticsTablePartition partition = getLatestTablePartition(tables); String sql = - replace( + replaceQualify( """ delete from ${tableName} ax \ where ax.id in ( \ - select concat(de.uid,'-',ps.iso,'-',ou.uid,'-',co.uid,'-',ao.uid) as id \ - from datavalue dv \ - inner join dataelement de on dv.dataelementid=de.dataelementid \ + select concat(des.dataelementuid,'-',ps.iso,'-',ous.organisationunituid,'-',dcs.categoryoptioncombouid,'-',acs.categoryoptioncombouid) as id \ + from ${datavalue} dv \ + inner join analytics_rs_dataelementstructure des on dv.dataelementid=des.dataelementid \ inner join analytics_rs_periodstructure ps on dv.periodid=ps.periodid \ - inner join organisationunit ou on dv.sourceid=ou.organisationunitid \ - inner join categoryoptioncombo co on dv.categoryoptioncomboid=co.categoryoptioncomboid \ - inner join categoryoptioncombo ao on dv.attributeoptioncomboid=ao.categoryoptioncomboid \ + inner join analytics_rs_orgunitstructure ous on dv.sourceid=ous.organisationunitid \ + inner join analytics_rs_categorystructure dcs on dv.categoryoptioncomboid=dcs.categoryoptioncomboid \ + inner join analytics_rs_categorystructure acs on dv.attributeoptioncomboid=acs.categoryoptioncomboid \ where dv.lastupdated >= '${startDate}'and dv.lastupdated < '${endDate}');""", Map.of( - "tableName", quote(getAnalyticsTableType().getTableName()), + "tableName", qualify(getAnalyticsTableType().getTableName()), "startDate", toLongDate(partition.getStartDate()), "endDate", toLongDate(partition.getEndDate()))); @@ -275,11 +277,7 @@ public void populateTable(AnalyticsTableUpdateParams params, AnalyticsTableParti String doubleDataType = sqlBuilder.dataTypeDouble(); String numericClause = - skipDataTypeValidation - ? "" - : replace( - "and dv.value ~* '${expression}'", - Map.of("expression", MathUtils.NUMERIC_LENIENT_REGEXP)); + skipDataTypeValidation ? "" : "and " + sqlBuilder.regexpMatch("dv.value", NUMERIC_REGEXP); String zeroValueCondition = includeZeroValues ? " or des.zeroissignificant = true" : ""; String zeroValueClause = replace( @@ -359,7 +357,7 @@ private void populateTable( } sql.append( - replace( + replaceQualify( """ ${approvalSelectExpression} \ as approvallevel, \ @@ -367,13 +365,12 @@ private void populateTable( ps.daysno as daysno, \ ${valueExpression} as value, \ ${textValueExpression} as textvalue \ - from datavalue dv \ + from ${datavalue} dv \ inner join analytics_rs_periodstructure ps on dv.periodid=ps.periodid \ inner join analytics_rs_dataelementstructure des on dv.dataelementid=des.dataelementid \ inner join analytics_rs_dataelementgroupsetstructure degs on dv.dataelementid=degs.dataelementid \ inner join analytics_rs_orgunitstructure ous on dv.sourceid=ous.organisationunitid \ inner join analytics_rs_organisationunitgroupsetstructure ougs on dv.sourceid=ougs.organisationunitid \ - and (cast(${peStartDateMonth} as date)=ougs.startdate or ougs.startdate is null) \ inner join analytics_rs_categorystructure dcs on dv.categoryoptioncomboid=dcs.categoryoptioncomboid \ inner join analytics_rs_categorystructure acs on dv.attributeoptioncomboid=acs.categoryoptioncomboid \ inner join analytics_rs_categoryoptioncomboname aon on dv.attributeoptioncomboid=aon.categoryoptioncomboid \ @@ -381,8 +378,7 @@ private void populateTable( Map.of( "approvalSelectExpression", approvalSelectExpression, "valueExpression", valueExpression, - "textValueExpression", textValueExpression, - "peStartDateMonth", sqlBuilder.dateTrunc("month", "ps.startdate")))); + "textValueExpression", textValueExpression))); if (!params.isSkipOutliers()) { sql.append(getOutliersJoinStatement()); @@ -395,6 +391,7 @@ private void populateTable( where des.valuetype in (${valTypes}) \ and des.domaintype = 'AGGREGATE' \ ${partitionClause} \ + and (ougs.startdate is null or ps.monthstartdate=ougs.startdate) \ and dv.lastupdated < '${startTime}' \ and dv.value is not null \ and dv.deleted = false\s""", @@ -481,7 +478,9 @@ private String getPartitionClause(AnalyticsTablePartition partition) { format("and dv.lastupdated >= '{}' ", toLongDate(partition.getStartDate())); String partitionFilter = format("and ps.year = {} ", partition.getYear()); - return partition.isLatestPartition() ? latestFilter : partitionFilter; + return partition.isLatestPartition() + ? latestFilter + : emptyIfTrue(partitionFilter, sqlBuilder.supportsDeclarativePartitioning()); } private List getColumns(AnalyticsTableUpdateParams params) { @@ -661,19 +660,19 @@ private List getOutlierStatsColumns() { private List getDataYears(AnalyticsTableUpdateParams params) { StringBuilder sql = new StringBuilder( - replace( + replaceQualify( """ - select distinct(extract(year from pe.startdate)) \ - from datavalue dv \ - inner join period pe on dv.periodid=pe.periodid \ - where pe.startdate is not null \ + select distinct(extract(year from pes.startdate)) \ + from ${datavalue} dv \ + inner join analytics_rs_periodstructure pes on dv.periodid=pes.periodid \ + where pes.startdate is not null \ and dv.lastupdated < '${startTime}'\s""", Map.of("startTime", toLongDate(params.getStartTime())))); - if (params.getFromDate() != null) { + if (params.hasFromDate()) { sql.append( replace( - "and pe.startdate >= '${fromDate}'", + "and pes.startdate >= '${fromDate}'", Map.of("fromDate", DateUtils.toMediumDate(params.getFromDate())))); } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcCompletenessTableManager.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcCompletenessTableManager.java index e5bbed3ddf81..ea052fd5de76 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcCompletenessTableManager.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcCompletenessTableManager.java @@ -29,6 +29,7 @@ import static org.hisp.dhis.analytics.table.model.AnalyticsValueType.FACT; import static org.hisp.dhis.analytics.table.util.PartitionUtils.getLatestTablePartition; +import static org.hisp.dhis.commons.util.TextUtils.emptyIfTrue; import static org.hisp.dhis.commons.util.TextUtils.format; import static org.hisp.dhis.commons.util.TextUtils.replace; import static org.hisp.dhis.db.model.DataType.BOOLEAN; @@ -37,6 +38,7 @@ import static org.hisp.dhis.db.model.DataType.TEXT; import static org.hisp.dhis.db.model.DataType.TIMESTAMP; import static org.hisp.dhis.db.model.constraint.Nullable.NOT_NULL; +import static org.hisp.dhis.util.DateUtils.SECONDS_PER_DAY; import static org.hisp.dhis.util.DateUtils.toLongDate; import java.util.ArrayList; @@ -89,6 +91,8 @@ public class JdbcCompletenessTableManager extends AbstractJdbcTableManager { .selectExpression("ps.year") .build()); + private static final List SORT_KEY = List.of("dx"); + public JdbcCompletenessTableManager( IdentifiableObjectManager idObjectManager, OrganisationUnitService organisationUnitService, @@ -130,7 +134,7 @@ public List getAnalyticsTables(AnalyticsTableUpdateParams params AnalyticsTable table = params.isLatestUpdate() ? getLatestAnalyticsTable(params, getColumns()) - : getRegularAnalyticsTable(params, getDataYears(params), getColumns()); + : getRegularAnalyticsTable(params, getDataYears(params), getColumns(), SORT_KEY); return table.hasTablePartitions() ? List.of(table) : List.of(); } @@ -148,10 +152,10 @@ public boolean validState() { @Override public boolean hasUpdatedLatestData(Date startDate, Date endDate) { String sql = - replace( + replaceQualify( """ select cdr.datasetid \ - from completedatasetregistration cdr \ + from ${completedatasetregistration} cdr \ where cdr.lastupdated >= '${startDate}' \ and cdr.lastupdated < '${endDate}' \ limit 1;""", @@ -164,16 +168,16 @@ public boolean hasUpdatedLatestData(Date startDate, Date endDate) { public void removeUpdatedData(List tables) { AnalyticsTablePartition partition = getLatestTablePartition(tables); String sql = - replace( + replaceQualify( """ delete from ${tableName} ax \ where ax.id in ( \ - select concat(ds.uid,'-',ps.iso,'-',ou.uid,'-',ao.uid) as id \ - from completedatasetregistration cdr \ - inner join dataset ds on cdr.datasetid=ds.datasetid \ + select concat(ds.uid,'-',ps.iso,'-',ous.organisationunituid,'-',acs.categoryoptioncombouid) as id \ + from ${completedatasetregistration} cdr \ + inner join ${dataset} ds on cdr.datasetid=ds.datasetid \ inner join analytics_rs_periodstructure ps on cdr.periodid=ps.periodid \ - inner join organisationunit ou on cdr.sourceid=ou.organisationunitid \ - inner join categoryoptioncombo ao on cdr.attributeoptioncomboid=ao.categoryoptioncomboid \ + inner join analytics_rs_orgunitstructure ous on cdr.sourceid=ous.organisationunitid \ + inner join analytics_rs_categorystructure acs on cdr.attributeoptioncomboid=acs.categoryoptioncomboid \ where cdr.lastupdated >= '${startDate}' \ and cdr.lastupdated < '${endDate}');""", Map.of( @@ -216,23 +220,20 @@ public void populateTable(AnalyticsTableUpdateParams params, AnalyticsTableParti sql = sql.replace("organisationunitid", "sourceid"); sql += - replace( + replaceQualify( """ - from completedatasetregistration cdr \ - inner join dataset ds on cdr.datasetid=ds.datasetid \ + from ${completedatasetregistration} cdr \ + inner join ${dataset} ds on cdr.datasetid=ds.datasetid \ inner join analytics_rs_periodstructure ps on cdr.periodid=ps.periodid \ inner join analytics_rs_organisationunitgroupsetstructure ougs on cdr.sourceid=ougs.organisationunitid \ - and (cast(${peStartDateMonth} as date)=ougs.startdate or ougs.startdate is null) \ left join analytics_rs_orgunitstructure ous on cdr.sourceid=ous.organisationunitid \ inner join analytics_rs_categorystructure acs on cdr.attributeoptioncomboid=acs.categoryoptioncomboid \ - inner join categoryoptioncombo ao on cdr.attributeoptioncomboid=ao.categoryoptioncomboid \ where cdr.date is not null \ ${partitionClause} \ + and (ougs.startdate is null or ps.monthstartdate=ougs.startdate) \ and cdr.lastupdated < '${startTime}' \ and cdr.completed = true""", Map.of( - "peStartDateMonth", - sqlBuilder.dateTrunc("month", "ps.startdate"), "partitionClause", partitionClause, "startTime", @@ -252,13 +253,16 @@ private String getPartitionClause(AnalyticsTablePartition partition) { format("and cdr.lastupdated >= '{}' ", toLongDate(partition.getStartDate())); String partitionFilter = format("and ps.year = {} ", partition.getYear()); - return partition.isLatestPartition() ? latestFilter : partitionFilter; + return partition.isLatestPartition() + ? latestFilter + : emptyIfTrue(partitionFilter, sqlBuilder.supportsDeclarativePartitioning()); } private List getColumns() { - String idColAlias = "concat(ds.uid,'-',ps.iso,'-',ous.organisationunituid,'-',ao.uid) as id "; - String timelyDateDiff = - "extract(epoch from (cdr.date - ps.enddate)) / ( " + DateUtils.SECONDS_PER_DAY + " )"; + String idColAlias = + "concat(ds.uid,'-',ps.iso,'-',ous.organisationunituid,'-',acs.categoryoptioncombouid) as id "; + String diffInSeconds = sqlBuilder.differenceInSeconds("cdr.date", "ps.enddate"); + String timelyDateDiff = diffInSeconds + " / (" + SECONDS_PER_DAY + ")"; String timelyAlias = "((" + timelyDateDiff + ") <= ds.timelydays) as timely"; List columns = new ArrayList<>(); @@ -293,19 +297,19 @@ private List getColumns() { private List getDataYears(AnalyticsTableUpdateParams params) { String sql = - replace( + replaceQualify( """ - select distinct(extract(year from pe.startdate)) \ - from completedatasetregistration cdr \ - inner join period pe on cdr.periodid=pe.periodid \ - where pe.startdate is not null \ + select distinct(extract(year from ps.startdate)) \ + from ${completedatasetregistration} cdr \ + inner join analytics_rs_periodstructure ps on cdr.periodid=ps.periodid \ + where ps.startdate is not null \ and cdr.date < '${startTime}'""", Map.of("startTime", toLongDate(params.getStartTime()))); if (params.getFromDate() != null) { sql += replace( - "and pe.startdate >= '${fromDate}'", + "and ps.startdate >= '${fromDate}'", Map.of("fromDate", DateUtils.toLongDate(params.getFromDate()))); } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcCompletenessTargetTableManager.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcCompletenessTargetTableManager.java index 7be603908d6c..086efebaf47b 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcCompletenessTargetTableManager.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcCompletenessTargetTableManager.java @@ -79,7 +79,7 @@ public class JdbcCompletenessTargetTableManager extends AbstractJdbcTableManager .name("ao") .dataType(CHARACTER_11) .nullable(NOT_NULL) - .selectExpression("ao.uid") + .selectExpression("acs.categoryoptioncombouid") .build(), AnalyticsTableColumn.builder() .name("ouopeningdate") @@ -102,6 +102,8 @@ public class JdbcCompletenessTargetTableManager extends AbstractJdbcTableManager .selectExpression("doc.coenddate") .build()); + private static final List SORT_KEY = List.of("dx"); + public JdbcCompletenessTargetTableManager( IdentifiableObjectManager idObjectManager, OrganisationUnitService organisationUnitService, @@ -143,7 +145,7 @@ public List getAnalyticsTables(AnalyticsTableUpdateParams params Logged logged = analyticsTableSettings.getTableLogged(); return params.isLatestUpdate() ? List.of() - : List.of(new AnalyticsTable(getAnalyticsTableType(), getColumns(), logged)); + : List.of(new AnalyticsTable(getAnalyticsTableType(), getColumns(), SORT_KEY, logged)); } @Override @@ -177,14 +179,14 @@ public void populateTable(AnalyticsTableUpdateParams params, AnalyticsTableParti sql = TextUtils.removeLastComma(sql) + " "; sql += - """ - from analytics_rs_datasetorganisationunitcategory doc - inner join dataset ds on doc.datasetid=ds.datasetid - inner join organisationunit ou on doc.organisationunitid=ou.organisationunitid - left join analytics_rs_orgunitstructure ous on doc.organisationunitid=ous.organisationunitid - left join analytics_rs_organisationunitgroupsetstructure ougs on doc.organisationunitid=ougs.organisationunitid - left join categoryoptioncombo ao on doc.attributeoptioncomboid=ao.categoryoptioncomboid - left join analytics_rs_categorystructure acs on doc.attributeoptioncomboid=acs.categoryoptioncomboid"""; + qualifyVariables( + """ + from analytics_rs_datasetorganisationunitcategory doc \ + inner join analytics_rs_dataset ds on doc.datasetid=ds.datasetid \ + inner join ${organisationunit} ou on doc.organisationunitid=ou.organisationunitid \ + left join analytics_rs_orgunitstructure ous on doc.organisationunitid=ous.organisationunitid \ + left join analytics_rs_organisationunitgroupsetstructure ougs on doc.organisationunitid=ougs.organisationunitid \ + left join analytics_rs_categorystructure acs on doc.attributeoptioncomboid=acs.categoryoptioncomboid"""); invokeTimeAndLog(sql, "Populating table: '{}'", tableName); } 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 bb3f9d4d51cf..a6f0841dc2c0 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 @@ -27,7 +27,6 @@ */ package org.hisp.dhis.analytics.table; -import static org.hisp.dhis.commons.util.TextUtils.replace; import static org.hisp.dhis.util.DateUtils.toLongDate; import java.util.ArrayList; @@ -65,31 +64,7 @@ @Service("org.hisp.dhis.analytics.EnrollmentAnalyticsTableManager") public class JdbcEnrollmentAnalyticsTableManager extends AbstractEventJdbcTableManager { - private static final List FIXED_COLS = - List.of( - EnrollmentAnalyticsColumn.ENROLLMENT, - EnrollmentAnalyticsColumn.ENROLLMENT_DATE, - EnrollmentAnalyticsColumn.OCCURRED_DATE, - EnrollmentAnalyticsColumn.COMPLETED_DATE, - EnrollmentAnalyticsColumn.LAST_UPDATED, - EnrollmentAnalyticsColumn.STORED_BY, - EnrollmentAnalyticsColumn.CREATED_BY_USERNAME, - EnrollmentAnalyticsColumn.CREATED_BY_NAME, - EnrollmentAnalyticsColumn.CREATED_BY_LASTNAME, - EnrollmentAnalyticsColumn.CREATED_BY_DISPLAYNAME, - EnrollmentAnalyticsColumn.LAST_UPDATED_BY_USERNAME, - EnrollmentAnalyticsColumn.LAST_UPDATED_BY_NAME, - EnrollmentAnalyticsColumn.LAST_UPDATED_BY_LASTNAME, - EnrollmentAnalyticsColumn.LAST_UPDATED_BY_DISPLAYNAME, - EnrollmentAnalyticsColumn.ENROLLMENT_STATUS, - EnrollmentAnalyticsColumn.LONGITUDE, - EnrollmentAnalyticsColumn.LATITUDE, - EnrollmentAnalyticsColumn.OU, - EnrollmentAnalyticsColumn.OU_NAME, - EnrollmentAnalyticsColumn.OU_CODE, - EnrollmentAnalyticsColumn.OU_LEVEL, - EnrollmentAnalyticsColumn.ENROLLMENT_GEOMETRY, - EnrollmentAnalyticsColumn.REGISTRATION_OU); + private final List fixedColumns; public JdbcEnrollmentAnalyticsTableManager( IdentifiableObjectManager idObjectManager, @@ -119,6 +94,7 @@ public JdbcEnrollmentAnalyticsTableManager( analyticsExportSettings, periodDataProvider, sqlBuilder); + fixedColumns = EnrollmentAnalyticsColumn.getColumns(sqlBuilder); } @Override @@ -164,14 +140,14 @@ public void populateTable(AnalyticsTableUpdateParams params, AnalyticsTableParti Program program = partition.getMasterTable().getProgram(); String fromClause = - replace( + replaceQualify( """ - \s from enrollment en \ - inner join program pr on en.programid=pr.programid \ - left join trackedentity te on en.trackedentityid=te.trackedentityid \ + \s from ${enrollment} en \ + inner join ${program} pr on en.programid=pr.programid \ + left join ${trackedentity} te on en.trackedentityid=te.trackedentityid \ and te.deleted = false \ - left join organisationunit registrationou on te.organisationunitid=registrationou.organisationunitid \ - inner join organisationunit ou on en.organisationunitid=ou.organisationunitid \ + left join ${organisationunit} registrationou on te.organisationunitid=registrationou.organisationunitid \ + inner join ${organisationunit} ou on en.organisationunitid=ou.organisationunitid \ left join analytics_rs_orgunitstructure ous on en.organisationunitid=ous.organisationunitid \ left join analytics_rs_organisationunitgroupsetstructure ougs on en.organisationunitid=ougs.organisationunitid \ and (cast(${enrollmentDateMonth} as date)=ougs.startdate or ougs.startdate is null) \ @@ -189,9 +165,15 @@ left join analytics_rs_dateperiodstructure dps on cast(en.enrollmentdate as date populateTableInternal(partition, fromClause); } + /** + * Returns a list of columns for the given program. + * + * @param program the {@link Program}. + * @return a list of {@link AnalyticsTableColumn}. + */ private List getColumns(Program program) { List columns = new ArrayList<>(); - columns.addAll(FIXED_COLS); + columns.addAll(fixedColumns); columns.addAll(getOrganisationUnitLevelColumns()); columns.add(getOrganisationUnitNameHierarchyColumn()); columns.addAll(getOrganisationUnitGroupSetColumns()); @@ -200,7 +182,9 @@ private List getColumns(Program program) { if (program.isRegistration()) { columns.add(EnrollmentAnalyticsColumn.TRACKED_ENTITY); - columns.add(EnrollmentAnalyticsColumn.TRACKED_ENTITY_GEOMETRY); + if (sqlBuilder.supportsGeospatialData()) { + columns.add(EnrollmentAnalyticsColumn.TRACKED_ENTITY_GEOMETRY); + } } return filterDimensionColumns(columns); 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 c1d7a3440e2d..077b1284393a 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 @@ -28,17 +28,17 @@ package org.hisp.dhis.analytics.table; import static java.util.stream.Collectors.toList; +import static org.apache.commons.lang3.StringUtils.EMPTY; import static org.hisp.dhis.analytics.table.model.Skip.SKIP; import static org.hisp.dhis.analytics.util.AnalyticsUtils.getClosingParentheses; import static org.hisp.dhis.analytics.util.AnalyticsUtils.getColumnType; +import static org.hisp.dhis.commons.util.TextUtils.emptyIfTrue; import static org.hisp.dhis.commons.util.TextUtils.format; import static org.hisp.dhis.commons.util.TextUtils.replace; import static org.hisp.dhis.db.model.DataType.CHARACTER_11; import static org.hisp.dhis.db.model.DataType.GEOMETRY; +import static org.hisp.dhis.db.model.DataType.INTEGER; import static org.hisp.dhis.db.model.DataType.TEXT; -import static org.hisp.dhis.period.PeriodDataProvider.DataSource.DATABASE; -import static org.hisp.dhis.period.PeriodDataProvider.DataSource.SYSTEM_DEFINED; -import static org.hisp.dhis.system.util.MathUtils.NUMERIC_LENIENT_REGEXP; import static org.hisp.dhis.util.DateUtils.toLongDate; import static org.hisp.dhis.util.DateUtils.toMediumDate; @@ -50,7 +50,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.hisp.dhis.analytics.AnalyticsTableHookService; import org.hisp.dhis.analytics.AnalyticsTableType; @@ -102,41 +101,7 @@ public class JdbcEventAnalyticsTableManager extends AbstractEventJdbcTableManage static final String[] EXPORTABLE_EVENT_STATUSES = {"'COMPLETED'", "'ACTIVE'", "'SCHEDULE'"}; - protected static final List FIXED_COLS = - List.of( - EventAnalyticsColumn.EVENT, - EventAnalyticsColumn.ENROLLMENT, - EventAnalyticsColumn.PS, - EventAnalyticsColumn.AO, - EventAnalyticsColumn.ENROLLMENT_DATE, - EventAnalyticsColumn.ENROLLMENT_OCCURRED_DATE, - EventAnalyticsColumn.OCCURRED_DATE, - EventAnalyticsColumn.SCHEDULED_DATE, - EventAnalyticsColumn.COMPLETED_DATE, - EventAnalyticsColumn.CREATED, - EventAnalyticsColumn.LAST_UPDATED, - EventAnalyticsColumn.STOREDBY, - EventAnalyticsColumn.CREATED_BY_USERNAME, - EventAnalyticsColumn.CREATED_BY_NAME, - EventAnalyticsColumn.CREATED_BY_LASTNAME, - EventAnalyticsColumn.CREATED_BY_DISPLAYNAME, - EventAnalyticsColumn.LAST_UPDATED_BY_USERNAME, - EventAnalyticsColumn.LAST_UPDATED_BY_NAME, - EventAnalyticsColumn.LAST_UPDATED_BY_LASTNAME, - EventAnalyticsColumn.LAST_UPDATED_BY_DISPLAYNAME, - EventAnalyticsColumn.EVENT_STATUS, - EventAnalyticsColumn.ENROLLMENT_STATUS, - EventAnalyticsColumn.EVENT_GEOMETRY, - EventAnalyticsColumn.LONGITUDE, - EventAnalyticsColumn.LATITUDE, - EventAnalyticsColumn.OU, - EventAnalyticsColumn.OU_NAME, - EventAnalyticsColumn.OU_CODE, - EventAnalyticsColumn.OU_LEVEL, - EventAnalyticsColumn.OU_GEOMETRY, - EventAnalyticsColumn.ENROLLMENT_GEOMETRY, - EventAnalyticsColumn.REGISTRATION_OU, - EventAnalyticsColumn.ENROLLMENT_OU); + protected final List fixedColumns; public JdbcEventAnalyticsTableManager( IdentifiableObjectManager idObjectManager, @@ -166,6 +131,7 @@ public JdbcEventAnalyticsTableManager( analyticsExportSettings, periodDataProvider, sqlBuilder); + fixedColumns = EventAnalyticsColumn.getColumns(sqlBuilder); } @Override @@ -182,8 +148,7 @@ public List getAnalyticsTables(AnalyticsTableUpdateParams params isSpatialSupport()); List availableDataYears = - periodDataProvider.getAvailableYears( - analyticsTableSettings.getMaxPeriodYearsOffset() == null ? SYSTEM_DEFINED : DATABASE); + periodDataProvider.getAvailableYears(analyticsTableSettings.getPeriodSource()); return params.isLatestUpdate() ? getLatestAnalyticsTables(params) @@ -256,7 +221,7 @@ private List getLatestAnalyticsTables(AnalyticsTableUpdateParams Assert.isTrue( lastFullTableUpdate.getTime() > 0L, - "A full analytics table update process must be run prior to a latest partition update process"); + "A full analytics table update process must be run prior to a latest partition update"); Date startDate = lastFullTableUpdate; Date endDate = params.getStartTime(); @@ -310,11 +275,11 @@ private List getLatestAnalyticsTables(AnalyticsTableUpdateParams */ private boolean hasUpdatedLatestData(Date startDate, Date endDate, Program program) { String sql = - replace( + replaceQualify( """ select ev.eventid \ - from event ev \ - inner join enrollment en on ev.enrollmentid=en.enrollmentid \ + from ${event} ev \ + inner join ${enrollment} en on ev.enrollmentid=en.enrollmentid \ where en.programid = ${programId} \ and ev.lastupdated >= '${startDate}' \ and ev.lastupdated < '${endDate}' \ @@ -333,17 +298,18 @@ public void removeUpdatedData(List tables) { AnalyticsTablePartition partition = table.getLatestTablePartition(); String sql = - replace( + replaceQualify( """ delete from ${tableName} ax \ where ax.event in ( \ select ev.uid \ - from event ev inner join enrollment en on ev.enrollmentid=en.enrollmentid \ + from ${event} ev \ + inner join ${enrollment} en on ev.enrollmentid=en.enrollmentid \ where en.programid = ${programId} \ and ev.lastupdated >= '${startDate}' \ and ev.lastupdated < '${endDate}');""", Map.of( - "tableName", quote(table.getName()), + "tableName", qualify(table.getName()), "programId", String.valueOf(table.getProgram().getId()), "startDate", toLongDate(partition.getStartDate()), "endDate", toLongDate(partition.getEndDate()))); @@ -361,29 +327,27 @@ protected List getPartitionChecks(Integer year, Date endDate) { @Override public void populateTable(AnalyticsTableUpdateParams params, AnalyticsTablePartition partition) { List availableDataYears = - periodDataProvider.getAvailableYears( - analyticsTableSettings.getMaxPeriodYearsOffset() == null ? SYSTEM_DEFINED : DATABASE); + periodDataProvider.getAvailableYears(analyticsTableSettings.getPeriodSource()); Integer firstDataYear = availableDataYears.get(0); Integer latestDataYear = availableDataYears.get(availableDataYears.size() - 1); Program program = partition.getMasterTable().getProgram(); String partitionClause = getPartitionClause(partition); String fromClause = - replace( + replaceQualify( """ - \sfrom event ev \ - inner join enrollment en on ev.enrollmentid=en.enrollmentid \ - inner join programstage ps on ev.programstageid=ps.programstageid \ - inner join program pr on en.programid=pr.programid and en.deleted = false \ - inner join categoryoptioncombo ao on ev.attributeoptioncomboid=ao.categoryoptioncomboid \ - left join trackedentity te on en.trackedentityid=te.trackedentityid \ - and te.deleted = false \ - left join organisationunit registrationou on te.organisationunitid=registrationou.organisationunitid \ - inner join organisationunit ou on ev.organisationunitid=ou.organisationunitid \ + \sfrom ${event} ev \ + inner join ${enrollment} en on ev.enrollmentid=en.enrollmentid \ + inner join ${programstage} ps on ev.programstageid=ps.programstageid \ + inner join ${program} pr on en.programid=pr.programid and en.deleted = false \ + inner join ${categoryoptioncombo} ao on ev.attributeoptioncomboid=ao.categoryoptioncomboid \ + left join ${trackedentity} te on en.trackedentityid=te.trackedentityid and te.deleted = false \ + left join ${organisationunit} registrationou on te.organisationunitid=registrationou.organisationunitid \ + inner join ${organisationunit} ou on ev.organisationunitid=ou.organisationunitid \ left join analytics_rs_orgunitstructure ous on ev.organisationunitid=ous.organisationunitid \ left join analytics_rs_organisationunitgroupsetstructure ougs on ev.organisationunitid=ougs.organisationunitid \ and (cast(${eventDateMonth} as date)=ougs.startdate or ougs.startdate is null) \ - left join organisationunit enrollmentou on en.organisationunitid=enrollmentou.organisationunitid \ + left join ${organisationunit} enrollmentou on en.organisationunitid=enrollmentou.organisationunitid \ inner join analytics_rs_categorystructure acs on ev.attributeoptioncomboid=acs.categoryoptioncomboid \ left join analytics_rs_dateperiodstructure dps on cast(${eventDateExpression} as date)=dps.dateperiod \ where ev.lastupdated < '${startTime}' ${partitionClause} \ @@ -421,7 +385,9 @@ private String getPartitionClause(AnalyticsTablePartition partition) { String partitionFilter = format("and ({}) >= '{}' and ({}) < '{}' ", statusDate, start, statusDate, end); - return partition.isLatestPartition() ? latestFilter : partitionFilter; + return partition.isLatestPartition() + ? latestFilter + : emptyIfTrue(partitionFilter, sqlBuilder.supportsDeclarativePartitioning()); } /** @@ -431,8 +397,7 @@ private String getPartitionClause(AnalyticsTablePartition partition) { * @return a list of {@link AnalyticsTableColumn}. */ private List getColumns(Program program) { - List columns = new ArrayList<>(); - columns.addAll(FIXED_COLS); + List columns = new ArrayList<>(fixedColumns); if (program.hasNonDefaultCategoryCombo()) { List categories = program.getCategoryCombo().getCategories(); @@ -459,122 +424,75 @@ private List getColumns(Program program) { columns.addAll( program.getAnalyticsDataElements().stream() - .map(de -> getColumnFromDataElement(de, false)) + .map(de -> getColumnForDataElement(de, false)) .flatMap(Collection::stream) - .collect(Collectors.toList())); + .toList()); columns.addAll( program.getAnalyticsDataElementsWithLegendSet().stream() - .map(de -> getColumnFromDataElement(de, true)) + .map(de -> getColumnForDataElement(de, true)) .flatMap(Collection::stream) - .collect(Collectors.toList())); + .toList()); columns.addAll( program.getNonConfidentialTrackedEntityAttributes().stream() - .map( - tea -> - getColumnFromTrackedEntityAttribute( - tea, getNumericClause(), getDateClause(), false)) + .map(tea -> getColumnForTrackedEntityAttribute(tea, false)) .flatMap(Collection::stream) - .collect(Collectors.toList())); + .toList()); columns.addAll( program.getNonConfidentialTrackedEntityAttributesWithLegendSet().stream() - .map( - tea -> - getColumnFromTrackedEntityAttribute( - tea, getNumericClause(), getDateClause(), true)) + .map(tea -> getColumnForTrackedEntityAttribute(tea, true)) .flatMap(Collection::stream) - .collect(Collectors.toList())); + .toList()); if (program.isRegistration()) { columns.add(EventAnalyticsColumn.TRACKED_ENTITY); - columns.add(EventAnalyticsColumn.TRACKED_ENTITY_GEOMETRY); + if (sqlBuilder.supportsGeospatialData()) { + columns.add(EventAnalyticsColumn.TRACKED_ENTITY_GEOMETRY); + } } - - return filterDimensionColumns(columns); - } - - private List getColumnFromTrackedEntityAttribute( - TrackedEntityAttribute attribute, - String numericClause, - String dateClause, - boolean withLegendSet) { - List columns = new ArrayList<>(); - - DataType dataType = getColumnType(attribute.getValueType(), isSpatialSupport()); - String dataClause = - attribute.isNumericType() ? numericClause : attribute.isDateType() ? dateClause : ""; - String select = getSelectClause(attribute.getValueType(), "value"); - String sql = selectForInsert(attribute, select, dataClause); - Skip skipIndex = skipIndex(attribute.getValueType(), attribute.hasOptionSet()); - - if (attribute.getValueType().isOrganisationUnit()) { - columns.addAll(getColumnsFromOrgUnitTrackedEntityAttribute(attribute, dataClause)); + if (sqlBuilder.supportsDeclarativePartitioning()) { + // Add the year column required for declarative partitioning + columns.add(getPartitionColumn()); } - columns.add( - AnalyticsTableColumn.builder() - .name(attribute.getUid()) - .columnType(AnalyticsColumnType.DYNAMIC) - .dataType(dataType) - .selectExpression(sql) - .skipIndex(skipIndex) - .build()); - return withLegendSet - ? getColumnFromTrackedEntityAttributeWithLegendSet(attribute, numericClause) - : columns; + return filterDimensionColumns(columns); } - private List getColumnFromTrackedEntityAttributeWithLegendSet( - TrackedEntityAttribute attribute, String numericClause) { - String selectClause = getSelectClause(attribute.getValueType(), "value"); - String query = - """ - \s(select l.uid from maplegend l \ - inner join trackedentityattributevalue av on l.startvalue <= ${selectClause} \ - and l.endvalue > ${selectClause} \ - and l.maplegendsetid=${legendSetId} \ - and av.trackedentityid=en.trackedentityid \ - and av.trackedentityattributeid=${attributeId} ${numericClause}) as ${column}"""; - - return attribute.getLegendSets().stream() - .map( - ls -> { - String column = attribute.getUid() + PartitionUtils.SEP + ls.getUid(); - String sql = - replace( - query, - Map.of( - "selectClause", selectClause, - "legendSetId", String.valueOf(ls.getId()), - "column", column, - "attributeId", String.valueOf(attribute.getId()), - "numericClause", numericClause)); - - return AnalyticsTableColumn.builder() - .name(column) - .dataType(CHARACTER_11) - .selectExpression(sql) - .build(); - }) - .collect(toList()); + @Override + protected AnalyticsTableColumn getPartitionColumn() { + return AnalyticsTableColumn.builder() + .name("year") + .dataType(INTEGER) + .selectExpression("dps.year") + .build(); } - private List getColumnFromDataElement( + /** + * Returns a column for the given data element. If the value type of the data element is {@link + * ValueType#ORGANISATION_UNIT}, an extra column will be included. + * + * @param dataElement the {@link DataElement}. + * @param withLegendSet indicates + * @return + */ + private List getColumnForDataElement( DataElement dataElement, boolean withLegendSet) { List columns = new ArrayList<>(); DataType dataType = getColumnType(dataElement.getValueType(), isSpatialSupport()); - String dataClause = getDataClause(dataElement.getUid(), dataElement.getValueType()); - String columnName = "eventdatavalues #>> '{" + dataElement.getUid() + ", value}'"; - String select = getSelectClause(dataElement.getValueType(), columnName); - String sql = selectForInsert(dataElement, select, dataClause); + String columnExpression = + sqlBuilder.jsonExtractNested("eventdatavalues", dataElement.getUid(), "value"); + String selectExpression = getSelectExpression(dataElement.getValueType(), columnExpression); + String dataFilterClause = getDataFilterClause(dataElement); + String sql = getSelectForInsert(dataElement, selectExpression, dataFilterClause); Skip skipIndex = skipIndex(dataElement.getValueType(), dataElement.hasOptionSet()); if (dataElement.getValueType().isOrganisationUnit()) { - columns.addAll(getColumnFromOrgUnitDataElement(dataElement, dataClause)); + columns.addAll(getColumnForOrgUnitDataElement(dataElement, dataFilterClause)); } + columns.add( AnalyticsTableColumn.builder() .name(dataElement.getUid()) @@ -585,23 +503,26 @@ private List getColumnFromDataElement( .build()); return withLegendSet - ? getColumnFromDataElementWithLegendSet(dataElement, select, dataClause) + ? getColumnFromDataElementWithLegendSet(dataElement, selectExpression, dataFilterClause) : columns; } - private List getColumnsFromOrgUnitTrackedEntityAttribute( - TrackedEntityAttribute attribute, String dataClause) { + private List getColumnForOrgUnitDataElement( + DataElement dataElement, String dataFilterClause) { List columns = new ArrayList<>(); + String columnExpression = + sqlBuilder.jsonExtractNested("eventdatavalues", dataElement.getUid(), "value"); + String fromClause = + qualifyVariables("from ${organisationunit} ou where ou.uid = " + columnExpression); + if (isSpatialSupport()) { - String geoSql = - selectForInsert( - attribute, - "ou.geometry from organisationunit ou where ou.uid = (select value", - dataClause); + String fromType = "ou.geometry " + fromClause; + String geoSql = getSelectForInsert(dataElement, fromType, dataFilterClause); + columns.add( AnalyticsTableColumn.builder() - .name((attribute.getUid() + OU_GEOMETRY_COL_SUFFIX)) + .name((dataElement.getUid() + OU_GEOMETRY_COL_SUFFIX)) .columnType(AnalyticsColumnType.DYNAMIC) .dataType(GEOMETRY) .selectExpression(geoSql) @@ -609,12 +530,12 @@ private List getColumnsFromOrgUnitTrackedEntityAttribute( .build()); } - String fromTypeSql = "ou.name from organisationunit ou where ou.uid = (select value"; - String ouNameSql = selectForInsert(attribute, fromTypeSql, dataClause); + String fromTypeSql = "ou.name " + fromClause; + String ouNameSql = getSelectForInsert(dataElement, fromTypeSql, dataFilterClause); columns.add( AnalyticsTableColumn.builder() - .name((attribute.getUid() + OU_NAME_COL_SUFFIX)) + .name((dataElement.getUid() + OU_NAME_COL_SUFFIX)) .columnType(AnalyticsColumnType.DYNAMIC) .dataType(TEXT) .selectExpression(ouNameSql) @@ -624,22 +545,94 @@ private List getColumnsFromOrgUnitTrackedEntityAttribute( return columns; } - private List getColumnFromOrgUnitDataElement( - DataElement dataElement, String dataClause) { + private List getColumnForTrackedEntityAttribute( + TrackedEntityAttribute attribute, boolean withLegendSet) { List columns = new ArrayList<>(); - String columnName = "eventdatavalues #>> '{" + dataElement.getUid() + ", value}'"; + DataType dataType = getColumnType(attribute.getValueType(), isSpatialSupport()); + String selectExpression = getSelectExpressionForAttribute(attribute.getValueType(), "value"); + String dataExpression = getDataFilterClause(attribute); + String sql = selectForInsert(attribute, selectExpression, dataExpression); + Skip skipIndex = skipIndex(attribute.getValueType(), attribute.hasOptionSet()); - if (isSpatialSupport()) { - String geoSql = - selectForInsert( - dataElement, - "ou.geometry from organisationunit ou where ou.uid = (select " + columnName, - dataClause); + if (attribute.getValueType().isOrganisationUnit()) { + columns.addAll(getColumnsForOrgUnitTrackedEntityAttribute(attribute, dataExpression)); + } + columns.add( + AnalyticsTableColumn.builder() + .name(attribute.getUid()) + .columnType(AnalyticsColumnType.DYNAMIC) + .dataType(dataType) + .selectExpression(sql) + .skipIndex(skipIndex) + .build()); + + return withLegendSet ? getColumnForAttributeWithLegendSet(attribute) : columns; + } + + /** + * Returns a list of columns based on the given attribute with legend set. + * + * @param attribute the {@link TrackedEntityAttribute}. + * @return a list of {@link AnalyticsTableColumn}. + */ + private List getColumnForAttributeWithLegendSet( + TrackedEntityAttribute attribute) { + String selectClause = getSelectExpression(attribute.getValueType(), "value"); + String numericClause = getNumericClause(); + String query = + """ + \s(select l.uid from ${maplegend} l \ + inner join ${trackedentityattributevalue} av on l.startvalue <= ${selectClause} \ + and l.endvalue > ${selectClause} \ + and l.maplegendsetid=${legendSetId} \ + and av.trackedentityid=en.trackedentityid \ + and av.trackedentityattributeid=${attributeId} ${numericClause}) as ${column}"""; + + return attribute.getLegendSets().stream() + .map( + ls -> { + String column = attribute.getUid() + PartitionUtils.SEP + ls.getUid(); + String sql = + replaceQualify( + query, + Map.of( + "selectClause", selectClause, + "legendSetId", String.valueOf(ls.getId()), + "column", column, + "attributeId", String.valueOf(attribute.getId()), + "numericClause", numericClause)); + + return AnalyticsTableColumn.builder() + .name(column) + .dataType(CHARACTER_11) + .selectExpression(sql) + .build(); + }) + .collect(toList()); + } + + /** + * Returns a list of columns based on the given attribute. + * + * @param attribute the {@link TrackedEntityAttribute}. + * @param dataFilterClause the data filter clause. + * @return a list of {@link AnalyticsTableColumn}. + */ + private List getColumnsForOrgUnitTrackedEntityAttribute( + TrackedEntityAttribute attribute, String dataFilterClause) { + List columns = new ArrayList<>(); + + String fromClause = + qualifyVariables("from ${organisationunit} ou where ou.uid = (select value"); + + if (isSpatialSupport()) { + String fromType = "ou.geometry " + fromClause; + String geoSql = selectForInsert(attribute, fromType, dataFilterClause); columns.add( AnalyticsTableColumn.builder() - .name((dataElement.getUid() + OU_GEOMETRY_COL_SUFFIX)) + .name((attribute.getUid() + OU_GEOMETRY_COL_SUFFIX)) .columnType(AnalyticsColumnType.DYNAMIC) .dataType(GEOMETRY) .selectExpression(geoSql) @@ -647,12 +640,12 @@ private List getColumnFromOrgUnitDataElement( .build()); } - String fromTypeSql = "ou.name from organisationunit ou where ou.uid = (select " + columnName; - String ouNameSql = selectForInsert(dataElement, fromTypeSql, dataClause); + String fromTypeSql = "ou.name " + fromClause; + String ouNameSql = selectForInsert(attribute, fromTypeSql, dataFilterClause); columns.add( AnalyticsTableColumn.builder() - .name((dataElement.getUid() + OU_NAME_COL_SUFFIX)) + .name((attribute.getUid() + OU_NAME_COL_SUFFIX)) .columnType(AnalyticsColumnType.DYNAMIC) .dataType(TEXT) .selectExpression(ouNameSql) @@ -662,42 +655,63 @@ private List getColumnFromOrgUnitDataElement( return columns; } - private String selectForInsert(DataElement dataElement, String fromType, String dataClause) { - return replace( - """ - (select ${fromType} from event \ - where eventid=ev.eventid ${dataClause})${closingParentheses} as ${dataElementUid}""", + /** + * Creates a select statement for the given select expression. + * + * @param dataElement the data element to create the select statement for. + * @param selectExpression the select expression. + * @param dataFilterClause the data filter clause. + * @return A SQL select expression for the data element. + */ + private String getSelectForInsert( + DataElement dataElement, String selectExpression, String dataFilterClause) { + String sqlTemplate = + dataElement.getValueType().isOrganisationUnit() + ? "(select ${selectExpression} ${dataClause})${closingParentheses} as ${uid}" + : "${selectExpression}${closingParentheses} as ${uid}"; + + return replaceQualify( + sqlTemplate, Map.of( - "fromType", - fromType, + "selectExpression", + selectExpression, "dataClause", - dataClause, + dataFilterClause, "closingParentheses", - getClosingParentheses(fromType), - "dataElementUid", + getClosingParentheses(selectExpression), + "uid", quote(dataElement.getUid()))); } + /** + * Returns a list of columns. + * + * @param dataElement the {@link DataElement}. + * @param selectExpression the select expression. + * @param dataFilterClause the data filter clause. + * @return a list of {@link AnayticsTableColumn}. + */ private List getColumnFromDataElementWithLegendSet( - DataElement dataElement, String select, String dataClause) { + DataElement dataElement, String selectExpression, String dataFilterClause) { String query = """ - (select l.uid from maplegend l - inner join event on l.startvalue <= ${select} - and l.endvalue > ${select} - and l.maplegendsetid=${legendSetId} - and eventid=ev.eventid ${dataClause}) as ${column}"""; + (select l.uid from ${maplegend} l \ + inner join ${event} on l.startvalue <= ${select} \ + and l.endvalue > ${select} \ + and l.maplegendsetid=${legendSetId} \ + ${dataClause} where eventid = ev.eventid) as ${column}"""; + return dataElement.getLegendSets().stream() .map( ls -> { String column = dataElement.getUid() + PartitionUtils.SEP + ls.getUid(); String sql = - replace( + replaceQualify( query, Map.of( - "select", select, + "select", selectExpression, "legendSetId", String.valueOf(ls.getId()), - "dataClause", dataClause, + "dataClause", dataFilterClause, "column", column)); return AnalyticsTableColumn.builder() @@ -706,26 +720,58 @@ private List getColumnFromDataElementWithLegendSet( .selectExpression(sql) .build(); }) - .collect(toList()); + .toList(); } - private String getDataClause(String uid, ValueType valueType) { + /** + * For numeric and date value types, returns a data filter clause for checking whether the value + * is valid according to the value type. For other value types, returns the empty string. + * + * @param dataElement the {@link DataElement}. + * @return an filter expression. + */ + private String getDataFilterClause(DataElement dataElement) { + String uid = dataElement.getUid(); + ValueType valueType = dataElement.getValueType(); + if (valueType.isNumeric() || valueType.isDate()) { - String regex = valueType.isNumeric() ? NUMERIC_LENIENT_REGEXP : DATE_REGEXP; + String jsonExpression = sqlBuilder.jsonExtractNested("eventdatavalues", uid, "value"); + String regex = valueType.isNumeric() ? NUMERIC_REGEXP : DATE_REGEXP; - return replace( - " and eventdatavalues #>> '{${uid},value}' ~* '${regex}'", - Map.of("uid", uid, "regex", regex)); + return " and " + sqlBuilder.regexpMatch(jsonExpression, regex); } - return ""; + return EMPTY; } + /** + * For numeric and date value types, returns a data filter clause for checking whether the value + * is valid according to the value type. For other value types, returns the empty string. + * + * @param attribute the {@link TrackedEntityAttribute}. + * @return an filter expression. + */ + private String getDataFilterClause(TrackedEntityAttribute attribute) { + if (attribute.isNumericType()) { + return getNumericClause(); + } + return attribute.isDateType() ? getDateClause() : EMPTY; + } + + /** + * Returns a list of years for which data exist. + * + * @param params the {@link AnalyticsTableUpdateParams}. + * @param program the {@link Program}. + * @param firstDataYear the first year to include. + * @param lastDataYear the last data year to include. + * @return a list of years for which data exist. + */ private List getDataYears( AnalyticsTableUpdateParams params, Program program, Integer firstDataYear, - Integer latestDataYear) { + Integer lastDataYear) { String fromDateClause = params.getFromDate() != null ? replace( @@ -735,16 +781,15 @@ private List getDataYears( eventDateExpression, "fromDate", toMediumDate(params.getFromDate()))) - : ""; + : EMPTY; String sql = - replace( + replaceQualify( """ select temp.supportedyear from \ (select distinct extract(year from ${eventDateExpression}) as supportedyear \ - from event ev \ - inner join enrollment en on ev.enrollmentid = en.enrollmentid \ - where ev.lastupdated <= '${startTime}' \ - and en.programid = ${programId} \ + from ${event} ev \ + inner join ${enrollment} en on ev.enrollmentid = en.enrollmentid \ + where ev.lastupdated <= '${startTime}' and en.programid = ${programId} \ and (${eventDateExpression}) is not null \ and (${eventDateExpression}) > '1000-01-01' \ and ev.deleted = false \ @@ -757,7 +802,7 @@ private List getDataYears( "programId", String.valueOf(program.getId()), "fromDateClause", fromDateClause, "firstDataYear", String.valueOf(firstDataYear), - "latestDataYear", String.valueOf(latestDataYear))); + "latestDataYear", String.valueOf(lastDataYear))); return jdbcTemplate.queryForList(sql, Integer.class); } @@ -766,8 +811,8 @@ private List getDataYears( * Retrieve years for partition tables. Year will become a partition key. The default return value * is the list with the recent year. * - * @param dataYears list of years coming from inner join of event and enrollment tables - * @return list of partition key values + * @param dataYears the list of years coming from inner join of event and enrollment tables. + * @return list of partition key values. */ private List getYearsForPartitionTable(List dataYears) { return ListUtils.mutableCopy(!dataYears.isEmpty() ? dataYears : List.of(Year.now().getValue())); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcOrgUnitTargetTableManager.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcOrgUnitTargetTableManager.java index b1e58a0d3f97..d7ca3423ded1 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcOrgUnitTargetTableManager.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcOrgUnitTargetTableManager.java @@ -77,6 +77,8 @@ public class JdbcOrgUnitTargetTableManager extends AbstractJdbcTableManager { .selectExpression("oug.uid") .build()); + private static final List SORT_KEY = List.of("oug"); + public JdbcOrgUnitTargetTableManager( IdentifiableObjectManager idObjectManager, OrganisationUnitService organisationUnitService, @@ -118,7 +120,7 @@ public List getAnalyticsTables(AnalyticsTableUpdateParams params Logged logged = analyticsTableSettings.getTableLogged(); return params.isLatestUpdate() ? List.of() - : List.of(new AnalyticsTable(getAnalyticsTableType(), getColumns(), logged)); + : List.of(new AnalyticsTable(getAnalyticsTableType(), getColumns(), SORT_KEY, logged)); } @Override @@ -135,7 +137,7 @@ protected List getPartitionChecks(Integer year, Date endDate) { public void populateTable(AnalyticsTableUpdateParams params, AnalyticsTablePartition partition) { String tableName = partition.getName(); - String sql = replace("insert into ${tableName} (", Map.of("tableName", tableName)); + String sql = replace("insert into ${tableName} (", Map.of("tableName", quote(tableName))); List columns = partition.getMasterTable().getAnalyticsTableColumns(); @@ -152,11 +154,12 @@ public void populateTable(AnalyticsTableUpdateParams params, AnalyticsTableParti sql = TextUtils.removeLastComma(sql) + " "; sql += - """ - from orgunitgroupmembers ougm - inner join orgunitgroup oug on ougm.orgunitgroupid=oug.orgunitgroupid - left join analytics_rs_orgunitstructure ous on ougm.organisationunitid=ous.organisationunitid - left join analytics_rs_organisationunitgroupsetstructure ougs on ougm.organisationunitid=ougs.organisationunitid"""; + qualifyVariables( + """ + from ${orgunitgroupmembers} ougm \ + inner join ${orgunitgroup} oug on ougm.orgunitgroupid=oug.orgunitgroupid \ + left join analytics_rs_orgunitstructure ous on ougm.organisationunitid=ous.organisationunitid \ + left join analytics_rs_organisationunitgroupsetstructure ougs on ougm.organisationunitid=ougs.organisationunitid"""); invokeTimeAndLog(sql, "Populating table: '{}'", tableName); } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcOwnershipAnalyticsTableManager.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcOwnershipAnalyticsTableManager.java index 7a06b3a5c8ea..7b882a0e7c30 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcOwnershipAnalyticsTableManager.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcOwnershipAnalyticsTableManager.java @@ -28,7 +28,6 @@ package org.hisp.dhis.analytics.table; import static java.util.stream.Collectors.toList; -import static org.hisp.dhis.commons.util.TextUtils.replace; import static org.hisp.dhis.db.model.DataType.CHARACTER_11; import static org.hisp.dhis.db.model.DataType.DATE; import static org.hisp.dhis.db.model.constraint.Nullable.NOT_NULL; @@ -179,7 +178,7 @@ public void populateTable(AnalyticsTableUpdateParams params, AnalyticsTableParti Program program = partition.getMasterTable().getProgram(); if (program.getProgramType() == WITHOUT_REGISTRATION) { - return; // Builds an empty table, but it may be joined in queries. + return; // Builds an empty table which may be joined in queries } String sql = getInputSql(program); @@ -225,9 +224,24 @@ private void populateOwnershipTableInternal(AnalyticsTablePartition partition, S } } + /** + * Returns a SQL select query. For the from clause, for tracked entities in this program in + * programownershiphistory, get one row for each programownershiphistory row and then get a final + * row from the trackedentityprogramowner table to show the final owner. + * + *

The start date values are dummy so that all the history table rows will be ordered first and + * the tracked entity owner table row will come last. + * + *

The start date in the analytics table will be a far past date for the first row for each + * tracked entity, or the previous row's end date plus one day in subsequent rows for that tracked + * entity. + * + *

Rows in programownershiphistory that don't have organisationunitid will be filtered out. + * + * @param program the {@link Program}. + * @return a SQL select query. + */ private String getInputSql(Program program) { - // SELECT clause - StringBuilder sb = new StringBuilder("select "); for (AnalyticsTableColumn col : getColumns()) { @@ -236,40 +250,24 @@ private String getInputSql(Program program) { sb.deleteCharAt(sb.length() - 1); // Remove the final ','. - // FROM clause - - // For TRACKED ENTITIES in this program that are in programownershiphistory, get - // one row for each programownershiphistory row and then get a final - // row from the trackedentityprogramowner table to show the final owner. - // - // The start date values are dummy so that all the history table rows - // will be ordered first and the tracked entity owner table row will come last. - // - // (The start date in the analytics table will be a far past date for - // the first row for each TRACKED ENTITY, or the previous row's end date plus one - // day in subsequent rows for that TRACKED ENTITY.) - // - // Rows in programownershiphistory that don't have organisationunitid - // will be filtered out. sb.append( - replace( + replaceQualify( """ \sfrom (\ select h.trackedentityid, '${historyTableId}' as startdate, h.enddate as enddate, h.organisationunitid \ - from programownershiphistory h \ + from ${programownershiphistory} h \ where h.programid=${programId} \ and h.organisationunitid is not null \ union \ select o.trackedentityid, '${trackedEntityOwnTableId}' as startdate, null as enddate, o.organisationunitid \ - from trackedentityprogramowner o \ + from ${trackedentityprogramowner} o \ where o.programid=${programId} \ and exists (\ - select 1 from programownershiphistory p \ + select 1 from ${programownershiphistory} p \ where o.trackedentityid = p.trackedentityid \ - and p.programid=${programId} \ - and p.organisationunitid is not null)) a \ - inner join trackedentity te on a.trackedentityid = te.trackedentityid \ - inner join organisationunit ou on a.organisationunitid = ou.organisationunitid \ + and p.programid=${programId} and p.organisationunitid is not null)) a \ + inner join ${trackedentity} te on a.trackedentityid = te.trackedentityid \ + inner join ${organisationunit} ou on a.organisationunitid = ou.organisationunitid \ left join analytics_rs_orgunitstructure ous on a.organisationunitid = ous.organisationunitid \ left join analytics_rs_organisationunitgroupsetstructure ougs on a.organisationunitid = ougs.organisationunitid \ order by te.uid, a.startdate, a.enddate""", 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 c7028c605b01..a4e1fc4b8997 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 @@ -96,159 +96,6 @@ public class JdbcTrackedEntityAnalyticsTableManager extends AbstractJdbcTableMan private final TrackedEntityAttributeService trackedEntityAttributeService; - private static final List FIXED_GROUP_BY_COLS = - List.of( - AnalyticsTableColumn.builder() - .name("trackedentity") - .dataType(CHARACTER_11) - .nullable(NOT_NULL) - .selectExpression("te.uid") - .build(), - AnalyticsTableColumn.builder() - .name("trackedentityid") - .dataType(INTEGER) - .nullable(NOT_NULL) - .selectExpression("te.trackedentityid") - .build(), - AnalyticsTableColumn.builder() - .name("created") - .dataType(TIMESTAMP) - .selectExpression("te.created") - .build(), - AnalyticsTableColumn.builder() - .name("lastupdated") - .dataType(TIMESTAMP) - .selectExpression("te.lastupdated") - .build(), - AnalyticsTableColumn.builder() - .name("inactive") - .dataType(BOOLEAN) - .selectExpression("te.inactive") - .build(), - AnalyticsTableColumn.builder() - .name("createdatclient") - .dataType(TIMESTAMP) - .selectExpression("te.createdatclient") - .build(), - AnalyticsTableColumn.builder() - .name("lastupdatedatclient") - .dataType(TIMESTAMP) - .selectExpression("te.lastupdatedatclient") - .build(), - AnalyticsTableColumn.builder() - .name("lastsynchronized") - .dataType(TIMESTAMP) - .selectExpression("te.lastsynchronized") - .build(), - AnalyticsTableColumn.builder() - .name("geometry") - .dataType(GEOMETRY) - .selectExpression("te.geometry") - .indexType(IndexType.GIST) - .build(), - AnalyticsTableColumn.builder() - .name("longitude") - .dataType(DOUBLE) - .selectExpression( - "case when 'POINT' = GeometryType(te.geometry) then ST_X(te.geometry) else null end") - .build(), - AnalyticsTableColumn.builder() - .name("latitude") - .dataType(DOUBLE) - .selectExpression( - "case when 'POINT' = GeometryType(te.geometry) then ST_Y(te.geometry) else null end") - .build(), - AnalyticsTableColumn.builder() - .name("featuretype") - .dataType(VARCHAR_255) - .selectExpression("te.featuretype") - .build(), - AnalyticsTableColumn.builder() - .name("coordinates") - .dataType(TEXT) - .selectExpression("te.coordinates") - .build(), - AnalyticsTableColumn.builder() - .name("storedby") - .dataType(VARCHAR_255) - .selectExpression("te.storedby") - .build(), - AnalyticsTableColumn.builder() - .name("potentialduplicate") - .dataType(BOOLEAN) - .selectExpression("te.potentialduplicate") - .build(), - AnalyticsTableColumn.builder() - .name("ou") - .dataType(CHARACTER_11) - .selectExpression("ou.uid") - .build(), - AnalyticsTableColumn.builder() - .name("ouname") - .dataType(VARCHAR_255) - .selectExpression("ou.name") - .build(), - AnalyticsTableColumn.builder() - .name("oucode") - .dataType(VARCHAR_50) - .selectExpression("ou.code") - .build(), - AnalyticsTableColumn.builder() - .name("oulevel") - .dataType(INTEGER) - .selectExpression("ous.level") - .build()); - - private static final List FIXED_NON_GROUP_BY_COLS = - List.of( - AnalyticsTableColumn.builder() - .name("createdbyusername") - .dataType(VARCHAR_255) - .selectExpression("te.createdbyuserinfo ->> 'username' as createdbyusername") - .build(), - AnalyticsTableColumn.builder() - .name("createdbyname") - .dataType(VARCHAR_255) - .selectExpression("te.createdbyuserinfo ->> 'firstName' as createdbyname") - .skipIndex(Skip.SKIP) - .build(), - AnalyticsTableColumn.builder() - .name("createdbylastname") - .dataType(VARCHAR_255) - .selectExpression("te.createdbyuserinfo ->> 'surname' as createdbylastname") - .skipIndex(Skip.SKIP) - .build(), - AnalyticsTableColumn.builder() - .name("createdbydisplayname") - .dataType(VARCHAR_255) - .selectExpression(getDisplayName("createdbyuserinfo", "te", "createdbydisplayname")) - .skipIndex(Skip.SKIP) - .build(), - AnalyticsTableColumn.builder() - .name("lastupdatedbyusername") - .dataType(VARCHAR_255) - .selectExpression("te.lastupdatedbyuserinfo ->> 'username' as lastupdatedbyusername") - .build(), - AnalyticsTableColumn.builder() - .name("lastupdatedbyname") - .dataType(VARCHAR_255) - .selectExpression("te.lastupdatedbyuserinfo ->> 'firstName' as lastupdatedbyname") - .skipIndex(Skip.SKIP) - .build(), - AnalyticsTableColumn.builder() - .name("lastupdatedbylastname") - .dataType(VARCHAR_255) - .selectExpression("te.lastupdatedbyuserinfo ->> 'surname' as lastupdatedbylastname") - .skipIndex(Skip.SKIP) - .build(), - AnalyticsTableColumn.builder() - .name("lastupdatedbydisplayname") - .dataType(VARCHAR_255) - .selectExpression( - getDisplayName("lastupdatedbyuserinfo", "te", "lastupdatedbydisplayname")) - .skipIndex(Skip.SKIP) - .build()); - public JdbcTrackedEntityAnalyticsTableManager( IdentifiableObjectManager idObjectManager, OrganisationUnitService organisationUnitService, @@ -338,7 +185,7 @@ private List getColumns( String enrolledInProgramExpression = """ - \s exists(select 1 from enrollment en_0 \ + \s exists(select 1 from ${enrollment} en_0 \ where en_0.trackedentityid = te.trackedentityid \ and en_0.programid = ${programId})"""; @@ -350,7 +197,7 @@ private List getColumns( .name(program.getUid()) .dataType(BOOLEAN) .selectExpression( - replace( + replaceQualify( enrolledInProgramExpression, Map.of("programId", String.valueOf(program.getId())))) .build())); @@ -371,11 +218,14 @@ private List getColumns( .name(tea.getUid()) .dataType(getColumnType(tea.getValueType(), isSpatialSupport())) .selectExpression( - castBasedOnType(tea.getValueType(), "\"" + tea.getUid() + "\".value")) + castBasedOnType(tea.getValueType(), quote(tea.getUid()) + ".value")) .build()) .toList()); columns.addAll(getOrganisationUnitGroupSetColumns()); + if (sqlBuilder.supportsDeclarativePartitioning()) { + columns.add(getPartitionColumn()); + } return columns; } @@ -405,13 +255,16 @@ private Stream getAllTrackedEntityAttributes( /** * Returns the select clause, potentially with a cast statement, based on the given value type. * (this method is an adapted version of {@link - * JdbcEventAnalyticsTableManager#getSelectClause(ValueType, String)}) + * JdbcEventAnalyticsTableManager#getSelectExpression(ValueType, String)}) * * @param valueType the value type to represent as database column type. */ private String castBasedOnType(ValueType valueType, String columnName) { if (valueType.isDecimal()) { - return replace(" cast(${columnName} as double precision)", Map.of("columnName", columnName)); + + return replace( + " cast(${columnName} as ${type})", + Map.of("columnName", columnName, "type", sqlBuilder.dataTypeDouble())); } if (valueType.isInteger()) { return replace(" cast(${columnName} as bigint)", Map.of("columnName", columnName)); @@ -422,7 +275,9 @@ private String castBasedOnType(ValueType valueType, String columnName) { Map.of("columnName", columnName)); } if (valueType.isDate()) { - return replace(" cast(${columnName} as timestamp)", Map.of("columnName", columnName)); + return replace( + " cast(${columnName} as ${type})", + Map.of("columnName", columnName, "type", sqlBuilder.dataTypeTimestamp())); } if (valueType.isGeo() && isSpatialSupport()) { return replace( @@ -464,10 +319,10 @@ private Stream getAllTrackedEntityAttributesByEntityType */ private List getFixedColumns() { List columns = new ArrayList<>(); - columns.addAll(FIXED_GROUP_BY_COLS); + columns.addAll(getFixedGroupByColumns()); columns.addAll(getOrganisationUnitLevelColumns()); columns.add(getOrganisationUnitNameHierarchyColumn()); - columns.addAll(FIXED_NON_GROUP_BY_COLS); + columns.addAll(getFixedNonGroupByColumns()); return columns; } @@ -506,13 +361,12 @@ public void populateTable(AnalyticsTableUpdateParams params, AnalyticsTableParti removeLastComma(sql) .append( - replace( + replaceQualify( """ - \s from trackedentity te \ - left join organisationunit ou on te.organisationunitid = ou.organisationunitid \ - left join analytics_rs_orgunitstructure ous on ous.organisationunitid = ou.organisationunitid \ - left join analytics_rs_organisationunitgroupsetstructure ougs on te.organisationunitid = ougs.organisationunitid \ - and (cast(${trackedEntityCreatedMonth} as date) = ougs.startdate \ + \s from ${trackedentity} te \ + left join analytics_rs_orgunitstructure ous on te.organisationunitid=ous.organisationunitid \ + left join analytics_rs_organisationunitgroupsetstructure ougs on te.organisationunitid=ougs.organisationunitid \ + and (cast(${trackedEntityCreatedMonth} as date)=ougs.startdate \ or ougs.startdate is null)""", Map.of("trackedEntityCreatedMonth", sqlBuilder.dateTrunc("month", "te.created")))); @@ -521,21 +375,21 @@ public void populateTable(AnalyticsTableUpdateParams params, AnalyticsTableParti .forEach( tea -> sql.append( - replace( + replaceQualify( """ - \s left join trackedentityattributevalue "${teaUid}" on "${teaUid}".trackedentityid = te.trackedentityid \ - and "${teaUid}".trackedentityattributeid = ${teaId}""", + \s left join ${trackedentityattributevalue} ${teaUid} on ${teaUid}.trackedentityid=te.trackedentityid \ + and ${teaUid}.trackedentityattributeid = ${teaId}""", Map.of( - "teaUid", tea.getUid(), + "teaUid", quote(tea.getUid()), "teaId", String.valueOf(tea.getId()))))); sql.append( - replace( + replaceQualify( """ \s where te.trackedentitytypeid = ${tetId} \ and te.lastupdated < '${startTime}' \ - and exists (select 1 from enrollment en \ + and exists (select 1 from ${enrollment} en \ where en.trackedentityid = te.trackedentityid \ - and exists (select 1 from event ev \ + and exists (select 1 from ${event} ev \ where ev.enrollmentid = en.enrollmentid \ and ev.status in (${statuses}) \ and ev.deleted = false)) \ @@ -548,4 +402,184 @@ and ev.status in (${statuses}) \ invokeTimeAndLog(sql.toString(), "Populating table: '{}'", tableName); } + + private List getFixedGroupByColumns() { + List columns = + new ArrayList<>( + List.of( + AnalyticsTableColumn.builder() + .name("trackedentity") + .dataType(CHARACTER_11) + .nullable(NOT_NULL) + .selectExpression("te.uid") + .build(), + AnalyticsTableColumn.builder() + .name("trackedentityid") + .dataType(INTEGER) + .nullable(NOT_NULL) + .selectExpression("te.trackedentityid") + .build(), + AnalyticsTableColumn.builder() + .name("created") + .dataType(TIMESTAMP) + .selectExpression("te.created") + .build(), + AnalyticsTableColumn.builder() + .name("lastupdated") + .dataType(TIMESTAMP) + .selectExpression("te.lastupdated") + .build(), + AnalyticsTableColumn.builder() + .name("inactive") + .dataType(BOOLEAN) + .selectExpression("te.inactive") + .build(), + AnalyticsTableColumn.builder() + .name("createdatclient") + .dataType(TIMESTAMP) + .selectExpression("te.createdatclient") + .build(), + AnalyticsTableColumn.builder() + .name("lastupdatedatclient") + .dataType(TIMESTAMP) + .selectExpression("te.lastupdatedatclient") + .build(), + AnalyticsTableColumn.builder() + .name("lastsynchronized") + .dataType(TIMESTAMP) + .selectExpression("te.lastsynchronized") + .build(), + AnalyticsTableColumn.builder() + .name("featuretype") + .dataType(VARCHAR_255) + .selectExpression("te.featuretype") + .build(), + AnalyticsTableColumn.builder() + .name("coordinates") + .dataType(TEXT) + .selectExpression("te.coordinates") + .build(), + AnalyticsTableColumn.builder() + .name("storedby") + .dataType(VARCHAR_255) + .selectExpression("te.storedby") + .build(), + AnalyticsTableColumn.builder() + .name("potentialduplicate") + .dataType(BOOLEAN) + .selectExpression("te.potentialduplicate") + .build(), + AnalyticsTableColumn.builder() + .name("ou") + .dataType(CHARACTER_11) + .selectExpression("ous.organisationunituid") + .build(), + AnalyticsTableColumn.builder() + .name("ouname") + .dataType(VARCHAR_255) + .selectExpression("ous.name") + .build(), + AnalyticsTableColumn.builder() + .name("oucode") + .dataType(VARCHAR_50) + .selectExpression("ous.code") + .build(), + AnalyticsTableColumn.builder() + .name("oulevel") + .dataType(INTEGER) + .selectExpression("ous.level") + .build())); + + if (sqlBuilder.supportsGeospatialData()) { + columns.addAll( + List.of( + AnalyticsTableColumn.builder() + .name("geometry") + .dataType(GEOMETRY) + .selectExpression("te.geometry") + .indexType(IndexType.GIST) + .build(), + AnalyticsTableColumn.builder() + .name("longitude") + .dataType(DOUBLE) + .selectExpression( + "case when 'POINT' = GeometryType(te.geometry) then ST_X(te.geometry) else null end") + .build(), + AnalyticsTableColumn.builder() + .name("latitude") + .dataType(DOUBLE) + .selectExpression( + "case when 'POINT' = GeometryType(te.geometry) then ST_Y(te.geometry) else null end") + .build())); + } + + return columns; + } + + private List getFixedNonGroupByColumns() { + + return new ArrayList<>( + List.of( + AnalyticsTableColumn.builder() + .name("createdbyusername") + .dataType(VARCHAR_255) + .selectExpression( + sqlBuilder.jsonExtract("te.createdbyuserinfo", "username") + + " as createdbyusername") + .build(), + AnalyticsTableColumn.builder() + .name("createdbyname") + .dataType(VARCHAR_255) + .selectExpression( + sqlBuilder.jsonExtract("te.createdbyuserinfo", "firstName") + + " as createdbyname") + .skipIndex(Skip.SKIP) + .build(), + AnalyticsTableColumn.builder() + .name("createdbylastname") + .dataType(VARCHAR_255) + .selectExpression( + sqlBuilder.jsonExtract("te.createdbyuserinfo", "surname") + + " as createdbylastname") + .skipIndex(Skip.SKIP) + .build(), + AnalyticsTableColumn.builder() + .name("createdbydisplayname") + .dataType(VARCHAR_255) + .selectExpression( + getDisplayName("createdbyuserinfo", "te", "createdbydisplayname", sqlBuilder)) + .skipIndex(Skip.SKIP) + .build(), + AnalyticsTableColumn.builder() + .name("lastupdatedbyusername") + .dataType(VARCHAR_255) + .selectExpression( + sqlBuilder.jsonExtract("te.lastupdatedbyuserinfo", "username") + + " as lastupdatedbyusername") + .build(), + AnalyticsTableColumn.builder() + .name("lastupdatedbyname") + .dataType(VARCHAR_255) + .selectExpression( + sqlBuilder.jsonExtract("te.lastupdatedbyuserinfo", "firstName") + + " as lastupdatedbyname") + .skipIndex(Skip.SKIP) + .build(), + AnalyticsTableColumn.builder() + .name("lastupdatedbylastname") + .dataType(VARCHAR_255) + .selectExpression( + sqlBuilder.jsonExtract("te.lastupdatedbyuserinfo", "surname") + + " as lastupdatedbylastname") + .skipIndex(Skip.SKIP) + .build(), + AnalyticsTableColumn.builder() + .name("lastupdatedbydisplayname") + .dataType(VARCHAR_255) + .selectExpression( + getDisplayName( + "lastupdatedbyuserinfo", "te", "lastupdatedbydisplayname", sqlBuilder)) + .skipIndex(Skip.SKIP) + .build())); + } } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcTrackedEntityEnrollmentsAnalyticsTableManager.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcTrackedEntityEnrollmentsAnalyticsTableManager.java index e00bf720af5d..6c9309174ebe 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcTrackedEntityEnrollmentsAnalyticsTableManager.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcTrackedEntityEnrollmentsAnalyticsTableManager.java @@ -32,9 +32,7 @@ import static org.hisp.dhis.analytics.AnalyticsTableType.TRACKED_ENTITY_INSTANCE_ENROLLMENTS; import static org.hisp.dhis.analytics.table.JdbcEventAnalyticsTableManager.EXPORTABLE_EVENT_STATUSES; import static org.hisp.dhis.commons.util.TextUtils.removeLastComma; -import static org.hisp.dhis.commons.util.TextUtils.replace; import static org.hisp.dhis.db.model.DataType.CHARACTER_11; -import static org.hisp.dhis.db.model.DataType.CHARACTER_32; import static org.hisp.dhis.db.model.DataType.DOUBLE; import static org.hisp.dhis.db.model.DataType.GEOMETRY; import static org.hisp.dhis.db.model.DataType.INTEGER; @@ -163,19 +161,19 @@ public class JdbcTrackedEntityEnrollmentsAnalyticsTableManager extends AbstractJ .name("ou") .dataType(CHARACTER_11) .nullable(NULL) - .selectExpression("ou.uid") + .selectExpression("ous.organisationunituid") .build(), AnalyticsTableColumn.builder() .name("ouname") .dataType(VARCHAR_255) .nullable(NULL) - .selectExpression("ou.name") + .selectExpression("ous.name") .build(), AnalyticsTableColumn.builder() .name("oucode") - .dataType(CHARACTER_32) + .dataType(VARCHAR_50) .nullable(NULL) - .selectExpression("ou.code") + .selectExpression("ous.code") .build(), AnalyticsTableColumn.builder() .name("oulevel") @@ -247,7 +245,9 @@ private List getColumns() { List columns = new ArrayList<>(); columns.addAll(FIXED_COLS); columns.add(getOrganisationUnitNameHierarchyColumn()); - + if (sqlBuilder.supportsDeclarativePartitioning()) { + columns.add(getPartitionColumn()); + } return columns; } @@ -282,17 +282,15 @@ public void populateTable(AnalyticsTableUpdateParams params, AnalyticsTableParti removeLastComma(sql) .append( - replace( + replaceQualify( """ - \sfrom enrollment en \ - inner join trackedentity te on en.trackedentityid = te.trackedentityid \ - and te.deleted = false \ - and te.trackedentitytypeid =${trackedEntityTypeId} \ + \sfrom ${enrollment} en \ + inner join ${trackedentity} te on en.trackedentityid=te.trackedentityid \ + and te.deleted = false and te.trackedentitytypeid = ${trackedEntityTypeId} \ and te.lastupdated < '${startTime}' \ - left join program p on p.programid = en.programid \ - left join organisationunit ou on en.organisationunitid = ou.organisationunitid \ - left join analytics_rs_orgunitstructure ous on ous.organisationunitid = ou.organisationunitid \ - where exists ( select 1 from event ev where ev.deleted = false \ + left join ${program} p on en.programid=p.programid \ + left join analytics_rs_orgunitstructure ous on en.organisationunitid=ous.organisationunitid \ + where exists (select 1 from event ev where ev.deleted = false \ and ev.enrollmentid = en.enrollmentid \ and ev.status in (${statuses})) \ and en.occurreddate is not null \ diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcTrackedEntityEventsAnalyticsTableManager.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcTrackedEntityEventsAnalyticsTableManager.java index 9982cf762700..41a96d2c7342 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcTrackedEntityEventsAnalyticsTableManager.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcTrackedEntityEventsAnalyticsTableManager.java @@ -32,11 +32,11 @@ import static org.hisp.dhis.analytics.table.JdbcEventAnalyticsTableManager.EXPORTABLE_EVENT_STATUSES; import static org.hisp.dhis.analytics.table.util.PartitionUtils.getEndDate; import static org.hisp.dhis.analytics.table.util.PartitionUtils.getStartDate; +import static org.hisp.dhis.commons.util.TextUtils.emptyIfTrue; import static org.hisp.dhis.commons.util.TextUtils.format; import static org.hisp.dhis.commons.util.TextUtils.removeLastComma; import static org.hisp.dhis.commons.util.TextUtils.replace; import static org.hisp.dhis.db.model.DataType.CHARACTER_11; -import static org.hisp.dhis.db.model.DataType.CHARACTER_32; import static org.hisp.dhis.db.model.DataType.DOUBLE; import static org.hisp.dhis.db.model.DataType.GEOMETRY; import static org.hisp.dhis.db.model.DataType.INTEGER; @@ -46,8 +46,8 @@ import static org.hisp.dhis.db.model.DataType.VARCHAR_50; import static org.hisp.dhis.db.model.constraint.Nullable.NOT_NULL; import static org.hisp.dhis.db.model.constraint.Nullable.NULL; -import static org.hisp.dhis.period.PeriodDataProvider.DataSource.DATABASE; -import static org.hisp.dhis.period.PeriodDataProvider.DataSource.SYSTEM_DEFINED; +import static org.hisp.dhis.period.PeriodDataProvider.PeriodSource.DATABASE; +import static org.hisp.dhis.period.PeriodDataProvider.PeriodSource.SYSTEM_DEFINED; import static org.hisp.dhis.util.DateUtils.toLongDate; import static org.hisp.dhis.util.DateUtils.toMediumDate; @@ -90,25 +90,26 @@ public class JdbcTrackedEntityEventsAnalyticsTableManager extends AbstractJdbcTa private static final String EVENT_DATA_VALUE_REBUILDER = """ - (select json_object_agg(l2.keys, l2.datavalue) as value - from (select l1.uid, - l1.keys, - json_strip_nulls(json_build_object( - 'value', l1.eventdatavalues -> l1.keys ->> 'value', - 'created', l1.eventdatavalues -> l1.keys ->> 'created', - 'storedBy', l1.eventdatavalues -> l1.keys ->> 'storedBy', - 'lastUpdated', l1.eventdatavalues -> l1.keys ->> 'lastUpdated', - 'providedElsewhere', l1.eventdatavalues -> l1.keys -> 'providedElsewhere', - 'value_name', (select ou.name - from organisationunit ou - where ou.uid = l1.eventdatavalues -> l1.keys ->> 'value'), - 'value_code', (select ou.code - from organisationunit ou - where ou.uid = l1.eventdatavalues -> l1.keys ->> 'value'))) as datavalue - from (select inner_evt.*, jsonb_object_keys(inner_evt.eventdatavalues) keys - from event inner_evt) as l1) as l2 - where l2.uid = ev.uid - group by l2.uid)::jsonb + (select json_object_agg(l2.keys, l2.datavalue) as value + from ( + select l1.uid, + l1.keys, + json_strip_nulls(json_build_object( + 'value', l1.eventdatavalues -> l1.keys ->> 'value', + 'created', l1.eventdatavalues -> l1.keys ->> 'created', + 'storedBy', l1.eventdatavalues -> l1.keys ->> 'storedBy', + 'lastUpdated', l1.eventdatavalues -> l1.keys ->> 'lastUpdated', + 'providedElsewhere', l1.eventdatavalues -> l1.keys -> 'providedElsewhere', + 'value_name', (select ou.name + from organisationunit ou + where ou.uid = l1.eventdatavalues -> l1.keys ->> 'value'), + 'value_code', (select ou.code + from organisationunit ou + where ou.uid = l1.eventdatavalues -> l1.keys ->> 'value'))) as datavalue + from (select inner_evt.*, jsonb_object_keys(inner_evt.eventdatavalues) keys + from event inner_evt) as l1) as l2 + where l2.uid = ev.uid + group by l2.uid)::jsonb """; private static final List FIXED_COLS = @@ -214,19 +215,19 @@ public class JdbcTrackedEntityEventsAnalyticsTableManager extends AbstractJdbcTa .name("ou") .dataType(CHARACTER_11) .nullable(NULL) - .selectExpression("ou.uid") + .selectExpression("ous.organisationunituid") .build(), AnalyticsTableColumn.builder() .name("ouname") .dataType(VARCHAR_255) .nullable(NULL) - .selectExpression("ou.name") + .selectExpression("ous.name") .build(), AnalyticsTableColumn.builder() .name("oucode") - .dataType(CHARACTER_32) + .dataType(VARCHAR_50) .nullable(NULL) - .selectExpression("ou.code") + .selectExpression("ous.code") .build(), AnalyticsTableColumn.builder() .name("oulevel") @@ -324,16 +325,15 @@ public List getAnalyticsTables(AnalyticsTableUpdateParams params private List getDataYears(AnalyticsTableUpdateParams params, TrackedEntityType tet) { StringBuilder sql = new StringBuilder(); sql.append( - replace( + replaceQualify( """ select temp.supportedyear from \ (select distinct extract(year from ${eventDateExpression}) as supportedyear \ - from trackedentity te \ - inner join trackedentitytype tet on tet.trackedentitytypeid = te.trackedentitytypeid \ - inner join enrollment en on en.trackedentityid = te.trackedentityid \ - inner join event ev on ev.enrollmentid = en.enrollmentid \ + from ${trackedentity} te \ + inner join ${enrollment} en on te.trackedentityid=en.trackedentityid \ + inner join ${event} ev on en.enrollmentid=ev.enrollmentid \ where ev.lastupdated <= '${startTime}' \ - and tet.trackedentitytypeid = ${tetId} \ + and te.trackedentitytypeid = ${tetId} \ and (${eventDateExpression}) is not null \ and (${eventDateExpression}) > '1000-01-01' \ and ev.deleted = false \ @@ -368,6 +368,11 @@ private List getDataYears(AnalyticsTableUpdateParams params, TrackedEnt private List getColumns() { List columns = new ArrayList<>(); columns.addAll(FIXED_COLS); + + if (sqlBuilder.supportsDeclarativePartitioning()) { + columns.add(getPartitionColumn()); + } + columns.add(getOrganisationUnitNameHierarchyColumn()); return columns; @@ -386,6 +391,7 @@ protected List getPartitionChecks(Integer year, Date endDate) { */ @Override public void populateTable(AnalyticsTableUpdateParams params, AnalyticsTablePartition partition) { + AnalyticsTable masterTable = partition.getMasterTable(); String tableName = partition.getName(); List columns = partition.getMasterTable().getAnalyticsTableColumns(); String partitionClause = getPartitionClause(partition); @@ -404,25 +410,20 @@ public void populateTable(AnalyticsTableUpdateParams params, AnalyticsTableParti removeLastComma(sql) .append( - replace( + replaceQualify( """ - \s from event ev \ - inner join enrollment en on en.enrollmentid = ev.enrollmentid \ - and en.deleted = false \ - inner join trackedentity te on te.trackedentityid = en.trackedentityid \ - and te.deleted = false \ - and te.trackedentitytypeid = ${tetId} \ - and te.lastupdated < '${startTime}' \ - left join programstage ps on ps.programstageid = ev.programstageid \ - left join program p on p.programid = ps.programid \ - left join organisationunit ou on ev.organisationunitid = ou.organisationunitid \ - left join analytics_rs_orgunitstructure ous on ous.organisationunitid = ou.organisationunitid \ + \s from ${event} ev \ + inner join ${enrollment} en on en.enrollmentid=ev.enrollmentid and en.deleted = false \ + inner join ${trackedentity} te on te.trackedentityid=en.trackedentityid \ + and te.deleted = false and te.trackedentitytypeid = ${tetId} and te.lastupdated < '${startTime}' \ + left join ${programstage} ps on ev.programstageid=ps.programstageid \ + left join ${program} p on ps.programid=p.programid \ + left join analytics_rs_orgunitstructure ous on ev.organisationunitid=ous.organisationunitid \ where ev.status in (${statuses}) \ ${partitionClause} \ and ev.deleted = false\s""", Map.of( - "tetId", - String.valueOf(partition.getMasterTable().getTrackedEntityType().getId()), + "tetId", String.valueOf(masterTable.getTrackedEntityType().getId()), "startTime", toLongDate(params.getStartTime()), "statuses", join(",", EXPORTABLE_EVENT_STATUSES), "partitionClause", partitionClause))); @@ -448,6 +449,8 @@ private String getPartitionClause(AnalyticsTablePartition partition) { eventDateExpression, end); - return partition.isLatestPartition() ? latestFilter : partitionFilter; + return partition.isLatestPartition() + ? latestFilter + : emptyIfTrue(partitionFilter, sqlBuilder.supportsDeclarativePartitioning()); } } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcValidationResultTableManager.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcValidationResultTableManager.java index 83d4c99fb8ab..e25bd5756486 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcValidationResultTableManager.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcValidationResultTableManager.java @@ -28,9 +28,9 @@ package org.hisp.dhis.analytics.table; import static org.hisp.dhis.analytics.table.model.AnalyticsValueType.FACT; +import static org.hisp.dhis.commons.util.TextUtils.emptyIfTrue; import static org.hisp.dhis.commons.util.TextUtils.format; import static org.hisp.dhis.commons.util.TextUtils.removeLastComma; -import static org.hisp.dhis.commons.util.TextUtils.replace; import static org.hisp.dhis.db.model.DataType.CHARACTER_11; import static org.hisp.dhis.db.model.DataType.DATE; import static org.hisp.dhis.db.model.DataType.INTEGER; @@ -84,12 +84,12 @@ public class JdbcValidationResultTableManager extends AbstractJdbcTableManager { AnalyticsTableColumn.builder() .name("pestartdate") .dataType(TIMESTAMP) - .selectExpression("pe.startdate") + .selectExpression("ps.startdate") .build(), AnalyticsTableColumn.builder() .name("peenddate") .dataType(TIMESTAMP) - .selectExpression("pe.enddate") + .selectExpression("ps.enddate") .build(), AnalyticsTableColumn.builder() .name("year") @@ -98,6 +98,8 @@ public class JdbcValidationResultTableManager extends AbstractJdbcTableManager { .selectExpression("ps.year") .build()); + private static final List SORT_KEY = List.of("dx"); + public JdbcValidationResultTableManager( IdentifiableObjectManager idObjectManager, OrganisationUnitService organisationUnitService, @@ -137,8 +139,9 @@ public AnalyticsTableType getAnalyticsTableType() { public List getAnalyticsTables(AnalyticsTableUpdateParams params) { AnalyticsTable table = params.isLatestUpdate() - ? new AnalyticsTable(AnalyticsTableType.VALIDATION_RESULT, List.of(), Logged.LOGGED) - : getRegularAnalyticsTable(params, getDataYears(params), getColumns()); + ? new AnalyticsTable( + AnalyticsTableType.VALIDATION_RESULT, List.of(), List.of(), Logged.LOGGED) + : getRegularAnalyticsTable(params, getDataYears(params), getColumns(), SORT_KEY); return table.hasTablePartitions() ? List.of(table) : List.of(); } @@ -185,21 +188,18 @@ public void populateTable(AnalyticsTableUpdateParams params, AnalyticsTableParti sql = sql.replace("organisationunitid", "sourceid"); sql += - replace( + replaceQualify( """ - from validationresult vrs - inner join period pe on vrs.periodid=pe.periodid - inner join analytics_rs_periodstructure ps on vrs.periodid=ps.periodid - inner join validationrule vr on vr.validationruleid=vrs.validationruleid - inner join analytics_rs_organisationunitgroupsetstructure ougs on vrs.organisationunitid=ougs.organisationunitid - and (cast(${peStartDateMonth} as date)=ougs.startdate or ougs.startdate is null) - left join analytics_rs_orgunitstructure ous on vrs.organisationunitid=ous.organisationunitid - inner join analytics_rs_categorystructure acs on vrs.attributeoptioncomboid=acs.categoryoptioncomboid - where vrs.created < '${startTime}' - and vrs.created is not null ${partitionClause}""", + from ${validationresult} vrs \ + inner join analytics_rs_periodstructure ps on vrs.periodid=ps.periodid \ + inner join ${validationrule} vr on vr.validationruleid=vrs.validationruleid \ + inner join analytics_rs_organisationunitgroupsetstructure ougs on vrs.organisationunitid=ougs.organisationunitid \ + left join analytics_rs_orgunitstructure ous on vrs.organisationunitid=ous.organisationunitid \ + inner join analytics_rs_categorystructure acs on vrs.attributeoptioncomboid=acs.categoryoptioncomboid \ + where vrs.created < '${startTime}' \ + and vrs.created is not null ${partitionClause} \ + and (ougs.startdate is null or ps.monthstartdate=ougs.startdate)""", Map.of( - "peStartDateMonth", - sqlBuilder.dateTrunc("month", "ps.startdate"), "startTime", toLongDate(params.getStartTime()), "partitionClause", @@ -212,18 +212,17 @@ private List getDataYears(AnalyticsTableUpdateParams params) { String fromDateClause = params.getFromDate() == null ? "" - : replace( - "and pe.startdate >= '${fromDate}'", - Map.of("fromDate", DateUtils.toMediumDate(params.getFromDate()))); + : String.format( + " and ps.startdate >= '%s'", DateUtils.toMediumDate(params.getFromDate())); + String sql = - replace( + replaceQualify( """ - select distinct(extract(year from pe.startdate)) - from validationresult vrs - inner join period pe on vrs.periodid=pe.periodid - where pe.startdate is not null - and vrs.created < '${startTime}' - ${fromDateClause}""", + select distinct(extract(year from ps.startdate)) \ + from ${validationresult} vrs \ + inner join analytics_rs_periodstructure ps on vrs.periodid=ps.periodid \ + where ps.startdate is not null \ + and vrs.created < '${startTime}'${fromDateClause}""", Map.of( "startTime", toLongDate(params.getStartTime()), "fromDateClause", fromDateClause)); @@ -237,7 +236,8 @@ select distinct(extract(year from pe.startdate)) * @return a partition SQL clause. */ private String getPartitionClause(AnalyticsTablePartition partition) { - return format("and ps.year = {} ", partition.getYear()); + String partitionFilter = format("and ps.year = {} ", partition.getYear()); + return emptyIfTrue(partitionFilter, sqlBuilder.supportsDeclarativePartitioning()); } private List getColumns() { diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/init/AnalyticsDatabaseInit.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/init/AnalyticsDatabaseInit.java new file mode 100644 index 000000000000..8f07af3b5d81 --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/init/AnalyticsDatabaseInit.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2004-2024, 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.table.init; + +import static org.hisp.dhis.db.sql.ClickHouseSqlBuilder.NAMED_COLLECTION; + +import java.util.Map; +import javax.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.hisp.dhis.analytics.table.setting.AnalyticsTableSettings; +import org.hisp.dhis.db.model.Database; +import org.hisp.dhis.db.sql.ClickHouseSqlBuilder; +import org.hisp.dhis.db.sql.SqlBuilder; +import org.hisp.dhis.db.sql.SqlBuilderProvider; +import org.hisp.dhis.external.conf.ConfigurationKey; +import org.hisp.dhis.external.conf.DhisConfigurationProvider; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +/** + * Class responsible for performing work for initialization of an analytics database. + * + *

The following steps are required to introduce a new analytics database platform. + * + *

    + *
  • Add value to enum {@link Database} + *
  • Add implementation of interface {@link SqlBuilder} + *
  • Add entry to switch statement in {@link SqlBuilderProvider} + *
  • Add method to {@link AnalyticsDatabaseInit} if necessary + *
+ * + * @author Lars Helge Overland + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class AnalyticsDatabaseInit { + private final DhisConfigurationProvider config; + + private final AnalyticsTableSettings settings; + + @Qualifier("analyticsJdbcTemplate") + private final JdbcTemplate jdbcTemplate; + + private final SqlBuilder sqlBuilder; + + @PostConstruct + public void init() { + if (!config.isAnalyticsDatabaseConfigured()) { + return; + } + + Database database = settings.getAnalyticsDatabase(); + + switch (database) { + case POSTGRESQL -> initPostgreSql(); + case DORIS -> initDoris(); + case CLICKHOUSE -> initClickHouse(); + } + + log.info("Initialized analytics database: '{}'", database); + } + + /** Work for initializing a PostgreSQL analytics database. */ + private void initPostgreSql() { + // No work yet + } + + /** Work for initializing a Doris analytics database. */ + private void initDoris() { + createDorisJdbcCatalog(); + } + + /** Work for initializing a ClickHouse analytics database. */ + private void initClickHouse() { + createClickHouseNamedCollection(); + } + + /** + * Creates a Doris JDBC catalog which is used to connect to and read from the PostgreSQL + * transaction database as an external data source. + */ + private void createDorisJdbcCatalog() { + String connectionUrl = config.getProperty(ConfigurationKey.CONNECTION_URL); + String username = config.getProperty(ConfigurationKey.CONNECTION_USERNAME); + String password = config.getProperty(ConfigurationKey.CONNECTION_PASSWORD); + + jdbcTemplate.execute(sqlBuilder.dropCatalogIfExists()); + jdbcTemplate.execute(sqlBuilder.createCatalog(connectionUrl, username, password)); + } + + /** + * Creates a ClickHouse named collection with connection information for the DHIS 2 PostgreSQL + * database. + */ + private void createClickHouseNamedCollection() { + Map keyValues = + Map.of( + "host", config.getProperty(ConfigurationKey.CONNECTION_HOST), + "port", config.getIntProperty(ConfigurationKey.CONNECTION_PORT), + "database", config.getProperty(ConfigurationKey.CONNECTION_DATABASE), + "username", config.getProperty(ConfigurationKey.CONNECTION_USERNAME), + "password", config.getProperty(ConfigurationKey.CONNECTION_PASSWORD)); + + ClickHouseSqlBuilder clickHouseSqlBuilder = new ClickHouseSqlBuilder(); + + jdbcTemplate.execute(clickHouseSqlBuilder.dropNamedCollectionIfExists(NAMED_COLLECTION)); + jdbcTemplate.execute(clickHouseSqlBuilder.createNamedCollection(NAMED_COLLECTION, keyValues)); + } +} diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/model/AnalyticsColumnType.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/model/AnalyticsColumnType.java index 7a334d3323e2..e00267ed3717 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/model/AnalyticsColumnType.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/model/AnalyticsColumnType.java @@ -27,9 +27,11 @@ */ package org.hisp.dhis.analytics.table.model; +/** + * Represents a type of dimension, either static, meaning fixed, or dynamic, meaning based on a + * dimensional configuration entity. + */ public enum AnalyticsColumnType { - // Column with static name STATIC, - // Column with calculated name (uid) - DYNAMIC + DYNAMIC; } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/model/AnalyticsTable.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/model/AnalyticsTable.java index 793e55d8fd90..5029d3632a73 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/model/AnalyticsTable.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/model/AnalyticsTable.java @@ -74,11 +74,21 @@ public class AnalyticsTable extends Table { * * @param tableType the {@link AnalyticsTableType}. * @param columns the list of {@link Column}. + * @param sortKey the sort key. * @param logged the {@link Logged} property. */ public AnalyticsTable( - AnalyticsTableType tableType, List columns, Logged logged) { - super(toStaging(tableType.getTableName()), toColumns(columns), List.of(), logged); + AnalyticsTableType tableType, + List columns, + List sortKey, + Logged logged) { + super( + toStaging(tableType.getTableName()), + toColumns(columns), + List.of(), + sortKey, + List.of(), + logged); this.tableType = tableType; this.analyticsTableColumns = columns; } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/model/AnalyticsTableColumn.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/model/AnalyticsTableColumn.java index 74fcc49016d8..2f04beb6f675 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/model/AnalyticsTableColumn.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/model/AnalyticsTableColumn.java @@ -70,7 +70,7 @@ public class AnalyticsTableColumn { /** Index type, defaults to database default type {@link IndexType#BTREE}. */ @Builder.Default private final IndexType indexType = IndexType.BTREE; - /** Index column names, defaults to column name. */ + /** Index column names. */ @Builder.Default private final List indexColumns = List.of(); /** The column type indicates the column origin. */ diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/model/Skip.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/model/Skip.java index 3a33991b7e9e..d4d1c4b702ba 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/model/Skip.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/model/Skip.java @@ -27,6 +27,11 @@ */ package org.hisp.dhis.analytics.table.model; +/** + * Represents whether an object should be skipped or included as part of a process. + * + * @author Lars Helge Overland + */ public enum Skip { SKIP, INCLUDE; diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/scheduling/AnalyticsTableJob.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/scheduling/AnalyticsTableJob.java index 820466d00333..7cfcf16db0d7 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/scheduling/AnalyticsTableJob.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/scheduling/AnalyticsTableJob.java @@ -39,6 +39,8 @@ import org.springframework.stereotype.Component; /** + * Job for full analytics table update. + * * @author Lars Helge Overland */ @Component diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/scheduling/ResourceTableJob.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/scheduling/ResourceTableJob.java index c5a3e8a2beff..3ce8be126ec9 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/scheduling/ResourceTableJob.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/scheduling/ResourceTableJob.java @@ -37,6 +37,8 @@ import org.springframework.stereotype.Component; /** + * Job for resource table update. + * * @author Lars Helge Overland */ @Component("resourceTableJob") diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/setting/AnalyticsTableSettings.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/setting/AnalyticsTableSettings.java index 8fdedbcd8937..18ea01e64857 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/setting/AnalyticsTableSettings.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/setting/AnalyticsTableSettings.java @@ -37,6 +37,8 @@ import static org.hisp.dhis.external.conf.ConfigurationKey.ANALYTICS_TABLE_SKIP_COLUMN; import static org.hisp.dhis.external.conf.ConfigurationKey.ANALYTICS_TABLE_SKIP_INDEX; import static org.hisp.dhis.external.conf.ConfigurationKey.ANALYTICS_TABLE_UNLOGGED; +import static org.hisp.dhis.period.PeriodDataProvider.PeriodSource.DATABASE; +import static org.hisp.dhis.period.PeriodDataProvider.PeriodSource.SYSTEM_DEFINED; import static org.hisp.dhis.util.ObjectUtils.isNull; import com.google.common.collect.Lists; @@ -50,6 +52,7 @@ import org.hisp.dhis.db.model.Database; import org.hisp.dhis.db.model.Logged; import org.hisp.dhis.external.conf.DhisConfigurationProvider; +import org.hisp.dhis.period.PeriodDataProvider.PeriodSource; import org.hisp.dhis.setting.SystemSettings; import org.hisp.dhis.setting.SystemSettingsProvider; import org.springframework.stereotype.Component; @@ -92,6 +95,15 @@ public Integer getMaxPeriodYearsOffset() { return yearsOffset < 0 ? null : yearsOffset; } + /** + * Returns the {@link PeriodSource} based on the max years offset. + * + * @return the {@link PeriodSource}. + */ + public PeriodSource getPeriodSource() { + return getMaxPeriodYearsOffset() == null ? SYSTEM_DEFINED : DATABASE; + } + /** * Indicates whether an analytics database instance is configured. * @@ -102,7 +114,7 @@ public boolean isAnalyticsDatabaseConfigured() { } /** - * Returns the configured analytics {@link Database}. Default is {@link Database#POSTGRESQL}. + * Returns the configured analytics {@link Database}. The default is {@link Database#POSTGRESQL}. * * @return the analytics {@link Database}. */ diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/util/PartitionUtils.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/util/PartitionUtils.java index 639c53713d49..ae13b7d2a67c 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/util/PartitionUtils.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/util/PartitionUtils.java @@ -110,11 +110,7 @@ public static Date getEndDate(Integer year) { public static Partitions getPartitions(List periods) { Set years = new HashSet<>(); - periods.forEach( - p -> { - Period period = (Period) p; - years.addAll(getYears(period)); - }); + periods.forEach(p -> years.addAll(getYears((Period) p))); return new Partitions(years); } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/trackedentity/CommonParamsSecurityManager.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/trackedentity/CommonParamsSecurityManager.java index f98950d92592..e017bf5a3a2d 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/trackedentity/CommonParamsSecurityManager.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/trackedentity/CommonParamsSecurityManager.java @@ -123,12 +123,12 @@ void decideAccess( Set objects = new HashSet<>(); objects.addAll(extraObjects); - // DimensionalObjects from TrackedEntityQueryParams. + // DimensionalObjects from TrackedEntityQueryParams objects.addAll( commonParams.getDimensionIdentifiers().stream() .filter(not(OrgUnitQueryBuilder::isOu)) .map(DimensionIdentifier::getDimension) - // TEAs/Program Attribute are not data shareable, so access depends on the program/TET. + // TEAs/Program Attribute are not data shareable, so access depends on the program/TET .filter(not(CommonParamsSecurityManager::shouldSkipCheck)) .map(DimensionParam::getDimensionalObject) .filter(Objects::nonNull) @@ -140,10 +140,10 @@ void decideAccess( objects.addAll( commonParams.getDimensionIdentifiers().stream() // We don't want to add the org units to the objects since they are - // already checked in the queryOrgUnits list. + // already checked in the queryOrgUnits list .filter(not(OrgUnitQueryBuilder::isOu)) .map(DimensionIdentifier::getDimension) - // TEAs/Program Attribute are not data shareable, so access depends on the program/TET. + // TEAs/Program Attribute are not data shareable, so access depends on the program/TET .filter(not(CommonParamsSecurityManager::shouldSkipCheck)) .map(DimensionParam::getQueryItem) .filter(Objects::nonNull) @@ -241,7 +241,7 @@ void applyDimensionConstraints(@Nonnull CommonParsedParams commonParams) { .filter(Objects::nonNull) .collect(toList()); - // Categories the user is constrained to. + // Categories the user is constrained to List categories = currentUser.isSuper() ? List.of() @@ -253,8 +253,7 @@ void applyDimensionConstraints(@Nonnull CommonParsedParams commonParams) { Stream.concat(currentUser.getDimensionConstraints().stream(), categories.stream()) .collect(toSet()); - if (dimensionConstraints.isEmpty()) // if no constraints - { + if (dimensionConstraints.isEmpty()) { return; // Nothing to do - no filters added to the query. } @@ -294,7 +293,7 @@ void applyDimensionConstraints(@Nonnull CommonParsedParams commonParams) { * with items. False otherwise. * * @param commonParams the {@link CommonParams}. - * @param dimensionUid the dimension uid. + * @param dimensionUid the dimension identifier. * @return true if the given dimensionUid in the {@link CommonParams} has a dimension or filter. */ private boolean hasDimensionOrFilterWithItems( diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/trackedentity/DefaultTrackedEntityAnalyticsDimensionsService.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/trackedentity/DefaultTrackedEntityAnalyticsDimensionsService.java index ebfb28265da7..c71c5441c46e 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/trackedentity/DefaultTrackedEntityAnalyticsDimensionsService.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/trackedentity/DefaultTrackedEntityAnalyticsDimensionsService.java @@ -35,7 +35,6 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Stream; -import javax.annotation.Nonnull; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.hisp.dhis.analytics.event.EnrollmentAnalyticsDimensionsService; @@ -51,11 +50,11 @@ @RequiredArgsConstructor class DefaultTrackedEntityAnalyticsDimensionsService implements TrackedEntityAnalyticsDimensionsService { - @Nonnull private final TrackedEntityTypeService trackedEntityTypeService; + private final TrackedEntityTypeService trackedEntityTypeService; - @Nonnull private final EnrollmentAnalyticsDimensionsService enrollmentAnalyticsDimensionsService; + private final ProgramService programService; - @Nonnull private final ProgramService programService; + private final EnrollmentAnalyticsDimensionsService enrollmentAnalyticsDimensionsService; @Override public List getQueryDimensionsByTrackedEntityTypeId( @@ -72,7 +71,7 @@ public List getQueryDimensionsByTrackedEntityTypeId( programs = programService.getPrograms(programUids).stream(); } - // Dimensions by programs defined on the given tracked entity type. + // Dimensions by programs defined on the given tracked entity type return programs .filter(program -> isDefinedOnTrackedEntityType(program, trackedEntityTypeId)) .map( diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/trackedentity/TrackedEntityQueryRequestValidator.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/trackedentity/TrackedEntityQueryRequestValidator.java index 7ad797ae6f2b..b56f661e771e 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/trackedentity/TrackedEntityQueryRequestValidator.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/trackedentity/TrackedEntityQueryRequestValidator.java @@ -41,7 +41,6 @@ */ @Component public class TrackedEntityQueryRequestValidator implements Validator { - /** * Runs a validation on the given query request object {@link TrackedEntityRequestParams}, * preventing basic syntax errors and inconsistencies. diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/util/AnalyticsPeriodCriteriaUtils.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/util/AnalyticsPeriodCriteriaUtils.java index c45ca97052ea..5fdd51b098fb 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/util/AnalyticsPeriodCriteriaUtils.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/util/AnalyticsPeriodCriteriaUtils.java @@ -50,12 +50,12 @@ public final class AnalyticsPeriodCriteriaUtils { * * @param criteria {@link EventsAnalyticsQueryCriteria} query criteria. * @param periodDataProvider {@link EventsAnalyticsQueryCriteria} period data provider. - * @param dataSource {@link PeriodDataProvider.DataSource} source of data. + * @param dataSource {@link PeriodDataProvider.PeriodSource} source of data. */ public static void defineDefaultPeriodForCriteria( EnrollmentAnalyticsQueryCriteria criteria, PeriodDataProvider periodDataProvider, - PeriodDataProvider.DataSource dataSource) { + PeriodDataProvider.PeriodSource dataSource) { List availableYears = periodDataProvider.getAvailableYears(dataSource); if (PeriodCriteriaUtils.hasPeriod(criteria)) { diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/util/DisplayNameUtils.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/util/DisplayNameUtils.java index f54c402b5cd5..5377f0807253 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/util/DisplayNameUtils.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/util/DisplayNameUtils.java @@ -27,8 +27,10 @@ */ package org.hisp.dhis.analytics.util; +import java.util.function.UnaryOperator; import lombok.AccessLevel; import lombok.NoArgsConstructor; +import org.hisp.dhis.db.sql.SqlBuilder; @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class DisplayNameUtils { @@ -57,56 +59,98 @@ public final class DisplayNameUtils { * @param columnAlias the alias of this column in the analytics database * @return the trimmed display name */ - public static String getDisplayName(String originColumn, String tablePrefix, String columnAlias) { - return ("case" - // If all are empty, return null - + " when coalesce(trim({prefix}.{column} ->> 'surname'), '') = ''" - + " and coalesce(trim({prefix}.{column} ->> 'firstName'), '') = ''" - + " and coalesce(trim({prefix}.{column} ->> 'username'), '') = ''" - + " then null" + public static String getDisplayName( + String originColumn, String tablePrefix, String columnAlias, SqlBuilder sqlBuilder) { + String surname = extractJsonValue(sqlBuilder, tablePrefix, originColumn, "surname"); + String firstName = extractJsonValue(sqlBuilder, tablePrefix, originColumn, "firstName"); + String username = extractJsonValue(sqlBuilder, tablePrefix, originColumn, "username"); - // If username only, return username - + " when coalesce(trim({prefix}.{column} ->> 'surname'), '') = ''" - + " and coalesce(trim({prefix}.{column} ->> 'firstName'), '') = ''" - + " and coalesce(trim({prefix}.{column} ->> 'username'), '') <> ''" - + " then trim({prefix}.{column} ->> 'username')" + // Helper methods for the CASE conditions + UnaryOperator isEmpty = expression -> sqlBuilder.coalesce(expression, "''") + " = ''"; - // If firstName only, return firstName - + " when coalesce(trim({prefix}.{column} ->> 'surname'), '') = ''" - + " and coalesce(trim({prefix}.{column} ->> 'firstName'), '') <> ''" - + " and coalesce(trim({prefix}.{column} ->> 'username'), '') = ''" - + " then trim({prefix}.{column} ->> 'firstName')" + UnaryOperator isNotEmpty = + expression -> sqlBuilder.coalesce(expression, "''") + " <> ''"; - // If surname only, return surname - + " when coalesce(trim({prefix}.{column} ->> 'surname'), '') <> ''" - + " and coalesce(trim({prefix}.{column} ->> 'firstName'), '') = ''" - + " and coalesce(trim({prefix}.{column} ->> 'username'), '') = ''" - + " then trim({prefix}.{column} ->> 'surname')" + return String.format( + "case" + + + // All empty + " when %s and %s and %s then null" + + + // Username only + " when %s and %s and %s then %s" + + + // FirstName only + " when %s and %s and %s then %s" + + + // Surname only + " when %s and %s and %s then %s" + + + // Surname and FirstName + " when %s and %s and %s then %s" + + + // FirstName and Username + " when %s and %s and %s then %s" + + + // Surname and Username + " when %s and %s and %s then %s" + + + // All fields + " else %s end as %s", + // All empty + isEmpty.apply(surname), + isEmpty.apply(firstName), + isEmpty.apply(username), - // If surname and firstName only, return surname + firstName - + " when coalesce(trim({prefix}.{column} ->> 'surname'), '') <> ''" - + " and coalesce(trim({prefix}.{column} ->> 'firstName'), '') <> ''" - + " and coalesce(trim({prefix}.{column} ->> 'username'), '') = ''" - + " then concat(trim({prefix}.{column} ->> 'surname'), ', ', trim({prefix}.{column} ->> 'firstName'))" + // Username only + isEmpty.apply(surname), + isEmpty.apply(firstName), + isNotEmpty.apply(username), + username, - // If firstName and username only, return firstName + username - + " when coalesce(trim({prefix}.{column} ->> 'surname'), '') = ''" - + " and coalesce(trim({prefix}.{column} ->> 'firstName'), '') <> ''" - + " and coalesce(trim({prefix}.{column} ->> 'username'), '') <> ''" - + " then concat(trim({prefix}.{column} ->> 'firstName'), ' (', trim({prefix}.{column} ->> 'username'), ')')" + // FirstName only + isEmpty.apply(surname), + isNotEmpty.apply(firstName), + isEmpty.apply(username), + firstName, - // If surname and username only, return surname + username - + " when coalesce(trim({prefix}.{column} ->> 'surname'), '') <> ''" - + " and coalesce(trim({prefix}.{column} ->> 'firstName'), '') = ''" - + " and coalesce(trim({prefix}.{column} ->> 'username'), '') <> ''" - + " then concat(trim({prefix}.{column} ->> 'surname'), ' (', trim({prefix}.{column} ->> 'username'), ')')" + // Surname only + isNotEmpty.apply(surname), + isEmpty.apply(firstName), + isEmpty.apply(username), + surname, - // If has all columns populated, return surname + firstName + - // username - + " else concat(trim({prefix}.{column} ->> 'surname'), ', ', trim({prefix}.{column} ->> 'firstName'), ' (', trim({prefix}.{column} ->> 'username'), ')') end" - + " as {alias}") - .replaceAll("\\{column}", originColumn) - .replaceAll("\\{prefix}", tablePrefix) - .replaceAll("\\{alias}", columnAlias); + // Surname and FirstName + isNotEmpty.apply(surname), + isNotEmpty.apply(firstName), + isEmpty.apply(username), + formatNames(sqlBuilder, surname, "', '", firstName), + + // FirstName and Username + isEmpty.apply(surname), + isNotEmpty.apply(firstName), + isNotEmpty.apply(username), + formatNames(sqlBuilder, firstName, "' ('", username, "')'"), + + // Surname and Username + isNotEmpty.apply(surname), + isEmpty.apply(firstName), + isNotEmpty.apply(username), + formatNames(sqlBuilder, surname, "' ('", username, "')'"), + + // All fields + formatNames(sqlBuilder, surname, "', '", firstName, "' ('", username, "')'"), + columnAlias); + } + + private static String extractJsonValue( + SqlBuilder sqlBuilder, String tablePrefix, String originColumn, String path) { + String json = tablePrefix + "." + originColumn; + String jsonExtracted = sqlBuilder.jsonExtract(json, path); + return sqlBuilder.trim(jsonExtracted); + } + + private static String formatNames(SqlBuilder sqlBuilder, String... elements) { + return sqlBuilder.concat(elements); } } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/model/DataType.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/model/DataType.java index c4269aa5beea..5c39ea738b40 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/model/DataType.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/model/DataType.java @@ -43,7 +43,6 @@ public enum DataType { DOUBLE, BOOLEAN, CHARACTER_11, - CHARACTER_32, VARCHAR_50, VARCHAR_255, TEXT, @@ -58,7 +57,7 @@ public enum DataType { EnumSet.of(SMALLINT, BIGINT, INTEGER, DECIMAL, FLOAT, DOUBLE); private static final EnumSet TYPES_CHARACTER = - EnumSet.of(CHARACTER_11, CHARACTER_32, VARCHAR_50, VARCHAR_255, TEXT); + EnumSet.of(CHARACTER_11, VARCHAR_50, VARCHAR_255, TEXT); /** * Indicates if the data type is numeric. diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/model/Database.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/model/Database.java index c20f9ac54767..57a73caf358f 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/model/Database.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/model/Database.java @@ -27,12 +27,27 @@ */ package org.hisp.dhis.db.model; +import org.hisp.dhis.analytics.table.init.AnalyticsDatabaseInit; +import org.hisp.dhis.db.sql.SqlBuilder; +import org.hisp.dhis.db.sql.SqlBuilderProvider; + /** * Enumeration of database platforms. * + *

To add support for a new analytics database engine, add the following: + * + *

    + *
  • Value to this {@link Database}. + *
  • Implementation class of {@link SqlBuilder}. + *
  • Register {@link SqlBuilder} implementation in {@link SqlBuilderProvider}. + *
  • Method to {@link AnalyticsDatabaseInit} (optional). + *
  • JDBC driver in pom.xml. + *
+ * * @author Lars Helge Overland */ public enum Database { POSTGRESQL, - DORIS; + DORIS, + CLICKHOUSE } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/model/Table.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/model/Table.java index b447bc36da0f..2c0c83f85d04 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/model/Table.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/model/Table.java @@ -61,10 +61,13 @@ public class Table { /** Table primary key column name(s). Optional. */ private final List primaryKey; - /** Table checks. PostgreSQL-only feature. Optional. */ + /** Table sort key. Applies to column-oriented databases including Doris and ClickHouse only. */ + private final List sortKey; + + /** Table checks. Applies to PostgreSQL only. Optional. */ private final List checks; - /** Whether table is logged or unlogged. PostgreSQL-only feature. */ + /** Whether table is logged or unlogged. Applies to PostgreSQL only. */ private final Logged logged; /** Parent table. This table inherits from the parent if specified. Optional. */ @@ -84,6 +87,7 @@ public Table(String name, List columns, List primaryKey) { this.name = name; this.columns = columns; this.primaryKey = primaryKey; + this.sortKey = List.of(); this.checks = List.of(); this.logged = Logged.UNLOGGED; this.parent = null; @@ -102,6 +106,7 @@ public Table(String name, List columns, List primaryKey, Logged this.name = name; this.columns = columns; this.primaryKey = primaryKey; + this.sortKey = List.of(); this.checks = List.of(); this.logged = logged; this.parent = null; @@ -114,6 +119,7 @@ public Table(String name, List columns, List primaryKey, Logged * @param name the table name. * @param columns the list of {@link Column}. * @param primaryKey the primary key. + * @param sortKey the sort key. * @param checks the list of checks. * @param logged the {@link Logged} parameter. */ @@ -121,11 +127,13 @@ public Table( String name, List columns, List primaryKey, + List sortKey, List checks, Logged logged) { this.name = name; this.columns = columns; this.primaryKey = primaryKey; + this.sortKey = sortKey; this.checks = checks; this.logged = logged; this.parent = null; @@ -152,6 +160,7 @@ public Table( this.name = name; this.columns = columns; this.primaryKey = primaryKey; + this.sortKey = List.of(); this.checks = checks; this.logged = logged; this.parent = parent; @@ -200,6 +209,15 @@ public String getFirstPrimaryKey() { return hasPrimaryKey() ? primaryKey.get(0) : null; } + /** + * Indicates whether the table has a sort key. + * + * @return true if the table has a sort key. + */ + public boolean hasSortKey() { + return isNotEmpty(sortKey); + } + /** * Indicates whether the table has at least one check. * diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/AbstractSqlBuilder.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/AbstractSqlBuilder.java index d40f83c2214a..a354b4ddaa97 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/AbstractSqlBuilder.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/AbstractSqlBuilder.java @@ -31,6 +31,7 @@ import java.util.Collection; import java.util.Map; +import java.util.function.Function; import java.util.stream.Collectors; import org.apache.commons.text.StringSubstitutor; import org.hisp.dhis.db.model.DataType; @@ -58,11 +59,21 @@ public abstract class AbstractSqlBuilder implements SqlBuilder { // Utilities + @Override + public String quote(String alias, String relation) { + return alias + DOT + quote(relation); + } + @Override public String quoteAx(String relation) { return ALIAS_AX + DOT + quote(relation); } + @Override + public String singleQuote(String value) { + return SINGLE_QUOTE + escape(value) + SINGLE_QUOTE; + } + @Override public String singleQuotedCommaDelimited(Collection items) { return isEmpty(items) @@ -70,6 +81,23 @@ public String singleQuotedCommaDelimited(Collection items) { : items.stream().map(this::singleQuote).collect(Collectors.joining(COMMA)); } + // Index types + + @Override + public String indexTypeBtree() { + return notSupported(); + } + + @Override + public String indexTypeGist() { + return notSupported(); + } + + @Override + public String indexTypeGin() { + return notSupported(); + } + // Statements @Override @@ -103,6 +131,33 @@ public String countRows(Table table) { return String.format("select count(*) as row_count from %s;", quote(table.getName())); } + // Table + + @Override + public String dropTableIfExists(String name) { + return String.format("drop table if exists %s;", quote(name)); + } + + @Override + public String analyzeTable(String name) { + return notSupported(); + } + + @Override + public String vacuumTable(Table table) { + return notSupported(); + } + + @Override + public String setParentTable(Table table, String parentName) { + return notSupported(); + } + + @Override + public String removeParentTable(Table table, String parentName) { + return notSupported(); + } + // Mapping /** @@ -121,7 +176,6 @@ protected String getDataTypeName(DataType dataType) { case DOUBLE -> dataTypeDouble(); case BOOLEAN -> dataTypeBoolean(); case CHARACTER_11 -> dataTypeCharacter(11); - case CHARACTER_32 -> dataTypeCharacter(32); case VARCHAR_50 -> dataTypeVarchar(50); case VARCHAR_255 -> dataTypeVarchar(255); case TEXT -> dataTypeText(); @@ -206,4 +260,17 @@ protected String toIndexColumn(Index index, String column) { protected String notSupported() { throw new UnsupportedOperationException(); } + + /** + * Converts the given collection to a comma-separated string, using the given mapping function to + * convert each item in the collection to a string. + * + * @param + * @param collection the {@link Collection}. + * @param mapper the string mapping {@link Function}. + * @return a comma-separated string. + */ + protected String toCommaSeparated(Collection collection, Function mapper) { + return collection.stream().map(mapper).collect(Collectors.joining(",")); + } } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/ClickHouseSqlBuilder.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/ClickHouseSqlBuilder.java new file mode 100644 index 000000000000..42938d1403d8 --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/ClickHouseSqlBuilder.java @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2004-2024, 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.db.sql; + +import java.util.Map; +import java.util.Map.Entry; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.Validate; +import org.hisp.dhis.db.model.Column; +import org.hisp.dhis.db.model.Index; +import org.hisp.dhis.db.model.Table; +import org.hisp.dhis.db.model.constraint.Nullable; + +@Getter +@RequiredArgsConstructor +public class ClickHouseSqlBuilder extends AbstractSqlBuilder { + + // Constants + + public static final String NAMED_COLLECTION = "pg_dhis"; + + private static final String QUOTE = "\""; + + // Data types + + @Override + public String dataTypeSmallInt() { + return "Int16"; + } + + @Override + public String dataTypeInteger() { + return "Int32"; + } + + @Override + public String dataTypeBigInt() { + return "Int64"; + } + + @Override + public String dataTypeDecimal() { + return "Decimal(10,6)"; + } + + @Override + public String dataTypeFloat() { + return "Float32"; + } + + @Override + public String dataTypeDouble() { + return "Float64"; + } + + @Override + public String dataTypeBoolean() { + return "Bool"; + } + + @Override + public String dataTypeCharacter(int length) { + return "String"; + } + + @Override + public String dataTypeVarchar(int length) { + return "String"; + } + + @Override + public String dataTypeText() { + return "String"; + } + + @Override + public String dataTypeDate() { + return "Date"; + } + + @Override + public String dataTypeTimestamp() { + return "DateTime64(3)"; + } + + @Override + public String dataTypeTimestampTz() { + return "DateTime64(3)"; + } + + @Override + public String dataTypeGeometry() { + return "String"; + } + + @Override + public String dataTypeGeometryPoint() { + return "String"; + } + + @Override + public String dataTypeJson() { + return "JSON"; + } + + // Index functions + + @Override + public String indexFunctionUpper() { + return "upper"; + } + + @Override + public String indexFunctionLower() { + return "lower"; + } + + // Capabilities + + @Override + public boolean supportsGeospatialData() { + return false; + } + + @Override + public boolean supportsDeclarativePartitioning() { + return true; + } + + @Override + public boolean supportsAnalyze() { + return false; + } + + @Override + public boolean supportsVacuum() { + return false; + } + + @Override + public boolean requiresIndexesForAnalytics() { + return false; + } + + // Utilities + + @Override + public String quote(String relation) { + String escapedRelation = relation.replace(QUOTE, (QUOTE + QUOTE)); + return QUOTE + escapedRelation + QUOTE; + } + + @Override + public String escape(String value) { + return value.replace(SINGLE_QUOTE, (SINGLE_QUOTE + SINGLE_QUOTE)); + } + + /** Uses the postgresql table function to query DHIS 2 PostgreSQL server. */ + @Override + public String qualifyTable(String name) { + return String.format("postgresql(%s, table=%s)", quote(NAMED_COLLECTION), singleQuote(name)); + } + + @Override + public String dateTrunc(String text, String timestamp) { + return String.format("date_trunc(%s, %s)", singleQuote(text), timestamp); + } + + @Override + public String differenceInSeconds(String columnA, String columnB) { + return String.format("(toUnixTimestamp(%s) - toUnixTimestamp(%s))", columnA, columnB); + } + + @Override + public String regexpMatch(String value, String pattern) { + return String.format("match(%s, %s)", value, pattern); + } + + @Override + public String concat(String... columns) { + return "concat(" + String.join(", ", columns) + ")"; + } + + @Override + public String trim(String expression) { + return "trim(" + expression + ")"; + } + + @Override + public String coalesce(String expression, String defaultValue) { + return "coalesce(" + expression + ", " + defaultValue + ")"; + } + + @Override + public String jsonExtract(String json, String property) { + return String.format("JSONExtractString(%s, '%s')", json, property); + } + + @Override + public String jsonExtractNested(String json, String... expression) { + String path = String.join(".", expression); + return String.format("JSONExtractString(%s, '%s')", json, path); + } + + // Statements + + @Override + public String createTable(Table table) { + Validate.notEmpty(table.getColumns()); + + StringBuilder sql = + new StringBuilder("create table ").append(quote(table.getName())).append(" "); + + // Columns + + if (table.hasColumns()) { + String columns = toCommaSeparated(table.getColumns(), this::toColumnString); + + sql.append("(").append(columns).append(") engine = MergeTree() "); + } + + // Order by + + sql.append(getOrderByClause(table)); + + return sql.append(";").toString(); + } + + /** + * Returns a column definition string. + * + * @param column the {@link Column}. + * @return a column clause. + */ + private String toColumnString(Column column) { + String dataType = getDataTypeName(column.getDataType()); + String nullable = column.getNullable() == Nullable.NOT_NULL ? " not null" : " null"; + return quote(column.getName()) + " " + dataType + nullable; + } + + /** + * Returns an order by clause. The primary key columns will be used for ordering. ClickHouse will + * use order by columns as primary key when the primary key clause is omitted. The primary key + * does not have to uniquely identify each row. + * + * @param table the {@link Table}. + * @return the order by clause. + */ + private String getOrderByClause(Table table) { + String keys = null; + + if (table.hasSortKey()) { + keys = toCommaSeparated(table.getSortKey(), this::quote); + } else if (table.hasPrimaryKey()) { + keys = toCommaSeparated(table.getPrimaryKey(), this::quote); + } else { + keys = quote(table.getFirstColumn().getName()); + } + + return String.format("order by (%s)", keys); + } + + @Override + public String renameTable(Table table, String newName) { + return String.format("rename table %s to %s;", quote(table.getName()), quote(newName)); + } + + @Override + public String dropTableIfExistsCascade(Table table) { + return dropTableIfExists(table); + } + + @Override + public String dropTableIfExistsCascade(String name) { + return dropTableIfExists(name); + } + + @Override + public String tableExists(String name) { + return String.format( + """ + select t.name as table_name \ + from system.tables t \ + where t.database = 'default' \ + and t.name = %s \ + and engine not in ('View', 'Materialized View');""", + singleQuote(name)); + } + + @Override + public String createIndex(Index index) { + return notSupported(); + } + + @Override + public String createCatalog(String connectionUrl, String username, String password) { + return notSupported(); + } + + @Override + public String dropCatalogIfExists() { + return notSupported(); + } + + /** + * @param name the collection name. + * @param keyValues the map of key value pairs. + * @return a create named collection statement. + */ + public String createNamedCollection(String name, Map keyValues) { + String pairs = toCommaSeparated(keyValues.entrySet(), this::toPairString); + return String.format("create named collection %s as %s;", quote(name), pairs); + } + + /** + * @param name the collection name. + * @return a drop named collection if exists statement. + */ + public String dropNamedCollectionIfExists(String name) { + return String.format("drop named collection if exists %s;", quote(name)); + } + + /** + * Converts the given {@link Map} {@link Entry} to a key value pair string. + * + * @param pair the {@link Entry}. + * @return a key value pair string. + */ + private String toPairString(Entry pair) { + return String.format( + "%s = %s", quote(pair.getKey()), singleQuote(String.valueOf(pair.getValue()))); + } +} diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/DorisSqlBuilder.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/DorisSqlBuilder.java index cf13ca1a9949..920ca25d1e8e 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/DorisSqlBuilder.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/DorisSqlBuilder.java @@ -27,8 +27,8 @@ */ package org.hisp.dhis.db.sql; -import static org.hisp.dhis.commons.util.TextUtils.removeLastComma; - +import java.util.Comparator; +import java.util.List; import java.util.Map; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.Validate; @@ -131,23 +131,6 @@ public String dataTypeJson() { return "json"; } - // Index types - - @Override - public String indexTypeBtree() { - return notSupported(); - } - - @Override - public String indexTypeGist() { - return notSupported(); - } - - @Override - public String indexTypeGin() { - return notSupported(); - } - // Index functions @Override @@ -195,16 +178,6 @@ public String quote(String relation) { return QUOTE + escapedRelation + QUOTE; } - @Override - public String quote(String alias, String relation) { - return alias + DOT + quote(relation); - } - - @Override - public String singleQuote(String value) { - return SINGLE_QUOTE + escape(value) + SINGLE_QUOTE; - } - @Override public String escape(String value) { return value @@ -222,6 +195,42 @@ public String dateTrunc(String text, String timestamp) { return String.format("date_trunc(%s, %s)", timestamp, singleQuote(text)); } + @Override + public String differenceInSeconds(String columnA, String columnB) { + return String.format("(unix_timestamp(%s) - unix_timestamp(%s))", columnA, columnB); + } + + @Override + public String regexpMatch(String value, String pattern) { + return String.format("%s regexp %s", value, pattern); + } + + @Override + public String concat(String... columns) { + return "concat(" + String.join(", ", columns) + ")"; + } + + @Override + public String trim(String expression) { + return "trim(" + expression + ")"; + } + + @Override + public String coalesce(String expression, String defaultValue) { + return "coalesce(" + expression + ", " + defaultValue + ")"; + } + + @Override + public String jsonExtract(String json, String property) { + return String.format("json_unquote(json_extract(%s, '$.%s'))", json, property); + } + + @Override + public String jsonExtractNested(String column, String... expression) { + String path = "$." + String.join(".", expression); + return String.format("json_unquote(json_extract(%s, '%s'))", column, path); + } + // Statements @Override @@ -234,29 +243,18 @@ public String createTable(Table table) { // Columns if (table.hasColumns()) { - sql.append("("); - - for (Column column : table.getColumns()) { - String dataType = getDataTypeName(column.getDataType()); - String nullable = column.getNullable() == Nullable.NOT_NULL ? " not null" : " null"; - - sql.append(quote(column.getName()) + " ").append(dataType).append(nullable).append(", "); - } + String columns = toCommaSeparated(table.getColumns(), this::toColumnString); - removeLastComma(sql).append(") engine = olap "); + sql.append("(").append(columns).append(") engine = olap "); } // Primary key if (table.hasPrimaryKey()) { // If primary key exists, use it as keys with the unique model - sql.append("unique key ("); - - for (String columnName : table.getPrimaryKey()) { - sql.append(quote(columnName) + ", "); - } + String keys = toCommaSeparated(table.getPrimaryKey(), this::quote); - removeLastComma(sql).append(") "); + sql.append("unique key (").append(keys).append(") "); } else if (table.hasColumns()) { // If columns exist, use first as key with the duplicate model String key = quote(table.getFirstColumn().getName()); @@ -267,17 +265,7 @@ public String createTable(Table table) { // Partitions if (table.hasPartitions()) { - sql.append("partition by range(year) ("); // Make configurable - - for (TablePartition partition : table.getPartitions()) { - sql.append("partition ") - .append(quote(partition.getName())) - .append(" values less than(\"") - .append(partition.getValue()) // Set last partition to max value - .append("\"),"); - } - - removeLastComma(sql).append(") "); + sql.append(generatePartitionClause(table.getPartitions())); } // Distribution @@ -298,6 +286,63 @@ public String createTable(Table table) { return sql.append(";").toString(); } + /** + * Generates the partition clause for the table creation SQL. + * + * @param partitions the list of table partitions + * @return the partition clause string + */ + private String generatePartitionClause(List partitions) { + StringBuilder partitionClause = + new StringBuilder("partition by range(year) ("); // Make configurable + + List sortedPartitions; + try { + sortedPartitions = + partitions.stream() + .sorted(Comparator.comparingInt(p -> Integer.parseInt(p.getValue().toString()))) + .toList(); + } catch (NumberFormatException e) { + sortedPartitions = partitions; + } + + for (int i = 0; i < sortedPartitions.size(); i++) { + if (i == sortedPartitions.size() - 1) { + // Handle last partition with MAXVALUE + partitionClause + .append("partition ") + .append(quote(sortedPartitions.get(i).getName())) + .append(" values less than(MAXVALUE),"); + } else { + partitionClause.append(toPartitionString(sortedPartitions.get(i))).append(","); + } + } + return partitionClause.substring(0, partitionClause.length() - 1) + ") "; + } + + /** + * Returns a column definition string. + * + * @param column the {@link Column}. + * @return a column clause. + */ + private String toColumnString(Column column) { + String dataType = getDataTypeName(column.getDataType()); + String nullable = column.getNullable() == Nullable.NOT_NULL ? " not null" : " null"; + return quote(column.getName()) + " " + dataType + nullable; + } + + /** + * Returns a partition definition string. + * + * @param partition the {@link TablePartition}. + * @return a partition definition string. + */ + private String toPartitionString(TablePartition partition) { + String condition = "values less than(\"" + partition.getValue() + "\")"; + return "partition " + quote(partition.getName()) + " " + condition; + } + /** * Returns the distribution key. Uses the first primary key column name if any exists, or the * first column name if any exists, otherwise null. @@ -314,26 +359,11 @@ private String getDistKey(Table table) { return null; } - @Override - public String analyzeTable(String name) { - return notSupported(); - } - - @Override - public String vacuumTable(Table table) { - return notSupported(); - } - @Override public String renameTable(Table table, String newName) { return String.format("alter table %s rename %s;", quote(table.getName()), quote(newName)); } - @Override - public String dropTableIfExists(String name) { - return String.format("drop table if exists %s;", quote(name)); - } - @Override public String dropTableIfExistsCascade(Table table) { return dropTableIfExists(table); @@ -344,22 +374,13 @@ public String dropTableIfExistsCascade(String name) { return dropTableIfExists(name); } - @Override - public String setParentTable(Table table, String parentName) { - return notSupported(); - } - - @Override - public String removeParentTable(Table table, String parentName) { - return notSupported(); - } - @Override public String tableExists(String name) { return String.format( """ select t.table_name from information_schema.tables t \ - where t.table_schema = 'public' and t.table_name = %s;""", + where t.table_schema = 'public' \ + and t.table_name = %s;""", singleQuote(name)); } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/PostgreSqlBuilder.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/PostgreSqlBuilder.java index 4dcfe13a4001..aebd74100e11 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/PostgreSqlBuilder.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/PostgreSqlBuilder.java @@ -29,7 +29,6 @@ import static org.hisp.dhis.commons.util.TextUtils.removeLastComma; -import java.util.stream.Collectors; import org.hisp.dhis.db.model.Collation; import org.hisp.dhis.db.model.Column; import org.hisp.dhis.db.model.Index; @@ -198,16 +197,6 @@ public String quote(String relation) { return QUOTE + escapedRelation + QUOTE; } - @Override - public String quote(String alias, String relation) { - return alias + DOT + quote(relation); - } - - @Override - public String singleQuote(String value) { - return SINGLE_QUOTE + escape(value) + SINGLE_QUOTE; - } - @Override public String escape(String value) { return value @@ -225,6 +214,41 @@ public String dateTrunc(String text, String timestamp) { return String.format("date_trunc(%s, %s)", singleQuote(text), timestamp); } + @Override + public String differenceInSeconds(String columnA, String columnB) { + return String.format("extract(epoch from (%s - %s))", columnA, columnB); + } + + @Override + public String regexpMatch(String value, String pattern) { + return String.format("%s ~* %s", value, pattern); + } + + @Override + public String concat(String... columns) { + return "concat(" + String.join(", ", columns) + ")"; + } + + @Override + public String trim(String expression) { + return "trim(" + expression + ")"; + } + + @Override + public String coalesce(String expression, String defaultValue) { + return "coalesce(" + expression + ", " + defaultValue + ")"; + } + + @Override + public String jsonExtract(String column, String property) { + return String.format("%s ->> '%s'", column, property); + } + + @Override + public String jsonExtractNested(String column, String... expression) { + return String.format("%s #>> '{%s}'", column, String.join(", ", expression)); + } + // Statements @Override @@ -300,11 +324,6 @@ public String renameTable(Table table, String newName) { return String.format("alter table %s rename to %s;", quote(table.getName()), quote(newName)); } - @Override - public String dropTableIfExists(String name) { - return String.format("drop table if exists %s;", quote(name)); - } - @Override public String dropTableIfExistsCascade(Table table) { return dropTableIfExistsCascade(table.getName()); @@ -340,12 +359,7 @@ public String createIndex(Index index) { String unique = index.getUnique() == Unique.UNIQUE ? "unique " : ""; String tableName = index.getTableName(); String typeName = getIndexTypeName(index.getIndexType()); - - String columns = - index.getColumns().stream() - .map(col -> toIndexColumn(index, col)) - .collect(Collectors.joining(COMMA)); - + String columns = toCommaSeparated(index.getColumns(), col -> toIndexColumn(index, col)); String sortOrder = index.getSortOrder(); return sortOrder == null diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/SqlBuilder.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/SqlBuilder.java index 1f768b886bc7..8655372ff807 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/SqlBuilder.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/SqlBuilder.java @@ -229,6 +229,64 @@ public interface SqlBuilder { */ String dateTrunc(String timeUnit, String source); + /** + * @param columnA the name of the first date column. + * @param columnB the name of the second date column. + * @return an expression which returns the difference in seconds. + */ + String differenceInSeconds(String columnA, String columnB); + + /** + * @param value the string value such as a column or expression. + * @param pattern the regular expression pattern to match against. + * @return a regular expression string matching clause. + */ + String regexpMatch(String value, String pattern); + + /** + * Creates a SQL concatenation function that combines multiple columns or expressions. + * + * @param columns the column names or expressions to concatenate. + * @return the SQL function for concatenation. + */ + String concat(String... columns); + + /** + * Creates a SQL trim function that removes leading and trailing spaces from an expression. + * + * @param expression the expression to trim. + * @return the SQL function for trimming. + */ + String trim(String expression); + + /** + * Creates a SQL COALESCE function that returns the first non-null expression from the provided + * expressions. If the first expression is null, it returns the default expression. + * + * @param expression the expression to check for null. + * @param defaultValue the value to return if the first expression is null. + * @return the SQL function for coalescing. + */ + String coalesce(String expression, String defaultValue); + + /** + * Extracts a value from a JSON column using a specified property path. + * + * @param json the JSON column name or value to extract from. + * @param property the JSON property path to extract. + * @return the SQL function for JSON value extraction. + */ + String jsonExtract(String json, String property); + + /** + * Extracts a nested value from a JSON column. + * + * @param json the JSON column name or value to extract from. + * @param expression the hierarchical path expression to the nested value. + * @return a SQL expression to extract the specified nested value from the JSON column. + */ + String jsonExtractNested(String json, String... expression); + // Statements /** diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/SqlBuilderProvider.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/SqlBuilderProvider.java index 9f8a967e5052..23d85a3b4757 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/SqlBuilderProvider.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/db/sql/SqlBuilderProvider.java @@ -67,6 +67,7 @@ private SqlBuilder getSqlBuilder(AnalyticsTableSettings config) { return switch (database) { case DORIS -> new DorisSqlBuilder(catalog, driverFilename); + case CLICKHOUSE -> new ClickHouseSqlBuilder(); default -> new PostgreSqlBuilder(); }; } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/DefaultResourceTableService.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/DefaultResourceTableService.java index 0feed3788ee0..8f01d9ac7fad 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/DefaultResourceTableService.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/DefaultResourceTableService.java @@ -29,8 +29,6 @@ import static java.time.temporal.ChronoUnit.YEARS; import static java.util.Comparator.reverseOrder; -import static org.hisp.dhis.period.PeriodDataProvider.DataSource.DATABASE; -import static org.hisp.dhis.period.PeriodDataProvider.DataSource.SYSTEM_DEFINED; import static org.hisp.dhis.scheduling.JobProgress.FailurePolicy.SKIP_ITEM; import com.google.common.collect.Lists; @@ -65,6 +63,7 @@ import org.hisp.dhis.resourcetable.table.DataElementGroupSetResourceTable; import org.hisp.dhis.resourcetable.table.DataElementResourceTable; import org.hisp.dhis.resourcetable.table.DataSetOrganisationUnitCategoryResourceTable; +import org.hisp.dhis.resourcetable.table.DataSetResourceTable; import org.hisp.dhis.resourcetable.table.DatePeriodResourceTable; import org.hisp.dhis.resourcetable.table.IndicatorGroupSetResourceTable; import org.hisp.dhis.resourcetable.table.OrganisationUnitGroupSetResourceTable; @@ -110,6 +109,14 @@ public void generateResourceTables() { } } + @Override + @Transactional + public void replicateAnalyticsResourceTables() { + for (ResourceTable table : getResourceTables()) { + resourceTableStore.replicateAnalyticsResourceTable(table); + } + } + @Override @Transactional public void generateDataApprovalResourceTables() { @@ -140,6 +147,7 @@ private final List getResourceTables() { logged, idObjectManager.getDataDimensionsNoAcl(DataElementGroupSet.class)), new IndicatorGroupSetResourceTable( logged, idObjectManager.getAllNoAcl(IndicatorGroupSet.class)), + new DataSetResourceTable(logged), new OrganisationUnitGroupSetResourceTable( logged, idObjectManager.getDataDimensionsNoAcl(OrganisationUnitGroupSet.class), @@ -175,8 +183,7 @@ private final List getApprovalResourceTables() { */ List getAndValidateAvailableDataYears() { List availableYears = - periodDataProvider.getAvailableYears( - analyticsTableSettings.getMaxPeriodYearsOffset() == null ? SYSTEM_DEFINED : DATABASE); + periodDataProvider.getAvailableYears(analyticsTableSettings.getPeriodSource()); validateYearsOffset(availableYears); return availableYears; } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/ResourceTableStore.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/ResourceTableStore.java index 944b08c69a95..a8c3d0b28b72 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/ResourceTableStore.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/ResourceTableStore.java @@ -34,7 +34,14 @@ public interface ResourceTableStore { /** * Generates the given resource table. * - * @param resourceTable the resource table. + * @param resourceTable the {@link ResourceTable}. */ void generateResourceTable(ResourceTable resourceTable); + + /** + * Replicates analytics resource tables. + * + * @param resourceTable the {@link ResourceTable}. + */ + void replicateAnalyticsResourceTable(ResourceTable resourceTable); } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/jdbc/JdbcResourceTableStore.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/jdbc/JdbcResourceTableStore.java index 41fc8eb3a3cd..362558394638 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/jdbc/JdbcResourceTableStore.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/jdbc/JdbcResourceTableStore.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis.resourcetable.jdbc; +import static java.lang.String.format; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import static org.hisp.dhis.commons.util.TextUtils.removeLastComma; @@ -46,6 +47,7 @@ import org.hisp.dhis.resourcetable.ResourceTableStore; import org.hisp.dhis.resourcetable.ResourceTableType; import org.hisp.dhis.system.util.Clock; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; @@ -61,8 +63,13 @@ public class JdbcResourceTableStore implements ResourceTableStore { private final JdbcTemplate jdbcTemplate; + @Qualifier("analyticsJdbcTemplate") + private final JdbcTemplate analyticsJdbcTemplate; + private final SqlBuilder sqlBuilder = new PostgreSqlBuilder(); + private final SqlBuilder analyticsDatabaseSqlBuilder; + @Override public void generateResourceTable(ResourceTable resourceTable) { final Clock clock = new Clock().startClock(); @@ -92,6 +99,59 @@ public void generateResourceTable(ResourceTable resourceTable) { log.info("Resource table update done: '{}' '{}'", tableName, clock.time()); } + @Override + public void replicateAnalyticsResourceTable(ResourceTable resourceTable) { + final Clock clock = new Clock().startClock(); + final Table table = resourceTable.getMainTable(); + final String tableName = table.getName(); + + dropAnalyticsDatabaseTable(table); + + createAnalyticsDatabaseTable(table); + + replicateAnalyticsDatabaseTable(table); + + log.info("Analytics resource table replication done: '{}' '{}'", tableName, clock.time()); + } + + /** + * Drops the given analytics database table. + * + * @param table the {@link Table}. + */ + private void dropAnalyticsDatabaseTable(Table table) { + String sql = analyticsDatabaseSqlBuilder.dropTableIfExists(table); + log.info("Drop table SQL: '{}'", sql); + analyticsJdbcTemplate.execute(sql); + } + + /** + * Creates the given analytics database table. + * + * @param table the {@link Table}. + */ + private void createAnalyticsDatabaseTable(Table table) { + String sql = analyticsDatabaseSqlBuilder.createTable(table); + log.info("Create table SQL: '{}'", sql); + analyticsJdbcTemplate.execute(sql); + } + + /** + * Replicates the given table in the analytics database. + * + * @param table the {@link Table}. + */ + private void replicateAnalyticsDatabaseTable(Table table) { + // TO DO use explicit column list + String sql = + format( + "insert into %s select * from %s", + analyticsDatabaseSqlBuilder.quote(table.getName()), + analyticsDatabaseSqlBuilder.qualifyTable(table.getName())); + log.info("Replicate table SQL: '{}'", sql); + analyticsJdbcTemplate.execute(sql); + } + /** * Drops the given table. * @@ -132,7 +192,7 @@ private void populateTable(ResourceTable resourceTable, Table table) { List content = populateTableContent.get(); log.debug("Populate table content rows: {}", content.size()); - if (content.size() > 0) { + if (isNotEmpty(content)) { int columns = content.get(0).length; batchUpdate(columns, table.getName(), content); } @@ -149,7 +209,7 @@ private void invokeTableHooks(ResourceTableType tableType) { analyticsTableHookService.getByPhaseAndResourceTableType( AnalyticsTablePhase.RESOURCE_TABLE_POPULATED, tableType); - if (!hooks.isEmpty()) { + if (isNotEmpty(hooks)) { analyticsTableHookService.executeAnalyticsTableSqlHooks(hooks); log.info("Invoked resource table hooks: '{}'", hooks.size()); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/table/DataSetResourceTable.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/table/DataSetResourceTable.java new file mode 100644 index 000000000000..18ec1c94174c --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/table/DataSetResourceTable.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2004-2024, 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.resourcetable.table; + +import static org.hisp.dhis.commons.util.TextUtils.replace; +import static org.hisp.dhis.db.model.Table.toStaging; +import static org.hisp.dhis.system.util.SqlUtils.appendRandom; + +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.hisp.dhis.db.model.Column; +import org.hisp.dhis.db.model.DataType; +import org.hisp.dhis.db.model.Index; +import org.hisp.dhis.db.model.Logged; +import org.hisp.dhis.db.model.Table; +import org.hisp.dhis.db.model.constraint.Nullable; +import org.hisp.dhis.db.model.constraint.Unique; +import org.hisp.dhis.resourcetable.ResourceTable; +import org.hisp.dhis.resourcetable.ResourceTableType; + +/** + * @author Lars Helge Overland + */ +@RequiredArgsConstructor +public class DataSetResourceTable implements ResourceTable { + public static final String TABLE_NAME = "analytics_rs_dataset"; + + private final Logged logged; + + @Override + public Table getTable() { + return new Table(toStaging(TABLE_NAME), getColumns(), getPrimaryKey(), logged); + } + + @Override + public Table getMainTable() { + return new Table(TABLE_NAME, getColumns(), getPrimaryKey(), logged); + } + + private List getColumns() { + return List.of( + new Column("datasetid", DataType.BIGINT, Nullable.NOT_NULL), + new Column("uid", DataType.CHARACTER_11, Nullable.NOT_NULL), + new Column("categorycomboid", DataType.BIGINT, Nullable.NOT_NULL)); + } + + private List getPrimaryKey() { + return List.of("datasetid"); + } + + @Override + public List getIndexes() { + return List.of( + Index.builder() + .name(appendRandom("in_rs_dataset_uid")) + .tableName(toStaging(TABLE_NAME)) + .unique(Unique.UNIQUE) + .columns(List.of("uid")) + .build()); + } + + @Override + public ResourceTableType getTableType() { + return ResourceTableType.DATA_SET; + } + + @Override + public Optional getPopulateTempTableStatement() { + String sql = + replace( + """ + insert into ${tableName} \ + (datasetid,uid,categorycomboid) \ + select datasetid,uid,categorycomboid \ + from dataset;""", + "tableName", + toStaging(TABLE_NAME)); + + return Optional.of(sql); + } + + @Override + public Optional> getPopulateTempTableContent() { + return Optional.empty(); + } +} diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/table/OrganisationUnitStructureResourceTable.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/table/OrganisationUnitStructureResourceTable.java index 3ee74b22c419..8b9e14844c19 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/table/OrganisationUnitStructureResourceTable.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/table/OrganisationUnitStructureResourceTable.java @@ -80,6 +80,10 @@ private List getColumns() { Lists.newArrayList( new Column("organisationunitid", DataType.BIGINT, Nullable.NOT_NULL), new Column("organisationunituid", DataType.CHARACTER_11, Nullable.NOT_NULL), + new Column("code", DataType.VARCHAR_50, Nullable.NULL), + new Column("name", DataType.VARCHAR_255, Nullable.NOT_NULL), + new Column("openingdate", DataType.DATE, Nullable.NULL), + new Column("closeddate", DataType.DATE, Nullable.NULL), new Column("level", DataType.INTEGER, Nullable.NOT_NULL), new Column("path", DataType.VARCHAR_255, Nullable.NULL)); @@ -147,6 +151,10 @@ List createBatchObjects(List units, int level) { values.add(unit.getId()); values.add(unit.getUid()); + values.add(unit.getCode()); + values.add(unit.getName()); + values.add(unit.getOpeningDate()); + values.add(unit.getClosedDate()); values.add(level); values.add(unit.getPath()); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/table/PeriodResourceTable.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/table/PeriodResourceTable.java index dc1a0ebfd213..d4b5695dc29c 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/table/PeriodResourceTable.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/resourcetable/table/PeriodResourceTable.java @@ -32,6 +32,7 @@ import com.google.common.collect.Lists; import java.util.ArrayList; +import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -52,6 +53,7 @@ import org.hisp.dhis.period.WeeklyAbstractPeriodType; import org.hisp.dhis.resourcetable.ResourceTable; import org.hisp.dhis.resourcetable.ResourceTableType; +import org.hisp.dhis.util.DateUtils; import org.joda.time.DateTime; /** @@ -86,6 +88,7 @@ private List getColumns() { new Column("enddate", DataType.DATE, Nullable.NOT_NULL), new Column("periodtypeid", DataType.INTEGER, Nullable.NOT_NULL), new Column("periodtypename", DataType.VARCHAR_50, Nullable.NOT_NULL), + new Column("monthstartdate", DataType.DATE, Nullable.NOT_NULL), new Column("year", DataType.INTEGER, Nullable.NOT_NULL)); for (PeriodType periodType : PeriodType.PERIOD_TYPES) { @@ -131,8 +134,9 @@ public Optional> getPopulateTempTableContent() { for (Period period : periods) { if (period != null && period.isValid()) { final String isoDate = period.getIsoDate(); - final int year = resolveYearFromPeriod(period); final PeriodType periodType = period.getPeriodType(); + final Date monthStartDate = DateUtils.dateTruncMonth(period.getStartDate()); + final int year = resolveYearFromPeriod(period); if (!uniqueIsoDates.add(isoDate)) { // Protect against duplicates produced by calendars @@ -150,6 +154,7 @@ public Optional> getPopulateTempTableContent() { values.add(period.getEndDate()); values.add(periodType.getId()); values.add(periodType.getName()); + values.add(monthStartDate); values.add(year); for (Period pe : PeriodType.getPeriodTypePeriods(period, calendar)) { diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/OrgUnitFieldTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/OrgUnitFieldTest.java index c0bad93321e9..840cea482ef0 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/OrgUnitFieldTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/OrgUnitFieldTest.java @@ -33,6 +33,8 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.hisp.dhis.db.sql.PostgreSqlBuilder; +import org.hisp.dhis.db.sql.SqlBuilder; import org.junit.jupiter.api.Test; /** @@ -41,17 +43,25 @@ * @author Jim Grace */ class OrgUnitFieldTest { + + private static final SqlBuilder sqlBuilder = new PostgreSqlBuilder(); + private static final OrgUnitField DEFALT = new OrgUnitField(null); - private static final OrgUnitField ATTRIB = new OrgUnitField("attributeId"); + private static final OrgUnitField ATTRIB = + new OrgUnitField("attributeId").withSqlBuilder(sqlBuilder); - private static final OrgUnitField REGIST = new OrgUnitField("REGISTRATION"); + private static final OrgUnitField REGIST = + new OrgUnitField("REGISTRATION").withSqlBuilder(sqlBuilder); - private static final OrgUnitField ENROLL = new OrgUnitField("ENROLLMENT"); + private static final OrgUnitField ENROLL = + new OrgUnitField("ENROLLMENT").withSqlBuilder(sqlBuilder); - private static final OrgUnitField OSTART = new OrgUnitField("OWNER_AT_START"); + private static final OrgUnitField OSTART = + new OrgUnitField("OWNER_AT_START").withSqlBuilder(sqlBuilder); - private static final OrgUnitField OEND = new OrgUnitField("OWNER_AT_END"); + private static final OrgUnitField OEND = + new OrgUnitField("OWNER_AT_END").withSqlBuilder(sqlBuilder); @Test void testIsJoinOrgUnitTables() { diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/common/params/dimension/DimensionIdentifierHelperTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/common/params/dimension/DimensionIdentifierHelperTest.java index f4c5f57ce8b1..e45a6728764d 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/common/params/dimension/DimensionIdentifierHelperTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/common/params/dimension/DimensionIdentifierHelperTest.java @@ -35,17 +35,13 @@ import org.hisp.dhis.common.IllegalQueryException; import org.junit.jupiter.api.Test; -/** Unit tests for {@link DimensionIdentifierHelper} */ class DimensionIdentifierHelperTest { @Test void testFromFullDimensionId() { - // Given String fullDimensionId = "lxAQ7Zs9VYR[0].RaMbOrTys0n[4].bh1Edk21e2n"; - // When StringDimensionIdentifier dimensionIdentifier = fromFullDimensionId(fullDimensionId); - // Then assertEquals( "bh1Edk21e2n", dimensionIdentifier.getDimension().getUid(), @@ -64,12 +60,9 @@ void testFromFullDimensionId() { @Test void testFromDimensionIdWithUnsupportedOffset() { - // Given String dimensionIdWithDashOffset = "lxAQ7Zs9VYR[-].bh1Edk21e2n"; String dimensionIdWithStringOffset = "lxAQ7Zs9VYR[X].bh1Edk21e2n"; - // When - // Then assertThrows(IllegalQueryException.class, () -> fromFullDimensionId(dimensionIdWithDashOffset)); assertThrows( IllegalQueryException.class, () -> fromFullDimensionId(dimensionIdWithStringOffset)); @@ -77,13 +70,10 @@ void testFromDimensionIdWithUnsupportedOffset() { @Test void testFromFullDimensionIdWithSingleDimension() { - // Given String singleDimensionId = "bh1Edk21e2n"; - // When StringDimensionIdentifier dimensionIdentifier = fromFullDimensionId(singleDimensionId); - // Then assertEquals( "bh1Edk21e2n", dimensionIdentifier.getDimension().getUid(), @@ -96,13 +86,10 @@ void testFromFullDimensionIdWithSingleDimension() { @Test void testFromFullDimensionIdWithProgramAndDimension() { - // Given String singleDimensionId = "lxAQ7Zs9VYR.bh1Edk21e2n"; - // When StringDimensionIdentifier dimensionIdentifier = fromFullDimensionId(singleDimensionId); - // Then assertEquals( "bh1Edk21e2n", dimensionIdentifier.getDimension().getUid(), @@ -117,15 +104,12 @@ void testFromFullDimensionIdWithProgramAndDimension() { @Test void testFromFullDimensionIdWhenSingleDimensionHasOffset() { - // Given String singleDimensionWithOffset = "bh1Edk21e2n[2]"; - // When IllegalArgumentException thrown = assertThrows( IllegalArgumentException.class, () -> fromFullDimensionId(singleDimensionWithOffset)); - // Then assertEquals( "Only program and program stage can have offset", thrown.getMessage(), @@ -134,15 +118,12 @@ void testFromFullDimensionIdWhenSingleDimensionHasOffset() { @Test void testFromFullDimensionIdWhenDimensionHasInvalidFormat() { - // Given String invalidFullDimensionId = "lxAQ7Zs9VYR[1].RaMbOrTys0n[4].bh1Edk21e2n.invalid-id"; - // When IllegalArgumentException thrown = assertThrows( IllegalArgumentException.class, () -> fromFullDimensionId(invalidFullDimensionId)); - // Then assertEquals( "Invalid dimension identifier: " + invalidFullDimensionId, thrown.getMessage(), diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/common/processing/CommonRequestParamsMapperTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/common/processing/CommonRequestParamsMapperTest.java index a2657a931d0e..4b1e883c9dae 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/common/processing/CommonRequestParamsMapperTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/common/processing/CommonRequestParamsMapperTest.java @@ -435,8 +435,7 @@ void mapWhenProgramsCannotBeFound() { programService, dimensionIdentifierConverter); - // List has only one Program, but the CommonQueryRequest, below, has - // two. + // List has only one Program, but the CommonQueryRequest, has two List programs = List.of(program1); CommonRequestParams commonRequestParams = diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/common/query/NotConditionRendererTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/common/query/NotConditionRendererTest.java index 20de5f79ff9c..b3ab91830786 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/common/query/NotConditionRendererTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/common/query/NotConditionRendererTest.java @@ -32,7 +32,6 @@ import org.junit.jupiter.api.Test; class NotConditionRendererTest { - @Test void testInWithSingleValueProduceCorrectSql() { Renderable renderable = () -> "test"; diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/AnalyticsManagerTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/AnalyticsManagerTest.java index 42bf5627e0b5..e4c6b410c5e3 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/AnalyticsManagerTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/AnalyticsManagerTest.java @@ -59,13 +59,14 @@ import org.hisp.dhis.period.Period; import org.hisp.dhis.period.YearlyPeriodType; import org.hisp.dhis.test.TestBase; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.jdbc.core.JdbcTemplate; @@ -80,9 +81,9 @@ class AnalyticsManagerTest extends TestBase { @Mock private ExecutionPlanStore executionPlanStore; - private final SqlBuilder sqlBuilder = new PostgreSqlBuilder(); + @Spy private final SqlBuilder sqlBuilder = new PostgreSqlBuilder(); - private JdbcAnalyticsManager analyticsManager; + @InjectMocks private JdbcAnalyticsManager analyticsManager; private static Stream data() { return Stream.of( @@ -92,12 +93,6 @@ private static Stream data() { arguments("2017Nov", 26.5D)); } - @BeforeEach - void before() { - analyticsManager = - new JdbcAnalyticsManager(queryPlanner, jdbcTemplate, executionPlanStore, sqlBuilder); - } - @ParameterizedTest @MethodSource("data") public void testWeightedAverage(String financialYear, Double weightedAverage) { diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/AnalyticsServiceReportingRateTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/AnalyticsServiceReportingRateTest.java index 12f852c8e464..975fd51f7844 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/AnalyticsServiceReportingRateTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/AnalyticsServiceReportingRateTest.java @@ -102,7 +102,7 @@ void verifyReportingRatesValueWhenPeriodIsFilter() { DataQueryParams.newBuilder() .withOrganisationUnit(ou) // DATA ELEMENTS - .withDataElements(newArrayList(reportingRateA, reportingRateB, reportingRateC)) + .withDataElements(List.of(reportingRateA, reportingRateB, reportingRateC)) .withIgnoreLimit(true) // FILTERS (OU) .withFilters(List.of(new BaseDimensionalObject("pe", DimensionType.PERIOD, periods))) diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/DataQueryServiceDimensionItemKeywordTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/DataQueryServiceDimensionItemKeywordTest.java index e0ab2257f7c1..622f2b45975f 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/DataQueryServiceDimensionItemKeywordTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/DataQueryServiceDimensionItemKeywordTest.java @@ -78,6 +78,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; @@ -103,14 +104,6 @@ class DataQueryServiceDimensionItemKeywordTest { OrganisationUnit.class, Set.of("geometry", "parent", "groups", "children"))); - private DimensionalObjectProducer dimensionalObjectProducer; - - private RequestBuilder rb; - - private OrganisationUnit rootOu; - - private DefaultDataQueryService target; - @Mock private IdentifiableObjectManager idObjectManager; @Mock private OrganisationUnitService organisationUnitService; @@ -129,18 +122,18 @@ class DataQueryServiceDimensionItemKeywordTest { @Mock private I18n i18n; + @InjectMocks private DimensionalObjectProvider dimensionalObjectProducer; + + private DefaultDataQueryService target; + + private RequestBuilder rb; + + private OrganisationUnit rootOu; + @BeforeEach public void setUp() { lenient().when(settingsService.getCurrentSettings()).thenReturn(SystemSettings.of(Map.of())); - dimensionalObjectProducer = - new DimensionalObjectProducer( - idObjectManager, - organisationUnitService, - settingsService, - i18nManager, - dimensionService, - aclService); target = new DefaultDataQueryService(dimensionalObjectProducer, idObjectManager, securityManager); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/DataQueryServiceTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/DataQueryServiceTest.java new file mode 100644 index 000000000000..11e9e08e6ee8 --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/DataQueryServiceTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2004-2024, 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.data; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hisp.dhis.common.UserOrgUnitType.DATA_OUTPUT; +import static org.hisp.dhis.test.TestBase.createOrganisationUnit; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Set; +import org.hisp.dhis.analytics.AnalyticsSecurityManager; +import org.hisp.dhis.analytics.DataQueryParams; +import org.hisp.dhis.common.BaseIdentifiableObject; +import org.hisp.dhis.common.IdentifiableObjectManager; +import org.hisp.dhis.organisationunit.OrganisationUnit; +import org.hisp.dhis.user.User; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class DataQueryServiceTest { + + @Mock private DimensionalObjectProvider dimensionalObjectProducer; + + @Mock private IdentifiableObjectManager idObjectManager; + + @Mock private AnalyticsSecurityManager analyticsSecurityManager; + + @InjectMocks private DefaultDataQueryService dataQueryService; + + @Test + void testGetUserOrgUnitsWithExplicitlyDefinedAnalyticsOrganisationUnits() { + OrganisationUnit ouB = createOrganisationUnit('B'); + OrganisationUnit ouC = createOrganisationUnit('C'); + OrganisationUnit ouD = createOrganisationUnit('D'); + DataQueryParams dataQueryParams = + DataQueryParams.newBuilder().withUserOrgUnitType(DATA_OUTPUT).build(); + User currentUser = mock(User.class); + when(currentUser.getDataViewOrganisationUnits()).thenReturn(Set.of(ouB, ouC, ouD)); + when(analyticsSecurityManager.getCurrentUser(dataQueryParams)).thenReturn(currentUser); + + List userOrgUnits = dataQueryService.getUserOrgUnits(dataQueryParams, null); + + assertEquals(3, userOrgUnits.size()); + assertThat( + userOrgUnits.stream().map(BaseIdentifiableObject::getName).toList(), + containsInAnyOrder("OrganisationUnitB", "OrganisationUnitC", "OrganisationUnitD")); + } + + @Test + void testGetUserOrgUnitsWithNoAnalyticsOrganisationUnitsDefined() { + OrganisationUnit ouA = createOrganisationUnit('A'); + DataQueryParams dataQueryParams = + DataQueryParams.newBuilder().withUserOrgUnitType(DATA_OUTPUT).build(); + User currentUser = mock(User.class); + when(currentUser.getOrganisationUnits()).thenReturn(Set.of(ouA)); + when(currentUser.getDataViewOrganisationUnits()).thenReturn(Set.of()); + when(analyticsSecurityManager.getCurrentUser(dataQueryParams)).thenReturn(currentUser); + + List userOrgUnits = dataQueryService.getUserOrgUnits(dataQueryParams, null); + + assertEquals(1, userOrgUnits.size()); + assertThat( + userOrgUnits.stream().map(BaseIdentifiableObject::getName).toList(), + containsInAnyOrder("OrganisationUnitA")); + } + + @Test + void testGetUserOrgUnitsWithNoneOrganisationUnitDefined() { + DataQueryParams dataQueryParams = + DataQueryParams.newBuilder().withUserOrgUnitType(DATA_OUTPUT).build(); + + List userOrgUnits = dataQueryService.getUserOrgUnits(dataQueryParams, null); + + assertEquals(0, userOrgUnits.size()); + } +} diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/DefaultAnalyticsServiceTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/DefaultAnalyticsServiceTest.java new file mode 100644 index 000000000000..a1c3aa369c99 --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/DefaultAnalyticsServiceTest.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2004-2024, 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.data; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.hisp.dhis.common.BaseDimensionalItemObject; +import org.hisp.dhis.common.DimensionalItemObject; +import org.hisp.dhis.common.Grid; +import org.hisp.dhis.common.GridHeader; +import org.hisp.dhis.system.grid.ListGrid; +import org.junit.jupiter.api.Test; + +class DefaultAnalyticsServiceTest { + + @Test + void testGetGridItems() { + Grid grid = new ListGrid(); + + List.of( + new GridHeader("R-A", false, false), + new GridHeader("R-B", false, false), + new GridHeader("R-C", false, false), + new GridHeader("C-A", false, false), + new GridHeader("C-B", false, false)) + .forEach(grid::addHeader); + + List.of( + List.of("R-A1", "R-B1", "R-C2", "C-A1", "C-B1", "v1"), + List.of("R-A1", "R-B2", "R-C1", "C-A1", "C-B3", "v2"), + List.of("R-A2", "R-B1", "R-C1", "C-A2", "C-B1", "v3")) + .forEach( + row -> { + grid.addRow(); + row.forEach(grid::addValue); + }); + + Map> dimensionItemsByDimension = + Map.of( + "R-A", + List.of( + new BaseDimensionalItemObject("R-A1"), + new BaseDimensionalItemObject("R-A2"), + new BaseDimensionalItemObject("R-A3")), + "R-B", + List.of( + new BaseDimensionalItemObject("R-B1"), + new BaseDimensionalItemObject("R-B2"), + new BaseDimensionalItemObject("R-B3")), + "R-C", + List.of( + new BaseDimensionalItemObject("R-C1"), + new BaseDimensionalItemObject("R-C2"), + new BaseDimensionalItemObject("R-C3")), + "C-A", + List.of( + new BaseDimensionalItemObject("C-A1"), + new BaseDimensionalItemObject("C-A2"), + new BaseDimensionalItemObject("C-A3")), + "C-B", + List.of( + new BaseDimensionalItemObject("C-B1"), + new BaseDimensionalItemObject("C-B2"), + new BaseDimensionalItemObject("C-B3"))); + + List dimensionIds = List.of("R-A", "R-B", "R-C", "C-A", "C-B"); + + List> gridItems = + DefaultAnalyticsService.getGridItems(grid, dimensionItemsByDimension, dimensionIds); + + assertEquals(grid.getRows().size(), gridItems.size()); + + for (int i = 0; i < grid.getRows().size(); i++) { + assertEquals(grid.getWidth() - 1, gridItems.get(i).size()); + } + + Set> rowsFromGrid = getRowsFromGrid(grid); + Set> rowsFromGridItems = getRowsFromGridItems(gridItems); + + assertEquals(rowsFromGrid, rowsFromGridItems); + } + + private Set> getRowsFromGrid(Grid grid) { + Set> rowsFromGrid = new HashSet<>(); + for (List gridRow : grid.getRows()) { + List gridRowAsString = new ArrayList<>(); + for (int j = 0; j < gridRow.size() - 1; j++) { + gridRowAsString.add(gridRow.get(j).toString()); + } + rowsFromGrid.add(gridRowAsString); + } + return rowsFromGrid; + } + + private Set> getRowsFromGridItems(List> gridItems) { + Set> rowsFromGridItems = new HashSet<>(); + for (List gridItem : gridItems) { + List gridItemAsString = new ArrayList<>(); + for (DimensionalItemObject dimensionalItemObject : gridItem) { + gridItemAsString.add(dimensionalItemObject.getDimensionItem()); + } + rowsFromGridItems.add(gridItemAsString); + } + return rowsFromGridItems; + } +} diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/DimensionalObjectProducerTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/DimensionalObjectProviderTest.java similarity index 98% rename from dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/DimensionalObjectProducerTest.java rename to dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/DimensionalObjectProviderTest.java index cabae51fb2e9..d0277d6514a5 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/DimensionalObjectProducerTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/DimensionalObjectProviderTest.java @@ -105,17 +105,17 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; /** - * Unit tests for {@link DimensionalObjectProducer}. + * Unit tests for {@link DimensionalObjectProvider}. * * @author maikel arabori */ @ExtendWith(MockitoExtension.class) -class DimensionalObjectProducerTest { - private DimensionalObjectProducer target; +class DimensionalObjectProviderTest { @Mock private IdentifiableObjectManager idObjectManager; @@ -124,6 +124,7 @@ class DimensionalObjectProducerTest { @Mock private DimensionService dimensionService; @Mock private SystemSettingsProvider settingsProvider; + @Mock private SystemSettings settings; @Mock private AclService aclService; @@ -134,17 +135,11 @@ class DimensionalObjectProducerTest { @Mock private I18nFormat i18nFormat; + @InjectMocks private DimensionalObjectProvider target; + @BeforeEach public void setUp() { lenient().when(settingsProvider.getCurrentSettings()).thenReturn(settings); - target = - new DimensionalObjectProducer( - idObjectManager, - organisationUnitService, - settingsProvider, - i18nManager, - dimensionService, - aclService); } @Test diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/JdbcAnalyticsManagerTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/JdbcAnalyticsManagerTest.java index 75893f264588..37af80ed2508 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/JdbcAnalyticsManagerTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/JdbcAnalyticsManagerTest.java @@ -57,12 +57,13 @@ import org.hisp.dhis.period.Period; import org.hisp.dhis.period.PeriodType; import org.hisp.dhis.setting.SystemSettingsService; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; +import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.rowset.SqlRowSet; @@ -82,20 +83,15 @@ class JdbcAnalyticsManagerTest { @Mock private NestedIndicatorCyclicDependencyInspector nestedIndicatorCyclicDependencyInspector; - private final SqlBuilder sqlBuilder = new PostgreSqlBuilder(); + @Mock private QueryPlanner queryPlanner; - @Captor private ArgumentCaptor sql; + @Spy private SqlBuilder sqlBuilder = new PostgreSqlBuilder(); - private JdbcAnalyticsManager subject; + @Captor private ArgumentCaptor sql; @Mock private ExecutionPlanStore executionPlanStore; - @BeforeEach - public void setUp() { - QueryPlanner queryPlanner = new DefaultQueryPlanner(partitionManager); - - subject = new JdbcAnalyticsManager(queryPlanner, jdbcTemplate, executionPlanStore, sqlBuilder); - } + @InjectMocks private JdbcAnalyticsManager subject; @Test void verifyQueryGeneratedWhenDataElementHasLastAggregationType() { diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/JdbcSubexpressionQueryGeneratorTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/JdbcSubexpressionQueryGeneratorTest.java index 2c954f291ab1..f7f4c363b727 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/JdbcSubexpressionQueryGeneratorTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/JdbcSubexpressionQueryGeneratorTest.java @@ -60,11 +60,12 @@ import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.period.Period; import org.hisp.dhis.subexpression.SubexpressionDimensionItem; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.jdbc.core.JdbcTemplate; @@ -80,20 +81,15 @@ class JdbcSubexpressionQueryGeneratorTest { @Mock private ExecutionPlanStore executionPlanStore; - private final SqlBuilder sqlBuilder = new PostgreSqlBuilder(); + @Mock private QueryPlanner queryPlanner; - private JdbcAnalyticsManager jam; + @Spy private SqlBuilder sqlBuilder = new PostgreSqlBuilder(); + + @InjectMocks private JdbcAnalyticsManager manager; /** Matches a UID with an initial single quote. */ private static final Pattern QUOTED_UID = Pattern.compile("'\\w{11}'"); - @BeforeAll - public void setUp() { - QueryPlanner queryPlanner = new DefaultQueryPlanner(partitionManager); - - jam = new JdbcAnalyticsManager(queryPlanner, jdbcTemplate, executionPlanStore, sqlBuilder); - } - @Test void testGetSql() { OrganisationUnit ouA = createOrganisationUnit('A'); @@ -147,7 +143,7 @@ ORGUNIT_DIM_ID, DimensionType.ORGANISATION_UNIT, getList(ouA))) .build(); JdbcSubexpressionQueryGenerator target = - new JdbcSubexpressionQueryGenerator(jam, params, DATA_VALUE); + new JdbcSubexpressionQueryGenerator(manager, params, DATA_VALUE); String expected = "select ax.\"pe\",'subexprxUID' as \"dx\"," diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/OrgUnitTableJoinerTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/OrgUnitTableJoinerTest.java index f9053ebcac55..0ad45adc592f 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/OrgUnitTableJoinerTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/OrgUnitTableJoinerTest.java @@ -55,29 +55,29 @@ * @author Jim Grace */ class OrgUnitTableJoinerTest extends TestBase { - private static final OrgUnitField DEFAULT = new OrgUnitField(null); + private final OrgUnitField DEFAULT = new OrgUnitField(null); - private static final OrgUnitField ATTRIBUTE = new OrgUnitField("AttributeId"); + private final OrgUnitField ATTRIBUTE = new OrgUnitField("AttributeId"); - private static final OrgUnitField OWNER_AT_START = new OrgUnitField("OWNER_AT_START"); + private final OrgUnitField OWNER_AT_START = new OrgUnitField("OWNER_AT_START"); - private static final OrgUnitField OWNER_AT_END = new OrgUnitField("OWNER_AT_END"); + private final OrgUnitField OWNER_AT_END = new OrgUnitField("OWNER_AT_END"); - private static final Program programA = createProgram('A'); + private final Program programA = createProgram('A'); - private static final Period periodDaily = PeriodType.getPeriodFromIsoString("20230101"); + private final Period periodDaily = PeriodType.getPeriodFromIsoString("20230101"); - private static final Period periodMonthly = PeriodType.getPeriodFromIsoString("202201"); + private final Period periodMonthly = PeriodType.getPeriodFromIsoString("202201"); - private static final Period periodQuarterly = PeriodType.getPeriodFromIsoString("2022Q1"); + private final Period periodQuarterly = PeriodType.getPeriodFromIsoString("2022Q1"); - private static final Date dateA = new GregorianCalendar(2022, JANUARY, 1).getTime(); + private final Date dateA = new GregorianCalendar(2022, JANUARY, 1).getTime(); - private static final Date dateB = new GregorianCalendar(2023, JANUARY, 1).getTime(); + private final Date dateB = new GregorianCalendar(2023, JANUARY, 1).getTime(); - private static final DimensionalItemObject ouA = createOrganisationUnit('A'); + private final DimensionalItemObject ouA = createOrganisationUnit('A'); - private static final DimensionalObject ouGroupSetA = + private final DimensionalObject ouGroupSetA = new BaseDimensionalObject( "OrgUnitGrSe", DimensionType.ORGANISATION_UNIT_GROUP_SET, emptyList()); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/QueryItemHelperTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/QueryItemHelperTest.java index 1ff002351e6a..370a5198df97 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/QueryItemHelperTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/QueryItemHelperTest.java @@ -60,21 +60,21 @@ * @author Dusan Bernat */ class QueryItemHelperTest extends TestBase { - private String UID_A = CodeGenerator.generateUid(); + private final String UID_A = CodeGenerator.generateUid(); - private String UID_B = CodeGenerator.generateUid(); + private final String UID_B = CodeGenerator.generateUid(); - private String OPTION_NAME_A = "OptionA"; + private final String OPTION_NAME_A = "OptionA"; - private String OPTION_NAME_B = "OptionB"; + private final String OPTION_NAME_B = "OptionB"; - private String LEGEND_NAME_A = "LegendA"; + private final String LEGEND_NAME_A = "LegendA"; - private String LEGEND_NAME_B = "LegendB"; + private final String LEGEND_NAME_B = "LegendB"; - private String LEGEND_CODE_A = "LegendCodeA"; + private final String LEGEND_CODE_A = "LegendCodeA"; - private String LEGEND_CODE_B = "LegendCodeB"; + private final String LEGEND_CODE_B = "LegendCodeB"; @Test void testGeItemOptionValueWithIdSchemeNAME() { diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/QueryPlannerGroupByAggregationTypeTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/QueryPlannerGroupByAggregationTypeTest.java index 5b1731e89464..3cc54c5d8a81 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/QueryPlannerGroupByAggregationTypeTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/QueryPlannerGroupByAggregationTypeTest.java @@ -49,7 +49,6 @@ import org.hisp.dhis.analytics.DataQueryGroups; import org.hisp.dhis.analytics.DataQueryParams; import org.hisp.dhis.analytics.DataType; -import org.hisp.dhis.analytics.QueryPlanner; import org.hisp.dhis.analytics.QueryPlannerParams; import org.hisp.dhis.analytics.partition.PartitionManager; import org.hisp.dhis.category.CategoryCombo; @@ -61,9 +60,9 @@ import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.period.MonthlyPeriodType; import org.joda.time.DateTime; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -72,14 +71,9 @@ */ @ExtendWith(MockitoExtension.class) class QueryPlannerGroupByAggregationTypeTest { - private QueryPlanner subject; - @Mock private PartitionManager partitionManager; - @BeforeEach - public void setUp() { - subject = new DefaultQueryPlanner(partitionManager); - } + @InjectMocks private DefaultQueryPlanner subject; @Test void verifyMultipleDataElementIsAggregatedWithTwoQueryGroupWhenDataTypeIsDifferent() { diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/SubexpressionPeriodOffsetUtilsTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/SubexpressionPeriodOffsetUtilsTest.java index 5ba3757e7a35..10d80dcab6ee 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/SubexpressionPeriodOffsetUtilsTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/SubexpressionPeriodOffsetUtilsTest.java @@ -55,36 +55,36 @@ */ @TestInstance(TestInstance.Lifecycle.PER_CLASS) class SubexpressionPeriodOffsetUtilsTest { - private DataElement de0 = createDataElement('A'); + private final DataElement de0 = createDataElement('A'); - private DataElement dem1 = createDataElementAWithPeriodOffset(-1); + private final DataElement dem1 = createDataElementAWithPeriodOffset(-1); - private DataElement dem2 = createDataElementAWithPeriodOffset(-2); + private final DataElement dem2 = createDataElementAWithPeriodOffset(-2); - private DataElement dep1 = createDataElementAWithPeriodOffset(1); + private final DataElement dep1 = createDataElementAWithPeriodOffset(1); - private String deAUid = de0.getUid(); + private final String deAUid = de0.getUid(); - private String expression = + private final String expression = format( "subExpression( #{%s} + #{%s}.periodOffset(-1) + #{%s}.periodOffset(-2) + #{%s}.periodOffset(1) )", deAUid, deAUid, deAUid, deAUid); - private List items = List.of(de0, dem1, dem2, dep1); + private final List items = List.of(de0, dem1, dem2, dep1); - private SubexpressionDimensionItem subex = + private final SubexpressionDimensionItem subExpr = new SubexpressionDimensionItem(expression, items, null); - private Period periodA = createPeriod("202309"); + private final Period periodA = createPeriod("202309"); - private Period periodB = createPeriod("202310"); + private final Period periodB = createPeriod("202310"); - private DataQueryParams params = + private final DataQueryParams params = DataQueryParams.newBuilder() .withPeriodType("monthly") .withPeriods(List.of(periodA, periodB)) .addDimension( - new BaseDimensionalObject(DATA_X_DIM_ID, DimensionType.DATA_X, getList(subex))) + new BaseDimensionalObject(DATA_X_DIM_ID, DimensionType.DATA_X, getList(subExpr))) .build(); @Test @@ -120,7 +120,7 @@ void testGetParamsWithDataPeriods() { getPeriodList("202307", "202308", "202309", "202310", "202311"); assertContainsOnly(expectedPeriods, result.getPeriods()); - List expectedData = List.of(subex); + List expectedData = List.of(subExpr); assertContainsOnly(expectedData, result.getAllDataDimensionItems()); } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/handler/DataHandlerTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/handler/DataHandlerTest.java new file mode 100644 index 000000000000..79766d139804 --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/handler/DataHandlerTest.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2004-2024, 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.data.handler; + +import static org.hisp.dhis.analytics.DataQueryParams.newBuilder; +import static org.hisp.dhis.common.DimensionType.DATA_X; +import static org.hisp.dhis.common.DimensionalObject.DATA_X_DIM_ID; +import static org.hisp.dhis.dataelement.DataElementOperand.TotalType.AOC_ONLY; +import static org.hisp.dhis.dataelement.DataElementOperand.TotalType.COC_ONLY; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import org.hisp.dhis.analytics.DataQueryParams; +import org.hisp.dhis.category.CategoryOptionCombo; +import org.hisp.dhis.common.BaseDimensionalObject; +import org.hisp.dhis.dataelement.DataElement; +import org.hisp.dhis.dataelement.DataElementOperand; +import org.hisp.dhis.dataelement.DataElementOperand.TotalType; +import org.junit.jupiter.api.Test; + +class DataHandlerTest { + + @Test + void testOperandDataQueryParamsOnlyOperands() { + DataElement dataElement = new DataElement("NameA"); + dataElement.setUid("uid1234567A"); + dataElement.setCode("CodeA"); + + DataElementOperand dataElementOperand = new DataElementOperand(dataElement, null); + + DataQueryParams stubParams = + newBuilder() + .addDimension( + new BaseDimensionalObject(DATA_X_DIM_ID, DATA_X, List.of(dataElementOperand))) + .build(); + + TotalType anyTotalType = COC_ONLY; + + DataHandler dataHandler = withNullDependencies(); + DataQueryParams params = + dataHandler.getOperandDataQueryParams( + stubParams, List.of(dataElementOperand), anyTotalType); + + assertEquals(1, params.getDimensions().size()); + } + + @Test + void testOperandDataQueryParamsWithCatOptionCombo() { + DataElement dataElement = new DataElement("NameA"); + dataElement.setUid("uid1234567A"); + dataElement.setCode("CodeA"); + + CategoryOptionCombo categoryOptionCombo = new CategoryOptionCombo(); + categoryOptionCombo.setName("NameC"); + categoryOptionCombo.setUid("uid1234567C"); + categoryOptionCombo.setCode("CodeC"); + + DataElementOperand dataElementOperand = + new DataElementOperand(dataElement, categoryOptionCombo); + + DataQueryParams stubParams = + newBuilder() + .addDimension( + new BaseDimensionalObject(DATA_X_DIM_ID, DATA_X, List.of(dataElementOperand))) + .build(); + + DataHandler dataHandler = withNullDependencies(); + DataQueryParams params = + dataHandler.getOperandDataQueryParams(stubParams, List.of(dataElementOperand), COC_ONLY); + + assertEquals(2, params.getDimensions().size()); + } + + @Test + void testOperandDataQueryParamsWithCatOptionComboInFilter() { + DataElement dataElement = new DataElement("NameA"); + dataElement.setUid("uid1234567A"); + dataElement.setCode("CodeA"); + + CategoryOptionCombo categoryOptionCombo = new CategoryOptionCombo(); + categoryOptionCombo.setName("NameC"); + categoryOptionCombo.setUid("uid1234567C"); + categoryOptionCombo.setCode("CodeC"); + + DataElementOperand dataElementOperand = + new DataElementOperand(dataElement, categoryOptionCombo); + + DataQueryParams stubParams = + newBuilder() + .addFilter( + new BaseDimensionalObject(DATA_X_DIM_ID, DATA_X, List.of(dataElementOperand))) + .build(); + + DataHandler dataHandler = withNullDependencies(); + DataQueryParams params = + dataHandler.getOperandDataQueryParams(stubParams, List.of(dataElementOperand), COC_ONLY); + + assertEquals(0, params.getDimensions().size()); + assertEquals(3, params.getFilters().size()); + } + + @Test + void testOperandDataQueryParamsWithAttrOptionCombo() { + DataElement dataElement = new DataElement("NameA"); + dataElement.setUid("uid1234567A"); + dataElement.setCode("CodeA"); + + CategoryOptionCombo attributeOptionCombo = new CategoryOptionCombo(); + attributeOptionCombo.setName("NameAt"); + attributeOptionCombo.setUid("uid1234567C"); + attributeOptionCombo.setCode("CodeAt"); + + DataElementOperand dataElementOperand = + new DataElementOperand(dataElement, null, attributeOptionCombo); + + DataQueryParams stubParams = + newBuilder() + .addDimension( + new BaseDimensionalObject(DATA_X_DIM_ID, DATA_X, List.of(dataElementOperand))) + .build(); + + DataHandler dataHandler = withNullDependencies(); + DataQueryParams params = + dataHandler.getOperandDataQueryParams(stubParams, List.of(dataElementOperand), AOC_ONLY); + + assertEquals(2, params.getDimensions().size()); + } + + @Test + void testOperandDataQueryParamsWithAttrOptionComboInFilter() { + DataElement dataElement = new DataElement("NameA"); + dataElement.setUid("uid1234567A"); + dataElement.setCode("CodeA"); + + CategoryOptionCombo attributeOptionCombo = new CategoryOptionCombo(); + attributeOptionCombo.setName("NameAt"); + attributeOptionCombo.setUid("uid1234567C"); + attributeOptionCombo.setCode("CodeAt"); + + DataElementOperand dataElementOperand = + new DataElementOperand(dataElement, null, attributeOptionCombo); + + DataQueryParams stubParams = + newBuilder() + .addFilter( + new BaseDimensionalObject(DATA_X_DIM_ID, DATA_X, List.of(dataElementOperand))) + .build(); + + DataHandler dataHandler = withNullDependencies(); + DataQueryParams params = + dataHandler.getOperandDataQueryParams(stubParams, List.of(dataElementOperand), AOC_ONLY); + + assertEquals(0, params.getDimensions().size()); + assertEquals(3, params.getFilters().size()); + } + + private DataHandler withNullDependencies() { + return new DataHandler(null, null, null, null, null, null, null, null, null); + } +} diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/handler/SchemeIdResponseMapperTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/handler/SchemeIdResponseMapperTest.java index 2f2d8008920c..edd44fbee2e6 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/handler/SchemeIdResponseMapperTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/handler/SchemeIdResponseMapperTest.java @@ -69,7 +69,6 @@ import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramIndicator; import org.hisp.dhis.system.grid.ListGrid; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; @@ -79,12 +78,7 @@ */ @ExtendWith(MockitoExtension.class) class SchemeIdResponseMapperTest { - private SchemeIdResponseMapper schemeIdResponseMapper; - - @BeforeEach - public void setUp() { - schemeIdResponseMapper = new SchemeIdResponseMapper(); - } + private final SchemeIdResponseMapper schemeIdResponseMapper = new SchemeIdResponseMapper(); @Test void testGetSchemeIdResponseMapWhenOutputIdSchemeIsSetToName() { diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/AbstractJdbcEventAnalyticsManagerTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/AbstractJdbcEventAnalyticsManagerTest.java index 5ae3c01b65d9..cfa4d4c09173 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/AbstractJdbcEventAnalyticsManagerTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/AbstractJdbcEventAnalyticsManagerTest.java @@ -123,6 +123,7 @@ class AbstractJdbcEventAnalyticsManagerTest extends EventAnalyticsTest { @Mock private ProgramIndicatorService programIndicatorService; @Mock private ExecutionPlanStore executionPlanStore; + @Mock private OrganisationUnitService organisationUnitService; private final SqlBuilder sqlBuilder = new PostgreSqlBuilder(); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/ColumnWithNullIfAndAliasTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/ColumnWithNullIfAndAliasTest.java index 3f806ce42c14..6b0d7032775c 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/ColumnWithNullIfAndAliasTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/ColumnWithNullIfAndAliasTest.java @@ -38,12 +38,9 @@ class ColumnWithNullIfAndAliasTest { @Test void testAsSqlReturnsRightInstance() { - // given - // when ColumnAndAlias columnAndAlias = ColumnWithNullIfAndAlias.ofColumnWithNullIfAndAlias(COLUMN, ALIAS); - // then assertEquals(COLUMN, columnAndAlias.getColumn()); assertEquals(ALIAS, columnAndAlias.getAlias()); @@ -51,12 +48,9 @@ void testAsSqlReturnsRightInstance() { @Test void testAsSqlReturnsRightSqlSnippetWhenCalled() { - // given - // when ColumnAndAlias columnAndAlias = ColumnWithNullIfAndAlias.ofColumnWithNullIfAndAlias(COLUMN, ALIAS); - // then assertEquals("nullif(" + COLUMN + ",'') as \"" + ALIAS + "\"", columnAndAlias.asSql()); } } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/DefaultEventCoordinateServiceTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/DefaultEventCoordinateServiceTest.java index fb665fca2f99..d7bc5b74197e 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/DefaultEventCoordinateServiceTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/DefaultEventCoordinateServiceTest.java @@ -45,6 +45,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -61,14 +62,13 @@ class DefaultEventCoordinateServiceTest { @Mock private TrackedEntityAttributeService attributeService; + @InjectMocks private DefaultEventCoordinateService service; + @ParameterizedTest @ValueSource(strings = {"eventgeometry", "enrollmentgeometry", "ougeometry"}) void testGetCoordinateFieldOrFail(String geometry) { when(programService.getProgram(any(String.class))).thenReturn(createProgram('A')); - EventCoordinateService service = - new DefaultEventCoordinateService(programService, dataElementService, attributeService); - assertEquals(geometry, service.getCoordinateField("A", geometry, ErrorCode.E7232)); } @@ -76,9 +76,6 @@ void testGetCoordinateFieldOrFail(String geometry) { void testGetFallbackCoordinateFieldsWithFallbackCoordinateFieldParam() { when(programService.getProgram(any(String.class))).thenReturn(createProgram('A')); - EventCoordinateService service = - new DefaultEventCoordinateService(programService, dataElementService, attributeService); - assertEquals(COL_NAME_GEOMETRY_LIST, service.getFallbackCoordinateFields("A", null, true)); } @@ -87,27 +84,18 @@ void testGetFallbackCoordinateFieldsWithFallbackCoordinateFieldParam() { void testGetFallbackCoordinateFieldsWithoutFallbackCoordinateFieldParam(String geometry) { when(programService.getProgram(any(String.class))).thenReturn(createProgram('A')); - EventCoordinateService service = - new DefaultEventCoordinateService(programService, dataElementService, attributeService); - assertEquals(List.of(geometry), service.getFallbackCoordinateFields("A", geometry, true)); } @ParameterizedTest @ValueSource(strings = {"enrollmentgeometry", "eventgeometry", "tegeometry", "ougeometry"}) void testVerifyFallbackCoordinateFieldWithRegistrationProgram(String geometry) { - EventCoordinateService service = - new DefaultEventCoordinateService(programService, dataElementService, attributeService); - assertTrue(service.isFallbackCoordinateFieldValid(true, geometry)); } @ParameterizedTest @ValueSource(strings = {"enrollmentgeometry", "eventgeometry", "ougeometry"}) void testVerifyFallbackCoordinateFieldWithoutRegistrationProgram(String geometry) { - EventCoordinateService service = - new DefaultEventCoordinateService(programService, dataElementService, attributeService); - assertTrue(service.isFallbackCoordinateFieldValid(false, geometry)); } @@ -116,12 +104,8 @@ void testVerifyFallbackCoordinateFieldWithoutRegistrationProgram(String geometry strings = {"badeventgeometry", "badenrollmentgeometry", "badtegeometry", "badougeometry"}) void testVerifyBadFallbackCoordinateField(String geometry) { when(dataElementService.getDataElement(any(String.class))).thenReturn(null); - when(attributeService.getTrackedEntityAttribute(any(String.class))).thenReturn(null); - EventCoordinateService service = - new DefaultEventCoordinateService(programService, dataElementService, attributeService); - assertThrows( IllegalQueryException.class, () -> service.isFallbackCoordinateFieldValid(false, geometry)); } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/EnrollmentAggregateServiceTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/EnrollmentAggregateServiceTest.java index 6987fa94483c..dec8679348ac 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/EnrollmentAggregateServiceTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/EnrollmentAggregateServiceTest.java @@ -69,9 +69,9 @@ import org.hisp.dhis.program.ProgramStage; import org.hisp.dhis.user.SystemUser; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.opengis.geometry.primitive.Point; @@ -84,8 +84,6 @@ @ExtendWith(MockitoExtension.class) class EnrollmentAggregateServiceTest { - private EnrollmentAggregateService dummyAnalyticsService; - @Mock private AnalyticsSecurityManager securityManager; @Mock private EnrollmentAnalyticsManager enrollmentAnalyticsManager; @@ -98,23 +96,13 @@ class EnrollmentAggregateServiceTest { @Mock private SchemeIdHandler schemeIdHandler; + @InjectMocks private EnrollmentAggregateService service; + @BeforeAll static void setup() { injectSecurityContextNoSettings(new SystemUser()); } - @BeforeEach - public void setUp() { - dummyAnalyticsService = - new EnrollmentAggregateService( - enrollmentAnalyticsManager, - queryPlanner, - securityManager, - queryValidator, - metadataHandler, - schemeIdHandler); - } - @Test void verifyHeaderCreationBasedOnQueryItemsAndDimensions() { // Given @@ -146,7 +134,7 @@ void verifyHeaderCreationBasedOnQueryItemsAndDimensions() { // When when(securityManager.withUserConstraints(any(EventQueryParams.class))).thenReturn(params); - Grid grid = dummyAnalyticsService.getEnrollments(params); + Grid grid = service.getEnrollments(params); // Then List headers = grid.getHeaders(); @@ -215,7 +203,7 @@ void verifyHeaderCreationBasedOnQueryItemsAndDimensionsWithSameNamesMultiStage() // When when(securityManager.withUserConstraints(any(EventQueryParams.class))).thenReturn(params); - Grid grid = dummyAnalyticsService.getEnrollments(params); + Grid grid = service.getEnrollments(params); // Then List headers = grid.getHeaders(); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/EnrollmentQueryHelperTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/EnrollmentQueryHelperTest.java new file mode 100644 index 000000000000..2b11e9b0cc5c --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/EnrollmentQueryHelperTest.java @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2004-2024, 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.common.DimensionType.ORGANISATION_UNIT; +import static org.hisp.dhis.common.DimensionType.PERIOD; +import static org.hisp.dhis.common.DimensionalObject.ORGUNIT_DIM_ID; +import static org.hisp.dhis.common.DimensionalObject.PERIOD_DIM_ID; +import static org.hisp.dhis.common.OrganisationUnitSelectionMode.CAPTURE; +import static org.hisp.dhis.common.OrganisationUnitSelectionMode.CHILDREN; +import static org.hisp.dhis.common.OrganisationUnitSelectionMode.SELECTED; +import static org.hisp.dhis.period.RelativePeriodEnum.LAST_3_DAYS; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.Set; +import org.hisp.dhis.analytics.event.EventQueryParams; +import org.hisp.dhis.common.BaseDimensionalObject; +import org.hisp.dhis.common.GridHeader; +import org.hisp.dhis.organisationunit.OrganisationUnit; +import org.hisp.dhis.period.Period; +import org.hisp.dhis.period.PeriodType; +import org.junit.jupiter.api.Test; + +/** Unit tests for {@link EnrollmentQueryHelper}. */ +class EnrollmentQueryHelperTest { + + @Test + void testGetHeaderColumnsSamePrefixedDimension() { + // Given + List headers = + List.of( + new GridHeader("name0"), + new GridHeader("name1"), + new GridHeader("dim.name2"), + new GridHeader("pe"), + new GridHeader("value"), + new GridHeader("ou")); + + String sql = "select name0, name1, dim.name2, pe, value, ou from table"; + + // When + Set headerColumns = EnrollmentQueryHelper.getHeaderColumns(headers, sql); + + // Then + String[] columns = headerColumns.toArray(String[]::new); + assertEquals(3, columns.length); + assertEquals("t1.\"name0\"", columns[0]); + assertEquals("t1.\"name1\"", columns[1]); + assertEquals("t1.\"dim.name2\"", columns[2]); + } + + @Test + void testGetHeaderColumnsDifferentPrefixedDimension() { + // Given + List headers = + List.of( + new GridHeader("name0"), + new GridHeader("name1"), + new GridHeader("dim.name2"), + new GridHeader("pe"), + new GridHeader("value"), + new GridHeader("ou")); + + String sql = "select name0, name1, ax.name2, pe, value, ou from table"; + + // When + Set headerColumns = EnrollmentQueryHelper.getHeaderColumns(headers, sql); + + // Then + String[] columns = headerColumns.toArray(String[]::new); + assertEquals(3, columns.length); + assertEquals("t1.\"name0\"", columns[0]); + assertEquals("t1.\"name1\"", columns[1]); + assertEquals("t1.\"name2\"", columns[2]); + } + + @Test + void testGetOrgUnitLevelColumnsOuMode() { + // Given + OrganisationUnit organisationUnit = new OrganisationUnit("OrgTest"); + organisationUnit.setPath("/Level1/OrgTest"); + + EventQueryParams params = + new EventQueryParams.Builder() + .withOrganisationUnitMode(CAPTURE) + .addDimension( + new BaseDimensionalObject( + ORGUNIT_DIM_ID, ORGANISATION_UNIT, List.of(organisationUnit))) + .build(); + + // When + Set orgUnitColumns = EnrollmentQueryHelper.getOrgUnitLevelColumns(params); + + // Then + String[] columns = orgUnitColumns.toArray(String[]::new); + assertEquals(1, columns.length); + assertEquals("uidlevel2", columns[0]); + } + + @Test + void testGetOrgUnitLevelColumnsOuModeSelected() { + // Given + OrganisationUnit organisationUnit = new OrganisationUnit("/Level1/Level2"); + + EventQueryParams params = + new EventQueryParams.Builder() + .withOrganisationUnitMode(SELECTED) + .addDimension( + new BaseDimensionalObject( + ORGUNIT_DIM_ID, ORGANISATION_UNIT, List.of(organisationUnit))) + .build(); + + // When + Set orgUnitColumns = EnrollmentQueryHelper.getOrgUnitLevelColumns(params); + + // Then + assertEquals(0, orgUnitColumns.size()); + } + + @Test + void testGetOrgUnitLevelColumnsOuModeChildren() { + // Given + OrganisationUnit organisationUnit = new OrganisationUnit("/Level1/Level2"); + + EventQueryParams params = + new EventQueryParams.Builder() + .withOrganisationUnitMode(CHILDREN) + .addDimension( + new BaseDimensionalObject( + ORGUNIT_DIM_ID, ORGANISATION_UNIT, List.of(organisationUnit))) + .build(); + + // When + Set orgUnitColumns = EnrollmentQueryHelper.getOrgUnitLevelColumns(params); + + // Then + assertEquals(0, orgUnitColumns.size()); + } + + @Test + void testGetPeriodColumns() { + // Given + Period period = new Period(LAST_3_DAYS); + period.setPeriodType(PeriodType.getPeriodTypeFromIsoString("201101")); + + EventQueryParams params = + new EventQueryParams.Builder() + .addDimension(new BaseDimensionalObject(PERIOD_DIM_ID, PERIOD, List.of(period))) + .build(); + + // When + Set periodColumns = EnrollmentQueryHelper.getPeriodColumns(params); + + // Then + String[] columns = periodColumns.toArray(String[]::new); + assertEquals(1, columns.length); + assertEquals("t1.Monthly", columns[0]); + } + + @Test + void testGetPeriodColumnsNoPeriods() { + // Given + EventQueryParams params = + new EventQueryParams.Builder() + .addDimension(new BaseDimensionalObject(ORGUNIT_DIM_ID, ORGANISATION_UNIT, List.of())) + .build(); + + // When + Set periodColumns = EnrollmentQueryHelper.getPeriodColumns(params); + + // Then + assertEquals(0, periodColumns.size()); + } +} diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/EventQueryServiceTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/EventQueryServiceTest.java index 0de1cb91b8cf..4d5b916e4475 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/EventQueryServiceTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/EventQueryServiceTest.java @@ -58,9 +58,9 @@ import org.hisp.dhis.system.database.DatabaseInfoProvider; import org.hisp.dhis.user.SystemUser; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -71,8 +71,6 @@ */ @ExtendWith(MockitoExtension.class) class EventQueryServiceTest { - @Mock private EventQueryService eventQueryService; - @Mock private EventQueryValidator queryValidator; @Mock private MetadataItemsHandler metadataHandler; @@ -91,24 +89,13 @@ class EventQueryServiceTest { @Mock private SchemeIdResponseMapper schemeIdResponseMapper; + @InjectMocks private EventQueryService eventQueryService; + @BeforeAll static void setup() { injectSecurityContextNoSettings(new SystemUser()); } - @BeforeEach - public void setUp() { - eventQueryService = - new EventQueryService( - securityManager, - queryValidator, - eventAnalyticsManager, - queryPlanner, - databaseInfoProvider, - metadataHandler, - schemeIdHandler); - } - @Test void testOutputSchemeWhenSchemeIsSet() { IdScheme codeScheme = IdScheme.CODE; diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/EventQueryValidatorTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/EventQueryValidatorTest.java index f2c27791b050..d67cb83988af 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/EventQueryValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/EventQueryValidatorTest.java @@ -89,6 +89,7 @@ class EventQueryValidatorTest extends TestBase { private OptionSet osA; @Mock private SystemSettingsProvider settingsProvider; + @Mock private SystemSettings settings; @Mock private QueryValidator queryValidator; diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/programindicator/ProgramIndicatorSubqueryBuilderTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/programindicator/ProgramIndicatorSubqueryBuilderTest.java index 3bfee00382e5..0b8057d8ec3c 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/programindicator/ProgramIndicatorSubqueryBuilderTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/programindicator/ProgramIndicatorSubqueryBuilderTest.java @@ -47,6 +47,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -61,22 +62,21 @@ class ProgramIndicatorSubqueryBuilderTest { private static final BeanRandomizer rnd = BeanRandomizer.create(); - @Mock private ProgramIndicatorService programIndicatorService; - private Program program; private Date startDate; private Date endDate; - private DefaultProgramIndicatorSubqueryBuilder subject; + @Mock private ProgramIndicatorService programIndicatorService; + + @InjectMocks private DefaultProgramIndicatorSubqueryBuilder subject; @BeforeEach public void setUp() { program = createProgram('A'); startDate = getDate(2018, 1, 1); endDate = getDate(2018, 6, 30); - subject = new DefaultProgramIndicatorSubqueryBuilder(programIndicatorService); } @Test diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/queryItem/QueryItemLocatorTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/queryItem/QueryItemLocatorTest.java index 512baf4b19ad..97f0a0837eaf 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/queryItem/QueryItemLocatorTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/event/data/queryItem/QueryItemLocatorTest.java @@ -53,7 +53,6 @@ import java.util.Set; import org.hisp.dhis.analytics.DataQueryService; import org.hisp.dhis.analytics.EventOutputType; -import org.hisp.dhis.analytics.event.QueryItemLocator; import org.hisp.dhis.analytics.event.data.DefaultQueryItemLocator; import org.hisp.dhis.common.BaseDimensionalObject; import org.hisp.dhis.common.CodeGenerator; @@ -78,6 +77,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -100,7 +100,7 @@ class QueryItemLocatorTest { @Mock private RelationshipTypeService relationshipTypeService; - private QueryItemLocator subject; + @InjectMocks private DefaultQueryItemLocator subject; private Program programA; @@ -114,16 +114,6 @@ public void setUp() { dimension = CodeGenerator.generateUid(); programStageUid = CodeGenerator.generateUid(); - - subject = - new DefaultQueryItemLocator( - programStageService, - dataElementService, - attributeService, - programIndicatorService, - legendSetService, - relationshipTypeService, - dataQueryService); } @Test diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/orgunit/data/JdbcOrgUnitAnalyticsManagerTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/orgunit/data/JdbcOrgUnitAnalyticsManagerTest.java index 7172035e84d5..27bdea994a78 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/orgunit/data/JdbcOrgUnitAnalyticsManagerTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/orgunit/data/JdbcOrgUnitAnalyticsManagerTest.java @@ -43,10 +43,11 @@ import org.hisp.dhis.db.sql.PostgreSqlBuilder; import org.hisp.dhis.db.sql.SqlBuilder; import org.hisp.dhis.organisationunit.OrganisationUnitGroupSet; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.rowset.SqlRowSet; @@ -65,15 +66,9 @@ class JdbcOrgUnitAnalyticsManagerTest { @Mock private TableInfoReader tableInfoReader; - private final SqlBuilder sqlBuilder = new PostgreSqlBuilder(); + @Spy private final SqlBuilder sqlBuilder = new PostgreSqlBuilder(); - private JdbcOrgUnitAnalyticsManager jdbcOrgUnitAnalyticsManager; - - @BeforeEach - public void beforeAll() { - jdbcOrgUnitAnalyticsManager = - new JdbcOrgUnitAnalyticsManager(tableInfoReader, sqlBuilder, jdbcTemplate); - } + @InjectMocks private JdbcOrgUnitAnalyticsManager manager; @Test void testGetOrgUnitDataWithSuccess() { @@ -97,7 +92,7 @@ void testGetOrgUnitDataWithSuccess() { "analytics_rs_organisationunitgroupsetstructure", Set.of("abc123", "abc456"))) .thenReturn(Set.of()); when(jdbcTemplate.queryForRowSet(anyString())).thenReturn(sqlRowSet); - Map data = jdbcOrgUnitAnalyticsManager.getOrgUnitData(params); + Map data = manager.getOrgUnitData(params); // Then // Based on the mocked sqlRowSet. @@ -128,15 +123,11 @@ void testGetOrgUnitDataWithInvalidOrgUnitSetDimension() { // Then assertThrows( - QueryRuntimeException.class, - () -> jdbcOrgUnitAnalyticsManager.getOrgUnitData(params), - E7302.getMessage()); + QueryRuntimeException.class, () -> manager.getOrgUnitData(params), E7302.getMessage()); } private void mockSqlRowSet() { - // Simulate 2 results. when(sqlRowSet.next()).thenReturn(true).thenReturn(true).thenReturn(false); - when(sqlRowSet.getString("orgunit")).thenReturn("OrgUnit"); when(sqlRowSet.getString("abc123")).thenReturn("Abc123"); when(sqlRowSet.getString("abc456")).thenReturn("Abc456"); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/outlier/data/OutlierQueryParserTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/outlier/data/OutlierQueryParserTest.java index e8fac6ff1c53..99118eb6e230 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/outlier/data/OutlierQueryParserTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/outlier/data/OutlierQueryParserTest.java @@ -41,7 +41,7 @@ import java.util.List; import java.util.Set; -import org.hisp.dhis.analytics.data.DimensionalObjectProducer; +import org.hisp.dhis.analytics.data.DimensionalObjectProvider; import org.hisp.dhis.common.BaseDimensionalObject; import org.hisp.dhis.common.DisplayProperty; import org.hisp.dhis.common.IdScheme; @@ -55,15 +55,19 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) class OutlierQueryParserTest { @Mock private IdentifiableObjectManager idObjectManager; - @Mock private DimensionalObjectProducer dimensionalObjectProducer; + + @Mock private DimensionalObjectProvider dimensionalObjectProducer; + @Mock private UserService userService; - private OutlierQueryParser subject; + + @InjectMocks private OutlierQueryParser subject; @BeforeEach void setup() { @@ -88,8 +92,6 @@ void setup() { user.setDataViewOrganisationUnits(Set.of(organisationUnit)); injectSecurityContextNoSettings(UserDetails.fromUser(user)); when(userService.getUserByUsername(anyString())).thenReturn(user); - - subject = new OutlierQueryParser(idObjectManager, dimensionalObjectProducer, userService); } @Test diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/outlier/service/AnalyticsZscoreSqlStatementProcessorTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/outlier/service/AnalyticsZscoreSqlStatementProcessorTest.java index 270d5c3f3c2b..bcc312f02f10 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/outlier/service/AnalyticsZscoreSqlStatementProcessorTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/outlier/service/AnalyticsZscoreSqlStatementProcessorTest.java @@ -57,10 +57,6 @@ class AnalyticsZscoreSqlStatementProcessorTest { private OutlierSqlStatementProcessor subject; - // ------------------------------------------------------------------------- - // Fixture - // ------------------------------------------------------------------------- - private List dataDimensions; private OrganisationUnit ouA; diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/resolver/CategoryOptionGroupResolverTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/resolver/CategoryOptionGroupResolverTest.java index 71c106898d56..ae5a6bb91c6f 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/resolver/CategoryOptionGroupResolverTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/resolver/CategoryOptionGroupResolverTest.java @@ -47,6 +47,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -62,7 +63,7 @@ class CategoryOptionGroupResolverTest { @Mock private ExpressionService expressionService; - private ExpressionResolver resolver; + @InjectMocks private CategoryOptionGroupResolver resolver; private String uid1; @@ -76,9 +77,9 @@ class CategoryOptionGroupResolverTest { private CategoryOptionCombo coc3; - DimensionalItemId dimensionalItemId; + private DimensionalItemId dimensionalItemId; - private static final String CATEGORY_OPTION_GROUP_PREFIX = "coGroup:"; + private final String CATEGORY_OPTION_GROUP_PREFIX = "coGroup:"; @BeforeEach public void setUp() { @@ -89,10 +90,6 @@ public void setUp() { coc1 = createCategoryOptionCombo('X'); coc2 = createCategoryOptionCombo('Y'); coc3 = createCategoryOptionCombo('Z'); - - resolver = - new CategoryOptionGroupResolver( - expressionService, categoryOptionGroupStore, categoryOptionComboStore); } @Test diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/resolver/CategoryOptionResolverTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/resolver/CategoryOptionResolverTest.java index d5debfd3cec8..245cc06733af 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/resolver/CategoryOptionResolverTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/resolver/CategoryOptionResolverTest.java @@ -46,6 +46,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; @@ -61,7 +62,7 @@ class CategoryOptionResolverTest { @Mock private ExpressionService expressionService; - private ExpressionResolver resolver; + @InjectMocks private CategoryOptionResolver resolver; private String uid1; @@ -75,34 +76,26 @@ class CategoryOptionResolverTest { private CategoryOptionCombo coc3; - DimensionalItemId dimensionalItemId; + private DimensionalItemId dimensionalItemId; - private static final String CATEGORY_OPTION_PREFIX = "co:"; + private final String CATEGORY_OPTION_PREFIX = "co:"; @BeforeEach public void setUp() { uid1 = CodeGenerator.generateUid(); - uid2 = CodeGenerator.generateUid(); - uid3 = CodeGenerator.generateUid(); CategoryOption categoryOption = createCategoryOption('A'); coc1 = createCategoryOptionCombo('X'); - - categoryOption.addCategoryOptionCombo(coc1); - coc2 = createCategoryOptionCombo('Y'); - - categoryOption.addCategoryOptionCombo(coc2); - coc3 = createCategoryOptionCombo('Z'); + categoryOption.addCategoryOptionCombo(coc1); + categoryOption.addCategoryOptionCombo(coc2); categoryOption.addCategoryOptionCombo(coc3); - resolver = new CategoryOptionResolver(expressionService, categoryOptionStore); - when(categoryOptionStore.getByUid(anyString())).thenReturn(categoryOption); } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/resolver/DataElementGroupResolverTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/resolver/DataElementGroupResolverTest.java index f5fb99dfcc85..dc47bf1e99a0 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/resolver/DataElementGroupResolverTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/resolver/DataElementGroupResolverTest.java @@ -46,6 +46,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; @@ -61,7 +62,7 @@ class DataElementGroupResolverTest { @Mock private ExpressionService expressionService; - private ExpressionResolver resolver; + @InjectMocks private DataElementGroupResolver resolver; private String uid1; @@ -75,34 +76,26 @@ class DataElementGroupResolverTest { private DataElement de3; - DimensionalItemId dimensionalItemId; + private DimensionalItemId dimensionalItemId; - private static final String DATA_ELEMENT_GROUP_PREFIX = "deGroup:"; + private final String DATA_ELEMENT_GROUP_PREFIX = "deGroup:"; @BeforeEach public void setUp() { uid1 = CodeGenerator.generateUid(); - uid2 = CodeGenerator.generateUid(); - uid3 = CodeGenerator.generateUid(); de1 = createDataElement('X'); - de2 = createDataElement('Y'); - de3 = createDataElement('Z'); DataElementGroup dataElementGroup = createDataElementGroup('A'); dataElementGroup.addDataElement(de1); - dataElementGroup.addDataElement(de2); - dataElementGroup.addDataElement(de3); - resolver = new DataElementGroupResolver(expressionService, dataElementGroupStore); - when(dataElementGroupStore.getByUid(anyString())).thenReturn(dataElementGroup); } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/AbstractEventJdbcTableManagerTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/AbstractEventJdbcTableManagerTest.java new file mode 100644 index 000000000000..80cf1dbee0bf --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/AbstractEventJdbcTableManagerTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2004-2024, 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.table; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.hisp.dhis.common.ValueType; +import org.hisp.dhis.db.sql.PostgreSqlBuilder; +import org.hisp.dhis.db.sql.SqlBuilder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class AbstractEventJdbcTableManagerTest { + + @Spy private SqlBuilder sqlBuilder = new PostgreSqlBuilder(); + + @InjectMocks private JdbcEventAnalyticsTableManager manager; + + @Test + void testGetCastExpression() { + String expected = + """ + case when eventdatavalues #>> '{GieVkTxp4HH, value}' ~* '^(-?[0-9]+)(\\.[0-9]+)?$' \ + then cast(eventdatavalues #>> '{GieVkTxp4HH, value}' as double precision) \ + else null end"""; + + String actual = + manager.getCastExpression( + "eventdatavalues #>> '{GieVkTxp4HH, value}'", + "'^(-?[0-9]+)(\\.[0-9]+)?$'", + "double precision"); + + assertEquals(expected, actual); + } + + @Test + void testGetSelectExpressionNumber() { + String expected = + """ + case when eventdatavalues #>> '{GieVkTxp4HH, value}' ~* '^(-?[0-9]+)(\\.[0-9]+)?$' \ + then cast(eventdatavalues #>> '{GieVkTxp4HH, value}' as double precision) \ + else null end"""; + + String actual = + manager.getSelectExpression(ValueType.NUMBER, "eventdatavalues #>> '{GieVkTxp4HH, value}'"); + + assertEquals(expected, actual); + } + + @Test + void testGetSelectExpressionDate() { + String expected = + """ + case when eventdatavalues #>> '{AL04Wbutskk, value}' ~* '^\\d{4}-\\d{2}-\\d{2}(\\s|T)?((\\d{2}:)(\\d{2}:)?(\\d{2}))?(|.(\\d{3})|.(\\d{3})Z)?$' \ + then cast(eventdatavalues #>> '{AL04Wbutskk, value}' as timestamp) \ + else null end"""; + + String actual = + manager.getSelectExpression(ValueType.DATE, "eventdatavalues #>> '{AL04Wbutskk, value}'"); + + assertEquals(expected, actual); + } + + @Test + void testGetSelectExpressionText() { + String expected = + """ + eventdatavalues #>> '{FwUzmc49Pcr, value}'"""; + + String actual = + manager.getSelectExpression(ValueType.TEXT, "eventdatavalues #>> '{FwUzmc49Pcr, value}'"); + + assertEquals(expected, actual); + } +} diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/AnalyticsTableServiceTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/AnalyticsTableServiceTest.java index f424bc91f021..91389524f5de 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/AnalyticsTableServiceTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/AnalyticsTableServiceTest.java @@ -55,8 +55,8 @@ */ @ExtendWith(MockitoExtension.class) class AnalyticsTableServiceTest { - @Mock private SystemSettingsProvider settingsProvider; + @Mock private SystemSettings settings; @Mock private SqlBuilder sqlBuilder; @@ -81,7 +81,10 @@ void testGetTablePartitions() { .selectExpression("value") .build()); - AnalyticsTable tA = new AnalyticsTable(AnalyticsTableType.DATA_VALUE, columns, Logged.UNLOGGED); + List sortKey = List.of("dx"); + + AnalyticsTable tA = + new AnalyticsTable(AnalyticsTableType.DATA_VALUE, columns, sortKey, Logged.UNLOGGED); tA.addTablePartition( List.of(), 2010, @@ -93,7 +96,7 @@ void testGetTablePartitions() { new DateTime(2011, 1, 1, 0, 0).toDate(), new DateTime(2011, 12, 31, 0, 0).toDate()); AnalyticsTable tB = - new AnalyticsTable(AnalyticsTableType.ORG_UNIT_TARGET, columns, Logged.UNLOGGED); + new AnalyticsTable(AnalyticsTableType.ORG_UNIT_TARGET, columns, sortKey, Logged.UNLOGGED); List partitions = tableService.getTablePartitions(List.of(tA, tB)); assertEquals(3, partitions.size()); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcAnalyticsTableManagerTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcAnalyticsTableManagerTest.java index f43572ee8ef9..25d7db4f7e31 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcAnalyticsTableManagerTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcAnalyticsTableManagerTest.java @@ -300,16 +300,16 @@ void testGetLatestAnalyticsTableNoFullTableUpdate() { "Verify if the method swapParentTable is called with the swapped table name not the staging table name") void testSwapTable() { Date startTime = new DateTime(2019, 3, 1, 10, 0).toDate(); + List columns = + List.of( + AnalyticsTableColumn.builder() + .name("year") + .dataType(INTEGER) + .selectExpression("") + .build()); + List sortKey = List.of("dx"); AnalyticsTable table = - new AnalyticsTable( - AnalyticsTableType.DATA_VALUE, - List.of( - AnalyticsTableColumn.builder() - .name("year") - .dataType(INTEGER) - .selectExpression("") - .build()), - LOGGED); + new AnalyticsTable(AnalyticsTableType.DATA_VALUE, columns, sortKey, LOGGED); table.addTablePartition(List.of(), 2023, new DateTime(2023, 1, 1, 0, 0).toDate(), null); AnalyticsTableUpdateParams params = AnalyticsTableUpdateParams.newBuilder().startTime(startTime).build().withLatestPartition(); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcEnrollmentAnalyticsTableManagerTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcEnrollmentAnalyticsTableManagerTest.java index e5caed47ad7c..12e6110c1646 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcEnrollmentAnalyticsTableManagerTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcEnrollmentAnalyticsTableManagerTest.java @@ -147,8 +147,8 @@ void verifyTeiTypeOrgUnitFetchesOuUidWhenPopulatingEventAnalyticsTable() { String ouQuery = format( """ - (select ou.%s from organisationunit ou where ou.uid = \ - (select value from trackedentityattributevalue where trackedentityid=en.trackedentityid and \ + (select ou.%s from \"organisationunit\" ou where ou.uid = \ + (select value from \"trackedentityattributevalue\" where trackedentityid=en.trackedentityid and \ trackedentityattributeid=9999)) as %s""", "uid", quote(tea.getUid())); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcEventAnalyticsTableManagerDorisTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcEventAnalyticsTableManagerDorisTest.java new file mode 100644 index 000000000000..351950d9f48c --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcEventAnalyticsTableManagerDorisTest.java @@ -0,0 +1,246 @@ +/* + * 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.table; + +import static java.time.LocalDate.now; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hisp.dhis.db.model.DataType.BIGINT; +import static org.hisp.dhis.db.model.DataType.DOUBLE; +import static org.hisp.dhis.db.model.DataType.INTEGER; +import static org.hisp.dhis.db.model.DataType.TEXT; +import static org.hisp.dhis.db.model.DataType.TIMESTAMP; +import static org.hisp.dhis.db.model.Table.STAGING_TABLE_SUFFIX; +import static org.hisp.dhis.period.PeriodDataProvider.PeriodSource.DATABASE; +import static org.hisp.dhis.test.TestBase.createDataElement; +import static org.hisp.dhis.test.TestBase.createProgram; +import static org.hisp.dhis.test.TestBase.createProgramStage; +import static org.mockito.Mockito.when; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Date; +import java.util.List; +import java.util.Set; +import org.hisp.dhis.analytics.AggregationType; +import org.hisp.dhis.analytics.AnalyticsTableType; +import org.hisp.dhis.analytics.AnalyticsTableUpdateParams; +import org.hisp.dhis.analytics.table.model.AnalyticsTable; +import org.hisp.dhis.analytics.table.model.AnalyticsTableColumn; +import org.hisp.dhis.analytics.table.model.Skip; +import org.hisp.dhis.analytics.table.setting.AnalyticsTableSettings; +import org.hisp.dhis.analytics.util.AnalyticsTableAsserter; +import org.hisp.dhis.category.CategoryService; +import org.hisp.dhis.common.IdentifiableObjectManager; +import org.hisp.dhis.common.ValueType; +import org.hisp.dhis.dataelement.DataElement; +import org.hisp.dhis.db.model.IndexType; +import org.hisp.dhis.db.sql.DorisSqlBuilder; +import org.hisp.dhis.db.sql.SqlBuilder; +import org.hisp.dhis.organisationunit.OrganisationUnitService; +import org.hisp.dhis.period.PeriodDataProvider; +import org.hisp.dhis.period.PeriodDataProvider.PeriodSource; +import org.hisp.dhis.period.PeriodType; +import org.hisp.dhis.program.Program; +import org.hisp.dhis.program.ProgramStage; +import org.hisp.dhis.resourcetable.ResourceTableService; +import org.hisp.dhis.setting.SystemSettings; +import org.hisp.dhis.setting.SystemSettingsProvider; +import org.hisp.dhis.system.database.DatabaseInfo; +import org.hisp.dhis.system.database.DatabaseInfoProvider; +import org.joda.time.DateTime; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.jdbc.core.JdbcTemplate; + +/** + * @author Luciano Fiandesio + */ +@ExtendWith(MockitoExtension.class) +class JdbcEventAnalyticsTableManagerDorisTest { + @Mock private IdentifiableObjectManager idObjectManager; + + @Mock private OrganisationUnitService organisationUnitService; + + @Mock private CategoryService categoryService; + + @Mock private SystemSettingsProvider settingsProvider; + + @Mock private SystemSettings settings; + + @Mock private DatabaseInfoProvider databaseInfoProvider; + + @Mock private JdbcTemplate jdbcTemplate; + + @Mock private ResourceTableService resourceTableService; + + @Mock private PeriodDataProvider periodDataProvider; + + @Mock private AnalyticsTableSettings analyticsTableSettings; + + @Spy private SqlBuilder sqlBuilder = new DorisSqlBuilder("dhis2", "driver"); + + @InjectMocks private JdbcEventAnalyticsTableManager subject; + + private Date today; + + private static final Date START_TIME = new DateTime(2019, 8, 1, 0, 0).toDate(); + + private static final String TABLE_PREFIX = "analytics_event_"; + + private static final int OU_NAME_HIERARCHY_COUNT = 1; + + private List periodColumns = + PeriodType.getAvailablePeriodTypes().stream() + .map( + pt -> { + String column = pt.getName().toLowerCase(); + return AnalyticsTableColumn.builder() + .name(column) + .dataType(TEXT) + .selectExpression("dps" + "." + quote(column)) + .build(); + }) + .toList(); + + private String quote(String relation) { + return sqlBuilder.quote(relation); + } + + @BeforeEach + void setUp() { + today = Date.from(LocalDate.of(2019, 7, 6).atStartOfDay(ZoneId.systemDefault()).toInstant()); + + when(databaseInfoProvider.getDatabaseInfo()).thenReturn(DatabaseInfo.builder().build()); + when(settingsProvider.getCurrentSettings()).thenReturn(settings); + when(settings.getLastSuccessfulResourceTablesUpdate()).thenReturn(new Date(0L)); + when(analyticsTableSettings.getPeriodSource()).thenReturn(PeriodSource.DATABASE); + } + + @Test + void verifyGetTableWithDataElements() { + when(databaseInfoProvider.getDatabaseInfo()) + .thenReturn(DatabaseInfo.builder().spatialSupport(true).build()); + Program program = createProgram('A'); + + DataElement deA = createDataElement('A', ValueType.TEXT, AggregationType.SUM); + DataElement deB = createDataElement('B', ValueType.PERCENTAGE, AggregationType.SUM); + DataElement deC = createDataElement('C', ValueType.BOOLEAN, AggregationType.NONE); + DataElement deD = createDataElement('D', ValueType.DATE, AggregationType.LAST); + DataElement deE = createDataElement('E', ValueType.ORGANISATION_UNIT, AggregationType.NONE); + DataElement deF = createDataElement('F', ValueType.INTEGER, AggregationType.SUM); + DataElement deG = createDataElement('G', ValueType.COORDINATE, AggregationType.NONE); + + ProgramStage ps1 = createProgramStage('A', Set.of(deA, deB, deC, deD, deE, deF, deG)); + + program.setProgramStages(Set.of(ps1)); + + when(idObjectManager.getAllNoAcl(Program.class)).thenReturn(List.of(program)); + + String aliasA = "json_unquote(json_extract(eventdatavalues, '$.%s.value')) as `%s`"; + String aliasB = + "case when json_unquote(json_extract(eventdatavalues, '$.%s.value')) regexp '^(-?[0-9]+)(\\.[0-9]+)?$' then cast(json_unquote(json_extract(eventdatavalues, '$.%s.value')) as double) else null end as `%s`"; + String aliasC = + "case when json_unquote(json_extract(eventdatavalues, '$.%s.value')) = 'true' then 1 when json_unquote(json_extract(eventdatavalues, '$.%s.value')) = 'false' then 0 else null end as `%s`"; + String aliasD = + "case when json_unquote(json_extract(eventdatavalues, '$.%s.value')) regexp '^\\d{4}-\\d{2}-\\d{2}(\\s|T)?((\\d{2}:)(\\d{2}:)?(\\d{2}))?(|.(\\d{3})|.(\\d{3})Z)?$' then cast(json_unquote(json_extract(eventdatavalues, '$.%s.value')) as datetime) else null end as `%s`"; + String aliasE = + "(select ou.uid from dhis2.public.`organisationunit` ou where ou.uid = json_unquote(json_extract(eventdatavalues, '$.%s.value')) ) as `%s`"; + String aliasF = + "(select ou.name from dhis2.public.`organisationunit` ou where ou.uid = json_unquote(json_extract(eventdatavalues, '$.%s.value')) ) as `%s`"; + String aliasG = + "case when json_unquote(json_extract(eventdatavalues, '$.%s.value')) regexp '^(-?[0-9]+)(\\.[0-9]+)?$' then cast(json_unquote(json_extract(eventdatavalues, '$.%s.value')) as bigint) else null end as `%s`"; + + AnalyticsTableUpdateParams params = + AnalyticsTableUpdateParams.newBuilder() + .lastYears(2) + .startTime(START_TIME) + .today(today) + .build(); + + when(periodDataProvider.getAvailableYears(DATABASE)) + .thenReturn(List.of(2018, 2019, now().getYear())); + + List tables = subject.getAnalyticsTables(params); + + assertThat(tables, hasSize(1)); + + new AnalyticsTableAsserter.Builder(tables.get(0)) + .withName(TABLE_PREFIX + program.getUid().toLowerCase() + STAGING_TABLE_SUFFIX) + .withMainName(TABLE_PREFIX + program.getUid().toLowerCase()) + .withTableType(AnalyticsTableType.EVENT) + .withColumnSize(58 + OU_NAME_HIERARCHY_COUNT) + .addColumns(periodColumns) + .addColumn( + deA.getUid(), + TEXT, + toSelectExpression(aliasA, deA.getUid()), + Skip.SKIP) // ValueType.TEXT + .addColumn( + deB.getUid(), + DOUBLE, + toSelectExpression(aliasB, deB.getUid()), + IndexType.BTREE) // ValueType.PERCENTAGE + .addColumn( + deC.getUid(), + INTEGER, + toSelectExpression(aliasC, deC.getUid()), + IndexType.BTREE) // ValueType.BOOLEAN + .addColumn( + deD.getUid(), + TIMESTAMP, + toSelectExpression(aliasD, deD.getUid()), + IndexType.BTREE) // ValueType.DATE + .addColumn( + deE.getUid(), + TEXT, + toSelectExpression(aliasE, deE.getUid()), + IndexType.BTREE) // ValueType.ORGANISATION_UNIT + .addColumn( + deF.getUid(), + BIGINT, + toSelectExpression(aliasG, deF.getUid()), + IndexType.BTREE) // ValueType.INTEGER + + // element d5 also creates a Name column + .addColumn( + deE.getUid() + "_name", TEXT, toSelectExpression(aliasF, deE.getUid()), Skip.SKIP) + .withDefaultColumns(EventAnalyticsColumn.getColumns(sqlBuilder)) + .build() + .verify(); + } + + private String toSelectExpression(String template, String uid) { + return String.format(template, uid, uid, uid); + } +} diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcEventAnalyticsTableManagerTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcEventAnalyticsTableManagerTest.java index 85e9d37bb7bf..4e64792c8546 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcEventAnalyticsTableManagerTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcEventAnalyticsTableManagerTest.java @@ -44,7 +44,7 @@ import static org.hisp.dhis.db.model.DataType.TIMESTAMP; import static org.hisp.dhis.db.model.Table.STAGING_TABLE_SUFFIX; import static org.hisp.dhis.db.model.constraint.Nullable.NULL; -import static org.hisp.dhis.period.PeriodDataProvider.DataSource.DATABASE; +import static org.hisp.dhis.period.PeriodDataProvider.PeriodSource.DATABASE; import static org.hisp.dhis.system.util.SqlUtils.quote; import static org.hisp.dhis.test.TestBase.createCategory; import static org.hisp.dhis.test.TestBase.createCategoryCombo; @@ -56,7 +56,6 @@ import static org.hisp.dhis.test.TestBase.getDate; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -70,10 +69,8 @@ import java.util.Map; import java.util.Set; import org.hisp.dhis.analytics.AggregationType; -import org.hisp.dhis.analytics.AnalyticsTableHookService; import org.hisp.dhis.analytics.AnalyticsTableType; import org.hisp.dhis.analytics.AnalyticsTableUpdateParams; -import org.hisp.dhis.analytics.partition.PartitionManager; import org.hisp.dhis.analytics.table.model.AnalyticsTable; import org.hisp.dhis.analytics.table.model.AnalyticsTableColumn; import org.hisp.dhis.analytics.table.model.AnalyticsTablePartition; @@ -86,7 +83,6 @@ import org.hisp.dhis.category.CategoryService; import org.hisp.dhis.common.IdentifiableObjectManager; import org.hisp.dhis.common.ValueType; -import org.hisp.dhis.dataapproval.DataApprovalLevelService; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.db.model.IndexType; import org.hisp.dhis.db.sql.PostgreSqlBuilder; @@ -95,6 +91,7 @@ import org.hisp.dhis.organisationunit.OrganisationUnitLevel; import org.hisp.dhis.organisationunit.OrganisationUnitService; import org.hisp.dhis.period.PeriodDataProvider; +import org.hisp.dhis.period.PeriodDataProvider.PeriodSource; import org.hisp.dhis.period.PeriodType; import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramStage; @@ -111,8 +108,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.jdbc.core.JdbcTemplate; @@ -128,6 +127,7 @@ class JdbcEventAnalyticsTableManagerTest { @Mock private CategoryService categoryService; @Mock private SystemSettingsProvider settingsProvider; + @Mock private SystemSettings settings; @Mock private DatabaseInfoProvider databaseInfoProvider; @@ -140,9 +140,9 @@ class JdbcEventAnalyticsTableManagerTest { @Mock private AnalyticsTableSettings analyticsTableSettings; - private final SqlBuilder sqlBuilder = new PostgreSqlBuilder(); + @Spy private SqlBuilder sqlBuilder = new PostgreSqlBuilder(); - private JdbcEventAnalyticsTableManager subject; + @InjectMocks private JdbcEventAnalyticsTableManager subject; private Date today; @@ -150,8 +150,6 @@ class JdbcEventAnalyticsTableManagerTest { private static final String TABLE_PREFIX = "analytics_event_"; - private static final String FROM_CLAUSE = "from event where eventid=ev.eventid"; - private static final String DATE_CLAUSE = "CASE WHEN 'SCHEDULE' = ev.status THEN ev.scheduleddate ELSE ev.occurreddate END"; @@ -179,23 +177,6 @@ public void setUp() { when(databaseInfoProvider.getDatabaseInfo()).thenReturn(DatabaseInfo.builder().build()); when(settingsProvider.getCurrentSettings()).thenReturn(settings); when(settings.getLastSuccessfulResourceTablesUpdate()).thenReturn(new Date(0L)); - - subject = - new JdbcEventAnalyticsTableManager( - idObjectManager, - organisationUnitService, - categoryService, - settingsProvider, - mock(DataApprovalLevelService.class), - resourceTableService, - mock(AnalyticsTableHookService.class), - mock(PartitionManager.class), - databaseInfoProvider, - jdbcTemplate, - analyticsTableSettings, - periodDataProvider, - sqlBuilder); - assertThat(subject.getAnalyticsTableType(), is(AnalyticsTableType.EVENT)); } @Test @@ -266,8 +247,7 @@ void verifyGetTableWithCategoryCombo() { addCategoryCombo(program, categoryCombo); when(idObjectManager.getAllNoAcl(Program.class)).thenReturn(List.of(program)); - when(periodDataProvider.getAvailableYears(DATABASE)) - .thenReturn(List.of(2018, 2019, now().getYear())); + mockPeriodYears(List.of(2018, 2019, now().getYear())); List availableDataYears = periodDataProvider.getAvailableYears(DATABASE); @@ -291,7 +271,7 @@ void verifyGetTableWithCategoryCombo() { .withName(TABLE_PREFIX + program.getUid().toLowerCase() + STAGING_TABLE_SUFFIX) .withMainName(TABLE_PREFIX + program.getUid().toLowerCase()) .withColumnSize(57 + OU_NAME_HIERARCHY_COUNT) - .withDefaultColumns(JdbcEventAnalyticsTableManager.FIXED_COLS) + .withDefaultColumns(EventAnalyticsColumn.getColumns(sqlBuilder)) .addColumns(periodColumns) .addColumn( categoryA.getUid(), @@ -311,8 +291,7 @@ void verifyGetTableWithCategoryCombo() { void verifyClientSideTimestampsColumns() { Program program = createProgram('A'); when(idObjectManager.getAllNoAcl(Program.class)).thenReturn(List.of(program)); - when(periodDataProvider.getAvailableYears(DATABASE)) - .thenReturn(List.of(2018, 2019, now().getYear())); + mockPeriodYears(List.of(2018, 2019, now().getYear())); List availableDataYears = periodDataProvider.getAvailableYears(DATABASE); @@ -347,8 +326,7 @@ void verifyClientSideTimestampsColumns() { void verifyAnalyticsEventTableHasDefaultPartition() { Program program = createProgram('A'); when(idObjectManager.getAllNoAcl(Program.class)).thenReturn(List.of(program)); - when(periodDataProvider.getAvailableYears(DATABASE)) - .thenReturn(List.of(2021, 2022, 2023, 2024, 2025)); + mockPeriodYears(List.of(2021, 2022, 2023, 2024, 2025)); List availableDataYears = periodDataProvider.getAvailableYears(DATABASE); @@ -397,51 +375,48 @@ void verifyGetTableWithDataElements() { when(idObjectManager.getAllNoAcl(Program.class)).thenReturn(List.of(program)); - String aliasD1 = "(select eventdatavalues #>> '{%s, value}' " + FROM_CLAUSE + " ) as \"%s\""; + String aliasD1 = "eventdatavalues #>> '{%s, value}' as \"%s\""; String aliasD2 = - "(select cast(eventdatavalues #>> '{%s, value}' as double precision) " - + FROM_CLAUSE - + " and eventdatavalues #>> '{%s,value}' ~* '^(-?[0-9]+)(\\.[0-9]+)?$') as \"%s\""; + """ + case when eventdatavalues #>> '{deabcdefghP, value}' ~* '^(-?[0-9]+)(\\.[0-9]+)?$' \ + then cast(eventdatavalues #>> '{deabcdefghP, value}' as double precision) else null end as "deabcdefghP\""""; String aliasD3 = - "(select case when eventdatavalues #>> '{%s, value}' = 'true' then 1 when eventdatavalues #>> '{%s, value}' = 'false' then 0 else null end " - + FROM_CLAUSE - + " ) as \"%s\""; + """ + case when eventdatavalues #>> '{deabcdefghY, value}' = 'true' then 1 \ + when eventdatavalues #>> '{deabcdefghY, value}' = 'false' then 0 else null end as "deabcdefghY\""""; String aliasD4 = - "(select cast(eventdatavalues #>> '{%s, value}' as timestamp) " - + FROM_CLAUSE - + " and eventdatavalues #>> '{%s,value}' ~* '^\\d{4}-\\d{2}-\\d{2}(\\s|T)?((\\d{2}:)(\\d{2}:)?(\\d{2}))?(|.(\\d{3})|.(\\d{3})Z)?$') as \"%s\""; + """ + case when eventdatavalues #>> '{deabcdefghW, value}' ~* '^\\d{4}-\\d{2}-\\d{2}(\\s|T)?((\\d{2}:)(\\d{2}:)?(\\d{2}))?(|.(\\d{3})|.(\\d{3})Z)?$' \ + then cast(eventdatavalues #>> '{deabcdefghW, value}' as timestamp) else null end as "deabcdefghW\""""; String aliasD5 = - "(select ou.uid from organisationunit ou where ou.uid = " - + "(select eventdatavalues #>> '{" + "(select ou.uid from \"organisationunit\" ou where ou.uid = " + + "eventdatavalues #>> '{" + d5.getUid() + ", value}' " - + FROM_CLAUSE - + " )) as \"" + + ") as \"" + d5.getUid() + "\""; String aliasD6 = - "(select cast(eventdatavalues #>> '{%s, value}' as bigint) " - + FROM_CLAUSE - + " and eventdatavalues #>> '{%s,value}' ~* '^(-?[0-9]+)(\\.[0-9]+)?$') as \"%s\""; + """ + case when eventdatavalues #>> '{deabcdefghH, value}' ~* '^(-?[0-9]+)(\\.[0-9]+)?$' \ + then cast(eventdatavalues #>> '{deabcdefghH, value}' as bigint) else null end as "deabcdefghH\""""; String aliasD7 = - "(select ST_GeomFromGeoJSON('{\"type\":\"Point\", \"coordinates\":' || (eventdatavalues #>> '{%s, value}') || ', \"crs\":{\"type\":\"name\", \"properties\":{\"name\":\"EPSG:4326\"}}}') from event where eventid=ev.eventid ) as \"%s\""; + """ + ST_GeomFromGeoJSON('{\"type\":\"Point\", \"coordinates\":' || (eventdatavalues #>> '{deabcdefghU, value}') || ', "crs":{"type":"name", "properties":{"name":"EPSG:4326"}}}') as "deabcdefghU\""""; String aliasD5_geo = - "(select ou.geometry from organisationunit ou where ou.uid = (select eventdatavalues #>> '{" + "(select ou.geometry from \"organisationunit\" ou where ou.uid = eventdatavalues #>> '{" + d5.getUid() + ", value}' " - + FROM_CLAUSE - + " )) as \"" + + ") as \"" + d5.getUid() + "\""; String aliasD5_name = - "(select ou.name from organisationunit ou where ou.uid = (select eventdatavalues #>> '{" + "(select ou.name from \"organisationunit\" ou where ou.uid = eventdatavalues #>> '{" + d5.getUid() + ", value}' " - + FROM_CLAUSE - + " )) as \"" + + ") as \"" + d5.getUid() + "\""; - AnalyticsTableUpdateParams params = AnalyticsTableUpdateParams.newBuilder() .lastYears(2) @@ -449,8 +424,7 @@ void verifyGetTableWithDataElements() { .today(today) .build(); - when(periodDataProvider.getAvailableYears(DATABASE)) - .thenReturn(List.of(2018, 2019, now().getYear())); + mockPeriodYears(List.of(2018, 2019, now().getYear())); List availableDataYears = periodDataProvider.getAvailableYears(DATABASE); @@ -503,18 +477,10 @@ void verifyGetTableWithDataElements() { GEOMETRY_POINT, toSelectExpression(aliasD7, d7.getUid())) // ValueType.COORDINATES // element d5 also creates a Geo column - .addColumn( - d5.getUid() + "_geom", - GEOMETRY, - toSelectExpression(aliasD5_geo, d5.getUid()), - IndexType.GIST) + .addColumn(d5.getUid() + "_geom", GEOMETRY, aliasD5_geo, IndexType.GIST) // element d5 also creates a Name column - .addColumn( - d5.getUid() + "_name", - TEXT, - toSelectExpression(aliasD5_name, d5.getUid() + "_name"), - Skip.SKIP) - .withDefaultColumns(JdbcEventAnalyticsTableManager.FIXED_COLS) + .addColumn(d5.getUid() + "_name", TEXT, aliasD5_name, Skip.SKIP) + .withDefaultColumns(EventAnalyticsColumn.getColumns(sqlBuilder)) .build() .verify(); } @@ -540,10 +506,12 @@ void verifyGetTableWithTrackedEntityAttribute() { when(idObjectManager.getAllNoAcl(Program.class)).thenReturn(List.of(program)); - String aliasD1 = "(select eventdatavalues #>> '{%s, value}' " + FROM_CLAUSE + " ) as \"%s\""; + String aliasD1 = + """ + eventdatavalues #>> '{deabcdefghZ, value}' as "deabcdefghZ\""""; String aliasTea1 = - "(select %s from organisationunit ou where ou.uid = (select value from " - + "trackedentityattributevalue where trackedentityid=en.trackedentityid and " + "(select %s from \"organisationunit\" ou where ou.uid = (select value from " + + "\"trackedentityattributevalue\" where trackedentityid=en.trackedentityid and " + "trackedentityattributeid=%d)) as \"%s\""; AnalyticsTableUpdateParams params = @@ -553,8 +521,7 @@ void verifyGetTableWithTrackedEntityAttribute() { .today(today) .build(); - when(periodDataProvider.getAvailableYears(DATABASE)) - .thenReturn(List.of(2018, 2019, now().getYear())); + mockPeriodYears(List.of(2018, 2019, now().getYear())); List availableDataYears = periodDataProvider.getAvailableYears(DATABASE); @@ -590,7 +557,7 @@ void verifyGetTableWithTrackedEntityAttribute() { TEXT, String.format(aliasTea1, "ou.name", tea1.getId(), tea1.getUid()), Skip.SKIP) - .withDefaultColumns(JdbcEventAnalyticsTableManager.FIXED_COLS) + .withDefaultColumns(EventAnalyticsColumn.getColumns(sqlBuilder)) .build() .verify(); } @@ -617,8 +584,7 @@ void verifyDataElementTypeOrgUnitFetchesOuNameWhenPopulatingEventAnalyticsTable( .today(today) .build(); - when(periodDataProvider.getAvailableYears(DATABASE)) - .thenReturn(List.of(2018, 2019, now().getYear())); + mockPeriodYears(List.of(2018, 2019, now().getYear())); List availableDataYears = periodDataProvider.getAvailableYears(DATABASE); @@ -634,11 +600,10 @@ void verifyDataElementTypeOrgUnitFetchesOuNameWhenPopulatingEventAnalyticsTable( verify(jdbcTemplate).execute(sql.capture()); String ouQuery = - "(select ou.%s from organisationunit ou where ou.uid = " - + "(select eventdatavalues #>> '{" + "(select ou.%s from \"organisationunit\" ou where ou.uid = " + + "eventdatavalues #>> '{" + d5.getUid() - + ", value}' from event where " - + "eventid=ev.eventid )) as \"" + + ", value}' ) as \"" + d5.getUid() + "\""; @@ -662,8 +627,7 @@ void verifyTeiTypeOrgUnitFetchesOuNameWhenPopulatingEventAnalyticsTable() { programA.setProgramAttributes(List.of(programTrackedEntityAttribute)); when(idObjectManager.getAllNoAcl(Program.class)).thenReturn(List.of(programA)); - when(periodDataProvider.getAvailableYears(DATABASE)) - .thenReturn(List.of(2018, 2019, now().getYear())); + mockPeriodYears(List.of(2018, 2019, now().getYear())); List availableDataYears = periodDataProvider.getAvailableYears(DATABASE); @@ -686,8 +650,8 @@ void verifyTeiTypeOrgUnitFetchesOuNameWhenPopulatingEventAnalyticsTable() { verify(jdbcTemplate).execute(sql.capture()); String ouQuery = - "(select ou.%s from organisationunit ou where ou.uid = " - + "(select value from trackedentityattributevalue where trackedentityid=en.trackedentityid and " + "(select ou.%s from \"organisationunit\" ou where ou.uid = " + + "(select value from \"trackedentityattributevalue\" where trackedentityid=en.trackedentityid and " + "trackedentityattributeid=9999)) as \"" + tea.getUid() + "\""; @@ -712,8 +676,7 @@ void verifyOrgUnitOwnershipJoinsWhenPopulatingEventAnalyticsTable() { programA.setProgramAttributes(List.of(programTrackedEntityAttribute)); when(idObjectManager.getAllNoAcl(Program.class)).thenReturn(List.of(programA)); - when(periodDataProvider.getAvailableYears(DATABASE)) - .thenReturn(List.of(2018, 2019, now().getYear())); + mockPeriodYears(List.of(2018, 2019, now().getYear())); List availableDataYears = periodDataProvider.getAvailableYears(DATABASE); @@ -737,9 +700,9 @@ void verifyOrgUnitOwnershipJoinsWhenPopulatingEventAnalyticsTable() { verify(jdbcTemplate).execute(sql.capture()); String ouEnrollmentLeftJoin = - "left join organisationunit enrollmentou on en.organisationunitid=enrollmentou.organisationunitid"; + "left join \"organisationunit\" enrollmentou on en.organisationunitid=enrollmentou.organisationunitid"; String ouRegistrationLeftJoin = - "left join organisationunit registrationou on te.organisationunitid=registrationou.organisationunitid"; + "left join \"organisationunit\" registrationou on te.organisationunitid=registrationou.organisationunitid"; assertThat(sql.getValue(), containsString(ouEnrollmentLeftJoin)); assertThat(sql.getValue(), containsString(ouRegistrationLeftJoin)); @@ -751,8 +714,7 @@ void verifyGetAnalyticsTableWithOuLevels() { Program programA = rnd.nextObject(Program.class); programA.setId(0); - when(periodDataProvider.getAvailableYears(DATABASE)) - .thenReturn(List.of(2018, 2019, now().getYear())); + mockPeriodYears(List.of(2018, 2019, now().getYear())); List availableDataYears = periodDataProvider.getAvailableYears(DATABASE); int startYear = availableDataYears.get(0); @@ -765,7 +727,7 @@ void verifyGetAnalyticsTableWithOuLevels() { "select temp.supportedyear from (select distinct extract(year from " + DATE_CLAUSE + ") as supportedyear " - + "from event ev inner join enrollment en on ev.enrollmentid = en.enrollmentid " + + "from \"event\" ev inner join \"enrollment\" en on ev.enrollmentid = en.enrollmentid " + "where ev.lastupdated <= '2019-08-01T00:00:00' and en.programid = 0 and (" + DATE_CLAUSE + ") is not null " @@ -791,13 +753,13 @@ void verifyGetAnalyticsTableWithOuLevels() { .withMainName(TABLE_PREFIX + programA.getUid().toLowerCase()) .withTableType(AnalyticsTableType.EVENT) .withColumnSize( - JdbcEventAnalyticsTableManager.FIXED_COLS.size() + EventAnalyticsColumn.getColumns(sqlBuilder).size() + PeriodType.getAvailablePeriodTypes().size() + ouLevels.size() + (programA.isRegistration() ? 1 : 0) + OU_NAME_HIERARCHY_COUNT) .addColumns(periodColumns) - .withDefaultColumns(JdbcEventAnalyticsTableManager.FIXED_COLS) + .withDefaultColumns(EventAnalyticsColumn.getColumns(sqlBuilder)) .addColumn(("uidlevel" + ouLevels.get(0).getLevel()), col -> match(ouLevels.get(0), col)) .addColumn(("uidlevel" + ouLevels.get(1).getLevel()), col -> match(ouLevels.get(1), col)) .build() @@ -814,8 +776,7 @@ void verifyGetAnalyticsTableWithOuGroupSet() { when(idObjectManager.getAllNoAcl(Program.class)).thenReturn(List.of(programA)); when(idObjectManager.getDataDimensionsNoAcl(OrganisationUnitGroupSet.class)) .thenReturn(ouGroupSet); - when(periodDataProvider.getAvailableYears(DATABASE)) - .thenReturn(List.of(2018, 2019, now().getYear())); + mockPeriodYears(List.of(2018, 2019, now().getYear())); List availableDataYears = periodDataProvider.getAvailableYears(DATABASE); @@ -834,13 +795,13 @@ void verifyGetAnalyticsTableWithOuGroupSet() { .withMainName(TABLE_PREFIX + programA.getUid().toLowerCase()) .withTableType(AnalyticsTableType.EVENT) .withColumnSize( - JdbcEventAnalyticsTableManager.FIXED_COLS.size() + EventAnalyticsColumn.getColumns(sqlBuilder).size() + PeriodType.getAvailablePeriodTypes().size() + ouGroupSet.size() + (programA.isRegistration() ? 1 : 0) + OU_NAME_HIERARCHY_COUNT) .addColumns(periodColumns) - .withDefaultColumns(JdbcEventAnalyticsTableManager.FIXED_COLS) + .withDefaultColumns(EventAnalyticsColumn.getColumns(sqlBuilder)) .addColumn(ouGroupSet.get(0).getUid(), col -> match(ouGroupSet.get(0), col)) .addColumn(ouGroupSet.get(1).getUid(), col -> match(ouGroupSet.get(1), col)) .build() @@ -855,8 +816,7 @@ void verifyGetAnalyticsTableWithOptionGroupSets() { when(idObjectManager.getAllNoAcl(Program.class)).thenReturn(List.of(programA)); when(categoryService.getAttributeCategoryOptionGroupSetsNoAcl()).thenReturn(cogs); - when(periodDataProvider.getAvailableYears(DATABASE)) - .thenReturn(List.of(2018, 2019, now().getYear())); + mockPeriodYears(List.of(2018, 2019, now().getYear())); List availableDataYears = periodDataProvider.getAvailableYears(DATABASE); @@ -876,13 +836,13 @@ void verifyGetAnalyticsTableWithOptionGroupSets() { .withMainName(TABLE_PREFIX + programA.getUid().toLowerCase()) .withTableType(AnalyticsTableType.EVENT) .withColumnSize( - JdbcEventAnalyticsTableManager.FIXED_COLS.size() + EventAnalyticsColumn.getColumns(sqlBuilder).size() + PeriodType.getAvailablePeriodTypes().size() + cogs.size() + (programA.isRegistration() ? 1 : 0) + OU_NAME_HIERARCHY_COUNT) .addColumns(periodColumns) - .withDefaultColumns(JdbcEventAnalyticsTableManager.FIXED_COLS) + .withDefaultColumns(EventAnalyticsColumn.getColumns(sqlBuilder)) .addColumn(cogs.get(0).getUid(), col -> match(cogs.get(0), col)) .addColumn(cogs.get(1).getUid(), col -> match(cogs.get(1), col)) .build() @@ -933,21 +893,19 @@ void verifyTeaTypeOrgUnitFetchesOuNameWhenPopulatingEventAnalyticsTable() { programA.setProgramAttributes(List.of(programTrackedEntityAttribute)); - when(periodDataProvider.getAvailableYears(DATABASE)) - .thenReturn(List.of(2018, 2019, now().getYear())); + when(idObjectManager.getAllNoAcl(Program.class)).thenReturn(List.of(programA)); + mockPeriodYears(List.of(2018, 2019, now().getYear())); List availableDataYears = periodDataProvider.getAvailableYears(DATABASE); int startYear = availableDataYears.get(0); int latestYear = availableDataYears.get(availableDataYears.size() - 1); - when(idObjectManager.getAllNoAcl(Program.class)).thenReturn(List.of(programA)); - when(jdbcTemplate.queryForList( "select temp.supportedyear from (select distinct extract(year from " + DATE_CLAUSE + ") as supportedyear " - + "from event ev " - + "inner join enrollment en on ev.enrollmentid = en.enrollmentid " + + "from \"event\" ev " + + "inner join \"enrollment\" en on ev.enrollmentid = en.enrollmentid " + "where ev.lastupdated <= '2019-08-01T00:00:00' and en.programid = 0 " + "and (" + DATE_CLAUSE @@ -981,9 +939,9 @@ void verifyTeaTypeOrgUnitFetchesOuNameWhenPopulatingEventAnalyticsTable() { String ouQuery = """ - (select ou.%s from organisationunit ou where ou.uid = \ - (select value from trackedentityattributevalue where trackedentityid=en.trackedentityid and \ - trackedentityattributeid=9999)) as %s"""; + (select ou.%s from \"organisationunit\" ou where ou.uid = \ + (select value from \"trackedentityattributevalue\" where trackedentityid=en.trackedentityid and \ + trackedentityattributeid=9999)) as %s"""; assertThat(sql.getValue(), containsString(String.format(ouQuery, "uid", quote(tea.getUid())))); assertThat(sql.getValue(), containsString(String.format(ouQuery, "name", quote(tea.getUid())))); @@ -1007,8 +965,8 @@ private String getYearQueryForCurrentYear( + "extract(year from " + DATE_CLAUSE + ") as supportedyear " - + "from event ev " - + "inner join enrollment en on ev.enrollmentid = en.enrollmentid " + + "from \"event\" ev " + + "inner join \"enrollment\" en on ev.enrollmentid = en.enrollmentid " + "where ev.lastupdated <= '2019-08-01T00:00:00' " + "and en.programid = " + program.getId() @@ -1030,4 +988,9 @@ private String getYearQueryForCurrentYear( return sql; } + + private void mockPeriodYears(List years) { + when(analyticsTableSettings.getPeriodSource()).thenReturn(PeriodSource.DATABASE); + when(periodDataProvider.getAvailableYears(DATABASE)).thenReturn(years); + } } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcOwnershipAnalyticsTableManagerTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcOwnershipAnalyticsTableManagerTest.java index af3cc0b3e652..b399999ff371 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcOwnershipAnalyticsTableManagerTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcOwnershipAnalyticsTableManagerTest.java @@ -87,8 +87,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockedStatic; +import org.mockito.Spy; import org.mockito.invocation.Invocation; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.jdbc.core.JdbcTemplate; @@ -135,7 +137,9 @@ class JdbcOwnershipAnalyticsTableManagerTest extends TestBase { @Mock private PeriodDataProvider periodDataProvider; - private final SqlBuilder sqlBuilder = new PostgreSqlBuilder(); + @Spy private final SqlBuilder sqlBuilder = new PostgreSqlBuilder(); + + @InjectMocks private JdbcOwnershipAnalyticsTableManager manager; private static final Program programA = createProgram('A'); @@ -147,27 +151,9 @@ class JdbcOwnershipAnalyticsTableManagerTest extends TestBase { private static AnalyticsTablePartition partitionA; - private JdbcOwnershipAnalyticsTableManager target; - @BeforeEach public void setUp() { lenient().when(settingsProvider.getCurrentSettings()).thenReturn(SystemSettings.of(Map.of())); - target = - new JdbcOwnershipAnalyticsTableManager( - idObjectManager, - organisationUnitService, - categoryService, - settingsProvider, - dataApprovalLevelService, - resourceTableService, - tableHookService, - partitionManager, - databaseInfoProvider, - jdbcTemplate, - jdbcConfiguration, - analyticsTableSettings, - periodDataProvider, - sqlBuilder); tableA = new AnalyticsTable( @@ -188,7 +174,7 @@ public void setUp() { @Test void testGetAnalyticsTableType() { - assertEquals(AnalyticsTableType.OWNERSHIP, target.getAnalyticsTableType()); + assertEquals(AnalyticsTableType.OWNERSHIP, manager.getAnalyticsTableType()); } @Test @@ -197,19 +183,19 @@ void testGetAnalyticsTables() { AnalyticsTableUpdateParams params = AnalyticsTableUpdateParams.newBuilder().build(); - assertEquals(List.of(tableA, tableB), target.getAnalyticsTables(params)); + assertEquals(List.of(tableA, tableB), manager.getAnalyticsTables(params)); params = AnalyticsTableUpdateParams.newBuilder() .lastYears(AnalyticsTablePartition.LATEST_PARTITION) .build(); - assertEquals(emptyList(), target.getAnalyticsTables(params)); + assertEquals(emptyList(), manager.getAnalyticsTables(params)); } @Test void testGetPartitionChecks() { - assertTrue(target.getPartitionChecks(1, new Date()).isEmpty()); + assertTrue(manager.getPartitionChecks(1, new Date()).isEmpty()); } @Test @@ -275,7 +261,7 @@ void testPopulateTable() throws SQLException { try (MockedStatic mocked = mockStatic(JdbcOwnershipWriter.class)) { mocked.when(() -> JdbcOwnershipWriter.getInstance(any())).thenReturn(writer); - target.populateTable(params, partitionA); + manager.populateTable(params, partitionA); } List jdbcInvocations = getInvocations(jdbcTemplate); @@ -291,18 +277,18 @@ void testPopulateTable() throws SQLException { """ select te.uid,a.startdate,a.enddate,ou.uid from (\ select h.trackedentityid, '1001-01-01' as startdate, h.enddate as enddate, h.organisationunitid \ - from programownershiphistory h \ + from "programownershiphistory" h \ where h.programid=0 and h.organisationunitid is not null \ union \ select o.trackedentityid, '2002-02-02' as startdate, null as enddate, o.organisationunitid \ - from trackedentityprogramowner o \ + from "trackedentityprogramowner" o \ where o.programid=0 \ - and exists (select 1 from programownershiphistory p \ + and exists (select 1 from "programownershiphistory" p \ where o.trackedentityid = p.trackedentityid \ and p.programid=0 and p.organisationunitid is not null)\ ) a \ - inner join trackedentity te on a.trackedentityid = te.trackedentityid \ - inner join organisationunit ou on a.organisationunitid = ou.organisationunitid \ + inner join "trackedentity" te on a.trackedentityid = te.trackedentityid \ + inner join "organisationunit" ou on a.organisationunitid = ou.organisationunitid \ left join analytics_rs_orgunitstructure ous on a.organisationunitid = ous.organisationunitid \ left join analytics_rs_organisationunitgroupsetstructure ougs on a.organisationunitid = ougs.organisationunitid \ order by te.uid, a.startdate, a.enddate""", 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 2112522861c5..606d62214bbe 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 @@ -30,6 +30,7 @@ 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; import static org.mockito.Mockito.when; @@ -53,6 +54,7 @@ import org.hisp.dhis.setting.SystemSettingsProvider; import org.hisp.dhis.system.database.DatabaseInfo; import org.hisp.dhis.system.database.DatabaseInfoProvider; +import org.hisp.dhis.system.util.SqlUtils; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; import org.hisp.dhis.trackedentity.TrackedEntityAttributeService; import org.hisp.dhis.trackedentity.TrackedEntityType; @@ -66,7 +68,6 @@ @ExtendWith(MockitoExtension.class) class JdbcTrackedEntityAnalyticsTableManagerTest { - @Mock private JdbcTemplate jdbcTemplate; @Mock private AnalyticsTableSettings analyticsTableSettings; @Mock private PeriodDataProvider periodDataProvider; @@ -102,9 +103,20 @@ void verifyNonConfidentialTeasAreSkipped() { confidentialTea.setConfidential(true); confidentialTea.setValueType(ValueType.TEXT); + Program program = mock(Program.class); + + when(sqlBuilder.qualifyTable(anyString())) + .thenAnswer(inv -> SqlUtils.quote(inv.getArgument(0))); + + when(sqlBuilder.jsonExtract(anyString(), anyString())).thenReturn("jsonExtract"); + + when(sqlBuilder.coalesce(anyString(), anyString())) + .thenAnswer(inv -> "coalesce(" + inv.getArgument(0) + ", " + inv.getArgument(1) + "))"); + + when(sqlBuilder.trim(anyString())).thenAnswer(inv -> "trim(" + inv.getArgument(0) + ")"); + when(tet.getTrackedEntityAttributes()).thenReturn(List.of(nonConfidentialTea, confidentialTea)); - Program program = mock(Program.class); when(program.getTrackedEntityType()).thenReturn(tet); when(trackedEntityTypeService.getAllTrackedEntityType()).thenReturn(List.of(tet)); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/model/AnalyticsTablePartitionTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/model/AnalyticsTablePartitionTest.java index aad4940ba21b..0a8a7a811916 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/model/AnalyticsTablePartitionTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/model/AnalyticsTablePartitionTest.java @@ -59,10 +59,12 @@ class AnalyticsTablePartitionTest { .selectExpression("value") .build()); + private final List sortKeyA = List.of("data"); + @Test void testGetName() { AnalyticsTable table = - new AnalyticsTable(AnalyticsTableType.DATA_VALUE, columnsA, Logged.UNLOGGED); + new AnalyticsTable(AnalyticsTableType.DATA_VALUE, columnsA, sortKeyA, Logged.UNLOGGED); List checks = List.of("value = 2023"); @@ -83,7 +85,7 @@ void testGetName() { @Test void testIsLatestPartition() { AnalyticsTable table = - new AnalyticsTable(AnalyticsTableType.COMPLETENESS, columnsA, Logged.UNLOGGED); + new AnalyticsTable(AnalyticsTableType.COMPLETENESS, columnsA, sortKeyA, Logged.UNLOGGED); AnalyticsTablePartition partition = new AnalyticsTablePartition( diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/model/AnalyticsTableTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/model/AnalyticsTableTest.java index 3bff29757f12..fb323ad82341 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/model/AnalyticsTableTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/model/AnalyticsTableTest.java @@ -37,6 +37,7 @@ import static org.hisp.dhis.db.model.constraint.Nullable.NULL; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; import org.hisp.dhis.analytics.AnalyticsTableType; @@ -67,10 +68,24 @@ class AnalyticsTableTest { .selectExpression("value") .build()); + private final List sortKeyA = List.of("data"); + + @Test + void testConstructor() { + AnalyticsTable table = + new AnalyticsTable(AnalyticsTableType.DATA_VALUE, columnsA, sortKeyA, Logged.UNLOGGED); + + assertEquals(AnalyticsTableType.DATA_VALUE, table.getTableType()); + assertTrue(table.getPrimaryKey().isEmpty()); + assertEquals(sortKeyA, table.getSortKey()); + assertTrue(table.getChecks().isEmpty()); + assertEquals(Logged.UNLOGGED, table.getLogged()); + } + @Test void testGetTableNameDataValue() { AnalyticsTable table = - new AnalyticsTable(AnalyticsTableType.DATA_VALUE, columnsA, Logged.UNLOGGED); + new AnalyticsTable(AnalyticsTableType.DATA_VALUE, columnsA, sortKeyA, Logged.UNLOGGED); assertEquals("analytics", table.getMainName()); assertEquals("analytics_temp", table.getName()); } @@ -78,7 +93,7 @@ void testGetTableNameDataValue() { @Test void testGetTableNameCompleteness() { AnalyticsTable table = - new AnalyticsTable(AnalyticsTableType.COMPLETENESS, columnsA, Logged.UNLOGGED); + new AnalyticsTable(AnalyticsTableType.COMPLETENESS, columnsA, sortKeyA, Logged.UNLOGGED); assertEquals("analytics_completeness", table.getMainName()); assertEquals("analytics_completeness_temp", table.getName()); } @@ -86,7 +101,8 @@ void testGetTableNameCompleteness() { @Test void testGetTableNameValidationResult() { AnalyticsTable table = - new AnalyticsTable(AnalyticsTableType.VALIDATION_RESULT, columnsA, Logged.UNLOGGED); + new AnalyticsTable( + AnalyticsTableType.VALIDATION_RESULT, columnsA, sortKeyA, Logged.UNLOGGED); assertEquals("analytics_validationresult", table.getMainName()); assertEquals("analytics_validationresult_temp", table.getName()); } @@ -151,7 +167,7 @@ void testGetDimensionAndFactColumns() { .build()); AnalyticsTable table = - new AnalyticsTable(AnalyticsTableType.DATA_VALUE, columns, Logged.UNLOGGED); + new AnalyticsTable(AnalyticsTableType.DATA_VALUE, columns, List.of(), Logged.UNLOGGED); assertEquals(3, table.getDimensionColumns().size()); assertEquals("dx", table.getDimensionColumns().get(0).getName()); @@ -189,9 +205,9 @@ void testGetTablePartitionName() { @Test void testEquals() { AnalyticsTable tableA = - new AnalyticsTable(AnalyticsTableType.DATA_VALUE, columnsA, Logged.UNLOGGED); + new AnalyticsTable(AnalyticsTableType.DATA_VALUE, columnsA, sortKeyA, Logged.UNLOGGED); AnalyticsTable tableB = - new AnalyticsTable(AnalyticsTableType.DATA_VALUE, columnsA, Logged.UNLOGGED); + new AnalyticsTable(AnalyticsTableType.DATA_VALUE, columnsA, sortKeyA, Logged.UNLOGGED); List uniqueList = new UniqueArrayList<>(); uniqueList.add(tableA); uniqueList.add(tableB); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/setting/AnalyticsTableSettingsTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/setting/AnalyticsTableSettingsTest.java index ef1bb6236b94..8a860b571a2e 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/setting/AnalyticsTableSettingsTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/setting/AnalyticsTableSettingsTest.java @@ -37,9 +37,9 @@ import org.hisp.dhis.external.conf.ConfigurationKey; import org.hisp.dhis.external.conf.DhisConfigurationProvider; import org.hisp.dhis.setting.SystemSettingsService; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -49,12 +49,7 @@ class AnalyticsTableSettingsTest { @Mock private SystemSettingsService systemSettings; - private AnalyticsTableSettings settings; - - @BeforeEach - public void before() { - settings = new AnalyticsTableSettings(config, systemSettings); - } + @InjectMocks private AnalyticsTableSettings settings; @Test void testGetAndValidateDatabase() { diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/TrackedEntityAnalyticsDimensionsServiceTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/TrackedEntityAnalyticsDimensionsServiceTest.java index 040a647c90ef..cb98c9e24b7c 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/TrackedEntityAnalyticsDimensionsServiceTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/TrackedEntityAnalyticsDimensionsServiceTest.java @@ -28,57 +28,43 @@ package org.hisp.dhis.analytics.trackedentity; import static java.util.Collections.emptySet; -import static org.hisp.dhis.analytics.common.AnalyticsDimensionsTestSupport.allValueTypeDataElements; -import static org.hisp.dhis.analytics.common.AnalyticsDimensionsTestSupport.allValueTypeTEAs; import static org.hisp.dhis.analytics.common.AnalyticsDimensionsTestSupport.trackedEntityType; import static org.hisp.dhis.analytics.common.DimensionServiceCommonTest.queryDisallowedValueTypesPredicate; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.Collection; -import org.hisp.dhis.analytics.event.data.DefaultEnrollmentAnalyticsDimensionsService; import org.hisp.dhis.common.BaseIdentifiableObject; import org.hisp.dhis.common.PrefixedDimension; import org.hisp.dhis.dataelement.DataElement; -import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramService; -import org.hisp.dhis.security.acl.AclService; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; import org.hisp.dhis.trackedentity.TrackedEntityTypeService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; -/** Unit tests for {@link TrackedEntityAnalyticsDimensionsService}. */ +@ExtendWith(MockitoExtension.class) class TrackedEntityAnalyticsDimensionsServiceTest { - private TrackedEntityAnalyticsDimensionsService trackedEntityAnalyticsDimensionsService; + @Mock private TrackedEntityTypeService trackedEntityTypeService; + + @Mock private ProgramService programService; + + @InjectMocks private DefaultTrackedEntityAnalyticsDimensionsService service; @BeforeEach void setup() { - ProgramService programService = mock(ProgramService.class); - Program program = mock(Program.class); - TrackedEntityTypeService trackedEntityTypeService = mock(TrackedEntityTypeService.class); - - when(programService.getProgram(any())).thenReturn(program); - when(program.getDataElements()).thenReturn(allValueTypeDataElements()); - when(program.getProgramIndicators()).thenReturn(emptySet()); - when(program.getTrackedEntityAttributes()).thenReturn(allValueTypeTEAs()); when(trackedEntityTypeService.getTrackedEntityType(any())).thenReturn(trackedEntityType()); - - trackedEntityAnalyticsDimensionsService = - new DefaultTrackedEntityAnalyticsDimensionsService( - trackedEntityTypeService, - new DefaultEnrollmentAnalyticsDimensionsService(programService, mock(AclService.class)), - programService); } @Test void testQueryDoesNotContainDisallowedValueTypes() { Collection analyticsDimensions = - trackedEntityAnalyticsDimensionsService - .getQueryDimensionsByTrackedEntityTypeId("aTeiId", emptySet()) - .stream() + service.getQueryDimensionsByTrackedEntityTypeId("aTeiId", emptySet()).stream() .map(PrefixedDimension::getItem) .toList(); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/TrackedEntityQueryRequestValidatorTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/TrackedEntityQueryRequestValidatorTest.java index 401d6acceae6..11e874343ee0 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/TrackedEntityQueryRequestValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/TrackedEntityQueryRequestValidatorTest.java @@ -44,38 +44,32 @@ class TrackedEntityQueryRequestValidatorTest { @Test void testValidateWhenTrackedEntityTypeIsInvalid() { String teiUid = CodeGenerator.generateUid() + "invalid"; - // Given TrackedEntityRequestParams trackedEntityRequestParams = new TrackedEntityRequestParams(teiUid); TrackedEntityQueryRequestValidator trackedEntityQueryRequestValidator = new TrackedEntityQueryRequestValidator(); - // When IllegalQueryException exception = assertThrows( IllegalQueryException.class, () -> trackedEntityQueryRequestValidator.validate(trackedEntityRequestParams)); - // Then assertEquals( "Invalid UID `" + teiUid + "` for property `trackedEntityType`", exception.getMessage()); } @Test void testValidateWhenNoTrackedEntityType() { - // Given TrackedEntityRequestParams trackedEntityRequestParams = new TrackedEntityRequestParams(null); TrackedEntityQueryRequestValidator trackedEntityQueryRequestValidator = new TrackedEntityQueryRequestValidator(); - // When IllegalQueryException exception = assertThrows( IllegalQueryException.class, () -> trackedEntityQueryRequestValidator.validate(trackedEntityRequestParams)); - // Then assertEquals("Invalid UID `null` for property `trackedEntityType`", exception.getMessage()); } } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/TrackedEntityRequestParamsMapperTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/TrackedEntityRequestParamsMapperTest.java index d7d088f3aed7..3b6159970d1a 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/TrackedEntityRequestParamsMapperTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/TrackedEntityRequestParamsMapperTest.java @@ -30,7 +30,6 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.Set; @@ -40,22 +39,19 @@ import org.hisp.dhis.program.ProgramService; import org.hisp.dhis.trackedentity.TrackedEntityType; import org.hisp.dhis.trackedentity.TrackedEntityTypeService; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +@ExtendWith(MockitoExtension.class) class TrackedEntityRequestParamsMapperTest { - private final TrackedEntityTypeService trackedEntityTypeService = - mock(TrackedEntityTypeService.class); + @Mock private TrackedEntityTypeService trackedEntityTypeService; - private final ProgramService programService = mock(ProgramService.class); + @Mock private ProgramService programService; - private TrackedEntityQueryRequestMapper trackedEntityQueryRequestMapper; - - @BeforeEach - public void setUp() { - trackedEntityQueryRequestMapper = - new TrackedEntityQueryRequestMapper(trackedEntityTypeService, programService); - } + @InjectMocks private TrackedEntityQueryRequestMapper mapper; @Test void testOneProgramFailing() { @@ -86,8 +82,7 @@ void testValidateTrackedEntityType(String trackedEntityTypeUid, String expectedM final IllegalQueryException thrown = assertThrows( - IllegalQueryException.class, - () -> trackedEntityQueryRequestMapper.map(trackedEntityTypeUid, requestParams)); + IllegalQueryException.class, () -> mapper.map(trackedEntityTypeUid, requestParams)); assertEquals(expectedMessage, thrown.getMessage()); } @@ -109,8 +104,7 @@ void testOK() { when(programService.getPrograms(Set.of("A", "B"))).thenReturn(Set.of(programA, programB)); - assertDoesNotThrow( - () -> trackedEntityQueryRequestMapper.map(trackedEntityTypeUid, requestParams)); + assertDoesNotThrow(() -> mapper.map(trackedEntityTypeUid, requestParams)); } @Test @@ -124,10 +118,7 @@ void testOKWhenNoPrograms() { when(trackedEntityTypeService.getTrackedEntityType(trackedEntityTypeUid)) .thenReturn(trackedEntityType); - when(programService.getPrograms(Set.of("A", "B"))).thenReturn(Set.of()); - - assertDoesNotThrow( - () -> trackedEntityQueryRequestMapper.map(trackedEntityTypeUid, requestParams)); + assertDoesNotThrow(() -> mapper.map(trackedEntityTypeUid, requestParams)); } private Program stubProgram(String uid, String tetUid) { diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/query/StatusConditionTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/query/StatusConditionTest.java index 11e563192ba8..6be1c6d0a446 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/query/StatusConditionTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/query/StatusConditionTest.java @@ -46,7 +46,6 @@ class StatusConditionTest { @Test void testProgramStatusCompletedProduceCorrectSql() { - // SETUP List values = List.of("COMPLETED"); DimensionIdentifier dimensionIdentifier = @@ -56,7 +55,6 @@ void testProgramStatusCompletedProduceCorrectSql() { DimensionParam.StaticDimension.ENROLLMENT_STATUS, values); - // CALL SqlParameterManager sqlParameterManager = new SqlParameterManager(); QueryContext queryContext = QueryContext.of(null, sqlParameterManager); @@ -70,7 +68,6 @@ void testProgramStatusCompletedProduceCorrectSql() { @Test void testProgramStatusCompletedActiveProduceCorrectSql() { - // SETUP List values = List.of("COMPLETED", "ACTIVE"); DimensionIdentifier dimensionIdentifier = @@ -80,7 +77,6 @@ void testProgramStatusCompletedActiveProduceCorrectSql() { DimensionParam.StaticDimension.ENROLLMENT_STATUS, values); - // CALL SqlParameterManager sqlParameterManager = new SqlParameterManager(); QueryContext queryContext = QueryContext.of(null, sqlParameterManager); @@ -94,14 +90,12 @@ void testProgramStatusCompletedActiveProduceCorrectSql() { @Test void testEventStatusCompletedProduceCorrectSql() { - // SETUP List values = List.of("COMPLETED"); DimensionIdentifier dimensionIdentifier = getProgramAttributeDimensionIdentifier( "programUid", "programStageUid", DimensionParam.StaticDimension.EVENT_STATUS, values); - // CALL SqlParameterManager sqlParameterManager = new SqlParameterManager(); QueryContext queryContext = QueryContext.of(null, sqlParameterManager); @@ -115,14 +109,12 @@ void testEventStatusCompletedProduceCorrectSql() { @Test void testEventStatusCompletedScheduleProduceCorrectSql() { - // SETUP List values = List.of("COMPLETED", "SCHEDULE"); DimensionIdentifier dimensionIdentifier = getProgramAttributeDimensionIdentifier( "programUid", "programStageUid", DimensionParam.StaticDimension.EVENT_STATUS, values); - // CALL SqlParameterManager sqlParameterManager = new SqlParameterManager(); QueryContext queryContext = QueryContext.of(null, sqlParameterManager); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/query/TrackedEntityAttributeConditionTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/query/TrackedEntityAttributeConditionTest.java index d8f15c6cffa3..21bd12db5e88 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/query/TrackedEntityAttributeConditionTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/query/TrackedEntityAttributeConditionTest.java @@ -46,13 +46,11 @@ class TrackedEntityAttributeConditionTest { @Test void testProgramAttributeConditionProduceCorrectSql() { - // SETUP String attr = "attr"; List values = List.of("eq:v1"); DimensionIdentifier dimensionIdentifier = getProgramAttributeDimensionIdentifier("attr", values); - // CALL SqlParameterManager sqlParameterManager = new SqlParameterManager(); QueryContext queryContext = QueryContext.of(null, sqlParameterManager); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/query/context/querybuilder/SqlQueryHelperTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/query/context/querybuilder/SqlQueryHelperTest.java index 3bd06e1d8ae6..289140c447d3 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/query/context/querybuilder/SqlQueryHelperTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/trackedentity/query/context/querybuilder/SqlQueryHelperTest.java @@ -41,18 +41,27 @@ import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramStage; import org.hisp.dhis.trackedentity.TrackedEntityType; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; -/** Unit tests for {@link SqlQueryHelper}. */ +@ExtendWith(MockitoExtension.class) @SuppressWarnings("unchecked") class SqlQueryHelperTest { - @Test - void test_throws_when_undetected_type() { - DimensionParam dimensionParam = mock(DimensionParam.class); - DimensionIdentifier testedDimension = mock(DimensionIdentifier.class); + @Mock private DimensionParam dimensionParam; + + @Mock private DimensionIdentifier testedDimension; + + @BeforeEach + void beforeEach() { when(testedDimension.getDimension()).thenReturn(dimensionParam); + } + @Test + void test_throws_when_undetected_type() { assertThrows( IllegalArgumentException.class, () -> SqlQueryHelper.buildOrderSubQuery(testedDimension, () -> "field")); @@ -64,10 +73,6 @@ void test_throws_when_undetected_type() { @Test void test_subQuery_TE() { - DimensionParam dimensionParam = mock(DimensionParam.class); - DimensionIdentifier testedDimension = mock(DimensionIdentifier.class); - when(testedDimension.getDimension()).thenReturn(dimensionParam); - when(testedDimension.isTeDimension()).thenReturn(true); assertEquals( @@ -80,9 +85,6 @@ void test_subQuery_TE() { @Test void test_subQuery_enrollment() { - DimensionParam dimensionParam = mock(DimensionParam.class); - DimensionIdentifier testedDimension = mock(DimensionIdentifier.class); - when(testedDimension.getDimension()).thenReturn(dimensionParam); when(testedDimension.getPrefix()).thenReturn("prefix"); TrackedEntityType trackedEntityType = mock(TrackedEntityType.class); @@ -125,9 +127,6 @@ void test_subQuery_enrollment() { @Test void test_subQuery_event() { - DimensionParam dimensionParam = mock(DimensionParam.class); - DimensionIdentifier testedDimension = mock(DimensionIdentifier.class); - when(testedDimension.getDimension()).thenReturn(dimensionParam); when(testedDimension.getPrefix()).thenReturn("prefix"); TrackedEntityType trackedEntityType = mock(TrackedEntityType.class); @@ -190,12 +189,7 @@ where exists(select 1 @Test void test_subQuery_data_element() { - DimensionParam dimensionParam = mock(DimensionParam.class); - when(dimensionParam.isOfType(any())).thenReturn(true); - - DimensionIdentifier testedDimension = mock(DimensionIdentifier.class); - when(testedDimension.getDimension()).thenReturn(dimensionParam); when(testedDimension.getPrefix()).thenReturn("prefix"); TrackedEntityType trackedEntityType = mock(TrackedEntityType.class); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/util/AnalyticsColumnAsserter.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/util/AnalyticsColumnAsserter.java index 06218354156b..ef6e06592070 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/util/AnalyticsColumnAsserter.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/util/AnalyticsColumnAsserter.java @@ -30,23 +30,22 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import lombok.RequiredArgsConstructor; +import lombok.Setter; import org.hisp.dhis.analytics.table.model.AnalyticsTableColumn; /** * @author Luciano Fiandesio */ +@Setter public class AnalyticsColumnAsserter { /** The analytics table column to verify. */ private AnalyticsTableColumn actual; - private void setActual(AnalyticsTableColumn actual) { - this.actual = actual; - } - public void verify(AnalyticsTableColumn expected) { assertThat("Column name does not match", expected.getName(), is(actual.getName())); assertThat( - "Column alias does not match!", + "Column " + actual.getName() + " expression does not match!", expected.getSelectExpression(), is(actual.getSelectExpression())); assertThat( @@ -62,12 +61,9 @@ public void verify(AnalyticsTableColumn expected) { is(actual.getIndexType())); } + @RequiredArgsConstructor public static class Builder { - AnalyticsTableColumn _column; - - public Builder(AnalyticsTableColumn column) { - _column = column; - } + final AnalyticsTableColumn _column; public AnalyticsColumnAsserter build() { AnalyticsColumnAsserter asserter = new AnalyticsColumnAsserter(); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/util/AnalyticsIndexHelperTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/util/AnalyticsIndexHelperTest.java index 7911b14e61d2..3de409d30405 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/util/AnalyticsIndexHelperTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/util/AnalyticsIndexHelperTest.java @@ -103,6 +103,6 @@ private AnalyticsTable stubAnalyticsTable() { .indexType(BTREE) .build()); - return new AnalyticsTable(EVENT, columns, Logged.UNLOGGED); + return new AnalyticsTable(EVENT, columns, List.of(), Logged.UNLOGGED); } } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/util/AnalyticsPeriodCriteriaUtilsTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/util/AnalyticsPeriodCriteriaUtilsTest.java index 7626a7f5bad7..c42379f577d6 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/util/AnalyticsPeriodCriteriaUtilsTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/util/AnalyticsPeriodCriteriaUtilsTest.java @@ -48,7 +48,7 @@ void testDefineDefaultPeriodForCriteria_without_period() { // when AnalyticsPeriodCriteriaUtils.defineDefaultPeriodForCriteria( - criteria, periodDataProvider, PeriodDataProvider.DataSource.SYSTEM_DEFINED); + criteria, periodDataProvider, PeriodDataProvider.PeriodSource.SYSTEM_DEFINED); // then assertEquals( @@ -69,7 +69,7 @@ void testDefineDefaultPeriodForCriteria_with_period() { // when AnalyticsPeriodCriteriaUtils.defineDefaultPeriodForCriteria( - criteria, periodDataProvider, PeriodDataProvider.DataSource.SYSTEM_DEFINED); + criteria, periodDataProvider, PeriodDataProvider.PeriodSource.SYSTEM_DEFINED); // then assertNull(criteria.getStartDate()); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/util/AnalyticsTableAsserter.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/util/AnalyticsTableAsserter.java index f536a8a0b04c..682240cc5185 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/util/AnalyticsTableAsserter.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/util/AnalyticsTableAsserter.java @@ -39,6 +39,8 @@ import java.util.Map; import java.util.function.Consumer; import java.util.stream.Collectors; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import org.hisp.dhis.analytics.AnalyticsTableType; import org.hisp.dhis.analytics.table.model.AnalyticsTable; import org.hisp.dhis.analytics.table.model.AnalyticsTableColumn; @@ -49,6 +51,7 @@ /** * @author Luciano Fiandesio */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class AnalyticsTableAsserter { /** The analytics table to verify. */ private AnalyticsTable table; @@ -67,8 +70,6 @@ public class AnalyticsTableAsserter { private String mainName; - private AnalyticsTableAsserter() {} - public void verify() { // verify column size assertThat(table.getDimensionColumns(), hasSize(columnsSize)); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/model/ColumnTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/model/ColumnTest.java index a8079fdbbee7..e1d2f48443c5 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/model/ColumnTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/model/ColumnTest.java @@ -34,7 +34,6 @@ import org.junit.jupiter.api.Test; class ColumnTest { - @Test void testIsNotNull() { Column colA = new Column("dx", DataType.CHARACTER_11, Nullable.NOT_NULL); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/model/IndexTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/model/IndexTest.java index 53d77d00775e..c7d46a847a70 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/model/IndexTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/model/IndexTest.java @@ -60,13 +60,10 @@ void testIsUnique() { @Test void testDefaults() { - // given Index.IndexBuilder builder = Index.builder(); - // when Index index = builder.build(); - // then assertNull(index.getName()); assertNull(index.getTableName()); assertNull(index.getCondition()); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/model/TableTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/model/TableTest.java index 2d33b3a6b2a2..bc57ee6db4cc 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/model/TableTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/model/TableTest.java @@ -59,8 +59,8 @@ void testFromStagingTable() { void testIsUnlogged() { List columns = List.of(colA, colB); - Table tableA = new Table("analytics", columns, List.of(), List.of(), Logged.UNLOGGED); - Table tableB = new Table("analytics", columns, List.of(), List.of(), Logged.LOGGED); + Table tableA = new Table("analytics", columns, List.of(), Logged.UNLOGGED); + Table tableB = new Table("analytics", columns, List.of(), Logged.LOGGED); assertTrue(tableA.isUnlogged()); assertFalse(tableB.isUnlogged()); @@ -87,6 +87,7 @@ void testHasPrimaryKey() { assertTrue(tableA.hasPrimaryKey()); assertFalse(tableB.hasPrimaryKey()); + assertEquals(List.of("dx"), tableA.getPrimaryKey()); } @Test @@ -96,6 +97,23 @@ void testGetFirstPrimaryKey() { assertEquals("dx", table.getFirstPrimaryKey()); } + @Test + void testHasSortKey() { + Table tableA = + new Table( + "analytics", + List.of(colA, colB), + List.of("dx", "value"), + List.of("dx"), + List.of(), + Logged.UNLOGGED); + Table tableB = new Table("analytics", List.of(colA, colB), List.of("dx", "value")); + + assertTrue(tableA.hasSortKey()); + assertFalse(tableB.hasSortKey()); + assertEquals(List.of("dx"), tableA.getSortKey()); + } + @Test void testSuccessfulValidation() { List columns = List.of(colA); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/sql/ClickHouseSqlBuilderTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/sql/ClickHouseSqlBuilderTest.java new file mode 100644 index 000000000000..5087172105e4 --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/sql/ClickHouseSqlBuilderTest.java @@ -0,0 +1,381 @@ +/* + * Copyright (c) 2004-2024, 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.db.sql; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.Map; +import org.hisp.dhis.db.model.Collation; +import org.hisp.dhis.db.model.Column; +import org.hisp.dhis.db.model.DataType; +import org.hisp.dhis.db.model.Logged; +import org.hisp.dhis.db.model.Table; +import org.hisp.dhis.db.model.constraint.Nullable; +import org.junit.jupiter.api.Test; + +class ClickHouseSqlBuilderTest { + private final ClickHouseSqlBuilder sqlBuilder = new ClickHouseSqlBuilder(); + + private Table getTableA() { + List columns = + List.of( + new Column("id", DataType.BIGINT, Nullable.NOT_NULL), + new Column("data", DataType.CHARACTER_11, Nullable.NOT_NULL), + new Column("period", DataType.VARCHAR_50, Nullable.NOT_NULL), + new Column("created", DataType.TIMESTAMP), + new Column("user", DataType.JSONB), + new Column("value", DataType.DOUBLE)); + + List primaryKey = List.of("id"); + + return new Table("immunization", columns, primaryKey, Logged.LOGGED); + } + + private Table getTableB() { + List columns = + List.of( + new Column("id", DataType.INTEGER, Nullable.NOT_NULL), + new Column("facility_type", DataType.VARCHAR_255, Nullable.NULL, Collation.C), + new Column("bcg_doses", DataType.DOUBLE)); + + return new Table("vaccination", columns, List.of()); + } + + private Table getTableC() { + List columns = + List.of( + new Column("id", DataType.BIGINT, Nullable.NOT_NULL), + new Column("vitamin_a", DataType.BIGINT), + new Column("vitamin_d", DataType.BIGINT)); + + List primaryKey = List.of("id"); + + return new Table("nutrition", columns, primaryKey, List.of(), Logged.LOGGED, getTableB()); + } + + private Table getTableD() { + List columns = + List.of( + new Column("id", DataType.BIGINT, Nullable.NOT_NULL), + new Column("data", DataType.CHARACTER_11, Nullable.NOT_NULL), + new Column("period", DataType.VARCHAR_50, Nullable.NOT_NULL), + new Column("value", DataType.DOUBLE)); + + List sortKey = List.of("data", "period"); + + return new Table("immunization", columns, List.of(), sortKey, List.of(), Logged.LOGGED); + } + + // Data types + + @Test + void testDataTypes() { + assertEquals("Float64", sqlBuilder.dataTypeDouble()); + assertEquals("DateTime64(3)", sqlBuilder.dataTypeTimestamp()); + } + + // Index types + + @Test + void testIndexTypes() { + assertThrows(UnsupportedOperationException.class, () -> sqlBuilder.indexTypeBtree()); + } + + // Capabilities + + @Test + void testSupportsAnalyze() { + assertFalse(sqlBuilder.supportsAnalyze()); + } + + @Test + void testSupportsVacuum() { + assertFalse(sqlBuilder.supportsVacuum()); + } + + // Utilities + + @Test + void testQuote() { + assertEquals( + "\"Treated \"\"malaria\"\" at facility\"", + sqlBuilder.quote("Treated \"malaria\" at facility")); + assertEquals( + "\"\"\"Patients on \"\"treatment\"\" for TB\"\"\"", + sqlBuilder.quote("\"Patients on \"treatment\" for TB\"")); + assertEquals("\"quarterly\"", sqlBuilder.quote("quarterly")); + assertEquals("\"Fully immunized\"", sqlBuilder.quote("Fully immunized")); + } + + @Test + void testQuoteAlias() { + assertEquals( + "ax.\"Treated \"\"malaria\"\" at facility\"", + sqlBuilder.quote("ax", "Treated \"malaria\" at facility")); + assertEquals( + "analytics.\"Patients on \"\"treatment\"\" for TB\"", + sqlBuilder.quote("analytics", "Patients on \"treatment\" for TB")); + assertEquals("analytics.\"quarterly\"", sqlBuilder.quote("analytics", "quarterly")); + assertEquals("dv.\"Fully immunized\"", sqlBuilder.quote("dv", "Fully immunized")); + } + + @Test + void testQuoteAx() { + assertEquals( + "ax.\"Treated \"\"malaria\"\" at facility\"", + sqlBuilder.quoteAx("Treated \"malaria\" at facility")); + assertEquals("ax.\"quarterly\"", sqlBuilder.quoteAx("quarterly")); + assertEquals("ax.\"Fully immunized\"", sqlBuilder.quoteAx("Fully immunized")); + } + + @Test + void testSingleQuote() { + assertEquals("'jkhYg65ThbF'", sqlBuilder.singleQuote("jkhYg65ThbF")); + assertEquals("'Age ''<5'' years'", sqlBuilder.singleQuote("Age '<5' years")); + assertEquals("'Status \"not checked\"'", sqlBuilder.singleQuote("Status \"not checked\"")); + } + + @Test + void testEscape() { + assertEquals("Age group ''under 5'' years", sqlBuilder.escape("Age group 'under 5' years")); + assertEquals("Level ''high'' found", sqlBuilder.escape("Level 'high' found")); + } + + @Test + void testSinqleQuotedCommaDelimited() { + assertEquals( + "'dmPbDBKwXyF', 'zMl4kciwJtz', 'q1Nqu1r1GTn'", + sqlBuilder.singleQuotedCommaDelimited( + List.of("dmPbDBKwXyF", "zMl4kciwJtz", "q1Nqu1r1GTn"))); + assertEquals("'1', '3', '5'", sqlBuilder.singleQuotedCommaDelimited(List.of("1", "3", "5"))); + assertEquals("", sqlBuilder.singleQuotedCommaDelimited(List.of())); + assertEquals("", sqlBuilder.singleQuotedCommaDelimited(null)); + } + + @Test + void testQualifyTable() { + assertEquals("postgresql(\"pg_dhis\", table='category')", sqlBuilder.qualifyTable("category")); + assertEquals( + "postgresql(\"pg_dhis\", table='categories_options')", + sqlBuilder.qualifyTable("categories_options")); + } + + @Test + void testDateTrunc() { + assertEquals( + "date_trunc('month', pe.startdate)", sqlBuilder.dateTrunc("month", "pe.startdate")); + } + + @Test + void testDifferenceInSeconds() { + assertEquals( + "(toUnixTimestamp(a.startdate) - toUnixTimestamp(b.enddate))", + sqlBuilder.differenceInSeconds("a.startdate", "b.enddate")); + assertEquals( + "(toUnixTimestamp(a.\"startdate\") - toUnixTimestamp(b.\"enddate\"))", + sqlBuilder.differenceInSeconds( + sqlBuilder.quote("a", "startdate"), sqlBuilder.quote("b", "enddate"))); + } + + @Test + void testRegexpMatch() { + assertEquals("match(value, 'test')", sqlBuilder.regexpMatch("value", "'test'")); + assertEquals("match(number, '\\d')", sqlBuilder.regexpMatch("number", "'\\d'")); + assertEquals("match(color, '^Blue$')", sqlBuilder.regexpMatch("color", "'^Blue$'")); + assertEquals("match(id, '[a-z]\\w+\\d{3}')", sqlBuilder.regexpMatch("id", "'[a-z]\\w+\\d{3}'")); + } + + @Test + void testJsonExtract() { + assertEquals( + "JSONExtractString(value, 'D7m8vpzxHDJ')", sqlBuilder.jsonExtract("value", "D7m8vpzxHDJ")); + } + + @Test + void testJsonExtractNested() { + assertEquals( + "JSONExtractString(eventdatavalues, 'D7m8vpzxHDJ.value')", + sqlBuilder.jsonExtractNested("eventdatavalues", "D7m8vpzxHDJ", "value")); + } + + // Statements + + @Test + void testCreateTableA() { + Table table = getTableA(); + + String expected = + """ + create table "immunization" ("id" Int64 not null,"data" String not null,\ + "period" String not null,"created" DateTime64(3) null,"user" JSON null,\ + "value" Float64 null) \ + engine = MergeTree() \ + order by ("id");"""; + + assertEquals(expected, sqlBuilder.createTable(table)); + } + + @Test + void testCreateTableB() { + Table table = getTableB(); + + String expected = + """ + create table "vaccination" ("id" Int32 not null,\ + "facility_type" String null,"bcg_doses" Float64 null) \ + engine = MergeTree() \ + order by ("id");"""; + + assertEquals(expected, sqlBuilder.createTable(table)); + } + + @Test + void testCreateTableC() { + Table table = getTableC(); + + String expected = + """ + create table "nutrition" ("id" Int64 not null,"vitamin_a" Int64 null,\ + "vitamin_d" Int64 null) \ + engine = MergeTree() order by ("id");"""; + + assertEquals(expected, sqlBuilder.createTable(table)); + } + + @Test + void testCreateTableD() { + Table table = getTableD(); + + String expected = + """ + create table "immunization" ("id" Int64 not null,"data" String not null,\ + "period" String not null,"value" Float64 null) \ + engine = MergeTree() \ + order by ("data","period");"""; + + assertEquals(expected, sqlBuilder.createTable(table)); + } + + @Test + void testRenameTable() { + Table table = getTableA(); + + String expected = "rename table \"immunization\" to \"vaccination\";"; + + assertEquals(expected, sqlBuilder.renameTable(table, "vaccination")); + } + + @Test + void testDropTableIfExists() { + Table table = getTableA(); + + String expected = "drop table if exists \"immunization\";"; + + assertEquals(expected, sqlBuilder.dropTableIfExists(table)); + } + + @Test + void testDropTableIfExistsString() { + String expected = "drop table if exists \"immunization\";"; + + assertEquals(expected, sqlBuilder.dropTableIfExists("immunization")); + } + + @Test + void testDropTableIfExistsCascade() { + Table table = getTableA(); + + String expected = "drop table if exists \"immunization\";"; + + assertEquals(expected, sqlBuilder.dropTableIfExistsCascade(table)); + } + + @Test + void testDropTableIfExistsCascadeString() { + String expected = "drop table if exists \"immunization\";"; + + assertEquals(expected, sqlBuilder.dropTableIfExistsCascade("immunization")); + } + + @Test + void testSwapTable() { + String expected = + """ + drop table if exists "vaccination"; \ + rename table "immunization" to "vaccination";"""; + + assertEquals(expected, sqlBuilder.swapTable(getTableA(), "vaccination")); + } + + @Test + void testTableExists() { + String expected = + """ + select t.name as table_name \ + from system.tables t \ + where t.database = 'default' \ + and t.name = 'immunization' \ + and engine not in ('View', 'Materialized View');"""; + + assertEquals(expected, sqlBuilder.tableExists("immunization")); + } + + @Test + void testCountRows() { + String expected = + """ + select count(*) as row_count from \"immunization\";"""; + + assertEquals(expected, sqlBuilder.countRows(getTableA())); + } + + // Named collection + + @Test + void testCreateNamedCollection() { + String expected = + """ + create named collection "pg_dhis" as """; + + assertTrue( + sqlBuilder + .createNamedCollection("pg_dhis", Map.of("host", "mydomain.org")) + .startsWith(expected)); + } + + @Test + void testDropNamedCollectionIfExists() { + assertEquals( + "drop named collection if exists \"pg_dhis\";", + sqlBuilder.dropNamedCollectionIfExists("pg_dhis")); + } +} diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/sql/DorisSqlBuilderTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/sql/DorisSqlBuilderTest.java index a6597c2a4755..ad372141c9bc 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/sql/DorisSqlBuilderTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/sql/DorisSqlBuilderTest.java @@ -65,9 +65,7 @@ private Table getTableB() { new Column("facility_type", DataType.VARCHAR_255, Nullable.NULL, Collation.C), new Column("bcg_doses", DataType.DOUBLE)); - List checks = List.of("\"id\">0", "\"bcg_doses\">0"); - - return new Table("vaccination", columns, List.of(), checks, Logged.UNLOGGED); + return new Table("vaccination", columns, List.of(), Logged.UNLOGGED); } private Table getTableC() { @@ -180,6 +178,39 @@ void testDateTrunc() { "date_trunc(pe.startdate, 'month')", sqlBuilder.dateTrunc("month", "pe.startdate")); } + @Test + void testDifferenceInSeconds() { + assertEquals( + "(unix_timestamp(a.startdate) - unix_timestamp(b.enddate))", + sqlBuilder.differenceInSeconds("a.startdate", "b.enddate")); + assertEquals( + "(unix_timestamp(a.`startdate`) - unix_timestamp(b.`enddate`))", + sqlBuilder.differenceInSeconds( + sqlBuilder.quote("a", "startdate"), sqlBuilder.quote("b", "enddate"))); + } + + @Test + void testRegexpMatch() { + assertEquals("value regexp 'test'", sqlBuilder.regexpMatch("value", "'test'")); + assertEquals("number regexp '\\d'", sqlBuilder.regexpMatch("number", "'\\d'")); + assertEquals("color regexp '^Blue$'", sqlBuilder.regexpMatch("color", "'^Blue$'")); + assertEquals("id regexp '[a-z]\\w+\\d{3}'", sqlBuilder.regexpMatch("id", "'[a-z]\\w+\\d{3}'")); + } + + @Test + void testJsonExtract() { + assertEquals( + "json_unquote(json_extract(value, '$.D7m8vpzxHDJ'))", + sqlBuilder.jsonExtract("value", "D7m8vpzxHDJ")); + } + + @Test + void testJsonExtractNested() { + assertEquals( + "json_unquote(json_extract(eventdatavalues, '$.D7m8vpzxHDJ.value'))", + sqlBuilder.jsonExtractNested("eventdatavalues", "D7m8vpzxHDJ", "value")); + } + // Statements @Test @@ -188,9 +219,9 @@ void testCreateTableA() { String expected = """ - create table `immunization` (`id` bigint not null, \ - `data` char(11) not null, `period` varchar(50) not null, \ - `created` datetime null, `user` json null, `value` double null) \ + create table `immunization` (`id` bigint not null,\ + `data` char(11) not null,`period` varchar(50) not null,\ + `created` datetime null,`user` json null,`value` double null) \ engine = olap \ unique key (`id`) \ distributed by hash(`id`) buckets 10 \ @@ -205,8 +236,8 @@ void testCreateTableB() { String expected = """ - create table `vaccination` (`id` int not null, \ - `facility_type` varchar(255) null, `bcg_doses` double null) \ + create table `vaccination` (`id` int not null,\ + `facility_type` varchar(255) null,`bcg_doses` double null) \ engine = olap \ duplicate key (`id`) \ distributed by hash(`id`) buckets 10 \ @@ -223,8 +254,8 @@ void testCreateTableC() { String expected = """ - create table `nutrition` (`id` bigint not null, \ - `vitamin_a` bigint null, `vitamin_d` bigint null) \ + create table `nutrition` (`id` bigint not null,\ + `vitamin_a` bigint null,`vitamin_d` bigint null) \ engine = olap \ unique key (`id`) \ distributed by hash(`id`) buckets 10 \ @@ -266,7 +297,8 @@ void testDropCatalogIfExists() { void testRenameTable() { Table table = getTableA(); - String expected = """ + String expected = + """ alter table `immunization` rename `immunization_main`;"""; assertEquals(expected, sqlBuilder.renameTable(table, "immunization_main")); @@ -326,7 +358,8 @@ void testTableExists() { @Test void testCountRows() { - String expected = """ + String expected = + """ select count(*) as row_count from `immunization`;"""; assertEquals(expected, sqlBuilder.countRows(getTableA())); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/sql/PostgreSqlBuilderTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/sql/PostgreSqlBuilderTest.java index 4ca94e8fd526..2d621ae766e4 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/sql/PostgreSqlBuilderTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/db/sql/PostgreSqlBuilderTest.java @@ -96,7 +96,7 @@ private Table getTableB() { List checks = List.of("\"id\">0", "\"bcg_doses\">0"); - return new Table("vaccination", columns, List.of(), checks, Logged.UNLOGGED); + return new Table("vaccination", columns, List.of(), List.of(), checks, Logged.UNLOGGED); } private Table getTableC() { @@ -206,6 +206,37 @@ void testDateTrunc() { "date_trunc('month', pe.startdate)", sqlBuilder.dateTrunc("month", "pe.startdate")); } + @Test + void testDifferenceInSeconds() { + assertEquals( + "extract(epoch from (a.startdate - b.enddate))", + sqlBuilder.differenceInSeconds("a.startdate", "b.enddate")); + assertEquals( + "extract(epoch from (a.\"startdate\" - b.\"enddate\"))", + sqlBuilder.differenceInSeconds( + sqlBuilder.quote("a", "startdate"), sqlBuilder.quote("b", "enddate"))); + } + + @Test + void testRegexpMatch() { + assertEquals("value ~* 'test'", sqlBuilder.regexpMatch("value", "'test'")); + assertEquals("number ~* '\\d'", sqlBuilder.regexpMatch("number", "'\\d'")); + assertEquals("color ~* '^Blue$'", sqlBuilder.regexpMatch("color", "'^Blue$'")); + assertEquals("id ~* '[a-z]\\w+\\d{3}'", sqlBuilder.regexpMatch("id", "'[a-z]\\w+\\d{3}'")); + } + + @Test + void testJsonExtract() { + assertEquals("value ->> 'D7m8vpzxHDJ'", sqlBuilder.jsonExtract("value", "D7m8vpzxHDJ")); + } + + @Test + void testJsonExtractNested() { + assertEquals( + "eventdatavalues #>> '{D7m8vpzxHDJ, value}'", + sqlBuilder.jsonExtractNested("eventdatavalues", "D7m8vpzxHDJ", "value")); + } + // Statements @Test @@ -351,7 +382,8 @@ void testTableExists() { @Test void testCountRows() { - String expected = """ + String expected = + """ select count(*) as row_count from "immunization";"""; assertEquals(expected, sqlBuilder.countRows(getTableA())); @@ -372,7 +404,7 @@ void testCreateIndexB() { List indexes = getIndexesA(); String expected = - "create index \"in_immunization_period_created\" on \"immunization\" using btree(\"period\", \"created\");"; + "create index \"in_immunization_period_created\" on \"immunization\" using btree(\"period\",\"created\");"; assertEquals(expected, sqlBuilder.createIndex(indexes.get(1))); } @@ -392,7 +424,7 @@ void testCreateIndexD() { List indexes = getIndexesA(); String expected = - "create index \"in_immunization_data_period\" on \"immunization\" using btree(lower(\"data\"), lower(\"period\"));"; + "create index \"in_immunization_data_period\" on \"immunization\" using btree(lower(\"data\"),lower(\"period\"));"; assertEquals(expected, sqlBuilder.createIndex(indexes.get(3))); } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/resourcetable/DefaultResourceTableServiceTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/resourcetable/DefaultResourceTableServiceTest.java index 894393258de9..a20545a91410 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/resourcetable/DefaultResourceTableServiceTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/resourcetable/DefaultResourceTableServiceTest.java @@ -28,7 +28,7 @@ package org.hisp.dhis.resourcetable; import static java.time.temporal.ChronoUnit.YEARS; -import static org.hisp.dhis.period.PeriodDataProvider.DataSource.DATABASE; +import static org.hisp.dhis.period.PeriodDataProvider.PeriodSource.DATABASE; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -38,6 +38,7 @@ import java.util.List; import org.hisp.dhis.analytics.table.setting.AnalyticsTableSettings; import org.hisp.dhis.period.PeriodDataProvider; +import org.hisp.dhis.period.PeriodDataProvider.PeriodSource; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -60,8 +61,9 @@ void generateDatePeriodTableWhenYearIsOutOfRange() { List yearsToCheck = List.of(2000, 2001, 2002, 2003, 2004); int defaultOffset = 22; - when(periodDataProvider.getAvailableYears(DATABASE)).thenReturn(yearsToCheck); + when(analyticsTableSettings.getPeriodSource()).thenReturn(PeriodSource.DATABASE); when(analyticsTableSettings.getMaxPeriodYearsOffset()).thenReturn(defaultOffset); + when(periodDataProvider.getAvailableYears(DATABASE)).thenReturn(yearsToCheck); RuntimeException exception = assertThrows( @@ -77,8 +79,9 @@ void generateDatePeriodTableWhenOffsetIsZeroWithPreviousYears() { List yearsToCheck = List.of(2000, 2001, 2002, 2003, 2004); int zeroOffset = 0; - when(periodDataProvider.getAvailableYears(DATABASE)).thenReturn(yearsToCheck); + when(analyticsTableSettings.getPeriodSource()).thenReturn(PeriodSource.DATABASE); when(analyticsTableSettings.getMaxPeriodYearsOffset()).thenReturn(zeroOffset); + when(periodDataProvider.getAvailableYears(DATABASE)).thenReturn(yearsToCheck); RuntimeException exception = assertThrows( @@ -94,8 +97,9 @@ void generateDatePeriodTableWhenOffsetIsZeroWithCurrentYear() { List yearsToCheck = List.of(Year.now().getValue()); int zeroOffset = 0; - when(periodDataProvider.getAvailableYears(DATABASE)).thenReturn(yearsToCheck); + when(analyticsTableSettings.getPeriodSource()).thenReturn(PeriodSource.DATABASE); when(analyticsTableSettings.getMaxPeriodYearsOffset()).thenReturn(zeroOffset); + when(periodDataProvider.getAvailableYears(DATABASE)).thenReturn(yearsToCheck); assertDoesNotThrow(() -> defaultResourceTableService.getAndValidateAvailableDataYears()); } @@ -109,8 +113,9 @@ void generateDatePeriodTableWhenYearsAreInExpectedRange() { Year.now().plus(2, YEARS).getValue()); int defaultOffset = 2; - when(periodDataProvider.getAvailableYears(DATABASE)).thenReturn(yearsToCheck); + when(analyticsTableSettings.getPeriodSource()).thenReturn(PeriodSource.DATABASE); when(analyticsTableSettings.getMaxPeriodYearsOffset()).thenReturn(defaultOffset); + when(periodDataProvider.getAvailableYears(DATABASE)).thenReturn(yearsToCheck); assertDoesNotThrow(() -> defaultResourceTableService.getAndValidateAvailableDataYears()); } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/resourcetable/table/OrganisationUnitStructureResourceTableTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/resourcetable/table/OrganisationUnitStructureResourceTableTest.java index be8d39e20a8d..2080837b84e6 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/resourcetable/table/OrganisationUnitStructureResourceTableTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/resourcetable/table/OrganisationUnitStructureResourceTableTest.java @@ -28,19 +28,18 @@ package org.hisp.dhis.resourcetable.table; import static org.hisp.dhis.test.TestBase.createOrganisationUnit; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.ArrayList; import java.util.List; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.junit.jupiter.api.Test; -/** Unit tests for {@link OrganisationUnitStructureResourceTable}. */ class OrganisationUnitStructureResourceTableTest { - @Test void testCreateBatchObjectsWhenLevelsAreSame() { - // Given int maxOrgUnitLevels = 3; int currentLevel = 3; @@ -65,13 +64,11 @@ void testCreateBatchObjectsWhenLevelsAreSame() { OrganisationUnitStructureResourceTable resourceTable = new OrganisationUnitStructureResourceTable(null, maxOrgUnitLevels, null); - // Then assertDoesNotThrow(() -> resourceTable.createBatchObjects(organisationUnits, currentLevel)); } @Test void testCreateBatchObjectsWhenHierarchyLevelIsLowerThanMaxLevel() { - // Given int maxOrgUnitLevels = 3; int currentLevel = 2; @@ -87,13 +84,11 @@ void testCreateBatchObjectsWhenHierarchyLevelIsLowerThanMaxLevel() { OrganisationUnitStructureResourceTable resourceTable = new OrganisationUnitStructureResourceTable(null, maxOrgUnitLevels, null); - // Then assertDoesNotThrow(() -> resourceTable.createBatchObjects(organisationUnits, currentLevel)); } @Test void testCreateBatchObjectsWhenCurrentLevelIsLargerThanMaxLevel() { - // Given int maxOrgUnitLevels = 2; int currentLevel = 3; @@ -110,7 +105,6 @@ void testCreateBatchObjectsWhenCurrentLevelIsLargerThanMaxLevel() { OrganisationUnitStructureResourceTable resourceTable = new OrganisationUnitStructureResourceTable(null, maxOrgUnitLevels, null); - // Then Exception ex = assertThrows( IllegalStateException.class, @@ -123,7 +117,6 @@ void testCreateBatchObjectsWhenCurrentLevelIsLargerThanMaxLevel() { @Test void testCreateBatchObjectsWhenCurrentLevelHasNoParent() { - // Given int maxOrgUnitLevels = 2; int currentLevel = 3; @@ -140,7 +133,6 @@ void testCreateBatchObjectsWhenCurrentLevelHasNoParent() { OrganisationUnitStructureResourceTable resourceTable = new OrganisationUnitStructureResourceTable(null, maxOrgUnitLevels, null); - // Then Exception ex = assertThrows( IllegalStateException.class, diff --git a/dhis-2/dhis-services/dhis-service-core/pom.xml b/dhis-2/dhis-services/dhis-service-core/pom.xml index 86fa7ae4513f..2e76d7dac6b4 100644 --- a/dhis-2/dhis-services/dhis-service-core/pom.xml +++ b/dhis-2/dhis-services/dhis-service-core/pom.xml @@ -30,6 +30,7 @@ org.hisp.dhis dhis-service-node + test org.hisp.dhis diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/analytics/hibernate/HibernateCategoryDimensionStore.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/analytics/hibernate/HibernateCategoryDimensionStore.java new file mode 100644 index 000000000000..6d195be268d6 --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/analytics/hibernate/HibernateCategoryDimensionStore.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2004-2024, 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.hibernate; + +import jakarta.persistence.EntityManager; +import java.util.Collection; +import java.util.List; +import javax.annotation.Nonnull; +import org.hisp.dhis.analytics.CategoryDimensionStore; +import org.hisp.dhis.category.CategoryDimension; +import org.hisp.dhis.hibernate.HibernateGenericStore; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +/** + * @author david mackessy + */ +@Repository +public class HibernateCategoryDimensionStore extends HibernateGenericStore + implements CategoryDimensionStore { + + public HibernateCategoryDimensionStore( + EntityManager entityManager, JdbcTemplate jdbcTemplate, ApplicationEventPublisher publisher) { + super(entityManager, jdbcTemplate, publisher, CategoryDimension.class, false); + } + + @Override + public List getByCategoryOption(@Nonnull Collection categoryOptions) { + if (categoryOptions.isEmpty()) return List.of(); + return getQuery( + """ + select distinct cd from CategoryDimension cd + join cd.items co + where co.uid in :categoryOptions + """, + CategoryDimension.class) + .setParameter("categoryOptions", categoryOptions) + .getResultList(); + } +} diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataelement/DefaultCategoryService.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/DefaultCategoryService.java similarity index 96% rename from dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataelement/DefaultCategoryService.java rename to dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/DefaultCategoryService.java index c3883409b406..6fd781feb037 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataelement/DefaultCategoryService.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/DefaultCategoryService.java @@ -25,7 +25,7 @@ * (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.dataelement; +package org.hisp.dhis.category; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @@ -39,23 +39,13 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.SetValuedMap; import org.hisp.dhis.association.jdbc.JdbcOrgUnitAssociationsStore; -import org.hisp.dhis.category.Category; -import org.hisp.dhis.category.CategoryCombo; -import org.hisp.dhis.category.CategoryComboStore; -import org.hisp.dhis.category.CategoryOption; -import org.hisp.dhis.category.CategoryOptionCombo; -import org.hisp.dhis.category.CategoryOptionComboStore; -import org.hisp.dhis.category.CategoryOptionGroup; -import org.hisp.dhis.category.CategoryOptionGroupSet; -import org.hisp.dhis.category.CategoryOptionGroupSetStore; -import org.hisp.dhis.category.CategoryOptionGroupStore; -import org.hisp.dhis.category.CategoryOptionStore; -import org.hisp.dhis.category.CategoryService; -import org.hisp.dhis.category.CategoryStore; import org.hisp.dhis.common.DataDimensionType; import org.hisp.dhis.common.DeleteNotAllowedException; import org.hisp.dhis.common.IdScheme; import org.hisp.dhis.common.IdentifiableObjectManager; +import org.hisp.dhis.common.UID; +import org.hisp.dhis.dataelement.DataElement; +import org.hisp.dhis.dataelement.DataElementOperand; import org.hisp.dhis.dataset.DataSet; import org.hisp.dhis.dataset.DataSetElement; import org.hisp.dhis.security.acl.AccessStringHelper; @@ -208,6 +198,11 @@ public List getAttributeDataDimensionCategoriesNoAcl() { return categoryStore.getCategoriesNoAcl(DataDimensionType.ATTRIBUTE, true); } + @Override + public List getCategoriesByCategoryOption(Collection categoryOptions) { + return categoryStore.getCategoriesByCategoryOption(UID.toValueList(categoryOptions)); + } + // ------------------------------------------------------------------------- // CategoryOption // ------------------------------------------------------------------------- @@ -310,6 +305,11 @@ public Set getCoDimensionConstraints(User user) { return options; } + @Override + public List getCategoryOptionsByUid(List catOptionUids) { + return categoryOptionStore.getByUid(catOptionUids); + } + // ------------------------------------------------------------------------- // CategoryCombo // ------------------------------------------------------------------------- @@ -662,6 +662,13 @@ public void updateCategoryOptionComboNames() { categoryOptionComboStore.updateNames(); } + @Override + public List getCategoryOptionCombosByCategoryOption( + Collection categoryOptionsUids) { + return categoryOptionComboStore.getCategoryOptionCombosByCategoryOption( + UID.toValueList(categoryOptionsUids)); + } + // ------------------------------------------------------------------------- // DataElementOperand // ------------------------------------------------------------------------- @@ -772,6 +779,12 @@ public List getCategoryOptionGroups(CategoryOptionGroupSet return categoryOptionGroupStore.getCategoryOptionGroups(groupSet); } + @Override + public List getCategoryOptionGroupByCategoryOption( + Collection categoryOptions) { + return categoryOptionGroupStore.getByCategoryOption(UID.toValueList(categoryOptions)); + } + @Override @Transactional(readOnly = true) public Set getCogDimensionConstraints(User user) { diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryOptionComboStore.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryOptionComboStore.java index c41b701147fd..d596cb1e07d9 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryOptionComboStore.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryOptionComboStore.java @@ -28,8 +28,10 @@ package org.hisp.dhis.category.hibernate; import jakarta.persistence.EntityManager; +import java.util.Collection; import java.util.List; import java.util.Set; +import javax.annotation.Nonnull; import org.hibernate.NonUniqueResultException; import org.hibernate.Session; import org.hibernate.query.Query; @@ -143,4 +145,19 @@ public void deleteNoRollBack(CategoryOptionCombo categoryOptionCombo) { getSession().delete(categoryOptionCombo); } + + @Override + public List getCategoryOptionCombosByCategoryOption( + @Nonnull Collection categoryOptions) { + if (categoryOptions.isEmpty()) return List.of(); + return getQuery( + """ + select distinct coc from CategoryOption co + join co.categoryOptionCombos coc + where co.uid in :categoryOptions + """, + CategoryOptionCombo.class) + .setParameter("categoryOptions", categoryOptions) + .getResultList(); + } } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryOptionGroupStore.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryOptionGroupStore.java index 7607c1882585..ef3b03a05abd 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryOptionGroupStore.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryOptionGroupStore.java @@ -30,7 +30,9 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.Join; +import java.util.Collection; import java.util.List; +import javax.annotation.Nonnull; import org.hisp.dhis.category.CategoryOptionGroup; import org.hisp.dhis.category.CategoryOptionGroupSet; import org.hisp.dhis.category.CategoryOptionGroupStore; @@ -74,4 +76,19 @@ public List getCategoryOptionGroups(CategoryOptionGroupSet return getList(builder, parameters); } + + @Override + public List getByCategoryOption( + @Nonnull Collection categoryOptions) { + if (categoryOptions.isEmpty()) return List.of(); + return getQuery( + """ + select distinct cog from CategoryOptionGroup cog + join cog.members co + where co.uid in :categoryOptions + """, + CategoryOptionGroup.class) + .setParameter("categoryOptions", categoryOptions) + .getResultList(); + } } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryStore.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryStore.java index 4e4f23b18b7f..142c59c16931 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryStore.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryStore.java @@ -29,7 +29,9 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.criteria.CriteriaBuilder; +import java.util.Collection; import java.util.List; +import javax.annotation.Nonnull; import org.hisp.dhis.category.Category; import org.hisp.dhis.category.CategoryStore; import org.hisp.dhis.common.DataDimensionType; @@ -87,4 +89,18 @@ public List getCategoriesNoAcl( .addPredicate(root -> builder.equal(root.get("dataDimensionType"), dataDimensionType)) .addPredicate(root -> builder.equal(root.get("dataDimension"), dataDimension))); } + + @Override + public List getCategoriesByCategoryOption(@Nonnull Collection categoryOptions) { + if (categoryOptions.isEmpty()) return List.of(); + return getQuery( + """ + select distinct c from Category c + join c.categoryOptions co + where co.uid in :categoryOptions + """, + Category.class) + .setParameter("categoryOptions", categoryOptions) + .getResultList(); + } } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/common/DefaultIdentifiableObjectManager.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/common/DefaultIdentifiableObjectManager.java index 88323f3197f8..cb904f2a4441 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/common/DefaultIdentifiableObjectManager.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/common/DefaultIdentifiableObjectManager.java @@ -39,9 +39,7 @@ import jakarta.persistence.EntityManager; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Date; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -261,6 +259,19 @@ public T get(@Nonnull Class type, @Nonnull Str return store.getByUid(uid); } + @CheckForNull + @Override + @Transactional(readOnly = true) + public T get(@Nonnull Class type, @Nonnull UID uid) { + IdentifiableObjectStore store = getIdentifiableObjectStore(type); + + if (store == null) { + return null; + } + + return store.getByUid(uid.getValue()); + } + @Nonnull @Override public T load(@Nonnull Class type, @Nonnull String uid) @@ -396,34 +407,6 @@ public T search(@Nonnull Class type, @Nonnull return object; } - @Nonnull - @Override - @Transactional(readOnly = true) - public List filter( - @Nonnull Class type, @Nonnull String query) { - Set uniqueObjects = new HashSet<>(); - - T uidObject = get(type, query); - - if (uidObject != null) { - uniqueObjects.add(uidObject); - } - - T codeObject = getByCode(type, query); - - if (codeObject != null) { - uniqueObjects.add(codeObject); - } - - uniqueObjects.addAll(getLikeName(type, query, false)); - - List objects = new ArrayList<>(uniqueObjects); - - Collections.sort(objects); - - return objects; - } - @Nonnull @Override @Transactional(readOnly = true) diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataset/DefaultDataSetService.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataset/DefaultDataSetService.java index 886d57c34f0b..20254f48a893 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataset/DefaultDataSetService.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataset/DefaultDataSetService.java @@ -51,7 +51,6 @@ import org.hisp.dhis.user.CurrentUserUtil; import org.hisp.dhis.user.User; import org.hisp.dhis.user.UserDetails; -import org.hisp.dhis.user.UserService; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -71,8 +70,6 @@ public class DefaultDataSetService implements DataSetService { @Qualifier("jdbcDataSetOrgUnitAssociationsStore") private final JdbcOrgUnitAssociationsStore jdbcOrgUnitAssociationsStore; - private final UserService userService; - // ------------------------------------------------------------------------- // DataSet // ------------------------------------------------------------------------- diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataset/DefaultSectionService.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataset/DefaultSectionService.java index 9cbc0cc1a4c2..0b4c11b9bb5a 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataset/DefaultSectionService.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataset/DefaultSectionService.java @@ -27,8 +27,10 @@ */ package org.hisp.dhis.dataset; +import java.util.Collection; import java.util.List; import lombok.RequiredArgsConstructor; +import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.indicator.Indicator; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -41,8 +43,6 @@ public class DefaultSectionService implements SectionService { private final SectionStore sectionStore; - private final DataSetService dataSetService; - @Override @Transactional public long addSection(Section section) { @@ -59,49 +59,29 @@ public void deleteSection(Section section) { @Override @Transactional(readOnly = true) - public List
getAllSections() { - return sectionStore.getAll(); - } - - @Override - @Transactional(readOnly = true) - public List
getSectionsByIndicators(List indicators) { - return sectionStore.getSectionsByIndicators(indicators); - } - - @Override - @Transactional - public void removeIndicator(Section s, Indicator i) { - s.removeIndicator(i); + public Section getSection(String uid) { + return sectionStore.getByUid(uid); } @Override @Transactional - public void addIndicator(Section s, Indicator i) { - s.addIndicator(i); + public void updateSection(Section section) { + sectionStore.update(section); } @Override - @Transactional(readOnly = true) - public Section getSection(long id) { - return sectionStore.get(id); + public List
getSectionsByDataElement(String uid) { + return sectionStore.getSectionsByDataElement(uid); } @Override - @Transactional(readOnly = true) - public Section getSection(String uid) { - return sectionStore.getByUid(uid); + public List
getSectionsByDataElement(Collection dataElements) { + return sectionStore.getSectionsByDataElement(dataElements); } @Override @Transactional(readOnly = true) - public Section getSectionByName(String name, Integer dataSetId) { - return sectionStore.getSectionByName(name, dataSetService.getDataSet(dataSetId)); - } - - @Override - @Transactional - public void updateSection(Section section) { - sectionStore.update(section); + public List
getSectionsByIndicators(Collection indicators) { + return sectionStore.getSectionsByIndicators(indicators); } } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataset/SectionDeletionHandler.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataset/SectionDeletionHandler.java index 03dd93731b05..a997dc7dc773 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataset/SectionDeletionHandler.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataset/SectionDeletionHandler.java @@ -28,8 +28,11 @@ package org.hisp.dhis.dataset; import java.util.Iterator; +import java.util.List; +import java.util.Set; import lombok.RequiredArgsConstructor; import org.hisp.dhis.dataelement.DataElement; +import org.hisp.dhis.indicator.Indicator; import org.hisp.dhis.system.deletion.IdObjectDeletionHandler; import org.springframework.stereotype.Component; @@ -41,22 +44,30 @@ public class SectionDeletionHandler extends IdObjectDeletionHandler
{ private final SectionService sectionService; - private final SectionStore sectionStore; - @Override protected void registerHandler() { whenDeleting(DataElement.class, this::deleteDataElement); + whenDeleting(Indicator.class, this::deleteIndicator); whenDeleting(DataSet.class, this::deleteDataSet); } private void deleteDataElement(DataElement dataElement) { - for (Section section : sectionStore.getSectionsByDataElement(dataElement.getUid())) { + List
sections = sectionService.getSectionsByDataElement(dataElement.getUid()); + for (Section section : sections) { section.getGreyedFields().removeIf(operand -> operand.getDataElement().equals(dataElement)); section.getDataElements().removeIf(de -> de.equals(dataElement)); sectionService.updateSection(section); } } + private void deleteIndicator(Indicator indicator) { + List
sections = sectionService.getSectionsByIndicators(Set.of(indicator)); + for (Section section : sections) { + section.getIndicators().removeIf(in -> indicator.equals(in)); + sectionService.updateSection(section); + } + } + private void deleteDataSet(DataSet dataSet) { Iterator
iterator = dataSet.getSections().iterator(); diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataset/hibernate/HibernateSectionStore.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataset/hibernate/HibernateSectionStore.java index f6f5ad27fd97..d4123ca5ebb7 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataset/hibernate/HibernateSectionStore.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataset/hibernate/HibernateSectionStore.java @@ -68,40 +68,40 @@ public Section getSectionByName(String name, DataSet dataSet) { } @Override - public List
getSectionsByDataElement(String dataElementUid) { - String sql = - "select * from section s" - + " left join sectiondataelements sde on s.sectionid = sde.sectionid" - + " left join sectiongreyedfields sgf on s.sectionid = sgf.sectionid" - + " left join dataelementoperand deo on sgf.dataelementoperandid = deo.dataelementoperandid" - + ", dataelement de" - + " where de.uid = :dataElementId and (sde.dataelementid = de.dataelementid or deo.dataelementid = de.dataelementid);"; - return nativeSynchronizedTypedQuery(sql).setParameter("dataElementId", dataElementUid).list(); + public List
getSectionsByDataElement(String uid) { + final String sql = + """ + select * from section s \ + left join sectiondataelements sde on s.sectionid = sde.sectionid \ + left join sectiongreyedfields sgf on s.sectionid = sgf.sectionid \ + left join dataelementoperand deo on sgf.dataelementoperandid = deo.dataelementoperandid \ + , dataelement de \ + where de.uid = :dataElementId and (sde.dataelementid = de.dataelementid or deo.dataelementid = de.dataelementid);"""; + + return nativeSynchronizedTypedQuery(sql).setParameter("dataElementId", uid).list(); } @Override - public List
getSectionsByIndicators(List indicators) { - String sql = + public List
getSectionsByIndicators(Collection indicators) { + final String sql = """ - select s.* from section s - join sectionindicators si on s.sectionid = si.sectionid - where si.indicatorid in :indicators - group by s.sectionid - """; + select s.* from section s \ + join sectionindicators si on s.sectionid = si.sectionid \ + where si.indicatorid in :indicators \ + group by s.sectionid"""; + return nativeSynchronizedTypedQuery(sql).setParameter("indicators", indicators).list(); } @Override - public List
getByDataElement(Collection dataElements) { - return getQuery( - """ - select s from Section s - join s.dataElements de - where de in :dataElements - group by s.id - """, - Section.class) - .setParameter("dataElements", dataElements) - .getResultList(); + public List
getSectionsByDataElement(Collection dataElements) { + final String sql = + """ + select s from Section s \ + join s.dataElements de \ + where de in :dataElements \ + group by s.id"""; + + return getQuery(sql, Section.class).setParameter("dataElements", dataElements).getResultList(); } } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueService.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueService.java index 40e8a84c06f4..1c733e2dd0af 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueService.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueService.java @@ -35,6 +35,7 @@ import java.util.Date; import java.util.List; import java.util.Objects; +import javax.annotation.Nonnull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.hisp.dhis.category.CategoryCombo; @@ -42,6 +43,7 @@ import org.hisp.dhis.category.CategoryService; import org.hisp.dhis.changelog.ChangeLogType; import org.hisp.dhis.common.IllegalQueryException; +import org.hisp.dhis.common.UID; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.external.conf.DhisConfigurationProvider; import org.hisp.dhis.feedback.ErrorCode; @@ -348,4 +350,9 @@ public int getDataValueCountLastUpdatedBetween( public boolean dataValueExists(CategoryCombo combo) { return dataValueStore.dataValueExists(combo); } + + @Override + public boolean dataValueExistsForDataElement(@Nonnull UID uid) { + return dataValueStore.dataValueExistsForDataElement(uid.getValue()); + } } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/hibernate/HibernateDataValueStore.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/hibernate/HibernateDataValueStore.java index a73716d7c6a8..338b825c5453 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/hibernate/HibernateDataValueStore.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/hibernate/HibernateDataValueStore.java @@ -267,6 +267,15 @@ public boolean dataValueExists(CategoryCombo combo) { .isEmpty(); } + @Override + public boolean dataValueExistsForDataElement(String uid) { + return !getQuery("select 1 from DataValue dv where dv.dataElement.uid = :uid") + .setParameter("uid", uid) + .setMaxResults(1) + .getResultList() + .isEmpty(); + } + // ------------------------------------------------------------------------- // getDataValues and related supportive methods // ------------------------------------------------------------------------- diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/gist/GistParams.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/gist/GistParams.java index cec3f2a34bf1..78308e65ee9a 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/gist/GistParams.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/gist/GistParams.java @@ -86,8 +86,7 @@ Fields like _name_ or _shortName_ can be translated (internationalised). See [Gist inverse parameter](https://docs.dhis2.org/en/develop/using-the-api/dhis-core-version-master/metadata-gist.html#the-inverse-parameter).""") boolean inverse = false; - @OpenApi.Description(""" - Old name for `totalPages`.""") + @OpenApi.Description("Old name for `totalPages`.") @Deprecated(since = "2.41", forRemoval = true) Boolean total; diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/icon/JdbcIconStore.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/icon/JdbcIconStore.java index f8481acdd120..2ad2f6e01261 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/icon/JdbcIconStore.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/icon/JdbcIconStore.java @@ -88,7 +88,8 @@ public List getAllKeys() { @Override public long count(IconQueryParams params) { - String sql = """ + String sql = + """ select count(*) from icon c """; diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/organisationunit/DefaultOrganisationUnitService.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/organisationunit/DefaultOrganisationUnitService.java index 59ac1ca295e6..280d0e6c2ec2 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/organisationunit/DefaultOrganisationUnitService.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/organisationunit/DefaultOrganisationUnitService.java @@ -49,6 +49,7 @@ import org.hisp.dhis.cache.CacheProvider; import org.hisp.dhis.common.IdentifiableObjectManager; import org.hisp.dhis.common.SortProperty; +import org.hisp.dhis.common.UID; import org.hisp.dhis.commons.collection.ListUtils; import org.hisp.dhis.commons.filter.FilterUtils; import org.hisp.dhis.configuration.ConfigurationService; @@ -162,12 +163,6 @@ public List getOrganisationUnitsByUid(@Nonnull Collection(uids)); } - @Override - @Transactional(readOnly = true) - public List getOrganisationUnitsByQuery(OrganisationUnitQueryParams params) { - return organisationUnitStore.getOrganisationUnits(params); - } - @Override @Transactional(readOnly = true) public OrganisationUnit getOrganisationUnit(String uid) { @@ -656,6 +651,11 @@ public List getSearchOrganisationUnitsUidsByUser(String username) { return organisationUnitStore.getSearchOrganisationUnitsUidsByUser(username); } + @Override + public List getByCategoryOption(Collection categoryOptions) { + return organisationUnitStore.getByCategoryOption(UID.toValueList(categoryOptions)); + } + @Override @Transactional(readOnly = true) public Integer getOrganisationUnitLevelByLevelOrUid(String level) { diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/organisationunit/hibernate/HibernateOrganisationUnitStore.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/organisationunit/hibernate/HibernateOrganisationUnitStore.java index 89da70ab503b..d45785e0a15d 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/organisationunit/hibernate/HibernateOrganisationUnitStore.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/organisationunit/hibernate/HibernateOrganisationUnitStore.java @@ -39,6 +39,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import javax.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.hibernate.Session; @@ -408,6 +409,20 @@ public int updateAllOrganisationUnitsGeometryToNull() { return getQuery("update OrganisationUnit o set o.geometry = null").executeUpdate(); } + @Override + public List getByCategoryOption(@Nonnull Collection categoryOptions) { + if (categoryOptions.isEmpty()) return List.of(); + return getQuery( + """ + select distinct ou from OrganisationUnit ou + join ou.categoryOptions co + where co.uid in :categoryOptions + """, + OrganisationUnit.class) + .setParameter("categoryOptions", categoryOptions) + .getResultList(); + } + private String buildOrganisationUnitDistinctUidsSql(OrganisationUnitQueryParams params) { SqlHelper hlp = new SqlHelper(); diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/period/PeriodDataProvider.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/period/PeriodDataProvider.java index 51d5c1f9b892..47f41b78ee4a 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/period/PeriodDataProvider.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/period/PeriodDataProvider.java @@ -31,7 +31,7 @@ import static java.util.Collections.sort; import static java.util.Collections.unmodifiableList; import static org.apache.commons.collections4.CollectionUtils.isEmpty; -import static org.hisp.dhis.period.PeriodDataProvider.DataSource.SYSTEM_DEFINED; +import static org.hisp.dhis.period.PeriodDataProvider.PeriodSource.SYSTEM_DEFINED; import java.util.ArrayList; import java.util.List; @@ -56,8 +56,10 @@ public class PeriodDataProvider { private final JdbcTemplate jdbcTemplate; - public enum DataSource { + public enum PeriodSource { + /** Fixed boundaries. Backwards compatible. */ SYSTEM_DEFINED, + /** Dynamic from data in database. */ DATABASE } @@ -65,21 +67,19 @@ public enum DataSource { * Returns a distinct union of all years available in the "event" table + "period" table, both * from aggregate and tracker, with 5 years previous and future additions. * - *

ie: [extra_5_previous_years, data_years, extra_5_future_year] + *

[extra_5_previous_years, data_years, extra_5_future_year] * - * @param source the source ({@link DataSource}) where the years wil be retrieved from. + * @param source the source ({@link PeriodSource}) where the years wil be retrieved from. * @return an unmodifiable list of distinct years and the respective additions. */ - public List getAvailableYears(DataSource source) { + public List getAvailableYears(PeriodSource source) { List availableDataYears = new ArrayList<>(); if (source == SYSTEM_DEFINED) { - // Add default hard-coded years (keeps it backward compatible). for (int year = DEFAULT_FIRST_YEAR_SUPPORTED; year <= DEFAULT_LATEST_YEAR_SUPPORTED; year++) { availableDataYears.add(year); } } else { - // Add years dynamically, based on the database. availableDataYears.addAll(fetchAvailableYears()); addSafetyBuffer(availableDataYears, BEFORE_AND_AFTER_DATA_YEARS_SUPPORTED); } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/preheat/DefaultPreheatService.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/preheat/DefaultPreheatService.java index faa2e71b6a0d..18d4a16a829a 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/preheat/DefaultPreheatService.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/preheat/DefaultPreheatService.java @@ -766,7 +766,13 @@ private void collectScanTargets(Map, List> targets) { } objects.forEach( - o -> list.addAll(ReflectionUtils.invokeMethod(o, property.getGetterMethod()))); + o -> { + Collection propertyValue = + ReflectionUtils.invokeMethod(o, property.getGetterMethod()); + if (!org.apache.commons.collections4.CollectionUtils.isEmpty(propertyValue)) { + list.addAll(propertyValue); + } + }); targets.put(property.getItemKlass(), list); } else { List list = new ArrayList<>(); @@ -776,7 +782,12 @@ private void collectScanTargets(Map, List> targets) { } objects.forEach( - o -> list.add(ReflectionUtils.invokeMethod(o, property.getGetterMethod()))); + o -> { + Object item = ReflectionUtils.invokeMethod(o, property.getGetterMethod()); + if (item != null) { + list.add(item); + } + }); targets.put(property.getKlass(), list); } } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/program/TrackedEntityDataValueChangeLogDeletionHandler.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/program/TrackedEntityDataValueChangeLogDeletionHandler.java index 1298e7db82d6..11a6b0e7d97e 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/program/TrackedEntityDataValueChangeLogDeletionHandler.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/program/TrackedEntityDataValueChangeLogDeletionHandler.java @@ -49,5 +49,7 @@ private void deleteDataElement(DataElement dataElement) { delete( "delete from trackedentitydatavalueaudit where dataelementid = :id", Map.of("id", dataElement.getId())); + delete( + "delete from eventchangelog where dataelementid = :id", Map.of("id", dataElement.getId())); } } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/Criteria.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/Criteria.java index 5cbe43026ac2..cf3d4b84bdb6 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/Criteria.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/Criteria.java @@ -61,7 +61,7 @@ public Criteria add(Criterion... criterions) { return this; } - public Criteria add(Collection criterions) { + public Criteria add(Collection criterions) { this.criterions.addAll(criterions); return this; } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/Criterion.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/Criterion.java index 470a6b110825..6e1f47cd0dec 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/Criterion.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/Criterion.java @@ -30,4 +30,13 @@ /** * @author Morten Olav Hansen */ -public interface Criterion {} +public interface Criterion { + + /** + * @return true, when the condition cannot match any rows, e.g. an in-operator with an empty + * collection to test against + */ + default boolean isAlwaysFalse() { + return false; + } +} diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/DefaultJpaQueryParser.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/DefaultJpaQueryParser.java index 8028a8425667..c405c5cfd989 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/DefaultJpaQueryParser.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/DefaultJpaQueryParser.java @@ -28,10 +28,15 @@ package org.hisp.dhis.query; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.stream.Collectors.joining; import static org.hisp.dhis.query.QueryUtils.parseValue; +import com.google.common.collect.Lists; +import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.stream.Stream; +import javax.annotation.Nonnull; import org.hisp.dhis.common.CodeGenerator; import org.hisp.dhis.query.operators.MatchMode; import org.hisp.dhis.schema.Property; @@ -52,16 +57,17 @@ public DefaultJpaQueryParser(SchemaService schemaService) { } @Override - public Query parse(Class klass, List filters) throws QueryParserException { + public Query parse(Class klass, @Nonnull List filters) throws QueryParserException { return parse(klass, filters, Junction.Type.AND); } @Override - public Query parse(Class klass, List filters, Junction.Type rootJunction) + public Query parse(Class klass, @Nonnull List filters, Junction.Type rootJunction) throws QueryParserException { Schema schema = schemaService.getDynamicSchema(klass); Query query = Query.from(schema, rootJunction); + List mentions = new ArrayList<>(); for (String filter : filters) { String[] split = filter.split(":"); @@ -69,16 +75,22 @@ public Query parse(Class klass, List filters, Junction.Type rootJunct throw new QueryParserException("Invalid filter => " + filter); } + String path = split[0]; + String operator = split[1]; if (split.length >= 3) { - int index = split[0].length() + ":".length() + split[1].length() + ":".length(); - - if (split[0].equals(IDENTIFIABLE) && !schema.hasProperty(IDENTIFIABLE)) { - handleIdentifiablePath(schema, split[1], filter.substring(index), query.addDisjunction()); + String arg = split.length == 3 ? split[2] : Stream.of(split).skip(2).collect(joining(":")); + if ("mentions".equals(path) && "in".equals(operator)) { + mentions.add(arg); + } else if (path.equals(IDENTIFIABLE) && !schema.hasProperty(IDENTIFIABLE)) { + handleIdentifiablePath(schema, operator, arg, query.addDisjunction()); } else { - query.add(getRestriction(schema, split[0], split[1], filter.substring(index))); + query.add(getRestriction(schema, path, operator, arg)); } } else { - query.add(getRestriction(schema, split[0], split[1], null)); + query.add(getRestriction(schema, path, operator, null)); + } + if (!mentions.isEmpty()) { + getDisjunctionsFromCustomMentions(mentions, query.getSchema()); } } return query; @@ -137,36 +149,8 @@ private Restriction getRestriction( case "endsWith", "ilike$" -> Restrictions.ilike(path, parseValue(valueType, arg), MatchMode.END); case "!ilike$" -> Restrictions.notIlike(path, parseValue(valueType, arg), MatchMode.END); - case "in" -> { - Collection values = null; - - if (property.isCollection()) { - values = parseValue(Collection.class, property.getItemKlass(), arg); - } else { - values = parseValue(Collection.class, valueType, arg); - } - - if (values == null || values.isEmpty()) { - throw new QueryParserException("Invalid argument `" + arg + "` for in operator."); - } - - yield Restrictions.in(path, values); - } - case "!in" -> { - Collection values = null; - - if (property.isCollection()) { - values = parseValue(Collection.class, property.getItemKlass(), arg); - } else { - values = parseValue(Collection.class, valueType, arg); - } - - if (values == null || values.isEmpty()) { - throw new QueryParserException("Invalid argument `" + arg + "` for in operator."); - } - - yield Restrictions.notIn(path, values); - } + case "in" -> Restrictions.in(path, parseValues(property, valueType, arg)); + case "!in" -> Restrictions.notIn(path, parseValues(property, valueType, arg)); case "null" -> Restrictions.isNull(path); case "!null" -> Restrictions.isNotNull(path); case "empty" -> Restrictions.isEmpty(path); @@ -174,6 +158,34 @@ private Restriction getRestriction( }; } + @Nonnull + @SuppressWarnings("rawtypes") + private Collection parseValues(Property property, Class valueType, Object arg) { + Collection values = + property.isCollection() + ? parseValue(Collection.class, property.getItemKlass(), arg) + : parseValue(Collection.class, valueType, arg); + + if (values == null || values.isEmpty()) { + throw new QueryParserException("Invalid argument `" + arg + "` for in operator."); + } + return values; + } + + private Collection getDisjunctionsFromCustomMentions( + List mentions, Schema schema) { + Collection disjunctions = new ArrayList<>(); + for (String m : mentions) { + Disjunction disjunction = new Disjunction(schema); + String[] split = m.substring(1, m.length() - 1).split(","); + List items = Lists.newArrayList(split); + disjunction.add(Restrictions.in("mentions.username", items)); + disjunction.add(Restrictions.in("comments.mentions.username", items)); + disjunctions.add(disjunction); + } + return disjunctions; + } + @Override public Property getProperty(Schema schema, String path) throws QueryParserException { String[] paths = path.split("\\."); diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/DefaultQueryService.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/DefaultQueryService.java index c58e2167162b..da638c64310e 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/DefaultQueryService.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/DefaultQueryService.java @@ -35,6 +35,8 @@ import org.hisp.dhis.preheat.Preheat; import org.hisp.dhis.query.planner.QueryPlan; import org.hisp.dhis.query.planner.QueryPlanner; +import org.hisp.dhis.schema.Schema; +import org.hisp.dhis.schema.SchemaService; import org.springframework.stereotype.Component; /** @@ -49,11 +51,10 @@ public class DefaultQueryService implements QueryService { private static final Junction.Type DEFAULT_JUNCTION_TYPE = Junction.Type.AND; private final QueryParser queryParser; - private final QueryPlanner queryPlanner; + private final SchemaService schemaService; private final JpaCriteriaQueryEngine criteriaQueryEngine; - private final InMemoryQueryEngine inMemoryQueryEngine; @Override @@ -85,23 +86,16 @@ public long count(Query query) { } @Override - public Query getQueryFromUrl( - Class klass, List filters, List orders, Pagination pagination) + public Query getQueryFromUrl(Class type, GetObjectListParams params) throws QueryParserException { - return getQueryFromUrl(klass, filters, orders, pagination, DEFAULT_JUNCTION_TYPE); - } + List filters = params.getFilters(); + if (filters == null) filters = List.of(); + Query query = queryParser.parse(type, filters, params.getRootJunction()); - @Override - public Query getQueryFromUrl( - Class klass, - List filters, - List orders, - Pagination pagination, - Junction.Type rootJunction) - throws QueryParserException { - Query query = queryParser.parse(klass, filters, rootJunction); - query.addOrders(orders); + Schema schema = schemaService.getDynamicSchema(type); + query.addOrders(QueryUtils.convertOrderStrings(params.getOrders(), schema)); + Pagination pagination = params.getPagination(); if (pagination.hasPagination()) { query.setFirstResult(pagination.getFirstResult()); query.setMaxResults(pagination.getSize()); @@ -110,12 +104,6 @@ public Query getQueryFromUrl( return query; } - @Override - public Query getQueryFromUrl(Class klass, List filters, List orders) - throws QueryParserException { - return getQueryFromUrl(klass, filters, orders, new Pagination(), DEFAULT_JUNCTION_TYPE); - } - // --------------------------------------------------------------------------------------------- // Helper methods // --------------------------------------------------------------------------------------------- diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/GetObjectListParams.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/GetObjectListParams.java new file mode 100644 index 000000000000..fb440e4aacb8 --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/GetObjectListParams.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2004-2024, 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.query; + +import static java.lang.Math.max; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + * Base for parameters supported by CRUD {@code CRUD.getObjectList}. + * + * @author Jan Bernitt + */ +@Data +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +public class GetObjectListParams extends GetObjectParams { + + @JsonProperty("filter") + @CheckForNull + List filters; + + @JsonProperty("order") + @CheckForNull + List orders; + + @JsonProperty Junction.Type rootJunction = Junction.Type.AND; + + @JsonProperty boolean paging = true; + @JsonProperty int page = 1; + @JsonProperty int pageSize = 50; + + /** + * A special filter that matches the query term against the ID and code (equals) and against name + * (contains). Can be used in combination with regular filters. + */ + @JsonProperty String query; + + @Nonnull + @JsonIgnore + public Pagination getPagination() { + if (!paging) return new Pagination(); + // ignore if page < 0 + return new Pagination((max(page, 1) - 1) * pageSize, pageSize); + } + + public GetObjectListParams addFilter(String filter) { + if (filters == null) filters = new ArrayList<>(); + filters.add(filter); + return this; + } + + public GetObjectListParams addOrder(String order) { + if (orders == null) orders = new ArrayList<>(); + orders.add(order); + return this; + } + + /** + * For spring URL parameter injection only. + * + *

The issue is that a filter value may contain comma as part of the value, not to separate + * values which can be misunderstood by spring so the splitting needs to be done with custom + * logic. + * + * @param filters all filters as a comma seperated string + */ + public void setFilter(String filters) { + this.filters = splitFilters(filters); + } + + public void setOrder(List orders) { + this.orders = orders; + } + + /** + * Pattern to split {@code filter} expressions which have 2 or 3 components: {@code + * property:operator} or {@code property:operator:value}. + * + *

Examples + * + *

+   * name:eq:Peter
+   * organisationUnits:empty
+   * 
+ * + * Unfortunately the value can also contain : and when used with [] it can also contain , which + * needs extra caution to not read too little or too much into operation and value. + */ + private static final Pattern FILTER_PARTS = + Pattern.compile( + "(?[a-zA-Z0-9.]+):(?[!$a-zA-Z]{2,10})(?::(?\\[[^]]*]|[^,\\n\\r]+))?"); + + /** + * Splits a string {@code filter,filter} into a list {@code [filter,filter]}. + * + *

This is non-trivial since filters can contain comma symbols. + * + * @param filters a comma seperated list of filter expressions + * @return a list where each element is a single filter expression + */ + @CheckForNull + static List splitFilters(@CheckForNull String filters) { + if (filters == null || filters.isEmpty()) return null; + if (!filters.contains(",")) return new ArrayList<>(List.of(filters)); + List res = new ArrayList<>(); + Matcher m = FILTER_PARTS.matcher(filters); + while (m.find()) { + res.add(m.group()); + } + return res; + } +} diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/GetObjectParams.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/GetObjectParams.java new file mode 100644 index 000000000000..484ca06890b1 --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/GetObjectParams.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2004-2024, 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.query; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; +import org.hisp.dhis.common.OpenApi; +import org.hisp.dhis.fieldfilter.Defaults; +import org.hisp.dhis.fieldfiltering.FieldPreset; + +/** + * Base for parameters supported by CRUD {@code CRUD.getObject}. + * + * @author Jan Bernitt + */ +@Getter +@EqualsAndHashCode +@ToString +@Accessors(chain = true) +@OpenApi.Shared +public class GetObjectParams { + + /** + * Fields allow {@code property[sub,sub]} syntax where a comma occurs as part of the property + * name. These commas need to be ignored when splitting a {@code fields} parameter list. + */ + private static final String FIELD_SPLIT = ",(?![^\\[\\]]*\\]|[^\\(\\)]*\\)|([a-zA-Z0-9]+,?)+\\))"; + + @OpenApi.Shared.Inline + @OpenApi.Property(OpenApi.PropertyNames[].class) + @JsonProperty + @CheckForNull + List fields; + + @Setter @JsonProperty @Nonnull Defaults defaults = Defaults.INCLUDE; + + /** + * Since splitting on comma is too unsophisticated to be correct this needs custom split code when + * called by spring injecting URL parmaeters + * + * @param fields field expression + */ + public void setFields(String fields) { + this.fields = fields == null ? null : List.of(fields.split(FIELD_SPLIT)); + } + + public GetObjectParams addField(String field) { + if (fields == null) fields = new ArrayList<>(); + fields.add(field); + return this; + } + + /** + * @return list of fields with defaults as used when viewing a single object + */ + @Nonnull + @JsonIgnore + public List getFieldsObject() { + if (fields == null || fields.isEmpty()) return List.of("*"); + return fields; + } + + /** + * @return list of fields with defaults as used when viewing a list as JSON + */ + @Nonnull + @JsonIgnore + public List getFieldsJsonList() { + if (fields == null || fields.isEmpty()) return FieldPreset.defaultPreset().getFields(); + return fields; + } + + /** + * @return list of fields with defaults as used when viewing a list as CSV + */ + @Nonnull + @JsonIgnore + public List getFieldsCsvList() { + List res = new ArrayList<>(); + if (fields != null) res.addAll(fields); + if (res.isEmpty() || res.contains("*") || res.contains(":all")) + res.addAll(FieldPreset.defaultPreset().getFields()); + return res; + } + + /** + * @return elevates a single object params object to a list params object as a way to adapt to the + * list based query engine that is also used for single object lookup + */ + public GetObjectListParams toListParams() { + GetObjectListParams res = new GetObjectListParams(); + if (fields != null) fields.forEach(res::addField); + res.setDefaults(defaults); + return res; + } +} diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/JpaCriteriaQueryEngine.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/JpaCriteriaQueryEngine.java index caf8dea42adb..9f9a02b251c7 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/JpaCriteriaQueryEngine.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/JpaCriteriaQueryEngine.java @@ -28,6 +28,7 @@ package org.hisp.dhis.query; import static com.google.common.base.Preconditions.checkNotNull; +import static org.hisp.dhis.user.CurrentUserUtil.getCurrentUserDetails; import jakarta.persistence.EntityManager; import jakarta.persistence.TypedQuery; @@ -39,6 +40,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; +import javax.annotation.Nonnull; import org.hibernate.jpa.QueryHints; import org.hisp.dhis.cache.QueryCacheManager; import org.hisp.dhis.common.IdentifiableObject; @@ -47,7 +50,6 @@ import org.hisp.dhis.query.planner.QueryPlan; import org.hisp.dhis.query.planner.QueryPlanner; import org.hisp.dhis.schema.Schema; -import org.hisp.dhis.user.CurrentUserUtil; import org.hisp.dhis.user.UserDetails; import org.springframework.stereotype.Component; @@ -64,7 +66,7 @@ public class JpaCriteriaQueryEngine implements Que private final QueryCacheManager queryCacheManager; - private Map, IdentifiableObjectStore> stores = new HashMap<>(); + private final Map, IdentifiableObjectStore> stores = new HashMap<>(); public JpaCriteriaQueryEngine( QueryPlanner queryPlanner, @@ -86,16 +88,17 @@ public JpaCriteriaQueryEngine( public List query(Query query) { Schema schema = query.getSchema(); + @SuppressWarnings("unchecked") Class klass = (Class) schema.getKlass(); - InternalHibernateGenericStore store = (InternalHibernateGenericStore) getStore(klass); + InternalHibernateGenericStore store = getStore(klass); if (store == null) { return new ArrayList<>(); } if (query.getCurrentUserDetails() == null) { - query.setCurrentUserDetails(CurrentUserUtil.getCurrentUserDetails()); + query.setCurrentUserDetails(getCurrentUserDetails()); } if (!query.isPlannedQuery()) { @@ -108,68 +111,11 @@ public List query(Query query) { CriteriaQuery criteriaQuery = builder.createQuery(klass); Root root = criteriaQuery.from(klass); - if (query.isEmpty()) { - - UserDetails userDetails = - query.getCurrentUserDetails() != null - ? query.getCurrentUserDetails() - : CurrentUserUtil.getCurrentUserDetails(); - - Predicate predicate = builder.conjunction(); - boolean shareable = schema.isShareable(); - if (shareable && !query.isSkipSharing()) { - predicate - .getExpressions() - .addAll( - store.getSharingPredicates(builder, userDetails).stream() - .map(t -> t.apply(root)) - .toList()); - } - criteriaQuery.where(predicate); - - TypedQuery typedQuery = entityManager.createQuery(criteriaQuery); - - typedQuery.setFirstResult(query.getFirstResult()); - typedQuery.setMaxResults(query.getMaxResults()); - - return typedQuery.getResultList(); - } - Predicate predicate = buildPredicates(builder, root, query); - boolean shareable = schema.isShareable(); - - String username = - CurrentUserUtil.getCurrentUsername() != null - ? CurrentUserUtil.getCurrentUsername() - : "system-process"; - - if (!username.equals("system-process") && shareable && !query.isSkipSharing()) { - - UserDetails userDetails = - query.getCurrentUserDetails() != null - ? query.getCurrentUserDetails() - : CurrentUserUtil.getCurrentUserDetails(); - - predicate - .getExpressions() - .addAll( - store.getSharingPredicates(builder, userDetails).stream() - .map(t -> t.apply(root)) - .toList()); - } - + addSharingPredicates(query, schema, predicate, store, builder, root); criteriaQuery.where(predicate); - if (!query.getOrders().isEmpty()) { - criteriaQuery.orderBy( - query.getOrders().stream() - .map( - o -> - o.isAscending() - ? builder.asc(root.get(o.getProperty().getFieldName())) - : builder.desc(root.get(o.getProperty().getFieldName()))) - .toList()); - } + if (!query.getOrders().isEmpty()) criteriaQuery.orderBy(getOrders(query, builder, root)); TypedQuery typedQuery = entityManager.createQuery(criteriaQuery); @@ -186,20 +132,42 @@ public List query(Query query) { return typedQuery.getResultList(); } + private static void addSharingPredicates( + Query query, + Schema schema, + Predicate predicate, + InternalHibernateGenericStore store, + CriteriaBuilder builder, + Root root) { + boolean shareable = schema.isShareable(); + if (!shareable) return; + UserDetails user = query.getCurrentUserDetails(); + if (user == null) user = getCurrentUserDetails(); + List, Predicate>> predicates = List.of(); + if (query.isDataSharing()) { + predicates = store.getDataSharingPredicates(builder, user); + } else if (!query.isSkipSharing()) { + predicates = store.getSharingPredicates(builder, user); + } + if (!predicates.isEmpty()) + predicate.getExpressions().addAll(predicates.stream().map(t -> t.apply(root)).toList()); + } + @Override public long count(Query query) { Schema schema = query.getSchema(); + @SuppressWarnings("unchecked") Class klass = (Class) schema.getKlass(); - InternalHibernateGenericStore store = (InternalHibernateGenericStore) getStore(klass); + InternalHibernateGenericStore store = getStore(klass); if (store == null) { return 0; } if (query.getCurrentUserDetails() == null) { - query.setCurrentUserDetails(CurrentUserUtil.getCurrentUserDetails()); + query.setCurrentUserDetails(getCurrentUserDetails()); } if (!query.isPlannedQuery()) { @@ -215,36 +183,26 @@ public long count(Query query) { criteriaQuery.select(builder.count(root)); Predicate predicate = buildPredicates(builder, root, query); - - boolean shareable = schema.isShareable(); - if (shareable && !query.isSkipSharing()) { - UserDetails currentUserDetails = query.getCurrentUserDetails(); - predicate - .getExpressions() - .addAll( - store.getSharingPredicates(builder, currentUserDetails).stream() - .map(t -> t.apply(root)) - .toList()); - } - + addSharingPredicates(query, schema, predicate, store, builder, root); criteriaQuery.where(predicate); - if (!query.getOrders().isEmpty()) { - criteriaQuery.orderBy( - query.getOrders().stream() - .map( - o -> - o.isAscending() - ? builder.asc(root.get(o.getProperty().getName())) - : builder.desc(root.get(o.getProperty().getName()))) - .toList()); - } - TypedQuery typedQuery = entityManager.createQuery(criteriaQuery); return typedQuery.getSingleResult(); } + @Nonnull + private static List getOrders( + Query query, CriteriaBuilder builder, Root root) { + return query.getOrders().stream() + .map( + o -> + o.isAscending() + ? builder.asc(root.get(o.getProperty().getFieldName())) + : builder.desc(root.get(o.getProperty().getFieldName()))) + .toList(); + } + private void initStoreMap() { if (!stores.isEmpty()) { return; @@ -255,32 +213,31 @@ private void initStoreMap() { } } - private IdentifiableObjectStore getStore(Class klass) { + @SuppressWarnings("unchecked") + private InternalHibernateGenericStore getStore(Class klass) { initStoreMap(); - return stores.get(klass); + return (InternalHibernateGenericStore) stores.get(klass); } private Predicate buildPredicates(CriteriaBuilder builder, Root root, Query query) { Predicate junction = builder.conjunction(); + if (query.isEmpty()) return junction; + query.getAliases().forEach(alias -> root.join(alias).alias(alias)); if (!query.getCriterions().isEmpty()) { junction = getJpaJunction(builder, query.getRootJunctionType()); for (org.hisp.dhis.query.Criterion criterion : query.getCriterions()) { addPredicate(builder, root, junction, criterion); } } - query.getAliases().forEach(alias -> root.get(alias).alias(alias)); + return junction; } private Predicate getJpaJunction(CriteriaBuilder builder, Junction.Type type) { - switch (type) { - case AND: - return builder.conjunction(); - case OR: - return builder.disjunction(); - } - - return builder.conjunction(); + return switch (type) { + case AND -> builder.conjunction(); + case OR -> builder.disjunction(); + }; } private Predicate getPredicate( @@ -288,7 +245,6 @@ private Predicate getPredicate( if (restriction == null || restriction.getOperator() == null) { return null; } - return restriction.getOperator().getPredicate(builder, root, restriction.getQueryPath()); } @@ -297,8 +253,7 @@ private void addPredicate( Root root, Predicate predicateJunction, org.hisp.dhis.query.Criterion criterion) { - if (criterion instanceof Restriction) { - Restriction restriction = (Restriction) criterion; + if (criterion instanceof Restriction restriction) { Predicate predicate = getPredicate(builder, root, restriction); if (predicate != null) { @@ -326,8 +281,7 @@ private void addJunction( Root root, Predicate junction, org.hisp.dhis.query.Criterion criterion) { - if (criterion instanceof Restriction) { - Restriction restriction = (Restriction) criterion; + if (criterion instanceof Restriction restriction) { Predicate predicate = getPredicate(builder, root, restriction); if (predicate != null) { diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/JpaQueryUtils.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/JpaQueryUtils.java index fd63fdf1366a..58768be647bf 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/JpaQueryUtils.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/JpaQueryUtils.java @@ -31,7 +31,6 @@ import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.Expression; -import jakarta.persistence.criteria.Order; import jakarta.persistence.criteria.Path; import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; @@ -52,12 +51,6 @@ public class JpaQueryUtils { public static final String HIBERNATE_CACHEABLE_HINT = "org.hibernate.cacheable"; - public static Function, Order> getOrders(CriteriaBuilder builder, String field) { - Function, Order> order = root -> builder.asc(root.get(field)); - - return order; - } - /** * Generate a String comparison Predicate base on input parameters. * diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/Query.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/Query.java index 564eb9dbef5b..0c3e514539d9 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/Query.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/Query.java @@ -58,6 +58,8 @@ public class Query extends Criteria { private boolean skipSharing; + private boolean dataSharing; + private Integer firstResult = 0; private Integer maxResults = Integer.MAX_VALUE; @@ -150,7 +152,7 @@ public Query add(Criterion... criterions) { } @Override - public Query add(Collection criterions) { + public Query add(Collection criterions) { super.add(criterions); return this; } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/QueryParser.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/QueryParser.java index 5cf2f4d96cb0..69547997e708 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/QueryParser.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/QueryParser.java @@ -28,6 +28,7 @@ package org.hisp.dhis.query; import java.util.List; +import javax.annotation.Nonnull; import org.hisp.dhis.schema.Property; import org.hisp.dhis.schema.Schema; @@ -48,10 +49,10 @@ public interface QueryParser { * @return Query instance based on Schema of klass and filters list * @throws QueryParserException */ - Query parse(Class klass, List filters, Junction.Type rootJunction) + Query parse(Class klass, @Nonnull List filters, Junction.Type rootJunction) throws QueryParserException; - Query parse(Class klass, List filters) throws QueryParserException; + Query parse(Class klass, @Nonnull List filters) throws QueryParserException; Property getProperty(Schema schema, String path) throws QueryParserException; } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/QueryService.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/QueryService.java index d48849d10c6b..816cee399409 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/QueryService.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/QueryService.java @@ -65,23 +65,8 @@ public interface QueryService { * orders. * * @param klass Type of object you want to query - * @param filters List of filters to use as basis for query instance - * @param orders List of orders to use for query - * @param rootJunction Root junction (defaults to AND) + * @param params standard object list query parameters * @return New query instance using provided filters/orders */ - Query getQueryFromUrl( - Class klass, - List filters, - List orders, - Pagination pagination, - Junction.Type rootJunction) - throws QueryParserException; - - Query getQueryFromUrl( - Class klass, List filters, List orders, Pagination pagination) - throws QueryParserException; - - Query getQueryFromUrl(Class klass, List filters, List orders) - throws QueryParserException; + Query getQueryFromUrl(Class klass, GetObjectListParams params) throws QueryParserException; } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/Restriction.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/Restriction.java index 210ae7c2cd32..5ebf3c95ff3e 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/Restriction.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/Restriction.java @@ -32,6 +32,7 @@ import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.experimental.Accessors; +import org.hisp.dhis.query.operators.InOperator; import org.hisp.dhis.query.operators.Operator; import org.hisp.dhis.query.planner.QueryPath; @@ -71,4 +72,11 @@ public Restriction asAttribute() { public String toString() { return "[" + path + ", op: " + operator + "]"; } + + @Override + public boolean isAlwaysFalse() { + if (operator instanceof InOperator in) + return in.getCollectionArgs().isEmpty() || in.getCollectionArgs().get(0).isEmpty(); + return false; + } } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/Restrictions.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/Restrictions.java index 1f3249b0612f..48bb0af8cd4b 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/Restrictions.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/Restrictions.java @@ -28,6 +28,7 @@ package org.hisp.dhis.query; import java.util.Collection; +import java.util.List; import org.hisp.dhis.query.operators.BetweenOperator; import org.hisp.dhis.query.operators.EmptyOperator; import org.hisp.dhis.query.operators.EqualOperator; @@ -45,6 +46,7 @@ import org.hisp.dhis.query.operators.NotTokenOperator; import org.hisp.dhis.query.operators.NullOperator; import org.hisp.dhis.query.operators.TokenOperator; +import org.hisp.dhis.schema.Schema; /** * @author Morten Olav Hansen @@ -131,5 +133,29 @@ public static Restriction isEmpty(String path) { return new Restriction(path, new EmptyOperator<>()); } + public static Disjunction query(Schema schema, String query) { + return or(schema, eq("id", query), eq("code", query), ilike("name", query, MatchMode.ANYWHERE)); + } + + public static Disjunction or(Schema schema, Criterion... filters) { + return or(schema, List.of(filters)); + } + + public static Disjunction or(Schema schema, List filters) { + Disjunction or = new Disjunction(schema); + or.add(filters); + return or; + } + + public static Conjunction and(Schema schema, Criterion... filters) { + return and(schema, List.of(filters)); + } + + public static Conjunction and(Schema schema, List filters) { + Conjunction and = new Conjunction(schema); + and.add(filters); + return and; + } + private Restrictions() {} } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/operators/EqualOperator.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/operators/EqualOperator.java index eca23c6e0e50..4a521ca46d42 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/operators/EqualOperator.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/operators/EqualOperator.java @@ -28,6 +28,7 @@ package org.hisp.dhis.query.operators; import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Join; import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; import java.util.Collection; @@ -86,6 +87,13 @@ public Predicate getPredicate(CriteriaBuilder builder, Root root, QueryPa return builder.equal(builder.size(root.get(queryPath.getPath())), value); } + if (queryPath.haveAlias()) { + for (Join join : root.getJoins()) { + if (join.getAlias().equals(queryPath.getAlias()[0])) { + return builder.equal(join.get(queryPath.getProperty().getFieldName()), args.get(0)); + } + } + } return builder.equal(root.get(queryPath.getPath()), args.get(0)); } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/operators/InOperator.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/operators/InOperator.java index 3670033e5978..2156d37fb07f 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/operators/InOperator.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/operators/InOperator.java @@ -28,6 +28,7 @@ package org.hisp.dhis.query.operators; import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Join; import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; import java.util.Collection; @@ -78,7 +79,13 @@ public Predicate getPredicate(CriteriaBuilder builder, Root root, QueryPa queryPath.getProperty().getItemKlass(), getCollectionArgs().get(0))); } - + if (queryPath.haveAlias()) { + for (Join join : root.getJoins()) { + if (join.getAlias().equals(queryPath.getAlias()[0])) { + return join.get(queryPath.getProperty().getFieldName()).in(getCollectionArgs().get(0)); + } + } + } return root.get(queryPath.getPath()).in(getCollectionArgs().get(0)); } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/operators/TokenOperator.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/operators/TokenOperator.java index 67f47a006d33..c3f4a0cd6edf 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/operators/TokenOperator.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/operators/TokenOperator.java @@ -61,7 +61,9 @@ public Criterion getHibernateCriterion(QueryPath queryPath) { @Override public Predicate getPredicate(CriteriaBuilder builder, Root root, QueryPath queryPath) { String value = caseSensitive ? getValue(String.class) : getValue(String.class).toLowerCase(); - + if (skipUidToken(value, queryPath)) { + return null; + } Predicate defaultSearch = builder.equal( builder.function( @@ -94,4 +96,15 @@ public Predicate getPredicate(CriteriaBuilder builder, Root root, QueryPa public boolean test(Object value) { return TokenUtils.test(value, getValue(String.class), caseSensitive, matchMode); } + + /** + * Return + * + * @param value + * @param query + * @return + */ + private boolean skipUidToken(String value, QueryPath query) { + return "uid".equals(query.getProperty().getFieldName()) && value.length() < 4; + } } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/planner/DefaultQueryPlanner.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/planner/DefaultQueryPlanner.java index 1757a0c573b9..cf5e5757b1d7 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/planner/DefaultQueryPlanner.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/query/planner/DefaultQueryPlanner.java @@ -217,14 +217,12 @@ private Query getQuery(Query query, boolean persistedOnly) { setQueryPathLocale(restriction); } - if (restriction.getQueryPath().isPersisted() - && !restriction.getQueryPath().haveAlias() - && !Attribute.ObjectType.isValidType(restriction.getQueryPath().getPath())) { - pQuery - .getAliases() - .addAll(Arrays.asList(((Restriction) criterion).getQueryPath().getAlias())); + if (restriction.getQueryPath().isPersisted() && !isAttributeFilter(query, restriction)) { pQuery.getCriterions().add(criterion); iterator.remove(); + if (restriction.getQueryPath().haveAlias()) { + pQuery.getAliases().addAll(Arrays.asList(restriction.getQueryPath().getAlias())); + } } } } @@ -266,14 +264,15 @@ private Junction handleJunction(Query query, Junction queryJunction, boolean per setQueryPathLocale(restriction); } - if (restriction.getQueryPath().isPersisted() - && !restriction.getQueryPath().haveAlias(1) - && !Attribute.ObjectType.isValidType(restriction.getQueryPath().getPath())) { - criteriaJunction - .getAliases() - .addAll(Arrays.asList(((Restriction) criterion).getQueryPath().getAlias())); + if (restriction.getQueryPath().isPersisted() && !isAttributeFilter(query, restriction)) { + if (restriction.getQueryPath().haveAlias()) { + criteriaJunction + .getAliases() + .addAll(Arrays.asList(restriction.getQueryPath().getAlias())); + } criteriaJunction.getCriterions().add(criterion); iterator.remove(); + } else if (persistedOnly) { throw new RuntimeException( "Path " @@ -300,4 +299,14 @@ private boolean isFilterByAttributeId(Property curProperty, String propertyName) private void setQueryPathLocale(Restriction restriction) { restriction.getQueryPath().setLocale(UserSettings.getCurrentSettings().getUserDbLocale()); } + + /** + * Handle attribute filter such as /attributes?fields=id,name&filter=userAttribute:eq:true + * + * @return true if attribute filter + */ + private boolean isAttributeFilter(Query query, Restriction restriction) { + return query.getSchema().getKlass().isAssignableFrom(Attribute.class) + && Attribute.ObjectType.isValidType(restriction.getQueryPath().getPath()); + } } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/oidc/DhisOidcUser.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/oidc/DhisOidcUser.java index 90818d9ba386..4b77673bf69e 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/oidc/DhisOidcUser.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/oidc/DhisOidcUser.java @@ -214,6 +214,11 @@ public boolean isTwoFactorEnabled() { return user.isTwoFactorEnabled(); } + @Override + public boolean isEmailVerified() { + return user.isEmailVerified(); + } + @Override public boolean hasAnyRestrictions(Collection restrictions) { return user.hasAnyRestrictions(restrictions); diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/spring2fa/TwoFactorAuthenticationProvider.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/spring2fa/TwoFactorAuthenticationProvider.java index e90db9f15e18..82b438917d0b 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/spring2fa/TwoFactorAuthenticationProvider.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/spring2fa/TwoFactorAuthenticationProvider.java @@ -51,6 +51,7 @@ /** * @author Henning Håkonsen + * @author Morten Svanæs */ @Slf4j @Component diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/user/DefaultUserService.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/user/DefaultUserService.java index 8f24b4792bed..6409343033a1 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/user/DefaultUserService.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/user/DefaultUserService.java @@ -68,10 +68,11 @@ import org.hisp.dhis.common.CodeGenerator; import org.hisp.dhis.common.IdentifiableObject; import org.hisp.dhis.common.PasswordGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.common.UserOrgUnitType; -import org.hisp.dhis.commons.filter.FilterUtils; import org.hisp.dhis.dataset.DataSet; import org.hisp.dhis.email.EmailResponse; +import org.hisp.dhis.feedback.ConflictException; import org.hisp.dhis.feedback.ErrorCode; import org.hisp.dhis.feedback.ErrorReport; import org.hisp.dhis.feedback.ForbiddenException; @@ -88,7 +89,6 @@ import org.hisp.dhis.security.TwoFactoryAuthenticationUtils; import org.hisp.dhis.security.acl.AclService; import org.hisp.dhis.setting.SystemSettingsProvider; -import org.hisp.dhis.system.filter.UserRoleCanIssueFilter; import org.hisp.dhis.system.util.ValidationUtils; import org.hisp.dhis.system.velocity.VelocityManager; import org.hisp.dhis.util.DateUtils; @@ -313,19 +313,35 @@ public List getUsers(UserQueryParams params) { public List getUsers(UserQueryParams params, @Nullable List orders) { handleUserQueryParams(params); - if (isNotValidUserQueryParams(params)) { + try { + validateUserQueryParams(params); + } catch (ConflictException ex) { + log.warn(ex.getMessage()); return Lists.newArrayList(); } return userStore.getUsers(params, orders); } + @Override + @Transactional(readOnly = true) + public List getUserIds(UserQueryParams params, @CheckForNull List orders) + throws ConflictException { + handleUserQueryParams(params); + validateUserQueryParams(params); + + return userStore.getUserIds(params, orders); + } + @Override @Transactional(readOnly = true) public int getUserCount(UserQueryParams params) { handleUserQueryParams(params); - if (isNotValidUserQueryParams(params)) { + try { + validateUserQueryParams(params); + } catch (ConflictException ex) { + log.warn(ex.getMessage()); return 0; } @@ -381,25 +397,21 @@ private void handleUserQueryParams(UserQueryParams params) { } } - private boolean isNotValidUserQueryParams(UserQueryParams params) { + private void validateUserQueryParams(UserQueryParams params) throws ConflictException { if (params.isCanManage() && (params.getUser() == null || !params.getUser().hasManagedGroups())) { - log.warn("Cannot get managed users as user does not have any managed groups"); - return true; + throw new ConflictException( + "Cannot get managed users as user does not have any managed groups"); } - if (params.isAuthSubset() && (params.getUser() == null || !params.getUser().hasAuthorities())) { - log.warn("Cannot get users with authority subset as user does not have any authorities"); - return true; + throw new ConflictException( + "Cannot get users with authority subset as user does not have any authorities"); } - if (params.isDisjointRoles() && (params.getUser() == null || !params.getUser().hasUserRoles())) { - log.warn("Cannot get users with disjoint roles as user does not have any user roles"); - return true; + throw new ConflictException( + "Cannot get users with disjoint roles as user does not have any user roles"); } - - return false; } @Override @@ -544,12 +556,15 @@ public int countDataSetUserRoles(DataSet dataSet) { @Override @Transactional(readOnly = true) - public void canIssueFilter(Collection userRoles) { - User user = getUserByUsername(CurrentUserUtil.getCurrentUsername()); + public List getRolesCurrentUserCanIssue() { + UserDetails user = CurrentUserUtil.getCurrentUserDetails(); boolean canGrantOwnUserRoles = settingsProvider.getCurrentSettings().getCanGrantOwnUserRoles(); - FilterUtils.filter(userRoles, new UserRoleCanIssueFilter(user, canGrantOwnUserRoles)); + return userRoleStore.getAll().stream() + .filter(role -> user.canIssueUserRole(role, canGrantOwnUserRoles)) + .map(role -> UID.of(role.getUid())) + .toList(); } @Override @@ -1577,6 +1592,12 @@ public User getUserByVerificationToken(String token) { return userStore.getUserByVerificationToken(token); } + @Override + public List getUsersWithOrgUnit( + @Nonnull UserOrgUnitProperty orgUnitProperty, @Nonnull UID uid) { + return userStore.getUsersWithOrgUnit(orgUnitProperty, uid); + } + @Override public boolean isEmailVerified(User user) { return user.getEmail().equals(user.getVerifiedEmail()); diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/user/hibernate/HibernateUserStore.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/user/hibernate/HibernateUserStore.java index 6187a6fe5d27..688c3a76c70f 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/user/hibernate/HibernateUserStore.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/user/hibernate/HibernateUserStore.java @@ -47,12 +47,9 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; -import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -63,6 +60,7 @@ import org.hibernate.query.Query; import org.hisp.dhis.cache.QueryCacheManager; import org.hisp.dhis.common.IdentifiableObjectUtils; +import org.hisp.dhis.common.UID; import org.hisp.dhis.common.UserOrgUnitType; import org.hisp.dhis.common.hibernate.HibernateIdentifiableObjectStore; import org.hisp.dhis.commons.util.SqlHelper; @@ -78,6 +76,7 @@ import org.hisp.dhis.user.UserAccountExpiryInfo; import org.hisp.dhis.user.UserGroup; import org.hisp.dhis.user.UserInvitationStatus; +import org.hisp.dhis.user.UserOrgUnitProperty; import org.hisp.dhis.user.UserQueryParams; import org.hisp.dhis.user.UserStore; import org.springframework.context.ApplicationEventPublisher; @@ -123,7 +122,7 @@ public void save(@Nonnull User user, boolean clearSharing) { @Override public List getUsers(UserQueryParams params, @Nullable List orders) { - Query userQuery = getUserQuery(params, orders, false); + Query userQuery = getUserQuery(params, orders, QueryMode.OBJECTS); return extractUserQueryUsers(userQuery.list()); } @@ -134,7 +133,7 @@ public List getUsers(UserQueryParams params) { @Override public List getExpiringUsers(UserQueryParams params) { - return extractUserQueryUsers(getUserQuery(params, null, false).list()); + return extractUserQueryUsers(getUserQuery(params, null, QueryMode.OBJECTS).list()); } @Override @@ -153,10 +152,17 @@ public List getExpiringUserAccounts(int inDays) { @Override public int getUserCount(UserQueryParams params) { - Long count = (Long) getUserQuery(params, null, true).uniqueResult(); + Long count = (Long) getUserQuery(params, null, QueryMode.COUNT).uniqueResult(); return count != null ? count.intValue() : 0; } + @Override + public List getUserIds(UserQueryParams params, @CheckForNull List orders) { + return getUserQuery(params, orders, QueryMode.IDS).stream() + .map(id -> UID.of((String) id)) + .toList(); + } + @Nonnull private List extractUserQueryUsers(@Nonnull List result) { if (result.isEmpty()) { @@ -174,30 +180,35 @@ private List extractUserQueryUsers(@Nonnull List result) { return users; } - private Query getUserQuery(UserQueryParams params, List orders, boolean count) { + private enum QueryMode { + OBJECTS, + COUNT, + IDS + } + + private Query getUserQuery(UserQueryParams params, List orders, QueryMode mode) { SqlHelper hlp = new SqlHelper(); List convertedOrder = null; - String hql = null; + String hql; - if (count) { + boolean fetch = mode == QueryMode.OBJECTS; + if (mode == QueryMode.COUNT) { hql = "select count(distinct u) "; + } else if (mode == QueryMode.IDS) { + hql = "select distinct u.uid "; } else { Schema userSchema = schemaService.getSchema(User.class); convertedOrder = QueryUtils.convertOrderStrings(orders, userSchema); - - hql = - Stream.of( - "select distinct u", - JpaQueryUtils.createSelectOrderExpression(convertedOrder, "u")) - .filter(Objects::nonNull) - .collect(Collectors.joining(",")); + String order = JpaQueryUtils.createSelectOrderExpression(convertedOrder, "u"); + hql = "select distinct u"; + if (order != null) hql += "," + order; hql += " "; } hql += "from User u "; - if (params.isPrefetchUserGroups() && !count) { + if (params.isPrefetchUserGroups() && fetch) { hql += "left join fetch u.groups g "; } else { hql += "left join u.groups g "; @@ -306,7 +317,7 @@ private Query getUserQuery(UserQueryParams params, List orders, boole + "and u.restoreExpiry < current_timestamp() "; } - if (!count) { + if (fetch) { String orderExpression = JpaQueryUtils.createOrderExpression(convertedOrder, "u"); hql += "order by " + StringUtils.defaultString(orderExpression, "u.surname, u.firstName"); } @@ -383,7 +394,7 @@ private Query getUserQuery(UserQueryParams params, List orders, boole query.setParameterList("userGroupIds", userGroupIds); } - if (!count) { + if (fetch) { if (params.getFirst() != null) { query.setFirstResult(params.getFirst()); } @@ -642,4 +653,18 @@ public User getUserByVerifiedEmail(String email) { query.setParameter("email", email); return query.uniqueResult(); } + + @Override + public List getUsersWithOrgUnit( + @Nonnull UserOrgUnitProperty orgUnitProperty, @Nonnull UID uid) { + return getQuery( + """ + select distinct u from User u + left join fetch u.%s ous + where ous.uid = :uid + """ + .formatted(orgUnitProperty.getValue())) + .setParameter("uid", uid.getValue()) + .getResultList(); + } } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/resources/i18n_global.properties b/dhis-2/dhis-services/dhis-service-core/src/main/resources/i18n_global.properties index 85ea64202b28..308f37641278 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/resources/i18n_global.properties +++ b/dhis-2/dhis-services/dhis-service-core/src/main/resources/i18n_global.properties @@ -1962,6 +1962,7 @@ data_integrity.periods_duplicates.section=Periods data_integrity.periods_duplicates.name=Duplicate Periods data_integrity.periods_duplicates.description=Lists all periods duplicates which have identical type and start date data_integrity.periods_duplicates.recommendation=Make sure all database references are moved to one of the duplicates before deleting the unused duplicates +data_integrity.option_groups_empty.name=Option groups with no options data_integrity.org_units_with_cyclic_references.section=Organisation Units data_integrity.org_units_with_cyclic_references.name=Organisation units with cyclic references data_integrity.org_units_with_cyclic_references.description=Lists all organisation units that have a conflicting parent hierarchy definition so that two organisation units become parent of each other or in other words they form a circular reference @@ -2033,6 +2034,7 @@ data_integrity.program_rule_actions_without_stage_id.name=Program rules with act data_integrity.program_rule_actions_without_stage_id.description=Lists program rules connected to an action of a type that requires a program stage but is not yet connected to one #YAML based data integrity checks data_integrity.categories_no_options.name=Categories with no category options +data_integrity.categories_dimensions_no_visualizations.name = Categories which are dimensions but which do not have any associated visualizations. data_integrity.categories_one_default_category.name=Only one default category should exist data_integrity.categories_one_default_category_combo.name=Only one default category combo should exist data_integrity.categories_one_default_category_option.name=Only one default category option should exist @@ -2042,6 +2044,8 @@ data_integrity.categories_unique_category_combo.name=Different category combinat data_integrity.invalid_category_combos.name=Invalid category combinations data_integrity.category_combos_unused.name=Category combinations not used by other metadata objects data_integrity.category_option_combos_disjoint.name=Category option combinations with disjoint associations. +data_integrity.category_option_combos_no_names.name=Category option combinations with no names +data_integrity.category_option_combos_have_duplicates.name=Category option combination duplicates (same category options and same category combo) data_integrity.category_option_group_sets_incomplete.name=Category option group sets which do not contain all category options. data_integrity.category_options_excess_groupset_membership.name=Category options which belong to multiple groups in a category option group set. data_integrity.category_options_no_categories.name=Category options with no categories. @@ -2050,7 +2054,8 @@ data_integrity.catoptioncombos_no_catcombo.name=Category options combinations wi data_integrity.cocs_wrong_cardinality.name=Category option combinations with incorrect cardinality. data_integrity.dashboards_no_items.name=Dashboards with no items. data_integrity.data_elements_aggregate_abandoned.name=Aggregate data elements that have not been changed in last 100 days and do not have any data values. -data_integrity.data_elements_aggregate_aggregation_operator.name=Non-numeric data elements which have an aggregation operator other than NONE. +data_integrity.data_elements_can_aggregate_with_none_operator.name=Aggregate data elements which can be aggregated but have an aggregation operator set to NONE. +data_integrity.data_elements_cannot_aggregate_operator_not_none.name=Data elements which cannot be aggregated but have an aggregation operator set to something other than NONE. data_integrity.data_elements_aggregate_no_analysis.name=Aggregate data elements not used in any favourites (directly or through indicators) data_integrity.data_elements_aggregate_no_data.name=Aggregate data elements with NO data values. data_integrity.data_elements_aggregate_no_groups.name=Aggregate data elements not in any data element groups. @@ -2112,6 +2117,7 @@ data_integrity.user_roles_with_no_users.name=User roles with no users data_integrity.option_groups_empty.users=Option groups with no options data_integrity.push_analysis_no_recipients.name=Push analyses without recipients + # -- End Data Integrity Checks--------------------------------------------# diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/dashboard/hibernate/Dashboard.hbm.xml b/dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/dashboard/hibernate/Dashboard.hbm.xml index 705ce90a3e12..59e8c98cbca5 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/dashboard/hibernate/Dashboard.hbm.xml +++ b/dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/dashboard/hibernate/Dashboard.hbm.xml @@ -39,6 +39,8 @@ + + diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/eventchangelog.hiberante/EventChangeLog.hbm.xml b/dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/eventchangelog.hiberante/EventChangeLog.hbm.xml new file mode 100644 index 000000000000..7c5376e0273a --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/eventchangelog.hiberante/EventChangeLog.hbm.xml @@ -0,0 +1,43 @@ + + + + + + + + + eventchangelog_sequence + + + + + + + + + + + + + + + + org.hisp.dhis.changelog.ChangeLogType + true + 12 + + + + + + + + + + + + diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/program/hibernate/ProgramTrackedEntityAttribute.hbm.xml b/dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/program/hibernate/ProgramTrackedEntityAttribute.hbm.xml index ac43c4c16ce5..505cd2d2422b 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/program/hibernate/ProgramTrackedEntityAttribute.hbm.xml +++ b/dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/program/hibernate/ProgramTrackedEntityAttribute.hbm.xml @@ -33,6 +33,8 @@ - + + + diff --git a/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/dataitem/query/ExpressionDimensionItemQueryTest.java b/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/dataitem/query/ExpressionDimensionItemQueryTest.java index 9d1098abed6e..70bdde9e0dcd 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/dataitem/query/ExpressionDimensionItemQueryTest.java +++ b/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/dataitem/query/ExpressionDimensionItemQueryTest.java @@ -40,14 +40,11 @@ class ExpressionDimensionItemQueryTest { @Test void testGetStatementContainsOwnerCheck() { - // Given MapSqlParameterSource anyMap = new MapSqlParameterSource(); ExpressionDimensionItemQuery query = new ExpressionDimensionItemQuery(); - // When String statement = query.getStatement(anyMap); - // Then assertTrue(statement.contains("(jsonb_extract_path_text(t.item_sharing, 'owner') = :userUid)")); } } diff --git a/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/expression/ExpressionService2Test.java b/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/expression/ExpressionServiceTest.java similarity index 98% rename from dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/expression/ExpressionService2Test.java rename to dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/expression/ExpressionServiceTest.java index e1c30e9ca515..b472290e850f 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/expression/ExpressionService2Test.java +++ b/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/expression/ExpressionServiceTest.java @@ -60,6 +60,7 @@ import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -112,7 +113,7 @@ * @author Luciano Fiandesio */ @ExtendWith(MockitoExtension.class) -class ExpressionService2Test extends TestBase { +class ExpressionServiceTest extends TestBase { @Mock private HibernateGenericStore hibernateGenericStore; @Mock private ConstantService constantService; @@ -229,8 +230,6 @@ class ExpressionService2Test extends TestBase { private String expressionN; - private String expressionO; - private String expressionP; private String expressionR; @@ -392,7 +391,6 @@ public void setUp() { + SEPARATOR + cocA.getUid() + "}"; - expressionO = "#{" + opA.getDimensionItem() + "}+sum(#{" + opB.getDimensionItem() + "})"; expressionP = "#{" + deB.getUid() + SEPARATOR + coc.getUid() + "}"; expressionR = "#{" @@ -693,20 +691,17 @@ void testGetBaseExpressionParams() { when(idObjectManager.getNoAcl( eq(OrganisationUnitGroup.class), - (java.util.Collection) - argThat(containsInAnyOrder(groupA.getUid(), groupB.getUid())))) + (Collection) argThat(containsInAnyOrder(groupA.getUid(), groupB.getUid())))) .thenReturn(List.of(groupA, groupB)); when(idObjectManager.getNoAcl( eq(DataSet.class), - (java.util.Collection) - argThat(containsInAnyOrder(dataSetA.getUid(), dataSetB.getUid())))) + (Collection) argThat(containsInAnyOrder(dataSetA.getUid(), dataSetB.getUid())))) .thenReturn(List.of(dataSetA, dataSetB)); when(idObjectManager.getNoAcl( eq(Program.class), - (java.util.Collection) - argThat(containsInAnyOrder(programA.getUid(), programB.getUid())))) + (Collection) argThat(containsInAnyOrder(programA.getUid(), programB.getUid())))) .thenReturn(List.of(programA, programB)); ExpressionParams baseParams = target.getBaseExpressionParams(info); diff --git a/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/period/PeriodDataProviderTest.java b/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/period/PeriodDataProviderTest.java index edd13c5cb893..6fdc1b699d3f 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/period/PeriodDataProviderTest.java +++ b/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/period/PeriodDataProviderTest.java @@ -31,8 +31,8 @@ import static org.hisp.dhis.period.PeriodDataProvider.BEFORE_AND_AFTER_DATA_YEARS_SUPPORTED; import static org.hisp.dhis.period.PeriodDataProvider.DEFAULT_FIRST_YEAR_SUPPORTED; import static org.hisp.dhis.period.PeriodDataProvider.DEFAULT_LATEST_YEAR_SUPPORTED; -import static org.hisp.dhis.period.PeriodDataProvider.DataSource.DATABASE; -import static org.hisp.dhis.period.PeriodDataProvider.DataSource.SYSTEM_DEFINED; +import static org.hisp.dhis.period.PeriodDataProvider.PeriodSource.DATABASE; +import static org.hisp.dhis.period.PeriodDataProvider.PeriodSource.SYSTEM_DEFINED; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/query/DefaultQueryServiceTest.java b/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/query/DefaultQueryServiceTest.java index 299d2fde1de6..bb84e91d776f 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/query/DefaultQueryServiceTest.java +++ b/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/query/DefaultQueryServiceTest.java @@ -73,7 +73,7 @@ public void setUp() { QueryPlanner queryPlanner = new DefaultQueryPlanner(schemaService, settingsService); subject = new DefaultQueryService( - queryParser, queryPlanner, criteriaQueryEngine, inMemoryQueryEngine); + queryParser, queryPlanner, schemaService, criteriaQueryEngine, inMemoryQueryEngine); } @Test diff --git a/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/query/GetObjectListParamsTest.java b/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/query/GetObjectListParamsTest.java new file mode 100644 index 000000000000..69ba2eac6088 --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/query/GetObjectListParamsTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2004-2024, 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.query; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import org.junit.jupiter.api.Test; + +/** + * Tests the parameter processing implemented in {@link GetObjectListParams}. + * + * @author Jan Bernitt + */ +class GetObjectListParamsTest { + + @Test + void testSplitFilters_empty() { + assertFilters(null, ""); + assertFilters(null, null); + } + + @Test + void testSplitFilters_2parts() { + assertFilters(List.of("name:empty"), "name:empty"); + assertFilters(List.of("organisationUnits:!empty"), "organisationUnits:!empty"); + assertFilters( + List.of("name:empty", "organisationUnits:!empty"), "name:empty,organisationUnits:!empty"); + } + + @Test + void testSplitFilters_3parts() { + assertFilters(List.of("name:eq:Peter"), "name:eq:Peter"); + assertFilters(List.of("parent.id:eq:a1234t6u8o"), "parent.id:eq:a1234t6u8o"); + assertFilters( + List.of("name:eq:Peter", "parent.id:eq:a1234t6u8o"), + "name:eq:Peter,parent.id:eq:a1234t6u8o"); + } + + @Test + void testSplitFilters_in() { + assertFilters(List.of("name:in:[Peter,Paul,Mary]"), "name:in:[Peter,Paul,Mary]"); + assertFilters( + List.of("age:eq:20", "name:in:[Peter,Paul,Mary]", "points:lt:200"), + "age:eq:20,name:in:[Peter,Paul,Mary],points:lt:200"); + } + + @Test + void testSplitFilters_allOperators() { + List ops = + List.of( + "eq", + "ieq", + "!eq", + "neq", + "ne", + "gt", + "lt", + "gte", + "ge", + "lte", + "le", + "like", + "!like", + "$like", + "!$like", + "like$", + "!like$", + "ilike", + "!ilike", + "startsWith", + "$ilike", + "!$ilike", + "token", + "!token", + "endsWith", + "ilkike$", + "!ilike$", + "in", + "!in", + "null", + "empty", + "!null"); + for (String op : ops) { + String filter = "name:%s:value".formatted(op); + assertFilters(List.of(filter), filter); + assertFilters(List.of("foo:eq:bar", filter), "foo:eq:bar," + filter); + assertFilters(List.of("foo:eq:bar", filter, "x:empty"), "foo:eq:bar," + filter + ",x:empty"); + } + } + + private static void assertFilters(List expected, String actual) { + assertEquals(expected, GetObjectListParams.splitFilters(actual)); + } +} diff --git a/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/query/TokenOperatorTest.java b/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/query/TokenOperatorTest.java index d01901dfd3e1..9ba40b3223f6 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/query/TokenOperatorTest.java +++ b/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/query/TokenOperatorTest.java @@ -28,11 +28,15 @@ package org.hisp.dhis.query; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import org.hisp.dhis.query.operators.MatchMode; import org.hisp.dhis.query.operators.NotTokenOperator; import org.hisp.dhis.query.operators.TokenOperator; +import org.hisp.dhis.query.planner.QueryPath; +import org.hisp.dhis.schema.Property; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; /** @@ -43,6 +47,16 @@ */ class TokenOperatorTest { + @Test + @DisplayName("Uid Token filter must have at least 4 characters. Otherwise return null.") + void testUidLength() { + TokenOperator operator = new TokenOperator("ABC", false, MatchMode.ANYWHERE); + Property property = new Property(); + property.setFieldName("uid"); + QueryPath queryPath = new QueryPath(property, true); + assertNull(operator.getPredicate(null, null, queryPath)); + } + @Test void nullValue() { for (MatchMode mode : MatchMode.values()) { diff --git a/dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/metadata/DefaultMetadataExportService.java b/dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/metadata/DefaultMetadataExportService.java index 31f7ffd44176..6a445daafc06 100644 --- a/dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/metadata/DefaultMetadataExportService.java +++ b/dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/metadata/DefaultMetadataExportService.java @@ -36,7 +36,6 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.common.collect.Sets; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; @@ -69,7 +68,6 @@ import org.hisp.dhis.dataset.DataSetElement; import org.hisp.dhis.dataset.Section; import org.hisp.dhis.document.Document; -import org.hisp.dhis.dxf2.common.OrderParams; import org.hisp.dhis.eventchart.EventChart; import org.hisp.dhis.eventreport.EventReport; import org.hisp.dhis.eventvisualization.EventVisualization; @@ -97,6 +95,7 @@ import org.hisp.dhis.programrule.ProgramRuleService; import org.hisp.dhis.programrule.ProgramRuleVariable; import org.hisp.dhis.programrule.ProgramRuleVariableService; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.query.Query; import org.hisp.dhis.query.QueryService; import org.hisp.dhis.report.Report; @@ -177,12 +176,12 @@ public Map, List> parameter for (Map.Entry, Map>> entry : map.entrySet()) { Map> classMap = entry.getValue(); - Schema schema = schemaService.getDynamicSchema(entry.getKey()); - - if (classMap.containsKey("fields")) params.addFields(entry.getKey(), classMap.get("fields")); - - if (classMap.containsKey("filter") && classMap.containsKey("order")) { - OrderParams orderParams = new OrderParams(Sets.newHashSet(classMap.get("order"))); - Query query = - queryService.getQueryFromUrl( - entry.getKey(), classMap.get("filter"), orderParams.getOrders(schema)); - query.setDefaultOrder(); - params.addQuery(query); - } else if (classMap.containsKey("filter")) { - Query query = - queryService.getQueryFromUrl(entry.getKey(), classMap.get("filter"), new ArrayList<>()); - query.setDefaultOrder(); - params.addQuery(query); - } else if (classMap.containsKey("order")) { - OrderParams orderParams = new OrderParams(); - orderParams.setOrder(Sets.newHashSet(classMap.get("order"))); - - Query query = - queryService.getQueryFromUrl( - entry.getKey(), new ArrayList<>(), orderParams.getOrders(schema)); + Class objectType = entry.getKey(); + if (classMap.containsKey("fields")) params.addFields(objectType, classMap.get("fields")); + + List filters = classMap.get("filter"); + List orders = classMap.get("order"); + if (filters != null || orders != null) { + GetObjectListParams queryParams = + new GetObjectListParams().setPaging(false).setFilters(filters).setOrders(orders); + Query query = queryService.getQueryFromUrl(objectType, queryParams); query.setDefaultOrder(); params.addQuery(query); } diff --git a/dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/metadata/objectbundle/hooks/DataElementObjectBundleHook.java b/dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/metadata/objectbundle/hooks/DataElementObjectBundleHook.java index 321cdb3eff44..91dcb119acc4 100644 --- a/dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/metadata/objectbundle/hooks/DataElementObjectBundleHook.java +++ b/dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/metadata/objectbundle/hooks/DataElementObjectBundleHook.java @@ -28,18 +28,35 @@ package org.hisp.dhis.dxf2.metadata.objectbundle.hooks; import java.util.function.Consumer; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.hisp.dhis.common.UID; +import org.hisp.dhis.common.ValueType; import org.hisp.dhis.dataelement.DataElement; +import org.hisp.dhis.datavalue.DataValue; +import org.hisp.dhis.datavalue.DataValueService; import org.hisp.dhis.dxf2.metadata.objectbundle.ObjectBundle; import org.hisp.dhis.feedback.ErrorCode; import org.hisp.dhis.feedback.ErrorReport; +import org.hisp.dhis.preheat.PreheatIdentifier; import org.hisp.dhis.textpattern.TextPatternParser; import org.springframework.stereotype.Component; +@Slf4j @Component +@RequiredArgsConstructor public class DataElementObjectBundleHook extends AbstractObjectBundleHook { + + private final DataValueService dataValueService; + @Override public void validate( DataElement dataElement, ObjectBundle bundle, Consumer addReports) { + fieldMaskValidation(dataElement, addReports); + valueTypeChangeValidation(dataElement, bundle, addReports); + } + + private void fieldMaskValidation(DataElement dataElement, Consumer addReports) { if (dataElement.getFieldMask() != null) { try { TextPatternParser.parse("\"" + dataElement.getFieldMask() + "\""); @@ -53,4 +70,52 @@ public void validate( } } } + + /** + * Performs validation to check if the {@link ValueType} of a {@link DataElement} is being + * changed.
+ * If the {@link ValueType} is not being changed, then no action taken
+ * If the {@link ValueType} is being changed, a check for {@link DataValue}s for the {@link + * DataElement} is performed: + * + *

    + *
  • If there are {@link DataValue}s for the {@link DataElement} then we prohibit the change + *
  • If there are no {@link DataValue}s for the {@link DataElement} then the change of {@link + * ValueType} is allowed + *
+ * + * @param dataElement {@link DataElement} to check + * @param bundle {@link ObjectBundle} to get existing {@link DataElement} + * @param addReports reports to add error if validation fails + */ + private void valueTypeChangeValidation( + DataElement dataElement, ObjectBundle bundle, Consumer addReports) { + log.debug("checking data element value type validation"); + + // existing value + DataElement dePreheat = bundle.getPreheat().get(PreheatIdentifier.UID, dataElement); + ValueType existingValueType = dePreheat.getValueType(); + + // new value + ValueType proposedNewValueType = dataElement.getValueType(); + + if (existingValueType != proposedNewValueType) { + log.debug( + "DataElement {} valueType {} is different from existing valueType {}, checking if any data associated with DataElement", + dataElement.getUid(), + proposedNewValueType, + existingValueType); + if (dataValueService.dataValueExistsForDataElement(UID.of(dataElement))) { + log.warn( + "DataElement {} has associated data values, changing of valueType is prohibited", + dataElement.getUid()); + addReports.accept( + new ErrorReport(DataElement.class, ErrorCode.E1121, dataElement.getUid())); + } else { + log.debug( + "DataElement {} has no associated data values, changing of valueType is allowed", + dataElement.getUid()); + } + } + } } diff --git a/dhis-2/dhis-services/dhis-service-dxf2/src/test/java/org/hisp/dhis/dxf2/metadata/DefaultMetadataExportServiceTest.java b/dhis-2/dhis-services/dhis-service-dxf2/src/test/java/org/hisp/dhis/dxf2/metadata/DefaultMetadataExportServiceTest.java index 376ddc884e9d..2901c0842942 100644 --- a/dhis-2/dhis-services/dhis-service-dxf2/src/test/java/org/hisp/dhis/dxf2/metadata/DefaultMetadataExportServiceTest.java +++ b/dhis-2/dhis-services/dhis-service-dxf2/src/test/java/org/hisp/dhis/dxf2/metadata/DefaultMetadataExportServiceTest.java @@ -64,6 +64,7 @@ import org.hisp.dhis.scheduling.JobConfiguration; import org.hisp.dhis.schema.Schema; import org.hisp.dhis.schema.SchemaService; +import org.hisp.dhis.user.SystemUser; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -214,12 +215,13 @@ void deprecatedAnalyticClassesSchemaRemovedTest() { schemas.forEach(s -> s.setPersisted(true)); Query query = Query.from(new Schema(EventReport.class, "eventReport", "eventReports")); - when(queryService.getQueryFromUrl(any(), any(), any())).thenReturn(query); + when(queryService.getQueryFromUrl(any(), any())).thenReturn(query); // return 5 schemas, including the 2 for the deprecated classes EventChart & EventReport when(schemaService.getMetadataSchemas()).thenReturn(schemas); // when + params.setCurrentUserDetails(new SystemUser()); service.getMetadata(params); // then diff --git a/dhis-2/dhis-services/dhis-service-node/pom.xml b/dhis-2/dhis-services/dhis-service-node/pom.xml index 5e75d0c0aa15..e3e72dfbabaa 100644 --- a/dhis-2/dhis-services/dhis-service-node/pom.xml +++ b/dhis-2/dhis-services/dhis-service-node/pom.xml @@ -127,10 +127,5 @@ mockito-junit-jupiter test - - org.hisp.dhis - dhis-service-field-filtering - - diff --git a/dhis-2/dhis-services/dhis-service-setting/src/main/java/org/hisp/dhis/setting/SystemSetting.java b/dhis-2/dhis-services/dhis-service-setting/src/main/java/org/hisp/dhis/setting/SystemSetting.java index e75984454ff0..bc20d2e231c9 100644 --- a/dhis-2/dhis-services/dhis-service-setting/src/main/java/org/hisp/dhis/setting/SystemSetting.java +++ b/dhis-2/dhis-services/dhis-service-setting/src/main/java/org/hisp/dhis/setting/SystemSetting.java @@ -32,10 +32,8 @@ import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; -import lombok.extern.slf4j.Slf4j; /** A system setting, for use in store layer only. */ -@Slf4j @Setter @Getter @ToString diff --git a/dhis-2/dhis-services/dhis-service-setting/src/test/java/org/hisp/dhis/setting/SystemSettingsTest.java b/dhis-2/dhis-services/dhis-service-setting/src/test/java/org/hisp/dhis/setting/SystemSettingsTest.java index cb0c4fba5ce9..0af982a766c3 100644 --- a/dhis-2/dhis-services/dhis-service-setting/src/test/java/org/hisp/dhis/setting/SystemSettingsTest.java +++ b/dhis-2/dhis-services/dhis-service-setting/src/test/java/org/hisp/dhis/setting/SystemSettingsTest.java @@ -90,7 +90,7 @@ void testIsTranslatable() { @Test void testKeysWithDefaults() { Set keys = SystemSettings.keysWithDefaults(); - assertEquals(135, keys.size()); + assertEquals(137, keys.size()); // just check some at random assertTrue(keys.contains("syncSkipSyncForDataChangedBefore")); assertTrue(keys.contains("keyTrackerDashboardLayout")); diff --git a/dhis-2/dhis-services/dhis-service-tracker/pom.xml b/dhis-2/dhis-services/dhis-service-tracker/pom.xml index abad84003560..e4067be79ef4 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/pom.xml +++ b/dhis-2/dhis-services/dhis-service-tracker/pom.xml @@ -33,10 +33,6 @@ org.hisp.dhis dhis-service-acl - - org.hisp.dhis - dhis-service-node - org.hisp.dhis dhis-support-artemis @@ -53,6 +49,10 @@ org.hisp.dhis dhis-support-system + + org.hisp.dhis + json-tree + org.hisp.dhis.rules rule-engine-jvm diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/TrackerIdScheme.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/TrackerIdScheme.java similarity index 97% rename from dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/TrackerIdScheme.java rename to dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/TrackerIdScheme.java index 7257548cf1c1..3dff25fd8e75 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/TrackerIdScheme.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/TrackerIdScheme.java @@ -25,7 +25,7 @@ * (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.imports; +package org.hisp.dhis.tracker; /** * @author Morten Olav Hansen diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/TrackerIdSchemeParam.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/TrackerIdSchemeParam.java similarity index 91% rename from dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/TrackerIdSchemeParam.java rename to dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/TrackerIdSchemeParam.java index 566b512ad7a3..a19e70c9f218 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/TrackerIdSchemeParam.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/TrackerIdSchemeParam.java @@ -25,7 +25,7 @@ * (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.imports; +package org.hisp.dhis.tracker; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -113,4 +113,20 @@ public MetadataIdentifier toMetadataIdentifier(String identifier) { } return MetadataIdentifier.of(this.idScheme, identifier, null); } + + /** + * Returns this {@link TrackerIdSchemeParam} as expected in a request parameter which makes it + * usable in tests. It is also a more readable format than what lombok produces. + */ + @Override + public String toString() { + if (this.idScheme == null) { + return ""; + } + if (this.idScheme == TrackerIdScheme.ATTRIBUTE) { + return this.idScheme.name() + ":" + this.attributeUid; + } + + return this.idScheme.name(); + } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/TrackerIdSchemeParams.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/TrackerIdSchemeParams.java similarity index 51% rename from dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/TrackerIdSchemeParams.java rename to dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/TrackerIdSchemeParams.java index 9639100ed5c5..fde59d9e68cc 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/TrackerIdSchemeParams.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/TrackerIdSchemeParams.java @@ -25,10 +25,12 @@ * (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.imports; +package org.hisp.dhis.tracker; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import java.io.Serializable; +import javax.annotation.Nonnull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -36,6 +38,7 @@ import org.hisp.dhis.category.CategoryOption; import org.hisp.dhis.category.CategoryOptionCombo; import org.hisp.dhis.common.IdentifiableObject; +import org.hisp.dhis.common.MetadataObject; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.program.Program; @@ -43,7 +46,11 @@ import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; /** - * Wrapper object to handle identifier-related parameters for tracker import/export + * IdScheme parameter for tracker import and export. + * + *

The default idScheme is {@link TrackerIdSchemeParam#UID}. The idScheme used for all metadata + * defaults to the value specified in the {@code idScheme} field. This can be overridden by + * metadata-specific fields, which take precedence over the general {@code idScheme} field. * * @author Stian Sandvold */ @@ -52,45 +59,122 @@ @NoArgsConstructor @AllArgsConstructor public class TrackerIdSchemeParams implements Serializable { - /** Specific identifier to match data elements on. */ - @JsonProperty @Builder.Default - private TrackerIdSchemeParam dataElementIdScheme = TrackerIdSchemeParam.UID; + /** IdScheme used for all metadata unless overridden by a metadata specific parameter. */ + @JsonProperty @Builder.Default private TrackerIdSchemeParam idScheme = TrackerIdSchemeParam.UID; - /** Specific identifier to match organisation units on. */ - @JsonProperty @Builder.Default - private TrackerIdSchemeParam orgUnitIdScheme = TrackerIdSchemeParam.UID; + /** Specific idScheme to match data elements on. */ + @JsonIgnore private TrackerIdSchemeParam dataElementIdScheme; - /** Specific identifier to match program on. */ - @JsonProperty @Builder.Default - private TrackerIdSchemeParam programIdScheme = TrackerIdSchemeParam.UID; + /** Specific idScheme to match organisation units on. */ + @JsonIgnore private TrackerIdSchemeParam orgUnitIdScheme; - /** Specific identifier to match program stage on. */ - @JsonProperty @Builder.Default - private TrackerIdSchemeParam programStageIdScheme = TrackerIdSchemeParam.UID; + /** Specific idScheme to match programs on. */ + @JsonIgnore private TrackerIdSchemeParam programIdScheme; - /** - * Specific identifier to match all metadata on. Will be overridden by metadata-specific - * idSchemes. - */ - @JsonProperty @Builder.Default private TrackerIdSchemeParam idScheme = TrackerIdSchemeParam.UID; + /** Specific idScheme to match program stages on. */ + @JsonIgnore private TrackerIdSchemeParam programStageIdScheme; + + /** Specific idScheme to match category option combos on. */ + @JsonIgnore private TrackerIdSchemeParam categoryOptionComboIdScheme; + + /** Specific idScheme to match category options on. */ + @JsonIgnore private TrackerIdSchemeParam categoryOptionIdScheme; + + @JsonProperty("dataElementIdScheme") + public void setDataElementIdScheme(TrackerIdSchemeParam idSchemeParam) { + this.dataElementIdScheme = idSchemeParam; + } + + @JsonProperty("dataElementIdScheme") + @Nonnull + public TrackerIdSchemeParam getDataElementIdScheme() { + return dataElementIdScheme != null ? dataElementIdScheme : idScheme; + } + + @JsonProperty("orgUnitIdScheme") + public void setOrgUnitIdScheme(TrackerIdSchemeParam idSchemeParam) { + this.orgUnitIdScheme = idSchemeParam; + } + + @JsonProperty("orgUnitIdScheme") + @Nonnull + public TrackerIdSchemeParam getOrgUnitIdScheme() { + return orgUnitIdScheme != null ? orgUnitIdScheme : idScheme; + } + + @JsonProperty("programIdScheme") + public void setProgramIdScheme(TrackerIdSchemeParam idSchemeParam) { + this.programIdScheme = idSchemeParam; + } - /** Specific identifier to match category option combo on. */ - @JsonProperty @Builder.Default - private TrackerIdSchemeParam categoryOptionComboIdScheme = TrackerIdSchemeParam.UID; + @JsonProperty("programIdScheme") + @Nonnull + public TrackerIdSchemeParam getProgramIdScheme() { + return programIdScheme != null ? programIdScheme : idScheme; + } - /** Specific identifier to match category option on. */ - @JsonProperty @Builder.Default - private TrackerIdSchemeParam categoryOptionIdScheme = TrackerIdSchemeParam.UID; + @JsonProperty("programStageIdScheme") + public void setProgramStageIdScheme(TrackerIdSchemeParam idSchemeParam) { + this.programStageIdScheme = idSchemeParam; + } + + @JsonProperty("programStageIdScheme") + @Nonnull + public TrackerIdSchemeParam getProgramStageIdScheme() { + return programStageIdScheme != null ? programStageIdScheme : idScheme; + } + + @JsonProperty("categoryOptionComboIdScheme") + public void setCategoryOptionComboIdScheme(TrackerIdSchemeParam idSchemeParam) { + this.categoryOptionComboIdScheme = idSchemeParam; + } + + @JsonProperty("categoryOptionComboIdScheme") + public TrackerIdSchemeParam getCategoryOptionComboIdScheme() { + return categoryOptionComboIdScheme != null ? categoryOptionComboIdScheme : idScheme; + } + + @JsonProperty("categoryOptionIdScheme") + public void setCategoryOptionIdScheme(TrackerIdSchemeParam idSchemeParam) { + this.categoryOptionIdScheme = idSchemeParam; + } + + @JsonProperty("categoryOptionIdScheme") + @Nonnull + public TrackerIdSchemeParam getCategoryOptionIdScheme() { + return categoryOptionIdScheme != null ? categoryOptionIdScheme : idScheme; + } + + /** + * Get the identifier using the matching {@code idScheme}. Defaults to {@link + * IdentifiableObject#getUid()} if the metadata has no dedicated {@code idScheme} parameter. + */ + public String getIdentifier(T metadata) { + if (metadata instanceof DataElement dataElement) { + return getDataElementIdScheme().getIdentifier(dataElement); + } else if (metadata instanceof OrganisationUnit orgUnit) { + return getOrgUnitIdScheme().getIdentifier(orgUnit); + } else if (metadata instanceof Program program) { + return getProgramIdScheme().getIdentifier(program); + } else if (metadata instanceof ProgramStage programStage) { + return getProgramStageIdScheme().getIdentifier(programStage); + } else if (metadata instanceof CategoryOptionCombo categoryOptionCombo) { + return getCategoryOptionComboIdScheme().getIdentifier(categoryOptionCombo); + } else if (metadata instanceof CategoryOption categoryOption) { + return getCategoryOptionIdScheme().getIdentifier(categoryOption); + } + return metadata.getUid(); + } public TrackerIdSchemeParam getByClass(Class klazz) { return switch (klazz.getSimpleName()) { - case "CategoryOptionCombo" -> categoryOptionComboIdScheme; - case "OrganisationUnit" -> orgUnitIdScheme; - case "CategoryOption" -> categoryOptionIdScheme; - case "DataElement" -> dataElementIdScheme; - case "Program" -> programIdScheme; - case "ProgramStage" -> programStageIdScheme; - default -> idScheme; + case "CategoryOptionCombo" -> getCategoryOptionComboIdScheme(); + case "OrganisationUnit" -> getOrgUnitIdScheme(); + case "CategoryOption" -> getCategoryOptionIdScheme(); + case "DataElement" -> getDataElementIdScheme(); + case "Program" -> getProgramIdScheme(); + case "ProgramStage" -> getProgramStageIdScheme(); + default -> getIdScheme(); }; } @@ -102,7 +186,7 @@ public TrackerIdSchemeParam getByClass(Class klazz) { * @return metadata identifier representing metadata using the idScheme */ public MetadataIdentifier toMetadataIdentifier(IdentifiableObject metadata) { - return idScheme.toMetadataIdentifier(metadata); + return getIdScheme().toMetadataIdentifier(metadata); } /** @@ -114,7 +198,7 @@ public MetadataIdentifier toMetadataIdentifier(IdentifiableObject metadata) { * @return metadata identifier representing metadata using the categoryOptionComboIdScheme */ public MetadataIdentifier toMetadataIdentifier(CategoryOptionCombo categoryOptionCombo) { - return categoryOptionComboIdScheme.toMetadataIdentifier(categoryOptionCombo); + return getCategoryOptionComboIdScheme().toMetadataIdentifier(categoryOptionCombo); } /** @@ -126,7 +210,7 @@ public MetadataIdentifier toMetadataIdentifier(CategoryOptionCombo categoryOptio * @return metadata identifier representing metadata using the categoryOptionIdScheme */ public MetadataIdentifier toMetadataIdentifier(CategoryOption categoryOption) { - return categoryOptionIdScheme.toMetadataIdentifier(categoryOption); + return getCategoryOptionIdScheme().toMetadataIdentifier(categoryOption); } /** @@ -137,7 +221,7 @@ public MetadataIdentifier toMetadataIdentifier(CategoryOption categoryOption) { * @return metadata identifier representing dataElement using the idScheme */ public MetadataIdentifier toMetadataIdentifier(DataElement dataElement) { - return dataElementIdScheme.toMetadataIdentifier(dataElement); + return getDataElementIdScheme().toMetadataIdentifier(dataElement); } /** @@ -148,7 +232,7 @@ public MetadataIdentifier toMetadataIdentifier(DataElement dataElement) { * @return metadata identifier representing metadata using the orgUnitIdScheme */ public MetadataIdentifier toMetadataIdentifier(OrganisationUnit orgUnit) { - return orgUnitIdScheme.toMetadataIdentifier(orgUnit); + return getOrgUnitIdScheme().toMetadataIdentifier(orgUnit); } /** @@ -159,7 +243,7 @@ public MetadataIdentifier toMetadataIdentifier(OrganisationUnit orgUnit) { * @return metadata identifier representing metadata using the programIdScheme */ public MetadataIdentifier toMetadataIdentifier(Program program) { - return programIdScheme.toMetadataIdentifier(program); + return getProgramIdScheme().toMetadataIdentifier(program); } /** @@ -167,9 +251,9 @@ public MetadataIdentifier toMetadataIdentifier(Program program) { * For more details refer to {@link TrackerIdSchemeParam#toMetadataIdentifier(IdentifiableObject)} * * @param programStage to create metadata identifier for - * @return metadata identifier representing metadata using the programIdScheme + * @return metadata identifier representing metadata using the programStageIdScheme */ public MetadataIdentifier toMetadataIdentifier(ProgramStage programStage) { - return programStageIdScheme.toMetadataIdentifier(programStage); + return getProgramStageIdScheme().toMetadataIdentifier(programStage); } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/deduplication/DefaultDeduplicationService.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/deduplication/DefaultDeduplicationService.java index cae1da224de1..2d494c1a6a08 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/deduplication/DefaultDeduplicationService.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/deduplication/DefaultDeduplicationService.java @@ -175,7 +175,7 @@ private void merge(DeduplicationMergeParams params) potentialDuplicateStore.moveRelationships(original, duplicate, mergeObject.getRelationships()); potentialDuplicateStore.moveEnrollments(original, duplicate, mergeObject.getEnrollments()); try { - trackerObjectDeletionService.deleteTrackedEntities(List.of(duplicate.getUid())); + trackerObjectDeletionService.deleteTrackedEntities(List.of(UID.of(duplicate))); } catch (NotFoundException e) { throw new RuntimeException("Could not find TrackedEntity: " + duplicate.getUid()); } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/enrollment/EnrollmentService.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/enrollment/EnrollmentService.java index 0add055cc7d5..b4697b19f436 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/enrollment/EnrollmentService.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/enrollment/EnrollmentService.java @@ -49,10 +49,12 @@ RelationshipItem getEnrollmentInRelationshipItem( UID uid, EnrollmentParams params, boolean includeDeleted) throws NotFoundException; /** Get all enrollments matching given params. */ + @Nonnull List getEnrollments(EnrollmentOperationParams params) throws BadRequestException, ForbiddenException; /** Get a page of enrollments matching given params. */ + @Nonnull Page getEnrollments(EnrollmentOperationParams params, PageParams pageParams) throws BadRequestException, ForbiddenException; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/DefaultEventChangeLogService.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/DefaultEventChangeLogService.java index 8be300780c0c..d3cfb0c0b417 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/DefaultEventChangeLogService.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/DefaultEventChangeLogService.java @@ -29,9 +29,18 @@ import static org.hisp.dhis.user.CurrentUserUtil.getCurrentUserDetails; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.List; +import java.util.Objects; import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nonnull; import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.tuple.Pair; +import org.hisp.dhis.changelog.ChangeLogType; import org.hisp.dhis.common.UID; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.feedback.ForbiddenException; @@ -40,6 +49,7 @@ import org.hisp.dhis.tracker.acl.TrackerAccessManager; import org.hisp.dhis.tracker.export.Page; import org.hisp.dhis.tracker.export.PageParams; +import org.locationtech.jts.geom.Geometry; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -48,7 +58,7 @@ public class DefaultEventChangeLogService implements EventChangeLogService { private final EventService eventService; - private final JdbcEventChangeLogStore jdbcEventChangeLogStore; + private final HibernateEventChangeLogStore hibernateEventChangeLogStore; private final HibernateTrackedEntityDataValueChangeLogStore trackedEntityDataValueChangeLogStore; private final TrackerAccessManager trackerAccessManager; @@ -60,7 +70,19 @@ public Page getEventChangeLog( // check existence and access eventService.getEvent(event); - return jdbcEventChangeLogStore.getEventChangeLog(event, operationParams.getOrder(), pageParams); + return hibernateEventChangeLogStore.getEventChangeLogs(event, operationParams, pageParams); + } + + @Transactional + @Override + public void deleteEventChangeLog(Event event) { + hibernateEventChangeLogStore.deleteEventChangeLog(event); + } + + @Transactional + @Override + public void deleteEventChangeLog(DataElement dataElement) { + hibernateEventChangeLogStore.deleteEventChangeLog(dataElement); } @Override @@ -89,6 +111,35 @@ public void addTrackedEntityDataValueChangeLog( trackedEntityDataValueChangeLog); } + @Override + @Transactional + public void addDataValueChangeLog( + Event event, + DataElement dataElement, + String previousValue, + String value, + ChangeLogType changeLogType, + String userName) { + + EventChangeLog eventChangeLog = + new EventChangeLog( + event, dataElement, null, previousValue, value, changeLogType, new Date(), userName); + + hibernateEventChangeLogStore.addEventChangeLog(eventChangeLog); + } + + @Override + @Transactional + public void addPropertyChangeLog( + @Nonnull Event currentEvent, @Nonnull Event event, @Nonnull String username) { + logIfChanged( + "occurredAt", Event::getOccurredDate, this::formatDate, currentEvent, event, username); + logIfChanged( + "scheduledAt", Event::getScheduledDate, this::formatDate, currentEvent, event, username); + logIfChanged( + "geometry", Event::getGeometry, this::formatGeometry, currentEvent, event, username); + } + @Override @Transactional(readOnly = true) public int countTrackedEntityDataValueChangeLogs( @@ -111,6 +162,73 @@ public void deleteTrackedEntityDataValueChangeLog(DataElement dataElement) { @Override @Transactional(readOnly = true) public Set getOrderableFields() { - return jdbcEventChangeLogStore.getOrderableFields(); + return hibernateEventChangeLogStore.getOrderableFields(); + } + + @Override + public Set>> getFilterableFields() { + return hibernateEventChangeLogStore.getFilterableFields(); + } + + private void logIfChanged( + String propertyName, + Function valueExtractor, + Function formatter, + Event currentEvent, + Event event, + String userName) { + + String currentValue = formatter.apply(valueExtractor.apply(currentEvent)); + String newValue = formatter.apply(valueExtractor.apply(event)); + + if (!Objects.equals(currentValue, newValue)) { + ChangeLogType changeLogType = getChangeLogType(currentValue, newValue); + + EventChangeLog eventChangeLog = + new EventChangeLog( + event, + null, + propertyName, + currentValue, + newValue, + changeLogType, + new Date(), + userName); + + hibernateEventChangeLogStore.addEventChangeLog(eventChangeLog); + } + } + + private ChangeLogType getChangeLogType(String oldValue, String newValue) { + if (isNewProperty(oldValue, newValue)) { + return ChangeLogType.CREATE; + } else if (isUpdateProperty(oldValue, newValue)) { + return ChangeLogType.UPDATE; + } else { + return ChangeLogType.DELETE; + } + } + + private boolean isNewProperty(String originalValue, String payloadValue) { + return originalValue == null && payloadValue != null; + } + + private boolean isUpdateProperty(String originalValue, String payloadValue) { + return originalValue != null && payloadValue != null; + } + + private String formatDate(Date date) { + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + return date != null ? formatter.format(date) : null; + } + + private String formatGeometry(Geometry geometry) { + if (geometry == null) { + return null; + } + + return Stream.of(geometry.getCoordinates()) + .map(c -> String.format("(%f, %f)", c.x, c.y)) + .collect(Collectors.joining(", ")); } } 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/EventChangeLog.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventChangeLog.java index dfbc383b5b8c..676d43e0cee6 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventChangeLog.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventChangeLog.java @@ -27,24 +27,79 @@ */ package org.hisp.dhis.tracker.export.event; -import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Date; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hisp.dhis.changelog.ChangeLogType; +import org.hisp.dhis.dataelement.DataElement; +import org.hisp.dhis.program.Event; import org.hisp.dhis.program.UserInfoSnapshot; -public record EventChangeLog( - /** - * Ideally we would reuse {@code org.hisp.dhis.webapi.controller.tracker.view.User} so our - * responses stay consistent. - */ - @JsonProperty UserInfoSnapshot createdBy, - @JsonProperty Date createdAt, - @JsonProperty String type, - @JsonProperty Change change) { - - public record Change(@JsonProperty DataValueChange dataValue) {} - - public record DataValueChange( - @JsonProperty String dataElement, - @JsonProperty String previousValue, - @JsonProperty String currentValue) {} +@NoArgsConstructor +@Getter +@Setter +public class EventChangeLog { + private long id; + + private Event event; + + private DataElement dataElement; + + private String eventProperty; + + private String previousValue; + + private String currentValue; + + private ChangeLogType changeLogType; + + private Date created; + + private String createdByUsername; + + private UserInfoSnapshot createdBy; + + public EventChangeLog( + Event event, + DataElement dataElement, + String eventProperty, + String previousValue, + String currentValue, + ChangeLogType changeLogType, + Date created, + String createdByUsername) { + this(event, dataElement, eventProperty, previousValue, currentValue, changeLogType, created); + this.createdByUsername = createdByUsername; + } + + public EventChangeLog( + Event event, + DataElement dataElement, + String eventProperty, + String previousValue, + String currentValue, + ChangeLogType changeLogType, + Date created, + UserInfoSnapshot createdBy) { + this(event, dataElement, eventProperty, previousValue, currentValue, changeLogType, created); + this.createdBy = createdBy; + } + + private EventChangeLog( + Event event, + DataElement dataElement, + String eventProperty, + String previousValue, + String currentValue, + ChangeLogType changeLogType, + Date created) { + this.event = event; + this.dataElement = dataElement; + this.eventProperty = eventProperty; + this.previousValue = previousValue; + this.currentValue = currentValue; + this.changeLogType = changeLogType; + this.created = created; + } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventChangeLogOperationParams.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventChangeLogOperationParams.java index 4872522b9da9..bbd8516024aa 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventChangeLogOperationParams.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventChangeLogOperationParams.java @@ -27,12 +27,12 @@ */ package org.hisp.dhis.tracker.export.event; -import java.util.ArrayList; -import java.util.List; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import org.apache.commons.lang3.tuple.Pair; +import org.hisp.dhis.common.QueryFilter; import org.hisp.dhis.common.SortDirection; import org.hisp.dhis.tracker.export.Order; @@ -41,22 +41,30 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) public class EventChangeLogOperationParams { - private List order; + private Order order; + private Pair filter; public static class EventChangeLogOperationParamsBuilder { - private final List order = new ArrayList<>(); + // Do not remove these unused methods. They hide the order and filter fields from the builder + // which Lombok + // does not support. + // They should be added via their respective orderBy and filterBy builder methods. + private EventChangeLogOperationParamsBuilder order(Order order) { + return this; + } - // Do not remove this unused method. This hides the order field from the builder which Lombok - // does not support. The repeated order field and private order method prevent access to order - // via the builder. - // Order should be added via the orderBy builder methods. - private EventChangeLogOperationParamsBuilder order(List order) { + private EventChangeLogOperationParamsBuilder filter(Pair filter) { return this; } public EventChangeLogOperationParamsBuilder orderBy(String field, SortDirection direction) { - this.order.add(new Order(field, direction)); + this.order = new Order(field, direction); + return this; + } + + public EventChangeLogOperationParamsBuilder filterBy(String field, QueryFilter filter) { + this.filter = Pair.of(field, filter); return this; } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventChangeLogService.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventChangeLogService.java index c09a29cbc4ac..c66b2e6e46c3 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventChangeLogService.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/EventChangeLogService.java @@ -29,6 +29,9 @@ import java.util.List; import java.util.Set; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.tuple.Pair; +import org.hisp.dhis.changelog.ChangeLogType; import org.hisp.dhis.common.UID; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.feedback.ForbiddenException; @@ -55,21 +58,45 @@ Page getEventChangeLog( List getTrackedEntityDataValueChangeLogs( TrackedEntityDataValueChangeLogQueryParams params); + @Deprecated(since = "2.42") void addTrackedEntityDataValueChangeLog( TrackedEntityDataValueChangeLog trackedEntityDataValueChangeLog); + void addDataValueChangeLog( + Event event, + DataElement dataElement, + String previousValue, + String value, + ChangeLogType changeLogType, + String userName); + + void addPropertyChangeLog( + @Nonnull Event currentEvent, @Nonnull Event event, @Nonnull String userName); + @Deprecated(since = "2.42") int countTrackedEntityDataValueChangeLogs(TrackedEntityDataValueChangeLogQueryParams params); void deleteTrackedEntityDataValueChangeLog(Event event); + void deleteEventChangeLog(Event event); + void deleteTrackedEntityDataValueChangeLog(DataElement dataElement); + void deleteEventChangeLog(DataElement dataElement); + /** * Fields the {@link #getEventChangeLog(UID, EventChangeLogOperationParams, PageParams)} can order - * event change logs by. Ordering by fields other than these is considered a programmer error. + * event change logs by. Ordering by fields other than these, is considered a programmer error. * Validation of user provided field names should occur before calling {@link * #getEventChangeLog(UID, EventChangeLogOperationParams, PageParams)}. */ Set getOrderableFields(); + + /** + * Fields the {@link #getEventChangeLog(UID, EventChangeLogOperationParams, PageParams)} can + * filter event change logs by. Filtering by fields other than these, is considered a programmer + * error. Validation of user provided field names should occur before calling {@link + * #getEventChangeLog(UID, EventChangeLogOperationParams, PageParams)}. + */ + Set>> getFilterableFields(); } 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 c51e10b1cdd6..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 @@ -41,7 +41,6 @@ import lombok.Getter; import org.hisp.dhis.category.CategoryOptionCombo; import org.hisp.dhis.common.AssignedUserSelectionMode; -import org.hisp.dhis.common.IdSchemes; import org.hisp.dhis.common.OrganisationUnitSelectionMode; import org.hisp.dhis.common.QueryFilter; import org.hisp.dhis.common.SortDirection; @@ -53,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 @@ -110,8 +110,6 @@ public class EventOperationParams { private CategoryOptionCombo categoryOptionCombo; - @Builder.Default private IdSchemes idSchemes = new IdSchemes(); - private boolean includeRelationships; /** @@ -149,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 a16d1f028544..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 @@ -136,13 +136,13 @@ public EventQueryParams map( .setEnrollmentOccurredAfter(operationParams.getEnrollmentOccurredAfter()) .setEventStatus(operationParams.getEventStatus()) .setCategoryOptionCombo(attributeOptionCombo) - .setIdSchemes(operationParams.getIdSchemes()) .setIncludeAttributes(false) .setIncludeAllDataElements(false) .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) @@ -262,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()); @@ -270,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 b1d60b2bf982..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 @@ -40,7 +40,6 @@ import org.apache.commons.collections4.SetUtils; import org.hisp.dhis.category.CategoryOptionCombo; import org.hisp.dhis.common.AssignedUserQueryParam; -import org.hisp.dhis.common.IdSchemes; import org.hisp.dhis.common.OrganisationUnitSelectionMode; import org.hisp.dhis.common.QueryFilter; import org.hisp.dhis.common.SortDirection; @@ -54,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; /** @@ -103,8 +103,6 @@ class EventQueryParams { private CategoryOptionCombo categoryOptionCombo; - private IdSchemes idSchemes = new IdSchemes(); - private boolean includeRelationships; /** @@ -152,6 +150,8 @@ class EventQueryParams { @Getter private AssignedUserQueryParam assignedUserQueryParam = AssignedUserQueryParam.ALL; + @Getter private TrackerIdSchemeParams idSchemeParams = TrackerIdSchemeParams.builder().build(); + public EventQueryParams() {} public boolean hasProgram() { @@ -376,15 +376,6 @@ public EventQueryParams setEnrollmentOccurredAfter(Date enrollmentOccurredAfter) return this; } - public IdSchemes getIdSchemes() { - return idSchemes; - } - - public EventQueryParams setIdSchemes(IdSchemes idSchemes) { - this.idSchemes = idSchemes; - return this; - } - public boolean isIncludeAttributes() { return includeAttributes; } @@ -560,7 +551,12 @@ public boolean isOrganisationUnitMode(OrganisationUnitSelectionMode mode) { public boolean isPathOrganisationUnitMode() { return orgUnitMode != null - && (orgUnitMode.equals(OrganisationUnitSelectionMode.DESCENDANTS) - || orgUnitMode.equals(OrganisationUnitSelectionMode.CHILDREN)); + && (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 94ac4d7ca2f0..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 @@ -29,6 +29,7 @@ import java.util.List; import java.util.Set; +import javax.annotation.Nonnull; import org.hisp.dhis.common.UID; import org.hisp.dhis.feedback.BadRequestException; import org.hisp.dhis.feedback.ForbiddenException; @@ -36,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; @@ -60,26 +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. */ - List getEvents(EventOperationParams params) throws BadRequestException, ForbiddenException; + @Nonnull + List getEvents(@Nonnull EventOperationParams params) + throws BadRequestException, ForbiddenException; /** * Get a page of events matching given params under the privileges of the currently authenticated * user. */ - Page getEvents(EventOperationParams params, PageParams pageParams) + @Nonnull + 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/HibernateEventChangeLogStore.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/HibernateEventChangeLogStore.java new file mode 100644 index 000000000000..b06bf4523e9d --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/HibernateEventChangeLogStore.java @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2004-2024, 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 static java.util.Map.entry; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.Query; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.tuple.Pair; +import org.hibernate.Session; +import org.hisp.dhis.changelog.ChangeLogType; +import org.hisp.dhis.common.QueryFilter; +import org.hisp.dhis.common.SortDirection; +import org.hisp.dhis.common.UID; +import org.hisp.dhis.dataelement.DataElement; +import org.hisp.dhis.program.Event; +import org.hisp.dhis.program.UserInfoSnapshot; +import org.hisp.dhis.tracker.export.Order; +import org.hisp.dhis.tracker.export.Page; +import org.hisp.dhis.tracker.export.PageParams; +import org.springframework.stereotype.Repository; + +@Repository("org.hisp.dhis.tracker.export.event.HibernateEventChangeLogStore") +public class HibernateEventChangeLogStore { + private static final String COLUMN_CHANGELOG_CREATED = "ecl.created"; + private static final String COLUMN_CHANGELOG_USER = "ecl.createdByUsername"; + private static final String COLUMN_CHANGELOG_DATA_ELEMENT = "d.uid"; + private static final String COLUMN_CHANGELOG_PROPERTY = "ecl.eventProperty"; + + private static final String DEFAULT_ORDER = + COLUMN_CHANGELOG_CREATED + " " + SortDirection.DESC.getValue(); + + /** + * Event change logs can be ordered by given fields which correspond to fields on {@link + * EventChangeLog}. Maps fields to DB columns. The order implementation for change logs is + * different from other tracker exporters {@link EventChangeLog} is the view which is already + * returned from the service/store. Tracker exporter services return a representation we have to + * map to a view model. This mapping is not necessary for change logs. + */ + private static final Map ORDERABLE_FIELDS = + Map.ofEntries( + entry("createdAt", COLUMN_CHANGELOG_CREATED), + entry("username", COLUMN_CHANGELOG_USER), + entry("dataElement", COLUMN_CHANGELOG_DATA_ELEMENT), + entry("property", COLUMN_CHANGELOG_PROPERTY)); + + private static final Map>, String> FILTERABLE_FIELDS = + Map.ofEntries( + entry(Pair.of("username", String.class), COLUMN_CHANGELOG_USER), + entry(Pair.of("dataElement", UID.class), COLUMN_CHANGELOG_DATA_ELEMENT), + entry(Pair.of("property", String.class), COLUMN_CHANGELOG_PROPERTY)); + + private final EntityManager entityManager; + + public HibernateEventChangeLogStore(EntityManager entityManager) { + this.entityManager = entityManager; + } + + public void addEventChangeLog(EventChangeLog eventChangeLog) { + entityManager.unwrap(Session.class).save(eventChangeLog); + } + + public Page getEventChangeLogs( + @Nonnull UID event, + @Nonnull EventChangeLogOperationParams operationParams, + @Nonnull PageParams pageParams) { + + Pair filter = operationParams.getFilter(); + + String hql = + """ + select ecl.event, + ecl.dataElement, + ecl.eventProperty, + ecl.previousValue, + ecl.currentValue, + ecl.changeLogType, + ecl.created, + ecl.createdByUsername, + u.firstName, + u.surname, + u.uid + from EventChangeLog ecl + join ecl.event e + left join ecl.dataElement d + left join ecl.createdBy u + where e.uid = :eventUid + """; + + if (filter != null) { + String filterField = + FILTERABLE_FIELDS.entrySet().stream() + .filter(entry -> entry.getKey().getLeft().equals(filter.getKey())) + .findFirst() + .map(Entry::getValue) + .get(); + + hql += String.format(" and %s = :filterValue ", filterField); + } + + hql += String.format("order by %s".formatted(sortExpressions(operationParams.getOrder()))); + + Query query = entityManager.createQuery(hql); + query.setParameter("eventUid", event.getValue()); + query.setFirstResult((pageParams.getPage() - 1) * pageParams.getPageSize()); + query.setMaxResults(pageParams.getPageSize() + 1); + + if (filter != null) { + query.setParameter("filterValue", filter.getValue().getFilter()); + } + + List results = query.getResultList(); + List eventChangeLogs = + results.stream() + .map( + row -> { + Event e = (Event) row[0]; + DataElement dataElement = (DataElement) row[1]; + String eventProperty = (String) row[2]; + String previousValue = (String) row[3]; + String currentValue = (String) row[4]; + ChangeLogType changeLogType = (ChangeLogType) row[5]; + Date created = (Date) row[6]; + + UserInfoSnapshot createdBy = + new UserInfoSnapshot((String) row[7], (String) row[8], (String) row[9]); + createdBy.setUid((String) row[10]); + + return new EventChangeLog( + e, + dataElement, + eventProperty, + previousValue, + currentValue, + changeLogType, + created, + createdBy); + }) + .toList(); + + Integer prevPage = pageParams.getPage() > 1 ? pageParams.getPage() - 1 : null; + if (eventChangeLogs.size() > pageParams.getPageSize()) { + return Page.withPrevAndNext( + eventChangeLogs.subList(0, pageParams.getPageSize()), + pageParams.getPage(), + pageParams.getPageSize(), + prevPage, + pageParams.getPage() + 1); + } + + return Page.withPrevAndNext( + eventChangeLogs, pageParams.getPage(), pageParams.getPageSize(), prevPage, null); + } + + public void deleteEventChangeLog(DataElement dataElement) { + String hql = "delete from EventChangeLog where dataElement = :dataElement"; + + entityManager.createQuery(hql).setParameter("dataElement", dataElement).executeUpdate(); + } + + public void deleteEventChangeLog(Event event) { + String hql = "delete from EventChangeLog where event = :event"; + + entityManager.createQuery(hql).setParameter("event", event).executeUpdate(); + } + + private static String sortExpressions(Order order) { + if (order == null) { + return DEFAULT_ORDER; + } + + StringBuilder orderBuilder = new StringBuilder(); + orderBuilder.append(ORDERABLE_FIELDS.get(order.getField())); + orderBuilder.append(" "); + orderBuilder.append(order.getDirection().getValue()); + + if (!order.getField().equals("createdAt")) { + orderBuilder.append(", ").append(DEFAULT_ORDER); + } + + return orderBuilder.toString(); + } + + public Set getOrderableFields() { + return ORDERABLE_FIELDS.keySet(); + } + + public Set>> getFilterableFields() { + return FILTERABLE_FIELDS.keySet(); + } +} diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/HibernateTrackedEntityDataValueChangeLogStore.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/HibernateTrackedEntityDataValueChangeLogStore.java index 4509a8fe541a..dfa9cc4bb1f0 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/HibernateTrackedEntityDataValueChangeLogStore.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/HibernateTrackedEntityDataValueChangeLogStore.java @@ -59,7 +59,7 @@ class HibernateTrackedEntityDataValueChangeLogStore { // Dependencies // ------------------------------------------------------------------------- - private EntityManager entityManager; + private final EntityManager entityManager; public HibernateTrackedEntityDataValueChangeLogStore(EntityManager entityManager) { this.entityManager = entityManager; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/JdbcEventChangeLogStore.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/JdbcEventChangeLogStore.java deleted file mode 100644 index 2cb074a0a17d..000000000000 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/JdbcEventChangeLogStore.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (c) 2004-2024, 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 static java.util.Map.entry; - -import java.util.List; -import java.util.Map; -import java.util.Set; -import lombok.RequiredArgsConstructor; -import org.hisp.dhis.common.SortDirection; -import org.hisp.dhis.common.UID; -import org.hisp.dhis.program.UserInfoSnapshot; -import org.hisp.dhis.tracker.export.Order; -import org.hisp.dhis.tracker.export.Page; -import org.hisp.dhis.tracker.export.PageParams; -import org.hisp.dhis.tracker.export.event.EventChangeLog.Change; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; -import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; -import org.springframework.stereotype.Repository; - -@Repository("org.hisp.dhis.tracker.export.event.JdbcEventChangeLogStore") -@RequiredArgsConstructor -class JdbcEventChangeLogStore { - private static final String COLUMN_CHANGELOG_CREATED = "created"; - private static final String DEFAULT_ORDER = - COLUMN_CHANGELOG_CREATED + " " + SortDirection.DESC.getValue(); - - /** - * Event change logs can be ordered by given fields which correspond to fields on {@link - * org.hisp.dhis.tracker.export.event.EventChangeLog}. Maps fields to DB columns. The order - * implementation for change logs is different from other tracker exporters {@link - * org.hisp.dhis.tracker.export.event.EventChangeLog} is the view which is already returned from - * the service/store. Tracker exporter services return a representation we have to map to a view - * model. This mapping is not necessary for change logs. - */ - private static final Map ORDERABLE_FIELDS = - Map.ofEntries(entry("createdAt", COLUMN_CHANGELOG_CREATED)); - - private final NamedParameterJdbcTemplate jdbcTemplate; - - private static final RowMapper customEventChangeLogRowMapper = - (rs, rowNum) -> { - UserInfoSnapshot createdBy = new UserInfoSnapshot(); - createdBy.setUsername(rs.getString("username")); - createdBy.setFirstName(rs.getString("firstname")); - createdBy.setSurname(rs.getString("surname")); - createdBy.setUid(rs.getString("useruid")); - - return new EventChangeLog( - createdBy, - rs.getTimestamp(COLUMN_CHANGELOG_CREATED), - rs.getString("type"), - new Change( - new EventChangeLog.DataValueChange( - rs.getString("dataelementuid"), - rs.getString("previousvalue"), - rs.getString("currentvalue")))); - }; - - public Page getEventChangeLog( - UID event, List order, PageParams pageParams) { - // language=SQL - String sql = - """ - select - cl.type, - case - when cl.type = 'CREATE' then cl.previouschangelogvalue - when cl.type = 'UPDATE' and cl.currentchangelogvalue is null then cl.currentvalue - when cl.type = 'UPDATE' and cl.currentchangelogvalue is not null then cl.currentchangelogvalue - end as currentvalue, - case - when cl.type = 'DELETE' then cl.previouschangelogvalue - when cl.type = 'UPDATE' then cl.previouschangelogvalue - end as previousvalue, cl.dataelementuid, cl.created, cl.username, cl.firstname, cl.surname, cl.useruid - from - (select t.created, d.uid as dataelementuid, t.audittype as type, u.firstname, u.surname, u.username, u.uid as useruid, - LAG (t.value) OVER (PARTITION BY t.eventid, t.dataelementid ORDER BY t.created DESC) AS currentchangelogvalue, - t.value as previouschangelogvalue, - e.eventdatavalues -> d.uid ->> 'value' as currentvalue - from trackedentitydatavalueaudit t - join event e using (eventid) - join dataelement d using (dataelementid) - join userinfo u on u.username = t.modifiedby - where t.audittype in ('CREATE', 'UPDATE', 'DELETE') - and e.uid = :uid - order by %s - limit :limit offset :offset) cl - """ - .formatted(sortExpressions(order)); - - final MapSqlParameterSource parameters = new MapSqlParameterSource(); - parameters - .addValue("uid", event.getValue()) - .addValue("limit", pageParams.getPageSize() + 1) - .addValue("offset", (pageParams.getPage() - 1) * pageParams.getPageSize()); - - List changeLogs = - jdbcTemplate.query(sql, parameters, customEventChangeLogRowMapper); - - Integer prevPage = pageParams.getPage() > 1 ? pageParams.getPage() - 1 : null; - if (changeLogs.size() > pageParams.getPageSize()) { - return Page.withPrevAndNext( - changeLogs.subList(0, pageParams.getPageSize()), - pageParams.getPage(), - pageParams.getPageSize(), - prevPage, - pageParams.getPage() + 1); - } - - return Page.withPrevAndNext( - changeLogs, pageParams.getPage(), pageParams.getPageSize(), prevPage, null); - } - - private static String sortExpressions(List order) { - if (order.isEmpty()) { - return DEFAULT_ORDER; - } - - return ORDERABLE_FIELDS.get(order.get(0).getField()) - + " " - + order.get(0).getDirection().getValue(); - } - - public Set getOrderableFields() { - return ORDERABLE_FIELDS.keySet(); - } -} 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 5056ad782d1e..8712da90c83f 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 @@ -47,7 +47,6 @@ import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -61,23 +60,23 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.hisp.dhis.attribute.AttributeValues; import org.hisp.dhis.category.CategoryOption; import org.hisp.dhis.category.CategoryOptionCombo; import org.hisp.dhis.common.AssignedUserSelectionMode; -import org.hisp.dhis.common.IdScheme; -import org.hisp.dhis.common.IdSchemes; import org.hisp.dhis.common.IdentifiableObjectManager; import org.hisp.dhis.common.QueryFilter; import org.hisp.dhis.common.QueryOperator; import org.hisp.dhis.common.UID; import org.hisp.dhis.common.collection.CollectionUtils; import org.hisp.dhis.commons.util.SqlHelper; -import org.hisp.dhis.commons.util.TextUtils; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.event.EventStatus; import org.hisp.dhis.eventdatavalue.EventDataValue; import org.hisp.dhis.hibernate.jsonb.type.JsonBinaryType; import org.hisp.dhis.hibernate.jsonb.type.JsonEventDataValueSetBinaryType; +import org.hisp.dhis.jsontree.JsonMixed; +import org.hisp.dhis.jsontree.JsonObject; import org.hisp.dhis.note.Note; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.program.Enrollment; @@ -93,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; @@ -118,29 +119,30 @@ @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 "; private static final String EVENT_NOTE_QUERY = - "select evn.eventid as evn_id," - + " n.noteid as note_id," - + " n.notetext as note_text," - + " n.created as note_created," - + " n.creator as note_creator," - + " n.uid as note_uid," - + " n.lastupdated as note_lastupdated," - + " userinfo.userinfoid as note_user_id," - + " userinfo.code as note_user_code," - + " userinfo.uid as note_user_uid," - + " userinfo.username as note_user_username," - + " userinfo.firstname as note_user_firstname," - + " userinfo.surname as note_user_surname" - + " from event_notes evn" - + " inner join note n" - + " on evn.noteid = n.noteid" - + " left join userinfo on n.lastupdatedby = userinfo.userinfoid "; + """ + select evn.eventid as evn_id,\ + n.noteid as note_id,\ + n.notetext as note_text,\ + n.created as note_created,\ + n.creator as note_creator,\ + n.uid as note_uid,\ + n.lastupdated as note_lastupdated,\ + userinfo.userinfoid as note_user_id,\ + userinfo.code as note_user_code,\ + userinfo.uid as note_user_uid,\ + userinfo.username as note_user_username,\ + userinfo.firstname as note_user_firstname,\ + userinfo.surname as note_user_surname\ + from event_notes evn\ + inner join note n\ + on evn.noteid = n.noteid\ + left join userinfo on n.lastupdatedby = userinfo.userinfoid\s"""; private static final String EVENT_STATUS_EQ = " ev.status = "; @@ -157,18 +159,26 @@ class JdbcEventStore implements EventStore { private static final String COLUMN_EVENT_ID = "ev_id"; private static final String COLUMN_EVENT_UID = "ev_uid"; private static final String COLUMN_PROGRAM_UID = "p_uid"; + private static final String COLUMN_PROGRAM_CODE = "p_code"; + private static final String COLUMN_PROGRAM_NAME = "p_name"; + private static final String COLUMN_PROGRAM_ATTRIBUTE_VALUES = "p_attributevalues"; private static final String COLUMN_PROGRAM_STAGE_UID = "ps_uid"; + private static final String COLUMN_PROGRAM_STAGE_CODE = "ps_code"; private static final String COLUMN_PROGRAM_STAGE_NAME = "ps_name"; + private static final String COLUMN_PROGRAM_STAGE_ATTRIBUTE_VALUES = "ps_attributevalues"; private static final String COLUMN_ENROLLMENT_UID = "en_uid"; private static final String COLUMN_ENROLLMENT_STATUS = "en_status"; private static final String COLUMN_ENROLLMENT_DATE = "en_enrollmentdate"; private static final String COLUMN_ORG_UNIT_UID = "orgunit_uid"; private static final String COLUMN_ORG_UNIT_CODE = "orgunit_code"; + private static final String COLUMN_ORG_UNIT_NAME = "orgunit_name"; + private static final String COLUMN_ORG_UNIT_ATTRIBUTE_VALUES = "orgunit_attributevalues"; private static final String COLUMN_TRACKEDENTITY_UID = "te_uid"; private static final String COLUMN_EVENT_OCCURRED_DATE = "ev_occurreddate"; 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"; @@ -178,13 +188,17 @@ class JdbcEventStore implements EventStore { private static final String COLUMN_EVENT_LAST_UPDATED_AT_CLIENT = "ev_lastupdatedatclient"; private static final String COLUMN_EVENT_COMPLETED_BY = "ev_completedby"; private static final String COLUMN_EVENT_ATTRIBUTE_OPTION_COMBO_UID = "coc_uid"; + private static final String COLUMN_EVENT_ATTRIBUTE_OPTION_COMBO_NAME = "coc_name"; + private static final String COLUMN_EVENT_ATTRIBUTE_OPTION_COMBO_CODE = "coc_code"; + private static final String COLUMN_EVENT_ATTRIBUTE_OPTION_COMBO_ATTRIBUTE_VALUES = + "coc_attributevalues"; private static final String COLUMN_EVENT_COMPLETED_DATE = "ev_completeddate"; private static final String COLUMN_EVENT_DELETED = "ev_deleted"; private static final String COLUMN_EVENT_ASSIGNED_USER_USERNAME = "user_assigned_username"; private static final String COLUMN_EVENT_ASSIGNED_USER_DISPLAY_NAME = "user_assigned_name"; private static final String COLUMN_USER_UID = "u_uid"; - private static final String COLUMN_ORG_UNIT_PATH = "ou_path"; private static final String DEFAULT_ORDER = COLUMN_EVENT_ID + " desc"; + private static final String COLUMN_ORG_UNIT_PATH = "ou_path"; private static final String USER_SCOPE_ORG_UNIT_PATH_LIKE_MATCH_QUERY = " ou.path like CONCAT(orgunit.path, '%') "; private static final String CUSTOM_ORG_UNIT_PATH_LIKE_MATCH_QUERY = @@ -238,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); @@ -268,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) { @@ -282,8 +297,6 @@ private List fetchEvents(EventQueryParams queryParams, PageParams pagePar String eventUid = resultSet.getString(COLUMN_EVENT_UID); - validateIdentifiersPresence(resultSet, queryParams.getIdSchemes()); - Event event; if (eventsByUid.containsKey(eventUid)) { event = eventsByUid.get(eventUid); @@ -296,20 +309,35 @@ private List fetchEvents(EventQueryParams queryParams, PageParams pagePar TrackedEntity te = new TrackedEntity(); te.setUid(resultSet.getString(COLUMN_TRACKEDENTITY_UID)); event.setStatus(EventStatus.valueOf(resultSet.getString(COLUMN_EVENT_STATUS))); + ProgramType programType = ProgramType.fromValue(resultSet.getString("p_type")); Program program = new Program(); - program.setUid(resultSet.getString("p_identifier")); + program.setUid(resultSet.getString(COLUMN_PROGRAM_UID)); + program.setCode(resultSet.getString(COLUMN_PROGRAM_CODE)); + program.setName(resultSet.getString(COLUMN_PROGRAM_NAME)); + program.setAttributeValues( + AttributeValues.of(resultSet.getString(COLUMN_PROGRAM_ATTRIBUTE_VALUES))); program.setProgramType(programType); + Enrollment enrollment = new Enrollment(); enrollment.setUid(resultSet.getString(COLUMN_ENROLLMENT_UID)); enrollment.setProgram(program); enrollment.setTrackedEntity(te); - OrganisationUnit ou = new OrganisationUnit(); - ou.setUid(resultSet.getString(COLUMN_ORG_UNIT_UID)); - ou.setCode(resultSet.getString(COLUMN_ORG_UNIT_CODE)); + + OrganisationUnit orgUnit = new OrganisationUnit(); + orgUnit.setUid(resultSet.getString(COLUMN_ORG_UNIT_UID)); + orgUnit.setCode(resultSet.getString(COLUMN_ORG_UNIT_CODE)); + orgUnit.setName(resultSet.getString(COLUMN_ORG_UNIT_NAME)); + orgUnit.setAttributeValues( + AttributeValues.of(resultSet.getString(COLUMN_ORG_UNIT_ATTRIBUTE_VALUES))); + event.setOrganisationUnit(orgUnit); + ProgramStage ps = new ProgramStage(); - ps.setUid(resultSet.getString("ps_identifier")); + ps.setUid(resultSet.getString(COLUMN_PROGRAM_STAGE_UID)); + ps.setCode(resultSet.getString(COLUMN_PROGRAM_STAGE_CODE)); ps.setName(resultSet.getString(COLUMN_PROGRAM_STAGE_NAME)); + ps.setAttributeValues( + AttributeValues.of(resultSet.getString(COLUMN_PROGRAM_STAGE_ATTRIBUTE_VALUES))); event.setDeleted(resultSet.getBoolean(COLUMN_EVENT_DELETED)); enrollment.setStatus( @@ -317,19 +345,29 @@ private List fetchEvents(EventQueryParams queryParams, PageParams pagePar enrollment.setFollowup(resultSet.getBoolean(COLUMN_ENROLLMENT_FOLLOWUP)); event.setEnrollment(enrollment); event.setProgramStage(ps); - event.setOrganisationUnit(ou); CategoryOptionCombo coc = new CategoryOptionCombo(); - coc.setUid(resultSet.getString("coc_identifier")); - Set options = - Arrays.stream(resultSet.getString("co_uids").split(TextUtils.COMMA)) - .map( - optionUid -> { - CategoryOption option = new CategoryOption(); - option.setUid(optionUid); - return option; - }) - .collect(Collectors.toSet()); + coc.setUid(resultSet.getString(COLUMN_EVENT_ATTRIBUTE_OPTION_COMBO_UID)); + coc.setCode(resultSet.getString(COLUMN_EVENT_ATTRIBUTE_OPTION_COMBO_CODE)); + coc.setName(resultSet.getString(COLUMN_EVENT_ATTRIBUTE_OPTION_COMBO_NAME)); + coc.setAttributeValues( + AttributeValues.of( + resultSet.getString(COLUMN_EVENT_ATTRIBUTE_OPTION_COMBO_ATTRIBUTE_VALUES))); + + String cosString = resultSet.getString("co_values"); + JsonMixed cosJson = JsonMixed.of(cosString); + JsonObject object = cosJson.asObject(); + Set options = new HashSet<>(object.names().size()); + for (String uid : object.names()) { + JsonObject categoryOptionJson = object.getObject(uid); + CategoryOption option = new CategoryOption(); + option.setUid(uid); + option.setCode(categoryOptionJson.getString("code").string("")); + option.setName(categoryOptionJson.getString("name").string("")); + option.setAttributeValues( + AttributeValues.of(categoryOptionJson.getObject("attributeValues").toJson())); + options.add(option); + } coc.setCategoryOptions(options); event.setAttributeOptionCombo(coc); @@ -371,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) { @@ -391,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(); @@ -438,101 +493,75 @@ private List fetchEvents(EventQueryParams queryParams, PageParams pagePar }); } - private Page getPage(PageParams pageParams, List events, LongSupplier eventCount) { - if (pageParams.isPageTotal()) { - return Page.withTotals( - events, pageParams.getPage(), pageParams.getPageSize(), eventCount.getAsLong()); + private EventDataValue parseEventDataValue( + TrackerIdSchemeParam dataElementIdScheme, ResultSet resultSet) throws SQLException { + String dataValueResult = resultSet.getString("ev_eventdatavalue"); + if (StringUtils.isEmpty(dataValueResult)) { + return null; } - - return Page.withoutTotals(events, pageParams.getPage(), pageParams.getPageSize()); - } - - @Override - public Set getOrderableFields() { - return ORDERABLE_FIELDS.keySet(); - } - - private String getIdSqlBasedOnIdScheme( - IdScheme idScheme, String uidSql, String attributeSql, String codeSql) { - if (idScheme == IdScheme.ID || idScheme == IdScheme.UID) { - return uidSql; - } else if (idScheme.isAttribute()) { - return String.format(attributeSql, idScheme.getAttribute()); - } else { - return codeSql; + String dataElement = getDataElementIdentifier(dataElementIdScheme, resultSet); + if (StringUtils.isEmpty(dataElement)) { + return null; } - } - private void validateIdentifiersPresence(ResultSet rowSet, IdSchemes idSchemes) - throws SQLException { - - if (StringUtils.isEmpty(rowSet.getString("p_identifier"))) { - throw new IllegalStateException( - String.format( - "Program %s does not have a value assigned for idScheme %s", - rowSet.getString(COLUMN_PROGRAM_UID), idSchemes.getProgramIdScheme().name())); + EventDataValue eventDataValue = new EventDataValue(); + eventDataValue.setDataElement(dataElement); + 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)); } - if (StringUtils.isEmpty(rowSet.getString("ps_identifier"))) { - throw new IllegalStateException( - String.format( - "ProgramStage %s does not have a value assigned for idScheme %s", - rowSet.getString(COLUMN_PROGRAM_STAGE_UID), - idSchemes.getProgramStageIdScheme().name())); + eventDataValue.setLastUpdated( + DateUtils.parseDate(dataValueJson.getString("lastUpdated").string(""))); + if (dataValueJson.has("lastUpdatedByUserInfo")) { + eventDataValue.setLastUpdatedByUserInfo( + EventUtils.jsonToUserInfo( + dataValueJson.getObject("lastUpdatedByUserInfo").toJson(), jsonMapper)); } - if (StringUtils.isEmpty(rowSet.getString("ou_identifier"))) { - throw new IllegalStateException( - String.format( - "OrgUnit %s does not have a value assigned for idScheme %s", - rowSet.getString(COLUMN_ORG_UNIT_UID), idSchemes.getOrgUnitIdScheme().name())); - } + return eventDataValue; + } - if (StringUtils.isEmpty(rowSet.getString("coc_identifier"))) { - throw new IllegalStateException( - String.format( - "CategoryOptionCombo %s does not have a value assigned for idScheme %s", - rowSet.getString(COLUMN_EVENT_ATTRIBUTE_OPTION_COMBO_UID), - idSchemes.getCategoryOptionComboIdScheme().name())); + 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 null; + } + 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 String getEventSelectIdentifiersByIdScheme(EventQueryParams params) { - IdSchemes idSchemes = params.getIdSchemes(); - - StringBuilder sqlBuilder = new StringBuilder(); + private Page getPage(PageParams pageParams, List events, LongSupplier eventCount) { + if (pageParams.isPageTotal()) { + return Page.withTotals( + events, pageParams.getPage(), pageParams.getPageSize(), eventCount.getAsLong()); + } - String ouTableName = getOuTableName(params); - - sqlBuilder.append( - getIdSqlBasedOnIdScheme( - idSchemes.getOrgUnitIdScheme(), - ouTableName + ".uid as ou_identifier, ", - ouTableName + ".attributevalues #>> '{%s, value}' as ou_identifier, ", - ouTableName + ".code as ou_identifier, ")); - - sqlBuilder.append( - getIdSqlBasedOnIdScheme( - idSchemes.getProgramIdScheme(), - "p.uid as p_identifier, ", - "p.attributevalues #>> '{%s, value}' as p_identifier, ", - "p.code as p_identifier, ")); - - sqlBuilder.append( - getIdSqlBasedOnIdScheme( - idSchemes.getProgramStageIdScheme(), - "ps.uid as ps_identifier, ", - "ps.attributevalues #>> '{%s, value}' as ps_identifier, ", - "ps.code as ps_identifier, ")); - - sqlBuilder.append( - getIdSqlBasedOnIdScheme( - idSchemes.getCategoryOptionComboIdScheme(), - "coc_agg.uid as coc_identifier, ", - "coc_agg.attributevalues #>> '{%s, value}' as coc_identifier, ", - "coc_agg.code as coc_identifier, ")); + return Page.withoutTotals(events, pageParams.getPage(), pageParams.getPageSize()); + } - return sqlBuilder.toString(); + public Set getOrderableFields() { + return ORDERABLE_FIELDS.keySet(); } private long getEventCount(EventQueryParams params) { @@ -578,7 +607,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)); @@ -602,6 +640,19 @@ 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 +"""); + } + if (queryParams.isIncludeRelationships()) { sqlBuilder.append(RELATIONSHIP_IDS_QUERY); } @@ -755,41 +806,49 @@ private String getEventSelectQuery( StringBuilder selectBuilder = new StringBuilder() - .append("select ") - .append(getEventSelectIdentifiersByIdScheme(params)) - .append(" ev.uid as ") + .append("select ev.uid as ") .append(COLUMN_EVENT_UID) - .append(", ") - .append("ou.uid as ") + .append(", ou.uid as ") .append(COLUMN_ORG_UNIT_UID) - .append(", ") - .append("ou.code as ") + .append(", ou.code as ") .append(COLUMN_ORG_UNIT_CODE) + .append(", ou.name as ") + .append(COLUMN_ORG_UNIT_NAME) + .append(", ou.attributevalues as ") + .append(COLUMN_ORG_UNIT_ATTRIBUTE_VALUES) .append(", p.uid as ") .append(COLUMN_PROGRAM_UID) + .append(", p.code as ") + .append(COLUMN_PROGRAM_CODE) + .append(", p.name as ") + .append(COLUMN_PROGRAM_NAME) + .append(", p.attributevalues as ") + .append(COLUMN_PROGRAM_ATTRIBUTE_VALUES) .append(", ps.uid as ") .append(COLUMN_PROGRAM_STAGE_UID) + .append(", ps.code as ") + .append(COLUMN_PROGRAM_STAGE_CODE) .append(", ps.name as ") .append(COLUMN_PROGRAM_STAGE_NAME) - .append(", ") - .append("ev.eventid as ") + .append(", ps.attributevalues as ") + .append(COLUMN_PROGRAM_STAGE_ATTRIBUTE_VALUES) + .append(", ev.eventid as ") .append(COLUMN_EVENT_ID) .append(", ev.status as ") .append(COLUMN_EVENT_STATUS) .append(", ev.occurreddate as ") .append(COLUMN_EVENT_OCCURRED_DATE) - .append(", ") - .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 ") .append(COLUMN_EVENT_STORED_BY) - .append(", ") - .append("ev.created as ") + .append(", ev.created as ") .append(COLUMN_EVENT_CREATED) - .append(", ") - .append("ev.createdatclient as ") + .append(", ev.createdatclient as ") .append(COLUMN_EVENT_CREATED_AT_CLIENT) .append(", ev.createdbyuserinfo as ") .append(COLUMN_EVENT_CREATED_BY) @@ -799,27 +858,28 @@ private String getEventSelectQuery( .append(COLUMN_EVENT_LAST_UPDATED_AT_CLIENT) .append(", ev.lastupdatedbyuserinfo as ") .append(COLUMN_EVENT_LAST_UPDATED_BY) - .append(", ") - .append("ev.completeddate as ") + .append(", ev.completeddate as ") .append(COLUMN_EVENT_COMPLETED_DATE) .append(", ev.deleted as ") .append(COLUMN_EVENT_DELETED) - .append(", ") .append( - "ST_AsText( ev.geometry ) as ev_geometry, au.uid as user_assigned, (au.firstName ||" - + " ' ' || au.surName) as ") + ", ST_AsText( ev.geometry ) as ev_geometry, au.uid as user_assigned, (au.firstName" + + " || ' ' || au.surName) as ") .append(COLUMN_EVENT_ASSIGNED_USER_DISPLAY_NAME) .append(",") .append( "au.firstName as user_assigned_first_name, au.surName as user_assigned_surname, ") .append("au.username as ") .append(COLUMN_EVENT_ASSIGNED_USER_USERNAME) - .append(",") - .append("coc_agg.uid as ") + .append(", coc_agg.uid as ") .append(COLUMN_EVENT_ATTRIBUTE_OPTION_COMBO_UID) - .append(", ") - .append("coc_agg.co_uids AS co_uids, ") - .append("coc_agg.co_count AS option_size, "); + .append(", coc_agg.code as ") + .append(COLUMN_EVENT_ATTRIBUTE_OPTION_COMBO_CODE) + .append(", coc_agg.name as ") + .append(COLUMN_EVENT_ATTRIBUTE_OPTION_COMBO_NAME) + .append(", coc_agg.attributevalues as ") + .append(COLUMN_EVENT_ATTRIBUTE_OPTION_COMBO_ATTRIBUTE_VALUES) + .append(", coc_agg.co_values AS co_values, coc_agg.co_count AS option_size, "); for (Order order : params.getOrder()) { if (order.getField() instanceof TrackedEntityAttribute tea) @@ -844,9 +904,6 @@ private String getEventSelectQuery( .append("p.type as p_type, ") .append("te.trackedentityid as te_id, te.uid as ") .append(COLUMN_TRACKEDENTITY_UID) - .append( - ", teou.uid as te_ou, teou.name as te_ou_name, te.created as te_created, te.inactive as" - + " te_inactive ") .append( getFromWhereClause( params, @@ -866,10 +923,6 @@ private boolean checkForOwnership(EventQueryParams params) { .isPresent(); } - private String getOuTableName(EventQueryParams params) { - return checkForOwnership(params) ? " evou" : " ou"; - } - private StringBuilder getFromWhereClause( EventQueryParams params, MapSqlParameterSource mapSqlParameterSource, @@ -898,8 +951,6 @@ private StringBuilder getFromWhereClause( fromBuilder .append("left join trackedentity te on te.trackedentityid=en.trackedentityid ") - .append( - "left join organisationunit teou on (te.organisationunitid=teou.organisationunitid) ") .append("left join userinfo au on (ev.assigneduserid=au.userinfoid) "); // JOIN attributes we need to filter on. @@ -1510,12 +1561,22 @@ private String addLastUpdatedFilters( */ private String getCategoryOptionComboQuery(User user) { String joinCondition = - " inner join (select coc.uid, coc.attributevalues, coc.code, coc.categoryoptioncomboid as" - + " id, string_agg(co.uid, ',') as co_uids, count(co.categoryoptionid) as co_count from" - + " categoryoptioncombo coc inner join categoryoptioncombos_categoryoptions cocco on" - + " coc.categoryoptioncomboid = cocco.categoryoptioncomboid inner join categoryoption" - + " co on cocco.categoryoptionid = co.categoryoptionid group by" - + " coc.categoryoptioncomboid "; + """ + inner join (select coc.uid, coc.code, coc.name, coc.attributevalues, coc.categoryoptioncomboid as id,\ + jsonb_object_agg( + co.uid, + jsonb_build_object( + 'name', co.name, + 'code', co.code, + 'attributeValues', co.attributevalues + ) + ) AS co_values, + count(co.categoryoptionid) as co_count from\ + categoryoptioncombo coc inner join categoryoptioncombos_categoryoptions cocco on\ + coc.categoryoptioncomboid = cocco.categoryoptioncomboid inner join categoryoption\ + co on cocco.categoryoptionid = co.categoryoptionid group by\ + coc.categoryoptioncomboid \ +"""; if (!isSuper(user)) { joinCondition = @@ -1571,11 +1632,12 @@ private String getOrderQuery(EventQueryParams params) { } private String getAttributeValueQuery() { - return "select pav.trackedentityid as pav_id, pav.created as pav_created, pav.lastupdated as" - + " pav_lastupdated, pav.value as pav_value, ta.uid as ta_uid, ta.name as ta_name," - + " ta.valuetype as ta_valuetype from trackedentityattributevalue pav inner join" - + " trackedentityattribute ta on" - + " pav.trackedentityattributeid=ta.trackedentityattributeid "; + return """ + select pav.trackedentityid as pav_id, pav.created as pav_created, pav.lastupdated as\ + pav_lastupdated, pav.value as pav_value, ta.uid as ta_uid, ta.name as ta_name,\ + ta.valuetype as ta_valuetype from trackedentityattributevalue pav inner join\ + trackedentityattribute ta on\ + pav.trackedentityattributeid=ta.trackedentityattributeid\s"""; } private boolean isSuper(User user) { @@ -1595,14 +1657,10 @@ private Set convertEventDataValueJsonIntoSet(String jsonString) private void setAccessiblePrograms(User user, EventQueryParams params) { if (!isSuper(user)) { params.setAccessiblePrograms( - manager.getDataReadAll(Program.class).stream() - .map(Program::getUid) - .map(UID::of) - .collect(Collectors.toSet())); + manager.getDataReadAll(Program.class).stream().map(UID::of).collect(Collectors.toSet())); params.setAccessibleProgramStages( manager.getDataReadAll(ProgramStage.class).stream() - .map(ProgramStage::getUid) .map(UID::of) .collect(Collectors.toSet())); } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/JdbcTrackedEntityChangeLogStore.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/JdbcTrackedEntityChangeLogStore.java index 8d6df21975ea..7edfbd6756c0 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/JdbcTrackedEntityChangeLogStore.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/JdbcTrackedEntityChangeLogStore.java @@ -138,7 +138,8 @@ where audit.audittype in ('CREATE', 'UPDATE', 'DELETE') List changeLogs; if (!attributes.isEmpty()) { - sql += """ + sql += + """ and tea.uid in (:attributes) """; parameters.addValue("attributes", attributes); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityOperationParamsMapper.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityOperationParamsMapper.java index 8ff005e65d70..35a645561f55 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityOperationParamsMapper.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityOperationParamsMapper.java @@ -267,7 +267,6 @@ private void validateTrackedEntityAttributeFilters( && orgUnits.isEmpty()) { List uniqueAttributeIds = trackedEntityAttributeService.getAllSystemWideUniqueTrackedEntityAttributes().stream() - .map(TrackedEntityAttribute::getUid) .map(UID::of) .toList(); @@ -310,7 +309,6 @@ private List getSearchableAttributeIds(TrackedEntityQueryParams params) { if (!params.hasProgram() && !params.hasTrackedEntityType()) { searchableAttributeIds.addAll( trackedEntityAttributeService.getAllSystemWideUniqueTrackedEntityAttributes().stream() - .map(TrackedEntityAttribute::getUid) .map(UID::of) .toList()); } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityQueryParams.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityQueryParams.java index 18b05cced58c..4a3f7f3db180 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityQueryParams.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityQueryParams.java @@ -43,7 +43,6 @@ import org.apache.commons.collections4.SetUtils; import org.hisp.dhis.common.AssignedUserQueryParam; import org.hisp.dhis.common.AssignedUserSelectionMode; -import org.hisp.dhis.common.BaseIdentifiableObject; import org.hisp.dhis.common.OrganisationUnitSelectionMode; import org.hisp.dhis.common.QueryFilter; import org.hisp.dhis.common.SortDirection; @@ -159,10 +158,7 @@ public boolean hasFilterForEvents() { /** Returns a list of attributes and filters combined. */ public Set getFilterIds() { - return filters.keySet().stream() - .map(BaseIdentifiableObject::getUid) - .map(UID::of) - .collect(Collectors.toSet()); + return filters.keySet().stream().map(UID::of).collect(Collectors.toSet()); } /** Indicates whether these parameters specify any filters. */ diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityService.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityService.java index 7ad9d43b4944..874886c46549 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityService.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityService.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.Set; +import javax.annotation.Nonnull; import org.hisp.dhis.common.UID; import org.hisp.dhis.feedback.BadRequestException; import org.hisp.dhis.feedback.ForbiddenException; @@ -69,10 +70,12 @@ TrackedEntity getTrackedEntity(UID uid, UID programIdentifier, TrackedEntityPara throws NotFoundException, ForbiddenException, BadRequestException; /** Get all tracked entities matching given params. */ + @Nonnull List getTrackedEntities(TrackedEntityOperationParams operationParams) throws BadRequestException, ForbiddenException, NotFoundException; /** Get a page of tracked entities matching given params. */ + @Nonnull Page getTrackedEntities(TrackedEntityOperationParams params, PageParams pageParams) throws BadRequestException, ForbiddenException, NotFoundException; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/aggregates/mapper/NoteRowCallbackHandler.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/aggregates/mapper/NoteRowCallbackHandler.java index 43971c8aa358..0526b7105945 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/aggregates/mapper/NoteRowCallbackHandler.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/aggregates/mapper/NoteRowCallbackHandler.java @@ -50,7 +50,7 @@ private Note getNote(ResultSet rs) throws SQLException { note.setUid(rs.getString("uid")); note.setNoteText(rs.getString("notetext")); note.setCreator(rs.getString("creator")); - note.setCreated(rs.getDate("created")); + note.setCreated(rs.getTimestamp("created")); return note; } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/aggregates/query/EnrollmentQuery.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/aggregates/query/EnrollmentQuery.java index dcd7aa5f84c7..9384db933f90 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/aggregates/query/EnrollmentQuery.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/aggregates/query/EnrollmentQuery.java @@ -95,7 +95,7 @@ public static String getQuery() { + "join program p on en.programid = p.programid " + "join trackedentity te on en.trackedentityid = te.trackedentityid " + "join trackedentitytype tet on te.trackedentitytypeid = tet.trackedentitytypeid " - + "join organisationunit o on te.organisationunitid = o.organisationunitid " + + "join organisationunit o on en.organisationunitid = o.organisationunitid " + "where en.trackedentityid in (:ids) "; } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/TrackerIdentifierCollector.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/TrackerIdentifierCollector.java index 1ea39b946112..d5e2382b9fb5 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/TrackerIdentifierCollector.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/TrackerIdentifierCollector.java @@ -33,10 +33,12 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import javax.annotation.Nonnull; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.hisp.dhis.category.CategoryOption; import org.hisp.dhis.category.CategoryOptionCombo; +import org.hisp.dhis.common.UID; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.program.Program; @@ -133,7 +135,7 @@ private void collectEnrollments( private void collectNotes(Map, Set> identifiers, List notes) { notes.forEach( note -> { - if (!StringUtils.isEmpty(note.getNote()) && !StringUtils.isEmpty(note.getValue())) { + if (note.getNote() != null && StringUtils.isNotEmpty(note.getValue())) { addIdentifier(identifiers, org.hisp.dhis.note.Note.class, note.getNote()); } }); @@ -186,17 +188,28 @@ private void collectRelationships( } private void addIdentifier( - Map, Set> identifiers, Class klass, MetadataIdentifier identifier) { + @Nonnull Map, Set> identifiers, + @Nonnull Class klass, + MetadataIdentifier identifier) { addIdentifier( identifiers, klass, identifier == null ? null : identifier.getIdentifierOrAttributeValue()); } private void addIdentifier( - Map, Set> identifiers, Class klass, String identifier) { - if (StringUtils.isEmpty(identifier) || identifiers == null || klass == null) { + @Nonnull Map, Set> identifiers, @Nonnull Class klass, String identifier) { + if (StringUtils.isEmpty(identifier)) { return; } identifiers.computeIfAbsent(klass, k -> new HashSet<>()).add(identifier); } + + private void addIdentifier( + @Nonnull Map, Set> identifiers, @Nonnull Class klass, UID identifier) { + if (identifier == null) { + return; + } + + identifiers.computeIfAbsent(klass, k -> new HashSet<>()).add(identifier.getValue()); + } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/TrackerImportParams.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/TrackerImportParams.java index 361f4e7faa4a..7f7eacf97280 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/TrackerImportParams.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/TrackerImportParams.java @@ -33,6 +33,7 @@ import lombok.Data; import lombok.NoArgsConstructor; import org.hisp.dhis.scheduling.JobParameters; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundleMode; /** diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/TrackerBundle.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/TrackerBundle.java index 1c41e5a9e85a..738447ea07e8 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/TrackerBundle.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/TrackerBundle.java @@ -38,6 +38,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import javax.annotation.Nonnull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -122,11 +123,10 @@ public class TrackerBundle { private Map>> eventRuleActionExecutors = new HashMap<>(); @Builder.Default - private Map> resolvedStrategyMap = - initStrategyMap(); + private Map> resolvedStrategyMap = initStrategyMap(); - private static Map> initStrategyMap() { - Map> resolvedStrategyMap = + private static Map> initStrategyMap() { + Map> resolvedStrategyMap = new EnumMap<>(TrackerType.class); resolvedStrategyMap.put(TrackerType.RELATIONSHIP, new HashMap<>()); @@ -139,23 +139,23 @@ private static Map> initStrategy @Builder.Default @JsonIgnore private Set updatedTrackedEntities = new HashSet<>(); - public Optional findTrackedEntityByUid(String uid) { + public Optional findTrackedEntityByUid(@Nonnull UID uid) { return findById(this.trackedEntities, uid); } - public Optional findEnrollmentByUid(String uid) { + public Optional findEnrollmentByUid(@Nonnull UID uid) { return findById(this.enrollments, uid); } - public Optional findEventByUid(String uid) { + public Optional findEventByUid(@Nonnull UID uid) { return findById(this.events, uid); } - public Optional findRelationshipByUid(String uid) { + public Optional findRelationshipByUid(@Nonnull UID uid) { return findById(this.relationships, uid); } - private static Optional findById(List entities, String uid) { + private static Optional findById(List entities, UID uid) { return entities.stream().filter(e -> Objects.equals(e.getUid(), uid)).findFirst(); } @@ -167,50 +167,11 @@ public Map> getEventNotifications() { return Map.copyOf(eventNotifications); } - public TrackerImportStrategy setStrategy(TrackerDto dto, TrackerImportStrategy strategy) { - return this.getResolvedStrategyMap().get(dto.getTrackerType()).put(dto.getUid(), strategy); + public void setStrategy(TrackerDto dto, TrackerImportStrategy strategy) { + this.getResolvedStrategyMap().get(dto.getTrackerType()).put(dto.getUid(), strategy); } public TrackerImportStrategy getStrategy(TrackerDto dto) { return getResolvedStrategyMap().get(dto.getTrackerType()).get(dto.getUid()); } - - @SuppressWarnings("unchecked") - public List get(Class type) { - Objects.requireNonNull(type); - if (type == TrackedEntity.class) { - return (List) trackedEntities; - } else if (type == Enrollment.class) { - return (List) enrollments; - } else if (type == Event.class) { - return (List) events; - } else if (type == Relationship.class) { - return (List) relationships; - } - // only reached if a new TrackerDto implementation is added - throw new IllegalStateException("TrackerType " + type.getName() + " not yet supported."); - } - - /** Checks if an entity exists in the payload. */ - public boolean exists(T entity) { - return exists(entity.getTrackerType(), entity.getUid()); - } - - /** - * Checks if an entity of given type and UID exists in the payload. - * - * @param type tracker type - * @param uid uid of entity to check - * @return true if an entity of given type and UID exists in the payload - */ - public boolean exists(TrackerType type, String uid) { - Objects.requireNonNull(type); - - return switch (type) { - case TRACKED_ENTITY -> findTrackedEntityByUid(uid).isPresent(); - case ENROLLMENT -> findEnrollmentByUid(uid).isPresent(); - case EVENT -> findEventByUid(uid).isPresent(); - case RELATIONSHIP -> findRelationshipByUid(uid).isPresent(); - }; - } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/TrackerObjectsMapper.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/TrackerObjectsMapper.java index b446ed92da7e..d172f864e692 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/TrackerObjectsMapper.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/TrackerObjectsMapper.java @@ -34,6 +34,7 @@ import java.util.stream.Collectors; import javax.annotation.Nonnull; import org.hisp.dhis.category.CategoryOptionCombo; +import org.hisp.dhis.common.UID; import org.hisp.dhis.event.EventStatus; import org.hisp.dhis.note.Note; import org.hisp.dhis.organisationunit.OrganisationUnit; @@ -78,7 +79,7 @@ private TrackerObjectsMapper() { if (dbTrackedEntity == null) { dbTrackedEntity = new TrackedEntity(); - dbTrackedEntity.setUid(trackedEntity.getTrackedEntity()); + dbTrackedEntity.setUid(trackedEntity.getTrackedEntity().getValue()); dbTrackedEntity.setCreated(now); dbTrackedEntity.setCreatedByUserInfo(UserInfoSnapshot.from(user)); dbTrackedEntity.setStoredBy(trackedEntity.getStoredBy()); @@ -113,7 +114,7 @@ private TrackerObjectsMapper() { if (dbEnrollment == null) { dbEnrollment = new Enrollment(); - dbEnrollment.setUid(enrollment.getEnrollment()); + dbEnrollment.setUid(enrollment.getEnrollment().getValue()); dbEnrollment.setCreated(now); dbEnrollment.setStoredBy(enrollment.getStoredBy()); dbEnrollment.setCreatedByUserInfo(UserInfoSnapshot.from(user)); @@ -182,7 +183,7 @@ private TrackerObjectsMapper() { if (dbEvent == null) { dbEvent = new Event(); - dbEvent.setUid(event.getEvent()); + dbEvent.setUid(event.getEvent().getValue()); dbEvent.setCreated(now); dbEvent.setStoredBy(event.getStoredBy()); dbEvent.setCreatedByUserInfo(UserInfoSnapshot.from(user)); @@ -256,7 +257,7 @@ private TrackerObjectsMapper() { @Nonnull UserDetails user) { Date now = new Date(); Relationship dbRelationship = new org.hisp.dhis.relationship.Relationship(); - dbRelationship.setUid(relationship.getRelationship()); + dbRelationship.setUid(relationship.getRelationship().getValue()); dbRelationship.setCreated(now); dbRelationship.setLastUpdated(now); dbRelationship.setLastUpdatedBy(preheat.getUserByUid(user.getUid()).orElse(null)); @@ -309,7 +310,7 @@ private TrackerObjectsMapper() { Date now = new Date(); Note dbNote = new Note(); - dbNote.setUid(note.getNote()); + dbNote.setUid(note.getNote().getValue()); dbNote.setCreated(now); dbNote.setLastUpdated(now); dbNote.setLastUpdatedBy(preheat.getUserByUid(user.getUid()).orElse(null)); @@ -319,8 +320,7 @@ private TrackerObjectsMapper() { return dbNote; } - private static Enrollment getEnrollment( - TrackerPreheat preheat, String enrollment, Program program) { + private static Enrollment getEnrollment(TrackerPreheat preheat, UID enrollment, Program program) { return switch (program.getProgramType()) { case WITH_REGISTRATION -> preheat.getEnrollment(enrollment); case WITHOUT_REGISTRATION -> preheat.getEnrollmentsWithoutRegistration(program.getUid()); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/AbstractTrackerPersister.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/AbstractTrackerPersister.java index c5618e757319..150c9fe42ae1 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/AbstractTrackerPersister.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/AbstractTrackerPersister.java @@ -51,12 +51,12 @@ import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; import org.hisp.dhis.trackedentityattributevalue.TrackedEntityAttributeValue; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.TrackerType; import org.hisp.dhis.tracker.export.trackedentity.TrackedEntityAttributeValueChangeLog; import org.hisp.dhis.tracker.export.trackedentity.TrackedEntityChangeLogService; import org.hisp.dhis.tracker.imports.AtomicMode; import org.hisp.dhis.tracker.imports.FlushMode; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Attribute; @@ -112,6 +112,8 @@ public TrackerTypeReport persist(EntityManager entityManager, TrackerBundle bund determineNotificationTriggers(bundle.getPreheat(), trackerDto); try { + V originalEntity = cloneEntityProperties(bundle.getPreheat(), trackerDto); + // // Convert the TrackerDto into an Hibernate-managed entity // @@ -122,14 +124,18 @@ public TrackerTypeReport persist(EntityManager entityManager, TrackerBundle bund // persistOwnership(bundle, trackerDto, convertedDto); - updateDataValues( - entityManager, bundle.getPreheat(), trackerDto, convertedDto, bundle.getUser()); - // // Save or update the entity // if (isNew(bundle, trackerDto)) { entityManager.persist(convertedDto); + updateDataValues( + entityManager, + bundle.getPreheat(), + trackerDto, + convertedDto, + originalEntity, + bundle.getUser()); typeReport.getStats().incCreated(); typeReport.addEntity(objectReport); updateAttributes( @@ -139,6 +145,13 @@ public TrackerTypeReport persist(EntityManager entityManager, TrackerBundle bund typeReport.getStats().incIgnored(); // Relationships are not updated. A warning was already added to the report } else { + updateDataValues( + entityManager, + bundle.getPreheat(), + trackerDto, + convertedDto, + originalEntity, + bundle.getUser()); updateAttributes( entityManager, bundle.getPreheat(), trackerDto, convertedDto, bundle.getUser()); entityManager.merge(convertedDto); @@ -171,7 +184,7 @@ public TrackerTypeReport persist(EntityManager entityManager, TrackerBundle bund + trackerDto.getUid() + ") failed to persist."; - if (bundle.getAtomicMode().equals(AtomicMode.ALL)) { + if (AtomicMode.ALL.equals(bundle.getAtomicMode())) { throw new PersistenceException(msg, e); } else { // TODO currently we do not keep track of the failed entity @@ -198,6 +211,9 @@ public TrackerTypeReport persist(EntityManager entityManager, TrackerBundle bund /** Get Tracked Entity for enrollments or events that have been updated */ protected abstract String getUpdatedTrackedEntity(V entity); + /** Clones the event properties that may potentially be change logged */ + protected abstract V cloneEntityProperties(TrackerPreheat preheat, T trackerDto); + /** * Converts an object implementing the {@link TrackerDto} interface into the corresponding * Hibernate-managed object @@ -212,7 +228,8 @@ protected abstract void updateDataValues( EntityManager entityManager, TrackerPreheat preheat, T trackerDto, - V hibernateEntity, + V payloadEntity, + V currentEntity, UserDetails user); /** Execute the persistence of Attribute values linked to the entity being processed */ @@ -250,13 +267,13 @@ protected boolean isNew(TrackerBundle bundle, TrackerDto trackerDto) { @SuppressWarnings("unchecked") private List getByType(TrackerType type, TrackerBundle bundle) { - if (type.equals(TrackerType.TRACKED_ENTITY)) { + if (TrackerType.TRACKED_ENTITY.equals(type)) { return (List) bundle.getTrackedEntities(); - } else if (type.equals(TrackerType.ENROLLMENT)) { + } else if (TrackerType.ENROLLMENT.equals(type)) { return (List) bundle.getEnrollments(); - } else if (type.equals(TrackerType.EVENT)) { + } else if (TrackerType.EVENT.equals(type)) { return (List) bundle.getEvents(); - } else if (type.equals(TrackerType.RELATIONSHIP)) { + } else if (TrackerType.RELATIONSHIP.equals(type)) { return (List) bundle.getRelationships(); } else { return new ArrayList<>(); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/DefaultTrackerObjectsDeletionService.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/DefaultTrackerObjectsDeletionService.java index 64eb462ed012..2fd6e1e54ff1 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/DefaultTrackerObjectsDeletionService.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/DefaultTrackerObjectsDeletionService.java @@ -35,8 +35,8 @@ import java.util.List; import java.util.Set; import lombok.RequiredArgsConstructor; -import org.hisp.dhis.common.BaseIdentifiableObject; import org.hisp.dhis.common.IdentifiableObjectManager; +import org.hisp.dhis.common.UID; import org.hisp.dhis.feedback.NotFoundException; import org.hisp.dhis.program.Enrollment; import org.hisp.dhis.program.Event; @@ -79,11 +79,11 @@ public class DefaultTrackerObjectsDeletionService implements TrackerObjectDeleti private final ProgramNotificationInstanceService programNotificationInstanceService; @Override - public TrackerTypeReport deleteEnrollments(List enrollments) throws NotFoundException { + public TrackerTypeReport deleteEnrollments(List enrollments) throws NotFoundException { UserInfoSnapshot userInfoSnapshot = UserInfoSnapshot.from(getCurrentUserDetails()); TrackerTypeReport typeReport = new TrackerTypeReport(TrackerType.ENROLLMENT); - for (String uid : enrollments) { + for (UID uid : enrollments) { Entity objectReport = new Entity(TrackerType.ENROLLMENT, uid); Enrollment enrollment = manager.get(Enrollment.class, uid); @@ -92,18 +92,13 @@ public TrackerTypeReport deleteEnrollments(List enrollments) throws NotF } enrollment.setLastUpdatedByUserInfo(userInfoSnapshot); - List events = - enrollment.getEvents().stream() - .filter(event -> !event.isDeleted()) - .map(BaseIdentifiableObject::getUid) - .toList(); + List events = + enrollment.getEvents().stream().filter(event -> !event.isDeleted()).map(UID::of).toList(); deleteEvents(events); RelationshipQueryParams params = RelationshipQueryParams.builder().entity(enrollment).build(); - List relationships = - relationshipStore.getByEnrollment(enrollment, params).stream() - .map(BaseIdentifiableObject::getUid) - .toList(); + List relationships = + relationshipStore.getByEnrollment(enrollment, params).stream().map(UID::of).toList(); deleteRelationships(relationships); @@ -128,26 +123,25 @@ public TrackerTypeReport deleteEnrollments(List enrollments) throws NotF } @Override - public TrackerTypeReport deleteEvents(List events) throws NotFoundException { + public TrackerTypeReport deleteEvents(List events) throws NotFoundException { UserInfoSnapshot userInfoSnapshot = UserInfoSnapshot.from(getCurrentUserDetails()); TrackerTypeReport typeReport = new TrackerTypeReport(TrackerType.EVENT); - for (String uid : events) { + for (UID uid : events) { Entity objectReport = new Entity(TrackerType.EVENT, uid); Event event = manager.get(Event.class, uid); event.setLastUpdatedByUserInfo(userInfoSnapshot); RelationshipQueryParams params = RelationshipQueryParams.builder().entity(event).build(); - List relationships = - relationshipStore.getByEvent(event, params).stream() - .map(BaseIdentifiableObject::getUid) - .toList(); + List relationships = + relationshipStore.getByEvent(event, params).stream().map(UID::of).toList(); deleteRelationships(relationships); // This is needed until deprecated method // eventChangeLogService.getTrackedEntityDataValueChangeLogs is removed. eventChangeLogService.deleteTrackedEntityDataValueChangeLog(event); + eventChangeLogService.deleteEventChangeLog(event); List notificationInstances = programNotificationInstanceService.getProgramNotificationInstances( @@ -177,12 +171,12 @@ public TrackerTypeReport deleteEvents(List events) throws NotFoundExcept } @Override - public TrackerTypeReport deleteTrackedEntities(List trackedEntities) + public TrackerTypeReport deleteTrackedEntities(List trackedEntities) throws NotFoundException { UserInfoSnapshot userInfoSnapshot = UserInfoSnapshot.from(getCurrentUserDetails()); TrackerTypeReport typeReport = new TrackerTypeReport(TrackerType.TRACKED_ENTITY); - for (String uid : trackedEntities) { + for (UID uid : trackedEntities) { Entity objectReport = new Entity(TrackerType.TRACKED_ENTITY, uid); TrackedEntity entity = manager.get(TrackedEntity.class, uid); @@ -195,18 +189,16 @@ public TrackerTypeReport deleteTrackedEntities(List trackedEntities) Set daoEnrollments = entity.getEnrollments(); - List enrollments = + List enrollments = daoEnrollments.stream() .filter(enrollment -> !enrollment.isDeleted()) - .map(BaseIdentifiableObject::getUid) + .map(UID::of) .toList(); deleteEnrollments(enrollments); RelationshipQueryParams params = RelationshipQueryParams.builder().entity(entity).build(); - List relationships = - relationshipStore.getByTrackedEntity(entity, params).stream() - .map(BaseIdentifiableObject::getUid) - .toList(); + List relationships = + relationshipStore.getByTrackedEntity(entity, params).stream().map(UID::of).toList(); deleteRelationships(relationships); @@ -229,11 +221,10 @@ public TrackerTypeReport deleteTrackedEntities(List trackedEntities) } @Override - public TrackerTypeReport deleteRelationships(List relationships) - throws NotFoundException { + public TrackerTypeReport deleteRelationships(List relationships) throws NotFoundException { TrackerTypeReport typeReport = new TrackerTypeReport(TrackerType.RELATIONSHIP); - for (String uid : relationships) { + for (UID uid : relationships) { Entity objectReport = new Entity(TrackerType.RELATIONSHIP, uid); Relationship relationship = manager.get(Relationship.class, uid); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/EnrollmentPersister.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/EnrollmentPersister.java index 7f47bbe56030..7d496a1bab36 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/EnrollmentPersister.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/EnrollmentPersister.java @@ -29,7 +29,6 @@ import jakarta.persistence.EntityManager; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import org.hisp.dhis.common.UID; import org.hisp.dhis.program.Enrollment; @@ -74,25 +73,15 @@ protected void updateAttributes( entityManager, preheat, enrollment.getAttributes(), - preheat.getTrackedEntity(enrollmentToPersist.getTrackedEntity().getUid()), + enrollmentToPersist.getTrackedEntity(), user); } - @Override - protected void updateDataValues( - EntityManager entityManager, - TrackerPreheat preheat, - org.hisp.dhis.tracker.imports.domain.Enrollment enrollment, - Enrollment enrollmentToPersist, - UserDetails user) { - // DO NOTHING - TE HAVE NO DATA VALUES - } - @Override protected void updatePreheat(TrackerPreheat preheat, Enrollment enrollment) { - preheat.putEnrollments(Collections.singletonList(enrollment)); + preheat.putEnrollment(enrollment); preheat.addProgramOwner( - enrollment.getTrackedEntity().getUid(), + UID.of(enrollment.getTrackedEntity()), enrollment.getProgram().getUid(), enrollment.getOrganisationUnit()); } @@ -103,8 +92,7 @@ protected TrackerNotificationDataBundle handleNotifications( return TrackerNotificationDataBundle.builder() .klass(Enrollment.class) - .enrollmentNotifications( - bundle.getEnrollmentNotifications().get(UID.of(enrollment.getUid()))) + .enrollmentNotifications(bundle.getEnrollmentNotifications().get(UID.of(enrollment))) .object(enrollment.getUid()) .importStrategy(bundle.getImportStrategy()) .accessedBy(bundle.getUser().getUsername()) @@ -156,11 +144,11 @@ protected void persistOwnership( org.hisp.dhis.tracker.imports.domain.Enrollment trackerDto, Enrollment entity) { if (isNew(bundle, trackerDto) - && (bundle.getPreheat().getProgramOwner().get(entity.getTrackedEntity().getUid()) == null + && (bundle.getPreheat().getProgramOwner().get(UID.of(entity.getTrackedEntity())) == null || bundle .getPreheat() .getProgramOwner() - .get(entity.getTrackedEntity().getUid()) + .get(UID.of(entity.getTrackedEntity())) .get(entity.getProgram().getUid()) == null)) { trackedEntityProgramOwnerService.createTrackedEntityProgramOwner( @@ -168,8 +156,26 @@ protected void persistOwnership( } } + @Override + protected void updateDataValues( + EntityManager entityManager, + TrackerPreheat preheat, + org.hisp.dhis.tracker.imports.domain.Enrollment trackerDto, + Enrollment payloadEntity, + Enrollment currentEntity, + UserDetails user) { + // DO NOTHING - TE HAVE NO DATA VALUES + } + @Override protected String getUpdatedTrackedEntity(Enrollment entity) { return entity.getTrackedEntity().getUid(); } + + @Override + protected Enrollment cloneEntityProperties( + TrackerPreheat preheat, org.hisp.dhis.tracker.imports.domain.Enrollment trackerDto) { + return null; + // NO NEED TO CLONE RELATIONSHIP PROPERTIES + } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/EventPersister.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/EventPersister.java index 2b512f3db051..db746f8c4953 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/EventPersister.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/EventPersister.java @@ -27,6 +27,10 @@ */ package org.hisp.dhis.tracker.imports.bundle.persister; +import static org.hisp.dhis.changelog.ChangeLogType.CREATE; +import static org.hisp.dhis.changelog.ChangeLogType.DELETE; +import static org.hisp.dhis.changelog.ChangeLogType.UPDATE; + import jakarta.persistence.EntityManager; import java.util.ArrayList; import java.util.Collections; @@ -41,7 +45,6 @@ import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import org.apache.commons.lang3.StringUtils; -import org.hisp.dhis.changelog.ChangeLogType; import org.hisp.dhis.common.UID; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.event.EventStatus; @@ -51,7 +54,6 @@ import org.hisp.dhis.reservedvalue.ReservedValueService; import org.hisp.dhis.tracker.TrackerType; import org.hisp.dhis.tracker.export.event.EventChangeLogService; -import org.hisp.dhis.tracker.export.event.TrackedEntityDataValueChangeLog; import org.hisp.dhis.tracker.export.trackedentity.TrackedEntityChangeLogService; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.bundle.TrackerObjectsMapper; @@ -89,7 +91,7 @@ protected TrackerNotificationDataBundle handleNotifications( return TrackerNotificationDataBundle.builder() .klass(Event.class) - .eventNotifications(bundle.getEventNotifications().get(UID.of(event.getUid()))) + .eventNotifications(bundle.getEventNotifications().get(UID.of(event))) .object(event.getUid()) .importStrategy(bundle.getImportStrategy()) .accessedBy(bundle.getUser().getUsername()) @@ -126,6 +128,24 @@ protected Event convert(TrackerBundle bundle, org.hisp.dhis.tracker.imports.doma return TrackerObjectsMapper.map(bundle.getPreheat(), event, bundle.getUser()); } + @Override + protected Event cloneEntityProperties( + TrackerPreheat preheat, org.hisp.dhis.tracker.imports.domain.Event event) { + Event originalEvent = preheat.getEvent(event.getUid()); + + if (originalEvent == null) { + return new Event(); + } + + Event clonedEvent = new Event(); + clonedEvent.setUid(originalEvent.getUid()); + clonedEvent.setOccurredDate(originalEvent.getOccurredDate()); + clonedEvent.setScheduledDate(originalEvent.getScheduledDate()); + clonedEvent.setGeometry(originalEvent.getGeometry()); + + return clonedEvent; + } + @Override protected TrackerType getType() { return TrackerType.EVENT; @@ -146,9 +166,11 @@ protected void updateDataValues( EntityManager entityManager, TrackerPreheat preheat, org.hisp.dhis.tracker.imports.domain.Event event, - Event hibernateEntity, + Event payloadEntity, + Event currentEntity, UserDetails user) { - handleDataValues(entityManager, preheat, event.getDataValues(), hibernateEntity, user); + handleDataValues(entityManager, preheat, event.getDataValues(), payloadEntity, user); + eventChangeLogService.addPropertyChangeLog(currentEntity, payloadEntity, user.getUsername()); } private void handleDataValues( @@ -158,7 +180,7 @@ private void handleDataValues( Event event, UserDetails user) { Map dataValueDBMap = - Optional.ofNullable(preheat.getEvent(event.getUid())) + Optional.ofNullable(event) .map( a -> a.getEventDataValues().stream() @@ -172,32 +194,22 @@ private void handleDataValues( EventDataValue dbDataValue = dataValueDBMap.get(dataElement.getUid()); if (isNewDataValue(dbDataValue, dataValue)) { - logDataValueChange( - user.getUsername(), - dataElement, - event, - dataValue.getValue(), - dataValue.isProvidedElsewhere(), - ChangeLogType.CREATE); + eventChangeLogService.addDataValueChangeLog( + event, dataElement, null, dataValue.getValue(), CREATE, user.getUsername()); saveDataValue(dataValue, event, dataElement, user, entityManager, preheat); } else if (isUpdate(dbDataValue, dataValue)) { - logDataValueChange( - user.getUsername(), - dataElement, + eventChangeLogService.addDataValueChangeLog( event, + dataElement, dbDataValue.getValue(), - dbDataValue.getProvidedElsewhere(), - ChangeLogType.UPDATE); + dataValue.getValue(), + UPDATE, + user.getUsername()); updateDataValue( dbDataValue, dataValue, event, dataElement, user, entityManager, preheat); } else if (isDeletion(dbDataValue, dataValue)) { - logDataValueChange( - user.getUsername(), - dataElement, - event, - dbDataValue.getValue(), - dbDataValue.getProvidedElsewhere(), - ChangeLogType.DELETE); + eventChangeLogService.addDataValueChangeLog( + event, dataElement, dbDataValue.getValue(), null, DELETE, user.getUsername()); deleteDataValue(dbDataValue, event, dataElement, entityManager, preheat); } }); @@ -262,26 +274,6 @@ private void deleteDataValue( event.getEventDataValues().remove(eventDataValue); } - private void logDataValueChange( - String userName, - DataElement de, - Event event, - String value, - boolean providedElsewhere, - ChangeLogType changeLogType) { - - TrackedEntityDataValueChangeLog changeLog = new TrackedEntityDataValueChangeLog(); - changeLog.setEvent(event); - changeLog.setValue(value); - changeLog.setAuditType(changeLogType); - changeLog.setDataElement(de); - changeLog.setModifiedBy(userName); - changeLog.setProvidedElsewhere(providedElsewhere); - changeLog.setCreated(new Date()); - - eventChangeLogService.addTrackedEntityDataValueChangeLog(changeLog); - } - @Override protected void persistOwnership( TrackerBundle bundle, org.hisp.dhis.tracker.imports.domain.Event trackerDto, Event entity) { diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/RelationshipPersister.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/RelationshipPersister.java index c749966b8639..04cc2a9acf73 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/RelationshipPersister.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/RelationshipPersister.java @@ -76,16 +76,6 @@ protected void updateAttributes( // NOTHING TO DO } - @Override - protected void updateDataValues( - EntityManager entityManager, - TrackerPreheat preheat, - Relationship trackerDto, - org.hisp.dhis.relationship.Relationship hibernateEntity, - UserDetails user) { - // NOTHING TO DO - } - @Override protected void updatePreheat( TrackerPreheat preheat, org.hisp.dhis.relationship.Relationship convertedDto) { @@ -114,11 +104,29 @@ protected void persistOwnership( } + @Override + protected void updateDataValues( + EntityManager entityManager, + TrackerPreheat preheat, + Relationship trackerDto, + org.hisp.dhis.relationship.Relationship payloadEntity, + org.hisp.dhis.relationship.Relationship currentEntity, + UserDetails user) { + // DO NOTHING - TE HAVE NO DATA VALUES + } + @Override protected String getUpdatedTrackedEntity(org.hisp.dhis.relationship.Relationship entity) { return null; } + @Override + protected org.hisp.dhis.relationship.Relationship cloneEntityProperties( + TrackerPreheat preheat, Relationship trackerDto) { + return null; + // NO NEED TO CLONE RELATIONSHIP PROPERTIES + } + @Override protected List determineNotificationTriggers( TrackerPreheat preheat, org.hisp.dhis.tracker.imports.domain.Relationship entity) { diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/TrackedEntityPersister.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/TrackedEntityPersister.java index 37778d38e002..0e3173cb2712 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/TrackedEntityPersister.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/TrackedEntityPersister.java @@ -67,16 +67,6 @@ protected void updateAttributes( entityManager, preheat, trackerDto.getAttributes(), te, user); } - @Override - protected void updateDataValues( - EntityManager entityManager, - TrackerPreheat preheat, - org.hisp.dhis.tracker.imports.domain.TrackedEntity trackerDto, - TrackedEntity te, - UserDetails user) { - // DO NOTHING - TE HAVE NO DATA VALUES - } - @Override protected void updatePreheat(TrackerPreheat preheat, TrackedEntity dto) { preheat.putTrackedEntities(Collections.singletonList(dto)); @@ -108,12 +98,30 @@ protected void persistOwnership( } + @Override + protected void updateDataValues( + EntityManager entityManager, + TrackerPreheat preheat, + org.hisp.dhis.tracker.imports.domain.TrackedEntity trackerDto, + TrackedEntity payloadEntity, + TrackedEntity currentEntity, + UserDetails user) { + // DO NOTHING - TE HAVE NO DATA VALUES + } + @Override protected String getUpdatedTrackedEntity(TrackedEntity entity) { return null; // We don't need to keep track, Tei has already been // updated } + @Override + protected TrackedEntity cloneEntityProperties( + TrackerPreheat preheat, org.hisp.dhis.tracker.imports.domain.TrackedEntity trackerDto) { + return null; + // NO NEED TO CLONE RELATIONSHIP PROPERTIES + } + @Override protected List determineNotificationTriggers( TrackerPreheat preheat, org.hisp.dhis.tracker.imports.domain.TrackedEntity entity) { diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/TrackerObjectDeletionService.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/TrackerObjectDeletionService.java index f1f73f7184f7..69b970b41987 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/TrackerObjectDeletionService.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/bundle/persister/TrackerObjectDeletionService.java @@ -28,6 +28,7 @@ package org.hisp.dhis.tracker.imports.bundle.persister; import java.util.List; +import org.hisp.dhis.common.UID; import org.hisp.dhis.feedback.NotFoundException; import org.hisp.dhis.tracker.imports.report.TrackerTypeReport; @@ -35,11 +36,11 @@ * @author Zubair Asghar */ public interface TrackerObjectDeletionService { - TrackerTypeReport deleteTrackedEntities(List trackedEntities) throws NotFoundException; + TrackerTypeReport deleteTrackedEntities(List trackedEntities) throws NotFoundException; - TrackerTypeReport deleteEnrollments(List enrollments) throws NotFoundException; + TrackerTypeReport deleteEnrollments(List enrollments) throws NotFoundException; - TrackerTypeReport deleteEvents(List events) throws NotFoundException; + TrackerTypeReport deleteEvents(List events) throws NotFoundException; - TrackerTypeReport deleteRelationships(List relationships) throws NotFoundException; + TrackerTypeReport deleteRelationships(List relationships) throws NotFoundException; } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/config/TrackerPreheatConfig.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/config/TrackerPreheatConfig.java index 65421821bc85..b7dc21d77e08 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/config/TrackerPreheatConfig.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/config/TrackerPreheatConfig.java @@ -39,7 +39,6 @@ import org.hisp.dhis.tracker.imports.preheat.supplier.EventProgramStageMapSupplier; import org.hisp.dhis.tracker.imports.preheat.supplier.FileResourceSupplier; import org.hisp.dhis.tracker.imports.preheat.supplier.OrgUnitValueTypeSupplier; -import org.hisp.dhis.tracker.imports.preheat.supplier.PeriodTypeSupplier; import org.hisp.dhis.tracker.imports.preheat.supplier.PreheatStrategyScanner; import org.hisp.dhis.tracker.imports.preheat.supplier.PreheatSupplier; import org.hisp.dhis.tracker.imports.preheat.supplier.ProgramOrgUnitsSupplier; @@ -63,7 +62,6 @@ public class TrackerPreheatConfig { EventProgramStageMapSupplier.class, ProgramOrgUnitsSupplier.class, ProgramOwnerSupplier.class, - PeriodTypeSupplier.class, UniqueAttributesSupplier.class, CurrentUserSupplier.class, UserSupplier.class, diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/Enrollment.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/Enrollment.java index 8af77cdcec24..dd1a4c44c2ba 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/Enrollment.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/Enrollment.java @@ -32,10 +32,12 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; +import javax.annotation.Nonnull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.EnrollmentStatus; import org.hisp.dhis.tracker.TrackerType; import org.locationtech.jts.geom.Geometry; @@ -48,13 +50,13 @@ @NoArgsConstructor @AllArgsConstructor public class Enrollment implements TrackerDto, Serializable { - @JsonProperty private String enrollment; + @Nonnull @JsonProperty private UID enrollment; @JsonProperty private Instant createdAtClient; @JsonProperty private Instant updatedAtClient; - @JsonProperty private String trackedEntity; + @JsonProperty private UID trackedEntity; @JsonProperty private MetadataIdentifier program; @@ -79,8 +81,8 @@ public class Enrollment implements TrackerDto, Serializable { @JsonProperty @Builder.Default private List notes = new ArrayList<>(); @Override - public String getUid() { - return this.enrollment; + public UID getUid() { + return enrollment; } @Override diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/Event.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/Event.java index ca526d080edf..2a7b18c3a69d 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/Event.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/Event.java @@ -35,10 +35,12 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import javax.annotation.Nonnull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.hisp.dhis.common.UID; import org.hisp.dhis.event.EventStatus; import org.hisp.dhis.tracker.TrackerType; import org.locationtech.jts.geom.Geometry; @@ -51,7 +53,7 @@ @NoArgsConstructor @AllArgsConstructor public class Event implements TrackerDto, Serializable { - @JsonProperty private String event; + @Nonnull @JsonProperty private UID event; @JsonProperty @Builder.Default private EventStatus status = EventStatus.ACTIVE; @@ -59,7 +61,7 @@ public class Event implements TrackerDto, Serializable { @JsonProperty private MetadataIdentifier programStage; - @JsonProperty private String enrollment; + @JsonProperty private UID enrollment; @JsonProperty private MetadataIdentifier orgUnit; @@ -96,7 +98,7 @@ public boolean isCreatableInSearchScope() { } @Override - public String getUid() { + public UID getUid() { return this.event; } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/MetadataIdentifier.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/MetadataIdentifier.java index c5a5b02ede53..911a50b53ae4 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/MetadataIdentifier.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/MetadataIdentifier.java @@ -36,7 +36,7 @@ import lombok.Value; import org.apache.commons.lang3.StringUtils; import org.hisp.dhis.common.IdentifiableObject; -import org.hisp.dhis.tracker.imports.TrackerIdScheme; +import org.hisp.dhis.tracker.TrackerIdScheme; /** * MetadataIdentifier represents an immutable idScheme aware identifier of metadata.
diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/Note.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/Note.java index 7eb5b3368904..67efa7799f13 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/Note.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/Note.java @@ -29,10 +29,12 @@ import com.fasterxml.jackson.annotation.JsonProperty; import java.io.Serializable; +import javax.annotation.Nonnull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.hisp.dhis.common.UID; /** * Notes are text-only objects attached to Events and Enrollments. An Event or Enrollment may have @@ -45,7 +47,7 @@ @NoArgsConstructor @AllArgsConstructor public class Note implements Serializable { - @JsonProperty private String note; + @Nonnull @JsonProperty private UID note; @JsonProperty private String value; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/Relationship.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/Relationship.java index 0b498be6a639..65a936013325 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/Relationship.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/Relationship.java @@ -34,6 +34,7 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.hisp.dhis.common.UID; import org.hisp.dhis.tracker.TrackerType; /** @@ -44,7 +45,7 @@ @NoArgsConstructor @AllArgsConstructor public class Relationship implements TrackerDto, Serializable { - @JsonProperty private String relationship; + @JsonProperty private UID relationship; @JsonProperty private MetadataIdentifier relationshipType; @@ -55,7 +56,7 @@ public class Relationship implements TrackerDto, Serializable { @JsonProperty private RelationshipItem to; @Override - public String getUid() { + public UID getUid() { return relationship; } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/RelationshipItem.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/RelationshipItem.java index cc80e0be0337..9241c4a77d4b 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/RelationshipItem.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/RelationshipItem.java @@ -33,6 +33,7 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.hisp.dhis.common.UID; /** * @author Morten Olav Hansen @@ -43,9 +44,9 @@ @AllArgsConstructor public class RelationshipItem implements Serializable { - @JsonProperty private String trackedEntity; + @JsonProperty private UID trackedEntity; - @JsonProperty private String enrollment; + @JsonProperty private UID enrollment; - @JsonProperty private String event; + @JsonProperty private UID event; } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/TrackedEntity.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/TrackedEntity.java index 1c16bdd93861..beb77ef37aa1 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/TrackedEntity.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/TrackedEntity.java @@ -32,10 +32,12 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; +import javax.annotation.Nonnull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.hisp.dhis.common.UID; import org.hisp.dhis.tracker.TrackerType; import org.locationtech.jts.geom.Geometry; @@ -47,7 +49,7 @@ @NoArgsConstructor @AllArgsConstructor public class TrackedEntity implements TrackerDto, Serializable { - @JsonProperty private String trackedEntity; + @Nonnull @JsonProperty private UID trackedEntity; @JsonProperty private MetadataIdentifier trackedEntityType; @@ -68,8 +70,8 @@ public class TrackedEntity implements TrackerDto, Serializable { @JsonProperty @Builder.Default private List attributes = new ArrayList<>(); @Override - public String getUid() { - return this.trackedEntity; + public UID getUid() { + return trackedEntity; } @Override diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/TrackerDto.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/TrackerDto.java index 2c66e7b57c41..2cd4714f39e5 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/TrackerDto.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/domain/TrackerDto.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis.tracker.imports.domain; +import org.hisp.dhis.common.UID; import org.hisp.dhis.tracker.TrackerType; /** @@ -34,7 +35,7 @@ */ public interface TrackerDto { - String getUid(); + UID getUid(); TrackerType getTrackerType(); } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/DefaultTrackerPreheatService.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/DefaultTrackerPreheatService.java index f72cc491b347..d0665f3b2f21 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/DefaultTrackerPreheatService.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/DefaultTrackerPreheatService.java @@ -33,7 +33,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.hisp.dhis.preheat.PreheatException; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.domain.TrackerObjects; import org.hisp.dhis.tracker.imports.preheat.supplier.PreheatSupplier; import org.springframework.beans.BeansException; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheat.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheat.java index 61765a2112ca..27b72ca5a96e 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheat.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheat.java @@ -53,11 +53,10 @@ import org.hisp.dhis.category.CategoryOption; import org.hisp.dhis.category.CategoryOptionCombo; import org.hisp.dhis.common.IdentifiableObject; +import org.hisp.dhis.common.UID; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.hibernate.HibernateProxyUtils; import org.hisp.dhis.organisationunit.OrganisationUnit; -import org.hisp.dhis.period.Period; -import org.hisp.dhis.period.PeriodType; import org.hisp.dhis.program.Enrollment; import org.hisp.dhis.program.Event; import org.hisp.dhis.program.Program; @@ -69,10 +68,10 @@ import org.hisp.dhis.trackedentity.TrackedEntityAttribute; import org.hisp.dhis.trackedentity.TrackedEntityProgramOwnerOrgUnit; import org.hisp.dhis.trackedentity.TrackedEntityType; +import org.hisp.dhis.tracker.TrackerIdScheme; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.TrackerType; -import org.hisp.dhis.tracker.imports.TrackerIdScheme; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; import org.hisp.dhis.tracker.imports.domain.TrackerDto; import org.hisp.dhis.user.User; @@ -92,12 +91,6 @@ public class TrackerPreheat { private final Map, IdentifiableObject> defaults = new HashMap<>(); - /** All periods available. */ - @Getter private final Map periodMap = new HashMap<>(); - - /** All periodTypes available. */ - @Getter private final Map periodTypeMap = new HashMap<>(); - /** * Internal map of category combo + category options (key) to category option combo (value). * @@ -197,25 +190,25 @@ public MetadataIdentifier getCategoryOptionComboIdentifier( * Internal map of all preheated tracked entities, mainly used for confirming existence for * updates, and used for object merging. */ - @Getter private final Map trackedEntities = new HashMap<>(); + @Getter private final Map trackedEntities = new HashMap<>(); /** * Internal map of all preheated enrollments, mainly used for confirming existence for updates, * and used for object merging. */ - @Getter private final Map enrollments = new HashMap<>(); + @Getter private final Map enrollments = new HashMap<>(); /** * Internal map of all preheated events, mainly used for confirming existence for updates, and * used for object merging. */ - @Getter private final Map events = new HashMap<>(); + @Getter private final Map events = new HashMap<>(); /** * Internal map of all preheated relationships, mainly used for confirming existence for updates, * and used for object merging. */ - @Getter private final Map relationships = new HashMap<>(); + @Getter private final Map relationships = new HashMap<>(); /** * Internal set of all relationship keys and inverted keys already present in the DB. This is used @@ -227,7 +220,7 @@ public MetadataIdentifier getCategoryOptionComboIdentifier( private final Set existingRelationships = new HashSet<>(); /** Internal set of all preheated notes uids (events and enrollments) */ - private final Set notes = new HashSet<>(); + private final Set notes = new HashSet<>(); /** * Internal map of all existing TrackedEntityProgramOwner. Used for ownership validations and @@ -236,12 +229,11 @@ public MetadataIdentifier getCategoryOptionComboIdentifier( * is an object of {@link TrackedEntityProgramOwnerOrgUnit} holding the ownership OrganisationUnit */ @Getter - private final Map> programOwner = + private final Map> programOwner = new HashMap<>(); /** A Map of trackedEntity uid connected to Enrollments */ - @Getter @Setter - private Map> trackedEntityToEnrollmentMap = new HashMap<>(); + @Getter @Setter private Map> trackedEntityToEnrollmentMap = new HashMap<>(); /** A Map of program uid and without registration {@see Enrollment}. */ private final Map enrollmentsWithoutRegistration = new HashMap<>(); @@ -262,8 +254,7 @@ public MetadataIdentifier getCategoryOptionComboIdentifier( @Getter @Setter private List uniqueAttributeValues = Lists.newArrayList(); /** A list of all Enrollment UID having at least one Event that is not deleted. */ - @Getter @Setter - private List enrollmentsWithOneOrMoreNonDeletedEvent = Lists.newArrayList(); + @Getter @Setter private List enrollmentsWithOneOrMoreNonDeletedEvent = Lists.newArrayList(); /** A list of Program Stage UID having 1 or more Events */ private final List> programStageWithEvents = Lists.newArrayList(); @@ -435,48 +426,48 @@ public TrackerPreheat put(CategoryOptionCombo categoryOptionCombo) { return this.put(idSchemes.getCategoryOptionComboIdScheme(), categoryOptionCombo); } - public TrackedEntity getTrackedEntity(String uid) { + public TrackedEntity getTrackedEntity(UID uid) { return trackedEntities.get(uid); } public void putTrackedEntities(List trackedEntities) { - trackedEntities.forEach(te -> putTrackedEntity(te.getUid(), te)); + trackedEntities.forEach(this::putTrackedEntity); } - private void putTrackedEntity(String uid, TrackedEntity trackedEntity) { - trackedEntities.put(uid, trackedEntity); + private void putTrackedEntity(TrackedEntity trackedEntity) { + trackedEntities.put(UID.of(trackedEntity), trackedEntity); } - public Enrollment getEnrollment(String uid) { + public Enrollment getEnrollment(UID uid) { return enrollments.get(uid); } public void putEnrollments(List enrollments) { - enrollments.forEach(e -> putEnrollment(e.getUid(), e)); + enrollments.forEach(this::putEnrollment); } - public void putEnrollment(String uid, Enrollment enrollment) { - enrollments.put(uid, enrollment); + public void putEnrollment(Enrollment enrollment) { + enrollments.put(UID.of(enrollment), enrollment); } - public Event getEvent(String uid) { + public Event getEvent(UID uid) { return events.get(uid); } public void putEvents(List events) { - events.forEach(event -> putEvent(event.getUid(), event)); + events.forEach(this::putEvent); } - public void putEvent(String uid, Event event) { - events.put(uid, event); + public void putEvent(Event event) { + events.put(UID.of(event), event); } - public void addNotes(Set notes) { + public void addNotes(Set notes) { this.notes.addAll(notes); } - public boolean hasNote(String uid) { + public boolean hasNote(UID uid) { return notes.contains(uid); } @@ -484,8 +475,8 @@ public RelationshipType getRelationshipType(MetadataIdentifier id) { return get(RelationshipType.class, id); } - public Relationship getRelationship(String relationshipUid) { - return relationships.get(relationshipUid); + public Relationship getRelationship(UID relationship) { + return relationships.get(relationship); } public Relationship getRelationship( @@ -517,7 +508,7 @@ public void putRelationships(List relationships) { public void putRelationship(Relationship relationship) { if (Objects.nonNull(relationship)) { - relationships.put(relationship.getUid(), relationship); + relationships.put(UID.of(relationship), relationship); } } @@ -537,20 +528,20 @@ public void putEnrollmentsWithoutRegistration(String programUid, Enrollment enro } public void addProgramOwners(List tepos) { - tepos.forEach(tepo -> addProgramOwner(tepo.getTrackedEntityId(), tepo.getProgramId(), tepo)); + tepos.forEach( + tepo -> addProgramOwner(UID.of(tepo.getTrackedEntityId()), tepo.getProgramId(), tepo)); } - private void addProgramOwner( - String teUid, String programUid, TrackedEntityProgramOwnerOrgUnit tepo) { - programOwner.computeIfAbsent(teUid, k -> new HashMap<>()).put(programUid, tepo); + private void addProgramOwner(UID te, String program, TrackedEntityProgramOwnerOrgUnit tepo) { + programOwner.computeIfAbsent(te, k -> new HashMap<>()).put(program, tepo); } - public void addProgramOwner(String teUid, String programUid, OrganisationUnit orgUnit) { - programOwner.computeIfAbsent(teUid, k -> new HashMap<>()); - if (!programOwner.get(teUid).containsKey(programUid)) { + public void addProgramOwner(UID te, String program, OrganisationUnit orgUnit) { + programOwner.computeIfAbsent(te, k -> new HashMap<>()); + if (!programOwner.get(te).containsKey(program)) { TrackedEntityProgramOwnerOrgUnit tepo = - new TrackedEntityProgramOwnerOrgUnit(teUid, programUid, orgUnit); - programOwner.get(teUid).put(programUid, tepo); + new TrackedEntityProgramOwnerOrgUnit(te.getValue(), program, orgUnit); + programOwner.get(te).put(program, tepo); } } @@ -617,8 +608,7 @@ public TrackerPreheat addProgramStageWithEvents(String programStageUid, String e public boolean hasProgramStageWithEvents(MetadataIdentifier programStage, String enrollmentUid) { ProgramStage ps = this.getProgramStage(programStage); - Enrollment enrollment = this.getEnrollment(enrollmentUid); - return this.programStageWithEvents.contains(Pair.of(ps.getUid(), enrollment.getUid())); + return this.programStageWithEvents.contains(Pair.of(ps.getUid(), enrollmentUid)); } /** Checks if an entity exists in the DB. */ @@ -633,7 +623,7 @@ public boolean exists(T entity) { * @param uid uid of entity to check * @return true if an entity of given type and UID exists in the DB */ - public boolean exists(TrackerType type, String uid) { + public boolean exists(TrackerType type, UID uid) { Objects.requireNonNull(type); return switch (type) { diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheatService.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheatService.java index 85e6abacc752..37b15a36ca8b 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheatService.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheatService.java @@ -28,7 +28,7 @@ package org.hisp.dhis.tracker.imports.preheat; import javax.annotation.Nonnull; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.domain.TrackerObjects; /** diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/UniqueAttributeValue.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/UniqueAttributeValue.java index c56d4055f39f..5b4f98799f1e 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/UniqueAttributeValue.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/UniqueAttributeValue.java @@ -30,6 +30,7 @@ import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; +import org.hisp.dhis.common.UID; import org.hisp.dhis.trackedentityattributevalue.TrackedEntityAttributeValue; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -44,7 +45,7 @@ @EqualsAndHashCode public class UniqueAttributeValue { - private String teUid; + private UID te; private MetadataIdentifier attribute; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/cache/DefaultPreheatCacheService.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/cache/DefaultPreheatCacheService.java index 7b9eb25c9b21..1f22d9417e76 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/cache/DefaultPreheatCacheService.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/cache/DefaultPreheatCacheService.java @@ -39,7 +39,7 @@ import org.cache2k.Cache2kBuilder; import org.hisp.dhis.common.IdentifiableObject; import org.hisp.dhis.common.event.ApplicationCacheClearedEvent; -import org.hisp.dhis.tracker.imports.TrackerIdScheme; +import org.hisp.dhis.tracker.TrackerIdScheme; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; @@ -102,6 +102,7 @@ public boolean hasKey(String cacheKey) { return cache.containsKey(cacheKey); } + @Override public List getAll(String cacheKey) { List res = new ArrayList<>(); if (hasKey(cacheKey)) { diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/mappers/EventMapper.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/mappers/EventMapper.java index 9b383a1ed4b7..ea69e8a65295 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/mappers/EventMapper.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/mappers/EventMapper.java @@ -62,5 +62,6 @@ public interface EventMapper extends PreheatMapper { @Mapping(target = "deleted") @Mapping(target = "createdByUserInfo") @Mapping(target = "lastUpdatedByUserInfo") + @Mapping(target = "geometry") Event map(Event event); } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/EnrollmentsWithAtLeastOneEventSupplier.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/EnrollmentsWithAtLeastOneEventSupplier.java index 1d8d8f6b4240..6f15783c77af 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/EnrollmentsWithAtLeastOneEventSupplier.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/EnrollmentsWithAtLeastOneEventSupplier.java @@ -29,10 +29,8 @@ import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; import org.hisp.dhis.common.IdentifiableObject; -import org.hisp.dhis.program.Enrollment; +import org.hisp.dhis.common.UID; import org.hisp.dhis.tracker.imports.domain.TrackerObjects; import org.hisp.dhis.tracker.imports.preheat.TrackerPreheat; import org.springframework.jdbc.core.JdbcTemplate; @@ -65,20 +63,19 @@ protected EnrollmentsWithAtLeastOneEventSupplier(JdbcTemplate jdbcTemplate) { @Override public void preheatAdd(TrackerObjects trackerObjects, TrackerPreheat preheat) { - final Map enrollments = preheat.getEnrollments(); - List programStageIds = - enrollments.values().stream().map(IdentifiableObject::getId).collect(Collectors.toList()); + List enrollmentIds = + preheat.getEnrollments().values().stream().map(IdentifiableObject::getId).toList(); - if (!programStageIds.isEmpty()) { - List uids = new ArrayList<>(); + if (!enrollmentIds.isEmpty()) { + List uids = new ArrayList<>(); MapSqlParameterSource parameters = new MapSqlParameterSource(); - parameters.addValue("ids", programStageIds); + parameters.addValue("ids", enrollmentIds); jdbcTemplate.query( SQL, parameters, rs -> { - uids.add(rs.getString(COLUMN)); + uids.add(UID.of(rs.getString(COLUMN))); }); preheat.setEnrollmentsWithOneOrMoreNonDeletedEvent(uids); } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/EventProgramEnrollmentSupplier.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/EventProgramEnrollmentSupplier.java index 239add0b7172..3dde02f95245 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/EventProgramEnrollmentSupplier.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/EventProgramEnrollmentSupplier.java @@ -56,7 +56,7 @@ public void preheatAdd(TrackerObjects trackerObjects, TrackerPreheat preheat) { enrollments.forEach( e -> { - preheat.putEnrollment(e.getUid(), e); + preheat.putEnrollment(e); preheat.putEnrollmentsWithoutRegistration(e.getProgram().getUid(), e); }); } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/EventProgramStageMapSupplier.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/EventProgramStageMapSupplier.java index 26d1727b9f38..352c0d30221b 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/EventProgramStageMapSupplier.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/EventProgramStageMapSupplier.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.Objects; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.ProgramStage; import org.hisp.dhis.tracker.imports.domain.Event; import org.hisp.dhis.tracker.imports.domain.TrackerObjects; @@ -88,7 +89,7 @@ public void preheatAdd(TrackerObjects trackerObjects, TrackerPreheat preheat) { .distinct() .toList(); - List enrollmentUids = + List enrollmentUids = trackerObjects.getEvents().stream() .map(Event::getEnrollment) .filter(Objects::nonNull) @@ -98,7 +99,7 @@ public void preheatAdd(TrackerObjects trackerObjects, TrackerPreheat preheat) { if (!notRepeatableProgramStageUids.isEmpty() && !enrollmentUids.isEmpty()) { MapSqlParameterSource parameters = new MapSqlParameterSource(); parameters.addValue("programStageUids", notRepeatableProgramStageUids); - parameters.addValue("enrollmentUids", enrollmentUids); + parameters.addValue("enrollmentUids", UID.toValueList(enrollmentUids)); jdbcTemplate.query( SQL, parameters, diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/FileResourceSupplier.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/FileResourceSupplier.java index e532885f98b2..43fbd89e85cd 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/FileResourceSupplier.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/FileResourceSupplier.java @@ -36,8 +36,8 @@ import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.fileresource.FileResourceService; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.domain.Attribute; import org.hisp.dhis.tracker.imports.domain.DataValue; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/OrgUnitValueTypeSupplier.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/OrgUnitValueTypeSupplier.java index 0b4ad7822f5c..dbb6c7973e8a 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/OrgUnitValueTypeSupplier.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/OrgUnitValueTypeSupplier.java @@ -38,8 +38,8 @@ import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.domain.Attribute; import org.hisp.dhis.tracker.imports.domain.DataValue; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/ProgramOwnerSupplier.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/ProgramOwnerSupplier.java index e651b799cd5c..d9694f9fa239 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/ProgramOwnerSupplier.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/ProgramOwnerSupplier.java @@ -33,6 +33,7 @@ import java.util.Set; import javax.annotation.Nonnull; import lombok.RequiredArgsConstructor; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.Enrollment; import org.hisp.dhis.program.Event; import org.hisp.dhis.trackedentity.TrackedEntity; @@ -53,9 +54,9 @@ public class ProgramOwnerSupplier extends AbstractPreheatSupplier { @Override public void preheatAdd(TrackerObjects trackerObjects, TrackerPreheat preheat) { - final Map preheatedTrackedEntities = preheat.getTrackedEntities(); - final Map preheatedEnrollments = preheat.getEnrollments(); - final Map preheatedEvents = preheat.getEvents(); + final Map preheatedTrackedEntities = preheat.getTrackedEntities(); + final Map preheatedEnrollments = preheat.getEnrollments(); + final Map preheatedEvents = preheat.getEvents(); Set teIds = new HashSet<>(); for (org.hisp.dhis.tracker.imports.domain.Enrollment en : trackerObjects.getEnrollments()) { Enrollment enrollment = preheatedEnrollments.get(en.getEnrollment()); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/TrackedEntityEnrollmentSupplier.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/TrackedEntityEnrollmentSupplier.java index 2d279adc2b61..728778d57675 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/TrackedEntityEnrollmentSupplier.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/TrackedEntityEnrollmentSupplier.java @@ -33,6 +33,7 @@ import java.util.List; import java.util.Map; import org.hisp.dhis.common.IdentifiableObject; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.Enrollment; import org.hisp.dhis.program.EnrollmentStatus; import org.hisp.dhis.program.Program; @@ -97,7 +98,7 @@ protected TrackedEntityEnrollmentSupplier(JdbcTemplate jdbcTemplate) { @Override public void preheatAdd(TrackerObjects trackerObjects, TrackerPreheat preheat) { - List trackedEntityList = + List trackedEntityList = trackerObjects.getEnrollments().stream() .map(org.hisp.dhis.tracker.imports.domain.Enrollment::getTrackedEntity) .toList(); @@ -105,16 +106,16 @@ public void preheatAdd(TrackerObjects trackerObjects, TrackerPreheat preheat) { List programList = preheat.getAll(Program.class).stream().map(IdentifiableObject::getUid).toList(); - List> trackedEntities = + List> trackedEntities = Lists.partition(new ArrayList<>(trackedEntityList), Constant.SPLIT_LIST_PARTITION_SIZE); if (programList.isEmpty() || trackedEntities.isEmpty()) return; - Map> trackedEntityToEnrollmentMap = new HashMap<>(); + Map> trackedEntityToEnrollmentMap = new HashMap<>(); if (trackerObjects.getEnrollments().isEmpty()) return; - for (List trackedEntityListSubList : trackedEntities) { + for (List trackedEntityListSubList : trackedEntities) { queryTeAndAddToMap(trackedEntityToEnrollmentMap, trackedEntityListSubList, programList); } @@ -122,18 +123,18 @@ public void preheatAdd(TrackerObjects trackerObjects, TrackerPreheat preheat) { } private void queryTeAndAddToMap( - Map> trackedEntityToEnrollmentMap, - List trackedEntityListSubList, + Map> trackedEntityToEnrollmentMap, + List trackedEntityListSubList, List programList) { MapSqlParameterSource parameters = new MapSqlParameterSource(); - parameters.addValue("teuids", trackedEntityListSubList); + parameters.addValue("teuids", UID.toValueList(trackedEntityListSubList)); parameters.addValue("pruids", programList); jdbcTemplate.query( SQL, parameters, resultSet -> { - String te = resultSet.getString(TE_UID_COLUMN_ALIAS); + UID te = UID.of(resultSet.getString(TE_UID_COLUMN_ALIAS)); Enrollment newPi = new Enrollment(); newPi.setUid(resultSet.getString(PI_UID_COLUMN_ALIAS)); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/UniqueAttributesSupplier.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/UniqueAttributesSupplier.java index ae116c2925c0..a29a54c34e67 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/UniqueAttributesSupplier.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/UniqueAttributesSupplier.java @@ -46,11 +46,12 @@ import java.util.stream.Stream; import javax.annotation.Nonnull; import lombok.RequiredArgsConstructor; +import org.hisp.dhis.common.UID; import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; import org.hisp.dhis.trackedentity.TrackedEntityAttributeService; import org.hisp.dhis.trackedentityattributevalue.TrackedEntityAttributeValue; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.domain.Attribute; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; import org.hisp.dhis.tracker.imports.domain.TrackerObjects; @@ -137,13 +138,13 @@ public void preheatAdd(TrackerObjects trackerObjects, TrackerPreheat preheat) { } private org.hisp.dhis.tracker.imports.domain.TrackedEntity getEntityForEnrollment( - TrackerObjects trackerObjects, TrackerPreheat preheat, String teUid) { + TrackerObjects trackerObjects, TrackerPreheat preheat, UID teUid) { TrackedEntity trackedEntity = preheat.getTrackedEntity(teUid); // Get te from Preheat Optional optionalTe = trackerObjects.getTrackedEntities().stream() - .filter(te -> Objects.equals(te.getTrackedEntity(), teUid)) + .filter(te -> Objects.equals(te.getUid(), teUid)) .findAny(); if (optionalTe.isPresent()) { return optionalTe.get(); @@ -247,7 +248,7 @@ private List getAlreadyPresentInDbUniqueValues( .map( av -> new UniqueAttributeValue( - av.getTrackedEntity().getUid(), + UID.of(av.getTrackedEntity()), idSchemes.toMetadataIdentifier(av.getAttribute()), av.getValue(), idSchemes.toMetadataIdentifier(av.getTrackedEntity().getOrganisationUnit()))) diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/UsernameValueTypeSupplier.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/UsernameValueTypeSupplier.java index ca95457211b6..010fcbd7898e 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/UsernameValueTypeSupplier.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/UsernameValueTypeSupplier.java @@ -35,7 +35,7 @@ import org.apache.commons.lang3.StringUtils; import org.hisp.dhis.common.ValueType; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.domain.Attribute; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; import org.hisp.dhis.tracker.imports.domain.TrackerObjects; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/strategy/AbstractSchemaStrategy.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/strategy/AbstractSchemaStrategy.java index 9460509f6313..f5e36a224811 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/strategy/AbstractSchemaStrategy.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/strategy/AbstractSchemaStrategy.java @@ -44,8 +44,8 @@ import org.hisp.dhis.query.Restrictions; import org.hisp.dhis.schema.Schema; import org.hisp.dhis.schema.SchemaService; -import org.hisp.dhis.tracker.imports.TrackerIdScheme; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdScheme; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; import org.hisp.dhis.tracker.imports.preheat.TrackerPreheat; import org.hisp.dhis.tracker.imports.preheat.cache.PreheatCacheService; import org.hisp.dhis.tracker.imports.preheat.mappers.CopyMapper; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/strategy/GenericStrategy.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/strategy/GenericStrategy.java index 5823cac4c530..f4087722d6ab 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/strategy/GenericStrategy.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/strategy/GenericStrategy.java @@ -32,7 +32,7 @@ import org.hisp.dhis.query.QueryService; import org.hisp.dhis.schema.Schema; import org.hisp.dhis.schema.SchemaService; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; import org.hisp.dhis.tracker.imports.preheat.TrackerPreheat; import org.hisp.dhis.tracker.imports.preheat.cache.PreheatCacheService; import org.hisp.dhis.tracker.imports.preheat.mappers.CopyMapper; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/strategy/NoteStrategy.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/strategy/NoteStrategy.java index 16ee0dcd8b4b..6f4ce8b34cea 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/strategy/NoteStrategy.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/strategy/NoteStrategy.java @@ -33,7 +33,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.hibernate.HibernateGenericStore; import org.hisp.dhis.note.Note; import org.hisp.dhis.tracker.imports.preheat.TrackerPreheat; @@ -57,23 +57,23 @@ public NoteStrategy( @Override public void add(List> splitList, TrackerPreheat preheat) { - List uids = - splitList.stream().flatMap(Collection::stream).filter(CodeGenerator::isValidUid).toList(); + List uids = splitList.stream().flatMap(Collection::stream).map(UID::of).toList(); preheat.addNotes(getExistingNotes(uids)); } - private Set getExistingNotes(List uids) { - Set notes = new HashSet<>(); - List> uidsPartitions = Lists.partition(uids, 20000); + private Set getExistingNotes(List uids) { + Set notes = new HashSet<>(); + List> uidsPartitions = Lists.partition(uids, 20000); - for (List uidsPartition : uidsPartitions) { + for (List uidsPartition : uidsPartitions) { if (!uidsPartition.isEmpty()) { notes.addAll( - getSession() - .createQuery("select n.uid from Note as n where n.uid in (:uids)", String.class) - .setParameter("uids", uidsPartition) - .list()); + UID.of( + getSession() + .createQuery("select n.uid from Note as n where n.uid in (:uids)", String.class) + .setParameter("uids", UID.toValueList(uidsPartition)) + .list())); } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preprocess/EventProgramPreProcessor.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preprocess/EventProgramPreProcessor.java index 192f17e292c3..bf38953899ab 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preprocess/EventProgramPreProcessor.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preprocess/EventProgramPreProcessor.java @@ -33,7 +33,7 @@ import java.util.Optional; import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramStage; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Event; import org.hisp.dhis.tracker.imports.preheat.TrackerPreheat; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preprocess/EventWithoutRegistrationPreProcessor.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preprocess/EventWithoutRegistrationPreProcessor.java index d60d339b06fb..5f2ec45fce07 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preprocess/EventWithoutRegistrationPreProcessor.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preprocess/EventWithoutRegistrationPreProcessor.java @@ -29,6 +29,7 @@ import static java.util.Objects.nonNull; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.Enrollment; import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramStage; @@ -86,7 +87,7 @@ private void setEnrollment(TrackerBundle bundle, String uid, Event event) { Enrollment enrollment = bundle.getPreheat().getEnrollmentsWithoutRegistration(uid); if (enrollment != null) { - event.setEnrollment(enrollment.getUid()); + event.setEnrollment(UID.of(enrollment)); } } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/DefaultProgramRuleService.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/DefaultProgramRuleService.java index 7bc293cd33c3..db3baf561a0a 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/DefaultProgramRuleService.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/DefaultProgramRuleService.java @@ -112,7 +112,7 @@ private RuleEngineEffects calculateEnrollmentRuleEffects( return programRuleEngine.evaluateEnrollmentAndEvents( enrollment, - getEventsFromEnrollment(enrollment.getEnrollment(), bundle, preheat), + getEventsFromEnrollment(e.getUid(), bundle, preheat), preheat.getProgram(e.getProgram()), bundle.getUser()); }) @@ -133,11 +133,11 @@ private RuleEngineEffects calculateTrackerEventRuleEffects( .map( e -> { List attributes = - getAttributes(e.getUid(), e.getTrackedEntity().getUid(), bundle, preheat); + getAttributes(UID.of(e), UID.of(e.getTrackedEntity()), bundle, preheat); RuleEnrollment enrollment = RuleEngineMapper.mapSavedEnrollment(e, attributes); return programRuleEngine.evaluateEnrollmentAndEvents( enrollment, - getEventsFromEnrollment(e.getUid(), bundle, preheat), + getEventsFromEnrollment(UID.of(e), bundle, preheat), e.getProgram(), bundle.getUser()); }) @@ -167,7 +167,7 @@ private RuleEngineEffects calculateProgramEventRuleEffects( // Get all the attributes linked to enrollment from the payload and the DB, // using the one from payload if they are present in both places private List getAttributes( - String enrollmentUid, String teUid, TrackerBundle bundle, TrackerPreheat preheat) { + UID enrollmentUid, UID teUid, TrackerBundle bundle, TrackerPreheat preheat) { List payloadProgramAttributes = bundle .findEnrollmentByUid(enrollmentUid) @@ -203,7 +203,7 @@ private List getAttributes( // Get all the events linked to enrollment from the payload and the DB, // using the one from payload if they are present in both places private List getEventsFromEnrollment( - String enrollmentUid, TrackerBundle bundle, TrackerPreheat preheat) { + UID enrollmentUid, TrackerBundle bundle, TrackerPreheat preheat) { Stream events; try { events = @@ -212,10 +212,10 @@ private List getEventsFromEnrollment( EventOperationParams.builder() .eventParams(EventParams.TRUE) .orgUnitMode(ACCESSIBLE) - .enrollments(Set.of(UID.of(enrollmentUid))) + .enrollments(Set.of(enrollmentUid)) .build()) .stream() - .filter(e -> bundle.findEventByUid(e.getUid()).isEmpty()); + .filter(e -> bundle.findEventByUid(UID.of(e)).isEmpty()); } catch (BadRequestException | ForbiddenException e) { throw new RuntimeException(e); } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/RuleActionEnrollmentMapper.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/RuleActionEnrollmentMapper.java index 3ab1071dde6e..cc40c7415686 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/RuleActionEnrollmentMapper.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/RuleActionEnrollmentMapper.java @@ -58,13 +58,13 @@ class RuleActionEnrollmentMapper { public Map>> mapRuleEffects( Map> enrollmentValidationEffects, TrackerBundle bundle) { return enrollmentValidationEffects.keySet().stream() - .filter(e -> bundle.findEnrollmentByUid(e.getValue()).isPresent()) + .filter(e -> bundle.findEnrollmentByUid(e).isPresent()) .collect( Collectors.toMap( - e -> bundle.findEnrollmentByUid(e.getValue()).get(), + e -> bundle.findEnrollmentByUid(e).get(), e -> mapRuleEffects( - bundle.findEnrollmentByUid(e.getValue()).get(), + bundle.findEnrollmentByUid(e).get(), enrollmentValidationEffects.get(e), bundle))); } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/RuleActionEventMapper.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/RuleActionEventMapper.java index 05684a115803..25985b8ed6fe 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/RuleActionEventMapper.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/RuleActionEventMapper.java @@ -60,15 +60,13 @@ class RuleActionEventMapper { public Map>> mapRuleEffects( Map> eventValidationEffects, TrackerBundle bundle) { return eventValidationEffects.keySet().stream() - .filter(e -> bundle.findEventByUid(e.getValue()).isPresent()) + .filter(e -> bundle.findEventByUid(e).isPresent()) .collect( Collectors.toMap( - e -> bundle.findEventByUid(e.getValue()).get(), + e -> bundle.findEventByUid(e).get(), e -> mapRuleEffects( - bundle.findEventByUid(e.getValue()).get(), - eventValidationEffects.get(e), - bundle))); + bundle.findEventByUid(e).get(), eventValidationEffects.get(e), bundle))); } private List> mapRuleEffects( diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/RuleEngineMapper.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/RuleEngineMapper.java index 31f9d2990f90..0a9481b2aa20 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/RuleEngineMapper.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/RuleEngineMapper.java @@ -68,7 +68,7 @@ private RuleEngineMapper() { Program program = preheat.getProgram(enrollment.getProgram()); return new RuleEnrollment( - enrollment.getUid(), + enrollment.getUid().getValue(), program.getName(), getDate(ObjectUtils.firstNonNull(enrollment.getOccurredAt(), enrollment.getEnrolledAt())), getDate(enrollment.getEnrolledAt()), @@ -165,7 +165,7 @@ private static RuleEvent mapPayloadEvent( : Instant.Companion.fromEpochMilliseconds(event.getCreated().getTime()); return new RuleEvent( - eventToEvaluate.getUid(), + eventToEvaluate.getUid().getValue(), programStage.getUid(), programStage.getName(), RuleEventStatus.valueOf(eventToEvaluate.getStatus().toString()), diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/AssignAttributeExecutor.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/AssignAttributeExecutor.java index 3314066ddf24..15c4b839a1f9 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/AssignAttributeExecutor.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/AssignAttributeExecutor.java @@ -80,7 +80,7 @@ public Optional executeRuleAction(TrackerBundle bundle, Enroll ruleUid, ValidationCode.E1310, attributeUid.getValue(), - enrollment.getTrackedEntity())); + enrollment.getTrackedEntity().getValue())); } return Optional.of(error(ruleUid, ValidationCode.E1309, attributeUid.getValue(), value)); } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/SetMandatoryFieldExecutor.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/SetMandatoryFieldExecutor.java index 1e70a7b21017..ef44f045b551 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/SetMandatoryFieldExecutor.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/SetMandatoryFieldExecutor.java @@ -37,7 +37,7 @@ import org.apache.commons.lang3.StringUtils; import org.hisp.dhis.common.UID; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Attribute; import org.hisp.dhis.tracker.imports.domain.Enrollment; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/executor/event/AssignDataValueExecutor.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/executor/event/AssignDataValueExecutor.java index cdb6cb5686b8..a57d302056f6 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/executor/event/AssignDataValueExecutor.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/executor/event/AssignDataValueExecutor.java @@ -90,7 +90,11 @@ public Optional executeRuleAction(TrackerBundle bundle, Event || isEqual(value, payloadDataValue.getValue(), dataElement.getValueType())) { addOrOverwriteDataValue(event, bundle, dataElement, payloadDataValue); return Optional.of( - warning(ruleUid, ValidationCode.E1308, dataElementUid.getValue(), event.getEvent())); + warning( + ruleUid, + ValidationCode.E1308, + dataElementUid.getValue(), + event.getEvent().getValue())); } return Optional.of(error(ruleUid, ValidationCode.E1307, dataElementUid.getValue(), value)); } @@ -99,13 +103,21 @@ private Optional assignInvalidOptionDataElement( DataValue payloadDataValue, Boolean canOverwrite, Event event) { if (payloadDataValue == null || payloadDataValue.getValue() == null) { return Optional.of( - warning(ruleUid, ValidationCode.E1308, dataElementUid.getValue(), event.getEvent())); + warning( + ruleUid, + ValidationCode.E1308, + dataElementUid.getValue(), + event.getEvent().getValue())); } if (Boolean.TRUE.equals(canOverwrite)) { payloadDataValue.setValue(null); return Optional.of( - warning(ruleUid, ValidationCode.E1308, dataElementUid.getValue(), event.getEvent())); + warning( + ruleUid, + ValidationCode.E1308, + dataElementUid.getValue(), + event.getEvent().getValue())); } return Optional.of(error(ruleUid, ValidationCode.E1307, dataElementUid.getValue(), "")); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/executor/event/SetMandatoryFieldExecutor.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/executor/event/SetMandatoryFieldExecutor.java index 877917bc692d..60598fbed46e 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/executor/event/SetMandatoryFieldExecutor.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/programrule/executor/event/SetMandatoryFieldExecutor.java @@ -36,7 +36,7 @@ import lombok.RequiredArgsConstructor; import org.hisp.dhis.common.UID; import org.hisp.dhis.program.ProgramStage; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Event; import org.hisp.dhis.tracker.imports.preheat.TrackerPreheat; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/report/Entity.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/report/Entity.java index 1b4c6215bd2c..576fcc89056f 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/report/Entity.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/report/Entity.java @@ -32,6 +32,7 @@ import java.util.ArrayList; import java.util.List; import lombok.Data; +import org.hisp.dhis.common.UID; import org.hisp.dhis.tracker.TrackerType; /** @@ -41,7 +42,7 @@ public class Entity { @JsonProperty private final TrackerType trackerType; - @JsonProperty private String uid; + @JsonProperty private UID uid; private List errorReports = new ArrayList<>(); @@ -49,7 +50,7 @@ public Entity(TrackerType trackerType) { this.trackerType = trackerType; } - public Entity(TrackerType trackerType, String uid) { + public Entity(TrackerType trackerType, UID uid) { this.trackerType = trackerType; this.uid = uid; } @@ -57,7 +58,7 @@ public Entity(TrackerType trackerType, String uid) { @JsonCreator public Entity( @JsonProperty("trackerType") TrackerType trackerType, - @JsonProperty("uid") String uid, + @JsonProperty("uid") UID uid, @JsonProperty("errorReports") List errorReports) { this.trackerType = trackerType; this.uid = uid; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/report/Error.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/report/Error.java index 880bbf30ff93..0b2eda5262d1 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/report/Error.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/report/Error.java @@ -36,6 +36,7 @@ import lombok.EqualsAndHashCode; import lombok.Value; import org.hisp.dhis.common.OpenApi; +import org.hisp.dhis.common.UID; @Value @Builder @@ -45,7 +46,7 @@ public class Error { @Nonnull @JsonProperty String message; @Nonnull @JsonProperty String errorCode; @Nonnull @JsonProperty String trackerType; - @Nonnull @JsonProperty String uid; + @Nonnull @JsonProperty UID uid; @EqualsAndHashCode.Exclude @Nonnull @JsonProperty List args; @JsonCreator @@ -53,7 +54,7 @@ public Error( @Nonnull @JsonProperty("message") String message, @Nonnull @JsonProperty("errorCode") String errorCode, @Nonnull @JsonProperty("trackerType") String trackerType, - @Nonnull @JsonProperty("uid") String uid, + @Nonnull @JsonProperty("uid") UID uid, @CheckForNull @JsonProperty("args") List args) { this.message = message; this.errorCode = errorCode; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/report/Warning.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/report/Warning.java index 2fbecbf79690..7cfe019f578f 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/report/Warning.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/report/Warning.java @@ -31,6 +31,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Builder; import lombok.Value; +import org.hisp.dhis.common.UID; /** * @author Enrico Colasante @@ -44,14 +45,14 @@ public class Warning { String trackerType; - String uid; + UID uid; @JsonCreator public Warning( @JsonProperty("message") String warningMessage, @JsonProperty("errorCode") String warningCode, @JsonProperty("trackerType") String trackerType, - @JsonProperty("uid") String uid) { + @JsonProperty("uid") UID uid) { this.warningMessage = warningMessage; this.warningCode = warningCode; this.trackerType = trackerType; @@ -74,7 +75,7 @@ public String getTrackerType() { } @JsonProperty - public String getUid() { + public UID getUid() { return uid; } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/sms/DeleteEventSMSListener.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/sms/DeleteEventSMSListener.java index 83e721561d81..103c0eb91d01 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/sms/DeleteEventSMSListener.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/sms/DeleteEventSMSListener.java @@ -31,6 +31,7 @@ import javax.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; import org.hisp.dhis.common.IdentifiableObjectManager; +import org.hisp.dhis.common.UID; import org.hisp.dhis.message.MessageSender; import org.hisp.dhis.sms.incoming.IncomingSms; import org.hisp.dhis.sms.incoming.IncomingSmsService; @@ -90,7 +91,7 @@ protected SmsResponse postProcess( @Nonnull private static TrackerObjects map(@Nonnull DeleteSmsSubmission submission) { return TrackerObjects.builder() - .events(List.of(Event.builder().event(submission.getEvent().getUid()).build())) + .events(List.of(Event.builder().event(UID.of(submission.getEvent().getUid())).build())) .build(); } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/sms/ProgramStageDataEntrySMSListener.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/sms/ProgramStageDataEntrySMSListener.java index f4880f6cf1fa..4a6aafa4736e 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/sms/ProgramStageDataEntrySMSListener.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/sms/ProgramStageDataEntrySMSListener.java @@ -40,6 +40,7 @@ import org.hisp.dhis.common.OrganisationUnitSelectionMode; import org.hisp.dhis.common.QueryFilter; import org.hisp.dhis.common.QueryOperator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.common.ValueType; import org.hisp.dhis.feedback.BadRequestException; import org.hisp.dhis.feedback.ForbiddenException; @@ -165,9 +166,9 @@ public void postProcess( return; } - String enrollment = null; + UID enrollment = null; if (!enrollments.isEmpty()) { - enrollment = enrollments.get(0).getUid(); + enrollment = UID.of(enrollments.get(0).getUid()); } TrackerImportParams params = diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/sms/RelationshipSMSListener.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/sms/RelationshipSMSListener.java index f0905378c41d..d4c8be55ff8b 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/sms/RelationshipSMSListener.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/sms/RelationshipSMSListener.java @@ -30,6 +30,7 @@ import java.util.List; import lombok.extern.slf4j.Slf4j; import org.hisp.dhis.common.IdentifiableObjectManager; +import org.hisp.dhis.common.UID; import org.hisp.dhis.message.MessageSender; import org.hisp.dhis.relationship.RelationshipConstraint; import org.hisp.dhis.relationship.RelationshipType; @@ -111,7 +112,7 @@ private static TrackerObjects map( .relationshipType(MetadataIdentifier.ofUid(relType)) .relationship( submission.getRelationship() != null - ? submission.getRelationship().getUid() + ? UID.of(submission.getRelationship().getUid()) : null) .from(relationshipItem(relType.getFromConstraint(), submission.getFrom())) .to(relationshipItem(relType.getToConstraint(), submission.getTo())) @@ -122,9 +123,9 @@ private static TrackerObjects map( private static RelationshipItem relationshipItem(RelationshipConstraint constraint, Uid uid) { return switch (constraint.getRelationshipEntity()) { case TRACKED_ENTITY_INSTANCE -> - RelationshipItem.builder().trackedEntity(uid.getUid()).build(); - case PROGRAM_INSTANCE -> RelationshipItem.builder().enrollment(uid.getUid()).build(); - case PROGRAM_STAGE_INSTANCE -> RelationshipItem.builder().event(uid.getUid()).build(); + RelationshipItem.builder().trackedEntity(UID.of(uid.getUid())).build(); + case PROGRAM_INSTANCE -> RelationshipItem.builder().enrollment(UID.of(uid.getUid())).build(); + case PROGRAM_STAGE_INSTANCE -> RelationshipItem.builder().event(UID.of(uid.getUid())).build(); }; } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/sms/SmsImportMapper.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/sms/SmsImportMapper.java index fa16817ae8c0..4ffa056354de 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/sms/SmsImportMapper.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/sms/SmsImportMapper.java @@ -45,7 +45,7 @@ import org.apache.commons.lang3.StringUtils; import org.hisp.dhis.category.CategoryService; import org.hisp.dhis.common.BaseIdentifiableObject; -import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.common.collection.CollectionUtils; import org.hisp.dhis.event.EventStatus; import org.hisp.dhis.organisationunit.OrganisationUnit; @@ -155,7 +155,7 @@ private static TrackedEntity mapTrackedEntity( Set existingAttributeValues) { return TrackedEntity.builder() .orgUnit(metadataUid(submission.getOrgUnit())) - .trackedEntity(submission.getTrackedEntityInstance().getUid()) + .trackedEntity(UID.of(submission.getTrackedEntityInstance().getUid())) .trackedEntityType(metadataUid(submission.getTrackedEntityType())) .attributes( mapTrackedEntityTypeAttributes( @@ -226,8 +226,8 @@ private static Enrollment mapToEnrollment( return Enrollment.builder() .orgUnit(metadataUid(submission.getOrgUnit())) .program(metadataUid(submission.getTrackerProgram())) - .trackedEntity(submission.getTrackedEntityInstance().getUid()) - .enrollment(submission.getEnrollment().getUid()) + .trackedEntity(UID.of(submission.getTrackedEntityInstance().getUid())) + .enrollment(UID.of(submission.getEnrollment().getUid())) .enrolledAt(toInstant(submission.getEnrollmentDate())) .occurredAt(toInstant(submission.getIncidentDate())) .status(map(submission.getEnrollmentStatus())) @@ -277,8 +277,8 @@ static List mapProgramAttributeValues( private static Event mapToEvent( @Nonnull SmsEvent submission, @Nonnull String username, @Nonnull Uid enrollment) { return Event.builder() - .event(submission.getEvent().getUid()) - .enrollment(enrollment.getUid()) + .event(UID.of(submission.getEvent().getUid())) + .enrollment(UID.of(enrollment.getUid())) .orgUnit(metadataUid(submission.getOrgUnit())) .programStage(metadataUid(submission.getProgramStage())) .attributeOptionCombo(metadataUid(submission.getAttributeOptionCombo())) @@ -301,8 +301,8 @@ static TrackerObjects map( private static Event mapEvent( @Nonnull TrackerEventSmsSubmission submission, @Nonnull String username) { return Event.builder() - .event(submission.getEvent().getUid()) - .enrollment(submission.getEnrollment().getUid()) + .event(UID.of(submission.getEvent().getUid())) + .enrollment(UID.of(submission.getEnrollment().getUid())) .orgUnit(metadataUid(submission.getOrgUnit())) .programStage(metadataUid(submission.getProgramStage())) .attributeOptionCombo(metadataUid(submission.getAttributeOptionCombo())) @@ -325,7 +325,7 @@ static TrackerObjects map( private static Event mapEvent( @Nonnull SimpleEventSmsSubmission submission, @Nonnull String username) { return Event.builder() - .event(submission.getEvent().getUid()) + .event(UID.of(submission.getEvent().getUid())) .orgUnit(metadataUid(submission.getOrgUnit())) .program(metadataUid(submission.getEventProgram())) .attributeOptionCombo(metadataUid(submission.getAttributeOptionCombo())) @@ -405,15 +405,15 @@ private static EventStatus map(SmsEventStatus status) { @Nonnull String username, @Nonnull CategoryService dataElementCategoryService, @Nonnull String trackedEntity, - @CheckForNull String enrollmentUid) { + @CheckForNull UID enrollmentUid) { List enrollments = List.of(); if (enrollmentUid == null) { - enrollmentUid = CodeGenerator.generateUid(); + enrollmentUid = UID.generate(); Instant now = Instant.now(); Enrollment enrollment = Enrollment.builder() .enrollment(enrollmentUid) - .trackedEntity(trackedEntity) + .trackedEntity(UID.of(trackedEntity)) .program(metadataUid(smsCommand.getProgram())) .orgUnit(MetadataIdentifier.ofUid(orgUnit)) .occurredAt(now) @@ -452,7 +452,7 @@ private static EventStatus map(SmsEventStatus status) { @Nonnull SMSCommand smsCommand, @Nonnull Map attributeValues, @Nonnull OrganisationUnit orgUnit) { - String trackedEntity = CodeGenerator.generateUid(); + UID trackedEntity = UID.generate(); Date now = new Date(); Date occurredDate = Objects.requireNonNullElse(SmsUtils.lookForDate(sms.getText()), now); @@ -485,7 +485,7 @@ private static EventStatus map(SmsEventStatus status) { .enrollments( List.of( Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .trackedEntity(trackedEntity) .orgUnit(metadataUid(orgUnit)) .program(metadataUid(smsCommand.getProgram())) @@ -504,7 +504,7 @@ private static EventStatus map(SmsEventStatus status) { @Nonnull String username, @Nonnull CategoryService dataElementCategoryService) { return Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .orgUnit(MetadataIdentifier.ofUid(orgUnit)) .program(metadataUid(smsCommand.getProgram())) .programStage(metadataUid(smsCommand.getProgramStage())) diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/util/RelationshipKeySupport.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/util/RelationshipKeySupport.java index 0cde27a34984..b3cc5c87384b 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/util/RelationshipKeySupport.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/util/RelationshipKeySupport.java @@ -27,8 +27,6 @@ */ package org.hisp.dhis.tracker.imports.util; -import static org.apache.commons.lang3.StringUtils.trimToEmpty; - import java.util.Objects; import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -50,9 +48,9 @@ private static RelationshipKey.RelationshipItemKey getRelationshipItemKey( RelationshipItem relationshipItem) { if (Objects.nonNull(relationshipItem)) { return RelationshipKey.RelationshipItemKey.builder() - .trackedEntity(trimToEmpty(relationshipItem.getTrackedEntity())) - .enrollment(trimToEmpty(relationshipItem.getEnrollment())) - .event(trimToEmpty(relationshipItem.getEvent())) + .trackedEntity(relationshipItem.getTrackedEntity()) + .enrollment(relationshipItem.getEnrollment()) + .event(relationshipItem.getEvent()) .build(); } throw new IllegalStateException("Unable to determine uid for relationship item"); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/Error.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/Error.java index a9857f1a9e4c..9e6466b70c31 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/Error.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/Error.java @@ -29,6 +29,7 @@ import java.util.List; import lombok.Value; +import org.hisp.dhis.common.UID; import org.hisp.dhis.tracker.TrackerType; @Value @@ -37,7 +38,7 @@ public class Error implements Validation { String message; ValidationCode code; TrackerType type; - String uid; + UID uid; List args; public ValidationCode getErrorCode() { @@ -64,7 +65,7 @@ public String getType() { } @Override - public String getUid() { + public UID getUid() { return uid; } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/MessageFormatter.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/MessageFormatter.java index 764291cd5d3a..0c8d329e69b5 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/MessageFormatter.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/MessageFormatter.java @@ -40,7 +40,7 @@ import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramStage; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.domain.Enrollment; import org.hisp.dhis.tracker.imports.domain.Event; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -107,10 +107,11 @@ private static String formatArgument(TrackerIdSchemeParams idSchemes, Object arg if (Date.class.isAssignableFrom(type)) return (DateFormat.getInstance().format(argument)); if (Instant.class.isAssignableFrom(type)) return DateUtils.toIso8601NoTz(DateUtils.fromInstant((Instant) argument)); - if (Enrollment.class.isAssignableFrom(type)) return ((Enrollment) argument).getEnrollment(); - if (Event.class.isAssignableFrom(type)) return ((Event) argument).getEvent(); + if (Enrollment.class.isAssignableFrom(type)) + return ((Enrollment) argument).getEnrollment().getValue(); + if (Event.class.isAssignableFrom(type)) return ((Event) argument).getEvent().getValue(); if (TrackedEntity.class.isAssignableFrom(type)) - return ((TrackedEntity) argument).getTrackedEntity(); + return ((TrackedEntity) argument).getTrackedEntity().getValue(); return argument.toString(); } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/PersistablesFilter.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/PersistablesFilter.java index 1181e2f2d45a..ffc518bffc61 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/PersistablesFilter.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/PersistablesFilter.java @@ -44,7 +44,7 @@ import java.util.function.Function; import java.util.function.Predicate; import lombok.Getter; -import org.apache.commons.lang3.StringUtils; +import org.hisp.dhis.common.UID; import org.hisp.dhis.tracker.TrackerType; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; @@ -63,9 +63,8 @@ * (i.e. enrollment of trackedEntity or event of enrollment). During {@link * TrackerImportStrategy#UPDATE} a valid child of an invalid parent can be updated. * - *

The {@link Result} returned from {@link #filter(TrackerBundle, EnumMap, - * TrackerImportStrategy)} can be trusted to only contain persistable entities. The {@link - * TrackerBundle} is not mutated! + *

The {@link Result} returned from {@link #filter(TrackerBundle, Map, TrackerImportStrategy)} + * can be trusted to only contain persistable entities. The {@link TrackerBundle} is not mutated! * *

Errors are only added to {@link Result#errors} if they add information. For example a valid * entity with invalid parent cannot be created because of its parent. Since the valid children did @@ -117,7 +116,7 @@ class PersistablesFilter { * example on DELETE event, enrollment, trackedEntity entities cannot be deleted if an invalid * relationship points to them. */ - private final EnumMap> markedEntities = + private final EnumMap> markedEntities = new EnumMap<>( Map.of( TRACKED_ENTITY, new HashSet<>(), @@ -131,20 +130,20 @@ class PersistablesFilter { private final TrackerPreheat preheat; - private final EnumMap> invalidEntities; + private final Map> invalidEntities; private final TrackerImportStrategy importStrategy; public static Result filter( TrackerBundle bundle, - EnumMap> invalidEntities, + Map> invalidEntities, TrackerImportStrategy importStrategy) { return new PersistablesFilter(bundle, invalidEntities, importStrategy).result; } private PersistablesFilter( TrackerBundle bundle, - EnumMap> invalidEntities, + Map> invalidEntities, TrackerImportStrategy importStrategy) { this.bundle = bundle; this.preheat = bundle.getPreheat(); @@ -200,7 +199,7 @@ private boolean isValid(TrackerDto entity) { return !isContained(this.invalidEntities, entity); } - private boolean isContained(EnumMap> map, TrackerDto entity) { + private boolean isContained(Map> map, TrackerDto entity) { return map.get(entity.getTrackerType()).contains(entity.getUid()); } @@ -304,11 +303,11 @@ private static Error error(ValidationCode code, TrackerDto notPersistable, Track * @return a tracker dto only capturing the uid and type */ private static TrackerDto toTrackerDto(RelationshipItem item) { - if (StringUtils.isNotEmpty(item.getTrackedEntity())) { + if (item.getTrackedEntity() != null) { return TrackedEntity.builder().trackedEntity(item.getTrackedEntity()).build(); - } else if (StringUtils.isNotEmpty(item.getEnrollment())) { + } else if (item.getEnrollment() != null) { return Enrollment.builder().enrollment(item.getEnrollment()).build(); - } else if (StringUtils.isNotEmpty(item.getEvent())) { + } else if (item.getEvent() != null) { return Event.builder().event(item.getEvent()).build(); } // only reached if a new TrackerDto implementation is added @@ -316,10 +315,10 @@ private static TrackerDto toTrackerDto(RelationshipItem item) { } /** - * Result of {@link #filter(TrackerBundle, EnumMap, TrackerImportStrategy)} operation indicating - * all entities that can be persisted. The meaning of persisted i.e. create, update, delete comes - * from the context which includes the {@link TrackerImportStrategy} and whether the entity - * existed or not. + * Result of {@link #filter(TrackerBundle, Map, TrackerImportStrategy)} operation indicating all + * entities that can be persisted. The meaning of persisted i.e. create, update, delete comes from + * the context which includes the {@link TrackerImportStrategy} and whether the entity existed or + * not. */ @Getter public static class Result { diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/Reporter.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/Reporter.java index 73a38ca10724..3493b45b5a14 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/Reporter.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/Reporter.java @@ -40,8 +40,9 @@ import lombok.Getter; import lombok.Value; import lombok.experimental.NonFinal; +import org.hisp.dhis.common.UID; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.TrackerType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.domain.TrackerDto; /** @@ -66,7 +67,7 @@ public class Reporter { * least one Error in the Reporter) encountered during the validation * process. */ - EnumMap> invalidDTOs; + EnumMap> invalidDTOs; /** * Create a {@link Reporter} reporting all errors and warnings with identifiers in given @@ -221,7 +222,7 @@ public boolean isInvalid(TrackerDto dto) { * Checks if a TrackerDto with given type and uid is invalid (i.e. has at least one Error in the * Reporter). */ - public boolean isInvalid(TrackerType trackerType, String uid) { + public boolean isInvalid(TrackerType trackerType, UID uid) { return this.invalidDTOs.getOrDefault(trackerType, new HashSet<>()).contains(uid); } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/Validation.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/Validation.java index 229de7adc478..09f0054e7c0e 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/Validation.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/Validation.java @@ -28,6 +28,7 @@ package org.hisp.dhis.tracker.imports.validation; import java.util.List; +import org.hisp.dhis.common.UID; /** * Validation represents an issue found by the validation process. It contains information that help @@ -40,7 +41,7 @@ public interface Validation { String getType(); - String getUid(); + UID getUid(); default List getArgs() { return List.of(); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/Warning.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/Warning.java index aacf16b81208..dc990ccf5a72 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/Warning.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/Warning.java @@ -28,6 +28,7 @@ package org.hisp.dhis.tracker.imports.validation; import lombok.Value; +import org.hisp.dhis.common.UID; import org.hisp.dhis.tracker.TrackerType; @Value @@ -38,7 +39,7 @@ public class Warning implements Validation { TrackerType type; - String uid; + UID uid; public ValidationCode getWarningCode() { return code; @@ -64,7 +65,7 @@ public String getType() { } @Override - public String getUid() { + public UID getUid() { return uid; } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/AttributeValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/AttributeValidator.java index e633fa74938f..88bbd97f38c7 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/AttributeValidator.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/AttributeValidator.java @@ -128,7 +128,7 @@ protected void validateFileNotAlreadyAssigned( () -> fileResource != null && fileResource.getFileResourceOwner() != null - && !fileResource.getFileResourceOwner().equals(trackerDto.getUid()), + && !fileResource.getFileResourceOwner().equals(trackerDto.getUid().getValue()), trackerDto, E1009, value); @@ -183,7 +183,7 @@ protected void validateAttributeUniqueness( boolean hasTheSameValue = Objects.equals(uniqueAttributeValue.getValue(), value); boolean isNotSameTei = trackedEntity == null - || !Objects.equals(trackedEntity.getUid(), uniqueAttributeValue.getTeUid()); + || !Objects.equals(trackedEntity.getUid(), uniqueAttributeValue.getTe().getValue()); if (isTeaUniqueInOrgUnitScope && isTheSameTea && hasTheSameValue && isNotSameTei) { reporter.addError(dto, ValidationCode.E1064, value, trackedEntityAttribute); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/Seq.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/Seq.java index ca68cee52f30..4f2f866c920f 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/Seq.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/Seq.java @@ -32,6 +32,7 @@ import java.util.function.Predicate; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; +import org.hisp.dhis.common.UID; import org.hisp.dhis.tracker.TrackerType; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; @@ -228,7 +229,7 @@ public boolean isInvalid(TrackerDto dto) { } @Override - public boolean isInvalid(TrackerType trackerType, String uid) { + public boolean isInvalid(TrackerType trackerType, UID uid) { return original.isInvalid(trackerType, uid); } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/ValidationUtils.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/ValidationUtils.java index 78619953ee6a..3118949d23b1 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/ValidationUtils.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/ValidationUtils.java @@ -41,6 +41,7 @@ import java.util.stream.Stream; import javax.annotation.Nonnull; import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.common.ValueType; import org.hisp.dhis.common.ValueTypedDimensionalItemObject; import org.hisp.dhis.event.EventStatus; @@ -49,7 +50,7 @@ import org.hisp.dhis.program.ValidationStrategy; import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentityattributevalue.TrackedEntityAttributeValue; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Attribute; import org.hisp.dhis.tracker.imports.domain.DataValue; @@ -94,7 +95,7 @@ public static List validateNotes( { // If a note having the same UID already exist in the db, raise // warning, ignore the note and continue - if (isNotEmpty(note.getNote()) && preheat.hasNote(note.getNote())) { + if (note.getNote() != null && preheat.hasNote(note.getNote())) { reporter.addWarning(dto, ValidationCode.E1119, note.getNote()); } else { notes.add(note); @@ -150,16 +151,16 @@ private static Set getEventDataValues(TrackerBundle bundle, public static boolean needsToValidateDataValues(Event event, @Nonnull ProgramStage programStage) { if (EventStatus.STATUSES_WITHOUT_DATA_VALUES.contains(event.getStatus())) { return false; - } else if (programStage.getValidationStrategy().equals(ValidationStrategy.ON_COMPLETE) - && event.getStatus().equals(EventStatus.COMPLETED)) { + } else if (ValidationStrategy.ON_COMPLETE.equals(programStage.getValidationStrategy()) + && EventStatus.COMPLETED.equals(event.getStatus())) { return true; } else { - return !programStage.getValidationStrategy().equals(ValidationStrategy.ON_COMPLETE); + return !ValidationStrategy.ON_COMPLETE.equals(programStage.getValidationStrategy()); } } public static Set getTrackedEntityAttributes( - TrackerBundle bundle, String trackedEntityUid) { + TrackerBundle bundle, UID trackedEntityUid) { TrackerIdSchemeParams idSchemes = bundle.getPreheat().getIdSchemes(); Set savedTrackedEntityAttributes = Optional.of(bundle) @@ -187,7 +188,7 @@ public static Set getTrackedEntityAttributes( public static void addIssuesToReporter( Reporter reporter, TrackerDto dto, List programRuleIssues) { programRuleIssues.stream() - .filter(issue -> issue.getIssueType().equals(ERROR)) + .filter(issue -> ERROR.equals(issue.getIssueType())) .forEach( issue -> { List args = Lists.newArrayList(issue.getRuleUid().getValue()); @@ -196,7 +197,7 @@ public static void addIssuesToReporter( }); programRuleIssues.stream() - .filter(issue -> issue.getIssueType().equals(WARNING)) + .filter(issue -> WARNING.equals(issue.getIssueType())) .forEach( issue -> { List args = Lists.newArrayList(issue.getRuleUid().getValue()); @@ -205,19 +206,18 @@ public static void addIssuesToReporter( }); } - public static boolean trackedEntityExists(TrackerBundle bundle, String teUid) { - return bundle.getPreheat().getTrackedEntity(teUid) != null - || bundle.findTrackedEntityByUid(teUid).isPresent(); + public static boolean trackedEntityExists(TrackerBundle bundle, UID te) { + return bundle.getPreheat().getTrackedEntity(te) != null + || bundle.findTrackedEntityByUid(te).isPresent(); } - public static boolean enrollmentExist(TrackerBundle bundle, String enrollmentUid) { - return bundle.getPreheat().getEnrollment(enrollmentUid) != null - || bundle.findEnrollmentByUid(enrollmentUid).isPresent(); + public static boolean enrollmentExist(TrackerBundle bundle, UID enrollment) { + return bundle.getPreheat().getEnrollment(enrollment) != null + || bundle.findEnrollmentByUid(enrollment).isPresent(); } - public static boolean eventExist(TrackerBundle bundle, String eventUid) { - return bundle.getPreheat().getEvent(eventUid) != null - || bundle.findEventByUid(eventUid).isPresent(); + public static boolean eventExist(TrackerBundle bundle, UID event) { + return bundle.getPreheat().getEvent(event) != null || bundle.findEventByUid(event).isPresent(); } public static void validateOptionSet( @@ -239,12 +239,6 @@ public static void validateOptionSet } } - public static void validateNotesUid(List notes, Reporter reporter, TrackerDto dto) { - for (Note note : notes) { - checkUidFormat(note.getNote(), reporter, dto, note, note.getNote()); - } - } - /** * Check if the given UID has a valid format. * diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/AttributeValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/AttributeValidator.java index f294ce005514..61b1e3864973 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/AttributeValidator.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/AttributeValidator.java @@ -41,13 +41,14 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import org.hisp.dhis.common.UID; import org.hisp.dhis.external.conf.DhisConfigurationProvider; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramTrackedEntityAttribute; import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Attribute; import org.hisp.dhis.tracker.imports.domain.Enrollment; @@ -194,9 +195,9 @@ private void validateMandatoryAttributes( enrollment.getEnrollment())); } - private MetadataIdentifier getOrgUnitUidFromTei(TrackerBundle bundle, String teUid) { + private MetadataIdentifier getOrgUnitUidFromTei(TrackerBundle bundle, UID te) { return bundle - .findTrackedEntityByUid(teUid) + .findTrackedEntityByUid(te) .map(org.hisp.dhis.tracker.imports.domain.TrackedEntity::getOrgUnit) .orElse(null); } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/DateValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/DateValidator.java index f541434cdc55..b04a0a71f038 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/DateValidator.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/DateValidator.java @@ -36,7 +36,7 @@ import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1052; import java.time.LocalDate; -import java.time.ZoneOffset; +import java.time.ZoneId; import java.util.Objects; import org.hisp.dhis.program.Program; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; @@ -84,13 +84,13 @@ private void validateEnrollmentDatesNotInFuture( final LocalDate now = LocalDate.now(); if (Objects.nonNull(enrollment.getEnrolledAt()) && Boolean.FALSE.equals(program.getSelectEnrollmentDatesInFuture()) - && enrollment.getEnrolledAt().atOffset(ZoneOffset.UTC).toLocalDate().isAfter(now)) { + && enrollment.getEnrolledAt().atZone(ZoneId.systemDefault()).toLocalDate().isAfter(now)) { reporter.addError(enrollment, E1020, enrollment.getEnrolledAt()); } if (Objects.nonNull(enrollment.getOccurredAt()) && Boolean.FALSE.equals(program.getSelectIncidentDatesInFuture()) - && enrollment.getOccurredAt().atOffset(ZoneOffset.UTC).toLocalDate().isAfter(now)) { + && enrollment.getOccurredAt().atZone(ZoneId.systemDefault()).toLocalDate().isAfter(now)) { reporter.addError(enrollment, E1021, enrollment.getOccurredAt()); } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/EnrollmentValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/EnrollmentValidator.java index d8cc6cb7a57b..df6981dec615 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/EnrollmentValidator.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/EnrollmentValidator.java @@ -50,7 +50,6 @@ public EnrollmentValidator( each( TrackerBundle::getEnrollments, seq( - new UidValidator(), new ExistenceValidator(), new MandatoryFieldsValidator(), new MetaValidator(), diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/ExistingEnrollmentValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/ExistingEnrollmentValidator.java index 1a6ef73bbb84..5bec4e21c01d 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/ExistingEnrollmentValidator.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/ExistingEnrollmentValidator.java @@ -36,6 +36,7 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.Enrollment; import org.hisp.dhis.program.EnrollmentStatus; import org.hisp.dhis.program.Program; @@ -81,7 +82,7 @@ private void validateTeiNotEnrolledAlready( .filter(e -> e.getProgram().isEqualTo(program)) .filter( e -> - e.getTrackedEntity().equals(te.getUid()) + e.getTrackedEntity().equals(UID.of(te)) && !e.getEnrollment().equals(enrollment.getEnrollment())) .filter( e -> @@ -99,7 +100,7 @@ private void validateTeiNotEnrolledAlready( .filter( e -> e.getProgram().getUid().equals(program.getUid()) - && !e.getUid().equals(enrollment.getEnrollment())) + && !e.getUid().equals(enrollment.getEnrollment().getValue())) .filter( e -> EnrollmentStatus.ACTIVE == e.getStatus() @@ -141,18 +142,18 @@ public org.hisp.dhis.tracker.imports.domain.Enrollment getEnrollmentFromDbEnroll Enrollment dbEnrollment) { org.hisp.dhis.tracker.imports.domain.Enrollment enrollment = new org.hisp.dhis.tracker.imports.domain.Enrollment(); - enrollment.setEnrollment(dbEnrollment.getUid()); + enrollment.setEnrollment(UID.of(dbEnrollment)); enrollment.setStatus(dbEnrollment.getStatus()); return enrollment; } - private TrackedEntity getTrackedEntity(TrackerBundle bundle, String uid) { + private TrackedEntity getTrackedEntity(TrackerBundle bundle, UID uid) { TrackedEntity te = bundle.getPreheat().getTrackedEntity(uid); if (te == null && bundle.findTrackedEntityByUid(uid).isPresent()) { te = new TrackedEntity(); - te.setUid(uid); + te.setUid(uid.getValue()); } return te; } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/MandatoryFieldsValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/MandatoryFieldsValidator.java index 5a9fa5c5cfe5..ce742537ae31 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/MandatoryFieldsValidator.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/MandatoryFieldsValidator.java @@ -29,7 +29,6 @@ import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1122; -import org.apache.commons.lang3.StringUtils; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Enrollment; import org.hisp.dhis.tracker.imports.validation.Reporter; @@ -44,9 +43,6 @@ public void validate(Reporter reporter, TrackerBundle bundle, Enrollment enrollm reporter.addErrorIf(() -> enrollment.getOrgUnit().isBlank(), enrollment, E1122, "orgUnit"); reporter.addErrorIf(() -> enrollment.getProgram().isBlank(), enrollment, E1122, "program"); reporter.addErrorIf( - () -> StringUtils.isEmpty(enrollment.getTrackedEntity()), - enrollment, - E1122, - "trackedEntity"); + () -> enrollment.getTrackedEntity() == null, enrollment, E1122, "trackedEntity"); } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/SecurityOwnershipValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/SecurityOwnershipValidator.java index 9986aafae371..29934dfa8eba 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/SecurityOwnershipValidator.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/SecurityOwnershipValidator.java @@ -33,6 +33,7 @@ import javax.annotation.Nonnull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.hisp.dhis.common.UID; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.program.Program; import org.hisp.dhis.security.Authorities; @@ -112,7 +113,7 @@ private TrackedEntity getTrackedEntityWhenStrategyCreate( .map( entity -> { TrackedEntity newEntity = new TrackedEntity(); - newEntity.setUid(entity.getUid()); + newEntity.setUid(entity.getUid().getValue()); newEntity.setOrganisationUnit( bundle.getPreheat().getOrganisationUnit(entity.getOrgUnit())); return newEntity; @@ -120,7 +121,7 @@ private TrackedEntity getTrackedEntityWhenStrategyCreate( .orElseGet( () -> { TrackedEntity newEntity = new TrackedEntity(); - newEntity.setUid(enrollment.getTrackedEntity()); + newEntity.setUid(enrollment.getTrackedEntity().getValue()); return newEntity; }); } @@ -128,7 +129,7 @@ private TrackedEntity getTrackedEntityWhenStrategyCreate( private OrganisationUnit getOwnerOrganisationUnit( TrackerPreheat preheat, TrackedEntity trackedEntity, Program program) { Map programOwner = - preheat.getProgramOwner().get(trackedEntity.getUid()); + preheat.getProgramOwner().get(UID.of(trackedEntity)); if (programOwner == null || programOwner.get(program.getUid()) == null) { return trackedEntity.getOrganisationUnit(); } else { @@ -136,7 +137,7 @@ private OrganisationUnit getOwnerOrganisationUnit( } } - private boolean enrollmentHasEvents(TrackerPreheat preheat, String enrollmentUid) { + private boolean enrollmentHasEvents(TrackerPreheat preheat, UID enrollmentUid) { return preheat.getEnrollmentsWithOneOrMoreNonDeletedEvent().contains(enrollmentUid); } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/UidValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/UidValidator.java deleted file mode 100644 index e9b448dba875..000000000000 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/UidValidator.java +++ /dev/null @@ -1,49 +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.imports.validation.validator.enrollment; - -import static org.hisp.dhis.tracker.imports.validation.validator.ValidationUtils.checkUidFormat; -import static org.hisp.dhis.tracker.imports.validation.validator.ValidationUtils.validateNotesUid; - -import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; -import org.hisp.dhis.tracker.imports.domain.Enrollment; -import org.hisp.dhis.tracker.imports.validation.Reporter; -import org.hisp.dhis.tracker.imports.validation.Validator; - -/** - * @author Morten Svanæs - */ -class UidValidator implements Validator { - @Override - public void validate(Reporter reporter, TrackerBundle bundle, Enrollment enrollment) { - checkUidFormat( - enrollment.getEnrollment(), reporter, enrollment, enrollment, enrollment.getEnrollment()); - - validateNotesUid(enrollment.getNotes(), reporter, enrollment); - } -} diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/UpdatableFieldsValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/UpdatableFieldsValidator.java index 4015cd107553..6dce68052cc2 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/UpdatableFieldsValidator.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/UpdatableFieldsValidator.java @@ -54,7 +54,7 @@ public void validate( reporter.addErrorIf( () -> !enrollment.getProgram().isEqualTo(program), enrollment, E1127, "program"); reporter.addErrorIf( - () -> !trackedEntity.getUid().equals(enrollment.getTrackedEntity()), + () -> !trackedEntity.getUid().equals(enrollment.getTrackedEntity().getValue()), enrollment, E1127, "trackedEntity"); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/DataRelationsValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/DataRelationsValidator.java index edbf00e2c5cb..4ee3d0b2a77b 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/DataRelationsValidator.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/DataRelationsValidator.java @@ -42,7 +42,6 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; import org.hisp.dhis.category.CategoryOption; import org.hisp.dhis.category.CategoryOptionCombo; import org.hisp.dhis.organisationunit.OrganisationUnit; @@ -77,7 +76,7 @@ public void validate(Reporter reporter, TrackerBundle bundle, Event event) { private void validateProgramWithRegistrationHasTrackedEntity( Reporter reporter, TrackerBundle bundle, Program program, Event event) { if (program.isRegistration() && !enrollmentFromEventHasTrackedEntity(bundle, event)) { - reporter.addError(event, E1313, event); + reporter.addError(event, E1313, event.getEvent()); } } @@ -94,7 +93,7 @@ private void validateRegistrationProgram( return; } - if (StringUtils.isEmpty(event.getEnrollment())) { + if (event.getEnrollment() == null) { reporter.addError(event, E1033, event.getEvent()); } else { Program enrollmentProgram = getEnrollmentProgramFromEvent(bundle, event); @@ -346,6 +345,10 @@ private Program getEnrollmentProgramFromEvent(TrackerBundle bundle, Event event) * @return whether the enrollment of the event has an existing tracked entity */ private boolean enrollmentFromEventHasTrackedEntity(TrackerBundle bundle, Event event) { + if (event.getEnrollment() == null) { + return true; + } + Enrollment enrollment = bundle.getPreheat().getEnrollment(event.getEnrollment()); if (enrollment == null) { diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/DataValuesValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/DataValuesValidator.java index 9b5afd9aab7b..16e4ea56aab7 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/DataValuesValidator.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/DataValuesValidator.java @@ -161,7 +161,7 @@ private void validateFileNotAlreadyAssigned( () -> fileResource != null && fileResource.getFileResourceOwner() != null - && !fileResource.getFileResourceOwner().equals(event.getEvent()), + && !fileResource.getFileResourceOwner().equals(event.getEvent().getValue()), event, E1009, value); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/EventValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/EventValidator.java index e11190c10d32..f587c893b105 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/EventValidator.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/EventValidator.java @@ -52,7 +52,6 @@ public EventValidator( each( TrackerBundle::getEvents, seq( - new UidValidator(), new ExistenceValidator(), new MandatoryFieldsValidator(), new MetaValidator(), diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/RepeatedEventsValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/RepeatedEventsValidator.java index e4c9a8a4d709..17d50486d7bc 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/RepeatedEventsValidator.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/RepeatedEventsValidator.java @@ -31,9 +31,8 @@ import java.util.Map; import java.util.stream.Collectors; import org.apache.commons.lang3.tuple.Pair; -import org.hisp.dhis.program.Enrollment; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.ProgramStage; -import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Event; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -50,22 +49,23 @@ class RepeatedEventsValidator implements Validator> { @Override public void validate(Reporter reporter, TrackerBundle bundle, List events) { - Map, List> - eventsByEnrollmentAndNotRepeatableProgramStage = - events.stream() - .filter(e -> !reporter.isInvalid(e)) - .filter(e -> !bundle.getStrategy(e).isDelete()) - .filter( - e -> { - ProgramStage programStage = - bundle.getPreheat().getProgramStage(e.getProgramStage()); - return programStage.getProgram().isRegistration() - && !programStage.getRepeatable(); - }) - .collect( - Collectors.groupingBy(e -> Pair.of(e.getProgramStage(), e.getEnrollment()))); + List validNotRepeatableEvents = + events.stream() + .filter(e -> !reporter.isInvalid(e)) + .filter( + e -> { + ProgramStage programStage = + bundle.getPreheat().getProgramStage(e.getProgramStage()); + return programStage.getProgram().isRegistration() + && !programStage.getRepeatable(); + }) + .toList(); + Map, List> eventsByEnrollmentAndNotRepeatableProgramStage = + validNotRepeatableEvents.stream() + .filter(e -> !bundle.getStrategy(e).isDelete()) + .collect(Collectors.groupingBy(e -> Pair.of(e.getProgramStage(), e.getEnrollment()))); - for (Map.Entry, List> mapEntry : + for (Map.Entry, List> mapEntry : eventsByEnrollmentAndNotRepeatableProgramStage.entrySet()) { if (mapEntry.getValue().size() > 1) { for (Event event : mapEntry.getValue()) { @@ -74,23 +74,13 @@ public void validate(Reporter reporter, TrackerBundle bundle, List events } } - bundle.getEvents().forEach(e -> validateNotMultipleEvents(reporter, bundle, e)); - } - - private void validateNotMultipleEvents(Reporter reporter, TrackerBundle bundle, Event event) { - Enrollment enrollment = bundle.getPreheat().getEnrollment(event.getEnrollment()); - ProgramStage programStage = bundle.getPreheat().getProgramStage(event.getProgramStage()); - - TrackerImportStrategy strategy = bundle.getStrategy(event); - - if (strategy == TrackerImportStrategy.CREATE - && programStage != null - && enrollment != null - && !programStage.getRepeatable() - && bundle - .getPreheat() - .hasProgramStageWithEvents(event.getProgramStage(), event.getEnrollment())) { - reporter.addError(event, ValidationCode.E1039, event.getProgramStage()); - } + validNotRepeatableEvents.stream() + .filter(e -> bundle.getStrategy(e).isCreate()) + .filter( + e -> + bundle + .getPreheat() + .hasProgramStageWithEvents(e.getProgramStage(), e.getEnrollment().getValue())) + .forEach(e -> reporter.addError(e, ValidationCode.E1039, e.getProgramStage())); } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/SecurityOwnershipValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/SecurityOwnershipValidator.java index ad7fddc717a6..89796a8892b8 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/SecurityOwnershipValidator.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/SecurityOwnershipValidator.java @@ -36,6 +36,7 @@ import lombok.extern.slf4j.Slf4j; import org.hisp.dhis.category.CategoryOption; import org.hisp.dhis.category.CategoryOptionCombo; +import org.hisp.dhis.common.UID; import org.hisp.dhis.event.EventStatus; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.program.Enrollment; @@ -102,7 +103,7 @@ public void validate( } } - String teUid = getTeUidFromEvent(bundle, event, program); + UID teUid = getTeUidFromEvent(bundle, event, program); CategoryOptionCombo categoryOptionCombo = bundle.getPreheat().getCategoryOptionCombo(event.getAttributeOptionCombo()); @@ -115,7 +116,7 @@ public void validate( bundle, event, preheatEvent, - trackedEntity == null ? null : trackedEntity.getUid(), + trackedEntity == null ? null : UID.of(trackedEntity), ownerOrgUnit, bundle.getUser()); } else { @@ -139,7 +140,7 @@ private void validateCreateEvent( org.hisp.dhis.tracker.imports.domain.Event event, CategoryOptionCombo categoryOptionCombo, ProgramStage programStage, - String teUid, + UID teUid, OrganisationUnit organisationUnit, OrganisationUnit ownerOrgUnit, Program program, @@ -168,7 +169,7 @@ private void validateUpdateAndDeleteEvent( TrackerBundle bundle, org.hisp.dhis.tracker.imports.domain.Event event, Event preheatEvent, - String teUid, + UID teUid, OrganisationUnit ownerOrgUnit, UserDetails user) { TrackerImportStrategy strategy = bundle.getStrategy(event); @@ -192,12 +193,17 @@ private void validateUpdateAndDeleteEvent( } } - private String getTeUidFromEvent( + private UID getTeUidFromEvent( TrackerBundle bundle, org.hisp.dhis.tracker.imports.domain.Event event, Program program) { if (program.isWithoutRegistration()) { return null; } + if (bundle.getStrategy(event).isUpdateOrDelete()) { + return UID.of( + bundle.getPreheat().getEvent(event.getUid()).getEnrollment().getTrackedEntity()); + } + Enrollment enrollment = bundle.getPreheat().getEnrollment(event.getEnrollment()); if (enrollment == null) { @@ -207,11 +213,11 @@ private String getTeUidFromEvent( .orElse(null); } - return enrollment.getTrackedEntity().getUid(); + return UID.of(enrollment.getTrackedEntity()); } private OrganisationUnit getOwnerOrganisationUnit( - TrackerPreheat preheat, String teUid, Program program) { + TrackerPreheat preheat, UID teUid, Program program) { Map programOwner = preheat.getProgramOwner().get(teUid); if (programOwner == null || programOwner.get(program.getUid()) == null) { @@ -236,7 +242,7 @@ private void checkOrgUnitInCaptureScope( private void checkTeTypeAndTeProgramAccess( Reporter reporter, TrackerDto dto, - String trackedEntity, + UID trackedEntity, OrganisationUnit ownerOrganisationUnit, Program program, UserDetails user) { @@ -245,7 +251,11 @@ private void checkTeTypeAndTeProgramAccess( } if (ownerOrganisationUnit != null - && !ownershipAccessManager.hasAccess(user, trackedEntity, ownerOrganisationUnit, program)) { + && !ownershipAccessManager.hasAccess( + user, + trackedEntity == null ? null : trackedEntity.getValue(), + ownerOrganisationUnit, + program)) { reporter.addError(dto, ValidationCode.E1102, user, trackedEntity, program); } } @@ -258,7 +268,7 @@ private void checkEventWriteAccess( OrganisationUnit eventOrgUnit, OrganisationUnit ownerOrgUnit, CategoryOptionCombo categoryOptionCombo, - String trackedEntity, + UID trackedEntity, boolean isCreatableInSearchScope) { if (bundle.getStrategy(event) != TrackerImportStrategy.UPDATE) { diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/StatusUpdateValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/StatusUpdateValidator.java index ed9def09d0ba..2457f3d08877 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/StatusUpdateValidator.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/StatusUpdateValidator.java @@ -49,11 +49,11 @@ public void validate(Reporter reporter, TrackerBundle bundle, Event event) { private boolean checkInvalidStatusTransition(EventStatus fromStatus, EventStatus toStatus) { return switch (fromStatus) { - // An event cannot transition from a STATUSES_WITH_DATA_VALUES to a - // STATUSES_WITHOUT_DATA_VALUES + // An event cannot transition from a STATUSES_WITH_DATA_VALUES to a + // STATUSES_WITHOUT_DATA_VALUES case VISITED, ACTIVE, COMPLETED -> EventStatus.STATUSES_WITHOUT_DATA_VALUES.contains(toStatus); - // An event can transition from a STATUSES_WITHOUT_DATA_VALUES to any status + // An event can transition from a STATUSES_WITHOUT_DATA_VALUES to any status case OVERDUE, SKIPPED, SCHEDULE -> false; }; } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/UpdatableFieldsValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/UpdatableFieldsValidator.java index a237e76d7315..0779671cefa6 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/UpdatableFieldsValidator.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/UpdatableFieldsValidator.java @@ -51,7 +51,7 @@ public void validate( reporter.addErrorIf( () -> event.getEnrollment() != null - && !event.getEnrollment().equals(preheatEvent.getEnrollment().getUid()), + && !event.getEnrollment().getValue().equals(preheatEvent.getEnrollment().getUid()), event, E1128, "enrollment"); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/ConstraintValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/ConstraintValidator.java index 9c5521cf9712..7a602c8ea8f9 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/ConstraintValidator.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/ConstraintValidator.java @@ -34,6 +34,7 @@ import static org.hisp.dhis.tracker.imports.validation.validator.relationship.ValidationUtils.relationshipItemValueType; import java.util.Optional; +import org.hisp.dhis.common.UID; import org.hisp.dhis.relationship.RelationshipConstraint; import org.hisp.dhis.relationship.RelationshipType; import org.hisp.dhis.trackedentity.TrackedEntity; @@ -173,13 +174,13 @@ private void validateTrackedEntityType( } private Optional getRelationshipTypeUidFromTrackedEntity( - TrackerBundle bundle, String uid) { + TrackerBundle bundle, UID uid) { return getTrackedEntityTypeFromTrackedEntity(bundle, uid) .or(() -> getTrackedEntityTypeFromTrackedEntityRef(bundle, uid)); } private Optional getTrackedEntityTypeFromTrackedEntity( - TrackerBundle bundle, String uid) { + TrackerBundle bundle, UID uid) { final TrackedEntity trackedEntity = bundle.getPreheat().getTrackedEntity(uid); return trackedEntity != null @@ -192,7 +193,7 @@ private Optional getTrackedEntityTypeFromTrackedEntity( } private Optional getTrackedEntityTypeFromTrackedEntityRef( - TrackerBundle bundle, String uid) { + TrackerBundle bundle, UID uid) { final Optional payloadTei = bundle.findTrackedEntityByUid(uid); return payloadTei.map(org.hisp.dhis.tracker.imports.domain.TrackedEntity::getTrackedEntityType); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/MandatoryFieldsValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/MandatoryFieldsValidator.java index 31f87fc5fe83..2a15290a33b5 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/MandatoryFieldsValidator.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/MandatoryFieldsValidator.java @@ -30,8 +30,8 @@ import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1124; import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E4001; +import java.util.Objects; import java.util.stream.Stream; -import org.apache.commons.lang3.StringUtils; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Relationship; import org.hisp.dhis.tracker.imports.domain.RelationshipItem; @@ -66,7 +66,7 @@ public void validate(Reporter reporter, TrackerBundle bundle, Relationship relat private boolean hasUnexpectedReferences(RelationshipItem item) { return Stream.of(item.getTrackedEntity(), item.getEnrollment(), item.getEvent()) - .filter(StringUtils::isNotBlank) + .filter(Objects::nonNull) .count() != 1; } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/RelationshipValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/RelationshipValidator.java index 79d6c24a5b1b..4fe634ba5dd4 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/RelationshipValidator.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/RelationshipValidator.java @@ -47,7 +47,6 @@ public RelationshipValidator(SecurityOwnershipValidator securityOwnershipValidat each( TrackerBundle::getRelationships, seq( - new UidValidator(), new ExistenceValidator(), new MandatoryFieldsValidator(), new MetaValidator(), diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/UidValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/UidValidator.java deleted file mode 100644 index efc3f51c5071..000000000000 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/UidValidator.java +++ /dev/null @@ -1,50 +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.imports.validation.validator.relationship; - -import static org.hisp.dhis.tracker.imports.validation.validator.ValidationUtils.checkUidFormat; - -import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; -import org.hisp.dhis.tracker.imports.domain.Relationship; -import org.hisp.dhis.tracker.imports.validation.Reporter; -import org.hisp.dhis.tracker.imports.validation.Validator; - -/** - * @author Morten Svanæs - */ -class UidValidator implements Validator { - @Override - public void validate(Reporter reporter, TrackerBundle bundle, Relationship relationship) { - checkUidFormat( - relationship.getRelationship(), - reporter, - relationship, - relationship.getRelationship(), - relationship.getRelationship()); - } -} diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/ValidationUtils.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/ValidationUtils.java index 0adb6448d123..9ef9d7e56a9b 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/ValidationUtils.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/ValidationUtils.java @@ -28,7 +28,7 @@ package org.hisp.dhis.tracker.imports.validation.validator.relationship; import org.apache.commons.lang3.ObjectUtils; -import org.apache.commons.lang3.StringUtils; +import org.hisp.dhis.common.UID; import org.hisp.dhis.tracker.TrackerType; import org.hisp.dhis.tracker.imports.domain.RelationshipItem; @@ -42,17 +42,17 @@ private ValidationUtils() { } public static TrackerType relationshipItemValueType(RelationshipItem item) { - if (StringUtils.isNotEmpty(item.getTrackedEntity())) { + if (item.getTrackedEntity() != null) { return TrackerType.TRACKED_ENTITY; - } else if (StringUtils.isNotEmpty(item.getEnrollment())) { + } else if (item.getEnrollment() != null) { return TrackerType.ENROLLMENT; - } else if (StringUtils.isNotEmpty(item.getEvent())) { + } else if (item.getEvent() != null) { return TrackerType.EVENT; } return null; } - public static String getUidFromRelationshipItem(RelationshipItem item) { + public static UID getUidFromRelationshipItem(RelationshipItem item) { return ObjectUtils.firstNonNull(item.getTrackedEntity(), item.getEnrollment(), item.getEvent()); } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/AttributeValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/AttributeValidator.java index f19eb5aaeb78..742916d4b2b6 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/AttributeValidator.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/AttributeValidator.java @@ -41,7 +41,7 @@ import org.hisp.dhis.trackedentity.TrackedEntityAttribute; import org.hisp.dhis.trackedentity.TrackedEntityType; import org.hisp.dhis.trackedentity.TrackedEntityTypeAttribute; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Attribute; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -87,7 +87,7 @@ private void validateMandatoryAttributes( TrackedEntityType trackedEntityType) { Set trackedEntityAttributes = - getTrackedEntityAttributes(bundle, trackedEntity.getTrackedEntity()); + getTrackedEntityAttributes(bundle, trackedEntity.getUid()); TrackerIdSchemeParams idSchemes = bundle.getPreheat().getIdSchemes(); trackedEntityType.getTrackedEntityTypeAttributes().stream() diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/TrackedEntityValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/TrackedEntityValidator.java index b53c6ece4230..ba29ad12ce0f 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/TrackedEntityValidator.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/TrackedEntityValidator.java @@ -51,7 +51,6 @@ public TrackedEntityValidator( each( TrackerBundle::getTrackedEntities, seq( - new UidValidator(), new ExistenceValidator(), new MandatoryFieldsValidator(), new MetaValidator(), diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/UidValidator.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/UidValidator.java deleted file mode 100644 index 75788e23e47e..000000000000 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/UidValidator.java +++ /dev/null @@ -1,50 +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.imports.validation.validator.trackedentity; - -import static org.hisp.dhis.tracker.imports.validation.validator.ValidationUtils.checkUidFormat; - -import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; -import org.hisp.dhis.tracker.imports.domain.TrackedEntity; -import org.hisp.dhis.tracker.imports.validation.Reporter; -import org.hisp.dhis.tracker.imports.validation.Validator; - -/** - * @author Morten Svanæs - */ -class UidValidator implements Validator { - @Override - public void validate(Reporter reporter, TrackerBundle bundle, TrackedEntity trackedEntity) { - checkUidFormat( - trackedEntity.getTrackedEntity(), - reporter, - trackedEntity, - trackedEntity, - trackedEntity.getTrackedEntity()); - } -} diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/TrackerIdSchemeParamTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/TrackerIdSchemeParamTest.java index 357e923eefd5..86a6215f4549 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/TrackerIdSchemeParamTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/TrackerIdSchemeParamTest.java @@ -28,10 +28,10 @@ package org.hisp.dhis.tracker; import static org.hisp.dhis.common.CodeGenerator.generateUid; -import static org.hisp.dhis.tracker.imports.TrackerIdScheme.ATTRIBUTE; -import static org.hisp.dhis.tracker.imports.TrackerIdScheme.CODE; -import static org.hisp.dhis.tracker.imports.TrackerIdScheme.NAME; -import static org.hisp.dhis.tracker.imports.TrackerIdScheme.UID; +import static org.hisp.dhis.tracker.TrackerIdScheme.ATTRIBUTE; +import static org.hisp.dhis.tracker.TrackerIdScheme.CODE; +import static org.hisp.dhis.tracker.TrackerIdScheme.NAME; +import static org.hisp.dhis.tracker.TrackerIdScheme.UID; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -39,7 +39,6 @@ import org.hisp.dhis.attribute.AttributeValues; import org.hisp.dhis.common.IdentifiableObject; import org.hisp.dhis.program.Program; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; import org.junit.jupiter.api.Test; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/TrackerIdSchemeParamsTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/TrackerIdSchemeParamsTest.java new file mode 100644 index 000000000000..89e782476e3c --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/TrackerIdSchemeParamsTest.java @@ -0,0 +1,84 @@ +/* + * 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; + +import static org.hisp.dhis.tracker.TrackerIdSchemeParam.CODE; +import static org.hisp.dhis.tracker.TrackerIdSchemeParam.NAME; +import static org.hisp.dhis.tracker.TrackerIdSchemeParam.UID; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class TrackerIdSchemeParamsTest { + + @Test + void shouldDefaultToUID() { + TrackerIdSchemeParams params = TrackerIdSchemeParams.builder().build(); + + assertEquals(UID, params.getIdScheme(), "idScheme"); + assertEquals(UID, params.getDataElementIdScheme(), "dataElementIdScheme"); + assertEquals(UID, params.getOrgUnitIdScheme(), "orgUnitIdScheme"); + assertEquals(UID, params.getProgramIdScheme(), "programIdScheme"); + assertEquals(UID, params.getProgramStageIdScheme(), "programStageIdScheme"); + assertEquals(UID, params.getCategoryOptionComboIdScheme(), "categoryOptionComboIdScheme"); + assertEquals(UID, params.getCategoryOptionIdScheme(), "categoryOptionIdScheme"); + } + + @Test + void idSchemeAppliesToAllMetadata() { + TrackerIdSchemeParams params = TrackerIdSchemeParams.builder().idScheme(CODE).build(); + + assertEquals(CODE, params.getIdScheme(), "idScheme"); + assertEquals(CODE, params.getDataElementIdScheme(), "dataElementIdScheme"); + assertEquals(CODE, params.getOrgUnitIdScheme(), "orgUnitIdScheme"); + assertEquals(CODE, params.getProgramIdScheme(), "programIdScheme"); + assertEquals(CODE, params.getProgramStageIdScheme(), "programStageIdScheme"); + assertEquals(CODE, params.getCategoryOptionComboIdScheme(), "categoryOptionComboIdScheme"); + assertEquals(CODE, params.getCategoryOptionIdScheme(), "categoryOptionIdScheme"); + } + + @Test + void idSchemeIsOverriddenBySpecificField() { + TrackerIdSchemeParam programIdScheme = + TrackerIdSchemeParam.ofAttribute(org.hisp.dhis.common.UID.generate().getValue()); + TrackerIdSchemeParams params = + TrackerIdSchemeParams.builder() + .idScheme(NAME) + .programIdScheme(programIdScheme) + .programStageIdScheme(CODE) + .build(); + + assertEquals(NAME, params.getIdScheme(), "idScheme"); + assertEquals(NAME, params.getDataElementIdScheme(), "dataElementIdScheme"); + assertEquals(NAME, params.getOrgUnitIdScheme(), "orgUnitIdScheme"); + assertEquals(programIdScheme, params.getProgramIdScheme(), "programIdScheme"); + assertEquals(CODE, params.getProgramStageIdScheme(), "programStageIdScheme"); + assertEquals(NAME, params.getCategoryOptionComboIdScheme(), "categoryOptionComboIdScheme"); + assertEquals(NAME, params.getCategoryOptionIdScheme(), "categoryOptionIdScheme"); + } +} diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/TrackerIdentifierCollectorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/TrackerIdentifierCollectorTest.java index b1fd7d638bc5..7a551afe8e6e 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/TrackerIdentifierCollectorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/TrackerIdentifierCollectorTest.java @@ -44,7 +44,7 @@ import java.util.Set; import org.hisp.dhis.category.CategoryOption; import org.hisp.dhis.category.CategoryOptionCombo; -import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.program.Program; @@ -93,7 +93,8 @@ void collectTrackedEntities() { Map, Set> ids = collector.collect(trackerObjects); assertNotNull(ids); - assertContainsOnly(Set.of(trackedEntity.getTrackedEntity()), ids.get(TrackedEntity.class)); + assertContainsOnly( + Set.of(trackedEntity.getTrackedEntity().getValue()), ids.get(TrackedEntity.class)); assertContainsOnly(Set.of("sunshine"), ids.get(TrackedEntityType.class)); assertContainsOnly(Set.of("ward"), ids.get(OrganisationUnit.class)); assertContainsOnly(Set.of("VohJnvWfvyo", "qv9xOw8fBzy"), ids.get(TrackedEntityAttribute.class)); @@ -116,8 +117,9 @@ void collectEnrollments() { Map, Set> ids = collector.collect(trackerObjects); assertNotNull(ids); - assertContainsOnly(Set.of(enrollment.getUid()), ids.get(Enrollment.class)); - assertContainsOnly(Set.of(enrollment.getTrackedEntity()), ids.get(TrackedEntity.class)); + assertContainsOnly(Set.of(enrollment.getUid().getValue()), ids.get(Enrollment.class)); + assertContainsOnly( + Set.of(enrollment.getTrackedEntity().getValue()), ids.get(TrackedEntity.class)); assertContainsOnly(Set.of("sunshine"), ids.get(Program.class)); assertContainsOnly(Set.of("ward"), ids.get(OrganisationUnit.class)); assertContainsOnly(Set.of("VohJnvWfvyo", "qv9xOw8fBzy"), ids.get(TrackedEntityAttribute.class)); @@ -135,7 +137,7 @@ void collectEvents() { .dataValues(dataValues("VohJnvWfvyo", "qv9xOw8fBzy")) .attributeOptionCombo(ofCode("rgb")) .attributeCategoryOptions(Set.of(ofCode("red"), ofCode("green"), ofCode("blue"))) - .notes(List.of(Note.builder().note("i1vviSlidJE").value("nice day!").build())) + .notes(List.of(Note.builder().note(UID.of("i1vviSlidJE")).value("nice day!").build())) .build(); TrackerObjects trackerObjects = TrackerObjects.builder().events(singletonList(event)).build(); @@ -143,8 +145,8 @@ void collectEvents() { Map, Set> ids = collector.collect(trackerObjects); assertNotNull(ids); - assertContainsOnly(Set.of(event.getUid()), ids.get(Event.class)); - assertContainsOnly(Set.of(event.getEnrollment()), ids.get(Enrollment.class)); + assertContainsOnly(Set.of(event.getUid().getValue()), ids.get(Event.class)); + assertContainsOnly(Set.of(event.getEnrollment().getValue()), ids.get(Enrollment.class)); assertContainsOnly(Set.of("sunshine"), ids.get(Program.class)); assertContainsOnly(Set.of("flowers"), ids.get(ProgramStage.class)); assertContainsOnly(Set.of("ward"), ids.get(OrganisationUnit.class)); @@ -154,22 +156,13 @@ void collectEvents() { assertContainsOnly(Set.of("i1vviSlidJE"), ids.get(org.hisp.dhis.note.Note.class)); } - @Test - void collectEventsSkipsNotesWithoutAnId() { - Event event = Event.builder().notes(List.of(Note.builder().value("nice day!").build())).build(); - - TrackerObjects trackerObjects = TrackerObjects.builder().events(singletonList(event)).build(); - - Map, Set> ids = collector.collect(trackerObjects); - - assertNotNull(ids); - assertNull(ids.get(org.hisp.dhis.note.Note.class)); - } - @Test void collectEventsSkipsNotesWithoutAValue() { Event event = - Event.builder().notes(List.of(Note.builder().note("i1vviSlidJE").build())).build(); + Event.builder() + .event(UID.generate()) + .notes(List.of(Note.builder().note(UID.of("i1vviSlidJE")).build())) + .build(); TrackerObjects trackerObjects = TrackerObjects.builder().events(singletonList(event)).build(); @@ -195,14 +188,16 @@ void collectRelationships() { Map, Set> ids = collector.collect(trackerObjects); assertNotNull(ids); - assertContainsOnly(Set.of(relationship.getRelationship()), ids.get(Relationship.class)); + assertContainsOnly( + Set.of(relationship.getRelationship().getValue()), ids.get(Relationship.class)); assertContainsOnly(Set.of("sunshine"), ids.get(RelationshipType.class)); - assertContainsOnly(Set.of(relationship.getFrom().getEnrollment()), ids.get(Enrollment.class)); - assertContainsOnly(Set.of(relationship.getTo().getEvent()), ids.get(Event.class)); + assertContainsOnly( + Set.of(relationship.getFrom().getEnrollment().getValue()), ids.get(Enrollment.class)); + assertContainsOnly(Set.of(relationship.getTo().getEvent().getValue()), ids.get(Event.class)); } - private String uid() { - return CodeGenerator.generateUid(); + private UID uid() { + return UID.generate(); } private List teAttributes(String... uids) { diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/deduplication/DeduplicationServiceTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/deduplication/DeduplicationServiceTest.java index 4ee8251169a8..518866e42de1 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/deduplication/DeduplicationServiceTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/deduplication/DeduplicationServiceTest.java @@ -180,7 +180,7 @@ void shouldBeAutoMergeable() trackedEntityA, trackedEntityB, mergeObject.getTrackedEntityAttributes()); verify(potentialDuplicateStore) .moveRelationships(trackedEntityA, trackedEntityB, mergeObject.getRelationships()); - verify(trackerObjectDeletionService).deleteTrackedEntities(List.of(trackedEntityB.getUid())); + verify(trackerObjectDeletionService).deleteTrackedEntities(List.of(UID.of(trackedEntityB))); verify(potentialDuplicateStore) .update(argThat(t -> t.getStatus().equals(DeduplicationStatus.MERGED))); verify(potentialDuplicateStore).auditMerge(deduplicationMergeParams); @@ -313,7 +313,7 @@ void shouldtBeAutoMergeableAttributeValuesIsEmpty() trackedEntityA, trackedEntityB, mergeObject.getTrackedEntityAttributes()); verify(potentialDuplicateStore) .moveRelationships(trackedEntityA, trackedEntityB, mergeObject.getRelationships()); - verify(trackerObjectDeletionService).deleteTrackedEntities(List.of(trackedEntityB.getUid())); + verify(trackerObjectDeletionService).deleteTrackedEntities(List.of(UID.of(trackedEntityB))); verify(potentialDuplicateStore).auditMerge(deduplicationMergeParams); } @@ -341,7 +341,7 @@ void shouldBeManualMergeable() trackedEntityA, trackedEntityB, deduplicationMergeParams.getMergeObject().getRelationships()); - verify(trackerObjectDeletionService).deleteTrackedEntities(List.of(trackedEntityB.getUid())); + verify(trackerObjectDeletionService).deleteTrackedEntities(List.of(UID.of(trackedEntityB))); verify(potentialDuplicateStore).auditMerge(deduplicationMergeParams); } 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-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackerBundleTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackerBundleTest.java index 49ed482d507a..d0e677e9702e 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackerBundleTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackerBundleTest.java @@ -29,18 +29,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Arrays; import java.util.Collections; -import java.util.List; -import org.hisp.dhis.tracker.TrackerType; import org.hisp.dhis.tracker.imports.AtomicMode; import org.hisp.dhis.tracker.imports.ValidationMode; import org.hisp.dhis.tracker.imports.domain.Enrollment; import org.hisp.dhis.tracker.imports.domain.Event; -import org.hisp.dhis.tracker.imports.domain.Relationship; import org.hisp.dhis.tracker.imports.domain.TrackedEntity; import org.junit.jupiter.api.Test; @@ -82,99 +77,4 @@ void testBasicSetup2() { assertEquals(2, trackerBundle.getEnrollments().size()); assertEquals(2, trackerBundle.getEvents().size()); } - - @Test - void testGetTrackedEntityGivenNull() { - TrackerBundle bundle = - TrackerBundle.builder() - .trackedEntities(List.of(TrackedEntity.builder().trackedEntity("uid").build())) - .build(); - - assertTrue(bundle.findTrackedEntityByUid(null).isEmpty()); - } - - @Test - void testExistsTrackedEntity() { - TrackerBundle bundle = - TrackerBundle.builder() - .trackedEntities(List.of(TrackedEntity.builder().trackedEntity("uid").build())) - .build(); - - assertFalse(bundle.exists(TrackerType.TRACKED_ENTITY, "missing")); - assertTrue(bundle.exists(TrackedEntity.builder().trackedEntity("uid").build())); - } - - @Test - void testGetEnrollmentGivenNull() { - TrackerBundle bundle = - TrackerBundle.builder() - .enrollments(List.of(Enrollment.builder().enrollment("uid").build())) - .build(); - - assertTrue(bundle.findEnrollmentByUid(null).isEmpty()); - } - - @Test - void testExistsEnrollment() { - TrackerBundle bundle = - TrackerBundle.builder() - .enrollments(List.of(Enrollment.builder().enrollment("uid").build())) - .build(); - - assertFalse(bundle.exists(TrackerType.ENROLLMENT, "missing")); - assertTrue(bundle.exists(Enrollment.builder().enrollment("uid").build())); - } - - @Test - void testGetEventGivenNull() { - TrackerBundle bundle = - TrackerBundle.builder().events(List.of(Event.builder().event("uid").build())).build(); - - assertTrue(bundle.findEventByUid(null).isEmpty()); - } - - @Test - void testExistsEvent() { - TrackerBundle bundle = - TrackerBundle.builder().events(List.of(Event.builder().event("uid").build())).build(); - - assertFalse(bundle.exists(TrackerType.EVENT, "missing")); - assertTrue(bundle.exists(Event.builder().event("uid").build())); - } - - @Test - void testGetRelationshipGivenNull() { - TrackerBundle bundle = - TrackerBundle.builder() - .relationships(List.of(Relationship.builder().relationship("uid").build())) - .build(); - - assertTrue(bundle.findRelationshipByUid(null).isEmpty()); - } - - @Test - void testGetRelationshipInBundleContainingNullUids() { - TrackerBundle bundle = - TrackerBundle.builder().relationships(List.of(Relationship.builder().build())).build(); - - assertTrue(bundle.findRelationshipByUid("uid").isEmpty()); - } - - @Test - void testExistsRelationship() { - TrackerBundle bundle = - TrackerBundle.builder() - .relationships(List.of(Relationship.builder().relationship("uid").build())) - .build(); - - assertFalse(bundle.exists(TrackerType.RELATIONSHIP, "missing")); - assertTrue(bundle.exists(Relationship.builder().relationship("uid").build())); - } - - @Test - void testExistsFailsOnNullType() { - TrackerBundle bundle = TrackerBundle.builder().build(); - - assertThrows(NullPointerException.class, () -> bundle.exists(null, "uid")); - } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackerImporterServiceTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackerImporterServiceTest.java index 419375d00b95..3c6baef0a62e 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackerImporterServiceTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackerImporterServiceTest.java @@ -39,6 +39,7 @@ import java.util.ArrayList; import java.util.List; +import org.hisp.dhis.common.UID; import org.hisp.dhis.scheduling.JobProgress; import org.hisp.dhis.scheduling.RecordingJobProgress; import org.hisp.dhis.tracker.imports.DefaultTrackerImportService; @@ -89,7 +90,7 @@ public void setUp() { injectSecurityContextNoSettings(user); Event event = new Event(); - event.setEvent("EventUid"); + event.setEvent(UID.generate()); final List events = List.of(event); params = TrackerImportParams.builder().build(); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackerObjectsMapperTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackerObjectsMapperTest.java index 90f164f7327f..58a6b753e58c 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackerObjectsMapperTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackerObjectsMapperTest.java @@ -28,6 +28,7 @@ package org.hisp.dhis.tracker.imports.bundle; import static org.hisp.dhis.program.EnrollmentStatus.ACTIVE; +import static org.hisp.dhis.test.utils.Assertions.assertEqualUids; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; @@ -37,6 +38,7 @@ import java.util.Set; import org.hisp.dhis.category.CategoryOptionCombo; import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.event.EventStatus; import org.hisp.dhis.note.Note; import org.hisp.dhis.organisationunit.OrganisationUnit; @@ -52,7 +54,7 @@ import org.hisp.dhis.test.TestBase; import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentity.TrackedEntityType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; import org.hisp.dhis.tracker.imports.domain.RelationshipItem; import org.hisp.dhis.tracker.imports.preheat.TrackerPreheat; @@ -60,7 +62,6 @@ import org.hisp.dhis.user.User; import org.hisp.dhis.user.UserDetails; import org.hisp.dhis.util.DateUtils; -import org.hisp.dhis.util.ObjectUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -69,15 +70,15 @@ class TrackerObjectsMapperTest extends TestBase { private static final Date YESTERDAY = DateUtils.addDays(NOW, -1); - private static final String TE_UID = CodeGenerator.generateUid(); + private static final UID TE_UID = UID.generate(); - private static final String ENROLLMENT_UID = CodeGenerator.generateUid(); + private static final UID ENROLLMENT_UID = UID.generate(); - private static final String EVENT_UID = CodeGenerator.generateUid(); + private static final UID EVENT_UID = UID.generate(); - private static final String RELATIONSHIP_UID = CodeGenerator.generateUid(); + private static final UID RELATIONSHIP_UID = UID.generate(); - private static final String NOTE_UID = CodeGenerator.generateUid(); + private static final UID NOTE_UID = UID.generate(); private static final String PROGRAM_STAGE_UID = CodeGenerator.generateUid(); @@ -498,8 +499,8 @@ void testMapRelationshipFromTEToEvent() { @Test void testMapRelationshipFromTEToTE() { TrackedEntity toTrackedEntity = trackedEntity(); - String toTrackedEntityUid = CodeGenerator.generateUid(); - toTrackedEntity.setUid(toTrackedEntityUid); + UID toTrackedEntityUid = UID.generate(); + toTrackedEntity.setUid(toTrackedEntityUid.getValue()); preheat.putTrackedEntities(List.of(toTrackedEntity, trackedEntity())); org.hisp.dhis.tracker.imports.domain.Relationship relationship = org.hisp.dhis.tracker.imports.domain.Relationship.builder() @@ -520,7 +521,7 @@ private void assertMappedTrackedEntity( TrackedEntity actual, UserDetails createdBy, UserDetails updatedBy) { - assertEquals(trackedEntity.getUid(), actual.getUid()); + assertEqualUids(trackedEntity.getTrackedEntity(), actual); assertEquals(trackedEntity.getOrgUnit().getIdentifier(), actual.getOrganisationUnit().getUid()); assertEquals( trackedEntity.getTrackedEntityType().getIdentifier(), @@ -542,8 +543,8 @@ private void assertMappedEnrollment( Enrollment actual, UserDetails createdBy, UserDetails updatedBy) { - assertEquals(enrollment.getUid(), actual.getUid()); - assertEquals(enrollment.getTrackedEntity(), actual.getTrackedEntity().getUid()); + assertEqualUids(enrollment.getEnrollment(), actual); + assertEqualUids(enrollment.getTrackedEntity(), actual.getTrackedEntity()); assertEquals(enrollment.getOrgUnit().getIdentifier(), actual.getOrganisationUnit().getUid()); assertEquals(enrollment.getProgram().getIdentifier(), actual.getProgram().getUid()); assertEquals(UserInfoSnapshot.from(createdBy), actual.getCreatedByUserInfo()); @@ -565,8 +566,8 @@ private void assertMappedEvent( Event actual, UserDetails createdBy, UserDetails updatedBy) { - assertEquals(event.getUid(), actual.getUid()); - assertEquals(event.getEnrollment(), actual.getEnrollment().getUid()); + assertEqualUids(event.getUid(), actual); + assertEqualUids(event.getEnrollment(), actual.getEnrollment()); assertEquals(event.getOrgUnit().getIdentifier(), actual.getOrganisationUnit().getUid()); assertEquals(event.getProgramStage().getIdentifier(), actual.getProgramStage().getUid()); assertEquals(UserInfoSnapshot.from(createdBy), actual.getCreatedByUserInfo()); @@ -586,30 +587,32 @@ private void assertMappedRelationship( org.hisp.dhis.tracker.imports.domain.Relationship relationship, Relationship actual, UserDetails createdBy) { - assertEquals(relationship.getUid(), actual.getUid()); + assertEqualUids(relationship.getRelationship(), actual); assertEquals(createdBy.getUid(), actual.getLastUpdatedBy().getUid()); assertEquals( DateUtils.fromInstant(relationship.getCreatedAtClient()), actual.getCreatedAtClient()); assertEquals( relationship.getRelationshipType().getIdentifier(), actual.getRelationshipType().getUid()); - assertEquals( - relationship.getFrom().getTrackedEntity(), - ObjectUtils.applyIfNotNull(actual.getFrom().getTrackedEntity(), TrackedEntity::getUid)); - assertEquals( - relationship.getFrom().getEnrollment(), - ObjectUtils.applyIfNotNull(actual.getFrom().getEnrollment(), Enrollment::getUid)); - assertEquals( - relationship.getFrom().getEvent(), - ObjectUtils.applyIfNotNull(actual.getFrom().getEvent(), Event::getUid)); - assertEquals( - relationship.getTo().getTrackedEntity(), - ObjectUtils.applyIfNotNull(actual.getTo().getTrackedEntity(), TrackedEntity::getUid)); - assertEquals( - relationship.getTo().getEnrollment(), - ObjectUtils.applyIfNotNull(actual.getTo().getEnrollment(), Enrollment::getUid)); - assertEquals( - relationship.getTo().getEvent(), - ObjectUtils.applyIfNotNull(actual.getTo().getEvent(), Event::getUid)); + switch (actual.getRelationshipType().getFromConstraint().getRelationshipEntity()) { + case TRACKED_ENTITY_INSTANCE -> + assertEqualUids( + relationship.getFrom().getTrackedEntity(), actual.getFrom().getTrackedEntity()); + case PROGRAM_INSTANCE -> + assertEqualUids(relationship.getFrom().getEnrollment(), actual.getFrom().getEnrollment()); + case PROGRAM_STAGE_INSTANCE -> + assertEqualUids(relationship.getFrom().getEvent(), actual.getFrom().getEvent()); + } + + switch (actual.getRelationshipType().getToConstraint().getRelationshipEntity()) { + case TRACKED_ENTITY_INSTANCE -> + assertEqualUids( + relationship.getTo().getTrackedEntity(), actual.getTo().getTrackedEntity()); + case PROGRAM_INSTANCE -> + assertEqualUids(relationship.getTo().getEnrollment(), actual.getTo().getEnrollment()); + case PROGRAM_STAGE_INSTANCE -> + assertEqualUids(relationship.getTo().getEvent(), actual.getTo().getEvent()); + } + RelationshipKey relationshipKey = RelationshipKeySupport.getRelationshipKey(relationship, actual.getRelationshipType()); assertEquals(relationshipKey.asString(), actual.getKey()); @@ -622,7 +625,10 @@ private void assertNotes( UserDetails updatedBy) { for (org.hisp.dhis.tracker.imports.domain.Note note : notes) { Note dbNote = - dbNotes.stream().filter(n -> n.getUid().equals(note.getNote())).findFirst().orElse(null); + dbNotes.stream() + .filter(n -> n.getUid().equals(note.getNote().getValue())) + .findFirst() + .orElse(null); assertNotNull(dbNote); assertEquals(note.getValue(), dbNote.getNoteText()); assertEquals(note.getStoredBy(), dbNote.getCreator()); @@ -632,7 +638,7 @@ private void assertNotes( private TrackedEntity trackedEntity() { TrackedEntity dbTrackedEntity = new TrackedEntity(); - dbTrackedEntity.setUid(TE_UID); + dbTrackedEntity.setUid(TE_UID.getValue()); dbTrackedEntity.setCreated(NOW); dbTrackedEntity.setCreatedByUserInfo(UserInfoSnapshot.from(creatingUser)); @@ -651,7 +657,7 @@ private TrackedEntity trackedEntity() { private Enrollment enrollment(EnrollmentStatus status) { Enrollment dbEnrollment = new Enrollment(); - dbEnrollment.setUid(ENROLLMENT_UID); + dbEnrollment.setUid(ENROLLMENT_UID.getValue()); dbEnrollment.setCreated(NOW); dbEnrollment.setCreatedByUserInfo(UserInfoSnapshot.from(creatingUser)); dbEnrollment.setLastUpdatedByUserInfo(UserInfoSnapshot.from(updatingUser)); @@ -669,7 +675,7 @@ private Enrollment enrollment(EnrollmentStatus status) { private Event event(EventStatus status) { Event dbEvent = new Event(); - dbEvent.setUid(EVENT_UID); + dbEvent.setUid(EVENT_UID.getValue()); dbEvent.setCreated(NOW); dbEvent.setCreatedByUserInfo(UserInfoSnapshot.from(creatingUser)); dbEvent.setLastUpdatedByUserInfo(UserInfoSnapshot.from(updatingUser)); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/domain/MetadataIdentifierTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/domain/MetadataIdentifierTest.java index 14dffbd453a6..3fb0fc30f4b2 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/domain/MetadataIdentifierTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/domain/MetadataIdentifierTest.java @@ -37,7 +37,7 @@ import org.hisp.dhis.attribute.AttributeValues; import org.hisp.dhis.common.IdentifiableObject; import org.hisp.dhis.program.Program; -import org.hisp.dhis.tracker.imports.TrackerIdScheme; +import org.hisp.dhis.tracker.TrackerIdScheme; import org.junit.jupiter.api.Test; class MetadataIdentifierTest { diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/job/NotificationSenderTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/job/NotificationSenderTest.java index 65287d3f6203..083cdda393c0 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/job/NotificationSenderTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/job/NotificationSenderTest.java @@ -58,7 +58,7 @@ class NotificationSenderTest { private static final UID TEMPLATE_UID = UID.of("h4w96yEMlzO"); private static final String ENROLLMENT_UID = "enrollmentUid"; - private static final String EVENT_UID = "eventUid"; + private static final UID EVENT_UID = UID.generate(); @Mock private ProgramNotificationTemplateService programNotificationTemplateService; @@ -347,7 +347,7 @@ private Event event() { Program program = new Program(); program.setProgramType(ProgramType.WITH_REGISTRATION); Event event = new Event(); - event.setUid(EVENT_UID); + event.setUid(EVENT_UID.getValue()); event.setEnrollment(enrollment()); return event; } @@ -356,7 +356,7 @@ private Event programEvent() { Program program = new Program(); program.setProgramType(ProgramType.WITHOUT_REGISTRATION); Event event = new Event(); - event.setUid(EVENT_UID); + event.setUid(EVENT_UID.getValue()); event.setEnrollment(enrollment(program)); return event; } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/job/TrackerSideValidationEffectDataBundleTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/job/TrackerSideValidationEffectDataBundleTest.java index a03a0a91e717..898d729c1897 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/job/TrackerSideValidationEffectDataBundleTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/job/TrackerSideValidationEffectDataBundleTest.java @@ -32,7 +32,7 @@ import java.util.List; import org.hisp.dhis.artemis.MessageType; -import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.Enrollment; import org.hisp.dhis.program.Event; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; @@ -47,17 +47,17 @@ class TrackerSideValidationEffectDataBundleTest { void testNotificationDataBundleForEnrollment() { org.hisp.dhis.tracker.imports.domain.Enrollment enrollment = new org.hisp.dhis.tracker.imports.domain.Enrollment(); - enrollment.setEnrollment("ja8NY4PW7Xm"); - String enrollmentUid = CodeGenerator.generateUid(); + enrollment.setEnrollment(UID.of("ja8NY4PW7Xm")); + UID enrollmentUid = UID.generate(); TrackerNotificationDataBundle bundle = TrackerNotificationDataBundle.builder() .enrollmentNotifications(List.of()) .accessedBy("testUser") .importStrategy(TrackerImportStrategy.CREATE) - .object(enrollmentUid) + .object(enrollmentUid.getValue()) .klass(Enrollment.class) .build(); - assertEquals(enrollmentUid, bundle.getObject()); + assertEquals(enrollmentUid.getValue(), bundle.getObject()); assertEquals(Enrollment.class, bundle.getKlass()); assertTrue(bundle.getEnrollmentNotifications().isEmpty()); assertTrue(bundle.getEventNotifications().isEmpty()); @@ -69,7 +69,7 @@ void testNotificationDataBundleForEnrollment() { void testNotificationDataBundleForEvent() { org.hisp.dhis.tracker.imports.domain.Event event = new org.hisp.dhis.tracker.imports.domain.Event(); - event.setEvent("ja8NY4PW7Xm"); + event.setEvent(UID.generate()); Event expected = new Event(); expected.setAutoFields(); TrackerNotificationDataBundle bundle = diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/DefaultTrackerPreheatServiceTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/DefaultTrackerPreheatServiceTest.java index 3f4a7c254391..da180f561490 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/DefaultTrackerPreheatServiceTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/DefaultTrackerPreheatServiceTest.java @@ -36,7 +36,7 @@ import java.util.Collections; import java.util.List; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.domain.TrackedEntity; import org.hisp.dhis.tracker.imports.domain.TrackerObjects; import org.hisp.dhis.tracker.imports.preheat.supplier.ClassBasedSupplier; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheatTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheatTest.java index 0ec6641cd76d..7132ffb7018c 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheatTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheatTest.java @@ -51,15 +51,16 @@ import org.hisp.dhis.category.CategoryOptionCombo; import org.hisp.dhis.common.CodeGenerator; import org.hisp.dhis.common.DataDimensionType; +import org.hisp.dhis.common.UID; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.program.Enrollment; import org.hisp.dhis.program.Event; import org.hisp.dhis.program.Program; import org.hisp.dhis.test.TestBase; import org.hisp.dhis.trackedentity.TrackedEntity; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.TrackerType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -359,57 +360,62 @@ void testPutCollectionUid() { @Test void testExistsTrackedEntity() { - assertFalse(preheat.exists(TrackerType.TRACKED_ENTITY, "uid")); + UID uid = UID.generate(); + assertFalse(preheat.exists(TrackerType.TRACKED_ENTITY, uid)); TrackedEntity te = new TrackedEntity(); - te.setUid("uid"); + te.setUid(uid.getValue()); preheat.putTrackedEntities(List.of(te)); - assertTrue(preheat.exists(TrackerType.TRACKED_ENTITY, "uid")); + assertTrue(preheat.exists(TrackerType.TRACKED_ENTITY, uid)); assertTrue( preheat.exists( org.hisp.dhis.tracker.imports.domain.TrackedEntity.builder() - .trackedEntity("uid") + .trackedEntity(uid) .build())); } @Test void testExistsEnrollment() { - assertFalse(preheat.exists(TrackerType.ENROLLMENT, "uid")); + UID uid = UID.generate(); + assertFalse(preheat.exists(TrackerType.ENROLLMENT, uid)); Enrollment enrollment = new Enrollment(); - enrollment.setUid("uid"); + enrollment.setUid(uid.getValue()); preheat.putEnrollments(List.of(enrollment)); - assertTrue(preheat.exists(TrackerType.ENROLLMENT, "uid")); + assertTrue(preheat.exists(TrackerType.ENROLLMENT, uid)); } @Test void testExistsEvent() { - assertFalse(preheat.exists(TrackerType.EVENT, "uid")); + UID uid = UID.generate(); + assertFalse(preheat.exists(TrackerType.EVENT, uid)); Event event = new Event(); - event.setUid("uid"); + event.setUid(uid.getValue()); preheat.putEvents(List.of(event)); - assertTrue(preheat.exists(TrackerType.EVENT, "uid")); + assertTrue(preheat.exists(TrackerType.EVENT, uid)); } @Test void testExistsRelationship() { - assertFalse(preheat.exists(TrackerType.RELATIONSHIP, "uid")); + UID uid = UID.generate(); + assertFalse(preheat.exists(TrackerType.RELATIONSHIP, uid)); org.hisp.dhis.relationship.Relationship relationship = new org.hisp.dhis.relationship.Relationship(); - relationship.setUid("uid"); + relationship.setUid(uid.getValue()); preheat.putRelationship(relationship); - assertTrue(preheat.exists(TrackerType.RELATIONSHIP, "uid")); + assertTrue(preheat.exists(TrackerType.RELATIONSHIP, uid)); } @Test void testExistsFailsOnNullType() { - assertThrows(NullPointerException.class, () -> preheat.exists(null, "uid")); + UID uid = UID.generate(); + assertThrows(NullPointerException.class, () -> preheat.exists(null, uid)); } private Set categoryOptionIds( diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/DuplicateRelationshipSupplierTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/DuplicateRelationshipSupplierTest.java index 2ba0167c52b5..961c9a3e5ebb 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/DuplicateRelationshipSupplierTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/DuplicateRelationshipSupplierTest.java @@ -32,13 +32,14 @@ import static org.mockito.Mockito.when; import java.util.List; +import org.hisp.dhis.common.UID; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.relationship.Relationship; import org.hisp.dhis.relationship.RelationshipType; import org.hisp.dhis.test.TestBase; import org.hisp.dhis.trackedentity.TrackedEntity; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; import org.hisp.dhis.tracker.export.relationship.RelationshipStore; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; import org.hisp.dhis.tracker.imports.domain.RelationshipItem; import org.hisp.dhis.tracker.imports.domain.TrackerObjects; @@ -53,23 +54,26 @@ @ExtendWith(MockitoExtension.class) class DuplicateRelationshipSupplierTest extends TestBase { - private static final String REL_A_UID = "RELA"; + private static final UID REL_A_UID = UID.generate(); - private static final String REL_B_UID = "RELB"; + private static final UID REL_B_UID = UID.generate(); - private static final String REL_C_UID = "RELC"; + private static final UID REL_C_UID = UID.generate(); - private static final String TE_A_UID = "TE_A"; + private static final UID TE_A_UID = UID.generate(); - private static final String TE_B_UID = "TE_B"; + private static final UID TE_B_UID = UID.generate(); - private static final String TE_C_UID = "TE_C"; + private static final UID TE_C_UID = UID.generate(); - private static final String KEY_REL_A = "UNIRELTYPE_TE_A_TE_B"; + private static final String KEY_REL_A = + "UNIRELTYPE_" + TE_A_UID.getValue() + "_" + TE_B_UID.getValue(); - private static final String KEY_REL_B = "BIRELTYPE_TE_B_TE_C"; + private static final String KEY_REL_B = + "BIRELTYPE_" + TE_B_UID.getValue() + "_" + TE_C_UID.getValue(); - private static final String KEY_REL_C = "UNIRELTYPE_TE_C_TE_A"; + private static final String KEY_REL_C = + "UNIRELTYPE_" + TE_C_UID.getValue() + "_" + TE_A_UID.getValue(); private static final String UNIDIRECTIONAL_RELATIONSHIP_TYPE_UID = "UNIRELTYPE"; @@ -106,11 +110,11 @@ public void setUp() { OrganisationUnit organisationUnit = createOrganisationUnit('A'); teA = createTrackedEntity(organisationUnit); - teA.setUid(TE_A_UID); + teA.setUid(TE_A_UID.getValue()); teB = createTrackedEntity(organisationUnit); - teB.setUid(TE_B_UID); + teB.setUid(TE_B_UID.getValue()); teC = createTrackedEntity(organisationUnit); - teC.setUid(TE_C_UID); + teC.setUid(TE_C_UID.getValue()); relationshipA = org.hisp.dhis.tracker.imports.domain.Relationship.builder() diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/EventCategoryOptionComboSupplierTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/EventCategoryOptionComboSupplierTest.java index c87fdca0c2db..c6de0bef8de5 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/EventCategoryOptionComboSupplierTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/EventCategoryOptionComboSupplierTest.java @@ -47,11 +47,12 @@ import org.hisp.dhis.category.CategoryOptionCombo; import org.hisp.dhis.category.CategoryService; import org.hisp.dhis.common.DataDimensionType; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramStage; import org.hisp.dhis.test.TestBase; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.domain.Event; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; import org.hisp.dhis.tracker.imports.domain.TrackerObjects; @@ -92,6 +93,7 @@ void shouldPreheatEventAOCIfNotProvided() { Event event = Event.builder() + .event(UID.generate()) .program(MetadataIdentifier.ofUid(program)) .attributeOptionCombo(identifierParams.toMetadataIdentifier((CategoryOptionCombo) null)) .attributeCategoryOptions(categoryOptionIds(identifierParams, options)) @@ -137,6 +139,7 @@ void shouldPreheatEventAOCIfNotProvidedAndEventHasProgramStageButNoProgram() { Event event = Event.builder() + .event(UID.generate()) .programStage(identifierParams.toMetadataIdentifier(stage)) .attributeOptionCombo(identifierParams.toMetadataIdentifier((CategoryOptionCombo) null)) .attributeCategoryOptions(categoryOptionIds(identifierParams, options)) @@ -178,6 +181,7 @@ void shouldPreheatEventAOCIfNotProvidedOnlyIfNotAlreadyFetched() { Event event = Event.builder() + .event(UID.generate()) .program(MetadataIdentifier.ofUid(program)) .attributeOptionCombo(identifierParams.toMetadataIdentifier((CategoryOptionCombo) null)) .attributeCategoryOptions(categoryOptionIds(identifierParams, options)) @@ -223,6 +227,7 @@ void shouldPreheatEventAOCEvenIfNotFound() { Event event = Event.builder() + .event(UID.generate()) .program(MetadataIdentifier.ofUid(program)) .attributeOptionCombo(identifierParams.toMetadataIdentifier((CategoryOptionCombo) null)) .attributeCategoryOptions(categoryOptionIds(identifierParams, options)) @@ -266,6 +271,7 @@ void shouldNotPreheatEventAOCIfNotProvidedAndCONotFound() { Event event = Event.builder() + .event(UID.generate()) .program(MetadataIdentifier.ofUid(program)) .attributeOptionCombo(identifierParams.toMetadataIdentifier((CategoryOptionCombo) null)) .attributeCategoryOptions(categoryOptionIds(identifierParams, options)) @@ -299,6 +305,7 @@ void shouldNotPreheatEventAOCIfEventHasNoProgram() { Event event = Event.builder() + .event(UID.generate()) .attributeCategoryOptions(categoryOptionIds(identifierParams, options)) .attributeOptionCombo(identifierParams.toMetadataIdentifier(aoc)) .programStage(MetadataIdentifier.EMPTY_UID) @@ -331,6 +338,7 @@ void shouldNotPreheatEventAOCIfEventHasNoProgramAndNoProgramStage() { Event event = Event.builder() + .event(UID.generate()) .attributeOptionCombo(identifierParams.toMetadataIdentifier((CategoryOptionCombo) null)) .attributeCategoryOptions(categoryOptionIds(identifierParams, options)) .programStage(MetadataIdentifier.EMPTY_UID) @@ -371,6 +379,7 @@ void shouldNotPreheatEventAOCIfEventHasNoProgramAndItsProgramStageHasNoProgram() Event event = Event.builder() + .event(UID.generate()) .programStage(identifierParams.toMetadataIdentifier(stage)) .attributeOptionCombo(identifierParams.toMetadataIdentifier((CategoryOptionCombo) null)) .attributeCategoryOptions(categoryOptionIds(identifierParams, options)) @@ -409,6 +418,7 @@ void shouldNotPreheatEventAOCIfAOCAndCOsAreProvided() { Event event = Event.builder() + .event(UID.generate()) .program(MetadataIdentifier.ofUid(program.getUid())) .attributeCategoryOptions(categoryOptionIds(identifierParams, options)) .attributeOptionCombo(identifierParams.toMetadataIdentifier(aoc)) @@ -441,6 +451,7 @@ void shouldNotPreheatEventAOCIfAOCIsProvided() { Event event = Event.builder() + .event(UID.generate()) .program(MetadataIdentifier.ofUid(program)) .attributeOptionCombo(identifierParams.toMetadataIdentifier(aoc)) .build(); @@ -472,6 +483,7 @@ void shouldNotPreheatEventAOCIfNoCategoryOptionsProvided() { Event event = Event.builder() + .event(UID.generate()) .program(MetadataIdentifier.ofUid(program)) .attributeOptionCombo(identifierParams.toMetadataIdentifier((CategoryOptionCombo) null)) .build(); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/EventProgramEnrollmentSupplierTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/EventProgramEnrollmentSupplierTest.java index 0c9f68db14c0..ddd59aff2bca 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/EventProgramEnrollmentSupplierTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/EventProgramEnrollmentSupplierTest.java @@ -33,6 +33,7 @@ import jakarta.persistence.EntityManager; import java.util.List; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.Enrollment; import org.hisp.dhis.program.Program; import org.hisp.dhis.test.TestBase; @@ -112,6 +113,6 @@ void verifySupplierWhenNoProgramsArePresent() { private void assertEnrollmentInPreheat(Enrollment expected, Enrollment actual) { assertEquals(expected.getUid(), actual.getUid()); assertEquals(expected.getProgram().getUid(), actual.getProgram().getUid()); - assertEquals(actual, preheat.getEnrollment(actual.getUid())); + assertEquals(actual, preheat.getEnrollment(UID.of(actual))); } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/FileResourceSupplierTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/FileResourceSupplierTest.java index 6034a1c45c0a..4ad23503ef7b 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/FileResourceSupplierTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/FileResourceSupplierTest.java @@ -36,13 +36,14 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import org.hisp.dhis.common.UID; import org.hisp.dhis.common.ValueType; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.fileresource.FileResource; import org.hisp.dhis.fileresource.FileResourceService; import org.hisp.dhis.test.TestBase; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; import org.hisp.dhis.tracker.imports.domain.Attribute; import org.hisp.dhis.tracker.imports.domain.DataValue; import org.hisp.dhis.tracker.imports.domain.Enrollment; @@ -195,11 +196,17 @@ private Attribute fileAttribute(String uid, String value) { } private TrackedEntity trackedEntity(Attribute... attributes) { - return TrackedEntity.builder().attributes(attributes(attributes)).build(); + return TrackedEntity.builder() + .trackedEntity(UID.generate()) + .attributes(attributes(attributes)) + .build(); } private Enrollment enrollment(Attribute... attributes) { - return Enrollment.builder().attributes(attributes(attributes)).build(); + return Enrollment.builder() + .enrollment(UID.generate()) + .attributes(attributes(attributes)) + .build(); } private List attributes(Attribute[] attributes) { @@ -223,7 +230,7 @@ private DataElement fileDataElement(String uid) { } private Event event(DataValue... dataValues) { - return Event.builder().dataValues(dataValues(dataValues)).build(); + return Event.builder().event(UID.generate()).dataValues(dataValues(dataValues)).build(); } private Set dataValues(DataValue[] dataValues) { diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/OrgUnitValueTypeSupplierTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/OrgUnitValueTypeSupplierTest.java index 5f7746b8f2c7..39a0dd6106f4 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/OrgUnitValueTypeSupplierTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/OrgUnitValueTypeSupplierTest.java @@ -37,12 +37,13 @@ import java.util.List; import java.util.Set; import org.hisp.dhis.common.IdentifiableObjectManager; +import org.hisp.dhis.common.UID; import org.hisp.dhis.common.ValueType; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.test.TestBase; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; import org.hisp.dhis.tracker.imports.domain.Attribute; import org.hisp.dhis.tracker.imports.domain.DataValue; import org.hisp.dhis.tracker.imports.domain.Enrollment; @@ -191,11 +192,17 @@ private Attribute orgUnitAttribute(String uid, String value) { } private TrackedEntity trackedEntity(Attribute... attributes) { - return TrackedEntity.builder().attributes(attributes(attributes)).build(); + return TrackedEntity.builder() + .trackedEntity(UID.generate()) + .attributes(attributes(attributes)) + .build(); } private Enrollment enrollment(Attribute... attributes) { - return Enrollment.builder().attributes(attributes(attributes)).build(); + return Enrollment.builder() + .enrollment(UID.generate()) + .attributes(attributes(attributes)) + .build(); } private List attributes(Attribute[] attributes) { @@ -219,7 +226,7 @@ private DataElement orgUnitDataElement(String uid) { } private Event event(DataValue... dataValues) { - return Event.builder().dataValues(dataValues(dataValues)).build(); + return Event.builder().event(UID.generate()).dataValues(dataValues(dataValues)).build(); } private Set dataValues(DataValue[] dataValues) { diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/PeriodTypeSupplierTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/PeriodTypeSupplierTest.java deleted file mode 100644 index 57beffeeb836..000000000000 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/PeriodTypeSupplierTest.java +++ /dev/null @@ -1,79 +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.imports.preheat.supplier; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasSize; -import static org.mockito.Mockito.when; - -import java.util.List; -import org.hisp.dhis.period.Period; -import org.hisp.dhis.period.PeriodStore; -import org.hisp.dhis.test.random.BeanRandomizer; -import org.hisp.dhis.tracker.imports.domain.TrackerObjects; -import org.hisp.dhis.tracker.imports.preheat.TrackerPreheat; -import org.hisp.dhis.tracker.imports.preheat.cache.DefaultPreheatCacheService; -import org.hisp.dhis.tracker.imports.preheat.cache.PreheatCacheService; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; - -/** - * @author Luciano Fiandesio - */ -@MockitoSettings(strictness = Strictness.LENIENT) -@ExtendWith(MockitoExtension.class) -class PeriodTypeSupplierTest { - - private final BeanRandomizer rnd = BeanRandomizer.create(); - private PeriodTypeSupplier supplier; - @Mock private PeriodStore periodStore; - - @BeforeEach - public void setUp() { - final PreheatCacheService cache = new DefaultPreheatCacheService(); - supplier = new PeriodTypeSupplier(periodStore, cache); - } - - @Test - void verifySupplier() { - final List periods = rnd.objects(Period.class, 20).toList(); - when(periodStore.getAll()).thenReturn(periods); - - final TrackerObjects params = TrackerObjects.builder().build(); - - TrackerPreheat preheat = new TrackerPreheat(); - this.supplier.preheatAdd(params, preheat); - - assertThat(preheat.getPeriodMap().values(), hasSize(20)); - } -} diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/UniqueAttributeSupplierTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/UniqueAttributeSupplierTest.java index 085ac6844634..d32c9d24b344 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/UniqueAttributeSupplierTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/UniqueAttributeSupplierTest.java @@ -29,7 +29,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.when; import com.google.common.collect.Lists; @@ -38,7 +38,7 @@ import java.util.Map; import org.hisp.dhis.attribute.Attribute; import org.hisp.dhis.attribute.AttributeValues; -import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.common.ValueType; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.program.Enrollment; @@ -67,9 +67,9 @@ class UniqueAttributeSupplierTest extends TestBase { private static final String UNIQUE_VALUE = "unique value"; - private static final String TE_UID = CodeGenerator.generateUid(); + private static final UID TE_UID = UID.generate(); - private static final String ANOTHER_TE_UID = CodeGenerator.generateUid(); + private static final UID ANOTHER_TE_UID = UID.generate(); @InjectMocks private UniqueAttributesSupplier supplier; @@ -98,7 +98,7 @@ public void setUp() { Program program = createProgram('A'); Attribute attribute = createAttribute('A'); trackedEntity = createTrackedEntity('A', orgUnit); - trackedEntity.setUid(TE_UID); + trackedEntity.setUid(TE_UID.getValue()); trackedEntity.setAttributeValues(AttributeValues.of(Map.of(attribute.getUid(), UNIQUE_VALUE))); enrollment = createEnrollment(program, trackedEntity, orgUnit); enrollment.setAttributeValues(AttributeValues.of(Map.of(attribute.getUid(), UNIQUE_VALUE))); @@ -196,7 +196,7 @@ void verifySupplierWhenTeinPayloadAndAnotherTeInDBHaveTheSameUniqueAttribute() { this.supplier.preheatAdd(importParams, preheat); assertThat(preheat.getUniqueAttributeValues(), hasSize(1)); - assertThat(preheat.getUniqueAttributeValues().get(0).getTeUid(), is(TE_UID)); + assertEquals(TE_UID, preheat.getUniqueAttributeValues().get(0).getTe()); } private List @@ -225,10 +225,10 @@ private org.hisp.dhis.tracker.imports.domain.TrackedEntity anotherTrackedEntity( .build(); } - private org.hisp.dhis.tracker.imports.domain.Enrollment enrollment(String teUid) { + private org.hisp.dhis.tracker.imports.domain.Enrollment enrollment(UID teUid) { return org.hisp.dhis.tracker.imports.domain.Enrollment.builder() .trackedEntity(teUid) - .enrollment("ENROLLMENT") + .enrollment(UID.generate()) .attributes(Collections.singletonList(uniqueAttribute())) .build(); } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/UserSupplierTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/UserSupplierTest.java index c82eaa23c82a..99464c99c95e 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/UserSupplierTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/UserSupplierTest.java @@ -39,6 +39,7 @@ import java.util.stream.Collectors; import org.hisp.dhis.common.CodeGenerator; import org.hisp.dhis.common.IdentifiableObjectManager; +import org.hisp.dhis.common.UID; import org.hisp.dhis.test.TestBase; import org.hisp.dhis.tracker.imports.domain.Event; import org.hisp.dhis.tracker.imports.domain.TrackerObjects; @@ -124,7 +125,7 @@ private org.hisp.dhis.user.User map(User assignedUser) { } private Event event(User user) { - return Event.builder().event(CodeGenerator.generateUid()).assignedUser(user).build(); + return Event.builder().event(UID.generate()).assignedUser(user).build(); } private User user() { diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/strategy/AbstractSchemaStrategyCachingTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/strategy/AbstractSchemaStrategyCachingTest.java index 6afb14e6dd7c..faa8b740378b 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/strategy/AbstractSchemaStrategyCachingTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preheat/supplier/strategy/AbstractSchemaStrategyCachingTest.java @@ -49,7 +49,7 @@ import org.hisp.dhis.schema.descriptors.ProgramSchemaDescriptor; import org.hisp.dhis.test.TestBase; import org.hisp.dhis.test.random.BeanRandomizer; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; import org.hisp.dhis.tracker.imports.preheat.TrackerPreheat; import org.hisp.dhis.tracker.imports.preheat.cache.PreheatCacheService; import org.hisp.dhis.tracker.imports.preheat.mappers.CopyMapper; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preprocess/DuplicateRelationshipsPreProcessorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preprocess/DuplicateRelationshipsPreProcessorTest.java index d322421251a2..c620b4a230fc 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preprocess/DuplicateRelationshipsPreProcessorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preprocess/DuplicateRelationshipsPreProcessorTest.java @@ -33,8 +33,9 @@ import com.google.common.collect.Lists; import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.relationship.RelationshipType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; import org.hisp.dhis.tracker.imports.domain.Relationship; @@ -72,18 +73,18 @@ void setUp() { @Test void test_relationshipIsIgnored_on_null_relType() { String relType = CodeGenerator.generateUid(); - String fromTeUid = CodeGenerator.generateUid(); - String toTeUid = CodeGenerator.generateUid(); + UID fromTeUid = UID.generate(); + UID toTeUid = UID.generate(); Relationship relationship1 = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .relationshipType(MetadataIdentifier.ofUid(relType)) .from(trackedEntityRelationshipItem(fromTeUid)) .to(trackedEntityRelationshipItem(toTeUid)) .build(); Relationship relationship2 = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .relationshipType(MetadataIdentifier.ofUid(relType)) .from(trackedEntityRelationshipItem(fromTeUid)) .to(trackedEntityRelationshipItem(toTeUid)) @@ -107,9 +108,9 @@ void test_relationshipIsIgnored_on_null_relType() { @Test void shouldRemoveRelationshipFromBundleWhenThereAreTwoIdenticalRelationships() { String relType = REL_TYPE_NONBIDIRECTIONAL_UID; - String fromTeUid = CodeGenerator.generateUid(); - String toTeUid = CodeGenerator.generateUid(); - String relUid = CodeGenerator.generateUid(); + UID fromTeUid = UID.generate(); + UID toTeUid = UID.generate(); + UID relUid = UID.generate(); Relationship relationship1 = Relationship.builder() @@ -137,9 +138,9 @@ void shouldRemoveRelationshipFromBundleWhenThereAreTwoIdenticalRelationships() { @Test void shouldRemoveRelationshipFromBundleWhenThereAreTwoIdenticalRelationshipsWithDifferentUids() { String relType = REL_TYPE_NONBIDIRECTIONAL_UID; - String fromTeUid = CodeGenerator.generateUid(); - String toTeUid = CodeGenerator.generateUid(); - String relationship1Uid = CodeGenerator.generateUid(); + UID fromTeUid = UID.generate(); + UID toTeUid = UID.generate(); + UID relationship1Uid = UID.generate(); Relationship relationship1 = Relationship.builder() .relationship(relationship1Uid) @@ -149,7 +150,7 @@ void shouldRemoveRelationshipFromBundleWhenThereAreTwoIdenticalRelationshipsWith .build(); Relationship relationship2 = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .relationshipType(MetadataIdentifier.ofUid(relType)) .from(trackedEntityRelationshipItem(fromTeUid)) .to(trackedEntityRelationshipItem(toTeUid)) @@ -173,18 +174,18 @@ void shouldRemoveRelationshipFromBundleWhenThereAreTwoIdenticalRelationshipsWith */ @Test void test_on_different_rels_none_is_removed() { - String fromTeUid = CodeGenerator.generateUid(); - String toTeUid = CodeGenerator.generateUid(); + UID fromTeUid = UID.generate(); + UID toTeUid = UID.generate(); Relationship relationship1 = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .relationshipType(MetadataIdentifier.ofUid(REL_TYPE_NONBIDIRECTIONAL_UID)) .from(trackedEntityRelationshipItem(fromTeUid)) .to(trackedEntityRelationshipItem(toTeUid)) .build(); Relationship relationship2 = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .relationshipType(MetadataIdentifier.ofUid(REL_TYPE_NONBIDIRECTIONAL_UID)) .from(trackedEntityRelationshipItem(fromTeUid)) .to(enrollmentRelationshipItem(toTeUid)) @@ -208,18 +209,18 @@ void test_on_different_rels_none_is_removed() { @Test void test_on_identical_but_inverted_rels_none_is_removed() { String relType = REL_TYPE_NONBIDIRECTIONAL_UID; - String fromTeUid = CodeGenerator.generateUid(); - String toTeUid = CodeGenerator.generateUid(); + UID fromTeUid = UID.generate(); + UID toTeUid = UID.generate(); Relationship relationship1 = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .relationshipType(MetadataIdentifier.ofUid(relType)) .from(trackedEntityRelationshipItem(fromTeUid)) .to(trackedEntityRelationshipItem(toTeUid)) .build(); Relationship relationship2 = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .relationshipType(MetadataIdentifier.ofUid(relType)) .from(trackedEntityRelationshipItem(toTeUid)) .to(trackedEntityRelationshipItem(fromTeUid)) @@ -245,18 +246,18 @@ void test_on_identical_but_inverted_rels_none_is_removed() { @Test void test_on_identical_rels_but_inverted_type_bi_1_is_removed() { String relType = REL_TYPE_BIDIRECTIONAL_UID; - String fromTeUid = CodeGenerator.generateUid(); - String toTeUid = CodeGenerator.generateUid(); + UID fromTeUid = UID.generate(); + UID toTeUid = UID.generate(); Relationship relationship1 = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .relationshipType(MetadataIdentifier.ofUid(relType)) .from(trackedEntityRelationshipItem(fromTeUid)) .to(trackedEntityRelationshipItem(toTeUid)) .build(); Relationship relationship2 = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .relationshipType(MetadataIdentifier.ofUid(relType)) .from(trackedEntityRelationshipItem(toTeUid)) .to(trackedEntityRelationshipItem(fromTeUid)) @@ -282,18 +283,18 @@ void test_on_identical_rels_but_inverted_type_bi_1_is_removed() { @Test void test_on_identical_rels_relType_bi_1_is_removed() { String relType = REL_TYPE_BIDIRECTIONAL_UID; - String fromTeUid = CodeGenerator.generateUid(); - String toTeUid = CodeGenerator.generateUid(); + UID fromTeUid = UID.generate(); + UID toTeUid = UID.generate(); Relationship relationship1 = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .relationshipType(MetadataIdentifier.ofUid(relType)) .from(trackedEntityRelationshipItem(fromTeUid)) .to(trackedEntityRelationshipItem(toTeUid)) .build(); Relationship relationship2 = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .relationshipType(MetadataIdentifier.ofUid(relType)) .from(trackedEntityRelationshipItem(fromTeUid)) .to(trackedEntityRelationshipItem(toTeUid)) @@ -307,11 +308,11 @@ void test_on_identical_rels_relType_bi_1_is_removed() { assertThat(bundle.getRelationships(), hasSize(1)); } - private RelationshipItem trackedEntityRelationshipItem(String trackedEntityUid) { + private RelationshipItem trackedEntityRelationshipItem(UID trackedEntityUid) { return RelationshipItem.builder().trackedEntity(trackedEntityUid).build(); } - private RelationshipItem enrollmentRelationshipItem(String enrollmentUid) { + private RelationshipItem enrollmentRelationshipItem(UID enrollmentUid) { return RelationshipItem.builder().enrollment(enrollmentUid).build(); } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preprocess/EventProgramPreProcessorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preprocess/EventProgramPreProcessorTest.java index 7f365c5dc66e..e513447afc10 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preprocess/EventProgramPreProcessorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preprocess/EventProgramPreProcessorTest.java @@ -44,11 +44,12 @@ import java.util.Set; import org.hisp.dhis.category.CategoryCombo; import org.hisp.dhis.category.CategoryOptionCombo; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramStage; import org.hisp.dhis.program.ProgramType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Event; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -152,6 +153,7 @@ void testProgramStageHasNoReferenceToProgram() { Event event = Event.builder() + .event(UID.generate()) .program(MetadataIdentifier.EMPTY_UID) .programStage(MetadataIdentifier.ofUid(programStage)) .attributeOptionCombo(MetadataIdentifier.EMPTY_UID) @@ -459,6 +461,7 @@ private Program programWithoutRegistration() { private Event invalidProgramEventWithNoProgramAndNoProgramStage() { return Event.builder() + .event(UID.generate()) .program(null) .programStage(null) .attributeOptionCombo(MetadataIdentifier.EMPTY_UID) @@ -467,6 +470,7 @@ private Event invalidProgramEventWithNoProgramAndNoProgramStage() { private Event programEventWithProgram() { return Event.builder() + .event(UID.generate()) .program(MetadataIdentifier.ofUid(PROGRAM_WITHOUT_REGISTRATION)) .programStage(MetadataIdentifier.EMPTY_UID) .attributeOptionCombo(MetadataIdentifier.EMPTY_UID) @@ -475,6 +479,7 @@ private Event programEventWithProgram() { private Event programEventWithProgramStage() { return Event.builder() + .event(UID.generate()) .program(MetadataIdentifier.EMPTY_UID) .programStage(MetadataIdentifier.ofUid(PROGRAM_STAGE_WITHOUT_REGISTRATION)) .attributeOptionCombo(MetadataIdentifier.EMPTY_UID) @@ -483,6 +488,7 @@ private Event programEventWithProgramStage() { private Event completeProgramEvent() { return Event.builder() + .event(UID.generate()) .programStage(MetadataIdentifier.ofUid(PROGRAM_STAGE_WITHOUT_REGISTRATION)) .program(MetadataIdentifier.ofUid(PROGRAM_WITHOUT_REGISTRATION)) .attributeOptionCombo(MetadataIdentifier.EMPTY_UID) @@ -491,6 +497,7 @@ private Event completeProgramEvent() { private Event trackerEventWithProgramStage() { return Event.builder() + .event(UID.generate()) .program(MetadataIdentifier.EMPTY_UID) .programStage(MetadataIdentifier.ofUid(PROGRAM_STAGE_WITH_REGISTRATION)) .attributeOptionCombo(MetadataIdentifier.EMPTY_UID) @@ -499,6 +506,7 @@ private Event trackerEventWithProgramStage() { private Event trackerEventWithProgram() { return Event.builder() + .event(UID.generate()) .program(MetadataIdentifier.ofUid(PROGRAM_WITH_REGISTRATION)) .programStage(MetadataIdentifier.EMPTY_UID) .attributeOptionCombo(MetadataIdentifier.EMPTY_UID) @@ -507,6 +515,7 @@ private Event trackerEventWithProgram() { private Event completeTrackerEvent() { return Event.builder() + .event(UID.generate()) .programStage(MetadataIdentifier.ofUid(PROGRAM_STAGE_WITH_REGISTRATION)) .program(MetadataIdentifier.ofUid(PROGRAM_WITH_REGISTRATION)) .attributeOptionCombo(MetadataIdentifier.EMPTY_UID) diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preprocess/EventWithoutRegistrationPreProcessorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preprocess/EventWithoutRegistrationPreProcessorTest.java index ff04aa98328d..fe215efd9568 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preprocess/EventWithoutRegistrationPreProcessorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preprocess/EventWithoutRegistrationPreProcessorTest.java @@ -31,6 +31,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import java.util.Collections; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.Enrollment; import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramStage; @@ -60,7 +61,8 @@ void testEnrollmentIsAddedIntoEventWhenItBelongsToProgramWithoutRegistration() { event.setProgramStage(MetadataIdentifier.ofUid("programStageUid")); TrackerBundle bundle = TrackerBundle.builder().events(Collections.singletonList(event)).build(); Enrollment enrollment = new Enrollment(); - enrollment.setUid("enrollmentUid"); + UID enrollmentUid = UID.generate(); + enrollment.setUid(enrollmentUid.getValue()); Program program = new Program(); program.setUid("programUid"); ProgramStage programStage = new ProgramStage(); @@ -73,7 +75,7 @@ void testEnrollmentIsAddedIntoEventWhenItBelongsToProgramWithoutRegistration() { // When preProcessorToTest.process(bundle); // Then - assertEquals("enrollmentUid", bundle.getEvents().get(0).getEnrollment()); + assertEquals(enrollmentUid, bundle.getEvents().get(0).getEnrollment()); } @Test diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preprocess/StrategyPreProcessorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preprocess/StrategyPreProcessorTest.java index c237e3e5ea19..3c8e052cb047 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preprocess/StrategyPreProcessorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/preprocess/StrategyPreProcessorTest.java @@ -27,10 +27,17 @@ */ package org.hisp.dhis.tracker.imports.preprocess; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.hisp.dhis.tracker.TrackerType.ENROLLMENT; +import static org.hisp.dhis.tracker.TrackerType.EVENT; +import static org.hisp.dhis.tracker.TrackerType.RELATIONSHIP; +import static org.hisp.dhis.tracker.TrackerType.TRACKED_ENTITY; +import static org.hisp.dhis.tracker.imports.TrackerImportStrategy.CREATE; +import static org.hisp.dhis.tracker.imports.TrackerImportStrategy.DELETE; +import static org.hisp.dhis.tracker.imports.TrackerImportStrategy.UPDATE; +import static org.junit.jupiter.api.Assertions.assertEquals; import com.google.common.collect.Lists; -import org.hamcrest.Matchers; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.Enrollment; import org.hisp.dhis.program.Event; import org.hisp.dhis.relationship.Relationship; @@ -50,21 +57,21 @@ @ExtendWith(MockitoExtension.class) class StrategyPreProcessorTest extends TestBase { - private static final String TE_UID = "TeiUid"; + private static final UID TE_UID = UID.generate(); - private static final String NEW_TE_UID = "NewTeiUid"; + private static final UID NEW_TE_UID = UID.generate(); - private static final String ENROLLMENT_UID = "EnrollmentUid"; + private static final UID ENROLLMENT_UID = UID.generate(); - private static final String NEW_ENROLLMENT_UID = "NewEnrollmentUid"; + private static final UID NEW_ENROLLMENT_UID = UID.generate(); - private static final String EVENT_UID = "EventUid"; + private static final UID EVENT_UID = UID.generate(); - private static final String NEW_EVENT_UID = "NewEventUid"; + private static final UID NEW_EVENT_UID = UID.generate(); - private static final String RELATIONSHIP_UID = "RelationshipUid"; + private static final UID RELATIONSHIP_UID = UID.generate(); - private static final String NEW_RELATIONSHIP_UID = "NewRelationshipUid"; + private static final UID NEW_RELATIONSHIP_UID = UID.generate(); private Event dbEvent; @@ -97,25 +104,25 @@ class StrategyPreProcessorTest extends TestBase { @BeforeEach void setUp() { te = new TrackedEntity(); - te.setUid(TE_UID); + te.setUid(TE_UID.getValue()); trackedEntity = new org.hisp.dhis.tracker.imports.domain.TrackedEntity(); trackedEntity.setTrackedEntity(TE_UID); newTrackedEntity = new org.hisp.dhis.tracker.imports.domain.TrackedEntity(); newTrackedEntity.setTrackedEntity(NEW_TE_UID); preheatEnrollment = new Enrollment(); - preheatEnrollment.setUid(ENROLLMENT_UID); + preheatEnrollment.setUid(ENROLLMENT_UID.getValue()); enrollment = new org.hisp.dhis.tracker.imports.domain.Enrollment(); enrollment.setEnrollment(ENROLLMENT_UID); newEnrollment = new org.hisp.dhis.tracker.imports.domain.Enrollment(); newEnrollment.setEnrollment(NEW_ENROLLMENT_UID); dbEvent = new Event(); - dbEvent.setUid(EVENT_UID); + dbEvent.setUid(EVENT_UID.getValue()); event = new org.hisp.dhis.tracker.imports.domain.Event(); event.setEvent(EVENT_UID); newEvent = new org.hisp.dhis.tracker.imports.domain.Event(); newEvent.setEvent(NEW_EVENT_UID); relationship = new Relationship(); - relationship.setUid(RELATIONSHIP_UID); + relationship.setUid(RELATIONSHIP_UID.getValue()); payloadRelationship = new org.hisp.dhis.tracker.imports.domain.Relationship(); payloadRelationship.setRelationship(RELATIONSHIP_UID); newPayloadRelationship = new org.hisp.dhis.tracker.imports.domain.Relationship(); @@ -138,30 +145,15 @@ void testStrategyPreprocessForCreateAndUpdate() { .preheat(preheat) .build(); preProcessorToTest.process(bundle); - assertThat( - bundle.getResolvedStrategyMap().get(TrackerType.TRACKED_ENTITY).get(TE_UID), - Matchers.is(TrackerImportStrategy.UPDATE)); - assertThat( - bundle.getResolvedStrategyMap().get(TrackerType.TRACKED_ENTITY).get(NEW_TE_UID), - Matchers.is(TrackerImportStrategy.CREATE)); - assertThat( - bundle.getResolvedStrategyMap().get(TrackerType.ENROLLMENT).get(ENROLLMENT_UID), - Matchers.is(TrackerImportStrategy.UPDATE)); - assertThat( - bundle.getResolvedStrategyMap().get(TrackerType.ENROLLMENT).get(NEW_ENROLLMENT_UID), - Matchers.is(TrackerImportStrategy.CREATE)); - assertThat( - bundle.getResolvedStrategyMap().get(TrackerType.EVENT).get(EVENT_UID), - Matchers.is(TrackerImportStrategy.UPDATE)); - assertThat( - bundle.getResolvedStrategyMap().get(TrackerType.EVENT).get(NEW_EVENT_UID), - Matchers.is(TrackerImportStrategy.CREATE)); - assertThat( - bundle.getResolvedStrategyMap().get(TrackerType.RELATIONSHIP).get(RELATIONSHIP_UID), - Matchers.is(TrackerImportStrategy.UPDATE)); - assertThat( - bundle.getResolvedStrategyMap().get(TrackerType.RELATIONSHIP).get(NEW_RELATIONSHIP_UID), - Matchers.is(TrackerImportStrategy.CREATE)); + + assertEquals(UPDATE, getStrategy(bundle, TRACKED_ENTITY, TE_UID)); + assertEquals(CREATE, getStrategy(bundle, TRACKED_ENTITY, NEW_TE_UID)); + assertEquals(UPDATE, getStrategy(bundle, ENROLLMENT, ENROLLMENT_UID)); + assertEquals(CREATE, getStrategy(bundle, ENROLLMENT, NEW_ENROLLMENT_UID)); + assertEquals(UPDATE, getStrategy(bundle, EVENT, EVENT_UID)); + assertEquals(CREATE, getStrategy(bundle, EVENT, NEW_EVENT_UID)); + assertEquals(UPDATE, getStrategy(bundle, RELATIONSHIP, RELATIONSHIP_UID)); + assertEquals(CREATE, getStrategy(bundle, RELATIONSHIP, NEW_RELATIONSHIP_UID)); } @Test @@ -172,33 +164,23 @@ void testStrategyPreprocessForDelete() { .enrollments(Lists.newArrayList(enrollment, newEnrollment)) .events(Lists.newArrayList(event, newEvent)) .relationships(Lists.newArrayList(payloadRelationship, newPayloadRelationship)) - .importStrategy(TrackerImportStrategy.DELETE) + .importStrategy(DELETE) .preheat(preheat) .build(); preProcessorToTest.process(bundle); - assertThat( - bundle.getResolvedStrategyMap().get(TrackerType.TRACKED_ENTITY).get(TE_UID), - Matchers.is(TrackerImportStrategy.DELETE)); - assertThat( - bundle.getResolvedStrategyMap().get(TrackerType.TRACKED_ENTITY).get(NEW_TE_UID), - Matchers.is(TrackerImportStrategy.DELETE)); - assertThat( - bundle.getResolvedStrategyMap().get(TrackerType.ENROLLMENT).get(ENROLLMENT_UID), - Matchers.is(TrackerImportStrategy.DELETE)); - assertThat( - bundle.getResolvedStrategyMap().get(TrackerType.ENROLLMENT).get(NEW_ENROLLMENT_UID), - Matchers.is(TrackerImportStrategy.DELETE)); - assertThat( - bundle.getResolvedStrategyMap().get(TrackerType.EVENT).get(EVENT_UID), - Matchers.is(TrackerImportStrategy.DELETE)); - assertThat( - bundle.getResolvedStrategyMap().get(TrackerType.EVENT).get(NEW_EVENT_UID), - Matchers.is(TrackerImportStrategy.DELETE)); - assertThat( - bundle.getResolvedStrategyMap().get(TrackerType.RELATIONSHIP).get(RELATIONSHIP_UID), - Matchers.is(TrackerImportStrategy.DELETE)); - assertThat( - bundle.getResolvedStrategyMap().get(TrackerType.RELATIONSHIP).get(NEW_RELATIONSHIP_UID), - Matchers.is(TrackerImportStrategy.DELETE)); + + assertEquals(DELETE, getStrategy(bundle, TRACKED_ENTITY, TE_UID)); + assertEquals(DELETE, getStrategy(bundle, TRACKED_ENTITY, NEW_TE_UID)); + assertEquals(DELETE, getStrategy(bundle, ENROLLMENT, ENROLLMENT_UID)); + assertEquals(DELETE, getStrategy(bundle, ENROLLMENT, NEW_ENROLLMENT_UID)); + assertEquals(DELETE, getStrategy(bundle, EVENT, EVENT_UID)); + assertEquals(DELETE, getStrategy(bundle, EVENT, NEW_EVENT_UID)); + assertEquals(DELETE, getStrategy(bundle, RELATIONSHIP, RELATIONSHIP_UID)); + assertEquals(DELETE, getStrategy(bundle, RELATIONSHIP, NEW_RELATIONSHIP_UID)); + } + + private TrackerImportStrategy getStrategy( + TrackerBundle bundle, TrackerType trackerType, UID uid) { + return bundle.getResolvedStrategyMap().get(trackerType).get(uid); } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/RuleEngineMapperTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/RuleEngineMapperTest.java index 19468a6ab289..8bd0e5a0b4b0 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/RuleEngineMapperTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/RuleEngineMapperTest.java @@ -39,6 +39,7 @@ import kotlinx.datetime.Instant; import kotlinx.datetime.LocalDate; import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.common.ValueType; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.eventdatavalue.EventDataValue; @@ -55,7 +56,7 @@ import org.hisp.dhis.test.TestBase; import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; import org.hisp.dhis.tracker.imports.domain.Attribute; import org.hisp.dhis.tracker.imports.domain.DataValue; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -130,10 +131,16 @@ void shouldMapPayloadEventsToRuleEvents() { assertEquals(2, ruleEvents.size()); assertEvent( eventA, - ruleEvents.stream().filter(e -> e.getEvent().equals(eventA.getUid())).findFirst().get()); + ruleEvents.stream() + .filter(e -> e.getEvent().equals(eventA.getUid().getValue())) + .findFirst() + .get()); assertEvent( eventB, - ruleEvents.stream().filter(e -> e.getEvent().equals(eventB.getUid())).findFirst().get()); + ruleEvents.stream() + .filter(e -> e.getEvent().equals(eventB.getUid().getValue())) + .findFirst() + .get()); } @Test @@ -249,7 +256,7 @@ private void assertEvent(Event event, RuleEvent ruleEvent) { } private void assertEvent(org.hisp.dhis.tracker.imports.domain.Event event, RuleEvent ruleEvent) { - assertEquals(event.getUid(), ruleEvent.getEvent()); + assertEquals(event.getUid().getValue(), ruleEvent.getEvent()); assertEquals(event.getProgramStage().getIdentifier(), ruleEvent.getProgramStage()); assertNotNull(ruleEvent.getProgramStageName()); assertEquals(event.getStatus().name(), ruleEvent.getStatus().name()); @@ -282,7 +289,7 @@ private void assertDataValue(DataValue expectedDataValue, RuleDataValue actualDa private void assertEnrollment( org.hisp.dhis.tracker.imports.domain.Enrollment enrollment, RuleEnrollment ruleEnrollment) { - assertEquals(enrollment.getUid(), ruleEnrollment.getEnrollment()); + assertEquals(enrollment.getUid().getValue(), ruleEnrollment.getEnrollment()); assertNotNull(ruleEnrollment.getProgramName()); assertDates(enrollment.getOccurredAt(), ruleEnrollment.getIncidentDate()); assertDates(enrollment.getEnrolledAt(), ruleEnrollment.getEnrollmentDate()); @@ -330,8 +337,8 @@ private org.hisp.dhis.tracker.imports.domain.Enrollment payloadEnrollment() { .enrolledAt(NOW.toInstant()) .occurredAt(TOMORROW.toInstant()) .program(MetadataIdentifier.ofUid(program)) - .trackedEntity(CodeGenerator.generateUid()) - .enrollment(CodeGenerator.generateUid()) + .trackedEntity(UID.generate()) + .enrollment(UID.generate()) .build(); } @@ -364,8 +371,8 @@ private EventDataValue eventDataValue(String dataElementUid, String value) { private org.hisp.dhis.tracker.imports.domain.Event payloadEvent() { return org.hisp.dhis.tracker.imports.domain.Event.builder() - .enrollment(CodeGenerator.generateUid()) - .event(CodeGenerator.generateUid()) + .enrollment(UID.generate()) + .event(UID.generate()) .programStage(MetadataIdentifier.ofUid(programStage.getUid())) .orgUnit(MetadataIdentifier.ofUid(organisationUnit.getUid())) .scheduledAt(YESTERDAY.toInstant()) diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/AssignAttributeExecutorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/AssignAttributeExecutorTest.java index 0499ab6fa8e6..2f97ac8814aa 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/AssignAttributeExecutorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/AssignAttributeExecutorTest.java @@ -43,8 +43,8 @@ import org.hisp.dhis.setting.SystemSettingsProvider; import org.hisp.dhis.test.TestBase; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Attribute; import org.hisp.dhis.tracker.imports.domain.Enrollment; @@ -65,11 +65,11 @@ @ExtendWith(MockitoExtension.class) class AssignAttributeExecutorTest extends TestBase { - private static final String TRACKED_ENTITY_ID = "TrackedEntityUid"; + private static final UID TRACKED_ENTITY_ID = UID.generate(); - private static final String FIRST_ENROLLMENT_ID = "ActiveEnrollmentUid"; + private static final UID FIRST_ENROLLMENT_ID = UID.generate(); - private static final String SECOND_ENROLLMENT_ID = "CompletedEnrollmentUid"; + private static final UID SECOND_ENROLLMENT_ID = UID.generate(); private static final UID ATTRIBUTE_UID = UID.of("h4w96yEMlzO"); @@ -281,7 +281,7 @@ void shouldAssignAttributeValueForEnrollmentsWhenAttributeIsAlreadyPresentAndHas } private Optional findTeiAttributeByUid( - TrackerBundle bundle, String teUid, UID attributeUid) { + TrackerBundle bundle, UID teUid, UID attributeUid) { TrackedEntity te = bundle.findTrackedEntityByUid(teUid).get(); return te.getAttributes().stream() .filter(at -> at.getAttribute().equals(MetadataIdentifier.ofUid(attributeUid.getValue()))) @@ -289,7 +289,7 @@ private Optional findTeiAttributeByUid( } private Optional findAttributeByUid( - TrackerBundle bundle, String enrollmentUid, UID attributeUid) { + TrackerBundle bundle, UID enrollmentUid, UID attributeUid) { Enrollment enrollment = bundle.findEnrollmentByUid(enrollmentUid).get(); return enrollment.getAttributes().stream() .filter(at -> at.getAttribute().equals(MetadataIdentifier.ofUid(attributeUid.getValue()))) @@ -297,7 +297,7 @@ private Optional findAttributeByUid( } private Optional findAttributeByCode( - TrackerBundle bundle, String enrollmentUid, String attributeCode) { + TrackerBundle bundle, UID enrollmentUid, String attributeCode) { Enrollment enrollment = bundle.findEnrollmentByUid(enrollmentUid).get(); return enrollment.getAttributes().stream() .filter(at -> at.getAttribute().equals(MetadataIdentifier.ofCode(attributeCode))) @@ -328,6 +328,7 @@ private void assertAttributeWasAssignedAndValidationIsPresent( private Enrollment getEnrollmentWithAttributeSet() { return Enrollment.builder() .enrollment(FIRST_ENROLLMENT_ID) + .trackedEntity(UID.generate()) .status(EnrollmentStatus.ACTIVE) .attributes(getAttributes()) .build(); @@ -344,6 +345,7 @@ private Enrollment getEnrollmentWithAttributeSet(TrackerIdSchemeParams idSchemes private Enrollment getEnrollmentWithAttributeSetSameValue() { return Enrollment.builder() .enrollment(FIRST_ENROLLMENT_ID) + .trackedEntity(UID.generate()) .status(EnrollmentStatus.ACTIVE) .attributes(getAttributesSameValue()) .build(); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/RuleEngineErrorExecutorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/RuleEngineErrorExecutorTest.java index 1d9a9d116757..cd642e14282b 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/RuleEngineErrorExecutorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/RuleEngineErrorExecutorTest.java @@ -51,9 +51,9 @@ class RuleEngineErrorExecutorTest extends TestBase { private static final String ENROLLMENT_ERROR_MESSAGE = "Enrollment error message"; - private static final String ENROLLMENT_ID = "EnrollmentUid"; + private static final UID ENROLLMENT_ID = UID.generate(); - private static final String TE_ID = "TeiId"; + private static final UID TE_ID = UID.generate(); private final RuleEngineErrorExecutor executor = new RuleEngineErrorExecutor(RULE_UID, ENROLLMENT_ERROR_MESSAGE); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/SetMandatoryFieldExecutorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/SetMandatoryFieldExecutorTest.java index abc67267974c..ed48fd81990a 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/SetMandatoryFieldExecutorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/SetMandatoryFieldExecutorTest.java @@ -48,8 +48,8 @@ import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; import org.hisp.dhis.trackedentityattributevalue.TrackedEntityAttributeValue; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Attribute; @@ -67,9 +67,9 @@ class SetMandatoryFieldExecutorTest extends TestBase { private static final UID RULE_UID = UID.of("TvctPPhpD8u"); - private static final String ACTIVE_ENROLLMENT_ID = "ActiveEnrollmentUid"; + private static final UID ACTIVE_ENROLLMENT_ID = UID.generate(); - private static final String COMPLETED_ENROLLMENT_ID = "CompletedEnrollmentUid"; + private static final UID COMPLETED_ENROLLMENT_ID = UID.generate(); private static final String DATA_ELEMENT_ID = "DataElementId"; @@ -77,7 +77,7 @@ class SetMandatoryFieldExecutorTest extends TestBase { private static final String ATTRIBUTE_CODE = "AttributeCode"; - private static final String TE_ID = "TeiId"; + private static final UID TE_ID = UID.generate(); private static final String ATTRIBUTE_VALUE = "23.0"; @@ -338,7 +338,7 @@ private org.hisp.dhis.tracker.imports.domain.TrackedEntity payloadTrackedEntity( private TrackedEntity trackedEntity() { TrackedEntity trackedEntity = new TrackedEntity(); - trackedEntity.setUid(TE_ID); + trackedEntity.setUid(TE_ID.getValue()); TrackedEntityAttributeValue attributeValue = createTrackedEntityAttributeValue('A', trackedEntity, attribute); attributeValue.setValue(ATTRIBUTE_VALUE); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/ValidationExecutorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/ValidationExecutorTest.java index 630d92c325a0..a3611e7302bb 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/ValidationExecutorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/enrollment/ValidationExecutorTest.java @@ -63,9 +63,9 @@ class ValidationExecutorTest extends TestBase { private static final String EVALUATED_DATA = "4.0"; - private static final String ACTIVE_ENROLLMENT_ID = "ActiveEnrollmentUid"; + private static final UID ACTIVE_ENROLLMENT_ID = UID.generate(); - private static final String COMPLETED_ENROLLMENT_ID = "CompletedEnrollmentUid"; + private static final UID COMPLETED_ENROLLMENT_ID = UID.generate(); private final ShowWarningOnCompleteExecutor warningOnCompleteExecutor = new ShowWarningOnCompleteExecutor( diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/event/AssignDataValueExecutorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/event/AssignDataValueExecutorTest.java index 82dc50cda0c3..2806112be907 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/event/AssignDataValueExecutorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/event/AssignDataValueExecutorTest.java @@ -49,8 +49,8 @@ import org.hisp.dhis.setting.SystemSettings; import org.hisp.dhis.setting.SystemSettingsProvider; import org.hisp.dhis.test.TestBase; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.DataValue; import org.hisp.dhis.tracker.imports.domain.Event; @@ -71,9 +71,9 @@ class AssignDataValueExecutorTest extends TestBase { private static final UID RULE_UID = UID.of("TvctPPhpD8u"); - private static final String EVENT_ID = "EventId"; + private static final UID EVENT_UID = UID.generate(); - private static final String SECOND_EVENT_ID = "SecondEventId"; + private static final UID SECOND_EVENT_UID = UID.generate(); private static final UID DATA_ELEMENT_UID = UID.of("h4w96yEMlzO"); @@ -168,7 +168,7 @@ void shouldFailWhenAssignedValueIsInvalidOptionAndDataValueIsValidOption() { executor.executeRuleAction(bundle, eventWithOptionDataValue); Optional dataValue = - findDataValueByUid(bundle, EVENT_ID, OPTION_SET_DATA_ELEMENT_UID); + findDataValueByUid(bundle, EVENT_UID, OPTION_SET_DATA_ELEMENT_UID); assertDataValueWasNotAssignedAndErrorIsPresent(VALID_OPTION_VALUE, dataValue, warning); } @@ -192,7 +192,7 @@ void shouldAssignDataValueWhenAssignedValueIsValidOptionAndDataValueIsEmpty() { executor.executeRuleAction(bundle, eventWithOptionDataValue); Optional dataValue = - findDataValueByUid(bundle, EVENT_ID, OPTION_SET_DATA_ELEMENT_UID); + findDataValueByUid(bundle, EVENT_UID, OPTION_SET_DATA_ELEMENT_UID); assertTrue(dataValue.isPresent()); assertEquals(VALID_OPTION_VALUE, dataValue.get().getValue()); @@ -219,7 +219,7 @@ void shouldAssignDataValueWhenAssignedValueIsInvalidOptionAndDataValueIsEmpty() executor.executeRuleAction(bundle, eventWithOptionDataValue); Optional dataValue = - findDataValueByUid(bundle, SECOND_EVENT_ID, OPTION_SET_DATA_ELEMENT_UID); + findDataValueByUid(bundle, SECOND_EVENT_UID, OPTION_SET_DATA_ELEMENT_UID); assertAll( () -> assertTrue(dataValue.isEmpty()), @@ -247,7 +247,7 @@ void shouldAssignNullDataValueWhenAssignedValueIsInvalidOptionAndOverwriteIsTrue executor.executeRuleAction(bundle, eventWithOptionDataValue); Optional dataValue = - findDataValueByUid(bundle, EVENT_ID, OPTION_SET_DATA_ELEMENT_UID); + findDataValueByUid(bundle, EVENT_UID, OPTION_SET_DATA_ELEMENT_UID); assertDataValueWasAssignedAndWarningIsPresent(null, dataValue, warning); } @@ -270,7 +270,7 @@ void shouldAssignDataValueValueForEventsWhenDataValueIsEmpty() { Optional warning = executor.executeRuleAction(bundle, eventWithDataValueNOTSet); - Optional dataValue = findDataValueByUid(bundle, SECOND_EVENT_ID, DATA_ELEMENT_UID); + Optional dataValue = findDataValueByUid(bundle, SECOND_EVENT_UID, DATA_ELEMENT_UID); assertDataValueWasAssignedAndWarningIsPresent(DATAELEMENT_NEW_VALUE, dataValue, warning); } @@ -291,7 +291,7 @@ void shouldNotAssignDataValueValueForEventsWhenDataValueIsAlreadyPresent() { Optional error = executor.executeRuleAction(bundle, eventWithDataValueSet); - Optional dataValue = findDataValueByUid(bundle, EVENT_ID, DATA_ELEMENT_UID); + Optional dataValue = findDataValueByUid(bundle, EVENT_UID, DATA_ELEMENT_UID); assertDataValueWasNotAssignedAndErrorIsPresent(DATAELEMENT_OLD_VALUE, dataValue, error); } @@ -315,7 +315,7 @@ void shouldNotAssignDataValueValueForEventsWhenDataValueIsAlreadyPresentUsingIdS Optional error = executor.executeRuleAction(bundle, eventWithDataValueSet); - Optional dataValue = findDataValueByCode(bundle, EVENT_ID, DATA_ELEMENT_CODE); + Optional dataValue = findDataValueByCode(bundle, EVENT_UID, DATA_ELEMENT_CODE); assertDataValueWasNotAssignedAndErrorIsPresent(DATAELEMENT_OLD_VALUE, dataValue, error); } @@ -337,7 +337,7 @@ void shouldAssignDataValueValueForEventsWhenDataValueIsAlreadyPresentAndHasTheSa Optional warning = executor.executeRuleAction(bundle, eventWithDataValueSetSameValue); - Optional dataValue = findDataValueByUid(bundle, EVENT_ID, DATA_ELEMENT_UID); + Optional dataValue = findDataValueByUid(bundle, EVENT_UID, DATA_ELEMENT_UID); assertDataValueWasAssignedAndWarningIsPresent(DATAELEMENT_NEW_VALUE, dataValue, warning); } @@ -360,13 +360,13 @@ void shouldAssignDataValueValueForEventsWhenDataValueIsAlreadyPresentAndHasTheSa Optional warning = executor.executeRuleAction(bundle, eventWithDataValueSet); - Optional dataValue = findDataValueByUid(bundle, EVENT_ID, DATA_ELEMENT_UID); + Optional dataValue = findDataValueByUid(bundle, EVENT_UID, DATA_ELEMENT_UID); assertDataValueWasAssignedAndWarningIsPresent(DATAELEMENT_NEW_VALUE, dataValue, warning); } private Optional findDataValueByUid( - TrackerBundle bundle, String eventUid, UID dataValueUid) { + TrackerBundle bundle, UID eventUid, UID dataValueUid) { Event event = bundle.findEventByUid(eventUid).get(); return event.getDataValues().stream() .filter(dv -> dv.getDataElement().equals(MetadataIdentifier.ofUid(dataValueUid.getValue()))) @@ -374,7 +374,7 @@ private Optional findDataValueByUid( } private Optional findDataValueByCode( - TrackerBundle bundle, String eventUid, String dataValueCode) { + TrackerBundle bundle, UID eventUid, String dataValueCode) { Event event = bundle.findEventByUid(eventUid).get(); return event.getDataValues().stream() .filter(dv -> dv.getDataElement().equals(MetadataIdentifier.ofCode(dataValueCode))) @@ -404,7 +404,7 @@ private void assertDataValueWasAssignedAndValidationIsPresent( private Event getEventWithDataValueSet() { return Event.builder() - .event(EVENT_ID) + .event(EVENT_UID) .status(EventStatus.ACTIVE) .dataValues(getDataValues()) .build(); @@ -412,7 +412,7 @@ private Event getEventWithDataValueSet() { private Event getEventWithDataValueSet(TrackerIdSchemeParams idSchemes) { return Event.builder() - .event(EVENT_ID) + .event(EVENT_UID) .status(EventStatus.ACTIVE) .dataValues(getDataValues(idSchemes)) .build(); @@ -420,7 +420,7 @@ private Event getEventWithDataValueSet(TrackerIdSchemeParams idSchemes) { private Event getEventWithDataValueSetSameValue() { return Event.builder() - .event(EVENT_ID) + .event(EVENT_UID) .status(EventStatus.ACTIVE) .dataValues(getDataValuesSameValue()) .build(); @@ -428,14 +428,14 @@ private Event getEventWithDataValueSetSameValue() { private Event getEventWithOptionSetDataValueWithValidValue() { return Event.builder() - .event(EVENT_ID) + .event(EVENT_UID) .status(EventStatus.ACTIVE) .dataValues(getOptionSetDataValues()) .build(); } private Event getEventWithDataValueNOTSet() { - return Event.builder().event(SECOND_EVENT_ID).status(EventStatus.COMPLETED).build(); + return Event.builder().event(SECOND_EVENT_UID).status(EventStatus.COMPLETED).build(); } private Set getDataValues(TrackerIdSchemeParams idSchemes) { diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/event/RuleEngineErrorExecutorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/event/RuleEngineErrorExecutorTest.java index f0c972c13eab..1f6f3fe9fa13 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/event/RuleEngineErrorExecutorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/event/RuleEngineErrorExecutorTest.java @@ -49,7 +49,7 @@ class RuleEngineErrorExecutorTest extends TestBase { private static final String ERROR_MESSAGE = "Error message"; - private static final String EVENT_ID = "EventUid"; + private static final UID EVENT_UID = UID.generate(); private final RuleEngineErrorExecutor ruleEngineErrorExecutor = new RuleEngineErrorExecutor(RULE_UID, ERROR_MESSAGE); @@ -71,6 +71,6 @@ void shouldReturnAWarningWhenRuleWithSyntaxErrorIsTriggeredOnEvent() { } private Event getEvent() { - return Event.builder().event(EVENT_ID).build(); + return Event.builder().event(EVENT_UID).build(); } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/event/SetMandatoryFieldExecutorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/event/SetMandatoryFieldExecutorTest.java index 961d69d6cc6d..4cb68ab2c698 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/event/SetMandatoryFieldExecutorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/event/SetMandatoryFieldExecutorTest.java @@ -47,8 +47,8 @@ import org.hisp.dhis.program.ProgramStageDataElement; import org.hisp.dhis.program.ValidationStrategy; import org.hisp.dhis.test.TestBase; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.DataValue; @@ -67,9 +67,9 @@ @ExtendWith(MockitoExtension.class) class SetMandatoryFieldExecutorTest extends TestBase { - private static final String ACTIVE_EVENT_ID = "EventUid"; + private static final UID ACTIVE_EVENT_UID = UID.generate(); - private static final String COMPLETED_EVENT_ID = "CompletedEventUid"; + private static final UID COMPLETED_EVENT_UID = UID.generate(); private static final UID DATA_ELEMENT_UID = UID.of("h4w96yEMlzO"); @@ -251,25 +251,25 @@ void shouldFailValidationWhenMandatoryFieldIsNotPresentAndDataValuesAreCreated( assertEquals(error(RULE_UID, E1301, dataElement.getUid()), error.get()); } - private org.hisp.dhis.program.Event eventWithMandatoryValue(String uid, EventStatus status) { + private org.hisp.dhis.program.Event eventWithMandatoryValue(UID uid, EventStatus status) { org.hisp.dhis.program.Event event = new org.hisp.dhis.program.Event(); - event.setUid(uid); + event.setUid(uid.getValue()); event.setStatus(status); event.setEventDataValues( Set.of(new EventDataValue(DATA_ELEMENT_UID.getValue(), DATA_ELEMENT_VALUE))); return event; } - private org.hisp.dhis.program.Event event(String uid, EventStatus status) { + private org.hisp.dhis.program.Event event(UID uid, EventStatus status) { org.hisp.dhis.program.Event event = new org.hisp.dhis.program.Event(); - event.setUid(uid); + event.setUid(uid.getValue()); event.setStatus(status); return event; } private Event getEventWithDeleteMandatoryValue() { return Event.builder() - .event(ACTIVE_EVENT_ID) + .event(ACTIVE_EVENT_UID) .status(EventStatus.ACTIVE) .programStage(MetadataIdentifier.ofUid(programStage)) .dataValues(getNullEventDataValues()) @@ -278,7 +278,7 @@ private Event getEventWithDeleteMandatoryValue() { private Event getEventWithMandatoryValueSet(TrackerIdSchemeParams idSchemes) { return Event.builder() - .event(ACTIVE_EVENT_ID) + .event(ACTIVE_EVENT_UID) .status(EventStatus.ACTIVE) .programStage(idSchemes.toMetadataIdentifier(programStage)) .dataValues(getActiveEventDataValues(idSchemes)) @@ -287,7 +287,7 @@ private Event getEventWithMandatoryValueSet(TrackerIdSchemeParams idSchemes) { private Event getEventWithMandatoryValueSet() { return Event.builder() - .event(ACTIVE_EVENT_ID) + .event(ACTIVE_EVENT_UID) .status(EventStatus.ACTIVE) .programStage(MetadataIdentifier.ofUid(programStage)) .dataValues(getActiveEventDataValues()) @@ -296,7 +296,7 @@ private Event getEventWithMandatoryValueSet() { private Event getEventWithMandatoryValueNOTSet() { return Event.builder() - .event(COMPLETED_EVENT_ID) + .event(COMPLETED_EVENT_UID) .status(EventStatus.ACTIVE) .programStage(MetadataIdentifier.ofUid(programStage)) .build(); @@ -304,7 +304,7 @@ private Event getEventWithMandatoryValueNOTSet() { private Event getEventWithMandatoryValueNOTSet(EventStatus status) { return Event.builder() - .event(COMPLETED_EVENT_ID) + .event(COMPLETED_EVENT_UID) .status(status) .programStage(MetadataIdentifier.ofUid(programStage)) .build(); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/event/ValidationExecutorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/event/ValidationExecutorTest.java index 373a254cc513..69d901af48b4 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/event/ValidationExecutorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/programrule/executor/event/ValidationExecutorTest.java @@ -72,9 +72,9 @@ class ValidationExecutorTest extends TestBase { private static final String EVALUATED_DATA = "4.0"; - private static final String ACTIVE_EVENT_ID = "EventUid"; + private static final UID ACTIVE_EVENT_UID = UID.generate(); - private static final String COMPLETED_EVENT_ID = "CompletedEventUid"; + private static final UID COMPLETED_EVENT_UID = UID.generate(); private static final String PROGRAM_STAGE_ID = "ProgramStageId"; @@ -209,7 +209,7 @@ private List getEvents() { private Event activeEvent() { return Event.builder() - .event(ACTIVE_EVENT_ID) + .event(ACTIVE_EVENT_UID) .status(EventStatus.ACTIVE) .programStage(MetadataIdentifier.ofUid(PROGRAM_STAGE_ID)) .build(); @@ -217,7 +217,7 @@ private Event activeEvent() { private Event completedEvent() { return Event.builder() - .event(COMPLETED_EVENT_ID) + .event(COMPLETED_EVENT_UID) .status(EventStatus.COMPLETED) .programStage(MetadataIdentifier.ofUid(PROGRAM_STAGE_ID)) .build(); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/report/TrackerBundleImportReportTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/report/TrackerBundleImportReportTest.java index c5676ea1d114..b1204c5cc53f 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/report/TrackerBundleImportReportTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/report/TrackerBundleImportReportTest.java @@ -38,6 +38,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.hisp.dhis.common.UID; import org.hisp.dhis.commons.jackson.config.JacksonObjectMapperConfig; import org.hisp.dhis.system.notification.Notifier; import org.hisp.dhis.tracker.TrackerType; @@ -115,7 +116,7 @@ void testSerializingAndDeserializingImportReport() throws JsonProcessingExceptio TrackerTypeReport typeReport = new TrackerTypeReport(TRACKED_ENTITY); Entity entity = new Entity(TRACKED_ENTITY); - entity.setUid("BltTZV9HvEZ"); + entity.setUid(UID.of("BltTZV9HvEZ")); typeReport.addEntity(entity); typeReport.getStats().setCreated(1); typeReport.getStats().setUpdated(2); @@ -132,7 +133,7 @@ void testSerializingAndDeserializingImportReport() throws JsonProcessingExceptio "Could not find OrganisationUnit: ``, linked to Tracked Entity.", ValidationCode.E1049.name(), TRACKED_ENTITY.name(), - "BltTZV9HvEZ", + UID.of("BltTZV9HvEZ"), List.of("BltTZV9HvEZ")))); tvr.addWarnings( List.of( @@ -140,7 +141,7 @@ void testSerializingAndDeserializingImportReport() throws JsonProcessingExceptio "ProgramStage `l8oDIfJJhtg` does not allow user assignment", ValidationCode.E1120.name(), TrackerType.EVENT.name(), - "BltTZV9HvEZ"))); + UID.of("BltTZV9HvEZ")))); // Create the TrackerImportReport final Map bundleSize = new HashMap<>(); bundleSize.put(TRACKED_ENTITY, 1); @@ -243,20 +244,20 @@ private ValidationReport createValidationReport() { "Could not find OrganisationUnit: ``, linked to Tracked Entity.", ValidationCode.E1049.name(), TRACKED_ENTITY.name(), - "BltTZV9HvEZ", + UID.of("BltTZV9HvEZ"), List.of("BltTZV9HvEZ"))), List.of( new Warning( "ProgramStage `l8oDIfJJhtg` does not allow user assignment", ValidationCode.E1120.name(), TrackerType.EVENT.name(), - "BltTZV9HvEZ"))); + UID.of("BltTZV9HvEZ")))); } private PersistenceReport createBundleReport() { PersistenceReport persistenceReport = PersistenceReport.emptyReport(); TrackerTypeReport typeReport = new TrackerTypeReport(TRACKED_ENTITY); - Entity objectReport = new Entity(TRACKED_ENTITY, "TE_UID"); + Entity objectReport = new Entity(TRACKED_ENTITY, UID.generate()); typeReport.addEntity(objectReport); typeReport.getStats().incCreated(); persistenceReport.getTypeReportMap().put(TRACKED_ENTITY, typeReport); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/sms/SmsImportMapperTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/sms/SmsImportMapperTest.java index 5f51b81c504e..458d4312d9ab 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/sms/SmsImportMapperTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/sms/SmsImportMapperTest.java @@ -38,6 +38,7 @@ import java.util.List; import java.util.Set; import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.event.EventStatus; import org.hisp.dhis.program.EnrollmentStatus; import org.hisp.dhis.program.Program; @@ -85,14 +86,14 @@ void mapEnrollmentWithNewTrackedEntityAndOnlyMandatoryFields() { Enrollment.builder() .orgUnit(MetadataIdentifier.ofUid(input.getOrgUnit().getUid())) .program(MetadataIdentifier.ofUid(input.getTrackerProgram().getUid())) - .trackedEntity(input.getTrackedEntityInstance().getUid()) - .enrollment(input.getEnrollment().getUid()) + .trackedEntity(UID.of(input.getTrackedEntityInstance().getUid())) + .enrollment(UID.of(input.getEnrollment().getUid())) .status(EnrollmentStatus.COMPLETED) .build())) .trackedEntities( List.of( TrackedEntity.builder() - .trackedEntity(input.getTrackedEntityInstance().getUid()) + .trackedEntity(UID.of(input.getTrackedEntityInstance().getUid())) .orgUnit(MetadataIdentifier.ofUid(input.getOrgUnit().getUid())) .trackedEntityType( MetadataIdentifier.ofUid(input.getTrackedEntityType().getUid())) @@ -122,14 +123,14 @@ void mapEnrollmentWithNewTrackedEntityAndAttributes() { Enrollment.builder() .orgUnit(MetadataIdentifier.ofUid(input.getOrgUnit().getUid())) .program(MetadataIdentifier.ofUid(input.getTrackerProgram().getUid())) - .trackedEntity(input.getTrackedEntityInstance().getUid()) - .enrollment(input.getEnrollment().getUid()) + .trackedEntity(UID.of(input.getTrackedEntityInstance().getUid())) + .enrollment(UID.of(input.getEnrollment().getUid())) .status(EnrollmentStatus.COMPLETED) .build())) .trackedEntities( List.of( TrackedEntity.builder() - .trackedEntity(input.getTrackedEntityInstance().getUid()) + .trackedEntity(UID.of(input.getTrackedEntityInstance().getUid())) .orgUnit(MetadataIdentifier.ofUid(input.getOrgUnit().getUid())) .trackedEntityType( MetadataIdentifier.ofUid(input.getTrackedEntityType().getUid())) @@ -167,8 +168,8 @@ void mapEnrollmentWithNewTrackedEntityAndOptionalNonCollectionFields() { Enrollment.builder() .orgUnit(MetadataIdentifier.ofUid(input.getOrgUnit().getUid())) .program(MetadataIdentifier.ofUid(input.getTrackerProgram().getUid())) - .trackedEntity(input.getTrackedEntityInstance().getUid()) - .enrollment(input.getEnrollment().getUid()) + .trackedEntity(UID.of(input.getTrackedEntityInstance().getUid())) + .enrollment(UID.of(input.getEnrollment().getUid())) .enrolledAt(enrollmentDate.toInstant()) .occurredAt(occurredDate.toInstant()) .status(EnrollmentStatus.CANCELLED) @@ -204,7 +205,7 @@ void mapEnrollmentWithEvents() { List expected = List.of( Event.builder() - .event(smsEvent.getEvent().getUid()) + .event(UID.of(smsEvent.getEvent().getUid())) .orgUnit(MetadataIdentifier.ofUid(smsEvent.getOrgUnit().getUid())) .programStage(MetadataIdentifier.ofUid(smsEvent.getProgramStage().getUid())) .attributeOptionCombo( @@ -218,7 +219,7 @@ void mapEnrollmentWithEvents() { .value("hello") .storedBy("francis") .build())) - .enrollment(input.getEnrollment().getUid()) + .enrollment(UID.of(input.getEnrollment().getUid())) .build()); assertEquals(expected, actual.getEvents()); } @@ -340,10 +341,10 @@ void mapEventWithMandatoryFields() { Event expected = Event.builder() - .event(input.getEvent().getUid()) + .event(UID.of(input.getEvent().getUid())) .orgUnit(MetadataIdentifier.ofUid(input.getOrgUnit().getUid())) .programStage(MetadataIdentifier.ofUid(input.getProgramStage().getUid())) - .enrollment(input.getEnrollment().getUid()) + .enrollment(UID.of(input.getEnrollment().getUid())) .attributeOptionCombo( MetadataIdentifier.ofUid(input.getAttributeOptionCombo().getUid())) .status(EventStatus.COMPLETED) @@ -374,10 +375,10 @@ void mapEventWithOptionalFields() { Event expected = Event.builder() - .event(input.getEvent().getUid()) + .event(UID.of(input.getEvent().getUid())) .orgUnit(MetadataIdentifier.ofUid(input.getOrgUnit().getUid())) .programStage(MetadataIdentifier.ofUid(input.getProgramStage().getUid())) - .enrollment(input.getEnrollment().getUid()) + .enrollment(UID.of(input.getEnrollment().getUid())) .attributeOptionCombo( MetadataIdentifier.ofUid(input.getAttributeOptionCombo().getUid())) .status(EventStatus.ACTIVE) diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/DefaultValidationServiceTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/DefaultValidationServiceTest.java index 850b417f19aa..2d59b89de68b 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/DefaultValidationServiceTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/DefaultValidationServiceTest.java @@ -43,9 +43,9 @@ import static org.mockito.Mockito.when; import java.util.List; -import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.test.TestBase; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.ValidationMode; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.TrackedEntity; @@ -152,7 +152,7 @@ void warningsDoNotInvalidateAndRemoveEntities() { } private TrackedEntity trackedEntity() { - return TrackedEntity.builder().trackedEntity(CodeGenerator.generateUid()).build(); + return TrackedEntity.builder().trackedEntity(UID.generate()).build(); } private List trackedEntities(TrackedEntity... trackedEntities) { diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/MessageFormatterTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/MessageFormatterTest.java index 77bb39afb9e7..dbb09804f82d 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/MessageFormatterTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/MessageFormatterTest.java @@ -41,6 +41,7 @@ import org.hisp.dhis.attribute.AttributeValues; import org.hisp.dhis.category.CategoryOption; import org.hisp.dhis.category.CategoryOptionCombo; +import org.hisp.dhis.common.UID; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.event.EventStatus; import org.hisp.dhis.organisationunit.OrganisationUnit; @@ -48,8 +49,8 @@ import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramStage; import org.hisp.dhis.relationship.RelationshipType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.domain.Enrollment; import org.hisp.dhis.tracker.imports.domain.Event; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -166,7 +167,7 @@ void formatArgumentsShouldTurnMetadataIdentifierIntoArguments() { void formatArgumentsShouldTurnTrackedEntityIntoArguments() { List args = MessageFormatter.formatArguments( - idSchemes, TrackedEntity.builder().trackedEntity("zwccdzhk5zc").build()); + idSchemes, TrackedEntity.builder().trackedEntity(UID.of("zwccdzhk5zc")).build()); assertContainsOnly(List.of("zwccdzhk5zc"), args); } @@ -175,7 +176,7 @@ void formatArgumentsShouldTurnTrackedEntityIntoArguments() { void formatArgumentsShouldTurnEnrollmentIntoArguments() { List args = MessageFormatter.formatArguments( - idSchemes, Enrollment.builder().enrollment("zwccdzhk5zc").build()); + idSchemes, Enrollment.builder().enrollment(UID.of("zwccdzhk5zc")).build()); assertContainsOnly(List.of("zwccdzhk5zc"), args); } @@ -183,7 +184,15 @@ void formatArgumentsShouldTurnEnrollmentIntoArguments() { @Test void formatArgumentsShouldTurnEventIntoArguments() { List args = - MessageFormatter.formatArguments(idSchemes, Event.builder().event("zwccdzhk5zc").build()); + MessageFormatter.formatArguments( + idSchemes, Event.builder().event(UID.of("zwccdzhk5zc")).build()); + + assertContainsOnly(List.of("zwccdzhk5zc"), args); + } + + @Test + void formatArgumentsShouldTurnUIDIntoArguments() { + List args = MessageFormatter.formatArguments(idSchemes, UID.of("zwccdzhk5zc")); assertContainsOnly(List.of("zwccdzhk5zc"), args); } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/PersistablesFilterTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/PersistablesFilterTest.java index ae5cef216dc3..454f4ac2140f 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/PersistablesFilterTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/PersistablesFilterTest.java @@ -47,6 +47,7 @@ import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import org.hisp.dhis.common.UID; import org.hisp.dhis.test.utils.Assertions; import org.hisp.dhis.tracker.TrackerType; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; @@ -403,7 +404,7 @@ private static class Entity { private static class Setup { private final TrackerBundle bundle; - private final EnumMap> invalidEntities; + private final EnumMap> invalidEntities; /** * Setup builds the arguments for calling {@link PersistablesFilter#filter(TrackerBundle, @@ -453,7 +454,7 @@ private static class Builder { */ Builder trackedEntity(String uid) { Entity entity = - new Entity<>(TrackedEntity.builder().trackedEntity(uid).build()); + new Entity<>(TrackedEntity.builder().trackedEntity(UID.of(uid)).build()); this.trackedEntities.add(entity); current = entity; currentTrackedEntity = entity; @@ -472,14 +473,14 @@ Builder trackedEntity(String uid) { * @return builder */ Builder enrollment(String uid) { - Entity entity = enrollment(uid, currentTrackedEntity); + Entity entity = enrollment(UID.of(uid), currentTrackedEntity); this.enrollments.add(entity); current = entity; currentEnrollment = entity; return this; } - private static Entity enrollment(String uid, Entity parent) { + private static Entity enrollment(UID uid, Entity parent) { // set child/parent links Enrollment enrollment = Enrollment.builder().enrollment(uid).trackedEntity(parent.entity.getUid()).build(); @@ -498,13 +499,13 @@ private static Entity enrollment(String uid, Entity p * @return builder */ Builder event(String uid) { - Entity entity = event(uid, currentEnrollment); + Entity entity = event(UID.of(uid), currentEnrollment); this.events.add(entity); current = entity; return this; } - private static Entity event(String uid, Entity parent) { + private static Entity event(UID uid, Entity parent) { // set child/parent links only if the event has a parent. Events in an event program have no // enrollment. // They do have a "fake" enrollment (a default program) but it's not set on the event DTO. @@ -529,7 +530,7 @@ private static Entity event(String uid, Entity parent) { */ Builder relationship(String uid, RelationshipItem from, RelationshipItem to) { Relationship relationship = - Relationship.builder().relationship(uid).from(from).to(to).build(); + Relationship.builder().relationship(UID.of(uid)).from(from).to(to).build(); Entity entity = new Entity<>(relationship); this.relationships.add(entity); current = entity; @@ -545,8 +546,7 @@ private Setup build() { bundle.setEvents(toEntitiesInPayload(events)); bundle.setRelationships(toEntitiesInPayload(relationships)); - EnumMap> invalidEntities = - PersistablesFilterTest.invalidEntities(); + EnumMap> invalidEntities = PersistablesFilterTest.invalidEntities(); invalidEntities.get(TRACKED_ENTITY).addAll(invalid(trackedEntities)); invalidEntities.get(ENROLLMENT).addAll(invalid(enrollments)); invalidEntities.get(EVENT).addAll(invalid(events)); @@ -564,7 +564,7 @@ private List toEntitiesInPayload(List> entit return entities.stream().filter(e -> e.isInPayload).map(e -> e.entity).toList(); } - private Set invalid(List> entities) { + private Set invalid(List> entities) { return entities.stream() .filter(e -> !e.valid) .map(e -> e.entity.getUid()) @@ -620,18 +620,18 @@ Builder isNotInPayload() { } private static RelationshipItem trackedEntity(String uid) { - return RelationshipItem.builder().trackedEntity(uid).build(); + return RelationshipItem.builder().trackedEntity(UID.of(uid)).build(); } private static RelationshipItem enrollment(String uid) { - return RelationshipItem.builder().enrollment(uid).build(); + return RelationshipItem.builder().enrollment(UID.of(uid)).build(); } private static RelationshipItem event(String uid) { - return RelationshipItem.builder().event(uid).build(); + return RelationshipItem.builder().event(UID.of(uid)).build(); } - private static EnumMap> invalidEntities() { + private static EnumMap> invalidEntities() { return new EnumMap<>( Map.of( TRACKED_ENTITY, new HashSet<>(), @@ -642,10 +642,10 @@ private static EnumMap> invalidEntities() { private static void assertContainsOnly( PersistablesFilter.Result persistable, Class type, String... uid) { - Assertions.assertContainsOnly(List.of(uid), persistableUids(persistable, type)); + Assertions.assertContainsOnly(UID.of(uid), persistableUids(persistable, type)); } - private static List persistableUids( + private static List persistableUids( PersistablesFilter.Result persistable, Class type) { return persistable.get(type).stream().map(TrackerDto::getUid).toList(); } @@ -667,8 +667,8 @@ public TrackerType getTrackerType() { } @Override - public String getUid() { - return uid; + public UID getUid() { + return UID.of(uid); } }; } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/ReporterTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/ReporterTest.java index f10e22052b98..cf99ebc91288 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/ReporterTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/ReporterTest.java @@ -31,8 +31,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; +import org.hisp.dhis.common.UID; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.TrackerType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; import org.junit.jupiter.api.Test; class ReporterTest { @@ -77,10 +78,11 @@ void hasWarningReportNotFound() { private Error eventError() { return new Error( - "some error", ValidationCode.E1000, TrackerType.EVENT, "JgDfHAGzzfS", List.of()); + "some error", ValidationCode.E1000, TrackerType.EVENT, UID.of("JgDfHAGzzfS"), List.of()); } private Warning eventWarning() { - return new Warning("some warning", ValidationCode.E1000, TrackerType.EVENT, "JgDfHAGzzfS"); + return new Warning( + "some warning", ValidationCode.E1000, TrackerType.EVENT, UID.of("JgDfHAGzzfS")); } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/ValidationResultTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/ValidationResultTest.java index ffddf319a64f..2e6b5ed56a25 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/ValidationResultTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/ValidationResultTest.java @@ -32,7 +32,7 @@ import java.util.List; import java.util.Set; -import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.tracker.TrackerType; import org.junit.jupiter.api.Test; @@ -70,22 +70,10 @@ void hasWarningsReturnsTrue() { } private Error newError() { - return newError(ValidationCode.E9999); - } - - private Error newError(ValidationCode code) { - return newError(CodeGenerator.generateUid(), code); - } - - private Error newError(String uid, ValidationCode code) { - return new Error("", code, TrackerType.EVENT, uid, List.of()); + return new Error("", ValidationCode.E9999, TrackerType.EVENT, UID.generate(), List.of()); } private Warning newWarning() { - return newWarning(CodeGenerator.generateUid(), ValidationCode.E9999); - } - - private Warning newWarning(String uid, ValidationCode code) { - return new Warning("", code, TrackerType.EVENT, uid); + return new Warning("", ValidationCode.E9999, TrackerType.EVENT, UID.generate()); } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/AllTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/AllTest.java index 66c440ca48dd..daec45c83971 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/AllTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/AllTest.java @@ -38,8 +38,9 @@ import java.util.EnumMap; import java.util.List; import java.util.Map; +import org.hisp.dhis.common.UID; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.TrackerType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Event; @@ -105,10 +106,11 @@ public boolean needsToRun(TrackerImportStrategy strategy) { @Test void testAllDoesNotCallValidatorIfItShouldNotRunOnGivenStrategyForATrackerDto() { + UID uid = UID.generate(); bundle = TrackerBundle.builder() .importStrategy(CREATE_AND_UPDATE) - .resolvedStrategyMap(new EnumMap<>(Map.of(TrackerType.EVENT, Map.of("event1", UPDATE)))) + .resolvedStrategyMap(new EnumMap<>(Map.of(TrackerType.EVENT, Map.of(uid, UPDATE)))) .build(); Validator validator = @@ -136,7 +138,7 @@ public boolean needsToRun(TrackerImportStrategy strategy) { } }); - validator.validate(reporter, bundle, Event.builder().event("event1").build()); + validator.validate(reporter, bundle, Event.builder().event(uid).build()); assertContainsOnly(List.of("V2"), actualErrorMessages()); } @@ -148,7 +150,8 @@ public boolean needsToRun(TrackerImportStrategy strategy) { */ private static void addError(Reporter reporter, String message) { reporter.addError( - new Error(message, ValidationCode.E9999, TrackerType.TRACKED_ENTITY, "uid", List.of())); + new Error( + message, ValidationCode.E9999, TrackerType.TRACKED_ENTITY, UID.generate(), List.of())); } private List actualErrorMessages() { diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/AssertValidations.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/AssertValidations.java index 1d72fe230db7..73cbff37adee 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/AssertValidations.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/AssertValidations.java @@ -32,6 +32,7 @@ import java.util.Collection; import java.util.stream.Collectors; +import org.hisp.dhis.common.UID; import org.hisp.dhis.tracker.TrackerType; import org.hisp.dhis.tracker.imports.domain.TrackerDto; import org.hisp.dhis.tracker.imports.validation.Reporter; @@ -73,7 +74,7 @@ public static void assertHasNoError(Reporter reporter, TrackerDto dto, Validatio } TrackerType type = dto.getTrackerType(); - String uid = dto.getUid(); + UID uid = dto.getUid(); assertFalse( reporter.getErrors().stream() .anyMatch( @@ -132,7 +133,7 @@ private static void assertHasValidation( TrackerDto dto, ValidationCode code) { TrackerType type = dto.getTrackerType(); - String uid = dto.getUid(); + UID uid = dto.getUid(); assertFalse( validations.isEmpty(), validationType + " not found since " + validationType + "s is empty"); @@ -155,7 +156,7 @@ private static void assertHasValidation( ValidationCode code, String messageContains) { TrackerType type = dto.getTrackerType(); - String uid = dto.getUid(); + UID uid = dto.getUid(); assertFalse( validations.isEmpty(), validationType + " not found since " + validationType + "s is empty"); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/EachTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/EachTest.java index 3bc4d180a488..3d72a557b381 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/EachTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/EachTest.java @@ -41,8 +41,9 @@ import java.util.EnumMap; import java.util.List; import java.util.Map; +import org.hisp.dhis.common.UID; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.TrackerType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Enrollment; @@ -70,12 +71,15 @@ void setUp() { @Test void testCallsValidatorForEachItemInCollection() { Validator validator = - each(Enrollment::getNotes, (r, b, n) -> addError(r, n.getNote())); + each(Enrollment::getNotes, (r, b, n) -> addError(r, n.getNote().getValue())); - validator.validate(reporter, bundle, enrollment("Kj6vYde4LHh", "V1", "V2", "V3")); + UID note1 = UID.generate(); + UID note2 = UID.generate(); + UID note3 = UID.generate(); + validator.validate(reporter, bundle, enrollment(UID.of("Kj6vYde4LHh"), note1, note2, note3)); // order of input collection is preserved - assertEquals(List.of("V1", "V2", "V3"), actualErrorMessages()); + assertEquals(UID.toValueList(List.of(note1, note2, note3)), actualErrorMessages()); } @Test @@ -97,7 +101,10 @@ public boolean needsToRun(TrackerImportStrategy strategy) { } }); - validator.validate(reporter, bundle, enrollment("Kj6vYde4LHh", "input1", "input2", "input2")); + validator.validate( + reporter, + bundle, + enrollment(UID.of("Kj6vYde4LHh"), UID.generate(), UID.generate(), UID.generate())); assertIsEmpty(actualErrorMessages()); } @@ -112,9 +119,10 @@ void testDoesNotCallValidatorIfItShouldNotRunOnGivenStrategyForATrackerDto() { Map.of( ENROLLMENT, Map.of( - "Kj6vYde4LHh", UPDATE, - "Nav6inZRw1u", CREATE)))) - .enrollments(List.of(enrollment("Kj6vYde4LHh"), enrollment("Nav6inZRw1u"))) + UID.of("Kj6vYde4LHh"), UPDATE, + UID.of("Nav6inZRw1u"), CREATE)))) + .enrollments( + List.of(enrollment(UID.of("Kj6vYde4LHh")), enrollment(UID.of("Nav6inZRw1u")))) .build(); Validator validator = @@ -123,7 +131,7 @@ void testDoesNotCallValidatorIfItShouldNotRunOnGivenStrategyForATrackerDto() { new Validator<>() { @Override public void validate(Reporter reporter, TrackerBundle bundle, Enrollment enrollment) { - addError(reporter, enrollment.getEnrollment()); + addError(reporter, enrollment.getEnrollment().getValue()); } @Override @@ -137,7 +145,7 @@ public boolean needsToRun(TrackerImportStrategy strategy) { assertContainsOnly(List.of("Nav6inZRw1u"), actualErrorMessages()); } - private static Enrollment enrollment(String uid, String... notes) { + private static Enrollment enrollment(UID uid, UID... notes) { List n = Arrays.stream(notes).map(s -> Note.builder().note(s).build()).toList(); return Enrollment.builder().enrollment(uid).notes(n).build(); @@ -150,7 +158,8 @@ private static Enrollment enrollment(String uid, String... notes) { */ private static void addError(Reporter reporter, String message) { reporter.addError( - new Error(message, ValidationCode.E9999, TrackerType.TRACKED_ENTITY, "uid", List.of())); + new Error( + message, ValidationCode.E9999, TrackerType.TRACKED_ENTITY, UID.generate(), List.of())); } private List actualErrorMessages() { diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/FieldTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/FieldTest.java index 9f9c3b874428..56b03983e245 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/FieldTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/FieldTest.java @@ -39,8 +39,9 @@ import java.util.EnumMap; import java.util.List; import java.util.Map; +import org.hisp.dhis.common.UID; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.TrackerType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Enrollment; @@ -68,12 +69,15 @@ void setUp() { @Test void testFieldWithValidator() { Enrollment enrollment = - Enrollment.builder().enrollment("Kj6vYde4LHh").trackedEntity("PuBvJxDB73z").build(); + Enrollment.builder() + .enrollment(UID.of("Kj6vYde4LHh")) + .trackedEntity(UID.of("PuBvJxDB73z")) + .build(); - Validator isValidUid = + Validator isValidUid = (r, b, uid) -> { // to demonstrate that we are getting the trackedEntity field - r.addError(new Error(uid, E1000, ENROLLMENT, uid, List.of(uid))); + r.addError(new Error(uid.getValue(), E1000, ENROLLMENT, uid, List.of(uid))); }; Validator validator = field(Enrollment::getTrackedEntity, isValidUid); @@ -87,12 +91,15 @@ void testFieldWithValidator() { void testFieldWithValidatorDoesNotCallValidatorIfItShouldNotRunOnGivenStrategy() { bundle = TrackerBundle.builder().importStrategy(UPDATE).build(); Enrollment enrollment = - Enrollment.builder().enrollment("Kj6vYde4LHh").trackedEntity("PuBvJxDB73z").build(); + Enrollment.builder() + .enrollment(UID.of("Kj6vYde4LHh")) + .trackedEntity(UID.of("PuBvJxDB73z")) + .build(); - Validator isValidUid = + Validator isValidUid = new Validator<>() { @Override - public void validate(Reporter reporter, TrackerBundle bundle, String input) { + public void validate(Reporter reporter, TrackerBundle bundle, UID input) { addError(reporter, "V1"); } @@ -114,15 +121,19 @@ void testFieldWithValidatorDoesNotCallValidatorIfItShouldNotRunOnGivenStrategyFo bundle = TrackerBundle.builder() .importStrategy(CREATE_AND_UPDATE) - .resolvedStrategyMap(new EnumMap<>(Map.of(ENROLLMENT, Map.of("Kj6vYde4LHh", UPDATE)))) + .resolvedStrategyMap( + new EnumMap<>(Map.of(ENROLLMENT, Map.of(UID.of("Kj6vYde4LHh"), UPDATE)))) .build(); Enrollment enrollment = - Enrollment.builder().enrollment("Kj6vYde4LHh").trackedEntity("PuBvJxDB73z").build(); + Enrollment.builder() + .enrollment(UID.of("Kj6vYde4LHh")) + .trackedEntity(UID.of("PuBvJxDB73z")) + .build(); - Validator isValidUid = + Validator isValidUid = new Validator<>() { @Override - public void validate(Reporter reporter, TrackerBundle bundle, String input) { + public void validate(Reporter reporter, TrackerBundle bundle, UID input) { addError(reporter, "V1"); } @@ -142,7 +153,10 @@ public boolean needsToRun(TrackerImportStrategy strategy) { @Test void testFieldWithPredicateFailing() { Enrollment enrollment = - Enrollment.builder().enrollment("Kj6vYde4LHh").trackedEntity("PuBvJxDB73z").build(); + Enrollment.builder() + .enrollment(UID.of("Kj6vYde4LHh")) + .trackedEntity(UID.of("PuBvJxDB73z")) + .build(); Validator validator = field(Enrollment::getTrackedEntity, e -> false, E1000, "wrong 1", "wrong 2"); @@ -155,7 +169,10 @@ void testFieldWithPredicateFailing() { @Test void testFieldWithPredicateSucceeding() { Enrollment enrollment = - Enrollment.builder().enrollment("Kj6vYde4LHh").trackedEntity("PuBvJxDB73z").build(); + Enrollment.builder() + .enrollment(UID.of("Kj6vYde4LHh")) + .trackedEntity(UID.of("PuBvJxDB73z")) + .build(); Validator validator = field(Enrollment::getTrackedEntity, e -> true, E1000, "wrong 1", "wrong 2"); @@ -172,7 +189,8 @@ void testFieldWithPredicateSucceeding() { */ private static void addError(Reporter reporter, String message) { reporter.addError( - new Error(message, ValidationCode.E9999, TrackerType.TRACKED_ENTITY, "uid", List.of())); + new Error( + message, ValidationCode.E9999, TrackerType.TRACKED_ENTITY, UID.generate(), List.of())); } private List actualErrorMessages() { diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/SeqTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/SeqTest.java index 94b9e5f41c23..44acb75464bf 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/SeqTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/SeqTest.java @@ -41,8 +41,9 @@ import java.util.List; import java.util.Map; import lombok.RequiredArgsConstructor; +import org.hisp.dhis.common.UID; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.TrackerType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Event; @@ -182,10 +183,11 @@ public boolean needsToRun(TrackerImportStrategy strategy) { @Test void testSeqDoesNotCallValidatorsIfItShouldNotRunOnGivenStrategyForATrackerDto() { + UID uid = UID.generate(); bundle = TrackerBundle.builder() .importStrategy(CREATE_AND_UPDATE) - .resolvedStrategyMap(new EnumMap<>(Map.of(TrackerType.EVENT, Map.of("event1", UPDATE)))) + .resolvedStrategyMap(new EnumMap<>(Map.of(TrackerType.EVENT, Map.of(uid, UPDATE)))) .build(); Validator validator = @@ -213,7 +215,7 @@ public boolean needsToRun(TrackerImportStrategy strategy) { } }); - validator.validate(reporter, bundle, Event.builder().event("event1").build()); + validator.validate(reporter, bundle, Event.builder().event(uid).build()); assertContainsOnly(List.of("V2"), actualErrorMessages()); } @@ -243,7 +245,8 @@ public void validate(Reporter reporter, TrackerBundle bundle, String input) { */ private static void addError(Reporter reporter, String message) { reporter.addError( - new Error(message, ValidationCode.E9999, TrackerType.TRACKED_ENTITY, "uid", List.of())); + new Error( + message, ValidationCode.E9999, TrackerType.TRACKED_ENTITY, UID.generate(), List.of())); } /** @@ -255,8 +258,8 @@ private static void addError(Reporter reporter, String message) { private static TrackerDto dummyDto() { return new TrackerDto() { @Override - public String getUid() { - return "uid"; + public UID getUid() { + return UID.generate(); } @Override diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/AttributeValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/AttributeValidatorTest.java index 734aa4c167e1..0ba5d168abd3 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/AttributeValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/AttributeValidatorTest.java @@ -39,7 +39,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.common.ValueType; import org.hisp.dhis.encryption.EncryptionStatus; import org.hisp.dhis.external.conf.DhisConfigurationProvider; @@ -48,7 +48,7 @@ import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; import org.hisp.dhis.trackedentityattributevalue.TrackedEntityAttributeValue; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Attribute; @@ -132,11 +132,11 @@ public void setUp() { when(dhisConfigurationProvider.getEncryptionStatus()) .thenReturn(EncryptionStatus.MISSING_ENCRYPTION_PASSWORD); - String uid = CodeGenerator.generateUid(); + UID uid = UID.generate(); when(enrollment.getUid()).thenReturn(uid); when(enrollment.getEnrollment()).thenReturn(uid); when(enrollment.getTrackerType()).thenCallRealMethod(); - enrollment.setTrackedEntity("trackedEntity"); + enrollment.setTrackedEntity(UID.generate()); bundle = TrackerBundle.builder().preheat(preheat).build(); @@ -165,6 +165,7 @@ void shouldPassValidationWhenCreatingEnrollmentAndMandatoryAttributeIsPresentOnl Arrays.asList( new TrackedEntityAttributeValue(trackedEntityAttribute, trackedEntity), new TrackedEntityAttributeValue(trackedEntityAttribute1, trackedEntity)))); + when(enrollment.getTrackedEntity()).thenReturn(UID.generate()); when(preheat.getTrackedEntity(enrollment.getTrackedEntity())).thenReturn(trackedEntity); bundle.setStrategy(enrollment, TrackerImportStrategy.CREATE); @@ -190,6 +191,7 @@ void shouldPassValidationWhenCreatingEnrollmentAndMandatoryAttributeIsPresentOnl when(enrollment.getAttributes()).thenReturn(Collections.singletonList(attribute)); when(trackedEntity.getTrackedEntityAttributeValues()).thenReturn(Set.of()); + when(enrollment.getTrackedEntity()).thenReturn(UID.generate()); when(preheat.getTrackedEntity(enrollment.getTrackedEntity())).thenReturn(trackedEntity); bundle.setStrategy(enrollment, TrackerImportStrategy.UPDATE); @@ -221,6 +223,7 @@ void shouldPassValidationWhenCreatingEnrollmentAndMandatoryAttributeIsPresentOnl when(enrollment.getAttributes()).thenReturn(Collections.singletonList(attribute)); when(trackedEntity.getTrackedEntityAttributeValues()).thenReturn(Set.of()); + when(enrollment.getTrackedEntity()).thenReturn(UID.generate()); when(preheat.getTrackedEntity(enrollment.getTrackedEntity())).thenReturn(trackedEntity); bundle.setStrategy(enrollment, TrackerImportStrategy.UPDATE); bundle.setTrackedEntities( @@ -257,6 +260,7 @@ void shouldPassValidationWhenCreatingEnrollmentAndMandatoryAttributeIsPresentOnl List.of( new TrackedEntityAttributeValue(trackedEntityAttribute, trackedEntity), new TrackedEntityAttributeValue(trackedEntityAttribute1, trackedEntity)))); + when(enrollment.getTrackedEntity()).thenReturn(UID.generate()); when(preheat.getTrackedEntity(enrollment.getTrackedEntity())).thenReturn(trackedEntity); bundle.setStrategy(enrollment, TrackerImportStrategy.UPDATE); @@ -285,6 +289,7 @@ void shouldFailValidationWhenCreatingEnrollmentAndValueIsNotPresentAndAttributeI new HashSet<>( Collections.singletonList( new TrackedEntityAttributeValue(trackedEntityAttribute, trackedEntity)))); + when(enrollment.getTrackedEntity()).thenReturn(UID.generate()); when(preheat.getTrackedEntity(enrollment.getTrackedEntity())).thenReturn(trackedEntity); bundle.setStrategy(enrollment, TrackerImportStrategy.CREATE); @@ -316,6 +321,7 @@ void shouldFailValidationWhenCreatingEnrollmentAndValueIsNullAndAttributeIsManda Arrays.asList( new TrackedEntityAttributeValue(trackedEntityAttribute, trackedEntity), new TrackedEntityAttributeValue(trackedEntityAttribute1, trackedEntity)))); + when(enrollment.getTrackedEntity()).thenReturn(UID.generate()); when(preheat.getTrackedEntity(enrollment.getTrackedEntity())).thenReturn(trackedEntity); bundle.setStrategy(enrollment, TrackerImportStrategy.CREATE); @@ -347,6 +353,7 @@ void shouldFailValidationWhenUpdatingEnrollmentAndValueIsNullAndAttributeIsManda Arrays.asList( new TrackedEntityAttributeValue(trackedEntityAttribute, trackedEntity), new TrackedEntityAttributeValue(trackedEntityAttribute1, trackedEntity)))); + when(enrollment.getTrackedEntity()).thenReturn(UID.generate()); when(preheat.getTrackedEntity(enrollment.getTrackedEntity())).thenReturn(trackedEntity); bundle.setStrategy(enrollment, TrackerImportStrategy.UPDATE); @@ -378,6 +385,7 @@ void shouldPassValidationWhenValueIsNullAndAttributeIsNotMandatory() { Arrays.asList( new TrackedEntityAttributeValue(trackedEntityAttribute, trackedEntity), new TrackedEntityAttributeValue(trackedEntityAttribute1, trackedEntity)))); + when(enrollment.getTrackedEntity()).thenReturn(UID.generate()); when(preheat.getTrackedEntity(enrollment.getTrackedEntity())).thenReturn(trackedEntity); validator.validate(reporter, bundle, enrollment); @@ -411,6 +419,7 @@ void shouldFailValidationWhenValueIsInvalidPercentage() { Arrays.asList( new TrackedEntityAttributeValue(trackedEntityAttribute, trackedEntity), new TrackedEntityAttributeValue(trackedEntityAttributeP, trackedEntity)))); + when(enrollment.getTrackedEntity()).thenReturn(UID.generate()); when(preheat.getTrackedEntity(enrollment.getTrackedEntity())).thenReturn(trackedEntity); validator.validate(reporter, bundle, enrollment); @@ -441,6 +450,7 @@ void shouldFailValidationWhenValueIsInvalidPercentage() { new HashSet<>( Collections.singletonList( new TrackedEntityAttributeValue(trackedEntityAttribute, trackedEntity)))); + when(enrollment.getTrackedEntity()).thenReturn(UID.generate()); when(preheat.getTrackedEntity(enrollment.getTrackedEntity())).thenReturn(trackedEntity); bundle.setStrategy(enrollment, TrackerImportStrategy.CREATE); @@ -474,6 +484,7 @@ void shouldFailValidationWhenValueIsInvalidPercentage() { new HashSet<>( Collections.singletonList( new TrackedEntityAttributeValue(trackedEntityAttribute, trackedEntity)))); + when(enrollment.getTrackedEntity()).thenReturn(UID.generate()); when(preheat.getTrackedEntity(enrollment.getTrackedEntity())).thenReturn(trackedEntity); bundle.setStrategy(enrollment, TrackerImportStrategy.UPDATE); @@ -498,6 +509,7 @@ void shouldFailValidationWhenAttributeIsNotPresentInDB() { new HashSet<>( Collections.singletonList( new TrackedEntityAttributeValue(trackedEntityAttribute, trackedEntity)))); + when(enrollment.getTrackedEntity()).thenReturn(UID.generate()); when(preheat.getTrackedEntity(enrollment.getTrackedEntity())).thenReturn(trackedEntity); validator.validate(reporter, bundle, enrollment); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/DataRelationsValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/DataRelationsValidatorTest.java index 5636bf448230..e3c731b14a2e 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/DataRelationsValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/DataRelationsValidatorTest.java @@ -34,13 +34,14 @@ import com.google.common.collect.Sets; import java.util.Collections; import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramType; import org.hisp.dhis.test.TestBase; import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentity.TrackedEntityType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Enrollment; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -67,7 +68,7 @@ class DataRelationsValidatorTest extends TestBase { private static final String ANOTHER_TE_TYPE_ID = "ANOTHER_TE_TYPE_ID"; - private static final String TE_ID = "TE_ID"; + private static final UID TE_ID = UID.generate(); private DataRelationsValidator validator; @@ -96,13 +97,13 @@ void verifyValidationSuccessForEnrollment() { .thenReturn(programWithRegistration(PROGRAM_UID, orgUnit, teType)); when(preheat.getProgramWithOrgUnitsMap()) .thenReturn(Collections.singletonMap(PROGRAM_UID, Collections.singletonList(ORG_UNIT_ID))); - when(preheat.getTrackedEntity(TE_ID)).thenReturn(trackedEntity(TE_TYPE_ID, teType, orgUnit)); + when(preheat.getTrackedEntity(TE_ID)).thenReturn(trackedEntity(TE_ID, teType, orgUnit)); Enrollment enrollment = Enrollment.builder() .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) .program(MetadataIdentifier.ofUid(PROGRAM_UID)) - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .trackedEntity(TE_ID) .build(); @@ -123,7 +124,8 @@ void verifyValidationFailsWhenEnrollmentIsNotARegistration() { Enrollment enrollment = Enrollment.builder() .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) + .trackedEntity(UID.generate()) .program(MetadataIdentifier.ofUid(PROGRAM_UID)) .build(); @@ -146,7 +148,8 @@ void verifyValidationFailsWhenEnrollmentAndProgramOrganisationUnitDontMatch() { Enrollment enrollment = Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) + .trackedEntity(UID.generate()) .program(MetadataIdentifier.ofUid(PROGRAM_UID)) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) .build(); @@ -164,13 +167,13 @@ void verifyValidationFailsWhenEnrollmentAndProgramTeiTypeDontMatch() { .thenReturn(programWithRegistration(PROGRAM_UID, orgUnit, trackedEntityType(TE_TYPE_ID))); when(preheat.getProgramWithOrgUnitsMap()) .thenReturn(Collections.singletonMap(PROGRAM_UID, Collections.singletonList(ORG_UNIT_ID))); - TrackedEntityType anotherTrackedEntityType = trackedEntityType(TE_ID, 'B'); + TrackedEntityType anotherTrackedEntityType = trackedEntityType(ANOTHER_TE_TYPE_ID, 'B'); when(preheat.getTrackedEntity(TE_ID)) .thenReturn(trackedEntity(TE_ID, anotherTrackedEntityType, orgUnit)); Enrollment enrollment = Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .program(MetadataIdentifier.ofUid(PROGRAM_UID)) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) .trackedEntity(TE_ID) @@ -200,7 +203,7 @@ void verifyValidationFailsWhenEnrollmentAndProgramTeiTypeDontMatchAndTeIsInPaylo Enrollment enrollment = Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .program(MetadataIdentifier.ofUid(PROGRAM_UID)) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) .trackedEntity(TE_ID) @@ -259,10 +262,9 @@ private TrackedEntityType trackedEntityType(String uid, char uniqueChar) { return trackedEntityType; } - private TrackedEntity trackedEntity( - String uid, TrackedEntityType type, OrganisationUnit orgUnit) { + private TrackedEntity trackedEntity(UID uid, TrackedEntityType type, OrganisationUnit orgUnit) { TrackedEntity te = createTrackedEntity(orgUnit); - te.setUid(uid); + te.setUid(uid.getValue()); te.setTrackedEntityType(type); return te; } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/DateValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/DateValidatorTest.java index b85f1a618769..4c05e068becf 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/DateValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/DateValidatorTest.java @@ -41,10 +41,13 @@ import java.time.Duration; import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.EnrollmentStatus; import org.hisp.dhis.program.Program; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Enrollment; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -86,7 +89,7 @@ public void setUp() { void testMandatoryDatesMustBePresent() { Enrollment enrollment = Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .program(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) .occurredAt(now()) .build(); @@ -100,10 +103,10 @@ void testMandatoryDatesMustBePresent() { @Test void testDatesMustNotBeInTheFuture() { - final Instant dateInTheFuture = now().plus(Duration.ofDays(2)); + final Instant dateInTheFuture = now().plus(Duration.ofDays(1)); Enrollment enrollment = Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .program(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) .occurredAt(dateInTheFuture) .enrolledAt(dateInTheFuture) @@ -118,12 +121,39 @@ void testDatesMustNotBeInTheFuture() { () -> assertHasError(reporter, enrollment, E1021)); } + @Test + void testDatesWithNoTimeZoneMustNotBeInTheFuture() { + ZoneId systemZone = ZoneId.systemDefault(); + LocalDate tomorrow = LocalDate.now(systemZone).plusDays(1); + Instant dateTomorrow = tomorrow.atStartOfDay(systemZone).toInstant(); + + // Create enrollment with dates set to tomorrow + Enrollment enrollment = + Enrollment.builder() + .enrollment(UID.generate()) + .program(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) + .occurredAt(dateTomorrow) + .enrolledAt(dateTomorrow) + .build(); + + when(preheat.getProgram(enrollment.getProgram())).thenReturn(new Program()); + + // Run validation + validator.validate(reporter, bundle, enrollment); + + // Assert that the future dates are detected as errors + assertAll( + () -> assertHasError(reporter, enrollment, E1020), // enrolledAt in the future + () -> assertHasError(reporter, enrollment, E1021) // occurredAt in the future + ); + } + @Test void testDatesShouldBeAllowedOnSameDayIfFutureDatesAreNotAllowed() { final Instant today = now().plus(Duration.ofMinutes(1)); Enrollment enrollment = Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .program(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) .occurredAt(today) .enrolledAt(today) @@ -141,7 +171,7 @@ void testDatesCanBeInTheFuture() { final Instant dateInTheFuture = now().plus(Duration.ofDays(2)); Enrollment enrollment = Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .program(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) .occurredAt(dateInTheFuture) .enrolledAt(dateInTheFuture) @@ -161,7 +191,7 @@ void testDatesCanBeInTheFuture() { void testFailOnMissingOccurredAtDate() { Enrollment enrollment = Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .program(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) .enrolledAt(now()) .build(); @@ -178,7 +208,7 @@ void testFailOnMissingOccurredAtDate() { @Test void shouldFailWhenCompletedAtIsPresentAndStatusIsNotCompleted() { Enrollment enrollment = new Enrollment(); - enrollment.setEnrollment(CodeGenerator.generateUid()); + enrollment.setEnrollment(UID.generate()); enrollment.setProgram(MetadataIdentifier.ofUid(CodeGenerator.generateUid())); enrollment.setOccurredAt(now()); enrollment.setCompletedAt(now()); @@ -197,7 +227,7 @@ void shouldFailWhenCompletedAtIsPresentAndStatusIsNotCompleted() { names = {"COMPLETED", "CANCELLED"}) void shouldValidateWhenCompletedAtIsPresentAndStatusAcceptCompletedAt(EnrollmentStatus status) { Enrollment enrollment = new Enrollment(); - enrollment.setEnrollment(CodeGenerator.generateUid()); + enrollment.setEnrollment(UID.generate()); enrollment.setProgram(MetadataIdentifier.ofUid(CodeGenerator.generateUid())); enrollment.setOccurredAt(now()); enrollment.setCompletedAt(now()); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/EnrollmentPreCheckExistenceValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/EnrollmentPreCheckExistenceValidatorTest.java index c5f18d858840..3a2be33c96d0 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/EnrollmentPreCheckExistenceValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/EnrollmentPreCheckExistenceValidatorTest.java @@ -35,8 +35,9 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.Enrollment; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.preheat.TrackerPreheat; @@ -52,11 +53,11 @@ */ @ExtendWith(MockitoExtension.class) class EnrollmentPreCheckExistenceValidatorTest { - private static final String SOFT_DELETED_ENROLLMENT_UID = "SoftDeletedEnrollmentId"; + private static final UID SOFT_DELETED_ENROLLMENT_UID = UID.generate(); - private static final String ENROLLMENT_UID = "EnrollmentId"; + private static final UID ENROLLMENT_UID = UID.generate(); - private static final String NOT_PRESENT_ENROLLMENT_UID = "NotPresentEnrollmentId"; + private static final UID NOT_PRESENT_ENROLLMENT_UID = UID.generate(); @Mock private TrackerBundle bundle; @@ -162,14 +163,14 @@ void verifyEnrollmentValidationFailsWhenIsUpdateAndEnrollmentIsNotPresent() { private Enrollment getSoftDeletedEnrollment() { Enrollment enrollment = new Enrollment(); - enrollment.setUid(SOFT_DELETED_ENROLLMENT_UID); + enrollment.setUid(SOFT_DELETED_ENROLLMENT_UID.getValue()); enrollment.setDeleted(true); return enrollment; } private Enrollment getEnrollment() { Enrollment enrollment = new Enrollment(); - enrollment.setUid(ENROLLMENT_UID); + enrollment.setUid(ENROLLMENT_UID.getValue()); enrollment.setDeleted(false); return enrollment; } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/ExistingEnrollmentValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/ExistingEnrollmentValidatorTest.java index d59a35776009..5f3b79f6ea38 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/ExistingEnrollmentValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/ExistingEnrollmentValidatorTest.java @@ -38,11 +38,12 @@ import java.util.Collections; import java.util.HashMap; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.Enrollment; import org.hisp.dhis.program.EnrollmentStatus; import org.hisp.dhis.program.Program; import org.hisp.dhis.trackedentity.TrackedEntity; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; import org.hisp.dhis.tracker.imports.preheat.TrackerPreheat; @@ -73,9 +74,11 @@ class ExistingEnrollmentValidatorTest { private static final String PROGRAM_UID = "program"; - private static final String TRACKED_ENTIT_UID = "trackedEntity"; + private static final UID TRACKED_ENTITY_UID = UID.generate(); - private static final String ENROLLMENT_UID = "enrollment"; + private static final UID ENROLLMENT_UID = UID.generate(); + + private static final UID ANOTHER_ENROLLMENT_UID = UID.generate(); @BeforeEach public void setUp() { @@ -84,14 +87,14 @@ public void setUp() { when(bundle.getPreheat()).thenReturn(preheat); when(preheat.getIdSchemes()).thenReturn(TrackerIdSchemeParams.builder().build()); when(enrollment.getProgram()).thenReturn(MetadataIdentifier.ofUid(PROGRAM_UID)); - when(enrollment.getTrackedEntity()).thenReturn(TRACKED_ENTIT_UID); + when(enrollment.getTrackedEntity()).thenReturn(TRACKED_ENTITY_UID); when(enrollment.getStatus()).thenReturn(EnrollmentStatus.ACTIVE); when(enrollment.getEnrollment()).thenReturn(ENROLLMENT_UID); when(enrollment.getUid()).thenReturn(ENROLLMENT_UID); when(enrollment.getTrackerType()).thenCallRealMethod(); - when(preheat.getTrackedEntity(TRACKED_ENTIT_UID)).thenReturn(trackedEntity); - when(trackedEntity.getUid()).thenReturn(TRACKED_ENTIT_UID); + when(preheat.getTrackedEntity(TRACKED_ENTITY_UID)).thenReturn(trackedEntity); + when(trackedEntity.getUid()).thenReturn(TRACKED_ENTITY_UID.getValue()); Program program = new Program(); program.setOnlyEnrollOnce(false); @@ -252,11 +255,11 @@ private void setTeInDb(EnrollmentStatus status) { Program program = new Program(); program.setUid(PROGRAM_UID); - enrollment.setUid("another_enrollment"); + enrollment.setUid(ANOTHER_ENROLLMENT_UID.getValue()); enrollment.setStatus(status); enrollment.setProgram(program); - put(TRACKED_ENTIT_UID, Collections.singletonList(enrollment)); + put(TRACKED_ENTITY_UID, Collections.singletonList(enrollment)); } }); } @@ -264,9 +267,9 @@ private void setTeInDb(EnrollmentStatus status) { private void setEnrollmentInPayload(EnrollmentStatus status) { org.hisp.dhis.tracker.imports.domain.Enrollment enrollmentInBundle = org.hisp.dhis.tracker.imports.domain.Enrollment.builder() - .enrollment("another_enrollment") + .enrollment(ANOTHER_ENROLLMENT_UID) .program(MetadataIdentifier.ofUid(PROGRAM_UID)) - .trackedEntity(TRACKED_ENTIT_UID) + .trackedEntity(TRACKED_ENTITY_UID) .status(status) .build(); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/GeoValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/GeoValidatorTest.java index f0ff951eaa1d..8ef22a56b4ae 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/GeoValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/GeoValidatorTest.java @@ -34,10 +34,10 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.when; -import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.organisationunit.FeatureType; import org.hisp.dhis.program.Program; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Enrollment; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -88,6 +88,7 @@ void testGeometryIsValid() { // given Enrollment enrollment = Enrollment.builder() + .enrollment(UID.generate()) .program(MetadataIdentifier.ofUid(PROGRAM)) .geometry(new GeometryFactory().createPoint()) .build(); @@ -117,7 +118,7 @@ void testProgramWithNullFeatureTypeFailsGeometryValidation() { // given Enrollment enrollment = Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .program(MetadataIdentifier.ofUid(PROGRAM)) .geometry(new GeometryFactory().createPoint()) .build(); @@ -137,7 +138,7 @@ void testProgramWithFeatureTypeNoneFailsGeometryValidation() { // given Enrollment enrollment = Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .program(MetadataIdentifier.ofUid(PROGRAM)) .geometry(new GeometryFactory().createPoint()) .build(); @@ -158,7 +159,7 @@ void testProgramWithFeatureTypeDifferentFromGeometryFails() { // given Enrollment enrollment = Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .program(MetadataIdentifier.ofUid(PROGRAM)) .geometry(new GeometryFactory().createPoint()) .build(); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/MandatoryFieldsValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/MandatoryFieldsValidatorTest.java index 211134e52d75..7bc4327575e8 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/MandatoryFieldsValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/MandatoryFieldsValidatorTest.java @@ -31,7 +31,8 @@ import static org.mockito.Mockito.when; import org.hisp.dhis.common.CodeGenerator; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.common.UID; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.ValidationMode; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; @@ -81,10 +82,10 @@ public void setUp() { void verifyEnrollmentValidationSuccess() { Enrollment enrollment = Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .orgUnit(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) .program(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) - .trackedEntity(CodeGenerator.generateUid()) + .trackedEntity(UID.generate()) .build(); validator.validate(reporter, bundle, enrollment); @@ -96,7 +97,7 @@ void verifyEnrollmentValidationSuccess() { void verifyEnrollmentValidationFailsOnMissingTrackedEntity() { Enrollment enrollment = Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .orgUnit(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) .program(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) .trackedEntity(null) @@ -111,10 +112,10 @@ void verifyEnrollmentValidationFailsOnMissingTrackedEntity() { void verifyEnrollmentValidationFailsOnMissingProgram() { Enrollment enrollment = Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .orgUnit(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) .program(MetadataIdentifier.EMPTY_UID) - .trackedEntity(CodeGenerator.generateUid()) + .trackedEntity(UID.generate()) .build(); validator.validate(reporter, bundle, enrollment); @@ -126,10 +127,10 @@ void verifyEnrollmentValidationFailsOnMissingProgram() { void verifyEnrollmentValidationFailsOnMissingOrgUnit() { Enrollment enrollment = Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .orgUnit(MetadataIdentifier.EMPTY_UID) .program(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) - .trackedEntity(CodeGenerator.generateUid()) + .trackedEntity(UID.generate()) .build(); validator.validate(reporter, bundle, enrollment); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/MetaValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/MetaValidatorTest.java index 82e4e3ca48f3..5af6e72e5d0e 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/MetaValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/MetaValidatorTest.java @@ -35,11 +35,11 @@ import static org.mockito.Mockito.when; import java.util.Optional; -import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.program.Program; import org.hisp.dhis.trackedentity.TrackedEntity; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Enrollment; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -58,7 +58,7 @@ class MetaValidatorTest { private static final String ORG_UNIT_UID = "OrgUnitUid"; - private static final String TRACKED_ENTITY_UID = "TrackedEntityUid"; + private static final UID TRACKED_ENTITY_UID = UID.generate(); private static final String PROGRAM_UID = "ProgramUid"; @@ -144,7 +144,7 @@ void verifyEnrollmentValidationFailsWhenProgramIsNotPresentInDb() { private Enrollment validEnrollment() { return Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .trackedEntity(TRACKED_ENTITY_UID) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_UID)) .program(MetadataIdentifier.ofUid(PROGRAM_UID)) diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/NoteValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/NoteValidatorTest.java index bd6ec2c70841..b06df407e1e8 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/NoteValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/NoteValidatorTest.java @@ -38,7 +38,7 @@ import java.util.Collections; import java.util.List; import org.hisp.dhis.test.random.BeanRandomizer; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Enrollment; import org.hisp.dhis.tracker.imports.domain.Note; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/SecurityOwnershipValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/SecurityOwnershipValidatorTest.java index 617430817021..f471e3731b0d 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/SecurityOwnershipValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/SecurityOwnershipValidatorTest.java @@ -40,8 +40,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.apache.commons.lang3.StringUtils; -import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.program.Enrollment; import org.hisp.dhis.program.EnrollmentStatus; @@ -54,8 +53,8 @@ import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentity.TrackedEntityProgramOwnerOrgUnit; import org.hisp.dhis.trackedentity.TrackedEntityType; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.acl.TrackerOwnershipManager; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -76,7 +75,7 @@ class SecurityOwnershipValidatorTest extends TestBase { private static final String ORG_UNIT_ID = "ORG_UNIT_ID"; - private static final String TE_ID = "TE_ID"; + private static final UID TE_ID = UID.generate(); private static final String TE_TYPE_ID = "TE_TYPE_ID"; @@ -106,7 +105,7 @@ class SecurityOwnershipValidatorTest extends TestBase { private TrackedEntity trackedEntity; - private Map> ownerOrgUnit; + private Map> ownerOrgUnit; @BeforeEach public void setUp() { @@ -130,13 +129,13 @@ public void setUp() { programStage.setUid(PS_ID); trackedEntity = new TrackedEntity(); - trackedEntity.setUid(TE_ID); + trackedEntity.setUid(TE_ID.getValue()); TrackerIdSchemeParams idSchemes = TrackerIdSchemeParams.builder().build(); reporter = new Reporter(idSchemes); TrackedEntityProgramOwnerOrgUnit owner = - new TrackedEntityProgramOwnerOrgUnit(TE_ID, PROGRAM_ID, organisationUnit); + new TrackedEntityProgramOwnerOrgUnit(TE_ID.getValue(), PROGRAM_ID, organisationUnit); ownerOrgUnit = Map.of(TE_ID, Map.of(PROGRAM_ID, owner)); validator = new SecurityOwnershipValidator(aclService, ownershipAccessManager); @@ -158,7 +157,7 @@ private UserDetails setUpUserWithOrgUnit() { void verifyValidationSuccessForEnrollment() { org.hisp.dhis.tracker.imports.domain.Enrollment enrollment = org.hisp.dhis.tracker.imports.domain.Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) .trackedEntity(TE_ID) .program(MetadataIdentifier.ofUid(PROGRAM_ID)) @@ -180,7 +179,7 @@ void verifyValidationSuccessForEnrollment() { void verifyCaptureScopeIsCheckedForEnrollmentCreation() { org.hisp.dhis.tracker.imports.domain.Enrollment enrollment = org.hisp.dhis.tracker.imports.domain.Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) .trackedEntity(TE_ID) .program(MetadataIdentifier.ofUid(PROGRAM_ID)) @@ -203,7 +202,7 @@ void verifyCaptureScopeIsCheckedForEnrollmentCreation() { @Test void verifyCaptureScopeIsCheckedForEnrollmentDeletion() { - String enrollmentUid = CodeGenerator.generateUid(); + UID enrollmentUid = UID.generate(); org.hisp.dhis.tracker.imports.domain.Enrollment enrollment = org.hisp.dhis.tracker.imports.domain.Enrollment.builder() .enrollment(enrollmentUid) @@ -221,7 +220,7 @@ void verifyCaptureScopeIsCheckedForEnrollmentDeletion() { when(aclService.canDataRead(userDetails, program.getTrackedEntityType())).thenReturn(true); when(preheat.getProgramOwner()).thenReturn(ownerOrgUnit); when(ownershipAccessManager.hasAccess( - userDetails, enrollment.getTrackedEntity(), organisationUnit, program)) + userDetails, enrollment.getTrackedEntity().getValue(), organisationUnit, program)) .thenReturn(true); validator.validate(reporter, bundle, enrollment); @@ -233,7 +232,7 @@ void verifyCaptureScopeIsCheckedForEnrollmentDeletion() { void verifyValidationSuccessForEnrollmentWithoutEventsUsingDeleteStrategy() { org.hisp.dhis.tracker.imports.domain.Enrollment enrollment = org.hisp.dhis.tracker.imports.domain.Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) .trackedEntity(TE_ID) .program(MetadataIdentifier.ofUid(PROGRAM_ID)) @@ -249,7 +248,7 @@ void verifyValidationSuccessForEnrollmentWithoutEventsUsingDeleteStrategy() { when(aclService.canDataRead(userDetails, program.getTrackedEntityType())).thenReturn(true); when(preheat.getProgramOwner()).thenReturn(ownerOrgUnit); when(ownershipAccessManager.hasAccess( - userDetails, enrollment.getTrackedEntity(), organisationUnit, program)) + userDetails, enrollment.getTrackedEntity().getValue(), organisationUnit, program)) .thenReturn(true); validator.validate(reporter, bundle, enrollment); @@ -261,7 +260,7 @@ void verifyValidationSuccessForEnrollmentWithoutEventsUsingDeleteStrategy() { void verifyValidationSuccessForEnrollmentUsingDeleteStrategyAndUserWithCascadeAuthority() { org.hisp.dhis.tracker.imports.domain.Enrollment enrollment = org.hisp.dhis.tracker.imports.domain.Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) .trackedEntity(TE_ID) .program(MetadataIdentifier.ofUid(PROGRAM_ID)) @@ -280,7 +279,7 @@ void verifyValidationSuccessForEnrollmentUsingDeleteStrategyAndUserWithCascadeAu when(aclService.canDataRead(userDetails, program.getTrackedEntityType())).thenReturn(true); when(preheat.getProgramOwner()).thenReturn(ownerOrgUnit); when(ownershipAccessManager.hasAccess( - userDetails, enrollment.getTrackedEntity(), organisationUnit, program)) + userDetails, enrollment.getTrackedEntity().getValue(), organisationUnit, program)) .thenReturn(true); validator.validate(reporter, bundle, enrollment); @@ -293,7 +292,7 @@ void verifyValidationSuccessForEnrollmentUsingDeleteStrategyAndUserWithCascadeAu verifyValidationFailsForEnrollmentWithoutEventsUsingDeleteStrategyAndUserNotInOrgUnitHierarchy() { org.hisp.dhis.tracker.imports.domain.Enrollment enrollment = org.hisp.dhis.tracker.imports.domain.Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) .trackedEntity(TE_ID) .program(MetadataIdentifier.ofUid(PROGRAM_ID)) @@ -317,7 +316,7 @@ void verifyValidationSuccessForEnrollmentUsingDeleteStrategyAndUserWithCascadeAu void verifyValidationFailsForEnrollmentUsingDeleteStrategyAndUserWithoutCascadeAuthority() { org.hisp.dhis.tracker.imports.domain.Enrollment enrollment = org.hisp.dhis.tracker.imports.domain.Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) .trackedEntity(TE_ID) .program(MetadataIdentifier.ofUid(PROGRAM_ID)) @@ -341,7 +340,7 @@ void verifyValidationFailsForEnrollmentUsingDeleteStrategyAndUserWithoutCascadeA @Test void verifyValidationFailsForEnrollmentDeletionAndUserWithoutProgramWriteAccess() { - String enrollmentUid = CodeGenerator.generateUid(); + UID enrollmentUid = UID.generate(); org.hisp.dhis.tracker.imports.domain.Enrollment enrollment = org.hisp.dhis.tracker.imports.domain.Enrollment.builder() .enrollment(enrollmentUid) @@ -366,7 +365,7 @@ void verifyValidationFailsForEnrollmentDeletionAndUserWithoutProgramWriteAccess( @Test void verifyValidationFailsForEnrollmentDeletionAndUserWithoutTrackedEntityTypeReadAccess() { - String enrollmentUid = CodeGenerator.generateUid(); + UID enrollmentUid = UID.generate(); org.hisp.dhis.tracker.imports.domain.Enrollment enrollment = org.hisp.dhis.tracker.imports.domain.Enrollment.builder() .enrollment(enrollmentUid) @@ -391,19 +390,19 @@ void verifyValidationFailsForEnrollmentDeletionAndUserWithoutTrackedEntityTypeRe private TrackedEntity teWithNoEnrollments() { TrackedEntity trackedEntity = createTrackedEntity(organisationUnit); - trackedEntity.setUid(TE_ID); + trackedEntity.setUid(TE_ID.getValue()); trackedEntity.setEnrollments(Sets.newHashSet()); trackedEntity.setTrackedEntityType(trackedEntityType); return trackedEntity; } - private Enrollment getEnrollment(String enrollmentUid) { - if (StringUtils.isEmpty(enrollmentUid)) { - enrollmentUid = CodeGenerator.generateUid(); + private Enrollment getEnrollment(UID enrollmentUid) { + if (enrollmentUid == null) { + enrollmentUid = UID.generate(); } Enrollment enrollment = new Enrollment(); - enrollment.setUid(enrollmentUid); + enrollment.setUid(enrollmentUid.getValue()); enrollment.setOrganisationUnit(organisationUnit); enrollment.setTrackedEntity(teWithNoEnrollments()); enrollment.setProgram(program); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/UidValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/UidValidatorTest.java deleted file mode 100644 index 038fb5dd89c6..000000000000 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/UidValidatorTest.java +++ /dev/null @@ -1,105 +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.imports.validation.validator.enrollment; - -import static org.hisp.dhis.test.utils.Assertions.assertIsEmpty; -import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1048; -import static org.hisp.dhis.tracker.imports.validation.validator.AssertValidations.assertHasError; - -import com.google.common.collect.Lists; -import org.hisp.dhis.common.CodeGenerator; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; -import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; -import org.hisp.dhis.tracker.imports.domain.Enrollment; -import org.hisp.dhis.tracker.imports.domain.Note; -import org.hisp.dhis.tracker.imports.preheat.TrackerPreheat; -import org.hisp.dhis.tracker.imports.validation.Reporter; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -/** - * @author Enrico Colasante - */ -class UidValidatorTest { - - private static final String INVALID_UID = "InvalidUID"; - - private UidValidator validator; - - private TrackerBundle bundle; - - private Reporter reporter; - - @BeforeEach - void setUp() { - TrackerPreheat preheat = new TrackerPreheat(); - TrackerIdSchemeParams idSchemes = TrackerIdSchemeParams.builder().build(); - preheat.setIdSchemes(idSchemes); - reporter = new Reporter(idSchemes); - bundle = TrackerBundle.builder().preheat(preheat).build(); - - validator = new UidValidator(); - } - - @Test - void verifyEnrollmentValidationSuccess() { - Note note = Note.builder().note(CodeGenerator.generateUid()).build(); - Enrollment enrollment = - Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) - .notes(Lists.newArrayList(note)) - .build(); - - validator.validate(reporter, bundle, enrollment); - - assertIsEmpty(reporter.getErrors()); - } - - @Test - void verifyEnrollmentWithInvalidUidFails() { - Enrollment enrollment = Enrollment.builder().enrollment(INVALID_UID).build(); - - validator.validate(reporter, bundle, enrollment); - - assertHasError(reporter, enrollment, E1048); - } - - @Test - void verifyEnrollmentWithNoteWithInvalidUidFails() { - Note note = Note.builder().note(INVALID_UID).build(); - Enrollment enrollment = - Enrollment.builder() - .enrollment(CodeGenerator.generateUid()) - .notes(Lists.newArrayList(note)) - .build(); - - validator.validate(reporter, bundle, enrollment); - - assertHasError(reporter, enrollment, E1048); - } -} diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/UpdatableFieldsValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/UpdatableFieldsValidatorTest.java index f0bc5d8427ca..17199703b36e 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/UpdatableFieldsValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/enrollment/UpdatableFieldsValidatorTest.java @@ -33,13 +33,14 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.Enrollment; import org.hisp.dhis.program.Event; import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramStage; import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentity.TrackedEntityType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -66,11 +67,11 @@ class UpdatableFieldsValidatorTest { private static final String PROGRAM_STAGE_ID = "ProgramStageId"; - private static final String TRACKED_ENTITY_ID = "TrackedEntityId"; + private static final UID TRACKED_ENTITY_ID = UID.generate(); - private static final String ENROLLMENT_ID = "EnrollmentId"; + private static final UID ENROLLMENT_ID = UID.generate(); - private static final String EVENT_ID = "EventId"; + private static final UID EVENT_UID = UID.generate(); private UpdatableFieldsValidator validator; @@ -95,7 +96,7 @@ public void setUp() { when(preheat.getTrackedEntity(TRACKED_ENTITY_ID)).thenReturn(trackedEntity()); when(preheat.getEnrollment(ENROLLMENT_ID)).thenReturn(getEnrollment()); - when(preheat.getEvent(EVENT_ID)).thenReturn(event()); + when(preheat.getEvent(EVENT_UID)).thenReturn(event()); when(bundle.getPreheat()).thenReturn(preheat); @@ -123,7 +124,7 @@ void verifyEnrollmentValidationFailsWhenUpdateProgram() { @Test void verifyEnrollmentValidationFailsWhenUpdateTrackedEntity() { org.hisp.dhis.tracker.imports.domain.Enrollment enrollment = validEnrollment(); - enrollment.setTrackedEntity("NewTrackedEntityId"); + enrollment.setTrackedEntity(UID.generate()); validator.validate(reporter, bundle, enrollment); @@ -147,7 +148,7 @@ private TrackedEntity trackedEntity() { trackedEntityType.setUid(TRACKED_ENTITY_TYPE_ID); TrackedEntity trackedEntity = new TrackedEntity(); - trackedEntity.setUid(TRACKED_ENTITY_ID); + trackedEntity.setUid(TRACKED_ENTITY_ID.getValue()); trackedEntity.setTrackedEntityType(trackedEntityType); return trackedEntity; } @@ -157,7 +158,7 @@ private Enrollment getEnrollment() { program.setUid(PROGRAM_ID); Enrollment enrollment = new Enrollment(); - enrollment.setUid(ENROLLMENT_ID); + enrollment.setUid(ENROLLMENT_ID.getValue()); enrollment.setProgram(program); enrollment.setTrackedEntity(trackedEntity()); return enrollment; @@ -168,7 +169,7 @@ private Event event() { programStage.setUid(PROGRAM_STAGE_ID); Event event = new Event(); - event.setUid(EVENT_ID); + event.setUid(EVENT_UID.getValue()); event.setEnrollment(getEnrollment()); event.setProgramStage(programStage); return event; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/AssignedUserValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/AssignedUserValidatorTest.java index 3df111d76d0d..fd7e46d81e06 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/AssignedUserValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/AssignedUserValidatorTest.java @@ -34,10 +34,10 @@ import static org.hisp.dhis.tracker.imports.validation.validator.AssertValidations.assertHasWarning; import com.google.common.collect.Sets; -import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.ProgramStage; import org.hisp.dhis.test.TestBase; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Event; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -139,7 +139,7 @@ void testAssignedUserIsEmpty() { void testEventWithNotValidUsername() { // given Event event = new Event(); - event.setEvent(CodeGenerator.generateUid()); + event.setEvent(UID.generate()); event.setAssignedUser(INVALID_USER); event.setProgramStage(MetadataIdentifier.ofUid(PROGRAM_STAGE)); @@ -154,7 +154,7 @@ void testEventWithNotValidUsername() { void testEventWithUserNotPresentInPreheat() { // given Event event = new Event(); - event.setEvent(CodeGenerator.generateUid()); + event.setEvent(UID.generate()); event.setAssignedUser(VALID_USER); event.setProgramStage(MetadataIdentifier.ofUid(PROGRAM_STAGE)); @@ -173,7 +173,7 @@ void testEventWithUserNotPresentInPreheat() { void testEventWithNotEnabledUserAssignment() { // given Event event = new Event(); - event.setEvent(CodeGenerator.generateUid()); + event.setEvent(UID.generate()); event.setAssignedUser(VALID_USER); event.setProgramStage(MetadataIdentifier.ofUid(PROGRAM_STAGE)); @@ -191,7 +191,7 @@ void testEventWithNotEnabledUserAssignment() { void testEventWithNullEnabledUserAssignment() { // given Event event = new Event(); - event.setEvent(CodeGenerator.generateUid()); + event.setEvent(UID.generate()); event.setAssignedUser(VALID_USER); event.setProgramStage(MetadataIdentifier.ofUid(PROGRAM_STAGE)); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/CategoryOptValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/CategoryOptValidatorTest.java index e2020b989cfb..ba369849f78a 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/CategoryOptValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/CategoryOptValidatorTest.java @@ -41,13 +41,13 @@ import org.hisp.dhis.category.CategoryCombo; import org.hisp.dhis.category.CategoryOption; import org.hisp.dhis.category.CategoryOptionCombo; -import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.i18n.I18nFormat; import org.hisp.dhis.i18n.I18nManager; import org.hisp.dhis.program.Program; import org.hisp.dhis.test.TestBase; import org.hisp.dhis.test.mock.MockI18nFormat; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Event; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -127,7 +127,7 @@ public void setUp() { program.setCategoryCombo(catCombo); event = new Event(); - event.setEvent(CodeGenerator.generateUid()); + event.setEvent(UID.generate()); event.setProgram(MetadataIdentifier.ofUid(program)); event.setOccurredAt(EVENT_INSTANT); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/DataRelationsValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/DataRelationsValidatorTest.java index e4f30fcf68d6..2dbc5376c3ba 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/DataRelationsValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/DataRelationsValidatorTest.java @@ -47,6 +47,7 @@ import org.hisp.dhis.category.CategoryOptionCombo; import org.hisp.dhis.common.CodeGenerator; import org.hisp.dhis.common.DataDimensionType; +import org.hisp.dhis.common.UID; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.program.Enrollment; import org.hisp.dhis.program.Program; @@ -55,7 +56,7 @@ import org.hisp.dhis.test.TestBase; import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentity.TrackedEntityType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Event; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -82,7 +83,7 @@ class DataRelationsValidatorTest extends TestBase { private static final String TE_TYPE_ID = "TE_TYPE_ID"; - private static final String ENROLLMENT_ID = "ENROLLMENT_ID"; + private static final UID ENROLLMENT_ID = UID.generate(); private DataRelationsValidator validator; @@ -118,7 +119,7 @@ void eventValidationSucceedsWhenAOCAndCOsAreNotSetAndProgramHasDefaultCC() { Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .program(MetadataIdentifier.ofUid(program)) .programStage(MetadataIdentifier.ofUid(PROGRAM_STAGE_ID)) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) @@ -148,7 +149,8 @@ void eventValidationFailsWhenEventAndProgramStageProgramDontMatch() { Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) + .enrollment(ENROLLMENT_ID) .program(MetadataIdentifier.ofUid(program)) .programStage(MetadataIdentifier.ofUid(PROGRAM_STAGE_ID)) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) @@ -175,7 +177,7 @@ void eventValidationFailsWhenProgramIsRegistrationAndEnrollmentIsMissing() { Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .program(MetadataIdentifier.ofUid(program)) .programStage(MetadataIdentifier.ofUid(PROGRAM_STAGE_ID)) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) @@ -206,7 +208,7 @@ void eventValidationFailsWhenEventAndEnrollmentProgramDontMatch() { Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .program(MetadataIdentifier.ofUid(program)) .programStage(MetadataIdentifier.ofUid(PROGRAM_STAGE_ID)) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) @@ -238,7 +240,7 @@ void eventValidationFailsWhenEventAndProgramOrganisationUnitDontMatch() { Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .program(MetadataIdentifier.ofUid(program)) .programStage(MetadataIdentifier.ofUid(PROGRAM_STAGE_ID)) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) @@ -769,7 +771,7 @@ void eventOfProgramWithRegistrationInEnrollmentWithTrackedEntityInBundle() { org.hisp.dhis.tracker.imports.domain.Enrollment e = new org.hisp.dhis.tracker.imports.domain.Enrollment(); - e.setTrackedEntity(CodeGenerator.generateUid()); + e.setTrackedEntity(UID.generate()); when(bundle.findEnrollmentByUid(ENROLLMENT_ID)).thenReturn(Optional.of(e)); @@ -857,23 +859,23 @@ private ProgramStage programStage(String uid, Program program) { return programStage; } - private Enrollment enrollment(String uid, Program program) { + private Enrollment enrollment(UID uid, Program program) { Enrollment enrollment = enrollment(uid); enrollment.setProgram(program); enrollment.setTrackedEntity(new TrackedEntity()); return enrollment; } - private Enrollment enrollment(String uid, TrackedEntity trackedEntity) { + private Enrollment enrollment(UID uid, TrackedEntity trackedEntity) { Enrollment enrollment = new Enrollment(); - enrollment.setUid(uid); + enrollment.setUid(uid.getValue()); enrollment.setTrackedEntity(trackedEntity); return enrollment; } - private Enrollment enrollment(String uid) { + private Enrollment enrollment(UID uid) { Enrollment enrollment = new Enrollment(); - enrollment.setUid(uid); + enrollment.setUid(uid.getValue()); return enrollment; } @@ -959,7 +961,7 @@ private CategoryOptionCombo firstCategoryOptionCombo(CategoryCombo categoryCombo private Event.EventBuilder eventBuilder() { return Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .program(MetadataIdentifier.ofUid(PROGRAM_UID)) .programStage(MetadataIdentifier.ofUid(PROGRAM_STAGE_ID)) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/DataValuesValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/DataValuesValidatorTest.java index 3d887dea4d06..9c46f774365b 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/DataValuesValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/DataValuesValidatorTest.java @@ -36,7 +36,7 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.common.ValueType; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.event.EventStatus; @@ -48,8 +48,8 @@ import org.hisp.dhis.program.ProgramStage; import org.hisp.dhis.program.ProgramStageDataElement; import org.hisp.dhis.program.ValidationStrategy; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.DataValue; @@ -138,6 +138,7 @@ void successValidationWhenDataElementIsValid() { Event event = Event.builder() + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.ACTIVE) .dataValues(Set.of(dataValue())) @@ -159,7 +160,7 @@ void failValidationWhenDataElementIsInvalid() { Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.SKIPPED) .dataValues(Set.of(dataValue())) @@ -194,7 +195,7 @@ void shouldFailValidationWhenAMandatoryDataElementIsMissingAndStrategyIsCreate() Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.COMPLETED) .dataValues(Set.of(dataValue())) @@ -230,6 +231,7 @@ void succeedsWhenMandatoryDataElementIsNotPresentButMandatoryValidationIsNotNeed Event event = Event.builder() + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.ACTIVE) .dataValues(Set.of(dataValue())) @@ -245,7 +247,7 @@ void succeedsWhenMandatoryDataElementIsNotPresentButMandatoryValidationIsNotNeed void shouldPassValidationWhenAMandatoryDataElementIsMissingAndDataValueIsAlreadyPresentInDB( EventStatus savedStatus, EventStatus newStatus) { DataElement dataElement = dataElement(); - String eventUid = CodeGenerator.generateUid(); + UID eventUid = UID.generate(); when(preheat.getDataElement(MetadataIdentifier.ofUid(DATA_ELEMENT_UID))) .thenReturn(dataElement); @@ -286,7 +288,7 @@ void shouldPassValidationWhenAMandatoryDataElementIsMissingAndDataValueIsAlready void shouldFailValidationWhenAMandatoryDataElementIsMissingAndDataValuesAreCreated( EventStatus savedStatus, EventStatus newStatus) { DataElement dataElement = dataElement(); - String eventUid = CodeGenerator.generateUid(); + UID eventUid = UID.generate(); when(preheat.getDataElement(MetadataIdentifier.ofUid(DATA_ELEMENT_UID))) .thenReturn(dataElement); @@ -345,6 +347,7 @@ void succeedsWhenMandatoryDataElementIsPartOfProgramStageAndIdSchemeIsSetToCode( dataValue.setDataElement(MetadataIdentifier.ofCode("DE_424050")); Event event = Event.builder() + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.COMPLETED) .dataValues(Set.of(dataValue)) @@ -380,7 +383,7 @@ void failValidationWhenDataElementIsNotPresentInProgramStage() { notPresentDataValue.setDataElement(MetadataIdentifier.ofUid("de_not_present_in_program_stage")); Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.ACTIVE) .dataValues(Set.of(dataValue(), notPresentDataValue)) @@ -415,6 +418,7 @@ void succeedsWhenDataElementIsPartOfProgramStageAndIdSchemeIsSetToCode() { dataValue.setDataElement(MetadataIdentifier.ofCode("DE_424050")); Event event = Event.builder() + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.ACTIVE) .dataValues(Set.of(dataValue)) @@ -440,7 +444,7 @@ void failValidationWhenFileResourceIsNull() { Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.SKIPPED) .dataValues(Set.of(validDataValue)) @@ -468,6 +472,7 @@ void successValidationWhenFileResourceValueIsNullAndDataElementIsNotCompulsory() validDataValue.setValue(null); Event event = Event.builder() + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.COMPLETED) .dataValues(Set.of(validDataValue)) @@ -492,7 +497,7 @@ void failValidationWhenFileResourceValueIsNullAndDataElementIsCompulsory() { validDataValue.setValue(null); Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.COMPLETED) .dataValues(Set.of(validDataValue)) @@ -518,7 +523,7 @@ void shouldFailValidationWhenDataElementValueNullAndStrategyCreate() { validDataValue.setValue(null); Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.ACTIVE) .dataValues(Set.of(validDataValue)) @@ -532,7 +537,7 @@ void shouldFailValidationWhenDataElementValueNullAndStrategyCreate() { @Test void shouldFailValidationWhenDataElementValueNullAndStrategyUpdate() { DataElement validDataElement = dataElement(); - String eventUid = CodeGenerator.generateUid(); + UID eventUid = UID.generate(); when(preheat.getDataElement(MetadataIdentifier.ofUid(DATA_ELEMENT_UID))) .thenReturn(validDataElement); @@ -572,7 +577,7 @@ void failsOnCompletedEventWithDataElementValueNullAndValidationStrategyOnUpdate( validDataValue.setValue(null); Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.COMPLETED) .dataValues(Set.of(validDataValue)) @@ -598,6 +603,7 @@ void succeedsOnActiveEventWithDataElementValueIsNullAndValidationStrategyOnCompl validDataValue.setValue(null); Event event = Event.builder() + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.ACTIVE) .dataValues(Set.of(validDataValue)) @@ -623,7 +629,7 @@ void failsOnCompletedEventWithDataElementValueIsNullAndValidationStrategyOnCompl validDataValue.setValue(null); Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.COMPLETED) .dataValues(Set.of(validDataValue)) @@ -648,7 +654,7 @@ void shouldFailWhenScheduledEventHasDataValueDefined() { validDataValue.setValue("1"); Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.SCHEDULE) .dataValues(Set.of(validDataValue)) @@ -673,7 +679,7 @@ void shouldFailValidationWhenSkippedEventHasDataValueDefined() { validDataValue.setValue("1"); Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.SKIPPED) .dataValues(Set.of(validDataValue)) @@ -698,7 +704,7 @@ void shouldFailValidationWhenOverdueEventHasDataValueDefined() { validDataValue.setValue("1"); Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.OVERDUE) .dataValues(Set.of(validDataValue)) @@ -723,6 +729,7 @@ void successValidationWhenDataElementIsNullAndDataElementIsNotCompulsory() { validDataValue.setValue(null); Event event = Event.builder() + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.COMPLETED) .dataValues(Set.of(validDataValue)) @@ -749,7 +756,7 @@ void failValidationWhenFileResourceIsAlreadyAssigned() { when(preheat.get(FileResource.class, validDataValue.getValue())).thenReturn(fileResource); Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.ACTIVE) .dataValues(Set.of(validDataValue)) @@ -786,7 +793,7 @@ void validateFileResourceOwner() { when(preheat.get(FileResource.class, validDataValue.getValue())).thenReturn(fileResource); Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.ACTIVE) .dataValues(Set.of(validDataValue)) @@ -798,8 +805,9 @@ void validateFileResourceOwner() { assertHasError(reporter, event, ValidationCode.E1009); - event.setEvent("XYZ"); - fileResource.setFileResourceOwner("ABC"); + event.setEvent(UID.generate()); + UID uid = UID.generate(); + fileResource.setFileResourceOwner(uid.getValue()); when(bundle.getStrategy(event)).thenReturn(TrackerImportStrategy.UPDATE); @@ -809,8 +817,8 @@ void validateFileResourceOwner() { assertHasError(reporter, event, ValidationCode.E1009); - event.setEvent("ABC"); - fileResource.setFileResourceOwner("ABC"); + event.setEvent(uid); + fileResource.setFileResourceOwner(uid.getValue()); when(bundle.getStrategy(event)).thenReturn(TrackerImportStrategy.UPDATE); @@ -861,6 +869,7 @@ void successValidationDataElementOptionValueIsValid() { Event event = Event.builder() + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.ACTIVE) .dataValues(Set.of(validDataValue, nullDataValue)) @@ -894,7 +903,7 @@ void failValidationDataElementOptionValueIsInValid() { Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.SKIPPED) .dataValues(Set.of(validDataValue)) @@ -928,6 +937,7 @@ void successValidationDataElementMultiTextOptionValueIsValid() { Event event = Event.builder() + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.ACTIVE) .dataValues(Set.of(validDataValue, nullDataValue)) @@ -961,7 +971,7 @@ void failValidationDataElementMultiTextOptionValueIsInValid() { Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.SKIPPED) .dataValues(Set.of(validDataValue)) @@ -987,7 +997,7 @@ void failValidationWhenOrgUnitValueIsInvalid() { Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.ACTIVE) .dataValues(Set.of(invalidDataValue)) @@ -1015,6 +1025,7 @@ void succeedsValidationWhenOrgUnitValueIsValid() { Event event = Event.builder() + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.ACTIVE) .dataValues(Set.of(validDataValue)) @@ -1039,7 +1050,7 @@ private void runAndAssertValidationForDataValue(ValueType valueType, String valu validDataValue.setValue(value); Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .programStage(idSchemes.toMetadataIdentifier(programStage)) .status(EventStatus.SKIPPED) .dataValues(Set.of(validDataValue)) @@ -1051,10 +1062,9 @@ private void runAndAssertValidationForDataValue(ValueType valueType, String valu assertHasError(reporter, event, ValidationCode.E1302); } - private org.hisp.dhis.program.Event event( - String uid, EventStatus status, Set dataElements) { + private org.hisp.dhis.program.Event event(UID uid, EventStatus status, Set dataElements) { org.hisp.dhis.program.Event event = new org.hisp.dhis.program.Event(); - event.setUid(uid); + event.setUid(uid.getValue()); event.setStatus(status); event.setEventDataValues( dataElements.stream() @@ -1063,9 +1073,9 @@ private org.hisp.dhis.program.Event event( return event; } - private org.hisp.dhis.program.Event event(String uid, EventStatus status) { + private org.hisp.dhis.program.Event event(UID uid, EventStatus status) { org.hisp.dhis.program.Event event = new org.hisp.dhis.program.Event(); - event.setUid(uid); + event.setUid(uid.getValue()); event.setStatus(status); return event; } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/DateValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/DateValidatorTest.java index 52eca791b667..61d539943f20 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/DateValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/DateValidatorTest.java @@ -40,13 +40,13 @@ import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; -import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.event.EventStatus; import org.hisp.dhis.period.DailyPeriodType; import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramType; import org.hisp.dhis.test.TestBase; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Event; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -111,7 +111,7 @@ void testEventIsNotValidWhenOccurredDateIsNotPresentAndProgramIsWithoutRegistrat when(preheat.getProgram(MetadataIdentifier.ofUid(PROGRAM_WITHOUT_REGISTRATION_ID))) .thenReturn(getProgramWithoutRegistration()); Event event = new Event(); - event.setEvent(CodeGenerator.generateUid()); + event.setEvent(UID.generate()); event.setProgram(MetadataIdentifier.ofUid(PROGRAM_WITHOUT_REGISTRATION_ID)); // when @@ -127,7 +127,7 @@ void testEventIsNotValidWhenOccurredDateIsNotPresentAndEventIsActive() { when(preheat.getProgram(MetadataIdentifier.ofUid(PROGRAM_WITH_REGISTRATION_ID))) .thenReturn(getProgramWithRegistration()); Event event = new Event(); - event.setEvent(CodeGenerator.generateUid()); + event.setEvent(UID.generate()); event.setProgram(MetadataIdentifier.ofUid(PROGRAM_WITH_REGISTRATION_ID)); event.setStatus(EventStatus.ACTIVE); @@ -144,7 +144,7 @@ void testEventIsNotValidWhenOccurredDateIsNotPresentAndEventIsCompleted() { when(preheat.getProgram(MetadataIdentifier.ofUid(PROGRAM_WITH_REGISTRATION_ID))) .thenReturn(getProgramWithRegistration()); Event event = new Event(); - event.setEvent(CodeGenerator.generateUid()); + event.setEvent(UID.generate()); event.setProgram(MetadataIdentifier.ofUid(PROGRAM_WITH_REGISTRATION_ID)); event.setStatus(EventStatus.COMPLETED); @@ -161,7 +161,7 @@ void testEventIsNotValidWhenScheduledDateIsNotPresentAndEventIsSchedule() { when(preheat.getProgram(MetadataIdentifier.ofUid(PROGRAM_WITH_REGISTRATION_ID))) .thenReturn(getProgramWithRegistration()); Event event = new Event(); - event.setEvent(CodeGenerator.generateUid()); + event.setEvent(UID.generate()); event.setProgram(MetadataIdentifier.ofUid(PROGRAM_WITH_REGISTRATION_ID)); event.setOccurredAt(Instant.now()); event.setStatus(EventStatus.SCHEDULE); @@ -179,7 +179,7 @@ void shouldFailWhenCompletedAtIsPresentAndStatusIsNotCompleted() { when(preheat.getProgram(MetadataIdentifier.ofUid(PROGRAM_WITH_REGISTRATION_ID))) .thenReturn(getProgramWithRegistration()); Event event = new Event(); - event.setEvent(CodeGenerator.generateUid()); + event.setEvent(UID.generate()); event.setProgram(MetadataIdentifier.ofUid(PROGRAM_WITH_REGISTRATION_ID)); event.setOccurredAt(now()); event.setCompletedAt(now()); @@ -196,7 +196,7 @@ void testEventIsNotValidWhenCompletedAtIsTooSoonAndEventIsCompleted() { when(preheat.getProgram(MetadataIdentifier.ofUid(PROGRAM_WITH_REGISTRATION_ID))) .thenReturn(getProgramWithRegistration()); Event event = new Event(); - event.setEvent(CodeGenerator.generateUid()); + event.setEvent(UID.generate()); event.setProgram(MetadataIdentifier.ofUid(PROGRAM_WITH_REGISTRATION_ID)); event.setOccurredAt(now()); event.setCompletedAt(sevenDaysAgo()); @@ -215,7 +215,7 @@ void testEventIsNotValidWhenOccurredAtAndScheduledAtAreNotPresent() { when(preheat.getProgram(MetadataIdentifier.ofUid(PROGRAM_WITH_REGISTRATION_ID))) .thenReturn(getProgramWithRegistration()); Event event = new Event(); - event.setEvent(CodeGenerator.generateUid()); + event.setEvent(UID.generate()); event.setProgram(MetadataIdentifier.ofUid(PROGRAM_WITH_REGISTRATION_ID)); event.setOccurredAt(null); event.setScheduledAt(null); @@ -234,7 +234,7 @@ void testEventIsNotValidWhenDateBelongsToExpiredPeriod() { when(preheat.getProgram(MetadataIdentifier.ofUid(PROGRAM_WITH_REGISTRATION_ID))) .thenReturn(getProgramWithRegistration()); Event event = new Event(); - event.setEvent(CodeGenerator.generateUid()); + event.setEvent(UID.generate()); event.setProgram(MetadataIdentifier.ofUid(PROGRAM_WITH_REGISTRATION_ID)); event.setOccurredAt(sevenDaysAgo()); event.setStatus(EventStatus.ACTIVE); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/ExistenceValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/ExistenceValidatorTest.java index 2b3a195e0fb0..0ece4b0e032e 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/ExistenceValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/ExistenceValidatorTest.java @@ -35,8 +35,9 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.Event; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.preheat.TrackerPreheat; @@ -52,11 +53,11 @@ */ @ExtendWith(MockitoExtension.class) class ExistenceValidatorTest { - private static final String SOFT_DELETED_EVENT_UID = "SoftDeletedEventId"; + private static final UID SOFT_DELETED_EVENT_UID = UID.generate(); - private static final String EVENT_UID = "EventId"; + private static final UID EVENT_UID = UID.generate(); - private static final String NOT_PRESENT_EVENT_UID = "NotPresentEventId"; + private static final UID NOT_PRESENT_EVENT_UID = UID.generate(); @Mock private TrackerBundle bundle; @@ -150,14 +151,14 @@ void verifyEventValidationFailsWhenIsUpdateAndEventIsNotPresent() { private Event getSoftDeletedEvent() { Event event = new Event(); - event.setUid(SOFT_DELETED_EVENT_UID); + event.setUid(SOFT_DELETED_EVENT_UID.getValue()); event.setDeleted(true); return event; } private Event getEvent() { Event event = new Event(); - event.setUid(EVENT_UID); + event.setUid(EVENT_UID.getValue()); event.setDeleted(false); return event; } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/GeoValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/GeoValidatorTest.java index ea1f4f5af93c..765ec11c8424 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/GeoValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/GeoValidatorTest.java @@ -36,10 +36,10 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.when; -import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.organisationunit.FeatureType; import org.hisp.dhis.program.ProgramStage; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Event; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -116,7 +116,7 @@ void testEventWithNoProgramStageThrowsAnError() { void testProgramStageWithNullFeatureTypeFailsGeometryValidation() { // given Event event = new Event(); - event.setEvent(CodeGenerator.generateUid()); + event.setEvent(UID.generate()); event.setProgramStage(MetadataIdentifier.ofUid(PROGRAM_STAGE)); event.setGeometry(new GeometryFactory().createPoint()); @@ -133,7 +133,7 @@ void testProgramStageWithNullFeatureTypeFailsGeometryValidation() { void testProgramStageWithFeatureTypeNoneFailsGeometryValidation() { // given Event event = new Event(); - event.setEvent(CodeGenerator.generateUid()); + event.setEvent(UID.generate()); event.setProgramStage(MetadataIdentifier.ofUid(PROGRAM_STAGE)); event.setGeometry(new GeometryFactory().createPoint()); @@ -152,7 +152,7 @@ void testProgramStageWithFeatureTypeNoneFailsGeometryValidation() { void testProgramStageWithFeatureTypeDifferentFromGeometryFails() { // given Event event = new Event(); - event.setEvent(CodeGenerator.generateUid()); + event.setEvent(UID.generate()); event.setProgramStage(MetadataIdentifier.ofUid(PROGRAM_STAGE)); event.setGeometry(new GeometryFactory().createPoint()); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/MandatoryFieldsValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/MandatoryFieldsValidatorTest.java index 3986b19849ba..9578d73eded5 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/MandatoryFieldsValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/MandatoryFieldsValidatorTest.java @@ -36,8 +36,9 @@ import static org.mockito.Mockito.when; import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.ProgramStage; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.ValidationMode; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; @@ -87,7 +88,7 @@ public void setUp() { void verifyEventValidationSuccess() { Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .orgUnit(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) .programStage(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) .program(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) @@ -102,7 +103,7 @@ void verifyEventValidationSuccess() { void verifyEventValidationFailsOnMissingProgram() { Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .orgUnit(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) .programStage(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) .program(MetadataIdentifier.EMPTY_UID) @@ -117,7 +118,7 @@ void verifyEventValidationFailsOnMissingProgram() { void verifyEventValidationFailsOnMissingProgramStageReferenceToProgram() { Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .orgUnit(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) .programStage(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) .build(); @@ -136,7 +137,7 @@ void verifyEventValidationFailsOnMissingProgramStageReferenceToProgram() { void verifyEventValidationFailsOnMissingProgramStage() { Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .orgUnit(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) .programStage(MetadataIdentifier.EMPTY_UID) .program(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) @@ -151,7 +152,7 @@ void verifyEventValidationFailsOnMissingProgramStage() { void verifyEventValidationFailsOnMissingOrgUnit() { Event event = Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .orgUnit(MetadataIdentifier.EMPTY_UID) .programStage(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) .program(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/MetaValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/MetaValidatorTest.java index c5238fda4359..2310914f10b6 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/MetaValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/MetaValidatorTest.java @@ -34,11 +34,11 @@ import static org.hisp.dhis.tracker.imports.validation.validator.AssertValidations.assertHasError; import static org.mockito.Mockito.when; -import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramStage; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Event; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -137,7 +137,7 @@ void verifyEventValidationFailsWhenOrgUnitIsNotPresentInDb() { private Event validEvent() { return Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .programStage(MetadataIdentifier.ofUid(PROGRAM_STAGE_UID)) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_UID)) .program(MetadataIdentifier.ofUid(PROGRAM_UID)) diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/NoteValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/NoteValidatorTest.java index f30cb377c27c..3c9f6380a4cb 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/NoteValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/NoteValidatorTest.java @@ -38,7 +38,7 @@ import java.util.Collections; import java.util.List; import org.hisp.dhis.test.random.BeanRandomizer; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Event; import org.hisp.dhis.tracker.imports.domain.Note; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/RepeatedEventsValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/RepeatedEventsValidatorTest.java index 6775b9626990..0ee21aa9eeb8 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/RepeatedEventsValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/RepeatedEventsValidatorTest.java @@ -35,14 +35,14 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.mockito.Mockito.when; -import com.google.common.collect.Lists; import java.util.List; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.Enrollment; import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramStage; import org.hisp.dhis.program.ProgramType; import org.hisp.dhis.test.TestBase; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Event; @@ -71,9 +71,9 @@ class RepeatedEventsValidatorTest extends TestBase { private static final String NOT_REPEATABLE_PROGRAM_STAGE_WITHOUT_REGISTRATION = "NOT_REPEATABLE_PROGRAM_STAGE_WITHOUT_REGISTRATION"; - private static final String ENROLLMENT_A = "ENROLLMENT_A"; + private static final UID ENROLLMENT_A = UID.generate(); - private static final String ENROLLMENT_B = "ENROLLMENT_B"; + private static final UID ENROLLMENT_B = UID.generate(); private RepeatedEventsValidator validator; @@ -99,7 +99,7 @@ void testSingleEventIsPassingValidation() { when(preheat.getProgramStage( MetadataIdentifier.ofUid(NOT_REPEATABLE_PROGRAM_STAGE_WITH_REGISTRATION))) .thenReturn(notRepeatebleProgramStageWithRegistration()); - List events = Lists.newArrayList(notRepeatableEvent("A")); + List events = List.of(notRepeatableEvent()); bundle.setEvents(events); events.forEach(e -> bundle.setStrategy(e, TrackerImportStrategy.CREATE_AND_UPDATE)); @@ -114,17 +114,17 @@ void testOneEventInNotRepeatableProgramStageAndOneAlreadyOnDBAreNotPassingValida MetadataIdentifier.ofUid(NOT_REPEATABLE_PROGRAM_STAGE_WITH_REGISTRATION))) .thenReturn(notRepeatebleProgramStageWithRegistration()); // given - Event event = notRepeatableEvent("A"); + Event event = notRepeatableEvent(); Enrollment enrollment = new Enrollment(); - enrollment.setUid(event.getEnrollment()); + enrollment.setUid(event.getEnrollment().getValue()); // when bundle.setStrategy(event, TrackerImportStrategy.CREATE); - when(preheat.getEnrollment(event.getEnrollment())).thenReturn(enrollment); - when(preheat.hasProgramStageWithEvents(event.getProgramStage(), event.getEnrollment())) + when(preheat.hasProgramStageWithEvents( + event.getProgramStage(), event.getEnrollment().getValue())) .thenReturn(true); - bundle.setEvents(Lists.newArrayList(event)); + bundle.setEvents(List.of(event)); validator.validate(reporter, bundle, bundle.getEvents()); @@ -143,7 +143,7 @@ void testTwoEventInNotRepeatableProgramStageAreNotPassingValidation() { when(preheat.getProgramStage( MetadataIdentifier.ofUid(NOT_REPEATABLE_PROGRAM_STAGE_WITH_REGISTRATION))) .thenReturn(notRepeatebleProgramStageWithRegistration()); - List events = Lists.newArrayList(notRepeatableEvent("A"), notRepeatableEvent("B")); + List events = List.of(notRepeatableEvent(), notRepeatableEvent()); bundle.setEvents(events); events.forEach(e -> bundle.setStrategy(e, TrackerImportStrategy.CREATE_AND_UPDATE)); @@ -173,7 +173,7 @@ void testTwoEventInRepeatableProgramStageArePassingValidation() { when(preheat.getProgramStage( MetadataIdentifier.ofUid(REPEATABLE_PROGRAM_STAGE_WITH_REGISTRATION))) .thenReturn(repeatebleProgramStageWithRegistration()); - List events = Lists.newArrayList(repeatableEvent("A"), repeatableEvent("B")); + List events = List.of(repeatableEvent(), repeatableEvent()); bundle.setEvents(events); events.forEach(e -> bundle.setStrategy(e, TrackerImportStrategy.CREATE_AND_UPDATE)); @@ -187,8 +187,8 @@ void testTwoEventsInNotRepeatableProgramStageWhenOneIsInvalidArePassingValidatio when(preheat.getProgramStage( MetadataIdentifier.ofUid(NOT_REPEATABLE_PROGRAM_STAGE_WITH_REGISTRATION))) .thenReturn(notRepeatebleProgramStageWithRegistration()); - Event invalidEvent = notRepeatableEvent("A"); - List events = Lists.newArrayList(invalidEvent, notRepeatableEvent("B")); + Event invalidEvent = notRepeatableEvent(); + List events = List.of(invalidEvent, notRepeatableEvent()); bundle.setEvents(events); events.forEach(e -> bundle.setStrategy(e, TrackerImportStrategy.CREATE_AND_UPDATE)); reporter.addError( @@ -204,10 +204,10 @@ void testTwoEventsInNotRepeatableProgramStageButInDifferentEnrollmentsArePassing when(preheat.getProgramStage( MetadataIdentifier.ofUid(NOT_REPEATABLE_PROGRAM_STAGE_WITH_REGISTRATION))) .thenReturn(notRepeatebleProgramStageWithRegistration()); - Event eventEnrollmentA = notRepeatableEvent("A"); - Event eventEnrollmentB = notRepeatableEvent("B"); + Event eventEnrollmentA = notRepeatableEvent(); + Event eventEnrollmentB = notRepeatableEvent(); eventEnrollmentB.setEnrollment(ENROLLMENT_B); - List events = Lists.newArrayList(eventEnrollmentA, eventEnrollmentB); + List events = List.of(eventEnrollmentA, eventEnrollmentB); bundle.setEvents(events); events.forEach(e -> bundle.setStrategy(e, TrackerImportStrategy.CREATE_AND_UPDATE)); @@ -221,9 +221,9 @@ void testTwoProgramEventsInSameProgramStageArePassingValidation() { when(preheat.getProgramStage( MetadataIdentifier.ofUid(NOT_REPEATABLE_PROGRAM_STAGE_WITHOUT_REGISTRATION))) .thenReturn(notRepeatebleProgramStageWithoutRegistration()); - Event eventProgramA = programEvent("A"); - Event eventProgramB = programEvent("B"); - List events = Lists.newArrayList(eventProgramA, eventProgramB); + Event eventProgramA = programEvent(); + Event eventProgramB = programEvent(); + List events = List.of(eventProgramA, eventProgramB); bundle.setEvents(events); events.forEach(e -> bundle.setStrategy(e, TrackerImportStrategy.CREATE_AND_UPDATE)); @@ -265,25 +265,26 @@ private Program programWithoutRegistration() { return program; } - private Event programEvent(String uid) { + private Event programEvent() { Event event = new Event(); - event.setEvent(uid); + event.setEvent(UID.generate()); + event.setEnrollment(ENROLLMENT_B); event.setProgramStage( MetadataIdentifier.ofUid(NOT_REPEATABLE_PROGRAM_STAGE_WITHOUT_REGISTRATION)); return event; } - private Event notRepeatableEvent(String uid) { + private Event notRepeatableEvent() { Event event = new Event(); - event.setEvent(uid); + event.setEvent(UID.generate()); event.setEnrollment(ENROLLMENT_A); event.setProgramStage(MetadataIdentifier.ofUid(NOT_REPEATABLE_PROGRAM_STAGE_WITH_REGISTRATION)); return event; } - private Event repeatableEvent(String uid) { + private Event repeatableEvent() { Event event = new Event(); - event.setEvent(uid); + event.setEvent(UID.generate()); event.setEnrollment(ENROLLMENT_A); event.setProgramStage(MetadataIdentifier.ofUid(REPEATABLE_PROGRAM_STAGE_WITH_REGISTRATION)); return event; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/SecurityOwnershipValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/SecurityOwnershipValidatorTest.java index 0e49d0309a3f..552ecdc2797d 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/SecurityOwnershipValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/SecurityOwnershipValidatorTest.java @@ -38,8 +38,7 @@ import com.google.common.collect.Sets; import java.util.Collections; import java.util.Set; -import org.apache.commons.lang3.StringUtils; -import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.event.EventStatus; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.program.Enrollment; @@ -53,8 +52,8 @@ import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentity.TrackedEntityProgramOwnerOrgUnit; import org.hisp.dhis.trackedentity.TrackedEntityType; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.acl.TrackerOwnershipManager; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -75,7 +74,7 @@ class SecurityOwnershipValidatorTest extends TestBase { private static final String ORG_UNIT_ID = "ORG_UNIT_ID"; - private static final String TE_ID = "TE_ID"; + private static final UID TE_ID = UID.generate(); private static final String TE_TYPE_ID = "TE_TYPE_ID"; @@ -147,9 +146,10 @@ private UserDetails changeCompletedEventAuthorisedUser() { @Test void verifyValidationSuccessForEventUsingDeleteStrategy() { - String enrollmentUid = CodeGenerator.generateUid(); + UID enrollmentUid = UID.generate(); org.hisp.dhis.tracker.imports.domain.Event event = org.hisp.dhis.tracker.imports.domain.Event.builder() + .event(UID.generate()) .enrollment(enrollmentUid) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) .programStage(MetadataIdentifier.ofUid(PS_ID)) @@ -163,7 +163,6 @@ void verifyValidationSuccessForEventUsingDeleteStrategy() { preheatEvent.setEnrollment(enrollment); when(preheat.getEvent(event.getEvent())).thenReturn(preheatEvent); - when(preheat.getEnrollment(event.getEnrollment())).thenReturn(enrollment); UserDetails userDetails = setUpUserWithOrgUnit(); when(aclService.canDataRead(userDetails, program.getTrackedEntityType())).thenReturn(true); @@ -178,9 +177,10 @@ void verifyValidationSuccessForEventUsingDeleteStrategy() { @Test void verifyValidationSuccessForNonTrackerEventUsingCreateStrategy() { program.setProgramType(ProgramType.WITHOUT_REGISTRATION); - String enrollmentUid = CodeGenerator.generateUid(); + UID enrollmentUid = UID.generate(); org.hisp.dhis.tracker.imports.domain.Event event = org.hisp.dhis.tracker.imports.domain.Event.builder() + .event(UID.generate()) .enrollment(enrollmentUid) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) .programStage(MetadataIdentifier.ofUid(PS_ID)) @@ -208,7 +208,8 @@ void verifyValidationSuccessForNonTrackerEventUsingCreateStrategy() { void verifyValidationSuccessForTrackerEventCreation() { org.hisp.dhis.tracker.imports.domain.Event event = org.hisp.dhis.tracker.imports.domain.Event.builder() - .enrollment(CodeGenerator.generateUid()) + .event(UID.generate()) + .enrollment(UID.generate()) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) .programStage(MetadataIdentifier.ofUid(PS_ID)) .program(MetadataIdentifier.ofUid(PROGRAM_ID)) @@ -235,7 +236,8 @@ void verifyValidationSuccessForTrackerEventCreation() { void verifyValidationSuccessForTrackerEventUpdate() { org.hisp.dhis.tracker.imports.domain.Event event = org.hisp.dhis.tracker.imports.domain.Event.builder() - .enrollment(CodeGenerator.generateUid()) + .event(UID.generate()) + .enrollment(UID.generate()) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) .programStage(MetadataIdentifier.ofUid(PS_ID)) .program(MetadataIdentifier.ofUid(PROGRAM_ID)) @@ -260,9 +262,10 @@ void verifyValidationSuccessForTrackerEventUpdate() { @Test void verifyValidationSuccessForEventUsingUpdateStrategy() { - String enrollmentUid = CodeGenerator.generateUid(); + UID enrollmentUid = UID.generate(); org.hisp.dhis.tracker.imports.domain.Event event = org.hisp.dhis.tracker.imports.domain.Event.builder() + .event(UID.generate()) .enrollment(enrollmentUid) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) .programStage(MetadataIdentifier.ofUid(PS_ID)) @@ -277,7 +280,6 @@ void verifyValidationSuccessForEventUsingUpdateStrategy() { Event preheatEvent = getEvent(); preheatEvent.setEnrollment(enrollment); when(preheat.getEvent(event.getEvent())).thenReturn(preheatEvent); - when(preheat.getEnrollment(event.getEnrollment())).thenReturn(enrollment); when(aclService.canDataRead(user, program.getTrackedEntityType())).thenReturn(true); when(aclService.canDataRead(user, program)).thenReturn(true); @@ -290,8 +292,8 @@ void verifyValidationSuccessForEventUsingUpdateStrategy() { @Test void verifyValidationSuccessForEventUsingUpdateStrategyOutsideCaptureScopeWithBrokenGlass() { - String enrollmentUid = CodeGenerator.generateUid(); - String eventUid = CodeGenerator.generateUid(); + UID enrollmentUid = UID.generate(); + UID eventUid = UID.generate(); org.hisp.dhis.tracker.imports.domain.Event event = org.hisp.dhis.tracker.imports.domain.Event.builder() .event(eventUid) @@ -309,25 +311,26 @@ void verifyValidationSuccessForEventUsingUpdateStrategyOutsideCaptureScopeWithBr Event preheatEvent = getEvent(); preheatEvent.setEnrollment(enrollment); when(preheat.getEvent(event.getEvent())).thenReturn(preheatEvent); - when(preheat.getEnrollment(event.getEnrollment())).thenReturn(enrollment); when(preheat.getProgramOwner()) .thenReturn( Collections.singletonMap( TE_ID, Collections.singletonMap( PROGRAM_ID, - new TrackedEntityProgramOwnerOrgUnit(TE_ID, PROGRAM_ID, organisationUnit)))); + new TrackedEntityProgramOwnerOrgUnit( + TE_ID.getValue(), PROGRAM_ID, organisationUnit)))); when(aclService.canDataRead(user, program.getTrackedEntityType())).thenReturn(true); when(aclService.canDataRead(user, program)).thenReturn(true); when(aclService.canDataWrite(user, programStage)).thenReturn(true); - when(ownershipAccessManager.hasAccess(user, TE_ID, organisationUnit, program)) + when(ownershipAccessManager.hasAccess(user, TE_ID.getValue(), organisationUnit, program)) .thenReturn(false); validator.validate(reporter, bundle, event); assertHasError(reporter, event, E1102); - when(ownershipAccessManager.hasAccess(user, TE_ID, organisationUnit, program)).thenReturn(true); + when(ownershipAccessManager.hasAccess(user, TE_ID.getValue(), organisationUnit, program)) + .thenReturn(true); reporter = new Reporter(idSchemes); validator.validate(reporter, bundle, event); @@ -337,9 +340,10 @@ void verifyValidationSuccessForEventUsingUpdateStrategyOutsideCaptureScopeWithBr @Test void verifyValidationSuccessForEventUsingUpdateStrategyAndUserWithAuthority() { - String enrollmentUid = CodeGenerator.generateUid(); + UID enrollmentUid = UID.generate(); org.hisp.dhis.tracker.imports.domain.Event event = org.hisp.dhis.tracker.imports.domain.Event.builder() + .event(UID.generate()) .enrollment(enrollmentUid) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) .programStage(MetadataIdentifier.ofUid(PS_ID)) @@ -355,7 +359,6 @@ void verifyValidationSuccessForEventUsingUpdateStrategyAndUserWithAuthority() { preheatEvent.setEnrollment(enrollment); when(preheat.getEvent(event.getEvent())).thenReturn(preheatEvent); - when(preheat.getEnrollment(event.getEnrollment())).thenReturn(enrollment); when(aclService.canDataRead(userDetails, program.getTrackedEntityType())).thenReturn(true); when(aclService.canDataRead(userDetails, program)).thenReturn(true); @@ -370,8 +373,8 @@ void verifyValidationSuccessForEventUsingUpdateStrategyAndUserWithAuthority() { void verifyValidationFailsForTrackerEventCreationAndUserNotInOrgUnitCaptureScope() { org.hisp.dhis.tracker.imports.domain.Event event = org.hisp.dhis.tracker.imports.domain.Event.builder() - .event(CodeGenerator.generateUid()) - .enrollment(CodeGenerator.generateUid()) + .event(UID.generate()) + .enrollment(UID.generate()) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) .programStage(MetadataIdentifier.ofUid(PS_ID)) .program(MetadataIdentifier.ofUid(PROGRAM_ID)) @@ -398,8 +401,8 @@ void verifyValidationFailsForTrackerEventCreationAndUserNotInOrgUnitCaptureScope verifyValidationFailsForEventCreationThatIsCreatableInSearchScopeAndUserNotInOrgUnitSearchHierarchy() { org.hisp.dhis.tracker.imports.domain.Event event = org.hisp.dhis.tracker.imports.domain.Event.builder() - .event(CodeGenerator.generateUid()) - .enrollment(CodeGenerator.generateUid()) + .event(UID.generate()) + .enrollment(UID.generate()) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) .programStage(MetadataIdentifier.ofUid(PS_ID)) .program(MetadataIdentifier.ofUid(PROGRAM_ID)) @@ -424,10 +427,10 @@ void verifyValidationFailsForTrackerEventCreationAndUserNotInOrgUnitCaptureScope @Test void verifyValidationFailsForEventUsingUpdateStrategyAndUserWithoutAuthority() { - String enrollmentUid = CodeGenerator.generateUid(); + UID enrollmentUid = UID.generate(); org.hisp.dhis.tracker.imports.domain.Event event = org.hisp.dhis.tracker.imports.domain.Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .enrollment(enrollmentUid) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) .programStage(MetadataIdentifier.ofUid(PS_ID)) @@ -440,7 +443,6 @@ void verifyValidationFailsForEventUsingUpdateStrategyAndUserWithoutAuthority() { Event preheatEvent = getEvent(); preheatEvent.setEnrollment(enrollment); when(preheat.getEvent(event.getEvent())).thenReturn(preheatEvent); - when(preheat.getEnrollment(event.getEnrollment())).thenReturn(enrollment); when(aclService.canDataRead(user, program.getTrackedEntityType())).thenReturn(true); when(aclService.canDataRead(user, program)).thenReturn(true); @@ -453,10 +455,10 @@ void verifyValidationFailsForEventUsingUpdateStrategyAndUserWithoutAuthority() { @Test void verifySuccessEventValidationWhenEventHasNoOrgUnitAssigned() { - String enrollmentUid = CodeGenerator.generateUid(); + UID enrollmentUid = UID.generate(); org.hisp.dhis.tracker.imports.domain.Event event = org.hisp.dhis.tracker.imports.domain.Event.builder() - .event(CodeGenerator.generateUid()) + .event(UID.generate()) .enrollment(enrollmentUid) .orgUnit(MetadataIdentifier.ofUid(ORG_UNIT_ID)) .programStage(MetadataIdentifier.ofUid(PS_ID)) @@ -473,7 +475,6 @@ void verifySuccessEventValidationWhenEventHasNoOrgUnitAssigned() { preheatEvent.setOrganisationUnit(null); when(preheat.getEvent(event.getEvent())).thenReturn(preheatEvent); - when(preheat.getEnrollment(event.getEnrollment())).thenReturn(enrollment); when(aclService.canDataRead(user, program.getTrackedEntityType())).thenReturn(true); when(aclService.canDataRead(user, program)).thenReturn(true); @@ -486,19 +487,19 @@ void verifySuccessEventValidationWhenEventHasNoOrgUnitAssigned() { private TrackedEntity teWithNoEnrollments() { TrackedEntity trackedEntity = createTrackedEntity(organisationUnit); - trackedEntity.setUid(TE_ID); + trackedEntity.setUid(TE_ID.getValue()); trackedEntity.setEnrollments(Sets.newHashSet()); trackedEntity.setTrackedEntityType(trackedEntityType); return trackedEntity; } - private Enrollment getEnrollment(String enrollmentUid) { - if (StringUtils.isEmpty(enrollmentUid)) { - enrollmentUid = CodeGenerator.generateUid(); + private Enrollment getEnrollment(UID enrollmentUid) { + if (enrollmentUid == null) { + enrollmentUid = UID.generate(); } Enrollment enrollment = new Enrollment(); - enrollment.setUid(enrollmentUid); + enrollment.setUid(enrollmentUid.getValue()); enrollment.setOrganisationUnit(organisationUnit); enrollment.setTrackedEntity(teWithNoEnrollments()); enrollment.setProgram(program); @@ -510,7 +511,7 @@ private Event getEvent() { Event event = new Event(); event.setProgramStage(programStage); event.setOrganisationUnit(organisationUnit); - event.setEnrollment(new Enrollment()); + event.setEnrollment(getEnrollment(UID.generate())); event.setStatus(EventStatus.COMPLETED); return event; } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/StatusUpdateValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/StatusUpdateValidatorTest.java index d199905c3d75..8a39134fd1d3 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/StatusUpdateValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/StatusUpdateValidatorTest.java @@ -38,8 +38,9 @@ import static org.mockito.Mockito.when; import java.util.stream.Stream; +import org.hisp.dhis.common.UID; import org.hisp.dhis.event.EventStatus; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Event; import org.hisp.dhis.tracker.imports.preheat.TrackerPreheat; @@ -63,7 +64,7 @@ class StatusUpdateValidatorTest { @Mock TrackerPreheat preheat; - private static final String EVENT_UID = "h4w96yEMlzO"; + private static final UID EVENT_UID = UID.generate(); @Mock private TrackerBundle bundle; @@ -84,7 +85,7 @@ public void setUp() { void shouldPassValidationWhenGoingFromStatusToStatus( EventStatus fromStatus, EventStatus toStatus) { org.hisp.dhis.program.Event savedEvent = new org.hisp.dhis.program.Event(); - savedEvent.setUid(EVENT_UID); + savedEvent.setUid(EVENT_UID.getValue()); savedEvent.setStatus(fromStatus); when(preheat.getEvent(EVENT_UID)).thenReturn(savedEvent); @@ -100,7 +101,7 @@ void shouldPassValidationWhenGoingFromStatusToStatus( void shouldFailValidationWhenGoingFromStatusToStatus( EventStatus fromStatus, EventStatus toStatus) { org.hisp.dhis.program.Event savedEvent = new org.hisp.dhis.program.Event(); - savedEvent.setUid(EVENT_UID); + savedEvent.setUid(EVENT_UID.getValue()); savedEvent.setStatus(fromStatus); when(preheat.getEvent(EVENT_UID)).thenReturn(savedEvent); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/UidValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/UidValidatorTest.java deleted file mode 100644 index ee7e1497e255..000000000000 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/UidValidatorTest.java +++ /dev/null @@ -1,99 +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.imports.validation.validator.event; - -import static org.hisp.dhis.test.utils.Assertions.assertIsEmpty; -import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1048; -import static org.hisp.dhis.tracker.imports.validation.validator.AssertValidations.assertHasError; - -import com.google.common.collect.Lists; -import org.hisp.dhis.common.CodeGenerator; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; -import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; -import org.hisp.dhis.tracker.imports.domain.Event; -import org.hisp.dhis.tracker.imports.domain.Note; -import org.hisp.dhis.tracker.imports.preheat.TrackerPreheat; -import org.hisp.dhis.tracker.imports.validation.Reporter; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -/** - * @author Enrico Colasante - */ -class UidValidatorTest { - - private static final String INVALID_UID = "InvalidUID"; - - private UidValidator validator; - - private TrackerBundle bundle; - - private Reporter reporter; - - @BeforeEach - void setUp() { - TrackerPreheat preheat = new TrackerPreheat(); - TrackerIdSchemeParams idSchemes = TrackerIdSchemeParams.builder().build(); - preheat.setIdSchemes(idSchemes); - reporter = new Reporter(idSchemes); - bundle = TrackerBundle.builder().preheat(preheat).build(); - - validator = new UidValidator(); - } - - @Test - void verifyEventValidationSuccess() { - Note note = Note.builder().note(CodeGenerator.generateUid()).build(); - Event event = - Event.builder().event(CodeGenerator.generateUid()).notes(Lists.newArrayList(note)).build(); - - validator.validate(reporter, bundle, event); - - assertIsEmpty(reporter.getErrors()); - } - - @Test - void verifyEventWithInvalidUidFails() { - Event event = Event.builder().event(INVALID_UID).build(); - - validator.validate(reporter, bundle, event); - - assertHasError(reporter, event, E1048); - } - - @Test - void verifyEventWithNoteWithInvalidUidFails() { - Note note = Note.builder().note(INVALID_UID).build(); - Event event = - Event.builder().event(CodeGenerator.generateUid()).notes(Lists.newArrayList(note)).build(); - - validator.validate(reporter, bundle, event); - - assertHasError(reporter, event, E1048); - } -} diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/UpdatableFieldsValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/UpdatableFieldsValidatorTest.java index 946c4e9cdb22..57e1de458243 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/UpdatableFieldsValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/event/UpdatableFieldsValidatorTest.java @@ -33,13 +33,14 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.Enrollment; import org.hisp.dhis.program.Event; import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramStage; import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentity.TrackedEntityType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -66,11 +67,11 @@ class UpdatableFieldsValidatorTest { private static final String PROGRAM_STAGE_ID = "ProgramStageId"; - private static final String TRACKED_ENTITY_ID = "TrackedEntityId"; + private static final UID TRACKED_ENTITY_ID = UID.generate(); - private static final String ENROLLMENT_ID = "EnrollmentId"; + private static final UID ENROLLMENT_ID = UID.generate(); - private static final String EVENT_ID = "EventId"; + private static final UID EVENT_UID = UID.generate(); private UpdatableFieldsValidator validator; @@ -95,7 +96,7 @@ public void setUp() { when(preheat.getTrackedEntity(TRACKED_ENTITY_ID)).thenReturn(trackedEntity()); when(preheat.getEnrollment(ENROLLMENT_ID)).thenReturn(getEnrollment()); - when(preheat.getEvent(EVENT_ID)).thenReturn(event()); + when(preheat.getEvent(EVENT_UID)).thenReturn(event()); when(bundle.getPreheat()).thenReturn(preheat); @@ -124,7 +125,7 @@ void verifyEventValidationFailsWhenUpdateProgramStage() { @Test void verifyEventValidationFailsWhenUpdateEnrollment() { org.hisp.dhis.tracker.imports.domain.Event event = validEvent(); - event.setEnrollment("NewEnrollmentId"); + event.setEnrollment(UID.generate()); validator.validate(reporter, bundle, event); @@ -133,7 +134,7 @@ void verifyEventValidationFailsWhenUpdateEnrollment() { private org.hisp.dhis.tracker.imports.domain.Event validEvent() { return org.hisp.dhis.tracker.imports.domain.Event.builder() - .event(EVENT_ID) + .event(EVENT_UID) .programStage(MetadataIdentifier.ofUid(PROGRAM_STAGE_ID)) .enrollment(ENROLLMENT_ID) .build(); @@ -144,7 +145,7 @@ private TrackedEntity trackedEntity() { trackedEntityType.setUid(TRACKED_ENTITY_TYPE_ID); TrackedEntity trackedEntity = new TrackedEntity(); - trackedEntity.setUid(TRACKED_ENTITY_ID); + trackedEntity.setUid(TRACKED_ENTITY_ID.getValue()); trackedEntity.setTrackedEntityType(trackedEntityType); return trackedEntity; } @@ -154,7 +155,7 @@ private Enrollment getEnrollment() { program.setUid(PROGRAM_ID); Enrollment enrollment = new Enrollment(); - enrollment.setUid(ENROLLMENT_ID); + enrollment.setUid(ENROLLMENT_ID.getValue()); enrollment.setProgram(program); enrollment.setTrackedEntity(trackedEntity()); return enrollment; @@ -165,7 +166,7 @@ private Event event() { programStage.setUid(PROGRAM_STAGE_ID); Event event = new Event(); - event.setUid(EVENT_ID); + event.setUid(EVENT_UID.getValue()); event.setEnrollment(getEnrollment()); event.setProgramStage(programStage); return event; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/ConstraintValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/ConstraintValidatorTest.java index 1838fe54a96f..d2f1ace2a040 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/ConstraintValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/ConstraintValidatorTest.java @@ -35,16 +35,17 @@ import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E4012; import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E4014; import static org.hisp.dhis.tracker.imports.validation.validator.AssertValidations.assertHasError; -import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.relationship.RelationshipConstraint; import org.hisp.dhis.relationship.RelationshipEntity; import org.hisp.dhis.relationship.RelationshipType; import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentity.TrackedEntityType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; import org.hisp.dhis.tracker.imports.domain.Relationship; @@ -91,7 +92,7 @@ void shouldBeValidWhenRelationshipTypeIsCorrectlySetAndEntitiesExist() { Relationship relationship = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .from(trackedEntityRelationshipItem()) .to(trackedEntityRelationshipItem()) .relationshipType(MetadataIdentifier.ofUid(relType.getUid())) @@ -99,7 +100,7 @@ void shouldBeValidWhenRelationshipTypeIsCorrectlySetAndEntitiesExist() { when(bundle.getPreheat().getRelationshipType(relationship.getRelationshipType())) .thenReturn(relType); - when(bundle.getPreheat().getTrackedEntity(anyString())).thenReturn(trackedEntity); + when(bundle.getPreheat().getTrackedEntity(any())).thenReturn(trackedEntity); when(params.toMetadataIdentifier(trackedEntityType)) .thenReturn(MetadataIdentifier.ofUid(trackedEntityType.getUid())); when(bundle.getPreheat().getIdSchemes()).thenReturn(params); @@ -116,7 +117,7 @@ void shouldFailWhenRelationshipEntityIsTrackedEntityAndToConstraintIsSetToEnroll Relationship relationship = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .from(trackedEntityRelationshipItem()) .to(enrollmentRelationshipItem()) .relationshipType(MetadataIdentifier.ofUid(relType.getUid())) @@ -141,7 +142,7 @@ void shouldFailWhenRelationshipEntityIsTrackedEntityAndEntityDoesNotExist() { Relationship relationship = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .from(trackedEntityRelationshipItem()) .to(enrollmentRelationshipItem()) .relationshipType(MetadataIdentifier.ofUid(relType.getUid())) @@ -167,7 +168,7 @@ void shouldFailWhenRelationshipEntityIsEventAndToConstraintIsSetToEnrollment() { Relationship relationship = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .from(enrollmentRelationshipItem()) .to(enrollmentRelationshipItem()) .relationshipType(MetadataIdentifier.ofUid(relType.getUid())) @@ -198,7 +199,7 @@ void shouldFailWhenRelationshipEntityIsTrackedEntityAndEntityTypeDoesNotMatch() Relationship relationship = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .from(trackedEntityRelationshipItem()) .to(trackedEntityRelationshipItem()) .relationshipType(MetadataIdentifier.ofUid(relType.getUid())) @@ -206,7 +207,7 @@ void shouldFailWhenRelationshipEntityIsTrackedEntityAndEntityTypeDoesNotMatch() when(bundle.getPreheat().getRelationshipType(relationship.getRelationshipType())) .thenReturn(relType); - when(bundle.getPreheat().getTrackedEntity(anyString())).thenReturn(trackedEntity); + when(bundle.getPreheat().getTrackedEntity(any())).thenReturn(trackedEntity); String uid = CodeGenerator.generateUid(); when(params.toMetadataIdentifier(trackedEntityType)).thenReturn(MetadataIdentifier.ofUid(uid)); when(bundle.getPreheat().getIdSchemes()).thenReturn(params); @@ -217,7 +218,8 @@ void shouldFailWhenRelationshipEntityIsTrackedEntityAndEntityTypeDoesNotMatch() reporter, relationship, E4014, - "Relationship type `from` constraint requires a tracked entity having type `madeUpUid` but `" + "Relationship type `from` constraint requires a tracked entity having type `madeUpUid` but" + + " `" + uid + "` was found."); } @@ -228,7 +230,7 @@ void shouldFailWhenRelationshipEntityIsEnrollmentAndEnrollmentDoesNotExist() { Relationship relationship = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .from(enrollmentRelationshipItem()) .to(enrollmentRelationshipItem()) .relationshipType(MetadataIdentifier.ofUid(relType.getUid())) @@ -254,7 +256,7 @@ void shouldFailWhenRelationshipEntityIsEnrollmentAndFromConstraintIsSetToEvent() Relationship relationship = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .from(RelationshipItem.builder().event(event()).build()) .to(trackedEntityRelationshipItem()) .relationshipType(MetadataIdentifier.ofUid(relType.getUid())) @@ -279,7 +281,7 @@ void shouldFailWhenRelationshipEntityIsEventAndEventDoesNotExist() { Relationship relationship = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .from(RelationshipItem.builder().event(event()).build()) .to(trackedEntityRelationshipItem()) .relationshipType(MetadataIdentifier.ofUid(relType.getUid())) @@ -321,15 +323,15 @@ private RelationshipItem enrollmentRelationshipItem() { return RelationshipItem.builder().enrollment(enrollment()).build(); } - private String trackedEntity() { - return CodeGenerator.generateUid(); + private UID trackedEntity() { + return UID.generate(); } - private String enrollment() { - return CodeGenerator.generateUid(); + private UID enrollment() { + return UID.generate(); } - private String event() { - return CodeGenerator.generateUid(); + private UID event() { + return UID.generate(); } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/DuplicationValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/DuplicationValidatorTest.java index 369409a17186..e03ed5d51daf 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/DuplicationValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/DuplicationValidatorTest.java @@ -34,9 +34,10 @@ import static org.mockito.Mockito.when; import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.relationship.RelationshipConstraint; import org.hisp.dhis.relationship.RelationshipType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; import org.hisp.dhis.tracker.imports.domain.Relationship; @@ -74,7 +75,7 @@ public void setUp() { void shouldFailWhenRelationshipIsDuplicated() { Relationship relationship = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .relationshipType(MetadataIdentifier.ofUid(createRelTypeConstraint().getUid())) .from(trackedEntityRelationshipItem()) .to(trackedEntityRelationshipItem()) @@ -91,7 +92,7 @@ void shouldFailWhenRelationshipIsDuplicated() { void shouldBeValidWhenRelationshipIsNotDuplicated() { Relationship relationship = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .relationshipType(MetadataIdentifier.ofUid(createRelTypeConstraint().getUid())) .from(trackedEntityRelationshipItem()) .to(trackedEntityRelationshipItem()) @@ -121,6 +122,6 @@ private RelationshipType createRelTypeConstraint() { } private RelationshipItem trackedEntityRelationshipItem() { - return RelationshipItem.builder().trackedEntity(CodeGenerator.generateUid()).build(); + return RelationshipItem.builder().trackedEntity(UID.generate()).build(); } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/ExistenceValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/ExistenceValidatorTest.java index 00a240dd89c3..937f2f80044a 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/ExistenceValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/ExistenceValidatorTest.java @@ -37,7 +37,8 @@ import static org.hisp.dhis.tracker.imports.validation.validator.AssertValidations.assertHasWarning; import static org.mockito.Mockito.when; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.common.UID; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Relationship; @@ -54,11 +55,11 @@ */ @ExtendWith(MockitoExtension.class) class ExistenceValidatorTest { - private static final String NOT_PRESENT_RELATIONSHIP_UID = "NotPresentRelationshipId"; + private static final UID NOT_PRESENT_RELATIONSHIP_UID = UID.generate(); - private static final String RELATIONSHIP_UID = "RelationshipId"; + private static final UID RELATIONSHIP_UID = UID.generate(); - private static final String SOFT_DELETED_RELATIONSHIP_UID = "SoftDeletedRelationshipId"; + private static final UID SOFT_DELETED_RELATIONSHIP_UID = UID.generate(); @Mock private TrackerBundle bundle; @@ -145,7 +146,7 @@ private Relationship getPayloadRelationship() { private org.hisp.dhis.relationship.Relationship softDeletedRelationship() { org.hisp.dhis.relationship.Relationship relationship = new org.hisp.dhis.relationship.Relationship(); - relationship.setUid(SOFT_DELETED_RELATIONSHIP_UID); + relationship.setUid(SOFT_DELETED_RELATIONSHIP_UID.getValue()); relationship.setDeleted(true); return relationship; } @@ -153,7 +154,7 @@ private org.hisp.dhis.relationship.Relationship softDeletedRelationship() { private org.hisp.dhis.relationship.Relationship getRelationship() { org.hisp.dhis.relationship.Relationship relationship = new org.hisp.dhis.relationship.Relationship(); - relationship.setUid(RELATIONSHIP_UID); + relationship.setUid(RELATIONSHIP_UID.getValue()); return relationship; } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/LinkValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/LinkValidatorTest.java index 7bc76278543c..e36eebb3d0ca 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/LinkValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/LinkValidatorTest.java @@ -33,9 +33,10 @@ import static org.hisp.dhis.tracker.imports.validation.validator.AssertValidations.assertHasError; import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.relationship.RelationshipConstraint; import org.hisp.dhis.relationship.RelationshipType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; import org.hisp.dhis.tracker.imports.domain.Relationship; @@ -66,9 +67,9 @@ void shouldBeValidWhenRelationshipLinksTwoDifferentEntities() { RelationshipType relType = createRelTypeConstraint(); Relationship relationship = Relationship.builder() - .relationship(CodeGenerator.generateUid()) - .from(trackedEntityRelationshipItem(CodeGenerator.generateUid())) - .to(trackedEntityRelationshipItem(CodeGenerator.generateUid())) + .relationship(UID.generate()) + .from(trackedEntityRelationshipItem(UID.generate())) + .to(trackedEntityRelationshipItem(UID.generate())) .relationshipType(MetadataIdentifier.ofUid(relType.getUid())) .build(); @@ -80,10 +81,10 @@ void shouldBeValidWhenRelationshipLinksTwoDifferentEntities() { @Test void shouldFailWhenRelationshipLinksEntityToItself() { RelationshipType relType = createRelTypeConstraint(); - String uid = CodeGenerator.generateUid(); + UID uid = UID.generate(); Relationship relationship = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .from(trackedEntityRelationshipItem(uid)) .to(trackedEntityRelationshipItem(uid)) .relationshipType(MetadataIdentifier.ofUid(relType.getUid())) @@ -114,7 +115,7 @@ private RelationshipType createRelTypeConstraint() { return relType; } - private RelationshipItem trackedEntityRelationshipItem(String trackedEntityUid) { + private RelationshipItem trackedEntityRelationshipItem(UID trackedEntityUid) { return RelationshipItem.builder().trackedEntity(trackedEntityUid).build(); } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/MandatoryFieldsValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/MandatoryFieldsValidatorTest.java index 5eb31bcb4c21..39bb09d360b9 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/MandatoryFieldsValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/MandatoryFieldsValidatorTest.java @@ -34,8 +34,9 @@ import java.util.Collections; import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.relationship.RelationshipType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.ValidationMode; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; @@ -90,7 +91,7 @@ void verifyRelationshipValidationSuccess() { Relationship relationship = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .relationshipType(MetadataIdentifier.ofUid(relTypeUid)) .from(RelationshipItem.builder().trackedEntity(trackedEntity()).build()) .to(RelationshipItem.builder().trackedEntity(trackedEntity()).build()) @@ -108,7 +109,7 @@ void verifyRelationshipValidationSuccess() { @Test void shouldFailWhenRelationshipMissingFrom() { - String relationshipUid = CodeGenerator.generateUid(); + UID relationshipUid = UID.generate(); Relationship relationship = Relationship.builder() .relationship(relationshipUid) @@ -129,7 +130,7 @@ void shouldFailWhenRelationshipMissingFrom() { @Test void shouldFailWhenFromItemHasNoEntities() { - String relationshipUid = CodeGenerator.generateUid(); + UID relationshipUid = UID.generate(); Relationship relationship = Relationship.builder() .relationship(relationshipUid) @@ -151,15 +152,15 @@ void shouldFailWhenFromItemHasNoEntities() { @Test void shouldFailWhenFromItemHasMultipleEntities() { - String relationshipUid = CodeGenerator.generateUid(); + UID relationshipUid = UID.generate(); Relationship relationship = Relationship.builder() .relationship(relationshipUid) .relationshipType(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) .from( RelationshipItem.builder() - .trackedEntity("tracked entity") - .enrollment("enrollment") + .trackedEntity(UID.generate()) + .enrollment(UID.generate()) .build()) .to(RelationshipItem.builder().trackedEntity(trackedEntity()).build()) .build(); @@ -177,7 +178,7 @@ void shouldFailWhenFromItemHasMultipleEntities() { @Test void shouldFailWhenRelationshipMissingTo() { - String relationshipUid = CodeGenerator.generateUid(); + UID relationshipUid = UID.generate(); Relationship relationship = Relationship.builder() .relationship(relationshipUid) @@ -198,7 +199,7 @@ void shouldFailWhenRelationshipMissingTo() { @Test void shouldFailWhenToItemHasNoEntities() { - String relationshipUid = CodeGenerator.generateUid(); + UID relationshipUid = UID.generate(); Relationship relationship = Relationship.builder() .relationship(relationshipUid) @@ -220,7 +221,7 @@ void shouldFailWhenToItemHasNoEntities() { @Test void shouldFailWhenToItemHasMultipleEntities() { - String relationshipUid = CodeGenerator.generateUid(); + UID relationshipUid = UID.generate(); Relationship relationship = Relationship.builder() .relationship(relationshipUid) @@ -228,8 +229,8 @@ void shouldFailWhenToItemHasMultipleEntities() { .from(RelationshipItem.builder().trackedEntity(trackedEntity()).build()) .to( RelationshipItem.builder() - .trackedEntity("tracked entity") - .enrollment("enrollment") + .trackedEntity(UID.generate()) + .enrollment(UID.generate()) .build()) .build(); @@ -248,7 +249,7 @@ void shouldFailWhenToItemHasMultipleEntities() { void verifyRelationshipValidationFailsOnMissingRelationshipType() { Relationship relationship = Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .relationshipType(MetadataIdentifier.EMPTY_UID) .from(RelationshipItem.builder().trackedEntity(trackedEntity()).build()) .to(RelationshipItem.builder().trackedEntity(trackedEntity()).build()) @@ -263,7 +264,7 @@ private void assertMissingProperty(Reporter reporter, TrackerDto dto, String pro AssertValidations.assertMissingProperty(reporter, dto, ValidationCode.E1124, property); } - private String trackedEntity() { - return CodeGenerator.generateUid(); + private UID trackedEntity() { + return UID.generate(); } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/MetaValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/MetaValidatorTest.java index c5104ed67d83..ffc4cd234306 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/MetaValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/MetaValidatorTest.java @@ -32,9 +32,9 @@ import static org.hisp.dhis.tracker.imports.validation.validator.AssertValidations.assertHasError; import static org.mockito.Mockito.when; -import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.relationship.RelationshipType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; import org.hisp.dhis.tracker.imports.domain.Relationship; @@ -93,7 +93,7 @@ void verifyRelationshipValidationFailsWhenRelationshipTypeIsNotPresentInDb() { private Relationship validRelationship() { return Relationship.builder() - .relationship(CodeGenerator.generateUid()) + .relationship(UID.generate()) .relationshipType(MetadataIdentifier.ofUid(RELATIONSHIP_TYPE_UID)) .build(); } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/SecurityOwnershipValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/SecurityOwnershipValidatorTest.java index e29a91340675..7dd781b8d58c 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/SecurityOwnershipValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/SecurityOwnershipValidatorTest.java @@ -38,9 +38,10 @@ import static org.mockito.Mockito.when; import java.util.List; +import org.hisp.dhis.common.UID; import org.hisp.dhis.test.TestBase; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.acl.TrackerAccessManager; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.bundle.TrackerObjectsMapper; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -88,10 +89,10 @@ public void setUp() { reporter = new Reporter(idSchemes); relationship = Relationship.builder() - .relationship("relationshipUid") + .relationship(UID.generate()) .relationshipType(relationshipTypeUid) - .from(RelationshipItem.builder().build()) - .to(RelationshipItem.builder().build()) + .from(RelationshipItem.builder().trackedEntity(UID.generate()).build()) + .to(RelationshipItem.builder().trackedEntity(UID.generate()).build()) .build(); convertedRelationship = TrackerObjectsMapper.map(preheat, relationship, new SystemUser()); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/UidValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/UidValidatorTest.java deleted file mode 100644 index fa33afef5b6d..000000000000 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/relationship/UidValidatorTest.java +++ /dev/null @@ -1,85 +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.imports.validation.validator.relationship; - -import static org.hisp.dhis.test.utils.Assertions.assertIsEmpty; -import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1048; -import static org.hisp.dhis.tracker.imports.validation.validator.AssertValidations.assertHasError; - -import org.hisp.dhis.common.CodeGenerator; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; -import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; -import org.hisp.dhis.tracker.imports.domain.Relationship; -import org.hisp.dhis.tracker.imports.preheat.TrackerPreheat; -import org.hisp.dhis.tracker.imports.validation.Reporter; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -/** - * @author Enrico Colasante - */ -class UidValidatorTest { - - private static final String INVALID_UID = "InvalidUID"; - - private UidValidator validator; - - private TrackerBundle bundle; - - private Reporter reporter; - - @BeforeEach - void setUp() { - TrackerPreheat preheat = new TrackerPreheat(); - TrackerIdSchemeParams idSchemes = TrackerIdSchemeParams.builder().build(); - preheat.setIdSchemes(idSchemes); - reporter = new Reporter(idSchemes); - bundle = TrackerBundle.builder().preheat(preheat).build(); - - validator = new UidValidator(); - } - - @Test - void verifyRelationshipValidationSuccess() { - Relationship relationship = - Relationship.builder().relationship(CodeGenerator.generateUid()).build(); - - validator.validate(reporter, bundle, relationship); - - assertIsEmpty(reporter.getErrors()); - } - - @Test - void verifyRelationshipWithInvalidUidFails() { - Relationship relationship = Relationship.builder().relationship(INVALID_UID).build(); - - validator.validate(reporter, bundle, relationship); - - assertHasError(reporter, relationship, E1048); - } -} diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/AttributeValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/AttributeValidatorTest.java index cc37a981cd61..bffb30b51d0f 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/AttributeValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/AttributeValidatorTest.java @@ -40,6 +40,7 @@ import java.util.Collections; import java.util.Set; import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.common.ValueType; import org.hisp.dhis.encryption.EncryptionStatus; import org.hisp.dhis.external.conf.DhisConfigurationProvider; @@ -50,7 +51,7 @@ import org.hisp.dhis.trackedentity.TrackedEntityType; import org.hisp.dhis.trackedentity.TrackedEntityTypeAttribute; import org.hisp.dhis.trackedentityattributevalue.TrackedEntityAttributeValue; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.Attribute; @@ -76,7 +77,7 @@ @MockitoSettings(strictness = Strictness.LENIENT) @ExtendWith(MockitoExtension.class) class AttributeValidatorTest { - private static final String TE_UID = "fdsf9e90rei"; + private static final UID TE_UID = UID.of("fdsf9e90rei"); private static final String MANDATORY_ATTRIBUTE_UID = "cFr94jf03AA"; @@ -116,6 +117,7 @@ void shouldPassValidation() { TrackedEntity trackedEntity = TrackedEntity.builder() + .trackedEntity(UID.generate()) .attributes( Collections.singletonList( Attribute.builder() @@ -149,7 +151,7 @@ void shouldFailValidationWhenCreatingTEAndThereAreMissingMandatoryFields() { TrackedEntity trackedEntity = TrackedEntity.builder() - .trackedEntity(CodeGenerator.generateUid()) + .trackedEntity(UID.generate()) .trackedEntityType(MetadataIdentifier.ofUid(tet)) .attributes( Arrays.asList( @@ -274,6 +276,7 @@ void shouldFailValidationWhenUpdatingTEAndPayloadIsMissingMandatoryFieldAndItIsN void shouldFailValidationMissingTea() { TrackedEntity trackedEntity = TrackedEntity.builder() + .trackedEntity(UID.generate()) .attributes( Arrays.asList( Attribute.builder().attribute(MetadataIdentifier.ofUid("aaaaa")).build(), @@ -303,7 +306,7 @@ void shouldFailWhenCreatingTEAndMandatoryAttributeIsSetToDeleted() { TrackedEntity trackedEntity = TrackedEntity.builder() - .trackedEntity(CodeGenerator.generateUid()) + .trackedEntity(UID.generate()) .attributes( Collections.singletonList( Attribute.builder().attribute(MetadataIdentifier.ofUid(tea)).build())) @@ -338,7 +341,7 @@ void shouldFailWhenUpdatingTEAndMandatoryAttributeIsSetToDeleted() { TrackedEntity trackedEntity = TrackedEntity.builder() - .trackedEntity(CodeGenerator.generateUid()) + .trackedEntity(UID.generate()) .attributes( Collections.singletonList( Attribute.builder().attribute(MetadataIdentifier.ofUid(tea)).build())) @@ -371,7 +374,7 @@ void shouldFailValueTooLong() { TrackedEntityAttribute trackedEntityAttribute = new TrackedEntityAttribute(); trackedEntityAttribute.setValueType(ValueType.TEXT); - TrackedEntity te = TrackedEntity.builder().trackedEntity(CodeGenerator.generateUid()).build(); + TrackedEntity te = TrackedEntity.builder().trackedEntity(UID.generate()).build(); validator.validateAttributeValue( reporter, te, trackedEntityAttribute, "a".repeat(Constant.MAX_ATTR_VALUE_LENGTH + 1)); @@ -383,7 +386,7 @@ void shouldFailDataValueIsValid() { TrackedEntityAttribute trackedEntityAttribute = new TrackedEntityAttribute(); trackedEntityAttribute.setValueType(ValueType.NUMBER); - TrackedEntity te = TrackedEntity.builder().trackedEntity(CodeGenerator.generateUid()).build(); + TrackedEntity te = TrackedEntity.builder().trackedEntity(UID.generate()).build(); validator.validateAttributeValue(reporter, te, trackedEntityAttribute, "value"); assertHasError(reporter, te, ValidationCode.E1085); @@ -402,7 +405,7 @@ void shouldFailEncryptionStatus() { when(preheat.getTrackedEntityAttribute((MetadataIdentifier) any())) .thenReturn(trackedEntityAttribute); - TrackedEntity te = TrackedEntity.builder().trackedEntity(CodeGenerator.generateUid()).build(); + TrackedEntity te = TrackedEntity.builder().trackedEntity(UID.generate()).build(); validator.validateAttributeValue(reporter, te, trackedEntityAttribute, "value"); assertHasError(reporter, te, ValidationCode.E1112); @@ -419,7 +422,7 @@ void shouldFailOptionSetNotValid() { TrackedEntity trackedEntity = TrackedEntity.builder() - .trackedEntity(CodeGenerator.generateUid()) + .trackedEntity(UID.generate()) .attributes( Collections.singletonList( Attribute.builder() @@ -445,6 +448,7 @@ void shouldPassValidationValueInOptionSet() { TrackedEntity trackedEntity = TrackedEntity.builder() + .trackedEntity(UID.generate()) .attributes( Collections.singletonList( Attribute.builder() @@ -470,7 +474,7 @@ void shouldFailValidationValueInMultiText() { TrackedEntity trackedEntity = TrackedEntity.builder() - .trackedEntity(CodeGenerator.generateUid()) + .trackedEntity(UID.generate()) .attributes( Collections.singletonList( Attribute.builder() @@ -530,6 +534,7 @@ void shouldPassValidationWhenValueIsNullAndAttributeIsNotMandatory() { TrackedEntity trackedEntity = TrackedEntity.builder() + .trackedEntity(UID.generate()) .attributes( Collections.singletonList( Attribute.builder() @@ -547,7 +552,7 @@ void shouldPassValidationWhenValueIsNullAndAttributeIsNotMandatory() { void shouldFailValidationWhenValueIsNullAndAttributeIsMandatory() { TrackedEntity trackedEntity = TrackedEntity.builder() - .trackedEntity(CodeGenerator.generateUid()) + .trackedEntity(UID.generate()) .attributes( Collections.singletonList( Attribute.builder() @@ -604,7 +609,7 @@ void validateFileResourceOwner() { TrackedEntity trackedEntity = TrackedEntity.builder() - .trackedEntity(CodeGenerator.generateUid()) + .trackedEntity(UID.generate()) .attributes(Collections.singletonList(attribute)) .trackedEntityType(MetadataIdentifier.ofUid("tet")) .build(); @@ -617,8 +622,10 @@ void validateFileResourceOwner() { reporter = new Reporter(idSchemes); - trackedEntity.setTrackedEntity("XYZ"); - fileResource.setFileResourceOwner("ABC"); + UID uid = UID.generate(); + + trackedEntity.setTrackedEntity(UID.generate()); + fileResource.setFileResourceOwner(uid.getValue()); bundle.setStrategy(trackedEntity, TrackerImportStrategy.UPDATE); @@ -628,8 +635,8 @@ void validateFileResourceOwner() { reporter = new Reporter(idSchemes); - trackedEntity.setTrackedEntity("ABC"); - fileResource.setFileResourceOwner("ABC"); + trackedEntity.setTrackedEntity(uid); + fileResource.setFileResourceOwner(uid.getValue()); bundle.setStrategy(trackedEntity, TrackerImportStrategy.UPDATE); @@ -680,7 +687,7 @@ private TrackedEntityAttribute getTrackedEntityAttributeWithMultiText() { private org.hisp.dhis.trackedentity.TrackedEntity trackedEntity() { org.hisp.dhis.trackedentity.TrackedEntity trackedEntity = new org.hisp.dhis.trackedentity.TrackedEntity(); - trackedEntity.setUid(TE_UID); + trackedEntity.setUid(TE_UID.getValue()); return trackedEntity; } @@ -688,7 +695,7 @@ private org.hisp.dhis.trackedentity.TrackedEntity trackedEntity( TrackedEntityAttribute attribute) { org.hisp.dhis.trackedentity.TrackedEntity trackedEntity = new org.hisp.dhis.trackedentity.TrackedEntity(); - trackedEntity.setUid(TE_UID); + trackedEntity.setUid(TE_UID.getValue()); TrackedEntityAttributeValue attributeValue = createTrackedEntityAttributeValue('c', trackedEntity, attribute); attributeValue.setValue("value"); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/ExistenceValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/ExistenceValidatorTest.java index fc7604e45744..e130e774cf40 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/ExistenceValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/ExistenceValidatorTest.java @@ -35,8 +35,9 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; +import org.hisp.dhis.common.UID; import org.hisp.dhis.trackedentity.TrackedEntity; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.preheat.TrackerPreheat; @@ -52,11 +53,11 @@ */ @ExtendWith(MockitoExtension.class) class ExistenceValidatorTest { - private static final String SOFT_DELETED_TE_UID = "SoftDeletedTEId"; + private static final UID SOFT_DELETED_TE_UID = UID.generate(); - private static final String TE_UID = "TEId"; + private static final UID TE_UID = UID.generate(); - private static final String NOT_PRESENT_TE_UID = "NotPresentTEId"; + private static final UID NOT_PRESENT_TE_UID = UID.generate(); @Mock private TrackerBundle bundle; @@ -158,14 +159,14 @@ void verifyTrackedEntityValidationFailsWhenIsUpdateAndTeNotPresent() { private TrackedEntity getSoftDeletedTei() { TrackedEntity trackedEntity = new TrackedEntity(); - trackedEntity.setUid(SOFT_DELETED_TE_UID); + trackedEntity.setUid(SOFT_DELETED_TE_UID.getValue()); trackedEntity.setDeleted(true); return trackedEntity; } private TrackedEntity trackedEntity() { TrackedEntity trackedEntity = new TrackedEntity(); - trackedEntity.setUid(TE_UID); + trackedEntity.setUid(TE_UID.getValue()); trackedEntity.setDeleted(false); return trackedEntity; } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/MandatoryFieldsValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/MandatoryFieldsValidatorTest.java index 2b035b6d4f8e..c49d004fefd9 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/MandatoryFieldsValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/MandatoryFieldsValidatorTest.java @@ -31,7 +31,8 @@ import static org.mockito.Mockito.when; import org.hisp.dhis.common.CodeGenerator; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.common.UID; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.ValidationMode; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; @@ -81,7 +82,7 @@ public void setUp() { void verifyTrackedEntityValidationSuccess() { TrackedEntity trackedEntity = TrackedEntity.builder() - .trackedEntity(CodeGenerator.generateUid()) + .trackedEntity(UID.generate()) .trackedEntityType(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) .orgUnit(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) .build(); @@ -95,7 +96,7 @@ void verifyTrackedEntityValidationSuccess() { void verifyTrackedEntityValidationFailsOnMissingOrgUnit() { TrackedEntity trackedEntity = TrackedEntity.builder() - .trackedEntity(CodeGenerator.generateUid()) + .trackedEntity(UID.generate()) .trackedEntityType(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) .orgUnit(MetadataIdentifier.EMPTY_UID) .build(); @@ -109,7 +110,7 @@ void verifyTrackedEntityValidationFailsOnMissingOrgUnit() { void verifyTrackedEntityValidationFailsOnMissingTrackedEntityType() { TrackedEntity trackedEntity = TrackedEntity.builder() - .trackedEntity(CodeGenerator.generateUid()) + .trackedEntity(UID.generate()) .trackedEntityType(MetadataIdentifier.EMPTY_UID) .orgUnit(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) .build(); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/MetaValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/MetaValidatorTest.java index 49c827b339f7..c9dbcde74fe0 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/MetaValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/MetaValidatorTest.java @@ -33,9 +33,10 @@ import static org.hisp.dhis.tracker.imports.validation.validator.AssertValidations.assertHasError; import static org.mockito.Mockito.when; +import org.hisp.dhis.common.UID; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.trackedentity.TrackedEntityType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; import org.hisp.dhis.tracker.imports.domain.TrackedEntity; @@ -56,7 +57,7 @@ class MetaValidatorTest { private static final String TRACKED_ENTITY_TYPE_UID = "TrackedEntityTypeUid"; - private static final String TRACKED_ENTITY_UID = "TrackedEntityUid"; + private static final UID TRACKED_ENTITY_UID = UID.generate(); private MetaValidator validator; diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/SecurityOwnershipValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/SecurityOwnershipValidatorTest.java index 97337effbe69..38186f6b4c65 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/SecurityOwnershipValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/SecurityOwnershipValidatorTest.java @@ -40,6 +40,7 @@ import com.google.common.collect.Sets; import java.util.List; import java.util.Set; +import org.hisp.dhis.common.UID; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.program.Enrollment; import org.hisp.dhis.program.Program; @@ -50,8 +51,8 @@ import org.hisp.dhis.test.TestBase; import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentity.TrackedEntityType; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.acl.TrackerAccessManager; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -73,7 +74,7 @@ class SecurityOwnershipValidatorTest extends TestBase { private static final String ORG_UNIT_ID = "ORG_UNIT_ID"; - private static final String TE_ID = "TE_ID"; + private static final UID TE_ID = UID.generate(); private static final String TE_TYPE_ID = "TE_TYPE_ID"; @@ -299,7 +300,7 @@ void shouldFailWhenUpdateTEAndUserHasNoWriteAccess() { private TrackedEntity teWithNoEnrollments() { TrackedEntity trackedEntity = createTrackedEntity(organisationUnit); - trackedEntity.setUid(TE_ID); + trackedEntity.setUid(TE_ID.getValue()); trackedEntity.setEnrollments(Sets.newHashSet()); trackedEntity.setTrackedEntityType(trackedEntityType); @@ -311,7 +312,7 @@ private TrackedEntity teWithDeleteEnrollments() { enrollment.setDeleted(true); TrackedEntity trackedEntity = createTrackedEntity(organisationUnit); - trackedEntity.setUid(TE_ID); + trackedEntity.setUid(TE_ID.getValue()); trackedEntity.setEnrollments(Sets.newHashSet(enrollment)); trackedEntity.setTrackedEntityType(trackedEntityType); @@ -320,7 +321,7 @@ private TrackedEntity teWithDeleteEnrollments() { private TrackedEntity teWithEnrollments() { TrackedEntity trackedEntity = createTrackedEntity(organisationUnit); - trackedEntity.setUid(TE_ID); + trackedEntity.setUid(TE_ID.getValue()); trackedEntity.setEnrollments(Sets.newHashSet(new Enrollment())); trackedEntity.setTrackedEntityType(trackedEntityType); diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/TrackedEntityCheckUidValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/TrackedEntityCheckUidValidatorTest.java deleted file mode 100644 index 0bb0b697b425..000000000000 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/TrackedEntityCheckUidValidatorTest.java +++ /dev/null @@ -1,93 +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.imports.validation.validator.trackedentity; - -import static org.hisp.dhis.test.utils.Assertions.assertIsEmpty; -import static org.hisp.dhis.tracker.imports.validation.ValidationCode.E1048; -import static org.hisp.dhis.tracker.imports.validation.validator.AssertValidations.assertHasError; - -import org.hisp.dhis.common.CodeGenerator; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; -import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; -import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; -import org.hisp.dhis.tracker.imports.domain.TrackedEntity; -import org.hisp.dhis.tracker.imports.preheat.TrackerPreheat; -import org.hisp.dhis.tracker.imports.validation.Reporter; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -/** - * @author Enrico Colasante - */ -class TrackedEntityCheckUidValidatorTest { - - private static final String INVALID_UID = "InvalidUID"; - - private UidValidator validator; - - private TrackerBundle bundle; - - private Reporter reporter; - - @BeforeEach - void setUp() { - TrackerPreheat preheat = new TrackerPreheat(); - TrackerIdSchemeParams idSchemes = TrackerIdSchemeParams.builder().build(); - preheat.setIdSchemes(idSchemes); - reporter = new Reporter(idSchemes); - bundle = TrackerBundle.builder().preheat(preheat).build(); - - validator = new UidValidator(); - } - - @Test - void verifyTrackedEntityValidationSuccess() { - TrackedEntity trackedEntity = - TrackedEntity.builder() - .trackedEntity(CodeGenerator.generateUid()) - .orgUnit(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) - .build(); - - validator.validate(reporter, bundle, trackedEntity); - - assertIsEmpty(reporter.getErrors()); - } - - @Test - void verifyTrackedEntityWithInvalidUidFails() { - TrackedEntity trackedEntity = - TrackedEntity.builder() - .trackedEntity(INVALID_UID) - .orgUnit(MetadataIdentifier.ofUid(CodeGenerator.generateUid())) - .build(); - - validator.validate(reporter, bundle, trackedEntity); - - assertHasError(reporter, trackedEntity, E1048); - } -} diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/UpdatableFieldsValidatorTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/UpdatableFieldsValidatorTest.java index 4ebd4b9b04ae..7a4c8b1dd21c 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/UpdatableFieldsValidatorTest.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/imports/validation/validator/trackedentity/UpdatableFieldsValidatorTest.java @@ -33,13 +33,14 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.Enrollment; import org.hisp.dhis.program.Event; import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramStage; import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentity.TrackedEntityType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -66,11 +67,11 @@ class UpdatableFieldsValidatorTest { private static final String PROGRAM_STAGE_ID = "ProgramStageId"; - private static final String TRACKED_ENTITY_ID = "TrackedEntityId"; + private static final UID TRACKED_ENTITY_ID = UID.generate(); - private static final String ENROLLMENT_ID = "EnrollmentId"; + private static final UID ENROLLMENT_ID = UID.generate(); - private static final String EVENT_ID = "EventId"; + private static final UID EVENT_UID = UID.generate(); private UpdatableFieldsValidator validator; @@ -95,7 +96,7 @@ public void setUp() { when(preheat.getTrackedEntity(TRACKED_ENTITY_ID)).thenReturn(trackedEntity()); when(preheat.getEnrollment(ENROLLMENT_ID)).thenReturn(getEnrollment()); - when(preheat.getEvent(EVENT_ID)).thenReturn(event()); + when(preheat.getEvent(EVENT_UID)).thenReturn(event()); when(bundle.getPreheat()).thenReturn(preheat); @@ -133,7 +134,7 @@ private TrackedEntity trackedEntity() { trackedEntityType.setUid(TRACKED_ENTITY_TYPE_ID); TrackedEntity trackedEntity = new TrackedEntity(); - trackedEntity.setUid(TRACKED_ENTITY_ID); + trackedEntity.setUid(TRACKED_ENTITY_ID.getValue()); trackedEntity.setTrackedEntityType(trackedEntityType); return trackedEntity; } @@ -143,7 +144,7 @@ private Enrollment getEnrollment() { program.setUid(PROGRAM_ID); Enrollment enrollment = new Enrollment(); - enrollment.setUid(ENROLLMENT_ID); + enrollment.setUid(ENROLLMENT_ID.getValue()); enrollment.setProgram(program); enrollment.setTrackedEntity(trackedEntity()); return enrollment; @@ -154,7 +155,7 @@ private Event event() { programStage.setUid(PROGRAM_STAGE_ID); Event event = new Event(); - event.setUid(EVENT_ID); + event.setUid(EVENT_UID.getValue()); event.setEnrollment(getEnrollment()); event.setProgramStage(programStage); return event; diff --git a/dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/jackson/config/JacksonObjectMapperConfig.java b/dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/jackson/config/JacksonObjectMapperConfig.java index 459741f5d72a..f988322c24f6 100644 --- a/dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/jackson/config/JacksonObjectMapperConfig.java +++ b/dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/jackson/config/JacksonObjectMapperConfig.java @@ -44,6 +44,7 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import java.time.Instant; import java.util.Date; +import org.hisp.dhis.common.UID; import org.hisp.dhis.commons.jackson.config.geometry.GeometrySerializer; import org.hisp.dhis.commons.jackson.config.geometry.JtsXmlModule; import org.hisp.dhis.dataexchange.aggregate.Api; @@ -198,10 +199,11 @@ private static CsvMapper configureCsvMapper(CsvMapper mapper) { mapper.registerModule( new SimpleModule() .addSerializer(Date.class, new WriteDateStdSerializer()) - .addSerializer(Instant.class, new WriteInstantStdSerializer())); + .addSerializer(Instant.class, new WriteInstantStdSerializer()) + .addSerializer(UID.class, new UIDStdSerializer()) + .addDeserializer(UID.class, new UIDStdDeserializer())); mapper.registerModule(new Jdk8Module()); - return mapper; } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/UidValidator.java b/dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/jackson/config/UIDStdDeserializer.java similarity index 64% rename from dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/UidValidator.java rename to dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/jackson/config/UIDStdDeserializer.java index 42ca17d13b6b..8622db391b6c 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/validation/validator/event/UidValidator.java +++ b/dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/jackson/config/UIDStdDeserializer.java @@ -25,24 +25,32 @@ * (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.imports.validation.validator.event; +package org.hisp.dhis.commons.jackson.config; -import static org.hisp.dhis.tracker.imports.validation.validator.ValidationUtils.checkUidFormat; -import static org.hisp.dhis.tracker.imports.validation.validator.ValidationUtils.validateNotesUid; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import java.io.IOException; +import org.apache.commons.lang3.StringUtils; +import org.hisp.dhis.common.UID; -import org.hisp.dhis.tracker.imports.bundle.TrackerBundle; -import org.hisp.dhis.tracker.imports.domain.Event; -import org.hisp.dhis.tracker.imports.validation.Reporter; -import org.hisp.dhis.tracker.imports.validation.Validator; +public class UIDStdDeserializer extends StdDeserializer { + public UIDStdDeserializer() { + super(UID.class); + } -/** - * @author Morten Svanæs - */ -class UidValidator implements Validator { @Override - public void validate(Reporter reporter, TrackerBundle bundle, Event event) { - checkUidFormat(event.getEvent(), reporter, event, event, event.getEvent()); + public UID deserialize(JsonParser parser, DeserializationContext context) throws IOException { + String valueAsString = parser.getText(); - validateNotesUid(event.getNotes(), reporter, event); + if (StringUtils.isNotBlank(valueAsString)) { + try { + return UID.of(valueAsString); + } catch (Exception e) { + throw new JsonParseException(parser, e.getMessage()); + } + } + return null; } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/ReferenceTrackerEntity.java b/dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/jackson/config/UIDStdSerializer.java similarity index 73% rename from dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/ReferenceTrackerEntity.java rename to dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/jackson/config/UIDStdSerializer.java index cb18b6c9e12a..68785590323b 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/ReferenceTrackerEntity.java +++ b/dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/jackson/config/UIDStdSerializer.java @@ -25,26 +25,22 @@ * (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.imports.preheat; +package org.hisp.dhis.commons.jackson.config; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import java.io.IOException; +import org.hisp.dhis.common.UID; -@Getter -@RequiredArgsConstructor -public class ReferenceTrackerEntity { - /** - * Reference uid: this correspond to the UID of a TE, PS or event from the tracker import payload - */ - private final String uid; - - /** - * Reference uid of the parent object of this Reference. This is only populated if uid references - * a ProgramStage or an Event - */ - private final String parentUid; +public class UIDStdSerializer extends StdSerializer { + public UIDStdSerializer() { + super(UID.class); + } - public boolean isRoot() { - return this.parentUid.equals("ROOT"); + @Override + public void serialize(UID uid, JsonGenerator gen, SerializerProvider provider) + throws IOException { + gen.writeString(uid == null ? null : uid.toString()); } } diff --git a/dhis-2/dhis-support/dhis-support-commons/src/test/java/org/hisp/dhis/commons/util/TextUtilsTest.java b/dhis-2/dhis-support/dhis-support-commons/src/test/java/org/hisp/dhis/commons/util/TextUtilsTest.java index c3d9cd7c915f..495b414b80b7 100644 --- a/dhis-2/dhis-support/dhis-support-commons/src/test/java/org/hisp/dhis/commons/util/TextUtilsTest.java +++ b/dhis-2/dhis-support/dhis-support-commons/src/test/java/org/hisp/dhis/commons/util/TextUtilsTest.java @@ -199,6 +199,13 @@ void testReplace() { Map.of("first_name", "John", "last_name", "Doe"))); } + @Test + void testReplaceMultiple() { + assertEquals( + "Hey John, my name is John", + TextUtils.replace("Hey ${name}, my name is ${name}", Map.of("name", "John"))); + } + @Test void testReplaceVarargs() { assertEquals("Welcome John", TextUtils.replace("Welcome ${first_name}", "first_name", "John")); diff --git a/dhis-2/dhis-support/dhis-support-db-migration/src/main/java/org/hisp/dhis/db/migration/v42/V2_42_21__Add_new_column_into_visualization_and_migrate_relative_periods.java b/dhis-2/dhis-support/dhis-support-db-migration/src/main/java/org/hisp/dhis/db/migration/v42/V2_42_21__Add_new_column_into_visualization_and_migrate_relative_periods.java index 261c5c463164..81ed3ddca1f9 100644 --- a/dhis-2/dhis-support/dhis-support-db-migration/src/main/java/org/hisp/dhis/db/migration/v42/V2_42_21__Add_new_column_into_visualization_and_migrate_relative_periods.java +++ b/dhis-2/dhis-support/dhis-support-db-migration/src/main/java/org/hisp/dhis/db/migration/v42/V2_42_21__Add_new_column_into_visualization_and_migrate_relative_periods.java @@ -111,8 +111,35 @@ public class V2_42_21__Add_new_column_into_visualization_and_migrate_relative_pe } public void migrate(Context context) throws SQLException { - step1(context); - step2(context); + if (!isMigrationAlreadyApplied(context)) { + step1(context); + step2(context); + } + } + + private boolean isMigrationAlreadyApplied(Context context) { + String schema = null; + try { + schema = context.getConnection().getSchema(); + } catch (SQLException e) { + log.error("Schema check: ", e); + } + + final String checkColumnExists = + "select exists (select 1 from information_schema.columns where " + + (schema != null ? "table_schema='" + schema + "' and " : "") + + "table_name='eventvisualization' and column_name='relativeperiods')"; + + try (Statement statement = context.getConnection().createStatement(); + ResultSet rs = statement.executeQuery(checkColumnExists)) { + while (rs.next()) { + return rs.getBoolean(1); + } + } catch (SQLException e) { + log.error("Check failed: ", e); + } + + return false; } /** @@ -213,7 +240,7 @@ private static void copyPeriodsToJsonColumn( ps.setLong(2, parentTableId); ps.executeUpdate(); - // Clear the list of periods so it can be reused in the next iteration. + // Clear the list of periods, so it can be reused in the next iteration. periodList.clear(); } } diff --git a/dhis-2/dhis-support/dhis-support-db-migration/src/main/resources/org/hisp/dhis/db/migration/2.42/V2_42_23__Add_embedded_to_dashboard.sql b/dhis-2/dhis-support/dhis-support-db-migration/src/main/resources/org/hisp/dhis/db/migration/2.42/V2_42_23__Add_embedded_to_dashboard.sql new file mode 100644 index 000000000000..fe3aea8d5ab0 --- /dev/null +++ b/dhis-2/dhis-support/dhis-support-db-migration/src/main/resources/org/hisp/dhis/db/migration/2.42/V2_42_23__Add_embedded_to_dashboard.sql @@ -0,0 +1,2 @@ + +alter table "dashboard" add column if not exists "embedded" jsonb null; diff --git a/dhis-2/dhis-support/dhis-support-db-migration/src/main/resources/org/hisp/dhis/db/migration/2.42/V2_42_24__Add_skipIndividualAnalytics_column_to_program_attributes.sql b/dhis-2/dhis-support/dhis-support-db-migration/src/main/resources/org/hisp/dhis/db/migration/2.42/V2_42_24__Add_skipIndividualAnalytics_column_to_program_attributes.sql new file mode 100644 index 000000000000..d910b54175b2 --- /dev/null +++ b/dhis-2/dhis-support/dhis-support-db-migration/src/main/resources/org/hisp/dhis/db/migration/2.42/V2_42_24__Add_skipIndividualAnalytics_column_to_program_attributes.sql @@ -0,0 +1,4 @@ + +-- Add the new column with a default value of false +ALTER TABLE program_attributes + ADD COLUMN IF NOT EXISTS skipIndividualAnalytics boolean DEFAULT FALSE; \ No newline at end of file diff --git a/dhis-2/dhis-support/dhis-support-db-migration/src/main/resources/org/hisp/dhis/db/migration/2.42/V2_42_25__Migrate_event_change_logs.sql b/dhis-2/dhis-support/dhis-support-db-migration/src/main/resources/org/hisp/dhis/db/migration/2.42/V2_42_25__Migrate_event_change_logs.sql new file mode 100644 index 000000000000..180f2349c658 --- /dev/null +++ b/dhis-2/dhis-support/dhis-support-db-migration/src/main/resources/org/hisp/dhis/db/migration/2.42/V2_42_25__Migrate_event_change_logs.sql @@ -0,0 +1,60 @@ +-- DHIS2-18117: migrate tracker event change logs from trackedentitydatavalueaudit to eventchangelogs + +-- Create new table and add constraints +create sequence if not exists eventchangelog_sequence; + +create table if not exists eventchangelog ( + eventchangelogid int8 not null default nextval('eventchangelog_sequence'), + eventid int8 not null, + dataelementid int8 null, + eventproperty varchar(100) null, + currentvalue varchar(50000) null, + previousvalue varchar(50000) null, + changelogtype varchar(100) not null, + created timestamp not null, + createdby varchar(255) not null, + constraint eventchangelog_pkey primary key (eventchangelogid), + constraint fk_eventchangelog_dataelementid foreign key (dataelementid) references dataelement(dataelementid), + constraint fk_eventchangelog_eventid foreign key (eventid) references event(eventid) +); + +create index if not exists eventchangelog_eventid_created_idx on eventchangelog (eventid, created desc); + +-- Migrate data from trackedentitydatavalueaudit to eventchangelog +insert into eventchangelog (eventchangelogid, eventid, dataelementid, currentvalue, previousvalue, created, createdby, changelogtype) +select + cl.trackedentitydatavalueauditid, + cl.eventid, + cl.dataelementid, + case + when cl.audittype = 'CREATE' then cl.previouschangelogvalue + when cl.audittype = 'UPDATE' and cl.currentchangelogvalue is null then cl.currentvalue + when cl.audittype = 'UPDATE' and cl.currentchangelogvalue is not null then cl.currentchangelogvalue + end, + case + when cl.audittype = 'DELETE' then cl.previouschangelogvalue + when cl.audittype = 'UPDATE' then cl.previouschangelogvalue + end, + coalesce(cl.created, '1970-01-01 00:00:00'), + coalesce (cl.modifiedby, '--'), + cl.audittype +from + (select t.trackedentitydatavalueauditid, t.eventid, t.dataelementid, t.created, t.audittype, t.modifiedby, + lag (t.value) over (partition by t.eventid, t.dataelementid order by t.created desc) as currentchangelogvalue, + t.value AS previouschangelogvalue, + e.eventdatavalues -> d.uid ->> 'value' as currentvalue + from trackedentitydatavalueaudit t + join event e using (eventid) + join dataelement d using (dataelementid) + order by t.trackedentitydatavalueauditid) cl +where cl.audittype in ('CREATE', 'UPDATE', 'DELETE') +and not exists ( + select 1 from eventchangelog ecl where ecl.eventchangelogid = cl.trackedentitydatavalueauditid +); + +-- Set sequence to highest value +select setval('eventchangelog_sequence', max(eventchangelogid)) from eventchangelog; + +-- Delete the migrated data from the old table, keeping the unmigrated data. +delete from trackedentitydatavalueaudit +where trackedentitydatavalueauditid in (select eventchangelogid from eventchangelog); \ No newline at end of file diff --git a/dhis-2/dhis-support/dhis-support-db-migration/src/main/resources/org/hisp/dhis/db/migration/2.42/V2_42_30__Create_uid_check_constraint.sql b/dhis-2/dhis-support/dhis-support-db-migration/src/main/resources/org/hisp/dhis/db/migration/2.42/V2_42_30__Create_uid_check_constraint.sql new file mode 100644 index 000000000000..deadff84df91 --- /dev/null +++ b/dhis-2/dhis-support/dhis-support-db-migration/src/main/resources/org/hisp/dhis/db/migration/2.42/V2_42_30__Create_uid_check_constraint.sql @@ -0,0 +1,15 @@ +create or replace function check_uid(uid varchar) returns boolean + language plpgsql +as +$$ +begin + return uid ~ '^[A-Za-z]' and uid ~ '^[A-Za-z0-9]{11}$'; +end; +$$; + +CREATE DOMAIN uid_type AS VARCHAR(11) + CHECK (check_uid(VALUE)); + +alter table potentialduplicate add constraint potentialduplicate_check_uid CHECK (check_uid(uid)); +alter table potentialduplicate add constraint potentialduplicate_check_original CHECK (check_uid(original)); +alter table potentialduplicate add constraint potentialduplicate_check_duplicate CHECK (check_uid(duplicate)); \ No newline at end of file diff --git a/dhis-2/dhis-support/dhis-support-external/src/main/java/org/hisp/dhis/external/conf/ConfigurationKey.java b/dhis-2/dhis-support/dhis-support-external/src/main/java/org/hisp/dhis/external/conf/ConfigurationKey.java index 2ae21e7515a0..91b31022342f 100644 --- a/dhis-2/dhis-support/dhis-support-external/src/main/java/org/hisp/dhis/external/conf/ConfigurationKey.java +++ b/dhis-2/dhis-support/dhis-support-external/src/main/java/org/hisp/dhis/external/conf/ConfigurationKey.java @@ -90,10 +90,10 @@ public enum ConfigurationKey { /** Analytics database platform. */ ANALYTICS_DATABASE("analytics.database", "POSTGRESQL", false), - /** Analytics database JDBC catalog name. */ + /** Analytics database JDBC catalog name. Applies to Apache Doris. */ ANALYTICS_DATABASE_CATALOG("analytics.database.catalog", "pg_dhis", false), - /** Analytics database JDBC driver filename. */ + /** Analytics database JDBC driver filename. Applies to Apache Doris. */ ANALYTICS_DATABASE_DRIVER_FILENAME("analytics.database.driver_filename", "postgresql.jar", false), /** JDBC driver class. */ @@ -118,6 +118,15 @@ public enum ConfigurationKey { /** Database password (sensitive). */ CONNECTION_PASSWORD("connection.password", "", true), + /** Database host (hostname or IP). Applies to ClickHouse. */ + CONNECTION_HOST("connection.host", "", false), + + /** Database port number. Applies to ClickHouse. */ + CONNECTION_PORT("connection.port", "5432", false), + + /** Database port number. Applies to ClickHouse. */ + CONNECTION_DATABASE("connection.database", "", false), + /** Analytics Database password (sensitive). */ ANALYTICS_CONNECTION_PASSWORD("analytics.connection.password", "", true), @@ -127,6 +136,12 @@ public enum ConfigurationKey { /** Sets 'hibernate.cache.use_query_cache'. (default: true) */ USE_QUERY_CACHE("hibernate.cache.use_query_cache", "true", false), + /** + * Show SQL statements generated by hibernate in the log. Can be 'true', 'false'. (default: false) + * This will also enable hibernate.format_sql and hibernate.highlight_sql + */ + HIBERNATE_SHOW_SQL("hibernate.show_sql", "false", false), + /** * Sets 'hibernate.hbm2ddl.auto' (default: validate). This can be overridden by the same property * loaded by any class implementing {@link DhisConfigurationProvider} like {@link @@ -676,6 +691,11 @@ public enum ConfigurationKey { private final String defaultValue; + /** + * Confidential means that the system setting will be encrypted and not visible through the API. + * The system setting will be used internally in the backend, but cannot be used by web apps and + * clients. + */ private final boolean confidential; private final String[] aliases; diff --git a/dhis-2/dhis-support/dhis-support-external/src/main/java/org/hisp/dhis/external/conf/DhisConfigurationProvider.java b/dhis-2/dhis-support/dhis-support-external/src/main/java/org/hisp/dhis/external/conf/DhisConfigurationProvider.java index 88a7c293aa10..3c1cebe2f81f 100644 --- a/dhis-2/dhis-support/dhis-support-external/src/main/java/org/hisp/dhis/external/conf/DhisConfigurationProvider.java +++ b/dhis-2/dhis-support/dhis-support-external/src/main/java/org/hisp/dhis/external/conf/DhisConfigurationProvider.java @@ -33,6 +33,7 @@ import java.util.Map; import java.util.Optional; import java.util.Properties; +import org.apache.commons.lang3.math.NumberUtils; import org.hisp.dhis.encryption.EncryptionStatus; import org.hisp.dhis.external.conf.model.GoogleAccessToken; @@ -63,6 +64,10 @@ static boolean isOff(String value) { return !isOn(value); } + static int toInt(String value) { + return NumberUtils.isParsable(value) ? Integer.parseInt(value) : -1; + } + /** * Indicates whether a value for the given key is equal to "on". * @@ -83,6 +88,18 @@ default boolean isDisabled(ConfigurationKey key) { return !DhisConfigurationProvider.isOn(getProperty(key)); } + /** + * Get the property value for the given key as an int, or the default value as + * specified in the {@link ConfigurationKey#getDefaultValue()} for the configuration key if not + * exists. + * + * @param key the configuration key. + * @return the property value. + */ + default int getIntProperty(ConfigurationKey key) { + return toInt(getProperty(key)); + } + /** * Get configuration as a set of properties. * diff --git a/dhis-2/dhis-support/dhis-support-external/src/test/java/org/hisp/dhis/external/conf/DhisConfigurationProviderTest.java b/dhis-2/dhis-support/dhis-support-external/src/test/java/org/hisp/dhis/external/conf/DhisConfigurationProviderTest.java index e5aa2afb00ed..d5dccbf1cc16 100644 --- a/dhis-2/dhis-support/dhis-support-external/src/test/java/org/hisp/dhis/external/conf/DhisConfigurationProviderTest.java +++ b/dhis-2/dhis-support/dhis-support-external/src/test/java/org/hisp/dhis/external/conf/DhisConfigurationProviderTest.java @@ -76,6 +76,12 @@ void isEnabled() { assertFalse(configProvider.isEnabled(ConfigurationKey.METHOD_QUERY_LOGGING_ENABLED)); } + @Test + void getIntProperty() { + assertEquals(80, configProvider.getIntProperty(ConfigurationKey.CONNECTION_POOL_MAX_SIZE)); + assertEquals(10, configProvider.getIntProperty(ConfigurationKey.CONNECTION_POOL_MIN_SIZE)); + } + @Test @DisplayName("remote servers retrieved from config should have expected values") void getRemoteServersAllowedTest() { diff --git a/dhis-2/dhis-support/dhis-support-external/src/test/resources/dhis.conf b/dhis-2/dhis-support/dhis-support-external/src/test/resources/dhis.conf index 0a5d68e70730..d22b482c8eb6 100644 --- a/dhis-2/dhis-support/dhis-support-external/src/test/resources/dhis.conf +++ b/dhis-2/dhis-support/dhis-support-external/src/test/resources/dhis.conf @@ -2,5 +2,4 @@ monitoring.api.enabled = off enable.query.logging = true method.query.logging.enabled = false system.remote_servers_allowed = https://validtesturl.com/,https://validtesturl2.com/ - - +connection.pool.min_size = 10 \ No newline at end of file diff --git a/dhis-2/dhis-support/dhis-support-hibernate/pom.xml b/dhis-2/dhis-support/dhis-support-hibernate/pom.xml index 4ecae692427e..2a818f633722 100644 --- a/dhis-2/dhis-support/dhis-support-hibernate/pom.xml +++ b/dhis-2/dhis-support/dhis-support-hibernate/pom.xml @@ -94,6 +94,16 @@ org.postgresql postgresql + + com.mysql + mysql-connector-j + + + com.clickhouse + clickhouse-jdbc + ${clickhouse-jdbc.version} + all + diff --git a/dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/config/AnalyticsDataSourceConfig.java b/dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/config/AnalyticsDataSourceConfig.java index 90fe42ec1d63..344fc26ad20d 100644 --- a/dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/config/AnalyticsDataSourceConfig.java +++ b/dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/config/AnalyticsDataSourceConfig.java @@ -73,14 +73,14 @@ public DataSource jdbcActualDataSource( @Qualifier("actualDataSource") DataSource actualDataSource) { if (config.isAnalyticsDatabaseConfigured()) { log.info( - "Analytics data source detected with database: '{}', connection URL: '{}'", + "Analytics database detected: '{}', connection URL: '{}'", config.getProperty(ANALYTICS_DATABASE), config.getProperty(ANALYTICS_CONNECTION_URL)); return getAnalyticsDataSource(); } else { log.info( - "Analytics data source connection URL not specified with key: '{}'", + "Analytics database connection URL not specified with key: '{}'", ANALYTICS_CONNECTION_URL.getKey()); return actualDataSource; diff --git a/dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/config/HibernateConfig.java b/dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/config/HibernateConfig.java index c26fb5e58ef9..ddd4f590c00f 100644 --- a/dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/config/HibernateConfig.java +++ b/dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/config/HibernateConfig.java @@ -45,6 +45,7 @@ import org.hibernate.cache.jcache.internal.JCacheRegionFactory; import org.hibernate.cfg.AvailableSettings; import org.hibernate.jpa.HibernatePersistenceProvider; +import org.hibernate.tool.schema.Action; import org.hisp.dhis.cache.DefaultHibernateCacheManager; import org.hisp.dhis.dbms.DbmsManager; import org.hisp.dhis.dbms.HibernateDbmsManager; @@ -179,6 +180,18 @@ private Properties getAdditionalProperties(DhisConfigurationProvider dhisConfig) MissingCacheStrategy.CREATE.getExternalRepresentation()); } + properties.put( + AvailableSettings.HBM2DDL_AUTO, + Action.valueOf(dhisConfig.getProperty(ConfigurationKey.CONNECTION_SCHEMA).toUpperCase())); + + properties.put( + AvailableSettings.SHOW_SQL, dhisConfig.getProperty(ConfigurationKey.HIBERNATE_SHOW_SQL)); + properties.put( + AvailableSettings.FORMAT_SQL, dhisConfig.getProperty(ConfigurationKey.HIBERNATE_SHOW_SQL)); + properties.put( + AvailableSettings.HIGHLIGHT_SQL, + dhisConfig.getProperty(ConfigurationKey.HIBERNATE_SHOW_SQL)); + // TODO: this is anti-pattern and should be turn off properties.put("hibernate.allow_update_outside_transaction", "true"); diff --git a/dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/dbms/HibernateDbmsManager.java b/dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/dbms/HibernateDbmsManager.java index 20eaeecce803..9b7a0fbb3307 100644 --- a/dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/dbms/HibernateDbmsManager.java +++ b/dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/dbms/HibernateDbmsManager.java @@ -216,6 +216,7 @@ public void emptyDatabase() { emptyTable("programnotificationinstance"); emptyTable("trackedentitydatavalueaudit"); + emptyTable("eventchangelog"); emptyTable("trackedentityprogramowner"); emptyTable("event_notes"); diff --git a/dhis-2/dhis-support/dhis-support-hibernate/src/main/resources/org/hisp/dhis/usertype/UserTypes.hbm.xml b/dhis-2/dhis-support/dhis-support-hibernate/src/main/resources/org/hisp/dhis/usertype/UserTypes.hbm.xml index 92f2ab4d29fc..5ffc6ca8c9fb 100644 --- a/dhis-2/dhis-support/dhis-support-hibernate/src/main/resources/org/hisp/dhis/usertype/UserTypes.hbm.xml +++ b/dhis-2/dhis-support/dhis-support-hibernate/src/main/resources/org/hisp/dhis/usertype/UserTypes.hbm.xml @@ -77,6 +77,10 @@ org.hisp.dhis.dashboard.design.ItemConfig + + org.hisp.dhis.dashboard.embedded.EmbeddedDashboard + + org.hisp.dhis.textpattern.TextPattern diff --git a/dhis-2/dhis-support/dhis-support-system/pom.xml b/dhis-2/dhis-support/dhis-support-system/pom.xml index c73ab28a73b6..72d57d056c6b 100644 --- a/dhis-2/dhis-support/dhis-support-system/pom.xml +++ b/dhis-2/dhis-support/dhis-support-system/pom.xml @@ -144,10 +144,6 @@ org.apache.commons commons-math3 - - commons-codec - commons-codec - commons-validator commons-validator diff --git a/dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/cache/DefaultCacheProvider.java b/dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/cache/DefaultCacheProvider.java index c59bedde6415..dc65831869b3 100644 --- a/dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/cache/DefaultCacheProvider.java +++ b/dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/cache/DefaultCacheProvider.java @@ -28,16 +28,17 @@ package org.hisp.dhis.cache; import static java.util.concurrent.TimeUnit.HOURS; -import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; import static org.hisp.dhis.commons.util.SystemUtils.isEnableCacheInTest; import static org.hisp.dhis.commons.util.SystemUtils.isTestRun; -import java.time.Duration; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; +import org.apache.commons.lang3.StringUtils; +import org.hisp.dhis.common.cache.Region; import org.hisp.dhis.common.event.ApplicationCacheClearedEvent; +import org.hisp.dhis.common.event.CacheInvalidationEvent; import org.hisp.dhis.external.conf.ConfigurationKey; import org.hisp.dhis.external.conf.DhisConfigurationProvider; import org.springframework.context.event.EventListener; @@ -56,8 +57,6 @@ public class DefaultCacheProvider implements CacheProvider { private static final long SIZE_100 = 100; - private static final long SIZE_500 = 500; - private static final long SIZE_1K = 1_000; private static final long SIZE_10K = 10_000; @@ -78,61 +77,6 @@ public DefaultCacheProvider( Double.parseDouble(dhisConfig.getProperty(ConfigurationKey.SYSTEM_CACHE_MAX_SIZE_FACTOR)); } - /** - * Enum is used to make sure we do not use same region twice. Each method should have its own - * constant. - */ - @SuppressWarnings("squid:S115") // allow non enum-ish names - private enum Region { - analyticsResponse, - defaultObjectCache, - isDataApproved, - allConstantsCache, - inUserOrgUnitHierarchy, - isUserViewOrgUnitHierarchy, - inUserSearchOrgUnitHierarchy, - userCaptureOrgUnitCountThreshold, - periodIdCache, - userAccountRecoverAttempt, - userFailedLoginAttempt, - twoFaDisableFailedAttempt, - programOwner, - programTempOwner, - userIdCache, - currentUserGroupInfoCache, - userSetting, - attrOptionComboIdCache, - systemSetting, - googleAccessToken, - dataItemsPagination, - metadataAttributes, - canDataWriteCocCache, - analyticsSql, - dataElementCache, - propertyTransformerCache, - programHasRulesCache, - programRuleVariablesCache, - userGroupNameCache, - userDisplayNameCache, - programWebHookNotificationTemplateCache, - programStageWebHookNotificationTemplateCache, - pgmOrgUnitAssocCache, - catOptOrgUnitAssocCache, - dataSetOrgUnitAssocCache, - apiTokensCache, - programCache, - teAttributesCache, - programTeAttributesCache, - userGroupUIDCache, - oldTrackerSecurityCache, - securityCache, - runningJobsInfo, - jobCancelRequested, - dataIntegritySummaryCache, - dataIntegrityDetailsCache, - queryAliasCache - } - private final Map> allCaches = new ConcurrentHashMap<>(); private long orZeroInTestRun(long value) { @@ -160,13 +104,19 @@ public void handleApplicationCachesCleared(ApplicationCacheClearedEvent event) { allCaches.values().forEach(Cache::invalidateAll); } + @EventListener @Override - public Cache createAnalyticsResponseCache(Duration initialExpirationTime) { - return registerCache( - this.newBuilder() - .forRegion(Region.analyticsResponse.name()) - .expireAfterWrite(initialExpirationTime.toMillis(), MILLISECONDS) - .withMaximumSize(orZeroInTestRun(getActualSize(SIZE_10K)))); + public void handleCacheInvalidationEvent(CacheInvalidationEvent event) { + Cache cache = allCaches.get(event.getRegion().name()); + if (cache == null) { + return; + } + + if (StringUtils.isNotBlank(event.getKey())) { + cache.invalidate(event.getKey()); + } else { + cache.invalidateAll(); + } } @Override @@ -233,17 +183,6 @@ public Cache createInUserOrgUnitHierarchyCache() { .withMaximumSize(orZeroInTestRun(getActualSize(SIZE_10K)))); } - @Override - public Cache createInUserViewOrgUnitHierarchyCache() { - return registerCache( - this.newBuilder() - .forRegion(Region.isUserViewOrgUnitHierarchy.name()) - .expireAfterWrite(3, TimeUnit.HOURS) - .withInitialCapacity((int) getActualSize(SIZE_1K)) - .forceInMemory() - .withMaximumSize(orZeroInTestRun(getActualSize(SIZE_10K)))); - } - @Override public Cache createInUserSearchOrgUnitHierarchyCache() { return registerCache( @@ -255,17 +194,6 @@ public Cache createInUserSearchOrgUnitHierarchyCache() { .withMaximumSize(orZeroInTestRun(getActualSize(SIZE_10K)))); } - @Override - public Cache createUserCaptureOrgUnitThresholdCache() { - return registerCache( - this.newBuilder() - .forRegion(Region.userCaptureOrgUnitCountThreshold.name()) - .expireAfterWrite(1, TimeUnit.HOURS) - .withInitialCapacity((int) getActualSize(SIZE_1K)) - .forceInMemory() - .withMaximumSize(orZeroInTestRun(getActualSize(SIZE_10K)))); - } - @Override public Cache createPeriodIdCache() { return registerCache( @@ -322,17 +250,6 @@ public Cache createProgramTempOwnerCache() { .withMaximumSize(orZeroInTestRun(getActualSize(SIZE_10K)))); } - @Override - public Cache createUserIdCache() { - return registerCache( - this.newBuilder() - .forRegion(Region.userIdCache.name()) - .expireAfterWrite(1, TimeUnit.HOURS) - .withInitialCapacity((int) getActualSize(200)) - .forceInMemory() - .withMaximumSize(orZeroInTestRun(getActualSize(SIZE_10K)))); - } - @Override public Cache createCurrentUserGroupInfoCache() { return registerCache( @@ -395,7 +312,6 @@ public Cache createCanDataWriteCocCache() { .forRegion(Region.canDataWriteCocCache.name()) .expireAfterWrite(3, TimeUnit.HOURS) .withInitialCapacity((int) getActualSize(SIZE_1K)) - .forceInMemory() .withMaximumSize(orZeroInTestRun(getActualSize(SIZE_10K)))); } @@ -410,17 +326,6 @@ public Cache createAnalyticsSqlCache() { .withMaximumSize(orZeroInTestRun(getActualSize(SIZE_10K)))); } - @Override - public Cache createDataElementCache() { - return registerCache( - this.newBuilder() - .forRegion(Region.dataElementCache.name()) - .expireAfterWrite(60, TimeUnit.MINUTES) - .withInitialCapacity((int) getActualSize(SIZE_1K)) - .forceInMemory() - .withMaximumSize(orZeroInTestRun(getActualSize(SIZE_10K)))); - } - @Override public Cache createPropertyTransformerCache() { return registerCache( @@ -465,28 +370,6 @@ public Cache createUserDisplayNameCache() { .withMaximumSize(orZeroInTestRun(SIZE_10K))); } - @Override - public Cache createProgramWebHookNotificationTemplateCache() { - return registerCache( - this.newBuilder() - .forRegion(Region.programWebHookNotificationTemplateCache.name()) - .expireAfterWrite(3, TimeUnit.HOURS) - .withInitialCapacity((int) getActualSize(20)) - .forceInMemory() - .withMaximumSize(orZeroInTestRun(getActualSize(SIZE_500)))); - } - - @Override - public Cache createProgramStageWebHookNotificationTemplateCache() { - return registerCache( - this.newBuilder() - .forRegion(Region.programStageWebHookNotificationTemplateCache.name()) - .expireAfterWrite(3, TimeUnit.HOURS) - .withInitialCapacity((int) getActualSize(20)) - .forceInMemory() - .withMaximumSize(orZeroInTestRun(getActualSize(SIZE_500)))); - } - @Override public Cache createProgramOrgUnitAssociationCache() { return registerCache( @@ -528,16 +411,6 @@ public Cache createApiKeyCache() { .withMaximumSize(orZeroInTestRun(getActualSize(SIZE_10K)))); } - @Override - public Cache createProgramCache() { - return registerCache( - this.newBuilder() - .forRegion(Region.programCache.name()) - .expireAfterWrite(1, TimeUnit.MINUTES) - .withInitialCapacity((int) getActualSize(SIZE_1K)) - .withMaximumSize(orZeroInTestRun(getActualSize(SIZE_10K)))); - } - @Override public Cache createTeAttributesCache() { return registerCache( diff --git a/dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/CodecUtils.java b/dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/CodecUtils.java index 95d2872c99f8..a4ca85cae8c0 100644 --- a/dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/CodecUtils.java +++ b/dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/CodecUtils.java @@ -31,7 +31,6 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Base64; -import org.apache.commons.codec.digest.DigestUtils; /** * Utility class for encoding and decoding operations. @@ -85,19 +84,4 @@ public static String utf8UrlEncode(String string) { throw new RuntimeException(e); } } - - /** - * Calculates the MD5 digest and returns the value as a 32 character hex string. Returns null if - * input is null. - * - * @param value the value to digest. - * @return MD5 digest as a hex string. - */ - public static String md5Hex(String value) { - return value != null ? DigestUtils.md5Hex(value) : null; - } - - public static String sha1Hex(String value) { - return value != null ? DigestUtils.sha1Hex(value) : null; - } } diff --git a/dhis-2/dhis-support/dhis-support-system/src/test/java/org/hisp/dhis/cache/DefaultCacheProviderTest.java b/dhis-2/dhis-support/dhis-support-system/src/test/java/org/hisp/dhis/cache/DefaultCacheProviderTest.java new file mode 100644 index 000000000000..a758ede0d2d7 --- /dev/null +++ b/dhis-2/dhis-support/dhis-support-system/src/test/java/org/hisp/dhis/cache/DefaultCacheProviderTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2004-2024, 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.cache; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.anyLong; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import org.hisp.dhis.common.cache.Region; +import org.hisp.dhis.common.event.CacheInvalidationEvent; +import org.hisp.dhis.external.conf.ConfigurationKey; +import org.hisp.dhis.external.conf.DhisConfigurationProvider; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.env.MockEnvironment; + +@ExtendWith(MockitoExtension.class) +class DefaultCacheProviderTest { + + @Mock private DhisConfigurationProvider dhisConfigurationProvider; + + @Mock private CacheBuilderProvider cacheBuilderProvider; + + @Mock private CacheBuilder cacheBuilder; + + @Mock private Cache mockCache; + + MockEnvironment environment = new MockEnvironment(); + + private DefaultCacheProvider defaultCacheProvider; + + @BeforeEach + public void setUp() { + environment.setActiveProfiles("nonTestProfile"); + when(dhisConfigurationProvider.getProperty(ConfigurationKey.SYSTEM_CACHE_MAX_SIZE_FACTOR)) + .thenReturn("0.5"); + defaultCacheProvider = + new DefaultCacheProvider(cacheBuilderProvider, environment, dhisConfigurationProvider); + } + + private void registerCache(Region region, Runnable cacheCreator) { + when(cacheBuilder.getRegion()).thenReturn(region.name()); + when(cacheBuilder.forRegion(any())).thenReturn(cacheBuilder); + when(cacheBuilder.expireAfterWrite(anyLong(), any())).thenReturn(cacheBuilder); + when(cacheBuilder.withMaximumSize(anyLong())).thenReturn(cacheBuilder); + when(cacheBuilderProvider.newCacheBuilder()).thenReturn(cacheBuilder); + when(cacheBuilder.build()).thenReturn(mockCache); + cacheCreator.run(); + } + + @Test + void testInvalidateSpecificKey() { + + registerCache(Region.isDataApproved, () -> defaultCacheProvider.createIsDataApprovedCache()); + String key = "specificKeyInRegionToInvalidate"; + CacheInvalidationEvent event = new CacheInvalidationEvent(this, Region.isDataApproved, key); + + defaultCacheProvider.handleCacheInvalidationEvent(event); + + verify(mockCache).invalidate(key); + verifyNoMoreInteractions(mockCache); + } + + @Test + void testInvalidateAllKeysInRegion() { + when(cacheBuilder.withInitialCapacity(anyInt())).thenReturn(cacheBuilder); + registerCache( + Region.canDataWriteCocCache, () -> defaultCacheProvider.createCanDataWriteCocCache()); + + CacheInvalidationEvent event = new CacheInvalidationEvent(this, Region.canDataWriteCocCache); + + defaultCacheProvider.handleCacheInvalidationEvent(event); + + verify(mockCache).invalidateAll(); + verifyNoMoreInteractions(mockCache); + } + + @Test + void testInvalidateUncachedRegion() { + CacheInvalidationEvent event = new CacheInvalidationEvent(this, Region.canDataWriteCocCache); + + defaultCacheProvider.handleCacheInvalidationEvent(event); + + verifyNoInteractions(mockCache); + } +} diff --git a/dhis-2/dhis-support/dhis-support-test/pom.xml b/dhis-2/dhis-support/dhis-support-test/pom.xml index 0ca9347cd56a..4d3082e80183 100644 --- a/dhis-2/dhis-support/dhis-support-test/pom.xml +++ b/dhis-2/dhis-support/dhis-support-test/pom.xml @@ -181,5 +181,9 @@ com.google.code.findbugs jsr305 + + net.ttddyy + datasource-proxy + diff --git a/dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/TestBase.java b/dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/TestBase.java index 895359a75396..dc740f453a01 100644 --- a/dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/TestBase.java +++ b/dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/TestBase.java @@ -72,6 +72,7 @@ import org.hisp.dhis.attribute.Attribute; import org.hisp.dhis.category.Category; import org.hisp.dhis.category.CategoryCombo; +import org.hisp.dhis.category.CategoryDimension; import org.hisp.dhis.category.CategoryOption; import org.hisp.dhis.category.CategoryOptionCombo; import org.hisp.dhis.category.CategoryOptionGroup; @@ -107,6 +108,7 @@ import org.hisp.dhis.dataexchange.aggregate.TargetRequest; import org.hisp.dhis.dataexchange.aggregate.TargetType; import org.hisp.dhis.dataset.DataSet; +import org.hisp.dhis.dataset.Section; import org.hisp.dhis.dataset.notifications.DataSetNotificationRecipient; import org.hisp.dhis.dataset.notifications.DataSetNotificationTemplate; import org.hisp.dhis.dataset.notifications.DataSetNotificationTrigger; @@ -728,6 +730,19 @@ public static CategoryOption createCategoryOption(String name, String uid) { return categoryOption; } + /** + * Creates a {@see CategoryDimension} with name and uid. + * + * @param dimension desired category + * @return {@see CategoryDimension} + */ + public static CategoryDimension createCategoryDimension(Category dimension) { + CategoryDimension categoryDimension = new CategoryDimension(); + categoryDimension.setDimension(dimension); + + return categoryDimension; + } + /** * @param uniqueIdentifier A unique character to identify the category option group. * @param categoryOptions the category options. @@ -903,6 +918,20 @@ public static IndicatorGroupSet createIndicatorGroupSet(char uniqueCharacter) { return groupSet; } + /** + * @param uniqueCharacter A unique character to identify the object. + */ + public static Section createSection( + char uniqueCharacter, + DataSet dataSet, + List dataElements, + List indicators) { + Section section = new Section("Section" + uniqueCharacter, dataSet, dataElements, Set.of()); + section.setAutoFields(); + section.getIndicators().addAll(indicators); + return section; + } + /** * @param uniqueCharacter A unique character to identify the object. */ diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/PeriodTypeSupplier.java b/dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/config/QueryCountDataSourceProxy.java similarity index 50% rename from dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/PeriodTypeSupplier.java rename to dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/config/QueryCountDataSourceProxy.java index 707ec0955156..8ab98aea420e 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/imports/preheat/supplier/PeriodTypeSupplier.java +++ b/dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/config/QueryCountDataSourceProxy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2022, University of Oslo + * Copyright (c) 2004-2024, University of Oslo * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -25,47 +25,56 @@ * (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.imports.preheat.supplier; +package org.hisp.dhis.test.config; -import java.util.List; import javax.annotation.Nonnull; -import lombok.RequiredArgsConstructor; -import org.hisp.dhis.common.IdentifiableObject; -import org.hisp.dhis.period.Period; -import org.hisp.dhis.period.PeriodStore; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; -import org.hisp.dhis.tracker.imports.domain.TrackerObjects; -import org.hisp.dhis.tracker.imports.preheat.TrackerPreheat; -import org.hisp.dhis.tracker.imports.preheat.cache.PreheatCacheService; +import javax.sql.DataSource; +import net.ttddyy.dsproxy.listener.ChainListener; +import net.ttddyy.dsproxy.listener.DataSourceQueryCountListener; +import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.stereotype.Component; /** - * @author Luciano Fiandesio + * Test class that enhances the originalDatasource with the ability to count queries, and returns it + * as a proxy. This should only be used in tests. To use, simply add: + * + *
{@code
+ * @ContextConfiguration(classes = {QueryCountDataSourceProxy.class})
+ * }
+ * + * on your test class and then use: + * + *
{@code
+ * SQLStatementCountValidator.reset();
+ * }
+ * + * before the condition to test and then use asserts afterward e.g. + * + *
{@code
+ * assertDeleteCount(1);
+ * }
*/ -@RequiredArgsConstructor @Component -public class PeriodTypeSupplier extends AbstractPreheatSupplier { - @Nonnull private final PeriodStore periodStore; - - @Nonnull private final PreheatCacheService cache; +public class QueryCountDataSourceProxy implements BeanPostProcessor { @Override - public void preheatAdd(TrackerObjects trackerObjects, TrackerPreheat preheat) { - if (cache.hasKey(Period.class.getName())) { - preheat.put(TrackerIdSchemeParam.UID, cache.getAll(Period.class.getName())); - } else { - final List periods = periodStore.getAll(); - addToCache(cache, periods); - addToPreheat(preheat, periods.stream().map(p -> (IdentifiableObject) p).toList()); - } - // Period store can't be cached because it's not extending - // `IdentifiableObject` - periodStore - .getAllPeriodTypes() - .forEach(periodType -> preheat.getPeriodTypeMap().put(periodType.getName(), periodType)); + public Object postProcessBeforeInitialization(@Nonnull Object bean, @Nonnull String beanName) { + return bean; } - private void addToPreheat(TrackerPreheat preheat, List periods) { - periods.forEach(p -> preheat.getPeriodMap().put(p.getName(), (Period) p)); + @Override + public Object postProcessAfterInitialization(@Nonnull Object bean, @Nonnull String beanName) + throws BeansException { + if (bean instanceof DataSource originalDataSource && beanName.equals("actualDataSource")) { + ChainListener listener = new ChainListener(); + listener.addListener(new DataSourceQueryCountListener()); + return ProxyDataSourceBuilder.create(originalDataSource) + .name("query-count-datasource-proxy") + .listener(listener) + .build(); + } + return bean; } } diff --git a/dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/utils/Assertions.java b/dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/utils/Assertions.java index e1b20aa51d2a..869fe537d80c 100644 --- a/dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/utils/Assertions.java +++ b/dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/utils/Assertions.java @@ -40,7 +40,11 @@ import java.util.List; import java.util.Map; import java.util.function.Function; +import java.util.function.Supplier; +import javax.annotation.Nonnull; import org.hisp.dhis.common.ErrorCodeException; +import org.hisp.dhis.common.UID; +import org.hisp.dhis.common.UidObject; import org.hisp.dhis.common.collection.CollectionUtils; import org.hisp.dhis.feedback.ErrorCode; import org.hisp.dhis.feedback.ErrorReport; @@ -124,6 +128,17 @@ public static void assertIsEmpty(Collection actual) { assertTrue(actual.isEmpty(), actual.toString()); } + /** + * Asserts that the given collection is not null and empty. + * + * @param actual the collection. + * @param message fails with this message + */ + public static void assertIsEmpty(Collection actual, String message) { + assertNotNull(actual, message); + assertTrue(actual.isEmpty(), message); + } + /** * Asserts that the given collection is not null and not empty. * @@ -134,6 +149,17 @@ public static void assertNotEmpty(Collection actual) { assertFalse(actual.isEmpty(), "expected collection not to be empty"); } + /** + * Asserts that the given collection is not null and not empty. + * + * @param actual the collection. + * @param message fails with this message + */ + public static void assertNotEmpty(Collection actual, String message) { + assertNotNull(actual, message); + assertFalse(actual.isEmpty(), message); + } + /** * Asserts that the given collection contains the expected number of elements. * @@ -170,7 +196,8 @@ public static void assertStartsWith(String expected, String actual) { } /** - * Asserts that the given string neither null, a length of zero nor whitespace only. + * Asserts that the given string is not null, has a non-zero length, and contains non-whitespace + * characters. * * @param actual the string. */ @@ -180,7 +207,7 @@ public static void assertNotBlank(String actual) { } /** - * Asserts that the given string neither null or a length of zero. + * Asserts that the given string is not null and has a non-zero length. * * @param actual the string. */ @@ -189,6 +216,48 @@ public static void assertNotEmpty(String actual) { assertTrue(!actual.isEmpty()); } + /** + * Asserts that the given string is not null and has a non-zero length. + * + * @param actual the string. + * @param message fails with this message + */ + public static void assertNotEmpty(String actual, String message) { + assertNotNull(actual, message); + assertTrue(!actual.isEmpty(), message); + } + + /** + * Asserts that the given string is not null and has a non-zero length. + * + * @param actual the string. + * @param messageSupplier fails with this supplied message + */ + public static void assertNotEmpty(String actual, Supplier messageSupplier) { + assertNotNull(actual, messageSupplier); + assertTrue(!actual.isEmpty(), messageSupplier); + } + + /** + * Asserts that the given character sequence is NOT contained within the actual string. + * + * @param expected expected character sequence not to be contained within the actual string + * @param actual actual string which should not contain the expected character sequence + */ + public static void assertNotContains(CharSequence expected, String actual) { + assertNotEmpty( + actual, + () -> + String.format( + "expected actual NOT to contain '%s', use assertIsEmpty if that is what you expect", + expected)); + assertFalse( + actual.contains(expected), + () -> + String.format( + "expected actual NOT to contain '%s', got '%s' instead", expected, actual)); + } + /** * Asserts that the given character sequence is contained within the actual string. * @@ -196,8 +265,7 @@ public static void assertNotEmpty(String actual) { * @param actual actual string which should contain the expected character sequence */ public static void assertContains(CharSequence expected, String actual) { - assertNotNull( - actual, () -> String.format("expected actual to contain '%s', got null instead", expected)); + assertNotEmpty(actual, () -> String.format("expected actual to contain '%s'", expected)); assertTrue( actual.contains(expected), () -> String.format("expected actual to contain '%s', got '%s' instead", expected, actual)); @@ -272,6 +340,22 @@ public static void assertEquivalentRelativeUrls(String expected, String actual) } } + /** + * Asserts that the UID of the given UidObject is equal to the expected UID. + * + * @param expected expected UID + * @param actual actual value to be checked + */ + public static void assertEqualUids(@Nonnull UID expected, @Nonnull UidObject actual) { + assertEquals( + expected.getValue(), + actual.getUid(), + () -> + String.format( + "expected actual UID to be '%s', got '%s' instead", + expected.getValue(), actual.getUid())); + } + public static void assertErrorReport( List actualErrors, ErrorCode expectedErrorCode) { assertErrorReport(actualErrors, expectedErrorCode, ""); diff --git a/dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/utils/RelationshipUtils.java b/dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/utils/RelationshipUtils.java index 8f05ecdecd61..2cfa57dee922 100644 --- a/dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/utils/RelationshipUtils.java +++ b/dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/utils/RelationshipUtils.java @@ -29,9 +29,9 @@ import java.util.Objects; import org.apache.commons.lang3.ObjectUtils; -import org.apache.commons.lang3.StringUtils; import org.hisp.dhis.common.BaseIdentifiableObject; import org.hisp.dhis.common.IdentifiableObject; +import org.hisp.dhis.common.UID; import org.hisp.dhis.relationship.Relationship; import org.hisp.dhis.relationship.RelationshipItem; import org.hisp.dhis.relationship.RelationshipKey; @@ -86,18 +86,16 @@ private static RelationshipKey.RelationshipItemKey getRelationshipItemKey( RelationshipItem relationshipItem) { if (Objects.nonNull(relationshipItem)) { return RelationshipKey.RelationshipItemKey.builder() - .trackedEntity(getUidOrEmptyString(relationshipItem.getTrackedEntity())) - .enrollment(getUidOrEmptyString(relationshipItem.getEnrollment())) - .event(getUidOrEmptyString(relationshipItem.getEvent())) + .trackedEntity(getUidOrNull(relationshipItem.getTrackedEntity())) + .enrollment(getUidOrNull(relationshipItem.getEnrollment())) + .event(getUidOrNull(relationshipItem.getEvent())) .build(); } throw new IllegalStateException("Unable to determine uid for relationship item"); } - private static String getUidOrEmptyString(BaseIdentifiableObject baseIdentifiableObject) { - return Objects.isNull(baseIdentifiableObject) - ? "" - : StringUtils.trimToEmpty(baseIdentifiableObject.getUid()); + private static UID getUidOrNull(BaseIdentifiableObject baseIdentifiableObject) { + return Objects.isNull(baseIdentifiableObject) ? null : UID.of(baseIdentifiableObject); } private static RelationshipKey getRelationshipKey(Relationship relationship) { diff --git a/dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/webapi/json/domain/JsonDataElement.java b/dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/webapi/json/domain/JsonDataElement.java index 9eafe868822b..e769df8c4e93 100644 --- a/dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/webapi/json/domain/JsonDataElement.java +++ b/dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/webapi/json/domain/JsonDataElement.java @@ -28,6 +28,7 @@ package org.hisp.dhis.test.webapi.json.domain; import org.hisp.dhis.analytics.AggregationType; +import org.hisp.dhis.common.ValueType; import org.hisp.dhis.dataelement.DataElementDomain; /** @@ -41,6 +42,10 @@ default DataElementDomain getDomainType() { return getString("domainType").parsed(DataElementDomain::valueOf); } + default ValueType getValueType() { + return getString("valueType").parsed(ValueType::valueOf); + } + default AggregationType getAggregationType() { return getString("aggregationType").parsed(AggregationType::valueOf); } diff --git a/dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/webapi/json/domain/JsonWebMessage.java b/dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/webapi/json/domain/JsonWebMessage.java index ed946a5223c3..0174b8bbdc8c 100644 --- a/dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/webapi/json/domain/JsonWebMessage.java +++ b/dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/test/webapi/json/domain/JsonWebMessage.java @@ -52,6 +52,10 @@ default String getMessage() { return getString("message").string(); } + default String getDevMessage() { + return getString("devMessage").string(); + } + default String getDescription() { return getString("description").string(); } diff --git a/dhis-2/dhis-test-integration/src/test/resources/tracker/simple_metadata.json b/dhis-2/dhis-support/dhis-support-test/src/main/resources/tracker/simple_metadata.json similarity index 61% rename from dhis-2/dhis-test-integration/src/test/resources/tracker/simple_metadata.json rename to dhis-2/dhis-support/dhis-support-test/src/main/resources/tracker/simple_metadata.json index 4f8039626bcc..2e3b28fdf203 100644 --- a/dhis-2/dhis-test-integration/src/test/resources/tracker/simple_metadata.json +++ b/dhis-2/dhis-support/dhis-support-test/src/main/resources/tracker/simple_metadata.json @@ -2,139 +2,58 @@ "attributes": [ { "id": "j45AR9cBQKc", - "name": "some attribute", + "categoryOptionAttribute": true, + "categoryOptionComboAttribute": true, + "dataElementAttribute": true, + "name": "idScheme related attribute", + "organisationUnitAttribute": true, + "programAttribute": true, + "programStageAttribute": true, + "relationshipTypeAttribute": true, "sharing": { "owner": "PQD6wXJ2r5j", - "users": {}, - "userGroups": {}, "public": "rw------" }, - "valueType": "TEXT", - "mandatory": false, + "trackedEntityAttributeAttribute": true, + "trackedEntityTypeAttribute": true, "unique": true, + "valueType": "TEXT" + }, + { + "id": "i57a0734128", + "categoryOptionAttribute": true, + "categoryOptionComboAttribute": true, + "dataElementAttribute": true, + "name": "no metadata should have an attribute value for this attribute", + "organisationUnitAttribute": true, "programAttribute": true, "programStageAttribute": true, - "trackedEntityTypeAttribute": true, - "trackedEntityAttributeAttribute": true, - "organisationUnitAttribute": true, - "dataElementAttribute": true, "relationshipTypeAttribute": true, - "categoryOptionAttribute": true, - "categoryOptionComboAttribute": true - } - ], - "categoryOptions": [ - { - "id": "xYerKDKCefk", - "name": "default", - "code": "default", - "shortName": "default" - }, - { - "id": "xwZ2u3WyQR0", - "name": "Unicef", - "code": "Unicef", - "shortName": "Unicef", "sharing": { - "owner": "tTgjgobT1oS", - "users": {}, - "userGroups": {}, - "public": "rwr-----" - } - }, - { - "id": "xEunk8LPzkb", - "name": "World Relief", - "code": "World Relief", - "shortName": "World Relief", - "description": "sharing.public data 'r' needs to be set to test this sharing case", - "sharing": { - "owner": "tTgjgobT1oS", - "users": {}, - "userGroups": {}, - "public": "rwr-----" - } - }, - { - "id": "i4Nbp8S2G6A", - "name": "Improve access to clean water", - "code": "Project01", - "shortName": "Improve access to clean water", - "description": "used to test sharing when owner is null", - "sharing": { - "owner": null, - "users": {}, - "userGroups": {}, - "public": "rw------" - } - }, - { - "id": "OUUdG3sdOqb", - "name": "Provide access to primary health care", - "code": "Project02", - "shortName": "Provide access to primary health care", - "description": "used to test non public data access. only super user or user with data access should get access", - "sharing": { - "owner": "tTgjgobT1oS", - "users": { - "o1HMTIzBGo7": { - "displayName": "foofi", - "access": "--r-----", - "id": "o1HMTIzBGo7" - } - }, - "userGroups": {}, + "owner": "PQD6wXJ2r5j", "public": "rw------" - } - }, - { - "id": "yMj2MnmNI8L", - "name": "Improve access to medicines", - "code": "Project03", - "shortName": "Improve access to medicines", - "description": "used to test non public data access. only super user or owner should get access", - "sharing": { - "owner": "o1HMTIzBGo7", - "users": {}, - "userGroups": {}, - "public": "--------" - } - }, - { - "id": "M58XdOfhiJ7", - "name": "Provide access to basic education", - "code": "Project04", - "shortName": "Provide access to basic education", - "description": "used to test sharing when public is null", - "sharing": { - "owner": "tTgjgobT1oS", - "users": {}, - "userGroups": {}, - "public": null - } + }, + "trackedEntityAttributeAttribute": true, + "trackedEntityTypeAttribute": true, + "unique": true, + "valueType": "TEXT" } ], "categories": [ { "id": "GLevLNI9wkl", - "name": "default", - "code": "default", - "shortName": "default", - "dataDimensionType": "DISAGGREGATION", - "dataDimension": false, "categoryOptions": [ { "id": "xYerKDKCefk" } - ] + ], + "code": "default", + "dataDimensionType": "DISAGGREGATION", + "name": "default", + "shortName": "default" }, { "id": "LFsZ8v5v7rq", - "name": "Implementing Partner", - "code": "IMPL_PARTNER", - "shortName": "Implementing Partner", - "dataDimensionType": "ATTRIBUTE", - "dataDimension": true, "categoryOptions": [ { "id": "xwZ2u3WyQR0" @@ -142,15 +61,15 @@ { "id": "xEunk8LPzkb" } - ] + ], + "code": "IMPL_PARTNER", + "dataDimension": true, + "dataDimensionType": "ATTRIBUTE", + "name": "Implementing Partner", + "shortName": "Implementing Partner" }, { "id": "yY2bQYqNt0o", - "name": "Project", - "code": "PROJECT", - "shortName": "Project", - "dataDimensionType": "ATTRIBUTE", - "dataDimension": true, "categoryOptions": [ { "id": "i4Nbp8S2G6A" @@ -164,32 +83,31 @@ { "id": "M58XdOfhiJ7" } - ] + ], + "code": "PROJECT", + "dataDimension": true, + "dataDimensionType": "ATTRIBUTE", + "name": "Project", + "shortName": "Project" } ], "categoryCombos": [ { "id": "bjDvmb4bfuf", - "code": "default", - "name": "default", - "dataDimensionType": "DISAGGREGATION", - "sharing": { - "users": {}, - "userGroups": {}, - "public": "rw------" - }, - "skipTotal": false, "categories": [ { "id": "GLevLNI9wkl" } - ] + ], + "code": "default", + "dataDimensionType": "DISAGGREGATION", + "name": "default", + "sharing": { + "public": "rw------" + } }, { "id": "O4VaNks6tta", - "name": "Implementing Partner and Projects", - "dataDimensionType": "ATTRIBUTE", - "skipTotal": false, "categories": [ { "id": "LFsZ8v5v7rq" @@ -197,31 +115,27 @@ { "id": "yY2bQYqNt0o" } - ] + ], + "dataDimensionType": "ATTRIBUTE", + "name": "Implementing Partner and Projects" } ], "categoryOptionCombos": [ { "id": "HllvX50cXC0", - "name": "default", - "code": "default", - "ignoreApproval": false, "categoryCombo": { "id": "bjDvmb4bfuf" }, - "translations": [], - "attributeValues": [], "categoryOptions": [ { "id": "xYerKDKCefk" } - ] + ], + "code": "default", + "name": "default" }, { "id": "tzsPhPtE94U", - "name": "World Relief, Provide access to basic education", - "code": "COC_1153413", - "ignoreApproval": false, "categoryCombo": { "id": "O4VaNks6tta" }, @@ -232,18 +146,15 @@ { "id": "M58XdOfhiJ7" } - ] + ], + "code": "COC_1153413", + "name": "World Relief, Provide access to basic education" }, { "id": "UcWQppzD1it", - "name": "World Relief, Improve access to medicines", - "code": "COC_1153405", - "ignoreApproval": false, "categoryCombo": { "id": "O4VaNks6tta" }, - "translations": [], - "attributeValues": [], "categoryOptions": [ { "id": "xEunk8LPzkb" @@ -251,13 +162,12 @@ { "id": "yMj2MnmNI8L" } - ] + ], + "code": "COC_1153405", + "name": "World Relief, Improve access to medicines" }, { "id": "i3HQ1ziPjC7", - "name": "World Relief, Improve access to clean water", - "code": "COC_1153403", - "ignoreApproval": false, "categoryCombo": { "id": "O4VaNks6tta" }, @@ -268,18 +178,15 @@ { "id": "xEunk8LPzkb" } - ] + ], + "code": "COC_1153403", + "name": "World Relief, Improve access to clean water" }, { "id": "AeOORUC0ISH", - "name": "World Relief, Provide access to primary health care", - "code": "COC_1153408", - "ignoreApproval": false, "categoryCombo": { "id": "O4VaNks6tta" }, - "translations": [], - "attributeValues": [], "categoryOptions": [ { "id": "xEunk8LPzkb" @@ -287,13 +194,20 @@ { "id": "OUUdG3sdOqb" } - ] + ], + "code": "COC_1153408", + "name": "World Relief, Provide access to primary health care" }, { "id": "cr89ebDZrac", - "name": "Unicef, Provide access to basic education", - "code": "COC_1153452", - "ignoreApproval": false, + "attributeValues": [ + { + "attribute": { + "id": "j45AR9cBQKc" + }, + "value": "COC_1153452-attribute" + } + ], "categoryCombo": { "id": "O4VaNks6tta" }, @@ -305,25 +219,14 @@ "id": "M58XdOfhiJ7" } ], - "attributeValues": [ - { - "attribute": { - "id": "j45AR9cBQKc" - }, - "value": "COC_1153452-attribute" - } - ] + "code": "COC_1153452", + "name": "Unicef, Provide access to basic education" }, { "id": "V2gbpszLQJm", - "name": "Unicef, Improve access to medicines", - "code": "COC_1153434", - "ignoreApproval": false, "categoryCombo": { "id": "O4VaNks6tta" }, - "translations": [], - "attributeValues": [], "categoryOptions": [ { "id": "xwZ2u3WyQR0" @@ -331,18 +234,23 @@ { "id": "yMj2MnmNI8L" } - ] + ], + "code": "COC_1153434", + "name": "Unicef, Improve access to medicines" }, { "id": "SeWJkpLAyLt", - "name": "Unicef, Improve access to clean water", - "code": "COC_1153438", - "ignoreApproval": false, + "attributeValues": [ + { + "attribute": { + "id": "j45AR9cBQKc" + }, + "value": "categoryOptionCombo SeWJkpLAyLt" + } + ], "categoryCombo": { "id": "O4VaNks6tta" }, - "translations": [], - "attributeValues": [], "categoryOptions": [ { "id": "xwZ2u3WyQR0" @@ -350,18 +258,15 @@ { "id": "i4Nbp8S2G6A" } - ] + ], + "code": "COC_1153438", + "name": "Unicef, Improve access to clean water" }, { "id": "B81quHGEY7H", - "name": "Unicef, Provide access to primary health care", - "code": "COC_1153446", - "ignoreApproval": false, "categoryCombo": { "id": "O4VaNks6tta" }, - "translations": [], - "attributeValues": [], "categoryOptions": [ { "id": "xwZ2u3WyQR0" @@ -369,983 +274,857 @@ { "id": "OUUdG3sdOqb" } - ] + ], + "code": "COC_1153446", + "name": "Unicef, Provide access to primary health care" } ], - "trackedEntityTypes": [ + "categoryOptions": [ { - "created": "2020-05-31T08:59:52.758", - "lastUpdated": "2020-05-31T11:41:22.419", - "name": "Person", - "id": "ja8NY4PW7Xm", - "publicAccess": "rw------", - "description": "person", - "maxTeiCountToReturn": 0, - "allowAuditLog": false, - "featureType": "NONE", - "minAttributesRequiredToSearch": 1, - "lastUpdatedBy": { - "id": "tTgjgobT1oS" - }, - "user": { - "id": "tTgjgobT1oS" - }, - "userGroupAccesses": [], - "attributeValues": [], - "trackedEntityTypeAttributes": [ + "id": "xYerKDKCefk", + "code": "default", + "name": "default", + "shortName": "default" + }, + { + "id": "xwZ2u3WyQR0", + "attributeValues": [ { - "lastUpdated": "2022-04-25T08:34:44.294", - "id": "IvcLJpReZPS", - "created": "2019-02-05T13:46:01.219", - "displayName": "TA Person TA First name", - "displayShortName": "null TA First name", - "valueType": "TEXT", - "searchable": true, - "displayInList": true, - "name": "TA Person TA First name", - "favorite": false, - "access": { - "read": true, - "update": true, - "externalize": true, - "delete": true, - "write": true, - "manage": true - }, - "trackedEntityAttribute": { - "id": "dIVt4l5vIOa" - }, - "sharing": { - "userGroups": {}, - "external": false, - "users": {} + "attribute": { + "id": "j45AR9cBQKc" }, - "favorites": [], - "translations": [], - "attributeValues": [] + "value": "categoryOption xwZ2u3WyQR0" } ], - "translations": [], - "userAccesses": [], + "code": "Unicef", + "name": "Unicef", "sharing": { - "external": false, - "users": {}, - "userGroups": {}, - "public": "rwrw----" - } + "owner": "tTgjgobT1oS", + "public": "rwr-----" + }, + "shortName": "Unicef" }, { - "created": "2020-05-31T08:59:52.758", - "lastUpdated": "2020-05-31T11:41:22.419", - "name": "Inaccessible person", - "id": "Ip8NY4PW7Xm", - "publicAccess": "rw------", - "description": "person", - "maxTeiCountToReturn": 0, - "allowAuditLog": false, - "featureType": "NONE", - "minAttributesRequiredToSearch": 1, - "lastUpdatedBy": { - "id": "tTgjgobT1oS" - }, - "user": { - "id": "tTgjgobT1oS" - }, - "userGroupAccesses": [], - "attributeValues": [], - "trackedEntityTypeAttributes": [], - "translations": [], - "userAccesses": [], + "id": "xEunk8LPzkb", + "code": "World Relief", + "description": "sharing.public data 'r' needs to be set to test this sharing case", + "name": "World Relief", "sharing": { - "external": false, - "users": {}, - "userGroups": {}, - "public": "--------" - } - } - ], - "relationshipTypes": [ - { - "created": "2019-04-23T09:20:41.834", - "lastUpdated": "2019-04-23T09:21:18.063", - "name": "TA Sibling", - "id": "xLmPUYJX8Ks", - "bidirectional": true, - "publicAccess": "rw------", - "toFromName": "Sibling of", - "fromToName": "Sibling of", - "fromConstraint": { - "relationshipEntity": "TRACKED_ENTITY_INSTANCE", - "trackedEntityType": { - "id": "ja8NY4PW7Xm" - } + "owner": "tTgjgobT1oS", + "public": "rwr-----" }, - "toConstraint": { - "relationshipEntity": "PROGRAM_INSTANCE", - "program": { - "id": "BFcipDERJnf" + "shortName": "World Relief" + }, + { + "id": "i4Nbp8S2G6A", + "attributeValues": [ + { + "attribute": { + "id": "j45AR9cBQKc" + }, + "value": "categoryOption i4Nbp8S2G6A" } - }, - "userGroupAccesses": [], - "translations": [], - "userAccesses": [], + ], + "code": "Project01", + "description": "used to test sharing when owner is null", + "name": "Improve access to clean water", "sharing": { - "public": "r-------", - "external": false, "owner": null, - "users": {}, - "userGroups": {} - } + "public": "rw------" + }, + "shortName": "Improve access to clean water" }, { - "lastUpdated": "2020-11-20T09:06:23.348", - "id": "TV9oB9LT3sh", - "created": "2020-11-20T09:06:23.348", - "name": "TA Parent to Child", - "bidirectional": false, - "displayName": "TA Parent to Child", - "publicAccess": "rw------", - "fromToName": "Parent Of", - "externalAccess": false, - "displayFromToName": "Parent Of", - "favorite": false, - "fromConstraint": { - "relationshipEntity": "TRACKED_ENTITY_INSTANCE", - "trackedEntityType": { - "id": "ja8NY4PW7Xm" + "id": "OUUdG3sdOqb", + "code": "Project02", + "description": "used to test non public data access. only super user or user with data access should get access", + "name": "Provide access to primary health care", + "sharing": { + "owner": "tTgjgobT1oS", + "public": "rw------", + "users": { + "o1HMTIzBGo7": { + "access": "--r-----", + "displayName": "foofi", + "id": "o1HMTIzBGo7" + } } }, - "toConstraint": { - "relationshipEntity": "PROGRAM_STAGE_INSTANCE", - "program": { - "id": "BFcipDERJnf" - }, - "programStage": { - "id": "NpsdDv6kKSO" - } + "shortName": "Provide access to primary health care" + }, + { + "id": "yMj2MnmNI8L", + "code": "Project03", + "description": "used to test non public data access. only super user or owner should get access", + "name": "Improve access to medicines", + "sharing": { + "owner": "o1HMTIzBGo7", + "public": "--------" }, - "userGroupAccesses": [], + "shortName": "Improve access to medicines" + }, + { + "id": "M58XdOfhiJ7", + "code": "Project04", + "description": "used to test sharing when public is null", + "name": "Provide access to basic education", "sharing": { - "public": "r-------", - "external": false, - "owner": null, - "users": {}, - "userGroups": {} - } + "owner": "tTgjgobT1oS", + "public": null + }, + "shortName": "Provide access to basic education" } ], "dataElements": [ { - "lastUpdated": "2020-05-31T11:41:22.404", "id": "DATAEL00001", - "created": "2020-05-31T08:58:48.406", - "name": "test-dataelement1", - "shortName": "test-dataelement1", "aggregationType": "SUM", - "domainType": "TRACKER", - "publicAccess": "rw------", - "valueType": "TEXT", - "zeroIsSignificant": false, "categoryCombo": { "id": "bjDvmb4bfuf" }, + "created": "2020-05-31T08:58:48.406", + "domainType": "TRACKER", + "lastUpdated": "2020-05-31T11:41:22.404", "lastUpdatedBy": { "id": "tTgjgobT1oS" }, + "name": "test-dataelement1", + "shortName": "test-dataelement1", "user": { "id": "tTgjgobT1oS" }, - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [], - "legendSets": [], - "aggregationLevels": [] + "valueType": "TEXT" }, { - "lastUpdated": "2020-05-31T11:41:22.404", "id": "DATAEL00002", - "created": "2020-05-31T08:58:48.406", - "name": "test-dataelement2", - "shortName": "test-dataelement2", "aggregationType": "SUM", - "domainType": "TRACKER", - "publicAccess": "rw------", - "valueType": "TEXT", - "zeroIsSignificant": false, "categoryCombo": { "id": "bjDvmb4bfuf" }, + "created": "2020-05-31T08:58:48.406", + "domainType": "TRACKER", + "lastUpdated": "2020-05-31T11:41:22.404", "lastUpdatedBy": { "id": "tTgjgobT1oS" }, + "name": "test-dataelement2", + "shortName": "test-dataelement2", "user": { "id": "tTgjgobT1oS" }, - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [], - "legendSets": [], - "aggregationLevels": [] + "valueType": "TEXT" }, { - "lastUpdated": "2020-05-31T11:41:22.404", "id": "DATAEL00003", - "created": "2020-05-31T08:58:48.406", - "name": "test-dataelement3", - "shortName": "test-dataelement3", "aggregationType": "SUM", - "domainType": "TRACKER", - "publicAccess": "rw------", - "valueType": "TEXT", - "zeroIsSignificant": false, "categoryCombo": { "id": "bjDvmb4bfuf" }, + "created": "2020-05-31T08:58:48.406", + "domainType": "TRACKER", + "lastUpdated": "2020-05-31T11:41:22.404", "lastUpdatedBy": { "id": "tTgjgobT1oS" }, + "name": "test-dataelement3", + "shortName": "test-dataelement3", "user": { "id": "tTgjgobT1oS" }, - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [], - "legendSets": [], - "aggregationLevels": [] + "valueType": "TEXT" }, { - "lastUpdated": "2020-05-31T11:41:22.404", "id": "DATAEL00004", - "created": "2020-05-31T08:58:48.406", - "name": "test-dataelement4", - "shortName": "test-dataelement4", "aggregationType": "SUM", - "domainType": "TRACKER", - "publicAccess": "rw------", - "valueType": "TEXT", - "zeroIsSignificant": false, "categoryCombo": { "id": "bjDvmb4bfuf" }, + "created": "2020-05-31T08:58:48.406", + "domainType": "TRACKER", + "lastUpdated": "2020-05-31T11:41:22.404", "lastUpdatedBy": { "id": "tTgjgobT1oS" }, + "name": "test-dataelement4", + "shortName": "test-dataelement4", "user": { "id": "tTgjgobT1oS" }, - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [], - "legendSets": [], - "aggregationLevels": [] + "valueType": "TEXT" }, { - "lastUpdated": "2020-05-31T11:41:22.404", "id": "DATAEL00005", - "created": "2020-05-31T08:58:48.406", - "name": "with-option-set", - "shortName": "with-option-set", "aggregationType": "SUM", - "domainType": "TRACKER", - "publicAccess": "rw------", - "valueType": "TEXT", - "zeroIsSignificant": false, "categoryCombo": { "id": "bjDvmb4bfuf" }, + "created": "2020-05-31T08:58:48.406", + "domainType": "TRACKER", + "lastUpdated": "2020-05-31T11:41:22.404", "lastUpdatedBy": { "id": "tTgjgobT1oS" }, + "name": "with-option-set", "optionSet": { "id": "VWzgQENmzPK" }, - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [], - "legendSets": [], - "aggregationLevels": [] + "shortName": "with-option-set", + "valueType": "TEXT" }, { - "lastUpdated": "2020-05-31T11:41:22.404", "id": "DATAEL00006", - "created": "2020-05-31T08:58:48.406", - "name": "test-dataelement6", - "shortName": "test-dataelement6", "aggregationType": "NONE", - "domainType": "TRACKER", - "publicAccess": "rw------", - "valueType": "INTEGER", - "zeroIsSignificant": false, "categoryCombo": { "id": "bjDvmb4bfuf" }, + "created": "2020-05-31T08:58:48.406", + "domainType": "TRACKER", + "lastUpdated": "2020-05-31T11:41:22.404", "lastUpdatedBy": { "id": "tTgjgobT1oS" }, + "name": "test-dataelement6", + "shortName": "test-dataelement6", "user": { "id": "tTgjgobT1oS" }, - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [], - "legendSets": [], - "aggregationLevels": [] + "valueType": "INTEGER" }, { - "lastUpdated": "2020-05-31T11:41:22.404", "id": "DATAEL00007", - "created": "2020-05-31T08:58:48.406", - "name": "test-dataelement7", - "shortName": "test-dataelement7", "aggregationType": "SUM", + "created": "2020-05-31T08:58:48.406", "domainType": "TRACKER", - "publicAccess": "rw------", - "valueType": "TEXT", - "zeroIsSignificant": false, + "lastUpdated": "2020-05-31T11:41:22.404", "lastUpdatedBy": { "id": "tTgjgobT1oS" }, + "name": "test-dataelement7", + "sharing": { + "public": "rw------" + }, + "shortName": "test-dataelement7", "user": { "id": "tTgjgobT1oS" }, - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [], - "legendSets": [], - "aggregationLevels": [], - "sharing": { - "external": false, - "users": {}, - "userGroups": {}, - "public": "rw------" - } + "valueType": "TEXT" }, { "id": "GieVkTxp4HH", + "attributeValues": [ + { + "attribute": { + "id": "j45AR9cBQKc" + }, + "value": "dataElement GieVkTxp4HH" + } + ], + "aggregationType": "AVERAGE", + "categoryCombo": { + "id": "bjDvmb4bfuf" + }, "code": "DE_240794", - "lastUpdated": "2015-03-31T11:22:51.642", "created": "2015-03-31T10:27:46.069", + "domainType": "TRACKER", + "lastUpdated": "2015-03-31T11:22:51.642", "name": "Height in cm", "shortName": "Height in cm", - "aggregationType": "AVERAGE", - "domainType": "TRACKER", - "valueType": "NUMBER", - "zeroIsSignificant": false, "url": "", - "categoryCombo": { - "id": "bjDvmb4bfuf" - } + "valueType": "NUMBER" }, { "id": "GieVkTxp4HG", + "attributeValues": [ + { + "attribute": { + "id": "j45AR9cBQKc" + }, + "value": "dataElement GieVkTxp4HG" + } + ], + "aggregationType": "AVERAGE", "code": "DG_240794", - "lastUpdated": "2015-03-31T11:22:51.642", "created": "2015-03-31T10:27:46.069", + "domainType": "TRACKER", + "lastUpdated": "2015-03-31T11:22:51.642", "name": "Height in mm", "shortName": "Height in mm", - "aggregationType": "AVERAGE", - "domainType": "TRACKER", - "valueType": "NUMBER", - "zeroIsSignificant": false, - "url": "" + "url": "", + "valueType": "NUMBER" + } + ], + "optionSets": [ + { + "id": "VWzgQENmzPK", + "code": "options", + "displayName": "options", + "name": "options", + "options": [ + { + "id": "IHYwYAtvjCY" + }, + { + "id": "HxFNfoxk7ac" + } + ], + "valueType": "TEXT", + "version": 5 } ], "options": [ { - "attributeValues": [], + "id": "IHYwYAtvjCY", "code": "option1", "displayFormName": "option1", "displayName": "option1", - "externalAccess": false, - "favorite": false, - "favorites": [], - "id": "IHYwYAtvjCY", "name": "option1", "optionSet": { "id": "VWzgQENmzPK" }, - "sortOrder": 3, - "userAccesses": [], - "userGroupAccesses": [] + "sortOrder": 3 }, { - "attributeValues": [], + "id": "HxFNfoxk7ac", "code": "option2", "displayFormName": "option2", "displayName": "option2", - "externalAccess": false, - "favorite": false, - "favorites": [], - "id": "HxFNfoxk7ac", "name": "option2", "optionSet": { "id": "VWzgQENmzPK" }, - "sortOrder": 4, - "userAccesses": [], - "userGroupAccesses": [] + "sortOrder": 4 } ], - "optionSets": [ + "organisationUnits": [ { - "attributeValues": [], - "code": "options", - "displayName": "options", - "externalAccess": false, - "favorite": false, - "favorites": [], - "id": "VWzgQENmzPK", - "name": "options", - "options": [ - { - "id": "IHYwYAtvjCY" - }, + "id": "h4w96yEMlzO", + "attributeValues": [ { - "id": "HxFNfoxk7ac" + "attribute": { + "id": "j45AR9cBQKc" + }, + "value": "orgUnit h4w96yEMlzO" } ], - "publicAccess": "rw------", - "userAccesses": [], - "userGroupAccesses": [], - "valueType": "TEXT", - "version": 5 - } - ], - "userGroups": [ - { - "created": "2020-05-31T09:03:15.823", - "lastUpdated": "2020-05-31T11:41:22.375", - "name": "test-user-group", - "id": "xfHoY6IZSWI", - "publicAccess": "rw------", + "code": "h4w96yEMlzO code", + "created": "2020-05-31T08:56:15.922", + "description": "h4w96yEMlzO description", + "lastUpdated": "2020-05-31T11:41:22.384", "lastUpdatedBy": { "id": "tTgjgobT1oS" }, + "level": 1, + "name": "h4w96yEMlzO name", + "openingDate": "2020-05-31T00:00:00.000", + "path": "/h4w96yEMlzO", + "shortName": "h4w96yEMlzO short name", "user": { "id": "tTgjgobT1oS" + } + }, + { + "id": "g4w96yEMlzO", + "code": "test-orgunit-code-no-sharing-access", + "created": "2020-05-31T08:56:15.922", + "description": "test-orgunit", + "lastUpdated": "2020-05-31T11:41:22.384", + "lastUpdatedBy": { + "id": "tTgjgobT1oS" }, - "userGroupAccesses": [], - "attributeValues": [], - "users": [ + "level": 1, + "name": "test-orgunit", + "openingDate": "2020-05-31T00:00:00.000", + "path": "/g4w96yEMlzO", + "shortName": "test-orgunit", + "user": { + "id": "tTgjgobT1oS" + } + }, + { + "id": "uoNW0E3xXUy", + "closedDate": "2020-12-27T00:00:00.000", + "code": "test-orgunit-code-2", + "created": "2020-05-31T09:05:34.570", + "lastUpdated": "2020-05-31T11:41:22.385", + "lastUpdatedBy": { + "id": "tTgjgobT1oS" + }, + "level": 2, + "name": "test-orgunit-2", + "openingDate": "2020-05-31T00:00:00.000", + "parent": { + "id": "h4w96yEMlzO" + }, + "path": "/h4w96yEMlzO/uoNW0E3xXUy", + "shortName": "test-program-rule", + "user": { + "id": "tTgjgobT1oS" + } + }, + { + "id": "lbDXJBlvtZe", + "closedDate": "2020-12-27T00:00:00.000", + "code": "test-orgunit-code-l2", + "created": "2020-05-31T09:05:34.570", + "lastUpdated": "2020-05-31T11:41:22.385", + "lastUpdatedBy": { + "id": "tTgjgobT1oS" + }, + "level": 2, + "name": "test-orgunit-2", + "openingDate": "2020-05-31T00:00:00.000", + "parent": { + "id": "h4w96yEMlzO" + }, + "path": "/h4w96yEMlzO/lbDXJBlvtZe", + "shortName": "test-program-rule", + "user": { + "id": "tTgjgobT1oS" + } + }, + { + "id": "RojfDTBhoGC", + "closedDate": "2020-12-27T00:00:00.000", + "code": "test-orgunit-code-3", + "created": "2020-05-31T09:05:34.570", + "lastUpdated": "2020-05-31T11:41:22.385", + "lastUpdatedBy": { + "id": "tTgjgobT1oS" + }, + "level": 3, + "name": "test-orgunit-3", + "openingDate": "2020-05-31T00:00:00.000", + "parent": { + "id": "uoNW0E3xXUy" + }, + "path": "/h4w96yEMlzO/uoNW0E3xXUy/RojfDTBhoGC", + "shortName": "test-program-rule", + "user": { + "id": "tTgjgobT1oS" + } + }, + { + "id": "tSsGrtfRzjY", + "closedDate": "2020-12-27T00:00:00.000", + "code": "test-orgunit-code-4", + "created": "2020-05-31T09:05:34.570", + "lastUpdated": "2020-05-31T11:41:22.385", + "lastUpdatedBy": { + "id": "tTgjgobT1oS" + }, + "level": 4, + "name": "test-orgunit-4", + "openingDate": "2020-05-31T00:00:00.000", + "parent": { + "id": "RojfDTBhoGC" + }, + "path": "/h4w96yEMlzO/uoNW0E3xXUy/RojfDTBhoGC/tSsGrtfRzjY", + "shortName": "test-program-rule", + "user": { + "id": "tTgjgobT1oS" + } + }, + { + "id": "DiszpKrYNg8", + "attributeValues": [ { - "id": "lPaILkLkgOM" + "attribute": { + "id": "j45AR9cBQKc" + }, + "value": "orgUnit DiszpKrYNg8" } ], - "managedGroups": [], - "translations": [], - "userAccesses": [] + "code": "DiszpKrYNg8 code", + "created": "2020-05-31T08:56:15.922", + "description": "DiszpKrYNg8 description", + "lastUpdated": "2020-05-31T11:41:22.384", + "lastUpdatedBy": { + "id": "tTgjgobT1oS" + }, + "level": 1, + "name": "DiszpKrYNg8 name", + "openingDate": "2020-05-31T00:00:00.000", + "path": "/DiszpKrYNg8", + "shortName": "DiszpKrYNg8 shortName", + "user": { + "id": "tTgjgobT1oS" + } + } + ], + "programNotificationTemplates": [ + { + "id": "FdIeUL4gyoB", + "access": { + "delete": true, + "externalize": true, + "manage": true, + "read": true, + "update": true, + "write": true + }, + "created": "2020-05-31T11:35:18.355", + "displayName": "test-program-stage-notification", + "href": "http://localhost:8081/api/programNotificationTemplates/FdIeUL4gyoB", + "lastUpdated": "2020-05-31T11:35:18.355", + "lastUpdatedBy": { + "id": "tTgjgobT1oS" + }, + "messageTemplate": "test message ", + "name": "test-program-stage-notification", + "notificationRecipient": "USER_GROUP", + "notificationTrigger": "PROGRAM_RULE", + "recipientUserGroup": { + "id": "xfHoY6IZSWI" + }, + "subjectTemplate": "test subject" } ], "programStages": [ { - "lastUpdated": "2020-05-31T11:41:22.426", "id": "NpsdDv6kKSO", + "attributeValues": [ + { + "attribute": { + "id": "j45AR9cBQKc" + }, + "value": "programStage NpsdDv6kKSO" + } + ], + "autoGenerateEvent": true, + "code": "NpsdDv6kKSO code", "created": "2020-05-31T09:02:52.687", - "name": "test-program-stage", - "allowGenerateNextVisit": false, - "preGenerateUID": false, - "publicAccess": "rwrw----", "description": "test-program-stage", - "openAfterEnrollment": false, - "repeatable": true, - "remindCompleted": false, "displayGenerateEventBox": true, - "generatedByEnrollmentDate": false, - "validationStrategy": "ON_UPDATE_AND_INSERT", - "autoGenerateEvent": true, - "sortOrder": 1, - "hideDueDate": false, - "blockEntryForm": false, - "enableUserAssignment": false, - "minDaysFromStart": 0, - "program": { - "id": "BFcipDERJnf" - }, + "lastUpdated": "2020-05-31T11:41:22.426", "lastUpdatedBy": { "id": "tTgjgobT1oS" }, - "user": { - "id": "tTgjgobT1oS" - }, + "minDaysFromStart": 0, + "name": "test-program-stage", "notificationTemplates": [ { "id": "FdIeUL4gyoB" } ], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [], - "programStageSections": [], + "program": { + "id": "BFcipDERJnf" + }, "programStageDataElements": [ { - "lastUpdated": "2019-01-28T11:23:20.855", "id": "PSDE0000001", - "created": "2019-01-28T11:23:20.855", - "displayInReports": false, - "skipSynchronization": false, - "externalAccess": false, - "renderOptionsAsRadio": false, - "allowFutureDate": false, - "compulsory": false, - "allowProvidedElsewhere": false, - "sortOrder": 1, - "favorite": false, "access": { + "delete": true, + "externalize": true, + "manage": true, "read": true, "update": true, - "externalize": true, - "delete": true, - "write": true, - "manage": true - }, - "programStage": { - "id": "NpsdDv6kKSO" + "write": true }, + "created": "2019-01-28T11:23:20.855", "dataElement": { "id": "DATAEL00001" }, - "favorites": [], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [] + "lastUpdated": "2019-01-28T11:23:20.855", + "programStage": { + "id": "NpsdDv6kKSO" + }, + "sortOrder": 1 }, { - "lastUpdated": "2019-01-28T11:23:20.855", "id": "PSDE0000002", - "created": "2019-01-28T11:23:20.855", - "displayInReports": false, - "skipSynchronization": false, - "externalAccess": false, - "renderOptionsAsRadio": false, - "allowFutureDate": false, - "compulsory": false, - "allowProvidedElsewhere": false, - "sortOrder": 1, - "favorite": false, "access": { + "delete": true, + "externalize": true, + "manage": true, "read": true, "update": true, - "externalize": true, - "delete": true, - "write": true, - "manage": true - }, - "programStage": { - "id": "NpsdDv6kKSO" + "write": true }, + "created": "2019-01-28T11:23:20.855", "dataElement": { "id": "DATAEL00002" }, - "favorites": [], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [] + "lastUpdated": "2019-01-28T11:23:20.855", + "programStage": { + "id": "NpsdDv6kKSO" + }, + "sortOrder": 1 }, { - "lastUpdated": "2019-01-28T11:23:20.855", "id": "PSDE0000003", - "created": "2019-01-28T11:23:20.855", - "displayInReports": false, - "skipSynchronization": false, - "externalAccess": false, - "renderOptionsAsRadio": false, - "allowFutureDate": false, - "compulsory": false, - "allowProvidedElsewhere": false, - "sortOrder": 1, - "favorite": false, "access": { + "delete": true, + "externalize": true, + "manage": true, "read": true, "update": true, - "externalize": true, - "delete": true, - "write": true, - "manage": true - }, - "programStage": { - "id": "NpsdDv6kKSO" + "write": true }, + "created": "2019-01-28T11:23:20.855", "dataElement": { "id": "DATAEL00003" }, - "favorites": [], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [] + "lastUpdated": "2019-01-28T11:23:20.855", + "programStage": { + "id": "NpsdDv6kKSO" + }, + "sortOrder": 1 }, { - "lastUpdated": "2019-01-28T11:23:20.855", "id": "PSDE0000004", - "created": "2019-01-28T11:23:20.855", - "displayInReports": false, - "skipSynchronization": false, - "externalAccess": false, - "renderOptionsAsRadio": false, - "allowFutureDate": false, - "compulsory": false, - "allowProvidedElsewhere": false, - "sortOrder": 1, - "favorite": false, "access": { + "delete": true, + "externalize": true, + "manage": true, "read": true, "update": true, - "externalize": true, - "delete": true, - "write": true, - "manage": true - }, - "programStage": { - "id": "NpsdDv6kKSO" + "write": true }, + "created": "2019-01-28T11:23:20.855", "dataElement": { "id": "DATAEL00004" }, - "favorites": [], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [] + "lastUpdated": "2019-01-28T11:23:20.855", + "programStage": { + "id": "NpsdDv6kKSO" + }, + "sortOrder": 1 }, { - "lastUpdated": "2019-01-28T11:23:20.855", "id": "PSDE0000005", - "created": "2019-01-28T11:23:20.855", - "displayInReports": false, - "skipSynchronization": false, - "externalAccess": false, - "renderOptionsAsRadio": false, - "allowFutureDate": false, - "compulsory": false, - "allowProvidedElsewhere": false, - "sortOrder": 1, - "favorite": false, "access": { + "delete": true, + "externalize": true, + "manage": true, "read": true, "update": true, - "externalize": true, - "delete": true, - "write": true, - "manage": true - }, - "programStage": { - "id": "NpsdDv6kKSO" + "write": true }, + "created": "2019-01-28T11:23:20.855", "dataElement": { "id": "DATAEL00005" }, - "favorites": [], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [] + "lastUpdated": "2019-01-28T11:23:20.855", + "programStage": { + "id": "NpsdDv6kKSO" + }, + "sortOrder": 1 }, { - "lastUpdated": "2019-01-28T11:23:20.855", "id": "PSDE0000006", - "created": "2019-01-28T11:23:20.855", - "displayInReports": false, - "skipSynchronization": false, - "externalAccess": false, - "renderOptionsAsRadio": false, - "allowFutureDate": false, - "compulsory": false, - "allowProvidedElsewhere": false, - "sortOrder": 1, - "favorite": false, "access": { + "delete": true, + "externalize": true, + "manage": true, "read": true, "update": true, - "externalize": true, - "delete": true, - "write": true, - "manage": true - }, - "programStage": { - "id": "NpsdDv6kKSO" + "write": true }, + "created": "2019-01-28T11:23:20.855", "dataElement": { "id": "DATAEL00006" }, - "favorites": [], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [] + "lastUpdated": "2019-01-28T11:23:20.855", + "programStage": { + "id": "NpsdDv6kKSO" + }, + "sortOrder": 1 }, { - "lastUpdated": "2019-01-28T11:23:20.855", "id": "PSDE0000007", - "created": "2019-01-28T11:23:20.855", - "displayInReports": false, - "skipSynchronization": false, - "externalAccess": false, - "renderOptionsAsRadio": false, - "allowFutureDate": false, - "compulsory": false, - "allowProvidedElsewhere": false, - "sortOrder": 1, - "favorite": false, "access": { + "delete": true, + "externalize": true, + "manage": true, "read": true, "update": true, - "externalize": true, - "delete": true, - "write": true, - "manage": true - }, - "programStage": { - "id": "NpsdDv6kKSO" + "write": true }, + "created": "2019-01-28T11:23:20.855", "dataElement": { "id": "DATAEL00007" }, - "favorites": [], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [] + "lastUpdated": "2019-01-28T11:23:20.855", + "programStage": { + "id": "NpsdDv6kKSO" + }, + "sortOrder": 1 } - ] + ], + "repeatable": true, + "sortOrder": 1, + "user": { + "id": "tTgjgobT1oS" + }, + "validationStrategy": "ON_UPDATE_AND_INSERT" }, { - "lastUpdated": "2020-05-31T11:41:22.426", "id": "NpsdDv6kKS2", + "autoGenerateEvent": true, "created": "2020-05-31T09:02:52.687", - "name": "test-program-stage2", - "allowGenerateNextVisit": false, - "preGenerateUID": false, - "publicAccess": "rwrw----", "description": "test-program-stage2", - "openAfterEnrollment": false, - "repeatable": true, - "remindCompleted": false, "displayGenerateEventBox": true, - "generatedByEnrollmentDate": false, - "validationStrategy": "ON_COMPLETE", - "autoGenerateEvent": true, - "sortOrder": 1, - "hideDueDate": false, - "blockEntryForm": false, - "enableUserAssignment": false, - "minDaysFromStart": 0, - "program": { - "id": "BFcipDERJnf" - }, + "lastUpdated": "2020-05-31T11:41:22.426", "lastUpdatedBy": { "id": "tTgjgobT1oS" }, - "user": { - "id": "tTgjgobT1oS" - }, + "minDaysFromStart": 0, + "name": "test-program-stage2", "notificationTemplates": [ { "id": "FdIeUL4gyoB" } ], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [], - "programStageSections": [], - "programStageDataElements": [] + "program": { + "id": "BFcipDERJnf" + }, + "repeatable": true, + "sortOrder": 1, + "user": { + "id": "tTgjgobT1oS" + }, + "validationStrategy": "ON_COMPLETE" }, { - "lastUpdated": "2020-05-31T11:41:22.426", "id": "NpsdDv6kKSe", + "autoGenerateEvent": true, "created": "2020-05-31T09:02:52.687", - "name": "test-program-stage", - "allowGenerateNextVisit": false, - "preGenerateUID": false, - "publicAccess": "--------", "description": "test-program-stage", - "openAfterEnrollment": false, - "repeatable": true, - "remindCompleted": false, "displayGenerateEventBox": true, - "generatedByEnrollmentDate": false, - "validationStrategy": "ON_COMPLETE", - "autoGenerateEvent": true, - "sortOrder": 1, - "hideDueDate": false, - "blockEntryForm": false, - "enableUserAssignment": false, - "minDaysFromStart": 0, - "program": { - "id": "BFcipDERJne" - }, + "lastUpdated": "2020-05-31T11:41:22.426", "lastUpdatedBy": { "id": "tTgjgobT1oS" }, - "user": { - "id": "tTgjgobT1oS" - }, + "minDaysFromStart": 0, + "name": "test-program-stage", "notificationTemplates": [ { "id": "FdIeUL4gyoB" } ], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [], - "programStageSections": [] + "program": { + "id": "BFcipDERJne" + }, + "repeatable": true, + "sortOrder": 1, + "user": { + "id": "tTgjgobT1oS" + }, + "validationStrategy": "ON_COMPLETE" }, { - "lastUpdated": "2020-05-31T11:41:22.426", "id": "NpsdDv6kKSg", + "autoGenerateEvent": true, "created": "2020-05-31T09:02:52.687", - "name": "test-program--stage", - "allowGenerateNextVisit": false, - "preGenerateUID": false, - "publicAccess": "--------", "description": "test-program-stage", - "openAfterEnrollment": false, - "repeatable": true, - "remindCompleted": false, "displayGenerateEventBox": true, - "generatedByEnrollmentDate": false, - "validationStrategy": "ON_COMPLETE", - "autoGenerateEvent": true, - "sortOrder": 1, - "hideDueDate": false, - "blockEntryForm": false, - "enableUserAssignment": false, - "minDaysFromStart": 0, - "program": { - "id": "BFcipDERJng" - }, + "lastUpdated": "2020-05-31T11:41:22.426", "lastUpdatedBy": { "id": "tTgjgobT1oS" }, - "user": { - "id": "tTgjgobT1oS" - }, + "minDaysFromStart": 0, + "name": "test-program--stage", "notificationTemplates": [ { "id": "FdIeUL4gyoB" } ], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [], - "programStageSections": [], + "program": { + "id": "BFcipDERJng" + }, "programStageDataElements": [ { - "lastUpdated": "2022-04-22T05:57:59.783", "id": "j4bu9hXjR6g", - "created": "2022-04-22T05:57:48.383", - "displayInReports": false, - "skipSynchronization": false, - "externalAccess": false, - "renderOptionsAsRadio": false, - "skipAnalytics": false, - "allowFutureDate": false, - "sharing": { - "users": {}, - "userGroups": {} - }, - "compulsory": false, - "allowProvidedElsewhere": false, - "sortOrder": 1, - "favorite": false, "access": { + "delete": true, + "manage": true, "read": true, "update": true, - "externalize": false, - "delete": true, - "write": true, - "manage": true - }, - "programStage": { - "id": "NpsdDv6kKSg" + "write": true }, + "created": "2022-04-22T05:57:48.383", "dataElement": { "id": "GieVkTxp4HG" }, - "favorites": [], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [] + "lastUpdated": "2022-04-22T05:57:59.783", + "programStage": { + "id": "NpsdDv6kKSg" + }, + "sharing": {}, + "sortOrder": 1 } - ] + ], + "repeatable": true, + "sortOrder": 1, + "user": { + "id": "tTgjgobT1oS" + }, + "validationStrategy": "ON_COMPLETE" }, { "id": "qLZC0lvvxQH", - "name": "Program stage for multiple categories program", + "attributeValues": [ + { + "attribute": { + "id": "j45AR9cBQKc" + }, + "value": "programStage qLZC0lvvxQH" + } + ], + "autoGenerateEvent": true, "code": "multi-stage", - "lastUpdated": "2022-04-22T05:57:59.849", "created": "2022-04-22T05:57:48.383", - "allowGenerateNextVisit": false, - "preGenerateUID": false, - "openAfterEnrollment": false, - "repeatable": false, - "remindCompleted": false, "displayGenerateEventBox": true, - "generatedByEnrollmentDate": false, - "validationStrategy": "ON_UPDATE_AND_INSERT", - "autoGenerateEvent": true, - "hideDueDate": false, - "blockEntryForm": false, - "enableUserAssignment": false, + "lastUpdated": "2022-04-22T05:57:59.849", "minDaysFromStart": 0, + "name": "Program stage for multiple categories program", "program": { "id": "iS7eutanDry" }, - "notificationTemplates": [], "programStageDataElements": [ { - "lastUpdated": "2022-04-22T05:57:59.783", "id": "j4bu9hXjR6n", - "created": "2022-04-22T05:57:48.383", - "displayInReports": false, - "skipSynchronization": false, - "externalAccess": false, - "renderOptionsAsRadio": false, - "skipAnalytics": false, - "allowFutureDate": false, - "sharing": { - "users": {}, - "userGroups": {} - }, - "compulsory": false, - "allowProvidedElsewhere": false, - "sortOrder": 1, - "favorite": false, "access": { + "delete": true, + "manage": true, "read": true, "update": true, - "externalize": false, - "delete": true, - "write": true, - "manage": true + "write": true + }, + "created": "2022-04-22T05:57:48.383", + "dataElement": { + "id": "GieVkTxp4HH" }, + "lastUpdated": "2022-04-22T05:57:59.783", "programStage": { "id": "qLZC0lvvxQH" }, + "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": "GieVkTxp4HH" + "id": "GieVkTxp4HG" }, - "favorites": [], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [] + "lastUpdated": "2022-04-22T05:57:59.783", + "programStage": { + "id": "qLZC0lvvxQH" + }, + "sharing": {}, + "sortOrder": 2 } ], - "translations": [], + "validationStrategy": "ON_UPDATE_AND_INSERT", + "featureType": "POINT", "attributeValues": [ { "attribute": { @@ -1353,1463 +1132,907 @@ }, "value": "multi-program-stage-attribute" } - ], - "programStageSections": [] + ] }, { - "lastUpdated": "2020-05-31T11:41:22.426", "id": "SKNvpoLioON", + "autoGenerateEvent": true, "created": "2020-05-31T09:02:52.687", - "name": "test-program-stage2", - "allowGenerateNextVisit": false, - "preGenerateUID": false, - "publicAccess": "rwrw----", "description": "test-program-stage2", - "openAfterEnrollment": false, - "repeatable": true, - "remindCompleted": false, "displayGenerateEventBox": true, - "generatedByEnrollmentDate": false, - "validationStrategy": "ON_COMPLETE", - "autoGenerateEvent": true, - "sortOrder": 1, - "hideDueDate": false, - "blockEntryForm": false, - "enableUserAssignment": false, - "minDaysFromStart": 0, - "program": { - "id": "shPjYNifvMK" - }, + "lastUpdated": "2020-05-31T11:41:22.426", "lastUpdatedBy": { "id": "FIgVWzUCkpw" }, - "user": { - "id": "FIgVWzUCkpw" - }, + "minDaysFromStart": 0, + "name": "test-program-stage2", "notificationTemplates": [ { "id": "FdIeUL4gyoB" } ], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [], - "programStageSections": [], - "programStageDataElements": [] + "program": { + "id": "shPjYNifvMK" + }, + "repeatable": true, + "sortOrder": 1, + "user": { + "id": "FIgVWzUCkpw" + }, + "validationStrategy": "ON_COMPLETE" }, { - "lastUpdated": "2020-05-31T11:41:22.426", "id": "ebGXHEqqEMF", + "autoGenerateEvent": true, "created": "2020-05-31T09:02:52.687", - "name": "test-program-stage-closed", - "allowGenerateNextVisit": false, - "preGenerateUID": false, - "publicAccess": "rwrw----", "description": "test-program-stage-closed", - "openAfterEnrollment": false, - "repeatable": true, - "remindCompleted": false, "displayGenerateEventBox": true, - "generatedByEnrollmentDate": false, - "validationStrategy": "ON_COMPLETE", - "autoGenerateEvent": true, - "sortOrder": 1, - "hideDueDate": false, - "blockEntryForm": false, - "enableUserAssignment": false, - "minDaysFromStart": 0, - "program": { - "id": "shPjYNifvMK" - }, + "lastUpdated": "2020-05-31T11:41:22.426", "lastUpdatedBy": { "id": "FIgVWzUCkpw" }, - "user": { - "id": "FIgVWzUCkpw" - }, + "minDaysFromStart": 0, + "name": "test-program-stage-closed", "notificationTemplates": [ { "id": "FdIeUL4gyoB" } ], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [], - "programStageSections": [], - "programStageDataElements": [] + "program": { + "id": "shPjYNifvMK" + }, + "repeatable": true, + "sortOrder": 1, + "user": { + "id": "FIgVWzUCkpw" + }, + "validationStrategy": "ON_COMPLETE" }, { - "lastUpdated": "2020-05-31T11:41:22.426", "id": "ZfzckZBvDkA", + "autoGenerateEvent": true, "created": "2020-05-31T09:02:52.687", - "name": "test-program-stage-low-level-org-unit", - "allowGenerateNextVisit": false, - "preGenerateUID": false, - "publicAccess": "rwrw----", "description": "test-program-stage-low-level-org-unit", - "openAfterEnrollment": false, - "repeatable": true, - "remindCompleted": false, "displayGenerateEventBox": true, - "generatedByEnrollmentDate": false, - "validationStrategy": "ON_COMPLETE", - "autoGenerateEvent": true, - "sortOrder": 1, - "hideDueDate": false, - "blockEntryForm": false, - "enableUserAssignment": false, - "minDaysFromStart": 0, - "program": { - "id": "UWRnoyBjvqi" - }, + "lastUpdated": "2020-05-31T11:41:22.426", "lastUpdatedBy": { "id": "FIgVWzUCkpw" }, - "user": { - "id": "FIgVWzUCkpw" - }, + "minDaysFromStart": 0, + "name": "test-program-stage-low-level-org-unit", "notificationTemplates": [ { "id": "FdIeUL4gyoB" } ], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [], - "programStageSections": [], - "programStageDataElements": [] + "program": { + "id": "UWRnoyBjvqi" + }, + "repeatable": true, + "sortOrder": 1, + "user": { + "id": "FIgVWzUCkpw" + }, + "validationStrategy": "ON_COMPLETE" }, { - "lastUpdated": "2020-05-31T11:41:22.426", "id": "uLQthmAPTPq", + "autoGenerateEvent": true, "created": "2020-05-31T09:02:52.687", - "name": "test-program-stage-low-level-org-unit", - "allowGenerateNextVisit": false, - "preGenerateUID": false, - "publicAccess": "rwrw----", "description": "test-program-stage-low-level-org-unit", - "openAfterEnrollment": false, - "repeatable": true, - "remindCompleted": false, "displayGenerateEventBox": true, - "generatedByEnrollmentDate": false, - "validationStrategy": "ON_COMPLETE", - "autoGenerateEvent": true, - "sortOrder": 1, - "hideDueDate": false, - "blockEntryForm": false, - "enableUserAssignment": false, - "minDaysFromStart": 0, - "program": { - "id": "YlUmbgnKWkd" - }, + "lastUpdated": "2020-05-31T11:41:22.426", "lastUpdatedBy": { "id": "FIgVWzUCkpw" }, - "user": { - "id": "FIgVWzUCkpw" - }, + "minDaysFromStart": 0, + "name": "test-program-stage-low-level-org-unit", "notificationTemplates": [ { "id": "FdIeUL4gyoB" } ], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [], - "programStageSections": [], - "programStageDataElements": [] - }, + "program": { + "id": "YlUmbgnKWkd" + }, + "repeatable": true, + "sortOrder": 1, + "user": { + "id": "FIgVWzUCkpw" + }, + "validationStrategy": "ON_COMPLETE" + }, { - "lastUpdated": "2020-05-31T11:41:22.426", "id": "GmxBvezOlGA", + "autoGenerateEvent": true, "created": "2020-05-31T09:02:52.687", - "name": "test-program-stage-low-level-org-unit", - "allowGenerateNextVisit": false, - "preGenerateUID": false, - "publicAccess": "rwrw----", "description": "test-program-stage-low-level-org-unit", - "openAfterEnrollment": false, - "repeatable": true, - "remindCompleted": false, "displayGenerateEventBox": true, - "generatedByEnrollmentDate": false, - "validationStrategy": "ON_COMPLETE", - "autoGenerateEvent": true, - "sortOrder": 1, - "hideDueDate": false, - "blockEntryForm": false, - "enableUserAssignment": false, - "minDaysFromStart": 0, - "program": { - "id": "SeeUNWLQmZk" - }, + "lastUpdated": "2020-05-31T11:41:22.426", "lastUpdatedBy": { "id": "FIgVWzUCkpw" }, - "user": { - "id": "FIgVWzUCkpw" - }, + "minDaysFromStart": 0, + "name": "test-program-stage-low-level-org-unit", "notificationTemplates": [ { "id": "FdIeUL4gyoB" } ], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [], - "programStageSections": [], - "programStageDataElements": [] + "program": { + "id": "SeeUNWLQmZk" + }, + "repeatable": true, + "sortOrder": 1, + "user": { + "id": "FIgVWzUCkpw" + }, + "validationStrategy": "ON_COMPLETE" }, { - "lastUpdated": "2020-05-31T11:41:22.426", "id": "zydGjigJcJb", + "autoGenerateEvent": true, "created": "2020-05-31T09:02:52.687", - "name": "test-program-stage-low-level-org-unit", - "allowGenerateNextVisit": false, - "preGenerateUID": false, - "publicAccess": "rwrw----", "description": "test-program-stage-low-level-org-unit", - "openAfterEnrollment": false, - "repeatable": true, - "remindCompleted": false, "displayGenerateEventBox": true, - "generatedByEnrollmentDate": false, - "validationStrategy": "ON_COMPLETE", - "autoGenerateEvent": true, - "sortOrder": 1, - "hideDueDate": false, - "blockEntryForm": false, - "enableUserAssignment": false, - "minDaysFromStart": 0, - "program": { - "id": "SeeUNWLQmZk" - }, + "lastUpdated": "2020-05-31T11:41:22.426", "lastUpdatedBy": { "id": "FIgVWzUCkpw" }, - "user": { - "id": "FIgVWzUCkpw" - }, + "minDaysFromStart": 0, + "name": "test-program-stage-low-level-org-unit", "notificationTemplates": [ { "id": "FdIeUL4gyoB" } ], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [], - "programStageSections": [], - "programStageDataElements": [] + "featureType": "POLYGON", + "repeatable": true, + "sortOrder": 1, + "user": { + "id": "FIgVWzUCkpw" + }, + "validationStrategy": "ON_COMPLETE" }, { - "lastUpdated": "2020-05-31T11:41:22.426", "id": "aZdGjigJcJb", + "autoGenerateEvent": true, "created": "2020-05-31T09:02:52.687", - "name": "test-program-stage-inaccessible-tet", - "allowGenerateNextVisit": false, - "preGenerateUID": false, - "publicAccess": "rwrw----", "description": "test-program-stage-inaccessible-tet", - "openAfterEnrollment": false, - "repeatable": true, - "remindCompleted": false, "displayGenerateEventBox": true, - "generatedByEnrollmentDate": false, - "validationStrategy": "ON_COMPLETE", - "autoGenerateEvent": true, - "sortOrder": 1, - "hideDueDate": false, - "blockEntryForm": false, - "enableUserAssignment": false, - "minDaysFromStart": 0, - "program": { - "id": "TsngICFQjvH" - }, + "lastUpdated": "2020-05-31T11:41:22.426", "lastUpdatedBy": { "id": "FIgVWzUCkpw" }, - "user": { - "id": "FIgVWzUCkpw" - }, + "minDaysFromStart": 0, + "name": "test-program-stage-inaccessible-tet", "notificationTemplates": [ { "id": "FdIeUL4gyoB" } ], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [], - "programStageSections": [], - "programStageDataElements": [] + "program": { + "id": "TsngICFQjvH" + }, + "repeatable": true, + "sortOrder": 1, + "user": { + "id": "FIgVWzUCkpw" + }, + "validationStrategy": "ON_COMPLETE" } ], - "users": [ + "programs": [ { - "lastUpdated": "2020-05-31T11:41:22.356", - "id": "lPaILkLkgOM", - "created": "2020-05-31T08:57:59.048", - "surname": "Asghar", - "email": "zubair@dhis2.org", - "firstName": "Zubair", - "password": "Test123###...", - "phoneNumber": "+4740332255", - "name": "Zubair Asghar", - "displayName": "Zubair Asghar", - "externalAuth": false, - "externalAccess": false, - "disabled": false, - "twoFA": false, - "passwordLastUpdated": "2020-05-31T08:57:59.060", - "invitation": false, - "selfRegistered": false, - "favorite": false, - "username": "testuser", - "access": { - "read": true, - "update": true, - "externalize": true, - "delete": true, - "write": true, - "manage": true - }, - "favorites": [], - "cogsDimensionConstraints": [], - "catDimensionConstraints": [], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userRoles": [ + "id": "BFcipDERJnf", + "accessLevel": "OPEN", + "attributeValues": [ { - "id": "nJ4Ml8ads4M" + "attribute": { + "id": "j45AR9cBQKc" + }, + "value": "programStage NpsdDv6kKSO" } ], - "userAccesses": [], - "teiSearchOrganisationUnits": [], + "categoryCombo": { + "id": "bjDvmb4bfuf" + }, + "completeEventsExpiryDays": 0, + "code": "BFcipDERJnf code", + "created": "2020-05-31T09:02:52.718", + "displayIncidentDate": true, + "expiryDays": 0, + "lastUpdated": "2020-05-31T11:41:22.438", + "lastUpdatedBy": { + "id": "tTgjgobT1oS" + }, + "maxTeiCountToReturn": 0, + "minAttributesRequiredToSearch": 1, + "name": "BFcipDERJnf name", "organisationUnits": [ { "id": "h4w96yEMlzO" } ], - "dataViewOrganisationUnits": [ - { - "id": "h4w96yEMlzO" - } - ] - }, - { - "code": "tracker admin", - "lastUpdated": "2020-05-31T11:41:22.356", - "id": "tTgjgobT1oS", - "created": "2020-05-31T08:55:27.984", - "password": "Test123###...", - "surname": "tracker admin", - "firstName": "tracker admin", - "name": "tracker admin", - "lastLogin": "2020-05-31T08:55:38.571", - "displayName": "tracker admin", - "externalAuth": false, - "externalAccess": false, - "disabled": false, - "twoFA": false, - "passwordLastUpdated": "2020-05-31T08:55:28.232", - "invitation": false, - "selfRegistered": false, - "favorite": false, - "username": "trackeradmin", - "access": { - "read": true, - "update": true, - "externalize": true, - "delete": true, - "write": true, - "manage": true - }, - "favorites": [], - "cogsDimensionConstraints": [], - "catDimensionConstraints": [], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userRoles": [ - { - "id": "nJ4Ml8ads4M" - } - ], - "userAccesses": [], - "teiSearchOrganisationUnits": [ - { - "id": "h4w96yEMlzO" - }, + "programStages": [ { - "id": "DiszpKrYNg8" + "id": "NpsdDv6kKSO" }, { - "id": "g4w96yEMlzO" + "id": "NpsdDv6kKS2" } ], - "organisationUnits": [ - { - "id": "h4w96yEMlzO" - }, + "programTrackedEntityAttributes": [ { - "id": "DiszpKrYNg8" + "id": "k45TmK2PnUx", + "access": { + "delete": true, + "externalize": true, + "manage": true, + "read": true, + "update": true, + "write": true + }, + "created": "2019-04-25T11:04:29.212", + "displayInList": true, + "displayName": "TA Tracker_program TA First name", + "displayShortName": "TA Tracker_program TA First name", + "lastUpdated": "2022-01-07T12:45:47.386", + "name": "TA Tracker_program TA First name", + "program": { + "id": "BFcipDERJnf" + }, + "searchable": true, + "sharing": {}, + "sortOrder": 1, + "trackedEntityAttribute": { + "id": "dIVt4l5vIOa" + }, + "valueType": "TEXT" }, { - "id": "g4w96yEMlzO" + "id": "r56Tmr3PnPf", + "access": { + "delete": true, + "externalize": true, + "manage": true, + "read": true, + "update": true, + "write": true + }, + "created": "2019-04-25T11:04:29.212", + "displayInList": true, + "displayName": "TA Tracker_program TA Test", + "displayShortName": "TA Tracker_program TA Test", + "lastUpdated": "2022-01-07T12:45:47.386", + "name": "TA Tracker_program TA Test", + "program": { + "id": "BFcipDERJnf" + }, + "searchable": true, + "sharing": {}, + "sortOrder": 2, + "trackedEntityAttribute": { + "id": "fRGt4l6yIRb" + }, + "valueType": "TEXT" } ], - "dataViewOrganisationUnits": [] + "programType": "WITH_REGISTRATION", + "shortName": "test-program", + "trackedEntityType": { + "id": "ja8NY4PW7Xm" + }, + "user": { + "id": "tTgjgobT1oS" + }, + "version": 2 }, { - "id": "o1HMTIzBGo7", - "created": "2020-05-31T08:57:59.048", - "surname": "foofi", - "email": "foofi@dhis2.org", - "firstName": "foofi", - "password": "Test123###...", - "phoneNumber": "+4740332255", - "name": "foofi", - "displayName": "foofi", - "externalAuth": false, - "externalAccess": false, - "disabled": false, - "twoFA": false, - "passwordLastUpdated": "2020-05-31T08:57:59.060", - "invitation": false, - "selfRegistered": false, - "favorite": false, - "username": "foofi", - "access": { - "read": true, - "update": true, - "externalize": true, - "delete": true, - "write": true, - "manage": true - }, - "favorites": [], - "cogsDimensionConstraints": [], - "catDimensionConstraints": [], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userRoles": [ - { - "id": "UbhT3bXWUyb" - } - ], - "userAccesses": [], - "teiSearchOrganisationUnits": [], + "id": "BFcipDERJne", + "accessLevel": "OPEN", + "categoryCombo": { + "id": "bjDvmb4bfuf" + }, + "completeEventsExpiryDays": 0, + "created": "2019-01-28T11:23:20.906", + "displayIncidentDate": true, + "expiryDays": 0, + "lastUpdated": "2019-01-28T11:23:20.906", + "lastUpdatedBy": { + "id": "tTgjgobT1oS" + }, + "maxTeiCountToReturn": 0, + "minAttributesRequiredToSearch": 1, + "name": "ProgramA", "organisationUnits": [ { - "id": "DiszpKrYNg8" + "id": "h4w96yEMlzO" } ], - "dataViewOrganisationUnits": [ + "programStages": [ { - "id": "DiszpKrYNg8" - } - ] - }, - { - "id": "CYVgFNKCaUS", - "created": "2020-05-31T08:57:59.048", - "surname": "aaaa", - "email": "aaaa@dhis2.org", - "firstName": "aaaa", - "password": "Test123###...", - "phoneNumber": "+4740332255", - "name": "aaaa", - "displayName": "aaaa", - "externalAuth": false, - "externalAccess": false, - "disabled": false, - "twoFA": false, - "passwordLastUpdated": "2020-05-31T08:57:59.060", - "invitation": false, - "selfRegistered": false, - "favorite": false, - "username": "aaaa", - "access": { - "read": true, - "update": true, - "externalize": true, - "delete": true, - "write": true, - "manage": true - }, - "favorites": [], - "cogsDimensionConstraints": [], - "catDimensionConstraints": [], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userRoles": [ - { - "id": "UbhT3bXWUyb" + "id": "NpsdDv6kKSe" } ], - "userAccesses": [], - "teiSearchOrganisationUnits": [], + "programType": "WITHOUT_REGISTRATION", + "shortName": "ProgramA", + "user": { + "id": "tTgjgobT1oS" + }, + "version": 0 + }, + { + "id": "BFcipDERJng", + "accessLevel": "OPEN", + "categoryCombo": { + "id": "bjDvmb4bfuf" + }, + "completeEventsExpiryDays": 0, + "created": "2019-01-28T11:23:20.906", + "displayIncidentDate": true, + "expiryDays": 0, + "lastUpdated": "2019-01-28T11:23:20.906", + "lastUpdatedBy": { + "id": "tTgjgobT1oS" + }, + "maxTeiCountToReturn": 0, + "minAttributesRequiredToSearch": 1, + "name": "ProgramG", "organisationUnits": [ { - "id": "DiszpKrYNg8" + "id": "g4w96yEMlzO" } ], - "dataViewOrganisationUnits": [ + "programStages": [ { - "id": "DiszpKrYNg8" + "id": "NpsdDv6kKSg" } - ] + ], + "programType": "WITHOUT_REGISTRATION", + "sharing": { + "public": "--------" + }, + "shortName": "ProgramG", + "user": { + "id": "tTgjgobT1oS" + }, + "version": 0 }, { - "id": "FIgVWzUCkpw", - "created": "2020-05-31T08:57:59.048", - "surname": "child level 2", - "email": "aaaa@dhis2.org", - "firstName": "child level 2", - "password": "Test123###...", - "phoneNumber": "+4740332255", - "name": "child level 2", - "displayName": "child level 2", - "externalAuth": false, - "externalAccess": false, - "disabled": false, - "twoFA": false, - "passwordLastUpdated": "2020-05-31T08:57:59.060", - "invitation": false, - "selfRegistered": false, - "favorite": false, - "username": "childlevel2", - "access": { - "read": true, - "update": true, - "externalize": true, - "delete": true, - "write": true, - "manage": true - }, - "favorites": [], - "cogsDimensionConstraints": [], - "catDimensionConstraints": [], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userRoles": [ - { - "id": "UbhT3bXWUyb" - } - ], - "userAccesses": [], - "teiSearchOrganisationUnits": [ + "id": "iS7eutanDry", + "accessLevel": "OPEN", + "attributeValues": [ { - "id": "h4w96yEMlzO" + "attribute": { + "id": "j45AR9cBQKc" + }, + "value": "program iS7eutanDry" } ], + "categoryCombo": { + "id": "O4VaNks6tta" + }, + "code": "iS7eutanDry code", + "completeEventsExpiryDays": 0, + "created": "2022-04-22T05:57:48.409", + "displayIncidentDate": true, + "expiryDays": 0, + "lastUpdated": "2022-04-22T05:57:59.829", + "maxTeiCountToReturn": 0, + "minAttributesRequiredToSearch": 1, + "name": "Program with multiple categories", "organisationUnits": [ { - "id": "uoNW0E3xXUy" + "id": "DiszpKrYNg8" } ], - "dataViewOrganisationUnits": [ + "programStages": [ { - "id": "uoNW0E3xXUy" + "id": "qLZC0lvvxQH" } - ] + ], + "programType": "WITHOUT_REGISTRATION", + "shortName": "Program with multiple categories", + "version": 1 }, { - "id": "nIidJVYpQQK", - "created": "2020-05-31T08:57:59.048", - "surname": "child level 3", - "email": "aaaa@dhis2.org", - "firstName": "child level 3", - "password": "Test123###...", - "phoneNumber": "+4740332255", - "name": "child level 2", - "displayName": "child level 2", - "externalAuth": false, - "externalAccess": false, - "disabled": false, - "twoFA": false, - "passwordLastUpdated": "2020-05-31T08:57:59.060", - "invitation": false, - "selfRegistered": false, - "favorite": false, - "username": "childlevel3", - "access": { - "read": true, - "update": true, - "externalize": true, - "delete": true, - "write": true, - "manage": true - }, - "favorites": [], - "cogsDimensionConstraints": [], - "catDimensionConstraints": [], - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userRoles": [ + "id": "shPjYNifvMK", + "accessLevel": "OPEN", + "categoryCombo": { + "id": "bjDvmb4bfuf" + }, + "completeEventsExpiryDays": 0, + "created": "2020-05-31T09:02:52.718", + "displayIncidentDate": true, + "expiryDays": 0, + "lastUpdated": "2020-05-31T11:41:22.438", + "lastUpdatedBy": { + "id": "FIgVWzUCkpw" + }, + "maxTeiCountToReturn": 0, + "minAttributesRequiredToSearch": 1, + "name": "test-program", + "organisationUnits": [ { - "id": "UbhT3bXWUyb" + "id": "uoNW0E3xXUy" } ], - "userAccesses": [], - "teiSearchOrganisationUnits": [ + "programStages": [ { - "id": "DiszpKrYNg8" + "id": "SKNvpoLioON" } ], + "programType": "WITH_REGISTRATION", + "shortName": "test-program", + "trackedEntityType": { + "id": "ja8NY4PW7Xm" + }, + "user": { + "id": "FIgVWzUCkpw" + }, + "version": 2 + }, + { + "id": "pcxIanBWlSY", + "accessLevel": "CLOSED", + "categoryCombo": { + "id": "bjDvmb4bfuf" + }, + "completeEventsExpiryDays": 0, + "created": "2020-05-31T09:02:52.718", + "displayIncidentDate": true, + "expiryDays": 0, + "lastUpdated": "2020-05-31T11:41:22.438", + "lastUpdatedBy": { + "id": "FIgVWzUCkpw" + }, + "maxTeiCountToReturn": 0, + "minAttributesRequiredToSearch": 1, + "name": "test-program-closed", "organisationUnits": [ { "id": "uoNW0E3xXUy" - }, - { - "id": "lbDXJBlvtZe" } ], - "dataViewOrganisationUnits": [ + "programStages": [ { - "id": "uoNW0E3xXUy" + "id": "ebGXHEqqEMF" } - ] - } - ], - "organisationUnits": [ - { - "id": "h4w96yEMlzO", - "name": "test-orgunit", - "code": "test-orgunit-code", - "level": 1, - "created": "2020-05-31T08:56:15.922", - "lastUpdated": "2020-05-31T11:41:22.384", - "publicAccess": "rwrw----", - "shortName": "test-orgunit", - "description": "test-orgunit", - "path": "/h4w96yEMlzO", - "openingDate": "2020-05-31T00:00:00.000", - "lastUpdatedBy": { - "id": "tTgjgobT1oS" + ], + "programType": "WITH_REGISTRATION", + "shortName": "test-program-closed", + "trackedEntityType": { + "id": "ja8NY4PW7Xm" }, "user": { - "id": "tTgjgobT1oS" + "id": "FIgVWzUCkpw" }, - "attributeValues": [], - "translations": [] + "version": 2 }, { - "id": "g4w96yEMlzO", - "name": "test-orgunit", - "code": "test-orgunit-code-no-sharing-access", - "level": 1, - "created": "2020-05-31T08:56:15.922", - "lastUpdated": "2020-05-31T11:41:22.384", - "publicAccess": "rwrw----", - "shortName": "test-orgunit", - "description": "test-orgunit", - "path": "/g4w96yEMlzO", - "openingDate": "2020-05-31T00:00:00.000", + "id": "UWRnoyBjvqi", + "accessLevel": "CLOSED", + "categoryCombo": { + "id": "bjDvmb4bfuf" + }, + "completeEventsExpiryDays": 0, + "created": "2020-05-31T09:02:52.718", + "displayIncidentDate": true, + "expiryDays": 0, + "lastUpdated": "2020-05-31T11:41:22.438", "lastUpdatedBy": { - "id": "tTgjgobT1oS" + "id": "FIgVWzUCkpw" + }, + "maxTeiCountToReturn": 0, + "minAttributesRequiredToSearch": 1, + "name": "test-program-low-level-org-unit", + "organisationUnits": [ + { + "id": "tSsGrtfRzjY" + } + ], + "programStages": [ + { + "id": "ZfzckZBvDkA" + } + ], + "programType": "WITH_REGISTRATION", + "shortName": "test-program-low-level-org-unit", + "trackedEntityType": { + "id": "ja8NY4PW7Xm" }, "user": { - "id": "tTgjgobT1oS" + "id": "FIgVWzUCkpw" }, - "attributeValues": [], - "translations": [] + "version": 2 }, { - "id": "uoNW0E3xXUy", - "name": "test-orgunit-2", - "code": "test-orgunit-code-2", - "level": 2, - "created": "2020-05-31T09:05:34.570", - "lastUpdated": "2020-05-31T11:41:22.385", - "shortName": "test-program-rule", - "path": "/h4w96yEMlzO/uoNW0E3xXUy", - "closedDate": "2020-12-27T00:00:00.000", - "openingDate": "2020-05-31T00:00:00.000", - "parent": { - "id": "h4w96yEMlzO" + "id": "YlUmbgnKWkd", + "accessLevel": "CLOSED", + "categoryCombo": { + "id": "bjDvmb4bfuf" }, + "completeEventsExpiryDays": 0, + "created": "2020-05-31T09:02:52.718", + "displayIncidentDate": true, + "expiryDays": 0, + "lastUpdated": "2020-05-31T11:41:22.438", "lastUpdatedBy": { - "id": "tTgjgobT1oS" - }, - "user": { - "id": "tTgjgobT1oS" - }, - "attributeValues": [], - "translations": [] - }, - { - "id": "lbDXJBlvtZe", - "name": "test-orgunit-2", - "code": "test-orgunit-code-l2", - "level": 2, - "created": "2020-05-31T09:05:34.570", - "lastUpdated": "2020-05-31T11:41:22.385", - "shortName": "test-program-rule", - "path": "/h4w96yEMlzO/lbDXJBlvtZe", - "closedDate": "2020-12-27T00:00:00.000", - "openingDate": "2020-05-31T00:00:00.000", - "parent": { - "id": "h4w96yEMlzO" + "id": "FIgVWzUCkpw" }, - "lastUpdatedBy": { - "id": "tTgjgobT1oS" + "maxTeiCountToReturn": 0, + "minAttributesRequiredToSearch": 1, + "name": "test-program-low-level-org-unit", + "organisationUnits": [ + { + "id": "RojfDTBhoGC" + } + ], + "programStages": [ + { + "id": "uLQthmAPTPq" + } + ], + "programType": "WITH_REGISTRATION", + "shortName": "test-program-low-level-org-unit", + "trackedEntityType": { + "id": "ja8NY4PW7Xm" }, "user": { - "id": "tTgjgobT1oS" + "id": "FIgVWzUCkpw" }, - "attributeValues": [], - "translations": [] + "version": 2 }, { - "id": "RojfDTBhoGC", - "name": "test-orgunit-3", - "code": "test-orgunit-code-3", - "level": 3, - "created": "2020-05-31T09:05:34.570", - "lastUpdated": "2020-05-31T11:41:22.385", - "shortName": "test-program-rule", - "path": "/h4w96yEMlzO/uoNW0E3xXUy/RojfDTBhoGC", - "closedDate": "2020-12-27T00:00:00.000", - "openingDate": "2020-05-31T00:00:00.000", - "parent": { - "id": "uoNW0E3xXUy" + "id": "SeeUNWLQmZk", + "accessLevel": "CLOSED", + "categoryCombo": { + "id": "bjDvmb4bfuf" }, + "completeEventsExpiryDays": 0, + "created": "2020-05-31T09:02:52.718", + "displayIncidentDate": true, + "expiryDays": 0, + "lastUpdated": "2020-05-31T11:41:22.438", "lastUpdatedBy": { - "id": "tTgjgobT1oS" - }, - "user": { - "id": "tTgjgobT1oS" - }, - "attributeValues": [], - "translations": [] - }, - { - "id": "tSsGrtfRzjY", - "name": "test-orgunit-4", - "code": "test-orgunit-code-4", - "level": 4, - "created": "2020-05-31T09:05:34.570", - "lastUpdated": "2020-05-31T11:41:22.385", - "shortName": "test-program-rule", - "path": "/h4w96yEMlzO/uoNW0E3xXUy/RojfDTBhoGC/tSsGrtfRzjY", - "closedDate": "2020-12-27T00:00:00.000", - "openingDate": "2020-05-31T00:00:00.000", - "parent": { - "id": "RojfDTBhoGC" + "id": "FIgVWzUCkpw" }, - "lastUpdatedBy": { - "id": "tTgjgobT1oS" + "maxTeiCountToReturn": 0, + "minAttributesRequiredToSearch": 1, + "name": "test-program-low-level-org-unit", + "organisationUnits": [ + { + "id": "DiszpKrYNg8" + } + ], + "programStages": [ + { + "id": "GmxBvezOlGA" + } + ], + "programType": "WITH_REGISTRATION", + "shortName": "test-program-low-level-org-unit", + "trackedEntityType": { + "id": "ja8NY4PW7Xm" }, "user": { - "id": "tTgjgobT1oS" + "id": "FIgVWzUCkpw" }, - "attributeValues": [], - "translations": [] + "version": 2 }, { - "id": "DiszpKrYNg8", - "name": "test-orgunit-3", - "code": "org3", - "level": 1, - "created": "2020-05-31T08:56:15.922", - "lastUpdated": "2020-05-31T11:41:22.384", - "publicAccess": "rwrw----", - "shortName": "test-orgunit-3", - "description": "test-orgunit-3", - "path": "/DiszpKrYNg8", - "openingDate": "2020-05-31T00:00:00.000", - "lastUpdatedBy": { - "id": "tTgjgobT1oS" - }, - "user": { - "id": "tTgjgobT1oS" - }, - "attributeValues": [ - { - "attribute": { - "id": "j45AR9cBQKc" - }, - "value": "multi-org-attribute" - } - ], - "translations": [] - } - ], - "programs": [ - { - "id": "BFcipDERJnf", - "name": "test-program", - "shortName": "test-program", - "lastUpdated": "2020-05-31T11:41:22.438", - "created": "2020-05-31T09:02:52.718", - "publicAccess": "rwrw----", - "completeEventsExpiryDays": 0, - "ignoreOverdueEvents": false, - "skipOffline": false, - "minAttributesRequiredToSearch": 1, - "displayFrontPageList": false, - "onlyEnrollOnce": false, - "programType": "WITH_REGISTRATION", + "id": "sLngICFQjvH", "accessLevel": "OPEN", - "version": 2, - "maxTeiCountToReturn": 0, - "selectIncidentDatesInFuture": false, - "displayIncidentDate": true, - "selectEnrollmentDatesInFuture": false, - "expiryDays": 0, - "useFirstStageDuringRegistration": false, "categoryCombo": { "id": "bjDvmb4bfuf" }, + "completeEventsExpiryDays": 0, + "created": "2020-05-31T09:02:52.718", + "displayIncidentDate": true, + "expiryDays": 0, + "lastUpdated": "2020-05-31T11:41:22.438", "lastUpdatedBy": { - "id": "tTgjgobT1oS" - }, - "trackedEntityType": { - "id": "ja8NY4PW7Xm" - }, - "user": { - "id": "tTgjgobT1oS" + "id": "FIgVWzUCkpw" }, - "programTrackedEntityAttributes": [ - { - "lastUpdated": "2022-01-07T12:45:47.386", - "id": "k45TmK2PnUx", - "created": "2019-04-25T11:04:29.212", - "displayName": "TA Tracker_program TA First name", - "mandatory": false, - "displayShortName": "TA Tracker_program TA First name", - "renderOptionsAsRadio": false, - "valueType": "TEXT", - "searchable": true, - "displayInList": true, - "sortOrder": 1, - "name": "TA Tracker_program TA First name", - "favorite": false, - "access": { - "read": true, - "update": true, - "externalize": true, - "write": true, - "delete": true, - "manage": true - }, - "program": { - "id": "BFcipDERJnf" - }, - "trackedEntityAttribute": { - "id": "dIVt4l5vIOa" - }, - "sharing": { - "userGroups": {}, - "external": false, - "users": {} - }, - "favorites": [], - "translations": [], - "attributeValues": [] - }, - { - "lastUpdated": "2022-01-07T12:45:47.386", - "id": "r56Tmr3PnPf", - "created": "2019-04-25T11:04:29.212", - "displayName": "TA Tracker_program TA Test", - "mandatory": false, - "displayShortName": "TA Tracker_program TA Test", - "renderOptionsAsRadio": false, - "valueType": "TEXT", - "searchable": true, - "displayInList": true, - "sortOrder": 2, - "name": "TA Tracker_program TA Test", - "favorite": false, - "access": { - "read": true, - "update": true, - "externalize": true, - "write": true, - "delete": true, - "manage": true - }, - "program": { - "id": "BFcipDERJnf" - }, - "trackedEntityAttribute": { - "id": "fRGt4l6yIRb" - }, - "sharing": { - "userGroups": {}, - "external": false, - "users": {} - }, - "favorites": [], - "translations": [], - "attributeValues": [] - } - ], - "notificationTemplates": [], - "translations": [], + "maxTeiCountToReturn": 0, + "minAttributesRequiredToSearch": 1, + "name": "test-program-low-level-org-unit", "organisationUnits": [ { - "id": "h4w96yEMlzO" + "id": "lbDXJBlvtZe" } ], - "userGroupAccesses": [], - "programSections": [], - "attributeValues": [], "programStages": [ { - "id": "NpsdDv6kKSO" - }, - { - "id": "NpsdDv6kKS2" + "id": "zydGjigJcJb" } ], - "userAccesses": [] + "programType": "WITH_REGISTRATION", + "shortName": "test-program-low-level-org-unit", + "trackedEntityType": { + "id": "ja8NY4PW7Xm" + }, + "user": { + "id": "FIgVWzUCkpw" + }, + "version": 2 }, { - "id": "BFcipDERJne", - "name": "ProgramA", - "shortName": "ProgramA", - "lastUpdated": "2019-01-28T11:23:20.906", - "created": "2019-01-28T11:23:20.906", - "publicAccess": "rw------", - "completeEventsExpiryDays": 0, - "ignoreOverdueEvents": false, - "skipOffline": false, - "minAttributesRequiredToSearch": 1, - "displayFrontPageList": false, - "onlyEnrollOnce": false, - "programType": "WITHOUT_REGISTRATION", + "id": "TsngICFQjvH", "accessLevel": "OPEN", - "version": 0, - "maxTeiCountToReturn": 0, - "selectIncidentDatesInFuture": false, - "displayIncidentDate": true, - "selectEnrollmentDatesInFuture": false, - "expiryDays": 0, - "useFirstStageDuringRegistration": false, "categoryCombo": { "id": "bjDvmb4bfuf" }, + "completeEventsExpiryDays": 0, + "created": "2020-05-31T09:02:52.718", + "displayIncidentDate": true, + "expiryDays": 0, + "lastUpdated": "2020-05-31T11:41:22.438", "lastUpdatedBy": { - "id": "tTgjgobT1oS" - }, - "user": { - "id": "tTgjgobT1oS" + "id": "FIgVWzUCkpw" }, - "programTrackedEntityAttributes": [], - "notificationTemplates": [], - "translations": [], + "maxTeiCountToReturn": 0, + "minAttributesRequiredToSearch": 1, + "name": "test-program-unaccessible-tet", "organisationUnits": [ { - "id": "h4w96yEMlzO" + "id": "DiszpKrYNg8" } ], - "userGroupAccesses": [], - "programSections": [], - "attributeValues": [], "programStages": [ { - "id": "NpsdDv6kKSe" + "id": "aZdGjigJcJb" } ], - "userAccesses": [] - }, - { - "id": "BFcipDERJng", - "name": "ProgramG", - "shortName": "ProgramG", - "lastUpdated": "2019-01-28T11:23:20.906", - "created": "2019-01-28T11:23:20.906", - "publicAccess": "rw------", - "completeEventsExpiryDays": 0, - "ignoreOverdueEvents": false, - "skipOffline": false, - "minAttributesRequiredToSearch": 1, - "displayFrontPageList": false, - "onlyEnrollOnce": false, - "programType": "WITHOUT_REGISTRATION", - "accessLevel": "OPEN", - "version": 0, - "maxTeiCountToReturn": 0, - "selectIncidentDatesInFuture": false, - "displayIncidentDate": true, - "selectEnrollmentDatesInFuture": false, - "expiryDays": 0, - "useFirstStageDuringRegistration": false, - "categoryCombo": { - "id": "bjDvmb4bfuf" - }, - "lastUpdatedBy": { - "id": "tTgjgobT1oS" + "programType": "WITH_REGISTRATION", + "shortName": "test-program-unaccessible-tet", + "trackedEntityType": { + "id": "Ip8NY4PW7Xm" }, "user": { - "id": "tTgjgobT1oS" + "id": "FIgVWzUCkpw" }, - "programTrackedEntityAttributes": [], - "notificationTemplates": [], - "translations": [], - "organisationUnits": [ - { - "id": "g4w96yEMlzO" + "version": 2 + } + ], + "relationshipTypes": [ + { + "id": "xLmPUYJX8Ks", + "bidirectional": true, + "created": "2019-04-23T09:20:41.834", + "fromConstraint": { + "relationshipEntity": "TRACKED_ENTITY_INSTANCE", + "trackedEntityType": { + "id": "ja8NY4PW7Xm" } - ], - "userGroupAccesses": [], - "programSections": [], - "attributeValues": [], - "programStages": [ - { - "id": "NpsdDv6kKSg" + }, + "fromToName": "Sibling of", + "lastUpdated": "2019-04-23T09:21:18.063", + "name": "TA Sibling", + "sharing": { + "owner": null, + "public": "r-------" + }, + "toConstraint": { + "program": { + "id": "BFcipDERJnf" + }, + "relationshipEntity": "PROGRAM_INSTANCE" + }, + "toFromName": "Sibling of" + }, + { + "id": "TV9oB9LT3sh", + "created": "2020-11-20T09:06:23.348", + "displayFromToName": "Parent Of", + "displayName": "TA Parent to Child", + "fromConstraint": { + "relationshipEntity": "TRACKED_ENTITY_INSTANCE", + "trackedEntityType": { + "id": "ja8NY4PW7Xm" } - ], - "userAccesses": [], + }, + "fromToName": "Parent Of", + "lastUpdated": "2020-11-20T09:06:23.348", + "name": "TA Parent to Child", "sharing": { - "users": {}, - "public": "--------" + "owner": null, + "public": "r-------" + }, + "toConstraint": { + "program": { + "id": "BFcipDERJnf" + }, + "programStage": { + "id": "NpsdDv6kKSO" + }, + "relationshipEntity": "PROGRAM_STAGE_INSTANCE" } - }, + } + ], + "trackedEntityAttributes": [ { - "id": "iS7eutanDry", - "name": "Program with multiple categories", - "code": "multi-program", - "shortName": "Program with multiple categories", - "lastUpdated": "2022-04-22T05:57:59.829", - "created": "2022-04-22T05:57:48.409", - "completeEventsExpiryDays": 0, - "ignoreOverdueEvents": false, - "skipOffline": false, - "minAttributesRequiredToSearch": 1, - "displayFrontPageList": false, - "onlyEnrollOnce": false, - "programType": "WITHOUT_REGISTRATION", - "accessLevel": "OPEN", - "version": 1, - "maxTeiCountToReturn": 0, - "selectIncidentDatesInFuture": false, - "displayIncidentDate": true, - "selectEnrollmentDatesInFuture": false, - "expiryDays": 0, - "useFirstStageDuringRegistration": false, - "categoryCombo": { - "id": "O4VaNks6tta" + "id": "dIVt4l5vIOa", + "aggregationType": "NONE", + "code": "TA_FIRST_NAME_ATT", + "created": "2019-02-05T13:45:36.361", + "displayInListNoProgram": true, + "lastUpdated": "2022-01-07T12:45:47.338", + "name": "TA First name", + "pattern": "", + "sharing": { + "owner": "tTgjgobT1oS", + "public": "rwrwrw--" }, - "programTrackedEntityAttributes": [], - "notificationTemplates": [], - "translations": [], - "organisationUnits": [ - { - "id": "DiszpKrYNg8" - } - ], - "programSections": [], - "attributeValues": [ - { - "attribute": { - "id": "j45AR9cBQKc" - }, - "value": "multi-program-attribute" - } - ], - "programStages": [ - { - "id": "qLZC0lvvxQH" - } - ] + "shortName": "TA First name", + "valueType": "TEXT" }, { - "id": "shPjYNifvMK", - "name": "test-program", - "shortName": "test-program", - "lastUpdated": "2020-05-31T11:41:22.438", - "created": "2020-05-31T09:02:52.718", - "publicAccess": "rwrw----", - "completeEventsExpiryDays": 0, - "ignoreOverdueEvents": false, - "skipOffline": false, - "minAttributesRequiredToSearch": 1, - "displayFrontPageList": false, - "onlyEnrollOnce": false, - "programType": "WITH_REGISTRATION", - "accessLevel": "OPEN", - "version": 2, - "maxTeiCountToReturn": 0, - "selectIncidentDatesInFuture": false, - "displayIncidentDate": true, - "selectEnrollmentDatesInFuture": false, - "expiryDays": 0, - "useFirstStageDuringRegistration": false, - "categoryCombo": { - "id": "bjDvmb4bfuf" + "id": "fRGt4l6yIRb", + "aggregationType": "NONE", + "code": "TA_TEST", + "created": "2019-02-05T13:45:36.361", + "displayInListNoProgram": true, + "lastUpdated": "2022-01-07T12:45:47.338", + "name": "TA Test", + "pattern": "", + "sharing": { + "owner": "tTgjgobT1oS", + "public": "rwrwrw--" }, + "shortName": "TA Test", + "valueType": "TEXT" + }, + { + "id": "numericAttr", + "aggregationType": "NONE", + "created": "2020-05-31T09:00:51.347", + "formName": "numeric-attribute", + "lastUpdated": "2020-05-31T11:41:22.414", "lastUpdatedBy": { - "id": "FIgVWzUCkpw" - }, - "trackedEntityType": { - "id": "ja8NY4PW7Xm" + "id": "tTgjgobT1oS" }, + "name": "numeric-attribute", + "pattern": "", + "shortName": "numeric-attribute", "user": { - "id": "FIgVWzUCkpw" + "id": "tTgjgobT1oS" }, - "programTrackedEntityAttributes": [], - "notificationTemplates": [], - "translations": [], - "organisationUnits": [ - { - "id": "uoNW0E3xXUy" - } - ], - "userGroupAccesses": [], - "programSections": [], - "attributeValues": [], - "programStages": [ - { - "id": "SKNvpoLioON" - } - ], - "userAccesses": [] + "valueType": "INTEGER" }, { - "id": "pcxIanBWlSY", - "name": "test-program-closed", - "shortName": "test-program-closed", - "lastUpdated": "2020-05-31T11:41:22.438", - "created": "2020-05-31T09:02:52.718", - "publicAccess": "rwrw----", - "completeEventsExpiryDays": 0, - "ignoreOverdueEvents": false, - "skipOffline": false, - "minAttributesRequiredToSearch": 1, - "displayFrontPageList": false, - "onlyEnrollOnce": false, - "programType": "WITH_REGISTRATION", - "accessLevel": "CLOSED", - "version": 2, - "maxTeiCountToReturn": 0, - "selectIncidentDatesInFuture": false, - "displayIncidentDate": true, - "selectEnrollmentDatesInFuture": false, - "expiryDays": 0, - "useFirstStageDuringRegistration": false, - "categoryCombo": { - "id": "bjDvmb4bfuf" - }, + "id": "toUpdate000", + "aggregationType": "NONE", + "created": "2020-05-31T09:00:51.347", + "formName": "to-update-tei-attribute", + "lastUpdated": "2020-05-31T11:41:22.414", "lastUpdatedBy": { - "id": "FIgVWzUCkpw" - }, - "trackedEntityType": { - "id": "ja8NY4PW7Xm" + "id": "tTgjgobT1oS" }, + "name": "to-update-tei-attribute", + "pattern": "", + "shortName": "to-update-tei-attribute", "user": { - "id": "FIgVWzUCkpw" + "id": "tTgjgobT1oS" }, - "programTrackedEntityAttributes": [], - "notificationTemplates": [], - "translations": [], - "organisationUnits": [ - { - "id": "uoNW0E3xXUy" - } - ], - "userGroupAccesses": [], - "programSections": [], - "attributeValues": [], - "programStages": [ - { - "id": "ebGXHEqqEMF" - } - ], - "userAccesses": [] + "valueType": "TEXT" }, { - "id": "UWRnoyBjvqi", - "name": "test-program-low-level-org-unit", - "shortName": "test-program-low-level-org-unit", - "lastUpdated": "2020-05-31T11:41:22.438", - "created": "2020-05-31T09:02:52.718", - "publicAccess": "rwrw----", - "completeEventsExpiryDays": 0, - "ignoreOverdueEvents": false, - "skipOffline": false, - "minAttributesRequiredToSearch": 1, - "displayFrontPageList": false, - "onlyEnrollOnce": false, - "programType": "WITH_REGISTRATION", - "accessLevel": "CLOSED", - "version": 2, - "maxTeiCountToReturn": 0, - "selectIncidentDatesInFuture": false, - "displayIncidentDate": true, - "selectEnrollmentDatesInFuture": false, - "expiryDays": 0, - "useFirstStageDuringRegistration": false, - "categoryCombo": { - "id": "bjDvmb4bfuf" - }, + "id": "toDelete000", + "aggregationType": "NONE", + "created": "2020-05-31T09:00:51.347", + "formName": "to-delete-tei-attribute", + "lastUpdated": "2020-05-31T11:41:22.414", "lastUpdatedBy": { - "id": "FIgVWzUCkpw" - }, - "trackedEntityType": { - "id": "ja8NY4PW7Xm" + "id": "tTgjgobT1oS" }, + "name": "to-delete-tei-attribute", + "pattern": "", + "shortName": "to-delete-tei-attribute", "user": { - "id": "FIgVWzUCkpw" + "id": "tTgjgobT1oS" }, - "programTrackedEntityAttributes": [], - "notificationTemplates": [], - "translations": [], - "organisationUnits": [ - { - "id": "tSsGrtfRzjY" - } - ], - "userGroupAccesses": [], - "programSections": [], - "attributeValues": [], - "programStages": [ - { - "id": "ZfzckZBvDkA" - } - ], - "userAccesses": [] + "valueType": "TEXT" }, { - "id": "YlUmbgnKWkd", - "name": "test-program-low-level-org-unit", - "shortName": "test-program-low-level-org-unit", - "lastUpdated": "2020-05-31T11:41:22.438", - "created": "2020-05-31T09:02:52.718", - "publicAccess": "rwrw----", - "completeEventsExpiryDays": 0, - "ignoreOverdueEvents": false, - "skipOffline": false, - "minAttributesRequiredToSearch": 1, - "displayFrontPageList": false, - "onlyEnrollOnce": false, - "programType": "WITH_REGISTRATION", - "accessLevel": "CLOSED", - "version": 2, - "maxTeiCountToReturn": 0, - "selectIncidentDatesInFuture": false, - "displayIncidentDate": true, - "selectEnrollmentDatesInFuture": false, - "expiryDays": 0, - "useFirstStageDuringRegistration": false, - "categoryCombo": { - "id": "bjDvmb4bfuf" - }, + "id": "notUpdated0", + "aggregationType": "NONE", + "created": "2020-05-31T09:00:51.347", + "formName": "not-updated-tei-attribute", + "lastUpdated": "2020-05-31T11:41:22.414", "lastUpdatedBy": { - "id": "FIgVWzUCkpw" - }, - "trackedEntityType": { - "id": "ja8NY4PW7Xm" + "id": "tTgjgobT1oS" }, + "name": "not-updated-tei-attribute", + "pattern": "", + "shortName": "not-updated-tei-attribute", "user": { - "id": "FIgVWzUCkpw" + "id": "tTgjgobT1oS" }, - "programTrackedEntityAttributes": [], - "notificationTemplates": [], - "translations": [], - "organisationUnits": [ - { - "id": "RojfDTBhoGC" - } - ], - "userGroupAccesses": [], - "programSections": [], - "attributeValues": [], - "programStages": [ - { - "id": "uLQthmAPTPq" - } - ], - "userAccesses": [] - }, + "valueType": "TEXT" + } + ], + "trackedEntityTypes": [ { - "id": "SeeUNWLQmZk", - "name": "test-program-low-level-org-unit", - "shortName": "test-program-low-level-org-unit", - "lastUpdated": "2020-05-31T11:41:22.438", - "created": "2020-05-31T09:02:52.718", - "publicAccess": "rwrw----", - "completeEventsExpiryDays": 0, - "ignoreOverdueEvents": false, - "skipOffline": false, - "minAttributesRequiredToSearch": 1, - "displayFrontPageList": false, - "onlyEnrollOnce": false, - "programType": "WITH_REGISTRATION", - "accessLevel": "CLOSED", - "version": 2, - "maxTeiCountToReturn": 0, - "selectIncidentDatesInFuture": false, - "displayIncidentDate": true, - "selectEnrollmentDatesInFuture": false, - "expiryDays": 0, - "useFirstStageDuringRegistration": false, - "categoryCombo": { - "id": "bjDvmb4bfuf" - }, + "id": "ja8NY4PW7Xm", + "allowAuditLog": true, + "created": "2020-05-31T08:59:52.758", + "description": "person", + "featureType": "NONE", + "lastUpdated": "2020-05-31T11:41:22.419", "lastUpdatedBy": { - "id": "FIgVWzUCkpw" - }, - "trackedEntityType": { - "id": "ja8NY4PW7Xm" + "id": "tTgjgobT1oS" }, - "user": { - "id": "FIgVWzUCkpw" + "maxTeiCountToReturn": 0, + "minAttributesRequiredToSearch": 1, + "name": "Person", + "sharing": { + "public": "rwrw----" }, - "programTrackedEntityAttributes": [], - "notificationTemplates": [], - "translations": [], - "organisationUnits": [ - { - "id": "DiszpKrYNg8" - } - ], - "userGroupAccesses": [], - "programSections": [], - "attributeValues": [], - "programStages": [ + "trackedEntityTypeAttributes": [ { - "id": "GmxBvezOlGA" + "displayInList": true, + "displayName": "Person Given name", + "displayShortName": "null Given name", + "id": "hKZ9AJpnVcG", + "name": "Person Given name", + "searchable": true, + "trackedEntityAttribute": { + "id": "numericAttr" + }, + "trackedEntityType": { + "id": "ja8NY4PW7Xm" + }, + "valueType": "TEXT" } ], - "userAccesses": [] + "user": { + "id": "tTgjgobT1oS" + } }, { - "id": "sLngICFQjvH", - "name": "test-program-low-level-org-unit", - "shortName": "test-program-low-level-org-unit", - "lastUpdated": "2020-05-31T11:41:22.438", - "created": "2020-05-31T09:02:52.718", - "publicAccess": "rwrw----", - "completeEventsExpiryDays": 0, - "ignoreOverdueEvents": false, - "skipOffline": false, - "minAttributesRequiredToSearch": 1, - "displayFrontPageList": false, - "onlyEnrollOnce": false, - "programType": "WITH_REGISTRATION", - "accessLevel": "OPEN", - "version": 2, - "maxTeiCountToReturn": 0, - "selectIncidentDatesInFuture": false, - "displayIncidentDate": true, - "selectEnrollmentDatesInFuture": false, - "expiryDays": 0, - "useFirstStageDuringRegistration": false, - "categoryCombo": { - "id": "bjDvmb4bfuf" - }, + "id": "Ip8NY4PW7Xm", + "created": "2020-05-31T08:59:52.758", + "description": "person", + "featureType": "NONE", + "lastUpdated": "2020-05-31T11:41:22.419", "lastUpdatedBy": { - "id": "FIgVWzUCkpw" + "id": "tTgjgobT1oS" }, - "trackedEntityType": { - "id": "ja8NY4PW7Xm" + "maxTeiCountToReturn": 0, + "minAttributesRequiredToSearch": 1, + "name": "Inaccessible person", + "sharing": { + "public": "--------" }, "user": { - "id": "FIgVWzUCkpw" - }, - "programTrackedEntityAttributes": [], - "notificationTemplates": [], - "translations": [], - "organisationUnits": [ - { - "id": "lbDXJBlvtZe" - } - ], - "userGroupAccesses": [], - "programSections": [], - "attributeValues": [], - "programStages": [ - { - "id": "zydGjigJcJb" - } - ], - "userAccesses": [] - }, + "id": "tTgjgobT1oS" + } + } + ], + "userGroups": [ { - "id": "TsngICFQjvH", - "name": "test-program-unaccessible-tet", - "shortName": "test-program-unaccessible-tet", - "lastUpdated": "2020-05-31T11:41:22.438", - "created": "2020-05-31T09:02:52.718", - "publicAccess": "rwrw----", - "completeEventsExpiryDays": 0, - "ignoreOverdueEvents": false, - "skipOffline": false, - "minAttributesRequiredToSearch": 1, - "displayFrontPageList": false, - "onlyEnrollOnce": false, - "programType": "WITH_REGISTRATION", - "accessLevel": "OPEN", - "version": 2, - "maxTeiCountToReturn": 0, - "selectIncidentDatesInFuture": false, - "displayIncidentDate": true, - "selectEnrollmentDatesInFuture": false, - "expiryDays": 0, - "useFirstStageDuringRegistration": false, - "categoryCombo": { - "id": "bjDvmb4bfuf" - }, + "id": "xfHoY6IZSWI", + "created": "2020-05-31T09:03:15.823", + "lastUpdated": "2020-05-31T11:41:22.375", "lastUpdatedBy": { - "id": "FIgVWzUCkpw" - }, - "trackedEntityType": { - "id": "Ip8NY4PW7Xm" + "id": "tTgjgobT1oS" }, + "name": "test-user-group", "user": { - "id": "FIgVWzUCkpw" + "id": "tTgjgobT1oS" }, - "programTrackedEntityAttributes": [], - "notificationTemplates": [], - "translations": [], - "organisationUnits": [ - { - "id": "DiszpKrYNg8" - } - ], - "userGroupAccesses": [], - "programSections": [], - "attributeValues": [], - "programStages": [ + "users": [ { - "id": "aZdGjigJcJb" + "id": "lPaILkLkgOM" } - ], - "userAccesses": [] + ] } ], "userRoles": [ { - "code": "tracker Superuser", - "created": "2020-05-31T08:55:28.141", - "lastUpdated": "2020-05-31T11:41:22.347", - "name": "tracker Superuser", "id": "nJ4Ml8ads4M", - "publicAccess": "--------", - "description": "tracker Superuser", - "lastUpdatedBy": { - "id": "tTgjgobT1oS" - }, - "user": { - "id": "tTgjgobT1oS" - }, - "userGroupAccesses": [], "authorities": [ "F_TRACKED_ENTITY_INSTANCE_SEARCH_IN_ALL_ORGUNITS", "ALL", @@ -2838,248 +2061,280 @@ "F_APPROVE_DATA_LOWER_LEVELS", "F_UNCOMPLETE_EVENT" ], - "translations": [], - "userAccesses": [] + "code": "tracker Superuser", + "created": "2020-05-31T08:55:28.141", + "description": "tracker Superuser", + "lastUpdated": "2020-05-31T11:41:22.347", + "lastUpdatedBy": { + "id": "tTgjgobT1oS" + }, + "name": "tracker Superuser", + "user": { + "id": "tTgjgobT1oS" + } }, { "id": "UbhT3bXWUyb", - "name": "Basic", + "authorities": [ + "M_dhis-web-capture" + ], "created": "2020-04-17T14:32:31.826", "lastUpdated": "2020-04-17T14:32:31.826", - "sharing": { - "public": "rw------", - "external": false - }, "lastUpdatedBy": { "id": "tTgjgobT1oS" }, + "name": "Basic", + "sharing": { + "public": "rw------" + }, "user": { "id": "tTgjgobT1oS" - }, - "authorities": [ - "M_dhis-web-capture" - ], - "translations": [] + } } ], - "trackedEntityAttributes": [ - { - "code": "TA_FIRST_NAME_ATT", - "lastUpdated": "2022-01-07T12:45:47.338", - "id": "dIVt4l5vIOa", - "created": "2019-02-05T13:45:36.361", - "name": "TA First name", - "shortName": "TA First name", - "aggregationType": "NONE", - "displayInListNoProgram": true, - "pattern": "", - "skipSynchronization": false, - "generated": false, - "displayOnVisitSchedule": false, - "valueType": "TEXT", - "confidential": false, - "orgunitScope": false, - "unique": false, - "inherit": false, - "sharing": { - "owner": "tTgjgobT1oS", - "userGroups": {}, - "external": false, - "public": "rwrwrw--", - "users": {} - }, - "translations": [], - "attributeValues": [], - "legendSets": [] - }, + "users": [ { - "code": "TA_TEST", - "lastUpdated": "2022-01-07T12:45:47.338", - "id": "fRGt4l6yIRb", - "created": "2019-02-05T13:45:36.361", - "name": "TA Test", - "shortName": "TA Test", - "aggregationType": "NONE", - "displayInListNoProgram": true, - "pattern": "", - "skipSynchronization": false, - "generated": false, - "displayOnVisitSchedule": false, - "valueType": "TEXT", - "confidential": false, - "orgunitScope": false, - "unique": false, - "inherit": false, - "sharing": { - "owner": "tTgjgobT1oS", - "userGroups": {}, - "external": false, - "public": "rwrwrw--", - "users": {} + "id": "lPaILkLkgOM", + "access": { + "delete": true, + "externalize": true, + "manage": true, + "read": true, + "update": true, + "write": true }, - "translations": [], - "attributeValues": [], - "legendSets": [] - }, - { - "lastUpdated": "2020-05-31T11:41:22.414", - "id": "numericAttr", - "created": "2020-05-31T09:00:51.347", - "name": "numeric-attribute", - "shortName": "numeric-attribute", - "aggregationType": "NONE", - "displayInListNoProgram": false, - "publicAccess": "rw------", - "pattern": "", - "skipSynchronization": false, - "generated": false, - "displayOnVisitSchedule": false, - "valueType": "INTEGER", - "formName": "numeric-attribute", - "orgunitScope": false, - "confidential": false, - "unique": false, - "inherit": false, - "lastUpdatedBy": { - "id": "tTgjgobT1oS" - }, - "user": { - "id": "tTgjgobT1oS" - }, - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [], - "legendSets": [] + "created": "2020-05-31T08:57:59.048", + "dataViewOrganisationUnits": [ + { + "id": "h4w96yEMlzO" + } + ], + "displayName": "Zubair Asghar", + "email": "zubair@dhis2.org", + "firstName": "Zubair", + "lastUpdated": "2020-05-31T11:41:22.356", + "name": "Zubair Asghar", + "organisationUnits": [ + { + "id": "h4w96yEMlzO" + } + ], + "password": "Test123###...", + "passwordLastUpdated": "2020-05-31T08:57:59.060", + "phoneNumber": "+4740332255", + "surname": "Asghar", + "userRoles": [ + { + "id": "nJ4Ml8ads4M" + } + ], + "username": "testuser" }, { - "lastUpdated": "2020-05-31T11:41:22.414", - "id": "toUpdate000", - "created": "2020-05-31T09:00:51.347", - "name": "to-update-tei-attribute", - "shortName": "to-update-tei-attribute", - "aggregationType": "NONE", - "displayInListNoProgram": false, - "publicAccess": "rw------", - "pattern": "", - "skipSynchronization": false, - "generated": false, - "displayOnVisitSchedule": false, - "valueType": "TEXT", - "formName": "to-update-tei-attribute", - "orgunitScope": false, - "confidential": false, - "unique": false, - "inherit": false, - "lastUpdatedBy": { - "id": "tTgjgobT1oS" - }, - "user": { - "id": "tTgjgobT1oS" + "id": "tTgjgobT1oS", + "access": { + "delete": true, + "externalize": true, + "manage": true, + "read": true, + "update": true, + "write": true }, - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [], - "legendSets": [] + "code": "tracker admin", + "created": "2020-05-31T08:55:27.984", + "displayName": "tracker admin", + "firstName": "tracker admin", + "lastLogin": "2020-05-31T08:55:38.571", + "lastUpdated": "2020-05-31T11:41:22.356", + "name": "tracker admin", + "organisationUnits": [ + { + "id": "h4w96yEMlzO" + }, + { + "id": "DiszpKrYNg8" + }, + { + "id": "g4w96yEMlzO" + } + ], + "password": "Test123###...", + "passwordLastUpdated": "2020-05-31T08:55:28.232", + "surname": "tracker admin", + "teiSearchOrganisationUnits": [ + { + "id": "h4w96yEMlzO" + }, + { + "id": "DiszpKrYNg8" + }, + { + "id": "g4w96yEMlzO" + } + ], + "userRoles": [ + { + "id": "nJ4Ml8ads4M" + } + ], + "username": "trackeradmin" }, { - "lastUpdated": "2020-05-31T11:41:22.414", - "id": "toDelete000", - "created": "2020-05-31T09:00:51.347", - "name": "to-delete-tei-attribute", - "shortName": "to-delete-tei-attribute", - "aggregationType": "NONE", - "displayInListNoProgram": false, - "publicAccess": "rw------", - "pattern": "", - "skipSynchronization": false, - "generated": false, - "displayOnVisitSchedule": false, - "valueType": "TEXT", - "formName": "to-delete-tei-attribute", - "orgunitScope": false, - "confidential": false, - "unique": false, - "inherit": false, - "lastUpdatedBy": { - "id": "tTgjgobT1oS" - }, - "user": { - "id": "tTgjgobT1oS" + "id": "o1HMTIzBGo7", + "access": { + "delete": true, + "externalize": true, + "manage": true, + "read": true, + "update": true, + "write": true }, - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [], - "legendSets": [] + "created": "2020-05-31T08:57:59.048", + "dataViewOrganisationUnits": [ + { + "id": "DiszpKrYNg8" + } + ], + "displayName": "foofi", + "email": "foofi@dhis2.org", + "firstName": "foofi", + "name": "foofi", + "organisationUnits": [ + { + "id": "DiszpKrYNg8" + } + ], + "password": "Test123###...", + "passwordLastUpdated": "2020-05-31T08:57:59.060", + "phoneNumber": "+4740332255", + "surname": "foofi", + "userRoles": [ + { + "id": "UbhT3bXWUyb" + } + ], + "username": "foofi" }, { - "lastUpdated": "2020-05-31T11:41:22.414", - "id": "notUpdated0", - "created": "2020-05-31T09:00:51.347", - "name": "not-updated-tei-attribute", - "shortName": "not-updated-tei-attribute", - "aggregationType": "NONE", - "displayInListNoProgram": false, - "publicAccess": "rw------", - "pattern": "", - "skipSynchronization": false, - "generated": false, - "displayOnVisitSchedule": false, - "valueType": "TEXT", - "formName": "not-updated-tei-attribute", - "orgunitScope": false, - "confidential": false, - "unique": false, - "inherit": false, - "lastUpdatedBy": { - "id": "tTgjgobT1oS" - }, - "user": { - "id": "tTgjgobT1oS" + "id": "CYVgFNKCaUS", + "access": { + "delete": true, + "externalize": true, + "manage": true, + "read": true, + "update": true, + "write": true }, - "translations": [], - "userGroupAccesses": [], - "attributeValues": [], - "userAccesses": [], - "legendSets": [] - } - ], - "programNotificationTemplates": [ + "created": "2020-05-31T08:57:59.048", + "dataViewOrganisationUnits": [ + { + "id": "DiszpKrYNg8" + } + ], + "displayName": "aaaa", + "email": "aaaa@dhis2.org", + "firstName": "aaaa", + "name": "aaaa", + "organisationUnits": [ + { + "id": "DiszpKrYNg8" + } + ], + "password": "Test123###...", + "passwordLastUpdated": "2020-05-31T08:57:59.060", + "phoneNumber": "+4740332255", + "surname": "aaaa", + "userRoles": [ + { + "id": "UbhT3bXWUyb" + } + ], + "username": "aaaa" + }, { - "lastUpdated": "2020-05-31T11:35:18.355", - "id": "FdIeUL4gyoB", - "href": "http://localhost:8081/api/programNotificationTemplates/FdIeUL4gyoB", - "created": "2020-05-31T11:35:18.355", - "name": "test-program-stage-notification", - "displayName": "test-program-stage-notification", - "externalAccess": false, - "notificationTrigger": "PROGRAM_RULE", - "subjectTemplate": "test subject", - "notificationRecipient": "USER_GROUP", - "favorite": false, - "messageTemplate": "test message ", + "id": "FIgVWzUCkpw", "access": { + "delete": true, + "externalize": true, + "manage": true, "read": true, "update": true, - "externalize": true, - "delete": true, - "write": true, - "manage": true - }, - "lastUpdatedBy": { - "id": "tTgjgobT1oS" + "write": true }, - "recipientUserGroup": { - "id": "xfHoY6IZSWI" + "created": "2020-05-31T08:57:59.048", + "dataViewOrganisationUnits": [ + { + "id": "uoNW0E3xXUy" + } + ], + "displayName": "child level 2", + "email": "aaaa@dhis2.org", + "firstName": "child level 2", + "name": "child level 2", + "organisationUnits": [ + { + "id": "uoNW0E3xXUy" + } + ], + "password": "Test123###...", + "passwordLastUpdated": "2020-05-31T08:57:59.060", + "phoneNumber": "+4740332255", + "surname": "child level 2", + "teiSearchOrganisationUnits": [ + { + "id": "h4w96yEMlzO" + } + ], + "userRoles": [ + { + "id": "UbhT3bXWUyb" + } + ], + "username": "childlevel2" + }, + { + "id": "nIidJVYpQQK", + "access": { + "delete": true, + "externalize": true, + "manage": true, + "read": true, + "update": true, + "write": true }, - "favorites": [], - "translations": [], - "userGroupAccesses": [], - "deliveryChannels": [], - "attributeValues": [], - "userAccesses": [] + "created": "2020-05-31T08:57:59.048", + "dataViewOrganisationUnits": [ + { + "id": "uoNW0E3xXUy" + } + ], + "displayName": "child level 2", + "email": "aaaa@dhis2.org", + "firstName": "child level 3", + "name": "child level 2", + "organisationUnits": [ + { + "id": "uoNW0E3xXUy" + }, + { + "id": "lbDXJBlvtZe" + } + ], + "password": "Test123###...", + "passwordLastUpdated": "2020-05-31T08:57:59.060", + "phoneNumber": "+4740332255", + "surname": "child level 3", + "teiSearchOrganisationUnits": [ + { + "id": "DiszpKrYNg8" + } + ], + "userRoles": [ + { + "id": "UbhT3bXWUyb" + } + ], + "username": "childlevel3" } ] } diff --git a/dhis-2/dhis-support/dhis-support-test/src/test/java/org/hisp/test/utils/RelationshipUtilsTest.java b/dhis-2/dhis-support/dhis-support-test/src/test/java/org/hisp/test/utils/RelationshipUtilsTest.java index a2eaa33e7d59..d72ca0f02562 100644 --- a/dhis-2/dhis-support/dhis-support-test/src/test/java/org/hisp/test/utils/RelationshipUtilsTest.java +++ b/dhis-2/dhis-support/dhis-support-test/src/test/java/org/hisp/test/utils/RelationshipUtilsTest.java @@ -29,6 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import org.hisp.dhis.common.UID; import org.hisp.dhis.program.Enrollment; import org.hisp.dhis.program.Event; import org.hisp.dhis.relationship.Relationship; @@ -40,13 +41,13 @@ import org.junit.jupiter.api.Test; class RelationshipUtilsTest { - private static final String TE_A_UID = "TE_A_UID"; + private static final UID TE_A_UID = UID.generate(); - private static final String TE_B_UID = "TE_B_UID"; + private static final UID TE_B_UID = UID.generate(); - private static final String ENROLLMENT_UID = "ENROLLMENT_UID"; + private static final UID ENROLLMENT_UID = UID.generate(); - private static final String EVENT_UID = "EVENT_UID"; + private static final UID EVENT_UID = UID.generate(); private static final String RELATIONSHIP_TYPE_UID = "RELATIONSHIP_TYPE_UID"; @@ -61,13 +62,13 @@ class RelationshipUtilsTest { @BeforeEach void setup() { teA = new TrackedEntity(); - teA.setUid(TE_A_UID); + teA.setUid(TE_A_UID.getValue()); teB = new TrackedEntity(); - teB.setUid(TE_B_UID); + teB.setUid(TE_B_UID.getValue()); enrollmentA = new Enrollment(); - enrollmentA.setUid(ENROLLMENT_UID); + enrollmentA.setUid(ENROLLMENT_UID.getValue()); eventA = new Event(); - eventA.setUid(EVENT_UID); + eventA.setUid(EVENT_UID.getValue()); relationshipType = new RelationshipType(); relationshipType.setUid(RELATIONSHIP_TYPE_UID); } diff --git a/dhis-2/dhis-test-e2e/pom.xml b/dhis-2/dhis-test-e2e/pom.xml index 048d3caa1982..e9c118efe62a 100644 --- a/dhis-2/dhis-test-e2e/pom.xml +++ b/dhis-2/dhis-test-e2e/pom.xml @@ -11,11 +11,11 @@ UTF-8 2.43.0 3.13.0 - 3.5.1 + 3.5.2 1.3.0 5.11.3 2.11.0 - 2.24.1 + 2.24.2 5.5.0 2.18.1 33.3.1-jre @@ -29,13 +29,13 @@ 2.9.0 1.5.3 4.2.2 - 1.18.34 + 1.18.36 2.29.0 - 4.25.0 + 4.27.0 2.0.16 4.5.14 5.4.1 - 2.17.0 + 2.18.0 diff --git a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/merge/CategoryOptionMergeTest.java b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/merge/CategoryOptionMergeTest.java new file mode 100644 index 000000000000..db7ad8e0972b --- /dev/null +++ b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/merge/CategoryOptionMergeTest.java @@ -0,0 +1,467 @@ +/* + * Copyright (c) 2004-2024, 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.merge; + +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.hasSize; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import io.restassured.response.ValidatableResponse; +import org.hisp.dhis.ApiTest; +import org.hisp.dhis.test.e2e.actions.LoginActions; +import org.hisp.dhis.test.e2e.actions.RestApiActions; +import org.hisp.dhis.test.e2e.actions.UserActions; +import org.hisp.dhis.test.e2e.actions.metadata.MetadataActions; +import org.hisp.dhis.test.e2e.dto.ApiResponse; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class CategoryOptionMergeTest extends ApiTest { + + private RestApiActions categoryOptionApiActions; + private MetadataActions metadataActions; + private UserActions userActions; + private LoginActions loginActions; + private final String sourceUid1 = "CatOptUid90"; + private final String sourceUid2 = "CatOptUid91"; + private final String targetUid = "CatOptUid92"; + + @BeforeAll + public void before() { + userActions = new UserActions(); + loginActions = new LoginActions(); + categoryOptionApiActions = new RestApiActions("categoryOptions"); + metadataActions = new MetadataActions(); + loginActions.loginAsSuperUser(); + + // add user with required merge auth + userActions.addUserFull( + "user", + "auth", + "userWithMergeAuth", + "Test1234!", + "F_CATEGORY_OPTION_MERGE", + "F_CATEGORY_OPTION_DELETE", + "F_CATEGORY_OPTION_PUBLIC_ADD"); + } + + @BeforeEach + public void setup() { + loginActions.loginAsSuperUser(); + setupMetadata(); + } + + @Test + @DisplayName( + "Valid CategoryOption merge completes successfully with all source CategoryOption refs replaced with target CategoryOption") + void validDataElementMergeTest() { + // given + loginActions.loginAsUser("userWithMergeAuth", "Test1234!"); + + // confirm state before merge + ValidatableResponse preMergeState = + categoryOptionApiActions.get(targetUid).validateStatus(200).validate(); + + preMergeState + .body("organisationUnits", hasSize(equalTo(1))) + .body("organisationUnits", hasItem(hasEntry("id", "OrgUnitUid2"))) + .body("categories", hasSize(equalTo(1))) + .body("categories", hasItem(hasEntry("id", "CategoUid92"))) + .body("categoryOptionCombos", hasSize(equalTo(1))) + .body("categoryOptionCombos", hasItem(hasEntry("id", "CatOptCom92"))) + .body("categoryOptionGroups", hasSize(equalTo(1))) + .body("categoryOptionGroups", hasItem(hasEntry("id", "CatOptGrp02"))); + + // when a category option request is submitted, deleting sources + ApiResponse response = + categoryOptionApiActions.post("merge", getMergeBody()).validateStatus(200); + + // then a success response received, sources are deleted & source references were merged + response + .validate() + .statusCode(200) + .body("httpStatus", equalTo("OK")) + .body("response.mergeReport.message", equalTo("CategoryOption merge complete")) + .body("response.mergeReport.mergeErrors", empty()) + .body("response.mergeReport.mergeType", equalTo("CategoryOption")) + .body("response.mergeReport.sourcesDeleted", hasItems(sourceUid1, sourceUid2)); + + categoryOptionApiActions.get(sourceUid1).validateStatus(404); + categoryOptionApiActions.get(sourceUid2).validateStatus(404); + ValidatableResponse postMergeState = + categoryOptionApiActions.get(targetUid).validateStatus(200).validate(); + + postMergeState + .body( + "organisationUnits", + hasItems( + hasEntry("id", "OrgUnitUid0"), + hasEntry("id", "OrgUnitUid1"), + hasEntry("id", "OrgUnitUid2"))) + .body( + "categories", + hasItems( + hasEntry("id", "CategoUid90"), + hasEntry("id", "CategoUid91"), + hasEntry("id", "CategoUid92"))) + .body( + "categoryOptionCombos", + hasItems( + hasEntry("id", "CatOptCom90"), + hasEntry("id", "CatOptCom91"), + hasEntry("id", "CatOptCom92"))) + .body( + "categoryOptionGroups", + hasItems( + hasEntry("id", "CatOptGrp01"), + hasEntry("id", "CatOptGrp00"), + hasEntry("id", "CatOptGrp02"))); + } + + private void setupMetadata() { + metadataActions.post(metadata()).validateStatus(200); + } + + @Test + @DisplayName("CategoryOption merge fails when user has not got the required authority") + void testDataElementMergeNoRequiredAuth() { + userActions.addUserFull("basic", "User", "basicUser", "Test1234!", "NO_AUTH"); + loginActions.loginAsUser("basicUser", "Test1234!"); + + // when + ApiResponse response = + categoryOptionApiActions.post("merge", getMergeBody()).validateStatus(403); + + // then + response + .validate() + .statusCode(403) + .body("httpStatus", equalTo("Forbidden")) + .body("status", equalTo("ERROR")) + .body( + "message", + equalTo("Access is denied, requires one Authority from [F_CATEGORY_OPTION_MERGE]")); + } + + private JsonObject getMergeBody() { + JsonObject json = new JsonObject(); + JsonArray sources = new JsonArray(); + sources.add(sourceUid1); + sources.add(sourceUid2); + json.add("sources", sources); + json.addProperty("target", targetUid); + json.addProperty("deleteSources", true); + return json; + } + + private String metadata() { + return """ + { + "categoryOptions": [ + { + "id": "CatOptUid90", + "name": "cat opt 0", + "shortName": "cat opt 0", + "organisationUnits": [ + { + "id": "OrgUnitUid0" + } + ] + }, + { + "id": "CatOptUid91", + "name": "cat opt 1", + "shortName": "cat opt 1", + "organisationUnits": [ + { + "id": "OrgUnitUid1" + } + ] + }, + { + "id": "CatOptUid92", + "name": "cat opt 2", + "shortName": "cat opt 2", + "organisationUnits": [ + { + "id": "OrgUnitUid2" + } + ] + }, + { + "id": "CatOptUid93", + "name": "cat opt 3", + "shortName": "cat opt 3", + "organisationUnits": [ + { + "id": "OrgUnitUid3" + } + ] + } + ], + "categories": [ + { + "id": "CategoUid90", + "name": "cat 0", + "shortName": "cat 0", + "dataDimensionType": "DISAGGREGATION", + "categoryOptions": [ + { + "id": "CatOptUid90" + } + ] + }, + { + "id": "CategoUid91", + "name": "cat 1", + "shortName": "cat 1", + "dataDimensionType": "DISAGGREGATION", + "categoryOptions": [ + { + "id": "CatOptUid91" + } + ] + }, + { + "id": "CategoUid92", + "name": "cat 2", + "shortName": "cat 2", + "dataDimensionType": "DISAGGREGATION", + "categoryOptions": [ + { + "id": "CatOptUid92" + } + ] + }, + { + "id": "CategoUid93", + "name": "cat 3", + "shortName": "cat 3", + "dataDimensionType": "DISAGGREGATION", + "categoryOptions": [ + { + "id": "CatOptUid93" + } + ] + } + ], + "categoryOptionCombos": [ + { + "id": "CatOptCom90", + "name": "cat option combo 0", + "categoryCombo": { + "id": "CatComUid90" + }, + "categoryOptions": [ + { + "id": "CatOptUid90" + } + ] + }, + { + "id": "CatOptCom91", + "name": "cat option combo 1", + "categoryCombo": { + "id": "CatComUid91" + }, + "categoryOptions": [ + { + "id": "CatOptUid91" + } + ] + }, + { + "id": "CatOptCom92", + "name": "cat option combo 2", + "categoryCombo": { + "id": "CatComUid92" + }, + "categoryOptions": [ + { + "id": "CatOptUid92" + } + ] + }, + { + "id": "CatOptCom93", + "name": "cat option combo 3", + "categoryCombo": { + "id": "CatComUid93" + }, + "categoryOptions": [ + { + "id": "CatOptUid93" + } + ] + } + ], + "organisationUnits": [ + { + "id": "OrgUnitUid0", + "name": "org 0", + "shortName": "org 0", + "openingDate": "2023-06-15" + }, + { + "id": "OrgUnitUid1", + "name": "org 1", + "shortName": "org 1", + "openingDate": "2024-06-15" + }, + { + "id": "OrgUnitUid2", + "name": "org 2", + "shortName": "org 2", + "openingDate": "2023-09-15" + }, + { + "id": "OrgUnitUid3", + "name": "org 3", + "shortName": "org 3", + "openingDate": "2023-06-25" + } + ], + "categoryOptionGroups": [ + { + "id": "CatOptGrp00", + "name": "cog 0", + "shortName": "cog 0", + "dataDimensionType": "DISAGGREGATION", + "categoryOptions": [ + { + "id": "CatOptUid90" + } + ] + }, + { + "id": "CatOptGrp01", + "name": "cog 1", + "shortName": "cog 1", + "dataDimensionType": "DISAGGREGATION", + "categoryOptions": [ + { + "id": "CatOptUid91" + } + ] + }, + { + "id": "CatOptGrp02", + "name": "cog 2", + "shortName": "cog 2", + "dataDimensionType": "DISAGGREGATION", + "categoryOptions": [ + { + "id": "CatOptUid92" + } + ] + }, + { + "id": "CatOptGrp03", + "name": "cog 3", + "shortName": "cog 3", + "dataDimensionType": "DISAGGREGATION", + "categoryOptions": [ + { + "id": "CatOptUid93" + } + ] + } + ], + "categoryCombos": [ + { + "id": "CatComUid90", + "name": "cat combo 0", + "dataDimensionType": "DISAGGREGATION", + "categories": [ + { + "id": "CategoUid90" + } + ], + "categoryOptionCombos": [ + { + "id": "CatOptCom90" + } + ] + }, + { + "id": "CatComUid91", + "name": "cat combo 1", + "dataDimensionType": "DISAGGREGATION", + "categories": [ + { + "id": "CategoUid91" + } + ], + "categoryOptionCombos": [ + { + "id": "CatOptCom91" + } + ] + }, + { + "id": "CatComUid92", + "name": "cat combo 2", + "dataDimensionType": "DISAGGREGATION", + "categories": [ + { + "id": "CategoUid92" + } + ], + "categoryOptionCombos": [ + { + "id": "CatOptCom92" + } + ] + }, + { + "id": "CatComUid93", + "name": "cat combo 3", + "dataDimensionType": "DISAGGREGATION", + "categories": [ + { + "id": "CategoUid93" + } + ], + "categoryOptionCombos": [ + { + "id": "CatOptCom93" + } + ] + } + ] + } + """; + } +} diff --git a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/merge/DataElementMergeTest.java b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/merge/DataElementMergeTest.java index 6e43ee1f3bad..c66af88bf65f 100644 --- a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/merge/DataElementMergeTest.java +++ b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/merge/DataElementMergeTest.java @@ -111,9 +111,9 @@ void validDataElementMergeTest() { .validate() .statusCode(200) .body("httpStatus", equalTo("OK")) - .body("response.mergeReport.message", equalTo("DATA_ELEMENT merge complete")) + .body("response.mergeReport.message", equalTo("DataElement merge complete")) .body("response.mergeReport.mergeErrors", empty()) - .body("response.mergeReport.mergeType", equalTo("DATA_ELEMENT")) + .body("response.mergeReport.mergeType", equalTo("DataElement")) .body("response.mergeReport.sourcesDeleted", hasItems(sourceUid1, sourceUid2)); // and all the following source data element references have been handled appropriately @@ -125,7 +125,7 @@ void validDataElementMergeTest() { @Test @Disabled( - "setup started failing on GitHub only 409 response, reason not know, e2e all passing locally") + "setup started failing on GitHub only 409 response, reason not known, e2e all passing locally") @DisplayName("DataElement merge fails when min max DE DB unique key constraint met") void dbConstraintMinMaxTest() { // given @@ -231,13 +231,13 @@ void invalidDataElementMergeValueType() { .validate() .statusCode(409) .body("httpStatus", equalTo("Conflict")) - .body("response.mergeReport.message", equalTo("DATA_ELEMENT merge has errors")) + .body("response.mergeReport.message", equalTo("DataElement merge has errors")) .body( "response.mergeReport.mergeErrors.message", allOf( hasItem( "All source ValueTypes must match target ValueType: `TEXT`. Other ValueTypes found: `[NUMBER]`"))) - .body("response.mergeReport.mergeErrors.errorCode", allOf(hasItem("E1554"))); + .body("response.mergeReport.mergeErrors.errorCode", allOf(hasItem("E1550"))); } @Test @@ -262,13 +262,13 @@ void invalidDataElementMergeDomainType() { .validate() .statusCode(409) .body("httpStatus", equalTo("Conflict")) - .body("response.mergeReport.message", equalTo("DATA_ELEMENT merge has errors")) + .body("response.mergeReport.message", equalTo("DataElement merge has errors")) .body( "response.mergeReport.mergeErrors.message", allOf( hasItem( "All source DataElementDomains must match target DataElementDomain: `AGGREGATE`. Other DataElementDomains found: `[TRACKER]`"))) - .body("response.mergeReport.mergeErrors.errorCode", allOf(hasItem("E1555"))); + .body("response.mergeReport.mergeErrors.errorCode", allOf(hasItem("E1551"))); } @Test diff --git a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/merge/IndicatorMergeTest.java b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/merge/IndicatorMergeTest.java index dd58732222f8..2d20c732dd32 100644 --- a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/merge/IndicatorMergeTest.java +++ b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/merge/IndicatorMergeTest.java @@ -130,9 +130,9 @@ void testValidIndicatorMerge() { .validate() .statusCode(200) .body("httpStatus", equalTo("OK")) - .body("response.mergeReport.message", equalTo("INDICATOR merge complete")) + .body("response.mergeReport.message", equalTo("Indicator merge complete")) .body("response.mergeReport.mergeErrors", empty()) - .body("response.mergeReport.mergeType", equalTo("INDICATOR")) + .body("response.mergeReport.mergeType", equalTo("Indicator")) .body("response.mergeReport.sourcesDeleted", hasItems(sourceUid1, sourceUid2)); // and all the following source indicator references have been handled appropriately diff --git a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/merge/IndicatorTypeMergeTest.java b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/merge/IndicatorTypeMergeTest.java index 6c09c4127313..53826638b3ac 100644 --- a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/merge/IndicatorTypeMergeTest.java +++ b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/merge/IndicatorTypeMergeTest.java @@ -150,9 +150,9 @@ void testValidMergeKeepSources() { .validate() .statusCode(200) .body("httpStatus", equalTo("OK")) - .body("response.mergeReport.message", equalTo("INDICATOR_TYPE merge complete")) + .body("response.mergeReport.message", equalTo("IndicatorType merge complete")) .body("response.mergeReport.mergeErrors", empty()) - .body("response.mergeReport.mergeType", equalTo("INDICATOR_TYPE")) + .body("response.mergeReport.mergeType", equalTo("IndicatorType")) .body("response.mergeReport.sourcesDeleted", empty()); // and sources & target exist @@ -230,9 +230,9 @@ void testValidMergeDeleteSources() { .validate() .statusCode(200) .body("httpStatus", equalTo("OK")) - .body("response.mergeReport.message", equalTo("INDICATOR_TYPE merge complete")) + .body("response.mergeReport.message", equalTo("IndicatorType merge complete")) .body("response.mergeReport.mergeErrors", empty()) - .body("response.mergeReport.mergeType", equalTo("INDICATOR_TYPE")) + .body("response.mergeReport.mergeType", equalTo("IndicatorType")) .body("response.mergeReport.sourcesDeleted", hasItems(indTypeUid1, indTypeUid2)); // and sources are deleted & target exists @@ -282,12 +282,12 @@ void testInvalidMergeNoSources() { .statusCode(409) .body("httpStatus", equalTo("Conflict")) .body("status", equalTo("WARNING")) - .body("response.mergeReport.message", equalTo("INDICATOR_TYPE merge has errors")) + .body("response.mergeReport.message", equalTo("IndicatorType merge has errors")) .body( "response.mergeReport.mergeErrors[0].message", - equalTo("At least one source indicator type must be specified")) + equalTo("At least one source IndicatorType must be specified")) .body("response.mergeReport.mergeErrors[0].errorCode", equalTo("E1530")) - .body("response.mergeReport.mergeType", equalTo("INDICATOR_TYPE")) + .body("response.mergeReport.mergeType", equalTo("IndicatorType")) .body("response.mergeReport.sourcesDeleted", empty()); } @@ -313,12 +313,12 @@ void testInvalidMergeNoTarget() { .statusCode(409) .body("httpStatus", equalTo("Conflict")) .body("status", equalTo("WARNING")) - .body("response.mergeReport.message", equalTo("INDICATOR_TYPE merge has errors")) + .body("response.mergeReport.message", equalTo("IndicatorType merge has errors")) .body( "response.mergeReport.mergeErrors[0].message", - equalTo("Target indicator type must be specified")) + equalTo("Target IndicatorType must be specified")) .body("response.mergeReport.mergeErrors[0].errorCode", equalTo("E1531")) - .body("response.mergeReport.mergeType", equalTo("INDICATOR_TYPE")) + .body("response.mergeReport.mergeType", equalTo("IndicatorType")) .body("response.mergeReport.sourcesDeleted", empty()); } @@ -354,12 +354,12 @@ void testInvalidMergeTargetInSources() { .statusCode(409) .body("httpStatus", equalTo("Conflict")) .body("status", equalTo("WARNING")) - .body("response.mergeReport.message", equalTo("INDICATOR_TYPE merge has errors")) + .body("response.mergeReport.message", equalTo("IndicatorType merge has errors")) .body( "response.mergeReport.mergeErrors[0].message", - equalTo("Target indicator type cannot be a source indicator type")) + equalTo("Target IndicatorType cannot be a source IndicatorType")) .body("response.mergeReport.mergeErrors[0].errorCode", equalTo("E1532")) - .body("response.mergeReport.mergeType", equalTo("INDICATOR_TYPE")) + .body("response.mergeReport.mergeType", equalTo("IndicatorType")) .body("response.mergeReport.sourcesDeleted", empty()); } @@ -378,9 +378,9 @@ void testInvalidMergeTargetAndSourcesDontExist() { .statusCode(409) .body("httpStatus", equalTo("Conflict")) .body("status", equalTo("WARNING")) - .body("response.mergeReport.message", equalTo("INDICATOR_TYPE merge has errors")) + .body("response.mergeReport.message", equalTo("IndicatorType merge has errors")) .body("response.mergeReport.mergeErrors.size()", equalTo(3)) - .body("response.mergeReport.mergeType", equalTo("INDICATOR_TYPE")) + .body("response.mergeReport.mergeType", equalTo("IndicatorType")) .body("response.mergeReport.sourcesDeleted", empty()); } diff --git a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/tracker/imports/events/EventsTests.java b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/tracker/imports/events/EventsTests.java index 6de4b8804fca..9cf47ea5e9dc 100644 --- a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/tracker/imports/events/EventsTests.java +++ b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/tracker/imports/events/EventsTests.java @@ -33,6 +33,7 @@ import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.startsWith; import static org.hisp.dhis.helpers.matchers.MatchesJson.matchesJSON; import com.google.gson.JsonObject; @@ -136,6 +137,26 @@ void eventsImportNewEventsFromFile(String fileName, String contentType) throws E response.validate().statusCode(200).body("status", equalTo("OK")); } + @Test + void eventsImportNewEventsWithInvalidUidForCSV() throws Exception { + Object obj = + new FileReaderUtils() + .read(new File("src/test/resources/tracker/importer/events/event.csv")) + .replacePropertyValuesWith("event", "invalid_uid") + .get(); + + ApiResponse response = + trackerImportExportActions.post("", "text/csv", obj, new QueryParamsBuilder()); + response + .validate() + .statusCode(400) + .body("status", equalTo("ERROR")) + .body( + "message", + startsWith( + "UID must be an alphanumeric string of 11 characters starting with a letter")); + } + @ParameterizedTest @ValueSource(strings = {"true", "false"}) void shouldImportToRepeatableStage(Boolean repeatableStage) throws Exception { diff --git a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/tracker/imports/relationships/RelationshipsTests.java b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/tracker/imports/relationships/RelationshipsTests.java index f36f4fadb12e..96ae8b9be1e6 100644 --- a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/tracker/imports/relationships/RelationshipsTests.java +++ b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/tracker/imports/relationships/RelationshipsTests.java @@ -335,8 +335,8 @@ public void shouldValidateBothSidesOfRelationship() { JsonObject object = JsonObjectBuilder.jsonObject() .addProperty("relationshipType", "xLmPUYJX8Ks") - .addObject("from", relationshipItem("trackedEntity", "invalid-trackedEntity")) - .addObject("to", relationshipItem("trackedEntity", "more-invalid")) + .addObject("from", relationshipItem("trackedEntity", "xLmPUYJXXXX")) + .addObject("to", relationshipItem("trackedEntity", "xLmPUYJXYYY")) .wrapIntoArray("relationships"); trackerImportExportActions diff --git a/dhis-2/dhis-test-integration/pom.xml b/dhis-2/dhis-test-integration/pom.xml index 146a0e5b3085..727a012dfcc1 100644 --- a/dhis-2/dhis-test-integration/pom.xml +++ b/dhis-2/dhis-test-integration/pom.xml @@ -319,6 +319,11 @@ spring-security-oauth2-core test + + io.hypersistence + hypersistence-utils-hibernate-55 + test + diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/analytics/dimension/CategoryDimensionStoreTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/analytics/dimension/CategoryDimensionStoreTest.java new file mode 100644 index 000000000000..849e3e5763f2 --- /dev/null +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/analytics/dimension/CategoryDimensionStoreTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2004-2024, 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.dimension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.hisp.dhis.analytics.CategoryDimensionStore; +import org.hisp.dhis.category.Category; +import org.hisp.dhis.category.CategoryDimension; +import org.hisp.dhis.category.CategoryOption; +import org.hisp.dhis.common.BaseIdentifiableObject; +import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.IdentifiableObjectManager; +import org.hisp.dhis.test.integration.PostgresIntegrationTestBase; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +/** + * @author david mackessy + */ +@Transactional +class CategoryDimensionStoreTest extends PostgresIntegrationTestBase { + + @Autowired private CategoryDimensionStore categoryDimensionStore; + @Autowired private IdentifiableObjectManager manager; + + @Test + @DisplayName("Retrieving CategoryDimensions by CategoryOptions returns the expected objects") + void getCatDimensionsByCatOptionsTest() { + // given 4 CategoryDimensions, each with a different CategoryOption + CategoryOption co1 = createCategoryOption("1", CodeGenerator.generateUid()); + CategoryOption co2 = createCategoryOption("2", CodeGenerator.generateUid()); + CategoryOption co3 = createCategoryOption("3", CodeGenerator.generateUid()); + CategoryOption co4 = createCategoryOption("4", CodeGenerator.generateUid()); + manager.save(List.of(co1, co2, co3, co4)); + + Category cat1 = createCategory('1', co1); + Category cat2 = createCategory('2', co2); + Category cat3 = createCategory('3', co3); + Category cat4 = createCategory('4', co4); + manager.save(List.of(cat1, cat2, cat3, cat4)); + + CategoryDimension cd1 = createCategoryDimension(cat1); + cd1.getItems().add(co1); + + CategoryDimension cd2 = createCategoryDimension(cat2); + cd2.getItems().add(co2); + + CategoryDimension cd3 = createCategoryDimension(cat3); + cd3.getItems().add(co3); + + CategoryDimension cd4 = createCategoryDimension(cat4); + cd4.getItems().add(co4); + + categoryDimensionStore.save(cd1); + categoryDimensionStore.save(cd2); + categoryDimensionStore.save(cd3); + categoryDimensionStore.save(cd4); + + // when retrieving CategoryDimensions by CategoryOption (3) + List cdsByCategoryOption = + categoryDimensionStore.getByCategoryOption( + List.of(co1.getUid(), co2.getUid(), co3.getUid())); + + // then + assertEquals(3, cdsByCategoryOption.size(), "3 CategoryDimensions should be present"); + List cos = + cdsByCategoryOption.stream() + .flatMap(cd -> cd.getItems().stream()) + .map(BaseIdentifiableObject::getUid) + .toList(); + + assertEquals(3, cos.size(), "3 CategoryOptions should be present"); + assertTrue( + cos.containsAll(List.of(co1.getUid(), co2.getUid(), co3.getUid())), + "Retrieved CategoryOption UIDs should have expected UIDs"); + } +} diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/analytics/event/data/EventAnalyticsServiceTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/analytics/event/data/EventAnalyticsServiceTest.java index 60a4b5bbd74e..3ca3e385d074 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/analytics/event/data/EventAnalyticsServiceTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/analytics/event/data/EventAnalyticsServiceTest.java @@ -115,7 +115,6 @@ import org.hisp.dhis.program.ProgramTrackedEntityAttribute; import org.hisp.dhis.scheduling.JobProgress; import org.hisp.dhis.security.acl.AccessStringHelper; -import org.hisp.dhis.setting.SystemSettingsService; import org.hisp.dhis.test.integration.PostgresIntegrationTestBase; import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; @@ -172,8 +171,6 @@ class EventAnalyticsServiceTest extends PostgresIntegrationTestBase { @Autowired private CategoryService categoryService; - @Autowired private SystemSettingsService settingsService; - private OrganisationUnit ouA; private OrganisationUnit ouB; diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/category/CategoryOptionComboStoreTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/category/CategoryOptionComboStoreTest.java index a81b6cb8f3b0..54502e1dc16e 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/category/CategoryOptionComboStoreTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/category/CategoryOptionComboStoreTest.java @@ -30,16 +30,19 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.common.collect.Sets; import java.util.HashSet; import java.util.List; import java.util.Set; +import org.hisp.dhis.common.BaseIdentifiableObject; import org.hisp.dhis.common.DataDimensionType; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.dataelement.DataElementService; import org.hisp.dhis.test.integration.PostgresIntegrationTestBase; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; @@ -289,4 +292,42 @@ void testGetCategoryOptionComboByOptionGroup() { assertNotNull(result); assertEquals(categoryComboA.getOptionCombos(), Sets.newHashSet(result)); } + + @Test + @DisplayName("Retrieving CategoryOptionCombos by CategoryOptions returns the expected objects") + void getCatOptCombosByCatOptionsTest() { + CategoryOption co1 = createCategoryOption('1'); + CategoryOption co2 = createCategoryOption('2'); + CategoryOption co3 = createCategoryOption('3'); + CategoryOption co4 = createCategoryOption('4'); + categoryService.addCategoryOption(co1); + categoryService.addCategoryOption(co2); + categoryService.addCategoryOption(co3); + categoryService.addCategoryOption(co4); + + Category c1 = createCategory('1', co1, co2); + Category c2 = createCategory('2', co3, co4); + categoryService.addCategory(c1); + categoryService.addCategory(c2); + + CategoryCombo categoryCombo = createCategoryCombo('Z', c1, c2); + categoryService.addCategoryCombo(categoryCombo); + categoryService.updateOptionCombos(categoryCombo); + + List cocsByCategoryOption = + categoryOptionComboStore.getCategoryOptionCombosByCategoryOption( + List.of(co1.getUid(), co2.getUid(), co3.getUid())); + + assertEquals(4, cocsByCategoryOption.size(), "4 CategoryOptionCombos should be present"); + List cos = + cocsByCategoryOption.stream() + .flatMap(coc -> coc.getCategoryOptions().stream()) + .map(BaseIdentifiableObject::getUid) + .toList(); + + assertEquals(8, cos.size(), "8 CategoryOptions should be present"); + assertTrue( + cos.containsAll(List.of(co1.getUid(), co2.getUid(), co3.getUid())), + "Retrieved CategoryOption UIDs should have expected UIDs"); + } } diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/category/CategoryOptionGroupStoreTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/category/CategoryOptionGroupStoreTest.java index bf1e264a2df0..f2252703a30a 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/category/CategoryOptionGroupStoreTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/category/CategoryOptionGroupStoreTest.java @@ -167,4 +167,29 @@ void testAddCogsWithoutDataDimensionType() { assertThrows( PropertyValueException.class, () -> categoryService.saveCategoryOptionGroupSet(cogsA)); } + + @Test + @DisplayName( + "Should return the expected category option groups when searching by category option") + void getByCategoryOptionTest() { + CategoryOptionGroup cogA = createCategoryOptionGroup('W', coA); + CategoryOptionGroup cogB = createCategoryOptionGroup('X', coB); + CategoryOptionGroup cogC = createCategoryOptionGroup('Y', coB, coC); + CategoryOptionGroup cogD = createCategoryOptionGroup('Z', coD); + categoryOptionGroupStore.save(cogA); + categoryOptionGroupStore.save(cogB); + categoryOptionGroupStore.save(cogC); + categoryOptionGroupStore.save(cogD); + + List cogs = + categoryOptionGroupStore.getByCategoryOption( + List.of(coA.getUid(), coB.getUid(), coC.getUid())); + + assertEquals(3, cogs.size()); + assertTrue( + cogs.stream() + .flatMap(cog -> cog.getMembers().stream()) + .toList() + .containsAll(List.of(coA, coB, coC))); + } } diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/category/CategoryStoreTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/category/CategoryStoreTest.java new file mode 100644 index 000000000000..c12e87a54ed7 --- /dev/null +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/category/CategoryStoreTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2004-2024, 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.category; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.hisp.dhis.common.BaseIdentifiableObject; +import org.hisp.dhis.test.integration.PostgresIntegrationTestBase; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +class CategoryStoreTest extends PostgresIntegrationTestBase { + @Autowired private CategoryStore categoryStore; + + @Test + @DisplayName("Retrieving Categories by CategoryOptions returns the expected objects") + void getCatByCatOptionsTest() { + CategoryOption co1 = createCategoryOption('1'); + CategoryOption co2 = createCategoryOption('2'); + CategoryOption co3 = createCategoryOption('3'); + CategoryOption co4 = createCategoryOption('4'); + categoryService.addCategoryOption(co1); + categoryService.addCategoryOption(co2); + categoryService.addCategoryOption(co3); + categoryService.addCategoryOption(co4); + + Category c1 = createCategory('1', co1); + c1.addCategoryOption(co2); + Category c2 = createCategory('2', co3); + Category c3 = createCategory('3'); + Category c4 = createCategory('4'); + categoryService.addCategory(c1); + categoryService.addCategory(c2); + categoryService.addCategory(c3); + categoryService.addCategory(c4); + + List categoriesByCategoryOption = + categoryStore.getCategoriesByCategoryOption( + List.of(co1.getUid(), co2.getUid(), co3.getUid())); + + assertEquals(2, categoriesByCategoryOption.size(), "2 Categories should be present"); + List categoryOptions = + categoriesByCategoryOption.stream() + .flatMap(c -> c.getCategoryOptions().stream()) + .map(BaseIdentifiableObject::getUid) + .toList(); + + assertEquals(3, categoryOptions.size(), "3 CategoryOptions should be present"); + assertTrue( + categoryOptions.containsAll(List.of(co1.getUid(), co2.getUid(), co3.getUid())), + "Retrieved CategoryOption UIDs should have expected UIDs"); + } +} diff --git a/dhis-2/dhis-support/dhis-support-system/src/test/java/org/hisp/dhis/system/util/CodecUtilsTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/common/UidDBConstraintCheckTest.java similarity index 57% rename from dhis-2/dhis-support/dhis-support-system/src/test/java/org/hisp/dhis/system/util/CodecUtilsTest.java rename to dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/common/UidDBConstraintCheckTest.java index a0fc9e7144f0..7aa4a127359e 100644 --- a/dhis-2/dhis-support/dhis-support-system/src/test/java/org/hisp/dhis/system/util/CodecUtilsTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/common/UidDBConstraintCheckTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2022, University of Oslo + * Copyright (c) 2004-2024, University of Oslo * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -25,29 +25,29 @@ * (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.system.util; +package org.hisp.dhis.common; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.hisp.dhis.test.utils.Assertions.assertContains; +import static org.junit.jupiter.api.Assertions.assertThrows; +import org.hisp.dhis.test.integration.PostgresIntegrationTestBase; +import org.hisp.dhis.tracker.deduplication.DeduplicationService; +import org.hisp.dhis.tracker.deduplication.PotentialDuplicate; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; -class CodecUtilsTest { - @Test - void testMd5Hex() { - String value = "10-05-2022T12:55:45"; - - assertNull(CodecUtils.md5Hex(null)); - assertEquals(32, CodecUtils.md5Hex(value).length()); - assertEquals("c149820871470e3ab15eb24d42b3561a", CodecUtils.md5Hex(value)); - } +class UidDBConstraintCheckTest extends PostgresIntegrationTestBase { + @Autowired private DeduplicationService deduplicationService; @Test - void testSha1Hex() { - String value = "/api/me"; - - assertNull(CodecUtils.sha1Hex(null)); - assertEquals(40, CodecUtils.sha1Hex(value).length()); - assertEquals("4f8cc3f306852ecb642ba4375453be1a4b860e71", CodecUtils.sha1Hex(value)); + void shouldFailWhenAddingAPotentialDuplicateWithInvalidUid() { + PotentialDuplicate potentialDuplicate = new PotentialDuplicate(UID.generate(), UID.generate()); + potentialDuplicate.setUid("INVALID"); + DataIntegrityViolationException exception = + assertThrows( + DataIntegrityViolationException.class, + () -> deduplicationService.addPotentialDuplicate(potentialDuplicate)); + assertContains("constraint [potentialduplicate_check_uid]", exception.getMessage()); } } diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/dashboard/DashboardServiceTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/dashboard/DashboardServiceTest.java index 29c790d8bbfc..6039c45e8305 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/dashboard/DashboardServiceTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/dashboard/DashboardServiceTest.java @@ -29,7 +29,6 @@ import static java.util.Arrays.asList; import static java.util.stream.IntStream.range; -import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hisp.dhis.dashboard.DashboardItemType.EVENT_REPORT; @@ -40,11 +39,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; import java.util.List; import java.util.Set; import org.hisp.dhis.common.IdentifiableObjectManager; +import org.hisp.dhis.dashboard.embedded.EmbeddedDashboard; +import org.hisp.dhis.dashboard.embedded.EmbeddedOptions; +import org.hisp.dhis.dashboard.embedded.EmbeddedProvider; +import org.hisp.dhis.dashboard.embedded.FilterOptions; import org.hisp.dhis.document.Document; import org.hisp.dhis.document.DocumentService; import org.hisp.dhis.eventchart.EventChart; @@ -78,10 +82,14 @@ class DashboardServiceTest extends PostgresIntegrationTestBase { @Autowired private IdentifiableObjectManager objectManager; + private static final String UUID_A = "41c52308-1db4-4971-ade4-50c4d12c201d"; + private Dashboard dbA; private Dashboard dbB; + private Dashboard dbC; + private DashboardItem diA; private DashboardItem diB; @@ -92,6 +100,8 @@ class DashboardServiceTest extends PostgresIntegrationTestBase { private DashboardItem diE; + private DashboardItem diF; + private Visualization vzA; private Visualization vzB; @@ -136,28 +146,53 @@ void setUp() { diE = new DashboardItem(); diE.setAutoFields(); diE.setEventVisualization(evzB); + diF = new DashboardItem(); + diF.setAutoFields(); + diF.setVisualization(vzA); + dbA = new Dashboard("A"); dbA.setAutoFields(); dbA.getItems().add(diA); dbA.getItems().add(diB); dbA.getItems().add(diC); + dbB = new Dashboard("B"); dbB.setAutoFields(); dbB.setRestrictFilters(true); dbB.setAllowedFilters(allowedFilters); dbB.getItems().add(diD); dbB.getItems().add(diE); + + dbC = new Dashboard("C"); + dbC.setAutoFields(); + dbC.getItems().add(diF); + dbC.setEmbedded( + new EmbeddedDashboard( + EmbeddedProvider.SUPERSET, + UUID_A, + new EmbeddedOptions(true, true, new FilterOptions(true, true)))); } @Test - void testAddGet() { + void testSaveGet() { long dAId = dashboardService.saveDashboard(dbA); long dBId = dashboardService.saveDashboard(dbB); + long dCId = dashboardService.saveDashboard(dbC); assertEquals(dbA, dashboardService.getDashboard(dAId)); assertEquals(dbB, dashboardService.getDashboard(dBId)); + assertEquals(dbC, dashboardService.getDashboard(dCId)); assertEquals(2, dbB.getAllowedFilters().size()); assertEquals(3, dashboardService.getDashboard(dAId).getItems().size()); assertEquals(2, dashboardService.getDashboard(dBId).getItems().size()); + assertEquals(1, dashboardService.getDashboard(dCId).getItems().size()); + + Dashboard rdC = dashboardService.getDashboard(dCId); + assertNotNull(rdC.getEmbedded()); + assertEquals(EmbeddedProvider.SUPERSET, rdC.getEmbedded().getProvider()); + assertEquals(UUID_A, rdC.getEmbedded().getId()); + assertNotNull(rdC.getEmbedded().getOptions()); + assertTrue(rdC.getEmbedded().getOptions().isHideChartControls()); + assertTrue(rdC.getEmbedded().getOptions().isHideChartControls()); } @Test @@ -234,7 +269,7 @@ void testSearchDashboardWithMaxCount() { .forEach( i -> { Visualization visualization = createVisualization('A'); - visualization.setName(randomAlphabetic(5)); + visualization.setName("Visualization" + i); visualizationService.save(visualization); }); @@ -242,18 +277,18 @@ void testSearchDashboardWithMaxCount() { .forEach( i -> { EventVisualization eventVisualization = createEventVisualization("A", prA); - eventVisualization.setName(randomAlphabetic(5)); + eventVisualization.setName("EventVisualization" + i); eventVisualizationService.save(eventVisualization); }); // Non Line List event visualization should be ignored when we search for EVENT_VISUALIZATION: EventVisualization eventVisualization = createEventVisualization("A", prA); - eventVisualization.setName(randomAlphabetic(5)); + eventVisualization.setName("EventVisualizationA"); eventVisualization.setType(COLUMN); eventVisualizationService.save(eventVisualization); - range(1, 30).forEach(i -> eventChartService.saveEventChart(createEventChart(prA))); - range(1, 20).forEach(i -> eventReportService.saveEventReport(createEventReport(prA))); + range(1, 30).forEach(i -> eventChartService.saveEventChart(createEventChart(prA, i))); + range(1, 20).forEach(i -> eventReportService.saveEventReport(createEventReport(prA, i))); DashboardSearchResult result = dashboardService.search(Set.of(VISUALIZATION)); assertThat(result.getVisualizationCount(), is(25)); @@ -292,15 +327,15 @@ private EventVisualization createEventVisualization(String name, Program program return eventVisualization; } - private EventChart createEventChart(Program program) { - EventChart eventChart = new EventChart(randomAlphabetic(5)); + private EventChart createEventChart(Program program, int i) { + EventChart eventChart = new EventChart("EventChart" + i); eventChart.setProgram(program); eventChart.setType(COLUMN); return eventChart; } - private EventReport createEventReport(Program program) { - EventReport eventReport = new EventReport(randomAlphabetic(5)); + private EventReport createEventReport(Program program, int i) { + EventReport eventReport = new EventReport("EventReport" + i); eventReport.setProgram(program); eventReport.setType(PIVOT_TABLE); return eventReport; diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/db/sql/PostgreSqlBuilderIntegrationTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/db/sql/PostgreSqlBuilderIntegrationTest.java index 1bd0774216d0..57352d26e283 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/db/sql/PostgreSqlBuilderIntegrationTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/db/sql/PostgreSqlBuilderIntegrationTest.java @@ -102,7 +102,7 @@ private Table getTableB() { List checks = List.of("\"id\">0", "\"bcg_doses\">0"); - return new Table("vaccination", columns, List.of(), checks, Logged.UNLOGGED); + return new Table("vaccination", columns, List.of(), List.of(), checks, Logged.UNLOGGED); } private Table getTableC() { diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/indicator/IndicatorServiceTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/indicator/IndicatorServiceTest.java index d17179359b20..3a446c8ebbcd 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/indicator/IndicatorServiceTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/indicator/IndicatorServiceTest.java @@ -38,6 +38,12 @@ import java.util.Map; import java.util.Set; import org.hisp.dhis.common.IdentifiableObjectManager; +import org.hisp.dhis.dataelement.DataElement; +import org.hisp.dhis.dataset.DataSet; +import org.hisp.dhis.dataset.Section; +import org.hisp.dhis.period.PeriodService; +import org.hisp.dhis.period.PeriodType; +import org.hisp.dhis.period.PeriodTypeEnum; import org.hisp.dhis.setting.ThreadUserSettings; import org.hisp.dhis.test.integration.PostgresIntegrationTestBase; import org.hisp.dhis.translation.Translation; @@ -55,6 +61,8 @@ @TestInstance(Lifecycle.PER_CLASS) @Transactional class IndicatorServiceTest extends PostgresIntegrationTestBase { + @Autowired private PeriodService periodService; + @Autowired private IndicatorService indicatorService; @Autowired private UserService injectUserService; @@ -70,6 +78,7 @@ void setUp() { // ------------------------------------------------------------------------- // Support methods // ------------------------------------------------------------------------- + private void assertEq(char uniqueCharacter, Indicator indicator) { assertEquals("Indicator" + uniqueCharacter, indicator.getName()); assertEquals("IndicatorShort" + uniqueCharacter, indicator.getShortName()); @@ -80,6 +89,7 @@ private void assertEq(char uniqueCharacter, Indicator indicator) { // ------------------------------------------------------------------------- // IndicatorType // ------------------------------------------------------------------------- + @Test void testAddIndicatorType() { IndicatorType typeA = new IndicatorType("IndicatorTypeA", 100, false); @@ -138,6 +148,7 @@ void testGetAllIndicatorTypes() { // ------------------------------------------------------------------------- // IndicatorGroup // ------------------------------------------------------------------------- + @Test void testAddIndicatorGroup() { IndicatorGroup groupA = new IndicatorGroup("IndicatorGroupA"); @@ -196,6 +207,7 @@ void testGetAllIndicatorGroups() { // ------------------------------------------------------------------------- // Indicator // ------------------------------------------------------------------------- + @Test void testAddIndicator() { IndicatorType type = new IndicatorType("IndicatorType", 100, false); @@ -281,4 +293,32 @@ void testNumeratorTranslation() { assertEquals(numeratorTranslated, indicatorA.getDisplayNumeratorDescription()); assertEquals(denominatorTranslated, indicatorA.getDisplayDenominatorDescription()); } + + @Test + void testRemoveIndicator() { + IndicatorType type = new IndicatorType("IndicatorType", 100, false); + indicatorService.addIndicatorType(type); + Indicator indicatorA = createIndicator('A', type); + Indicator indicatorB = createIndicator('B', type); + + PeriodType ptA = periodService.getPeriodType(PeriodTypeEnum.MONTHLY); + DataElement dataElementA = createDataElement('A'); + DataSet dataSetA = createDataSet('A', ptA); + Section sectionA = createSection('A', dataSetA, List.of(dataElementA), List.of(indicatorA)); + identifiableObjectManager.save(dataElementA); + identifiableObjectManager.save(dataSetA); + identifiableObjectManager.save(sectionA); + + long idA = indicatorService.addIndicator(indicatorA); + long idB = indicatorService.addIndicator(indicatorB); + assertNotNull(indicatorService.getIndicator(idA)); + assertNotNull(indicatorService.getIndicator(idB)); + + indicatorService.deleteIndicator(indicatorB); + assertNotNull(indicatorService.getIndicator(idA)); + assertNull(indicatorService.getIndicator(idB)); + indicatorService.deleteIndicator(indicatorA); + assertNull(indicatorService.getIndicator(idA)); + assertNull(indicatorService.getIndicator(idB)); + } } diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/maintenance/MaintenanceServiceTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/maintenance/MaintenanceServiceTest.java index 99464e155efb..96f5aa075b91 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/maintenance/MaintenanceServiceTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/maintenance/MaintenanceServiceTest.java @@ -362,7 +362,7 @@ void testDeleteSoftDeletedEventLinkedToARelationshipItem() { relationshipTypeService.addRelationshipType(rType); Event eventA = new Event(enrollment, program.getProgramStageByStage(1)); eventA.setScheduledDate(enrollmentDate); - eventA.setUid("UID-A"); + eventA.setUid(UID.generate().getValue()); eventA.setAttributeOptionCombo(coA); manager.save(eventA); long idA = eventA.getId(); diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/merge/category/CategoryOptionMergeServiceTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/merge/category/CategoryOptionMergeServiceTest.java new file mode 100644 index 000000000000..b7b350563eb3 --- /dev/null +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/merge/category/CategoryOptionMergeServiceTest.java @@ -0,0 +1,726 @@ +/* + * Copyright (c) 2004-2024, 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.merge.category; + +import static io.hypersistence.utils.jdbc.validator.SQLStatementCountValidator.assertDeleteCount; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.hypersistence.utils.jdbc.validator.SQLStatementCountValidator; +import java.util.List; +import java.util.Set; +import org.hisp.dhis.analytics.CategoryDimensionStore; +import org.hisp.dhis.category.Category; +import org.hisp.dhis.category.CategoryCombo; +import org.hisp.dhis.category.CategoryDimension; +import org.hisp.dhis.category.CategoryOption; +import org.hisp.dhis.category.CategoryOptionCombo; +import org.hisp.dhis.category.CategoryOptionGroup; +import org.hisp.dhis.category.CategoryService; +import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.IdentifiableObjectManager; +import org.hisp.dhis.common.UID; +import org.hisp.dhis.feedback.ConflictException; +import org.hisp.dhis.feedback.MergeReport; +import org.hisp.dhis.merge.MergeParams; +import org.hisp.dhis.merge.MergeService; +import org.hisp.dhis.organisationunit.OrganisationUnit; +import org.hisp.dhis.organisationunit.OrganisationUnitService; +import org.hisp.dhis.test.config.QueryCountDataSourceProxy; +import org.hisp.dhis.test.integration.PostgresIntegrationTestBase; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.transaction.annotation.Transactional; + +/** + * All the tests in this class basically test the same thing: + * + *

- Create metadata which have source CategoryOption references + * + *

- Perform a CategoryOption merge, passing a target CategoryOption + * + *

- Check that source CategoryOptions have had their references removed/replaced with the target + * CategoryOption + */ +@Transactional +@ContextConfiguration(classes = {QueryCountDataSourceProxy.class}) +class CategoryOptionMergeServiceTest extends PostgresIntegrationTestBase { + + @Autowired private CategoryService categoryService; + @Autowired private OrganisationUnitService organisationUnitService; + @Autowired private CategoryDimensionStore dimensionStore; + @Autowired private IdentifiableObjectManager manager; + @Autowired private MergeService categoryOptionMergeService; + + private Category cat1; + private Category cat2; + private Category cat3; + private Category cat4; + private CategoryOption coSource1A; + private CategoryOption co1B; + private CategoryOption co2A; + private CategoryOption coSource2B; + private CategoryOption coTarget3A; + private CategoryOption co3B; + private CategoryOption co4A; + private CategoryOption co4B; + + @BeforeEach + public void setUp() { + // 8 category options + coSource1A = createCategoryOption("1A source", CodeGenerator.generateUid()); + co1B = createCategoryOption("1B", CodeGenerator.generateUid()); + co2A = createCategoryOption("2A", CodeGenerator.generateUid()); + coSource2B = createCategoryOption("2B source", CodeGenerator.generateUid()); + coTarget3A = createCategoryOption("3A target", CodeGenerator.generateUid()); + co3B = createCategoryOption("3B", CodeGenerator.generateUid()); + co4A = createCategoryOption("4A", CodeGenerator.generateUid()); + co4B = createCategoryOption("4B", CodeGenerator.generateUid()); + categoryService.addCategoryOption(coSource1A); + categoryService.addCategoryOption(co1B); + categoryService.addCategoryOption(co2A); + categoryService.addCategoryOption(coSource2B); + categoryService.addCategoryOption(coTarget3A); + categoryService.addCategoryOption(co3B); + categoryService.addCategoryOption(co4A); + categoryService.addCategoryOption(co4B); + + // 4 categories (each with 2 category options) + cat1 = createCategory('1', coSource1A, co1B); + cat2 = createCategory('2', co2A, coSource2B); + cat3 = createCategory('3', coTarget3A, co3B); + cat4 = createCategory('4', co4A, co4B); + categoryService.addCategory(cat1); + categoryService.addCategory(cat2); + categoryService.addCategory(cat3); + categoryService.addCategory(cat4); + } + + // ----------------------------- + // --------- Category ---------- + // ----------------------------- + @Test + @DisplayName("Category refs to source CategoryOptions are replaced, sources not deleted") + void categoryRefsReplacedSourcesNotDeletedTest() throws ConflictException { + // given category state before merge + List categorySourcesBefore = + categoryService.getCategoriesByCategoryOption( + List.of(UID.of(coSource1A.getUid()), UID.of(coSource2B.getUid()))); + List categoryTargetBefore = + categoryService.getCategoriesByCategoryOption(List.of(UID.of(coTarget3A.getUid()))); + + assertEquals( + 2, categorySourcesBefore.size(), "Expect 2 categories with source category option refs"); + assertEquals( + 1, categoryTargetBefore.size(), "Expect 1 category with target category option refs"); + + // when + MergeParams mergeParams = getMergeParams(); + MergeReport report = categoryOptionMergeService.processMerge(mergeParams); + + // then + List categorySources = + categoryService.getCategoriesByCategoryOption( + List.of(UID.of(coSource1A), UID.of(coSource2B))); + List categoryTarget = + categoryService.getCategoriesByCategoryOption(List.of(UID.of(coTarget3A))); + List allCategoryOptions = categoryService.getAllCategoryOptions(); + + assertFalse(report.hasErrorMessages()); + assertEquals(0, categorySources.size(), "Expect 0 entries with source category option refs"); + assertEquals(3, categoryTarget.size(), "Expect 3 entries with target category option refs"); + + // 8 custom + 1 default + assertEquals(9, allCategoryOptions.size(), "Expect 9 category options present"); + assertTrue(allCategoryOptions.containsAll(List.of(coTarget3A, coSource1A, coSource2B))); + } + + @Test + @DisplayName("Category refs to source CategoryOptions are replaced, sources are deleted") + void categoryRefsReplacedSourcesDeletedTest() throws ConflictException { + // given category state before merge + List categorySourcesBefore = + categoryService.getCategoriesByCategoryOption( + List.of(UID.of(coSource1A), UID.of(coSource2B))); + List categoryTargetBefore = + categoryService.getCategoriesByCategoryOption(List.of(UID.of(coTarget3A))); + + assertEquals( + 2, categorySourcesBefore.size(), "Expect 2 categories with source category option refs"); + assertEquals( + 1, categoryTargetBefore.size(), "Expect 1 category with target category option refs"); + + // when + MergeParams mergeParams = getMergeParams(); + mergeParams.setDeleteSources(true); + MergeReport report = categoryOptionMergeService.processMerge(mergeParams); + + // then + List categorySources = + categoryService.getCategoriesByCategoryOption( + List.of(UID.of(coSource1A), UID.of(coSource2B))); + List categoryTarget = + categoryService.getCategoriesByCategoryOption(List.of(UID.of(coTarget3A))); + List allCategoryOptions = categoryService.getAllCategoryOptions(); + + assertFalse(report.hasErrorMessages()); + assertEquals(0, categorySources.size(), "Expect 0 entries with source category option refs"); + assertEquals(3, categoryTarget.size(), "Expect 3 entries with target category option refs"); + + // 6 custom + 1 default + assertEquals(7, allCategoryOptions.size(), "Expect 7 category options present"); + assertTrue(allCategoryOptions.contains(coTarget3A)); + assertFalse(allCategoryOptions.containsAll(List.of(coSource1A, coSource2B))); + } + + // ----------------------------- + // ---- CategoryOptionCombo ---- + // ----------------------------- + @Test + @DisplayName( + "CategoryOptionCombo refs to source CategoryOptions are replaced, sources not deleted") + void catOptComboRefsReplacedSourcesNotDeletedTest() throws ConflictException { + // given + CategoryCombo cc1 = createCategoryCombo('1', cat1, cat2); + CategoryCombo cc2 = createCategoryCombo('2', cat3, cat4); + manager.save(List.of(cc1, cc2)); + // these calls generate cat option combos + categoryService.updateOptionCombos(cc1); + categoryService.updateOptionCombos(cc2); + + // confirm cat option combo state before merge + List sourceCocsBefore = + categoryService.getCategoryOptionCombosByCategoryOption( + List.of(UID.of(coSource1A), UID.of(coSource2B))); + List targetCocsBefore = + categoryService.getCategoryOptionCombosByCategoryOption(List.of(UID.of(coTarget3A))); + + assertEquals(3, sourceCocsBefore.size(), "Expect 3 entries with source category option refs"); + assertEquals(2, targetCocsBefore.size(), "Expect 2 entries with target category option refs"); + + // when + MergeParams mergeParams = getMergeParams(); + MergeReport report = categoryOptionMergeService.processMerge(mergeParams); + + // then + List sourceCocs = + categoryService.getCategoryOptionCombosByCategoryOption( + List.of(UID.of(coSource1A), UID.of(coSource2B))); + List targetCocs = + categoryService.getCategoryOptionCombosByCategoryOption(List.of(UID.of(coTarget3A))); + List allCategoryOptions = categoryService.getAllCategoryOptions(); + + assertFalse(report.hasErrorMessages()); + assertEquals(0, sourceCocs.size(), "Expect 0 entries with source category option refs"); + assertEquals(5, targetCocs.size(), "Expect 5 entries with target category option refs"); + assertEquals(9, allCategoryOptions.size(), "Expect 9 category options present"); + assertTrue(allCategoryOptions.containsAll(List.of(coTarget3A, coSource1A, coSource2B))); + } + + @Test + @DisplayName("Expect the correct number of SQL delete queries when merging") + void catOptMergeQueryTest() throws ConflictException { + // given + CategoryCombo cc1 = createCategoryCombo('1', cat1); + CategoryCombo cc2 = createCategoryCombo('2', cat3); + manager.save(List.of(cc1, cc2)); + + categoryService.updateOptionCombos(cc1); + categoryService.updateOptionCombos(cc2); + + // when + MergeParams mergeParams = getMergeParams(); + mergeParams.setSources(Set.of(UID.of(coSource1A))); + mergeParams.setDeleteSources(true); + + SQLStatementCountValidator.reset(); + categoryOptionMergeService.processMerge(mergeParams); + + // then + assertDeleteCount(1); + assertNull( + categoryService.getCategoryOption(coSource1A.getUid()), + "source cat option should not exist"); + } + + @Test + @DisplayName( + "CategoryOptionCombo refs to source CategoryOptions are replaced, sources are deleted") + void catOptComboRefsReplacedSourcesDeletedTest() throws ConflictException { + // given + CategoryCombo cc1 = createCategoryCombo('1', cat1, cat2); + CategoryCombo cc2 = createCategoryCombo('2', cat3, cat4); + manager.save(List.of(cc1, cc2)); + categoryService.updateOptionCombos(cc1); + categoryService.updateOptionCombos(cc2); + + // confirm cat option combos state before merge + List sourceCocsBefore = + categoryService.getCategoryOptionCombosByCategoryOption( + List.of(UID.of(coSource1A), UID.of(coSource2B))); + List targetCocsBefore = + categoryService.getCategoryOptionCombosByCategoryOption(List.of(UID.of(coTarget3A))); + + assertEquals(3, sourceCocsBefore.size(), "Expect 3 entries with source category option refs"); + assertEquals(2, targetCocsBefore.size(), "Expect 2 entries with target category option refs"); + + // when + MergeParams mergeParams = getMergeParams(); + mergeParams.setDeleteSources(true); + + MergeReport report = categoryOptionMergeService.processMerge(mergeParams); + + // then + List sourceCocs = + categoryService.getCategoryOptionCombosByCategoryOption( + List.of(UID.of(coSource1A), UID.of(coSource2B))); + List targetCocs = + categoryService.getCategoryOptionCombosByCategoryOption(List.of(UID.of(coTarget3A))); + List allCategoryOptions = categoryService.getAllCategoryOptions(); + + assertFalse(report.hasErrorMessages()); + assertEquals(0, sourceCocs.size(), "Expect 0 entries with source category option refs"); + assertEquals(5, targetCocs.size(), "Expect 5 entries with target category option refs"); + + // 6 custom + 1 default + assertEquals(7, allCategoryOptions.size(), "Expect 7 category options present"); + assertTrue(allCategoryOptions.contains(coTarget3A)); + assertFalse(allCategoryOptions.containsAll(List.of(coSource1A, coSource2B))); + } + + @Test + @DisplayName( + "1 CategoryOptionCombo with refs to source & target CategoryOption results in CategoryOptionCombo with just target") + void catOptComboSourceAndTargetRefsTest() throws ConflictException { + // given + CategoryCombo cc1 = createCategoryCombo('1', cat1, cat2); + manager.save(cc1); + + CategoryOptionCombo coc = createCategoryOptionCombo(cc1, coSource1A, coSource2B, coTarget3A); + manager.save(coc); + + // confirm cat option combo state before merge + List sourceCocsBefore = + categoryService.getCategoryOptionCombosByCategoryOption( + List.of(UID.of(coSource1A), UID.of(coSource2B))); + List targetCocsBefore = + categoryService.getCategoryOptionCombosByCategoryOption(List.of(UID.of(coTarget3A))); + + assertEquals(1, sourceCocsBefore.size(), "Expect 1 entry with source category option ref"); + assertEquals(1, targetCocsBefore.size(), "Expect 1 entry with target category option ref"); + + // when + MergeParams mergeParams = getMergeParams(); + MergeReport report = categoryOptionMergeService.processMerge(mergeParams); + + // then + List sourceCocs = + categoryService.getCategoryOptionCombosByCategoryOption( + List.of(UID.of(coSource1A), UID.of(coSource2B))); + List targetCocs = + categoryService.getCategoryOptionCombosByCategoryOption(List.of(UID.of(coTarget3A))); + List allCategoryOptions = categoryService.getAllCategoryOptions(); + List catOptions = + targetCocs.stream() + .flatMap(optionCombo -> optionCombo.getCategoryOptions().stream()) + .toList(); + + assertFalse(report.hasErrorMessages()); + assertEquals(0, sourceCocs.size(), "Expect 0 entries with source category option refs"); + assertEquals(1, targetCocs.size(), "Expect 1 entry with target category option ref"); + assertEquals(1, catOptions.size()); + assertTrue(catOptions.contains(coTarget3A)); + assertEquals(9, allCategoryOptions.size(), "Expect 9 category options present"); + assertTrue(allCategoryOptions.containsAll(List.of(coTarget3A, coSource1A, coSource2B))); + } + + // ----------------------------- + // --------- Org Unit ---------- + // ----------------------------- + @Test + @DisplayName("OrgUnit refs to source CategoryOptions are replaced, sources not deleted") + void orgUnitRefsReplacedSourcesNotDeletedTest() throws ConflictException { + // given + OrganisationUnit ou1 = createOrganisationUnit('x'); + ou1.addCategoryOption(coSource1A); + ou1.addCategoryOption(co1B); + + OrganisationUnit ou2 = createOrganisationUnit('y'); + ou2.addCategoryOption(coSource2B); + ou2.addCategoryOption(co2A); + + OrganisationUnit ou3 = createOrganisationUnit('z'); + ou3.addCategoryOption(coTarget3A); + ou3.addCategoryOption(co4A); + + OrganisationUnit ou4 = createOrganisationUnit('p'); + ou4.addCategoryOption(co3B); + ou4.addCategoryOption(co4B); + + manager.save(List.of(ou1, ou2, ou3, ou4)); + + // when + MergeParams mergeParams = getMergeParams(); + MergeReport report = categoryOptionMergeService.processMerge(mergeParams); + + // then + List orgUnitSources = + organisationUnitService.getByCategoryOption( + List.of(UID.of(coSource1A), UID.of(coSource2B))); + List orgUnitTarget = + organisationUnitService.getByCategoryOption(List.of(UID.of(coTarget3A))); + List allCategoryOptions = categoryService.getAllCategoryOptions(); + + assertFalse(report.hasErrorMessages()); + assertEquals(0, orgUnitSources.size(), "Expect 0 entries with source org units refs"); + assertEquals(3, orgUnitTarget.size(), "Expect 3 entries with target org unit refs"); + + assertEquals(9, allCategoryOptions.size(), "Expect 9 category options present"); + assertTrue(allCategoryOptions.containsAll(List.of(coTarget3A, coSource1A, coSource2B))); + } + + @Test + @DisplayName("OrgUnit refs to source CategoryOptions are replaced, sources deleted") + void orgUnitRefsReplacedSourcesDeletedTest() throws ConflictException { + // given + OrganisationUnit ou1 = createOrganisationUnit('x'); + ou1.addCategoryOption(coSource1A); + ou1.addCategoryOption(co1B); + + OrganisationUnit ou2 = createOrganisationUnit('y'); + ou2.addCategoryOption(coSource2B); + ou2.addCategoryOption(co2A); + + OrganisationUnit ou3 = createOrganisationUnit('z'); + ou3.addCategoryOption(coTarget3A); + ou3.addCategoryOption(co4A); + + OrganisationUnit ou4 = createOrganisationUnit('p'); + ou4.addCategoryOption(co3B); + ou4.addCategoryOption(co4B); + + manager.save(List.of(ou1, ou2, ou3, ou4)); + + // when + MergeParams mergeParams = getMergeParams(); + mergeParams.setDeleteSources(true); + MergeReport report = categoryOptionMergeService.processMerge(mergeParams); + + // then + List orgUnitSources = + organisationUnitService.getByCategoryOption( + List.of(UID.of(coSource1A), UID.of(coSource2B))); + List orgUnitTarget = + organisationUnitService.getByCategoryOption(List.of(UID.of(coTarget3A))); + List allCategoryOptions = categoryService.getAllCategoryOptions(); + + assertFalse(report.hasErrorMessages()); + assertEquals(0, orgUnitSources.size(), "Expect 0 entries with source org units refs"); + assertEquals(3, orgUnitTarget.size(), "Expect 3 entries with target org unit refs"); + + assertEquals(7, allCategoryOptions.size(), "Expect 7 category options present"); + assertTrue(allCategoryOptions.contains(coTarget3A)); + assertFalse(allCategoryOptions.containsAll(List.of(coSource1A, coSource2B))); + } + + @Test + @DisplayName( + "1 OrgUnit with refs to source & target CategoryOption results in OrgUnit with just target") + void orgUnitSourceAndTargetRefsTest() throws ConflictException { + // given + OrganisationUnit ou = createOrganisationUnit('o'); + ou.addCategoryOption(coSource1A); + ou.addCategoryOption(coSource2B); + ou.addCategoryOption(coTarget3A); + + manager.save(ou); + + // confirm org unit state before merge + List sourceOusBefore = + organisationUnitService.getByCategoryOption( + List.of(UID.of(coSource1A), UID.of(coSource2B))); + List targetOusBefore = + organisationUnitService.getByCategoryOption(List.of(UID.of(coTarget3A))); + + assertEquals(1, sourceOusBefore.size(), "Expect 1 entry with source category option refs"); + assertEquals(1, targetOusBefore.size(), "Expect 1 entry with target category option ref"); + + // when + MergeParams mergeParams = getMergeParams(); + MergeReport report = categoryOptionMergeService.processMerge(mergeParams); + + // then + List sourceOus = + organisationUnitService.getByCategoryOption( + List.of(UID.of(coSource1A), UID.of(coSource2B))); + List targetOus = + organisationUnitService.getByCategoryOption(List.of(UID.of(coTarget3A))); + List allCategoryOptions = categoryService.getAllCategoryOptions(); + List catOptions = + targetOus.stream().flatMap(orgUnit -> orgUnit.getCategoryOptions().stream()).toList(); + + assertFalse(report.hasErrorMessages()); + assertEquals(0, sourceOus.size(), "Expect 0 entries with source category option refs"); + assertEquals(1, targetOus.size(), "Expect 1 entry with target category option ref"); + assertEquals(1, catOptions.size()); + assertTrue(catOptions.contains(coTarget3A)); + assertEquals(9, allCategoryOptions.size(), "Expect 9 category options present"); + assertTrue(allCategoryOptions.containsAll(List.of(coTarget3A, coSource1A, coSource2B))); + } + + // ----------------------------- + // --- Category Option Group --- + // ----------------------------- + @Test + @DisplayName( + "CategoryOptionGroup refs to source CategoryOptions are replaced, sources not deleted") + void catOptionGroupSourcesNotDeletedTest() throws ConflictException { + // given + CategoryOptionGroup cog1 = createCategoryOptionGroup('x'); + cog1.addCategoryOption(coSource1A); + cog1.addCategoryOption(co1B); + + CategoryOptionGroup cog2 = createCategoryOptionGroup('y'); + cog2.addCategoryOption(coSource2B); + cog2.addCategoryOption(co2A); + + CategoryOptionGroup cog3 = createCategoryOptionGroup('z'); + cog3.addCategoryOption(coTarget3A); + cog3.addCategoryOption(co4A); + + CategoryOptionGroup cog4 = createCategoryOptionGroup('p'); + cog4.addCategoryOption(co3B); + cog4.addCategoryOption(co4B); + + manager.save(List.of(cog1, cog2, cog3, cog4)); + + // when + MergeParams mergeParams = getMergeParams(); + MergeReport report = categoryOptionMergeService.processMerge(mergeParams); + + // then + List cogSources = + categoryService.getCategoryOptionGroupByCategoryOption( + List.of(UID.of(coSource1A), UID.of(coSource2B))); + List cogTarget = + categoryService.getCategoryOptionGroupByCategoryOption(List.of(UID.of(coTarget3A))); + List allCategoryOptions = categoryService.getAllCategoryOptions(); + + assertFalse(report.hasErrorMessages()); + assertEquals(0, cogSources.size(), "Expect 0 entries with source cat option group refs"); + assertEquals(3, cogTarget.size(), "Expect 3 entries with target cat option group refs"); + + assertEquals(9, allCategoryOptions.size(), "Expect 9 category options present"); + assertTrue(allCategoryOptions.containsAll(List.of(coTarget3A, coSource1A, coSource2B))); + } + + @Test + @DisplayName("CategoryOptionGroup refs to source CategoryOptions are replaced, sources deleted") + void catOptionGroupSourcesDeletedTest() throws ConflictException { + // given + CategoryOptionGroup cog1 = createCategoryOptionGroup('x'); + cog1.addCategoryOption(coSource1A); + cog1.addCategoryOption(co1B); + + CategoryOptionGroup cog2 = createCategoryOptionGroup('y'); + cog2.addCategoryOption(coSource2B); + cog2.addCategoryOption(co2A); + + CategoryOptionGroup cog3 = createCategoryOptionGroup('z'); + cog3.addCategoryOption(coTarget3A); + cog3.addCategoryOption(co4A); + + CategoryOptionGroup cog4 = createCategoryOptionGroup('p'); + cog4.addCategoryOption(co3B); + cog4.addCategoryOption(co4B); + + manager.save(List.of(cog1, cog2, cog3, cog4)); + + // when + MergeParams mergeParams = getMergeParams(); + mergeParams.setDeleteSources(true); + MergeReport report = categoryOptionMergeService.processMerge(mergeParams); + + // then + List cogSources = + categoryService.getCategoryOptionGroupByCategoryOption( + List.of(UID.of(coSource1A), UID.of(coSource2B))); + List cogTarget = + categoryService.getCategoryOptionGroupByCategoryOption(List.of(UID.of(coTarget3A))); + List allCategoryOptions = categoryService.getAllCategoryOptions(); + + assertFalse(report.hasErrorMessages()); + assertEquals(0, cogSources.size(), "Expect 0 entries with source cat option group refs"); + assertEquals(3, cogTarget.size(), "Expect 3 entries with target cat option group refs"); + + assertEquals(7, allCategoryOptions.size(), "Expect 7 category options present"); + assertTrue(allCategoryOptions.contains(coTarget3A)); + assertFalse(allCategoryOptions.containsAll(List.of(coSource1A, coSource2B))); + } + + @Test + @DisplayName( + "1 CategoryOptionGroup with refs to source & target CategoryOption results in CategoryOptionGroup with just target") + void catOptGroupSourceAndTargetRefsTest() throws ConflictException { + // given + CategoryOptionGroup cog = createCategoryOptionGroup('g'); + cog.addCategoryOption(coSource1A); + cog.addCategoryOption(coSource2B); + cog.addCategoryOption(coTarget3A); + manager.save(cog); + + // confirm cat option combo state before merge + List sourceCogsBefore = + categoryService.getCategoryOptionGroupByCategoryOption( + List.of(UID.of(coSource1A), UID.of(coSource2B))); + List targetCogsBefore = + categoryService.getCategoryOptionGroupByCategoryOption(List.of(UID.of(coTarget3A))); + + assertEquals(1, sourceCogsBefore.size(), "Expect 1 entry with source category option ref"); + assertEquals(1, targetCogsBefore.size(), "Expect 1 entry with target category option ref"); + + // when + MergeParams mergeParams = getMergeParams(); + MergeReport report = categoryOptionMergeService.processMerge(mergeParams); + + // then + List sourceCogs = + categoryService.getCategoryOptionGroupByCategoryOption( + List.of(UID.of(coSource1A), UID.of(coSource2B))); + List targetCogs = + categoryService.getCategoryOptionGroupByCategoryOption(List.of(UID.of(coTarget3A))); + List allCategoryOptions = categoryService.getAllCategoryOptions(); + List catOptions = + targetCogs.stream().flatMap(catOptGroup -> catOptGroup.getMembers().stream()).toList(); + + assertFalse(report.hasErrorMessages()); + assertEquals(0, sourceCogs.size(), "Expect 0 entries with source category option refs"); + assertEquals(1, targetCogs.size(), "Expect 1 entry with target category option ref"); + assertEquals(1, catOptions.size()); + assertTrue(catOptions.contains(coTarget3A)); + assertEquals(9, allCategoryOptions.size(), "Expect 9 category options present"); + assertTrue(allCategoryOptions.containsAll(List.of(coTarget3A, coSource1A, coSource2B))); + } + + // ----------------------------- + // ----- Category Dimension ---- + // ----------------------------- + @Test + @DisplayName("CategoryDimension refs to source CategoryOptions are replaced, sources not deleted") + void catDimensionSourcesNotDeletedTest() throws ConflictException { + // given + CategoryDimension cd1 = createCategoryDimension(cat1); + cd1.getItems().add(coSource1A); + + CategoryDimension cd2 = createCategoryDimension(cat2); + cd2.getItems().add(coSource2B); + + CategoryDimension cd3 = createCategoryDimension(cat3); + cd3.getItems().add(coTarget3A); + + CategoryDimension cd4 = createCategoryDimension(cat4); + cd4.getItems().add(co3B); + + dimensionStore.save(cd1); + dimensionStore.save(cd2); + dimensionStore.save(cd3); + dimensionStore.save(cd4); + + // when + MergeParams mergeParams = getMergeParams(); + MergeReport report = categoryOptionMergeService.processMerge(mergeParams); + + // then + List cogSources = + dimensionStore.getByCategoryOption(List.of(coSource1A.getUid(), coSource2B.getUid())); + List cogTarget = + dimensionStore.getByCategoryOption(List.of(coTarget3A.getUid())); + List allCategoryOptions = categoryService.getAllCategoryOptions(); + + assertFalse(report.hasErrorMessages()); + assertEquals(0, cogSources.size(), "Expect 0 entries with source category dimension refs"); + assertEquals(3, cogTarget.size(), "Expect 3 entries with target category dimension refs"); + + assertEquals(9, allCategoryOptions.size(), "Expect 9 category options present"); + assertTrue(allCategoryOptions.containsAll(List.of(coTarget3A, coSource1A, coSource2B))); + } + + @Test + @DisplayName("CategoryDimension refs to source CategoryOptions are replaced, sources deleted") + void catDimensionSourcesDeletedTest() throws ConflictException { + // given + CategoryDimension cd1 = createCategoryDimension(cat1); + cd1.getItems().add(coSource1A); + + CategoryDimension cd2 = createCategoryDimension(cat2); + cd2.getItems().add(coSource2B); + + CategoryDimension cd3 = createCategoryDimension(cat3); + cd3.getItems().add(coTarget3A); + + CategoryDimension cd4 = createCategoryDimension(cat4); + cd4.getItems().add(co3B); + + dimensionStore.save(cd1); + dimensionStore.save(cd2); + dimensionStore.save(cd3); + dimensionStore.save(cd4); + + // when + MergeParams mergeParams = getMergeParams(); + mergeParams.setDeleteSources(true); + MergeReport report = categoryOptionMergeService.processMerge(mergeParams); + + // then + List cdSources = + dimensionStore.getByCategoryOption(List.of(coSource1A.getUid(), coSource2B.getUid())); + List cdTarget = + dimensionStore.getByCategoryOption(List.of(coTarget3A.getUid())); + List allCategoryOptions = categoryService.getAllCategoryOptions(); + + assertFalse(report.hasErrorMessages()); + assertEquals(0, cdSources.size(), "Expect 0 entries with source category dimension refs"); + assertEquals(3, cdTarget.size(), "Expect 3 entries with target category dimension refs"); + + assertEquals(7, allCategoryOptions.size(), "Expect 7 category options present"); + assertTrue(allCategoryOptions.contains(coTarget3A)); + assertFalse(allCategoryOptions.containsAll(List.of(coSource1A, coSource2B))); + } + + private MergeParams getMergeParams() { + MergeParams mergeParams = new MergeParams(); + mergeParams.setSources(UID.of(List.of(coSource1A.getUid(), coSource2B.getUid()))); + mergeParams.setTarget(UID.of(coTarget3A.getUid())); + return mergeParams; + } +} diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/merge/dataelement/DataElementMergeProcessorTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/merge/dataelement/DataElementMergeServiceTest.java similarity index 94% rename from dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/merge/dataelement/DataElementMergeProcessorTest.java rename to dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/merge/dataelement/DataElementMergeServiceTest.java index 330ca4e3c7a2..da9b724bf67b 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/merge/dataelement/DataElementMergeProcessorTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/merge/dataelement/DataElementMergeServiceTest.java @@ -77,6 +77,7 @@ import org.hisp.dhis.mapping.MapView; import org.hisp.dhis.merge.DataMergeStrategy; import org.hisp.dhis.merge.MergeParams; +import org.hisp.dhis.merge.MergeService; import org.hisp.dhis.minmax.MinMaxDataElement; import org.hisp.dhis.minmax.MinMaxDataElementStore; import org.hisp.dhis.organisationunit.OrganisationUnit; @@ -135,11 +136,11 @@ */ @TestInstance(Lifecycle.PER_CLASS) @Transactional -class DataElementMergeProcessorTest extends PostgresIntegrationTestBase { +class DataElementMergeServiceTest extends PostgresIntegrationTestBase { @Autowired private DataElementService dataElementService; @Autowired private PeriodService periodService; - @Autowired private DataElementMergeProcessor mergeProcessor; + @Autowired private MergeService dataElementMergeService; @Autowired private MinMaxDataElementStore minMaxDataElementStore; @Autowired private EventVisualizationStore eventVisualizationStore; @Autowired private AnalyticalObjectStore analyticalEventVizStore; @@ -165,7 +166,7 @@ class DataElementMergeProcessorTest extends PostgresIntegrationTestBase { @Autowired private DataDimensionItemStore dataDimensionItemStore; @Autowired private DataValueStore dataValueStore; @Autowired private DataValueAuditStore dataValueAuditStore; - @Autowired private EventChangeLogService teDataValueChangeLogService; + @Autowired private EventChangeLogService eventChangeLogService; private DataElement deSource1; private DataElement deSource2; @@ -247,7 +248,7 @@ void minMaxDataElementMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List minMaxSources = @@ -282,7 +283,7 @@ void minMaxDataElementMergeDeleteSourcesTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List minMaxSources = @@ -318,7 +319,7 @@ void eventVisualizationMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List eventVizSources = @@ -353,7 +354,7 @@ void eventVisualizationMergeDeleteSourcesTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List eventVizSources = @@ -422,7 +423,7 @@ void trackedEntityDataElDimMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then // event viz @@ -498,7 +499,7 @@ void trackedEntityDataElDimMergeSourcesDeletedTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then // event viz @@ -542,7 +543,7 @@ void smsCodeMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List smsCommandSources = @@ -574,7 +575,7 @@ void smsCodesMergeDeleteSourcesTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List smsCommandSources = @@ -605,7 +606,7 @@ void predictorMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List predictorSources = @@ -638,7 +639,7 @@ void predictorMergeDeleteSourcesTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List predictorSources = @@ -665,7 +666,7 @@ void predictorGeneratorMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List predictorSources = @@ -696,7 +697,7 @@ void predictorGeneratorMergeSourcesDeletedTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List predictorSources = @@ -725,7 +726,7 @@ void predictorSampleSkipTestMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List predictorSources = @@ -757,7 +758,7 @@ void predictorSampleSkipTestMergeSourcesDeletedTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List predictorSources = @@ -795,7 +796,7 @@ void programStageDEMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List psdeSources = @@ -829,7 +830,7 @@ void programStageDEMergeDeleteSourcesTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List psdeSources = @@ -864,7 +865,7 @@ void programStageSectionMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List pssSources = @@ -897,7 +898,7 @@ void programStageSectionMergeDeleteSourcesTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List pssSources = @@ -932,7 +933,7 @@ void programNotificationTemplateMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List pntSources = @@ -965,7 +966,7 @@ void programNotificationTemplateDeleteSourcesMergeTest() throws ConflictExceptio mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List pntSources = @@ -1000,7 +1001,7 @@ void programRuleVariableMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List prvSources = @@ -1033,7 +1034,7 @@ void programRuleVariableSourcesDeletedMergeTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List prvSources = @@ -1068,7 +1069,7 @@ void programRuleActionMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List prvSources = @@ -1100,7 +1101,7 @@ void programRuleActionSourcesDeletedMergeTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List prvSources = @@ -1134,7 +1135,7 @@ void programIndicatorExpressionMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List piSources = @@ -1175,7 +1176,7 @@ void programIndicatorExpressionMergeSourcesDeletedTest() throws ConflictExceptio mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List piSources = @@ -1211,7 +1212,7 @@ void programIndicatorFilterMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List piSources = @@ -1245,7 +1246,7 @@ void programIndicatorFilterMergeSourcesDeletedTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List piSources = @@ -1316,7 +1317,7 @@ void eventMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List eventSources = @@ -1402,7 +1403,7 @@ void eventMergeDiscardTest() throws ConflictException { mergeParams.setDataMergeStrategy(DataMergeStrategy.DISCARD); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List eventSources = @@ -1495,7 +1496,7 @@ void eventMergeSourcesDeletedTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List eventSources = @@ -1549,7 +1550,7 @@ void dataElementOperandMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List deoSources = @@ -1582,7 +1583,7 @@ void dataElementOperandSourcesDeletedMergeTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List deoSources = @@ -1632,7 +1633,7 @@ void dataSetElementTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List dseSources = @@ -1694,7 +1695,7 @@ void dataSetElementDeleteSourcesTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List dseSources = @@ -1751,11 +1752,12 @@ void programSectionMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then - List

sectionSources = sectionStore.getByDataElement(List.of(deSource1, deSource2)); - List
sectionTarget = sectionStore.getByDataElement(List.of(deTarget)); + List
sectionSources = + sectionStore.getSectionsByDataElement(List.of(deSource1, deSource2)); + List
sectionTarget = sectionStore.getSectionsByDataElement(List.of(deTarget)); List allDataElements = dataElementService.getAllDataElements(); assertMergeSuccessfulSourcesNotDeleted(report, sectionSources, sectionTarget, allDataElements); @@ -1780,11 +1782,12 @@ void programSectionMergeDeleteSourcesTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then - List
sectionSources = sectionStore.getByDataElement(List.of(deSource1, deSource2)); - List
sectionTarget = sectionStore.getByDataElement(List.of(deTarget)); + List
sectionSources = + sectionStore.getSectionsByDataElement(List.of(deSource1, deSource2)); + List
sectionTarget = sectionStore.getSectionsByDataElement(List.of(deTarget)); List allDataElements = dataElementService.getAllDataElements(); assertMergeSuccessfulSourcesDeleted(report, sectionSources, sectionTarget, allDataElements); @@ -1812,7 +1815,7 @@ void dataElementGroupMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List degSources = @@ -1843,7 +1846,7 @@ void dataElementGroupMergeSourceDeletedTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List degSources = @@ -1879,7 +1882,7 @@ void indicatorNumeratorMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List sourceIndicators1 = @@ -1925,7 +1928,7 @@ void indicatorNumeratorMergeSourcesDeletedTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List sourceIndicators1 = @@ -1966,7 +1969,7 @@ void indicatorDenominatorMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List sourceIndicators1 = @@ -2005,7 +2008,7 @@ void indicatorDenominatorMergeSourcesDeletedTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List sourceIndicators1 = @@ -2050,7 +2053,7 @@ void indicatorNumeratorDenominatorMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then Set sourceIndicators1 = @@ -2101,7 +2104,7 @@ void indicatorNumeratorDenominatorMergeSourcesDeletedTest() throws ConflictExcep mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then Set sourceIndicators1 = @@ -2143,7 +2146,7 @@ void formHtmlMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then Set sourceForms = @@ -2185,7 +2188,7 @@ void formHtmlMergeSourceDeletedTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then Set sourceForms = @@ -2225,7 +2228,7 @@ void dataDimItemMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List sourceItems = @@ -2258,7 +2261,7 @@ void dataDimItemMergeSourcesDeletedTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List sourceItems = @@ -2301,7 +2304,7 @@ void dataValueMergeLastUpdatedTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List sourceItems = @@ -2338,7 +2341,7 @@ void duplicateDataValueMergeLastUpdatedTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then there should be no source data values present List sourceItems = @@ -2387,7 +2390,7 @@ void dataValueMergeDiscardTest() throws ConflictException { mergeParams.setDataMergeStrategy(DataMergeStrategy.DISCARD); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List sourceItems = @@ -2431,7 +2434,7 @@ void dataValueMergeSourcesDeletedTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then List sourceItems = @@ -2471,7 +2474,7 @@ void dataValueAuditMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then DataValueAuditQueryParams sourceDvaQueryParams = @@ -2517,7 +2520,7 @@ void dataValueAuditMergeDeleteTest() throws ConflictException { mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then DataValueAuditQueryParams sourceDvaQueryParams = @@ -2562,17 +2565,17 @@ void trackedEntityDataValueChangeLogMergeTest() throws ConflictException { TrackedEntityDataValueChangeLog tedvcl4 = createTrackedEntityDataValueAudit(e, deSource2, "2"); TrackedEntityDataValueChangeLog tedvcl5 = createTrackedEntityDataValueAudit(e, deTarget, "1"); - teDataValueChangeLogService.addTrackedEntityDataValueChangeLog(tedvcl1); - teDataValueChangeLogService.addTrackedEntityDataValueChangeLog(tedvcl2); - teDataValueChangeLogService.addTrackedEntityDataValueChangeLog(tedvcl3); - teDataValueChangeLogService.addTrackedEntityDataValueChangeLog(tedvcl4); - teDataValueChangeLogService.addTrackedEntityDataValueChangeLog(tedvcl5); + eventChangeLogService.addTrackedEntityDataValueChangeLog(tedvcl1); + eventChangeLogService.addTrackedEntityDataValueChangeLog(tedvcl2); + eventChangeLogService.addTrackedEntityDataValueChangeLog(tedvcl3); + eventChangeLogService.addTrackedEntityDataValueChangeLog(tedvcl4); + eventChangeLogService.addTrackedEntityDataValueChangeLog(tedvcl5); // params MergeParams mergeParams = getMergeParams(); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then TrackedEntityDataValueChangeLogQueryParams sourceTeDvChangeLogQuery = @@ -2581,9 +2584,9 @@ void trackedEntityDataValueChangeLogMergeTest() throws ConflictException { getTeQueryParams(e, List.of(deTarget)); List sourceAudits = - teDataValueChangeLogService.getTrackedEntityDataValueChangeLogs(sourceTeDvChangeLogQuery); + eventChangeLogService.getTrackedEntityDataValueChangeLogs(sourceTeDvChangeLogQuery); List targetAudits = - teDataValueChangeLogService.getTrackedEntityDataValueChangeLogs(targeteDvChangeLogQuery); + eventChangeLogService.getTrackedEntityDataValueChangeLogs(targeteDvChangeLogQuery); List allDataElements = dataElementService.getAllDataElements(); @@ -2615,18 +2618,18 @@ void trackedEntityDataValueChangeLogMergeDeletedTest() throws ConflictException TrackedEntityDataValueChangeLog tedvcl4 = createTrackedEntityDataValueAudit(e, deSource2, "2"); TrackedEntityDataValueChangeLog tedvcl5 = createTrackedEntityDataValueAudit(e, deTarget, "1"); - teDataValueChangeLogService.addTrackedEntityDataValueChangeLog(tedvcl1); - teDataValueChangeLogService.addTrackedEntityDataValueChangeLog(tedvcl2); - teDataValueChangeLogService.addTrackedEntityDataValueChangeLog(tedvcl3); - teDataValueChangeLogService.addTrackedEntityDataValueChangeLog(tedvcl4); - teDataValueChangeLogService.addTrackedEntityDataValueChangeLog(tedvcl5); + eventChangeLogService.addTrackedEntityDataValueChangeLog(tedvcl1); + eventChangeLogService.addTrackedEntityDataValueChangeLog(tedvcl2); + eventChangeLogService.addTrackedEntityDataValueChangeLog(tedvcl3); + eventChangeLogService.addTrackedEntityDataValueChangeLog(tedvcl4); + eventChangeLogService.addTrackedEntityDataValueChangeLog(tedvcl5); // params MergeParams mergeParams = getMergeParams(); mergeParams.setDeleteSources(true); // when - MergeReport report = mergeProcessor.processMerge(mergeParams); + MergeReport report = dataElementMergeService.processMerge(mergeParams); // then TrackedEntityDataValueChangeLogQueryParams sourceTeDvChangeLogQuery = @@ -2635,9 +2638,9 @@ void trackedEntityDataValueChangeLogMergeDeletedTest() throws ConflictException getTeQueryParams(e, List.of(deTarget)); List sourceAudits = - teDataValueChangeLogService.getTrackedEntityDataValueChangeLogs(sourceTeDvChangeLogQuery); + eventChangeLogService.getTrackedEntityDataValueChangeLogs(sourceTeDvChangeLogQuery); List targetAudits = - teDataValueChangeLogService.getTrackedEntityDataValueChangeLogs(targeteDvChangeLogQuery); + eventChangeLogService.getTrackedEntityDataValueChangeLogs(targeteDvChangeLogQuery); List allDataElements = dataElementService.getAllDataElements(); diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/indicator/IndicatorMergeProcessorTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/merge/indicator/IndicatorMergeServiceTest.java similarity index 92% rename from dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/indicator/IndicatorMergeProcessorTest.java rename to dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/merge/indicator/IndicatorMergeServiceTest.java index cbb0e3a75438..a26a65d8bc74 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/indicator/IndicatorMergeProcessorTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/merge/indicator/IndicatorMergeServiceTest.java @@ -25,7 +25,7 @@ * (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.indicator; +package org.hisp.dhis.merge.indicator; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -48,9 +48,11 @@ import org.hisp.dhis.feedback.ConflictException; import org.hisp.dhis.feedback.ErrorMessage; import org.hisp.dhis.feedback.MergeReport; +import org.hisp.dhis.indicator.Indicator; +import org.hisp.dhis.indicator.IndicatorGroup; +import org.hisp.dhis.indicator.IndicatorType; import org.hisp.dhis.merge.MergeParams; -import org.hisp.dhis.merge.MergeProcessor; -import org.hisp.dhis.merge.MergeType; +import org.hisp.dhis.merge.MergeService; import org.hisp.dhis.period.PeriodType; import org.hisp.dhis.period.PeriodTypeEnum; import org.hisp.dhis.test.integration.PostgresIntegrationTestBase; @@ -62,8 +64,8 @@ /** * @author david mackessy */ -class IndicatorMergeProcessorTest extends PostgresIntegrationTestBase { - @Autowired private MergeProcessor indicatorMergeProcessor; +class IndicatorMergeServiceTest extends PostgresIntegrationTestBase { + @Autowired private MergeService indicatorMergeService; @Autowired private IdentifiableObjectManager manager; @Autowired private ConfigurationService configService; @@ -91,18 +93,17 @@ void mergeWithNoSourcesTest() { MergeParams params = new MergeParams(); params.setSources(Set.of()); params.setTarget(UID.of(validTarget.getUid())); - params.setMergeType(MergeType.INDICATOR); // when a merge request is processed ConflictException conflictException = - assertThrows(ConflictException.class, () -> indicatorMergeProcessor.processMerge(params)); + assertThrows(ConflictException.class, () -> indicatorMergeService.processMerge(params)); // then the merge report has the correct error info MergeReport mergeReport = conflictException.getMergeReport(); List list = mergeReport.getMergeErrors().stream().map(ErrorMessage::getMessage).toList(); assertEquals(1, list.size()); - assertTrue(list.contains("At least one source indicator must be specified")); + assertTrue(list.contains("At least one source Indicator must be specified")); } @Test @@ -117,18 +118,17 @@ void mergeWithInvalidTargetTest() { MergeParams params = new MergeParams(); params.setSources(Set.of(UID.of(validSource1.getUid()))); params.setTarget(UID.of("Uid00000011")); - params.setMergeType(MergeType.INDICATOR); // when a merge request is processed ConflictException conflictException = - assertThrows(ConflictException.class, () -> indicatorMergeProcessor.processMerge(params)); + assertThrows(ConflictException.class, () -> indicatorMergeService.processMerge(params)); // then the merge report has the correct error info MergeReport mergeReport = conflictException.getMergeReport(); List list = mergeReport.getMergeErrors().stream().map(ErrorMessage::getMessage).toList(); assertEquals(1, list.size()); - assertTrue(list.contains("TARGET indicator does not exist: `Uid00000011`")); + assertTrue(list.contains("TARGET Indicator does not exist: `Uid00000011`")); } @Test @@ -145,18 +145,17 @@ void mergeWithInvalidSourceTest() { MergeParams params = new MergeParams(); params.setSources(UID.of(validSource1.getUid(), "Uid00000011")); params.setTarget(UID.of(validTarget.getUid())); - params.setMergeType(MergeType.INDICATOR); // when a merge request is processed ConflictException conflictException = - assertThrows(ConflictException.class, () -> indicatorMergeProcessor.processMerge(params)); + assertThrows(ConflictException.class, () -> indicatorMergeService.processMerge(params)); // then the merge report has the correct error info MergeReport mergeReport = conflictException.getMergeReport(); List list = mergeReport.getMergeErrors().stream().map(ErrorMessage::getMessage).toList(); assertEquals(1, list.size()); - assertTrue(list.contains("SOURCE indicator does not exist: `Uid00000011`")); + assertTrue(list.contains("SOURCE Indicator does not exist: `Uid00000011`")); } @Test @@ -171,11 +170,10 @@ void mergeWithTargetAsSourceTest() { MergeParams params = new MergeParams(); params.setSources(Set.of(UID.of(validTarget.getUid()))); params.setTarget(UID.of(validTarget.getUid())); - params.setMergeType(MergeType.INDICATOR); // when a merge request is processed ConflictException conflictException = - assertThrows(ConflictException.class, () -> indicatorMergeProcessor.processMerge(params)); + assertThrows(ConflictException.class, () -> indicatorMergeService.processMerge(params)); // then the merge report has the correct error info MergeReport mergeReport = conflictException.getMergeReport(); @@ -183,7 +181,7 @@ void mergeWithTargetAsSourceTest() { mergeReport.getMergeErrors().stream().map(ErrorMessage::getMessage).toList(); assertEquals(1, list.size()); - assertTrue(list.contains("Target indicator cannot be a source indicator")); + assertTrue(list.contains("Target Indicator cannot be a source Indicator")); } @Test @@ -198,18 +196,17 @@ void mergeWithNoTargetTest() { MergeParams params = new MergeParams(); params.setSources(Set.of(UID.of(validTarget.getUid()))); params.setTarget(null); - params.setMergeType(MergeType.INDICATOR); // when a merge request is processed ConflictException conflictException = - assertThrows(ConflictException.class, () -> indicatorMergeProcessor.processMerge(params)); + assertThrows(ConflictException.class, () -> indicatorMergeService.processMerge(params)); // then the merge report has the correct error info MergeReport mergeReport = conflictException.getMergeReport(); List list = mergeReport.getMergeErrors().stream().map(ErrorMessage::getMessage).toList(); assertEquals(1, list.size()); - assertTrue(list.contains("Target indicator must be specified")); + assertTrue(list.contains("Target Indicator must be specified")); } @Test @@ -222,10 +219,9 @@ void validMergeTest() throws ConflictException { params.setSources(UID.of(validSource1.getUid(), validSource2.getUid())); params.setTarget(UID.of(validTarget.getUid())); params.setDeleteSources(true); - params.setMergeType(MergeType.INDICATOR); // when a merge request is processed - MergeReport report = indicatorMergeProcessor.processMerge(params); + MergeReport report = indicatorMergeService.processMerge(params); // then the merge report has the correct error info assertFalse(report.hasErrorMessages()); diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/merge/indicator/IndicatorTypeMergeServiceTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/merge/indicator/IndicatorTypeMergeServiceTest.java index edf97a3870e3..cf7a3a73b71c 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/merge/indicator/IndicatorTypeMergeServiceTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/merge/indicator/IndicatorTypeMergeServiceTest.java @@ -46,7 +46,6 @@ import org.hisp.dhis.merge.MergeParams; import org.hisp.dhis.merge.MergeRequest; import org.hisp.dhis.merge.MergeService; -import org.hisp.dhis.merge.MergeType; import org.hisp.dhis.test.integration.PostgresIntegrationTestBase; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -96,7 +95,7 @@ void testGetFromParams() { params.setSources(Set.of(uidA, uidB)); params.setTarget(uidC); params.setDeleteSources(true); - MergeReport mergeReport = new MergeReport(MergeType.INDICATOR_TYPE); + MergeReport mergeReport = new MergeReport(); // when MergeRequest request = indicatorTypeMergeService.validate(params, mergeReport); @@ -115,7 +114,7 @@ void testGetFromParams() { void testGetFromParamsWithErrors() { // given MergeParams params = new MergeParams(); - MergeReport mergeReport = new MergeReport(MergeType.INDICATOR_TYPE); + MergeReport mergeReport = new MergeReport(); // when MergeRequest request = indicatorTypeMergeService.validate(params, mergeReport); @@ -127,8 +126,8 @@ void testGetFromParamsWithErrors() { assertMatchesErrorMessages( mergeReport, Set.of( - "At least one source indicator type must be specified", - "Target indicator type must be specified")); + "At least one source IndicatorType must be specified", + "Target IndicatorType must be specified")); } @Test @@ -139,7 +138,7 @@ void testSourceNotFound() { MergeParams params = new MergeParams(); params.setSources(Set.of(uidA, uidX)); params.setTarget(uidC); - MergeReport mergeReport = new MergeReport(MergeType.INDICATOR_TYPE); + MergeReport mergeReport = new MergeReport(); // when MergeRequest request = indicatorTypeMergeService.validate(params, mergeReport); @@ -150,7 +149,7 @@ void testSourceNotFound() { assertTrue(mergeReport.hasErrorMessages()); assertMatchesErrorCodes(mergeReport, Set.of(ErrorCode.E1533)); assertMatchesErrorMessages( - mergeReport, Set.of("SOURCE indicator type does not exist: `IntY123abgX`")); + mergeReport, Set.of("SOURCE IndicatorType does not exist: `IntY123abgX`")); } @Test @@ -161,7 +160,7 @@ void testTargetNotFound() { MergeParams params = new MergeParams(); params.setSources(Set.of(uidA, uidB)); params.setTarget(uidX); - MergeReport mergeReport = new MergeReport(MergeType.INDICATOR_TYPE); + MergeReport mergeReport = new MergeReport(); // when MergeRequest request = indicatorTypeMergeService.validate(params, mergeReport); @@ -171,7 +170,7 @@ void testTargetNotFound() { assertTrue(mergeReport.hasErrorMessages()); assertMatchesErrorCodes(mergeReport, Set.of(ErrorCode.E1533)); assertMatchesErrorMessages( - mergeReport, Set.of("TARGET indicator type does not exist: `IntY123abgX`")); + mergeReport, Set.of("TARGET IndicatorType does not exist: `IntY123abgX`")); } @Test @@ -197,7 +196,7 @@ void testValidate() { params.setDeleteSources(true); // when an indicator merge request is validated - MergeReport mergeReport = new MergeReport(MergeType.INDICATOR_TYPE); + MergeReport mergeReport = new MergeReport(); indicatorTypeMergeService.validate(params, mergeReport); // then @@ -226,7 +225,7 @@ void testValidateWithErrorNoSources() { params.setDeleteSources(true); // when an indicator merge request is validated - MergeReport mergeReport = new MergeReport(MergeType.INDICATOR_TYPE); + MergeReport mergeReport = new MergeReport(); MergeRequest validatedRequest = indicatorTypeMergeService.validate(params, mergeReport); // then @@ -240,7 +239,7 @@ void testValidateWithErrorNoSources() { assertTrue(validatedRequest.isDeleteSources()); assertMatchesErrorCodes(mergeReport, Set.of(ErrorCode.E1530)); assertMatchesErrorMessages( - mergeReport, Set.of("At least one source indicator type must be specified")); + mergeReport, Set.of("At least one source IndicatorType must be specified")); } @Test @@ -262,7 +261,7 @@ void testValidateWithErrorNoTarget() { params.setDeleteSources(true); // when an indicator merge request is validated - MergeReport mergeReport = new MergeReport(MergeType.INDICATOR_TYPE); + MergeReport mergeReport = new MergeReport(); MergeRequest validatedRequest = indicatorTypeMergeService.validate(params, mergeReport); // then @@ -274,7 +273,7 @@ void testValidateWithErrorNoTarget() { assertTrue(mergeReport.hasErrorMessages()); assertRequestIsEmpty(validatedRequest); assertMatchesErrorCodes(mergeReport, Set.of(ErrorCode.E1531)); - assertMatchesErrorMessages(mergeReport, Set.of("Target indicator type must be specified")); + assertMatchesErrorMessages(mergeReport, Set.of("Target IndicatorType must be specified")); } @Test @@ -298,7 +297,7 @@ void testValidMergeDeleteSources() { MergeRequest.builder().sources(Set.of(uidA, uidB)).target(uidC).deleteSources(true).build(); // when an indicator merge request is merged - MergeReport mergeReport = new MergeReport(MergeType.INDICATOR_TYPE); + MergeReport mergeReport = new MergeReport(); MergeReport completeReport = indicatorTypeMergeService.merge(request, mergeReport); // then @@ -345,7 +344,7 @@ void testValidMergeKeepSources() { .build(); // when an indicator merge request is merged - MergeReport mergeReport = new MergeReport(MergeType.INDICATOR_TYPE); + MergeReport mergeReport = new MergeReport(); MergeReport completeReport = indicatorTypeMergeService.merge(request, mergeReport); // then diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/message/MessageConversationStoreTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/message/MessageConversationStoreTest.java index c741e08ab594..1348f2e5ad44 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/message/MessageConversationStoreTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/message/MessageConversationStoreTest.java @@ -34,6 +34,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import org.hisp.dhis.common.UID; import org.hisp.dhis.test.integration.PostgresIntegrationTestBase; import org.hisp.dhis.user.User; import org.junit.jupiter.api.BeforeAll; @@ -76,7 +77,7 @@ void setUp() { conversationIds = new HashSet<>(); conversationA = messageService.sendPrivateMessage(usersA, "Subject1", "Text", "Meta", null); MessageConversation mc = messageService.getMessageConversation(conversationA); - mc.markRead(userC); + mc.markRead(UID.of(userC.getUid())); messageService.updateMessageConversation(mc); conversationIds.add(mc.getUid()); messageService.sendReply(mc, "Message 1", "Meta", false, null); diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/notification/ProgramNotificationMessageRendererTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/notification/ProgramNotificationMessageRendererTest.java index 0fe5d90ed056..337ae87844f9 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/notification/ProgramNotificationMessageRendererTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/notification/ProgramNotificationMessageRendererTest.java @@ -38,6 +38,7 @@ import org.hisp.dhis.common.CodeGenerator; import org.hisp.dhis.common.DeliveryChannel; import org.hisp.dhis.common.IdentifiableObjectManager; +import org.hisp.dhis.common.UID; import org.hisp.dhis.common.ValueType; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.dataelement.DataElementDomain; @@ -86,7 +87,7 @@ class ProgramNotificationMessageRendererTest extends PostgresIntegrationTestBase private String orgUnitUid = CodeGenerator.generateUid(); - private String enrollmentUid = CodeGenerator.generateUid(); + private UID enrollmentUid = UID.generate(); private String trackedEntityUid = CodeGenerator.generateUid(); @@ -213,7 +214,7 @@ void setUp() { enrollmentA = createEnrollment(programA, trackedEntityA, organisationUnitA); enrollmentA.setEnrollmentDate(enrollmentDate); enrollmentA.setOccurredDate(occurredDate); - enrollmentA.setUid(enrollmentUid); + enrollmentA.setUid(enrollmentUid.getValue()); manager.save(enrollmentA); trackedEntityA.getEnrollments().add(enrollmentA); manager.update(trackedEntityA); diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/organisationunit/OrganisationUnitStoreIntegrationTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/organisationunit/OrganisationUnitStoreIntegrationTest.java index af7febbfe0a7..ba7a24aef38e 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/organisationunit/OrganisationUnitStoreIntegrationTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/organisationunit/OrganisationUnitStoreIntegrationTest.java @@ -30,16 +30,19 @@ import static org.hisp.dhis.organisationunit.FeatureType.POINT; import static org.hisp.dhis.test.utils.Assertions.assertContainsOnly; 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 java.io.IOException; import java.util.List; +import org.hisp.dhis.category.CategoryOption; import org.hisp.dhis.common.IdentifiableObjectManager; import org.hisp.dhis.dataset.DataSet; import org.hisp.dhis.period.PeriodType; import org.hisp.dhis.program.Program; import org.hisp.dhis.system.util.GeoUtils; import org.hisp.dhis.test.integration.PostgresIntegrationTestBase; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.locationtech.jts.geom.Geometry; import org.springframework.beans.factory.annotation.Autowired; @@ -159,6 +162,42 @@ void verifyGetOrgUnitsWithinAGeoBox() throws IOException { assertContainsOnly(List.of(ouA, ouB, ouC, ouD), ous); } + @Test + @DisplayName("Getting OrgUnits by CategoryOption returns the correct result set") + void getOrgUnitsByCategoryOptionTest() { + // given 2 org units have refs to category options + CategoryOption co1 = createCategoryOption('1'); + CategoryOption co2 = createCategoryOption('2'); + CategoryOption co3 = createCategoryOption('3'); + manager.save(List.of(co1, co2, co3)); + + OrganisationUnit ou1 = createOrganisationUnit('x'); + ou1.addCategoryOption(co1); + ou1.addCategoryOption(co2); + + OrganisationUnit ou2 = createOrganisationUnit('y'); + ou2.addCategoryOption(co3); + + // no cat option refs + OrganisationUnit ou3 = createOrganisationUnit('z'); + + manager.save(List.of(ou1, ou2, ou3)); + + // when + List organisationUnits = + organisationUnitStore.getByCategoryOption( + List.of(co1.getUid(), co2.getUid(), co3.getUid())); + + // then + assertEquals(2, organisationUnits.size()); + assertTrue( + organisationUnits.stream() + .flatMap(ou -> ou.getCategoryOptions().stream()) + .toList() + .containsAll(List.of(co1, co2, co3))); + assertFalse(organisationUnits.contains(ou3)); + } + private List getOUsFromPointToDistance(Geometry point, long distance) { double[] box = GeoUtils.getBoxShape(point.getCoordinate().x, point.getCoordinate().y, distance); return organisationUnitStore.getWithinCoordinateArea(box); diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/query/CriteriaQueryEngineTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/query/CriteriaQueryEngineTest.java index 5ea5a8be4a4a..c31bf545a0fd 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/query/CriteriaQueryEngineTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/query/CriteriaQueryEngineTest.java @@ -27,8 +27,6 @@ */ package org.hisp.dhis.query; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -509,9 +507,9 @@ void testIdentifiableSearch7() { @Test void testUnicodeSearch() { addDataElement('U', "Кириллица", ValueType.NUMBER, "2021"); - Query query = - queryService.getQueryFromUrl( - DataElement.class, singletonList("identifiable:token:Кири"), emptyList()); + GetObjectListParams params = + new GetObjectListParams().setPaging(false).setFilters(List.of("identifiable:token:Кири")); + Query query = queryService.getQueryFromUrl(DataElement.class, params); List matches = queryService.query(query); assertEquals(1, matches.size()); assertEquals("Кириллица", matches.get(0).getName()); diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/query/QueryServiceTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/query/QueryServiceTest.java index faab9e68b762..8a401834d99c 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/query/QueryServiceTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/query/QueryServiceTest.java @@ -39,6 +39,8 @@ import java.util.Date; import java.util.List; import java.util.stream.Collectors; +import org.hibernate.Session; +import org.hibernate.stat.Statistics; import org.hisp.dhis.common.IdentifiableObject; import org.hisp.dhis.common.IdentifiableObjectManager; import org.hisp.dhis.common.ValueType; @@ -117,9 +119,8 @@ void getAllQuery() { @Test void getAllQueryUrl() throws QueryParserException { - Query query = - queryService.getQueryFromUrl( - DataElement.class, Lists.newArrayList(), Lists.newArrayList(), new Pagination()); + GetObjectListParams params = new GetObjectListParams().setPaging(false); + Query query = queryService.getQueryFromUrl(DataElement.class, params); assertEquals(6, queryService.query(query).size()); } @@ -145,10 +146,8 @@ void getAllQueryUrlWithPaginationReturnsNoDataWhenPageNumberHigherThanMaxNumberO } private List getResultWithPagination(int page, int pageSize) { - Pagination pagination = new Pagination(page, pageSize); - Query query = - queryService.getQueryFromUrl( - DataElement.class, Lists.newArrayList(), Lists.newArrayList(), pagination); + GetObjectListParams params = new GetObjectListParams().setPage(page).setPageSize(pageSize); + Query query = queryService.getQueryFromUrl(DataElement.class, params); return queryService.query(query); } @@ -175,12 +174,9 @@ void getEqQuery() { @Test void getEqQueryUrl() throws QueryParserException { - Query query = - queryService.getQueryFromUrl( - DataElement.class, - Lists.newArrayList("id:eq:deabcdefghA"), - Lists.newArrayList(), - new Pagination()); + GetObjectListParams params = + new GetObjectListParams().setPaging(false).setFilters(List.of("id:eq:deabcdefghA")); + Query query = queryService.getQueryFromUrl(DataElement.class, params); List objects = queryService.query(query); assertEquals(1, objects.size()); assertEquals("deabcdefghA", objects.get(0).getUid()); @@ -231,12 +227,9 @@ void getIlikeQueryNoMatchExtraCharAtStart() { @Test void getIeqQueryUrlMatch() throws QueryParserException { - Query query = - queryService.getQueryFromUrl( - DataElement.class, - Lists.newArrayList("name:ieq:dataelementa"), - Lists.newArrayList(), - new Pagination()); + GetObjectListParams params = + new GetObjectListParams().setPaging(false).setFilters(List.of("name:ieq:dataelementa")); + Query query = queryService.getQueryFromUrl(DataElement.class, params); List objects = queryService.query(query); assertEquals(1, objects.size()); assertEquals("DataElementA", objects.get(0).getName()); @@ -244,12 +237,9 @@ void getIeqQueryUrlMatch() throws QueryParserException { @Test void getIeqQueryUrlMatchMixedCase() throws QueryParserException { - Query query = - queryService.getQueryFromUrl( - DataElement.class, - Lists.newArrayList("name:ieq:dAtAeLeMeNta"), - Lists.newArrayList(), - new Pagination()); + GetObjectListParams params = + new GetObjectListParams().setPaging(false).setFilters(List.of("name:ieq:dAtAeLeMeNta")); + Query query = queryService.getQueryFromUrl(DataElement.class, params); List objects = queryService.query(query); assertEquals(1, objects.size()); assertEquals("DataElementA", objects.get(0).getName()); @@ -257,12 +247,9 @@ void getIeqQueryUrlMatchMixedCase() throws QueryParserException { @Test void getIeqQueryUrlNoMatchExtraCharAtStart() throws QueryParserException { - Query query = - queryService.getQueryFromUrl( - DataElement.class, - Lists.newArrayList("name:ieq:ddataelementa"), - Lists.newArrayList(), - new Pagination()); + GetObjectListParams params = + new GetObjectListParams().setPaging(false).setFilters(List.of("name:ieq:ddataelementa")); + Query query = queryService.getQueryFromUrl(DataElement.class, params); List objects = queryService.query(query); assertEquals(0, objects.size()); } @@ -283,12 +270,9 @@ void getNeQuery() { @Test void getNeQueryUrl() throws QueryParserException { - Query query = - queryService.getQueryFromUrl( - DataElement.class, - Lists.newArrayList("id:ne:deabcdefghA"), - Lists.newArrayList(), - new Pagination()); + GetObjectListParams params = + new GetObjectListParams().setPaging(false).setFilters(List.of("id:ne:deabcdefghA")); + Query query = queryService.getQueryFromUrl(DataElement.class, params); List objects = queryService.query(query); assertEquals(5, objects.size()); assertFalse(collectionContainsUid(objects, "deabcdefghA")); @@ -310,12 +294,9 @@ void getLikeQuery() { @Test void getLikeQueryUrl() throws QueryParserException { - Query query = - queryService.getQueryFromUrl( - DataElement.class, - Lists.newArrayList("name:like:F"), - Lists.newArrayList(), - new Pagination()); + GetObjectListParams params = + new GetObjectListParams().setPaging(false).setFilters(List.of("name:like:F")); + Query query = queryService.getQueryFromUrl(DataElement.class, params); List objects = queryService.query(query); assertEquals(1, objects.size()); assertEquals("deabcdefghF", objects.get(0).getUid()); @@ -334,12 +315,9 @@ void getGtQuery() { @Test void getGtQueryUrl() throws QueryParserException { - Query query = - queryService.getQueryFromUrl( - DataElement.class, - Lists.newArrayList("created:gt:2003"), - Lists.newArrayList(), - new Pagination()); + GetObjectListParams params = + new GetObjectListParams().setPaging(false).setFilters(List.of("created:gt:2003")); + Query query = queryService.getQueryFromUrl(DataElement.class, params); List objects = queryService.query(query); assertEquals(3, objects.size()); assertTrue(collectionContainsUid(objects, "deabcdefghD")); @@ -359,12 +337,9 @@ void getLtQuery() { @Test void getLtQueryUrl() throws QueryParserException { - Query query = - queryService.getQueryFromUrl( - DataElement.class, - Lists.newArrayList("created:lt:2003"), - Lists.newArrayList(), - new Pagination()); + GetObjectListParams params = + new GetObjectListParams().setPaging(false).setFilters(List.of("created:lt:2003")); + Query query = queryService.getQueryFromUrl(DataElement.class, params); List objects = queryService.query(query); assertEquals(2, objects.size()); assertTrue(collectionContainsUid(objects, "deabcdefghA")); @@ -385,12 +360,9 @@ void getGeQuery() { @Test void getGeQueryUrl() throws QueryParserException { - Query query = - queryService.getQueryFromUrl( - DataElement.class, - Lists.newArrayList("created:ge:2003"), - Lists.newArrayList(), - new Pagination()); + GetObjectListParams params = + new GetObjectListParams().setPaging(false).setFilters(List.of("created:ge:2003")); + Query query = queryService.getQueryFromUrl(DataElement.class, params); List objects = queryService.query(query); assertEquals(4, objects.size()); assertTrue(collectionContainsUid(objects, "deabcdefghC")); @@ -412,12 +384,9 @@ void getLeQuery() { @Test void getLeQueryUrl() throws QueryParserException { - Query query = - queryService.getQueryFromUrl( - DataElement.class, - Lists.newArrayList("created:le:2003"), - Lists.newArrayList(), - new Pagination()); + GetObjectListParams params = + new GetObjectListParams().setPaging(false).setFilters(List.of("created:le:2003")); + Query query = queryService.getQueryFromUrl(DataElement.class, params); List objects = queryService.query(query); assertEquals(3, objects.size()); assertTrue(collectionContainsUid(objects, "deabcdefghA")); @@ -585,12 +554,9 @@ void testIsNotNull() { @Test void testIsNotNullUrl() throws QueryParserException { - Query query = - queryService.getQueryFromUrl( - DataElement.class, - Lists.newArrayList("categoryCombo:!null"), - Lists.newArrayList(), - new Pagination()); + GetObjectListParams params = + new GetObjectListParams().setPaging(false).setFilters(List.of("categoryCombo:!null")); + Query query = queryService.getQueryFromUrl(DataElement.class, params); List objects = queryService.query(query); assertEquals(6, objects.size()); assertTrue(collectionContainsUid(objects, "deabcdefghA")); @@ -631,8 +597,22 @@ void testCriteriaAndRootJunctionDEG() { query.add(Restrictions.eq("dataElements.id", "deabcdefghD")); query.add(Restrictions.eq("dataElements.id", "deabcdefghE")); query.add(Restrictions.eq("dataElements.id", "deabcdefghF")); - List objects = queryService.query(query); + Session session = entityManager.unwrap(Session.class); + Statistics statistics = session.getSessionFactory().getStatistics(); + statistics.setStatisticsEnabled(true); + List objects = queryService.query(query); + String[] queries = statistics.getQueries(); + boolean findQuery = false; + for (String q : queries) { + if (q.equals( + "select generatedAlias0 from DataElementGroup as generatedAlias0 inner join generatedAlias0.members as members where ( members.uid=:param0 ) and ( members.uid=:param1 ) and ( members.uid=:param2 ) and ( members.uid=:param3 ) and ( members.uid=:param4 ) and ( members.uid=:param5 )")) { + findQuery = true; + break; + } + } + assertTrue(findQuery); assertTrue(objects.isEmpty()); + statistics.setStatisticsEnabled(false); } @Test diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/section/SectionStoreTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/section/SectionStoreTest.java index 5839ba104b36..9c1ee8edbbb1 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/section/SectionStoreTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/section/SectionStoreTest.java @@ -66,7 +66,7 @@ void sectionsByDataElementTest() { createSectionAndSave(deZ, 'd'); // when - List
sections = sectionStore.getByDataElement(List.of(deW, deX, deY)); + List
sections = sectionStore.getSectionsByDataElement(List.of(deW, deX, deY)); // then assertEquals(3, sections.size()); diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/split/orgunit/OrgUnitSplitServiceTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/split/orgunit/OrgUnitSplitServiceTest.java index 4896a2ec3e3c..88fb78fb806d 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/split/orgunit/OrgUnitSplitServiceTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/split/orgunit/OrgUnitSplitServiceTest.java @@ -34,6 +34,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.common.collect.Lists; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import org.hisp.dhis.common.IdentifiableObjectManager; import org.hisp.dhis.common.IllegalQueryException; import org.hisp.dhis.dataset.DataSet; @@ -44,7 +48,9 @@ import org.hisp.dhis.period.PeriodType; import org.hisp.dhis.program.Program; import org.hisp.dhis.test.integration.PostgresIntegrationTestBase; +import org.hisp.dhis.user.User; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; @@ -159,4 +165,76 @@ void testSplit() { assertNotNull(idObjectManager.get(OrganisationUnit.class, ouB.getUid())); assertNotNull(idObjectManager.get(OrganisationUnit.class, ouC.getUid())); } + + @Test + @DisplayName("OrgUnit split has correct users for new split org units") + void orgUnitSplitCorrectUsersTest() { + // given multiple users + // each of which have different kinds of access to the same org unit + Set source = new HashSet<>(Collections.singletonList(ouA)); + + User userWithNoOrgUnits = createAndAddUser("user1"); + + User userDataCaptureOrgUnits = createAndAddUser("user2"); + userDataCaptureOrgUnits.setOrganisationUnits(source); + + User userDataViewOrgUnits = createAndAddUser("user3"); + userDataViewOrgUnits.setDataViewOrganisationUnits(source); + + User userTeiSearchOrgUnits = createAndAddUser("user4"); + userTeiSearchOrgUnits.setTeiSearchOrganisationUnits(source); + + User userAllOrgUnits = createAndAddUser("user5"); + userAllOrgUnits.setOrganisationUnits(source); + userAllOrgUnits.setDataViewOrganisationUnits(source); + userAllOrgUnits.setTeiSearchOrganisationUnits(source); + + idObjectManager.save( + List.of( + userWithNoOrgUnits, + userAllOrgUnits, + userDataCaptureOrgUnits, + userDataViewOrgUnits, + userTeiSearchOrgUnits)); + + assertUserHasExpectedOrgUnits(userAllOrgUnits, 1, 1, 1); + assertUserHasExpectedOrgUnits(userWithNoOrgUnits, 0, 0, 0); + assertUserHasExpectedOrgUnits(userDataCaptureOrgUnits, 1, 0, 0); + assertUserHasExpectedOrgUnits(userDataViewOrgUnits, 0, 1, 0); + assertUserHasExpectedOrgUnits(userTeiSearchOrgUnits, 0, 0, 1); + + OrgUnitSplitRequest request = + new OrgUnitSplitRequest.Builder() + .withSource(ouA) + .addTargets(Set.of(ouB, ouC)) + .withPrimaryTarget(ouB) + .withDeleteSource(true) + .build(); + + // when + service.split(request); + + // then all users should have the appropriate access for the split org units + assertUserHasExpectedOrgUnits(userAllOrgUnits, 2, 2, 2); + assertUserHasExpectedOrgUnits(userWithNoOrgUnits, 0, 0, 0); + assertUserHasExpectedOrgUnits(userDataCaptureOrgUnits, 2, 0, 0); + assertUserHasExpectedOrgUnits(userDataViewOrgUnits, 0, 2, 0); + assertUserHasExpectedOrgUnits(userTeiSearchOrgUnits, 0, 0, 2); + } + + private void assertUserHasExpectedOrgUnits( + User user, int orgUnits, int dataViewOrgUnits, int teiSearchOrgUnits) { + assertEquals( + orgUnits, + user.getOrganisationUnits().size(), + "user should have %s org units".formatted(orgUnits)); + assertEquals( + dataViewOrgUnits, + user.getDataViewOrganisationUnits().size(), + "user should have %s data view org units".formatted(dataViewOrgUnits)); + assertEquals( + teiSearchOrgUnits, + user.getTeiSearchOrganisationUnits().size(), + "user should have %s tei search org units".formatted(teiSearchOrgUnits)); + } } diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/trackedentity/TrackedEntityServiceTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/trackedentity/TrackedEntityServiceTest.java index 15038d64abc3..7a77e454a9f4 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/trackedentity/TrackedEntityServiceTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/trackedentity/TrackedEntityServiceTest.java @@ -111,7 +111,7 @@ void testDeleteTrackedEntityAndLinkedEnrollmentsAndEvents() throws NotFoundExcep manager.update(trackedEntityA1); TrackedEntity trackedEntityA = manager.get(TrackedEntity.class, trackedEntityA1.getUid()); assertNotNull(trackedEntityA); - trackerObjectDeletionService.deleteTrackedEntities(List.of(trackedEntityA.getUid())); + trackerObjectDeletionService.deleteTrackedEntities(List.of(UID.of(trackedEntityA))); assertNull(manager.get(TrackedEntity.class, trackedEntityA.getUid())); } diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/Assertions.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/Assertions.java index b8d52525110f..b501ffa73fdd 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/Assertions.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/Assertions.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis.tracker; +import static org.hisp.dhis.test.utils.Assertions.assertContainsOnly; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -38,10 +39,16 @@ import java.util.Arrays; import java.util.Collections; import java.util.Date; +import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collectors; import org.hisp.dhis.common.Pager; import org.hisp.dhis.common.SlimPager; +import org.hisp.dhis.common.UID; +import org.hisp.dhis.note.Note; import org.hisp.dhis.tracker.imports.report.ImportReport; import org.hisp.dhis.tracker.imports.report.Status; import org.hisp.dhis.tracker.imports.report.ValidationReport; @@ -213,7 +220,7 @@ public static void assertHasError(ImportReport report, ValidationCode code) { () -> assertHasError(report.getValidationReport(), code)); } - public static void assertHasError(ImportReport report, ValidationCode code, String entityUid) { + public static void assertHasError(ImportReport report, ValidationCode code, UID entityUid) { assertNotNull(report); assertAll( () -> @@ -243,8 +250,7 @@ public static void assertHasError(ValidationReport report, ValidationCode code) "error with code %s not found in report with error(s) %s", code, report.getErrors())); } - public static void assertHasError( - ValidationReport report, ValidationCode code, String entityUid) { + public static void assertHasError(ValidationReport report, ValidationCode code, UID entityUid) { assertTrue(report.hasErrors(), "error not found since report has no errors"); assertTrue( report.hasError( @@ -309,6 +315,42 @@ public static void assertHasTimeStamp(Date date) { String.format("Supported format is %s but found %s", DATE_WITH_TIMESTAMP_PATTERN, date)); } + public static void assertNotes(List expected, List actual) { + assertContainsOnly(expected, actual); + Map expectedNotes = + expected.stream().collect(Collectors.toMap(Note::getUid, Function.identity())); + Map actualNotes = + actual.stream().collect(Collectors.toMap(Note::getUid, Function.identity())); + List assertions = + expectedNotes.entrySet().stream() + .map( + entry -> + (Executable) + () -> { + Note expectedNote = entry.getValue(); + Note actualNote = actualNotes.get(entry.getKey()); + assertAll( + "note assertions " + expectedNote.getUid(), + () -> + assertEquals( + expectedNote.getNoteText(), + actualNote.getNoteText(), + "noteText"), + () -> + assertEquals( + expectedNote.getCreator(), + actualNote.getCreator(), + "creator"), + () -> + assertEquals( + expectedNote.getCreated(), + actualNote.getCreated(), + "created")); + }) + .toList(); + assertAll("note assertions", assertions); + } + private static boolean hasTimeStamp(Date date) { try { diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/deduplication/PotentialDuplicateRemoveTrackedEntityTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/deduplication/PotentialDuplicateRemoveTrackedEntityTest.java index dc82ee7a0735..8e3dbb5de456 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/deduplication/PotentialDuplicateRemoveTrackedEntityTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/deduplication/PotentialDuplicateRemoveTrackedEntityTest.java @@ -193,7 +193,7 @@ private TrackedEntity createTrackedEntity(TrackedEntityAttribute trackedEntityAt } private void removeTrackedEntity(TrackedEntity trackedEntity) throws NotFoundException { - trackerObjectDeletionService.deleteTrackedEntities(List.of(trackedEntity.getUid())); + trackerObjectDeletionService.deleteTrackedEntities(List.of(UID.of(trackedEntity))); } private Relationship getRelationship(String uid) { diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/event/EventChangeLogServiceTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/event/EventChangeLogServiceTest.java index 21165176155a..b74477d3c94a 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/event/EventChangeLogServiceTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/event/EventChangeLogServiceTest.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis.tracker.export.event; +import static org.hisp.dhis.changelog.ChangeLogType.UPDATE; import static org.hisp.dhis.tracker.Assertions.assertNoErrors; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -34,38 +35,34 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.IOException; +import java.time.Instant; import java.util.List; -import java.util.Map; -import org.hisp.dhis.common.IdentifiableObject; import org.hisp.dhis.common.IdentifiableObjectManager; import org.hisp.dhis.common.UID; -import org.hisp.dhis.dxf2.metadata.objectbundle.ObjectBundle; -import org.hisp.dhis.dxf2.metadata.objectbundle.ObjectBundleMode; -import org.hisp.dhis.dxf2.metadata.objectbundle.ObjectBundleParams; -import org.hisp.dhis.dxf2.metadata.objectbundle.ObjectBundleService; -import org.hisp.dhis.dxf2.metadata.objectbundle.ObjectBundleValidationService; -import org.hisp.dhis.feedback.Assertions; +import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.feedback.ForbiddenException; import org.hisp.dhis.feedback.NotFoundException; -import org.hisp.dhis.importexport.ImportStrategy; import org.hisp.dhis.program.Event; -import org.hisp.dhis.render.RenderFormat; -import org.hisp.dhis.render.RenderService; -import org.hisp.dhis.test.integration.PostgresIntegrationTestBase; +import org.hisp.dhis.tracker.TrackerTest; import org.hisp.dhis.tracker.export.Page; import org.hisp.dhis.tracker.export.PageParams; -import org.hisp.dhis.tracker.export.event.EventChangeLog.DataValueChange; import org.hisp.dhis.tracker.imports.TrackerImportParams; import org.hisp.dhis.tracker.imports.TrackerImportService; import org.hisp.dhis.tracker.imports.bundle.persister.TrackerObjectDeletionService; import org.hisp.dhis.tracker.imports.domain.TrackerObjects; import org.hisp.dhis.user.User; +import org.joda.time.LocalDateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.ClassPathResource; -class EventChangeLogServiceTest extends PostgresIntegrationTestBase { +class EventChangeLogServiceTest extends TrackerTest { @Autowired private EventChangeLogService eventChangeLogService; @@ -75,12 +72,6 @@ class EventChangeLogServiceTest extends PostgresIntegrationTestBase { @Autowired private IdentifiableObjectManager manager; - @Autowired private ObjectBundleService objectBundleService; - - @Autowired private ObjectBundleValidationService objectBundleValidationService; - - @Autowired private RenderService renderService; - private User importUser; private TrackerImportParams importParams; @@ -89,22 +80,26 @@ class EventChangeLogServiceTest extends PostgresIntegrationTestBase { EventChangeLogOperationParams.builder().build(); private final PageParams defaultPageParams = new PageParams(null, null, false); - @BeforeEach + private final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS"); + + private TrackerObjects trackerObjects; + + @BeforeAll void setUp() throws IOException { + injectSecurityContextUser(getAdminUser()); setUpMetadata("tracker/simple_metadata.json"); importUser = userService.getUser("tTgjgobT1oS"); injectSecurityContextUser(importUser); importParams = TrackerImportParams.builder().build(); - assertNoErrors( - trackerImportService.importTracker( - importParams, fromJson("tracker/event_and_enrollment.json"))); + trackerObjects = fromJson("tracker/event_and_enrollment.json"); + + assertNoErrors(trackerImportService.importTracker(importParams, trackerObjects)); } @BeforeEach - void setUpUser() { - importUser = userService.getUser("tTgjgobT1oS"); + void resetSecurityContext() { injectSecurityContextUser(importUser); } @@ -117,7 +112,7 @@ void shouldFailWhenEventDoesNotExist() { @Test void shouldFailWhenEventIsSoftDeleted() throws NotFoundException { - trackerObjectDeletionService.deleteEvents(List.of("D9PbzJY8bJM")); + trackerObjectDeletionService.deleteEvents(List.of(UID.of("D9PbzJY8bJM"))); assertThrows( NotFoundException.class, @@ -165,114 +160,291 @@ void shouldReturnChangeLogsWhenDataValueIsCreated() throws NotFoundException, Fo String event = "QRYjLTiJTrA"; String dataElement = getDataElement(event); - Page changeLogs = - eventChangeLogService.getEventChangeLog( - UID.of(event), defaultOperationParams, defaultPageParams); + List changeLogs = + getDataElementChangeLogs( + eventChangeLogService.getEventChangeLog( + UID.of(event), defaultOperationParams, defaultPageParams)); - assertNumberOfChanges(1, changeLogs.getItems()); - assertCreate(dataElement, "15", changeLogs.getItems().get(0)); + assertNumberOfChanges(1, changeLogs); + assertDataElementCreate(dataElement, "15", changeLogs.get(0)); } @Test - void shouldReturnChangeLogsWhenDataValueIsDeleted() - throws NotFoundException, IOException, ForbiddenException { + void shouldReturnChangeLogsWhenDataValueIsDeleted() throws NotFoundException, ForbiddenException { String event = "QRYjLTiJTrA"; String dataElement = getDataElement(event); updateDataValue(event, dataElement, ""); - Page changeLogs = - eventChangeLogService.getEventChangeLog( - UID.of(event), defaultOperationParams, defaultPageParams); + List changeLogs = + getDataElementChangeLogs( + eventChangeLogService.getEventChangeLog( + UID.of(event), defaultOperationParams, defaultPageParams)); - assertNumberOfChanges(2, changeLogs.getItems()); + assertNumberOfChanges(2, changeLogs); assertAll( - () -> assertDelete(dataElement, "15", changeLogs.getItems().get(0)), - () -> assertCreate(dataElement, "15", changeLogs.getItems().get(1))); + () -> assertDataElementDelete(dataElement, "15", changeLogs.get(0)), + () -> assertDataElementCreate(dataElement, "15", changeLogs.get(1))); } @Test void shouldNotUpdateChangeLogsWhenDataValueIsDeletedTwiceInARow() - throws NotFoundException, IOException, ForbiddenException { + throws NotFoundException, ForbiddenException { String event = "QRYjLTiJTrA"; String dataElement = getDataElement(event); updateDataValue(event, dataElement, ""); updateDataValue(event, dataElement, ""); - Page changeLogs = - eventChangeLogService.getEventChangeLog( - UID.of(event), defaultOperationParams, defaultPageParams); + List changeLogs = + getDataElementChangeLogs( + eventChangeLogService.getEventChangeLog( + UID.of(event), defaultOperationParams, defaultPageParams)); - assertNumberOfChanges(2, changeLogs.getItems()); + assertNumberOfChanges(2, changeLogs); assertAll( - () -> assertDelete(dataElement, "15", changeLogs.getItems().get(0)), - () -> assertCreate(dataElement, "15", changeLogs.getItems().get(1))); + () -> assertDataElementDelete(dataElement, "15", changeLogs.get(0)), + () -> assertDataElementCreate(dataElement, "15", changeLogs.get(1))); } @Test - void shouldReturnChangeLogsWhenDataValueIsUpdated() - throws NotFoundException, IOException, ForbiddenException { + void shouldReturnChangeLogsWhenDataValueIsUpdated() throws NotFoundException, ForbiddenException { String event = "QRYjLTiJTrA"; String dataElement = getDataElement(event); updateDataValue(event, dataElement, "20"); - Page changeLogs = - eventChangeLogService.getEventChangeLog( - UID.of(event), defaultOperationParams, defaultPageParams); + List changeLogs = + getDataElementChangeLogs( + eventChangeLogService.getEventChangeLog( + UID.of(event), defaultOperationParams, defaultPageParams)); - assertNumberOfChanges(2, changeLogs.getItems()); + assertNumberOfChanges(2, changeLogs); assertAll( - () -> assertUpdate(dataElement, "15", "20", changeLogs.getItems().get(0)), - () -> assertCreate(dataElement, "15", changeLogs.getItems().get(1))); + () -> assertDataElementUpdate(dataElement, "15", "20", changeLogs.get(0)), + () -> assertDataElementCreate(dataElement, "15", changeLogs.get(1))); } @Test void shouldReturnChangeLogsWhenDataValueIsUpdatedTwiceInARow() - throws NotFoundException, IOException, ForbiddenException { + throws NotFoundException, ForbiddenException { String event = "QRYjLTiJTrA"; String dataElement = getDataElement(event); updateDataValue(event, dataElement, "20"); updateDataValue(event, dataElement, "25"); - Page changeLogs = - eventChangeLogService.getEventChangeLog( - UID.of(event), defaultOperationParams, defaultPageParams); + List changeLogs = + getDataElementChangeLogs( + eventChangeLogService.getEventChangeLog( + UID.of(event), defaultOperationParams, defaultPageParams)); - assertNumberOfChanges(3, changeLogs.getItems()); + assertNumberOfChanges(3, changeLogs); assertAll( - () -> assertUpdate(dataElement, "20", "25", changeLogs.getItems().get(0)), - () -> assertUpdate(dataElement, "15", "20", changeLogs.getItems().get(1)), - () -> assertCreate(dataElement, "15", changeLogs.getItems().get(2))); + () -> assertDataElementUpdate(dataElement, "20", "25", changeLogs.get(0)), + () -> assertDataElementUpdate(dataElement, "15", "20", changeLogs.get(1)), + () -> assertDataElementCreate(dataElement, "15", changeLogs.get(2))); } @Test void shouldReturnChangeLogsWhenDataValueIsCreatedUpdatedAndDeleted() - throws IOException, NotFoundException, ForbiddenException { + throws NotFoundException, ForbiddenException { String event = "QRYjLTiJTrA"; String dataElement = getDataElement(event); updateDataValue(event, dataElement, "20"); updateDataValue(event, dataElement, ""); + List changeLogs = + getDataElementChangeLogs( + eventChangeLogService.getEventChangeLog( + UID.of(event), defaultOperationParams, defaultPageParams)); + + assertNumberOfChanges(3, changeLogs); + assertAll( + () -> assertDataElementDelete(dataElement, "20", changeLogs.get(0)), + () -> assertDataElementUpdate(dataElement, "15", "20", changeLogs.get(1)), + () -> assertDataElementCreate(dataElement, "15", changeLogs.get(2))); + } + + @Test + void shouldReturnOnlyUserNameWhenUserDoesNotExistInDatabase() + throws ForbiddenException, NotFoundException { + Event event = getEvent("QRYjLTiJTrA"); + String dataElementUid = event.getEventDataValues().iterator().next().getDataElement(); + DataElement dataElement = manager.get(DataElement.class, dataElementUid); + User deletedUser = new User(); + deletedUser.setUsername("deletedUserName"); + eventChangeLogService.addDataValueChangeLog( + event, dataElement, "previous", "current", UPDATE, deletedUser.getUsername()); + + List changeLogs = + getDataElementChangeLogs( + eventChangeLogService.getEventChangeLog( + UID.of("QRYjLTiJTrA"), defaultOperationParams, defaultPageParams)); + + assertNumberOfChanges(2, changeLogs); + assertAll( + () -> + assertUpdate( + dataElementUid, null, "previous", "current", changeLogs.get(0), deletedUser), + () -> assertDataElementCreate(dataElementUid, "15", changeLogs.get(1))); + } + + @Test + void shouldReturnEventPropertiesChangeLogWhenNewDatePropertyValueAdded() + throws ForbiddenException, NotFoundException { + String event = "QRYjLTiJTrA"; + Page changeLogs = eventChangeLogService.getEventChangeLog( UID.of(event), defaultOperationParams, defaultPageParams); + List scheduledAtLogs = getChangeLogsByProperty(changeLogs, "scheduledAt"); + List occurredAtLogs = getChangeLogsByProperty(changeLogs, "occurredAt"); - assertNumberOfChanges(3, changeLogs.getItems()); + assertNumberOfChanges(1, scheduledAtLogs); + assertNumberOfChanges(1, occurredAtLogs); assertAll( - () -> assertDelete(dataElement, "20", changeLogs.getItems().get(0)), - () -> assertUpdate(dataElement, "15", "20", changeLogs.getItems().get(1)), - () -> assertCreate(dataElement, "15", changeLogs.getItems().get(2))); + () -> + assertPropertyCreate("scheduledAt", "2022-04-22 06:00:38.343", scheduledAtLogs.get(0)), + () -> assertPropertyCreate("occurredAt", "2022-04-20 06:00:38.343", occurredAtLogs.get(0))); } - private void updateDataValue(String event, String dataElementUid, String newValue) - throws IOException { - TrackerObjects trackerObjects = fromJson("tracker/event_and_enrollment.json"); + @Test + void shouldReturnEventPropertiesChangeLogWhenExistingDatePropertyUpdated() + throws IOException, ForbiddenException, NotFoundException { + UID event = UID.of("QRYjLTiJTrA"); + LocalDateTime currentTime = LocalDateTime.now(); + + updateEventDates(event, currentTime.toDate().toInstant()); + + Page changeLogs = + eventChangeLogService.getEventChangeLog(event, defaultOperationParams, defaultPageParams); + List scheduledAtLogs = getChangeLogsByProperty(changeLogs, "scheduledAt"); + List occurredAtLogs = getChangeLogsByProperty(changeLogs, "occurredAt"); + + assertNumberOfChanges(2, scheduledAtLogs); + assertNumberOfChanges(2, occurredAtLogs); + assertAll( + () -> + assertPropertyUpdate( + "scheduledAt", + "2022-04-22 06:00:38.343", + currentTime.toString(formatter), + scheduledAtLogs.get(0)), + () -> + assertPropertyCreate("scheduledAt", "2022-04-22 06:00:38.343", scheduledAtLogs.get(1)), + () -> + assertPropertyUpdate( + "occurredAt", + "2022-04-20 06:00:38.343", + currentTime.toString(formatter), + occurredAtLogs.get(0)), + () -> assertPropertyCreate("occurredAt", "2022-04-20 06:00:38.343", occurredAtLogs.get(1))); + } + + @Test + void shouldReturnEventPropertiesChangeLogWhenExistingDatePropertyDeleted() + throws ForbiddenException, NotFoundException { + UID event = UID.of("QRYjLTiJTrA"); + + deleteScheduledAtDate(event); + + Page changeLogs = + eventChangeLogService.getEventChangeLog(event, defaultOperationParams, defaultPageParams); + List scheduledAtLogs = getChangeLogsByProperty(changeLogs, "scheduledAt"); + List occurredAtLogs = getChangeLogsByProperty(changeLogs, "occurredAt"); + + assertNumberOfChanges(2, scheduledAtLogs); + assertNumberOfChanges(1, occurredAtLogs); + assertAll( + () -> + assertPropertyDelete("scheduledAt", "2022-04-22 06:00:38.343", scheduledAtLogs.get(0)), + () -> + assertPropertyCreate("scheduledAt", "2022-04-22 06:00:38.343", scheduledAtLogs.get(1)), + () -> assertPropertyCreate("occurredAt", "2022-04-20 06:00:38.343", occurredAtLogs.get(0))); + } + + @Test + void shouldReturnEventPropertiesChangeLogWhenNewGeometryPointPropertyValueAdded() + throws ForbiddenException, NotFoundException { + String event = "QRYjLTiJTrA"; + + Page changeLogs = + eventChangeLogService.getEventChangeLog( + UID.of(event), defaultOperationParams, defaultPageParams); + List geometryChangeLogs = getChangeLogsByProperty(changeLogs, "geometry"); + + assertNumberOfChanges(1, geometryChangeLogs); + assertAll( + () -> + assertPropertyCreate("geometry", "(-11.419700, 8.103900)", geometryChangeLogs.get(0))); + } + + @Test + void shouldReturnEventPropertiesChangeLogWhenNewGeometryPolygonPropertyValueAdded() + throws ForbiddenException, NotFoundException { + String event = "YKmfzHdjUDL"; + + Page changeLogs = + eventChangeLogService.getEventChangeLog( + UID.of(event), defaultOperationParams, defaultPageParams); + List geometryChangeLogs = getChangeLogsByProperty(changeLogs, "geometry"); + + assertNumberOfChanges(1, geometryChangeLogs); + assertAll( + () -> + assertPropertyCreate( + "geometry", + "(-11.416855, 8.132308), (-11.445351, 8.089312), (-11.383896, 8.089652), (-11.416855, 8.132308)", + geometryChangeLogs.get(0))); + } + + @Test + void shouldReturnEventPropertiesChangeLogWhenExistingGeometryPointPropertyUpdated() + throws ForbiddenException, NotFoundException { + UID event = UID.of("QRYjLTiJTrA"); + + Geometry geometry = createGeometryPoint(16.435547, 49.26422); + updateEventGeometry(event, geometry); + + Page changeLogs = + eventChangeLogService.getEventChangeLog(event, defaultOperationParams, defaultPageParams); + List geometryChangeLogs = getChangeLogsByProperty(changeLogs, "geometry"); + + assertNumberOfChanges(2, geometryChangeLogs); + assertAll( + () -> + assertPropertyUpdate( + "geometry", + "(-11.419700, 8.103900)", + "(16.435547, 49.264220)", + geometryChangeLogs.get(0)), + () -> + assertPropertyCreate("geometry", "(-11.419700, 8.103900)", geometryChangeLogs.get(1))); + } + + @Test + void shouldReturnEventPropertiesChangeLogWhenExistingGeometryPointPropertyDeleted() + throws ForbiddenException, NotFoundException { + UID event = UID.of("QRYjLTiJTrA"); + + deleteEventGeometry(event); + + Page changeLogs = + eventChangeLogService.getEventChangeLog(event, defaultOperationParams, defaultPageParams); + List geometryChangeLogs = getChangeLogsByProperty(changeLogs, "geometry"); + + assertNumberOfChanges(2, geometryChangeLogs); + assertAll( + () -> assertPropertyDelete("geometry", "(-11.419700, 8.103900)", geometryChangeLogs.get(0)), + () -> + assertPropertyCreate("geometry", "(-11.419700, 8.103900)", geometryChangeLogs.get(1))); + } + + private void updateDataValue(String event, String dataElementUid, String newValue) { trackerObjects.getEvents().stream() - .filter(e -> e.getEvent().equalsIgnoreCase(event)) + .filter(e -> e.getEvent().getValue().equalsIgnoreCase(event)) .findFirst() .flatMap( e -> @@ -284,6 +456,43 @@ private void updateDataValue(String event, String dataElementUid, String newValu assertNoErrors(trackerImportService.importTracker(importParams, trackerObjects)); } + private void updateEventDates(UID event, Instant newDate) throws IOException { + TrackerObjects trackerObjects = fromJson("tracker/event_and_enrollment.json"); + trackerObjects.getEvents().stream() + .filter(e -> e.getEvent().equals(event)) + .findFirst() + .ifPresent( + e -> { + e.setOccurredAt(newDate); + e.setScheduledAt(newDate); + }); + assertNoErrors(trackerImportService.importTracker(importParams, trackerObjects)); + } + + private void deleteScheduledAtDate(UID event) { + trackerObjects.getEvents().stream() + .filter(e -> e.getEvent().equals(event)) + .findFirst() + .ifPresent(e -> e.setScheduledAt(null)); + assertNoErrors(trackerImportService.importTracker(importParams, trackerObjects)); + } + + private void updateEventGeometry(UID event, Geometry newGeometry) { + trackerObjects.getEvents().stream() + .filter(e -> e.getEvent().equals(event)) + .findFirst() + .ifPresent(e -> e.setGeometry(newGeometry)); + assertNoErrors(trackerImportService.importTracker(importParams, trackerObjects)); + } + + private void deleteEventGeometry(UID event) { + trackerObjects.getEvents().stream() + .filter(e -> e.getEvent().equals(event)) + .findFirst() + .ifPresent(e -> e.setGeometry(null)); + assertNoErrors(trackerImportService.importTracker(importParams, trackerObjects)); + } + private String getDataElement(String uid) { Event event = getEvent(uid); String dataElement = event.getEventDataValues().iterator().next().getDataElement(); @@ -311,57 +520,123 @@ private static void assertNumberOfChanges(int expected, List cha expected, changeLogs.size(), changeLogs)); } - private void assertCreate(String dataElement, String currentValue, EventChangeLog changeLog) { + private void assertDataElementCreate( + String dataElement, String currentValue, EventChangeLog changeLog) { assertAll( () -> assertUser(importUser, changeLog), - () -> assertEquals("CREATE", changeLog.type()), - () -> assertChange(dataElement, null, currentValue, changeLog)); + () -> assertEquals("CREATE", changeLog.getChangeLogType().name()), + () -> assertDataElementChange(dataElement, null, currentValue, changeLog)); } - private void assertUpdate( - String dataElement, String previousValue, String currentValue, EventChangeLog changeLog) { + private void assertPropertyCreate( + String property, String currentValue, EventChangeLog changeLog) { assertAll( () -> assertUser(importUser, changeLog), - () -> assertEquals("UPDATE", changeLog.type()), - () -> assertChange(dataElement, previousValue, currentValue, changeLog)); + () -> assertEquals("CREATE", changeLog.getChangeLogType().name()), + () -> assertPropertyChange(property, null, currentValue, changeLog)); + } + + private void assertDataElementUpdate( + String dataElement, String previousValue, String currentValue, EventChangeLog changeLog) { + assertUpdate(dataElement, null, previousValue, currentValue, changeLog, importUser); } - private void assertDelete(String dataElement, String previousValue, EventChangeLog changeLog) { + private void assertPropertyUpdate( + String property, String previousValue, String currentValue, EventChangeLog changeLog) { + assertUpdate(null, property, previousValue, currentValue, changeLog, importUser); + } + + private void assertUpdate( + String dataElement, + String property, + String previousValue, + String currentValue, + EventChangeLog changeLog, + User user) { + assertAll( + () -> assertUser(user, changeLog), + () -> assertEquals("UPDATE", changeLog.getChangeLogType().name()), + () -> { + if (dataElement != null) { + assertDataElementChange(dataElement, previousValue, currentValue, changeLog); + } else { + assertPropertyChange(property, previousValue, currentValue, changeLog); + } + }); + } + + private void assertDataElementDelete( + String dataElement, String previousValue, EventChangeLog changeLog) { + assertDelete(dataElement, null, previousValue, changeLog); + } + + private void assertPropertyDelete( + String property, String previousValue, EventChangeLog changeLog) { + assertDelete(null, property, previousValue, changeLog); + } + + private void assertDelete( + String dataElement, String property, String previousValue, EventChangeLog changeLog) { assertAll( () -> assertUser(importUser, changeLog), - () -> assertEquals("DELETE", changeLog.type()), - () -> assertChange(dataElement, previousValue, null, changeLog)); + () -> assertEquals("DELETE", changeLog.getChangeLogType().name()), + () -> { + if (dataElement != null) { + assertDataElementChange(dataElement, previousValue, null, changeLog); + } else { + assertPropertyChange(property, previousValue, null, changeLog); + } + }); } - private static void assertChange( + private static void assertDataElementChange( String dataElement, String previousValue, String currentValue, EventChangeLog changeLog) { - DataValueChange expected = new DataValueChange(dataElement, previousValue, currentValue); - assertEquals(expected, changeLog.change().dataValue()); + assertEquals( + dataElement, + changeLog.getDataElement() != null ? changeLog.getDataElement().getUid() : null); + assertEquals(previousValue, changeLog.getPreviousValue()); + assertEquals(currentValue, changeLog.getCurrentValue()); + } + + private static void assertPropertyChange( + String property, String previousValue, String currentValue, EventChangeLog changeLog) { + assertEquals(property, changeLog.getEventProperty()); + assertEquals(previousValue, changeLog.getPreviousValue()); + assertEquals(currentValue, changeLog.getCurrentValue()); } private static void assertUser(User user, EventChangeLog changeLog) { assertAll( - () -> assertEquals(user.getUsername(), changeLog.createdBy().getUsername()), - () -> assertEquals(user.getFirstName(), changeLog.createdBy().getFirstName()), - () -> assertEquals(user.getSurname(), changeLog.createdBy().getSurname()), - () -> assertEquals(user.getUid(), changeLog.createdBy().getUid())); - } - - private ObjectBundle setUpMetadata(String path) throws IOException { - Map, List> metadata = - renderService.fromMetadata(new ClassPathResource(path).getInputStream(), RenderFormat.JSON); - ObjectBundleParams params = new ObjectBundleParams(); - params.setObjectBundleMode(ObjectBundleMode.COMMIT); - params.setImportStrategy(ImportStrategy.CREATE); - params.setObjects(metadata); - ObjectBundle bundle = objectBundleService.create(params); - Assertions.assertNoErrors(objectBundleValidationService.validate(bundle)); - objectBundleService.commit(bundle); - return bundle; - } - - private TrackerObjects fromJson(String path) throws IOException { - return renderService.fromJson( - new ClassPathResource(path).getInputStream(), TrackerObjects.class); + () -> assertEquals(user.getUsername(), changeLog.getCreatedBy().getUsername()), + () -> + assertEquals( + user.getFirstName(), + changeLog.getCreatedBy() == null ? null : changeLog.getCreatedBy().getFirstName()), + () -> + assertEquals( + user.getSurname(), + changeLog.getCreatedBy() == null ? null : changeLog.getCreatedBy().getSurname()), + () -> + assertEquals( + user.getUid(), + changeLog.getCreatedBy() == null ? null : changeLog.getCreatedBy().getUid())); + } + + private List getDataElementChangeLogs(Page changeLogs) { + return changeLogs.getItems().stream().filter(cl -> cl.getDataElement() != null).toList(); + } + + private List getChangeLogsByProperty( + Page changeLogs, String propertyName) { + return changeLogs.getItems().stream() + .filter(cl -> cl.getEventProperty() != null && cl.getEventProperty().equals(propertyName)) + .toList(); + } + + private Geometry createGeometryPoint(double x, double y) { + GeometryFactory geometryFactory = new GeometryFactory(); + Coordinate coordinate = new Coordinate(x, y); + + return geometryFactory.createPoint(coordinate); } } diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/event/EventExporterTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/event/EventExporterTest.java index 3ed1a326bb53..83b4f9226bca 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/event/EventExporterTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/event/EventExporterTest.java @@ -29,17 +29,15 @@ import static org.hisp.dhis.common.OrganisationUnitSelectionMode.ACCESSIBLE; import static org.hisp.dhis.common.OrganisationUnitSelectionMode.SELECTED; -import static org.hisp.dhis.test.utils.Assertions.assertContains; import static org.hisp.dhis.test.utils.Assertions.assertContainsOnly; import static org.hisp.dhis.test.utils.Assertions.assertIsEmpty; -import static org.hisp.dhis.test.utils.Assertions.assertStartsWith; import static org.hisp.dhis.tracker.Assertions.assertHasTimeStamp; import static org.hisp.dhis.tracker.Assertions.assertNoErrors; +import static org.hisp.dhis.tracker.Assertions.assertNotes; import static org.hisp.dhis.util.DateUtils.parseDate; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.IOException; import java.time.ZoneId; @@ -48,11 +46,9 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.Function; import java.util.stream.Collectors; import org.hisp.dhis.category.CategoryOption; import org.hisp.dhis.common.BaseIdentifiableObject; -import org.hisp.dhis.common.IdSchemes; import org.hisp.dhis.common.IdentifiableObject; import org.hisp.dhis.common.IdentifiableObjectManager; import org.hisp.dhis.common.QueryFilter; @@ -63,7 +59,6 @@ import org.hisp.dhis.dataelement.DataElementService; import org.hisp.dhis.feedback.BadRequestException; import org.hisp.dhis.feedback.ForbiddenException; -import org.hisp.dhis.note.Note; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.program.EnrollmentStatus; import org.hisp.dhis.program.Event; @@ -497,132 +492,6 @@ void shouldReturnEventsGivenCategoryOptionCombo() throws ForbiddenException, Bad assertAll("all events should have the same category option combo and options", executables); } - @Test - void shouldFailIfCategoryOptionComboOfGivenEventDoesNotHaveAValueForGivenIdScheme() { - IdSchemes idSchemes = new IdSchemes(); - idSchemes.setCategoryOptionComboIdScheme("ATTRIBUTE:GOLswS44mh8"); - EventOperationParams params = - operationParamsBuilder - .orgUnit(UID.of("DiszpKrYNg8")) - .orgUnitMode(SELECTED) - .idSchemes(idSchemes) - .events(Set.of(UID.of("kWjSezkXHVp"))) - .build(); - - IllegalStateException ex = - assertThrows(IllegalStateException.class, () -> eventService.getEvents(params)); - assertStartsWith("CategoryOptionCombo", ex.getMessage()); - assertContains("not have a value assigned for idScheme ATTRIBUTE:GOLswS44mh8", ex.getMessage()); - } - - @Test - void shouldReturnEventsGivenIdSchemeCode() throws ForbiddenException, BadRequestException { - IdSchemes idSchemes = new IdSchemes(); - idSchemes.setProgramIdScheme("code"); - idSchemes.setProgramStageIdScheme("code"); - idSchemes.setOrgUnitIdScheme("code"); - idSchemes.setCategoryOptionComboIdScheme("code"); - - EventOperationParams params = - operationParamsBuilder - .orgUnit(UID.of("DiszpKrYNg8")) - .orgUnitMode(SELECTED) - .idSchemes(idSchemes) - .attributeCategoryCombo(UID.of("O4VaNks6tta")) - .attributeCategoryOptions(UID.of("xwZ2u3WyQR0", "M58XdOfhiJ7")) - .build(); - - List events = eventService.getEvents(params); - - assertContainsOnly(List.of("kWjSezkXHVp", "OTmjvJDn0Fu"), uids(events)); - List executables = - events.stream() - .map( - e -> - (Executable) - () -> - assertAll( - "event " + e.getUid(), - () -> - assertEquals( - "multi-program", e.getEnrollment().getProgram().getUid()), - () -> assertEquals("multi-stage", e.getProgramStage().getUid()), - () -> - assertEquals( - "DiszpKrYNg8", - e.getOrganisationUnit() - .getUid()), // TODO(DHIS2-14968): this might be a bug - // caused by - // https://github.com/dhis2/dhis2-core/pull/12518 - () -> - assertEquals( - "COC_1153452", e.getAttributeOptionCombo().getUid()), - () -> - assertContainsOnly( - Set.of("xwZ2u3WyQR0", "M58XdOfhiJ7"), - e.getAttributeOptionCombo().getCategoryOptions().stream() - .map(CategoryOption::getUid) - .collect(Collectors.toSet())))) - .toList(); - assertAll("all events should have the same category option combo and options", executables); - } - - @Test - void shouldReturnEventsGivenIdSchemeAttribute() throws ForbiddenException, BadRequestException { - IdSchemes idSchemes = new IdSchemes(); - idSchemes.setProgramIdScheme("ATTRIBUTE:j45AR9cBQKc"); - idSchemes.setProgramStageIdScheme("ATTRIBUTE:j45AR9cBQKc"); - idSchemes.setOrgUnitIdScheme("ATTRIBUTE:j45AR9cBQKc"); - idSchemes.setCategoryOptionComboIdScheme("ATTRIBUTE:j45AR9cBQKc"); - EventOperationParams params = - operationParamsBuilder - .orgUnit(UID.of("DiszpKrYNg8")) - .orgUnitMode(SELECTED) - .idSchemes(idSchemes) - .attributeCategoryCombo(UID.of("O4VaNks6tta")) - .attributeCategoryOptions(UID.of("xwZ2u3WyQR0", "M58XdOfhiJ7")) - .build(); - - List events = eventService.getEvents(params); - - assertContainsOnly(List.of("kWjSezkXHVp", "OTmjvJDn0Fu"), uids(events)); - List executables = - events.stream() - .map( - e -> - (Executable) - () -> - assertAll( - "event " + e.getUid(), - () -> - assertEquals( - "multi-program-attribute", - e.getEnrollment().getProgram().getUid()), - () -> - assertEquals( - "multi-program-stage-attribute", - e.getProgramStage().getUid()), - () -> - assertEquals( - "DiszpKrYNg8", - e.getOrganisationUnit() - .getUid()), // TODO(DHIS2-14968): this might be a bug - // caused by - // https://github.com/dhis2/dhis2-core/pull/12518 - () -> - assertEquals( - "COC_1153452-attribute", - e.getAttributeOptionCombo().getUid()), - () -> - assertContainsOnly( - Set.of("xwZ2u3WyQR0", "M58XdOfhiJ7"), - e.getAttributeOptionCombo().getCategoryOptions().stream() - .map(CategoryOption::getUid) - .collect(Collectors.toSet())))) - .toList(); - assertAll("all events should have the same category option combo and options", executables); - } - @Test void testExportEventsWhenFilteringByDataElementsWithCategoryOptionNotSuperUser() throws ForbiddenException, BadRequestException { @@ -1059,47 +928,6 @@ void shouldReturnEventsWhenParamEndDueDateLaterThanEventsDueDate() assertContainsOnly(List.of("D9PbzJY8bJM", "pTzf9KYMk72"), events); } - private static void assertNotes(List expected, List actual) { - assertContainsOnly(expected, actual); - Map expectedNotes = - expected.stream().collect(Collectors.toMap(Note::getUid, Function.identity())); - Map actualNotes = - actual.stream().collect(Collectors.toMap(Note::getUid, Function.identity())); - List assertions = - expectedNotes.entrySet().stream() - .map( - entry -> - (Executable) - () -> { - Note expectedNote = entry.getValue(); - Note actualNote = actualNotes.get(entry.getKey()); - assertAll( - "note assertions " + expectedNote.getUid(), - () -> - assertEquals( - expectedNote.getNoteText(), - actualNote.getNoteText(), - "noteText"), - () -> - assertEquals( - expectedNote.getCreator(), - actualNote.getCreator(), - "creator"), - () -> - assertEquals( - expectedNote.getCreated(), - actualNote.getCreated(), - "created"), - () -> - assertEquals( - expectedNote.getLastUpdated(), - actualNote.getLastUpdated(), - "lastUpdated")); - }) - .toList(); - assertAll("note assertions", assertions); - } - private DataElement dataElement(UID uid) { return dataElementService.getDataElement(uid.getValue()); } diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/event/OrderAndFilterEventChangeLogTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/event/OrderAndFilterEventChangeLogTest.java new file mode 100644 index 000000000000..dc14a6afde5b --- /dev/null +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/event/OrderAndFilterEventChangeLogTest.java @@ -0,0 +1,467 @@ +/* + * Copyright (c) 2004-2024, 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 static org.hisp.dhis.test.utils.Assertions.assertContainsOnly; +import static org.hisp.dhis.tracker.Assertions.assertNoErrors; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.IOException; +import java.time.Instant; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.hisp.dhis.common.IdentifiableObjectManager; +import org.hisp.dhis.common.QueryFilter; +import org.hisp.dhis.common.QueryOperator; +import org.hisp.dhis.common.SortDirection; +import org.hisp.dhis.common.UID; +import org.hisp.dhis.feedback.ForbiddenException; +import org.hisp.dhis.feedback.NotFoundException; +import org.hisp.dhis.program.Event; +import org.hisp.dhis.tracker.TrackerTest; +import org.hisp.dhis.tracker.export.Page; +import org.hisp.dhis.tracker.export.PageParams; +import org.hisp.dhis.tracker.imports.TrackerImportParams; +import org.hisp.dhis.tracker.imports.TrackerImportService; +import org.hisp.dhis.tracker.imports.domain.TrackerObjects; +import org.hisp.dhis.user.User; +import org.joda.time.LocalDateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; + +class OrderAndFilterEventChangeLogTest extends TrackerTest { + + @Autowired private EventChangeLogService eventChangeLogService; + + @Autowired private TrackerImportService trackerImportService; + + @Autowired private IdentifiableObjectManager manager; + + private User importUser; + + private TrackerImportParams importParams; + + private final PageParams defaultPageParams = new PageParams(null, null, false); + + private final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS"); + + private TrackerObjects trackerObjects; + + @BeforeAll + void setUp() throws IOException { + injectSecurityContextUser(getAdminUser()); + setUpMetadata("tracker/simple_metadata.json"); + + importUser = userService.getUser("tTgjgobT1oS"); + injectSecurityContextUser(importUser); + + importParams = TrackerImportParams.builder().build(); + trackerObjects = fromJson("tracker/event_and_enrollment.json"); + + assertNoErrors(trackerImportService.importTracker(importParams, trackerObjects)); + } + + private static Stream provideDateAndUsernameOrderParams() { + return Stream.of( + Arguments.of("createdAt", SortDirection.DESC), + Arguments.of("username", SortDirection.DESC), + Arguments.of("username", SortDirection.ASC)); + } + + @ParameterizedTest + @MethodSource("provideDateAndUsernameOrderParams") + void shouldSortChangeLogsByCreatedAtDescWhenOrderingByDateOrUsername( + String field, SortDirection sortDirection) throws ForbiddenException, NotFoundException { + EventChangeLogOperationParams params = + EventChangeLogOperationParams.builder().orderBy(field, sortDirection).build(); + Event event = getEvent("QRYjLTiJTrA"); + String dataElementUid = getFirstDataElement(event); + + updateDataValues(event, dataElementUid, "20", "25"); + + List changeLogs = + getDataElementChangeLogs( + eventChangeLogService.getEventChangeLog( + UID.of("QRYjLTiJTrA"), params, defaultPageParams)); + + assertNumberOfChanges(3, changeLogs); + assertAll( + () -> assertDataElementUpdate("GieVkTxp4HH", "20", "25", changeLogs.get(0)), + () -> assertDataElementUpdate("GieVkTxp4HH", "15", "20", changeLogs.get(1)), + () -> assertDataElementCreate("GieVkTxp4HH", "15", changeLogs.get(2))); + } + + @Test + void shouldSortChangeLogsWhenOrderingByCreatedAtAsc() + throws ForbiddenException, NotFoundException { + EventChangeLogOperationParams params = + EventChangeLogOperationParams.builder().orderBy("createdAt", SortDirection.ASC).build(); + + Event event = getEvent("QRYjLTiJTrA"); + String dataElementUid = getFirstDataElement(event); + + updateDataValues(event, dataElementUid, "20", "25"); + + List changeLogs = + getDataElementChangeLogs( + eventChangeLogService.getEventChangeLog( + UID.of("QRYjLTiJTrA"), params, defaultPageParams)); + + assertNumberOfChanges(3, changeLogs); + assertAll( + () -> assertDataElementCreate(dataElementUid, "15", changeLogs.get(0)), + () -> assertDataElementUpdate(dataElementUid, "15", "20", changeLogs.get(1)), + () -> assertDataElementUpdate(dataElementUid, "20", "25", changeLogs.get(2))); + } + + @Test + void shouldSortChangeLogsWhenOrderingByDataElementAsc() + throws ForbiddenException, NotFoundException { + EventChangeLogOperationParams params = + EventChangeLogOperationParams.builder().orderBy("dataElement", SortDirection.ASC).build(); + Event event = getEvent("kWjSezkXHVp"); + + updateDataValues(event, "GieVkTxp4HH", "20", "25"); + updateDataValues(event, "GieVkTxp4HG", "20"); + + List changeLogs = + getDataElementChangeLogs( + eventChangeLogService.getEventChangeLog( + UID.of("kWjSezkXHVp"), params, defaultPageParams)); + + assertNumberOfChanges(5, changeLogs); + assertAll( + () -> assertDataElementUpdate("GieVkTxp4HG", "10", "20", changeLogs.get(0)), + () -> assertDataElementCreate("GieVkTxp4HG", "10", changeLogs.get(1)), + () -> assertDataElementUpdate("GieVkTxp4HH", "20", "25", changeLogs.get(2)), + () -> assertDataElementUpdate("GieVkTxp4HH", "15", "20", changeLogs.get(3)), + () -> assertDataElementCreate("GieVkTxp4HH", "15", changeLogs.get(4))); + } + + @Test + void shouldSortChangeLogsWhenOrderingByDataElementDesc() + throws ForbiddenException, NotFoundException { + EventChangeLogOperationParams params = + EventChangeLogOperationParams.builder().orderBy("dataElement", SortDirection.DESC).build(); + Event event = getEvent("kWjSezkXHVp"); + + updateDataValues(event, "GieVkTxp4HH", "20", "25"); + updateDataValues(event, "GieVkTxp4HG", "20"); + + List changeLogs = + getDataElementChangeLogs( + eventChangeLogService.getEventChangeLog( + UID.of("kWjSezkXHVp"), params, defaultPageParams)); + + assertNumberOfChanges(5, changeLogs); + assertAll( + () -> assertDataElementUpdate("GieVkTxp4HH", "20", "25", changeLogs.get(0)), + () -> assertDataElementUpdate("GieVkTxp4HH", "15", "20", changeLogs.get(1)), + () -> assertDataElementCreate("GieVkTxp4HH", "15", changeLogs.get(2)), + () -> assertDataElementUpdate("GieVkTxp4HG", "10", "20", changeLogs.get(3)), + () -> assertDataElementCreate("GieVkTxp4HG", "10", changeLogs.get(4))); + } + + @Test + void shouldSortChangeLogsWhenOrderingByPropertyAsc() + throws ForbiddenException, NotFoundException, IOException { + EventChangeLogOperationParams params = + EventChangeLogOperationParams.builder().orderBy("property", SortDirection.ASC).build(); + UID event = UID.of("QRYjLTiJTrA"); + + LocalDateTime currentTime = LocalDateTime.now(); + updateEventDates(event, currentTime.toDate().toInstant()); + + List changeLogs = + getAllPropertyChangeLogs( + eventChangeLogService.getEventChangeLog( + UID.of("QRYjLTiJTrA"), params, defaultPageParams)); + + assertNumberOfChanges(5, changeLogs); + assertAll( + () -> assertPropertyCreate("geometry", "(-11.419700, 8.103900)", changeLogs.get(0)), + () -> + assertPropertyUpdate( + "occurredAt", + "2022-04-20 06:00:38.343", + currentTime.toString(formatter), + changeLogs.get(1)), + () -> assertPropertyCreate("occurredAt", "2022-04-20 06:00:38.343", changeLogs.get(2)), + () -> + assertPropertyUpdate( + "scheduledAt", + "2022-04-22 06:00:38.343", + currentTime.toString(formatter), + changeLogs.get(3)), + () -> assertPropertyCreate("scheduledAt", "2022-04-22 06:00:38.343", changeLogs.get(4))); + } + + @Test + void shouldSortChangeLogsWhenOrderingByPropertyDesc() + throws ForbiddenException, NotFoundException, IOException { + EventChangeLogOperationParams params = + EventChangeLogOperationParams.builder().orderBy("property", SortDirection.DESC).build(); + UID event = UID.of("QRYjLTiJTrA"); + + LocalDateTime currentTime = LocalDateTime.now(); + updateEventDates(event, currentTime.toDate().toInstant()); + + List changeLogs = + getAllPropertyChangeLogs( + eventChangeLogService.getEventChangeLog( + UID.of("QRYjLTiJTrA"), params, defaultPageParams)); + + assertNumberOfChanges(5, changeLogs); + assertAll( + () -> + assertPropertyUpdate( + "scheduledAt", + "2022-04-22 06:00:38.343", + currentTime.toString(formatter), + changeLogs.get(0)), + () -> assertPropertyCreate("scheduledAt", "2022-04-22 06:00:38.343", changeLogs.get(1)), + () -> + assertPropertyUpdate( + "occurredAt", + "2022-04-20 06:00:38.343", + currentTime.toString(formatter), + changeLogs.get(2)), + () -> assertPropertyCreate("occurredAt", "2022-04-20 06:00:38.343", changeLogs.get(3)), + () -> assertPropertyCreate("geometry", "(-11.419700, 8.103900)", changeLogs.get(4))); + } + + @Test + void shouldFilterChangeLogsWhenFilteringByUser() throws ForbiddenException, NotFoundException { + EventChangeLogOperationParams params = + EventChangeLogOperationParams.builder() + .filterBy("username", new QueryFilter(QueryOperator.EQ, importUser.getUsername())) + .build(); + + Page changeLogs = + eventChangeLogService.getEventChangeLog(UID.of("QRYjLTiJTrA"), params, defaultPageParams); + + Set changeLogUsers = + changeLogs.getItems().stream() + .map(cl -> cl.getCreatedBy().getUsername()) + .collect(Collectors.toSet()); + assertContainsOnly(List.of(importUser.getUsername()), changeLogUsers); + } + + @Test + void shouldFilterChangeLogsWhenFilteringByDataElement() + throws ForbiddenException, NotFoundException { + Event event = getEvent("kWjSezkXHVp"); + String dataElement = getFirstDataElement(event); + EventChangeLogOperationParams params = + EventChangeLogOperationParams.builder() + .filterBy("dataElement", new QueryFilter(QueryOperator.EQ, dataElement)) + .build(); + + Page changeLogs = + eventChangeLogService.getEventChangeLog(UID.of(event.getUid()), params, defaultPageParams); + + Set changeLogDataElements = + changeLogs.getItems().stream() + .map(cl -> cl.getDataElement().getUid()) + .collect(Collectors.toSet()); + assertContainsOnly(List.of(dataElement), changeLogDataElements); + } + + private Stream provideEventProperties() { + return Stream.of( + Arguments.of("occurredAt"), Arguments.of("scheduledAt"), Arguments.of("geometry")); + } + + @ParameterizedTest + @MethodSource("provideEventProperties") + void shouldFilterChangeLogsWhenFilteringByProperties(String filterValue) + throws ForbiddenException, NotFoundException { + EventChangeLogOperationParams params = + EventChangeLogOperationParams.builder() + .filterBy("property", new QueryFilter(QueryOperator.EQ, filterValue)) + .build(); + + Page changeLogs = + eventChangeLogService.getEventChangeLog(UID.of("QRYjLTiJTrA"), params, defaultPageParams); + + Set changeLogOccurredAtProperties = + changeLogs.getItems().stream() + .map(EventChangeLog::getEventProperty) + .collect(Collectors.toSet()); + assertContainsOnly(List.of(filterValue), changeLogOccurredAtProperties); + } + + private void updateDataValue(String event, String dataElementUid, String newValue) { + trackerObjects.getEvents().stream() + .filter(e -> e.getEvent().getValue().equalsIgnoreCase(event)) + .findFirst() + .flatMap( + e -> + e.getDataValues().stream() + .filter( + dv -> dv.getDataElement().getIdentifier().equalsIgnoreCase(dataElementUid)) + .findFirst()) + .ifPresent(dv -> dv.setValue(newValue)); + assertNoErrors(trackerImportService.importTracker(importParams, trackerObjects)); + } + + private void updateEventDates(UID event, Instant newDate) throws IOException { + TrackerObjects trackerObjects = fromJson("tracker/event_and_enrollment.json"); + trackerObjects.getEvents().stream() + .filter(e -> e.getEvent().equals(event)) + .findFirst() + .ifPresent( + e -> { + e.setOccurredAt(newDate); + e.setScheduledAt(newDate); + }); + assertNoErrors(trackerImportService.importTracker(importParams, trackerObjects)); + } + + private Event getEvent(String uid) { + Event event = manager.get(Event.class, uid); + assertNotNull(event); + return event; + } + + private static void assertNumberOfChanges(int expected, List changeLogs) { + assertNotNull(changeLogs); + assertEquals( + expected, + changeLogs.size(), + String.format( + "Expected to find %s elements in the event change log list, found %s instead: %s", + expected, changeLogs.size(), changeLogs)); + } + + private void assertDataElementCreate( + String dataElement, String currentValue, EventChangeLog changeLog) { + assertAll( + () -> assertUser(importUser, changeLog), + () -> assertEquals("CREATE", changeLog.getChangeLogType().name()), + () -> assertDataElementChange(dataElement, null, currentValue, changeLog)); + } + + private void assertPropertyCreate( + String property, String currentValue, EventChangeLog changeLog) { + assertAll( + () -> assertUser(importUser, changeLog), + () -> assertEquals("CREATE", changeLog.getChangeLogType().name()), + () -> assertPropertyChange(property, null, currentValue, changeLog)); + } + + private void assertDataElementUpdate( + String dataElement, String previousValue, String currentValue, EventChangeLog changeLog) { + assertUpdate(dataElement, null, previousValue, currentValue, changeLog, importUser); + } + + private void assertPropertyUpdate( + String property, String previousValue, String currentValue, EventChangeLog changeLog) { + assertUpdate(null, property, previousValue, currentValue, changeLog, importUser); + } + + private void assertUpdate( + String dataElement, + String property, + String previousValue, + String currentValue, + EventChangeLog changeLog, + User user) { + assertAll( + () -> assertUser(user, changeLog), + () -> assertEquals("UPDATE", changeLog.getChangeLogType().name()), + () -> { + if (dataElement != null) { + assertDataElementChange(dataElement, previousValue, currentValue, changeLog); + } else { + assertPropertyChange(property, previousValue, currentValue, changeLog); + } + }); + } + + private static void assertDataElementChange( + String dataElement, String previousValue, String currentValue, EventChangeLog changeLog) { + assertEquals( + dataElement, + changeLog.getDataElement() != null ? changeLog.getDataElement().getUid() : null); + assertEquals(previousValue, changeLog.getPreviousValue()); + assertEquals(currentValue, changeLog.getCurrentValue()); + } + + private static void assertPropertyChange( + String property, String previousValue, String currentValue, EventChangeLog changeLog) { + assertEquals(property, changeLog.getEventProperty()); + assertEquals(previousValue, changeLog.getPreviousValue()); + assertEquals(currentValue, changeLog.getCurrentValue()); + } + + private static void assertUser(User user, EventChangeLog changeLog) { + assertAll( + () -> assertEquals(user.getUsername(), changeLog.getCreatedBy().getUsername()), + () -> + assertEquals( + user.getFirstName(), + changeLog.getCreatedBy() == null ? null : changeLog.getCreatedBy().getFirstName()), + () -> + assertEquals( + user.getSurname(), + changeLog.getCreatedBy() == null ? null : changeLog.getCreatedBy().getSurname()), + () -> + assertEquals( + user.getUid(), + changeLog.getCreatedBy() == null ? null : changeLog.getCreatedBy().getUid())); + } + + private List getDataElementChangeLogs(Page changeLogs) { + return changeLogs.getItems().stream().filter(cl -> cl.getDataElement() != null).toList(); + } + + private List getAllPropertyChangeLogs(Page changeLogs) { + return changeLogs.getItems().stream().filter(cl -> cl.getEventProperty() != null).toList(); + } + + private void updateDataValues(Event event, String dataElementUid, String... values) { + for (String value : values) { + updateDataValue(event.getUid(), dataElementUid, value); + } + } + + private String getFirstDataElement(Event event) { + return event.getEventDataValues().iterator().next().getDataElement(); + } +} diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityServiceTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityServiceTest.java index 3d19c64dd568..671da213c93d 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityServiceTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityServiceTest.java @@ -36,6 +36,7 @@ import static org.hisp.dhis.test.utils.Assertions.assertContains; import static org.hisp.dhis.test.utils.Assertions.assertContainsOnly; import static org.hisp.dhis.test.utils.Assertions.assertIsEmpty; +import static org.hisp.dhis.tracker.Assertions.assertNotes; import static org.hisp.dhis.tracker.TrackerTestUtils.oneHourAfter; import static org.hisp.dhis.tracker.TrackerTestUtils.oneHourBefore; import static org.hisp.dhis.tracker.TrackerTestUtils.twoHoursAfter; @@ -369,7 +370,7 @@ void setUp() { enrollmentA.setFollowup(true); manager.save(enrollmentA, false); - enrollmentB = createEnrollment(programB, trackedEntityA, orgUnitA); + enrollmentB = createEnrollment(programB, trackedEntityA, orgUnitB); manager.save(enrollmentB); trackedEntityA.getEnrollments().add(enrollmentB); manager.update(trackedEntityA); @@ -642,6 +643,42 @@ void shouldReturnTrackedEntityIncludingAllAttributesEnrollmentsEventsRelationshi .collect(Collectors.toSet()))); } + @Test + void shouldReturnTrackedEntityIncludingAllEnrollments() + throws ForbiddenException, NotFoundException, BadRequestException { + TrackedEntityOperationParams operationParams = + TrackedEntityOperationParams.builder() + .organisationUnits(orgUnitA) + .orgUnitMode(SELECTED) + .trackedEntityType(trackedEntityTypeA) + .trackedEntityParams(TrackedEntityParams.TRUE) + .build(); + + final List trackedEntities = + trackedEntityService.getTrackedEntities(operationParams); + + assertContainsOnly(List.of(trackedEntityA.getUid()), uids(trackedEntities)); + assertContainsOnly( + Set.of(enrollmentA.getUid(), enrollmentB.getUid()), + uids(trackedEntities.get(0).getEnrollments())); + assertEquals( + orgUnitA.getUid(), + trackedEntities.get(0).getEnrollments().stream() + .filter(e -> e.getUid().equals(enrollmentA.getUid())) + .findFirst() + .get() + .getOrganisationUnit() + .getUid()); + assertEquals( + orgUnitB.getUid(), + trackedEntities.get(0).getEnrollments().stream() + .filter(e -> e.getUid().equals(enrollmentB.getUid())) + .findFirst() + .get() + .getOrganisationUnit() + .getUid()); + } + @Test void shouldReturnTrackedEntityIncludeSpecificProtectedProgram() throws ForbiddenException, NotFoundException, BadRequestException { @@ -1275,7 +1312,7 @@ void shouldReturnTrackedEntityWithEventsAndNotesGivenTheyShouldBeIncluded() .findFirst(); Set events = enrollmentA.get().getEvents(); assertContainsOnly(Set.of(eventA), events); - assertContainsOnly(Set.of(note), events.stream().findFirst().get().getNotes()); + assertNotes(eventA.getNotes(), events.stream().findFirst().get().getNotes()); } @Test diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/TrackerImportParamsSerdeTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/TrackerImportParamsSerdeTest.java index 049904f9ae42..2a6225b3bbe0 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/TrackerImportParamsSerdeTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/TrackerImportParamsSerdeTest.java @@ -32,6 +32,9 @@ import java.io.IOException; import org.hisp.dhis.render.RenderService; +import org.hisp.dhis.tracker.TrackerIdScheme; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.TrackerTest; import org.hisp.dhis.tracker.imports.bundle.TrackerBundleMode; import org.junit.jupiter.api.Test; @@ -64,22 +67,22 @@ void testJsonSerialization() throws Exception { .build(); String json = renderService.toJsonAsString(trackerImportParams); JSONAssert.assertEquals( - "" - + "{\"importMode\":\"COMMIT\"," - + "\"idSchemes\":{\"dataElementIdScheme\":{\"idScheme\":\"UID\"}," - + "\"orgUnitIdScheme\":{\"idScheme\":\"UID\"}," - + "\"programIdScheme\":{\"idScheme\":\"ATTRIBUTE\",\"attributeUid\":\"aaaa\"}," - + "\"programStageIdScheme\":{\"idScheme\":\"UID\"}," - + "\"idScheme\":{\"idScheme\":\"CODE\"}," - + "\"categoryOptionComboIdScheme\":{\"idScheme\":\"UID\"}," - + "\"categoryOptionIdScheme\":{\"idScheme\":\"UID\"}}," - + "\"importStrategy\":\"DELETE\"," - + "\"atomicMode\":\"OBJECT\"," - + "\"flushMode\":\"OBJECT\"," - + "\"validationMode\":\"SKIP\"," - + "\"skipPatternValidation\":false," - + "\"skipSideEffects\":false," - + "\"skipRuleEngine\":true}", + """ + {"importMode":"COMMIT",\ + "idSchemes":{"dataElementIdScheme":{"idScheme":"CODE"},\ + "orgUnitIdScheme":{"idScheme":"CODE"},\ + "programIdScheme":{"idScheme":"ATTRIBUTE","attributeUid":"aaaa"},\ + "programStageIdScheme":{"idScheme":"CODE"},\ + "idScheme":{"idScheme":"CODE"},\ + "categoryOptionComboIdScheme":{"idScheme":"CODE"},\ + "categoryOptionIdScheme":{"idScheme":"CODE"}},\ + "importStrategy":"DELETE",\ + "atomicMode":"OBJECT",\ + "flushMode":"OBJECT",\ + "validationMode":"SKIP",\ + "skipPatternValidation":false,\ + "skipSideEffects":false,\ + "skipRuleEngine":true}""", json, JSONCompareMode.LENIENT); } @@ -87,22 +90,22 @@ void testJsonSerialization() throws Exception { @Test void testJsonDeserialization() throws IOException { final String json = - "" - + "{\"importMode\":\"COMMIT\"," - + "\"idSchemes\":{\"dataElementIdScheme\":{\"idScheme\":\"UID\"}," - + "\"orgUnitIdScheme\":{\"idScheme\":\"UID\"}," - + "\"programIdScheme\":{\"idScheme\":\"ATTRIBUTE\",\"attributeUid\":\"aaaa\"}," - + "\"programStageIdScheme\":{\"idScheme\":\"UID\"}," - + "\"idScheme\":{\"idScheme\":\"CODE\"}," - + "\"categoryOptionComboIdScheme\":{\"idScheme\":\"UID\"}," - + "\"categoryOptionIdScheme\":{\"idScheme\":\"UID\"}}," - + "\"importStrategy\":\"DELETE\"," - + "\"atomicMode\":\"OBJECT\"," - + "\"flushMode\":\"OBJECT\"," - + "\"validationMode\":\"SKIP\"," - + "\"skipPatternValidation\":true," - + "\"skipSideEffects\":true," - + "\"skipRuleEngine\":true}"; + """ + {"importMode":"COMMIT",\ + "idSchemes":{"dataElementIdScheme":{"idScheme":"UID"},\ + "orgUnitIdScheme":{"idScheme":"UID"},\ + "programIdScheme":{"idScheme":"ATTRIBUTE","attributeUid":"aaaa"},\ + "programStageIdScheme":{"idScheme":"UID"},\ + "idScheme":{"idScheme":"CODE"},\ + "categoryOptionComboIdScheme":{"idScheme":"UID"},\ + "categoryOptionIdScheme":{"idScheme":"UID"}},\ + "importStrategy":"DELETE",\ + "atomicMode":"OBJECT",\ + "flushMode":"OBJECT",\ + "validationMode":"SKIP",\ + "skipPatternValidation":true,\ + "skipSideEffects":true,\ + "skipRuleEngine":true}"""; final TrackerImportParams trackerImportParams = renderService.fromJson(json, TrackerImportParams.class); diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/EnrollmentImportTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/EnrollmentImportTest.java index ac1115bc31c3..def546289b75 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/EnrollmentImportTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/EnrollmentImportTest.java @@ -37,7 +37,6 @@ import java.io.IOException; import java.util.stream.Stream; -import org.hisp.dhis.common.UID; import org.hisp.dhis.feedback.ForbiddenException; import org.hisp.dhis.feedback.NotFoundException; import org.hisp.dhis.program.Enrollment; @@ -83,7 +82,7 @@ void shouldCorrectlyPopulateCompletedDataWhenCreatingAnEnrollment(EnrollmentStat assertNoErrors(importReport); Enrollment enrollment = - enrollmentService.getEnrollment(UID.of(trackerObjects.getEnrollments().get(0).getUid())); + enrollmentService.getEnrollment(trackerObjects.getEnrollments().get(0).getUid()); assertEnrollmentCompletedData(enrollment); } @@ -107,7 +106,7 @@ void shouldCorrectlyPopulateCompletedDataWhenUpdatingAnEnrollment( assertNoErrors(importReport); Enrollment enrollment = - enrollmentService.getEnrollment(UID.of(trackerObjects.getEnrollments().get(0).getUid())); + enrollmentService.getEnrollment(trackerObjects.getEnrollments().get(0).getUid()); assertEnrollmentCompletedData(enrollment); } diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/EventImportTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/EventImportTest.java index 56e9c9d309fc..1d253dce4190 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/EventImportTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/EventImportTest.java @@ -34,7 +34,6 @@ import java.io.IOException; import java.util.stream.Stream; -import org.hisp.dhis.common.UID; import org.hisp.dhis.event.EventStatus; import org.hisp.dhis.feedback.ForbiddenException; import org.hisp.dhis.feedback.NotFoundException; @@ -76,7 +75,7 @@ void shouldPopulateCompletedDataWhenCreatingAnEventWithStatusCompleted() importTracker(params, trackerObjects); - Event event = eventService.getEvent(UID.of(trackerObjects.getEvents().get(0).getUid())); + Event event = eventService.getEvent(trackerObjects.getEvents().get(0).getUid()); assertEquals(importUser.getUsername(), event.getCompletedBy()); assertNotNull(event.getCompletedDate()); @@ -92,7 +91,7 @@ void shouldNotPopulateCompletedDataWhenCreatingAnEventWithNotCompletedStatus(Eve importTracker(params, trackerObjects); - Event event = eventService.getEvent(UID.of(trackerObjects.getEvents().get(0).getUid())); + Event event = eventService.getEvent(trackerObjects.getEvents().get(0).getUid()); assertNull(event.getCompletedBy()); assertNull(event.getCompletedDate()); @@ -111,7 +110,7 @@ void shouldDeleteCompletedDataWhenUpdatingAnEventWithStatusActive() importTracker(params, trackerObjects); - Event event = eventService.getEvent(UID.of(trackerObjects.getEvents().get(0).getUid())); + Event event = eventService.getEvent(trackerObjects.getEvents().get(0).getUid()); assertNull(event.getCompletedBy()); assertNull(event.getCompletedDate()); @@ -131,7 +130,7 @@ void shouldPopulateCompletedDataWhenUpdatingAnEventWithStatusCompleted(EventStat importTracker(params, trackerObjects); - Event event = eventService.getEvent(UID.of(trackerObjects.getEvents().get(0).getUid())); + Event event = eventService.getEvent(trackerObjects.getEvents().get(0).getUid()); assertEquals(importUser.getUsername(), event.getCompletedBy()); assertNotNull(event.getCompletedDate()); diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/LastUpdateImportTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/LastUpdateImportTest.java index a158032322ce..22bbdb33646a 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/LastUpdateImportTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/LastUpdateImportTest.java @@ -38,15 +38,16 @@ import org.hisp.dhis.common.CodeGenerator; import org.hisp.dhis.common.IdentifiableObjectManager; import org.hisp.dhis.common.SoftDeletableObject; +import org.hisp.dhis.common.UID; import org.hisp.dhis.dbms.DbmsManager; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.program.Enrollment; import org.hisp.dhis.program.EnrollmentStatus; import org.hisp.dhis.program.Event; import org.hisp.dhis.trackedentity.TrackedEntity; +import org.hisp.dhis.tracker.TrackerIdScheme; import org.hisp.dhis.tracker.TrackerTest; import org.hisp.dhis.tracker.export.trackedentity.TrackedEntityService; -import org.hisp.dhis.tracker.imports.TrackerIdScheme; import org.hisp.dhis.tracker.imports.TrackerImportParams; import org.hisp.dhis.tracker.imports.TrackerImportService; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; @@ -123,7 +124,8 @@ void shouldUpdateTrackedEntityWhenTrackedEntityIsUpdated() throws IOException { assertTrue( lastUpdateAfter.getTime() > entityBeforeUpdate.getLastUpdated().getTime(), String.format( - "Data integrity error for tracked entity %s. The lastUpdated date has not been updated after the import", + "Data integrity error for tracked entity %s. The lastUpdated date has not been updated" + + " after the import", trackedEntity.getUid())); } @@ -152,13 +154,15 @@ void shouldUpdateTrackedEntityWhenEventIsUpdated() throws IOException { entityAfterUpdate.getLastUpdated().getTime() > entityBeforeUpdate.getLastUpdated().getTime(), String.format( - "Data integrity error for tracked entity %s. The lastUpdated date has not been updated after the import", + "Data integrity error for tracked entity %s. The lastUpdated date has not been updated" + + " after the import", trackedEntity.getUid())); assertEquals( importUser.getUid(), entityAfterUpdate.getLastUpdatedByUserInfo().getUid(), String.format( - "Data integrity error for tracked entity %s. The lastUpdatedByUserinfo has not been saved during the import", + "Data integrity error for tracked entity %s. The lastUpdatedByUserinfo has not been" + + " saved during the import", trackedEntity.getUid())); } @@ -185,13 +189,15 @@ void shouldUpdateTrackedEntityWhenEnrollmentIsUpdated() { entityAfterUpdate.getLastUpdated().getTime() > entityBeforeUpdate.getLastUpdated().getTime(), String.format( - "Data integrity error for tracked entity %s. The lastUpdated date has not been updated after the import", + "Data integrity error for tracked entity %s. The lastUpdated date has not been updated" + + " after the import", trackedEntity.getUid())); assertEquals( importUser.getUid(), entityAfterUpdate.getLastUpdatedByUserInfo().getUid(), String.format( - "Data integrity error for tracked entity %s. The lastUpdatedByUserinfo has not been saved during the import", + "Data integrity error for tracked entity %s. The lastUpdatedByUserinfo has not been" + + " saved during the import", trackedEntity.getUid())); } @@ -219,7 +225,8 @@ void shouldUpdateAndDeleteTrackedEntityWhenTeIsDeleted() { entityAfterDeletion.getLastUpdated().getTime() > entityBeforeUpdate.getLastUpdated().getTime(), String.format( - "Data integrity error for tracked entity %s. The lastUpdated date has not been updated after the import", + "Data integrity error for tracked entity %s. The lastUpdated date has not been" + + " updated after the import", trackedEntity.getUid()))); } @@ -257,7 +264,8 @@ void shouldUpdateAndDeleteTrackedEntityCascadeWhenTeWithEnrollmentIsDeleted() { entityAfterDelete.getLastUpdated().getTime() > entityBeforeUpdate.getLastUpdated().getTime(), String.format( - "Data integrity error for tracked entity %s. The lastUpdated date has not been updated after the import", + "Data integrity error for tracked entity %s. The lastUpdated date has not been" + + " updated after the import", trackedEntity.getUid())), () -> assertTrue(enrollmentAfterDelete.isDeleted()), () -> @@ -265,7 +273,8 @@ void shouldUpdateAndDeleteTrackedEntityCascadeWhenTeWithEnrollmentIsDeleted() { enrollmentAfterDelete.getLastUpdated().getTime() > enrollmentBeforeDelete.getLastUpdated().getTime(), String.format( - "Data integrity error for enrollment %s. The lastUpdated date has not been updated after the import", + "Data integrity error for enrollment %s. The lastUpdated date has not been" + + " updated after the import", enrollment.getUid()))); } @@ -302,7 +311,8 @@ void shouldUpdateTrackedEntityAndDeleteEnrollmentWhenEnrollmentIsDeleted() { entityAfterDeletion.getLastUpdated().getTime() > entityBeforeUpdate.getLastUpdated().getTime(), String.format( - "Data integrity error for tracked entity %s. The lastUpdated date has not been updated after the import", + "Data integrity error for tracked entity %s. The lastUpdated date has not been" + + " updated after the import", trackedEntity.getUid())), () -> assertTrue(enrollmentAfterDeletion.isDeleted()), () -> @@ -310,21 +320,24 @@ void shouldUpdateTrackedEntityAndDeleteEnrollmentWhenEnrollmentIsDeleted() { user.getUid(), entityAfterDeletion.getLastUpdatedByUserInfo().getUid(), String.format( - "Data integrity error for tracked entity %s. The lastUpdatedByUserinfo has not been saved during the import", + "Data integrity error for tracked entity %s. The lastUpdatedByUserinfo has not" + + " been saved during the import", trackedEntity.getUid())), () -> assertTrue( enrollmentAfterDeletion.getLastUpdated().getTime() > enrollmentBeforeDeletion.getLastUpdated().getTime(), String.format( - "Data integrity error for enrollment %s. The lastUpdated date has not been updated after the import", + "Data integrity error for enrollment %s. The lastUpdated date has not been" + + " updated after the import", enrollment.getUid())), () -> assertEquals( user.getUid(), enrollmentAfterDeletion.getLastUpdatedByUserInfo().getUid(), String.format( - "Data integrity error for enrollment %s. The lastUpdatedByUserinfo has not been saved during the import", + "Data integrity error for enrollment %s. The lastUpdatedByUserinfo has not been" + + " saved during the import", enrollment.getUid())), () -> assertTrue(eventAfterDeletion.isDeleted()), () -> @@ -332,14 +345,16 @@ void shouldUpdateTrackedEntityAndDeleteEnrollmentWhenEnrollmentIsDeleted() { eventAfterDeletion.getLastUpdated().getTime() > eventBeforeDeletion.getLastUpdated().getTime(), String.format( - "Data integrity error for event %s. The lastUpdated date has not been updated after the import", + "Data integrity error for event %s. The lastUpdated date has not been updated" + + " after the import", event.getUid())), () -> assertEquals( user.getUid(), eventAfterDeletion.getLastUpdatedByUserInfo().getUid(), String.format( - "Data integrity error for event %s. The lastUpdatedByUserinfo has not been saved during the import", + "Data integrity error for event %s. The lastUpdatedByUserinfo has not been" + + " saved during the import", event.getUid()))); } @@ -375,28 +390,32 @@ void shouldUpdateTrackedEntityAndDeleteEnrolledEventWhenEventIsDeleted() { entityAfterDeletion.getLastUpdated().getTime() > entityBeforeUpdate.getLastUpdated().getTime(), String.format( - "Data integrity error for tracked entity %s. The lastUpdated date has not been updated after the import", + "Data integrity error for tracked entity %s. The lastUpdated date has not been" + + " updated after the import", trackedEntity.getUid())), () -> assertEquals( user.getUid(), entityAfterDeletion.getLastUpdatedByUserInfo().getUid(), String.format( - "Data integrity error for tracked entity %s. The lastUpdatedByUserinfo has not been saved during the import", + "Data integrity error for tracked entity %s. The lastUpdatedByUserinfo has not" + + " been saved during the import", trackedEntity.getUid())), () -> assertTrue( enrollmentAfterDeletion.getLastUpdated().getTime() > enrollmentBeforeDeletion.getLastUpdated().getTime(), String.format( - "Data integrity error for enrollment %s. The lastUpdated date has not been updated after the import", + "Data integrity error for enrollment %s. The lastUpdated date has not been" + + " updated after the import", enrollment.getUid())), () -> assertEquals( user.getUid(), enrollmentAfterDeletion.getLastUpdatedByUserInfo().getUid(), String.format( - "Data integrity error for enrollment %s. The lastUpdatedByUserinfo has not been saved during the import", + "Data integrity error for enrollment %s. The lastUpdatedByUserinfo has not been" + + " saved during the import", enrollment.getUid())), () -> assertTrue(eventAfterDeletion.isDeleted()), () -> @@ -404,14 +423,16 @@ void shouldUpdateTrackedEntityAndDeleteEnrolledEventWhenEventIsDeleted() { eventAfterDeletion.getLastUpdated().getTime() > eventBeforeDeletion.getLastUpdated().getTime(), String.format( - "Data integrity error for event %s. The lastUpdated date has not been updated after the import", + "Data integrity error for event %s. The lastUpdated date has not been updated" + + " after the import", event.getUid())), () -> assertEquals( user.getUid(), eventAfterDeletion.getLastUpdatedByUserInfo().getUid(), String.format( - "Data integrity error for event %s. The lastUpdatedByUserinfo has not been saved during the import", + "Data integrity error for event %s. The lastUpdatedByUserinfo has not been" + + " saved during the import", event.getUid()))); } @@ -443,14 +464,16 @@ void shouldUpdatedEventProgramWhenEventIsDeleted() throws IOException { eventAfterDeletion.getLastUpdated().getTime() > eventBeforeDeletion.getLastUpdated().getTime(), String.format( - "Data integrity error for event %s. The lastUpdated date has not been updated after the import", + "Data integrity error for event %s. The lastUpdated date has not been updated" + + " after the import", event.getUid())), () -> assertEquals( user.getUid(), eventAfterDeletion.getLastUpdatedByUserInfo().getUid(), String.format( - "Data integrity error for event %s. The lastUpdatedByUserinfo has not been saved during the import", + "Data integrity error for event %s. The lastUpdatedByUserinfo has not been" + + " saved during the import", event.getUid()))); } @@ -465,7 +488,7 @@ private org.hisp.dhis.tracker.imports.domain.Event importEventProgram() throws I TrackerObjects trackerObjects = fromJson("tracker/single_event.json"); org.hisp.dhis.tracker.imports.domain.Event ev = trackerObjects.getEvents().get(0); ev.setEnrollment(null); - ev.setEvent(CodeGenerator.generateUid()); + ev.setEvent(UID.generate()); // set event program and program stage ev.setProgramStage(MetadataIdentifier.of(TrackerIdScheme.UID, "NpsdDv6kKSe", null)); ev.setProgram(MetadataIdentifier.of(TrackerIdScheme.UID, "BFcipDERJne", null)); @@ -496,19 +519,19 @@ void clearSession() { } Enrollment getEnrollment() { - return getEntityJpql(Enrollment.class.getSimpleName(), enrollment.getUid()); + return getEntityJpql(Enrollment.class.getSimpleName(), enrollment.getUid().getValue()); } Event getEvent() { - return getEntityJpql(Event.class.getSimpleName(), event.getUid()); + return getEntityJpql(Event.class.getSimpleName(), event.getUid().getValue()); } - Event getEvent(String uid) { - return getEntityJpql(Event.class.getSimpleName(), uid); + Event getEvent(UID uid) { + return getEntityJpql(Event.class.getSimpleName(), uid.getValue()); } TrackedEntity getTrackedEntity() { - return getEntityJpql(TrackedEntity.class.getSimpleName(), trackedEntity.getUid()); + return getEntityJpql(TrackedEntity.class.getSimpleName(), trackedEntity.getUid().getValue()); } /** diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/OwnershipTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/OwnershipTest.java index b09fd731b169..1ef2345b57fd 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/OwnershipTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/OwnershipTest.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis.tracker.imports.bundle; +import static org.hisp.dhis.test.utils.Assertions.assertEqualUids; import static org.hisp.dhis.tracker.Assertions.assertHasError; import static org.hisp.dhis.tracker.Assertions.assertHasOnlyErrors; import static org.hisp.dhis.tracker.Assertions.assertNoErrors; @@ -39,8 +40,8 @@ import java.time.Instant; import java.util.List; import java.util.Set; -import org.hisp.dhis.common.CodeGenerator; import org.hisp.dhis.common.IdentifiableObjectManager; +import org.hisp.dhis.common.UID; import org.hisp.dhis.feedback.ForbiddenException; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.program.Enrollment; @@ -110,9 +111,8 @@ void testProgramOwnerWhenEnrolled() throws IOException { assertTrue(trackerObjects.getEnrollments().get(0).getProgram().isEqualTo(tepo.getProgram())); assertTrue( trackerObjects.getEnrollments().get(0).getOrgUnit().isEqualTo(tepo.getOrganisationUnit())); - assertEquals( - trackerObjects.getEnrollments().get(0).getTrackedEntity(), - tepo.getTrackedEntity().getUid()); + assertEqualUids( + trackerObjects.getEnrollments().get(0).getTrackedEntity(), tepo.getTrackedEntity()); } @Test @@ -223,7 +223,7 @@ void testCreateEnrollmentAfterDeleteEnrollment() throws IOException { enrollments = manager.getAll(Enrollment.class); assertEquals(1, enrollments.size()); params.setImportStrategy(TrackerImportStrategy.CREATE); - trackerObjects.getEnrollments().get(0).setEnrollment(CodeGenerator.generateUid()); + trackerObjects.getEnrollments().get(0).setEnrollment(UID.generate()); updatedReport = trackerImportService.importTracker(params, trackerObjects); assertNoErrors(updatedReport); assertEquals(1, updatedReport.getStats().getCreated()); @@ -247,7 +247,7 @@ void testCreateEnrollmentWithoutOwnership() throws IOException, ForbiddenExcepti Program pgm = manager.get(Program.class, "BFcipDERJnf"); trackerOwnershipManager.transferOwnership(trackedEntity, pgm, ou); params.setImportStrategy(TrackerImportStrategy.CREATE); - trackerObjects.getEnrollments().get(0).setEnrollment(CodeGenerator.generateUid()); + trackerObjects.getEnrollments().get(0).setEnrollment(UID.generate()); updatedReport = trackerImportService.importTracker(params, trackerObjects); assertEquals(1, updatedReport.getStats().getIgnored()); assertHasError(updatedReport, ValidationCode.E1102); diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackedEntityAttributeTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackedEntityAttributeTest.java index ae740a8ad8ca..9dab76d1af35 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackedEntityAttributeTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackedEntityAttributeTest.java @@ -39,8 +39,8 @@ import org.hisp.dhis.trackedentity.TrackedEntityAttribute; import org.hisp.dhis.trackedentity.TrackedEntityType; import org.hisp.dhis.trackedentityattributevalue.TrackedEntityAttributeValue; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.TrackerTest; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportParams; import org.hisp.dhis.tracker.imports.TrackerImportService; import org.hisp.dhis.tracker.imports.domain.TrackerObjects; diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackedEntityDataValueChangeLogTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackedEntityDataValueChangeLogTest.java deleted file mode 100644 index f5cfe98f153b..000000000000 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackedEntityDataValueChangeLogTest.java +++ /dev/null @@ -1,163 +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.imports.bundle; - -import static org.hisp.dhis.tracker.Assertions.assertNoErrors; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import java.io.IOException; -import java.util.List; -import org.hisp.dhis.changelog.ChangeLogType; -import org.hisp.dhis.common.IdentifiableObjectManager; -import org.hisp.dhis.dataelement.DataElement; -import org.hisp.dhis.program.Event; -import org.hisp.dhis.tracker.TrackerTest; -import org.hisp.dhis.tracker.export.event.EventChangeLogService; -import org.hisp.dhis.tracker.export.event.TrackedEntityDataValueChangeLog; -import org.hisp.dhis.tracker.export.event.TrackedEntityDataValueChangeLogQueryParams; -import org.hisp.dhis.tracker.imports.TrackerImportParams; -import org.hisp.dhis.tracker.imports.TrackerImportService; -import org.hisp.dhis.user.User; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -/** - * @author Zubair Asghar - */ -public class TrackedEntityDataValueChangeLogTest extends TrackerTest { - private static final String ORIGINAL_VALUE = "value1"; - - private static final String UPDATED_VALUE = "value1-updated"; - - private static final String PSI = "D9PbzJY8bJO"; - - public static final String DE = "DATAEL00001"; - - @Autowired private TrackerImportService trackerImportService; - - @Autowired private IdentifiableObjectManager manager; - - @Autowired private EventChangeLogService eventChangeLogService; - - private DataElement dataElement; - - private Event event; - - @BeforeAll - void setUp() throws IOException { - setUpMetadata("tracker/simple_metadata.json"); - - User importUser = userService.getUser("tTgjgobT1oS"); - injectSecurityContextUser(importUser); - } - - @Test - void testTrackedEntityDataValueAuditCreate() throws IOException { - TrackerImportParams params = new TrackerImportParams(); - assertNoErrors( - trackerImportService.importTracker( - params, fromJson("tracker/event_and_enrollment_with_data_values.json"))); - assertNoErrors( - trackerImportService.importTracker( - params, fromJson("tracker/event_with_data_values_for_update_audit.json"))); - assertNoErrors( - trackerImportService.importTracker( - params, fromJson("tracker/event_with_data_values_for_delete_audit.json"))); - - dataElement = manager.search(DataElement.class, DE); - event = manager.search(Event.class, PSI); - assertNotNull(dataElement); - assertNotNull(event); - - List createdAudit = - eventChangeLogService.getTrackedEntityDataValueChangeLogs( - new TrackedEntityDataValueChangeLogQueryParams() - .setDataElements(List.of(dataElement)) - .setEvents(List.of(event)) - .setAuditTypes(List.of(ChangeLogType.CREATE))); - List updatedAudit = - eventChangeLogService.getTrackedEntityDataValueChangeLogs( - new TrackedEntityDataValueChangeLogQueryParams() - .setDataElements(List.of(dataElement)) - .setEvents(List.of(event)) - .setAuditTypes(List.of(ChangeLogType.UPDATE))); - List deletedAudit = - eventChangeLogService.getTrackedEntityDataValueChangeLogs( - new TrackedEntityDataValueChangeLogQueryParams() - .setDataElements(List.of(dataElement)) - .setEvents(List.of(event)) - .setAuditTypes(List.of(ChangeLogType.DELETE))); - - assertAll( - () -> assertNotNull(createdAudit), - () -> assertNotNull(updatedAudit), - () -> assertNotNull(deletedAudit)); - assertAuditCollection(createdAudit, ChangeLogType.CREATE, ORIGINAL_VALUE); - assertAuditCollection(updatedAudit, ChangeLogType.UPDATE, ORIGINAL_VALUE); - assertAuditCollection(deletedAudit, ChangeLogType.DELETE, UPDATED_VALUE); - } - - private void assertAuditCollection( - List audits, - ChangeLogType changeLogType, - String expectedValue) { - assertAll( - () -> assertFalse(audits.isEmpty()), - () -> - assertEquals( - changeLogType, - audits.get(0).getAuditType(), - () -> - "Expected audit type is " - + changeLogType - + " but found " - + audits.get(0).getAuditType()), - () -> - assertEquals( - audits.get(0).getDataElement().getUid(), - dataElement.getUid(), - () -> - "Expected dataElement is " - + dataElement.getUid() - + " but found " - + audits.get(0).getDataElement().getUid()), - () -> - assertEquals( - expectedValue, - audits.get(0).getValue(), - () -> - "Expected value is " - + expectedValue - + " but found " - + audits.get(0).getValue())); - } -} diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackerNotificationHandlerServiceTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackerNotificationHandlerServiceTest.java index aa46c5c8564f..62dd57196bc1 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackerNotificationHandlerServiceTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/bundle/TrackerNotificationHandlerServiceTest.java @@ -38,6 +38,7 @@ import java.util.concurrent.TimeUnit; import org.hisp.dhis.common.CodeGenerator; import org.hisp.dhis.common.IdentifiableObjectManager; +import org.hisp.dhis.common.UID; import org.hisp.dhis.event.EventStatus; import org.hisp.dhis.message.MessageConversation; import org.hisp.dhis.organisationunit.OrganisationUnit; @@ -78,15 +79,8 @@ class TrackerNotificationHandlerServiceTest extends PostgresIntegrationTestBase private OrganisationUnit orgUnitA; - private TrackedEntityType trackedEntityTypeA; - private TrackedEntity trackedEntityA; - private ProgramNotificationTemplate templateForEnrollmentCompletion; - private ProgramNotificationTemplate templateForEnrollment; - private ProgramNotificationTemplate templateForEventCompletion; - - private User user; private UserGroup userGroup; @BeforeEach @@ -94,7 +88,7 @@ void setUp() { orgUnitA = createOrganisationUnit('A'); manager.save(orgUnitA, false); - trackedEntityTypeA = createTrackedEntityType('A'); + TrackedEntityType trackedEntityTypeA = createTrackedEntityType('A'); manager.save(trackedEntityTypeA, false); programA = createProgram('P', new HashSet<>(), orgUnitA); @@ -111,7 +105,7 @@ void setUp() { trackedEntityA.setTrackedEntityType(trackedEntityTypeA); manager.save(trackedEntityA, false); - user = createAndAddUser(false, "user", Set.of(orgUnitA), Set.of(orgUnitA), "ALL"); + User user = createAndAddUser(false, "user", Set.of(orgUnitA), Set.of(orgUnitA), "ALL"); userGroup = createUserGroup('U', Set.of(user)); manager.save(userGroup, false); @@ -121,19 +115,19 @@ void setUp() { injectSecurityContextUser(user); - templateForEnrollment = + ProgramNotificationTemplate templateForEnrollment = createProgramNotification( "enrollment", CodeGenerator.generateUid(), "enrollment_subject", NotificationTrigger.ENROLLMENT); - templateForEnrollmentCompletion = + ProgramNotificationTemplate templateForEnrollmentCompletion = createProgramNotification( "enrollment_completion", CodeGenerator.generateUid(), "enrollment_completion_subject", NotificationTrigger.COMPLETION); - templateForEventCompletion = + ProgramNotificationTemplate templateForEventCompletion = createProgramNotification( "event_completion", CodeGenerator.generateUid(), @@ -158,11 +152,11 @@ void shouldSendTrackerNotificationAtEnrollment() { Enrollment.builder() .program(MetadataIdentifier.ofUid(programA.getUid())) .orgUnit(MetadataIdentifier.ofUid(orgUnitA.getUid())) - .trackedEntity(trackedEntityA.getUid()) + .trackedEntity(UID.of(trackedEntityA)) .status(EnrollmentStatus.ACTIVE) .enrolledAt(Instant.now()) .occurredAt(Instant.now()) - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .build(); ImportReport importReport = @@ -188,24 +182,24 @@ void shouldSendTrackerNotificationAtEnrollment() { @Test void shouldSendTrackerNotificationAtEnrollmentCompletionAndThenEventCompletion() { - String eventUid = CodeGenerator.generateUid(); + UID eventUid = UID.generate(); Enrollment enrollment = Enrollment.builder() .program(MetadataIdentifier.ofUid(programA.getUid())) .orgUnit(MetadataIdentifier.ofUid(orgUnitA.getUid())) - .trackedEntity(trackedEntityA.getUid()) + .trackedEntity(UID.of(trackedEntityA)) .status(EnrollmentStatus.COMPLETED) .enrolledAt(Instant.now()) .occurredAt(Instant.now()) - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .build(); org.hisp.dhis.tracker.imports.domain.Event event = org.hisp.dhis.tracker.imports.domain.Event.builder() .program(MetadataIdentifier.ofUid(programA.getUid())) .orgUnit(MetadataIdentifier.ofUid(orgUnitA.getUid())) - .enrollment(enrollment.getEnrollment()) + .enrollment(enrollment.getUid()) .event(eventUid) .programStage(MetadataIdentifier.ofUid(programStageA.getUid())) .status(EventStatus.ACTIVE) @@ -241,7 +235,7 @@ void shouldSendTrackerNotificationAtEnrollmentCompletionAndThenEventCompletion() org.hisp.dhis.tracker.imports.domain.Event.builder() .program(MetadataIdentifier.ofUid(programA.getUid())) .orgUnit(MetadataIdentifier.ofUid(orgUnitA.getUid())) - .enrollment(enrollment.getEnrollment()) + .enrollment(enrollment.getUid()) .event(eventUid) .programStage(MetadataIdentifier.ofUid(programStageA.getUid())) .status(EventStatus.COMPLETED) @@ -272,12 +266,12 @@ void shouldSendTrackerNotificationAtEnrollmentCompletionAndThenEventCompletion() @Test void shouldSendEnrollmentCompletionNotificationWhenStatusIsUpdatedFromActiveToCompleted() { - String uid = CodeGenerator.generateUid(); + UID uid = UID.generate(); org.hisp.dhis.tracker.imports.domain.Enrollment enrollment = org.hisp.dhis.tracker.imports.domain.Enrollment.builder() .program(MetadataIdentifier.ofUid(programA.getUid())) .orgUnit(MetadataIdentifier.ofUid(orgUnitA.getUid())) - .trackedEntity(trackedEntityA.getUid()) + .trackedEntity(UID.of(trackedEntityA)) .status(EnrollmentStatus.ACTIVE) .enrollment(uid) .enrolledAt(Instant.now()) @@ -295,7 +289,7 @@ void shouldSendEnrollmentCompletionNotificationWhenStatusIsUpdatedFromActiveToCo await() .atMost(3, TimeUnit.SECONDS) - .until(() -> manager.getAll(MessageConversation.class).size() > 0); + .until(() -> !manager.getAll(MessageConversation.class).isEmpty()); List messageConversations = manager.getAll(MessageConversation.class); @@ -308,7 +302,7 @@ void shouldSendEnrollmentCompletionNotificationWhenStatusIsUpdatedFromActiveToCo org.hisp.dhis.tracker.imports.domain.Enrollment.builder() .program(MetadataIdentifier.ofUid(programA.getUid())) .orgUnit(MetadataIdentifier.ofUid(orgUnitA.getUid())) - .trackedEntity(trackedEntityA.getUid()) + .trackedEntity(UID.of(trackedEntityA)) .status(EnrollmentStatus.COMPLETED) .enrollment(uid) .enrolledAt(Instant.now()) @@ -336,12 +330,12 @@ void shouldSendEnrollmentCompletionNotificationWhenStatusIsUpdatedFromActiveToCo @Test void shouldSendEnrollmentCompletionNotificationOnlyOnce() { - String uid = CodeGenerator.generateUid(); + UID uid = UID.generate(); Enrollment enrollment = Enrollment.builder() .program(MetadataIdentifier.ofUid(programA.getUid())) .orgUnit(MetadataIdentifier.ofUid(orgUnitA.getUid())) - .trackedEntity(trackedEntityA.getUid()) + .trackedEntity(UID.of(trackedEntityA)) .status(EnrollmentStatus.COMPLETED) .enrollment(uid) .enrolledAt(Instant.now()) diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheatIdentifiersTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheatIdentifiersTest.java index 38ae4636002b..69891c46c990 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheatIdentifiersTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheatIdentifiersTest.java @@ -47,12 +47,13 @@ import org.hisp.dhis.common.CodeGenerator; import org.hisp.dhis.common.IdentifiableObject; import org.hisp.dhis.common.IdentifiableObjectManager; +import org.hisp.dhis.common.UID; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.program.ProgramStage; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.TrackerTest; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.domain.DataValue; import org.hisp.dhis.tracker.imports.domain.Event; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; @@ -84,7 +85,8 @@ void testOrgUnitIdentifiers() { for (Pair pair : data) { String id = pair.getLeft(); TrackerIdSchemeParam param = pair.getRight(); - Event event = Event.builder().orgUnit(param.toMetadataIdentifier(id)).build(); + Event event = + Event.builder().event(UID.generate()).orgUnit(param.toMetadataIdentifier(id)).build(); TrackerObjects trackerObjects = TrackerObjects.builder().events(List.of(event)).build(); TrackerIdSchemeParams params = TrackerIdSchemeParams.builder().orgUnitIdScheme(param).build(); @@ -100,7 +102,11 @@ void testProgramStageIdentifiers() { for (Pair pair : data) { String id = pair.getLeft(); TrackerIdSchemeParam param = pair.getRight(); - Event event = Event.builder().programStage(param.toMetadataIdentifier(id)).build(); + Event event = + Event.builder() + .event(UID.generate()) + .programStage(param.toMetadataIdentifier(id)) + .build(); TrackerObjects trackerObjects = TrackerObjects.builder().events(List.of(event)).build(); TrackerIdSchemeParams params = TrackerIdSchemeParams.builder().programStageIdScheme(param).build(); @@ -121,6 +127,7 @@ void testDataElementIdentifiers() { DataValue.builder().dataElement(param.toMetadataIdentifier(id)).value("val1").build(); Event event = Event.builder() + .event(UID.generate()) .programStage(MetadataIdentifier.ofUid("NpsdDv6kKSO")) .dataValues(Collections.singleton(dv1)) .build(); @@ -141,7 +148,10 @@ void testCategoryOptionIdentifiers() { String id = pair.getLeft(); TrackerIdSchemeParam param = pair.getRight(); Event event = - Event.builder().attributeCategoryOptions(Set.of(param.toMetadataIdentifier(id))).build(); + Event.builder() + .event(UID.generate()) + .attributeCategoryOptions(Set.of(param.toMetadataIdentifier(id))) + .build(); TrackerObjects trackerObjects = TrackerObjects.builder().events(List.of(event)).build(); TrackerIdSchemeParams params = TrackerIdSchemeParams.builder().categoryOptionIdScheme(param).build(); @@ -154,11 +164,16 @@ void testCategoryOptionIdentifiers() { @Test void testCategoryOptionComboIdentifiers() { - List> data = buildDataSet("XXXvX50cXC0", "COCA", "COCAname"); + List> data = + buildDataSet("HllvX50cXC0", "default", "default"); for (Pair pair : data) { String id = pair.getLeft(); TrackerIdSchemeParam param = pair.getRight(); - Event event = Event.builder().attributeOptionCombo(param.toMetadataIdentifier(id)).build(); + Event event = + Event.builder() + .event(UID.generate()) + .attributeOptionCombo(param.toMetadataIdentifier(id)) + .build(); TrackerObjects trackerObjects = TrackerObjects.builder().events(List.of(event)).build(); TrackerIdSchemeParams params = TrackerIdSchemeParams.builder().categoryOptionComboIdScheme(param).build(); @@ -185,7 +200,10 @@ void testDefaultsWithIdSchemeUID() { @Test void testDefaultsWithIdSchemesOtherThanUID() { - TrackerObjects trackerObjects = TrackerObjects.builder().events(List.of(new Event())).build(); + TrackerObjects trackerObjects = + TrackerObjects.builder() + .events(List.of(Event.builder().event(UID.generate()).build())) + .build(); TrackerIdSchemeParams params = TrackerIdSchemeParams.builder() .idScheme(TrackerIdSchemeParam.NAME) diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheatServiceIntegrationTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheatServiceIntegrationTest.java index 7ce1506279dd..51e9cbd690d3 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheatServiceIntegrationTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheatServiceIntegrationTest.java @@ -35,6 +35,7 @@ import org.hisp.dhis.attribute.Attribute; import org.hisp.dhis.attribute.AttributeService; import org.hisp.dhis.attribute.AttributeValues; +import org.hisp.dhis.common.UID; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.organisationunit.OrganisationUnitService; import org.hisp.dhis.program.Program; @@ -43,8 +44,8 @@ import org.hisp.dhis.test.integration.PostgresIntegrationTestBase; import org.hisp.dhis.trackedentity.TrackedEntityType; import org.hisp.dhis.trackedentity.TrackedEntityTypeService; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.domain.Enrollment; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; import org.hisp.dhis.tracker.imports.domain.TrackedEntity; @@ -105,14 +106,16 @@ void setUp() { void testPreheatWithDifferentIdSchemes() { TrackedEntity teA = TrackedEntity.builder() + .trackedEntity(UID.generate()) .orgUnit(MetadataIdentifier.ofCode("OUA")) .trackedEntityType(MetadataIdentifier.ofUid(TET_UID)) .build(); Enrollment enrollmentA = Enrollment.builder() + .enrollment(UID.generate()) .orgUnit(MetadataIdentifier.ofCode("OUA")) .program(MetadataIdentifier.ofAttribute(ATTRIBUTE_UID, programAttribute)) - .trackedEntity("TE123456789") + .trackedEntity(UID.of("TE123456789")) .build(); TrackerObjects trackerObjects = diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheatServiceTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheatServiceTest.java index a719d6836877..49defcb12f24 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheatServiceTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/preheat/TrackerPreheatServiceTest.java @@ -38,11 +38,12 @@ import java.util.Set; import org.hisp.dhis.category.CategoryOption; import org.hisp.dhis.category.CategoryOptionCombo; +import org.hisp.dhis.common.UID; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.program.ProgramStage; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.TrackerTest; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerIdentifierCollector; import org.hisp.dhis.tracker.imports.domain.MetadataIdentifier; import org.hisp.dhis.tracker.imports.domain.TrackedEntity; @@ -105,7 +106,7 @@ void testCollectIdentifiersAttributeValues() { .trackedEntities( Lists.newArrayList( TrackedEntity.builder() - .trackedEntity("TE012345678") + .trackedEntity(UID.of("TE012345678")) .orgUnit(MetadataIdentifier.ofCode("OU123456789")) .build())) .build(); @@ -148,7 +149,7 @@ void testPreheatEvents() throws IOException { assertFalse(preheat.getAll(OrganisationUnit.class).isEmpty()); assertFalse(preheat.getAll(ProgramStage.class).isEmpty()); assertFalse(preheat.getAll(CategoryOptionCombo.class).isEmpty()); - assertNotNull(preheat.get(CategoryOptionCombo.class, "XXXvX50cXC0")); + assertNotNull(preheat.get(CategoryOptionCombo.class, "HllvX50cXC0")); assertNotNull(preheat.get(CategoryOption.class, "XXXrKDKCefk")); } } diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/programrule/ProgramRuleAssignActionTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/programrule/ProgramRuleAssignActionTest.java index 732b0819490f..723b1cfdbb2a 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/programrule/ProgramRuleAssignActionTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/programrule/ProgramRuleAssignActionTest.java @@ -38,7 +38,7 @@ import java.io.IOException; import java.util.List; -import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.dxf2.metadata.objectbundle.ObjectBundle; import org.hisp.dhis.eventdatavalue.EventDataValue; @@ -173,10 +173,10 @@ void shouldImportEventAndCorrectlyAssignPreviousEventDataValue( void shouldImportEventAndCorrectlyAssignPreviousEventDataValueConsideringCreateAtWhenOccurredAtIsSame() throws IOException { - String firstEventUid = CodeGenerator.generateUid(); - String secondEventUid = CodeGenerator.generateUid(); - String thirdEventUid = CodeGenerator.generateUid(); - String fourthEventUid = CodeGenerator.generateUid(); + UID firstEventUid = UID.generate(); + UID secondEventUid = UID.generate(); + UID thirdEventUid = UID.generate(); + UID fourthEventUid = UID.generate(); TrackerImportParams params = new TrackerImportParams(); params.setImportStrategy(TrackerImportStrategy.CREATE_AND_UPDATE); @@ -281,7 +281,7 @@ void shouldNotImportWhenDataElementWithDifferentValueIsAssignedByAssignRule() th assertHasOnlyWarnings(importReport, E1308); } - private TrackerObjects getEvent(String eventUid, String occurredDate, String value) + private TrackerObjects getEvent(UID eventUid, String occurredDate, String value) throws IOException { TrackerObjects trackerObjects = fromJson("tracker/programrule/event_without_date.json"); trackerObjects @@ -294,8 +294,8 @@ private TrackerObjects getEvent(String eventUid, String occurredDate, String val return trackerObjects; } - private List getValueForAssignedDataElement(String eventUid) { - return manager.get(Event.class, eventUid).getEventDataValues().stream() + private List getValueForAssignedDataElement(UID eventUid) { + return manager.get(Event.class, eventUid.getValue()).getEventDataValues().stream() .filter(dv -> dv.getDataElement().equals("DATAEL00002")) .map(EventDataValue::getValue) .toList(); diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/programrule/ProgramRuleTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/programrule/ProgramRuleTest.java index d3edd1deb3e4..7c558d98229a 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/programrule/ProgramRuleTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/programrule/ProgramRuleTest.java @@ -38,6 +38,7 @@ import static org.junit.jupiter.api.Assertions.assertAll; import java.io.IOException; +import org.hisp.dhis.common.UID; import org.hisp.dhis.constant.Constant; import org.hisp.dhis.constant.ConstantService; import org.hisp.dhis.dataelement.DataElement; @@ -64,11 +65,11 @@ import org.springframework.beans.factory.annotation.Autowired; class ProgramRuleTest extends TrackerTest { - private static final String ENROLLMENT_UID = "TvctPPhpD8u"; + private static final UID ENROLLMENT_UID = UID.of("TvctPPhpD8u"); - private static final String EVENT_UID = "D9PbzJY8bJO"; + private static final UID EVENT_UID = UID.of("D9PbzJY8bJO"); - private static final String PROGRAM_EVENT_UID = "PEVENT12345"; + private static final UID PROGRAM_EVENT_UID = UID.of("PEVENT12345"); @Autowired private TrackerImportService trackerImportService; diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/validation/EnrollmentImportValidationTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/validation/EnrollmentImportValidationTest.java index fa0181c9520c..abcfd866a784 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/validation/EnrollmentImportValidationTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/validation/EnrollmentImportValidationTest.java @@ -36,9 +36,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; +import org.hisp.dhis.tracker.TrackerIdSchemeParams; import org.hisp.dhis.tracker.TrackerTest; import org.hisp.dhis.tracker.TrackerType; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParams; import org.hisp.dhis.tracker.imports.TrackerImportParams; import org.hisp.dhis.tracker.imports.TrackerImportService; import org.hisp.dhis.tracker.imports.TrackerImportStrategy; diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/validation/EnrollmentSecurityImportValidationTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/validation/EnrollmentSecurityImportValidationTest.java index 1703fcb9d467..caa9954f3523 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/validation/EnrollmentSecurityImportValidationTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/validation/EnrollmentSecurityImportValidationTest.java @@ -39,8 +39,8 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import org.hisp.dhis.common.CodeGenerator; import org.hisp.dhis.common.IdentifiableObjectManager; +import org.hisp.dhis.common.UID; import org.hisp.dhis.common.ValueType; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.organisationunit.OrganisationUnit; @@ -365,11 +365,11 @@ private List createEnrollment( org.hisp.dhis.tracker.imports.domain.Enrollment.builder() .program(MetadataIdentifier.ofUid(program.getUid())) .orgUnit(MetadataIdentifier.ofUid(orgUnit.getUid())) - .trackedEntity(trackedEntity.getUid()) + .trackedEntity(UID.of(trackedEntity)) .status(EnrollmentStatus.ACTIVE) .enrolledAt(Instant.now()) .occurredAt(Instant.now()) - .enrollment(CodeGenerator.generateUid()) + .enrollment(UID.generate()) .build()); } } diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/validation/EventImportValidationTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/validation/EventImportValidationTest.java index 83a9e1dd5837..646276e5e0ec 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/validation/EventImportValidationTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/validation/EventImportValidationTest.java @@ -49,6 +49,7 @@ import java.util.stream.Stream; import lombok.SneakyThrows; import org.hisp.dhis.common.CodeGenerator; +import org.hisp.dhis.common.UID; import org.hisp.dhis.note.Note; import org.hisp.dhis.program.Event; import org.hisp.dhis.tracker.TrackerTest; @@ -430,7 +431,7 @@ private Note getByNote(List notes, String noteText) { private Event getEventFromReport(ImportReport importReport) { final Map typeReportMap = importReport.getPersistenceReport().getTypeReportMap(); - String newEvent = typeReportMap.get(TrackerType.EVENT).getEntityReport().get(0).getUid(); + UID newEvent = typeReportMap.get(TrackerType.EVENT).getEntityReport().get(0).getUid(); return manager.get(Event.class, newEvent); } } diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/validation/TrackedEntityImportValidationTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/validation/TrackedEntityImportValidationTest.java index e3220acab1d7..e5a682b08d7a 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/validation/TrackedEntityImportValidationTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/imports/validation/TrackedEntityImportValidationTest.java @@ -41,6 +41,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; +import org.hisp.dhis.common.UID; import org.hisp.dhis.feedback.ForbiddenException; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.program.Program; @@ -318,7 +319,7 @@ void shouldFailToUpdateWhenUserHasAccessToRegistrationUnitAndTEWasTransferred() trackerOwnershipManager.transferOwnership(trackedEntity, program, orgUnit); manager.flush(); manager.clear(); - importReport = updateTransferredTrackedEntity(USER_10, "KKKKj6vYdes"); + importReport = updateTransferredTrackedEntity(USER_10, UID.of("KKKKj6vYdes")); assertHasErrors(importReport, 1, ValidationCode.E1003); } @@ -339,7 +340,7 @@ void shouldUpdateWhenTEWasTransferredAndUserHasAccessToTransferredOrgUnit() trackerOwnershipManager.transferOwnership(trackedEntity, program, orgUnit); manager.flush(); manager.clear(); - ImportReport importReport = updateTransferredTrackedEntity(USER_9, "Kj6vYde4LHh"); + ImportReport importReport = updateTransferredTrackedEntity(USER_9, UID.of("Kj6vYde4LHh")); assertNoErrors(importReport); } @@ -379,7 +380,7 @@ protected ImportReport deleteTransferredTrackedEntity(User user) throws IOExcept return trackerImportService.importTracker(params, trackerObjects); } - protected ImportReport updateTransferredTrackedEntity(String userId, String trackedEntity) + protected ImportReport updateTransferredTrackedEntity(String userId, UID trackedEntity) throws IOException { TrackerObjects trackerObjects = fromJson("tracker/validations/te-transferred-data-update.json"); trackerObjects.getTrackedEntities().get(0).setTrackedEntity(trackedEntity); diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/user/UserStoreTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/user/UserStoreTest.java index 6e9c685d3273..e7aa59513231 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/user/UserStoreTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/user/UserStoreTest.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis.user; +import static io.hypersistence.utils.jdbc.validator.SQLStatementCountValidator.assertSelectCount; import static org.hisp.dhis.util.DateUtils.parseDate; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -34,20 +35,25 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import io.hypersistence.utils.jdbc.validator.SQLStatementCountValidator; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; +import org.hisp.dhis.common.UID; import org.hisp.dhis.dbms.DbmsManager; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.organisationunit.OrganisationUnitService; +import org.hisp.dhis.test.config.QueryCountDataSourceProxy; import org.hisp.dhis.test.integration.PostgresIntegrationTestBase; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; import org.springframework.transaction.annotation.Transactional; /** @@ -55,6 +61,7 @@ */ @TestInstance(Lifecycle.PER_CLASS) @Transactional +@ContextConfiguration(classes = {QueryCountDataSourceProxy.class}) class UserStoreTest extends PostgresIntegrationTestBase { public static final String AUTH_A = "AuthA"; @@ -278,4 +285,37 @@ void testGetUserByOpenId() { User foundUser = userStore.getUserByOpenId(openId1); assertEquals(userB.getUid(), foundUser.getUid()); } + + @Test + @DisplayName("Get users by org unit uid with expected select count") + void getUsersByOrgUnitUidExpectedSelectCountTest() { + // given 2 org units & 4 users + OrganisationUnit ou1 = createOrganisationUnit("org unit test 1"); + OrganisationUnit ou2 = createOrganisationUnit("org unit test 2"); + organisationUnitService.addOrganisationUnit(ou1); + organisationUnitService.addOrganisationUnit(ou2); + + User user1 = createAndAddUser("user1 test", ou1); + User user2 = createAndAddUser("user2 test", ou1); + User user3 = createAndAddUser("user3 test", ou2); + User user4 = createAndAddUser("user4 test no orgs"); + userService.addUser(user1); + userService.addUser(user2); + userService.addUser(user3); + userService.addUser(user4); + + // when retrieving users by org unit uid + SQLStatementCountValidator.reset(); + List users = userStore.getUsersWithOrgUnit(UserOrgUnitProperty.ORG_UNITS, UID.of(ou1)); + // getting each org unit to assert later that no other select queries triggered + users.forEach( + u -> + assertTrue( + u.getOrganisationUnits().stream() + .allMatch(ou -> ou.getUid().equals(ou1.getUid())))); + + // then only 1 select query is triggered + assertSelectCount(1); + assertEquals(2, users.size()); + } } diff --git a/dhis-2/dhis-test-integration/src/test/resources/tracker/event_and_enrollment.json b/dhis-2/dhis-test-integration/src/test/resources/tracker/event_and_enrollment.json index 65e456734b90..5a08b6674640 100644 --- a/dhis-2/dhis-test-integration/src/test/resources/tracker/event_and_enrollment.json +++ b/dhis-2/dhis-test-integration/src/test/resources/tracker/event_and_enrollment.json @@ -826,7 +826,7 @@ "idScheme": "UID", "identifier": "DiszpKrYNg8" }, - "status": "ACTIVE", + "status": "COMPLETED", "deleted": false, "dataValues": [ { @@ -839,8 +839,13 @@ "providedElsewhere": false } ], - "notes": [], - "relationships": [] + "geometry": { + "type": "Point", + "coordinates": [ + -11.4197, + 8.1039 + ] + } }, { "event": "kWjSezkXHVp", @@ -878,12 +883,19 @@ "deleted": false, "dataValues": [ { - "created": "2022-04-22T06:00:34.319", "dataElement": { "idScheme": "UID", "identifier": "GieVkTxp4HH" }, - "value": "14", + "value": "15", + "providedElsewhere": false + }, + { + "dataElement": { + "idScheme": "UID", + "identifier": "GieVkTxp4HG" + }, + "value": "10", "providedElsewhere": false } ], @@ -1163,7 +1175,29 @@ "idScheme": "UID", "identifier": "HllvX50cXC0" }, - "notes": [] + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -11.416855, + 8.132308 + ], + [ + -11.445351, + 8.089312 + ], + [ + -11.383896, + 8.089652 + ], + [ + -11.416855, + 8.132308 + ] + ] + ] + } }, { "event": "G9PbzJY8bJG", diff --git a/dhis-2/dhis-test-integration/src/test/resources/tracker/event_and_enrollment_with_data_values.json b/dhis-2/dhis-test-integration/src/test/resources/tracker/event_and_enrollment_with_data_values.json deleted file mode 100644 index e7b1f8e69ae8..000000000000 --- a/dhis-2/dhis-test-integration/src/test/resources/tracker/event_and_enrollment_with_data_values.json +++ /dev/null @@ -1,210 +0,0 @@ -{ - "importMode": "COMMIT", - "idSchemes": { - "dataElementIdScheme": { - "idScheme": "UID" - }, - "orgUnitIdScheme": { - "idScheme": "UID" - }, - "programIdScheme": { - "idScheme": "UID" - }, - "programStageIdScheme": { - "idScheme": "UID" - }, - "idScheme": { - "idScheme": "UID" - }, - "categoryOptionComboIdScheme": { - "idScheme": "UID" - }, - "categoryOptionIdScheme": { - "idScheme": "UID" - } - }, - "importStrategy": "CREATE", - "atomicMode": "ALL", - "flushMode": "AUTO", - "validationMode": "FULL", - "skipPatternValidation": false, - "skipSideEffects": false, - "skipRuleEngine": false, - "trackedEntities": [ - { - "trackedEntity": "IOR1AXXl24H", - "trackedEntityType": { - "idScheme": "UID", - "identifier": "ja8NY4PW7Xm" - }, - "orgUnit": { - "idScheme": "UID", - "identifier": "h4w96yEMlzO" - }, - "inactive": false, - "deleted": false, - "potentialDuplicate": false, - "relationships": [], - "attributes": [], - "enrollments": [] - }, - { - "trackedEntity": "IOR1AXXl24G", - "trackedEntityType": { - "idScheme": "UID", - "identifier": "ja8NY4PW7Xm" - }, - "orgUnit": { - "idScheme": "UID", - "identifier": "h4w96yEMlzO" - }, - "inactive": false, - "deleted": false, - "potentialDuplicate": false, - "relationships": [], - "attributes": [], - "enrollments": [] - } - ], - "enrollments": [ - { - "enrollment": "TvctPPhpD8u", - "createdAtClient": "2017-01-26T13:48:13.363", - "trackedEntity": "IOR1AXXl24H", - "program": { - "idScheme": "UID", - "identifier": "BFcipDERJnf" - }, - "status": "ACTIVE", - "orgUnit": { - "idScheme": "UID", - "identifier": "h4w96yEMlzO" - }, - "orgUnitName": "Mbokie CHP", - "enrolledAt": "2021-03-28T12:05:00.000", - "occurredAt": "2021-03-28T12:05:00.000", - "followUp": false, - "deleted": false, - "events": [], - "relationships": [], - "attributes": [], - "notes": [] - }, - { - "enrollment": "TvctPPhpD8z", - "createdAtClient": "2017-01-26T13:48:13.363", - "trackedEntity": "IOR1AXXl24G", - "program": { - "idScheme": "UID", - "identifier": "BFcipDERJnf" - }, - "status": "ACTIVE", - "orgUnit": { - "idScheme": "UID", - "identifier": "h4w96yEMlzO" - }, - "orgUnitName": "Mbokie CHP", - "enrolledAt": "2021-03-28T12:05:00.000", - "occurredAt": "2021-03-28T12:05:00.000", - "followUp": false, - "deleted": false, - "events": [], - "relationships": [], - "attributes": [], - "notes": [] - } - ], - "events": [ - { - "event": "D9PbzJY8bJO", - "status": "COMPLETED", - "program": { - "idScheme": "UID", - "identifier": "BFcipDERJnf" - }, - "programStage": { - "idScheme": "UID", - "identifier": "NpsdDv6kKSO" - }, - "enrollment": "TvctPPhpD8u", - "orgUnit": { - "idScheme": "UID", - "identifier": "h4w96yEMlzO" - }, - "relationships": [], - "occurredAt": "2019-01-28T00:00:00.000", - "scheduledAt": "2019-01-28T12:10:38.100", - "storedBy": "admin", - "deleted": false, - "attributeOptionCombo": { - "idScheme": "UID", - "identifier": "HllvX50cXC0" - }, - "attributeCategoryOptions": [ - { - "idScheme": "UID", - "identifier": "xYerKDKCefk" - } - ], - - "dataValues": [{ - "createdAt": "2019-01-28T12:10:38.113", - "storedBy": "admin", - "providedElsewhere": false, - "dataElement": { - "idScheme": "UID", - "identifier": "DATAEL00001" - }, - "value": "value1" - }, - { - "createdAt": "2019-01-28T12:10:38.113", - "storedBy": "admin", - "providedElsewhere": false, - "dataElement": { - "idScheme": "UID", - "identifier": "DATAEL00002" - }, - "value": "value2" - }], - "notes": [] - }, - { - "event": "D9PbzJY8bJM", - "status": "COMPLETED", - "program": { - "idScheme": "UID", - "identifier": "BFcipDERJnf" - }, - "programStage": { - "idScheme": "UID", - "identifier": "NpsdDv6kKSO" - }, - "enrollment": "TvctPPhpD8z", - "orgUnit": { - "idScheme": "UID", - "identifier": "h4w96yEMlzO" - }, - "relationships": [], - "occurredAt": "2019-01-28T00:00:00.000", - "scheduledAt": "2019-01-28T12:10:38.100", - "storedBy": "admin", - "deleted": false, - "attributeOptionCombo": { - "idScheme": "UID", - "identifier": "HllvX50cXC0" - }, - "attributeCategoryOptions": [ - { - "idScheme": "UID", - "identifier": "xYerKDKCefk" - } - ], - - "dataValues": [], - "notes": [] - } - ], - "relationships": [], - "username": "system-process" -} \ No newline at end of file diff --git a/dhis-2/dhis-test-integration/src/test/resources/tracker/event_events.json b/dhis-2/dhis-test-integration/src/test/resources/tracker/event_events.json index 15b5ac2646e9..12fabd955311 100644 --- a/dhis-2/dhis-test-integration/src/test/resources/tracker/event_events.json +++ b/dhis-2/dhis-test-integration/src/test/resources/tracker/event_events.json @@ -104,7 +104,7 @@ "deleted": false, "attributeOptionCombo": { "idScheme": "UID", - "identifier": "XXXvX50cXC0" + "identifier": "HllvX50cXC0" }, "attributeCategoryOptions": [ { diff --git a/dhis-2/dhis-test-integration/src/test/resources/tracker/event_metadata.json b/dhis-2/dhis-test-integration/src/test/resources/tracker/event_metadata.json index 08e0c19e4bf9..8e41a3676f9c 100644 --- a/dhis-2/dhis-test-integration/src/test/resources/tracker/event_metadata.json +++ b/dhis-2/dhis-test-integration/src/test/resources/tracker/event_metadata.json @@ -1820,24 +1820,6 @@ "id": "xYerKDKCefk" } ] - }, - { - "lastUpdated": "2019-01-28T11:01:30.775", - "code": "COCA", - "created": "2019-01-28T11:01:30.771", - "name": "COCAname", - "id": "XXXvX50cXC0", - "ignoreApproval": false, - "categoryCombo": { - "id": "bjDvmb4bfuf" - }, - "translations": [], - "attributeValues": [], - "categoryOptions": [ - { - "id": "xYerKDKCefk" - } - ] } ], "categories": [ diff --git a/dhis-2/dhis-test-integration/src/test/resources/tracker/event_with_data_values_for_delete_audit.json b/dhis-2/dhis-test-integration/src/test/resources/tracker/event_with_data_values_for_delete_audit.json deleted file mode 100644 index 1031568e6247..000000000000 --- a/dhis-2/dhis-test-integration/src/test/resources/tracker/event_with_data_values_for_delete_audit.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "importMode": "COMMIT", - "idSchemes": { - "dataElementIdScheme": { - "idScheme": "UID" - }, - "orgUnitIdScheme": { - "idScheme": "UID" - }, - "programIdScheme": { - "idScheme": "UID" - }, - "programStageIdScheme": { - "idScheme": "UID" - }, - "idScheme": { - "idScheme": "UID" - }, - "categoryOptionComboIdScheme": { - "idScheme": "UID" - }, - "categoryOptionIdScheme": { - "idScheme": "UID" - } - }, - "importStrategy": "CREATE_AND_UPDATE", - "atomicMode": "ALL", - "flushMode": "AUTO", - "validationMode": "FULL", - "skipPatternValidation": false, - "skipSideEffects": false, - "skipRuleEngine": false, - "trackedEntities": [], - "enrollments": [ - { - "enrollment": "TvctPPhpD8u", - "createdAtClient": "2017-01-26T13:48:13.363", - "trackedEntity": "IOR1AXXl24H", - "program": { - "idScheme": "UID", - "identifier": "BFcipDERJnf" - }, - "status": "ACTIVE", - "orgUnit": { - "idScheme": "UID", - "identifier": "h4w96yEMlzO" - }, - "enrolledAt": "2021-03-28T12:05:00.000", - "occurredAt": "2021-03-28T12:05:00.000", - "followUp": false, - "deleted": false, - "events": [], - "relationships": [], - "attributes": [], - "notes": [] - } - ], - "events": [ - { - "event": "D9PbzJY8bJO", - "status": "COMPLETED", - "program": { - "idScheme": "UID", - "identifier": "BFcipDERJnf" - }, - "programStage": { - "idScheme": "UID", - "identifier": "NpsdDv6kKSO" - }, - "enrollment": "TvctPPhpD8u", - "orgUnit": { - "idScheme": "UID", - "identifier": "h4w96yEMlzO" - }, - "relationships": [], - "occurredAt": "2019-01-28T00:00:00.000", - "scheduledAt": "2019-01-28T12:10:38.100", - "storedBy": "admin", - "deleted": false, - "attributeOptionCombo": { - "idScheme": "UID", - "identifier": "HllvX50cXC0" - }, - "attributeCategoryOptions": [ - { - "idScheme": "UID", - "identifier": "xYerKDKCefk" - } - ], - - "dataValues": [ - { - "createdAt": "2019-01-28T12:10:38.113", - "storedBy": "admin", - "providedElsewhere": false, - "dataElement": { - "idScheme": "UID", - "identifier": "DATAEL00001" - } - } - ], - "notes": [] - } - ], - "relationships": [], - "username": "system-process" -} \ No newline at end of file diff --git a/dhis-2/dhis-test-integration/src/test/resources/tracker/event_with_data_values_for_update_audit.json b/dhis-2/dhis-test-integration/src/test/resources/tracker/event_with_data_values_for_update_audit.json deleted file mode 100644 index 42abeb8cd6e2..000000000000 --- a/dhis-2/dhis-test-integration/src/test/resources/tracker/event_with_data_values_for_update_audit.json +++ /dev/null @@ -1,118 +0,0 @@ -{ - "importMode": "COMMIT", - "idSchemes": { - "dataElementIdScheme": { - "idScheme": "UID" - }, - "orgUnitIdScheme": { - "idScheme": "UID" - }, - "programIdScheme": { - "idScheme": "UID" - }, - "programStageIdScheme": { - "idScheme": "UID" - }, - "idScheme": { - "idScheme": "UID" - }, - "categoryOptionComboIdScheme": { - "idScheme": "UID" - }, - "categoryOptionIdScheme": { - "idScheme": "UID" - } - }, - "importStrategy": "CREATE_AND_UPDATE", - "atomicMode": "ALL", - "flushMode": "AUTO", - "validationMode": "FULL", - "skipPatternValidation": false, - "skipSideEffects": false, - "skipRuleEngine": false, - "trackedEntities": [], - "enrollments": [ - { - "enrollment": "TvctPPhpD8u", - "createdAtClient": "2017-01-26T13:48:13.363", - "trackedEntity": "IOR1AXXl24H", - "program": { - "idScheme": "UID", - "identifier": "BFcipDERJnf" - }, - "status": "ACTIVE", - "orgUnit": { - "idScheme": "UID", - "identifier": "h4w96yEMlzO" - }, - "enrolledAt": "2021-03-28T12:05:00.000", - "occurredAt": "2021-03-28T12:05:00.000", - "followUp": false, - "deleted": false, - "events": [], - "relationships": [], - "attributes": [], - "notes": [] - } - ], - "events": [ - { - "event": "D9PbzJY8bJO", - "status": "COMPLETED", - "program": { - "idScheme": "UID", - "identifier": "BFcipDERJnf" - }, - "programStage": { - "idScheme": "UID", - "identifier": "NpsdDv6kKSO" - }, - "enrollment": "TvctPPhpD8u", - "orgUnit": { - "idScheme": "UID", - "identifier": "h4w96yEMlzO" - }, - "relationships": [], - "occurredAt": "2019-01-28T00:00:00.000", - "scheduledAt": "2019-01-28T12:10:38.100", - "storedBy": "admin", - "deleted": false, - "attributeOptionCombo": { - "idScheme": "UID", - "identifier": "HllvX50cXC0" - }, - "attributeCategoryOptions": [ - { - "idScheme": "UID", - "identifier": "xYerKDKCefk" - } - ], - - "dataValues": [ - { - "createdAt": "2019-01-28T12:10:38.113", - "storedBy": "admin", - "providedElsewhere": false, - "dataElement": { - "idScheme": "UID", - "identifier": "DATAEL00001" - }, - "value": "value1-updated" - }, - { - "createdAt": "2019-01-28T12:10:38.113", - "storedBy": "admin", - "providedElsewhere": false, - "dataElement": { - "idScheme": "UID", - "identifier": "DATAEL00002" - }, - "value": "value2" - } - ], - "notes": [] - } - ], - "relationships": [], - "username": "system-process" -} \ No newline at end of file diff --git a/dhis-2/dhis-test-integration/src/test/resources/tracker/identifier_metadata.json b/dhis-2/dhis-test-integration/src/test/resources/tracker/identifier_metadata.json index f4925a896ee3..eed1a1b69336 100644 --- a/dhis-2/dhis-test-integration/src/test/resources/tracker/identifier_metadata.json +++ b/dhis-2/dhis-test-integration/src/test/resources/tracker/identifier_metadata.json @@ -204,23 +204,6 @@ "id": "xYerKDKCefk" } ] - }, - { - "lastUpdated": "2019-01-28T11:01:30.775", - "code": "COCA", - "created": "2019-01-28T11:01:30.771", - "name": "COCAname", - "id": "XXXvX50cXC0", - "ignoreApproval": false, - "categoryCombo": { - "id": "bjDvmb4bfuf" - }, - "attributeValues": [], - "categoryOptions": [ - { - "id": "xYerKDKCefk" - } - ] } ], "categories": [ diff --git a/dhis-2/dhis-test-integration/src/test/resources/tracker/one_update_and_one_new_and_one_invalid_enrollment.json b/dhis-2/dhis-test-integration/src/test/resources/tracker/one_update_and_one_new_and_one_invalid_enrollment.json index bd4fc0a608c6..b74e084fef34 100644 --- a/dhis-2/dhis-test-integration/src/test/resources/tracker/one_update_and_one_new_and_one_invalid_enrollment.json +++ b/dhis-2/dhis-test-integration/src/test/resources/tracker/one_update_and_one_new_and_one_invalid_enrollment.json @@ -79,12 +79,12 @@ "notes": [] }, { - "enrollment": "INVALIDUID-TOOLONG", + "enrollment": "XjRvstYX68A", "createdAtClient": "2017-01-26T13:48:13.363", "trackedEntity": "XjRvstYX689", "program": { "idScheme": "UID", - "identifier": "BFcipDERJnf" + "identifier": "invalid" }, "status": "ACTIVE", "orgUnit": { diff --git a/dhis-2/dhis-test-integration/src/test/resources/tracker/one_update_and_one_new_and_one_invalid_event.json b/dhis-2/dhis-test-integration/src/test/resources/tracker/one_update_and_one_new_and_one_invalid_event.json index 159ff551b9cf..45b036f47f1e 100644 --- a/dhis-2/dhis-test-integration/src/test/resources/tracker/one_update_and_one_new_and_one_invalid_event.json +++ b/dhis-2/dhis-test-integration/src/test/resources/tracker/one_update_and_one_new_and_one_invalid_event.json @@ -92,11 +92,11 @@ "notes": [] }, { - "event": "EVENTUID-TOOLONG", + "event": "BFcipDERJnX", "status": "COMPLETED", "program": { - "idScheme": "UID", - "identifier": "BFcipDERJnf" + "idScheme": "CODE", + "identifier": "invalid" }, "programStage": { "idScheme": "UID", diff --git a/dhis-2/dhis-test-integration/src/test/resources/tracker/one_update_te_and_one_new_te_and_one_invalid_te.json b/dhis-2/dhis-test-integration/src/test/resources/tracker/one_update_te_and_one_new_te_and_one_invalid_te.json index c6fd1b8a67f9..0dcaaa3178e7 100644 --- a/dhis-2/dhis-test-integration/src/test/resources/tracker/one_update_te_and_one_new_te_and_one_invalid_te.json +++ b/dhis-2/dhis-test-integration/src/test/resources/tracker/one_update_te_and_one_new_te_and_one_invalid_te.json @@ -66,10 +66,10 @@ "enrollments": [] }, { - "trackedEntity": "INVALIDTEI-UIDTOOLONG", + "trackedEntity": "ja8NY4PW7XF", "trackedEntityType": { - "idScheme": "UID", - "identifier": "ja8NY4PW7Xm" + "idScheme": "CODE", + "identifier": "invalid" }, "orgUnit": { "idScheme": "UID", diff --git a/dhis-2/dhis-test-web-api/pom.xml b/dhis-2/dhis-test-web-api/pom.xml index 6dde179a91b5..e7aacf51a6a8 100644 --- a/dhis-2/dhis-test-web-api/pom.xml +++ b/dhis-2/dhis-test-web-api/pom.xml @@ -146,6 +146,16 @@ spring-webmvc test + + jakarta.persistence + jakarta.persistence-api + test + + + org.hibernate + hibernate-core-jakarta + test + org.slf4j slf4j-api @@ -341,7 +351,7 @@ org.openapitools openapi-generator - 7.8.0 + 7.10.0 test diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/AbstractFullReadOnlyControllerTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/AbstractFullReadOnlyControllerTest.java index b061b5aed2ee..0621b0724844 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/AbstractFullReadOnlyControllerTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/AbstractFullReadOnlyControllerTest.java @@ -44,7 +44,7 @@ import org.springframework.transaction.annotation.Transactional; /** - * Tests the generic operations offered by the {@link AbstractFullReadOnlyController} using specific + * Tests the generic operations offered by the {@code AbstractFullReadOnlyController} using specific * endpoints. * * @author David Mackessy diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/AppHubControllerTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/AppHubControllerTest.java deleted file mode 100644 index 6ff340434753..000000000000 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/AppHubControllerTest.java +++ /dev/null @@ -1,95 +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.webapi.controller; - -import static org.hisp.dhis.test.webapi.Assertions.assertWebMessage; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.hisp.dhis.external.conf.ConfigurationKey; -import org.hisp.dhis.external.conf.DhisConfigurationProvider; -import org.hisp.dhis.http.HttpStatus; -import org.hisp.dhis.jsontree.JsonArray; -import org.hisp.dhis.jsontree.JsonObject; -import org.hisp.dhis.test.webapi.H2ControllerIntegrationTestBase; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.transaction.annotation.Transactional; - -/** - * Tests the {@link AppHubController} using (mocked) REST requests. - * - * @author Jan Bernitt - */ -@Transactional -class AppHubControllerTest extends H2ControllerIntegrationTestBase { - @Autowired private DhisConfigurationProvider configuration; - - @AfterEach - void cleanUp() { - // back to default - configuration.getProperties().remove(ConfigurationKey.APPHUB_API_URL.getKey()); - } - - @Test - void testListAppHub() { - JsonArray apps = GET("/appHub").content(); - assertTrue(apps.isArray()); - assertTrue(apps.size() > 0, "There should be apps registered"); - } - - @Test - void testGetAppHubApiResponse() { - assertWebMessage( - "Not Found", - 404, - "ERROR", - "404 Not Found: \"{\"statusCode\":404,\"error\":\"Not Found\",\"message\":\"Not Found\"}\"", - GET("/appHub/v37/test").content(HttpStatus.NOT_FOUND)); - } - - @Test - void testAppHubInstallResponseContainsAppInfo() { - JsonArray apps = GET("/appHub").content(); - JsonObject firstApp = apps.getObject(0).getArray("versions").getObject(0); - - String versionId = firstApp.getString("id").string(); - String version = firstApp.getString("version").string(); - - HttpResponse response = POST("/appHub/" + versionId); - - assertEquals(201, response.status().code()); - - JsonObject result = response.content(); - assertEquals( - version, - result.getString("version").string(), - "an object should be returned containing the version of the app"); - } -} diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/CategoryComboModificationControllerTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/CategoryComboModificationControllerTest.java index 97a143e8c10d..6d0c4eec4a74 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/CategoryComboModificationControllerTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/CategoryComboModificationControllerTest.java @@ -158,7 +158,8 @@ private String createCategoryOptions(String name) { "/categoryOptions", // language=JSON """ - { "name": "%s", "shortName": "%s" }""".formatted(name, name))); + { "name": "%s", "shortName": "%s" }""" + .formatted(name, name))); } private String createSimpleCategory(String name, String categoryOptionId) { @@ -207,7 +208,8 @@ void setupTest() { getCurrentUser().getUid(), Body( // language=JSON """ - {"additions":[{"id":"%s"}]}""".formatted(orgUnitId)))); + {"additions":[{"id":"%s"}]}""" + .formatted(orgUnitId)))); dataElementId = addDataElement("My data element", "DE1", ValueType.INTEGER, null, testCatCombo); } diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/CategoryOptionControllerTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/CategoryOptionControllerTest.java index 0af44f4fc90d..fa6c8c86da18 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/CategoryOptionControllerTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/CategoryOptionControllerTest.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis.webapi.controller; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -36,6 +37,8 @@ import org.hisp.dhis.category.CategoryService; import org.hisp.dhis.http.HttpStatus; import org.hisp.dhis.jsontree.JsonArray; +import org.hisp.dhis.jsontree.JsonMixed; +import org.hisp.dhis.jsontree.JsonObject; import org.hisp.dhis.test.webapi.H2ControllerIntegrationTestBase; import org.hisp.dhis.test.webapi.json.domain.JsonCategoryOption; import org.hisp.dhis.test.webapi.json.domain.JsonIdentifiableObject; @@ -93,4 +96,58 @@ void catOptionsExcludingDefaultTest() { assertFalse(catOptions.contains("default"), "default catOption was not in payload"); } + + @Test + @DisplayName("Invalid merge with source and target missing") + void testInvalidMerge() { + JsonMixed mergeResponse = + POST( + "/categoryOptions/merge", + """ + { + "sources": ["Uid00000010"], + "target": "Uid00000012", + "deleteSources": true, + "dataMergeStrategy": "DISCARD" + }""") + .content(HttpStatus.CONFLICT); + assertEquals("Conflict", mergeResponse.getString("httpStatus").string()); + assertEquals("WARNING", mergeResponse.getString("status").string()); + assertEquals( + "One or more errors occurred, please see full details in merge report.", + mergeResponse.getString("message").string()); + + JsonArray errors = + mergeResponse.getObject("response").getObject("mergeReport").getArray("mergeErrors"); + JsonObject error1 = errors.getObject(0); + JsonObject error2 = errors.getObject(1); + assertEquals( + "SOURCE CategoryOption does not exist: `Uid00000010`", + error1.getString("message").string()); + assertEquals( + "TARGET CategoryOption does not exist: `Uid00000012`", + error2.getString("message").string()); + } + + @Test + @DisplayName("invalid merge, missing required auth") + void testMergeNoAuth() { + switchToNewUser("noAuth", "NoAuth"); + JsonMixed mergeResponse = + POST( + "/categoryOptions/merge", + """ + { + "sources": ["Uid00000010"], + "target": "Uid00000012", + "deleteSources": true, + "dataMergeStrategy": "DISCARD" + }""") + .content(HttpStatus.FORBIDDEN); + assertEquals("Forbidden", mergeResponse.getString("httpStatus").string()); + assertEquals("ERROR", mergeResponse.getString("status").string()); + assertEquals( + "Access is denied, requires one Authority from [F_CATEGORY_OPTION_MERGE]", + mergeResponse.getString("message").string()); + } } diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/CrudControllerIntegrationTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/CrudControllerIntegrationTest.java index d4677f098c61..00ddbca01603 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/CrudControllerIntegrationTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/CrudControllerIntegrationTest.java @@ -37,6 +37,7 @@ import org.hisp.dhis.dataset.DataSet; import org.hisp.dhis.http.HttpStatus; import org.hisp.dhis.jsontree.JsonArray; +import org.hisp.dhis.jsontree.JsonMixed; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.setting.SystemSettingsService; import org.hisp.dhis.test.webapi.PostgresControllerIntegrationTestBase; @@ -197,6 +198,30 @@ void testSearchTokenWithFallback() throws Exception { .isEmpty()); } + @Test + @DisplayName("Should not apply token filter for UID if value has length < 4") + void testIdentifiableTokenFilterLength() { + assertStatus( + HttpStatus.CREATED, + POST( + "/organisationUnits/", + "{'name':'My Unit 1', 'shortName':'OU1', 'openingDate': '2020-01-01'}")); + String ou2 = + assertStatus( + HttpStatus.CREATED, + POST( + "/organisationUnits/", + "{'name':'My Unit 2', 'shortName':'OU2', 'openingDate': '2020-01-01'}")); + + JsonMixed response = + GET("/organisationUnits?filter=identifiable:token:" + ou2.substring(0, 3)).content(); + assertEquals(0, response.getArray("organisationUnits").size()); + + response = GET("/organisationUnits?filter=identifiable:token:" + ou2.substring(0, 4)).content(); + assertEquals(1, response.getArray("organisationUnits").size()); + assertEquals(ou2, response.getArray("organisationUnits").getObject(0).getString("id").string()); + } + private void setUpTranslation() { injectSecurityContextUser(getAdminUser()); diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/DataElementControllerTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/DataElementControllerTest.java index 831b827cba55..3e4f668744ff 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/DataElementControllerTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/DataElementControllerTest.java @@ -27,7 +27,12 @@ */ package org.hisp.dhis.webapi.controller; +import static org.hisp.dhis.common.ValueType.NUMBER; +import static org.hisp.dhis.common.ValueType.TEXT; +import static org.hisp.dhis.http.HttpStatus.OK; +import static org.hisp.dhis.test.webapi.Assertions.assertWebMessage; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.util.List; import java.util.Map; @@ -37,12 +42,15 @@ import org.hisp.dhis.category.CategoryService; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.dataelement.DataElementService; +import org.hisp.dhis.feedback.ErrorCode; import org.hisp.dhis.http.HttpStatus; import org.hisp.dhis.jsontree.JsonArray; import org.hisp.dhis.jsontree.JsonValue; import org.hisp.dhis.test.webapi.H2ControllerIntegrationTestBase; import org.hisp.dhis.test.webapi.json.domain.JsonDataElement; +import org.hisp.dhis.test.webapi.json.domain.JsonErrorReport; import org.hisp.dhis.test.webapi.json.domain.JsonIdentifiableObject; +import org.hisp.dhis.test.webapi.json.domain.JsonWebMessage; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -81,9 +89,7 @@ void setUp() { "DataElement with default categoryCombo should be present in payload when defaults are INCLUDE by default") void getAllDataElementsWithCatComboFieldsIncludingDefaultsTest() { JsonArray dataElements = - GET("/dataElements?fields=id,name,categoryCombo") - .content(HttpStatus.OK) - .getArray("dataElements"); + GET("/dataElements?fields=id,name,categoryCombo").content(OK).getArray("dataElements"); assertEquals( Set.of(catComboA.getUid(), catComboB.getUid(), catComboC.getUid(), "bjDvmb4bfuf"), @@ -101,7 +107,7 @@ void getAllDataElementsWithCatComboFieldsIncludingDefaultsTest() { void dataElementsExcludingDefaultCatComboTest() { JsonArray dataElements = GET("/dataElements?fields=id,name,categoryCombo&defaults=EXCLUDE") - .content(HttpStatus.OK) + .content(OK) .getArray("dataElements"); // get map of data elements with/without cat combo @@ -133,4 +139,309 @@ void dataElementsExcludingDefaultCatComboTest() { .collect(Collectors.toSet()), "Returned cat combo IDs equal custom cat combos Ids only"); } + + @Test + @DisplayName( + "Changing a data element's value type is prohibited when it has associated data values - DataElement API") + void prohibitValueTypeChangeWhenHasDataDeApiTest() { + // create metadata + POST( + "/metadata", + """ + { + "dataElements": [ + { + "id": "DeUid000001", + "aggregationType": "DEFAULT", + "domainType": "AGGREGATE", + "name": "test de 1", + "shortName": "test DE 1", + "valueType": "NUMBER" + } + ], + "organisationUnits": [ + { + "id": "OrgUnitUid1", + "name": "test org 1", + "shortName": "test org 1", + "openingDate": "2023-06-15" + } + ], + "dataSets": [ + { + "id": "DsUid000001", + "name": "ds 1", + "shortName": "ds 1", + "periodType": "Monthly", + "dataSetElements": [ + { + "dataElement": { + "id": "DeUid000001" + } + } + ] + } + ] + } + """) + .content(OK); + + // add org unit to user for data entry + PATCH( + "/users/" + ADMIN_USER_UID, + """ + [ + { + "op": "add", + "path": "/organisationUnits", + "value": [ + { + "id": "OrgUnitUid1" + } + ] + } + ] + """) + .content(OK); + + // add data value for data element + POST( + "/dataValueSets", + """ + { + "dataSet": "DsUid000001", + "period": "202311", + "orgUnit": "OrgUnitUid1", + "completedDate": "2023-11-05", + "dataValues": [ + { + "dataElement": "DeUid000001", + "period": "202311", + "orgUnit": "OrgUnitUid1", + "value": "2000", + "followup": false + } + ] + }""") + .content(OK); + + // try update data element with new value type (TEXT) + JsonWebMessage validationErrorMsg = + assertWebMessage( + "Conflict", + 409, + "ERROR", + "One or more errors occurred, please see full details in import report.", + PUT( + "/dataElements/DeUid000001", + """ + { + "id": "DeUid000001", + "aggregationType": "DEFAULT", + "domainType": "AGGREGATE", + "name": "test de 1", + "shortName": "test DE 1", + "valueType": "TEXT" + }""") + .content(HttpStatus.CONFLICT)); + + JsonErrorReport errorReport = + validationErrorMsg.find( + JsonErrorReport.class, error -> error.getErrorCode() == ErrorCode.E1121); + assertNotNull(errorReport); + assertEquals( + "Data element `DeUid000001` value type cannot be changed as it has associated data values", + errorReport.getMessage()); + JsonDataElement updatedDataElement = + GET("/dataElements/DeUid000001").content(OK).as(JsonDataElement.class); + assertEquals(NUMBER, updatedDataElement.getValueType(), "value type should be NUMBER"); + } + + @Test + @DisplayName( + "Changing a data element's value type is prohibited when it has associated data values - metadata API") + void prohibitValueTypeChangeWhenHasDataMetadataApiTest() { + // create metadata + POST( + "/metadata", + """ + { + "dataElements": [ + { + "id": "DeUid000003", + "aggregationType": "DEFAULT", + "domainType": "AGGREGATE", + "name": "test de 3", + "shortName": "test DE 3", + "valueType": "NUMBER" + } + ], + "organisationUnits": [ + { + "id": "OrgUnitUid3", + "name": "test org 3", + "shortName": "test org 3", + "openingDate": "2023-06-15" + } + ], + "dataSets": [ + { + "id": "DsUid000003", + "name": "ds 3", + "shortName": "ds 3", + "periodType": "Monthly", + "dataSetElements": [ + { + "dataElement": { + "id": "DeUid000003" + } + } + ] + } + ] + } + """) + .content(OK); + + // add org unit to user for data entry + PATCH( + "/users/" + ADMIN_USER_UID, + """ + [ + { + "op": "add", + "path": "/organisationUnits", + "value": [ + { + "id": "OrgUnitUid3" + } + ] + } + ] + """) + .content(OK); + + // add data value for data element + POST( + "/dataValueSets", + """ + { + "dataSet": "DsUid000003", + "period": "202311", + "orgUnit": "OrgUnitUid3", + "completedDate": "2023-11-05", + "dataValues": [ + { + "dataElement": "DeUid000003", + "period": "202311", + "orgUnit": "OrgUnitUid3", + "value": "2000", + "followup": false + } + ] + }""") + .content(OK); + + // try update data element with new value type (TEXT) + JsonWebMessage validationErrorMsg = + assertWebMessage( + "Conflict", + 409, + "WARNING", + "One or more errors occurred, please see full details in import report.", + POST( + "/metadata?importStrategy=UPDATE", + """ + { + "dataElements":[ + { + "id": "DeUid000003", + "aggregationType": "DEFAULT", + "domainType": "AGGREGATE", + "name": "test de 3", + "shortName": "test DE 3", + "valueType": "TEXT" + } + ] + } + """) + .content(HttpStatus.CONFLICT)); + + JsonErrorReport errorReport = + validationErrorMsg.find( + JsonErrorReport.class, error -> error.getErrorCode() == ErrorCode.E1121); + assertNotNull(errorReport); + assertEquals( + "Data element `DeUid000003` value type cannot be changed as it has associated data values", + errorReport.getMessage()); + + JsonDataElement updatedDataElement = + GET("/dataElements/DeUid000003").content(OK).as(JsonDataElement.class); + assertEquals(NUMBER, updatedDataElement.getValueType(), "value type should be NUMBER"); + } + + @Test + @DisplayName( + "Changing a data element's value type is allowed when it has no associated data values") + void allowValueTypeChangeWhenHasNoDataTest() { + // create metadata + POST( + "/metadata", + """ + { + "dataElements": [ + { + "id": "DeUid000002", + "aggregationType": "DEFAULT", + "domainType": "AGGREGATE", + "name": "test de 2", + "shortName": "test DE 2", + "valueType": "NUMBER" + } + ], + "organisationUnits": [ + { + "id": "OrgUnitUid2", + "name": "test org 2", + "shortName": "test org 2", + "openingDate": "2023-06-15" + } + ], + "dataSets": [ + { + "id": "DsUid000002", + "name": "ds 2", + "shortName": "ds 2", + "periodType": "Monthly", + "dataSetElements": [ + { + "dataElement": { + "id": "DeUid000002" + } + } + ] + } + ] + } + """) + .content(OK); + + // update data element with new value type (TEXT) + PUT( + "/dataElements/DeUid000002", + """ + { + "id": "DeUid000002", + "aggregationType": "DEFAULT", + "domainType": "AGGREGATE", + "name": "test de 2", + "shortName": "test DE 2", + "valueType": "TEXT" + }""") + .content(OK); + + JsonDataElement updatedDataElement = + GET("/dataElements/DeUid000002").content(OK).as(JsonDataElement.class); + assertEquals(TEXT, updatedDataElement.getValueType(), "value type should be updated to TEXT"); + } } diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/EventVisualizationControllerTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/EventVisualizationControllerTest.java index 58b5feff801d..edb920fec7e9 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/EventVisualizationControllerTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/EventVisualizationControllerTest.java @@ -153,6 +153,27 @@ void testPostForSingleEventDate() { assertThat(response.get("filters").toString(), not(containsString(eventDateDimension))); } + @Test + void testDelete() { + // Given + String eventDateDimension = "eventDate"; + String eventDate = "2021-07-21_2021-08-01"; + String dimensionBody = + "{'dimension': '" + eventDateDimension + "', 'items': [{'id': '" + eventDate + "'}]}"; + String body = + "{'name': 'Name Test', 'type': 'STACKED_COLUMN','eventRepetitions':null, 'program': {'id':'" + + mockProgram.getUid() + + "'}, 'columns': [" + + dimensionBody + + "]}"; + + // When + String uid = assertStatus(CREATED, POST("/eventVisualizations/", body)); + + // Then + DELETE("/eventVisualizations/" + uid).content(OK); + } + @Test void testPostForMultiEventDates() { // Given @@ -661,7 +682,9 @@ void testPostMultiPrograms() { assertThat(response.get("legacy").node().value(), is(equalTo(false))); assertThat( response.get("trackedEntityType").node().value().toString(), - is(equalTo(""" + is( + equalTo( + """ {"id":"nEenWmSyUEp"}"""))); JsonNode simpleDimensionNode0 = response.get("simpleDimensions").node().element(0); @@ -670,7 +693,9 @@ void testPostMultiPrograms() { assertThat(simpleDimensionNode0.get("program").value().toString(), is(equalTo("deabcdefghP"))); assertThat( simpleDimensionNode0.get("values").value().toString(), - is(equalTo(""" + is( + equalTo( + """ ["2023-07-21_2023-08-01","2023-01-21_2023-02-01"]"""))); assertThat(simpleDimensionNode0.get("parent").value().toString(), is(equalTo("COLUMN"))); @@ -697,13 +722,17 @@ void testPostMultiPrograms() { assertThat( response.get("filterDimensions").node().value().toString(), - is(equalTo(""" + is( + equalTo( + """ ["deabcdefghP.deabcdefghS.ou","deabcdefghE"]"""))); JsonNode dataElementDimensionsNode0 = response.get("dataElementDimensions").node().element(0); assertThat( dataElementDimensionsNode0.get("dataElement").value().toString(), - is(equalTo(""" + is( + equalTo( + """ {"id":"deabcdefghC"}"""))); assertThat( dataElementDimensionsNode0.get("filter").value().toString(), is(equalTo("IN:Female"))); @@ -711,18 +740,24 @@ void testPostMultiPrograms() { JsonNode dataElementDimensionsNode1 = response.get("dataElementDimensions").node().element(1); assertThat( dataElementDimensionsNode1.get("dataElement").value().toString(), - is(equalTo(""" + is( + equalTo( + """ {"id":"deabcdefghE"}"""))); assertFalse(dataElementDimensionsNode1.isMember("filter")); assertThat( response.get("programIndicatorDimensions").node().value().toString(), - is(equalTo(""" + is( + equalTo( + """ [{"programIndicator":{"id":"deabcdefghB"}}]"""))); assertThat( response.get("organisationUnits").node().value().toString(), - is(equalTo(""" + is( + equalTo( + """ [{"id":"ImspTQPwCqd"}]"""))); JsonNode repetitionsNode0 = response.get("repetitions").node().element(0); @@ -742,7 +777,10 @@ void testPostMultiPrograms() { JsonNode columnsNode0 = response.get("columns").node().element(0); assertThat(columnsNode0.get("items").value().toString(), is(equalTo("[]"))); assertThat( - columnsNode0.get("program").value().toString(), is(equalTo(""" + columnsNode0.get("program").value().toString(), + is( + equalTo( + """ {"id":"deabcdefghP"}"""))); assertThat(columnsNode0.get("dimension").value().toString(), is(equalTo("deabcdefghB"))); @@ -754,17 +792,24 @@ void testPostMultiPrograms() { JsonNode columnsNode2 = response.get("columns").node().element(2); assertThat( columnsNode2.get("items").value().toString(), - is(equalTo(""" + is( + equalTo( + """ [{"id":"2023-07-21_2023-08-01"},{"id":"2023-01-21_2023-02-01"}]"""))); assertThat( - columnsNode2.get("program").value().toString(), is(equalTo(""" + columnsNode2.get("program").value().toString(), + is( + equalTo( + """ {"id":"deabcdefghP"}"""))); assertThat(columnsNode2.get("dimension").value().toString(), is(equalTo("eventDate"))); JsonNode columnsNode3 = response.get("columns").node().element(3); assertThat( columnsNode3.get("items").value().toString(), - is(equalTo(""" + is( + equalTo( + """ [{"id":"2021-01-21_2021-02-01"}]"""))); assertThat(columnsNode3.get("dimension").value().toString(), is(equalTo("created"))); @@ -777,7 +822,9 @@ void testPostMultiPrograms() { is(equalTo("ImspTQPwCqd"))); assertThat( filtersNode0.get("programStage").value().toString(), - is(equalTo(""" + is( + equalTo( + """ {"id":"deabcdefghS"}"""))); assertThat(filtersNode0.get("dimension").value().toString(), is(equalTo("ou"))); assertThat( diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/IndicatorControllerTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/IndicatorControllerTest.java index 953a89b11c5a..dd68ef0ff9c9 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/IndicatorControllerTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/IndicatorControllerTest.java @@ -92,9 +92,9 @@ void testInvalidMerge() { JsonObject error1 = errors.getObject(0); JsonObject error2 = errors.getObject(1); assertEquals( - "SOURCE indicator does not exist: `Uid00000010`", error1.getString("message").string()); + "SOURCE Indicator does not exist: `Uid00000010`", error1.getString("message").string()); assertEquals( - "TARGET indicator does not exist: `Uid00000012`", error2.getString("message").string()); + "TARGET Indicator does not exist: `Uid00000012`", error2.getString("message").string()); } @Test diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/OpenApiControllerTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/OpenApiControllerTest.java index 3f0af1268ea5..ed4040d58fb2 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/OpenApiControllerTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/OpenApiControllerTest.java @@ -81,7 +81,7 @@ void testGetOpenApiDocumentJson() { @Test void testGetOpenApiDocument_PathFilter() { - JsonObject doc = GET("/openapi/openapi.json?scope=path./api/users").content(); + JsonObject doc = GET("/openapi/openapi.json?scope=path:/api/users").content(); assertTrue(doc.isObject()); assertTrue( doc.getObject("paths") @@ -95,8 +95,8 @@ void testGetOpenApiDocument_PathFilter() { } @Test - void testGetOpenApiDocument_DomainFilter() { - JsonObject doc = GET("/openapi/openapi.json?scope=entity.User").content(); + void testGetOpenApiDocument_ScopeFilter() { + JsonObject doc = GET("/openapi/openapi.json?scope=entity:User").content(); assertTrue(doc.isObject()); assertTrue( doc.getObject("paths") diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/OptionControllerTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/OptionControllerTest.java index 116770a383d2..49fd8b49213d 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/OptionControllerTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/OptionControllerTest.java @@ -31,6 +31,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.List; +import org.hibernate.Session; +import org.hibernate.stat.Statistics; import org.hisp.dhis.http.HttpStatus; import org.hisp.dhis.jsontree.JsonObject; import org.hisp.dhis.test.webapi.H2ControllerIntegrationTestBase; @@ -140,4 +142,40 @@ void testOptionSetsWithDescription() { List.of("this-is-a", "this-is-b"), set.getOptions().toList(JsonIdentifiableObject::getDescription)); } + + @Test + void testQueryOptionsByOptionSetIds() { + Session session = entityManager.unwrap(Session.class); + + String id = + assertStatus( + HttpStatus.CREATED, + POST( + "/optionSets/", + "{'name': 'test', 'version': 2, 'valueType': 'TEXT', 'description':'desc' }")); + assertStatus( + HttpStatus.CREATED, + POST( + "/options/", + "{'optionSet': { 'id':'" + + id + + "'}, 'id':'Uh4HvjK6zg3', 'code': 'A', 'name': 'Anna', 'description': 'this-is-a'}")); + assertStatus( + HttpStatus.CREATED, + POST( + "/options/", + "{'optionSet': { 'id':'" + + id + + "'},'id':'BQMei56UBl6','code': 'B', 'name': 'Betta', 'description': 'this-is-b'}")); + Statistics statistics = session.getSessionFactory().getStatistics(); + statistics.setStatisticsEnabled(true); + assertEquals(0, statistics.getQueryExecutionCount()); + JsonOptionSet set = + GET(String.format("/options?filter=optionSet.id:in:[%s,%s]", id, "TESTUIDA")) + .content() + .as(JsonOptionSet.class); + assertEquals(2, statistics.getQueryExecutionCount()); + assertEquals(2, set.getOptions().size()); + statistics.setStatisticsEnabled(false); + } } diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/OrganisationUnitControllerTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/OrganisationUnitControllerTest.java index 81e601a53f36..84bf552d609d 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/OrganisationUnitControllerTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/OrganisationUnitControllerTest.java @@ -190,6 +190,11 @@ void testGetParents() { GET("/organisationUnits/{id}/parents?filter=displayName:ilike:L0", ou21).content(), "L0"); } + @Test + void testGetParents_Root() { + assertListOfOrganisationUnits(GET("/organisationUnits/{id}/parents", ou0).content()); + } + @Test void testGetQuery() { assertListOfOrganisationUnits(GET("/organisationUnits?query=L21").content(), "L21"); diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/SchemaBasedControllerTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/SchemaBasedControllerTest.java index d41ddcc3b5aa..9ad040d75cd9 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/SchemaBasedControllerTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/SchemaBasedControllerTest.java @@ -148,7 +148,8 @@ private void runPatchObject(JsonSchema schema) { String endpoint = schema.getRelativeApiEndpoint(); String uid = createdObjectIds.get(endpoint); @Language("json") - String json = """ + String json = + """ [{ "op": "add", "path": "/name", "value": "new_name_patch" }]"""; assertStatus(HttpStatus.OK, PATCH(endpoint + "/" + uid, json)); } diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/SchemaControllerTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/SchemaControllerTest.java index c327f0326d05..8a93037b7cfd 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/SchemaControllerTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/SchemaControllerTest.java @@ -35,6 +35,7 @@ import org.hisp.dhis.http.HttpStatus; import org.hisp.dhis.jsontree.JsonObject; +import org.hisp.dhis.schema.PropertyType; import org.hisp.dhis.test.webapi.H2ControllerIntegrationTestBase; import org.hisp.dhis.test.webapi.json.domain.JsonSchema; import org.junit.jupiter.api.Test; @@ -114,4 +115,19 @@ void testFieldFilteringAllSchemas() { assertNull(schema.getPlural()); } } + + @Test + void testAttributeWritable() { + JsonSchema schema = GET("/schemas/attribute").content().as(JsonSchema.class); + schema + .getProperties() + .forEach( + p -> { + if (p.getName().endsWith("Attribute") + && p.getPropertyType() == PropertyType.BOOLEAN) { + assertTrue(p.isWritable()); + assertTrue(p.isPersisted()); + } + }); + } } diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/SmsGatewayControllerTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/SmsGatewayControllerTest.java index 0b2c1ccee839..cf9e0eb6e04f 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/SmsGatewayControllerTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/SmsGatewayControllerTest.java @@ -96,7 +96,8 @@ void testSetDefault_NoSuchObject() { @Test void testUpdateGateway() { // language=JSON - String json = """ + String json = + """ {"name":"test", "username":"user", "password":"pwd", "type":"http"}"""; uid = assertStatus(HttpStatus.OK, POST("/gateways", json)); diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/dataintegrity/DataIntegrityCategoryDimensionNoFavoritesControllerTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/dataintegrity/DataIntegrityCategoryDimensionNoFavoritesControllerTest.java new file mode 100644 index 000000000000..69e2b4a90d92 --- /dev/null +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/dataintegrity/DataIntegrityCategoryDimensionNoFavoritesControllerTest.java @@ -0,0 +1,86 @@ +/* + * 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.webapi.controller.dataintegrity; + +import static org.hisp.dhis.http.HttpAssertions.assertStatus; + +import org.hisp.dhis.http.HttpStatus; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class DataIntegrityCategoryDimensionNoFavoritesControllerTest + extends AbstractDataIntegrityIntegrationTest { + private final String check = "categories_dimensions_no_visualizations"; + + private String categoryOptionRed; + + @Test + void testCategoryDimensionsNoFavorites() { + + categoryOptionRed = + assertStatus( + HttpStatus.CREATED, POST("/categoryOptions", "{ 'name': 'Red', 'shortName': 'Red' }")); + + String categoryColor = + assertStatus( + HttpStatus.CREATED, + POST( + "/categories", + "{ 'name': 'Color', 'shortName': 'Color', 'dataDimensionType': 'DISAGGREGATION' , 'dataDimension' : true," + + "'categoryOptions' : [{'id' : '" + + categoryOptionRed + + "'} ] }")); + + /* Note that we have three categories now because of default */ + assertHasDataIntegrityIssues( + "categories", check, 33, categoryColor, "Color", "DISAGGREGATION", true); + } + + @Test + void testCategoryNotDimensionNotFlagged() { + + assertHasNoDataIntegrityIssues("categories", check, true); + } + + @BeforeEach + void setupTest() { + String categoryOptionSour = + assertStatus( + HttpStatus.CREATED, + POST("/categoryOptions", "{ 'name': 'Sour', 'shortName': 'Sour' }")); + + assertStatus( + HttpStatus.CREATED, + POST( + "/categories", + "{ 'name': 'Taste', 'shortName': 'Taste', 'dataDimensionType': 'DISAGGREGATION' , 'dataDimension' : false," + + "'categoryOptions' : [{'id' : '" + + categoryOptionSour + + "'} ] }")); + } +} diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/dataintegrity/DataIntegrityCategoryOptionComboDuplicatedTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/dataintegrity/DataIntegrityCategoryOptionComboDuplicatedTest.java new file mode 100644 index 000000000000..66016541b3c3 --- /dev/null +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/dataintegrity/DataIntegrityCategoryOptionComboDuplicatedTest.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2004-2024, 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.webapi.controller.dataintegrity; + +import static org.hisp.dhis.http.HttpAssertions.assertStatus; + +import java.util.Set; +import org.hisp.dhis.http.HttpStatus; +import org.hisp.dhis.jsontree.JsonList; +import org.hisp.dhis.jsontree.JsonObject; +import org.hisp.dhis.test.webapi.json.domain.JsonCategoryOptionCombo; +import org.junit.jupiter.api.Test; + +/** + * Tests the metadata check for category option combos with the same category options. + * + *

{@see + * dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/categories/category_option_combos_have_duplicates.yaml} + * + * @author David Mackessy + */ +class DataIntegrityCategoryOptionComboDuplicatedTest extends AbstractDataIntegrityIntegrationTest { + + private final String check = "category_option_combos_have_duplicates"; + + private String cocWithOptionsA; + + private String categoryOptionRed; + + @Test + void testCategoryOptionCombosDuplicated() { + + categoryOptionRed = + assertStatus( + HttpStatus.CREATED, POST("/categoryOptions", "{ 'name': 'Red', 'shortName': 'Red' }")); + + String categoryColor = + assertStatus( + HttpStatus.CREATED, + POST( + "/categories", + "{ 'name': 'Color', 'shortName': 'Color', 'dataDimensionType': 'DISAGGREGATION' ," + + "'categoryOptions' : [{'id' : '" + + categoryOptionRed + + "'} ] }")); + + String testCatCombo = + assertStatus( + HttpStatus.CREATED, + POST( + "/categoryCombos", + "{ 'name' : 'Color', " + + "'dataDimensionType' : 'DISAGGREGATION', 'categories' : [" + + "{'id' : '" + + categoryColor + + "'}]} ")); + + cocWithOptionsA = + assertStatus( + HttpStatus.CREATED, + POST( + "/categoryOptionCombos", + """ + { "name": "Reddish", + "categoryOptions" : [{"id" : "%s"}], + "categoryCombo" : {"id" : "%s"} } + """ + .formatted(categoryOptionRed, testCatCombo))); + + assertStatus( + HttpStatus.CREATED, + POST( + "/categoryOptionCombos", + """ + { "name": "Not Red", + "categoryOptions" : [{"id" : "%s"}]}, + "categoryCombo" : {"id" : "%s"} } + """ + .formatted(categoryOptionRed, testCatCombo))); + + assertNamedMetadataObjectExists("categoryOptionCombos", "default"); + assertNamedMetadataObjectExists("categoryOptionCombos", "Red"); + assertNamedMetadataObjectExists("categoryOptionCombos", "Reddish"); + assertNamedMetadataObjectExists("categoryOptionCombos", "Not Red"); + + /*We need to get the Red category option combo to be able to check the data integrity issues*/ + + JsonObject response = GET("/categoryOptionCombos?fields=id,name&filter=name:eq:Red").content(); + JsonList catOptionCombos = + response.getList("categoryOptionCombos", JsonCategoryOptionCombo.class); + String redCategoryOptionComboId = catOptionCombos.get(0).getId(); + /* There are four total category option combos, so we expect 25% */ + checkDataIntegritySummary(check, 1, 25, true); + + Set expectedCategoryOptCombos = Set.of(cocWithOptionsA, redCategoryOptionComboId); + Set expectedMessages = Set.of("Red", "Reddish"); + checkDataIntegrityDetailsIssues( + check, expectedCategoryOptCombos, expectedMessages, Set.of(), "categoryOptionCombos"); + } + + @Test + void testCategoryOptionCombosNotDuplicated() { + + categoryOptionRed = + assertStatus( + HttpStatus.CREATED, POST("/categoryOptions", "{ 'name': 'Red', 'shortName': 'Red' }")); + + String categoryOptionBlue = + assertStatus( + HttpStatus.CREATED, + POST("/categoryOptions", "{ 'name': 'Blue', 'shortName': 'Blue' }")); + + cocWithOptionsA = + assertStatus( + HttpStatus.CREATED, + POST( + "/categoryOptionCombos", + """ + { "name": "Color", + "categoryOptions" : [{"id" : "%s"} ] } + """ + .formatted(categoryOptionRed))); + + assertStatus( + HttpStatus.CREATED, + POST( + "/categoryOptionCombos", + """ + { "name": "Colour", + "categoryOptions" : [{"id" : "%s"} ] } + """ + .formatted(categoryOptionBlue))); + + assertHasNoDataIntegrityIssues("categoryOptionCombos", check, true); + } + + @Test + void testInvalidCategoryOptionCombosDivideByZero() { + + // Expect a percentage here, since there should always be the default category option combo + assertHasNoDataIntegrityIssues("categoryOptionCombos", check, true); + } +} diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/dataintegrity/DataIntegrityCategoryOptionCombosNoNamesControllerTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/dataintegrity/DataIntegrityCategoryOptionCombosNoNamesControllerTest.java new file mode 100644 index 000000000000..b451a208c824 --- /dev/null +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/dataintegrity/DataIntegrityCategoryOptionCombosNoNamesControllerTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2004-2024, 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.webapi.controller.dataintegrity; + +import static org.hisp.dhis.http.HttpAssertions.assertStatus; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.hisp.dhis.http.HttpStatus; +import org.hisp.dhis.jsontree.JsonList; +import org.hisp.dhis.jsontree.JsonObject; +import org.hisp.dhis.test.webapi.json.domain.JsonCategoryOptionCombo; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Tests metadata integrity check category option combinations with no names. + * + *

{@see + * dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/categories/category_option_combos_no_name.yaml} + * + * @author Jason P. Pickering + */ +class DataIntegrityCategoryOptionCombosNoNames extends AbstractDataIntegrityIntegrationTest { + + private final String check = "category_option_combos_no_names"; + + private String categoryOptionRed; + + @Test + void testCategoryOptionCombosNoNames() { + + /*We need to get the Red category option combo to be able to check the data integrity issues*/ + + JsonObject response = GET("/categoryOptionCombos?fields=id,name&filter=name:eq:Red").content(); + JsonList catOptionCombos = + response.getList("categoryOptionCombos", JsonCategoryOptionCombo.class); + String redCategoryOptionComboId = catOptionCombos.get(0).getId(); + /*Update the name to be empty*/ + assertStatus( + HttpStatus.OK, + PUT( + "/categoryOptionCombos/" + redCategoryOptionComboId, + "{ 'name': '', 'categoryOptions': [{'id': '" + categoryOptionRed + "'}]}")); + + JsonCategoryOptionCombo blankNameCatOptionCombo = + GET("/categoryOptionCombos/" + redCategoryOptionComboId + "?fields=id,name") + .content() + .as(JsonCategoryOptionCombo.class); + + assertEquals("", blankNameCatOptionCombo.getName()); + assertEquals(redCategoryOptionComboId, blankNameCatOptionCombo.getId()); + /* There are four total category option combos, so we expect 25% */ + checkDataIntegritySummary(check, 1, 50, true); + + assertHasDataIntegrityIssues( + "categoryOptionCombos", check, 50, redCategoryOptionComboId, "", "", true); + } + + @Test + void testCategoryOptionCombosHaveNames() { + + assertHasNoDataIntegrityIssues("categoryOptionCombos", check, true); + } + + @BeforeEach + void setupTest() { + categoryOptionRed = + assertStatus( + HttpStatus.CREATED, POST("/categoryOptions", "{ 'name': 'Red', 'shortName': 'Red' }")); + + String categoryColor = + assertStatus( + HttpStatus.CREATED, + POST( + "/categories", + "{ 'name': 'Color', 'shortName': 'Color', 'dataDimensionType': 'DISAGGREGATION' ," + + "'categoryOptions' : [{'id' : '" + + categoryOptionRed + + "'} ] }")); + + assertStatus( + HttpStatus.CREATED, + POST( + "/categoryCombos", + "{ 'name' : 'Color', " + + "'dataDimensionType' : 'DISAGGREGATION', 'categories' : [" + + "{'id' : '" + + categoryColor + + "'}]} ")); + + assertNamedMetadataObjectExists("categoryOptionCombos", "default"); + assertNamedMetadataObjectExists("categoryOptionCombos", "Red"); + } +} diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/dataintegrity/DataIntegrityDataElementsAggregationOperatorControllerTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/dataintegrity/DataIntegrityDataElementsAggregationOperatorControllerTest.java index 12d1fe2882f2..6d4866aec1f0 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/dataintegrity/DataIntegrityDataElementsAggregationOperatorControllerTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/dataintegrity/DataIntegrityDataElementsAggregationOperatorControllerTest.java @@ -33,22 +33,27 @@ import org.junit.jupiter.api.Test; /** - * Generally, non-numeric data elements should have their aggregation type set to NONE, while - * numeric data elements should have their aggregation type set to something other than NONE. {@see - * dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/data_elements/aggregate_des_inconsistent_agg_operator.yaml} + * <<<<<<< HEAD Generally, data elements which can be aggregated should have their aggregation type + * set to something other than NONE. {@see + * dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/data_elements/aggregate_des_can_aggregate_operator_none.yaml} + * Data elements which cannot be aggregate should have their aggregation type set to NONE. {@see + * dhis-2/dhis-services/dhis-service-administration/src/main/resources/data-integrity-checks/data_elements/aggregate_des_cannot_aggregate_operator_not_none.yaml} * * @author Jason P. Pickering */ class DataIntegrityDataElementsAggregationOperatorControllerTest extends AbstractDataIntegrityIntegrationTest { - private final String check = "data_elements_aggregate_aggregation_operator"; + + private final String check = "data_elements_can_aggregate_with_none_operator"; + + private final String check2 = "data_elements_cannot_aggregate_operator_not_none"; private final String detailsIdType = "dataElements"; private String dataElementB; @Test - void testDataElementInconsistentAggregation() { + void testCanAggregateInconsistentAggregation() { setUpDataElements(); dataElementB = @@ -56,24 +61,73 @@ void testDataElementInconsistentAggregation() { HttpStatus.CREATED, POST( "/dataElements", - "{ 'name': 'ANC3', 'shortName': 'ANC3', 'valueType' : 'TEXT'," - + "'domainType' : 'AGGREGATE', 'aggregationType' : 'SUM' }")); + "{ 'name': 'ANC3', 'shortName': 'ANC3', 'valueType' : 'INTEGER'," + + "'domainType' : 'AGGREGATE', 'aggregationType' : 'NONE' }")); assertHasDataIntegrityIssues(detailsIdType, check, 33, dataElementB, "ANC3", null, true); } @Test - void testDataElementsConsistentAggregation() { + void testCanAggregateConsistentAggregation() { setUpDataElements(); - dataElementB = + assertStatus( + HttpStatus.CREATED, + POST( + "/dataElements", + "{ 'name': 'ANC3', 'shortName': 'ANC3', 'valueType' : 'INTEGER'," + + "'domainType' : 'AGGREGATE', 'aggregationType' : 'SUM' }")); + + assertHasNoDataIntegrityIssues(detailsIdType, check, true); + } + + @Test + void testCannotAggregateConsistentAggregation() { + + setUpDataElements(); + assertStatus( + HttpStatus.CREATED, + POST( + "/dataElements", + "{ 'name': 'Narrative', 'shortName': 'Narrative', 'valueType' : 'TEXT'," + + "'domainType' : 'AGGREGATE', 'aggregationType' : 'NONE' }")); + + assertHasNoDataIntegrityIssues(detailsIdType, check2, true); + } + + @Test + void testCannotAggregateInconsistentAggregation() { + setUpDataElements(); + + String dataElementC = assertStatus( HttpStatus.CREATED, POST( "/dataElements", - "{ 'name': 'ANC3', 'shortName': 'ANC3', 'valueType' : 'TEXT'," - + "'domainType' : 'AGGREGATE', 'aggregationType' : 'NONE' }")); + "{ 'name': 'Narrative', 'shortName': 'Narrative', 'valueType' : 'TEXT'," + + "'domainType' : 'AGGREGATE', 'aggregationType' : 'SUM' }")); + + assertHasDataIntegrityIssues(detailsIdType, check2, 33, dataElementC, "Narrative", null, true); + } + + @Test + void trackerDataElementsAreNotIncluded() { + setUpDataElements(); + + assertStatus( + HttpStatus.CREATED, + POST( + "/dataElements", + "{ 'name': 'TRACKER_A', 'shortName': 'TRACKER_A', 'valueType' : 'INTEGER'," + + "'domainType' : 'TRACKER', 'aggregationType' : 'NONE' }")); + + assertStatus( + HttpStatus.CREATED, + POST( + "/dataElements", + "{ 'name': 'TRACKER_B', 'shortName': 'TRACKER_B', 'valueType' : 'INTEGER'," + + "'domainType' : 'TRACKER', 'aggregationType' : 'SUM' }")); assertHasNoDataIntegrityIssues(detailsIdType, check, true); } @@ -82,6 +136,7 @@ void testDataElementsConsistentAggregation() { void testDataElementsAggregationDividedByZero() { assertHasNoDataIntegrityIssues(detailsIdType, check, false); + assertHasNoDataIntegrityIssues(detailsIdType, check2, false); } void setUpDataElements() { diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/JsonEvent.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/JsonEvent.java index 38a9541b7459..c5e68ab77f64 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/JsonEvent.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/JsonEvent.java @@ -60,6 +60,14 @@ default String getOrgUnit() { return getString("orgUnit").string(); } + default String attributeOptionCombo() { + return getString("attributeOptionCombo").string(); + } + + default String attributeCategoryOptions() { + return getString("attributeCategoryOptions").string(); + } + default Boolean getDeleted() { return getBoolean("deleted").bool(); } diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/JsonEventChangeLog.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/JsonEventChangeLog.java index f2612ec5614f..638a7949365d 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/JsonEventChangeLog.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/JsonEventChangeLog.java @@ -28,8 +28,9 @@ package org.hisp.dhis.webapi.controller.tracker; import org.hisp.dhis.jsontree.JsonObject; +import org.hisp.dhis.webapi.controller.tracker.view.EventChangeLog; -/** Representation of {@link org.hisp.dhis.tracker.export.event.EventChangeLog}. */ +/** Representation of {@link EventChangeLog}. */ public interface JsonEventChangeLog extends JsonObject { default JsonUser getCreatedBy() { return get("createdBy").as(JsonUser.class); @@ -47,6 +48,10 @@ interface JsonChange extends JsonObject { default JsonDataValue getDataValue() { return get("dataValue").as(JsonDataValue.class); } + + default JsonEventProperty getEventProperty() { + return get("eventProperty").as(JsonEventProperty.class); + } } interface JsonDataValue extends JsonObject { @@ -62,4 +67,18 @@ default String getCurrentValue() { return getString("currentValue").string(); } } + + interface JsonEventProperty extends JsonObject { + default String getProperty() { + return getString("property").string(); + } + + default String getPreviousValue() { + return getString("previousValue").string(); + } + + default String getCurrentValue() { + return getString("currentValue").string(); + } + } } diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/JsonTrackedEntityChangeLog.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/JsonTrackedEntityChangeLog.java index 97e82e11623b..224bfbe58d9b 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/JsonTrackedEntityChangeLog.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/JsonTrackedEntityChangeLog.java @@ -28,8 +28,9 @@ package org.hisp.dhis.webapi.controller.tracker; import org.hisp.dhis.jsontree.JsonObject; +import org.hisp.dhis.webapi.controller.tracker.view.EventChangeLog; -/** Representation of {@link org.hisp.dhis.tracker.export.event.EventChangeLog}. */ +/** Representation of {@link EventChangeLog}. */ public interface JsonTrackedEntityChangeLog extends JsonObject { default JsonUser getCreatedBy() { return get("createdBy").as(JsonUser.class); 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 new file mode 100644 index 000000000000..34b73b004540 --- /dev/null +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/IdSchemeExportControllerTest.java @@ -0,0 +1,398 @@ +/* + * 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.webapi.controller.tracker.export; + +import static org.hisp.dhis.test.utils.Assertions.assertContains; +import static org.hisp.dhis.test.utils.Assertions.assertContainsOnly; +import static org.hisp.dhis.test.utils.Assertions.assertIsEmpty; +import static org.hisp.dhis.test.utils.Assertions.assertNotEmpty; +import static org.hisp.dhis.test.webapi.Assertions.assertWebMessage; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; +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; +import org.hisp.dhis.dxf2.metadata.objectbundle.ObjectBundleService; +import org.hisp.dhis.dxf2.metadata.objectbundle.ObjectBundleValidationService; +import org.hisp.dhis.dxf2.metadata.objectbundle.feedback.ObjectBundleValidationReport; +import org.hisp.dhis.http.HttpStatus; +import org.hisp.dhis.importexport.ImportStrategy; +import org.hisp.dhis.jsontree.JsonObject; +import org.hisp.dhis.program.Event; +import org.hisp.dhis.render.RenderFormat; +import org.hisp.dhis.render.RenderService; +import org.hisp.dhis.test.webapi.PostgresControllerIntegrationTestBase; +import org.hisp.dhis.test.webapi.json.domain.JsonWebMessage; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; +import org.hisp.dhis.tracker.imports.TrackerImportParams; +import org.hisp.dhis.tracker.imports.TrackerImportService; +import org.hisp.dhis.tracker.imports.domain.TrackerObjects; +import org.hisp.dhis.tracker.imports.report.ImportReport; +import org.hisp.dhis.tracker.imports.report.Status; +import org.hisp.dhis.tracker.imports.report.ValidationReport; +import org.hisp.dhis.user.User; +import org.hisp.dhis.webapi.controller.tracker.JsonEvent; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; +import org.springframework.transaction.annotation.Transactional; + +/** Tests tracker exporter idScheme support. */ +@Transactional +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class IdSchemeExportControllerTest extends PostgresControllerIntegrationTestBase { + + private static final String METADATA_ATTRIBUTE = "j45AR9cBQKc"; + private static final String UNUSED_METADATA_ATTRIBUTE = "i57a0734128"; + + @Autowired private RenderService renderService; + + @Autowired private ObjectBundleService objectBundleService; + + @Autowired private ObjectBundleValidationService objectBundleValidationService; + + @Autowired private TrackerImportService trackerImportService; + + @Autowired private IdentifiableObjectManager manager; + + private User importUser; + + protected ObjectBundle setUpMetadata(String path) throws IOException { + Map, List> metadata = + renderService.fromMetadata(new ClassPathResource(path).getInputStream(), RenderFormat.JSON); + ObjectBundleParams params = new ObjectBundleParams(); + params.setObjectBundleMode(ObjectBundleMode.COMMIT); + params.setImportStrategy(ImportStrategy.CREATE); + params.setObjects(metadata); + ObjectBundle bundle = objectBundleService.create(params); + assertNoErrors(objectBundleValidationService.validate(bundle)); + objectBundleService.commit(bundle); + return bundle; + } + + protected TrackerObjects fromJson(String path) throws IOException { + return renderService.fromJson( + new ClassPathResource(path).getInputStream(), TrackerObjects.class); + } + + @BeforeAll + void setUp() throws IOException { + setUpMetadata("tracker/simple_metadata.json"); + + importUser = userService.getUser("tTgjgobT1oS"); + injectSecurityContextUser(importUser); + + TrackerImportParams params = TrackerImportParams.builder().build(); + assertNoErrors( + trackerImportService.importTracker(params, fromJson("tracker/event_and_enrollment.json"))); + // ensure these are created in the setup + get(Attribute.class, METADATA_ATTRIBUTE); + get(Attribute.class, UNUSED_METADATA_ATTRIBUTE); + + manager.flush(); + manager.clear(); + } + + @BeforeEach + void setUpUser() { + switchContextToUser(importUser); + } + + @ParameterizedTest + @MethodSource(value = "shouldExportMetadataUsingGivenIdSchemeProvider") + void shouldExportMetadataUsingGivenIdScheme(TrackerIdSchemeParam idSchemeParam) { + Event event = get(Event.class, "QRYjLTiJTrA"); + assertNotEmpty(event.getEventDataValues(), "test expects an event with data values"); + + // maps JSON fields to idScheme request parameters + Map idSchemeRequestParams = + Map.of( + "orgUnit", + "orgUnit", + "program", + "program", + "programStage", + "programStage", + "attributeOptionCombo", + "categoryOptionCombo", + "attributeCategoryOptions", + "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 -> + (() -> + assertIdScheme( + idSchemeParam.getIdentifier(event.getOrganisationUnit()), + actual, + idSchemeParam, + "orgUnit")), + "program", + actual -> + (() -> + assertIdScheme( + idSchemeParam.getIdentifier(event.getProgramStage().getProgram()), + actual, + idSchemeParam, + "program")), + "programStage", + actual -> + (() -> + assertIdScheme( + idSchemeParam.getIdentifier(event.getProgramStage()), + actual, + idSchemeParam, + "programStage")), + "attributeOptionCombo", + actual -> + (() -> + assertIdScheme( + idSchemeParam.getIdentifier(event.getAttributeOptionCombo()), + actual, + idSchemeParam, + "attributeOptionCombo")), + "attributeCategoryOptions", + json -> + (() -> { + 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( + "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() + .map(m -> idSchemeRequestParams.get(m) + "IdScheme=" + idSchemeParam) + .collect(Collectors.joining("&")); + + JsonEvent actual = + GET("/tracker/events/{id}?fields={fields}&{idSchemes}", event.getUid(), fields, idSchemes) + .content(HttpStatus.OK) + .as(JsonEvent.class); + + assertMetadataIdScheme(metadata, actual, idSchemeParam, "event"); + } + + @Test + void shouldExportEventUsingNonUIDDataElementIdSchemeEvenIfItHasNoDataValues() { + Event event = get(Event.class, "jxgFyJEMUPf"); + assertIsEmpty(event.getEventDataValues(), "test expects an event with no data values"); + + JsonEvent actual = + GET("/tracker/events/{id}?fields=event,dataValues&dataElementIdScheme=NAME", event.getUid()) + .content(HttpStatus.OK) + .as(JsonEvent.class); + + assertEquals("jxgFyJEMUPf", actual.getEvent()); + } + + @ParameterizedTest + @ValueSource(strings = {"/{id}?", "?events={id}&paging=true&", "?events={id}&paging=false&"}) + void shouldReportMetadataWhichDoesNotHaveAnIdentifierForGivenIdScheme(String urlPortion) { + Event event = get(Event.class, "QRYjLTiJTrA"); + + JsonWebMessage msg = + assertWebMessage( + HttpStatus.UNPROCESSABLE_ENTITY, + GET( + "/tracker/events" + + urlPortion + + "fields=orgUnit,program,programStage,attributeOptionCombo,attributeCategoryOptions,dataValues&idScheme=ATTRIBUTE:{attribute}", + event.getUid(), + UNUSED_METADATA_ATTRIBUTE)); + + assertAll( + () -> + assertContains( + "Not all metadata has an identifier for the requested idScheme", msg.getMessage()), + () -> + assertContains( + "Program[ATTRIBUTE:" + UNUSED_METADATA_ATTRIBUTE + "]", msg.getDevMessage())); + } + + public static Stream shouldExportMetadataUsingGivenIdSchemeProvider() { + return Stream.of( + TrackerIdSchemeParam.UID, + TrackerIdSchemeParam.CODE, + TrackerIdSchemeParam.NAME, + TrackerIdSchemeParam.ofAttribute(METADATA_ATTRIBUTE)); + } + + /** + * Asserts that every metadata key from {@code expected} is a field in the {@code actual} JSON and + * that its string value matches the requested {@code idSchemeParam}. + */ + 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 for idScheme=" + idSchemeParam, assertions); + } + + private static void assertIdScheme( + String expected, JsonObject actual, TrackerIdSchemeParam idSchemeParam, String field) { + assertNotEmpty( + expected, + String.format( + "metadata corresponding to field \"%s\" has no value in test data for idScheme '%s'", + field, idSchemeParam)); + assertTrue( + actual.has(field), + () -> + String.format( + "field \"%s\" is not in response %s for idScheme '%s'", + field, actual, idSchemeParam)); + assertEquals( + expected, + actual.getString(field).string(), + () -> + String.format( + "field \"%s\" does not have required idScheme '%s' in response", + field, idSchemeParam)); + } + + private T get(Class type, String uid) { + T t = manager.get(type, uid); + assertNotNull( + t, + () -> + String.format( + "'%s' with uid '%s' should have been created", type.getSimpleName(), uid)); + return t; + } + + public static void assertNoErrors(ImportReport report) { + assertNotNull(report); + assertEquals( + Status.OK, + report.getStatus(), + errorMessage( + "Expected import with status OK, instead got:%n", report.getValidationReport())); + } + + private static Supplier errorMessage(String errorTitle, ValidationReport report) { + return () -> { + StringBuilder msg = new StringBuilder(errorTitle); + report + .getErrors() + .forEach( + e -> { + msg.append(e.getErrorCode()); + msg.append(": "); + msg.append(e.getMessage()); + msg.append('\n'); + }); + return msg.toString(); + }; + } + + public static void assertNoErrors(ObjectBundleValidationReport report) { + assertNotNull(report); + List errors = new ArrayList<>(); + report.forEachErrorReport( + err -> { + errors.add(err.toString()); + }); + assertFalse( + report.hasErrorReports(), String.format("Expected no errors, instead got: %s%n", errors)); + } +} diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventsExportChangeLogsControllerTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventsExportChangeLogsControllerTest.java index 26ced75b8f8d..6a87881c6a73 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventsExportChangeLogsControllerTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventsExportChangeLogsControllerTest.java @@ -29,17 +29,21 @@ import static org.hisp.dhis.security.Authorities.ALL; import static org.hisp.dhis.test.utils.Assertions.assertContains; +import static org.hisp.dhis.test.utils.Assertions.assertHasSize; import static org.hisp.dhis.test.utils.Assertions.assertStartsWith; import static org.hisp.dhis.webapi.controller.tracker.JsonAssertions.assertHasNoMember; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.common.collect.Sets; import java.util.Date; +import java.util.List; import org.hisp.dhis.analytics.AggregationType; import org.hisp.dhis.category.CategoryOptionCombo; import org.hisp.dhis.category.CategoryService; +import org.hisp.dhis.changelog.ChangeLogType; import org.hisp.dhis.common.IdentifiableObjectManager; import org.hisp.dhis.common.ValueType; import org.hisp.dhis.dataelement.DataElement; @@ -62,6 +66,7 @@ import org.hisp.dhis.user.User; import org.hisp.dhis.user.UserDetails; import org.hisp.dhis.user.sharing.UserAccess; +import org.hisp.dhis.webapi.controller.tracker.JsonAssertions; import org.hisp.dhis.webapi.controller.tracker.JsonEventChangeLog; import org.hisp.dhis.webapi.controller.tracker.JsonPage; import org.hisp.dhis.webapi.controller.tracker.JsonPage.JsonPager; @@ -165,12 +170,28 @@ void shouldGetEventChangeLogInDescOrderByDefault() { GET("/tracker/events/{id}/changeLogs", event.getUid()) .content(HttpStatus.OK) .getList("changeLogs", JsonEventChangeLog.class); + List dataValueChangeLogs = + changeLogs.stream() + .filter(log -> log.getChange().getDataValue().getDataElement() != null) + .toList(); + List eventPropertyChangeLogs = + changeLogs.stream() + .filter(log -> log.getChange().getEventProperty().getProperty() != null) + .toList(); + + assertHasSize(3, dataValueChangeLogs); + assertHasSize(2, eventPropertyChangeLogs); - assertNumberOfChanges(3, changeLogs); assertAll( - () -> assertDelete(dataElement, "value 3", changeLogs.get(0)), - () -> assertUpdate(dataElement, "value 2", "value 3", changeLogs.get(1)), - () -> assertUpdate(dataElement, "value 1", "value 2", changeLogs.get(2))); + () -> assertDelete(dataElement, "value 3", dataValueChangeLogs.get(0)), + () -> assertUpdate(dataElement, "value 2", "value 3", dataValueChangeLogs.get(1)), + () -> assertUpdate(dataElement, "value 1", "value 2", dataValueChangeLogs.get(2)), + () -> + assertPropertyCreateExists( + "occurredAt", "2023-01-10 00:00:00.000", eventPropertyChangeLogs), + () -> + assertPropertyCreateExists( + "scheduledAt", "2023-01-10 00:00:00.000", eventPropertyChangeLogs)); } @Test @@ -179,12 +200,45 @@ void shouldGetEventChangeLogInAscOrder() { GET("/tracker/events/{id}/changeLogs?order=createdAt:asc", event.getUid()) .content(HttpStatus.OK) .getList("changeLogs", JsonEventChangeLog.class); + List dataValueChangeLogs = + changeLogs.stream() + .filter(log -> log.getChange().getDataValue().getDataElement() != null) + .toList(); + List eventPropertyChangeLogs = + changeLogs.stream() + .filter(log -> log.getChange().getEventProperty().getProperty() != null) + .toList(); + + assertHasSize(3, dataValueChangeLogs); + assertHasSize(2, eventPropertyChangeLogs); + assertAll( + () -> assertUpdate(dataElement, "value 1", "value 2", dataValueChangeLogs.get(0)), + () -> assertUpdate(dataElement, "value 2", "value 3", dataValueChangeLogs.get(1)), + () -> assertDelete(dataElement, "value 3", dataValueChangeLogs.get(2)), + () -> + assertPropertyCreateExists( + "occurredAt", "2023-01-10 00:00:00.000", eventPropertyChangeLogs), + () -> + assertPropertyCreateExists( + "scheduledAt", "2023-01-10 00:00:00.000", eventPropertyChangeLogs)); + } + + @Test + void shouldGetEventChangeLogsWhenFilteringByProperty() { + JsonList changeLogs = + GET("/tracker/events/{id}/changeLogs?filter=property:eq:occurredAt", event.getUid()) + .content(HttpStatus.OK) + .getList("changeLogs", JsonEventChangeLog.class); + List eventPropertyChangeLogs = + changeLogs.stream() + .filter(log -> log.getChange().getEventProperty().getProperty() != null) + .toList(); - assertNumberOfChanges(3, changeLogs); assertAll( - () -> assertUpdate(dataElement, "value 1", "value 2", changeLogs.get(0)), - () -> assertUpdate(dataElement, "value 2", "value 3", changeLogs.get(1)), - () -> assertDelete(dataElement, "value 3", changeLogs.get(2))); + () -> assertHasSize(1, eventPropertyChangeLogs), + () -> + assertPropertyCreateExists( + "occurredAt", "2023-01-10 00:00:00.000", eventPropertyChangeLogs)); } @Test @@ -250,19 +304,19 @@ void shouldGetChangeLogPagerWithNextElementWhenMultipleElementsImportedAndFirstP GET( "/tracker/events/{id}/changeLogs?page={page}&pageSize={pageSize}", event.getUid(), - "3", + "5", "1") .content(HttpStatus.OK) .asA(JsonPage.class); JsonPager pager = changeLogs.getPager(); assertAll( - () -> assertEquals(3, pager.getPage()), + () -> assertEquals(5, pager.getPage()), () -> assertEquals(1, pager.getPageSize()), () -> assertPagerLink( pager.getPrevPage(), - 2, + 4, 1, String.format("http://localhost/api/tracker/events/%s/changeLogs", event.getUid())), () -> assertHasNoMember(pager, "nextPage")); @@ -276,14 +330,14 @@ void shouldGetChangeLogPagerWithNextElementWhenMultipleElementsImportedAndFirstP "/tracker/events/{id}/changeLogs?page={page}&pageSize={pageSize}", event.getUid(), "1", - "3") + "5") .content(HttpStatus.OK) .asA(JsonPage.class); JsonPager pagerObject = changeLogs.getPager(); assertAll( () -> assertEquals(1, pagerObject.getPage()), - () -> assertEquals(3, pagerObject.getPageSize()), + () -> assertEquals(5, pagerObject.getPageSize()), () -> assertHasNoMember(pagerObject, "prevPage"), () -> assertHasNoMember(pagerObject, "nextPage")); } @@ -386,16 +440,6 @@ private String createJson(Event event, String value) { value); } - private static void assertNumberOfChanges(int expected, JsonList changeLogs) { - assertNotNull(changeLogs); - assertEquals( - expected, - changeLogs.size(), - String.format( - "Expected to find %s elements in the change log list, found %s instead: %s", - expected, changeLogs.size(), changeLogs)); - } - private static void assertUser(JsonEventChangeLog changeLog) { UserDetails currentUser = CurrentUserUtil.getCurrentUserDetails(); JsonUser createdBy = changeLog.getCreatedBy(); @@ -434,7 +478,8 @@ private static void assertChange( () -> assertEquals(dataElement.getUid(), actual.getChange().getDataValue().getDataElement()), () -> assertEquals(previousValue, actual.getChange().getDataValue().getPreviousValue()), - () -> assertEquals(currentValue, actual.getChange().getDataValue().getCurrentValue())); + () -> assertEquals(currentValue, actual.getChange().getDataValue().getCurrentValue()), + () -> JsonAssertions.assertHasNoMember(actual.getChange(), "eventProperty")); } private static void assertPagerLink(String actual, int page, int pageSize, String start) { @@ -444,4 +489,26 @@ private static void assertPagerLink(String actual, int page, int pageSize, Strin () -> assertContains("page=" + page, actual), () -> assertContains("pageSize=" + pageSize, actual)); } + + private static void assertPropertyCreateExists( + String property, String currentValue, List changeLogs) { + assertTrue( + changeLogs.stream().anyMatch(cl -> isEventPropertyCreate(cl, property, currentValue)), + "Expected a " + + property + + " change with value " + + currentValue + + " among the change log entries."); + assertTrue( + changeLogs.stream().noneMatch(cl -> cl.getChange().has("dataValue")), + "Data value change not expected to be present, but it was"); + } + + private static boolean isEventPropertyCreate( + JsonEventChangeLog actual, String property, String currentValue) { + return actual.getType().equals(ChangeLogType.CREATE.name()) + && actual.getChange().getEventProperty().getProperty().equals(property) + && actual.getChange().getEventProperty().getCurrentValue().equals(currentValue) + && actual.getChange().getEventProperty().getPreviousValue() == null; + } } diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventsExportControllerUnitTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventsExportControllerUnitTest.java index 4701e82d55e3..021e8e429390 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventsExportControllerUnitTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventsExportControllerUnitTest.java @@ -67,8 +67,7 @@ void shouldFailInstantiatingControllerIfAnyOrderableFieldIsUnsupported() { assertThrows( IllegalStateException.class, () -> - new EventsExportController( - eventService, null, null, null, null, null, null, null, null)); + new EventsExportController(eventService, null, null, null, null, null, null, null)); assertAll( () -> assertStartsWith("event controller supports ordering by", exception.getMessage()), diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/trackedentity/TrackedEntitiesExportControllerPostgresTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/trackedentity/TrackedEntitiesExportControllerPostgresTest.java index 11cf64fe4d8e..8dff7f95e871 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/trackedentity/TrackedEntitiesExportControllerPostgresTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/trackedentity/TrackedEntitiesExportControllerPostgresTest.java @@ -320,22 +320,22 @@ private Supplier errorMessage(String errorTitle, ValidationReport report private String createJsonPayload(int value) { return """ - { - "trackedEntities": [ - { - "attributes": [ - { - "attribute": "numericAttr", - "value": %d - } - ], - "trackedEntity": "IOR1AXXl24H", - "trackedEntityType": "ja8NY4PW7Xm", - "orgUnit": "h4w96yEMlzO" - } - ] - } - """ + { + "trackedEntities": [ + { + "attributes": [ + { + "attribute": "numericAttr", + "value": %d + } + ], + "trackedEntity": "IOR1AXXl24H", + "trackedEntityType": "ja8NY4PW7Xm", + "orgUnit": "h4w96yEMlzO" + } + ] + } + """ .formatted(value); } diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/imports/TrackerEnrollmentSMSTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/imports/TrackerEnrollmentSMSTest.java index 10dceb2ecca8..b8e55d8f0967 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/imports/TrackerEnrollmentSMSTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/imports/TrackerEnrollmentSMSTest.java @@ -244,12 +244,16 @@ void shouldCreateTrackedEntityAndEnrollIt() switchContextToUser(user); JsonWebMessage response = - POST("/sms/inbound", format(""" + POST( + "/sms/inbound", + format( + """ { "text": "%s", "originator": "%s" } -""", text, originator)) +""", + text, originator)) .content(HttpStatus.OK) .as(JsonWebMessage.class); diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/imports/TrackerEventSMSTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/imports/TrackerEventSMSTest.java index b2eb9104a426..ed78ae34ae7a 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/imports/TrackerEventSMSTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/imports/TrackerEventSMSTest.java @@ -497,12 +497,16 @@ void shouldUpdateEvent() throws SmsCompressionException, ForbiddenException, Not switchContextToUser(user1); JsonWebMessage response = - POST("/sms/inbound", format(""" + POST( + "/sms/inbound", + format( + """ { "text": "%s", "originator": "%s" } -""", text, originator)) +""", + text, originator)) .content(HttpStatus.OK) .as(JsonWebMessage.class); @@ -564,12 +568,16 @@ void shouldCreateEventInEventProgram() switchContextToUser(user1); JsonWebMessage response = - POST("/sms/inbound", format(""" + POST( + "/sms/inbound", + format( + """ { "text": "%s", "originator": "%s" } -""", text, originator)) +""", + text, originator)) .content(HttpStatus.OK) .as(JsonWebMessage.class); @@ -626,12 +634,14 @@ void shouldCreateEventInEventProgramViaEventRegistrationParserCommand() JsonWebMessage response = POST( "/sms/inbound", - format(""" + format( + """ { "text": "visit a=hello", "originator": "%s" } -""", originator)) +""", + originator)) .content(HttpStatus.OK) .as(JsonWebMessage.class); @@ -691,12 +701,14 @@ void shouldCreateEventAndEnrollmentInTrackerProgramViaProgramStageDataEntryComma JsonWebMessage response = POST( "/sms/inbound", - format(""" + format( + """ { "text": "birth a=hello", "originator": "%s" } -""", originator)) +""", + originator)) .content(HttpStatus.OK) .as(JsonWebMessage.class); @@ -775,12 +787,14 @@ void shouldCreateEventInExistingEnrollmentInTrackerProgramViaProgramStageDataEnt JsonWebMessage response = POST( "/sms/inbound", - format(""" + format( + """ { "text": "birth a=hello", "originator": "%s" } -""", originator)) +""", + originator)) .content(HttpStatus.OK) .as(JsonWebMessage.class); diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/imports/TrackerImportControllerTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/imports/TrackerImportControllerTest.java index f14bb9884e6a..839e3ca505e0 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/imports/TrackerImportControllerTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/imports/TrackerImportControllerTest.java @@ -61,6 +61,30 @@ void shouldSucceedWhenAllValidParametersArePassed() { .content(HttpStatus.OK)); } + @Test + void shouldReturnBadRequestWhenThereIsAnInvalidUidInThePayload() { + assertWebMessage( + "Bad Request", + 400, + "ERROR", + "JSON parse error: Cannot construct instance of `org.hisp.dhis.common.UID`, problem: UID must be an alphanumeric string of 11 characters starting with a letter.", + POST( + "/tracker?async=false", + """ + + { + "trackedEntities": [ + { + "trackedEntity": "invalid_uid", + "trackedEntityType": "PrZMWi7rBga", + "orgUnit": "PSeMWi7rBgb" + } + ] + } + """) + .content(HttpStatus.BAD_REQUEST)); + } + @Test void shouldReturnBadRequestWhenEmptyIdSchemeArePassed() { assertWebMessage( 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 new file mode 100644 index 000000000000..12a75f2722c6 --- /dev/null +++ b/dhis-2/dhis-test-web-api/src/test/resources/tracker/event_and_enrollment.json @@ -0,0 +1,1267 @@ +{ + "importMode": "COMMIT", + "idSchemes": { + "dataElementIdScheme": { + "idScheme": "UID" + }, + "orgUnitIdScheme": { + "idScheme": "UID" + }, + "programIdScheme": { + "idScheme": "UID" + }, + "programStageIdScheme": { + "idScheme": "UID" + }, + "idScheme": { + "idScheme": "UID" + }, + "categoryOptionComboIdScheme": { + "idScheme": "UID" + }, + "categoryOptionIdScheme": { + "idScheme": "UID" + } + }, + "importStrategy": "CREATE", + "atomicMode": "ALL", + "flushMode": "AUTO", + "validationMode": "FULL", + "skipPatternValidation": false, + "skipSideEffects": false, + "skipRuleEngine": false, + "trackedEntities": [ + { + "trackedEntity": "QS6w44flWAf", + "trackedEntityType": { + "idScheme": "UID", + "identifier": "ja8NY4PW7Xm" + }, + "orgUnit": { + "idScheme": "UID", + "identifier": "h4w96yEMlzO" + }, + "inactive": true, + "deleted": false, + "potentialDuplicate": false, + "createdAtClient": "2018-10-01T12:17:30.163", + "relationships": [], + "attributes": [ + { + "valueType": "TEXT", + "attribute": { + "idScheme": "UID", + "identifier": "toDelete000" + }, + "value": "just day" + }, + { + "valueType": "TEXT", + "attribute": { + "idScheme": "UID", + "identifier": "toUpdate000" + }, + "value": "summer day" + }, + { + "valueType": "INTEGER", + "attribute": { + "idScheme": "UID", + "identifier": "numericAttr" + }, + "value": "88" + } + ], + "enrollments": [] + }, + { + "trackedEntity": "dUE514NMOlo", + "trackedEntityType": { + "idScheme": "UID", + "identifier": "ja8NY4PW7Xm" + }, + "orgUnit": { + "idScheme": "UID", + "identifier": "h4w96yEMlzO" + }, + "inactive": false, + "deleted": false, + "potentialDuplicate": false, + "createdAtClient": "2018-11-01T12:17:30.163", + "relationships": [], + "attributes": [ + { + "valueType": "TEXT", + "attribute": { + "idScheme": "UID", + "identifier": "toDelete000" + }, + "value": "just day" + }, + { + "valueType": "TEXT", + "attribute": { + "idScheme": "UID", + "identifier": "toUpdate000" + }, + "value": "rainy day" + }, + { + "valueType": "TEXT", + "attribute": { + "idScheme": "UID", + "identifier": "notUpdated0" + }, + "value": "winter day" + }, + { + "valueType": "INTEGER", + "attribute": { + "idScheme": "UID", + "identifier": "numericAttr" + }, + "value": "70" + } + ], + "enrollments": [] + }, + { + "trackedEntity": "mHWCacsGYYn", + "trackedEntityType": { + "idScheme": "UID", + "identifier": "ja8NY4PW7Xm" + }, + "orgUnit": { + "idScheme": "UID", + "identifier": "h4w96yEMlzO" + }, + "inactive": false, + "deleted": false, + "potentialDuplicate": false, + "relationships": [], + "attributes": [ + { + "valueType": "TEXT", + "attribute": { + "idScheme": "UID", + "identifier": "toDelete000" + }, + "value": "just day" + }, + { + "valueType": "TEXT", + "attribute": { + "idScheme": "UID", + "identifier": "toUpdate000" + }, + "value": "summer day" + }, + { + "valueType": "INTEGER", + "attribute": { + "idScheme": "UID", + "identifier": "numericAttr" + }, + "value": "72" + } + ], + "enrollments": [] + }, + { + "trackedEntity": "QesgJkTyTCk", + "trackedEntityType": { + "idScheme": "UID", + "identifier": "ja8NY4PW7Xm" + }, + "orgUnit": { + "idScheme": "UID", + "identifier": "h4w96yEMlzO" + }, + "inactive": false, + "deleted": false, + "potentialDuplicate": false, + "relationships": [], + "attributes": [ + { + "valueType": "TEXT", + "attribute": { + "idScheme": "UID", + "identifier": "toDelete000" + }, + "value": "just day" + }, + { + "valueType": "TEXT", + "attribute": { + "idScheme": "UID", + "identifier": "toUpdate000" + }, + "value": "summer day" + }, + { + "valueType": "INTEGER", + "attribute": { + "idScheme": "UID", + "identifier": "numericAttr" + }, + "value": "89" + } + ], + "enrollments": [] + }, + { + "trackedEntity": "guVNoAerxWo", + "trackedEntityType": { + "idScheme": "UID", + "identifier": "ja8NY4PW7Xm" + }, + "orgUnit": { + "idScheme": "UID", + "identifier": "tSsGrtfRzjY" + }, + "inactive": false, + "deleted": false, + "potentialDuplicate": false, + "relationships": [], + "attributes": [ + { + "valueType": "TEXT", + "attribute": { + "idScheme": "UID", + "identifier": "toDelete000" + }, + "value": "just day" + }, + { + "valueType": "TEXT", + "attribute": { + "idScheme": "UID", + "identifier": "toUpdate000" + }, + "value": "summer day" + }, + { + "valueType": "INTEGER", + "attribute": { + "idScheme": "UID", + "identifier": "numericAttr" + }, + "value": "91" + } + ], + "enrollments": [] + }, + { + "trackedEntity": "woitxQbWYNq", + "trackedEntityType": { + "idScheme": "UID", + "identifier": "ja8NY4PW7Xm" + }, + "orgUnit": { + "idScheme": "UID", + "identifier": "RojfDTBhoGC" + }, + "inactive": false, + "deleted": false, + "potentialDuplicate": false, + "relationships": [], + "attributes": [ + { + "valueType": "TEXT", + "attribute": { + "idScheme": "UID", + "identifier": "toDelete000" + }, + "value": "just day" + }, + { + "valueType": "TEXT", + "attribute": { + "idScheme": "UID", + "identifier": "toUpdate000" + }, + "value": "summer day" + }, + { + "valueType": "INTEGER", + "attribute": { + "idScheme": "UID", + "identifier": "numericAttr" + }, + "value": "90" + } + ], + "enrollments": [] + }, + { + "trackedEntity": "XUitxQbWYNq", + "trackedEntityType": { + "idScheme": "UID", + "identifier": "Ip8NY4PW7Xm" + }, + "orgUnit": { + "idScheme": "UID", + "identifier": "DiszpKrYNg8" + }, + "inactive": false, + "deleted": false, + "potentialDuplicate": false, + "relationships": [], + "enrollments": [] + } + ], + "enrollments": [ + { + "enrollment": "nxP7UnKhomJ", + "createdAtClient": "2017-01-26T13:48:13.363", + "trackedEntity": "QS6w44flWAf", + "program": { + "idScheme": "UID", + "identifier": "BFcipDERJnf" + }, + "status": "ACTIVE", + "orgUnit": { + "idScheme": "UID", + "identifier": "h4w96yEMlzO" + }, + "orgUnitName": "Mbokie CHP", + "enrolledAt": "2021-02-28T12:05:00.000", + "occurredAt": "2021-02-28T12:05:00.000", + "scheduledAt": "2021-02-28T12:05:00.000", + "followUp": false, + "deleted": false, + "events": [], + "relationships": [], + "attributes": [], + "notes": [] + }, + { + "enrollment": "nxP8UnKhomJ", + "createdAtClient": "2017-01-26T13:48:13.363", + "trackedEntity": "QS6w44flWAf", + "program": { + "idScheme": "UID", + "identifier": "shPjYNifvMK" + }, + "status": "ACTIVE", + "orgUnit": { + "idScheme": "UID", + "identifier": "uoNW0E3xXUy" + }, + "enrolledAt": "2021-04-28T12:05:00.000", + "occurredAt": "2021-04-28T12:05:00.000", + "scheduledAt": "2021-04-28T12:05:00.000", + "followUp": false, + "deleted": false, + "events": [], + "relationships": [], + "attributes": [], + "notes": [] + }, + { + "enrollment": "TvctPPhpD8z", + "createdAtClient": "2017-01-26T13:48:13.363", + "trackedEntity": "dUE514NMOlo", + "program": { + "idScheme": "UID", + "identifier": "BFcipDERJnf" + }, + "status": "COMPLETED", + "orgUnit": { + "idScheme": "UID", + "identifier": "h4w96yEMlzO" + }, + "orgUnitName": "Mbokie CHP", + "enrolledAt": "2021-03-28T12:05:00.000", + "occurredAt": "2021-03-28T12:05:00.000", + "scheduledAt": "2021-02-28T12:05:00.000", + "followUp": false, + "deleted": false, + "events": [], + "relationships": [], + "attributes": [], + "notes": [] + }, + { + "enrollment": "JuioKiICQqI", + "createdAtClient": "2017-01-26T13:48:13.363", + "trackedEntity": "mHWCacsGYYn", + "program": { + "idScheme": "UID", + "identifier": "shPjYNifvMK" + }, + "status": "ACTIVE", + "orgUnit": { + "idScheme": "UID", + "identifier": "uoNW0E3xXUy" + }, + "orgUnitName": "test-orgunit-2", + "enrolledAt": "2021-02-28T12:05:00.000", + "occurredAt": "2021-02-28T12:05:00.000", + "scheduledAt": "2021-02-28T12:05:00.000", + "followUp": false, + "deleted": false, + "events": [], + "relationships": [], + "attributes": [], + "notes": [] + }, + { + "enrollment": "iHFHfPKTSYP", + "createdAtClient": "2017-01-26T13:48:13.363", + "trackedEntity": "QesgJkTyTCk", + "program": { + "idScheme": "UID", + "identifier": "pcxIanBWlSY" + }, + "status": "ACTIVE", + "orgUnit": { + "idScheme": "UID", + "identifier": "uoNW0E3xXUy" + }, + "orgUnitName": "test-orgunit-2", + "enrolledAt": "2021-02-28T12:05:00.000", + "occurredAt": "2021-02-28T12:05:00.000", + "scheduledAt": "2021-02-28T12:05:00.000", + "followUp": false, + "deleted": false, + "events": [], + "relationships": [], + "attributes": [], + "notes": [] + }, + { + "enrollment": "ipBifypAQTo", + "createdAtClient": "2017-01-26T13:48:13.363", + "trackedEntity": "guVNoAerxWo", + "program": { + "idScheme": "UID", + "identifier": "UWRnoyBjvqi" + }, + "status": "ACTIVE", + "orgUnit": { + "idScheme": "UID", + "identifier": "tSsGrtfRzjY" + }, + "orgUnitName": "test-orgunit-3", + "enrolledAt": "2021-02-28T12:05:00.000", + "occurredAt": "2021-02-28T12:05:00.000", + "scheduledAt": "2021-02-28T12:05:00.000", + "followUp": false, + "deleted": false, + "events": [], + "relationships": [], + "attributes": [], + "notes": [] + }, + { + "enrollment": "qxOSXoEZkOA", + "createdAtClient": "2018-01-26T13:48:13.363", + "trackedEntity": "woitxQbWYNq", + "program": { + "idScheme": "UID", + "identifier": "YlUmbgnKWkd" + }, + "status": "ACTIVE", + "orgUnit": { + "idScheme": "UID", + "identifier": "RojfDTBhoGC" + }, + "orgUnitName": "test-orgunit-3", + "enrolledAt": "2021-02-28T12:05:00.000", + "occurredAt": "2021-02-28T12:05:00.000", + "scheduledAt": "2021-02-28T12:05:00.000", + "followUp": false, + "deleted": false, + "events": [], + "relationships": [], + "attributes": [], + "notes": [] + }, + { + "enrollment": "HDWTYSYkICe", + "createdAtClient": "2017-01-26T13:48:13.363", + "trackedEntity": "woitxQbWYNq", + "program": { + "idScheme": "UID", + "identifier": "SeeUNWLQmZk" + }, + "status": "ACTIVE", + "orgUnit": { + "idScheme": "UID", + "identifier": "DiszpKrYNg8" + }, + "orgUnitName": "test-orgunit-3", + "enrolledAt": "2021-02-28T12:05:00.000", + "occurredAt": "2021-02-28T12:05:00.000", + "scheduledAt": "2021-02-28T12:05:00.000", + "followUp": false, + "deleted": false, + "events": [], + "relationships": [], + "attributes": [], + "notes": [] + }, + { + "enrollment": "FXWSSZunTLk", + "createdAtClient": "2017-01-26T13:48:13.363", + "trackedEntity": "woitxQbWYNq", + "program": { + "idScheme": "UID", + "identifier": "sLngICFQjvH" + }, + "status": "ACTIVE", + "orgUnit": { + "idScheme": "UID", + "identifier": "lbDXJBlvtZe" + }, + "orgUnitName": "test-orgunit-3", + "enrolledAt": "2021-02-28T12:05:00.000", + "occurredAt": "2021-02-28T12:05:00.000", + "scheduledAt": "2021-02-28T12:05:00.000", + "followUp": false, + "deleted": false, + "events": [], + "relationships": [], + "attributes": [], + "notes": [] + }, + { + "enrollment": "GYWSSZunTLk", + "createdAtClient": "2017-01-26T13:48:13.363", + "trackedEntity": "XUitxQbWYNq", + "program": { + "idScheme": "UID", + "identifier": "TsngICFQjvH" + }, + "status": "ACTIVE", + "orgUnit": { + "idScheme": "UID", + "identifier": "DiszpKrYNg8" + }, + "orgUnitName": "test-orgunit-3", + "enrolledAt": "2021-02-28T12:05:00.000", + "occurredAt": "2021-02-28T12:05:00.000", + "scheduledAt": "2021-02-28T12:05:00.000", + "followUp": false, + "deleted": false, + "events": [], + "relationships": [], + "attributes": [], + "notes": [] + } + ], + "events": [ + { + "event": "pTzf9KYMk72", + "status": "COMPLETED", + "program": { + "idScheme": "UID", + "identifier": "BFcipDERJnf" + }, + "programStage": { + "idScheme": "UID", + "identifier": "NpsdDv6kKSO" + }, + "enrollment": "nxP7UnKhomJ", + "orgUnit": { + "idScheme": "UID", + "identifier": "h4w96yEMlzO" + }, + "relationships": [], + "occurredAt": "2019-01-25T12:10:38.100", + "scheduledAt": "2019-01-28T12:32:38.100", + "createdAtClient": "2020-01-25T12:10:38.100", + "updatedAtClient": "2020-01-26T12:10:38.100", + "storedBy": "admin", + "followUp": true, + "deleted": false, + "attributeOptionCombo": { + "idScheme": "UID", + "identifier": "HllvX50cXC0" + }, + "attributeCategoryOptions": [ + { + "idScheme": "UID", + "identifier": "xYerKDKCefk" + } + ], + "completedAt": "2020-01-28T00:00:00.000", + "dataValues": [ + { + "dataElement": { + "idScheme": "UID", + "identifier": "DATAEL00001" + }, + "value": "value00001", + "createdAt": "2021-07-01T12:05:00", + "storedBy": null, + "providedElsewhere": false + }, + { + "dataElement": { + "idScheme": "UID", + "identifier": "DATAEL00005" + }, + "value": "option1", + "createdAt": "2021-07-01T12:05:00", + "storedBy": null, + "providedElsewhere": false + }, + { + "dataElement": { + "idScheme": "UID", + "identifier": "DATAEL00006" + }, + "value": "88", + "createdAt": "2021-07-01T12:05:00", + "storedBy": null, + "providedElsewhere": false + } + ], + "notes": [ + { + "note": "SGuCABkhpgn", + "value": "comment value", + "storedBy": "admin" + }, + { + "note": "DRKO4xUVrpr", + "value": "comment value", + "storedBy": "admin" + } + ] + }, + { + "event": "D9PbzJY8bJM", + "status": "COMPLETED", + "program": { + "idScheme": "UID", + "identifier": "BFcipDERJnf" + }, + "programStage": { + "idScheme": "UID", + "identifier": "NpsdDv6kKSO" + }, + "enrollment": "TvctPPhpD8z", + "orgUnit": { + "idScheme": "UID", + "identifier": "h4w96yEMlzO" + }, + "relationships": [], + "occurredAt": "2020-01-28T00:00:00.000", + "scheduledAt": "2019-01-28T12:10:38.100", + "createdAtClient": "2019-01-25T12:10:38.100", + "updatedAtClient": "2021-01-26T12:10:38.100", + "storedBy": "admin", + "followUp": true, + "deleted": false, + "attributeOptionCombo": { + "idScheme": "UID", + "identifier": "HllvX50cXC0" + }, + "attributeCategoryOptions": [ + { + "idScheme": "UID", + "identifier": "xYerKDKCefk" + } + ], + "dataValues": [ + { + "dataElement": { + "idScheme": "UID", + "identifier": "DATAEL00001" + }, + "value": "value00002", + "created": "2021-07-01T12:05:00", + "storedBy": null, + "providedElsewhere": false + }, + { + "dataElement": { + "idScheme": "UID", + "identifier": "DATAEL00002" + }, + "value": "value00002", + "created": "2021-07-01T12:05:00", + "storedBy": null, + "providedElsewhere": false + }, + { + "dataElement": { + "idScheme": "UID", + "identifier": "DATAEL00005" + }, + "value": "option2", + "created": "2021-07-01T12:05:00", + "storedBy": null, + "providedElsewhere": false + }, + { + "dataElement": { + "idScheme": "UID", + "identifier": "DATAEL00006" + }, + "value": "70", + "created": "2021-07-01T12:05:00", + "storedBy": null, + "providedElsewhere": false + }, + { + "dataElement": { + "idScheme": "UID", + "identifier": "DATAEL00007" + }, + "value": "70", + "created": "2021-07-01T12:05:00", + "storedBy": null, + "providedElsewhere": false + } + ], + "notes": [], + "assignedUser": { + "uid": "xE7jOejl9FI", + "firstName": "John", + "surname": "Traore", + "username": "admin" + } + }, + { + "event": "jxgFyJEMUPf", + "status": "COMPLETED", + "program": { + "idScheme": "UID", + "identifier": "shPjYNifvMK" + }, + "programStage": { + "idScheme": "UID", + "identifier": "SKNvpoLioON" + }, + "enrollment": "JuioKiICQqI", + "orgUnit": { + "idScheme": "UID", + "identifier": "uoNW0E3xXUy" + }, + "relationships": [], + "occurredAt": "2019-01-28T00:00:00.000", + "scheduledAt": "2019-01-28T12:10:38.100", + "storedBy": "admin", + "followUp": true, + "deleted": false, + "attributeOptionCombo": { + "idScheme": "UID", + "identifier": "HllvX50cXC0" + }, + "attributeCategoryOptions": [ + { + "idScheme": "UID", + "identifier": "xYerKDKCefk" + } + ], + "notes": [] + }, + { + "event": "JaRDIvcEcEx", + "status": "COMPLETED", + "program": { + "idScheme": "UID", + "identifier": "pcxIanBWlSY" + }, + "programStage": { + "idScheme": "UID", + "identifier": "ebGXHEqqEMF" + }, + "enrollment": "iHFHfPKTSYP", + "orgUnit": { + "idScheme": "UID", + "identifier": "uoNW0E3xXUy" + }, + "relationships": [], + "occurredAt": "2019-01-28T00:00:00.000", + "scheduledAt": "2019-01-28T12:10:38.100", + "storedBy": "admin", + "followUp": true, + "deleted": false, + "attributeOptionCombo": { + "idScheme": "UID", + "identifier": "HllvX50cXC0" + }, + "attributeCategoryOptions": [ + { + "idScheme": "UID", + "identifier": "xYerKDKCefk" + } + ], + "notes": [] + }, + { + "event": "QRYjLTiJTrA", + "occurredAt": "2022-04-20T06:00:38.343", + "attributeOptionCombo": { + "idScheme": "UID", + "identifier": "SeWJkpLAyLt" + }, + "attributeCategoryOptions": [ + { + "idScheme": "UID", + "identifier": "xwZ2u3WyQR0" + }, + { + "idScheme": "UID", + "identifier": "i4Nbp8S2G6A" + } + ], + "storedBy": "admin", + "scheduledAt": "2022-04-22T06:00:38.343", + "program": { + "idScheme": "UID", + "identifier": "iS7eutanDry" + }, + "programStage": { + "idScheme": "UID", + "identifier": "qLZC0lvvxQH" + }, + "programType": "WITHOUT_REGISTRATION", + "orgUnit": { + "idScheme": "UID", + "identifier": "DiszpKrYNg8" + }, + "status": "ACTIVE", + "deleted": false, + "dataValues": [ + { + "created": "2022-04-22T06:00:38.339", + "dataElement": { + "idScheme": "UID", + "identifier": "GieVkTxp4HH" + }, + "value": "15", + "providedElsewhere": false + }, + { + "dataElement": { + "idScheme": "UID", + "identifier": "GieVkTxp4HG" + }, + "value": "1.5", + "providedElsewhere": false + } + ], + "notes": [], + "relationships": [] + }, + { + "event": "kWjSezkXHVp", + "occurredAt": "2022-04-22T06:00:38.343", + "attributeOptionCombo": { + "idScheme": "UID", + "identifier": "cr89ebDZrac" + }, + "attributeCategoryOptions": [ + { + "idScheme": "UID", + "identifier": "xwZ2u3WyQR0" + }, + { + "idScheme": "UID", + "identifier": "M58XdOfhiJ7" + } + ], + "storedBy": "admin", + "scheduledAt": "2022-04-26T06:00:34.323", + "program": { + "idScheme": "UID", + "identifier": "iS7eutanDry" + }, + "programStage": { + "idScheme": "UID", + "identifier": "qLZC0lvvxQH" + }, + "programType": "WITHOUT_REGISTRATION", + "orgUnit": { + "idScheme": "UID", + "identifier": "DiszpKrYNg8" + }, + "status": "ACTIVE", + "deleted": false, + "dataValues": [ + { + "created": "2022-04-22T06:00:34.319", + "dataElement": { + "idScheme": "UID", + "identifier": "GieVkTxp4HH" + }, + "value": "14", + "providedElsewhere": false + } + ], + "notes": [], + "relationships": [] + }, + { + "event": "OTmjvJDn0Fu", + "occurredAt": "2022-04-23T06:00:38.343", + "attributeOptionCombo": { + "idScheme": "UID", + "identifier": "cr89ebDZrac" + }, + "attributeCategoryOptions": [ + { + "idScheme": "UID", + "identifier": "xwZ2u3WyQR0" + }, + { + "idScheme": "UID", + "identifier": "M58XdOfhiJ7" + } + ], + "storedBy": "admin", + "scheduledAt": "2022-04-22T06:00:30.562", + "program": { + "idScheme": "UID", + "identifier": "iS7eutanDry" + }, + "programStage": { + "idScheme": "UID", + "identifier": "qLZC0lvvxQH" + }, + "programType": "WITHOUT_REGISTRATION", + "orgUnit": { + "idScheme": "UID", + "identifier": "DiszpKrYNg8" + }, + "status": "ACTIVE", + "deleted": false, + "dataValues": [ + { + "created": "2022-04-22T06:00:30.559", + "dataElement": { + "idScheme": "UID", + "identifier": "GieVkTxp4HH" + }, + "value": "13", + "providedElsewhere": false + } + ], + "notes": [], + "relationships": [] + }, + { + "event": "ck7DzdxqLqA", + "occurredAt": "2022-04-24T06:00:38.343", + "attributeOptionCombo": { + "idScheme": "UID", + "identifier": "tzsPhPtE94U" + }, + "attributeCategoryOptions": [ + { + "idScheme": "UID", + "identifier": "xEunk8LPzkb" + }, + { + "idScheme": "UID", + "identifier": "M58XdOfhiJ7" + } + ], + "storedBy": "admin", + "scheduledAt": "2022-04-22T06:00:14.228", + "program": { + "idScheme": "UID", + "identifier": "iS7eutanDry" + }, + "programStage": { + "idScheme": "UID", + "identifier": "qLZC0lvvxQH" + }, + "programType": "WITHOUT_REGISTRATION", + "orgUnit": { + "idScheme": "UID", + "identifier": "DiszpKrYNg8" + }, + "status": "ACTIVE", + "deleted": false, + "dataValues": [ + { + "created": "2022-04-22T06:00:14.224", + "dataElement": { + "idScheme": "UID", + "identifier": "GieVkTxp4HH" + }, + "value": "12", + "providedElsewhere": false + } + ], + "notes": [], + "relationships": [] + }, + { + "event": "lumVtWwwy0O", + "occurredAt": "2022-04-21T06:00:38.343", + "attributeOptionCombo": { + "idScheme": "UID", + "identifier": "AeOORUC0ISH" + }, + "storedBy": "admin", + "scheduledAt": "2022-04-22T06:00:14.228", + "program": { + "idScheme": "UID", + "identifier": "iS7eutanDry" + }, + "programStage": { + "idScheme": "UID", + "identifier": "qLZC0lvvxQH" + }, + "programType": "WITHOUT_REGISTRATION", + "orgUnit": { + "idScheme": "UID", + "identifier": "DiszpKrYNg8" + }, + "status": "ACTIVE", + "dataValues": [ + { + "created": "2022-04-22T06:00:14.224", + "dataElement": { + "idScheme": "UID", + "identifier": "GieVkTxp4HH" + }, + "value": "12" + } + ] + }, + { + "event": "cadc5eGj0j7", + "occurredAt": "2022-04-20T03:00:38.343", + "attributeOptionCombo": { + "idScheme": "UID", + "identifier": "V2gbpszLQJm" + }, + "storedBy": "admin", + "scheduledAt": "2022-04-22T06:00:14.228", + "program": { + "idScheme": "UID", + "identifier": "iS7eutanDry" + }, + "programStage": { + "idScheme": "UID", + "identifier": "qLZC0lvvxQH" + }, + "programType": "WITHOUT_REGISTRATION", + "orgUnit": { + "idScheme": "UID", + "identifier": "DiszpKrYNg8" + }, + "status": "ACTIVE", + "dataValues": [ + { + "created": "2022-04-22T06:00:14.224", + "dataElement": { + "idScheme": "UID", + "identifier": "GieVkTxp4HH" + }, + "value": "12" + } + ] + }, + { + "event": "gvULMgNiAfM", + "status": "COMPLETED", + "program": { + "idScheme": "UID", + "identifier": "UWRnoyBjvqi" + }, + "programStage": { + "idScheme": "UID", + "identifier": "ZfzckZBvDkA" + }, + "enrollment": "ipBifypAQTo", + "orgUnit": { + "idScheme": "UID", + "identifier": "tSsGrtfRzjY" + }, + "relationships": [], + "occurredAt": "2019-01-28T00:00:00.000", + "scheduledAt": "2019-01-28T12:10:38.100", + "storedBy": "admin", + "followUp": true, + "deleted": false, + "attributeOptionCombo": { + "idScheme": "UID", + "identifier": "HllvX50cXC0" + }, + "notes": [] + }, + { + "event": "SbUJzkxKYAG", + "status": "ACTIVE", + "program": { + "idScheme": "UID", + "identifier": "YlUmbgnKWkd" + }, + "programStage": { + "idScheme": "UID", + "identifier": "uLQthmAPTPq" + }, + "enrollment": "qxOSXoEZkOA", + "orgUnit": { + "idScheme": "UID", + "identifier": "RojfDTBhoGC" + }, + "relationships": [], + "occurredAt": "2019-01-28T00:00:00.000", + "scheduledAt": "2019-01-28T11:10:38.100", + "storedBy": "tracker", + "followUp": true, + "deleted": false, + "attributeOptionCombo": { + "idScheme": "UID", + "identifier": "HllvX50cXC0" + } + }, + { + "event": "LCSfHnurnNB", + "status": "COMPLETED", + "program": { + "idScheme": "UID", + "identifier": "SeeUNWLQmZk" + }, + "programStage": { + "idScheme": "UID", + "identifier": "GmxBvezOlGA" + }, + "enrollment": "HDWTYSYkICe", + "orgUnit": { + "idScheme": "UID", + "identifier": "DiszpKrYNg8" + }, + "relationships": [], + "occurredAt": "2019-01-28T00:00:00.000", + "scheduledAt": "2019-01-28T12:10:38.100", + "storedBy": "admin", + "followUp": true, + "deleted": false, + "attributeOptionCombo": { + "idScheme": "UID", + "identifier": "HllvX50cXC0" + }, + "notes": [] + }, + { + "event": "YKmfzHdjUDL", + "status": "COMPLETED", + "program": { + "idScheme": "UID", + "identifier": "sLngICFQjvH" + }, + "programStage": { + "idScheme": "UID", + "identifier": "zydGjigJcJb" + }, + "enrollment": "FXWSSZunTLk", + "orgUnit": { + "idScheme": "UID", + "identifier": "lbDXJBlvtZe" + }, + "relationships": [], + "occurredAt": "2019-01-28T00:00:00.000", + "scheduledAt": "2019-01-28T12:10:38.100", + "storedBy": "admin", + "followUp": true, + "deleted": false, + "attributeOptionCombo": { + "idScheme": "UID", + "identifier": "HllvX50cXC0" + }, + "notes": [] + }, + { + "event": "G9PbzJY8bJG", + "status": "COMPLETED", + "program": { + "idScheme": "UID", + "identifier": "BFcipDERJng" + }, + "programStage": { + "idScheme": "UID", + "identifier": "NpsdDv6kKSg" + }, + "orgUnit": { + "idScheme": "UID", + "identifier": "g4w96yEMlzO" + }, + "relationships": [], + "occurredAt": "2019-01-28T00:00:00.000", + "scheduledAt": "2019-01-28T12:10:38.100", + "storedBy": "admin", + "followUp": true, + "deleted": false, + "attributeOptionCombo": { + "idScheme": "UID", + "identifier": "HllvX50cXC0" + }, + "notes": [] + }, + { + "event": "H0PbzJY8bJG", + "status": "COMPLETED", + "program": { + "idScheme": "UID", + "identifier": "TsngICFQjvH" + }, + "programStage": { + "idScheme": "UID", + "identifier": "aZdGjigJcJb" + }, + "enrollment": "GYWSSZunTLk", + "orgUnit": { + "idScheme": "UID", + "identifier": "DiszpKrYNg8" + }, + "relationships": [], + "occurredAt": "2019-01-28T00:00:00.000", + "scheduledAt": "2019-01-28T12:10:38.100", + "storedBy": "admin", + "followUp": true, + "deleted": false, + "attributeOptionCombo": { + "idScheme": "UID", + "identifier": "HllvX50cXC0" + }, + "notes": [] + } + ], + "relationships": [ + { + "relationship": "oLT07jKRu9e", + "relationshipType": { + "idScheme": "UID", + "identifier": "TV9oB9LT3sh" + }, + "createdAtClient": "2018-10-01T12:17:30.163", + "bidirectional": false, + "deleted": false, + "from": { + "trackedEntity": "QS6w44flWAf" + }, + "to": { + "event": "pTzf9KYMk72" + } + }, + { + "relationship": "yZxjxJli9mO", + "relationshipType": { + "idScheme": "UID", + "identifier": "TV9oB9LT3sh" + }, + "createdAtClient": "2018-11-01T13:24:37.118", + "bidirectional": false, + "deleted": false, + "from": { + "trackedEntity": "dUE514NMOlo" + }, + "to": { + "event": "pTzf9KYMk72" + } + } + ], + "username": "system-process" +} diff --git a/dhis-2/dhis-test-web-api/src/test/resources/tracker/import_invalid_uid_payload.json b/dhis-2/dhis-test-web-api/src/test/resources/tracker/import_invalid_uid_payload.json new file mode 100644 index 000000000000..7721d9e7ba33 --- /dev/null +++ b/dhis-2/dhis-test-web-api/src/test/resources/tracker/import_invalid_uid_payload.json @@ -0,0 +1,9 @@ +{ + "trackedEntities": [ + { + "trackedEntity": "invalid_uid", + "trackedEntityType": "PrZMWi7rBga", + "orgUnit": "PSeMWi7rBgb" + } + ] +} \ No newline at end of file diff --git a/dhis-2/dhis-test-web-api/src/test/resources/tracker/invalid_uid_event.csv b/dhis-2/dhis-test-web-api/src/test/resources/tracker/invalid_uid_event.csv new file mode 100644 index 000000000000..c451ec6930cc --- /dev/null +++ b/dhis-2/dhis-test-web-api/src/test/resources/tracker/invalid_uid_event.csv @@ -0,0 +1 @@ +invalid_uid,COMPLETED,programId,programStageId,PSeMWi7rBgb,orgUnitId,2020-02-26T23:01:00Z,2020-02-26T23:02:00Z,,,,false,false,2020-02-26T23:03:00Z,2020-02-26T23:04:00Z,2020-02-26T23:05:00Z,2020-02-26T23:06:00Z,admin,2020-02-26T23:07:00Z,system,attributeOptionCombo,attributeCategoryOptions,assignedUser,dataElement,value,admin,false,admin,2020-02-26T23:08:00Z,2020-02-26T23:09:00Z \ No newline at end of file diff --git a/dhis-2/dhis-test-web-api/src/test/resources/tracker/simple_metadata.json b/dhis-2/dhis-test-web-api/src/test/resources/tracker/simple_metadata.json deleted file mode 100644 index 5a1ae624cc95..000000000000 --- a/dhis-2/dhis-test-web-api/src/test/resources/tracker/simple_metadata.json +++ /dev/null @@ -1,216 +0,0 @@ -{ - "attributes": [ - { - "id": "j45AR9cBQKc", - "name": "some attribute", - "sharing": { - "owner": "PQD6wXJ2r5j", - "public": "rw------" - }, - "valueType": "TEXT", - "mandatory": false, - "unique": true, - "programAttribute": true, - "programStageAttribute": true, - "trackedEntityTypeAttribute": true, - "trackedEntityAttributeAttribute": true, - "organisationUnitAttribute": true, - "dataElementAttribute": true, - "relationshipTypeAttribute": true, - "categoryOptionAttribute": true, - "categoryOptionComboAttribute": true - } - ], - "trackedEntityTypes": [ - { - "name": "Person", - "id": "ja8NY4PW7Xm", - "publicAccess": "rw------", - "description": "person", - "maxTeiCountToReturn": 0, - "allowAuditLog": true, - "featureType": "NONE", - "minAttributesRequiredToSearch": 1, - "user": { - "id": "tTgjgobT1oS" - }, - "sharing": { - "external": false, - "public": "rwrw----" - }, - "trackedEntityTypeAttributes": [ - { - "displayInList": true, - "displayName": "Person Given name", - "displayShortName": "null Given name", - "externalAccess": false, - "favorite": false, - "favorites": [], - "id": "hKZ9AJpnVcG", - "name": "Person Given name", - "searchable": true, - "trackedEntityAttribute": { - "id": "numericAttr" - }, - "trackedEntityType": { - "id": "ja8NY4PW7Xm" - }, - "valueType": "TEXT" - } - ] - }, - { - "name": "Inaccessible person", - "id": "Ip8NY4PW7Xm", - "publicAccess": "rw------", - "description": "person", - "maxTeiCountToReturn": 0, - "allowAuditLog": false, - "featureType": "NONE", - "minAttributesRequiredToSearch": 1, - "user": { - "id": "tTgjgobT1oS" - }, - "sharing": { - "external": false, - "public": "--------" - } - } - ], - "users": [ - { - "code": "tracker admin", - "id": "tTgjgobT1oS", - "password": "Test123###...", - "surname": "tracker Surnameadmin", - "firstName": "tracker FirstNameadmin", - "name": "tracker admin", - "lastLogin": "2020-05-31T08:55:38.571", - "displayName": "tracker admin", - "externalAuth": false, - "externalAccess": false, - "disabled": false, - "twoFA": false, - "passwordLastUpdated": "2020-05-31T08:55:28.232", - "invitation": false, - "selfRegistered": false, - "favorite": false, - "username": "trackeradmin", - "access": { - "read": true, - "update": true, - "externalize": true, - "delete": true, - "write": true, - "manage": true - }, - "userRoles": [ - { - "id": "nJ4Ml8ads4M" - } - ], - "teiSearchOrganisationUnits": [ - { - "id": "h4w96yEMlzO" - } - ], - "organisationUnits": [ - { - "id": "h4w96yEMlzO" - } - ] - } - ], - "organisationUnits": [ - { - "id": "h4w96yEMlzO", - "name": "test-orgunit", - "code": "test-orgunit-code", - "level": 1, - "publicAccess": "rwrw----", - "shortName": "test-orgunit", - "description": "test-orgunit", - "path": "/h4w96yEMlzO", - "openingDate": "2020-05-31T00:00:00.000", - "user": { - "id": "tTgjgobT1oS" - } - } - ], - "programs": [ - { - "id": "BFcipDERJnf", - "name": "test-program", - "shortName": "test-program", - "publicAccess": "rwrw----", - "completeEventsExpiryDays": 0, - "ignoreOverdueEvents": false, - "skipOffline": false, - "minAttributesRequiredToSearch": 1, - "displayFrontPageList": false, - "onlyEnrollOnce": false, - "programType": "WITH_REGISTRATION", - "accessLevel": "OPEN", - "version": 2, - "maxTeiCountToReturn": 0, - "selectIncidentDatesInFuture": false, - "displayIncidentDate": true, - "selectEnrollmentDatesInFuture": false, - "expiryDays": 0, - "useFirstStageDuringRegistration": false, - "categoryCombo": { - "id": "bjDvmb4bfuf" - }, - "trackedEntityType": { - "id": "ja8NY4PW7Xm" - }, - "user": { - "id": "tTgjgobT1oS" - }, - "organisationUnits": [ - { - "id": "h4w96yEMlzO" - } - ] - } - ], - "userRoles": [ - { - "code": "tracker Superuser", - "name": "tracker Superuser", - "id": "nJ4Ml8ads4M", - "publicAccess": "--------", - "description": "tracker Superuser", - "user": { - "id": "tTgjgobT1oS" - }, - "authorities": [ - "F_TRACKED_ENTITY_INSTANCE_SEARCH_IN_ALL_ORGUNITS", - "ALL" - ] - } - ], - "trackedEntityAttributes": [ - { - "id": "numericAttr", - "name": "numeric-attribute", - "shortName": "numeric-attribute", - "aggregationType": "NONE", - "displayInListNoProgram": false, - "publicAccess": "rw------", - "pattern": "", - "skipSynchronization": false, - "generated": false, - "displayOnVisitSchedule": false, - "valueType": "INTEGER", - "formName": "numeric-attribute", - "orgunitScope": false, - "confidential": false, - "unique": false, - "inherit": false, - "user": { - "id": "tTgjgobT1oS" - } - } - ] -} diff --git a/dhis-2/dhis-web-api/pom.xml b/dhis-2/dhis-web-api/pom.xml index d2544d095b72..508037ba2f08 100644 --- a/dhis-2/dhis-web-api/pom.xml +++ b/dhis-2/dhis-web-api/pom.xml @@ -291,10 +291,6 @@ org.locationtech.jts jts-core - - org.cache2k - cache2k-api - com.lowagie itext diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AbstractCrudController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AbstractCrudController.java index 6729114fd393..307f00565f57 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AbstractCrudController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AbstractCrudController.java @@ -83,6 +83,7 @@ import org.hisp.dhis.jsonpatch.BulkPatchParameters; import org.hisp.dhis.jsonpatch.JsonPatchManager; import org.hisp.dhis.jsonpatch.validator.BulkPatchValidatorFactory; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.render.RenderService; import org.hisp.dhis.schema.MetadataMergeService; import org.hisp.dhis.schema.validation.SchemaValidator; @@ -117,8 +118,9 @@ @Stable @ApiVersion({DhisApiVersion.DEFAULT, DhisApiVersion.ALL}) @OpenApi.Document(group = OpenApi.Document.GROUP_MANAGE) -public abstract class AbstractCrudController - extends AbstractFullReadOnlyController { +public abstract class AbstractCrudController< + T extends IdentifiableObject, P extends GetObjectListParams> + extends AbstractFullReadOnlyController { @Autowired protected SchemaValidator schemaValidator; @Autowired protected RenderService renderService; @@ -178,9 +180,7 @@ public WebMessage patchObject( IOException, JsonPatchException, ConflictException { - WebOptions options = new WebOptions(rpParameters); - - final T persistedObject = getEntity(pvUid, options); + final T persistedObject = getEntity(pvUid); if (!aclService.canUpdate(currentUser, persistedObject)) { throw new ForbiddenException("You don't have the proper permissions to update this object."); @@ -454,13 +454,10 @@ public WebMessage putJsonObject( @ResponseBody public WebMessage replaceTranslations( @OpenApi.Param(UID.class) @PathVariable("uid") String pvUid, - @RequestParam Map rpParameters, @CurrentUser UserDetails currentUser, HttpServletRequest request) throws NotFoundException, ForbiddenException, IOException { - WebOptions options = new WebOptions(rpParameters); - - BaseIdentifiableObject persistedObject = (BaseIdentifiableObject) getEntity(pvUid, options); + BaseIdentifiableObject persistedObject = (BaseIdentifiableObject) getEntity(pvUid); if (!aclService.canUpdate(currentUser, persistedObject)) { throw new ForbiddenException("You don't have the proper permissions to update this object."); diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AbstractFullReadOnlyController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AbstractFullReadOnlyController.java index 670851025140..2034dcf490dd 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AbstractFullReadOnlyController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AbstractFullReadOnlyController.java @@ -27,7 +27,6 @@ */ package org.hisp.dhis.webapi.controller; -import static java.util.stream.Collectors.toList; import static org.springframework.http.CacheControl.noCache; import com.fasterxml.jackson.databind.SequenceWriter; @@ -37,17 +36,16 @@ import com.fasterxml.jackson.dataformat.csv.CsvSchema.Builder; import com.fasterxml.jackson.dataformat.csv.CsvWriteException; import com.google.common.base.Joiner; -import com.google.common.collect.Lists; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; import javax.annotation.CheckForNull; @@ -64,20 +62,19 @@ import org.hisp.dhis.common.Pager; import org.hisp.dhis.common.PrimaryKeyObject; import org.hisp.dhis.common.UID; -import org.hisp.dhis.dxf2.common.OrderParams; import org.hisp.dhis.feedback.BadRequestException; import org.hisp.dhis.feedback.ConflictException; import org.hisp.dhis.feedback.ForbiddenException; import org.hisp.dhis.feedback.NotFoundException; -import org.hisp.dhis.fieldfilter.Defaults; import org.hisp.dhis.fieldfilter.FieldFilterService; import org.hisp.dhis.fieldfiltering.FieldFilterParams; -import org.hisp.dhis.fieldfiltering.FieldPreset; -import org.hisp.dhis.query.Order; -import org.hisp.dhis.query.Pagination; +import org.hisp.dhis.query.Criterion; +import org.hisp.dhis.query.GetObjectListParams; +import org.hisp.dhis.query.GetObjectParams; import org.hisp.dhis.query.Query; import org.hisp.dhis.query.QueryParserException; import org.hisp.dhis.query.QueryService; +import org.hisp.dhis.query.Restrictions; import org.hisp.dhis.schema.Property; import org.hisp.dhis.schema.PropertyType; import org.hisp.dhis.schema.Schema; @@ -90,10 +87,7 @@ import org.hisp.dhis.webapi.service.ContextService; import org.hisp.dhis.webapi.service.LinkService; import org.hisp.dhis.webapi.utils.ContextUtils; -import org.hisp.dhis.webapi.utils.PaginationUtils; import org.hisp.dhis.webapi.webdomain.StreamingJsonRoot; -import org.hisp.dhis.webapi.webdomain.WebMetadata; -import org.hisp.dhis.webapi.webdomain.WebOptions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -111,11 +105,9 @@ @Maturity.Stable @ApiVersion({DhisApiVersion.DEFAULT, DhisApiVersion.ALL}) @OpenApi.Document(group = OpenApi.Document.GROUP_QUERY) -public abstract class AbstractFullReadOnlyController +public abstract class AbstractFullReadOnlyController< + T extends IdentifiableObject, P extends GetObjectListParams> extends AbstractGistReadOnlyController { - protected static final String DEFAULTS = "INCLUDE"; - - protected static final WebOptions NO_WEB_OPTIONS = new WebOptions(new HashMap<>()); @Autowired protected IdentifiableObjectManager manager; @@ -145,21 +137,22 @@ public abstract class AbstractFullReadOnlyController entityList, WebOptions options, Map parameters) {} + protected void postProcessResponseEntities(List entityList, P params) {} /** * Override to process a single entity after it has been retrieved from storage and before it is * returned to the view. Entity is null-safe. */ - protected void postProcessResponseEntity( - T entity, WebOptions options, Map parameters) {} + protected void postProcessResponseEntity(T entity, GetObjectParams params) {} /** - * Allows to append new filters to the incoming ones. Recommended only on very specific cases - * where forcing a new filter, programmatically, make sense. + * Allows to append further filters to the incoming ones. Recommended only on very specific cases + * where forcing a new filter, programmatically, make sense. This is usually used to ensure that + * some filters are always present. */ - protected void forceFiltering(final WebOptions webOptions, final List filters) {} + protected void addProgrammaticModifiers(P params) {} + + protected void addProgrammaticFilters(Consumer add) {} // -------------------------------------------------------------------------- // GET Full @@ -174,88 +167,93 @@ protected static class ObjectListResponse { List entries; } - @OpenApi.Param(name = "fields", value = String[].class) - @OpenApi.Param(name = "filter", value = String[].class) - @OpenApi.Params(WebOptions.class) @OpenApi.Response(ObjectListResponse.class) @GetMapping public @ResponseBody ResponseEntity> getObjectList( - @RequestParam Map rpParameters, - OrderParams orderParams, + P params, HttpServletResponse response, @CurrentUser UserDetails currentUser) + throws ForbiddenException, BadRequestException, ConflictException { + return getObjectListInternal(params, response, currentUser, getAdditionalFilters(params)); + } + + protected final ResponseEntity> getObjectListWith( + P params, HttpServletResponse response, - @CurrentUser UserDetails currentUser) - throws ForbiddenException, BadRequestException { - return getObjectList( - rpParameters, orderParams, response, currentUser, !rpParameters.containsKey("query"), null); + UserDetails currentUser, + List additionalFilters) + throws ForbiddenException, BadRequestException, ConflictException { + List filters = getAdditionalFilters(params); + filters.addAll(additionalFilters); + return getObjectListInternal(params, response, currentUser, filters); } - protected final ResponseEntity> getObjectList( - @RequestParam Map rpParameters, - OrderParams orderParams, + protected final ResponseEntity> getObjectListInternal( + P params, HttpServletResponse response, - UserDetails userDetails, - boolean countTotal, - @CheckForNull List objects) + UserDetails currentUser, + List additionalFilters) throws ForbiddenException, BadRequestException { - List orders = orderParams.getOrders(getSchema()); - List fields = Lists.newArrayList(contextService.getParameterValues("fields")); - List filters = Lists.newArrayList(contextService.getParameterValues("filter")); - - if (fields.isEmpty()) { - fields.addAll(FieldPreset.defaultPreset().getFields()); - } - - WebOptions options = new WebOptions(rpParameters); - WebMetadata metadata = new WebMetadata(); - if (!aclService.canRead(userDetails, getEntityClass())) { + if (!aclService.canRead(currentUser, getEntityClass())) { throw new ForbiddenException( "You don't have the proper permissions to read objects of this type."); } - forceFiltering(options, filters); - - List entities = getEntityList(metadata, options, filters, orders, objects); - - Pager pager = metadata.getPager(); - - if (options.hasPaging() && pager == null) { - long totalCount; + addProgrammaticModifiers(params); - if (!countTotal) { - totalCount = entities.size(); + boolean isAlwaysEmpty = additionalFilters.stream().anyMatch(Criterion::isAlwaysFalse); + List entities = isAlwaysEmpty ? List.of() : getEntityList(params, additionalFilters); + postProcessResponseEntities(entities, params); - long skip = (long) (options.getPage() - 1) * options.getPageSize(); - entities = entities.stream().skip(skip).limit(options.getPageSize()).collect(toList()); - } else { - totalCount = countTotal(options, filters, orders); - } + List fields = params.getFieldsJsonList(); + handleLinksAndAccess(entities, fields, false); - pager = new Pager(options.getPage(), totalCount, options.getPageSize()); + Pager pager = null; + if (params.isPaging()) { + long totalCount = isAlwaysEmpty ? 0 : countGetObjectList(params, additionalFilters); + pager = new Pager(params.getPage(), totalCount, params.getPageSize()); + linkService.generatePagerLinks(pager, getEntityClass()); } - postProcessResponseEntities(entities, options, rpParameters); - - handleLinksAndAccess(entities, fields, false); - linkService.generatePagerLinks(pager, getEntityClass()); - cachePrivate(response); - return ResponseEntity.ok( new StreamingJsonRoot<>( pager, getSchema().getCollectionName(), FieldFilterParams.of(entities, fields), - Defaults.valueOf(options.get("defaults", DEFAULTS)).isExclude())); + params.getDefaults().isExclude())); + } + + /** + * A way to incorporate additional filters to the {@link #getObjectList(GetObjectListParams, + * HttpServletResponse, UserDetails)} endpoint that require running a separate query resulting in + * matching ID list which then is used as filter in the standard query process. + * + * @param params options used + * @return the ID of matches, nor null when no such filter is present/used + */ + @CheckForNull + protected List getPreQueryMatches(P params) throws ConflictException { + return null; // no special filters used + } + + @Nonnull + protected List getAdditionalFilters(P params) throws ConflictException { + List filters = new ArrayList<>(); + if (params.getQuery() != null && !params.getQuery().isEmpty()) + filters.add(Restrictions.query(getSchema(), params.getQuery())); + List matches = getPreQueryMatches(params); + // Note: null = no special filters, empty = no matches for special filters + if (matches != null) filters.add(createIdInFilter(matches)); + return filters; + } + + protected final Criterion createIdInFilter(@Nonnull List matches) { + return Restrictions.in("id", UID.toValueList(matches)); } - @OpenApi.Param(name = "fields", value = String[].class) - @OpenApi.Param(name = "filter", value = String[].class) - @OpenApi.Params(WebOptions.class) @GetMapping(produces = {"text/csv", "application/text"}) public ResponseEntity getObjectListCsv( - @RequestParam Map rpParameters, - OrderParams orderParams, + P params, @CurrentUser UserDetails currentUser, @RequestParam(defaultValue = ",") char separator, @RequestParam(defaultValue = ";") String arraySeparator, @@ -266,16 +264,6 @@ public ResponseEntity getObjectListCsv( ConflictException, ForbiddenException, BadRequestException { - List orders = orderParams.getOrders(getSchema()); - List fields = Lists.newArrayList(contextService.getParameterValues("fields")); - List filters = Lists.newArrayList(contextService.getParameterValues("filter")); - - WebOptions options = new WebOptions(rpParameters); - WebMetadata metadata = new WebMetadata(); - - if (fields.isEmpty() || fields.contains("*") || fields.contains(":all")) { - fields.addAll(FieldPreset.defaultPreset().getFields()); - } // only support metadata if (!getSchema().isMetadata()) { @@ -288,8 +276,8 @@ public ResponseEntity getObjectListCsv( "You don't have the proper permissions to read objects of this type."); } - List entities = getEntityList(metadata, options, filters, orders, null); - + List entities = getEntityList(params, List.of()); + List fields = params.getFieldsCsvList(); try { String csv = applyCsvSteps(fields, entities, separator, arraySeparator, skipHeader); return ResponseEntity.ok(csv); @@ -400,15 +388,11 @@ private static Object getAttributeValue(Object obj, String attrId) { return null; } - @OpenApi.Param(name = "fields", value = String[].class) - @OpenApi.Param(name = "filter", value = String[].class) - @OpenApi.Params(WebOptions.class) @OpenApi.Response(OpenApi.EntityType.class) @GetMapping("/{uid:[a-zA-Z0-9]{11}}") - @SuppressWarnings("unchecked") public @ResponseBody ResponseEntity getObject( @OpenApi.Param(UID.class) @PathVariable("uid") String pvUid, - @RequestParam Map rpParameters, + GetObjectParams params, @CurrentUser UserDetails currentUser, HttpServletRequest request, HttpServletResponse response) @@ -419,48 +403,35 @@ private static Object getAttributeValue(Object obj, String attrId) { "You don't have the proper permissions to read objects of this type."); } - List fields = Lists.newArrayList(contextService.getParameterValues("fields")); - List filters = Lists.newArrayList(contextService.getParameterValues("filter")); - forceFiltering(new WebOptions(rpParameters), filters); - - if (fields.isEmpty()) { - fields.add("*"); - } - cachePrivate(response); - WebOptions options = new WebOptions(rpParameters); - T entity = getEntity(pvUid, options); + T entity = getEntity(pvUid); - Query query = - queryService.getQueryFromUrl( - getEntityClass(), - filters, - new ArrayList<>(), - getPaginationData(options), - options.getRootJunction()); + GetObjectListParams listParams = params.toListParams(); + addProgrammaticFilters(listParams::addFilter); // temporary workaround + Query query = queryService.getQueryFromUrl(getEntityClass(), listParams); query.setCurrentUserDetails(currentUser); query.setObjects(List.of(entity)); - query.setDefaults(Defaults.valueOf(options.get("defaults", DEFAULTS))); + query.setDefaults(params.getDefaults()); + @SuppressWarnings("unchecked") List entities = (List) queryService.query(query); + List fields = params.getFieldsObject(); handleLinksAndAccess(entities, fields, true); - entities.forEach(e -> postProcessResponseEntity(e, options, rpParameters)); + entities.forEach(e -> postProcessResponseEntity(e, params)); return ResponseEntity.ok( new StreamingJsonRoot<>( null, null, FieldFilterParams.of(entities, fields), query.getDefaults().isExclude())); } - @OpenApi.Param(name = "fields", value = String[].class) - @OpenApi.Params(WebOptions.class) @GetMapping("/{uid:[a-zA-Z0-9]{11}}/{property}") public @ResponseBody ResponseEntity getObjectProperty( @OpenApi.Param(UID.class) @PathVariable("uid") String pvUid, @OpenApi.Param(PropertyNames.class) @PathVariable("property") String pvProperty, - @RequestParam Map rpParameters, + @RequestParam(required = false) List fields, @CurrentUser UserDetails currentUser, HttpServletResponse response) throws ForbiddenException, NotFoundException { @@ -470,54 +441,37 @@ private static Object getAttributeValue(Object obj, String attrId) { "You don't have the proper permissions to read objects of this type."); } - List fields = Lists.newArrayList(contextService.getParameterValues("fields")); - - if (fields.isEmpty()) { - fields.add(":all"); + if (fields == null || fields.isEmpty()) { + fields = List.of(":all"); } String fieldFilter = "[" + Joiner.on(',').join(fields) + "]"; cachePrivate(response); - ObjectNode objectNode = - getObjectInternal( - pvUid, - rpParameters, - Lists.newArrayList(), - Lists.newArrayList(pvProperty + fieldFilter), - currentUser); + GetObjectParams params = new GetObjectParams(); + params.addField(pvProperty + fieldFilter); + ObjectNode objectNode = getObjectInternal(pvUid, params, currentUser); return ResponseEntity.ok(objectNode); } @SuppressWarnings("unchecked") - private ObjectNode getObjectInternal( - String uid, - Map parameters, - List filters, - List fields, - UserDetails currentUser) + private ObjectNode getObjectInternal(String uid, GetObjectParams params, UserDetails currentUser) throws NotFoundException { - WebOptions options = new WebOptions(parameters); - T entity = getEntity(uid, options); + T entity = getEntity(uid); - Query query = - queryService.getQueryFromUrl( - getEntityClass(), - filters, - new ArrayList<>(), - getPaginationData(options), - options.getRootJunction()); + Query query = queryService.getQueryFromUrl(getEntityClass(), params.toListParams()); query.setCurrentUserDetails(currentUser); query.setObjects(List.of(entity)); - query.setDefaults(Defaults.valueOf(options.get("defaults", DEFAULTS))); + query.setDefaults(params.getDefaults()); List entities = (List) queryService.query(query); + List fields = params.getFieldsObject(); handleLinksAndAccess(entities, fields, true); - entities.forEach(e -> postProcessResponseEntity(entity, options, parameters)); + entities.forEach(e -> postProcessResponseEntity(entity, params)); FieldFilterParams filterParams = FieldFilterParams.of(entities, fields); List objectNodes = fieldFilterService.toObjectNodes(filterParams); @@ -525,55 +479,39 @@ private ObjectNode getObjectInternal( return objectNodes.isEmpty() ? fieldFilterService.createObjectNode() : objectNodes.get(0); } - @SuppressWarnings("unchecked") - protected List getEntityList( - WebMetadata metadata, - WebOptions options, - List filters, - List orders, - List objects) + private List getEntityList(P params, List additionalFilters) throws BadRequestException { Query query = BadRequestException.on( QueryParserException.class, - () -> - queryService.getQueryFromUrl( - getEntityClass(), - filters, - orders, - getPaginationData(options), - options.getRootJunction())); + () -> queryService.getQueryFromUrl(getEntityClass(), params)); + query.add(additionalFilters); + query.setDefaultOrder(); - query.setDefaults(Defaults.valueOf(options.get("defaults", DEFAULTS))); - query.setObjects(objects); - - // Note: objects being null means no query had been running whereas empty means a query did run - // with no result - if (objects == null && options.getOptions().containsKey("query")) { - return getEntityListPostProcess( - options, - Lists.newArrayList(manager.filter(getEntityClass(), options.getOptions().get("query")))); - } - return getEntityListPostProcess(options, (List) queryService.query(query)); + query.setDefaults(params.getDefaults()); + + modifyGetObjectList(params, query); + + @SuppressWarnings("unchecked") + List res = (List) queryService.query(query); + getEntityListPostProcess(params, res); + return res; } - protected List getEntityListPostProcess(WebOptions options, List entities) { - return entities; + protected void modifyGetObjectList(P params, Query query) { + // by default: nothing special to do } - private long countTotal(WebOptions options, List filters, List orders) + protected void getEntityListPostProcess(P params, List entities) {} + + private long countGetObjectList(P params, List additionalFilters) throws BadRequestException { Query query = BadRequestException.on( QueryParserException.class, - () -> - queryService.getQueryFromUrl( - getEntityClass(), - filters, - orders, - new Pagination(), - options.getRootJunction())); - + () -> queryService.getQueryFromUrl(getEntityClass(), params)); + query.add(additionalFilters); + modifyGetObjectList(params, query); return queryService.count(query); } @@ -607,33 +545,17 @@ private boolean fieldsContains(String match, List fields) { // Reflection helpers // -------------------------------------------------------------------------- - private String entityName; - private String entitySimpleName; - protected final String getEntityName() { - if (entityName == null) { - entityName = getEntityClass().getName(); - } - - return entityName; - } - protected final String getEntitySimpleName() { if (entitySimpleName == null) { entitySimpleName = getEntityClass().getSimpleName(); } - return entitySimpleName; } @Nonnull - protected final T getEntity(String uid) throws NotFoundException { - return getEntity(uid, NO_WEB_OPTIONS); - } - - @Nonnull - protected T getEntity(String uid, WebOptions options) throws NotFoundException { + protected T getEntity(String uid) throws NotFoundException { return getEntity(uid, getEntityClass()) .orElseThrow(() -> new NotFoundException(getEntityClass(), uid)); } @@ -646,12 +568,4 @@ protected final java.util.Optional getEntity( protected final Schema getSchema(Class klass) { return schemaService.getDynamicSchema(klass); } - - // -------------------------------------------------------------------------- - // Helpers - // -------------------------------------------------------------------------- - - protected final Pagination getPaginationData(WebOptions options) { - return PaginationUtils.getPaginationData(options); - } } diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AggregateDataExchangeController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AggregateDataExchangeController.java index 1fa1db292cb9..f8715c580ecb 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AggregateDataExchangeController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AggregateDataExchangeController.java @@ -40,6 +40,7 @@ import org.hisp.dhis.dxf2.webmessage.WebMessage; import org.hisp.dhis.dxf2.webmessage.WebMessageUtils; import org.hisp.dhis.feedback.ForbiddenException; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.scheduling.JobProgress; import org.hisp.dhis.user.CurrentUser; import org.hisp.dhis.user.UserDetails; @@ -63,7 +64,8 @@ @RequiredArgsConstructor @RequestMapping("/api/aggregateDataExchanges") @ApiVersion({DhisApiVersion.DEFAULT, DhisApiVersion.ALL}) -public class AggregateDataExchangeController extends AbstractCrudController { +public class AggregateDataExchangeController + extends AbstractCrudController { private final AggregateDataExchangeService service; @PostMapping("/exchange") diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AnalyticsTableHookController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AnalyticsTableHookController.java index 590c333adb0f..e9118625c2bc 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AnalyticsTableHookController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AnalyticsTableHookController.java @@ -29,6 +29,7 @@ import org.hisp.dhis.analytics.AnalyticsTableHook; import org.hisp.dhis.common.OpenApi; +import org.hisp.dhis.query.GetObjectListParams; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -38,4 +39,5 @@ @Controller @RequestMapping("/api/analyticsTableHooks") @OpenApi.Document(classifiers = {"team:analytics", "purpose:metadata"}) -public class AnalyticsTableHookController extends AbstractCrudController {} +public class AnalyticsTableHookController + extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AttributeController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AttributeController.java index a4f1c97bb99c..7ff36cae4f46 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AttributeController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AttributeController.java @@ -29,6 +29,7 @@ import org.hisp.dhis.attribute.Attribute; import org.hisp.dhis.common.OpenApi; +import org.hisp.dhis.query.GetObjectListParams; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -38,4 +39,4 @@ @Controller @RequestMapping("/api/attributes") @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) -public class AttributeController extends AbstractCrudController {} +public class AttributeController extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/ConstantController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/ConstantController.java index 1716a6cf8860..a9b998b0de78 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/ConstantController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/ConstantController.java @@ -29,6 +29,7 @@ import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.constant.Constant; +import org.hisp.dhis.query.GetObjectListParams; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -38,4 +39,4 @@ @Controller @RequestMapping("/api/constants") @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) -public class ConstantController extends AbstractCrudController {} +public class ConstantController extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/CrudControllerAdvice.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/CrudControllerAdvice.java index 420f3c08ccb0..c791d4433cff 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/CrudControllerAdvice.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/CrudControllerAdvice.java @@ -37,6 +37,7 @@ import static org.hisp.dhis.dxf2.webmessage.WebMessageUtils.unauthorized; import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.RuntimeJsonMappingException; import io.github.classgraph.ClassGraph; import jakarta.persistence.PersistenceException; import jakarta.servlet.ServletException; @@ -78,9 +79,9 @@ import org.hisp.dhis.schema.SchemaPathException; import org.hisp.dhis.security.spring2fa.TwoFactorAuthenticationException; import org.hisp.dhis.system.util.HttpUtils; +import org.hisp.dhis.tracker.TrackerIdSchemeParam; import org.hisp.dhis.tracker.deduplication.PotentialDuplicateConflictException; import org.hisp.dhis.tracker.deduplication.PotentialDuplicateForbiddenException; -import org.hisp.dhis.tracker.imports.TrackerIdSchemeParam; import org.hisp.dhis.util.DateUtils; import org.hisp.dhis.webapi.controller.exception.MetadataImportConflictException; import org.hisp.dhis.webapi.controller.exception.MetadataSyncException; @@ -327,7 +328,8 @@ private static String getConversionErrorMessage( && (rootValue != null && rootValue.getClass().isArray())) { return "You likely repeated request parameter '" + field - + "' and used multiple comma-separated values within at least one of its values. Choose one of these approaches. " + + "' and used multiple comma-separated values within at least one of its values. Choose" + + " one of these approaches. " + ex.getCause().getMessage(); } @@ -512,6 +514,18 @@ public WebMessage illegalArgumentExceptionHandler(IllegalArgumentException ex) { return badRequest(ex.getMessage()); } + /** + * Handles {@link RuntimeJsonMappingException} and logs the stack trace. {@link + * RuntimeJsonMappingException} is used in DHIS 2 application code but also by various frameworks + * to indicate parsing errors, so stack trace must be printed and not swallowed. + */ + @ExceptionHandler(RuntimeJsonMappingException.class) + @ResponseBody + public WebMessage runtimeJsonMappingExceptionHandler(RuntimeJsonMappingException ex) { + log.error(RuntimeJsonMappingException.class.getName(), ex); + return badRequest(ex.getMessage()); + } + /** * Handles {@link IllegalStateException} and logs the stack trace to standard error. {@link * IllegalStateException} is used in DHIS 2 application code but also by various frameworks to @@ -557,8 +571,7 @@ public WebMessage handleMetadataImportConflictException(MetadataImportConflictEx @ResponseBody public WebMessage handleOAuth2AuthenticationException(OAuth2AuthenticationException ex) { OAuth2Error error = ex.getError(); - if (error instanceof BearerTokenError) { - BearerTokenError bearerTokenError = (BearerTokenError) error; + if (error instanceof BearerTokenError bearerTokenError) { HttpStatus status = ((BearerTokenError) error).getHttpStatus(); return createWebMessage( diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DashboardController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DashboardController.java index 1ba5f501fb1e..171ce8005c7f 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DashboardController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DashboardController.java @@ -41,6 +41,7 @@ import org.hisp.dhis.dxf2.metadata.MetadataExportParams; import org.hisp.dhis.dxf2.webmessage.WebMessageException; import org.hisp.dhis.feedback.ConflictException; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.sharing.CascadeSharingParameters; import org.hisp.dhis.sharing.CascadeSharingReport; import org.hisp.dhis.sharing.CascadeSharingService; @@ -62,7 +63,7 @@ @Controller @RequestMapping("/api/dashboards") @OpenApi.Document(classifiers = {"team:analytics", "purpose:metadata"}) -public class DashboardController extends AbstractCrudController { +public class DashboardController extends AbstractCrudController { @Autowired private DashboardService dashboardService; @Autowired private CascadeSharingService cascadeSharingService; diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DashboardItemController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DashboardItemController.java index bcc6b804b5d8..05b3f284d2a9 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DashboardItemController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DashboardItemController.java @@ -38,6 +38,7 @@ import org.hisp.dhis.dashboard.DashboardService; import org.hisp.dhis.dxf2.webmessage.WebMessageException; import org.hisp.dhis.feedback.ForbiddenException; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.user.CurrentUser; import org.hisp.dhis.user.UserDetails; import org.springframework.beans.factory.annotation.Autowired; @@ -56,7 +57,8 @@ classifiers = {"team:analytics", "purpose:metadata"}) @Controller @RequestMapping("/api/dashboardItems") -public class DashboardItemController extends AbstractCrudController { +public class DashboardItemController + extends AbstractCrudController { // TODO this controller class is only needed for the pre 2.30 old dashboard // app and should be removed diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataApprovalLevelController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataApprovalLevelController.java index 3d641289e591..6ae3b51202ec 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataApprovalLevelController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataApprovalLevelController.java @@ -30,6 +30,7 @@ import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.dataapproval.DataApprovalLevel; import org.hisp.dhis.dataapproval.DataApprovalLevelService; +import org.hisp.dhis.query.GetObjectListParams; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -37,7 +38,8 @@ @Controller @RequestMapping("/api/dataApprovalLevels") @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) -public class DataApprovalLevelController extends AbstractCrudController { +public class DataApprovalLevelController + extends AbstractCrudController { @Autowired private DataApprovalLevelService dataApprovalLevelService; @Override diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataApprovalWorkflowController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataApprovalWorkflowController.java index c4091071c426..aae62bf58db4 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataApprovalWorkflowController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataApprovalWorkflowController.java @@ -29,10 +29,12 @@ import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.dataapproval.DataApprovalWorkflow; +import org.hisp.dhis.query.GetObjectListParams; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/api/dataApprovalWorkflows") @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) -public class DataApprovalWorkflowController extends AbstractCrudController {} +public class DataApprovalWorkflowController + extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataEntryFormController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataEntryFormController.java index c9ccf70ae0be..b26978c3b39f 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataEntryFormController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataEntryFormController.java @@ -29,6 +29,7 @@ import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.dataentryform.DataEntryForm; +import org.hisp.dhis.query.GetObjectListParams; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -38,4 +39,5 @@ @Controller @RequestMapping("/api/dataEntryForms") @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) -public class DataEntryFormController extends AbstractCrudController {} +public class DataEntryFormController + extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataSetController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataSetController.java index ac2fa2a76bdc..c80843aababd 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataSetController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataSetController.java @@ -89,6 +89,7 @@ import org.hisp.dhis.period.Period; import org.hisp.dhis.period.PeriodService; import org.hisp.dhis.period.PeriodType; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.query.Query; import org.hisp.dhis.webapi.mvc.annotation.ApiVersion; import org.hisp.dhis.webapi.utils.FormUtils; @@ -116,7 +117,7 @@ @Controller @RequestMapping("/api/dataSets") @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) -public class DataSetController extends AbstractCrudController { +public class DataSetController extends AbstractCrudController { public static final String DSD_TRANSFORM = "/templates/metadata2dsd.xsl"; // ------------------------------------------------------------------------- diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DocumentController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DocumentController.java index 5a880b352e3c..6ecf16d9575a 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DocumentController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DocumentController.java @@ -45,6 +45,7 @@ import org.hisp.dhis.external.location.LocationManager; import org.hisp.dhis.fileresource.FileResource; import org.hisp.dhis.fileresource.FileResourceService; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.utils.ContextUtils; import org.hisp.dhis.webapi.utils.HeaderUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -62,7 +63,7 @@ @Slf4j @RequestMapping("/api/documents") @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) -public class DocumentController extends AbstractCrudController { +public class DocumentController extends AbstractCrudController { @Autowired private DocumentService documentService; diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/EnrollmentQueryAnalyticsController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/EnrollmentQueryAnalyticsController.java index f6984bcce96b..1cc9503abf01 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/EnrollmentQueryAnalyticsController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/EnrollmentQueryAnalyticsController.java @@ -32,8 +32,8 @@ import static org.hisp.dhis.common.RequestTypeAware.EndpointAction.QUERY; import static org.hisp.dhis.common.RequestTypeAware.EndpointItem.ENROLLMENT; import static org.hisp.dhis.common.cache.CacheStrategy.RESPECT_SYSTEM_SETTING; -import static org.hisp.dhis.period.PeriodDataProvider.DataSource.DATABASE; -import static org.hisp.dhis.period.PeriodDataProvider.DataSource.SYSTEM_DEFINED; +import static org.hisp.dhis.period.PeriodDataProvider.PeriodSource.DATABASE; +import static org.hisp.dhis.period.PeriodDataProvider.PeriodSource.SYSTEM_DEFINED; import static org.hisp.dhis.security.Authorities.F_PERFORM_ANALYTICS_EXPLAIN; import static org.hisp.dhis.system.grid.GridUtils.toCsv; import static org.hisp.dhis.system.grid.GridUtils.toHtml; diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/EventFilterController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/EventFilterController.java index 615ccc4fd921..51f0133cd419 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/EventFilterController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/EventFilterController.java @@ -33,6 +33,7 @@ import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.programstagefilter.EventFilter; import org.hisp.dhis.programstagefilter.EventFilterService; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.mvc.annotation.ApiVersion; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -44,7 +45,8 @@ @RequestMapping("/api/eventFilters") @ApiVersion(include = {DhisApiVersion.ALL, DhisApiVersion.DEFAULT}) @OpenApi.Document(classifiers = {"team:tracker", "purpose:metadata"}) -public class EventFilterController extends AbstractCrudController { +public class EventFilterController + extends AbstractCrudController { private final EventFilterService eventFilterService; public EventFilterController(EventFilterService eventFilterService) { diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/EventHookController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/EventHookController.java index 76f2cdddae86..b20dd4a239cd 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/EventHookController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/EventHookController.java @@ -31,6 +31,7 @@ import org.hisp.dhis.common.DhisApiVersion; import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.eventhook.EventHook; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.mvc.annotation.ApiVersion; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -43,4 +44,4 @@ @RequestMapping("/api/eventHooks") @ApiVersion({DhisApiVersion.DEFAULT, DhisApiVersion.ALL}) @OpenApi.Document(classifiers = {"team:tracker", "purpose:metadata"}) -public class EventHookController extends AbstractCrudController {} +public class EventHookController extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/ExpressionDimensionItemController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/ExpressionDimensionItemController.java index 1bbc1e2a4abe..c06a0278fc88 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/ExpressionDimensionItemController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/ExpressionDimensionItemController.java @@ -31,6 +31,7 @@ import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.expressiondimensionitem.ExpressionDimensionItem; import org.hisp.dhis.feedback.ConflictException; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.mvc.annotation.ApiVersion; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -41,7 +42,7 @@ @ApiVersion({DhisApiVersion.DEFAULT, DhisApiVersion.ALL}) @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) public class ExpressionDimensionItemController - extends AbstractCrudController { + extends AbstractCrudController { @Override protected void preCreateEntity(ExpressionDimensionItem expressionDimensionItem) throws ConflictException { diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/FileResourceController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/FileResourceController.java index 860ce5e735af..c1112a806e2b 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/FileResourceController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/FileResourceController.java @@ -37,7 +37,6 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; -import java.util.Map; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.hisp.dhis.common.DhisApiVersion; @@ -56,6 +55,8 @@ import org.hisp.dhis.fileresource.FileResourceOwner; import org.hisp.dhis.fileresource.FileResourceService; import org.hisp.dhis.fileresource.ImageFileDimension; +import org.hisp.dhis.query.GetObjectListParams; +import org.hisp.dhis.query.GetObjectParams; import org.hisp.dhis.user.CurrentUser; import org.hisp.dhis.user.User; import org.hisp.dhis.user.UserDetails; @@ -82,7 +83,8 @@ @ApiVersion({DhisApiVersion.DEFAULT, DhisApiVersion.ALL}) @AllArgsConstructor @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) -public class FileResourceController extends AbstractFullReadOnlyController { +public class FileResourceController + extends AbstractFullReadOnlyController { private final FileResourceService fileResourceService; private final FileResourceUtils fileResourceUtils; @@ -95,12 +97,12 @@ public class FileResourceController extends AbstractFullReadOnlyController getObject( @PathVariable String uid, - Map rpParameters, + GetObjectParams params, @CurrentUser UserDetails currentUser, HttpServletRequest request, HttpServletResponse response) throws ForbiddenException, NotFoundException { - return super.getObject(uid, rpParameters, currentUser, request, response); + return super.getObject(uid, params, currentUser, request, response); } @GetMapping(value = "/{uid}") diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/IdentifiableObjectController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/IdentifiableObjectController.java index b6b4bc23c5b6..d40906308d0c 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/IdentifiableObjectController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/IdentifiableObjectController.java @@ -37,9 +37,9 @@ import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.dxf2.webmessage.WebMessage; import org.hisp.dhis.feedback.NotFoundException; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.user.CurrentUser; import org.hisp.dhis.user.UserDetails; -import org.hisp.dhis.webapi.webdomain.WebOptions; import org.springframework.stereotype.Controller; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.annotation.PathVariable; @@ -51,12 +51,13 @@ @Controller @RequestMapping("/api/identifiableObjects") @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) -public class IdentifiableObjectController extends AbstractCrudController { +public class IdentifiableObjectController + extends AbstractCrudController { @Nonnull @Override @SuppressWarnings("unchecked") - public IdentifiableObject getEntity(String uid, WebOptions options) throws NotFoundException { + public IdentifiableObject getEntity(String uid) throws NotFoundException { Optional object = (Optional) manager.find(uid); if (object.isEmpty()) { throw new NotFoundException(format("No identifiable object with id `%s` exists", uid)); diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/InterpretationController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/InterpretationController.java index 2cbe281f86ed..dd8a9fb7c87e 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/InterpretationController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/InterpretationController.java @@ -31,13 +31,9 @@ import static org.hisp.dhis.dxf2.webmessage.WebMessageUtils.created; import static org.hisp.dhis.dxf2.webmessage.WebMessageUtils.notFound; -import com.google.common.collect.Lists; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import java.util.ArrayList; -import java.util.Collection; import java.util.Iterator; -import java.util.List; import org.hisp.dhis.common.AnalyticalObject; import org.hisp.dhis.common.IdentifiableObjectManager; import org.hisp.dhis.common.OpenApi; @@ -48,29 +44,20 @@ import org.hisp.dhis.eventreport.EventReport; import org.hisp.dhis.eventvisualization.EventVisualization; import org.hisp.dhis.feedback.ForbiddenException; -import org.hisp.dhis.fieldfilter.Defaults; import org.hisp.dhis.interpretation.Interpretation; import org.hisp.dhis.interpretation.InterpretationComment; import org.hisp.dhis.interpretation.InterpretationService; -import org.hisp.dhis.interpretation.MentionUtils; import org.hisp.dhis.mapping.Map; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.period.Period; import org.hisp.dhis.period.PeriodType; -import org.hisp.dhis.query.Disjunction; -import org.hisp.dhis.query.Order; -import org.hisp.dhis.query.Query; -import org.hisp.dhis.query.QueryParserException; -import org.hisp.dhis.query.Restrictions; -import org.hisp.dhis.schema.Schema; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.schema.descriptors.InterpretationSchemaDescriptor; import org.hisp.dhis.user.CurrentUser; import org.hisp.dhis.user.CurrentUserUtil; import org.hisp.dhis.user.User; import org.hisp.dhis.user.UserDetails; import org.hisp.dhis.visualization.Visualization; -import org.hisp.dhis.webapi.webdomain.WebMetadata; -import org.hisp.dhis.webapi.webdomain.WebOptions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; @@ -90,51 +77,12 @@ @Controller @RequestMapping("/api/interpretations") @OpenApi.Document(classifiers = {"team:analytics", "purpose:metadata"}) -public class InterpretationController extends AbstractCrudController { +public class InterpretationController + extends AbstractCrudController { @Autowired private InterpretationService interpretationService; @Autowired private IdentifiableObjectManager idObjectManager; - @Override - @SuppressWarnings("unchecked") - protected List getEntityList( - WebMetadata metadata, - WebOptions options, - List filters, - List orders, - List objects) - throws QueryParserException { - // If custom filter (mentions:in:[username]) in filters -> Remove from - // filters - List mentionsFromCustomFilters = MentionUtils.removeCustomFilters(filters); - - Query query = - queryService.getQueryFromUrl( - getEntityClass(), - filters, - orders, - getPaginationData(options), - options.getRootJunction()); - query.setDefaultOrder(); - query.setDefaults(Defaults.valueOf(options.get("defaults", DEFAULTS))); - // If custom filter (mentions:in:[username]) in filters -> Add as - // disjunction including interpretation mentions and comments mentions - for (Disjunction disjunction : - (Collection) - getDisjunctionsFromCustomMentions(mentionsFromCustomFilters, query.getSchema())) { - query.add(disjunction); - } - - List entityList; - if (objects == null && options.getOptions().containsKey("query")) { - entityList = - Lists.newArrayList(manager.filter(getEntityClass(), options.getOptions().get("query"))); - } else { - entityList = (List) queryService.query(query); - } - return entityList; - } - // ------------------------------------------------------------------------- // Interpretation create // ------------------------------------------------------------------------- @@ -500,22 +448,4 @@ public WebMessage unlike(@PathVariable("uid") String uid) { } return conflict("Could not remove like, user had not previously liked interpretation"); } - - // ------------------------------------------------------------------------- - // Supportive methods - // ------------------------------------------------------------------------- - - private Collection getDisjunctionsFromCustomMentions( - List mentions, Schema schema) { - Collection disjunctions = new ArrayList(); - for (String m : mentions) { - Disjunction disjunction = new Disjunction(schema); - String[] split = m.substring(1, m.length() - 1).split(","); - List items = Lists.newArrayList(split); - disjunction.add(Restrictions.in("mentions.username", items)); - disjunction.add(Restrictions.in("comments.mentions.username", items)); - disjunctions.add(disjunction); - } - return disjunctions; - } } diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/LegendSetController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/LegendSetController.java index 2bbe7453ffe9..a24d2f6c9ccf 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/LegendSetController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/LegendSetController.java @@ -40,6 +40,7 @@ import org.hisp.dhis.feedback.ForbiddenException; import org.hisp.dhis.feedback.NotFoundException; import org.hisp.dhis.legend.LegendSet; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.security.RequiresAuthority; import org.hisp.dhis.user.CurrentUser; import org.hisp.dhis.user.UserDetails; @@ -54,7 +55,7 @@ @Controller @RequestMapping("/api/legendSets") @OpenApi.Document(classifiers = {"team:analytics", "purpose:metadata"}) -public class LegendSetController extends AbstractCrudController { +public class LegendSetController extends AbstractCrudController { @Override @RequiresAuthority(anyOf = {F_LEGEND_SET_PUBLIC_ADD, F_LEGEND_SET_PRIVATE_ADD}) public WebMessage postJsonObject(HttpServletRequest request) diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/MessageConversationController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/MessageConversationController.java index 66e78789eace..e4a5ad88d55f 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/MessageConversationController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/MessageConversationController.java @@ -31,6 +31,7 @@ import static org.hisp.dhis.dxf2.webmessage.WebMessageUtils.created; import static org.hisp.dhis.dxf2.webmessage.WebMessageUtils.notFound; import static org.hisp.dhis.security.Authorities.F_METADATA_IMPORT; +import static org.hisp.dhis.user.CurrentUserUtil.getCurrentUserDetails; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.collect.Lists; @@ -38,18 +39,16 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; +import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; import org.hisp.dhis.common.OpenApi; -import org.hisp.dhis.common.Pager; +import org.hisp.dhis.common.UID; import org.hisp.dhis.configuration.ConfigurationService; import org.hisp.dhis.dxf2.webmessage.WebMessage; import org.hisp.dhis.dxf2.webmessage.WebMessageException; @@ -57,7 +56,6 @@ import org.hisp.dhis.feedback.ConflictException; import org.hisp.dhis.feedback.ForbiddenException; import org.hisp.dhis.feedback.NotFoundException; -import org.hisp.dhis.fieldfilter.Defaults; import org.hisp.dhis.fileresource.FileResource; import org.hisp.dhis.fileresource.FileResourceDomain; import org.hisp.dhis.fileresource.FileResourceService; @@ -72,15 +70,13 @@ import org.hisp.dhis.node.types.SimpleNode; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.organisationunit.OrganisationUnitService; +import org.hisp.dhis.query.GetObjectListParams; +import org.hisp.dhis.query.GetObjectParams; import org.hisp.dhis.query.Junction; -import org.hisp.dhis.query.Order; -import org.hisp.dhis.query.Pagination; import org.hisp.dhis.query.Query; -import org.hisp.dhis.query.QueryParserException; import org.hisp.dhis.schema.descriptors.MessageConversationSchemaDescriptor; import org.hisp.dhis.security.RequiresAuthority; import org.hisp.dhis.user.CurrentUser; -import org.hisp.dhis.user.CurrentUserUtil; import org.hisp.dhis.user.User; import org.hisp.dhis.user.UserDetails; import org.hisp.dhis.user.UserGroup; @@ -88,8 +84,6 @@ import org.hisp.dhis.webapi.utils.ContextUtils; import org.hisp.dhis.webapi.utils.FileResourceUtils; import org.hisp.dhis.webapi.webdomain.MessageConversation; -import org.hisp.dhis.webapi.webdomain.WebMetadata; -import org.hisp.dhis.webapi.webdomain.WebOptions; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -113,7 +107,10 @@ @RequiredArgsConstructor @OpenApi.Document(classifiers = {"team:platform", "purpose:support"}) public class MessageConversationController - extends AbstractCrudController { + extends AbstractCrudController< + org.hisp.dhis.message.MessageConversation, + MessageConversationController.GetMessageConversationObjectListParams> { + private final MessageService messageService; private final OrganisationUnitService organisationUnitService; private final UserGroupService userGroupService; @@ -122,30 +119,29 @@ public class MessageConversationController private final FileResourceService fileResourceService; private final DhisConfigurationProvider dhisConfig; + @Data + @EqualsAndHashCode(callSuper = true) + public static final class GetMessageConversationObjectListParams extends GetObjectListParams { + String queryString; + String queryOperator; + } + @Override protected void postProcessResponseEntity( - org.hisp.dhis.message.MessageConversation entity, - WebOptions options, - Map parameters) { + org.hisp.dhis.message.MessageConversation entity, GetObjectParams params) { - User currentUser = userService.getUserByUsername(CurrentUserUtil.getCurrentUsername()); - if (!messageService.hasAccessToManageFeedbackMessages(UserDetails.fromUser(currentUser))) { + if (!messageService.hasAccessToManageFeedbackMessages(getCurrentUserDetails())) { entity.setMessages( entity.getMessages().stream().filter(message -> !message.isInternal()).toList()); } - - boolean markRead = Boolean.parseBoolean(parameters.get("markRead")); - - if (markRead) { - entity.markRead(currentUser); - manager.update(entity); - } } @Override + @OpenApi.Param(name = "markRead", value = boolean.class) + @GetMapping("/{uid:[a-zA-Z0-9]{11}}") public ResponseEntity getObject( @PathVariable String uid, - Map rpParameters, + GetObjectParams params, @CurrentUser UserDetails currentUser, HttpServletRequest request, HttpServletResponse response) @@ -167,80 +163,40 @@ public ResponseEntity getObject( throw new ForbiddenException("Not authorized to access this conversation."); } - return super.getObject(uid, rpParameters, currentUser, request, response); - } - - @Override - @SuppressWarnings("unchecked") - protected List getEntityList( - WebMetadata metadata, - WebOptions options, - List filters, - List orders, - List objects) - throws QueryParserException { - List messageConversations; - - if (objects == null && options.getOptions().containsKey("query")) { - messageConversations = - Lists.newArrayList(manager.filter(getEntityClass(), options.getOptions().get("query"))); - } else { - messageConversations = new ArrayList<>(messageService.getMessageConversations()); - } - - Query query = - queryService.getQueryFromUrl( - getEntityClass(), filters, orders, new Pagination(), options.getRootJunction()); - query.setDefaultOrder(); - query.setDefaults(Defaults.valueOf(options.get("defaults", DEFAULTS))); - query.setObjects(messageConversations); - - messageConversations = - (List) queryService.query(query); - - if (options.get("queryString") != null) { - String queryOperator = "token"; - if (options.get("queryOperator") != null) { - queryOperator = options.get("queryOperator"); - } + boolean markRead = "true".equals(request.getParameter("markRead")); - List queryFilter = - Arrays.asList( - "subject:" + queryOperator + ":" + options.get("queryString"), - "messages.text:" + queryOperator + ":" + options.get("queryString"), - "messages.sender.displayName:" + queryOperator + ":" + options.get("queryString")); - Query subQuery = - queryService.getQueryFromUrl( - getEntityClass(), - queryFilter, - Collections.emptyList(), - new Pagination(), - Junction.Type.OR); - subQuery.setObjects(messageConversations); - messageConversations = - (List) queryService.query(subQuery); + if (markRead) { + messageConversation.markRead(UID.of(currentUser.getUid())); + manager.update(messageConversation); } - int count = messageConversations.size(); - - Query paginatedQuery = - queryService.getQueryFromUrl( - getEntityClass(), - Collections.emptyList(), - Collections.emptyList(), - getPaginationData(options), - options.getRootJunction()); - paginatedQuery.setObjects(messageConversations); - - messageConversations = - (List) queryService.query(paginatedQuery); - - if (options.hasPaging()) { - Pager pager = new Pager(options.getPage(), count, options.getPageSize()); - metadata.setPager(pager); - } + return super.getObject(uid, params, currentUser, request, response); + } - return messageConversations; + @Override + protected List getPreQueryMatches(GetMessageConversationObjectListParams params) { + String query = params.getQueryString(); + if (query == null) return null; + + String op = params.getQueryOperator(); + if (op == null) op = "token"; + + List filters = + List.of( + "subject:" + op + ":" + query, + "messages.text:" + op + ":" + query, + "messages.sender.displayName:" + op + ":" + query); + + GetObjectListParams subQueryParams = + new GetObjectListParams() + .setPaging(false) + .setRootJunction(Junction.Type.OR) + .setFilters(filters); + Query subQuery = queryService.getQueryFromUrl(getEntityClass(), subQueryParams); + // Note: in theory these filters could be added to the main query + // but the OR concerns both DB and in-memory properties + // which would break if added to the main query ATM + return queryService.query(subQuery).stream().map(UID::of).toList(); } // -------------------------------------------------------------------------- @@ -1061,7 +1017,10 @@ private RootNode modifyMessageConversationRead( for (org.hisp.dhis.message.MessageConversation conversation : messageConversations) { - boolean success = (readValue ? conversation.markRead(user) : conversation.markUnread(user)); + boolean success = + (readValue + ? conversation.markRead(UID.of(user.getUid())) + : conversation.markUnread(user)); if (success) { messageService.updateMessageConversation(conversation); marked.addChild(new SimpleNode("uid", conversation.getUid())); diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/PredictorController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/PredictorController.java index 82b58f462564..095692195e9a 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/PredictorController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/PredictorController.java @@ -47,6 +47,7 @@ import org.hisp.dhis.predictor.PredictionSummary; import org.hisp.dhis.predictor.Predictor; import org.hisp.dhis.predictor.PredictorService; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.security.RequiresAuthority; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; @@ -66,7 +67,7 @@ @Slf4j @RequestMapping("/api/predictors") @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) -public class PredictorController extends AbstractCrudController { +public class PredictorController extends AbstractCrudController { @Autowired private PredictorService predictorService; @Autowired private PredictionService predictionService; diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/PredictorGroupController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/PredictorGroupController.java index 2a3a7e1cd23d..10d2739b2a3d 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/PredictorGroupController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/PredictorGroupController.java @@ -39,6 +39,7 @@ import org.hisp.dhis.predictor.PredictionService; import org.hisp.dhis.predictor.PredictionSummary; import org.hisp.dhis.predictor.PredictorGroup; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.scheduling.JobProgress; import org.hisp.dhis.security.RequiresAuthority; import org.springframework.stereotype.Controller; @@ -55,7 +56,8 @@ @RequestMapping("/api/predictorGroups") @RequiredArgsConstructor @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) -public class PredictorGroupController extends AbstractCrudController { +public class PredictorGroupController + extends AbstractCrudController { private final PredictionService predictionService; diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/ProgramStageWorkingListController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/ProgramStageWorkingListController.java index 1128a591dbd6..6734fdfba2ee 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/ProgramStageWorkingListController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/ProgramStageWorkingListController.java @@ -30,6 +30,7 @@ import org.hisp.dhis.common.DhisApiVersion; import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.programstageworkinglist.ProgramStageWorkingList; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.mvc.annotation.ApiVersion; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -39,4 +40,4 @@ @ApiVersion(include = {DhisApiVersion.ALL, DhisApiVersion.DEFAULT}) @OpenApi.Document(classifiers = {"team:tracker", "purpose:metadata"}) public class ProgramStageWorkingListController - extends AbstractCrudController {} + extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/PushAnalysisController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/PushAnalysisController.java index 180ef58cc42f..725f4a462f4b 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/PushAnalysisController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/PushAnalysisController.java @@ -41,6 +41,7 @@ import org.hisp.dhis.feedback.NotFoundException; import org.hisp.dhis.pushanalysis.PushAnalysis; import org.hisp.dhis.pushanalysis.PushAnalysisService; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.scheduling.JobConfiguration; import org.hisp.dhis.scheduling.JobConfigurationService; import org.hisp.dhis.scheduling.JobSchedulerService; @@ -67,7 +68,8 @@ @ApiVersion({DhisApiVersion.DEFAULT, DhisApiVersion.ALL}) @RequiredArgsConstructor @OpenApi.Document(classifiers = {"team:platform", "purpose:support"}) -public class PushAnalysisController extends AbstractCrudController { +public class PushAnalysisController + extends AbstractCrudController { private final PushAnalysisService pushAnalysisService; private final ContextUtils contextUtils; diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/QueryController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/QueryController.java index 7c37c1bb26c7..cc00c1a07914 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/QueryController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/QueryController.java @@ -38,11 +38,11 @@ import lombok.Value; import org.hisp.dhis.cache.Cache; import org.hisp.dhis.cache.CacheProvider; +import org.hisp.dhis.common.HashUtils; import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.feedback.BadRequestException; import org.hisp.dhis.feedback.NotFoundException; import org.hisp.dhis.render.RenderService; -import org.hisp.dhis.system.util.CodecUtils; import org.hisp.dhis.webapi.utils.HttpServletRequestPaths; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @@ -139,7 +139,7 @@ private QueryAlias createAlias(String target, HttpServletRequest request) throw new BadRequestException("Target url exceeds maximum length"); } - String alias = CodecUtils.sha1Hex(target); + String alias = HashUtils.hashSHA1(target.getBytes()); aliasCache.put(alias, target); String contextPath = HttpServletRequestPaths.getContextPath(request); diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/ReportController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/ReportController.java index 96a8afe769ed..b44644735a43 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/ReportController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/ReportController.java @@ -46,6 +46,7 @@ import org.hisp.dhis.period.MonthlyPeriodType; import org.hisp.dhis.period.Period; import org.hisp.dhis.period.PeriodType; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.report.Report; import org.hisp.dhis.report.ReportService; import org.hisp.dhis.report.ReportType; @@ -72,7 +73,7 @@ @Controller @RequestMapping("/api/reports") @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) -public class ReportController extends AbstractCrudController { +public class ReportController extends AbstractCrudController { @Autowired public ReportService reportService; @Autowired private OrganisationUnitService organisationUnitService; diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/RouteController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/RouteController.java index a02412bf474d..fdda5c0d2d13 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/RouteController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/RouteController.java @@ -39,6 +39,7 @@ import org.hisp.dhis.feedback.ConflictException; import org.hisp.dhis.feedback.ForbiddenException; import org.hisp.dhis.feedback.NotFoundException; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.route.Route; import org.hisp.dhis.route.RouteService; import org.hisp.dhis.schema.descriptors.RouteSchemaDescriptor; @@ -62,7 +63,7 @@ @RequestMapping("/api/routes") @ApiVersion({DhisApiVersion.DEFAULT, DhisApiVersion.ALL}) @OpenApi.Document(classifiers = {"team:extensibility", "purpose:metadata"}) -public class RouteController extends AbstractCrudController { +public class RouteController extends AbstractCrudController { private final RouteService routeService; @RequestMapping( diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/SectionController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/SectionController.java index 211f5a6b8b96..2877e3058f3b 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/SectionController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/SectionController.java @@ -29,6 +29,7 @@ import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.dataset.Section; +import org.hisp.dhis.query.GetObjectListParams; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -38,4 +39,4 @@ @Controller @RequestMapping("/api/sections") @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) -public class SectionController extends AbstractCrudController
{} +public class SectionController extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/SharingController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/SharingController.java index b2d8fb56a079..e73beb87215f 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/SharingController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/SharingController.java @@ -42,6 +42,7 @@ import java.util.Set; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; +import org.hisp.dhis.category.CategoryOption; import org.hisp.dhis.common.BaseIdentifiableObject; import org.hisp.dhis.common.DataDimensionItem; import org.hisp.dhis.common.DhisApiVersion; @@ -50,6 +51,8 @@ import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.common.Pager; import org.hisp.dhis.common.SystemDefaultMetadataObject; +import org.hisp.dhis.common.cache.Region; +import org.hisp.dhis.common.event.CacheInvalidationEvent; import org.hisp.dhis.dxf2.webmessage.WebMessage; import org.hisp.dhis.dxf2.webmessage.WebMessageException; import org.hisp.dhis.feedback.ForbiddenException; @@ -76,6 +79,7 @@ import org.hisp.dhis.webapi.webdomain.sharing.SharingUserGroupAccess; import org.hisp.dhis.webapi.webdomain.sharing.comparator.SharingUserGroupAccessNameComparator; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @@ -111,6 +115,8 @@ public class SharingController { @Autowired private EntityManager entityManager; + @Autowired private ApplicationEventPublisher eventPublisher; + // ------------------------------------------------------------------------- // Resources // ------------------------------------------------------------------------- @@ -344,6 +350,8 @@ public WebMessage postSharing( (Visualization) object, sharing.getObject().getUserAccesses(), sharing.getObject().getUserGroupAccesses()); + } else if (object instanceof CategoryOption) { + eventPublisher.publishEvent(new CacheInvalidationEvent(this, Region.canDataWriteCocCache)); } return ok("Access control set"); diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/SqlViewController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/SqlViewController.java index 119fe8053fe2..418c48c1e1d9 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/SqlViewController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/SqlViewController.java @@ -46,6 +46,7 @@ import org.hisp.dhis.external.conf.DhisConfigurationProvider; import org.hisp.dhis.feedback.ConflictException; import org.hisp.dhis.feedback.NotFoundException; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.scheduling.JobConfiguration; import org.hisp.dhis.scheduling.JobConfigurationService; import org.hisp.dhis.scheduling.JobType; @@ -73,7 +74,7 @@ @RequestMapping("/api/sqlViews") @RequiredArgsConstructor @OpenApi.Document(classifiers = {"team:platform", "purpose:support"}) -public class SqlViewController extends AbstractCrudController { +public class SqlViewController extends AbstractCrudController { private final SqlViewService sqlViewService; private final JobConfigurationService jobConfigurationService; diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/TrackedEntityFilterController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/TrackedEntityFilterController.java index 9d26fbeb5184..b8760b71e09c 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/TrackedEntityFilterController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/TrackedEntityFilterController.java @@ -31,6 +31,7 @@ import org.hisp.dhis.common.DhisApiVersion; import org.hisp.dhis.common.IllegalQueryException; import org.hisp.dhis.common.OpenApi; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.trackedentityfilter.TrackedEntityFilter; import org.hisp.dhis.trackedentityfilter.TrackedEntityFilterService; import org.hisp.dhis.webapi.mvc.annotation.ApiVersion; @@ -44,7 +45,8 @@ @RequestMapping("/api/trackedEntityInstanceFilters") @ApiVersion(include = {DhisApiVersion.ALL, DhisApiVersion.DEFAULT}) @OpenApi.Document(classifiers = {"team:tracker", "purpose:metadata"}) -public class TrackedEntityFilterController extends AbstractCrudController { +public class TrackedEntityFilterController + extends AbstractCrudController { private final TrackedEntityFilterService teiFilterService; public TrackedEntityFilterController(TrackedEntityFilterService teiFilterService) { diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/VisualizationController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/VisualizationController.java index a163845380a3..18592c1d6fe7 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/VisualizationController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/VisualizationController.java @@ -37,7 +37,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Set; import org.apache.commons.collections4.CollectionUtils; import org.hisp.dhis.common.BaseDimensionalItemObject; @@ -53,17 +52,19 @@ import org.hisp.dhis.legend.LegendSetService; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.period.Period; +import org.hisp.dhis.query.GetObjectListParams; +import org.hisp.dhis.query.GetObjectParams; import org.hisp.dhis.user.CurrentUserUtil; import org.hisp.dhis.user.User; import org.hisp.dhis.visualization.Visualization; -import org.hisp.dhis.webapi.webdomain.WebOptions; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/api/visualizations") @OpenApi.Document(classifiers = {"team:analytics", "purpose:metadata"}) -public class VisualizationController extends AbstractCrudController { +public class VisualizationController + extends AbstractCrudController { private final LegendSetService legendSetService; private final DimensionService dimensionService; @@ -141,7 +142,7 @@ private void maybeLoadLegendSetInto(Visualization visualization) { @Override public void postProcessResponseEntities( - List entityList, WebOptions options, Map parameters) { + List entityList, GetObjectListParams params) { if (CollectionUtils.isEmpty(entityList)) return; User currentUser = userService.getUserByUsername(CurrentUserUtil.getCurrentUsername()); @@ -169,8 +170,7 @@ public void postProcessResponseEntities( } @Override - public void postProcessResponseEntity( - Visualization visualization, WebOptions options, Map parameters) { + public void postProcessResponseEntity(Visualization visualization, GetObjectParams params) { if (visualization != null) { visualization.populateAnalyticalProperties(); diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryComboController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryComboController.java index 8d811020490d..d10ebbd19881 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryComboController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryComboController.java @@ -38,6 +38,7 @@ import org.hisp.dhis.feedback.ConflictException; import org.hisp.dhis.feedback.ErrorCode; import org.hisp.dhis.feedback.NotFoundException; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; @@ -53,7 +54,8 @@ @Controller @RequestMapping("/api/categoryCombos") @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) -public class CategoryComboController extends AbstractCrudController { +public class CategoryComboController + extends AbstractCrudController { @Autowired private CategoryService categoryService; @Autowired private DataValueService dataValueService; diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryController.java index d1cfec59d1ea..7fb6dc1595bd 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryController.java @@ -29,6 +29,7 @@ import org.hisp.dhis.category.Category; import org.hisp.dhis.common.OpenApi; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -39,4 +40,4 @@ @Controller @RequestMapping("/api/categories") @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) -public class CategoryController extends AbstractCrudController {} +public class CategoryController extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryOptionComboController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryOptionComboController.java index ddfab84e9a08..e243d3a836a0 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryOptionComboController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryOptionComboController.java @@ -29,6 +29,7 @@ import org.hisp.dhis.category.CategoryOptionCombo; import org.hisp.dhis.common.OpenApi; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -39,4 +40,5 @@ @Controller @RequestMapping("/api/categoryOptionCombos") @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) -public class CategoryOptionComboController extends AbstractCrudController {} +public class CategoryOptionComboController + extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryOptionController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryOptionController.java index 8d177533a471..f828daa7bc37 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryOptionController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryOptionController.java @@ -27,32 +27,54 @@ */ package org.hisp.dhis.webapi.controller.category; +import static org.hisp.dhis.security.Authorities.F_CATEGORY_OPTION_MERGE; +import static org.hisp.dhis.webapi.controller.CrudControllerAdvice.getHelpfulMessage; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +import jakarta.persistence.PersistenceException; import java.util.Collection; import java.util.Map; import java.util.Optional; import java.util.Set; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.SetValuedMap; import org.hisp.dhis.category.CategoryOption; import org.hisp.dhis.category.CategoryService; +import org.hisp.dhis.common.Maturity.Beta; import org.hisp.dhis.common.OpenApi; +import org.hisp.dhis.dxf2.webmessage.WebMessage; +import org.hisp.dhis.dxf2.webmessage.WebMessageUtils; +import org.hisp.dhis.feedback.ConflictException; +import org.hisp.dhis.feedback.MergeReport; +import org.hisp.dhis.merge.MergeParams; +import org.hisp.dhis.merge.MergeService; +import org.hisp.dhis.query.GetObjectListParams; +import org.hisp.dhis.security.RequiresAuthority; import org.hisp.dhis.webapi.controller.AbstractCrudController; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; /** * @author Morten Olav Hansen */ +@Slf4j @Controller @RequestMapping("/api/categoryOptions") @RequiredArgsConstructor @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) -public class CategoryOptionController extends AbstractCrudController { +public class CategoryOptionController + extends AbstractCrudController { private final CategoryService categoryService; + private final MergeService categoryOptionMergeService; @ResponseBody @GetMapping(value = "orgUnits") @@ -66,4 +88,25 @@ public Map> getOrgUnitsAssociations( () -> new IllegalArgumentException("At least one categoryOption uid must be specified")); } + + @Beta + @ResponseStatus(HttpStatus.OK) + @RequiresAuthority(anyOf = F_CATEGORY_OPTION_MERGE) + @PostMapping(value = "/merge", produces = APPLICATION_JSON_VALUE) + public @ResponseBody WebMessage mergeCategoryOptions(@RequestBody MergeParams params) + throws ConflictException { + log.info("CategoryOption merge received"); + + MergeReport report; + try { + report = categoryOptionMergeService.processMerge(params); + } catch (PersistenceException ex) { + String helpfulMessage = getHelpfulMessage(ex); + log.error("Error while processing CategoryOption merge: {}", helpfulMessage); + throw ex; + } + + log.info("CategoryOption merge processed with report: {}", report); + return WebMessageUtils.mergeReport(report); + } } diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryOptionGroupController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryOptionGroupController.java index f1b548320189..e96f98af42f8 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryOptionGroupController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryOptionGroupController.java @@ -29,6 +29,7 @@ import org.hisp.dhis.category.CategoryOptionGroup; import org.hisp.dhis.common.OpenApi; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -39,4 +40,5 @@ @Controller @RequestMapping("/api/categoryOptionGroups") @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) -public class CategoryOptionGroupController extends AbstractCrudController {} +public class CategoryOptionGroupController + extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryOptionGroupSetController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryOptionGroupSetController.java index 9c6ff8e3a40b..6ca10099199c 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryOptionGroupSetController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/category/CategoryOptionGroupSetController.java @@ -29,6 +29,7 @@ import org.hisp.dhis.category.CategoryOptionGroupSet; import org.hisp.dhis.common.OpenApi; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -40,4 +41,4 @@ @RequestMapping("/api/categoryOptionGroupSets") @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) public class CategoryOptionGroupSetController - extends AbstractCrudController {} + extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataelement/DataElementController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataelement/DataElementController.java index a924cc4b986e..4e161536dc8e 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataelement/DataElementController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataelement/DataElementController.java @@ -42,8 +42,8 @@ import org.hisp.dhis.feedback.ConflictException; import org.hisp.dhis.feedback.MergeReport; import org.hisp.dhis.merge.MergeParams; -import org.hisp.dhis.merge.MergeProcessor; -import org.hisp.dhis.merge.MergeType; +import org.hisp.dhis.merge.MergeService; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.security.RequiresAuthority; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.springframework.http.HttpStatus; @@ -62,10 +62,11 @@ @RequestMapping("/api/dataElements") @RequiredArgsConstructor @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) -public class DataElementController extends AbstractCrudController { +public class DataElementController + extends AbstractCrudController { private final DataElementService dataElementService; - private final MergeProcessor dataElementMergeProcessor; + private final MergeService dataElementMergeService; @ResponseStatus(HttpStatus.OK) @RequiresAuthority(anyOf = F_DATA_ELEMENT_MERGE) @@ -73,11 +74,10 @@ public class DataElementController extends AbstractCrudController { public @ResponseBody WebMessage mergeDataElements(@RequestBody MergeParams params) throws ConflictException { log.info("Data element merge received"); - params.setMergeType(MergeType.DATA_ELEMENT); MergeReport report; try { - report = dataElementMergeProcessor.processMerge(params); + report = dataElementMergeService.processMerge(params); } catch (PersistenceException ex) { String helpfulMessage = getHelpfulMessage(ex); log.error("Error while processing Data element merge: {}", helpfulMessage); diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataelement/DataElementGroupController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataelement/DataElementGroupController.java index 0be59f38e91d..0914e655202d 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataelement/DataElementGroupController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataelement/DataElementGroupController.java @@ -45,6 +45,7 @@ import org.hisp.dhis.dxf2.metadata.MetadataExportParams; import org.hisp.dhis.dxf2.webmessage.WebMessageException; import org.hisp.dhis.feedback.NotFoundException; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.hisp.dhis.webapi.webdomain.WebMetadata; import org.hisp.dhis.webapi.webdomain.WebOptions; @@ -63,7 +64,8 @@ @Controller @RequestMapping("/api/dataElementGroups") @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) -public class DataElementGroupController extends AbstractCrudController { +public class DataElementGroupController + extends AbstractCrudController { @Autowired private CategoryService dataElementCategoryService; @Autowired private DataElementService dataElementService; diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataelement/DataElementGroupSetController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataelement/DataElementGroupSetController.java index a337845383b4..467671d2898d 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataelement/DataElementGroupSetController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataelement/DataElementGroupSetController.java @@ -29,6 +29,7 @@ import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.dataelement.DataElementGroupSet; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -39,4 +40,5 @@ @Controller @RequestMapping("/api/dataElementGroupSets") @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) -public class DataElementGroupSetController extends AbstractCrudController {} +public class DataElementGroupSetController + extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataelement/DataElementOperandController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataelement/DataElementOperandController.java index 6d6f049637cc..61a5b860dbef 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataelement/DataElementOperandController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataelement/DataElementOperandController.java @@ -32,50 +32,39 @@ import com.google.common.collect.Lists; import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; +import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; -import org.cache2k.Cache; -import org.cache2k.Cache2kBuilder; import org.hisp.dhis.category.CategoryService; import org.hisp.dhis.common.DhisApiVersion; import org.hisp.dhis.common.IdentifiableObjectManager; import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.common.Pager; +import org.hisp.dhis.common.UID; import org.hisp.dhis.common.collection.CollectionUtils; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.dataelement.DataElementGroup; import org.hisp.dhis.dataelement.DataElementOperand; import org.hisp.dhis.dataset.DataSet; -import org.hisp.dhis.dxf2.common.OrderParams; import org.hisp.dhis.fieldfilter.FieldFilterParams; import org.hisp.dhis.fieldfilter.FieldFilterService; -import org.hisp.dhis.fieldfiltering.FieldPreset; import org.hisp.dhis.node.NodeUtils; import org.hisp.dhis.node.types.RootNode; -import org.hisp.dhis.query.Order; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.query.Query; import org.hisp.dhis.query.QueryParserException; import org.hisp.dhis.query.QueryService; -import org.hisp.dhis.schema.Schema; -import org.hisp.dhis.schema.SchemaService; -import org.hisp.dhis.user.CurrentUser; -import org.hisp.dhis.user.User; import org.hisp.dhis.webapi.mvc.annotation.ApiVersion; -import org.hisp.dhis.webapi.service.ContextService; import org.hisp.dhis.webapi.service.LinkService; -import org.hisp.dhis.webapi.utils.PaginationUtils; -import org.hisp.dhis.webapi.webdomain.WebMetadata; -import org.hisp.dhis.webapi.webdomain.WebOptions; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; /** * @author Morten Olav Hansen */ +@OpenApi.EntityType(DataElementOperand.class) @OpenApi.Document( entity = DataElementOperand.class, classifiers = {"team:platform", "purpose:metadata"}) @@ -92,45 +81,33 @@ public class DataElementOperandController { private final LinkService linkService; - private final ContextService contextService; - - private final SchemaService schemaService; - private final CategoryService dataElementCategoryService; - private final Cache paginationCountCache = - new Cache2kBuilder() {}.expireAfterWrite(1, TimeUnit.MINUTES).build(); - - @GetMapping - @SuppressWarnings("unchecked") - public @ResponseBody RootNode getObjectList( - @RequestParam Map rpParameters, - OrderParams orderParams, - @CurrentUser User currentUser) - throws QueryParserException { - Schema schema = schemaService.getDynamicSchema(DataElementOperand.class); - - List fields = Lists.newArrayList(contextService.getParameterValues("fields")); - List filters = Lists.newArrayList(contextService.getParameterValues("filter")); - List orders = orderParams.getOrders(schema); + @Data + @EqualsAndHashCode(callSuper = true) + public static final class GetDataElementOperandObjectListParams extends GetObjectListParams { + boolean persisted; + boolean totals; - if (fields.isEmpty()) { - fields.addAll(FieldPreset.ALL.getFields()); - } + @OpenApi.Property({UID.class, DataSet.class}) + String dataSet; + } - WebOptions options = new WebOptions(rpParameters); - WebMetadata metadata = new WebMetadata(); + private List getUnfilteredDataElementOperands( + GetDataElementOperandObjectListParams params) { List dataElementOperands = List.of(); - if (options.isTrue("persisted")) { + if (params.isPersisted()) { dataElementOperands = Lists.newArrayList(manager.getAll(DataElementOperand.class)); } else { - boolean totals = options.isTrue("totals"); + boolean totals = params.isTotals(); - String deg = CollectionUtils.popStartsWith(filters, "dataElement.dataElementGroups.id:eq:"); + String deg = + CollectionUtils.popStartsWith( + params.getFilters(), "dataElement.dataElementGroups.id:eq:"); deg = deg != null ? deg.substring("dataElement.dataElementGroups.id:eq:".length()) : null; - String ds = options.get("dataSet"); + String ds = params.getDataSet(); if (deg != null) { DataElementGroup dataElementGroup = manager.get(DataElementGroup.class, deg); @@ -146,71 +123,52 @@ public class DataElementOperandController { dataElementOperands = dataElementCategoryService.getOperands(dataElements, totals); } } + return dataElementOperands; + } + + @GetMapping + @SuppressWarnings("unchecked") + public @ResponseBody RootNode getObjectList(GetDataElementOperandObjectListParams params) + throws QueryParserException { + List allItems = getUnfilteredDataElementOperands(params); // This is needed for two reasons: // 1) We are doing in-memory paging; // 2) We have to count all items respecting the filtering and the // initial universe of elements. In this case, the variable // "dataElementOperands". - Query queryForCount = queryService.getQueryFromUrl(DataElementOperand.class, filters, orders); - queryForCount.setObjects(dataElementOperands); - - List totalOfItems = - (List) queryService.query(queryForCount); - - Query query = - queryService.getQueryFromUrl( - DataElementOperand.class, - filters, - orders, - PaginationUtils.getPaginationData(options), - options.getRootJunction()); - query.setDefaultOrder(); - query.setObjects(dataElementOperands); - - dataElementOperands = (List) queryService.query(query); - Pager pager = metadata.getPager(); - - if (options.hasPaging() && pager == null) { - final long countTotal = isNotEmpty(totalOfItems) ? totalOfItems.size() : 0; - - // fetch the count for the current query from a short-lived cache - - long cachedCountTotal = - !isFilterNullOrBlank(options.get("filter")) - ? paginationCountCache.computeIfAbsent( - calculatePaginationCountKey(currentUser, options), () -> countTotal) - : countTotal; - - pager = new Pager(options.getPage(), cachedCountTotal, options.getPageSize()); - + Pager pager = null; + if (params.isPaging()) { + params.setPaging(false); + Query queryForCount = queryService.getQueryFromUrl(DataElementOperand.class, params); + queryForCount.setObjects(allItems); + + List totalOfItems = queryService.query(queryForCount); + int countTotal = isNotEmpty(totalOfItems) ? totalOfItems.size() : 0; + pager = new Pager(params.getPage(), countTotal, params.getPageSize()); linkService.generatePagerLinks(pager, DataElementOperand.class); + params.setPaging(true); } + Query query = queryService.getQueryFromUrl(DataElementOperand.class, params); + query.setDefaultOrder(); + query.setObjects(allItems); + + List pageItems = (List) queryService.query(query); + RootNode rootNode = NodeUtils.createMetadata(); if (pager != null) { rootNode.addChild(NodeUtils.createPager(pager)); } + List fields = params.getFields(); + if (fields == null || fields.isEmpty()) fields = List.of("*"); + rootNode.addChild( fieldFilterService.toCollectionNode( - DataElementOperand.class, new FieldFilterParams(dataElementOperands, fields))); + DataElementOperand.class, new FieldFilterParams(pageItems, fields))); return rootNode; } - - private String calculatePaginationCountKey(User currentUser, WebOptions options) { - return currentUser.getUsername() - + "." - + "DataElementOperand" - + "." - + options.get("filter") - + "." - + options.getRootJunction().name(); - } - - private boolean isFilterNullOrBlank(String filter) { - return filter == null || filter.isBlank(); - } } diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dimension/DimensionController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dimension/DimensionController.java index 55a792e96f9b..a90fc377956d 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dimension/DimensionController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dimension/DimensionController.java @@ -27,19 +27,16 @@ */ package org.hisp.dhis.webapi.controller.dimension; -import static com.google.common.collect.Lists.newArrayList; import static java.lang.String.format; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.hisp.dhis.common.CodeGenerator.isValidUid; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.common.collect.Lists; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; import lombok.RequiredArgsConstructor; @@ -55,18 +52,16 @@ import org.hisp.dhis.common.Pager; import org.hisp.dhis.commons.jackson.domain.JsonRoot; import org.hisp.dhis.dataset.DataSet; -import org.hisp.dhis.dxf2.common.OrderParams; import org.hisp.dhis.feedback.NotFoundException; import org.hisp.dhis.fieldfilter.FieldFilterParams; import org.hisp.dhis.fieldfiltering.FieldPath; -import org.hisp.dhis.fieldfiltering.FieldPreset; import org.hisp.dhis.hibernate.InternalHibernateGenericStore; import org.hisp.dhis.node.AbstractNode; import org.hisp.dhis.node.Node; import org.hisp.dhis.node.NodeUtils; import org.hisp.dhis.node.types.CollectionNode; import org.hisp.dhis.node.types.RootNode; -import org.hisp.dhis.query.Order; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.query.Query; import org.hisp.dhis.query.QueryParserException; import org.hisp.dhis.user.CurrentUser; @@ -77,8 +72,6 @@ import org.hisp.dhis.webapi.utils.PaginationUtils.PagedEntities; import org.hisp.dhis.webapi.webdomain.StreamingJsonRoot; import org.hisp.dhis.webapi.webdomain.WebMetadata; -import org.hisp.dhis.webapi.webdomain.WebOptions; -import org.hisp.dhis.webapi.webdomain.WebRequestData; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @@ -95,7 +88,8 @@ @Controller @RequestMapping("/api/dimensions") @RequiredArgsConstructor -public class DimensionController extends AbstractCrudController { +public class DimensionController + extends AbstractCrudController { // ------------------------------------------------------------------------- // Dependencies @@ -116,7 +110,7 @@ public class DimensionController extends AbstractCrudController> getObjectList( - @RequestParam Map rpParameters, - OrderParams orderParams, + GetObjectListParams params, HttpServletResponse response, @CurrentUser UserDetails currentUser) { - WebRequestData requestData = applyRequestSetup(rpParameters, orderParams); - PagedEntities pagedEntities = getPagedEntities(requestData); + addProgrammaticModifiers(params); + PagedEntities pagedEntities = getPagedEntities(params); linkService.generatePagerLinks(pagedEntities.pager(), RESOURCE_PATH); return ResponseEntity.ok( @@ -152,14 +140,13 @@ protected DimensionalObject getEntity(String uid, WebOptions options) throws Not pagedEntities.pager(), getSchema().getCollectionName(), org.hisp.dhis.fieldfiltering.FieldFilterParams.of( - pagedEntities.entities(), requestData.fields()))); + pagedEntities.entities(), params.getFieldsJsonList()))); } @Override @GetMapping(produces = {"text/csv", "application/text"}) public ResponseEntity getObjectListCsv( - @RequestParam Map rpParameters, - OrderParams orderParams, + GetObjectListParams params, @CurrentUser UserDetails currentUser, @RequestParam(defaultValue = ",") char separator, @RequestParam(defaultValue = ";") String arraySeparator, @@ -167,11 +154,15 @@ public ResponseEntity getObjectListCsv( HttpServletResponse response) throws IOException { - WebRequestData requestData = applyRequestSetup(rpParameters, orderParams); - PagedEntities pagedEntities = getPagedEntities(requestData); + addProgrammaticModifiers(params); + PagedEntities pagedEntities = getPagedEntities(params); String csv = applyCsvSteps( - requestData.fields(), pagedEntities.entities(), separator, arraySeparator, skipHeader); + params.getFieldsCsvList(), + pagedEntities.entities(), + separator, + arraySeparator, + skipHeader); return ResponseEntity.ok(csv); } @@ -182,47 +173,42 @@ public ResponseEntity getObjectListCsv( @OpenApi.Property(name = "pager", value = Pager.class), @OpenApi.Property(name = "items", value = BaseDimensionalItemObject[].class) }) - @SuppressWarnings("unchecked") @GetMapping("/{uid}/items") - public @ResponseBody RootNode getItems( - @PathVariable String uid, - @RequestParam Map parameters, - OrderParams orderParams) + public @ResponseBody RootNode getItems(@PathVariable String uid, GetObjectListParams params) throws QueryParserException { - List fields = newArrayList(contextService.getParameterValues("fields")); - List filters = newArrayList(contextService.getParameterValues("filter")); - List orders = orderParams.getOrders(getSchema(DimensionalItemObject.class)); - WebOptions options = new WebOptions(parameters); - - if (fields.isEmpty()) { - fields.addAll(FieldPreset.defaultPreset().getFields()); - } // This is the base list used in this flow. It contains only items // allowed to the current user. List readableItems = dimensionService.getCanReadDimensionItems(uid); + // The query engine is just used as a tool to do in-memory filtering // This is needed for two reasons: // 1) We are doing in-memory paging; // 2) We have to count all items respecting the filtering. - Query queryForCount = - queryService.getQueryFromUrl(DimensionalItemObject.class, filters, orders); - queryForCount.setObjects(readableItems); - - List forCountItems = - (List) queryService.query(queryForCount); + boolean paging = params.isPaging(); + int totalOfItems = 0; + if (paging) { + params.setPaging(false); + Query queryForCount = queryService.getQueryFromUrl(DimensionalItemObject.class, params); + queryForCount.setObjects(readableItems); + + List totalItems = queryService.query(queryForCount); + totalOfItems = isNotEmpty(totalItems) ? totalItems.size() : 0; + params.setPaging(true); + } - Query query = - queryService.getQueryFromUrl( - DimensionalItemObject.class, filters, orders, getPaginationData(options)); + Query query = queryService.getQueryFromUrl(DimensionalItemObject.class, params); query.setObjects(readableItems); query.setDefaultOrder(); + @SuppressWarnings("unchecked") List paginatedItems = (List) queryService.query(query); + if (!paging) totalOfItems = paginatedItems.size(); RootNode rootNode = NodeUtils.createMetadata(); + List fields = params.getFieldsJsonList(); CollectionNode collectionNode = rootNode.addChild( oldFieldFilterService.toCollectionNode( @@ -234,8 +220,7 @@ public ResponseEntity getObjectListCsv( } // Adding pagination elements to the root node. - final int totalOfItems = isNotEmpty(forCountItems) ? forCountItems.size() : 0; - dimensionItemPageHandler.addPaginationToNodeIfEnabled(rootNode, options, uid, totalOfItems); + dimensionItemPageHandler.addPaginationToNodeIfEnabled(rootNode, params, uid, totalOfItems); return rootNode; } @@ -324,43 +309,14 @@ public ResponseEntity getDimensionsForDataSet( return ResponseEntity.ok(new JsonRoot("dimensions", objectNodes)); } - private PagedEntities getPagedEntities(WebRequestData requestData) { - List entities = dimensionService.getAllDimensions(); - WebMetadata metadata = new WebMetadata(); - - Query filteredQuery = - queryService.getQueryFromUrl( - DimensionalObject.class, requestData.filters(), requestData.orders()); - filteredQuery.setObjects(entities); + private PagedEntities getPagedEntities(GetObjectListParams params) { + Query filteredQuery = queryService.getQueryFromUrl(DimensionalObject.class, params); + filteredQuery.setObjects(dimensionService.getAllDimensions()); - List filteredEntities = + filteredQuery.setSkipPaging(true); // paging is done post + @SuppressWarnings("unchecked") + List filteredNotPaged = (List) queryService.query(filteredQuery); - return PaginationUtils.addPagingIfEnabled(metadata, requestData.options(), filteredEntities); - } - - /** - * This method performs some generic steps. It can be moved into the {@link - * org.hisp.dhis.webapi.controller.AbstractFullReadOnlyController} so other Controllers can use it - * to avoid duplication. - * - * @param rpParameters request parameters - * @return {@link WebRequestData} record purely for data packaging purposes, containing {@link - * WebOptions}, {@link List} of fields and {@link List} of filters - */ - protected WebRequestData applyRequestSetup( - Map rpParameters, OrderParams orderParams) { - - List fields = Lists.newArrayList(contextService.getParameterValues("fields")); - List filters = Lists.newArrayList(contextService.getParameterValues("filter")); - List orders = orderParams.getOrders(getSchema(DimensionalObject.class)); - - if (fields.isEmpty()) { - fields.addAll(FieldPreset.defaultPreset().getFields()); - } - - WebOptions options = new WebOptions(rpParameters); - forceFiltering(options, filters); - - return new WebRequestData(options, fields, filters, orders); + return PaginationUtils.addPagingIfEnabled(params, filteredNotPaged); } } diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dimension/DimensionItemPageHandler.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dimension/DimensionItemPageHandler.java index ecf4be1f0a53..7cdd65e9a680 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dimension/DimensionItemPageHandler.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dimension/DimensionItemPageHandler.java @@ -36,6 +36,7 @@ import org.hisp.dhis.common.Pager; import org.hisp.dhis.dxf2.common.OrderParams; import org.hisp.dhis.node.types.RootNode; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.service.LinkService; import org.hisp.dhis.webapi.webdomain.WebOptions; import org.springframework.stereotype.Component; @@ -61,7 +62,7 @@ public class DimensionItemPageHandler { * pagination flag must be set to true. See {@link WebOptions#hasPaging(boolean)}. * * @param rootNode the root node where the pagination node will be appended to. - * @param webOptions the WebOptions settings. + * @param params for the paging used * @param dimensionUid the uid of the dimension queried in the API url. See {@link * DimensionController#getItems(String, Map, OrderParams)}. * @param totalOfItems the total of items. This is represented as page total. See {@link @@ -69,14 +70,14 @@ public class DimensionItemPageHandler { */ void addPaginationToNodeIfEnabled( final RootNode rootNode, - final WebOptions webOptions, + final GetObjectListParams params, final String dimensionUid, final int totalOfItems) { - final boolean isPaginationEnabled = webOptions.hasPaging(false); + final boolean isPaginationEnabled = params.isPaging(); if (isPaginationEnabled) { final String apiRelativeUrl = format(RESOURCE_PATH + "/%s/items", dimensionUid); - final Pager pager = new Pager(webOptions.getPage(), totalOfItems, webOptions.getPageSize()); + final Pager pager = new Pager(params.getPage(), totalOfItems, params.getPageSize()); linkService.generatePagerLinks(pager, apiRelativeUrl); rootNode.addChild(createPager(pager)); diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/EventChartController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/EventChartController.java index 3aa5ec0246d1..15070a26b94d 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/EventChartController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/EventChartController.java @@ -37,9 +37,8 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Date; -import java.util.List; -import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import org.hisp.dhis.common.DimensionService; import org.hisp.dhis.common.IllegalQueryException; import org.hisp.dhis.common.OpenApi; @@ -53,6 +52,8 @@ import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.organisationunit.OrganisationUnitService; import org.hisp.dhis.period.Period; +import org.hisp.dhis.query.GetObjectListParams; +import org.hisp.dhis.query.GetObjectParams; import org.hisp.dhis.system.util.CodecUtils; import org.hisp.dhis.user.CurrentUserUtil; import org.hisp.dhis.user.User; @@ -60,7 +61,6 @@ import org.hisp.dhis.visualization.PlotData; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.hisp.dhis.webapi.utils.ContextUtils; -import org.hisp.dhis.webapi.webdomain.WebOptions; import org.jfree.chart.ChartUtilities; import org.jfree.chart.JFreeChart; import org.springframework.beans.factory.annotation.Autowired; @@ -80,7 +80,7 @@ @Deprecated @Controller @RequestMapping("/api/eventCharts") -public class EventChartController extends AbstractCrudController { +public class EventChartController extends AbstractCrudController { @Autowired private EventChartService eventChartService; @Autowired private ChartService chartService; @@ -164,14 +164,18 @@ public void getChart( * @deprecated This is a temporary workaround to keep EventChart backward compatible with the new * EventVisualization entity. Only legacy and chart related types can be returned by this * endpoint. Also, multi-program charts cannot be generated, so they are filtered out. - * @param filters */ @Deprecated @Override - protected void forceFiltering(final WebOptions webOptions, final List filters) { - filters.add("type:!eq:PIVOT_TABLE"); - filters.add("type:!eq:LINE_LIST"); - filters.add("legacy:eq:true"); + protected void addProgrammaticModifiers(GetObjectListParams params) { + addProgrammaticFilters(params::addFilter); + } + + @Override + protected void addProgrammaticFilters(Consumer add) { + add.accept("type:!eq:PIVOT_TABLE"); + add.accept("type:!eq:LINE_LIST"); + add.accept("legacy:eq:true"); } @Override @@ -186,8 +190,7 @@ protected void preUpdateEntity(final EventChart eventChart, final EventChart new } @Override - protected void postProcessResponseEntity( - EventChart eventChart, WebOptions options, Map parameters) { + protected void postProcessResponseEntity(EventChart eventChart, GetObjectParams params) { eventChart.populateAnalyticalProperties(); User currentUser = userService.getUserByUsername(CurrentUserUtil.getCurrentUsername()); diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/EventReportController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/EventReportController.java index d6879917b166..b265b1e17e13 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/EventReportController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/EventReportController.java @@ -36,9 +36,8 @@ import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; -import java.util.List; -import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import org.hisp.dhis.common.DimensionService; import org.hisp.dhis.common.IllegalQueryException; import org.hisp.dhis.common.OpenApi; @@ -48,10 +47,11 @@ import org.hisp.dhis.i18n.I18nManager; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.period.Period; +import org.hisp.dhis.query.GetObjectListParams; +import org.hisp.dhis.query.GetObjectParams; import org.hisp.dhis.user.CurrentUserUtil; import org.hisp.dhis.user.User; import org.hisp.dhis.webapi.controller.AbstractCrudController; -import org.hisp.dhis.webapi.webdomain.WebOptions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -66,7 +66,8 @@ @Deprecated @Controller @RequestMapping("/api/eventReports") -public class EventReportController extends AbstractCrudController { +public class EventReportController + extends AbstractCrudController { @Autowired private DimensionService dimensionService; @Autowired private I18nManager i18nManager; @@ -103,13 +104,17 @@ protected EventReport deserializeXmlEntity(HttpServletRequest request) throws IO * @deprecated This is a temporary workaround to keep EventReport backward compatible with the new * EventVisualization entity. Only legacy and report related types can be returned by this * endpoint. - * @param filters */ @Deprecated @Override - protected void forceFiltering(final WebOptions webOptions, final List filters) { - filters.add("type:in:[PIVOT_TABLE,LINE_LIST]"); - filters.add("legacy:eq:true"); + protected void addProgrammaticModifiers(GetObjectListParams params) { + addProgrammaticFilters(params::addFilter); + } + + @Override + protected void addProgrammaticFilters(Consumer add) { + add.accept("type:in:[PIVOT_TABLE,LINE_LIST]"); + add.accept("legacy:eq:true"); } @Override @@ -124,8 +129,7 @@ protected void preUpdateEntity(final EventReport eventReport, final EventReport } @Override - protected void postProcessResponseEntity( - EventReport report, WebOptions options, Map parameters) { + protected void postProcessResponseEntity(EventReport report, GetObjectParams params) { report.populateAnalyticalProperties(); User currentUser = userService.getUserByUsername(CurrentUserUtil.getCurrentUsername()); diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/EventVisualizationController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/EventVisualizationController.java index d2e175403294..39a73a9c89c5 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/EventVisualizationController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/EventVisualizationController.java @@ -44,7 +44,6 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.Function; @@ -65,6 +64,8 @@ import org.hisp.dhis.period.Period; import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramService; +import org.hisp.dhis.query.GetObjectListParams; +import org.hisp.dhis.query.GetObjectParams; import org.hisp.dhis.user.CurrentUserUtil; import org.hisp.dhis.user.User; import org.hisp.dhis.visualization.ChartService; @@ -72,7 +73,6 @@ import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.hisp.dhis.webapi.mvc.annotation.ApiVersion; import org.hisp.dhis.webapi.utils.ContextUtils; -import org.hisp.dhis.webapi.webdomain.WebOptions; import org.jfree.chart.ChartUtilities; import org.jfree.chart.JFreeChart; import org.springframework.stereotype.Controller; @@ -91,7 +91,8 @@ @ApiVersion({DEFAULT, ALL}) @AllArgsConstructor @OpenApi.Document(classifiers = {"team:tracker", "purpose:metadata"}) -public class EventVisualizationController extends AbstractCrudController { +public class EventVisualizationController + extends AbstractCrudController { private final DimensionService dimensionService; private final LegendSetService legendSetService; @@ -161,7 +162,7 @@ protected EventVisualization deserializeXmlEntity(HttpServletRequest request) th @Override protected void postProcessResponseEntity( - EventVisualization eventVisualization, WebOptions options, Map parameters) { + EventVisualization eventVisualization, GetObjectParams params) { eventVisualization.populateAnalyticalProperties(); User currentUser = userService.getUserByUsername(CurrentUserUtil.getCurrentUsername()); diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramController.java index 83feef749591..b1b3a4434d59 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramController.java @@ -30,12 +30,12 @@ import static org.hisp.dhis.dxf2.webmessage.WebMessageUtils.created; import static org.hisp.dhis.dxf2.webmessage.WebMessageUtils.notFound; -import com.google.common.collect.Lists; import java.util.Collection; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.SetValuedMap; @@ -47,15 +47,11 @@ import org.hisp.dhis.feedback.ConflictException; import org.hisp.dhis.feedback.ForbiddenException; import org.hisp.dhis.feedback.NotFoundException; -import org.hisp.dhis.fieldfilter.Defaults; import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramService; -import org.hisp.dhis.query.Order; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.query.Query; -import org.hisp.dhis.query.QueryParserException; import org.hisp.dhis.webapi.controller.AbstractCrudController; -import org.hisp.dhis.webapi.webdomain.WebMetadata; -import org.hisp.dhis.webapi.webdomain.WebOptions; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -75,47 +71,25 @@ @RequestMapping("/api/programs") @RequiredArgsConstructor @OpenApi.Document(classifiers = {"team:tracker", "purpose:metadata"}) -public class ProgramController extends AbstractCrudController { +public class ProgramController + extends AbstractCrudController { + + @Data + @EqualsAndHashCode(callSuper = true) + public static class GetProgramObjectListParams extends GetObjectListParams { + boolean userFilter; + } + private final ProgramService programService; private final CopyService copyService; @Override - @SuppressWarnings("unchecked") - protected List getEntityList( - WebMetadata metadata, - WebOptions options, - List filters, - List orders, - List objects) - throws QueryParserException { - boolean userFilter = Boolean.parseBoolean(options.getOptions().get("userFilter")); - - List entityList; - Query query = - queryService.getQueryFromUrl( - getEntityClass(), - filters, - orders, - getPaginationData(options), - options.getRootJunction()); - query.setDefaultOrder(); - query.setDefaults(Defaults.valueOf(options.get("defaults", DEFAULTS))); - - if (objects == null && options.getOptions().containsKey("query")) { - entityList = - Lists.newArrayList(manager.filter(getEntityClass(), options.getOptions().get("query"))); - } else { - entityList = (List) queryService.query(query); - } - - if (userFilter) { - List programs = programService.getCurrentUserPrograms(); - entityList.retainAll(programs); - metadata.setPager(null); + protected void modifyGetObjectList(GetProgramObjectListParams params, Query query) { + if (params.isUserFilter()) { + query.setSkipSharing(true); + query.setDataSharing(true); } - - return entityList; } @GetMapping("/{uid}/metadata") diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramDataElementController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramDataElementController.java index 11e9a508aab9..04dc42092f5b 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramDataElementController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramDataElementController.java @@ -27,33 +27,27 @@ */ package org.hisp.dhis.webapi.controller.event; -import com.google.common.collect.Lists; +import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; + import java.util.List; -import java.util.Map; +import lombok.RequiredArgsConstructor; import org.hisp.dhis.common.DhisApiVersion; import org.hisp.dhis.common.DimensionalItemObject; import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.common.Pager; -import org.hisp.dhis.common.PagerUtils; -import org.hisp.dhis.dxf2.common.OrderParams; +import org.hisp.dhis.common.UID; import org.hisp.dhis.fieldfilter.FieldFilterParams; import org.hisp.dhis.fieldfilter.FieldFilterService; -import org.hisp.dhis.fieldfiltering.FieldPreset; import org.hisp.dhis.node.NodeUtils; import org.hisp.dhis.node.types.RootNode; +import org.hisp.dhis.program.Program; import org.hisp.dhis.program.ProgramDataElementDimensionItem; import org.hisp.dhis.program.ProgramService; -import org.hisp.dhis.query.Order; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.query.Query; import org.hisp.dhis.query.QueryParserException; import org.hisp.dhis.query.QueryService; -import org.hisp.dhis.schema.Schema; -import org.hisp.dhis.schema.SchemaService; import org.hisp.dhis.webapi.mvc.annotation.ApiVersion; -import org.hisp.dhis.webapi.service.ContextService; -import org.hisp.dhis.webapi.utils.PaginationUtils; -import org.hisp.dhis.webapi.webdomain.WebMetadata; -import org.hisp.dhis.webapi.webdomain.WebOptions; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -63,78 +57,48 @@ /** * @author Lars Helge Overland */ +@OpenApi.EntityType(ProgramDataElementDimensionItem.class) @OpenApi.Document( entity = DimensionalItemObject.class, classifiers = {"team:tracker", "purpose:metadata"}) @Controller @RequestMapping("/api/programDataElements") +@RequiredArgsConstructor @ApiVersion({DhisApiVersion.DEFAULT, DhisApiVersion.ALL}) public class ProgramDataElementController { - private final QueryService queryService; + private final QueryService queryService; private final FieldFilterService fieldFilterService; - - private final ContextService contextService; - - private final SchemaService schemaService; - private final ProgramService programService; - public ProgramDataElementController( - QueryService queryService, - FieldFilterService fieldFilterService, - ContextService contextService, - SchemaService schemaService, - ProgramService programService) { - this.queryService = queryService; - this.fieldFilterService = fieldFilterService; - this.contextService = contextService; - this.schemaService = schemaService; - this.programService = programService; - } - @GetMapping - @SuppressWarnings("unchecked") public @ResponseBody RootNode getObjectList( - @RequestParam Map rpParameters, OrderParams orderParams) + @OpenApi.Param({UID.class, Program.class}) @RequestParam String program, + GetObjectListParams params) throws QueryParserException { - Schema schema = schemaService.getDynamicSchema(ProgramDataElementDimensionItem.class); - List fields = Lists.newArrayList(contextService.getParameterValues("fields")); - List filters = Lists.newArrayList(contextService.getParameterValues("filter")); - List orders = orderParams.getOrders(schema); - - if (fields.isEmpty()) { - fields.addAll(FieldPreset.ALL.getFields()); + List allItems = + programService.getGeneratedProgramDataElements(program); + + Pager pager = null; + if (params.isPaging()) { + params.setPaging(false); + Query queryForCount = + queryService.getQueryFromUrl(ProgramDataElementDimensionItem.class, params); + queryForCount.setObjects(allItems); + List totalOfItems = queryService.query(queryForCount); + int countTotal = isNotEmpty(totalOfItems) ? totalOfItems.size() : 0; + pager = new Pager(params.getPage(), countTotal, params.getPageSize()); + params.setPaging(true); } - WebOptions options = new WebOptions(rpParameters); - WebMetadata metadata = new WebMetadata(); - - List programDataElements; - Query query = - queryService.getQueryFromUrl( - ProgramDataElementDimensionItem.class, - filters, - orders, - PaginationUtils.getPaginationData(options), - options.getRootJunction()); + Query query = queryService.getQueryFromUrl(ProgramDataElementDimensionItem.class, params); query.setDefaultOrder(); + query.setObjects(allItems); - if (options.contains("program")) { - String programUid = options.get("program"); - programDataElements = programService.getGeneratedProgramDataElements(programUid); - query.setObjects(programDataElements); - } - - programDataElements = (List) queryService.query(query); - - Pager pager = metadata.getPager(); - - if (options.hasPaging() && pager == null) { - pager = new Pager(options.getPage(), programDataElements.size(), options.getPageSize()); - programDataElements = PagerUtils.pageCollection(programDataElements, pager); - } + @SuppressWarnings("unchecked") + List pageItems = + (List) queryService.query(query); RootNode rootNode = NodeUtils.createMetadata(); @@ -142,10 +106,11 @@ public ProgramDataElementController( rootNode.addChild(NodeUtils.createPager(pager)); } + List fields = params.getFields(); + if (fields == null || fields.isEmpty()) fields = List.of("*"); rootNode.addChild( fieldFilterService.toCollectionNode( - ProgramDataElementDimensionItem.class, - new FieldFilterParams(programDataElements, fields))); + ProgramDataElementDimensionItem.class, new FieldFilterParams(pageItems, fields))); return rootNode; } diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramIndicatorController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramIndicatorController.java index 9eae6a24b4af..8e3a63f021e1 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramIndicatorController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramIndicatorController.java @@ -41,6 +41,7 @@ import org.hisp.dhis.i18n.I18nManager; import org.hisp.dhis.program.ProgramIndicator; import org.hisp.dhis.program.ProgramIndicatorService; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; @@ -56,7 +57,8 @@ @RequestMapping("/api/programIndicators") @RequiredArgsConstructor @OpenApi.Document(classifiers = {"team:tracker", "purpose:metadata"}) -public class ProgramIndicatorController extends AbstractCrudController { +public class ProgramIndicatorController + extends AbstractCrudController { private final ProgramIndicatorService programIndicatorService; private final I18nManager i18nManager; diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramIndicatorGroupController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramIndicatorGroupController.java index 102e6808dbe4..1eaf2f9e22b9 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramIndicatorGroupController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramIndicatorGroupController.java @@ -29,6 +29,7 @@ import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.program.ProgramIndicatorGroup; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -40,4 +41,4 @@ @RequestMapping("/api/programIndicatorGroups") @OpenApi.Document(classifiers = {"team:tracker", "purpose:metadata"}) public class ProgramIndicatorGroupController - extends AbstractCrudController {} + extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramRuleActionController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramRuleActionController.java index ee7eef49ca64..86bc2957e99e 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramRuleActionController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramRuleActionController.java @@ -40,6 +40,7 @@ import org.hisp.dhis.i18n.I18nManager; import org.hisp.dhis.program.ProgramIndicator; import org.hisp.dhis.programrule.ProgramRuleAction; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.rules.models.RuleValidationResult; import org.hisp.dhis.tracker.imports.programrule.engine.ProgramRuleEngine; import org.hisp.dhis.webapi.controller.AbstractCrudController; @@ -58,7 +59,8 @@ @RequiredArgsConstructor @RequestMapping("/api/programRuleActions") @OpenApi.Document(classifiers = {"team:tracker", "purpose:metadata"}) -public class ProgramRuleActionController extends AbstractCrudController { +public class ProgramRuleActionController + extends AbstractCrudController { private final I18nManager i18nManager; private final ProgramRuleEngine programRuleEngine; diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramRuleController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramRuleController.java index 8f36fefa5855..dc1d27783311 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramRuleController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramRuleController.java @@ -40,6 +40,7 @@ import org.hisp.dhis.i18n.I18nManager; import org.hisp.dhis.program.ProgramIndicator; import org.hisp.dhis.programrule.ProgramRule; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.rules.models.RuleValidationResult; import org.hisp.dhis.tracker.imports.programrule.engine.ProgramRuleEngine; import org.hisp.dhis.webapi.controller.AbstractCrudController; @@ -58,7 +59,8 @@ @AllArgsConstructor @RequestMapping("/api/programRules") @OpenApi.Document(classifiers = {"team:tracker", "purpose:metadata"}) -public class ProgramRuleController extends AbstractCrudController { +public class ProgramRuleController + extends AbstractCrudController { private final I18nManager i18nManager; private final ProgramRuleEngine programRuleEngine; diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramRuleVariableController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramRuleVariableController.java index 9a99747240e4..4a2cad0dfc17 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramRuleVariableController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramRuleVariableController.java @@ -29,6 +29,7 @@ import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.programrule.ProgramRuleVariable; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -39,4 +40,5 @@ @Controller @RequestMapping("/api/programRuleVariables") @OpenApi.Document(classifiers = {"team:tracker", "purpose:metadata"}) -public class ProgramRuleVariableController extends AbstractCrudController {} +public class ProgramRuleVariableController + extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramSectionController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramSectionController.java index 9471bbb92f7e..5df0b4da246b 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramSectionController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramSectionController.java @@ -29,6 +29,7 @@ import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.program.ProgramSection; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -39,4 +40,5 @@ @Controller @RequestMapping("/api/programSections") @OpenApi.Document(classifiers = {"team:tracker", "purpose:metadata"}) -public class ProgramSectionController extends AbstractCrudController {} +public class ProgramSectionController + extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramStageController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramStageController.java index ef3c848fbc87..471b99d59459 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramStageController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramStageController.java @@ -29,6 +29,7 @@ import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.program.ProgramStage; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -39,4 +40,5 @@ @Controller @RequestMapping("/api/programStages") @OpenApi.Document(classifiers = {"team:tracker", "purpose:metadata"}) -public class ProgramStageController extends AbstractCrudController {} +public class ProgramStageController + extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramStageSectionController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramStageSectionController.java index c4367dab271c..e479fbc039fa 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramStageSectionController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/ProgramStageSectionController.java @@ -29,6 +29,7 @@ import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.program.ProgramStageSection; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -39,4 +40,5 @@ @Controller @RequestMapping("/api/programStageSections") @OpenApi.Document(classifiers = {"team:tracker", "purpose:metadata"}) -public class ProgramStageSectionController extends AbstractCrudController {} +public class ProgramStageSectionController + extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/RelationshipTypeController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/RelationshipTypeController.java index 1afac30e4630..1f0c28996f23 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/RelationshipTypeController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/RelationshipTypeController.java @@ -28,6 +28,7 @@ package org.hisp.dhis.webapi.controller.event; import org.hisp.dhis.common.OpenApi; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.relationship.RelationshipType; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.springframework.stereotype.Controller; @@ -39,4 +40,5 @@ @Controller @RequestMapping("/api/relationshipTypes") @OpenApi.Document(classifiers = {"team:tracker", "purpose:metadata"}) -public class RelationshipTypeController extends AbstractCrudController {} +public class RelationshipTypeController + extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/TrackedEntityAttributeController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/TrackedEntityAttributeController.java index b2a6b63e2df5..fd0d5213e8ab 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/TrackedEntityAttributeController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/TrackedEntityAttributeController.java @@ -37,10 +37,13 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import lombok.Data; +import lombok.EqualsAndHashCode; import org.hisp.dhis.common.DhisApiVersion; import org.hisp.dhis.common.IdentifiableObject; import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.dxf2.webmessage.WebMessageException; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.reservedvalue.ReserveValueException; import org.hisp.dhis.reservedvalue.ReservedValue; import org.hisp.dhis.reservedvalue.ReservedValueService; @@ -53,7 +56,6 @@ import org.hisp.dhis.webapi.mvc.annotation.ApiVersion; import org.hisp.dhis.webapi.service.ContextService; import org.hisp.dhis.webapi.utils.ContextUtils; -import org.hisp.dhis.webapi.webdomain.WebOptions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.util.StringUtils; @@ -70,7 +72,9 @@ @RequestMapping("/api/trackedEntityAttributes") @OpenApi.Document(classifiers = {"team:tracker", "purpose:metadata"}) public class TrackedEntityAttributeController - extends AbstractCrudController { + extends AbstractCrudController< + TrackedEntityAttribute, + TrackedEntityAttributeController.GetTrackedEntityAttributeObjectListParams> { @Autowired private TrackedEntityAttributeService trackedEntityAttributeService; @@ -80,6 +84,12 @@ public class TrackedEntityAttributeController @Autowired private ContextService context; + @Data + @EqualsAndHashCode(callSuper = true) + public static final class GetTrackedEntityAttributeObjectListParams extends GetObjectListParams { + boolean indexableOnly; + } + @GetMapping( value = "/{id}/generateAndReserve", produces = {ContextUtils.CONTENT_TYPE_JSON, ContextUtils.CONTENT_TYPE_JAVASCRIPT}) @@ -167,7 +177,7 @@ private Map getRequiredValues( requiredValues.removeAll(result.keySet()); - if (requiredValues.size() > 0) { + if (!requiredValues.isEmpty()) { throw new WebMessageException( conflict( "Missing required values: " @@ -178,12 +188,11 @@ private Map getRequiredValues( } @Override - protected void forceFiltering(final WebOptions webOptions, final List filters) { - if (webOptions == null || !webOptions.isTrue("indexableOnly")) { - return; - } + protected void addProgrammaticModifiers(GetTrackedEntityAttributeObjectListParams params) { + if (!params.isIndexableOnly()) return; - if (filters.stream().anyMatch(f -> f.startsWith("id:"))) { + List filters = params.getFilters(); + if (filters != null && filters.stream().anyMatch(f -> f.startsWith("id:"))) { throw new IllegalArgumentException( "indexableOnly parameter cannot be set if a separate filter for id is specified"); } @@ -191,13 +200,13 @@ protected void forceFiltering(final WebOptions webOptions, final List fi Set indexableTeas = trackedEntityAttributeService.getAllTrigramIndexableTrackedEntityAttributes(); - StringBuilder sb = new StringBuilder("id:in:"); - sb.append( - indexableTeas.stream() - .map(IdentifiableObject::getUid) - .collect(Collectors.joining(",", "[", "]"))); + String filter = + "id:in:" + + indexableTeas.stream() + .map(IdentifiableObject::getUid) + .collect(Collectors.joining(",", "[", "]")); - filters.add(sb.toString()); + params.addFilter(filter); } private TrackedEntityAttribute getTrackedEntityAttribute(String id) throws WebMessageException { diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/TrackedEntityTypeController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/TrackedEntityTypeController.java index f536571a9c6a..269b56d57c89 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/TrackedEntityTypeController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/event/TrackedEntityTypeController.java @@ -28,6 +28,7 @@ package org.hisp.dhis.webapi.controller.event; import org.hisp.dhis.common.OpenApi; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.trackedentity.TrackedEntityType; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.springframework.stereotype.Controller; @@ -39,4 +40,5 @@ @Controller @RequestMapping("/api/trackedEntityTypes") @OpenApi.Document(classifiers = {"team:tracker", "purpose:metadata"}) -public class TrackedEntityTypeController extends AbstractCrudController {} +public class TrackedEntityTypeController + extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/indicator/IndicatorController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/indicator/IndicatorController.java index 42558e221213..6c4bacd80d01 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/indicator/IndicatorController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/indicator/IndicatorController.java @@ -46,8 +46,8 @@ import org.hisp.dhis.i18n.I18nManager; import org.hisp.dhis.indicator.Indicator; import org.hisp.dhis.merge.MergeParams; -import org.hisp.dhis.merge.MergeProcessor; -import org.hisp.dhis.merge.MergeType; +import org.hisp.dhis.merge.MergeService; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.security.RequiresAuthority; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.springframework.http.HttpStatus; @@ -65,14 +65,14 @@ @Slf4j @RequiredArgsConstructor @RequestMapping("/api/indicators") -public class IndicatorController extends AbstractCrudController { +public class IndicatorController extends AbstractCrudController { private final ExpressionService expressionService; private final ExpressionResolverCollection resolvers; private final I18nManager i18nManager; - private final MergeProcessor indicatorMergeProcessor; + private final MergeService indicatorMergeService; @PostMapping(value = "/expression/description", produces = APPLICATION_JSON_VALUE) @ResponseBody @@ -102,9 +102,8 @@ public WebMessage getExpressionDescription(@RequestBody String expression) { public @ResponseBody WebMessage mergeIndicators(@RequestBody MergeParams params) throws ConflictException { log.info("Indicator merge received"); - params.setMergeType(MergeType.INDICATOR); - MergeReport report = indicatorMergeProcessor.processMerge(params); + MergeReport report = indicatorMergeService.processMerge(params); log.info("Indicator merge processed with report: {}", report); return WebMessageUtils.mergeReport(report); diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/indicator/IndicatorGroupController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/indicator/IndicatorGroupController.java index e04377a1ca40..4b82e2c4ef31 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/indicator/IndicatorGroupController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/indicator/IndicatorGroupController.java @@ -28,6 +28,7 @@ package org.hisp.dhis.webapi.controller.indicator; import org.hisp.dhis.indicator.IndicatorGroup; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -37,4 +38,5 @@ */ @Controller @RequestMapping("/api/indicatorGroups") -public class IndicatorGroupController extends AbstractCrudController {} +public class IndicatorGroupController + extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/indicator/IndicatorGroupSetController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/indicator/IndicatorGroupSetController.java index c2e09ddcc168..e8427918d517 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/indicator/IndicatorGroupSetController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/indicator/IndicatorGroupSetController.java @@ -28,6 +28,7 @@ package org.hisp.dhis.webapi.controller.indicator; import org.hisp.dhis.indicator.IndicatorGroupSet; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -37,4 +38,5 @@ */ @Controller @RequestMapping("/api/indicatorGroupSets") -public class IndicatorGroupSetController extends AbstractCrudController {} +public class IndicatorGroupSetController + extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/indicator/IndicatorTypeController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/indicator/IndicatorTypeController.java index 6bab10edd76e..ac1f15f029f7 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/indicator/IndicatorTypeController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/indicator/IndicatorTypeController.java @@ -38,8 +38,8 @@ import org.hisp.dhis.feedback.MergeReport; import org.hisp.dhis.indicator.IndicatorType; import org.hisp.dhis.merge.MergeParams; -import org.hisp.dhis.merge.MergeProcessor; -import org.hisp.dhis.merge.MergeType; +import org.hisp.dhis.merge.MergeService; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.security.RequiresAuthority; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.springframework.http.HttpStatus; @@ -57,9 +57,10 @@ @RequestMapping("/api/indicatorTypes") @RequiredArgsConstructor @Slf4j -public class IndicatorTypeController extends AbstractCrudController { +public class IndicatorTypeController + extends AbstractCrudController { - private final MergeProcessor indicatorTypeMergeProcessor; + private final MergeService indicatorTypeMergeService; @ResponseStatus(HttpStatus.OK) @RequiresAuthority(anyOf = F_INDICATOR_TYPE_MERGE) @@ -67,9 +68,8 @@ public class IndicatorTypeController extends AbstractCrudController {} +public class ExternalMapLayerController + extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/mapping/MapController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/mapping/MapController.java index 3e50e3776988..dea82258b4c8 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/mapping/MapController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/mapping/MapController.java @@ -64,6 +64,8 @@ import org.hisp.dhis.period.Period; import org.hisp.dhis.program.ProgramService; import org.hisp.dhis.program.ProgramStageService; +import org.hisp.dhis.query.GetObjectListParams; +import org.hisp.dhis.query.GetObjectParams; import org.hisp.dhis.schema.MetadataMergeParams; import org.hisp.dhis.trackedentity.TrackedEntityType; import org.hisp.dhis.user.CurrentUser; @@ -73,7 +75,6 @@ import org.hisp.dhis.user.UserService; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.hisp.dhis.webapi.utils.ContextUtils; -import org.hisp.dhis.webapi.webdomain.WebOptions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; @@ -92,7 +93,7 @@ @Controller @RequestMapping("/api/maps") @OpenApi.Document(classifiers = {"team:analytics", "purpose:metadata"}) -public class MapController extends AbstractCrudController { +public class MapController extends AbstractCrudController { private static final int MAP_MIN_WIDTH = 140; private static final int MAP_MIN_HEIGHT = 25; @@ -208,8 +209,7 @@ public void getMapData( // -------------------------------------------------------------------------- @Override - public void postProcessResponseEntity( - Map map, WebOptions options, java.util.Map parameters) { + public void postProcessResponseEntity(Map map, GetObjectParams params) { I18nFormat format = i18nManager.getI18nFormat(); User currentUser = userService.getUserByUsername(CurrentUserUtil.getCurrentUsername()); diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/mapping/MapViewController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/mapping/MapViewController.java index 94ff7fd06991..c20b09ea4403 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/mapping/MapViewController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/mapping/MapViewController.java @@ -29,27 +29,20 @@ import static org.hisp.dhis.dxf2.webmessage.WebMessageUtils.notFound; -import com.google.common.collect.Lists; import jakarta.servlet.http.HttpServletResponse; import java.awt.image.BufferedImage; -import java.util.List; import javax.imageio.ImageIO; import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.common.cache.CacheStrategy; import org.hisp.dhis.dxf2.webmessage.WebMessageException; -import org.hisp.dhis.fieldfilter.Defaults; import org.hisp.dhis.mapgeneration.MapGenerationService; import org.hisp.dhis.mapping.MapView; import org.hisp.dhis.mapping.MappingService; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.organisationunit.OrganisationUnitService; -import org.hisp.dhis.query.Order; -import org.hisp.dhis.query.Query; -import org.hisp.dhis.query.QueryParserException; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.hisp.dhis.webapi.utils.ContextUtils; -import org.hisp.dhis.webapi.webdomain.WebMetadata; -import org.hisp.dhis.webapi.webdomain.WebOptions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -64,7 +57,7 @@ @Controller @RequestMapping("/api/mapViews") @OpenApi.Document(classifiers = {"team:analytics", "purpose:metadata"}) -public class MapViewController extends AbstractCrudController { +public class MapViewController extends AbstractCrudController { @Autowired private MappingService mappingService; @Autowired private OrganisationUnitService organisationUnitService; @@ -109,40 +102,6 @@ public void getMapView( renderMapViewPng(mapView, response); } - // -------------------------------------------------------------------------- - // Hooks - // -------------------------------------------------------------------------- - - @Override - @SuppressWarnings("unchecked") - protected List getEntityList( - WebMetadata metadata, - WebOptions options, - List filters, - List orders, - List objects) - throws QueryParserException { - List entityList; - Query query = - queryService.getQueryFromUrl( - getEntityClass(), - filters, - orders, - getPaginationData(options), - options.getRootJunction()); - query.setDefaultOrder(); - query.setDefaults(Defaults.valueOf(options.get("defaults", DEFAULTS))); - - if (objects == null && options.getOptions().containsKey("query")) { - entityList = - Lists.newArrayList(manager.filter(getEntityClass(), options.getOptions().get("query"))); - } else { - entityList = (List) queryService.query(query); - } - - return entityList; - } - // -------------------------------------------------------------------------- // Supportive methods // -------------------------------------------------------------------------- diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/message/ProgramMessageController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/message/ProgramMessageController.java index 8cb40bcda07b..6a93abdd7364 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/message/ProgramMessageController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/message/ProgramMessageController.java @@ -45,6 +45,7 @@ import org.hisp.dhis.program.message.ProgramMessageOperationParams; import org.hisp.dhis.program.message.ProgramMessageService; import org.hisp.dhis.program.message.ProgramMessageStatus; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.security.RequiresAuthority; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.hisp.dhis.webapi.mvc.annotation.ApiVersion; @@ -60,7 +61,8 @@ @RequestMapping("/api/messages") @ApiVersion({DhisApiVersion.DEFAULT, DhisApiVersion.ALL}) @OpenApi.Document(classifiers = {"team:tracker", "purpose:metadata"}) -public class ProgramMessageController extends AbstractCrudController { +public class ProgramMessageController + extends AbstractCrudController { @Autowired private ProgramMessageService programMessageService; @Autowired protected ProgramMessageRequestParamMapper requestParamMapper; diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/notification/DataSetNotificationTemplateController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/notification/DataSetNotificationTemplateController.java index 95664fd68445..d16e81422259 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/notification/DataSetNotificationTemplateController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/notification/DataSetNotificationTemplateController.java @@ -30,6 +30,7 @@ import org.hisp.dhis.common.DhisApiVersion; import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.dataset.notifications.DataSetNotificationTemplate; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.hisp.dhis.webapi.mvc.annotation.ApiVersion; import org.springframework.stereotype.Controller; @@ -41,4 +42,4 @@ @ApiVersion(include = {DhisApiVersion.DEFAULT, DhisApiVersion.ALL}) @OpenApi.Document(classifiers = {"team:tracker", "purpose:metadata"}) public class DataSetNotificationTemplateController - extends AbstractCrudController {} + extends AbstractCrudController {} diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/notification/ProgramNotificationTemplateController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/notification/ProgramNotificationTemplateController.java index c2d1bddc058d..ffb35a318c54 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/notification/ProgramNotificationTemplateController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/notification/ProgramNotificationTemplateController.java @@ -38,6 +38,7 @@ import org.hisp.dhis.program.notification.ProgramNotificationTemplate; import org.hisp.dhis.program.notification.ProgramNotificationTemplateOperationParams; import org.hisp.dhis.program.notification.ProgramNotificationTemplateService; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.schema.descriptors.ProgramNotificationTemplateSchemaDescriptor; import org.hisp.dhis.security.RequiresAuthority; import org.hisp.dhis.webapi.controller.AbstractCrudController; @@ -56,7 +57,7 @@ @ApiVersion(include = {DhisApiVersion.DEFAULT, DhisApiVersion.ALL}) @OpenApi.Document(classifiers = {"team:tracker", "purpose:metadata"}) public class ProgramNotificationTemplateController - extends AbstractCrudController { + extends AbstractCrudController { private final ProgramNotificationTemplateService programNotificationTemplateService; private final ProgramNotificationTemplateRequestParamsMapper requestParamsMapper; diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/option/OptionController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/option/OptionController.java index ee7ad49ddfb5..2a291fbac7ab 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/option/OptionController.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/option/OptionController.java @@ -29,6 +29,7 @@ import org.hisp.dhis.common.OpenApi; import org.hisp.dhis.option.Option; +import org.hisp.dhis.query.GetObjectListParams; import org.hisp.dhis.webapi.controller.AbstractCrudController; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -39,4 +40,4 @@ @Controller @RequestMapping("/api/options") @OpenApi.Document(classifiers = {"team:platform", "purpose:metadata"}) -public class OptionController extends AbstractCrudController