From ea48fb867a1cc933be5caad2c92a727b0e0b5c56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Fri, 17 Oct 2025 15:37:05 +0200 Subject: [PATCH 001/119] fix: Update code --- .../org/hisp/dhis/util/CollectionUtils.java | 15 +++++++++++++++ .../org/hisp/dhis/util/CollectionUtilsTest.java | 17 +++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/main/java/org/hisp/dhis/util/CollectionUtils.java b/src/main/java/org/hisp/dhis/util/CollectionUtils.java index 56c91aa0..c55c8575 100644 --- a/src/main/java/org/hisp/dhis/util/CollectionUtils.java +++ b/src/main/java/org/hisp/dhis/util/CollectionUtils.java @@ -234,6 +234,21 @@ public static T firstMatch(Collection collection, Predicate predicate) return collection.stream().filter(predicate).findFirst().orElse(null); } + /** + * Indicates if any item in the given collection is a prefix of the given input string. + * + * @param collection the collection. + * @param input the input string. + * @return true if any item is a prefix, false otherwise. + */ + public static boolean anyStartsWith(Collection collection, String input) { + if (input == null) { + return false; + } + + return collection.stream().anyMatch(item -> input.startsWith(item)); + } + /** * Returns a new mutable list of the items in the given collection which match the given * predicate. diff --git a/src/test/java/org/hisp/dhis/util/CollectionUtilsTest.java b/src/test/java/org/hisp/dhis/util/CollectionUtilsTest.java index a3f30803..b75b976c 100644 --- a/src/test/java/org/hisp/dhis/util/CollectionUtilsTest.java +++ b/src/test/java/org/hisp/dhis/util/CollectionUtilsTest.java @@ -28,6 +28,7 @@ package org.hisp.dhis.util; import static org.hisp.dhis.support.Assertions.assertContainsExactly; +import static org.hisp.dhis.util.CollectionUtils.anyStartsWith; import static org.hisp.dhis.util.CollectionUtils.first; import static org.hisp.dhis.util.CollectionUtils.firstMatch; import static org.hisp.dhis.util.CollectionUtils.get; @@ -87,6 +88,22 @@ void testFirstMatch() { assertNull(firstMatch(list, (v) -> "x".equals(v))); } + @Test + void testAnyStartsWith() { + List list = + list("/ImspTQPwCqd/jUb8gELQApl", "/ImspTQPwCqd/qhqAxPSTUXp", "/ImspTQPwCqd/Vth0fbpFcsO"); + + assertTrue(anyStartsWith(list, "/ImspTQPwCqd/jUb8gELQApl")); + assertTrue(anyStartsWith(list, "/ImspTQPwCqd/jUb8gELQApl/U6Kr7Gtpidn")); + assertTrue(anyStartsWith(list, "/ImspTQPwCqd/qhqAxPSTUXp/RndxKqQGzUl/aSfF9kuNINJ")); + assertTrue(anyStartsWith(list, "/ImspTQPwCqd/Vth0fbpFcsO/U6Kr7Gtpidn/rx9ubw0UCqj")); + + assertFalse(anyStartsWith(list, "/MpcMjLmbATv")); + assertFalse(anyStartsWith(list, "/ImspTQPwCqd/at6UHUQatSo/qtr8GGlm4gg")); + assertFalse(anyStartsWith(list, "/ImspTQPwCqd/PMa2VCrupOd/FlBemv1NfEC/rxc497GUdDt")); + assertFalse(anyStartsWith(list, null)); + } + @Test void testSetAcceptsNull() { Set set = set("a", null, "b"); From 2a38950fb0cd260c47ec0e6e939bf85bf5438be3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Fri, 17 Oct 2025 15:43:50 +0200 Subject: [PATCH 002/119] fix: Update code --- src/main/java/org/hisp/dhis/Dhis2.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/hisp/dhis/Dhis2.java b/src/main/java/org/hisp/dhis/Dhis2.java index 6d99956f..8002e4a9 100644 --- a/src/main/java/org/hisp/dhis/Dhis2.java +++ b/src/main/java/org/hisp/dhis/Dhis2.java @@ -3665,7 +3665,7 @@ public ObjectResponse removeUserRole(String id) { /** * Saves a {@link Visualization}. * - * @param userRole the object to save. + * @param visualization the object to save. * @return {@link ObjectResponse} holding information about the operation. */ public ObjectResponse saveVisualization(Visualization visualization) { From 421f230d1cee748509715fdd4fea7a8f8ee4f4c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Fri, 17 Oct 2025 16:05:32 +0200 Subject: [PATCH 003/119] fix: Update code --- .../org/hisp/dhis/model/analytics/AnalyticsData.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index d0f28c1e..62a503e2 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -51,6 +51,8 @@ public class AnalyticsData { @JsonProperty private Integer headerWidth; + @JsonProperty private Boolean truncated; + /** * Indicates whether metadata exists. * @@ -59,4 +61,13 @@ public class AnalyticsData { public boolean hasMetaData() { return metaData != null; } + + /** + * Indicates whether the data rows are truncated. + * + * @return true if data rows are truncated, false otherwise. + */ + public boolean isTruncated() { + return truncated != null && truncated; + } } From 161675d1c4dd6f86766288e2f21dbeb86992a7b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Fri, 17 Oct 2025 16:06:27 +0200 Subject: [PATCH 004/119] fix: Update code --- src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 62a503e2..10bd653b 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis.model.analytics; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; import lombok.Getter; @@ -58,6 +59,7 @@ public class AnalyticsData { * * @return true if metadata exists, false otherwise. */ + @JsonIgnore public boolean hasMetaData() { return metaData != null; } @@ -67,6 +69,7 @@ public boolean hasMetaData() { * * @return true if data rows are truncated, false otherwise. */ + @JsonIgnore public boolean isTruncated() { return truncated != null && truncated; } From 37fb947d40b790b0a13ae2db5a639ca77aef66f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Fri, 17 Oct 2025 17:38:39 +0200 Subject: [PATCH 005/119] fix: Update code --- src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 10bd653b..c0bd5246 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -73,4 +73,7 @@ public boolean hasMetaData() { public boolean isTruncated() { return truncated != null && truncated; } + + public void truncate() { + } } From 13aa55da319071b42b432a522ec5f0583a544feb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Fri, 17 Oct 2025 19:43:43 +0200 Subject: [PATCH 006/119] fix: Update code --- .../dhis/model/analytics/AnalyticsData.java | 45 ++++++++++-- .../dhis/model/analytics/AnalyticsHeader.java | 23 ++++++ .../model/analytics/AnalyticsDataTest.java | 71 +++++++++++++++++++ 3 files changed, 133 insertions(+), 6 deletions(-) create mode 100644 src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index c0bd5246..0cf4d6d7 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -46,13 +46,37 @@ public class AnalyticsData { @JsonProperty private List> rows; - @JsonProperty private Integer height; + @JsonProperty private Boolean truncated; - @JsonProperty private Integer width; + /** + * Gets the number ofd ata rows. + * + * @return the number of data rows. + */ + @JsonProperty + public int getHeight() { + return rows != null ? rows.size() : 0; + } - @JsonProperty private Integer headerWidth; + /** + * Gets the number of columns in the data rows. + * + * @return the number of columns in the data rows. + */ + @JsonProperty + public int getWidth() { + return rows != null && !rows.isEmpty() ? rows.get(0).size() : 0; + } - @JsonProperty private Boolean truncated; + /** + * Gets the number of headers. + * + * @return the number of headers. + */ + @JsonProperty + public int getHeaderWidth() { + return headers != null ? headers.size() : 0; + } /** * Indicates whether metadata exists. @@ -73,7 +97,16 @@ public boolean hasMetaData() { public boolean isTruncated() { return truncated != null && truncated; } - - public void truncate() { + + /** + * Truncates the data rows to the specified maximum number of rows. + * + * @param maxRows the maximum number of rows to retain. + */ + public void truncate(int maxRows) { + if (rows != null && rows.size() > maxRows) { + rows = rows.subList(0, maxRows); + truncated = true; + } } } diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java index 5c0418fa..2f2db396 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java @@ -48,4 +48,27 @@ public class AnalyticsHeader { @JsonProperty private Boolean hidden; @JsonProperty private Boolean meta; + + /** + * Constructor with required fields. + * + * @param name the name. + * @param column the column. + * @param valueType the value type. + */ + public AnalyticsHeader(String name, String column, ValueType valueType) { + this.name = name; + this.column = column; + this.valueType = valueType; + } + + /** + * Alias for column. + * + * @return the label. + */ + @JsonProperty + public String getLabel() { + return column; + } } diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java new file mode 100644 index 00000000..e1455b1b --- /dev/null +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2004-2025, 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.model.analytics; + +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.util.List; +import org.hisp.dhis.model.ValueType; +import org.junit.jupiter.api.Test; + +class AnalyticsDataTest { + @Test + void testTruncateDataRows() { + AnalyticsData data = new AnalyticsData(); + + assertEquals(0, data.getWidth()); + assertEquals(0, data.getHeaderWidth()); + assertEquals(0, data.getHeight()); + + data.setHeaders( + List.of( + new AnalyticsHeader("C1", "C1", ValueType.TEXT), + new AnalyticsHeader("C2", "C2", ValueType.TEXT), + new AnalyticsHeader("C3", "C3", ValueType.TEXT))); + data.setRows( + List.of( + List.of("1A", "1B", "1C"), + List.of("2A", "2B", "2C"), + List.of("3A", "3B", "3C"), + List.of("4A", "4B", "4C"))); + + assertEquals(3, data.getWidth()); + assertEquals(3, data.getHeaderWidth()); + assertEquals(4, data.getHeight()); + assertFalse(data.isTruncated()); + + data.truncate(2); + + assertEquals(3, data.getWidth()); + assertEquals(3, data.getHeaderWidth()); + assertEquals(2, data.getHeight()); + assertTrue(data.isTruncated()); + } +} From 38a631bbdbce5c9a4cd3ff7377d573bb4f3000ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sat, 18 Oct 2025 21:04:28 +0200 Subject: [PATCH 007/119] fix: Update code --- .../hisp/dhis/model/analytics/AnalyticsData.java | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 0cf4d6d7..79db1b01 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -40,13 +40,17 @@ @ToString @NoArgsConstructor public class AnalyticsData { + /** Analytics column headers. */ @JsonProperty private List headers; + /** Response metadata. */ @JsonProperty private AnalyticsMetaData metaData; + /** Analytics data rows. */ @JsonProperty private List> rows; - @JsonProperty private Boolean truncated; + /** Whether the data rows were truncatd to max limit. */ + @JsonProperty private boolean truncated; /** * Gets the number ofd ata rows. @@ -88,16 +92,6 @@ public boolean hasMetaData() { return metaData != null; } - /** - * Indicates whether the data rows are truncated. - * - * @return true if data rows are truncated, false otherwise. - */ - @JsonIgnore - public boolean isTruncated() { - return truncated != null && truncated; - } - /** * Truncates the data rows to the specified maximum number of rows. * From 781f86321bf85379ef3bad66a967f579cfbe8725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 20 Oct 2025 08:55:14 +0200 Subject: [PATCH 008/119] fix: Update code --- .../org/hisp/dhis/util/CollectionUtils.java | 30 ++++++++++++++- .../java/org/hisp/dhis/model/Product.java | 4 +- .../hisp/dhis/util/CollectionUtilsTest.java | 38 +++++++++++++++++++ 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/hisp/dhis/util/CollectionUtils.java b/src/main/java/org/hisp/dhis/util/CollectionUtils.java index c55c8575..a6710ce9 100644 --- a/src/main/java/org/hisp/dhis/util/CollectionUtils.java +++ b/src/main/java/org/hisp/dhis/util/CollectionUtils.java @@ -115,7 +115,7 @@ public static List mutableList(T... items) { * @param type. * @param objects the objects of type U. * @param mapper the mapping function. - * @return a list of objects of type T. + * @return an immutable list of objects of type T. */ public static List mapToList(Collection objects, Function mapper) { return objects.stream().map(mapper).toList(); @@ -128,7 +128,7 @@ public static List mapToList(Collection objects, Function map * @param type. * @param objects the objects to cast. * @param type the class type to cast to. - * @return the list of objects cast to the given type. + * @return a mutable list of objects cast to the given type. */ public static List toTypedList(List objects, Class type) { return objects.stream().map(type::cast).collect(Collectors.toList()); @@ -234,6 +234,32 @@ public static T firstMatch(Collection collection, Predicate predicate) return collection.stream().filter(predicate).findFirst().orElse(null); } + /** + * Returns a new list of the items in the given collection which are not null and match the given + * predicate. + * + * @param type. + * @param collection the collection. + * @param predicate the predicate. + * @return an immutable list of objects of type T. + */ + public static List filterToList(Collection collection, Predicate predicate) { + return collection.stream().filter(Objects::nonNull).filter(predicate).toList(); + } + + /** + * Returns a new set of the items in the given collection which are not null and match the given + * predicate. + * + * @param type. + * @param collection the collection. + * @param predicate the predicate. + * @return an immutable set of objects of type T. + */ + public static Set filterToSet(Collection collection, Predicate predicate) { + return collection.stream().filter(predicate).collect(Collectors.toUnmodifiableSet()); + } + /** * Indicates if any item in the given collection is a prefix of the given input string. * diff --git a/src/test/java/org/hisp/dhis/model/Product.java b/src/test/java/org/hisp/dhis/model/Product.java index 16490af0..0efe4f5a 100644 --- a/src/test/java/org/hisp/dhis/model/Product.java +++ b/src/test/java/org/hisp/dhis/model/Product.java @@ -32,14 +32,16 @@ import java.util.Date; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor +@EqualsAndHashCode(onlyExplicitlyIncluded = true) @JacksonXmlRootElement(localName = "product") public class Product { - @JsonProperty private String id; + @EqualsAndHashCode.Include @JsonProperty private String id; @JsonProperty private String name; diff --git a/src/test/java/org/hisp/dhis/util/CollectionUtilsTest.java b/src/test/java/org/hisp/dhis/util/CollectionUtilsTest.java index b75b976c..86787a4a 100644 --- a/src/test/java/org/hisp/dhis/util/CollectionUtilsTest.java +++ b/src/test/java/org/hisp/dhis/util/CollectionUtilsTest.java @@ -29,6 +29,8 @@ import static org.hisp.dhis.support.Assertions.assertContainsExactly; import static org.hisp.dhis.util.CollectionUtils.anyStartsWith; +import static org.hisp.dhis.util.CollectionUtils.filterToList; +import static org.hisp.dhis.util.CollectionUtils.filterToSet; import static org.hisp.dhis.util.CollectionUtils.first; import static org.hisp.dhis.util.CollectionUtils.firstMatch; import static org.hisp.dhis.util.CollectionUtils.get; @@ -88,6 +90,42 @@ void testFirstMatch() { assertNull(firstMatch(list, (v) -> "x".equals(v))); } + @Test + void testFilterToList() { + Product pA = new Product("P01", "Keyboard"); + Product pB = new Product("P02", "Mouse"); + Product pC = new Product("P03", "Monitor"); + + List list = List.of(pA, pB, pC); + + assertContainsExactly(filterToList(list, p -> "P01".equals(p.getId())), pA); + assertContainsExactly( + filterToList(list, p -> Set.of("P01", "P03").contains(p.getId())), pA, pC); + assertContainsExactly( + filterToList(list, p -> Set.of("P02", "P03").contains(p.getId())), pB, pC); + assertContainsExactly( + filterToList(list, p -> mutableSet("P02", null, "P03").contains(p.getId())), pB, pC); + assertContainsExactly( + filterToList(list, p -> mutableSet(null, "P01", "P02").contains(p.getId())), pA, pB); + } + + @Test + void testFilterToSet() { + Product pA = new Product("P01", "Keyboard"); + Product pB = new Product("P02", "Mouse"); + Product pC = new Product("P03", "Monitor"); + + List list = List.of(pA, pB, pC); + + assertContainsExactly(filterToSet(list, p -> "P01".equals(p.getId())), pA); + assertContainsExactly(filterToSet(list, p -> Set.of("P01", "P03").contains(p.getId())), pA, pC); + assertContainsExactly(filterToSet(list, p -> Set.of("P02", "P03").contains(p.getId())), pB, pC); + assertContainsExactly( + filterToSet(list, p -> mutableSet("P02", null, "P03").contains(p.getId())), pB, pC); + assertContainsExactly( + filterToSet(list, p -> mutableSet(null, "P01", "P02").contains(p.getId())), pA, pB); + } + @Test void testAnyStartsWith() { List list = From 6c58b97d9b056859067963d801e61c0e0adbe3eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 20 Oct 2025 09:55:45 +0200 Subject: [PATCH 009/119] fix: Update code --- .../hisp/dhis/model/IdentifiableObject.java | 15 +++++++++++++ .../dhis/util/IdentifiableObjectUtils.java | 22 +++++++++++-------- .../util/IdentifiableObjectUtilsTest.java | 11 ++++++++++ 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/IdentifiableObject.java b/src/main/java/org/hisp/dhis/model/IdentifiableObject.java index 9df9e94b..61db56f5 100644 --- a/src/main/java/org/hisp/dhis/model/IdentifiableObject.java +++ b/src/main/java/org/hisp/dhis/model/IdentifiableObject.java @@ -34,6 +34,7 @@ import java.util.Objects; import java.util.Set; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; import org.hisp.dhis.model.acl.Access; @@ -43,6 +44,7 @@ @Getter @Setter @ToString +@NoArgsConstructor public class IdentifiableObject implements Serializable { @JsonProperty protected String id; @@ -66,6 +68,19 @@ public class IdentifiableObject implements Serializable { /** Read only representation of sharing access for the currently authenticated user. */ @JsonProperty protected Access access; + /** + * Constructor. + * + * @param id the identifier. + * @param code the code. + * @param name the name. + */ + public IdentifiableObject(String id, String code, String name) { + this.id = id; + this.code = code; + this.name = name; + } + // ------------------------------------------------------------------------- // Logic methods // ------------------------------------------------------------------------- diff --git a/src/main/java/org/hisp/dhis/util/IdentifiableObjectUtils.java b/src/main/java/org/hisp/dhis/util/IdentifiableObjectUtils.java index f597496c..be06f804 100644 --- a/src/main/java/org/hisp/dhis/util/IdentifiableObjectUtils.java +++ b/src/main/java/org/hisp/dhis/util/IdentifiableObjectUtils.java @@ -32,7 +32,6 @@ import java.util.Collection; import java.util.List; import java.util.Objects; -import java.util.stream.Collectors; import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.hisp.dhis.model.IdentifiableObject; @@ -48,10 +47,7 @@ public class IdentifiableObjectUtils { * @return a list of object identifiers. */ public static List toIdentifiers(Collection objects) { - return objects.stream() - .filter(Objects::nonNull) - .map(IdentifiableObject::getId) - .collect(Collectors.toList()); + return objects.stream().filter(Objects::nonNull).map(IdentifiableObject::getId).toList(); } /** @@ -62,10 +58,18 @@ public static List toIdentifiers(Collecti * @return a list of object codes. */ public static List toCodes(Collection objects) { - return objects.stream() - .filter(Objects::nonNull) - .map(IdentifiableObject::getCode) - .collect(Collectors.toList()); + return objects.stream().filter(Objects::nonNull).map(IdentifiableObject::getCode).toList(); + } + + /** + * Converts the given collection of identifiable objects to a new list of object codes. + * + * @param the type. + * @param objects the collection of {@link IdentifiableObject}. + * @return a list of object codes. + */ + public static List toNames(Collection objects) { + return objects.stream().filter(Objects::nonNull).map(IdentifiableObject::getName).toList(); } /** diff --git a/src/test/java/org/hisp/dhis/util/IdentifiableObjectUtilsTest.java b/src/test/java/org/hisp/dhis/util/IdentifiableObjectUtilsTest.java index 22118204..5a728e8f 100644 --- a/src/test/java/org/hisp/dhis/util/IdentifiableObjectUtilsTest.java +++ b/src/test/java/org/hisp/dhis/util/IdentifiableObjectUtilsTest.java @@ -63,6 +63,17 @@ void testToCodes() { assertContainsExactlyInOrder(objects, deA.getCode(), deB.getCode(), deC.getCode()); } + @Test + void testToNames() { + DataElement deA = set(new DataElement(), 'A'); + DataElement deB = set(new DataElement(), 'B'); + DataElement deC = set(new DataElement(), 'C'); + + List objects = IdentifiableObjectUtils.toNames(List.of(deA, deB, deC)); + + assertContainsExactlyInOrder(objects, deA.getName(), deB.getName(), deC.getName()); + } + @Test void testNewInstance() { DataElement instance = IdentifiableObjectUtils.newInstance(DataElement.class); From f80713f939504567cd781d880943d640a568ef0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 20 Oct 2025 17:21:18 +0200 Subject: [PATCH 010/119] fix: Update code --- .../dhis/model/analytics/AnalyticsData.java | 60 ++++++++++++++- .../model/analytics/AnalyticsDataTest.java | 75 +++++++++++++++---- 2 files changed, 120 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 79db1b01..5fa69436 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -27,18 +27,21 @@ */ package org.hisp.dhis.model.analytics; +import static org.apache.commons.collections4.CollectionUtils.isEmpty; + import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.ArrayList; +import java.util.Comparator; import java.util.List; +import java.util.Objects; import lombok.Getter; -import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; @Getter @Setter @ToString -@NoArgsConstructor public class AnalyticsData { /** Analytics column headers. */ @JsonProperty private List headers; @@ -52,6 +55,12 @@ public class AnalyticsData { /** Whether the data rows were truncatd to max limit. */ @JsonProperty private boolean truncated; + public AnalyticsData() { + this.headers = new ArrayList<>(); + this.rows = new ArrayList>(); + this.truncated = false; + } + /** * Gets the number ofd ata rows. * @@ -92,6 +101,29 @@ public boolean hasMetaData() { return metaData != null; } + /** + * Adds a data row. + * + * @param row the data row. + */ + public void addRow(List row) { + this.rows.add(row); + } + + /** + * Gets the data row at the specified index. + * + * @param index the row index, starting with 0. + * @return the data row at the specified index, or null if out of bounds. + */ + public List getRow(int index) { + if (rows == null || index < 0 || index >= rows.size()) { + return null; + } + + return rows.get(index); + } + /** * Truncates the data rows to the specified maximum number of rows. * @@ -103,4 +135,28 @@ public void truncate(int maxRows) { truncated = true; } } + + /** Orders the data rows in natural order based on their values. */ + public void sortRows() { + if (isEmpty(rows)) { + return; + } + + rows.sort( + (rowA, rowB) -> { + int maxLength = Math.max(rowA.size(), rowB.size()); + + for (int i = 0; i < maxLength; i++) { + String val1 = i < rowA.size() ? rowA.get(i) : null; + String val2 = i < rowB.size() ? rowB.get(i) : null; + + int comparison = Objects.compare(val1, val2, Comparator.naturalOrder()); + + if (comparison != 0) { + return comparison; + } + } + return 0; + }); + } } diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java index e1455b1b..09ca6f4c 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java @@ -33,32 +33,40 @@ import java.util.List; import org.hisp.dhis.model.ValueType; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class AnalyticsDataTest { - @Test - void testTruncateDataRows() { - AnalyticsData data = new AnalyticsData(); + private AnalyticsData data; - assertEquals(0, data.getWidth()); - assertEquals(0, data.getHeaderWidth()); - assertEquals(0, data.getHeight()); + @BeforeEach + public void beforeEach() { + data = new AnalyticsData(); data.setHeaders( List.of( new AnalyticsHeader("C1", "C1", ValueType.TEXT), new AnalyticsHeader("C2", "C2", ValueType.TEXT), new AnalyticsHeader("C3", "C3", ValueType.TEXT))); - data.setRows( - List.of( - List.of("1A", "1B", "1C"), - List.of("2A", "2B", "2C"), - List.of("3A", "3B", "3C"), - List.of("4A", "4B", "4C"))); + data.addRow(List.of("1A", "1B", "1C")); + data.addRow(List.of("3A", "3B", "3C")); + data.addRow(List.of("2A", "2B", "2C")); + data.addRow(List.of("5A", "5B", "5C")); + data.addRow(List.of("4A", "4B", "4C")); + } + @Test + void testGetWidthHeight() { + assertEquals(3, data.getWidth()); + assertEquals(3, data.getHeaderWidth()); + assertEquals(5, data.getHeight()); + } + + @Test + void testTruncateDataRows() { assertEquals(3, data.getWidth()); assertEquals(3, data.getHeaderWidth()); - assertEquals(4, data.getHeight()); + assertEquals(5, data.getHeight()); assertFalse(data.isTruncated()); data.truncate(2); @@ -68,4 +76,45 @@ void testTruncateDataRows() { assertEquals(2, data.getHeight()); assertTrue(data.isTruncated()); } + + @Test + void testGetRow() { + List row1 = data.getRow(0); + List row3 = data.getRow(2); + List row5 = data.getRow(4); + + assertEquals("1A", row1.get(0)); + assertEquals("1C", row1.get(2)); + assertEquals("2A", row3.get(0)); + assertEquals("2C", row3.get(2)); + assertEquals("4A", row5.get(0)); + assertEquals("4C", row5.get(2)); + } + + @Test + void testSortData() { + List row1 = data.getRow(0); + List row3 = data.getRow(2); + List row5 = data.getRow(4); + + assertEquals("1A", row1.get(0)); + assertEquals("1C", row1.get(2)); + assertEquals("2A", row3.get(0)); + assertEquals("2C", row3.get(2)); + assertEquals("4A", row5.get(0)); + assertEquals("4C", row5.get(2)); + + data.sortRows(); + + row1 = data.getRow(0); + row3 = data.getRow(2); + row5 = data.getRow(4); + + assertEquals("1A", row1.get(0)); + assertEquals("1C", row1.get(2)); + assertEquals("3A", row3.get(0)); + assertEquals("3C", row3.get(2)); + assertEquals("5A", row5.get(0)); + assertEquals("5C", row5.get(2)); + } } From 8ad0cb39a039fe0a532daadacd19998b5b4fc3b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 20 Oct 2025 17:29:34 +0200 Subject: [PATCH 011/119] fix: Update code --- .../org/hisp/dhis/model/RelativePeriod.java | 90 ++++++++++--------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/RelativePeriod.java b/src/main/java/org/hisp/dhis/model/RelativePeriod.java index 2ccf58ef..d361438f 100644 --- a/src/main/java/org/hisp/dhis/model/RelativePeriod.java +++ b/src/main/java/org/hisp/dhis/model/RelativePeriod.java @@ -33,50 +33,52 @@ @Getter @RequiredArgsConstructor public enum RelativePeriod { - TODAY("Today"), - YESTERDAY("Yesterday"), - LAST_3_DAYS("Last 3 days"), - LAST_7_DAYS("Last 7 days"), - LAST_14_DAYS("Last 14 days"), - LAST_30_DAYS("Last 30 days"), - LAST_60_DAYS("Last 60 days"), - LAST_90_DAYS("Last 90 days"), - LAST_180_DAYS("Last 180 days"), - THIS_MONTH("This month"), - THIS_BIMONTH("This bi-month"), - LAST_BIMONTH("Last bi-month"), - THIS_QUARTER("This quarter"), - LAST_QUARTER("Last quarter"), - THIS_SIX_MONTH("This six-month"), - LAST_SIX_MONTH("Last six-month"), - WEEKS_THIS_YEAR("Weeks this year"), - MONTHS_THIS_YEAR("Months this year"), - BIMONTHS_THIS_YEAR("Bi-months this year"), - QUARTERS_THIS_YEAR("Quarters this year"), - THIS_YEAR("This year"), - MONTHS_LAST_YEAR("Months last year"), - QUARTERS_LAST_YEAR("Quarters last year"), - LAST_YEAR("Last year"), - LAST_5_YEARS("Last 5 years"), - LAST_10_YEARS("Last 10 years"), - LAST_12_MONTHS("Last 12 months"), - LAST_6_MONTHS("Last 6 months"), - LAST_3_MONTHS("Last 3 months"), - LAST_6_BIMONTHS("Last 6 bi-months"), - LAST_4_QUARTERS("Last 4 quarters"), - LAST_2_SIXMONTHS("Last 2 six-months"), - THIS_FINANCIAL_YEAR("This financial year"), - LAST_FINANCIAL_YEAR("Last financial year"), - LAST_5_FINANCIAL_YEARS("Last 5 financial years"), - LAST_10_FINANCIAL_YEARS("Last 10 financial years"), - THIS_WEEK("This week"), - LAST_WEEK("Last week"), - THIS_BIWEEK("This bi-week"), - LAST_BIWEEK("Last bi-week"), - LAST_4_WEEKS("Last 4 weeks"), - LAST_4_BIWEEKS("Last 4 bi-weeks"), - LAST_12_WEEKS("Last 12 weeks"), - LAST_52_WEEKS("Last 52 weeks"); + TODAY("Today", 1), + YESTERDAY("Yesterday", 1), + LAST_3_DAYS("Last 3 days", 3), + LAST_7_DAYS("Last 7 days", 7), + LAST_14_DAYS("Last 14 days", 14), + LAST_30_DAYS("Last 30 days", 30), + LAST_60_DAYS("Last 60 days", 60), + LAST_90_DAYS("Last 90 days", 90), + LAST_180_DAYS("Last 180 days", 180), + THIS_MONTH("This month", 1), + THIS_BIMONTH("This bi-month", 1), + LAST_BIMONTH("Last bi-month", 1), + THIS_QUARTER("This quarter", 1), + LAST_QUARTER("Last quarter", 1), + THIS_SIX_MONTH("This six-month", 1), + LAST_SIX_MONTH("Last six-month", 1), + WEEKS_THIS_YEAR("Weeks this year", 52), + MONTHS_THIS_YEAR("Months this year", 12), + BIMONTHS_THIS_YEAR("Bi-months this year", 6), + QUARTERS_THIS_YEAR("Quarters this year", 4), + THIS_YEAR("This year", 1), + MONTHS_LAST_YEAR("Months last year", 12), + QUARTERS_LAST_YEAR("Quarters last year", 4), + LAST_YEAR("Last year", 1), + LAST_5_YEARS("Last 5 years", 5), + LAST_10_YEARS("Last 10 years", 10), + LAST_12_MONTHS("Last 12 months", 12), + LAST_6_MONTHS("Last 6 months", 6), + LAST_3_MONTHS("Last 3 months", 3), + LAST_6_BIMONTHS("Last 6 bi-months", 6), + LAST_4_QUARTERS("Last 4 quarters", 4), + LAST_2_SIXMONTHS("Last 2 six-months", 2), + THIS_FINANCIAL_YEAR("This financial year", 1), + LAST_FINANCIAL_YEAR("Last financial year", 1), + LAST_5_FINANCIAL_YEARS("Last 5 financial years", 5), + LAST_10_FINANCIAL_YEARS("Last 10 financial years", 10), + THIS_WEEK("This week", 1), + LAST_WEEK("Last week", 1), + THIS_BIWEEK("This bi-week", 1), + LAST_BIWEEK("Last bi-week", 1), + LAST_4_WEEKS("Last 4 weeks", 4), + LAST_4_BIWEEKS("Last 4 bi-weeks", 4), + LAST_12_WEEKS("Last 12 weeks", 12), + LAST_52_WEEKS("Last 52 weeks", 52); private final String name; + + private final int periods; } From 162c1d877270756fd3dd14e4fb510970c1d58a99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 21 Oct 2025 15:41:20 +0200 Subject: [PATCH 012/119] fix: Update code --- src/main/java/org/hisp/dhis/Dhis2.java | 32 +++++++++- .../java/org/hisp/dhis/api/ApiFields.java | 63 ++++++++++++------- .../dhis/model/metadata/MetadataEntity.java | 4 +- .../java/org/hisp/dhis/ApiFieldsTest.java | 27 ++++---- 4 files changed, 85 insertions(+), 41 deletions(-) diff --git a/src/main/java/org/hisp/dhis/Dhis2.java b/src/main/java/org/hisp/dhis/Dhis2.java index 8002e4a9..6273aea0 100644 --- a/src/main/java/org/hisp/dhis/Dhis2.java +++ b/src/main/java/org/hisp/dhis/Dhis2.java @@ -67,6 +67,7 @@ import org.apache.hc.core5.http.io.entity.InputStreamEntity; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.net.URIBuilder; +import org.hisp.dhis.api.ApiFields; import org.hisp.dhis.auth.AccessTokenAuthentication; import org.hisp.dhis.auth.BasicAuthentication; import org.hisp.dhis.auth.CookieAuthentication; @@ -640,17 +641,31 @@ public ObjectResponse updateMetadataObject(IdentifiableObject object) { /** * Retrieves a metadata object using HTTP GET. * + * @param the type. * @param entity the {@link MetadataEntity}. * @param id the object identifier. + * @return the metadata object. + * @throws Dhis2ClientException if unauthorized, access denied or resource not found. + */ + public T getMetadataObject(MetadataEntity entity, String id) { + return getMetadataObject(entity, id, entity.getExtFields()); + } + + /** + * Retrieves a metadata object using HTTP GET. + * * @param the type. + * @param entity the {@link MetadataEntity}. + * @param id the object identifier. + * @param fields the API fields. * @return the metadata object. * @throws Dhis2ClientException if unauthorized, access denied or resource not found. */ @SuppressWarnings("unchecked") - public T getMetadataObject(MetadataEntity entity, String id) { + protected T getMetadataObject( + MetadataEntity entity, String id, String fields) { // Unchecked cast is safe as all metadata entities extend identifiable object String path = entity.getPath(); - String fields = entity.getExtFields(); Class type = (Class) entity.getType(); return getObject( @@ -2694,7 +2709,7 @@ public ObjectsResponse saveProgram(ProgramObjects objects) { } /** - * Retrieves a {@link Program}. + * Retrieves a {@link Program} using extended fields. * * @param id the object identifier. * @return the {@link Program}. @@ -2704,6 +2719,17 @@ public Program getProgram(String id) { return getMetadataObject(MetadataEntity.PROGRAM, id); } + /** + * Retrieves a {@link Program} using standard fields. + * + * @param id the object identifier. + * @return the {@link Program}. + * @throws Dhis2ClientException if the object does not exist. + */ + public Program getProgramStd(String id) { + return getMetadataObject(MetadataEntity.PROGRAM, id, ApiFields.PROGRAM_STD_FIELDS); + } + /** * Retrieves a {@link ProgramObjects}. * diff --git a/src/main/java/org/hisp/dhis/api/ApiFields.java b/src/main/java/org/hisp/dhis/api/ApiFields.java index abf154e3..61d1df97 100644 --- a/src/main/java/org/hisp/dhis/api/ApiFields.java +++ b/src/main/java/org/hisp/dhis/api/ApiFields.java @@ -388,23 +388,48 @@ public class ApiFields { condition,priority""", ID_EXT_FIELDS, ID_FIELDS, PROGRAM_RULE_ACTION_FIELDS); + /** Program simple fields. */ + private static final String PROGRAM_SIMPLE_FIELDS = + """ + formName,programType,\ + enrollmentDateLabel,incidentDateLabel,enrollmentLabel,followUpLabel,\ + orgUnitLabel,relationshipLabel,noteLabel,trackedEntityAttributeLabel,\ + programStageLabel,eventLabel,\ + version,displayIncidentDate,ignoreOverdueEvents,onlyEnrollOnce,\ + selectEnrollmentDatesInFuture,selectIncidentDatesInFuture,displayFrontPageList,\ + skipOffline,useFirstStageDuringRegistration,expiryDays,\ + minAttributesRequiredToSearch,maxTeiCountToReturn,accessLevel,featureType"""; + /** Program extended fields. */ public static final String PROGRAM_EXT_FIELDS = String.format( """ - %1$s,formName,programType,trackedEntityType[%3$s],categoryCombo[%2$s,categories[%4$s]],\ + %1$s,trackedEntityType[%3$s],categoryCombo[%2$s,categories[%4$s]],\ organisationUnits[%2$s],\ programSections[%5$s],\ programStages[%6$s],\ programTrackedEntityAttributes[%7$s],\ - enrollmentDateLabel,incidentDateLabel,enrollmentLabel,followUpLabel,\ - orgUnitLabel,relationshipLabel,noteLabel,trackedEntityAttributeLabel,\ - programStageLabel,eventLabel,\ - version,displayIncidentDate,ignoreOverdueEvents,onlyEnrollOnce,\ - selectEnrollmentDatesInFuture,selectIncidentDatesInFuture,displayFrontPageList,\ - skipOffline,useFirstStageDuringRegistration,expiryDays,\ - minAttributesRequiredToSearch,maxTeiCountToReturn,accessLevel,featureType,\ - programRuleVariables[%8$s]""", + programRuleVariables[%8$s],\ + %9$s""", + NAME_EXT_FIELDS, + NAME_FIELDS, + TRACKED_ENTITY_TYPE_FIELDS, + CATEGORY_FIELDS, + PROGRAM_SECTION_FIELDS, + PROGRAM_STAGE_FIELDS, + PROGRAM_TRACKED_ENTITY_ATTRIBUTES_FIELDS, + PROGRAM_RULE_VARIABLE_FIELDS, + PROGRAM_SIMPLE_FIELDS); + + /** Program standard fields. */ + public static final String PROGRAM_STD_FIELDS = + String.format( + """ + %1$s,trackedEntityType[%3$s],categoryCombo[%2$s,categories[%4$s]],\ + programSections[%5$s],\ + programStages[%5$s],\ + programTrackedEntityAttributes[%6$s],\ + %7$s""", NAME_EXT_FIELDS, NAME_FIELDS, TRACKED_ENTITY_TYPE_FIELDS, @@ -412,29 +437,21 @@ public class ApiFields { PROGRAM_SECTION_FIELDS, PROGRAM_STAGE_FIELDS, PROGRAM_TRACKED_ENTITY_ATTRIBUTES_FIELDS, - PROGRAM_RULE_VARIABLE_FIELDS); + PROGRAM_SIMPLE_FIELDS); /** Program fields. */ - public static final String PROGRAM_FIELDS = + public static final String PROGRAM_MIN_FIELDS = String.format( """ - %1$s,formName,programType,trackedEntityType[%2$s],categoryCombo[%2$s],\ + %1$s,trackedEntityType[%2$s],categoryCombo[%2$s],\ programSections[%2$s],\ programStages[%2$s],\ - programTrackedEntityAttributes[%4$s],\ - enrollmentDateLabel,incidentDateLabel,enrollmentLabel,followUpLabel,\ - orgUnitLabel,relationshipLabel,noteLabel,trackedEntityAttributeLabel,\ - programStageLabel,eventLabel,\ - version,displayIncidentDate,ignoreOverdueEvents,onlyEnrollOnce,\ - selectEnrollmentDatesInFuture,selectIncidentDatesInFuture,displayFrontPageList,\ - skipOffline,useFirstStageDuringRegistration,expiryDays,\ - minAttributesRequiredToSearch,maxTeiCountToReturn,accessLevel,featureType,\ - programRuleVariables[%5$s]""", + programTrackedEntityAttributes[%3$s],\ + %4$s""", NAME_EXT_FIELDS, NAME_FIELDS, - TRACKED_ENTITY_ATTRIBUTE_FIELDS, PROGRAM_TRACKED_ENTITY_ATTRIBUTES_FIELDS, - ID_FIELDS); + PROGRAM_SIMPLE_FIELDS); /** User fields. */ public static final String USER_FIELDS = diff --git a/src/main/java/org/hisp/dhis/model/metadata/MetadataEntity.java b/src/main/java/org/hisp/dhis/model/metadata/MetadataEntity.java index 85721542..25b1117e 100644 --- a/src/main/java/org/hisp/dhis/model/metadata/MetadataEntity.java +++ b/src/main/java/org/hisp/dhis/model/metadata/MetadataEntity.java @@ -65,8 +65,8 @@ import static org.hisp.dhis.api.ApiFields.ORG_UNIT_GROUP_SET_FIELDS; import static org.hisp.dhis.api.ApiFields.ORG_UNIT_LEVEL_FIELDS; import static org.hisp.dhis.api.ApiFields.PROGRAM_EXT_FIELDS; -import static org.hisp.dhis.api.ApiFields.PROGRAM_FIELDS; import static org.hisp.dhis.api.ApiFields.PROGRAM_INDICATOR_FIELDS; +import static org.hisp.dhis.api.ApiFields.PROGRAM_MIN_FIELDS; import static org.hisp.dhis.api.ApiFields.PROGRAM_RULE_ACTION_FIELDS; import static org.hisp.dhis.api.ApiFields.PROGRAM_RULE_FIELDS; import static org.hisp.dhis.api.ApiFields.PROGRAM_RULE_VARIABLE_FIELDS; @@ -314,7 +314,7 @@ public enum MetadataEntity { Dhis2Objects::getOptions), PROGRAM( Program.class, - PROGRAM_FIELDS, + PROGRAM_MIN_FIELDS, PROGRAM_EXT_FIELDS, "programs", Dhis2Objects::getPrograms), diff --git a/src/test/java/org/hisp/dhis/ApiFieldsTest.java b/src/test/java/org/hisp/dhis/ApiFieldsTest.java index 332822c9..4627fc17 100644 --- a/src/test/java/org/hisp/dhis/ApiFieldsTest.java +++ b/src/test/java/org/hisp/dhis/ApiFieldsTest.java @@ -66,7 +66,7 @@ void testProgramStageFields() { void testProgramExtFields() { String expected = """ - id,code,name,created,lastUpdated,attributeValues,translations,sharing,access,shortName,description,formName,programType,\ + id,code,name,created,lastUpdated,attributeValues,translations,sharing,access,shortName,description,\ trackedEntityType[id,code,name,created,lastUpdated,attributeValues,translations,sharing,access,shortName,description,\ trackedEntityTypeAttributes[id,\ trackedEntityAttribute[id,code,name,created,lastUpdated,attributeValues,shortName,description,valueType,\ @@ -101,28 +101,29 @@ void testProgramExtFields() { trackedEntityAttribute[id,code,name,created,lastUpdated,attributeValues,shortName,\ description,valueType,aggregationType,confidential,unique],\ sortOrder,displayInList,mandatory,allowFutureDate,searchable],\ + programRuleVariables[id,code,name,created,lastUpdated,attributeValues,translations,\ + sharing,access,program[id,code,name,created,lastUpdated,attributeValues],\ + programRuleVariableSourceType,valueType,programStage[id,code,name,created,\ + lastUpdated,attributeValues],trackedEntityAttribute[id,code,name,created,\ + lastUpdated,attributeValues],useCodeForOptionSet,\ + dataElement[id,code,name,created,lastUpdated,attributeValues]],\ + formName,programType,\ enrollmentDateLabel,incidentDateLabel,enrollmentLabel,followUpLabel,\ orgUnitLabel,relationshipLabel,noteLabel,trackedEntityAttributeLabel,\ programStageLabel,eventLabel,\ version,displayIncidentDate,ignoreOverdueEvents,onlyEnrollOnce,\ selectEnrollmentDatesInFuture,selectIncidentDatesInFuture,displayFrontPageList,\ skipOffline,useFirstStageDuringRegistration,expiryDays,\ - minAttributesRequiredToSearch,maxTeiCountToReturn,accessLevel,featureType,\ - programRuleVariables[id,code,name,created,lastUpdated,attributeValues,translations,\ - sharing,access,program[id,code,name,created,lastUpdated,attributeValues],\ - programRuleVariableSourceType,valueType,programStage[id,code,name,created,\ - lastUpdated,attributeValues],trackedEntityAttribute[id,code,name,created,\ - lastUpdated,attributeValues],useCodeForOptionSet,\ - dataElement[id,code,name,created,lastUpdated,attributeValues]]"""; + minAttributesRequiredToSearch,maxTeiCountToReturn,accessLevel,featureType"""; assertEquals(expected, ApiFields.PROGRAM_EXT_FIELDS); } @Test - void testProgramFields() { + void testProgramMinFields() { String expected = """ - id,code,name,created,lastUpdated,attributeValues,translations,sharing,access,shortName,description,formName,programType,\ + id,code,name,created,lastUpdated,attributeValues,translations,sharing,access,shortName,description,\ trackedEntityType[id,code,name,created,lastUpdated,attributeValues,shortName,description],\ categoryCombo[id,code,name,created,lastUpdated,attributeValues,shortName,description],\ programSections[id,code,name,created,lastUpdated,attributeValues,shortName,description],\ @@ -132,15 +133,15 @@ void testProgramFields() { trackedEntityAttribute[id,code,name,created,lastUpdated,attributeValues,shortName,\ description,valueType,aggregationType,confidential,unique],\ sortOrder,displayInList,mandatory,allowFutureDate,searchable],\ + formName,programType,\ enrollmentDateLabel,incidentDateLabel,enrollmentLabel,followUpLabel,\ orgUnitLabel,relationshipLabel,noteLabel,trackedEntityAttributeLabel,\ programStageLabel,eventLabel,\ version,displayIncidentDate,ignoreOverdueEvents,onlyEnrollOnce,\ selectEnrollmentDatesInFuture,selectIncidentDatesInFuture,displayFrontPageList,\ skipOffline,useFirstStageDuringRegistration,expiryDays,\ - minAttributesRequiredToSearch,maxTeiCountToReturn,accessLevel,featureType,\ - programRuleVariables[id,code,name,created,lastUpdated,attributeValues]"""; + minAttributesRequiredToSearch,maxTeiCountToReturn,accessLevel,featureType"""; - assertEquals(expected, ApiFields.PROGRAM_FIELDS); + assertEquals(expected, ApiFields.PROGRAM_MIN_FIELDS); } } From a0def0055b8c4b4d6c28295e84ec80d2892e7c24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 21 Oct 2025 17:20:21 +0200 Subject: [PATCH 013/119] fix: Update code --- .../java/org/hisp/dhis/ProgramApiTest.java | 102 ------------ .../java/org/hisp/dhis/ProgramsApiTest.java | 150 ++++++++++++++++++ 2 files changed, 150 insertions(+), 102 deletions(-) create mode 100644 src/test/java/org/hisp/dhis/ProgramsApiTest.java diff --git a/src/test/java/org/hisp/dhis/ProgramApiTest.java b/src/test/java/org/hisp/dhis/ProgramApiTest.java index 91e7492a..8499d641 100644 --- a/src/test/java/org/hisp/dhis/ProgramApiTest.java +++ b/src/test/java/org/hisp/dhis/ProgramApiTest.java @@ -54,9 +54,6 @@ import org.hisp.dhis.model.trackedentity.TrackedEntityAttribute; import org.hisp.dhis.model.trackedentity.TrackedEntityType; import org.hisp.dhis.model.trackedentity.TrackedEntityTypeAttribute; -import org.hisp.dhis.query.Filter; -import org.hisp.dhis.query.Order; -import org.hisp.dhis.query.Query; import org.hisp.dhis.response.HttpStatus; import org.hisp.dhis.response.Status; import org.hisp.dhis.response.object.ObjectResponse; @@ -594,103 +591,4 @@ void testGetProgramMalariaCaseDiagnosis() { assertNotNull(ps.getProgram()); assertNotBlank(ps.getProgram().getId()); } - - @Test - void testGetProgramsExpandAssociationsA() { - Dhis2 dhis2 = new Dhis2(TestFixture.DEFAULT_CONFIG); - - List programs = - dhis2.getPrograms( - Query.instance().withExpandAssociations().addFilter(Filter.eq("id", "IpHINAT79UW"))); - - assertNotNull(programs); - assertSize(1, programs); - assertNotNull(programs.get(0)); - assertEquals("IpHINAT79UW", programs.get(0).getId()); - - Program program = programs.get(0); - - assertNotNull(program); - assertEquals("IpHINAT79UW", program.getId()); - assertEquals(ProgramType.WITH_REGISTRATION, program.getProgramType()); - - assertNotEmpty(program.getTrackedEntityAttributes()); - assertNotNull(program.getTrackedEntityAttributes().get(0)); - - assertNotEmpty(program.getProgramTrackedEntityAttributes()); - assertNotNull(program.getProgramTrackedEntityAttributes().get(0)); - assertNotNull(program.getProgramTrackedEntityAttributes().get(0).getId()); - - TrackedEntityAttribute tea = - program.getProgramTrackedEntityAttributes().get(0).getTrackedEntityAttribute(); - - assertNotNull(tea); - assertNotNull(tea.getId()); - assertNotNull(tea.getValueType()); - - assertNotEmpty(program.getProgramStages()); - - ProgramStage ps = program.getProgramStages().get(0); - - assertNotNull(ps); - assertNotNull(ps.getId()); - assertNotBlank(ps.getName()); - assertNotNull(ps.getProgram()); - assertNotBlank(ps.getProgram().getId()); - assertNotEmpty(ps.getProgramStageDataElements()); - - ProgramStageDataElement psde = ps.getProgramStageDataElements().get(0); - - assertNotNull(psde); - assertNotNull(psde.getId()); - assertNotNull(psde.getProgramStage()); - assertNotBlank(psde.getProgramStage().getId()); - assertNotNull(psde.getDataElement()); - - DataElement de = psde.getDataElement(); - - assertNotNull(de); - assertNotNull(de.getId()); - assertNotNull(de.getAggregationType()); - assertNotNull(de.getValueType()); - } - - @Test - void testGetProgramsExpandAssociationsB() { - Dhis2 dhis2 = new Dhis2(TestFixture.DEFAULT_CONFIG); - - List programs = - dhis2.getPrograms( - Query.instance() - .withExpandAssociations() - .addFilter(Filter.in("id", List.of("M3xtLkYBlKI", "WSGAb5XwJ3Y")))); - - assertSize(2, programs); - } - - @Test - void testGetPrograms() { - Dhis2 dhis2 = new Dhis2(TestFixture.DEFAULT_CONFIG); - - List programs = dhis2.getPrograms(Query.instance()); - - assertNotEmpty(programs); - assertNotNull(programs.get(0)); - } - - @Test - void testGetProgramsWithFilter() { - Dhis2 dhis2 = new Dhis2(TestFixture.DEFAULT_CONFIG); - - List programs = - dhis2.getPrograms( - Query.instance() - .addFilter(Filter.in("id", List.of("WSGAb5XwJ3Y", "M3xtLkYBlKI", "IpHINAT79UW"))) - .setOrder(Order.asc("id"))); - - assertSize(3, programs); - assertEquals("IpHINAT79UW", programs.get(0).getId()); - assertEquals("M3xtLkYBlKI", programs.get(1).getId()); - assertEquals("WSGAb5XwJ3Y", programs.get(2).getId()); - } } diff --git a/src/test/java/org/hisp/dhis/ProgramsApiTest.java b/src/test/java/org/hisp/dhis/ProgramsApiTest.java new file mode 100644 index 00000000..67f60822 --- /dev/null +++ b/src/test/java/org/hisp/dhis/ProgramsApiTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2004-2025, 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; + +import static org.hisp.dhis.support.Assertions.assertNotBlank; +import static org.hisp.dhis.support.Assertions.assertNotEmpty; +import static org.hisp.dhis.support.Assertions.assertSize; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.List; +import org.hisp.dhis.model.DataElement; +import org.hisp.dhis.model.Program; +import org.hisp.dhis.model.ProgramStage; +import org.hisp.dhis.model.ProgramStageDataElement; +import org.hisp.dhis.model.ProgramType; +import org.hisp.dhis.model.trackedentity.TrackedEntityAttribute; +import org.hisp.dhis.query.Filter; +import org.hisp.dhis.query.Order; +import org.hisp.dhis.query.Query; +import org.hisp.dhis.support.TestTags; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(TestTags.INTEGRATION) +class ProgramsApiTest { + @Test + void testGetProgramsExpandAssociationsA() { + Dhis2 dhis2 = new Dhis2(TestFixture.DEFAULT_CONFIG); + + List programs = + dhis2.getPrograms( + Query.instance().withExpandAssociations().addFilter(Filter.eq("id", "IpHINAT79UW"))); + + assertNotNull(programs); + assertSize(1, programs); + assertNotNull(programs.get(0)); + assertEquals("IpHINAT79UW", programs.get(0).getId()); + + Program program = programs.get(0); + + assertNotNull(program); + assertEquals("IpHINAT79UW", program.getId()); + assertEquals(ProgramType.WITH_REGISTRATION, program.getProgramType()); + + assertNotEmpty(program.getTrackedEntityAttributes()); + assertNotNull(program.getTrackedEntityAttributes().get(0)); + + assertNotEmpty(program.getProgramTrackedEntityAttributes()); + assertNotNull(program.getProgramTrackedEntityAttributes().get(0)); + assertNotNull(program.getProgramTrackedEntityAttributes().get(0).getId()); + + TrackedEntityAttribute tea = + program.getProgramTrackedEntityAttributes().get(0).getTrackedEntityAttribute(); + + assertNotNull(tea); + assertNotNull(tea.getId()); + assertNotNull(tea.getValueType()); + + assertNotEmpty(program.getProgramStages()); + + ProgramStage ps = program.getProgramStages().get(0); + + assertNotNull(ps); + assertNotNull(ps.getId()); + assertNotBlank(ps.getName()); + assertNotNull(ps.getProgram()); + assertNotBlank(ps.getProgram().getId()); + assertNotEmpty(ps.getProgramStageDataElements()); + + ProgramStageDataElement psde = ps.getProgramStageDataElements().get(0); + + assertNotNull(psde); + assertNotNull(psde.getId()); + assertNotNull(psde.getProgramStage()); + assertNotBlank(psde.getProgramStage().getId()); + assertNotNull(psde.getDataElement()); + + DataElement de = psde.getDataElement(); + + assertNotNull(de); + assertNotNull(de.getId()); + assertNotNull(de.getAggregationType()); + assertNotNull(de.getValueType()); + } + + @Test + void testGetProgramsExpandAssociationsB() { + Dhis2 dhis2 = new Dhis2(TestFixture.DEFAULT_CONFIG); + + List programs = + dhis2.getPrograms( + Query.instance() + .withExpandAssociations() + .addFilter(Filter.in("id", List.of("M3xtLkYBlKI", "WSGAb5XwJ3Y")))); + + assertSize(2, programs); + } + + @Test + void testGetPrograms() { + Dhis2 dhis2 = new Dhis2(TestFixture.DEFAULT_CONFIG); + + List programs = dhis2.getPrograms(Query.instance()); + + assertNotEmpty(programs); + assertNotNull(programs.get(0)); + } + + @Test + void testGetProgramsWithFilter() { + Dhis2 dhis2 = new Dhis2(TestFixture.DEFAULT_CONFIG); + + List programs = + dhis2.getPrograms( + Query.instance() + .addFilter(Filter.in("id", List.of("WSGAb5XwJ3Y", "M3xtLkYBlKI", "IpHINAT79UW"))) + .setOrder(Order.asc("id"))); + + assertSize(3, programs); + assertEquals("IpHINAT79UW", programs.get(0).getId()); + assertEquals("M3xtLkYBlKI", programs.get(1).getId()); + assertEquals("WSGAb5XwJ3Y", programs.get(2).getId()); + } +} From ee2959fb680108106940f6e9c703c165cf8679a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 21 Oct 2025 18:04:29 +0200 Subject: [PATCH 014/119] fix: Update code --- .../java/org/hisp/dhis/api/ApiFields.java | 6 +- .../java/org/hisp/dhis/ProgramApiTest.java | 112 +++++++++++++++++- 2 files changed, 114 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/hisp/dhis/api/ApiFields.java b/src/main/java/org/hisp/dhis/api/ApiFields.java index 61d1df97..1060e588 100644 --- a/src/main/java/org/hisp/dhis/api/ApiFields.java +++ b/src/main/java/org/hisp/dhis/api/ApiFields.java @@ -427,9 +427,9 @@ public class ApiFields { """ %1$s,trackedEntityType[%3$s],categoryCombo[%2$s,categories[%4$s]],\ programSections[%5$s],\ - programStages[%5$s],\ - programTrackedEntityAttributes[%6$s],\ - %7$s""", + programStages[%6$s],\ + programTrackedEntityAttributes[%7$s],\ + %8$s""", NAME_EXT_FIELDS, NAME_FIELDS, TRACKED_ENTITY_TYPE_FIELDS, diff --git a/src/test/java/org/hisp/dhis/ProgramApiTest.java b/src/test/java/org/hisp/dhis/ProgramApiTest.java index 8499d641..f3371a77 100644 --- a/src/test/java/org/hisp/dhis/ProgramApiTest.java +++ b/src/test/java/org/hisp/dhis/ProgramApiTest.java @@ -278,7 +278,7 @@ void testGetProgramObjectsChildProgramme() { } @Test - void testGetProgramChildProgramme() { + void testGetProgramExtChildProgramme() { Dhis2 dhis2 = new Dhis2(TestFixture.DEFAULT_CONFIG); Program pr = dhis2.getProgram("IpHINAT79UW"); @@ -292,11 +292,15 @@ void testGetProgramChildProgramme() { assertNotNull(pr.getCreated()); assertNotNull(pr.getLastUpdated()); assertEquals(ProgramType.WITH_REGISTRATION, pr.getProgramType()); + assertEquals(ProgramAccessLevel.OPEN, pr.getAccessLevel()); + assertEquals(FeatureType.POINT, pr.getFeatureType()); + assertNotNull(pr.getCategoryCombo()); assertNotEmpty(pr.getOrganisationUnits()); assertNotEmpty(pr.getTrackedEntityTypeAttributes()); assertNotEmpty(pr.getNonConfidentialTrackedEntityAttributes()); assertNotEmpty(pr.getTrackedEntityAttributes()); assertNotEmpty(pr.getNonConfidentialTrackedEntityAttributes()); + assertNotEmpty(pr.getProgramRuleVariables()); assertNotNull(pr.getVersion()); assertNotNull(pr.getDisplayIncidentDate()); assertNotNull(pr.getOnlyEnrollOnce()); @@ -379,6 +383,112 @@ void testGetProgramChildProgramme() { assertNotEmpty(pr.getNonConfidentialTrackedEntityAttributes()); assertNotEmpty(pr.getDataElements()); assertNotEmpty(pr.getAnalyticsDataElements()); + + ProgramRuleVariable prv = pr.getProgramRuleVariables().iterator().next(); + + assertNotBlank(prv.getId()); + + assertNotNull(prv); + } + + @Test + void testGetProgramStdChildProgramme() { + Dhis2 dhis2 = new Dhis2(TestFixture.DEFAULT_CONFIG); + + Program pr = dhis2.getProgramStd("IpHINAT79UW"); + + assertNotNull(pr); + assertEquals("IpHINAT79UW", pr.getId()); + assertNotBlank(pr.getName()); + assertNotBlank(pr.getShortName()); + assertNotNull(pr.getSharing()); + assertNotNull(pr.getAccess()); + assertNotNull(pr.getCreated()); + assertNotNull(pr.getLastUpdated()); + assertEquals(ProgramType.WITH_REGISTRATION, pr.getProgramType()); + assertEquals(ProgramAccessLevel.OPEN, pr.getAccessLevel()); + assertEquals(FeatureType.POINT, pr.getFeatureType()); + assertNotNull(pr.getCategoryCombo()); + assertEmpty(pr.getOrganisationUnits()); + assertNotEmpty(pr.getTrackedEntityTypeAttributes()); + assertNotEmpty(pr.getNonConfidentialTrackedEntityAttributes()); + assertNotEmpty(pr.getTrackedEntityAttributes()); + assertNotEmpty(pr.getNonConfidentialTrackedEntityAttributes()); + assertNotNull(pr.getVersion()); + assertNotNull(pr.getDisplayIncidentDate()); + assertNotNull(pr.getOnlyEnrollOnce()); + assertNotNull(pr.getDisplayFrontPageList()); + assertNotNull(pr.getMinAttributesRequiredToSearch()); + assertNotNull(pr.getMaxTeiCountToReturn()); + assertNotNull(pr.getAccessLevel()); + assertEmpty(pr.getProgramRuleVariables()); + + TrackedEntityType tet = pr.getTrackedEntityType(); + + assertNotNull(tet); + assertEquals("nEenWmSyUEp", tet.getId()); + assertNotBlank(tet.getName()); + assertNotEmpty(tet.getTrackedEntityTypeAttributes()); + + TrackedEntityTypeAttribute teta = tet.getTrackedEntityTypeAttributes().get(0); + + assertNotNull(teta); + assertEquals("Jdd8hMStmvF", teta.getId()); + assertNotNull(teta.getTrackedEntityAttribute()); + assertEquals("lZGmxYbs97q", teta.getTrackedEntityAttribute().getId()); + assertEquals("MMD_PER_ID", teta.getTrackedEntityAttribute().getCode()); + assertNotBlank(teta.getTrackedEntityAttribute().getName()); + + ProgramTrackedEntityAttribute ptea = pr.getProgramTrackedEntityAttributes().get(0); + + assertNotNull(ptea); + assertNotBlank(ptea.getId()); + assertNotNull(ptea.getProgram()); + assertNotNull(ptea.getSortOrder()); + assertNotNull(ptea.getDisplayInList()); + assertNotNull(ptea.getMandatory()); + + TrackedEntityAttribute tea = ptea.getTrackedEntityAttribute(); + + assertNotNull(tea); + assertNotBlank(tea.getId()); + + ProgramStage ps = pr.getProgramStages().get(0); + + assertNotNull(ps); + assertNotBlank(ps.getId()); + assertNotBlank(ps.getName()); + assertNotNull(ps.getProgram()); + assertNotBlank(ps.getProgram().getId()); + assertNotEmpty(ps.getProgramStageDataElements()); + assertNotEmpty(ps.getDataElements()); + assertNotEmpty(ps.getAnalyticsDataElements()); + + ProgramStageDataElement psde = ps.getProgramStageDataElements().get(0); + + assertNotNull(psde); + + assertNotNull(psde.getProgramStage()); + assertNotBlank(psde.getProgramStage().getId()); + assertNotNull(psde.getDataElement()); + assertNotBlank(psde.getDataElement().getId()); + assertNotNull(psde.getCompulsory()); + assertNotNull(psde.getDisplayInReports()); + assertNotNull(psde.getSkipSynchronization()); + assertNotNull(psde.getSkipAnalytics()); + assertNotNull(psde.getSortOrder()); + + DataElement de = psde.getDataElement(); + + assertNotNull(de); + assertNotBlank(de.getId()); + assertNotBlank(de.getShortName()); + assertNotBlank(de.getName()); + + assertNotEmpty(pr.getTrackedEntityAttributes()); + assertNotEmpty(pr.getNonConfidentialTrackedEntityAttributes()); + assertNotEmpty(pr.getDataElements()); + assertNotEmpty(pr.getAnalyticsDataElements()); } @Test From 537bc797238823a78073d357008110dba47ca07c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 21 Oct 2025 18:09:40 +0200 Subject: [PATCH 015/119] fix: Update code --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 88da31f6..520b7d2a 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.hisp dhis2-java-client - 2.4.2 + 2.4.3 jar DHIS 2 API client for Java From 9456c80a4980dd6bd40169f5eabffdd0d2327361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sun, 26 Oct 2025 15:07:11 +0100 Subject: [PATCH 016/119] fix: Update code --- .../org/hisp/dhis/model/analytics/AnalyticsData.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 5fa69436..42bc2b51 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -55,12 +55,24 @@ public class AnalyticsData { /** Whether the data rows were truncatd to max limit. */ @JsonProperty private boolean truncated; + /** Default constructor. */ public AnalyticsData() { this.headers = new ArrayList<>(); this.rows = new ArrayList>(); this.truncated = false; } + /** + * Constructor. + * + * @param headers the {@link AnalyticsHeader}. + * @param rows the rows. + */ + public AnalyticsData(List headers, List> rows) { + this.headers = headers; + this.rows = rows; + } + /** * Gets the number ofd ata rows. * From 5c51ad837d2df911a5250c62766571fefacc8412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sun, 26 Oct 2025 19:28:06 +0100 Subject: [PATCH 017/119] fix: Update code --- .../dhis/model/analytics/AnalyticsData.java | 32 +++++++++++++++++++ .../dhis/model/analytics/AnalyticsHeader.java | 11 +++++++ .../model/analytics/AnalyticsMetaData.java | 11 +++++++ 3 files changed, 54 insertions(+) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 42bc2b51..3ecf636f 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -148,6 +148,38 @@ public void truncate(int maxRows) { } } + /** + * Gets the data rows with metadata names instead of identifiers for meta columns. + * + * @return the data rows with metadata names. + */ + public List> getRowsWithNames() { + if (headers == null || metaData != null || metaData.getItems() == null || rows == null) { + throw new IllegalStateException("Headers, metadata and rows must be present"); + } + + List> _rows = new ArrayList<>(); + + for (List row : rows) { + List _row = new ArrayList<>(); + + for (int i = 0; i < row.size(); i++) { + String value = row.get(i); + boolean meta = headers.get(i).isMeta(); + + if (meta) { + row.add(metaData.getItemName(value)); + } else { + row.add(value); + } + } + + _rows.add(_row); + } + + return _rows; + } + /** Orders the data rows in natural order based on their values. */ public void sortRows() { if (isEmpty(rows)) { diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java index 2f2db396..9075ddfc 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis.model.analytics; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.NoArgsConstructor; @@ -71,4 +72,14 @@ public AnalyticsHeader(String name, String column, ValueType valueType) { public String getLabel() { return column; } + + @JsonIgnore + public boolean isHidden() { + return hidden != null && hidden; + } + + @JsonIgnore + public boolean isMeta() { + return meta != null && meta; + } } diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java index 1e2cb18c..95bb25b9 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java @@ -43,4 +43,15 @@ public class AnalyticsMetaData { @JsonProperty private Map items; @JsonProperty private Map> dimensions; + + /** + * Get item name by item identifier. + * + * @param id the item identifier. + * @return the item name, or null if no item exists with the given identifier. + */ + public String getItemName(String id) { + MetaDataItem item = items.get(id); + return item != null ? item.getName() : null; + } } From 4e7a23148d423bfcbf0c40c55911f5f96120bcfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sun, 26 Oct 2025 20:21:45 +0100 Subject: [PATCH 018/119] fix: Update code --- .../dhis/model/analytics/AnalyticsData.java | 24 ++++++------- .../dhis/model/analytics/AnalyticsHeader.java | 9 +++-- .../model/analytics/AnalyticsDataTest.java | 34 +++++++++++-------- 3 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 3ecf636f..9287dc53 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -148,38 +148,34 @@ public void truncate(int maxRows) { } } - /** - * Gets the data rows with metadata names instead of identifiers for meta columns. - * - * @return the data rows with metadata names. - */ public List> getRowsWithNames() { if (headers == null || metaData != null || metaData.getItems() == null || rows == null) { throw new IllegalStateException("Headers, metadata and rows must be present"); } - + List> _rows = new ArrayList<>(); - + for (List row : rows) { List _row = new ArrayList<>(); - + for (int i = 0; i < row.size(); i++) { String value = row.get(i); boolean meta = headers.get(i).isMeta(); - - if (meta) { + + if (meta) { row.add(metaData.getItemName(value)); - } else { + } + else { row.add(value); } } - + _rows.add(_row); } - + return _rows; } - + /** Orders the data rows in natural order based on their values. */ public void sortRows() { if (isEmpty(rows)) { diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java index 9075ddfc..134b8e94 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java @@ -29,6 +29,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -39,6 +40,7 @@ @Setter @ToString @NoArgsConstructor +@AllArgsConstructor public class AnalyticsHeader { @JsonProperty private String name; @@ -51,16 +53,19 @@ public class AnalyticsHeader { @JsonProperty private Boolean meta; /** - * Constructor with required fields. + * Constructor. * * @param name the name. * @param column the column. * @param valueType the value type. + * @param meta whether the column represents metadata. */ - public AnalyticsHeader(String name, String column, ValueType valueType) { + public AnalyticsHeader(String name, String column, ValueType valueType, boolean meta) { this.name = name; this.column = column; this.valueType = valueType; + this.hidden = false; + this.meta = meta; } /** diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java index 09ca6f4c..ee8a86c1 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java @@ -45,34 +45,35 @@ public void beforeEach() { data.setHeaders( List.of( - new AnalyticsHeader("C1", "C1", ValueType.TEXT), - new AnalyticsHeader("C2", "C2", ValueType.TEXT), - new AnalyticsHeader("C3", "C3", ValueType.TEXT))); - data.addRow(List.of("1A", "1B", "1C")); - data.addRow(List.of("3A", "3B", "3C")); - data.addRow(List.of("2A", "2B", "2C")); - data.addRow(List.of("5A", "5B", "5C")); - data.addRow(List.of("4A", "4B", "4C")); + new AnalyticsHeader("dx", "Data", ValueType.TEXT, true), + new AnalyticsHeader("pe", "Period", ValueType.TEXT, true), + new AnalyticsHeader("ou", "OrgUnit", ValueType.TEXT, true), + new AnalyticsHeader("value", "Value", ValueType.NUMBER, false))); + data.addRow(List.of("1A", "1B", "1C", "2")); + data.addRow(List.of("3A", "3B", "3C", "4")); + data.addRow(List.of("2A", "2B", "2C", "7")); + data.addRow(List.of("5A", "5B", "5C", "3")); + data.addRow(List.of("4A", "4B", "4C", "8")); } @Test void testGetWidthHeight() { - assertEquals(3, data.getWidth()); - assertEquals(3, data.getHeaderWidth()); + assertEquals(4, data.getWidth()); + assertEquals(4, data.getHeaderWidth()); assertEquals(5, data.getHeight()); } @Test void testTruncateDataRows() { - assertEquals(3, data.getWidth()); - assertEquals(3, data.getHeaderWidth()); + assertEquals(4, data.getWidth()); + assertEquals(4, data.getHeaderWidth()); assertEquals(5, data.getHeight()); assertFalse(data.isTruncated()); data.truncate(2); - assertEquals(3, data.getWidth()); - assertEquals(3, data.getHeaderWidth()); + assertEquals(4, data.getWidth()); + assertEquals(4, data.getHeaderWidth()); assertEquals(2, data.getHeight()); assertTrue(data.isTruncated()); } @@ -91,6 +92,11 @@ void testGetRow() { assertEquals("4C", row5.get(2)); } + @Test + void testDataWithNames() { + + } + @Test void testSortData() { List row1 = data.getRow(0); From f8e92e1c261229534acd55d17d251da5893aca1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sun, 26 Oct 2025 20:21:49 +0100 Subject: [PATCH 019/119] fix: Update code --- .../dhis/model/analytics/AnalyticsData.java | 19 +++++++++---------- .../model/analytics/AnalyticsDataTest.java | 4 +--- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 9287dc53..b7e5bb0d 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -152,30 +152,29 @@ public List> getRowsWithNames() { if (headers == null || metaData != null || metaData.getItems() == null || rows == null) { throw new IllegalStateException("Headers, metadata and rows must be present"); } - + List> _rows = new ArrayList<>(); - + for (List row : rows) { List _row = new ArrayList<>(); - + for (int i = 0; i < row.size(); i++) { String value = row.get(i); boolean meta = headers.get(i).isMeta(); - - if (meta) { + + if (meta) { row.add(metaData.getItemName(value)); - } - else { + } else { row.add(value); } } - + _rows.add(_row); } - + return _rows; } - + /** Orders the data rows in natural order based on their values. */ public void sortRows() { if (isEmpty(rows)) { diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java index ee8a86c1..2e2aeef7 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java @@ -93,9 +93,7 @@ void testGetRow() { } @Test - void testDataWithNames() { - - } + void testDataWithNames() {} @Test void testSortData() { From f62cd0ecb69992fc801b9ab672cbb9a5f2799025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sun, 26 Oct 2025 20:29:30 +0100 Subject: [PATCH 020/119] fix: Update code --- .../model/analytics/AnalyticsMetaData.java | 2 + .../model/analytics/AnalyticsDataTest.java | 67 +++++++++++-------- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java index 95bb25b9..fcb50826 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java @@ -30,6 +30,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; import java.util.Map; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -39,6 +40,7 @@ @Setter @ToString @NoArgsConstructor +@AllArgsConstructor public class AnalyticsMetaData { @JsonProperty private Map items; diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java index 2e2aeef7..b3d360f1 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java @@ -49,51 +49,60 @@ public void beforeEach() { new AnalyticsHeader("pe", "Period", ValueType.TEXT, true), new AnalyticsHeader("ou", "OrgUnit", ValueType.TEXT, true), new AnalyticsHeader("value", "Value", ValueType.NUMBER, false))); - data.addRow(List.of("1A", "1B", "1C", "2")); - data.addRow(List.of("3A", "3B", "3C", "4")); - data.addRow(List.of("2A", "2B", "2C", "7")); - data.addRow(List.of("5A", "5B", "5C", "3")); - data.addRow(List.of("4A", "4B", "4C", "8")); + data.setMetaData(new AnalyticsMetaData()); + data.addRow(List.of("A1", "B1", "C1", "2")); + data.addRow(List.of("A2", "B2", "C2", "4")); + data.addRow(List.of("A4", "B4", "C2", "3")); + data.addRow(List.of("A3", "B3", "C1", "7")); + data.addRow(List.of("A5", "B1", "C1", "8")); + data.addRow(List.of("A7", "B3", "C1", "1")); + data.addRow(List.of("A6", "B2", "C2", "6")); + data.addRow(List.of("A8", "B4", "C2", "9")); } @Test void testGetWidthHeight() { assertEquals(4, data.getWidth()); assertEquals(4, data.getHeaderWidth()); - assertEquals(5, data.getHeight()); + assertEquals(8, data.getHeight()); } @Test void testTruncateDataRows() { assertEquals(4, data.getWidth()); assertEquals(4, data.getHeaderWidth()); - assertEquals(5, data.getHeight()); + assertEquals(8, data.getHeight()); assertFalse(data.isTruncated()); - data.truncate(2); + data.truncate(4); assertEquals(4, data.getWidth()); assertEquals(4, data.getHeaderWidth()); - assertEquals(2, data.getHeight()); + assertEquals(4, data.getHeight()); assertTrue(data.isTruncated()); } @Test void testGetRow() { List row1 = data.getRow(0); + List row2 = data.getRow(1); List row3 = data.getRow(2); List row5 = data.getRow(4); - assertEquals("1A", row1.get(0)); - assertEquals("1C", row1.get(2)); - assertEquals("2A", row3.get(0)); - assertEquals("2C", row3.get(2)); - assertEquals("4A", row5.get(0)); - assertEquals("4C", row5.get(2)); + assertEquals("A1", row1.get(0)); + assertEquals("C1", row1.get(2)); + assertEquals("A2", row2.get(0)); + assertEquals("C2", row2.get(2)); + assertEquals("A4", row3.get(0)); + assertEquals("C2", row3.get(2)); + assertEquals("A5", row5.get(0)); + assertEquals("C1", row5.get(2)); } @Test - void testDataWithNames() {} + void testDataWithNames() { + + } @Test void testSortData() { @@ -101,24 +110,24 @@ void testSortData() { List row3 = data.getRow(2); List row5 = data.getRow(4); - assertEquals("1A", row1.get(0)); - assertEquals("1C", row1.get(2)); - assertEquals("2A", row3.get(0)); - assertEquals("2C", row3.get(2)); - assertEquals("4A", row5.get(0)); - assertEquals("4C", row5.get(2)); - + assertEquals("A1", row1.get(0)); + assertEquals("C1", row1.get(2)); + assertEquals("A4", row3.get(0)); + assertEquals("C2", row3.get(2)); + assertEquals("A5", row5.get(0)); + assertEquals("C1", row5.get(2)); + data.sortRows(); row1 = data.getRow(0); row3 = data.getRow(2); row5 = data.getRow(4); - assertEquals("1A", row1.get(0)); - assertEquals("1C", row1.get(2)); - assertEquals("3A", row3.get(0)); - assertEquals("3C", row3.get(2)); - assertEquals("5A", row5.get(0)); - assertEquals("5C", row5.get(2)); + assertEquals("A1", row1.get(0)); + assertEquals("C1", row1.get(2)); + assertEquals("A3", row3.get(0)); + assertEquals("C1", row3.get(2)); + assertEquals("A5", row5.get(0)); + assertEquals("C1", row5.get(2)); } } From ec8d1ba59b664d510a605ae93a0371f8f443c819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sun, 26 Oct 2025 21:14:28 +0100 Subject: [PATCH 021/119] fix: Update code --- .../dhis/model/analytics/MetaDataItem.java | 9 +++++++++ .../model/analytics/AnalyticsDataTest.java | 20 ++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java index 5c1c4917..8881dbd0 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java +++ b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java @@ -59,4 +59,13 @@ public class MetaDataItem { @JsonProperty private String legendSet; @JsonProperty private String aggregationType; + + /** + * Constructor. + * + * @param name the name. + */ + public MetaDataItem(String name) { + this.name = name; + } } diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java index b3d360f1..36ece622 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java @@ -33,6 +33,7 @@ import java.util.List; import org.hisp.dhis.model.ValueType; +import org.hisp.dhis.util.MapBuilder; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -41,6 +42,23 @@ class AnalyticsDataTest { @BeforeEach public void beforeEach() { + AnalyticsMetaData metadata = new AnalyticsMetaData(); + metadata.setItems(new MapBuilder() + .put("A1", new MetaDataItem("Indicator1")) + .put("A2", new MetaDataItem("Indicator2")) + .put("A3", new MetaDataItem("Indicator3")) + .put("A4", new MetaDataItem("Indicator4")) + .put("A5", new MetaDataItem("Indicator5")) + .put("A6", new MetaDataItem("Indicator6")) + .put("A7", new MetaDataItem("Indicator7")) + .put("A8", new MetaDataItem("Indicator8")) + .build()); + metadata.setDimensions(new MapBuilder>() + .put("dx", List.of("A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8")) + .put("pe", List.of("B1", "B2", "B3", "B4")) + .put("ou", List.of("C1", "C2")) + .build()); + data = new AnalyticsData(); data.setHeaders( @@ -49,7 +67,7 @@ public void beforeEach() { new AnalyticsHeader("pe", "Period", ValueType.TEXT, true), new AnalyticsHeader("ou", "OrgUnit", ValueType.TEXT, true), new AnalyticsHeader("value", "Value", ValueType.NUMBER, false))); - data.setMetaData(new AnalyticsMetaData()); + data.setMetaData(metadata); data.addRow(List.of("A1", "B1", "C1", "2")); data.addRow(List.of("A2", "B2", "C2", "4")); data.addRow(List.of("A4", "B4", "C2", "3")); From 434ac63b73cbdd15137dcdec1e745ac74ecd7f6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sun, 26 Oct 2025 21:27:45 +0100 Subject: [PATCH 022/119] fix: Update code --- .../dhis/model/analytics/AnalyticsData.java | 6 +-- .../model/analytics/AnalyticsDataTest.java | 51 ++++++++++++++----- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index b7e5bb0d..82017e95 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -149,7 +149,7 @@ public void truncate(int maxRows) { } public List> getRowsWithNames() { - if (headers == null || metaData != null || metaData.getItems() == null || rows == null) { + if (headers == null || metaData == null || metaData.getItems() == null || rows == null) { throw new IllegalStateException("Headers, metadata and rows must be present"); } @@ -163,9 +163,9 @@ public List> getRowsWithNames() { boolean meta = headers.get(i).isMeta(); if (meta) { - row.add(metaData.getItemName(value)); + _row.add(metaData.getItemName(value)); } else { - row.add(value); + _row.add(value); } } diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java index 36ece622..83ef8293 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java @@ -42,16 +42,28 @@ class AnalyticsDataTest { @BeforeEach public void beforeEach() { + List headers = List.of( + new AnalyticsHeader("dx", "Data", ValueType.TEXT, true), + new AnalyticsHeader("pe", "Period", ValueType.TEXT, true), + new AnalyticsHeader("ou", "OrgUnit", ValueType.TEXT, true), + new AnalyticsHeader("value", "Value", ValueType.NUMBER, false)); + AnalyticsMetaData metadata = new AnalyticsMetaData(); metadata.setItems(new MapBuilder() - .put("A1", new MetaDataItem("Indicator1")) - .put("A2", new MetaDataItem("Indicator2")) - .put("A3", new MetaDataItem("Indicator3")) - .put("A4", new MetaDataItem("Indicator4")) - .put("A5", new MetaDataItem("Indicator5")) - .put("A6", new MetaDataItem("Indicator6")) - .put("A7", new MetaDataItem("Indicator7")) - .put("A8", new MetaDataItem("Indicator8")) + .put("A1", new MetaDataItem("Indicator 1")) + .put("A2", new MetaDataItem("Indicator 2")) + .put("A3", new MetaDataItem("Indicator 3")) + .put("A4", new MetaDataItem("Indicator 4")) + .put("A5", new MetaDataItem("Indicator 5")) + .put("A6", new MetaDataItem("Indicator 6")) + .put("A7", new MetaDataItem("Indicator 7")) + .put("A8", new MetaDataItem("Indicator 8")) + .put("B1", new MetaDataItem("Month 1")) + .put("B2", new MetaDataItem("Month 2")) + .put("B3", new MetaDataItem("Month 3")) + .put("B4", new MetaDataItem("Month 4")) + .put("C1", new MetaDataItem("Facility 1")) + .put("C2", new MetaDataItem("Facility 2")) .build()); metadata.setDimensions(new MapBuilder>() .put("dx", List.of("A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8")) @@ -61,13 +73,9 @@ public void beforeEach() { data = new AnalyticsData(); - data.setHeaders( - List.of( - new AnalyticsHeader("dx", "Data", ValueType.TEXT, true), - new AnalyticsHeader("pe", "Period", ValueType.TEXT, true), - new AnalyticsHeader("ou", "OrgUnit", ValueType.TEXT, true), - new AnalyticsHeader("value", "Value", ValueType.NUMBER, false))); + data.setHeaders(headers); data.setMetaData(metadata); + data.addRow(List.of("A1", "B1", "C1", "2")); data.addRow(List.of("A2", "B2", "C2", "4")); data.addRow(List.of("A4", "B4", "C2", "3")); @@ -119,6 +127,21 @@ void testGetRow() { @Test void testDataWithNames() { + List> rows = data.getRowsWithNames(); + + List row1 = rows.get(0); + List row2 = rows.get(1); + List row3 = rows.get(2); + List row5 = rows.get(4); + + assertEquals("Indicator 1", row1.get(0)); + assertEquals("Facility 1", row1.get(2)); + assertEquals("Indicator 2", row2.get(0)); + assertEquals("Facility 2", row2.get(2)); + assertEquals("Indicator 4", row3.get(0)); + assertEquals("Facility 2", row3.get(2)); + assertEquals("Indicator 5", row5.get(0)); + assertEquals("Facility 1", row5.get(2)); } From 66406627c782caf66648503fbf7280b38ba47db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sun, 26 Oct 2025 21:31:23 +0100 Subject: [PATCH 023/119] fix: Update code --- .../dhis/model/analytics/AnalyticsData.java | 14 ++++ .../dhis/model/analytics/MetaDataItem.java | 6 +- .../model/analytics/AnalyticsDataTest.java | 75 +++++++++++-------- 3 files changed, 60 insertions(+), 35 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 82017e95..11d2289a 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -73,6 +73,20 @@ public AnalyticsData(List headers, List> rows) { this.rows = rows; } + /** + * Gets a copy of the headers. + * + * @return a copy of the headers as an immutable list. + */ + public List getCopyOfHeaders() { + return headers.stream() + .map( + h -> + new AnalyticsHeader( + h.getName(), h.getColumn(), h.getValueType(), h.getHidden(), h.getMeta())) + .toList(); + } + /** * Gets the number ofd ata rows. * diff --git a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java index 8881dbd0..1bb7758d 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java +++ b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java @@ -59,13 +59,13 @@ public class MetaDataItem { @JsonProperty private String legendSet; @JsonProperty private String aggregationType; - + /** * Constructor. - * + * * @param name the name. */ public MetaDataItem(String name) { this.name = name; - } + } } diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java index 83ef8293..2128327c 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java @@ -42,40 +42,43 @@ class AnalyticsDataTest { @BeforeEach public void beforeEach() { - List headers = List.of( - new AnalyticsHeader("dx", "Data", ValueType.TEXT, true), - new AnalyticsHeader("pe", "Period", ValueType.TEXT, true), - new AnalyticsHeader("ou", "OrgUnit", ValueType.TEXT, true), - new AnalyticsHeader("value", "Value", ValueType.NUMBER, false)); - + List headers = + List.of( + new AnalyticsHeader("dx", "Data", ValueType.TEXT, true), + new AnalyticsHeader("pe", "Period", ValueType.TEXT, true), + new AnalyticsHeader("ou", "OrgUnit", ValueType.TEXT, true), + new AnalyticsHeader("value", "Value", ValueType.NUMBER, false)); + AnalyticsMetaData metadata = new AnalyticsMetaData(); - metadata.setItems(new MapBuilder() - .put("A1", new MetaDataItem("Indicator 1")) - .put("A2", new MetaDataItem("Indicator 2")) - .put("A3", new MetaDataItem("Indicator 3")) - .put("A4", new MetaDataItem("Indicator 4")) - .put("A5", new MetaDataItem("Indicator 5")) - .put("A6", new MetaDataItem("Indicator 6")) - .put("A7", new MetaDataItem("Indicator 7")) - .put("A8", new MetaDataItem("Indicator 8")) - .put("B1", new MetaDataItem("Month 1")) - .put("B2", new MetaDataItem("Month 2")) - .put("B3", new MetaDataItem("Month 3")) - .put("B4", new MetaDataItem("Month 4")) - .put("C1", new MetaDataItem("Facility 1")) - .put("C2", new MetaDataItem("Facility 2")) - .build()); - metadata.setDimensions(new MapBuilder>() - .put("dx", List.of("A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8")) - .put("pe", List.of("B1", "B2", "B3", "B4")) - .put("ou", List.of("C1", "C2")) - .build()); + metadata.setItems( + new MapBuilder() + .put("A1", new MetaDataItem("Indicator 1")) + .put("A2", new MetaDataItem("Indicator 2")) + .put("A3", new MetaDataItem("Indicator 3")) + .put("A4", new MetaDataItem("Indicator 4")) + .put("A5", new MetaDataItem("Indicator 5")) + .put("A6", new MetaDataItem("Indicator 6")) + .put("A7", new MetaDataItem("Indicator 7")) + .put("A8", new MetaDataItem("Indicator 8")) + .put("B1", new MetaDataItem("Month 1")) + .put("B2", new MetaDataItem("Month 2")) + .put("B3", new MetaDataItem("Month 3")) + .put("B4", new MetaDataItem("Month 4")) + .put("C1", new MetaDataItem("Facility 1")) + .put("C2", new MetaDataItem("Facility 2")) + .build()); + metadata.setDimensions( + new MapBuilder>() + .put("dx", List.of("A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8")) + .put("pe", List.of("B1", "B2", "B3", "B4")) + .put("ou", List.of("C1", "C2")) + .build()); data = new AnalyticsData(); - data.setHeaders(headers); + data.setHeaders(headers); data.setMetaData(metadata); - + data.addRow(List.of("A1", "B1", "C1", "2")); data.addRow(List.of("A2", "B2", "C2", "4")); data.addRow(List.of("A4", "B4", "C2", "3")); @@ -86,6 +89,15 @@ public void beforeEach() { data.addRow(List.of("A8", "B4", "C2", "9")); } + @Test + void testGetCopyOfHeaders() { + List copy = data.getCopyOfHeaders(); + + assertEquals(4, copy.size()); + assertEquals("dx", copy.get(0).getName()); + assertEquals("ou", copy.get(2).getName()); + } + @Test void testGetWidthHeight() { assertEquals(4, data.getWidth()); @@ -133,7 +145,7 @@ void testDataWithNames() { List row2 = rows.get(1); List row3 = rows.get(2); List row5 = rows.get(4); - + assertEquals("Indicator 1", row1.get(0)); assertEquals("Facility 1", row1.get(2)); assertEquals("Indicator 2", row2.get(0)); @@ -142,7 +154,6 @@ void testDataWithNames() { assertEquals("Facility 2", row3.get(2)); assertEquals("Indicator 5", row5.get(0)); assertEquals("Facility 1", row5.get(2)); - } @Test @@ -157,7 +168,7 @@ void testSortData() { assertEquals("C2", row3.get(2)); assertEquals("A5", row5.get(0)); assertEquals("C1", row5.get(2)); - + data.sortRows(); row1 = data.getRow(0); From 6581d1dc74cd0bb6874388f79fe36733fd90af7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sun, 26 Oct 2025 22:06:24 +0100 Subject: [PATCH 024/119] fix: Update code --- .../dhis/model/analytics/AnalyticsData.java | 37 ++++++++++++------- .../model/analytics/AnalyticsDataTest.java | 6 +++ 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 11d2289a..7274d643 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -35,6 +35,7 @@ import java.util.Comparator; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import lombok.Getter; import lombok.Setter; import lombok.ToString; @@ -73,20 +74,6 @@ public AnalyticsData(List headers, List> rows) { this.rows = rows; } - /** - * Gets a copy of the headers. - * - * @return a copy of the headers as an immutable list. - */ - public List getCopyOfHeaders() { - return headers.stream() - .map( - h -> - new AnalyticsHeader( - h.getName(), h.getColumn(), h.getValueType(), h.getHidden(), h.getMeta())) - .toList(); - } - /** * Gets the number ofd ata rows. * @@ -117,6 +104,23 @@ public int getHeaderWidth() { return headers != null ? headers.size() : 0; } + // Non-serializable logic methods + + /** + * Gets a copy of the headers. + * + * @return a copy of the headers. + */ + @JsonIgnore + public List getCopyOfHeaders() { + return headers.stream() + .map( + h -> + new AnalyticsHeader( + h.getName(), h.getColumn(), h.getValueType(), h.getHidden(), h.getMeta())) + .collect(Collectors.toList()); + } + /** * Indicates whether metadata exists. * @@ -132,6 +136,7 @@ public boolean hasMetaData() { * * @param row the data row. */ + @JsonIgnore public void addRow(List row) { this.rows.add(row); } @@ -142,6 +147,7 @@ public void addRow(List row) { * @param index the row index, starting with 0. * @return the data row at the specified index, or null if out of bounds. */ + @JsonIgnore public List getRow(int index) { if (rows == null || index < 0 || index >= rows.size()) { return null; @@ -155,6 +161,7 @@ public List getRow(int index) { * * @param maxRows the maximum number of rows to retain. */ + @JsonIgnore public void truncate(int maxRows) { if (rows != null && rows.size() > maxRows) { rows = rows.subList(0, maxRows); @@ -162,6 +169,7 @@ public void truncate(int maxRows) { } } + @JsonIgnore public List> getRowsWithNames() { if (headers == null || metaData == null || metaData.getItems() == null || rows == null) { throw new IllegalStateException("Headers, metadata and rows must be present"); @@ -190,6 +198,7 @@ public List> getRowsWithNames() { } /** Orders the data rows in natural order based on their values. */ + @JsonIgnore public void sortRows() { if (isEmpty(rows)) { return; diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java index 2128327c..957ee7fe 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java @@ -161,6 +161,7 @@ void testSortData() { List row1 = data.getRow(0); List row3 = data.getRow(2); List row5 = data.getRow(4); + List row7 = data.getRow(6); assertEquals("A1", row1.get(0)); assertEquals("C1", row1.get(2)); @@ -168,12 +169,15 @@ void testSortData() { assertEquals("C2", row3.get(2)); assertEquals("A5", row5.get(0)); assertEquals("C1", row5.get(2)); + assertEquals("A6", row7.get(0)); + assertEquals("C2", row7.get(2)); data.sortRows(); row1 = data.getRow(0); row3 = data.getRow(2); row5 = data.getRow(4); + row7 = data.getRow(6); assertEquals("A1", row1.get(0)); assertEquals("C1", row1.get(2)); @@ -181,5 +185,7 @@ void testSortData() { assertEquals("C1", row3.get(2)); assertEquals("A5", row5.get(0)); assertEquals("C1", row5.get(2)); + assertEquals("A7", row7.get(0)); + assertEquals("C1", row7.get(2)); } } From 3a179e35c055e65209014e979d7932265cf9a774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sun, 26 Oct 2025 22:31:55 +0100 Subject: [PATCH 025/119] fix: Update code --- .../org/hisp/dhis/query/analytics/AnalyticsQuery.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/org/hisp/dhis/query/analytics/AnalyticsQuery.java b/src/main/java/org/hisp/dhis/query/analytics/AnalyticsQuery.java index d0aecf71..8c805e91 100644 --- a/src/main/java/org/hisp/dhis/query/analytics/AnalyticsQuery.java +++ b/src/main/java/org/hisp/dhis/query/analytics/AnalyticsQuery.java @@ -226,6 +226,16 @@ public AnalyticsQuery withSkipMetadata() { return this; } + /** + * Includes numerator and denominator values in the response. + * + * @return this {@link AnalyticsQuery}. + */ + public AnalyticsQuery withNumDen() { + this.includeNumDen = true; + return this; + } + /** * Includes metadata details in the response. * From d4c8e71be2151311802d3d7a5fbcdc6eb58c7652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sun, 26 Oct 2025 22:33:46 +0100 Subject: [PATCH 026/119] fix: Update code --- src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 7274d643..15a6a572 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -204,6 +204,8 @@ public void sortRows() { return; } + // TODO sort period dimension by ISO name instead of display name + rows.sort( (rowA, rowB) -> { int maxLength = Math.max(rowA.size(), rowB.size()); From 4a1562febbbb8c101ab5b42a5b0b8cd8879119e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sun, 26 Oct 2025 22:34:20 +0100 Subject: [PATCH 027/119] fix: Update code --- src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 15a6a572..142ba007 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -204,7 +204,7 @@ public void sortRows() { return; } - // TODO sort period dimension by ISO name instead of display name + // TODO sort period dimension by ISO name using metadata items details rows.sort( (rowA, rowB) -> { From 3cb3f54f50ae1a14ad7791580c5899ae4147f33d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 09:01:41 +0100 Subject: [PATCH 028/119] fix: Update code --- .../model/analytics/AnalyticsMetaData.java | 39 ++++++++++++ .../java/org/hisp/dhis/util/ObjectUtils.java | 27 +++++++++ .../analytics/AnalyticsMetadataTest.java | 59 +++++++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 src/main/java/org/hisp/dhis/util/ObjectUtils.java create mode 100644 src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java index fcb50826..90aae26e 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java @@ -27,7 +27,12 @@ */ package org.hisp.dhis.model.analytics; +import static org.hisp.dhis.util.ObjectUtils.isNull; +import static org.apache.commons.collections4.CollectionUtils.isEmpty; +import static org.apache.commons.collections4.MapUtils.isEmpty; + import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.HashMap; import java.util.List; import java.util.Map; import lombok.AllArgsConstructor; @@ -46,6 +51,40 @@ public class AnalyticsMetaData { @JsonProperty private Map> dimensions; + /** + * Get a complete map of period names to period ISO identifiers. If a period dimension with items + * or metadata items not present, an empty map is returned. If the name of any period metadata + * item is not present, an empty map is returned. + * + * @return a map of period ISO strings to period names. + */ + public Map getPeriodNameToIsoIdMap() { + if (isEmpty(items) || isEmpty(dimensions)) { + return Map.of(); + } + + List peDimItems = dimensions.get(AnalyticsDimension.PERIOD); + + if (isEmpty(peDimItems)) { + return Map.of(); + } + + Map output = new HashMap<>(); + + for (String pe : peDimItems) { + MetaDataItem peItem = items.get(pe); + + if (isNull(peItem) || isNull(peItem.getName())) { + return Map.of(); + } + + output.put(peItem.getName(), pe); + } + + return output; + + } + /** * Get item name by item identifier. * diff --git a/src/main/java/org/hisp/dhis/util/ObjectUtils.java b/src/main/java/org/hisp/dhis/util/ObjectUtils.java new file mode 100644 index 00000000..bb3eff1f --- /dev/null +++ b/src/main/java/org/hisp/dhis/util/ObjectUtils.java @@ -0,0 +1,27 @@ +package org.hisp.dhis.util; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ObjectUtils { + /** + * Indicates whether the given object is not null. + * + * @param object the object. + * @return true if the given object is not null, false otherwise. + */ + public static boolean isNotNull(Object object) { + return object != null; + } + + /** + * Indicates whether the given object is null. + * + * @param object the object. + * @return true if the given object is null, false otherwise. + */ + public static boolean isNull(Object object) { + return object == null; + } +} diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java new file mode 100644 index 00000000..d545ba6d --- /dev/null +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java @@ -0,0 +1,59 @@ +package org.hisp.dhis.model.analytics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import java.util.List; +import java.util.Map; +import org.hisp.dhis.util.MapBuilder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class AnalyticsMetadataTest { + private AnalyticsMetaData metadata; + + @BeforeEach + public void beforeEach() { + metadata = new AnalyticsMetaData(); + metadata.setItems( + new MapBuilder() + .put("A1", new MetaDataItem("Indicator 1")) + .put("A2", new MetaDataItem("Indicator 2")) + .put("A3", new MetaDataItem("Indicator 3")) + .put("A4", new MetaDataItem("Indicator 4")) + .put("A5", new MetaDataItem("Indicator 5")) + .put("A6", new MetaDataItem("Indicator 6")) + .put("A7", new MetaDataItem("Indicator 7")) + .put("A8", new MetaDataItem("Indicator 8")) + .put("B1", new MetaDataItem("Month 1")) + .put("B2", new MetaDataItem("Month 2")) + .put("B3", new MetaDataItem("Month 3")) + .put("B4", new MetaDataItem("Month 4")) + .put("C1", new MetaDataItem("Facility 1")) + .put("C2", new MetaDataItem("Facility 2")) + .build()); + metadata.setDimensions( + new MapBuilder>() + .put("dx", List.of("A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8")) + .put("pe", List.of("B1", "B2", "B3", "B4")) + .put("ou", List.of("C1", "C2")) + .build()); + } + + @Test + void testGetItemName() { + assertEquals("Indicator 2", metadata.getItemName("A2")); + assertEquals("Month 3", metadata.getItemName("B3")); + assertEquals("Facility 1", metadata.getItemName("C1")); + assertNull(metadata.getItemName("X1")); + } + + @Test + void testGetPeriodNameToIsoIdMap() { + Map map = metadata.getPeriodNameToIsoIdMap(); + + assertEquals(4, map.keySet().size()); + assertEquals("B1", map.get("Month 1")); + assertEquals("B2", map.get("Month 2")); + assertEquals("B4", map.get("Month 4")); + } +} From b3ee7c375d9c749124d910d08226475032c85644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 09:01:47 +0100 Subject: [PATCH 029/119] fix: Update code --- .../model/analytics/AnalyticsMetaData.java | 19 +++++------ .../java/org/hisp/dhis/util/ObjectUtils.java | 27 +++++++++++++++ .../analytics/AnalyticsMetadataTest.java | 34 +++++++++++++++++-- 3 files changed, 67 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java index 90aae26e..ad065430 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java @@ -27,9 +27,9 @@ */ package org.hisp.dhis.model.analytics; -import static org.hisp.dhis.util.ObjectUtils.isNull; import static org.apache.commons.collections4.CollectionUtils.isEmpty; import static org.apache.commons.collections4.MapUtils.isEmpty; +import static org.hisp.dhis.util.ObjectUtils.isNull; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.HashMap; @@ -62,29 +62,28 @@ public Map getPeriodNameToIsoIdMap() { if (isEmpty(items) || isEmpty(dimensions)) { return Map.of(); } - + List peDimItems = dimensions.get(AnalyticsDimension.PERIOD); - + if (isEmpty(peDimItems)) { return Map.of(); } - + Map output = new HashMap<>(); - + for (String pe : peDimItems) { MetaDataItem peItem = items.get(pe); - + if (isNull(peItem) || isNull(peItem.getName())) { return Map.of(); } - + output.put(peItem.getName(), pe); } - + return output; - } - + /** * Get item name by item identifier. * diff --git a/src/main/java/org/hisp/dhis/util/ObjectUtils.java b/src/main/java/org/hisp/dhis/util/ObjectUtils.java index bb3eff1f..59fcce2e 100644 --- a/src/main/java/org/hisp/dhis/util/ObjectUtils.java +++ b/src/main/java/org/hisp/dhis/util/ObjectUtils.java @@ -1,3 +1,30 @@ +/* + * Copyright (c) 2004-2025, 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.util; import lombok.AccessLevel; diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java index d545ba6d..33c61c01 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java @@ -1,7 +1,35 @@ +/* + * Copyright (c) 2004-2025, 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.model.analytics; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; + import java.util.List; import java.util.Map; import org.hisp.dhis.util.MapBuilder; @@ -38,7 +66,7 @@ public void beforeEach() { .put("ou", List.of("C1", "C2")) .build()); } - + @Test void testGetItemName() { assertEquals("Indicator 2", metadata.getItemName("A2")); @@ -46,11 +74,11 @@ void testGetItemName() { assertEquals("Facility 1", metadata.getItemName("C1")); assertNull(metadata.getItemName("X1")); } - + @Test void testGetPeriodNameToIsoIdMap() { Map map = metadata.getPeriodNameToIsoIdMap(); - + assertEquals(4, map.keySet().size()); assertEquals("B1", map.get("Month 1")); assertEquals("B2", map.get("Month 2")); From 129d7432cd1aa60488e19a0120fb6ccfd435dc28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 09:03:45 +0100 Subject: [PATCH 030/119] fix: Update code --- .../dhis/model/analytics/AnalyticsMetadataTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java index 33c61c01..23dba93a 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java @@ -29,6 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; import java.util.Map; @@ -84,4 +85,13 @@ void testGetPeriodNameToIsoIdMap() { assertEquals("B2", map.get("Month 2")); assertEquals("B4", map.get("Month 4")); } + + @Test + void testGetPeriodNameToIsoIdMapEmpty() { + AnalyticsMetaData metadata = new AnalyticsMetaData(); + metadata.setItems(Map.of()); + metadata.setDimensions(Map.of()); + + assertTrue(metadata.getPeriodNameToIsoIdMap().isEmpty()); + } } From 85bd992d75d56bd8a17ef68fd2187f2c37dc718c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 09:05:51 +0100 Subject: [PATCH 031/119] fix: Update code --- .../dhis/model/analytics/AnalyticsMetadataTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java index 23dba93a..c786854d 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java @@ -41,7 +41,7 @@ class AnalyticsMetadataTest { private AnalyticsMetaData metadata; @BeforeEach - public void beforeEach() { + void beforeEach() { metadata = new AnalyticsMetaData(); metadata.setItems( new MapBuilder() @@ -88,10 +88,10 @@ void testGetPeriodNameToIsoIdMap() { @Test void testGetPeriodNameToIsoIdMapEmpty() { - AnalyticsMetaData metadata = new AnalyticsMetaData(); - metadata.setItems(Map.of()); - metadata.setDimensions(Map.of()); + AnalyticsMetaData metadataA = new AnalyticsMetaData(); + metadataA.setItems(Map.of()); + metadataA.setDimensions(Map.of()); - assertTrue(metadata.getPeriodNameToIsoIdMap().isEmpty()); + assertTrue(metadataA.getPeriodNameToIsoIdMap().isEmpty()); } } From 8443b1ac33a9d167d95ec80f7a6937298c71b5ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 09:34:46 +0100 Subject: [PATCH 032/119] fix: Update code --- .../hisp/dhis/model/analytics/AnalyticsData.java | 14 ++++++++++++-- .../dhis/model/analytics/AnalyticsDimension.java | 2 +- .../hisp/dhis/model/analytics/AnalyticsHeader.java | 13 ++++++++++++- .../dhis/model/analytics/AnalyticsDataTest.java | 14 +++++++++++--- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 142ba007..56b85672 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -103,9 +103,19 @@ public int getWidth() { public int getHeaderWidth() { return headers != null ? headers.size() : 0; } - + // Non-serializable logic methods + /** + * Indicates whether a header with the given name exists. + * + * @param name the header name. + * @return true if a header with the given name exists, false otherwise. + */ + public boolean headerExists(String name) { + return headers.indexOf(new AnalyticsHeader(name)) != -1; + } + /** * Gets a copy of the headers. * @@ -203,7 +213,7 @@ public void sortRows() { if (isEmpty(rows)) { return; } - + // TODO sort period dimension by ISO name using metadata items details rows.sort( diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDimension.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDimension.java index 079b616d..8460aef8 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDimension.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDimension.java @@ -48,5 +48,5 @@ public class AnalyticsDimension { public static final String PERIOD = "pe"; /** Org unit dimension identifier. */ - public static final String ORG_UNIT = "ou"; + public static final String ORG_UNIT = "ou"; } diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java index 134b8e94..2141c51a 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java @@ -30,6 +30,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -41,8 +42,9 @@ @ToString @NoArgsConstructor @AllArgsConstructor +@EqualsAndHashCode(onlyExplicitlyIncluded = true) public class AnalyticsHeader { - @JsonProperty private String name; + @EqualsAndHashCode.Include @JsonProperty private String name; @JsonProperty private String column; @@ -52,6 +54,15 @@ public class AnalyticsHeader { @JsonProperty private Boolean meta; + /** + * Constructor. + * + * @param name the name. + */ + public AnalyticsHeader(String name) { + this.name = name; + } + /** * Constructor. * diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java index 957ee7fe..320a5f34 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis.model.analytics; +import static org.hisp.dhis.model.analytics.AnalyticsDimension.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -44,9 +45,9 @@ class AnalyticsDataTest { public void beforeEach() { List headers = List.of( - new AnalyticsHeader("dx", "Data", ValueType.TEXT, true), - new AnalyticsHeader("pe", "Period", ValueType.TEXT, true), - new AnalyticsHeader("ou", "OrgUnit", ValueType.TEXT, true), + new AnalyticsHeader(DATA_X, "Data", ValueType.TEXT, true), + new AnalyticsHeader(PERIOD, "Period", ValueType.TEXT, true), + new AnalyticsHeader(ORG_UNIT, "OrgUnit", ValueType.TEXT, true), new AnalyticsHeader("value", "Value", ValueType.NUMBER, false)); AnalyticsMetaData metadata = new AnalyticsMetaData(); @@ -89,6 +90,13 @@ public void beforeEach() { data.addRow(List.of("A8", "B4", "C2", "9")); } + @Test + void testHeaderExists() { + assertTrue(data.headerExists(DATA_X)); + assertTrue(data.headerExists(ORG_UNIT)); + assertFalse(data.headerExists("product")); + } + @Test void testGetCopyOfHeaders() { List copy = data.getCopyOfHeaders(); From 9771774f2e710521ffed8f275eb9742c038634d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 09:37:30 +0100 Subject: [PATCH 033/119] fix: Update code --- .../dhis/model/analytics/AnalyticsData.java | 18 ++++++++++++++---- .../model/analytics/AnalyticsDimension.java | 2 +- .../dhis/model/analytics/AnalyticsHeader.java | 4 ++-- .../model/analytics/AnalyticsDataTest.java | 9 ++++++++- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 56b85672..1179616f 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -103,7 +103,7 @@ public int getWidth() { public int getHeaderWidth() { return headers != null ? headers.size() : 0; } - + // Non-serializable logic methods /** @@ -113,9 +113,19 @@ public int getHeaderWidth() { * @return true if a header with the given name exists, false otherwise. */ public boolean headerExists(String name) { - return headers.indexOf(new AnalyticsHeader(name)) != -1; + return headerIndex(name) != -1; } - + + /** + * Gets the index of the header with the given name. + * + * @param name the header name. + * @return the index of the header with the given name, or -1 if not found. + */ + public int headerIndex(String name) { + return headers.indexOf(new AnalyticsHeader(name)); + } + /** * Gets a copy of the headers. * @@ -213,7 +223,7 @@ public void sortRows() { if (isEmpty(rows)) { return; } - + // TODO sort period dimension by ISO name using metadata items details rows.sort( diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDimension.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDimension.java index 8460aef8..079b616d 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDimension.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDimension.java @@ -48,5 +48,5 @@ public class AnalyticsDimension { public static final String PERIOD = "pe"; /** Org unit dimension identifier. */ - public static final String ORG_UNIT = "ou"; + public static final String ORG_UNIT = "ou"; } diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java index 2141c51a..981ef217 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java @@ -56,13 +56,13 @@ public class AnalyticsHeader { /** * Constructor. - * + * * @param name the name. */ public AnalyticsHeader(String name) { this.name = name; } - + /** * Constructor. * diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java index 320a5f34..e8e57d80 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java @@ -96,7 +96,14 @@ void testHeaderExists() { assertTrue(data.headerExists(ORG_UNIT)); assertFalse(data.headerExists("product")); } - + + @Test + void testHeaderIndex() { + assertEquals(0, data.headerIndex(DATA_X)); + assertEquals(2, data.headerIndex(ORG_UNIT)); + assertEquals(-1, data.headerIndex("product")); + } + @Test void testGetCopyOfHeaders() { List copy = data.getCopyOfHeaders(); From bf48f73b3eed7159958d8f92f0484fc46e0c5a5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 09:38:15 +0100 Subject: [PATCH 034/119] fix: Update code --- src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 1179616f..0798a49e 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -112,6 +112,7 @@ public int getHeaderWidth() { * @param name the header name. * @return true if a header with the given name exists, false otherwise. */ + @JsonIgnore public boolean headerExists(String name) { return headerIndex(name) != -1; } @@ -122,6 +123,7 @@ public boolean headerExists(String name) { * @param name the header name. * @return the index of the header with the given name, or -1 if not found. */ + @JsonIgnore public int headerIndex(String name) { return headers.indexOf(new AnalyticsHeader(name)); } From eb82d7d829c38eb15fef09fadae597d06ad50570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 09:44:56 +0100 Subject: [PATCH 035/119] fix: Update code --- .../java/org/hisp/dhis/model/analytics/AnalyticsData.java | 7 +++++++ .../org/hisp/dhis/model/analytics/AnalyticsMetaData.java | 2 +- .../hisp/dhis/model/analytics/AnalyticsMetadataTest.java | 6 +++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 0798a49e..b572f4f2 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -28,12 +28,15 @@ package org.hisp.dhis.model.analytics; import static org.apache.commons.collections4.CollectionUtils.isEmpty; +import static org.hisp.dhis.model.analytics.AnalyticsDimension.PERIOD; +import static org.hisp.dhis.util.ObjectUtils.isNotNull; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; import lombok.Getter; @@ -226,7 +229,11 @@ public void sortRows() { return; } + Map peMap = isNotNull(metaData) ? metaData.getPeriodNameIsoIdMap() : Map.of(); + boolean hasPeMap = headerExists(PERIOD) && !peMap.isEmpty(); + // TODO sort period dimension by ISO name using metadata items details + // TODO only sort metadata columns rows.sort( (rowA, rowB) -> { diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java index ad065430..35c8b761 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java @@ -58,7 +58,7 @@ public class AnalyticsMetaData { * * @return a map of period ISO strings to period names. */ - public Map getPeriodNameToIsoIdMap() { + public Map getPeriodNameIsoIdMap() { if (isEmpty(items) || isEmpty(dimensions)) { return Map.of(); } diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java index c786854d..9f5b544e 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java @@ -77,8 +77,8 @@ void testGetItemName() { } @Test - void testGetPeriodNameToIsoIdMap() { - Map map = metadata.getPeriodNameToIsoIdMap(); + void testGetPeriodNameIsoIdMap() { + Map map = metadata.getPeriodNameIsoIdMap(); assertEquals(4, map.keySet().size()); assertEquals("B1", map.get("Month 1")); @@ -92,6 +92,6 @@ void testGetPeriodNameToIsoIdMapEmpty() { metadataA.setItems(Map.of()); metadataA.setDimensions(Map.of()); - assertTrue(metadataA.getPeriodNameToIsoIdMap().isEmpty()); + assertTrue(metadataA.getPeriodNameIsoIdMap().isEmpty()); } } From 6e0205373eafa53008e35fd10bc74935bf4df417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 11:55:09 +0100 Subject: [PATCH 036/119] fix: Update code --- .../dhis/model/analytics/AnalyticsData.java | 22 ++++++++++++++++--- .../model/analytics/AnalyticsDataTest.java | 12 +++++++++- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index b572f4f2..5b65eaa4 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -38,6 +38,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; import lombok.Getter; import lombok.Setter; @@ -131,6 +132,19 @@ public int headerIndex(String name) { return headers.indexOf(new AnalyticsHeader(name)); } + /** + * Returns a set of indexes (positions) of metadata headers which represent metadata. + * + * @return a set of indexes of metadata headers which represent metadata. + */ + @JsonIgnore + public Set getHeaderMetaIndexes() { + return headers.stream() + .filter(AnalyticsHeader::isMeta) + .map(headers::indexOf) + .collect(Collectors.toSet()); + } + /** * Gets a copy of the headers. * @@ -230,6 +244,7 @@ public void sortRows() { } Map peMap = isNotNull(metaData) ? metaData.getPeriodNameIsoIdMap() : Map.of(); + Set metaInx = getHeaderMetaIndexes(); boolean hasPeMap = headerExists(PERIOD) && !peMap.isEmpty(); // TODO sort period dimension by ISO name using metadata items details @@ -237,11 +252,11 @@ public void sortRows() { rows.sort( (rowA, rowB) -> { - int maxLength = Math.max(rowA.size(), rowB.size()); + int maxLength = Math.min(rowA.size(), rowB.size()); for (int i = 0; i < maxLength; i++) { - String val1 = i < rowA.size() ? rowA.get(i) : null; - String val2 = i < rowB.size() ? rowB.get(i) : null; + String val1 = rowA.get(i); + String val2 = rowB.get(i); int comparison = Objects.compare(val1, val2, Comparator.naturalOrder()); @@ -249,6 +264,7 @@ public void sortRows() { return comparison; } } + return 0; }); } diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java index e8e57d80..8823b004 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java @@ -27,12 +27,16 @@ */ package org.hisp.dhis.model.analytics; -import static org.hisp.dhis.model.analytics.AnalyticsDimension.*; +import static org.hisp.dhis.model.analytics.AnalyticsDimension.DATA_X; +import static org.hisp.dhis.model.analytics.AnalyticsDimension.ORG_UNIT; +import static org.hisp.dhis.model.analytics.AnalyticsDimension.PERIOD; +import static org.hisp.dhis.support.Assertions.assertContainsExactly; 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.util.List; +import java.util.Set; import org.hisp.dhis.model.ValueType; import org.hisp.dhis.util.MapBuilder; import org.junit.jupiter.api.BeforeEach; @@ -104,6 +108,12 @@ void testHeaderIndex() { assertEquals(-1, data.headerIndex("product")); } + @Test + void testGetHeaderMetaIndexes() { + Set indexes = data.getHeaderMetaIndexes(); + assertContainsExactly(indexes, 0, 1, 2); + } + @Test void testGetCopyOfHeaders() { List copy = data.getCopyOfHeaders(); From 8e18635a8aeec813a78ed9663530b5aa9c7ef024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 11:58:42 +0100 Subject: [PATCH 037/119] fix: Update code --- .../dhis/model/analytics/AnalyticsData.java | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 5b65eaa4..2bc7530c 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -28,15 +28,12 @@ package org.hisp.dhis.model.analytics; import static org.apache.commons.collections4.CollectionUtils.isEmpty; -import static org.hisp.dhis.model.analytics.AnalyticsDimension.PERIOD; -import static org.hisp.dhis.util.ObjectUtils.isNotNull; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -236,32 +233,32 @@ public List> getRowsWithNames() { return _rows; } - /** Orders the data rows in natural order based on their values. */ + /** Orders the data rows in natural order based on their metadata values. */ @JsonIgnore public void sortRows() { if (isEmpty(rows)) { return; } - Map peMap = isNotNull(metaData) ? metaData.getPeriodNameIsoIdMap() : Map.of(); - Set metaInx = getHeaderMetaIndexes(); - boolean hasPeMap = headerExists(PERIOD) && !peMap.isEmpty(); + Set metaIndexes = getHeaderMetaIndexes(); // TODO sort period dimension by ISO name using metadata items details - // TODO only sort metadata columns rows.sort( (rowA, rowB) -> { int maxLength = Math.min(rowA.size(), rowB.size()); for (int i = 0; i < maxLength; i++) { - String val1 = rowA.get(i); - String val2 = rowB.get(i); + // Only sort metadata column values + if (metaIndexes.contains(i)) { + String val1 = rowA.get(i); + String val2 = rowB.get(i); - int comparison = Objects.compare(val1, val2, Comparator.naturalOrder()); + int comparison = Objects.compare(val1, val2, Comparator.naturalOrder()); - if (comparison != 0) { - return comparison; + if (comparison != 0) { + return comparison; + } } } From 566d0082203614030c5be26134e3f2f7f17b8a22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 11:59:58 +0100 Subject: [PATCH 038/119] fix: Update code --- src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 2bc7530c..a4d57a24 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -139,7 +139,7 @@ public Set getHeaderMetaIndexes() { return headers.stream() .filter(AnalyticsHeader::isMeta) .map(headers::indexOf) - .collect(Collectors.toSet()); + .collect(Collectors.toUnmodifiableSet()); } /** From 4166363b50e9fb2159574e6ab526fa49916b50f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 12:00:18 +0100 Subject: [PATCH 039/119] fix: Update code --- src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index a4d57a24..bc0f9377 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -132,7 +132,7 @@ public int headerIndex(String name) { /** * Returns a set of indexes (positions) of metadata headers which represent metadata. * - * @return a set of indexes of metadata headers which represent metadata. + * @return an immutable set of indexes of metadata headers which represent metadata. */ @JsonIgnore public Set getHeaderMetaIndexes() { From 20e00861e9786f6603087aa3e80ec93ddd61554a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 14:08:25 +0100 Subject: [PATCH 040/119] fix: Update code --- .../java/org/hisp/dhis/model/analytics/AnalyticsData.java | 7 ++++++- .../org/hisp/dhis/model/analytics/AnalyticsMetaData.java | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index bc0f9377..d1b3e783 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -27,6 +27,9 @@ */ package org.hisp.dhis.model.analytics; +import static org.hisp.dhis.model.analytics.AnalyticsDimension.PERIOD; +import static org.hisp.dhis.util.ObjectUtils.isNotNull; + import static org.apache.commons.collections4.CollectionUtils.isEmpty; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -34,6 +37,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -241,7 +245,8 @@ public void sortRows() { } Set metaIndexes = getHeaderMetaIndexes(); - + Map peMap = isNotNull(metaData) ? metaData.getPeriodNameIsoIdMap() : Map.of(); + // TODO sort period dimension by ISO name using metadata items details rows.sort( diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java index 35c8b761..eecc90f8 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java @@ -30,7 +30,7 @@ import static org.apache.commons.collections4.CollectionUtils.isEmpty; import static org.apache.commons.collections4.MapUtils.isEmpty; import static org.hisp.dhis.util.ObjectUtils.isNull; - +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.HashMap; import java.util.List; @@ -58,6 +58,7 @@ public class AnalyticsMetaData { * * @return a map of period ISO strings to period names. */ + @JsonIgnore public Map getPeriodNameIsoIdMap() { if (isEmpty(items) || isEmpty(dimensions)) { return Map.of(); @@ -90,6 +91,7 @@ public Map getPeriodNameIsoIdMap() { * @param id the item identifier. * @return the item name, or null if no item exists with the given identifier. */ + @JsonIgnore public String getItemName(String id) { MetaDataItem item = items.get(id); return item != null ? item.getName() : null; From 14455e1a90a0af7583a640d272a9df56cdfd5a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 14:08:29 +0100 Subject: [PATCH 041/119] fix: Update code --- .../java/org/hisp/dhis/model/analytics/AnalyticsData.java | 8 +++----- .../org/hisp/dhis/model/analytics/AnalyticsMetaData.java | 1 + 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index d1b3e783..af346283 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -27,10 +27,8 @@ */ package org.hisp.dhis.model.analytics; -import static org.hisp.dhis.model.analytics.AnalyticsDimension.PERIOD; -import static org.hisp.dhis.util.ObjectUtils.isNotNull; - import static org.apache.commons.collections4.CollectionUtils.isEmpty; +import static org.hisp.dhis.util.ObjectUtils.isNotNull; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; @@ -245,8 +243,8 @@ public void sortRows() { } Set metaIndexes = getHeaderMetaIndexes(); - Map peMap = isNotNull(metaData) ? metaData.getPeriodNameIsoIdMap() : Map.of(); - + Map peMap = isNotNull(metaData) ? metaData.getPeriodNameIsoIdMap() : Map.of(); + // TODO sort period dimension by ISO name using metadata items details rows.sort( diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java index eecc90f8..3472ab0d 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java @@ -30,6 +30,7 @@ import static org.apache.commons.collections4.CollectionUtils.isEmpty; import static org.apache.commons.collections4.MapUtils.isEmpty; import static org.hisp.dhis.util.ObjectUtils.isNull; + import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.HashMap; From 0f6037bb11a41c4b746de437fc49b23095d02af1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 15:37:52 +0100 Subject: [PATCH 042/119] fix: Update code --- .../java/org/hisp/dhis/model/analytics/AnalyticsData.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index af346283..515242e6 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -244,6 +244,7 @@ public void sortRows() { Set metaIndexes = getHeaderMetaIndexes(); Map peMap = isNotNull(metaData) ? metaData.getPeriodNameIsoIdMap() : Map.of(); + int peIndex = headerIndex(AnalyticsDimension.PERIOD); // TODO sort period dimension by ISO name using metadata items details @@ -257,6 +258,12 @@ public void sortRows() { String val1 = rowA.get(i); String val2 = rowB.get(i); + // Retrieve and sort by period ISO ID instead of name + if (i == peIndex && !peMap.isEmpty()) { + val1 = peMap.get(val1); + val2 = peMap.get(val2); + } + int comparison = Objects.compare(val1, val2, Comparator.naturalOrder()); if (comparison != 0) { From 4dbe4f2af8b19b6ccd577bb3da94c7fd32680c66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 17:50:43 +0100 Subject: [PATCH 043/119] fix: Update code --- .../dhis/model/analytics/AnalyticsData.java | 2 - .../analytics/AnalyticsDataSortTest.java | 101 ++++++++++++++++++ .../model/analytics/AnalyticsDataTest.java | 48 ++------- .../analytics/AnalyticsMetadataTest.java | 3 + .../hisp/dhis/response/HttpStatusTest.java | 3 + 5 files changed, 116 insertions(+), 41 deletions(-) create mode 100644 src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataSortTest.java diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 515242e6..54646a3a 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -246,8 +246,6 @@ public void sortRows() { Map peMap = isNotNull(metaData) ? metaData.getPeriodNameIsoIdMap() : Map.of(); int peIndex = headerIndex(AnalyticsDimension.PERIOD); - // TODO sort period dimension by ISO name using metadata items details - rows.sort( (rowA, rowB) -> { int maxLength = Math.min(rowA.size(), rowB.size()); diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataSortTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataSortTest.java new file mode 100644 index 00000000..0ec8f465 --- /dev/null +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataSortTest.java @@ -0,0 +1,101 @@ +package org.hisp.dhis.model.analytics; + +import static org.hisp.dhis.model.analytics.AnalyticsDimension.DATA_X; +import static org.hisp.dhis.model.analytics.AnalyticsDimension.ORG_UNIT; +import static org.hisp.dhis.model.analytics.AnalyticsDimension.PERIOD; +import static org.junit.jupiter.api.Assertions.assertEquals; +import java.util.List; +import org.hisp.dhis.model.ValueType; +import org.hisp.dhis.support.TestTags; +import org.hisp.dhis.util.MapBuilder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(TestTags.UNIT) +class AnalyticsDataSortTest { + private AnalyticsData getDataA() { + List headers = + List.of( + new AnalyticsHeader(DATA_X, "Data", ValueType.TEXT, true), + new AnalyticsHeader(PERIOD, "Period", ValueType.TEXT, true), + new AnalyticsHeader(ORG_UNIT, "OrgUnit", ValueType.TEXT, true), + new AnalyticsHeader("value", "Value", ValueType.NUMBER, false)); + + AnalyticsMetaData metadata = new AnalyticsMetaData(); + metadata.setItems( + new MapBuilder() + .put("A1", new MetaDataItem("Indicator 1")) + .put("A2", new MetaDataItem("Indicator 2")) + .put("A3", new MetaDataItem("Indicator 3")) + .put("A4", new MetaDataItem("Indicator 4")) + .put("A5", new MetaDataItem("Indicator 5")) + .put("A6", new MetaDataItem("Indicator 6")) + .put("A7", new MetaDataItem("Indicator 7")) + .put("A8", new MetaDataItem("Indicator 8")) + .put("B1", new MetaDataItem("Month 1")) + .put("B2", new MetaDataItem("Month 2")) + .put("B3", new MetaDataItem("Month 3")) + .put("B4", new MetaDataItem("Month 4")) + .put("C1", new MetaDataItem("Facility 1")) + .put("C2", new MetaDataItem("Facility 2")) + .build()); + metadata.setDimensions( + new MapBuilder>() + .put("dx", List.of("A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8")) + .put("pe", List.of("B1", "B2", "B3", "B4")) + .put("ou", List.of("C1", "C2")) + .build()); + + AnalyticsData data = new AnalyticsData(); + + data.setHeaders(headers); + data.setMetaData(metadata); + + data.addRow(List.of("A1", "B1", "C1", "2")); + data.addRow(List.of("A2", "B2", "C2", "4")); + data.addRow(List.of("A4", "B4", "C2", "3")); + data.addRow(List.of("A3", "B3", "C1", "7")); + data.addRow(List.of("A5", "B1", "C1", "8")); + data.addRow(List.of("A7", "B3", "C1", "1")); + data.addRow(List.of("A6", "B2", "C2", "6")); + data.addRow(List.of("A8", "B4", "C2", "9")); + + return data; + } + + @Test + void testSortDataA() { + AnalyticsData data = getDataA(); + + List row1 = data.getRow(0); + List row3 = data.getRow(2); + List row5 = data.getRow(4); + List row7 = data.getRow(6); + + assertEquals("A1", row1.get(0)); + assertEquals("C1", row1.get(2)); + assertEquals("A4", row3.get(0)); + assertEquals("C2", row3.get(2)); + assertEquals("A5", row5.get(0)); + assertEquals("C1", row5.get(2)); + assertEquals("A6", row7.get(0)); + assertEquals("C2", row7.get(2)); + + data.sortRows(); + + row1 = data.getRow(0); + row3 = data.getRow(2); + row5 = data.getRow(4); + row7 = data.getRow(6); + + assertEquals("A1", row1.get(0)); + assertEquals("C1", row1.get(2)); + assertEquals("A3", row3.get(0)); + assertEquals("C1", row3.get(2)); + assertEquals("A5", row5.get(0)); + assertEquals("C1", row5.get(2)); + assertEquals("A7", row7.get(0)); + assertEquals("C1", row7.get(2)); + } +} diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java index 8823b004..cd63acd5 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java @@ -38,10 +38,13 @@ import java.util.List; import java.util.Set; import org.hisp.dhis.model.ValueType; +import org.hisp.dhis.support.TestTags; import org.hisp.dhis.util.MapBuilder; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; +@Tag(TestTags.UNIT) class AnalyticsDataTest { private AnalyticsData data; @@ -86,11 +89,11 @@ public void beforeEach() { data.addRow(List.of("A1", "B1", "C1", "2")); data.addRow(List.of("A2", "B2", "C2", "4")); - data.addRow(List.of("A4", "B4", "C2", "3")); data.addRow(List.of("A3", "B3", "C1", "7")); + data.addRow(List.of("A4", "B4", "C2", "3")); data.addRow(List.of("A5", "B1", "C1", "8")); - data.addRow(List.of("A7", "B3", "C1", "1")); data.addRow(List.of("A6", "B2", "C2", "6")); + data.addRow(List.of("A7", "B3", "C1", "1")); data.addRow(List.of("A8", "B4", "C2", "9")); } @@ -156,8 +159,8 @@ void testGetRow() { assertEquals("C1", row1.get(2)); assertEquals("A2", row2.get(0)); assertEquals("C2", row2.get(2)); - assertEquals("A4", row3.get(0)); - assertEquals("C2", row3.get(2)); + assertEquals("A3", row3.get(0)); + assertEquals("C1", row3.get(2)); assertEquals("A5", row5.get(0)); assertEquals("C1", row5.get(2)); } @@ -175,42 +178,9 @@ void testDataWithNames() { assertEquals("Facility 1", row1.get(2)); assertEquals("Indicator 2", row2.get(0)); assertEquals("Facility 2", row2.get(2)); - assertEquals("Indicator 4", row3.get(0)); - assertEquals("Facility 2", row3.get(2)); + assertEquals("Indicator 3", row3.get(0)); + assertEquals("Facility 1", row3.get(2)); assertEquals("Indicator 5", row5.get(0)); assertEquals("Facility 1", row5.get(2)); } - - @Test - void testSortData() { - List row1 = data.getRow(0); - List row3 = data.getRow(2); - List row5 = data.getRow(4); - List row7 = data.getRow(6); - - assertEquals("A1", row1.get(0)); - assertEquals("C1", row1.get(2)); - assertEquals("A4", row3.get(0)); - assertEquals("C2", row3.get(2)); - assertEquals("A5", row5.get(0)); - assertEquals("C1", row5.get(2)); - assertEquals("A6", row7.get(0)); - assertEquals("C2", row7.get(2)); - - data.sortRows(); - - row1 = data.getRow(0); - row3 = data.getRow(2); - row5 = data.getRow(4); - row7 = data.getRow(6); - - assertEquals("A1", row1.get(0)); - assertEquals("C1", row1.get(2)); - assertEquals("A3", row3.get(0)); - assertEquals("C1", row3.get(2)); - assertEquals("A5", row5.get(0)); - assertEquals("C1", row5.get(2)); - assertEquals("A7", row7.get(0)); - assertEquals("C1", row7.get(2)); - } } diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java index 9f5b544e..8d45e8fc 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java @@ -33,10 +33,13 @@ import java.util.List; import java.util.Map; +import org.hisp.dhis.support.TestTags; import org.hisp.dhis.util.MapBuilder; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; +@Tag(TestTags.UNIT) class AnalyticsMetadataTest { private AnalyticsMetaData metadata; diff --git a/src/test/java/org/hisp/dhis/response/HttpStatusTest.java b/src/test/java/org/hisp/dhis/response/HttpStatusTest.java index 52d086d6..34191e28 100644 --- a/src/test/java/org/hisp/dhis/response/HttpStatusTest.java +++ b/src/test/java/org/hisp/dhis/response/HttpStatusTest.java @@ -32,8 +32,11 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import org.hisp.dhis.model.exception.IllegalArgumentFormatException; +import org.hisp.dhis.support.TestTags; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; +@Tag(TestTags.UNIT) class HttpStatusTest { @Test void testValueOf() { From a85f8978e6004eb8dfa7ca3a69e86e79cfab44c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 19:18:57 +0100 Subject: [PATCH 044/119] fix: Update code --- .../analytics/AnalyticsDataSortTest.java | 113 +++++++++++++++++- .../model/analytics/AnalyticsDataTest.java | 26 +++- 2 files changed, 130 insertions(+), 9 deletions(-) diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataSortTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataSortTest.java index 0ec8f465..3a43e31b 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataSortTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataSortTest.java @@ -1,14 +1,41 @@ +/* + * Copyright (c) 2004-2025, 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.model.analytics; import static org.hisp.dhis.model.analytics.AnalyticsDimension.DATA_X; import static org.hisp.dhis.model.analytics.AnalyticsDimension.ORG_UNIT; import static org.hisp.dhis.model.analytics.AnalyticsDimension.PERIOD; import static org.junit.jupiter.api.Assertions.assertEquals; + import java.util.List; import org.hisp.dhis.model.ValueType; import org.hisp.dhis.support.TestTags; import org.hisp.dhis.util.MapBuilder; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -60,14 +87,14 @@ private AnalyticsData getDataA() { data.addRow(List.of("A7", "B3", "C1", "1")); data.addRow(List.of("A6", "B2", "C2", "6")); data.addRow(List.of("A8", "B4", "C2", "9")); - + return data; } @Test void testSortDataA() { AnalyticsData data = getDataA(); - + List row1 = data.getRow(0); List row3 = data.getRow(2); List row5 = data.getRow(4); @@ -98,4 +125,84 @@ void testSortDataA() { assertEquals("A7", row7.get(0)); assertEquals("C1", row7.get(2)); } + + private AnalyticsData getDataB() { + List headers = + List.of( + new AnalyticsHeader(DATA_X, "Data", ValueType.TEXT, true), + new AnalyticsHeader(PERIOD, "Period", ValueType.TEXT, true), + new AnalyticsHeader(ORG_UNIT, "OrgUnit", ValueType.TEXT, true), + new AnalyticsHeader("value", "Value", ValueType.NUMBER, false)); + + AnalyticsMetaData metadata = new AnalyticsMetaData(); + metadata.setItems( + new MapBuilder() + .put("A1", new MetaDataItem("ANC 1")) + .put("A2", new MetaDataItem("ANC 2")) + .put("012025", new MetaDataItem("January 2024")) + .put("022025", new MetaDataItem("February 2024")) + .put("032025", new MetaDataItem("March 2024")) + .put("042025", new MetaDataItem("April 2024")) + .put("052025", new MetaDataItem("May 2024")) + .put("062025", new MetaDataItem("June 2024")) + .put("C1", new MetaDataItem("Ngelehun CHC")) + .put("C2", new MetaDataItem("Benduma MCHP")) + .build()); + metadata.setDimensions( + new MapBuilder>() + .put("dx", List.of("A1", "A2")) + .put("pe", List.of("012025", "022025", "032025", "042025", "052025", "062025")) + .put("ou", List.of("C1", "C2")) + .build()); + + AnalyticsData data = new AnalyticsData(); + + data.setHeaders(headers); + data.setMetaData(metadata); + + data.addRow(List.of("A1", "May 2024", "C1", "1")); + data.addRow(List.of("A1", "January 2024", "C1", "2")); + data.addRow(List.of("A1", "March 2024", "C1", "4")); + data.addRow(List.of("A1", "June 2024", "C2", "3")); + data.addRow(List.of("A1", "February 2024", "C2", "7")); + data.addRow(List.of("A1", "April 2024", "C2", "6")); + data.addRow(List.of("A2", "June 2024", "C2", "5")); + data.addRow(List.of("A2", "January 2024", "C1", "2")); + data.addRow(List.of("A2", "March 2024", "C1", "2")); + data.addRow(List.of("A2", "April 2024", "C2", "9")); + data.addRow(List.of("A2", "February 2024", "C2", "5")); + data.addRow(List.of("A2", "May 2024", "C1", "3")); + + return data; + } + + @Test + void testSortDataB() { + AnalyticsData data = getDataB(); + + data.sortRows(); + + List row1 = data.getRow(0); + List row2 = data.getRow(1); + List row3 = data.getRow(2); + List row4 = data.getRow(3); + List row6 = data.getRow(5); + List row7 = data.getRow(6); + List row8 = data.getRow(7); + + assertEquals("A1", row1.get(0)); + assertEquals("January 2024", row1.get(1)); + assertEquals("A1", row2.get(0)); + assertEquals("February 2024", row2.get(1)); + assertEquals("A1", row3.get(0)); + assertEquals("March 2024", row3.get(1)); + assertEquals("A1", row4.get(0)); + assertEquals("April 2024", row4.get(1)); + assertEquals("A1", row6.get(0)); + assertEquals("June 2024", row6.get(1)); + assertEquals("A2", row7.get(0)); + assertEquals("January 2024", row7.get(1)); + assertEquals("A2", row8.get(0)); + assertEquals("February 2024", row8.get(1)); + } } diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java index cd63acd5..3dae72c2 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java @@ -40,16 +40,12 @@ import org.hisp.dhis.model.ValueType; import org.hisp.dhis.support.TestTags; import org.hisp.dhis.util.MapBuilder; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @Tag(TestTags.UNIT) class AnalyticsDataTest { - private AnalyticsData data; - - @BeforeEach - public void beforeEach() { + private AnalyticsData getDataA() { List headers = List.of( new AnalyticsHeader(DATA_X, "Data", ValueType.TEXT, true), @@ -82,7 +78,7 @@ public void beforeEach() { .put("ou", List.of("C1", "C2")) .build()); - data = new AnalyticsData(); + AnalyticsData data = new AnalyticsData(); data.setHeaders(headers); data.setMetaData(metadata); @@ -95,10 +91,14 @@ public void beforeEach() { data.addRow(List.of("A6", "B2", "C2", "6")); data.addRow(List.of("A7", "B3", "C1", "1")); data.addRow(List.of("A8", "B4", "C2", "9")); + + return data; } @Test void testHeaderExists() { + AnalyticsData data = getDataA(); + assertTrue(data.headerExists(DATA_X)); assertTrue(data.headerExists(ORG_UNIT)); assertFalse(data.headerExists("product")); @@ -106,6 +106,8 @@ void testHeaderExists() { @Test void testHeaderIndex() { + AnalyticsData data = getDataA(); + assertEquals(0, data.headerIndex(DATA_X)); assertEquals(2, data.headerIndex(ORG_UNIT)); assertEquals(-1, data.headerIndex("product")); @@ -113,12 +115,16 @@ void testHeaderIndex() { @Test void testGetHeaderMetaIndexes() { + AnalyticsData data = getDataA(); + Set indexes = data.getHeaderMetaIndexes(); assertContainsExactly(indexes, 0, 1, 2); } @Test void testGetCopyOfHeaders() { + AnalyticsData data = getDataA(); + List copy = data.getCopyOfHeaders(); assertEquals(4, copy.size()); @@ -128,6 +134,8 @@ void testGetCopyOfHeaders() { @Test void testGetWidthHeight() { + AnalyticsData data = getDataA(); + assertEquals(4, data.getWidth()); assertEquals(4, data.getHeaderWidth()); assertEquals(8, data.getHeight()); @@ -135,6 +143,8 @@ void testGetWidthHeight() { @Test void testTruncateDataRows() { + AnalyticsData data = getDataA(); + assertEquals(4, data.getWidth()); assertEquals(4, data.getHeaderWidth()); assertEquals(8, data.getHeight()); @@ -150,6 +160,8 @@ void testTruncateDataRows() { @Test void testGetRow() { + AnalyticsData data = getDataA(); + List row1 = data.getRow(0); List row2 = data.getRow(1); List row3 = data.getRow(2); @@ -167,6 +179,8 @@ void testGetRow() { @Test void testDataWithNames() { + AnalyticsData data = getDataA(); + List> rows = data.getRowsWithNames(); List row1 = rows.get(0); From 5beba5d48c6b2e92ad507fb8fb86947c5158ef0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 21:59:20 +0100 Subject: [PATCH 045/119] fix: Update code --- .../dhis/model/analytics/AnalyticsData.java | 6 +++++- .../model/analytics/AnalyticsMetaData.java | 20 +++++++++---------- .../analytics/AnalyticsMetadataTest.java | 8 ++++---- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 54646a3a..28d046d9 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -243,7 +243,11 @@ public void sortRows() { } Set metaIndexes = getHeaderMetaIndexes(); - Map peMap = isNotNull(metaData) ? metaData.getPeriodNameIsoIdMap() : Map.of(); + + Map peMap = + isNotNull(metaData) + ? metaData.getDimensionItemNameIdMap(AnalyticsDimension.PERIOD) + : Map.of(); int peIndex = headerIndex(AnalyticsDimension.PERIOD); rows.sort( diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java index 3472ab0d..fdf9814f 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java @@ -53,34 +53,34 @@ public class AnalyticsMetaData { @JsonProperty private Map> dimensions; /** - * Get a complete map of period names to period ISO identifiers. If a period dimension with items - * or metadata items not present, an empty map is returned. If the name of any period metadata + * Returns a map of dimension item names to identifiers. If a dimension with items or metadata + * items are not present, an empty map is returned. If the name of any metadata dimension item * item is not present, an empty map is returned. * - * @return a map of period ISO strings to period names. + * @return a map of dimension item names to identifiers. */ @JsonIgnore - public Map getPeriodNameIsoIdMap() { + public Map getDimensionItemNameIdMap(String dimension) { if (isEmpty(items) || isEmpty(dimensions)) { return Map.of(); } - List peDimItems = dimensions.get(AnalyticsDimension.PERIOD); + List dimItems = dimensions.get(dimension); - if (isEmpty(peDimItems)) { + if (isEmpty(dimItems)) { return Map.of(); } Map output = new HashMap<>(); - for (String pe : peDimItems) { - MetaDataItem peItem = items.get(pe); + for (String item : dimItems) { + MetaDataItem metadataItem = items.get(item); - if (isNull(peItem) || isNull(peItem.getName())) { + if (isNull(metadataItem) || isNull(metadataItem.getName())) { return Map.of(); } - output.put(peItem.getName(), pe); + output.put(metadataItem.getName(), item); } return output; diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java index 8d45e8fc..6de207a5 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java @@ -80,8 +80,8 @@ void testGetItemName() { } @Test - void testGetPeriodNameIsoIdMap() { - Map map = metadata.getPeriodNameIsoIdMap(); + void testGetDimensionItemNameIdMap() { + Map map = metadata.getDimensionItemNameIdMap(AnalyticsDimension.PERIOD); assertEquals(4, map.keySet().size()); assertEquals("B1", map.get("Month 1")); @@ -90,11 +90,11 @@ void testGetPeriodNameIsoIdMap() { } @Test - void testGetPeriodNameToIsoIdMapEmpty() { + void testGetDimensionItemNameIdMapEmpty() { AnalyticsMetaData metadataA = new AnalyticsMetaData(); metadataA.setItems(Map.of()); metadataA.setDimensions(Map.of()); - assertTrue(metadataA.getPeriodNameIsoIdMap().isEmpty()); + assertTrue(metadataA.getDimensionItemNameIdMap(AnalyticsDimension.PERIOD).isEmpty()); } } From 3b18c15e9f921920ee12b7712718d27196fcdb21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 22:14:15 +0100 Subject: [PATCH 046/119] fix: Update code --- .../hisp/dhis/model/analytics/AnalyticsData.java | 15 +++++++++++++-- .../model/analytics/AnalyticsDataSortTest.java | 6 +++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 28d046d9..d0da0edc 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -28,6 +28,7 @@ package org.hisp.dhis.model.analytics; import static org.apache.commons.collections4.CollectionUtils.isEmpty; +import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.hisp.dhis.util.ObjectUtils.isNotNull; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -42,6 +43,7 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; +import org.apache.commons.lang3.StringUtils; @Getter @Setter @@ -238,6 +240,15 @@ public List> getRowsWithNames() { /** Orders the data rows in natural order based on their metadata values. */ @JsonIgnore public void sortRows() { + sortRows(StringUtils.EMPTY); + } + + public void sortRowsWithPeriodNameAsId() { + sortRows(AnalyticsDimension.PERIOD); + } + + @JsonIgnore + private void sortRows(String dimension) { if (isEmpty(rows)) { return; } @@ -245,7 +256,7 @@ public void sortRows() { Set metaIndexes = getHeaderMetaIndexes(); Map peMap = - isNotNull(metaData) + isNotBlank(dimension) && isNotNull(metaData) ? metaData.getDimensionItemNameIdMap(AnalyticsDimension.PERIOD) : Map.of(); int peIndex = headerIndex(AnalyticsDimension.PERIOD); @@ -261,7 +272,7 @@ public void sortRows() { String val2 = rowB.get(i); // Retrieve and sort by period ISO ID instead of name - if (i == peIndex && !peMap.isEmpty()) { + if (!peMap.isEmpty() && i == peIndex) { val1 = peMap.get(val1); val2 = peMap.get(val2); } diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataSortTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataSortTest.java index 3a43e31b..dc890ca0 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataSortTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataSortTest.java @@ -92,7 +92,7 @@ private AnalyticsData getDataA() { } @Test - void testSortDataA() { + void testSortRows() { AnalyticsData data = getDataA(); List row1 = data.getRow(0); @@ -177,10 +177,10 @@ private AnalyticsData getDataB() { } @Test - void testSortDataB() { + void testSortRowsWithPeriodNameAsId() { AnalyticsData data = getDataB(); - data.sortRows(); + data.sortRowsWithPeriodNameAsId(); List row1 = data.getRow(0); List row2 = data.getRow(1); From 71199ce6b6634c8ba026ba698ed1fde4d2c58fa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 22:31:45 +0100 Subject: [PATCH 047/119] fix: Update code --- .../java/org/hisp/dhis/model/analytics/AnalyticsData.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index d0da0edc..ff02c46f 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -243,6 +243,13 @@ public void sortRows() { sortRows(StringUtils.EMPTY); } + /** + * Orders the data rows in natural order based on their metadata values. For a period column, + * identified by a column name {@link AnalyticsDimension#PERIOD}, period names are mapped to + * identifiers before compared as part of the sorting. The mapping requires that an {@link + * AnalyticsMetaData} object is present. + */ + @JsonIgnore public void sortRowsWithPeriodNameAsId() { sortRows(AnalyticsDimension.PERIOD); } From 4dfe22e680c5b071d88d0a9b8f25d23be6ea5feb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 22:33:58 +0100 Subject: [PATCH 048/119] fix: Update code --- .../org/hisp/dhis/model/analytics/AnalyticsData.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index ff02c46f..1f594c7e 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -262,11 +262,11 @@ private void sortRows(String dimension) { Set metaIndexes = getHeaderMetaIndexes(); - Map peMap = + Map dimItemNameIdMap = isNotBlank(dimension) && isNotNull(metaData) - ? metaData.getDimensionItemNameIdMap(AnalyticsDimension.PERIOD) + ? metaData.getDimensionItemNameIdMap(dimension) : Map.of(); - int peIndex = headerIndex(AnalyticsDimension.PERIOD); + int dimIndex = headerIndex(dimension); rows.sort( (rowA, rowB) -> { @@ -279,9 +279,9 @@ private void sortRows(String dimension) { String val2 = rowB.get(i); // Retrieve and sort by period ISO ID instead of name - if (!peMap.isEmpty() && i == peIndex) { - val1 = peMap.get(val1); - val2 = peMap.get(val2); + if (!dimItemNameIdMap.isEmpty() && i == dimIndex) { + val1 = dimItemNameIdMap.get(val1); + val2 = dimItemNameIdMap.get(val2); } int comparison = Objects.compare(val1, val2, Comparator.naturalOrder()); From 11de293e62a1942560ed6f50b0a9501a9b7dd413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 22:34:50 +0100 Subject: [PATCH 049/119] fix: Update code --- src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 1f594c7e..316c4edf 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -278,7 +278,7 @@ private void sortRows(String dimension) { String val1 = rowA.get(i); String val2 = rowB.get(i); - // Retrieve and sort by period ISO ID instead of name + // Retrieve and sort by dimension item ID in place of name if (!dimItemNameIdMap.isEmpty() && i == dimIndex) { val1 = dimItemNameIdMap.get(val1); val2 = dimItemNameIdMap.get(val2); From 69ee04b423d78bcaaeb4f1d9c5ccee542abac6ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 27 Oct 2025 22:35:36 +0100 Subject: [PATCH 050/119] fix: Update code --- .../java/org/hisp/dhis/model/analytics/AnalyticsData.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 316c4edf..142ba0a8 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -254,6 +254,11 @@ public void sortRowsWithPeriodNameAsId() { sortRows(AnalyticsDimension.PERIOD); } + /** + * Orders the data rows in natural order based on their metadata values. + * + * @param dimension the dimension identifier matching a column name. + */ @JsonIgnore private void sortRows(String dimension) { if (isEmpty(rows)) { From 99a23b4a441ffb4d7602c629991dcd12aa969417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 28 Oct 2025 14:10:44 +0100 Subject: [PATCH 051/119] fix: Update code --- .../java/org/hisp/dhis/model/NameableObject.java | 13 +++++++++++++ .../hisp/dhis/model/dimension/DimensionItem.java | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/main/java/org/hisp/dhis/model/NameableObject.java b/src/main/java/org/hisp/dhis/model/NameableObject.java index a21d011d..b4492e04 100644 --- a/src/main/java/org/hisp/dhis/model/NameableObject.java +++ b/src/main/java/org/hisp/dhis/model/NameableObject.java @@ -29,14 +29,27 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; @Getter @Setter @ToString(callSuper = true) +@NoArgsConstructor public class NameableObject extends IdentifiableObject { @JsonProperty protected String shortName; @JsonProperty protected String description; + + /** + * Constructor. + * + * @param id the identifier. + * @param code the code. + * @param name the name. + */ + public NameableObject(String id, String code, String name) { + super(id, code, name); + } } diff --git a/src/main/java/org/hisp/dhis/model/dimension/DimensionItem.java b/src/main/java/org/hisp/dhis/model/dimension/DimensionItem.java index 9ff7c68e..3308fadb 100644 --- a/src/main/java/org/hisp/dhis/model/dimension/DimensionItem.java +++ b/src/main/java/org/hisp/dhis/model/dimension/DimensionItem.java @@ -38,4 +38,17 @@ @NoArgsConstructor public class DimensionItem extends NameableObject { @JsonProperty private DimensionItemType dimensionItemType; + + /** + * Constructor. + * + * @param id the identifier. + * @param name the name. + * @param dimensionItemType the {@link DimensionItemType}. + */ + public DimensionItem(String id, String name, DimensionItemType dimensionItemType) { + this.id = id; + this.name = name; + this.dimensionItemType = dimensionItemType; + } } From 0c02dd3e5c9e33bb2daea6db49ae49489fd811cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 28 Oct 2025 14:12:27 +0100 Subject: [PATCH 052/119] fix: Update code --- src/main/java/org/hisp/dhis/model/OrgUnitLevel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/hisp/dhis/model/OrgUnitLevel.java b/src/main/java/org/hisp/dhis/model/OrgUnitLevel.java index 39af0d4f..a42deade 100644 --- a/src/main/java/org/hisp/dhis/model/OrgUnitLevel.java +++ b/src/main/java/org/hisp/dhis/model/OrgUnitLevel.java @@ -35,6 +35,6 @@ @Getter @Setter @NoArgsConstructor -public class OrgUnitLevel extends IdentifiableObject { +public class OrgUnitLevel extends NameableObject { @JsonProperty private int level; } From b1cba595d9772f20064604ace30882d26bf06c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 28 Oct 2025 23:25:53 +0100 Subject: [PATCH 053/119] fix: Update code --- .../dhis/model/analytics/AnalyticsData.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 142ba0a8..aa90e89f 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -237,6 +237,27 @@ public List> getRowsWithNames() { return _rows; } + /** + * Returns a row index as a map. The map key is a string of meta row values concatenated by hypen. + * The map value is the row value at the given value index. + * + * @param valueIndex the value index. + * @return a row index as a map. + */ + public Map getIndex(int valueIndex) { + Set metaIndexes = getHeaderMetaIndexes(); + + return rows.stream() + .collect( + Collectors.toMap( + row -> { + List keys = new ArrayList<>(); + metaIndexes.forEach(i -> keys.add(row.get(valueIndex))); + return String.join("-", keys); + }, + row -> row.get(valueIndex))); + } + /** Orders the data rows in natural order based on their metadata values. */ @JsonIgnore public void sortRows() { From fafe8d87306e918262ef5aeeea8e1151c89ef4f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Wed, 29 Oct 2025 12:37:07 +0100 Subject: [PATCH 054/119] fix: Update code --- .../dhis/model/analytics/AnalyticsData.java | 23 +++-- .../model/analytics/AnalyticsDataIndex.java | 65 +++++++++++++ .../analytics/AnalyticsDataIndexTest.java | 97 +++++++++++++++++++ 3 files changed, 175 insertions(+), 10 deletions(-) create mode 100644 src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java create mode 100644 src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index aa90e89f..d9b903a3 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -244,18 +244,21 @@ public List> getRowsWithNames() { * @param valueIndex the value index. * @return a row index as a map. */ - public Map getIndex(int valueIndex) { + public AnalyticsDataIndex getIndex(int valueIndex) { Set metaIndexes = getHeaderMetaIndexes(); - return rows.stream() - .collect( - Collectors.toMap( - row -> { - List keys = new ArrayList<>(); - metaIndexes.forEach(i -> keys.add(row.get(valueIndex))); - return String.join("-", keys); - }, - row -> row.get(valueIndex))); + Map map = + rows.stream() + .collect( + Collectors.toMap( + row -> { + List keys = new ArrayList<>(); + metaIndexes.forEach(i -> keys.add(row.get(valueIndex))); + return String.join("-", keys); + }, + row -> row.get(valueIndex))); + + return new AnalyticsDataIndex(map, metaIndexes.size()); } /** Orders the data rows in natural order based on their metadata values. */ diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java new file mode 100644 index 00000000..bb0b0261 --- /dev/null +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2004-2025, 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.model.analytics; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class AnalyticsDataIndex extends HashMap { + private static final String SEP = "=="; + + private final int keyCount; + + public AnalyticsDataIndex(Map data, int keyCount) { + super(data); + this.keyCount = keyCount; + } + + public String getValue(String... keys) { + if (keys.length != keyCount) { + String msg = + String.format( + "Provided key count: %d must be equal to index key count: %d", keys.length, keyCount); + throw new IllegalArgumentException(msg); + } + + String key = toKey(keys); + return super.get(key); + } + + /** + * Returns an index map key, where each given key is separated by {@link #SEP}. + * + * @param keys + * @return + */ + private String toKey(String... keys) { + return String.join(SEP, Arrays.asList(keys)); + } +} diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java new file mode 100644 index 00000000..8a14f6be --- /dev/null +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2004-2025, 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.model.analytics; + +import static org.hisp.dhis.model.analytics.AnalyticsDimension.DATA_X; +import static org.hisp.dhis.model.analytics.AnalyticsDimension.ORG_UNIT; +import static org.hisp.dhis.model.analytics.AnalyticsDimension.PERIOD; + +import java.util.List; +import org.hisp.dhis.model.ValueType; +import org.hisp.dhis.support.TestTags; +import org.hisp.dhis.util.MapBuilder; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(TestTags.UNIT) +class AnalyticsDataIndexTest { + private AnalyticsData getDataA() { + List headers = + List.of( + new AnalyticsHeader(DATA_X, "Data", ValueType.TEXT, true), + new AnalyticsHeader(PERIOD, "Period", ValueType.TEXT, true), + new AnalyticsHeader(ORG_UNIT, "OrgUnit", ValueType.TEXT, true), + new AnalyticsHeader("value", "Value", ValueType.NUMBER, false)); + + AnalyticsMetaData metadata = new AnalyticsMetaData(); + metadata.setItems( + new MapBuilder() + .put("A1", new MetaDataItem("Indicator 1")) + .put("A2", new MetaDataItem("Indicator 2")) + .put("A3", new MetaDataItem("Indicator 3")) + .put("A4", new MetaDataItem("Indicator 4")) + .put("A5", new MetaDataItem("Indicator 5")) + .put("A6", new MetaDataItem("Indicator 6")) + .put("A7", new MetaDataItem("Indicator 7")) + .put("A8", new MetaDataItem("Indicator 8")) + .put("B1", new MetaDataItem("Month 1")) + .put("B2", new MetaDataItem("Month 2")) + .put("B3", new MetaDataItem("Month 3")) + .put("B4", new MetaDataItem("Month 4")) + .put("C1", new MetaDataItem("Facility 1")) + .put("C2", new MetaDataItem("Facility 2")) + .build()); + metadata.setDimensions( + new MapBuilder>() + .put("dx", List.of("A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8")) + .put("pe", List.of("B1", "B2", "B3", "B4")) + .put("ou", List.of("C1", "C2")) + .build()); + + AnalyticsData data = new AnalyticsData(); + + data.setHeaders(headers); + data.setMetaData(metadata); + + data.addRow(List.of("A1", "B1", "C1", "2")); + data.addRow(List.of("A2", "B2", "C2", "4")); + data.addRow(List.of("A3", "B3", "C1", "7")); + data.addRow(List.of("A4", "B4", "C2", "3")); + data.addRow(List.of("A5", "B1", "C1", "8")); + data.addRow(List.of("A6", "B2", "C2", "6")); + data.addRow(List.of("A7", "B3", "C1", "1")); + data.addRow(List.of("A8", "B4", "C2", "9")); + + return data; + } + + @Test + void testHeaderExists() { + AnalyticsData data = getDataA(); + } +} From 1e81ba8385c0d8485ecdaea8874163fbbfb1a933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Wed, 29 Oct 2025 12:53:30 +0100 Subject: [PATCH 055/119] fix: Update code --- .../dhis/model/analytics/AnalyticsData.java | 16 ++++----------- .../model/analytics/AnalyticsDataIndex.java | 20 +++++++++++++++++-- .../analytics/AnalyticsDataIndexTest.java | 2 ++ 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index d9b903a3..6c262cdf 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -29,6 +29,7 @@ import static org.apache.commons.collections4.CollectionUtils.isEmpty; import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.hisp.dhis.model.analytics.AnalyticsDataIndex.toKey; import static org.hisp.dhis.util.ObjectUtils.isNotNull; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -245,20 +246,11 @@ public List> getRowsWithNames() { * @return a row index as a map. */ public AnalyticsDataIndex getIndex(int valueIndex) { - Set metaIndexes = getHeaderMetaIndexes(); - + Set keyIndexes = getHeaderMetaIndexes(); Map map = rows.stream() - .collect( - Collectors.toMap( - row -> { - List keys = new ArrayList<>(); - metaIndexes.forEach(i -> keys.add(row.get(valueIndex))); - return String.join("-", keys); - }, - row -> row.get(valueIndex))); - - return new AnalyticsDataIndex(map, metaIndexes.size()); + .collect(Collectors.toMap(row -> toKey(row, keyIndexes), row -> row.get(valueIndex))); + return new AnalyticsDataIndex(map, keyIndexes.size()); } /** Orders the data rows in natural order based on their metadata values. */ diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java index bb0b0261..5f63d8ce 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java @@ -27,9 +27,12 @@ */ package org.hisp.dhis.model.analytics; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Set; public class AnalyticsDataIndex extends HashMap { private static final String SEP = "=="; @@ -56,10 +59,23 @@ public String getValue(String... keys) { /** * Returns an index map key, where each given key is separated by {@link #SEP}. * - * @param keys - * @return + * @param keys the array of key items. + * @return a key. */ private String toKey(String... keys) { return String.join(SEP, Arrays.asList(keys)); } + + /** + * Returns a row index key for the given row based on the given key indexes. + * + * @param row the data row. + * @param valueIndex the index of the value item. + * @return a key. + */ + public static String toKey(List row, Set keyIndexes) { + List keys = new ArrayList<>(); + keyIndexes.forEach(i -> keys.add(row.get(i))); + return String.join(SEP, keys); + } } diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java index 8a14f6be..dbbea571 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java @@ -93,5 +93,7 @@ private AnalyticsData getDataA() { @Test void testHeaderExists() { AnalyticsData data = getDataA(); + + AnalyticsDataIndex dataIndex = data.getIndex(3); } } From c433bd9ea40296bbfb270b5a5ff1dbbe8cf47b5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Wed, 29 Oct 2025 12:53:41 +0100 Subject: [PATCH 056/119] fix: Update code --- .../org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java index dbbea571..f531797c 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java @@ -94,6 +94,8 @@ private AnalyticsData getDataA() { void testHeaderExists() { AnalyticsData data = getDataA(); - AnalyticsDataIndex dataIndex = data.getIndex(3); + AnalyticsDataIndex index = data.getIndex(3); + + } } From 82d548100a56d728bad7c9e447d96ebc706c94b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Wed, 29 Oct 2025 12:54:17 +0100 Subject: [PATCH 057/119] fix: Update code --- .../hisp/dhis/model/analytics/AnalyticsDataIndexTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java index f531797c..85d82e15 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java @@ -30,6 +30,7 @@ import static org.hisp.dhis.model.analytics.AnalyticsDimension.DATA_X; import static org.hisp.dhis.model.analytics.AnalyticsDimension.ORG_UNIT; import static org.hisp.dhis.model.analytics.AnalyticsDimension.PERIOD; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.List; import org.hisp.dhis.model.ValueType; @@ -95,7 +96,7 @@ void testHeaderExists() { AnalyticsData data = getDataA(); AnalyticsDataIndex index = data.getIndex(3); - - + + assertEquals(8, index.keySet().size()); } } From 3b1f92c1074f63ff2bb5d5c83845eaf762771b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Wed, 29 Oct 2025 13:13:55 +0100 Subject: [PATCH 058/119] fix: Update code --- .../dhis/model/analytics/AnalyticsData.java | 18 +++++++--- .../model/analytics/AnalyticsDataIndex.java | 19 ++++++----- .../analytics/AnalyticsDataIndexTest.java | 33 ++++++++++++++++++- .../model/analytics/AnalyticsDataTest.java | 12 +++++-- 4 files changed, 66 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 6c262cdf..a9bd83c1 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -140,13 +140,23 @@ public int headerIndex(String name) { * @return an immutable set of indexes of metadata headers which represent metadata. */ @JsonIgnore - public Set getHeaderMetaIndexes() { + public Set getHeaderMetaIndexSet() { return headers.stream() .filter(AnalyticsHeader::isMeta) .map(headers::indexOf) .collect(Collectors.toUnmodifiableSet()); } + /** + * Returns a set of indexes (positions) of metadata headers which represent metadata. + * + * @return an immutable set of indexes of metadata headers which represent metadata. + */ + @JsonIgnore + public List getHeaderMetaIndexList() { + return headers.stream().filter(AnalyticsHeader::isMeta).map(headers::indexOf).toList(); + } + /** * Gets a copy of the headers. * @@ -246,11 +256,11 @@ public List> getRowsWithNames() { * @return a row index as a map. */ public AnalyticsDataIndex getIndex(int valueIndex) { - Set keyIndexes = getHeaderMetaIndexes(); + List keyIndexes = getHeaderMetaIndexList(); Map map = rows.stream() .collect(Collectors.toMap(row -> toKey(row, keyIndexes), row -> row.get(valueIndex))); - return new AnalyticsDataIndex(map, keyIndexes.size()); + return new AnalyticsDataIndex(map, keyIndexes); } /** Orders the data rows in natural order based on their metadata values. */ @@ -281,7 +291,7 @@ private void sortRows(String dimension) { return; } - Set metaIndexes = getHeaderMetaIndexes(); + Set metaIndexes = getHeaderMetaIndexSet(); Map dimItemNameIdMap = isNotBlank(dimension) && isNotNull(metaData) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java index 5f63d8ce..558d1dfe 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java @@ -32,23 +32,24 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; public class AnalyticsDataIndex extends HashMap { - private static final String SEP = "=="; + private static final String SEP = "::"; - private final int keyCount; + private final List keyIndexes; - public AnalyticsDataIndex(Map data, int keyCount) { + public AnalyticsDataIndex( + Map data, List keyIndexes) { super(data); - this.keyCount = keyCount; + this.keyIndexes = keyIndexes; } public String getValue(String... keys) { - if (keys.length != keyCount) { + if (keys.length != keyIndexes.size()) { String msg = String.format( - "Provided key count: %d must be equal to index key count: %d", keys.length, keyCount); + "Provided key count: %d must be equal to index key count: %d", + keys.length, keyIndexes.size()); throw new IllegalArgumentException(msg); } @@ -70,10 +71,10 @@ private String toKey(String... keys) { * Returns a row index key for the given row based on the given key indexes. * * @param row the data row. - * @param valueIndex the index of the value item. + * @param keyIndexes the list of indexes for keys. * @return a key. */ - public static String toKey(List row, Set keyIndexes) { + public static String toKey(List row, List keyIndexes) { List keys = new ArrayList<>(); keyIndexes.forEach(i -> keys.add(row.get(i))); return String.join(SEP, keys); diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java index 85d82e15..aa9e9cdf 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java @@ -31,6 +31,8 @@ import static org.hisp.dhis.model.analytics.AnalyticsDimension.ORG_UNIT; import static org.hisp.dhis.model.analytics.AnalyticsDimension.PERIOD; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.List; import org.hisp.dhis.model.ValueType; @@ -92,11 +94,40 @@ private AnalyticsData getDataA() { } @Test - void testHeaderExists() { + void testToKey() { + String keyA = AnalyticsDataIndex.toKey(List.of("A1", "B1", "C1", "2"), List.of(0, 1, 2)); + String keyB = AnalyticsDataIndex.toKey(List.of("A4", "B2", "2"), List.of(0, 1)); + + assertEquals("A1::B1::C1", keyA); + assertEquals("A4::B2", keyB); + } + + @Test + void testGetValue() { AnalyticsData data = getDataA(); AnalyticsDataIndex index = data.getIndex(3); assertEquals(8, index.keySet().size()); + + assertEquals("2", index.getValue("A1", "B1", "C1")); + assertEquals("4", index.getValue("A2", "B2", "C2")); + assertEquals("7", index.getValue("A3", "B3", "C1")); + assertEquals("3", index.getValue("A4", "B4", "C2")); + assertEquals("8", index.getValue("A5", "B1", "C1")); + assertEquals("6", index.getValue("A6", "B2", "C2")); + assertEquals("1", index.getValue("A7", "B3", "C1")); + assertEquals("9", index.getValue("A8", "B4", "C2")); + } + + @Test + void testGetValueException() { + AnalyticsData data = getDataA(); + + AnalyticsDataIndex index = data.getIndex(3); + + assertNull(index.getValue("A1", "B9", "C9")); + + assertThrows(IllegalArgumentException.class, () -> index.getValue("A1", "B1")); } } diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java index 3dae72c2..045a420b 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java @@ -114,10 +114,18 @@ void testHeaderIndex() { } @Test - void testGetHeaderMetaIndexes() { + void testGetHeaderMetaIndexSet() { AnalyticsData data = getDataA(); - Set indexes = data.getHeaderMetaIndexes(); + Set indexes = data.getHeaderMetaIndexSet(); + assertContainsExactly(indexes, 0, 1, 2); + } + + @Test + void testGetHeaderMetaIndexList() { + AnalyticsData data = getDataA(); + + List indexes = data.getHeaderMetaIndexList(); assertContainsExactly(indexes, 0, 1, 2); } From b7c3742955470d6b9359141f9ff9f52a51337f1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Wed, 29 Oct 2025 13:22:16 +0100 Subject: [PATCH 059/119] fix: Update code --- src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java | 3 ++- .../java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index a9bd83c1..43586441 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -34,6 +34,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -49,7 +50,7 @@ @Getter @Setter @ToString -public class AnalyticsData { +public class AnalyticsData implements Serializable { /** Analytics column headers. */ @JsonProperty private List headers; diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java index 558d1dfe..14a65eea 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java @@ -38,8 +38,7 @@ public class AnalyticsDataIndex extends HashMap { private final List keyIndexes; - public AnalyticsDataIndex( - Map data, List keyIndexes) { + public AnalyticsDataIndex(Map data, List keyIndexes) { super(data); this.keyIndexes = keyIndexes; } From de9917b1b1cca771d1d77af3ec3c9f4e56b0b962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Wed, 29 Oct 2025 13:23:40 +0100 Subject: [PATCH 060/119] fix: Update code --- .../java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java index 14a65eea..8ae86853 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java @@ -52,8 +52,7 @@ public String getValue(String... keys) { throw new IllegalArgumentException(msg); } - String key = toKey(keys); - return super.get(key); + return super.get(toKey(keys)); } /** From 8b264a8fd9897e352c7dcc06ea74b6524b5d4ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Wed, 29 Oct 2025 13:26:06 +0100 Subject: [PATCH 061/119] fix: Update code --- .../java/org/hisp/dhis/model/analytics/AnalyticsHeader.java | 3 ++- .../java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java | 3 ++- src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java index 981ef217..67f5562a 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsHeader.java @@ -29,6 +29,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -43,7 +44,7 @@ @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode(onlyExplicitlyIncluded = true) -public class AnalyticsHeader { +public class AnalyticsHeader implements Serializable { @EqualsAndHashCode.Include @JsonProperty private String name; @JsonProperty private String column; diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java index fdf9814f..9d1ed557 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java @@ -33,6 +33,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -47,7 +48,7 @@ @ToString @NoArgsConstructor @AllArgsConstructor -public class AnalyticsMetaData { +public class AnalyticsMetaData implements Serializable { @JsonProperty private Map items; @JsonProperty private Map> dimensions; diff --git a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java index 1bb7758d..ba169f39 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java +++ b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java @@ -28,6 +28,7 @@ package org.hisp.dhis.model.analytics; import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -37,7 +38,7 @@ @Setter @ToString @NoArgsConstructor -public class MetaDataItem { +public class MetaDataItem implements Serializable { @JsonProperty private String uid; @JsonProperty private String name; From 0b7db98f68a3aa6e0e3640805ee9a60bb61b9296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Wed, 29 Oct 2025 13:26:55 +0100 Subject: [PATCH 062/119] fix: Update code --- src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 43586441..d826f1ad 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -44,12 +44,10 @@ import java.util.stream.Collectors; import lombok.Getter; import lombok.Setter; -import lombok.ToString; import org.apache.commons.lang3.StringUtils; @Getter @Setter -@ToString public class AnalyticsData implements Serializable { /** Analytics column headers. */ @JsonProperty private List headers; From 99fda6176df9a9dcb9e0eb88dbcaf54540b48277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Wed, 29 Oct 2025 19:15:57 +0100 Subject: [PATCH 063/119] fix: Update code --- src/main/java/org/hisp/dhis/util/JacksonUtils.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/hisp/dhis/util/JacksonUtils.java b/src/main/java/org/hisp/dhis/util/JacksonUtils.java index 4c111283..6e27e08d 100644 --- a/src/main/java/org/hisp/dhis/util/JacksonUtils.java +++ b/src/main/java/org/hisp/dhis/util/JacksonUtils.java @@ -81,6 +81,7 @@ private static ObjectMapper getMapper() { objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); objectMapper.disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES); objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); objectMapper.setSerializationInclusion(Include.NON_NULL); objectMapper.setDateFormat(getDateFormatInternal()); objectMapper.setTimeZone(DateTimeUtils.TZ_UTC); From 53dbb3fcaebf81796c05870bf84d5d160229faa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Wed, 29 Oct 2025 22:07:31 +0100 Subject: [PATCH 064/119] fix: Update code --- .../dhis/model/analytics/AnalyticsData.java | 28 ------------------- .../model/analytics/AnalyticsDataTest.java | 21 -------------- 2 files changed, 49 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index d826f1ad..85d5f2eb 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -219,34 +219,6 @@ public void truncate(int maxRows) { } } - @JsonIgnore - public List> getRowsWithNames() { - if (headers == null || metaData == null || metaData.getItems() == null || rows == null) { - throw new IllegalStateException("Headers, metadata and rows must be present"); - } - - List> _rows = new ArrayList<>(); - - for (List row : rows) { - List _row = new ArrayList<>(); - - for (int i = 0; i < row.size(); i++) { - String value = row.get(i); - boolean meta = headers.get(i).isMeta(); - - if (meta) { - _row.add(metaData.getItemName(value)); - } else { - _row.add(value); - } - } - - _rows.add(_row); - } - - return _rows; - } - /** * Returns a row index as a map. The map key is a string of meta row values concatenated by hypen. * The map value is the row value at the given value index. diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java index 045a420b..08e87e70 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataTest.java @@ -184,25 +184,4 @@ void testGetRow() { assertEquals("A5", row5.get(0)); assertEquals("C1", row5.get(2)); } - - @Test - void testDataWithNames() { - AnalyticsData data = getDataA(); - - List> rows = data.getRowsWithNames(); - - List row1 = rows.get(0); - List row2 = rows.get(1); - List row3 = rows.get(2); - List row5 = rows.get(4); - - assertEquals("Indicator 1", row1.get(0)); - assertEquals("Facility 1", row1.get(2)); - assertEquals("Indicator 2", row2.get(0)); - assertEquals("Facility 2", row2.get(2)); - assertEquals("Indicator 3", row3.get(0)); - assertEquals("Facility 1", row3.get(2)); - assertEquals("Indicator 5", row5.get(0)); - assertEquals("Facility 1", row5.get(2)); - } } From a72fac5ba0aade51f91def314f741bb72626b3a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Wed, 29 Oct 2025 22:27:18 +0100 Subject: [PATCH 065/119] fix: Update code --- .../model/analytics/AnalyticsMetaData.java | 35 +++++++++++++++- .../analytics/AnalyticsMetadataTest.java | 41 +++++++++++++++++-- 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java index 9d1ed557..0ea9e621 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java @@ -28,6 +28,7 @@ package org.hisp.dhis.model.analytics; import static org.apache.commons.collections4.CollectionUtils.isEmpty; +import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import static org.apache.commons.collections4.MapUtils.isEmpty; import static org.hisp.dhis.util.ObjectUtils.isNull; @@ -37,6 +38,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -88,7 +90,7 @@ public Map getDimensionItemNameIdMap(String dimension) { } /** - * Get item name by item identifier. + * Returns the name for the item with the given identifier. * * @param id the item identifier. * @return the item name, or null if no item exists with the given identifier. @@ -98,4 +100,35 @@ public String getItemName(String id) { MetaDataItem item = items.get(id); return item != null ? item.getName() : null; } + + /** + * Returns the {@link MetaDataItem} with the given identifier. + * + * @param id the item identifier. + * @return the {@link MetaDataItem}, or null if not item exists with the given identifier. + */ + public MetaDataItem getMetadataItem(String id) { + MetaDataItem item = items.get(id); + if (item != null) { + item.setUid(id); + } + return item; + } + + /** + * Returns a list of {@link MetaDataItem} for the given dimension. Returns an empty list of the + * dimension does not exist. + * + * @param dimension the dimension identifier. + * @return a list of {@link MetaDataItem}. + */ + public List getMetadataItems(String dimension) { + List items = dimensions.get(dimension); + + if (isNotEmpty(items)) { + return items.stream().map(this::getMetadataItem).filter(Objects::nonNull).toList(); + } + + return List.of(); + } } diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java index 6de207a5..ea38a304 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java @@ -27,6 +27,9 @@ */ package org.hisp.dhis.model.analytics; +import static org.hisp.dhis.model.analytics.AnalyticsDimension.DATA_X; +import static org.hisp.dhis.model.analytics.AnalyticsDimension.ORG_UNIT; +import static org.hisp.dhis.model.analytics.AnalyticsDimension.PERIOD; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -65,9 +68,9 @@ void beforeEach() { .build()); metadata.setDimensions( new MapBuilder>() - .put("dx", List.of("A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8")) - .put("pe", List.of("B1", "B2", "B3", "B4")) - .put("ou", List.of("C1", "C2")) + .put(DATA_X, List.of("A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8")) + .put(PERIOD, List.of("B1", "B2", "B3", "B4")) + .put(ORG_UNIT, List.of("C1", "C2")) .build()); } @@ -79,6 +82,38 @@ void testGetItemName() { assertNull(metadata.getItemName("X1")); } + @Test + void testGetMetadataItem() { + assertEquals("A1", metadata.getMetadataItem("A1").getUid()); + assertEquals("Indicator 1", metadata.getMetadataItem("A1").getName()); + + assertEquals("B1", metadata.getMetadataItem("B1").getUid()); + assertEquals("Month 1", metadata.getMetadataItem("B1").getName()); + + assertNull(metadata.getMetadataItem("X1")); + } + + @Test + void testGetMetadataItems() { + List dataItems = metadata.getMetadataItems(DATA_X); + + assertEquals(8, dataItems.size()); + assertEquals("A1", dataItems.get(0).getUid()); + assertEquals("Indicator 1", dataItems.get(0).getName()); + + List periodItems = metadata.getMetadataItems(PERIOD); + + assertEquals(4, periodItems.size()); + assertEquals("B1", periodItems.get(0).getUid()); + assertEquals("Month 1", periodItems.get(0).getName()); + + List orgUnitItems = metadata.getMetadataItems(ORG_UNIT); + + assertEquals(2, orgUnitItems.size()); + assertEquals("C1", orgUnitItems.get(0).getUid()); + assertEquals("Facility 1", orgUnitItems.get(0).getName()); + } + @Test void testGetDimensionItemNameIdMap() { Map map = metadata.getDimensionItemNameIdMap(AnalyticsDimension.PERIOD); From 2bfc598b0d15455086842bcb763ceec090326784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Wed, 29 Oct 2025 22:30:54 +0100 Subject: [PATCH 066/119] fix: Update code --- .../org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java index ea38a304..14241852 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java @@ -114,6 +114,11 @@ void testGetMetadataItems() { assertEquals("Facility 1", orgUnitItems.get(0).getName()); } + @Test + void testGetMetadataItemsEmpty() { + assertTrue(metadata.getMetadataItems("foo").isEmpty()); + } + @Test void testGetDimensionItemNameIdMap() { Map map = metadata.getDimensionItemNameIdMap(AnalyticsDimension.PERIOD); From 3e4a80342acb9679432bb8d0e349ad4d62a397b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Wed, 29 Oct 2025 22:38:48 +0100 Subject: [PATCH 067/119] fix: Update code --- .../dhis/model/analytics/MetaDataItem.java | 11 ++++ .../dhis/util/IdentifiableObjectUtils.java | 2 +- .../org/hisp/dhis/util/MetadataItemUtils.java | 46 +++++++++++++++++ .../hisp/dhis/util/MetadataItemUtilsTest.java | 50 +++++++++++++++++++ 4 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/hisp/dhis/util/MetadataItemUtils.java create mode 100644 src/test/java/org/hisp/dhis/util/MetadataItemUtilsTest.java diff --git a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java index ba169f39..ba11fd13 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java +++ b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java @@ -69,4 +69,15 @@ public class MetaDataItem implements Serializable { public MetaDataItem(String name) { this.name = name; } + + /** + * Constructor. + * + * @param uid the identifier. + * @param name the name. + */ + public MetaDataItem(String uid, String name) { + this.uid = uid; + this.name = name; + } } diff --git a/src/main/java/org/hisp/dhis/util/IdentifiableObjectUtils.java b/src/main/java/org/hisp/dhis/util/IdentifiableObjectUtils.java index be06f804..148b52b3 100644 --- a/src/main/java/org/hisp/dhis/util/IdentifiableObjectUtils.java +++ b/src/main/java/org/hisp/dhis/util/IdentifiableObjectUtils.java @@ -62,7 +62,7 @@ public static List toCodes(Collection } /** - * Converts the given collection of identifiable objects to a new list of object codes. + * Converts the given collection of identifiable objects to a new list of object names. * * @param the type. * @param objects the collection of {@link IdentifiableObject}. diff --git a/src/main/java/org/hisp/dhis/util/MetadataItemUtils.java b/src/main/java/org/hisp/dhis/util/MetadataItemUtils.java new file mode 100644 index 00000000..55a5b466 --- /dev/null +++ b/src/main/java/org/hisp/dhis/util/MetadataItemUtils.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2004-2025, 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.util; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import org.hisp.dhis.model.analytics.MetaDataItem; + +public class MetadataItemUtils { + /** + * Converts the given collection of metaedata items a new list of names. + * + * @param the type. + * @param objects the collection of {@link MetaDataItem}. + * @return a list of object codes. + */ + public static List toNames(Collection objects) { + return objects.stream().filter(Objects::nonNull).map(MetaDataItem::getName).toList(); + } +} diff --git a/src/test/java/org/hisp/dhis/util/MetadataItemUtilsTest.java b/src/test/java/org/hisp/dhis/util/MetadataItemUtilsTest.java new file mode 100644 index 00000000..521c99db --- /dev/null +++ b/src/test/java/org/hisp/dhis/util/MetadataItemUtilsTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2004-2025, 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.util; + +import static org.hisp.dhis.support.Assertions.assertContainsExactlyInOrder; + +import java.util.List; +import org.hisp.dhis.model.analytics.MetaDataItem; +import org.hisp.dhis.support.TestTags; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(TestTags.UNIT) +class MetadataItemUtilsTest { + @Test + void testToNames() { + MetaDataItem mdA = new MetaDataItem("IDA", "Name A"); + MetaDataItem mdB = new MetaDataItem("IDB", "Name B"); + MetaDataItem mdC = new MetaDataItem("IDC", "Name C"); + + List names = MetadataItemUtils.toNames(List.of(mdA, mdB, mdC)); + + assertContainsExactlyInOrder(names, mdA.getName(), mdB.getName(), mdC.getName()); + } +} From 0a99f4555e4d6e40dd16bdf174d6b538c9d2085b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Thu, 30 Oct 2025 09:50:25 +0100 Subject: [PATCH 068/119] fix: Update code --- .../model/analytics/AnalyticsMetaData.java | 42 +++++++++++++------ .../analytics/AnalyticsMetadataTest.java | 21 ++++++---- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java index 0ea9e621..5db66a92 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java @@ -51,8 +51,14 @@ @NoArgsConstructor @AllArgsConstructor public class AnalyticsMetaData implements Serializable { + /** Map between dimension item identifiers and metadata item objects. */ @JsonProperty private Map items; + /** + * Map between dimension identifier and dimension item identifiers. Dynamic keywords including + * relative periods and org unit levels are resolved to individual item identifiers. See {@link + * AnalyticsDimension} for dimension identifier constants. + */ @JsonProperty private Map> dimensions; /** @@ -89,18 +95,6 @@ public Map getDimensionItemNameIdMap(String dimension) { return output; } - /** - * Returns the name for the item with the given identifier. - * - * @param id the item identifier. - * @return the item name, or null if no item exists with the given identifier. - */ - @JsonIgnore - public String getItemName(String id) { - MetaDataItem item = items.get(id); - return item != null ? item.getName() : null; - } - /** * Returns the {@link MetaDataItem} with the given identifier. * @@ -115,6 +109,28 @@ public MetaDataItem getMetadataItem(String id) { return item; } + /** + * Returns a list of dimension item identifiers for the given dimension. Returns null if the + * dimension does not exist. + * + * @param dimension the dimension identifier. + * @return a list of dimension item identifiers. + */ + public List getDimensionItems(String dimension) { + return dimensions.get(dimension); + } + + /** + * Returns the count of dimension items. Returns -1 if the dimension does not exist. + * + * @param dimension the dimension identifier. + * @return the count of dimension items. + */ + public int getDimensionItemCount(String dimension) { + List items = getDimensionItems(dimension); + return items != null ? items.size() : -1; + } + /** * Returns a list of {@link MetaDataItem} for the given dimension. Returns an empty list of the * dimension does not exist. @@ -123,7 +139,7 @@ public MetaDataItem getMetadataItem(String id) { * @return a list of {@link MetaDataItem}. */ public List getMetadataItems(String dimension) { - List items = dimensions.get(dimension); + List items = getDimensionItems(dimension); if (isNotEmpty(items)) { return items.stream().map(this::getMetadataItem).filter(Objects::nonNull).toList(); diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java index 14241852..5157e428 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java @@ -74,14 +74,6 @@ void beforeEach() { .build()); } - @Test - void testGetItemName() { - assertEquals("Indicator 2", metadata.getItemName("A2")); - assertEquals("Month 3", metadata.getItemName("B3")); - assertEquals("Facility 1", metadata.getItemName("C1")); - assertNull(metadata.getItemName("X1")); - } - @Test void testGetMetadataItem() { assertEquals("A1", metadata.getMetadataItem("A1").getUid()); @@ -93,6 +85,19 @@ void testGetMetadataItem() { assertNull(metadata.getMetadataItem("X1")); } + @Test + void testGetDimensionItems() { + assertEquals(8, metadata.getDimensionItems(DATA_X).size()); + assertEquals(4, metadata.getDimensionItems(PERIOD).size()); + assertNull(metadata.getDimensionItems("foo")); + } + + void testGetDimensionItemCount() { + assertEquals(8, metadata.getDimensionItemCount(DATA_X)); + assertEquals(4, metadata.getDimensionItemCount(PERIOD)); + assertEquals(-1, metadata.getDimensionItemCount("foo")); + } + @Test void testGetMetadataItems() { List dataItems = metadata.getMetadataItems(DATA_X); From 79b940c215df37324df11964aad2e0a6d15167af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Thu, 30 Oct 2025 09:52:15 +0100 Subject: [PATCH 069/119] fix: Update code --- .../org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java index 5157e428..604a3aa8 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java @@ -92,6 +92,7 @@ void testGetDimensionItems() { assertNull(metadata.getDimensionItems("foo")); } + @Test void testGetDimensionItemCount() { assertEquals(8, metadata.getDimensionItemCount(DATA_X)); assertEquals(4, metadata.getDimensionItemCount(PERIOD)); From 3b8728215c06ca62b6cd237e155b81bf6ade5e83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Thu, 30 Oct 2025 09:53:32 +0100 Subject: [PATCH 070/119] fix: Update code --- .../java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java index 5db66a92..1f18a131 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java @@ -127,8 +127,8 @@ public List getDimensionItems(String dimension) { * @return the count of dimension items. */ public int getDimensionItemCount(String dimension) { - List items = getDimensionItems(dimension); - return items != null ? items.size() : -1; + List dimItems = getDimensionItems(dimension); + return dimItems != null ? dimItems.size() : -1; } /** From b0c57b3b80f0a4036a1acda79004cd473c9c7ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Thu, 30 Oct 2025 09:53:45 +0100 Subject: [PATCH 071/119] fix: Update code --- .../org/hisp/dhis/model/analytics/AnalyticsMetaData.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java index 1f18a131..c48c9ee4 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java @@ -139,10 +139,10 @@ public int getDimensionItemCount(String dimension) { * @return a list of {@link MetaDataItem}. */ public List getMetadataItems(String dimension) { - List items = getDimensionItems(dimension); + List dimItems = getDimensionItems(dimension); - if (isNotEmpty(items)) { - return items.stream().map(this::getMetadataItem).filter(Objects::nonNull).toList(); + if (isNotEmpty(dimItems)) { + return dimItems.stream().map(this::getMetadataItem).filter(Objects::nonNull).toList(); } return List.of(); From 84afab9aaed2a8a0163c7fc77a427f668760b939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Fri, 31 Oct 2025 13:59:50 +0100 Subject: [PATCH 072/119] fix: Update code --- .../dhis/model/analytics/AnalyticsMetaData.java | 17 ++++++++++++++++- .../model/analytics/AnalyticsMetadataTest.java | 8 ++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java index c48c9ee4..bb96ef9b 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java @@ -51,7 +51,11 @@ @NoArgsConstructor @AllArgsConstructor public class AnalyticsMetaData implements Serializable { - /** Map between dimension item identifiers and metadata item objects. */ + /** + * Map between dimension item identifiers and metadata item objects. The {@code name} property is + * always set. Other properties are only set if the API request parameter {@code + * includeMetadataDetails} was enabled. + */ @JsonProperty private Map items; /** @@ -109,6 +113,17 @@ public MetaDataItem getMetadataItem(String id) { return item; } + /** + * Returns the name of the metadata item with the given identifier. + * + * @param id the item identifier. + * @return the name of the metadata item, or null if no item exists with the given identifier. + */ + public String getMetadataItemName(String id) { + MetaDataItem item = items.get(id); + return item != null ? item.getName() : null; + } + /** * Returns a list of dimension item identifiers for the given dimension. Returns null if the * dimension does not exist. diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java index 604a3aa8..5b3d7552 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java @@ -83,6 +83,14 @@ void testGetMetadataItem() { assertEquals("Month 1", metadata.getMetadataItem("B1").getName()); assertNull(metadata.getMetadataItem("X1")); + assertNull(metadata.getMetadataItem("Y1")); + } + + @Test + void testGetMetadataItemName() { + assertEquals("Indicator 1", metadata.getMetadataItemName("A1")); + assertEquals("Month 1", metadata.getMetadataItemName("B1")); + assertNull(metadata.getMetadataItemName("X1")); } @Test From 26fc5b2f738d8ee8e1510d01b29a8516c949a2c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Fri, 31 Oct 2025 14:58:00 +0100 Subject: [PATCH 073/119] fix: Update code --- src/main/java/org/hisp/dhis/model/NameableObject.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/org/hisp/dhis/model/NameableObject.java b/src/main/java/org/hisp/dhis/model/NameableObject.java index b4492e04..0758c1b0 100644 --- a/src/main/java/org/hisp/dhis/model/NameableObject.java +++ b/src/main/java/org/hisp/dhis/model/NameableObject.java @@ -42,6 +42,15 @@ public class NameableObject extends IdentifiableObject { @JsonProperty protected String description; + /** + * Constructor. + * + * @param id the identifier. + * @param name the name. + */ + public NameableObject(String id, String name) { + super(id, null, name); + } /** * Constructor. * From 57aee9c54496d01bd663a077faabe0e73e995f80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Fri, 31 Oct 2025 14:58:05 +0100 Subject: [PATCH 074/119] fix: Update code --- src/main/java/org/hisp/dhis/model/NameableObject.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/hisp/dhis/model/NameableObject.java b/src/main/java/org/hisp/dhis/model/NameableObject.java index 0758c1b0..b8d5ee0c 100644 --- a/src/main/java/org/hisp/dhis/model/NameableObject.java +++ b/src/main/java/org/hisp/dhis/model/NameableObject.java @@ -51,6 +51,7 @@ public class NameableObject extends IdentifiableObject { public NameableObject(String id, String name) { super(id, null, name); } + /** * Constructor. * From 0484cc25da83801758d62eda4426123e7168a5b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Fri, 31 Oct 2025 15:43:16 +0100 Subject: [PATCH 075/119] fix: Update code --- .../org/hisp/dhis/model/analytics/AnalyticsKeyword.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsKeyword.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsKeyword.java index 3df6f84e..d7214d4f 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsKeyword.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsKeyword.java @@ -61,13 +61,13 @@ public static final String getOrgUnitLevel(int level) { } /** - * Returns the org unit level keyword for the given level string. + * Returns the org unit level keyword for the given identifier string. * - * @param level the level string. + * @param id the org unit level identifier. * @return the org unit level keyword. */ - public static final String getOrgUnitLevel(String level) { - return ORG_UNIT_LEVEL_PREFIX + level; + public static final String getOrgUnitLevel(String id) { + return ORG_UNIT_LEVEL_PREFIX + id; } /** From c5c6fc4f7f8a044aa39f345e8e31576e4e07b124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sat, 1 Nov 2025 09:32:56 +0100 Subject: [PATCH 076/119] fix: Update code --- .../dhis/model/analytics/AnalyticsMetaData.java | 15 +++++++++++++++ .../model/analytics/AnalyticsMetadataTest.java | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java index bb96ef9b..f053c79b 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java @@ -146,6 +146,21 @@ public int getDimensionItemCount(String dimension) { return dimItems != null ? dimItems.size() : -1; } + /** Returns the count of data dimension items. */ + public int getDataItemCount() { + return getDimensionItemCount(AnalyticsDimension.DATA_X); + } + + /** Returns the count of period dimension items. */ + public int getPeriodItemCount() { + return getDimensionItemCount(AnalyticsDimension.PERIOD); + } + + /** Returns the count of org unit dimension items. */ + public int getOrgUnitItemCount() { + return getDimensionItemCount(AnalyticsDimension.ORG_UNIT); + } + /** * Returns a list of {@link MetaDataItem} for the given dimension. Returns an empty list of the * dimension does not exist. diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java index 5b3d7552..2d83d7ba 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java @@ -107,6 +107,21 @@ void testGetDimensionItemCount() { assertEquals(-1, metadata.getDimensionItemCount("foo")); } + @Test + void testGetDataItemCount() { + assertEquals(8, metadata.getDataItemCount()); + } + + @Test + void testGetPeriodItemCount() { + assertEquals(4, metadata.getPeriodItemCount()); + } + + @Test + void testGetOrgUnitItemCount() { + assertEquals(2, metadata.getOrgUnitItemCount()); + } + @Test void testGetMetadataItems() { List dataItems = metadata.getMetadataItems(DATA_X); From ac7b76aa9725dbc179c4707bf07fc69fdab31459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sun, 2 Nov 2025 17:19:49 +0100 Subject: [PATCH 077/119] fix: Update code --- .../dhis/model/analytics/AnalyticsDataIndex.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java index 8ae86853..dc64a7fc 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java @@ -33,16 +33,31 @@ import java.util.List; import java.util.Map; +/** + * Mapping of analytics dimension items to values (metrics). + */ public class AnalyticsDataIndex extends HashMap { private static final String SEP = "::"; private final List keyIndexes; + /** + * Constructor. + * + * @param data the mapping of analytics dimension items to values (metrics). + * @param keyIndexes the indexes of dimension items in data keys. + */ public AnalyticsDataIndex(Map data, List keyIndexes) { super(data); this.keyIndexes = keyIndexes; } + /** + * Returns a value for the given dimension item keys. + * + * @param keys the dimension item keys. + * @return a value. + */ public String getValue(String... keys) { if (keys.length != keyIndexes.size()) { String msg = From da2233ac21d13f599f5e8e7b4e0fc072b4c25772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sun, 2 Nov 2025 17:19:53 +0100 Subject: [PATCH 078/119] fix: Update code --- .../org/hisp/dhis/model/analytics/AnalyticsDataIndex.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java index dc64a7fc..a666ee04 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java @@ -33,9 +33,7 @@ import java.util.List; import java.util.Map; -/** - * Mapping of analytics dimension items to values (metrics). - */ +/** Mapping of analytics dimension items to values (metrics). */ public class AnalyticsDataIndex extends HashMap { private static final String SEP = "::"; @@ -43,7 +41,7 @@ public class AnalyticsDataIndex extends HashMap { /** * Constructor. - * + * * @param data the mapping of analytics dimension items to values (metrics). * @param keyIndexes the indexes of dimension items in data keys. */ @@ -54,7 +52,7 @@ public AnalyticsDataIndex(Map data, List keyIndexes) { /** * Returns a value for the given dimension item keys. - * + * * @param keys the dimension item keys. * @return a value. */ From 7011be79344d1f75c92d30da09c23dd25f714097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sun, 2 Nov 2025 17:30:40 +0100 Subject: [PATCH 079/119] fix: Update code --- .../dhis/model/analytics/AnalyticsDataIndex.java | 13 +++++++++++++ .../model/analytics/AnalyticsDataIndexTest.java | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java index a666ee04..22d5d650 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsDataIndex.java @@ -32,6 +32,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.commons.lang3.math.NumberUtils; /** Mapping of analytics dimension items to values (metrics). */ public class AnalyticsDataIndex extends HashMap { @@ -68,6 +69,18 @@ public String getValue(String... keys) { return super.get(toKey(keys)); } + /** + * Returns a value as a {@link Double} for the given dimension item keys. Returns null if no value + * exists or value is not numeric. + * + * @param keys the dimension item keys. + * @return a value as {@link Double}. + */ + public Double getDoubleValue(String... keys) { + String value = getValue(keys); + return NumberUtils.isCreatable(value) ? Double.parseDouble(value) : null; + } + /** * Returns an index map key, where each given key is separated by {@link #SEP}. * diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java index aa9e9cdf..b03ae760 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java @@ -120,6 +120,19 @@ void testGetValue() { assertEquals("9", index.getValue("A8", "B4", "C2")); } + @Test + void testGetDoubleValue() { + AnalyticsData data = getDataA(); + + AnalyticsDataIndex index = data.getIndex(3); + + assertEquals(8, index.keySet().size()); + + assertEquals(2d, index.getDoubleValue("A1", "B1", "C1")); + assertEquals(4d, index.getDoubleValue("A2", "B2", "C2")); + assertEquals(7d, index.getDoubleValue("A3", "B3", "C1")); + } + @Test void testGetValueException() { AnalyticsData data = getDataA(); From 6acdb6d153f3ad413220e7b58c6e2e8b8ee0e2fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sun, 2 Nov 2025 17:33:35 +0100 Subject: [PATCH 080/119] fix: Update code --- .../org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java index b03ae760..f2f8b448 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsDataIndexTest.java @@ -118,6 +118,8 @@ void testGetValue() { assertEquals("6", index.getValue("A6", "B2", "C2")); assertEquals("1", index.getValue("A7", "B3", "C1")); assertEquals("9", index.getValue("A8", "B4", "C2")); + + assertNull(index.getValue("X1", "Y1", "Z1")); } @Test @@ -131,6 +133,8 @@ void testGetDoubleValue() { assertEquals(2d, index.getDoubleValue("A1", "B1", "C1")); assertEquals(4d, index.getDoubleValue("A2", "B2", "C2")); assertEquals(7d, index.getDoubleValue("A3", "B3", "C1")); + + assertNull(index.getDoubleValue("X1", "Y1", "Z1")); } @Test From fbfb3f9e4c5bd2a078dc2efc01b1e7ff4e32709f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 4 Nov 2025 16:06:09 +0100 Subject: [PATCH 081/119] fix: Update code --- src/main/java/org/hisp/dhis/model/CategoryOption.java | 9 ++++++++- .../java/org/hisp/dhis/model/CategoryOptionGroup.java | 8 +++++++- src/main/java/org/hisp/dhis/model/DataElement.java | 9 ++++++++- src/main/java/org/hisp/dhis/model/DataElementGroup.java | 9 ++++++++- src/main/java/org/hisp/dhis/model/DataSet.java | 8 +++++++- src/main/java/org/hisp/dhis/model/Indicator.java | 9 ++++++++- src/main/java/org/hisp/dhis/model/OrgUnit.java | 9 ++++++++- src/main/java/org/hisp/dhis/model/OrgUnitGroup.java | 9 ++++++++- src/main/java/org/hisp/dhis/model/ProgramIndicator.java | 9 ++++++++- .../org/hisp/dhis/model/dimension/DimensionItem.java | 9 +++++++++ src/test/java/org/hisp/dhis/util/JacksonUtilsTest.java | 1 + 11 files changed, 80 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/CategoryOption.java b/src/main/java/org/hisp/dhis/model/CategoryOption.java index 01c790a0..349560df 100644 --- a/src/main/java/org/hisp/dhis/model/CategoryOption.java +++ b/src/main/java/org/hisp/dhis/model/CategoryOption.java @@ -37,11 +37,13 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hisp.dhis.model.dimension.DimensionItem; +import org.hisp.dhis.model.dimension.DimensionItemType; @Getter @Setter @NoArgsConstructor -public class CategoryOption extends NameableObject { +public class CategoryOption extends DimensionItem { @JsonProperty @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DATE_FORMAT) private Date startDate; @@ -57,4 +59,9 @@ public class CategoryOption extends NameableObject { @JsonProperty private Set categoryOptionCombos = new HashSet<>(); @JsonProperty private Set organisationUnits = new HashSet<>(); + + @Override + public DimensionItemType getDimensionItemType() { + return DimensionItemType.CATEGORY_OPTION; + } } diff --git a/src/main/java/org/hisp/dhis/model/CategoryOptionGroup.java b/src/main/java/org/hisp/dhis/model/CategoryOptionGroup.java index da20ff33..30203f13 100644 --- a/src/main/java/org/hisp/dhis/model/CategoryOptionGroup.java +++ b/src/main/java/org/hisp/dhis/model/CategoryOptionGroup.java @@ -34,12 +34,13 @@ import lombok.NoArgsConstructor; import lombok.Setter; import org.hisp.dhis.model.dimension.DataDimensionType; +import org.hisp.dhis.model.dimension.DimensionItem; import org.hisp.dhis.model.dimension.DimensionItemType; @Getter @Setter @NoArgsConstructor -public class CategoryOptionGroup extends NameableObject { +public class CategoryOptionGroup extends DimensionItem { @JsonProperty private DataDimensionType dataDimensionType; @JsonProperty private DimensionItemType dimensionItemType; @@ -47,4 +48,9 @@ public class CategoryOptionGroup extends NameableObject { @JsonProperty private List categoryOptions = new ArrayList<>(); @JsonProperty private List groupSets = new ArrayList<>(); + + @Override + public DimensionItemType getDimensionItemType() { + return DimensionItemType.CATEGORY_OPTION_GROUP; + } } diff --git a/src/main/java/org/hisp/dhis/model/DataElement.java b/src/main/java/org/hisp/dhis/model/DataElement.java index afe98bb5..90c370fa 100644 --- a/src/main/java/org/hisp/dhis/model/DataElement.java +++ b/src/main/java/org/hisp/dhis/model/DataElement.java @@ -34,11 +34,13 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hisp.dhis.model.dimension.DimensionItem; +import org.hisp.dhis.model.dimension.DimensionItemType; @Getter @Setter @NoArgsConstructor -public class DataElement extends NameableObject { +public class DataElement extends DimensionItem { @JsonProperty private AggregationType aggregationType; @JsonProperty private ValueType valueType; @@ -59,4 +61,9 @@ public class DataElement extends NameableObject { public boolean hasOptionSet() { return optionSet != null; } + + @Override + public DimensionItemType getDimensionItemType() { + return DimensionItemType.DATA_ELEMENT; + } } diff --git a/src/main/java/org/hisp/dhis/model/DataElementGroup.java b/src/main/java/org/hisp/dhis/model/DataElementGroup.java index 77bf12f3..a0f24c20 100644 --- a/src/main/java/org/hisp/dhis/model/DataElementGroup.java +++ b/src/main/java/org/hisp/dhis/model/DataElementGroup.java @@ -33,12 +33,19 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hisp.dhis.model.dimension.DimensionItem; +import org.hisp.dhis.model.dimension.DimensionItemType; @Getter @Setter @NoArgsConstructor -public class DataElementGroup extends NameableObject { +public class DataElementGroup extends DimensionItem { @JsonProperty private List dataElements = new ArrayList<>(); @JsonProperty private List groupSets = new ArrayList<>(); + + @Override + public DimensionItemType getDimensionItemType() { + return DimensionItemType.DATA_ELEMENT_GROUP; + } } diff --git a/src/main/java/org/hisp/dhis/model/DataSet.java b/src/main/java/org/hisp/dhis/model/DataSet.java index 631b16b4..c8c2aaae 100644 --- a/src/main/java/org/hisp/dhis/model/DataSet.java +++ b/src/main/java/org/hisp/dhis/model/DataSet.java @@ -33,12 +33,13 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hisp.dhis.model.dimension.DimensionItem; import org.hisp.dhis.model.dimension.DimensionItemType; @Getter @Setter @NoArgsConstructor -public class DataSet extends NameableObject { +public class DataSet extends DimensionItem { @JsonProperty private String formName; @JsonProperty private String displayFormName; @@ -98,4 +99,9 @@ public class DataSet extends NameableObject { @JsonProperty private Boolean fieldCombinationRequired; @JsonProperty private Boolean mobile; + + @Override + public DimensionItemType getDimensionItemType() { + return DimensionItemType.REPORTING_RATE; + } } diff --git a/src/main/java/org/hisp/dhis/model/Indicator.java b/src/main/java/org/hisp/dhis/model/Indicator.java index 8ab04da1..e218e334 100644 --- a/src/main/java/org/hisp/dhis/model/Indicator.java +++ b/src/main/java/org/hisp/dhis/model/Indicator.java @@ -31,11 +31,13 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hisp.dhis.model.dimension.DimensionItem; +import org.hisp.dhis.model.dimension.DimensionItemType; @Getter @Setter @NoArgsConstructor -public class Indicator extends NameableObject { +public class Indicator extends DimensionItem { @JsonProperty private IndicatorType indicatorType; @JsonProperty private boolean annualized; @@ -49,4 +51,9 @@ public class Indicator extends NameableObject { @JsonProperty private String denominatorDescription; @JsonProperty private String url; + + @Override + public DimensionItemType getDimensionItemType() { + return DimensionItemType.INDICATOR; + } } diff --git a/src/main/java/org/hisp/dhis/model/OrgUnit.java b/src/main/java/org/hisp/dhis/model/OrgUnit.java index 9a20c1c9..b8d67905 100644 --- a/src/main/java/org/hisp/dhis/model/OrgUnit.java +++ b/src/main/java/org/hisp/dhis/model/OrgUnit.java @@ -35,11 +35,13 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hisp.dhis.model.dimension.DimensionItem; +import org.hisp.dhis.model.dimension.DimensionItemType; @Getter @Setter @NoArgsConstructor -public class OrgUnit extends NameableObject { +public class OrgUnit extends DimensionItem { @JsonProperty private String path; @JsonProperty private Integer level; @@ -85,4 +87,9 @@ public OrgUnit(String id, String name, String shortName, OrgUnit parent, Date op this(id, name, shortName, parent); this.openingDate = openingDate; } + + @Override + public DimensionItemType getDimensionItemType() { + return DimensionItemType.ORGANISATION_UNIT; + } } diff --git a/src/main/java/org/hisp/dhis/model/OrgUnitGroup.java b/src/main/java/org/hisp/dhis/model/OrgUnitGroup.java index 6344a20c..622a6023 100644 --- a/src/main/java/org/hisp/dhis/model/OrgUnitGroup.java +++ b/src/main/java/org/hisp/dhis/model/OrgUnitGroup.java @@ -33,11 +33,13 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hisp.dhis.model.dimension.DimensionItem; +import org.hisp.dhis.model.dimension.DimensionItemType; @Getter @Setter @NoArgsConstructor -public class OrgUnitGroup extends NameableObject { +public class OrgUnitGroup extends DimensionItem { @JsonProperty("organisationUnits") private List orgUnits = new ArrayList<>(); @@ -50,4 +52,9 @@ public OrgUnitGroup(String id, String name, String shortName) { this(id, name); this.shortName = shortName; } + + @Override + public DimensionItemType getDimensionItemType() { + return DimensionItemType.ORGANISATION_UNIT_GROUP; + } } diff --git a/src/main/java/org/hisp/dhis/model/ProgramIndicator.java b/src/main/java/org/hisp/dhis/model/ProgramIndicator.java index 332a8000..d5741c2d 100644 --- a/src/main/java/org/hisp/dhis/model/ProgramIndicator.java +++ b/src/main/java/org/hisp/dhis/model/ProgramIndicator.java @@ -30,8 +30,15 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hisp.dhis.model.dimension.DimensionItem; +import org.hisp.dhis.model.dimension.DimensionItemType; @Getter @Setter @NoArgsConstructor -public class ProgramIndicator extends NameableObject {} +public class ProgramIndicator extends DimensionItem { + @Override + public DimensionItemType getDimensionItemType() { + return DimensionItemType.PROGRAM_INDICATOR; + } +} diff --git a/src/main/java/org/hisp/dhis/model/dimension/DimensionItem.java b/src/main/java/org/hisp/dhis/model/dimension/DimensionItem.java index 3308fadb..ee55eb76 100644 --- a/src/main/java/org/hisp/dhis/model/dimension/DimensionItem.java +++ b/src/main/java/org/hisp/dhis/model/dimension/DimensionItem.java @@ -51,4 +51,13 @@ public DimensionItem(String id, String name, DimensionItemType dimensionItemType this.name = name; this.dimensionItemType = dimensionItemType; } + + /** + * Returns the {@link DimensionItemType}. Method to override by subclasses. + * + * @return the {@link DimensionItemType}. + */ + public DimensionItemType getDimensionItemType() { + return dimensionItemType; + } } diff --git a/src/test/java/org/hisp/dhis/util/JacksonUtilsTest.java b/src/test/java/org/hisp/dhis/util/JacksonUtilsTest.java index 075e2ff0..ba9eb40e 100644 --- a/src/test/java/org/hisp/dhis/util/JacksonUtilsTest.java +++ b/src/test/java/org/hisp/dhis/util/JacksonUtilsTest.java @@ -103,6 +103,7 @@ void testDataElementToJsonString() { "attributeValues":[],\ "translations":[],\ "shortName":"ANC",\ + "dimensionItemType":"DATA_ELEMENT",\ "aggregationType":"SUM",\ "valueType":"NUMBER",\ "domainType":"AGGREGATE",\ From dedad48984bb784fe3fb7fb0f2d92fa68adba449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 4 Nov 2025 16:07:51 +0100 Subject: [PATCH 082/119] fix: Update code --- src/test/java/org/hisp/dhis/TestFixture.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/hisp/dhis/TestFixture.java b/src/test/java/org/hisp/dhis/TestFixture.java index 4b8fb57e..5a63ae97 100644 --- a/src/test/java/org/hisp/dhis/TestFixture.java +++ b/src/test/java/org/hisp/dhis/TestFixture.java @@ -38,7 +38,7 @@ public final class TestFixture { public static final String DEV_URL = "https://play.im.dhis2.org/dev"; - public static final String V41_URL = "https://play.im.dhis2.org/stable-2-41-5-1"; + public static final String V41_URL = "https://play.im.dhis2.org/stable-2-41-6"; public static final String LOCAL_URL = "http://localhost/dhis"; From 7409fc6c6fb6eb7f8c74a0b42a3292e89038535c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 4 Nov 2025 16:10:13 +0100 Subject: [PATCH 083/119] fix: Update code --- src/test/java/org/hisp/dhis/OrgUnitGroupApiTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/hisp/dhis/OrgUnitGroupApiTest.java b/src/test/java/org/hisp/dhis/OrgUnitGroupApiTest.java index 8f23569d..08ac7b78 100644 --- a/src/test/java/org/hisp/dhis/OrgUnitGroupApiTest.java +++ b/src/test/java/org/hisp/dhis/OrgUnitGroupApiTest.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis; +import static org.hisp.dhis.support.Assertions.assertNotBlank; import static org.hisp.dhis.support.Assertions.assertNotEmpty; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -67,8 +68,8 @@ void testGetOrgUnitGroup() { assertEquals(1, orgUnits.size()); assertEquals("ImspTQPwCqd", orgUnits.get(0).getId()); - assertEquals("Sierra Leone", orgUnits.get(0).getName()); - assertEquals("OU_525", orgUnits.get(0).getCode()); + assertNotBlank(orgUnits.get(0).getName()); + assertNotBlank(orgUnits.get(0).getCode()); } @Test From 196438d05aa2468d4c8d7e572766412d76e89b6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 4 Nov 2025 16:33:57 +0100 Subject: [PATCH 084/119] fix: Update code --- src/main/java/org/hisp/dhis/model/CategoryOptionGroup.java | 2 -- src/test/java/org/hisp/dhis/CategoryOptionApiTest.java | 2 ++ src/test/java/org/hisp/dhis/DataElementApiTest.java | 2 ++ src/test/java/org/hisp/dhis/DataElementGroupApiTest.java | 2 ++ src/test/java/org/hisp/dhis/DataSetApiTest.java | 1 + src/test/java/org/hisp/dhis/IndicatorApiTest.java | 2 ++ src/test/java/org/hisp/dhis/OrgUnitApiTest.java | 2 ++ src/test/java/org/hisp/dhis/OrgUnitGroupApiTest.java | 2 ++ 8 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/CategoryOptionGroup.java b/src/main/java/org/hisp/dhis/model/CategoryOptionGroup.java index 30203f13..1189c490 100644 --- a/src/main/java/org/hisp/dhis/model/CategoryOptionGroup.java +++ b/src/main/java/org/hisp/dhis/model/CategoryOptionGroup.java @@ -43,8 +43,6 @@ public class CategoryOptionGroup extends DimensionItem { @JsonProperty private DataDimensionType dataDimensionType; - @JsonProperty private DimensionItemType dimensionItemType; - @JsonProperty private List categoryOptions = new ArrayList<>(); @JsonProperty private List groupSets = new ArrayList<>(); diff --git a/src/test/java/org/hisp/dhis/CategoryOptionApiTest.java b/src/test/java/org/hisp/dhis/CategoryOptionApiTest.java index cc29ac5d..7c7037cb 100644 --- a/src/test/java/org/hisp/dhis/CategoryOptionApiTest.java +++ b/src/test/java/org/hisp/dhis/CategoryOptionApiTest.java @@ -36,6 +36,7 @@ import java.util.List; import org.hisp.dhis.model.CategoryOption; +import org.hisp.dhis.model.dimension.DimensionItemType; import org.hisp.dhis.query.Query; import org.hisp.dhis.response.Status; import org.hisp.dhis.response.object.ObjectResponse; @@ -59,6 +60,7 @@ void getCategoryOption() { assertNotNull(categoryOption.getSharing()); assertNotNull(categoryOption.getAccess()); assertNotEmpty(categoryOption.getCategories()); + assertEquals(DimensionItemType.CATEGORY_OPTION, categoryOption.getDimensionItemType()); } @Test diff --git a/src/test/java/org/hisp/dhis/DataElementApiTest.java b/src/test/java/org/hisp/dhis/DataElementApiTest.java index 8c75c564..3308b703 100644 --- a/src/test/java/org/hisp/dhis/DataElementApiTest.java +++ b/src/test/java/org/hisp/dhis/DataElementApiTest.java @@ -40,6 +40,7 @@ import org.hisp.dhis.model.OptionSet; import org.hisp.dhis.model.ValueType; import org.hisp.dhis.model.acl.Access; +import org.hisp.dhis.model.dimension.DimensionItemType; import org.hisp.dhis.model.metadata.Metadata; import org.hisp.dhis.model.sharing.Sharing; import org.hisp.dhis.model.sharing.UserAccess; @@ -70,6 +71,7 @@ void testGetDataElement() { assertNotNull(dataElement.getAccess()); assertNotNull(dataElement.getSharing()); assertNotNull(dataElement.getValueType()); + assertEquals(DimensionItemType.DATA_ELEMENT, dataElement.getDimensionItemType()); OptionSet optionSet = dataElement.getOptionSet(); diff --git a/src/test/java/org/hisp/dhis/DataElementGroupApiTest.java b/src/test/java/org/hisp/dhis/DataElementGroupApiTest.java index 4a433c0a..f1efe990 100644 --- a/src/test/java/org/hisp/dhis/DataElementGroupApiTest.java +++ b/src/test/java/org/hisp/dhis/DataElementGroupApiTest.java @@ -37,6 +37,7 @@ import org.hisp.dhis.model.DataElement; import org.hisp.dhis.model.DataElementGroup; import org.hisp.dhis.model.IdentifiableObject; +import org.hisp.dhis.model.dimension.DimensionItemType; import org.hisp.dhis.support.TestTags; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -58,6 +59,7 @@ void testGetDataElementGroup() { assertNotNull(deg.getSharing()); assertNotNull(deg.getAccess()); assertNull(deg.getDescription()); + assertEquals(DimensionItemType.DATA_ELEMENT_GROUP, deg.getDimensionItemType()); // Group members assertions diff --git a/src/test/java/org/hisp/dhis/DataSetApiTest.java b/src/test/java/org/hisp/dhis/DataSetApiTest.java index 50384553..919ff03c 100644 --- a/src/test/java/org/hisp/dhis/DataSetApiTest.java +++ b/src/test/java/org/hisp/dhis/DataSetApiTest.java @@ -66,6 +66,7 @@ void testGetDataSet() { assertEquals("bjDvmb4bfuf", dataSet.getCategoryCombo().getId()); assertNotEmpty(dataSet.getDataSetElements()); assertEquals("pBOMPrpg1QX", dataSet.getDimensionItem()); + assertEquals(DimensionItemType.REPORTING_RATE, dataSet.getDimensionItemType()); assertNotNull(dataSet.getOpenFuturePeriods()); assertNotNull(dataSet.getExpiryDays()); assertEquals(FormType.DEFAULT, dataSet.getFormType()); diff --git a/src/test/java/org/hisp/dhis/IndicatorApiTest.java b/src/test/java/org/hisp/dhis/IndicatorApiTest.java index 1fd68f7b..55eeae50 100644 --- a/src/test/java/org/hisp/dhis/IndicatorApiTest.java +++ b/src/test/java/org/hisp/dhis/IndicatorApiTest.java @@ -37,6 +37,7 @@ import java.util.List; import org.hisp.dhis.model.Indicator; import org.hisp.dhis.model.IndicatorType; +import org.hisp.dhis.model.dimension.DimensionItemType; import org.hisp.dhis.model.metadata.Metadata; import org.hisp.dhis.query.Filter; import org.hisp.dhis.query.Query; @@ -65,6 +66,7 @@ void testGetIndicator() { assertNotNull(indicator.getLastUpdated()); assertFalse(indicator.isAnnualized()); assertNotNull(indicator.getUrl()); + assertEquals(DimensionItemType.INDICATOR, indicator.getDimensionItemType()); IndicatorType indicatorType = indicator.getIndicatorType(); assertNotNull(indicatorType.getId()); diff --git a/src/test/java/org/hisp/dhis/OrgUnitApiTest.java b/src/test/java/org/hisp/dhis/OrgUnitApiTest.java index 7554d732..efd5929c 100644 --- a/src/test/java/org/hisp/dhis/OrgUnitApiTest.java +++ b/src/test/java/org/hisp/dhis/OrgUnitApiTest.java @@ -40,6 +40,7 @@ import org.hisp.dhis.model.AttributeValue; import org.hisp.dhis.model.OrgUnit; import org.hisp.dhis.model.OrgUnitGroup; +import org.hisp.dhis.model.dimension.DimensionItemType; import org.hisp.dhis.query.Filter; import org.hisp.dhis.query.Query; import org.hisp.dhis.query.RootJunction; @@ -70,6 +71,7 @@ void testGetOrgUnit() { assertNotNull(ou.getParent()); assertEquals("O6uvpzGd5pu", ou.getParent().getId()); assertNotNull(ou.getOpeningDate()); + assertEquals(DimensionItemType.ORGANISATION_UNIT, ou.getDimensionItemType()); } @Test diff --git a/src/test/java/org/hisp/dhis/OrgUnitGroupApiTest.java b/src/test/java/org/hisp/dhis/OrgUnitGroupApiTest.java index 08ac7b78..f7e9ce0c 100644 --- a/src/test/java/org/hisp/dhis/OrgUnitGroupApiTest.java +++ b/src/test/java/org/hisp/dhis/OrgUnitGroupApiTest.java @@ -36,6 +36,7 @@ import java.util.List; import org.hisp.dhis.model.OrgUnit; import org.hisp.dhis.model.OrgUnitGroup; +import org.hisp.dhis.model.dimension.DimensionItemType; import org.hisp.dhis.model.metadata.Metadata; import org.hisp.dhis.query.Query; import org.hisp.dhis.support.TestTags; @@ -59,6 +60,7 @@ void testGetOrgUnitGroup() { assertNotNull(oug.getSharing()); assertNotNull(oug.getAccess()); assertNull(oug.getDescription()); + assertEquals(DimensionItemType.ORGANISATION_UNIT_GROUP, oug.getDimensionItemType()); // Group members assertions From 029c8e75af84bdb5340fbc2c529acd7a7f2efb76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 4 Nov 2025 16:43:31 +0100 Subject: [PATCH 085/119] fix: Update code --- .../hisp/dhis/model/dimension/DimensionItem.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/org/hisp/dhis/model/dimension/DimensionItem.java b/src/main/java/org/hisp/dhis/model/dimension/DimensionItem.java index ee55eb76..0e2c5bf2 100644 --- a/src/main/java/org/hisp/dhis/model/dimension/DimensionItem.java +++ b/src/main/java/org/hisp/dhis/model/dimension/DimensionItem.java @@ -52,6 +52,19 @@ public DimensionItem(String id, String name, DimensionItemType dimensionItemType this.dimensionItemType = dimensionItemType; } + /** + * Constructor. + * + * @param id the identifier. + * @param code the code. + * @param name the name. + * @param dimensionItemType the {@link DimensionItemType}. + */ + public DimensionItem(String id, String code, String name, DimensionItemType dimensionItemType) { + this(id, name, dimensionItemType); + this.code = code; + } + /** * Returns the {@link DimensionItemType}. Method to override by subclasses. * From e516989719c6a08b2f342f06640f883f34e403c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 4 Nov 2025 17:09:18 +0100 Subject: [PATCH 086/119] fix: Update code --- .../java/org/hisp/dhis/model/analytics/MetaDataItem.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java index ba11fd13..8bbb8258 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java +++ b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java @@ -33,6 +33,8 @@ import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; +import org.hisp.dhis.model.ValueType; +import org.hisp.dhis.model.dimension.DimensionItemType; @Getter @Setter @@ -47,9 +49,9 @@ public class MetaDataItem implements Serializable { @JsonProperty private String code; - @JsonProperty private String dimensionItemType; + @JsonProperty private DimensionItemType dimensionItemType; - @JsonProperty private String valueType; + @JsonProperty private ValueType valueType; @JsonProperty private String totalAggregationType; From ceb9e17c5e611c6b1b869ec1a6fcf07d7f0f8372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 4 Nov 2025 18:11:27 +0100 Subject: [PATCH 087/119] fix: Update code --- .../model/analytics/AnalyticsMetaData.java | 21 ++++++++++ .../dhis/model/analytics/MetaDataItem.java | 13 ++++++ .../analytics/AnalyticsMetadataTest.java | 41 ++++++++++++------- 3 files changed, 61 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java index f053c79b..88ff8d00 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java @@ -44,6 +44,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; +import org.hisp.dhis.model.dimension.DimensionItemType; @Getter @Setter @@ -177,4 +178,24 @@ public List getMetadataItems(String dimension) { return List.of(); } + + /** + * Indicates whether the data dimension has both indicator and data element items. + * + * @return true if the data dimension has both indicator and data element items. + */ + public boolean hasIndicatorsAndDataElementItems() { + return hasDataItem(DimensionItemType.INDICATOR) && hasDataItem(DimensionItemType.DATA_ELEMENT); + } + + /** + * Indicates whether any dimension items of the given type exists for the data dimension. + * + * @param type the {@link DimensionItemType}. + * @return true if any dimension items of the given type exists for the data dimension. + */ + public boolean hasDataItem(DimensionItemType type) { + List items = getMetadataItems(AnalyticsDimension.DATA_X); + return items.stream().anyMatch(item -> item.getDimensionItemType() == type); + } } diff --git a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java index 8bbb8258..0f7a7092 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java +++ b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java @@ -82,4 +82,17 @@ public MetaDataItem(String uid, String name) { this.uid = uid; this.name = name; } + + /** + * Constructor. + * + * @param uid the identifier. + * @param name the name. + * @param dimensionItemType the {@link DimensionItemType}. + */ + public MetaDataItem(String uid, String name, DimensionItemType dimensionItemType) { + this.uid = uid; + this.name = name; + this.dimensionItemType = dimensionItemType; + } } diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java index 2d83d7ba..ab62dec3 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java @@ -31,11 +31,13 @@ import static org.hisp.dhis.model.analytics.AnalyticsDimension.ORG_UNIT; import static org.hisp.dhis.model.analytics.AnalyticsDimension.PERIOD; 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 java.util.List; import java.util.Map; +import org.hisp.dhis.model.dimension.DimensionItemType; import org.hisp.dhis.support.TestTags; import org.hisp.dhis.util.MapBuilder; import org.junit.jupiter.api.BeforeEach; @@ -51,20 +53,20 @@ void beforeEach() { metadata = new AnalyticsMetaData(); metadata.setItems( new MapBuilder() - .put("A1", new MetaDataItem("Indicator 1")) - .put("A2", new MetaDataItem("Indicator 2")) - .put("A3", new MetaDataItem("Indicator 3")) - .put("A4", new MetaDataItem("Indicator 4")) - .put("A5", new MetaDataItem("Indicator 5")) - .put("A6", new MetaDataItem("Indicator 6")) - .put("A7", new MetaDataItem("Indicator 7")) - .put("A8", new MetaDataItem("Indicator 8")) - .put("B1", new MetaDataItem("Month 1")) - .put("B2", new MetaDataItem("Month 2")) - .put("B3", new MetaDataItem("Month 3")) - .put("B4", new MetaDataItem("Month 4")) - .put("C1", new MetaDataItem("Facility 1")) - .put("C2", new MetaDataItem("Facility 2")) + .put("A1", new MetaDataItem("A1", "Indicator 1", DimensionItemType.INDICATOR)) + .put("A2", new MetaDataItem("A2", "Indicator 2", DimensionItemType.INDICATOR)) + .put("A3", new MetaDataItem("A3", "Indicator 3", DimensionItemType.INDICATOR)) + .put("A4", new MetaDataItem("A4", "Indicator 4", DimensionItemType.INDICATOR)) + .put("A5", new MetaDataItem("A5", "Indicator 5", DimensionItemType.INDICATOR)) + .put("A6", new MetaDataItem("A6", "Indicator 6", DimensionItemType.INDICATOR)) + .put("A7", new MetaDataItem("A7", "Indicator 7", DimensionItemType.INDICATOR)) + .put("A8", new MetaDataItem("A8", "Indicator 8", DimensionItemType.INDICATOR)) + .put("B1", new MetaDataItem("B1", "Month 1", DimensionItemType.PERIOD)) + .put("B2", new MetaDataItem("B1", "Month 2", DimensionItemType.PERIOD)) + .put("B3", new MetaDataItem("B1", "Month 3", DimensionItemType.PERIOD)) + .put("B4", new MetaDataItem("B1", "Month 4", DimensionItemType.PERIOD)) + .put("C1", new MetaDataItem("C1", "Facility 1", DimensionItemType.ORGANISATION_UNIT)) + .put("C2", new MetaDataItem("C1", "Facility 2", DimensionItemType.ORGANISATION_UNIT)) .build()); metadata.setDimensions( new MapBuilder>() @@ -166,4 +168,15 @@ void testGetDimensionItemNameIdMapEmpty() { assertTrue(metadataA.getDimensionItemNameIdMap(AnalyticsDimension.PERIOD).isEmpty()); } + + @Test + void testHasDataItem() { + assertTrue(metadata.hasDataItem(DimensionItemType.INDICATOR)); + assertFalse(metadata.hasDataItem(DimensionItemType.CATEGORY_OPTION_GROUP)); + } + + @Test + void testHasIndicatorOrDataElement() { + assertFalse(metadata.hasIndicatorsAndDataElementItems()); + } } From 3f286a70d11d24975d15a97270c4d6853c4bfad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 4 Nov 2025 18:54:34 +0100 Subject: [PATCH 088/119] fix: Update code --- .../org/hisp/dhis/model/analytics/MetaDataItem.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java index 0f7a7092..971f0fe9 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java +++ b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java @@ -95,4 +95,14 @@ public MetaDataItem(String uid, String name, DimensionItemType dimensionItemType this.name = name; this.dimensionItemType = dimensionItemType; } + + /** + * Indicates whether the dimension item is of the given {@link DimensionItemType}. + * + * @param dimensionItemType the {@link DimensionItemType}. + * @return true if the dimension item is of the given type. + */ + public boolean isDimensionItemType(DimensionItemType dimensionItemType) { + return this.dimensionItemType == dimensionItemType; + } } From fdcd1ca9aff1967fe0c9bdd9c87967c8cacfe139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 4 Nov 2025 18:54:41 +0100 Subject: [PATCH 089/119] fix: Update code --- src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java index 971f0fe9..1728dd42 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java +++ b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java @@ -95,7 +95,7 @@ public MetaDataItem(String uid, String name, DimensionItemType dimensionItemType this.name = name; this.dimensionItemType = dimensionItemType; } - + /** * Indicates whether the dimension item is of the given {@link DimensionItemType}. * From 87bb7a3d180ab886a9beebd0a0c94e18d68853f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 4 Nov 2025 19:21:34 +0100 Subject: [PATCH 090/119] fix: Update code --- .../java/org/hisp/dhis/util/CodecUtils.java | 13 ++++++ .../java/org/hisp/dhis/BaseDhis2Test.java | 40 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/main/java/org/hisp/dhis/util/CodecUtils.java b/src/main/java/org/hisp/dhis/util/CodecUtils.java index 197580d4..412b7bdb 100644 --- a/src/main/java/org/hisp/dhis/util/CodecUtils.java +++ b/src/main/java/org/hisp/dhis/util/CodecUtils.java @@ -27,6 +27,9 @@ */ package org.hisp.dhis.util; +import java.net.URI; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.apache.commons.codec.digest.DigestUtils; @@ -42,4 +45,14 @@ public class CodecUtils { public static String md5Hash(String input) { return DigestUtils.md5Hex(input); } + + /** + * Decodes a URI. + * + * @param uri the URI to decode. + * @return the decoded string. + */ + public static String decode(URI uri) { + return URLDecoder.decode(uri.toString(), StandardCharsets.UTF_8); + } } diff --git a/src/test/java/org/hisp/dhis/BaseDhis2Test.java b/src/test/java/org/hisp/dhis/BaseDhis2Test.java index ef32dc89..28263d2d 100644 --- a/src/test/java/org/hisp/dhis/BaseDhis2Test.java +++ b/src/test/java/org/hisp/dhis/BaseDhis2Test.java @@ -32,12 +32,15 @@ import java.io.IOException; import java.net.URI; +import java.util.List; import org.apache.hc.core5.net.URIBuilder; import org.hisp.dhis.model.AggregationType; import org.hisp.dhis.model.DataDomain; import org.hisp.dhis.model.DataElement; import org.hisp.dhis.model.ValueType; +import org.hisp.dhis.query.analytics.AnalyticsQuery; import org.hisp.dhis.support.TestTags; +import org.hisp.dhis.util.CodecUtils; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -109,4 +112,41 @@ void testWithMetadataImportParams() throws Exception { assertEquals(expected, dhis2.withMetadataImportParams(uriBuilder)); } + + @Test + void testWithAnalyticsQueryParams() { + Dhis2 dhis2 = new Dhis2(TestFixture.DEFAULT_CONFIG); + + URIBuilder uriBuilder = + TestFixture.DEFAULT_CONFIG.getResolvedUriBuilder().appendPath("analytics"); + + AnalyticsQuery query = + AnalyticsQuery.instance() + // "ANC 1st visit", "ANC 2nd visit", "ANC 3rd visit" + .addDataDimension(List.of("fbfJHSPpUQD", "cYeuwXTCPkU", "Jtf34kNZhzP")) + .addPeriodDimension(List.of("202501", "202502", "202503")) + // "Sierra Leone" + .addOrgUnitFilter(List.of("ImspTQPwCqd")) + .setSkipData(false) + .setSkipMeta(false) + .setIncludeMetadataDetails(true) + .setIncludeNumDen(true); + + URI url = dhis2.withAnalyticsQueryParams(uriBuilder, query); + + String decodedUrl = CodecUtils.decode(url); + + String expected = + """ + https://play.im.dhis2.org/stable-2-41-6/api/analytics\ + ?dimension=dx:fbfJHSPpUQD;cYeuwXTCPkU;Jtf34kNZhzP\ + &dimension=pe:202501;202502;202503\ + &filter=ou:ImspTQPwCqd\ + &skipMeta=false\ + &skipData=false\ + &includeNumDen=true\ + &includeMetadataDetails=true"""; + + assertEquals(expected, decodedUrl); + } } From 129e21dd3fc51f2b7caef207f888e702f2fc11a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 4 Nov 2025 23:26:48 +0100 Subject: [PATCH 091/119] fix: Update code --- .../model/analytics/AnalyticsMetaData.java | 41 ++++++++++++++++--- .../analytics/AnalyticsMetadataTest.java | 12 +++++- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java index 88ff8d00..3a71aae0 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java @@ -30,6 +30,7 @@ import static org.apache.commons.collections4.CollectionUtils.isEmpty; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import static org.apache.commons.collections4.MapUtils.isEmpty; +import static org.hisp.dhis.util.CollectionUtils.filterToList; import static org.hisp.dhis.util.ObjectUtils.isNull; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -126,25 +127,25 @@ public String getMetadataItemName(String id) { } /** - * Returns a list of dimension item identifiers for the given dimension. Returns null if the - * dimension does not exist. + * Returns a list of dimension item identifiers for the given dimension. Returns an empty list if + * the dimension does not exist. * * @param dimension the dimension identifier. * @return a list of dimension item identifiers. */ public List getDimensionItems(String dimension) { - return dimensions.get(dimension); + return dimensions.getOrDefault(dimension, List.of()); } /** * Returns the count of dimension items. Returns -1 if the dimension does not exist. * * @param dimension the dimension identifier. - * @return the count of dimension items. + * @return the count of dimension items, or -1 if the dimension does not exist. */ public int getDimensionItemCount(String dimension) { - List dimItems = getDimensionItems(dimension); - return dimItems != null ? dimItems.size() : -1; + List items = dimensions.get(dimension); + return items != null ? items.size() : -1; } /** Returns the count of data dimension items. */ @@ -179,6 +180,34 @@ public List getMetadataItems(String dimension) { return List.of(); } + /** + * Returns a list of data element items. + * + * @return a list of data element items as {@link MetadataItem}. + */ + public List getDataElementItems() { + return filterToList( + getDataItems(), it -> it.isDimensionItemType(DimensionItemType.DATA_ELEMENT)); + } + + /** + * Returns a list of data element items. + * + * @return a list of data element items as {@link MetadataItem}. + */ + public List getIndicatorItems() { + return filterToList(getDataItems(), it -> it.isDimensionItemType(DimensionItemType.INDICATOR)); + } + + /** + * Returns a list of data items + * + * @return a list of data items as {@link MetadataItem}. + */ + public List getDataItems() { + return getMetadataItems(AnalyticsDimension.DATA_X); + } + /** * Indicates whether the data dimension has both indicator and data element items. * diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java index ab62dec3..3d187d36 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java @@ -99,7 +99,7 @@ void testGetMetadataItemName() { void testGetDimensionItems() { assertEquals(8, metadata.getDimensionItems(DATA_X).size()); assertEquals(4, metadata.getDimensionItems(PERIOD).size()); - assertNull(metadata.getDimensionItems("foo")); + assertEquals(0, metadata.getDimensionItems("foo").size()); } @Test @@ -179,4 +179,14 @@ void testHasDataItem() { void testHasIndicatorOrDataElement() { assertFalse(metadata.hasIndicatorsAndDataElementItems()); } + + @Test + void testGetDataItems() { + assertEquals(8, metadata.getDataItems().size()); + } + + @Test + void testGetIndicatorItems() { + assertEquals(8, metadata.getIndicatorItems().size()); + } } From b9b55e3505cee216d6c9a183afbd7e5aee1fc2ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 4 Nov 2025 23:29:08 +0100 Subject: [PATCH 092/119] fix: Update code --- .../java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java index 3a71aae0..59a4a1a3 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java @@ -144,8 +144,8 @@ public List getDimensionItems(String dimension) { * @return the count of dimension items, or -1 if the dimension does not exist. */ public int getDimensionItemCount(String dimension) { - List items = dimensions.get(dimension); - return items != null ? items.size() : -1; + List dimItems = dimensions.get(dimension); + return dimItems != null ? dimItems.size() : -1; } /** Returns the count of data dimension items. */ From fa76d6fc9a8b42c7de2de38eb73d3cb68634dc8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 4 Nov 2025 23:50:43 +0100 Subject: [PATCH 093/119] fix: Update code --- .../model/analytics/AnalyticsMetaData.java | 20 ++++++++++++++++++- .../analytics/AnalyticsMetadataTest.java | 15 ++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java index 59a4a1a3..13df1c70 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java @@ -200,7 +200,7 @@ public List getIndicatorItems() { } /** - * Returns a list of data items + * Returns a list of data items. * * @return a list of data items as {@link MetadataItem}. */ @@ -208,6 +208,24 @@ public List getDataItems() { return getMetadataItems(AnalyticsDimension.DATA_X); } + /** + * Returns a list of period items. + * + * @return a list of period items as {@link MetadataItem}. + */ + public List getPeriodItems() { + return getMetadataItems(AnalyticsDimension.PERIOD); + } + + /** + * Returns a list of org unit items. + * + * @return a list of org unit items as {@link MetadataItem}. + */ + public List getOrgUnitItems() { + return getMetadataItems(AnalyticsDimension.ORG_UNIT); + } + /** * Indicates whether the data dimension has both indicator and data element items. * diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java index 3d187d36..1c8224df 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java @@ -30,6 +30,7 @@ import static org.hisp.dhis.model.analytics.AnalyticsDimension.DATA_X; import static org.hisp.dhis.model.analytics.AnalyticsDimension.ORG_UNIT; import static org.hisp.dhis.model.analytics.AnalyticsDimension.PERIOD; +import static org.hisp.dhis.support.Assertions.assertSize; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; @@ -182,11 +183,21 @@ void testHasIndicatorOrDataElement() { @Test void testGetDataItems() { - assertEquals(8, metadata.getDataItems().size()); + assertSize(8, metadata.getDataItems()); } @Test void testGetIndicatorItems() { - assertEquals(8, metadata.getIndicatorItems().size()); + assertSize(8, metadata.getIndicatorItems()); + } + + @Test + void testGetPeriodItems() { + assertSize(4, metadata.getPeriodItems()); + } + + @Test + void testGetOrgUnitItems() { + assertSize(2, metadata.getOrgUnitItems()); } } From e3d4f2bb3f3b8488fed450289ce9ee7aa27d2df2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Wed, 5 Nov 2025 09:40:24 +0100 Subject: [PATCH 094/119] fix: Update code --- .../org/hisp/dhis/util/CollectionUtils.java | 21 +++++++++++++++++++ .../hisp/dhis/util/CollectionUtilsTest.java | 17 +++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/main/java/org/hisp/dhis/util/CollectionUtils.java b/src/main/java/org/hisp/dhis/util/CollectionUtils.java index a6710ce9..62ce81c1 100644 --- a/src/main/java/org/hisp/dhis/util/CollectionUtils.java +++ b/src/main/java/org/hisp/dhis/util/CollectionUtils.java @@ -107,6 +107,27 @@ public static List mutableList(T... items) { return list; } + /** + * Returns a sublist of the given list of the given length, starting from the beginning at index + * 0. Does not throw any exceptions. Returns an empty list of the given items is null, or of the + * given length is less than or equal to zero. + * + * @param type. + * @param items the items. + * @return a sublist. + */ + public static List sublist(List items, int length) { + if (items == null || items.isEmpty() || length <= 0) { + return Collections.emptyList(); + } + + if (items.size() <= length) { + return new ArrayList<>(items); + } + + return new ArrayList<>(items.subList(0, length)); + } + /** * Maps the given collection of objects of type U to an immutable list of objects of type T. Null * objects are not allowed. diff --git a/src/test/java/org/hisp/dhis/util/CollectionUtilsTest.java b/src/test/java/org/hisp/dhis/util/CollectionUtilsTest.java index 86787a4a..59b08f7a 100644 --- a/src/test/java/org/hisp/dhis/util/CollectionUtilsTest.java +++ b/src/test/java/org/hisp/dhis/util/CollectionUtilsTest.java @@ -28,6 +28,7 @@ package org.hisp.dhis.util; import static org.hisp.dhis.support.Assertions.assertContainsExactly; +import static org.hisp.dhis.support.Assertions.assertEmpty; import static org.hisp.dhis.util.CollectionUtils.anyStartsWith; import static org.hisp.dhis.util.CollectionUtils.filterToList; import static org.hisp.dhis.util.CollectionUtils.filterToSet; @@ -44,6 +45,7 @@ import static org.hisp.dhis.util.CollectionUtils.mutableList; import static org.hisp.dhis.util.CollectionUtils.mutableSet; import static org.hisp.dhis.util.CollectionUtils.set; +import static org.hisp.dhis.util.CollectionUtils.sublist; import static org.hisp.dhis.util.CollectionUtils.toCommaSeparated; import static org.hisp.dhis.util.CollectionUtils.toTypedList; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -170,6 +172,21 @@ void testMutableListAcceptsNull() { assertEquals(3, list.size()); } + @Test + void testSublist() { + List list = list("a", "b", "c"); + + assertContainsExactly(sublist(list, 1), "a"); + assertContainsExactly(sublist(list, 2), "a", "b"); + assertContainsExactly(sublist(list, 3), "a", "b", "c"); + assertContainsExactly(sublist(list, 4), "a", "b", "c"); + assertContainsExactly(sublist(list, 9), "a", "b", "c"); + + assertEmpty(sublist(list, 0)); + assertEmpty(sublist(list, -1)); + assertEmpty(sublist(null, 2)); + } + @Test void testGetAtIndex() { List list = list("a", "b", "c"); From 9a51d02d8d635de96a856ccd80543a36700c0b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Wed, 5 Nov 2025 09:43:20 +0100 Subject: [PATCH 095/119] fix: Update code --- .../hisp/dhis/model/analytics/AnalyticsMetaData.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java index 13df1c70..dbb80555 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java @@ -183,7 +183,7 @@ public List getMetadataItems(String dimension) { /** * Returns a list of data element items. * - * @return a list of data element items as {@link MetadataItem}. + * @return a list of data element items as {@link MetaDataItem}. */ public List getDataElementItems() { return filterToList( @@ -193,7 +193,7 @@ public List getDataElementItems() { /** * Returns a list of data element items. * - * @return a list of data element items as {@link MetadataItem}. + * @return a list of data element items as {@link MetaDataItem}. */ public List getIndicatorItems() { return filterToList(getDataItems(), it -> it.isDimensionItemType(DimensionItemType.INDICATOR)); @@ -202,7 +202,7 @@ public List getIndicatorItems() { /** * Returns a list of data items. * - * @return a list of data items as {@link MetadataItem}. + * @return a list of data items as {@link MetaDataItem}. */ public List getDataItems() { return getMetadataItems(AnalyticsDimension.DATA_X); @@ -211,7 +211,7 @@ public List getDataItems() { /** * Returns a list of period items. * - * @return a list of period items as {@link MetadataItem}. + * @return a list of period items as {@link MetaDataItem}. */ public List getPeriodItems() { return getMetadataItems(AnalyticsDimension.PERIOD); @@ -220,7 +220,7 @@ public List getPeriodItems() { /** * Returns a list of org unit items. * - * @return a list of org unit items as {@link MetadataItem}. + * @return a list of org unit items as {@link MetaDataItem}. */ public List getOrgUnitItems() { return getMetadataItems(AnalyticsDimension.ORG_UNIT); From b2895c8cd918dfa57e8f4fbc3a0a2972f5fbf4f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Wed, 5 Nov 2025 21:50:03 +0100 Subject: [PATCH 096/119] fix: Update code --- .../model/analytics/AnalyticsMetaData.java | 117 +++++++++++------- .../analytics/AnalyticsMetadataTest.java | 5 + 2 files changed, 80 insertions(+), 42 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java index a0753d3f..f07a5a57 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsMetaData.java @@ -67,6 +67,8 @@ public class AnalyticsMetaData implements Serializable { */ @JsonProperty private Map> dimensions; + // Dimension items + /** * Returns a map of dimension item names to identifiers. If a dimension with items or metadata * items are not present, an empty map is returned. If the name of any metadata dimension item @@ -102,67 +104,112 @@ public Map getDimensionItemNameIdMap(String dimension) { } /** - * Returns the {@link MetaDataItem} with the given identifier. + * Returns a list of dimension item identifiers for the given dimension. Returns an empty list if + * the dimension does not exist. * - * @param id the item identifier. - * @return the {@link MetaDataItem}, or null if not item exists with the given identifier. + * @param dimension the dimension identifier. + * @return a list of dimension item identifiers. */ - public MetaDataItem getMetadataItem(String id) { - MetaDataItem item = items.get(id); - if (item != null) { - item.setUid(id); - } - return item; + @JsonIgnore + public List getDimensionItems(String dimension) { + return dimensions.getOrDefault(dimension, List.of()); } /** - * Returns the name of the metadata item with the given identifier. + * Indicates whether the data dimension has both indicator and data element items. * - * @param id the item identifier. - * @return the name of the metadata item, or null if no item exists with the given identifier. + * @return true if the data dimension has both indicator and data element items. */ - public String getMetadataItemName(String id) { - MetaDataItem item = items.get(id); - return item != null ? item.getName() : null; + @JsonIgnore + public boolean hasIndicatorsAndDataElementItems() { + return hasDataItem(DimensionItemType.INDICATOR) && hasDataItem(DimensionItemType.DATA_ELEMENT); } /** - * Returns a list of dimension item identifiers for the given dimension. Returns an empty list if - * the dimension does not exist. + * Indicates whether any dimension items of the given type exists for the data dimension. * - * @param dimension the dimension identifier. - * @return a list of dimension item identifiers. + * @param type the {@link DimensionItemType}. + * @return true if any dimension items of the given type exists for the data dimension. */ - public List getDimensionItems(String dimension) { - return dimensions.getOrDefault(dimension, List.of()); + @JsonIgnore + public boolean hasDataItem(DimensionItemType type) { + List items = getMetadataItems(AnalyticsDimension.DATA_X); + return items.stream().anyMatch(item -> item.getDimensionItemType() == type); } + // Dimension item counts + /** * Returns the count of dimension items. Returns -1 if the dimension does not exist. * * @param dimension the dimension identifier. * @return the count of dimension items, or -1 if the dimension does not exist. */ + @JsonIgnore public int getDimensionItemCount(String dimension) { List dimItems = dimensions.get(dimension); return dimItems != null ? dimItems.size() : -1; } /** Returns the count of data dimension items. */ + @JsonIgnore public int getDataItemCount() { return getDimensionItemCount(AnalyticsDimension.DATA_X); } - + + /** Returns the count of data element items. */ + @JsonIgnore + public int getDataElementItemCount() { + return getDataElementItems().size(); + } + + /** Returns the count of indicator items. */ + @JsonIgnore + public int getIndicatorItemCount() { + return getIndicatorItems().size(); + } + /** Returns the count of period dimension items. */ + @JsonIgnore public int getPeriodItemCount() { return getDimensionItemCount(AnalyticsDimension.PERIOD); } /** Returns the count of org unit dimension items. */ + @JsonIgnore public int getOrgUnitItemCount() { return getDimensionItemCount(AnalyticsDimension.ORG_UNIT); } + // Meta data items + + /** + * Returns the {@link MetaDataItem} with the given identifier. + * + * @param id the item identifier. + * @return the {@link MetaDataItem}, or null if not item exists with the given identifier. + */ + @JsonIgnore + public MetaDataItem getMetadataItem(String id) { + MetaDataItem item = items.get(id); + if (item != null) { + item.setUid(id); + } + return item; + } + + /** + * Returns the name of the metadata item with the given identifier. + * + * @param id the item identifier. + * @return the name of the metadata item, or null if no item exists with the given identifier. + */ + @JsonIgnore + public String getMetadataItemName(String id) { + MetaDataItem item = items.get(id); + return item != null ? item.getName() : null; + } + /** * Returns a list of {@link MetaDataItem} for the given dimension. Returns an empty list of the * dimension does not exist. @@ -170,6 +217,7 @@ public int getOrgUnitItemCount() { * @param dimension the dimension identifier. * @return a list of {@link MetaDataItem}. */ + @JsonIgnore public List getMetadataItems(String dimension) { List dimItems = getDimensionItems(dimension); @@ -185,6 +233,7 @@ public List getMetadataItems(String dimension) { * * @return a list of data element items as {@link MetaDataItem}. */ + @JsonIgnore public List getDataElementItems() { return filterToList( getDataItems(), it -> it.isDimensionItemType(DimensionItemType.DATA_ELEMENT)); @@ -195,6 +244,7 @@ public List getDataElementItems() { * * @return a list of indicator items as {@link MetaDataItem}. */ + @JsonIgnore public List getIndicatorItems() { return filterToList(getDataItems(), it -> it.isDimensionItemType(DimensionItemType.INDICATOR)); } @@ -204,6 +254,7 @@ public List getIndicatorItems() { * * @return a list of data items as {@link MetaDataItem}. */ + @JsonIgnore public List getDataItems() { return getMetadataItems(AnalyticsDimension.DATA_X); } @@ -213,6 +264,7 @@ public List getDataItems() { * * @return a list of period items as {@link MetaDataItem}. */ + @JsonIgnore public List getPeriodItems() { return getMetadataItems(AnalyticsDimension.PERIOD); } @@ -222,27 +274,8 @@ public List getPeriodItems() { * * @return a list of org unit items as {@link MetaDataItem}. */ + @JsonIgnore public List getOrgUnitItems() { return getMetadataItems(AnalyticsDimension.ORG_UNIT); } - - /** - * Indicates whether the data dimension has both indicator and data element items. - * - * @return true if the data dimension has both indicator and data element items. - */ - public boolean hasIndicatorsAndDataElementItems() { - return hasDataItem(DimensionItemType.INDICATOR) && hasDataItem(DimensionItemType.DATA_ELEMENT); - } - - /** - * Indicates whether any dimension items of the given type exists for the data dimension. - * - * @param type the {@link DimensionItemType}. - * @return true if any dimension items of the given type exists for the data dimension. - */ - public boolean hasDataItem(DimensionItemType type) { - List items = getMetadataItems(AnalyticsDimension.DATA_X); - return items.stream().anyMatch(item -> item.getDimensionItemType() == type); - } } diff --git a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java index 1c8224df..8e930c67 100644 --- a/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java +++ b/src/test/java/org/hisp/dhis/model/analytics/AnalyticsMetadataTest.java @@ -115,6 +115,11 @@ void testGetDataItemCount() { assertEquals(8, metadata.getDataItemCount()); } + @Test + void testGetIndicatorItemCount() { + assertEquals(8, metadata.getIndicatorItemCount()); + } + @Test void testGetPeriodItemCount() { assertEquals(4, metadata.getPeriodItemCount()); From df10196358b9b8ebb87d8f999c6491d444795c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Fri, 7 Nov 2025 19:21:26 +0100 Subject: [PATCH 097/119] fix: Update code --- .../java/org/hisp/dhis/util/TextUtils.java | 21 +++++++++++++++++++ .../org/hisp/dhis/util/TextUtilsTest.java | 14 +++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/main/java/org/hisp/dhis/util/TextUtils.java b/src/main/java/org/hisp/dhis/util/TextUtils.java index 5f5889b7..e657f97e 100644 --- a/src/main/java/org/hisp/dhis/util/TextUtils.java +++ b/src/main/java/org/hisp/dhis/util/TextUtils.java @@ -175,6 +175,27 @@ public static String wrapInCodeFences(String input, String language) { language, input); } + /** + * Truncates the input string to the specified maximum length, appending the specified ellipsis. + * + * @param input the input string. + * @param maxLength the max length. + * @param ellipsis the ellipsis string to be appended if input length is greater than max length. + * @return the possibly truncated input string. + */ + public static String truncate(String input, int maxLength, String ellipsis) { + if (StringUtils.isEmpty(input) || maxLength <= 0 || ellipsis == null) { + return input; + } + + if (input.length() > maxLength) { + int length = maxLength - ellipsis.length(); + input = input.substring(0, length) + ellipsis; + } + + return input; + } + /** * Wraps the input string in code fences with {@code json} as language. * diff --git a/src/test/java/org/hisp/dhis/util/TextUtilsTest.java b/src/test/java/org/hisp/dhis/util/TextUtilsTest.java index 76f64546..f33f5877 100644 --- a/src/test/java/org/hisp/dhis/util/TextUtilsTest.java +++ b/src/test/java/org/hisp/dhis/util/TextUtilsTest.java @@ -256,6 +256,20 @@ void testWrapInJsonCodeFences() { assertEquals(expected, TextUtils.wrapInJsonCodeFences(input)); } + @Test + void testTruncate() { + String inputA = "ANC 1st, ANC 2nd and ANC 3rd visit coverage for last 12 months"; + String inputB = "Bonthe, Kailahun, Kambia and Moyamba for last 4 quarters"; + String inputC = "Last 12 months"; + + assertEquals("ANC 1st, ANC 2nd and ANC 3rd visit cov..", TextUtils.truncate(inputA, 40, "..")); + assertEquals("Bonthe, Kailahun, Kambia and..", TextUtils.truncate(inputB, 30, "..")); + assertEquals("Last 12 months", TextUtils.truncate(inputC, 40, "..")); + + assertNull(TextUtils.truncate(null, 0, "..")); + assertEquals("Last 12 months", TextUtils.truncate(inputC, -10, "..")); + } + @Test void testIsNull() { assertTrue(TextUtils.isNull(null)); From 65624340a75fd9913fce9c107a74d4278055ce00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sat, 8 Nov 2025 14:06:08 +0100 Subject: [PATCH 098/119] fix: Update code --- src/main/java/org/hisp/dhis/util/TextUtils.java | 16 ++++++++++++++-- .../java/org/hisp/dhis/util/TextUtilsTest.java | 8 +++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/hisp/dhis/util/TextUtils.java b/src/main/java/org/hisp/dhis/util/TextUtils.java index e657f97e..5c4c343b 100644 --- a/src/main/java/org/hisp/dhis/util/TextUtils.java +++ b/src/main/java/org/hisp/dhis/util/TextUtils.java @@ -103,8 +103,8 @@ public static void replaceLast(StringBuilder builder, String text, String replac } /** - * Returns a string based on the predicate. If the predicate is true, it returns the true string, - * otherwise, it returns the false string. + * Returns a string based on the predicate. If the predicate is true, the true string is returned, + * otherwise, the false string is returned. * * @param predicate the boolean predicate. * @param trueString the true string. @@ -115,6 +115,18 @@ public static String getString(boolean predicate, String trueString, String fals return predicate ? trueString : falseString; } + /** + * Returns a string based on the predicate. If the predicate is true, the true string is returned, + * otherwise, {@code null} is returned. + * + * @param predicate the boolean predicate. + * @param trueString the true string. + * @return a string. + */ + public static String getString(boolean predicate, String trueString) { + return predicate ? trueString : null; + } + /** * Removes a substring if it is at the end of the input string, otherwise returns the input. * diff --git a/src/test/java/org/hisp/dhis/util/TextUtilsTest.java b/src/test/java/org/hisp/dhis/util/TextUtilsTest.java index f33f5877..cb2a7df2 100644 --- a/src/test/java/org/hisp/dhis/util/TextUtilsTest.java +++ b/src/test/java/org/hisp/dhis/util/TextUtilsTest.java @@ -99,11 +99,17 @@ void testRemoveEndNull() { } @Test - void testGetString() { + void testGetStringTrueFalse() { assertEquals("exists", TextUtils.getString(true, "exists", "does not exist")); assertEquals("does not exist", TextUtils.getString(false, "exists", "does not exist")); } + @Test + void testGetStringTrue() { + assertEquals("exists", TextUtils.getString(true, "exists")); + assertNull(TextUtils.getString(false, "exists")); + } + @Test void testStripCodeFencesSqlBlockWithNewlines() { String input = From 6a9a86afd83953ad71a65192845f90ea5151ba2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sat, 8 Nov 2025 18:32:05 +0100 Subject: [PATCH 099/119] fix: Update code --- .../java/org/hisp/dhis/util/NumberUtils.java | 21 +++++++++++++++++++ .../org/hisp/dhis/util/NumberUtilsTest.java | 11 ++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/main/java/org/hisp/dhis/util/NumberUtils.java b/src/main/java/org/hisp/dhis/util/NumberUtils.java index 20cc62ab..255f921a 100644 --- a/src/main/java/org/hisp/dhis/util/NumberUtils.java +++ b/src/main/java/org/hisp/dhis/util/NumberUtils.java @@ -116,4 +116,25 @@ public static int toInt(Integer integer) { public static Double toDouble(String string) { return org.apache.commons.lang3.math.NumberUtils.createDouble(string); } + + /** + * Formats the double value so that if the value has no decimals, or has a single zero decimal as + * in {@code .0}, the formatted string excludes the decimal. Otherwise uses two decimals for the + * formatted string. + * + * @param value the double value to format. + * @return the value formatted as a string. + */ + public static String formatDouble(Double value) { + if (value == null) { + return ""; + } + if (value % 1 == 0) { + return String.format("%.0f", value); + } else if (value * 10 % 1 == 0) { + return String.format("%.1f", value); + } else { + return String.format("%.2f", value); + } + } } diff --git a/src/test/java/org/hisp/dhis/util/NumberUtilsTest.java b/src/test/java/org/hisp/dhis/util/NumberUtilsTest.java index 7bc8d8bb..02dc6a7c 100644 --- a/src/test/java/org/hisp/dhis/util/NumberUtilsTest.java +++ b/src/test/java/org/hisp/dhis/util/NumberUtilsTest.java @@ -96,4 +96,15 @@ void testIsInteger() { assertFalse(NumberUtils.isInteger("")); assertFalse(NumberUtils.isInteger(null)); } + + @Test + void testFormatDouble() { + assertEquals("3851", NumberUtils.formatDouble(3851D)); + assertEquals("1472", NumberUtils.formatDouble(1472.0)); + assertEquals("3851.2", NumberUtils.formatDouble(3851.2)); + assertEquals("1472.5", NumberUtils.formatDouble(1472.50)); + assertEquals("5249.39", NumberUtils.formatDouble(5249.387)); + assertEquals("54.25", NumberUtils.formatDouble(54.2485)); + assertEquals("", NumberUtils.formatDouble(null)); + } } From 20977db2c521e1880d6127a64bdea50f54de6a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sat, 8 Nov 2025 18:58:59 +0100 Subject: [PATCH 100/119] fix: Update code --- .../analytics/AnalyticsIndicatorType.java | 46 ++++++++++++++ .../dhis/model/analytics/MetaDataItem.java | 2 + .../dhis/analytics/AnalyticsDataApiTest.java | 60 ++++++++++++++++++- 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/hisp/dhis/model/analytics/AnalyticsIndicatorType.java diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsIndicatorType.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsIndicatorType.java new file mode 100644 index 00000000..d0487ebb --- /dev/null +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsIndicatorType.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2004-2025, 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.model.analytics; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class AnalyticsIndicatorType { + @JsonProperty private String name; + + @JsonProperty private Integer factor; + + @JsonProperty private Boolean number; +} diff --git a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java index 1728dd42..acaeeaf8 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java +++ b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java @@ -63,6 +63,8 @@ public class MetaDataItem implements Serializable { @JsonProperty private String aggregationType; + @JsonProperty private AnalyticsIndicatorType indicatorType; + /** * Constructor. * diff --git a/src/test/java/org/hisp/dhis/analytics/AnalyticsDataApiTest.java b/src/test/java/org/hisp/dhis/analytics/AnalyticsDataApiTest.java index 89fb6706..bb41de76 100644 --- a/src/test/java/org/hisp/dhis/analytics/AnalyticsDataApiTest.java +++ b/src/test/java/org/hisp/dhis/analytics/AnalyticsDataApiTest.java @@ -27,9 +27,12 @@ */ package org.hisp.dhis.analytics; +import static org.hisp.dhis.support.Assertions.assertContainsExactly; +import static org.hisp.dhis.support.Assertions.assertNotBlank; import static org.hisp.dhis.support.Assertions.assertNotEmpty; import static org.hisp.dhis.support.Assertions.assertSize; 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; @@ -40,9 +43,12 @@ import org.hisp.dhis.TestFixture; import org.hisp.dhis.model.ValueType; import org.hisp.dhis.model.analytics.AnalyticsData; +import org.hisp.dhis.model.analytics.AnalyticsDimension; import org.hisp.dhis.model.analytics.AnalyticsHeader; +import org.hisp.dhis.model.analytics.AnalyticsIndicatorType; import org.hisp.dhis.model.analytics.AnalyticsMetaData; import org.hisp.dhis.model.analytics.MetaDataItem; +import org.hisp.dhis.model.dimension.DimensionItemType; import org.hisp.dhis.query.analytics.AnalyticsQuery; import org.hisp.dhis.support.TestTags; import org.junit.jupiter.api.Tag; @@ -130,7 +136,7 @@ void testGetAnalyticsDataWithDimensionAndNoItems() { AnalyticsData data = dhis2.getAnalyticsData(query); - log.info(data.toString()); + log.debug(data.toString()); assertEquals(4, data.getWidth()); assertSize(4, data.getHeaders()); @@ -140,4 +146,56 @@ void testGetAnalyticsDataWithDimensionAndNoItems() { assertSize(4, firstRow); } + + @Test + void testGetAnalyticsDataIndicatorAndMetadataDetails() { + Dhis2 dhis2 = new Dhis2(TestFixture.DEFAULT_CONFIG); + + AnalyticsQuery query = + AnalyticsQuery.instance() + // "ANC 1 Coverage" + .addDataDimension(List.of("Uvn6LCg7dVU")) + .addPeriodDimension(List.of("THIS_YEAR")) + // "Sierra Leone" + .addOrgUnitFilter(List.of("ImspTQPwCqd")) + .setSkipData(true) + .setSkipMeta(false) + .setIncludeMetadataDetails(true); + + AnalyticsData data = dhis2.getAnalyticsData(query); + + AnalyticsMetaData metadata = data.getMetaData(); + + log.info(metadata.toString()); + + Map> dimensions = metadata.getDimensions(); + + assertContainsExactly(dimensions.get(AnalyticsDimension.DATA_X), "Uvn6LCg7dVU"); + assertContainsExactly(dimensions.get(AnalyticsDimension.ORG_UNIT), "ImspTQPwCqd"); + + Map items = metadata.getItems(); + + MetaDataItem indicator = items.get("Uvn6LCg7dVU"); + + assertNotNull(indicator); + assertEquals("Uvn6LCg7dVU", indicator.getUid()); + assertNotBlank(indicator.getName()); + assertNotNull(indicator.getIndicatorType()); + assertEquals(DimensionItemType.INDICATOR, indicator.getDimensionItemType()); + assertEquals(ValueType.NUMBER, indicator.getValueType()); + + AnalyticsIndicatorType indicatorType = indicator.getIndicatorType(); + + assertNotNull(indicatorType); + + assertNotBlank(indicatorType.getName()); + assertEquals(100, indicatorType.getFactor()); + assertFalse(indicatorType.getNumber()); + + MetaDataItem orgUnit = items.get("ImspTQPwCqd"); + + assertNotNull(orgUnit); + assertEquals("ImspTQPwCqd", orgUnit.getUid()); + assertNotBlank(orgUnit.getName()); + } } From eb47777df169cfa095f325259f6cd3bbeadefebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sat, 8 Nov 2025 19:05:34 +0100 Subject: [PATCH 101/119] fix: Update code --- .../org/hisp/dhis/model/analytics/AnalyticsIndicatorType.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsIndicatorType.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsIndicatorType.java index d0487ebb..7c005563 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsIndicatorType.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsIndicatorType.java @@ -28,6 +28,7 @@ package org.hisp.dhis.model.analytics; import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -37,7 +38,7 @@ @Setter @ToString @NoArgsConstructor -public class AnalyticsIndicatorType { +public class AnalyticsIndicatorType implements Serializable { @JsonProperty private String name; @JsonProperty private Integer factor; From 34d99fc3a2fa2ca0b9e4bf0f411d4f37c601a813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sat, 8 Nov 2025 21:14:28 +0100 Subject: [PATCH 102/119] fix: Update code --- .../dhis/model/analytics/AnalyticsIndicatorType.java | 2 ++ .../org/hisp/dhis/model/analytics/MetaDataItem.java | 12 ++++++++++++ src/main/java/org/hisp/dhis/util/NumberUtils.java | 10 ++++++---- .../java/org/hisp/dhis/util/NumberUtilsTest.java | 4 ++-- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsIndicatorType.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsIndicatorType.java index 7c005563..f896e6ff 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsIndicatorType.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsIndicatorType.java @@ -29,6 +29,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import java.io.Serializable; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -38,6 +39,7 @@ @Setter @ToString @NoArgsConstructor +@AllArgsConstructor public class AnalyticsIndicatorType implements Serializable { @JsonProperty private String name; diff --git a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java index acaeeaf8..4a5a9a15 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java +++ b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis.model.analytics; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import java.io.Serializable; import lombok.Getter; @@ -104,7 +105,18 @@ public MetaDataItem(String uid, String name, DimensionItemType dimensionItemType * @param dimensionItemType the {@link DimensionItemType}. * @return true if the dimension item is of the given type. */ + @JsonIgnore public boolean isDimensionItemType(DimensionItemType dimensionItemType) { return this.dimensionItemType == dimensionItemType; } + + /** + * Indicates whether an {@link AnalyticsIndicatorType} exists. + * + * @return true if an {@link AnalyticsIndicatorType} exists. + */ + @JsonIgnore + public boolean hasIndicatorType() { + return indicatorType != null; + } } diff --git a/src/main/java/org/hisp/dhis/util/NumberUtils.java b/src/main/java/org/hisp/dhis/util/NumberUtils.java index 255f921a..9359beec 100644 --- a/src/main/java/org/hisp/dhis/util/NumberUtils.java +++ b/src/main/java/org/hisp/dhis/util/NumberUtils.java @@ -119,7 +119,7 @@ public static Double toDouble(String string) { /** * Formats the double value so that if the value has no decimals, or has a single zero decimal as - * in {@code .0}, the formatted string excludes the decimal. Otherwise uses two decimals for the + * in {@code .0}, the formatted string excludes any decimal. Otherwise uses one decimal place for the * formatted string. * * @param value the double value to format. @@ -129,12 +129,14 @@ public static String formatDouble(Double value) { if (value == null) { return ""; } + if (value % 1 == 0) { + // Whole number return String.format("%.0f", value); - } else if (value * 10 % 1 == 0) { + } + else { + // Formats with one decimal place return String.format("%.1f", value); - } else { - return String.format("%.2f", value); } } } diff --git a/src/test/java/org/hisp/dhis/util/NumberUtilsTest.java b/src/test/java/org/hisp/dhis/util/NumberUtilsTest.java index 02dc6a7c..b5ad808c 100644 --- a/src/test/java/org/hisp/dhis/util/NumberUtilsTest.java +++ b/src/test/java/org/hisp/dhis/util/NumberUtilsTest.java @@ -103,8 +103,8 @@ void testFormatDouble() { assertEquals("1472", NumberUtils.formatDouble(1472.0)); assertEquals("3851.2", NumberUtils.formatDouble(3851.2)); assertEquals("1472.5", NumberUtils.formatDouble(1472.50)); - assertEquals("5249.39", NumberUtils.formatDouble(5249.387)); - assertEquals("54.25", NumberUtils.formatDouble(54.2485)); + assertEquals("5249.4", NumberUtils.formatDouble(5249.387)); + assertEquals("54.3", NumberUtils.formatDouble(54.2685)); assertEquals("", NumberUtils.formatDouble(null)); } } From 693b45f066420f5457c708ff2ae63c21b7d0c7ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sat, 8 Nov 2025 21:39:21 +0100 Subject: [PATCH 103/119] fix: Update code --- .../dhis/model/analytics/MetaDataItem.java | 4 +-- .../java/org/hisp/dhis/util/NumberUtils.java | 34 +++++++++++++------ .../org/hisp/dhis/util/NumberUtilsTest.java | 12 ++++--- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java index 4a5a9a15..f0cab912 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java +++ b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java @@ -109,10 +109,10 @@ public MetaDataItem(String uid, String name, DimensionItemType dimensionItemType public boolean isDimensionItemType(DimensionItemType dimensionItemType) { return this.dimensionItemType == dimensionItemType; } - + /** * Indicates whether an {@link AnalyticsIndicatorType} exists. - * + * * @return true if an {@link AnalyticsIndicatorType} exists. */ @JsonIgnore diff --git a/src/main/java/org/hisp/dhis/util/NumberUtils.java b/src/main/java/org/hisp/dhis/util/NumberUtils.java index 9359beec..5764bae2 100644 --- a/src/main/java/org/hisp/dhis/util/NumberUtils.java +++ b/src/main/java/org/hisp/dhis/util/NumberUtils.java @@ -29,6 +29,9 @@ import java.math.BigDecimal; import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.apache.commons.lang3.StringUtils; @@ -119,24 +122,35 @@ public static Double toDouble(String string) { /** * Formats the double value so that if the value has no decimals, or has a single zero decimal as - * in {@code .0}, the formatted string excludes any decimal. Otherwise uses one decimal place for the - * formatted string. + * in {@code .0}, the formatted string excludes any decimal. Otherwise uses one decimal place for + * the formatted string. Uses {@code ,} (comma) as the thousands separator. * * @param value the double value to format. * @return the value formatted as a string. */ public static String formatDouble(Double value) { if (value == null) { - return ""; + return StringUtils.EMPTY; } - + + // Define custom symbols to force comma as grouping separator and period as decimal separator + DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US); + symbols.setGroupingSeparator(','); + + // Define format pattern, '#' is "digit, zero as absent", '0' is "digit, zero as 0" + String pattern = "#,##0.#"; + + // Create the DecimalFormat formatter + DecimalFormat formatter = new DecimalFormat(pattern, symbols); + + // Set max number of fraction digits, max 1 ensures only one decimal place + formatter.setMaximumFractionDigits(1); + + // Check if the value is a whole number (or .0), and force max 0 fraction digits if (value % 1 == 0) { - // Whole number - return String.format("%.0f", value); - } - else { - // Formats with one decimal place - return String.format("%.1f", value); + formatter.setMaximumFractionDigits(0); } + + return formatter.format(value); } } diff --git a/src/test/java/org/hisp/dhis/util/NumberUtilsTest.java b/src/test/java/org/hisp/dhis/util/NumberUtilsTest.java index b5ad808c..125edf97 100644 --- a/src/test/java/org/hisp/dhis/util/NumberUtilsTest.java +++ b/src/test/java/org/hisp/dhis/util/NumberUtilsTest.java @@ -99,11 +99,13 @@ void testIsInteger() { @Test void testFormatDouble() { - assertEquals("3851", NumberUtils.formatDouble(3851D)); - assertEquals("1472", NumberUtils.formatDouble(1472.0)); - assertEquals("3851.2", NumberUtils.formatDouble(3851.2)); - assertEquals("1472.5", NumberUtils.formatDouble(1472.50)); - assertEquals("5249.4", NumberUtils.formatDouble(5249.387)); + assertEquals("351", NumberUtils.formatDouble(351D)); + assertEquals("172", NumberUtils.formatDouble(172.0)); + assertEquals("431.5", NumberUtils.formatDouble(431.5)); + assertEquals("3,851.2", NumberUtils.formatDouble(3851.2)); + assertEquals("1,472.5", NumberUtils.formatDouble(1472.50)); + assertEquals("5,249.4", NumberUtils.formatDouble(5249.387)); + assertEquals("1,874,642.6", NumberUtils.formatDouble(1874642.591)); assertEquals("54.3", NumberUtils.formatDouble(54.2685)); assertEquals("", NumberUtils.formatDouble(null)); } From 73026e062d77cc9958e891e8919fd62ca1f83f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 10 Nov 2025 10:07:07 +0100 Subject: [PATCH 104/119] fix: Update code --- src/main/java/org/hisp/dhis/BaseDhis2.java | 6 +-- .../dhis/model/dimension/DimensionItem.java | 10 ++++ .../dhis/query/analytics/AnalyticsQuery.java | 22 ++++---- .../{Dimension.java => QueryDimension.java} | 4 +- .../query/analytics/QueryDimensionTest.java | 50 +++++++++++++++++++ 5 files changed, 76 insertions(+), 16 deletions(-) rename src/main/java/org/hisp/dhis/query/analytics/{Dimension.java => QueryDimension.java} (96%) create mode 100644 src/test/java/org/hisp/dhis/query/analytics/QueryDimensionTest.java diff --git a/src/main/java/org/hisp/dhis/BaseDhis2.java b/src/main/java/org/hisp/dhis/BaseDhis2.java index a5d836de..00399914 100644 --- a/src/main/java/org/hisp/dhis/BaseDhis2.java +++ b/src/main/java/org/hisp/dhis/BaseDhis2.java @@ -98,7 +98,7 @@ import org.hisp.dhis.query.Query; import org.hisp.dhis.query.RootJunction; import org.hisp.dhis.query.analytics.AnalyticsQuery; -import org.hisp.dhis.query.analytics.Dimension; +import org.hisp.dhis.query.analytics.QueryDimension; import org.hisp.dhis.query.completedatasetregistration.CompleteDataSetRegistrationQuery; import org.hisp.dhis.query.datavalue.DataValueQuery; import org.hisp.dhis.query.datavalue.DataValueSetQuery; @@ -426,11 +426,11 @@ protected T getAnalyticsResponse(URIBuilder uriBuilder, AnalyticsQuery query * @return a {@link URI}. */ protected URI withAnalyticsQueryParams(URIBuilder uriBuilder, AnalyticsQuery query) { - for (Dimension dimension : query.getDimensions()) { + for (QueryDimension dimension : query.getDimensions()) { addParameter(uriBuilder, "dimension", dimension.getDimensionValue()); } - for (Dimension filter : query.getFilters()) { + for (QueryDimension filter : query.getFilters()) { addParameter(uriBuilder, "filter", filter.getDimensionValue()); } diff --git a/src/main/java/org/hisp/dhis/model/dimension/DimensionItem.java b/src/main/java/org/hisp/dhis/model/dimension/DimensionItem.java index 0e2c5bf2..98a3f880 100644 --- a/src/main/java/org/hisp/dhis/model/dimension/DimensionItem.java +++ b/src/main/java/org/hisp/dhis/model/dimension/DimensionItem.java @@ -73,4 +73,14 @@ public DimensionItem(String id, String code, String name, DimensionItemType dime public DimensionItemType getDimensionItemType() { return dimensionItemType; } + + /** + * Checks if the dimension item is of the given type. + * + * @param type the {@link DimensionItemType}. + * @return true if the dimension item is of the given type. + */ + public boolean isDimensionItemType(DimensionItemType type) { + return this.dimensionItemType == type; + } } diff --git a/src/main/java/org/hisp/dhis/query/analytics/AnalyticsQuery.java b/src/main/java/org/hisp/dhis/query/analytics/AnalyticsQuery.java index 8c805e91..2773dbcc 100644 --- a/src/main/java/org/hisp/dhis/query/analytics/AnalyticsQuery.java +++ b/src/main/java/org/hisp/dhis/query/analytics/AnalyticsQuery.java @@ -55,9 +55,9 @@ @Accessors(chain = true) @NoArgsConstructor(access = AccessLevel.PRIVATE) public class AnalyticsQuery { - private final List dimensions = new ArrayList<>(); + private final List dimensions = new ArrayList<>(); - private final List filters = new ArrayList<>(); + private final List filters = new ArrayList<>(); private AggregationType aggregationType; @@ -105,10 +105,10 @@ public static AnalyticsQuery instance() { /** * Adds a dimension to this query. * - * @param dimension the {@link Dimension}. + * @param dimension the {@link QueryDimension}. * @return this {@link AnalyticsQuery}. */ - public AnalyticsQuery addDimension(Dimension dimension) { + public AnalyticsQuery addDimension(QueryDimension dimension) { this.dimensions.add(dimension); return this; } @@ -121,7 +121,7 @@ public AnalyticsQuery addDimension(Dimension dimension) { * @return this {@link AnalyticsQuery}. */ public AnalyticsQuery addDimension(String dimension, List items) { - return addDimension(new Dimension(dimension, items)); + return addDimension(new QueryDimension(dimension, items)); } /** @@ -157,10 +157,10 @@ public AnalyticsQuery addOrgUnitDimension(List orgUnits) { /** * Adds a filter to this query. * - * @param filter the {@link Dimension}. + * @param filter the {@link QueryDimension}. * @return this {@link AnalyticsQuery}. */ - public AnalyticsQuery addFilter(Dimension filter) { + public AnalyticsQuery addFilter(QueryDimension filter) { this.filters.add(filter); return this; } @@ -173,7 +173,7 @@ public AnalyticsQuery addFilter(Dimension filter) { * @return this {@link AnalyticsQuery}. */ public AnalyticsQuery addFilter(String filter, List items) { - return addFilter(new Dimension(filter, items)); + return addFilter(new QueryDimension(filter, items)); } /** @@ -183,7 +183,7 @@ public AnalyticsQuery addFilter(String filter, List items) { * @return this {@link AnalyticsQuery}. */ public AnalyticsQuery addDataFilter(List dataItems) { - return addFilter(new Dimension(AnalyticsDimension.DATA_X, dataItems)); + return addFilter(new QueryDimension(AnalyticsDimension.DATA_X, dataItems)); } /** @@ -193,7 +193,7 @@ public AnalyticsQuery addDataFilter(List dataItems) { * @return this {@link AnalyticsQuery}. */ public AnalyticsQuery addPeriodFilter(List periods) { - return addFilter(new Dimension(AnalyticsDimension.PERIOD, periods)); + return addFilter(new QueryDimension(AnalyticsDimension.PERIOD, periods)); } /** @@ -203,7 +203,7 @@ public AnalyticsQuery addPeriodFilter(List periods) { * @return this {@link AnalyticsQuery}. */ public AnalyticsQuery addOrgUnitFilter(List orgUnits) { - return addFilter(new Dimension(AnalyticsDimension.ORG_UNIT, orgUnits)); + return addFilter(new QueryDimension(AnalyticsDimension.ORG_UNIT, orgUnits)); } /** diff --git a/src/main/java/org/hisp/dhis/query/analytics/Dimension.java b/src/main/java/org/hisp/dhis/query/analytics/QueryDimension.java similarity index 96% rename from src/main/java/org/hisp/dhis/query/analytics/Dimension.java rename to src/main/java/org/hisp/dhis/query/analytics/QueryDimension.java index 2638be0b..60e3e962 100644 --- a/src/main/java/org/hisp/dhis/query/analytics/Dimension.java +++ b/src/main/java/org/hisp/dhis/query/analytics/QueryDimension.java @@ -33,11 +33,11 @@ import lombok.Setter; import org.apache.commons.lang3.StringUtils; -/** Analytics dimension. */ +/** Analytics query dimension. */ @Getter @Setter @RequiredArgsConstructor -public class Dimension { +public class QueryDimension { private final String dimension; private final List items; diff --git a/src/test/java/org/hisp/dhis/query/analytics/QueryDimensionTest.java b/src/test/java/org/hisp/dhis/query/analytics/QueryDimensionTest.java new file mode 100644 index 00000000..fc74d9d4 --- /dev/null +++ b/src/test/java/org/hisp/dhis/query/analytics/QueryDimensionTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2004-2025, 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.analytics; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import org.hisp.dhis.model.analytics.AnalyticsDimension; +import org.hisp.dhis.support.TestTags; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(TestTags.UNIT) +class QueryDimensionTest { + @Test + void testGetDimensionValue() { + QueryDimension dimension = + new QueryDimension( + AnalyticsDimension.DATA_X, List.of("fbfJHSPpUQD", "cYeuwXTCPkU", "Jtf34kNZhzP")); + + String expected = "dx:fbfJHSPpUQD;cYeuwXTCPkU;Jtf34kNZhzP"; + + assertEquals(expected, dimension.getDimensionValue()); + } +} From ba78ebf79b75e1b505549e821ec8d0193550946f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 10 Nov 2025 10:09:28 +0100 Subject: [PATCH 105/119] fix: Update code --- src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java index 85d5f2eb..e6c2fc10 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java +++ b/src/main/java/org/hisp/dhis/model/analytics/AnalyticsData.java @@ -207,7 +207,8 @@ public List getRow(int index) { } /** - * Truncates the data rows to the specified maximum number of rows. + * Truncates the data rows to the specified maximum number of rows. The data rows are left + * unchanged if the max rows is equal or greater than the number of data rows. * * @param maxRows the maximum number of rows to retain. */ From 0a3ac54054519aee4d3855f63fe0102acec079da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 10 Nov 2025 10:43:33 +0100 Subject: [PATCH 106/119] fix: Update code --- .../org/hisp/dhis/model/dimension/Dimension.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/org/hisp/dhis/model/dimension/Dimension.java b/src/main/java/org/hisp/dhis/model/dimension/Dimension.java index 415be9d9..d68ff860 100644 --- a/src/main/java/org/hisp/dhis/model/dimension/Dimension.java +++ b/src/main/java/org/hisp/dhis/model/dimension/Dimension.java @@ -44,4 +44,17 @@ public class Dimension extends NameableObject { @JsonProperty private DataDimensionType dataDimensionType; @JsonProperty private List items = new ArrayList<>(); + + /** + * Constructor. + * + * @param id the identifier. + * @param dimensionType the {@link DimensionType}. + * @param items the list of {@link DimensionItem}. + */ + public Dimension(String id, DimensionType dimensionType, List items) { + super(id, id, id); + this.dimensionType = dimensionType; + this.items = items; + } } From 2a1665dddae1fe1658e27aa5b7b136dd0b6cb1e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Mon, 10 Nov 2025 19:29:02 +0100 Subject: [PATCH 107/119] fix: Update code --- .../org/hisp/dhis/model/analytics/MetaDataItem.java | 12 ++++++++++++ .../org/hisp/dhis/model/dimension/DimensionItem.java | 5 ++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java index f0cab912..b5a5c6db 100644 --- a/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java +++ b/src/main/java/org/hisp/dhis/model/analytics/MetaDataItem.java @@ -27,6 +27,8 @@ */ package org.hisp.dhis.model.analytics; +import static org.apache.commons.lang3.StringUtils.isNotEmpty; + import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import java.io.Serializable; @@ -110,6 +112,16 @@ public boolean isDimensionItemType(DimensionItemType dimensionItemType) { return this.dimensionItemType == dimensionItemType; } + /** + * Indicates whether a name exists. + * + * @return true if a name exists. + */ + @JsonIgnore + public boolean hasName() { + return isNotEmpty(name); + } + /** * Indicates whether an {@link AnalyticsIndicatorType} exists. * diff --git a/src/main/java/org/hisp/dhis/model/dimension/DimensionItem.java b/src/main/java/org/hisp/dhis/model/dimension/DimensionItem.java index 98a3f880..376c9ee4 100644 --- a/src/main/java/org/hisp/dhis/model/dimension/DimensionItem.java +++ b/src/main/java/org/hisp/dhis/model/dimension/DimensionItem.java @@ -77,10 +77,13 @@ public DimensionItemType getDimensionItemType() { /** * Checks if the dimension item is of the given type. * + *

Note that the method uses the get method for dimension item type, as the method may be + * overridden by subclasses. + * * @param type the {@link DimensionItemType}. * @return true if the dimension item is of the given type. */ public boolean isDimensionItemType(DimensionItemType type) { - return this.dimensionItemType == type; + return this.getDimensionItemType() == type; } } From 5ef967cbe618313768368f416f33b503415420c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sun, 16 Nov 2025 18:05:32 +0100 Subject: [PATCH 108/119] fix: Update code --- .../org/hisp/dhis/CategoryComboApiTest.java | 23 ++++++++++++++++--- src/test/java/org/hisp/dhis/TestFixture.java | 10 +++++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/hisp/dhis/CategoryComboApiTest.java b/src/test/java/org/hisp/dhis/CategoryComboApiTest.java index 1f93d883..4b5498ce 100644 --- a/src/test/java/org/hisp/dhis/CategoryComboApiTest.java +++ b/src/test/java/org/hisp/dhis/CategoryComboApiTest.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis; +import static org.hisp.dhis.support.Assertions.assertNotEmpty; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -62,7 +63,7 @@ void testGetCategoryCombo() { assertFalse(categories.isEmpty()); List categoryOptionCombos = categoryCombo.getCategoryOptionCombos(); - assertFalse(categoryOptionCombos.isEmpty()); + assertNotEmpty(categoryOptionCombos); assertFalse(categoryOptionCombos.get(0).getIgnoreApproval()); assertFalse(categoryOptionCombos.get(0).getCategoryOptions().isEmpty()); } @@ -78,7 +79,23 @@ void testGetCategoryCombos() { CategoryCombo categoryCombo = categoryCombos.get(0); assertNotNull(categoryCombo.getId()); - assertFalse(categoryCombo.getCategories().isEmpty()); - assertFalse(categoryCombo.getCategoryOptionCombos().isEmpty()); + assertNotEmpty(categoryCombo.getCategories()); + assertNotEmpty(categoryCombo.getCategoryOptionCombos()); + } + + @Test + void testGetCategoryCombosExpandAssociations() { + Dhis2 dhis2 = new Dhis2(TestFixture.V42_CONFIG); + + List categoryCombos = + dhis2.getCategoryCombos(Query.instance().withExpandAssociations()); + + assertNotNull(categoryCombos); + assertFalse(categoryCombos.isEmpty()); + + CategoryCombo categoryCombo = categoryCombos.get(0); + assertNotNull(categoryCombo.getId()); + assertNotEmpty(categoryCombo.getCategories()); + assertNotEmpty(categoryCombo.getCategoryOptionCombos()); } } diff --git a/src/test/java/org/hisp/dhis/TestFixture.java b/src/test/java/org/hisp/dhis/TestFixture.java index 5a63ae97..d36c84d0 100644 --- a/src/test/java/org/hisp/dhis/TestFixture.java +++ b/src/test/java/org/hisp/dhis/TestFixture.java @@ -40,14 +40,18 @@ public final class TestFixture { public static final String V41_URL = "https://play.im.dhis2.org/stable-2-41-6"; + public static final String V42_URL = "https://play.im.dhis2.org/stable-2-42-3"; + public static final String LOCAL_URL = "http://localhost/dhis"; public static final String DEFAULT_URL = V41_URL; - public static final Dhis2Config DEFAULT_CONFIG = - new Dhis2Config(DEFAULT_URL, "system", "System123"); - public static final Dhis2Config DEV_CONFIG = new Dhis2Config(DEV_URL, "system", "System123"); + public static final Dhis2Config V42_CONFIG = new Dhis2Config(V42_URL, "system", "System123"); + public static final Dhis2Config LOCAL_CONFIG = new Dhis2Config(LOCAL_URL, "system", "System123"); + + public static final Dhis2Config DEFAULT_CONFIG = + new Dhis2Config(DEFAULT_URL, "system", "System123"); } From bbd89c106aa1ec6b4d6fd12bd49e9898901dae29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 18 Nov 2025 16:02:59 +0100 Subject: [PATCH 109/119] fix: Update code --- src/main/java/org/hisp/dhis/BaseDhis2.java | 7 ++- src/main/java/org/hisp/dhis/Dhis2.java | 27 +++++++++ src/main/java/org/hisp/dhis/api/LogLevel.java | 41 +++++++++++++ .../dhis/security/context/Dhis2Context.java | 54 +++++++++++++++++ .../security/context/Dhis2ContextHolder.java | 60 +++++++++++++++++++ 5 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/hisp/dhis/api/LogLevel.java create mode 100644 src/main/java/org/hisp/dhis/security/context/Dhis2Context.java create mode 100644 src/main/java/org/hisp/dhis/security/context/Dhis2ContextHolder.java diff --git a/src/main/java/org/hisp/dhis/BaseDhis2.java b/src/main/java/org/hisp/dhis/BaseDhis2.java index 00399914..a26555bb 100644 --- a/src/main/java/org/hisp/dhis/BaseDhis2.java +++ b/src/main/java/org/hisp/dhis/BaseDhis2.java @@ -80,6 +80,7 @@ import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.BasicNameValuePair; import org.apache.hc.core5.net.URIBuilder; +import org.hisp.dhis.api.LogLevel; import org.hisp.dhis.model.completedatasetregistration.CompleteDataSetRegistrationImportOptions; import org.hisp.dhis.model.datavalueset.DataValueSet; import org.hisp.dhis.model.datavalueset.DataValueSetImportOptions; @@ -137,7 +138,7 @@ public class BaseDhis2 { private static final String LOG_LEVEL_WARN = "warn"; /** Override current log level for debugging here. */ - private static final Optional LOG_LEVEL = Optional.empty(); + protected Optional logLevel = Optional.empty(); // Status codes @@ -1487,8 +1488,8 @@ private void log(String format, Object... arguments) { * @return the log level. */ private String getLogLevel() { - if (LOG_LEVEL.isPresent()) { - return LOG_LEVEL.get(); + if (logLevel.isPresent()) { + return logLevel.get().getValue(); } return System.getProperty(LOG_LEVEL_SYSTEM_PROPERTY); diff --git a/src/main/java/org/hisp/dhis/Dhis2.java b/src/main/java/org/hisp/dhis/Dhis2.java index 6273aea0..460d13ca 100644 --- a/src/main/java/org/hisp/dhis/Dhis2.java +++ b/src/main/java/org/hisp/dhis/Dhis2.java @@ -68,6 +68,7 @@ import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.net.URIBuilder; import org.hisp.dhis.api.ApiFields; +import org.hisp.dhis.api.LogLevel; import org.hisp.dhis.auth.AccessTokenAuthentication; import org.hisp.dhis.auth.BasicAuthentication; import org.hisp.dhis.auth.CookieAuthentication; @@ -238,6 +239,32 @@ public static Dhis2 withCookieAuth(String url, String sessionId) { return new Dhis2(new Dhis2Config(url, new CookieAuthentication(sessionId))); } + // ------------------------------------------------------------------------- + // Log level + // ------------------------------------------------------------------------- + + /** + * Sets the log level for subsequent requests. + * + * @param logLevel the {@link LogLevel}. + * @return the {@link Dhis2} instance. + */ + public Dhis2 setLogLevel(LogLevel logLevel) { + this.logLevel = Optional.of(logLevel); + return this; + } + + /** + * Resets the log level for subsequent requests. + * + * @param logLevel the {@link LogLevel}. + * @return the {@link Dhis2} instance. + */ + public Dhis2 setDefaultLogLevel() { + this.logLevel = Optional.empty(); + return this; + } + // ------------------------------------------------------------------------- // Status // ------------------------------------------------------------------------- diff --git a/src/main/java/org/hisp/dhis/api/LogLevel.java b/src/main/java/org/hisp/dhis/api/LogLevel.java new file mode 100644 index 00000000..bd0d707d --- /dev/null +++ b/src/main/java/org/hisp/dhis/api/LogLevel.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2004-2025, 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.api; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum LogLevel { + INFO("info"), + WARN("warn"), + ERROR("error"); + + private final String value; +} diff --git a/src/main/java/org/hisp/dhis/security/context/Dhis2Context.java b/src/main/java/org/hisp/dhis/security/context/Dhis2Context.java new file mode 100644 index 00000000..7b3936c4 --- /dev/null +++ b/src/main/java/org/hisp/dhis/security/context/Dhis2Context.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2004-2025, 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.security.context; + +import java.util.Objects; +import lombok.Getter; +import org.hisp.dhis.model.Me; + +/** Represents the security context for a DHIS2 session. */ +@Getter +public class Dhis2Context { + /** The DHIS2 session identifier. */ + private final String sessionId; + + /** The currently authenticated user as {@link Me}. */ + private final Me user; + + /** + * Constructor. + * + * @param sessionId the session identifier. + * @param user the currently authenticated user as {@link Me}. + */ + public Dhis2Context(String sessionId, Me user) { + this.sessionId = sessionId; + this.user = user; + Objects.requireNonNull(sessionId); + } +} diff --git a/src/main/java/org/hisp/dhis/security/context/Dhis2ContextHolder.java b/src/main/java/org/hisp/dhis/security/context/Dhis2ContextHolder.java new file mode 100644 index 00000000..c37e3fbe --- /dev/null +++ b/src/main/java/org/hisp/dhis/security/context/Dhis2ContextHolder.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2004-2025, 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.security.context; + +import java.util.Objects; + +/** Thread-local holder for {@link Dhis2Context}. */ +public class Dhis2ContextHolder { + private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>(); + + /** + * Sets the {@link Dhis2Context} for the current thread. + * + * @param context the {@link Dhis2Context}. + */ + public static void setContext(Dhis2Context context) { + Objects.requireNonNull(context); + Objects.requireNonNull(context.getSessionId()); + CONTEXT_HOLDER.set(context); + } + + /** + * Gets the {@link Dhis2Context} for the current thread. + * + * @return the {@link Dhis2Context}. + */ + public static Dhis2Context getContext() { + return CONTEXT_HOLDER.get(); + } + + /** Clears the {@link Dhis2Context} for the current thread. */ + public static void clearContext() { + CONTEXT_HOLDER.remove(); + } +} From e9d5a97a8a71101fac3795a019325952c63b826e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 18 Nov 2025 17:01:43 +0100 Subject: [PATCH 110/119] fix: Update code --- src/main/java/org/hisp/dhis/Dhis2.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/hisp/dhis/Dhis2.java b/src/main/java/org/hisp/dhis/Dhis2.java index 460d13ca..927d4095 100644 --- a/src/main/java/org/hisp/dhis/Dhis2.java +++ b/src/main/java/org/hisp/dhis/Dhis2.java @@ -257,7 +257,6 @@ public Dhis2 setLogLevel(LogLevel logLevel) { /** * Resets the log level for subsequent requests. * - * @param logLevel the {@link LogLevel}. * @return the {@link Dhis2} instance. */ public Dhis2 setDefaultLogLevel() { From 712feb6b653ed57cd2d7166158df9e3625c636c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 18 Nov 2025 17:11:58 +0100 Subject: [PATCH 111/119] fix: Update code --- src/main/java/org/hisp/dhis/BaseDhis2.java | 6 +++--- .../org/hisp/dhis/security/context/Dhis2ContextHolder.java | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/hisp/dhis/BaseDhis2.java b/src/main/java/org/hisp/dhis/BaseDhis2.java index a26555bb..f756d37b 100644 --- a/src/main/java/org/hisp/dhis/BaseDhis2.java +++ b/src/main/java/org/hisp/dhis/BaseDhis2.java @@ -1470,11 +1470,11 @@ private void log(int statusCode, String format, Object... arguments) { * @param arguments the message arguments. */ private void log(String format, Object... arguments) { - String logLevel = getLogLevel(); + String level = getLogLevel(); - if (LOG_LEVEL_INFO.equalsIgnoreCase(logLevel)) { + if (LOG_LEVEL_INFO.equalsIgnoreCase(level)) { log.info(format, arguments); - } else if (LOG_LEVEL_WARN.equalsIgnoreCase(logLevel)) { + } else if (LOG_LEVEL_WARN.equalsIgnoreCase(level)) { log.warn(format, arguments); } else { log.debug(format, arguments); diff --git a/src/main/java/org/hisp/dhis/security/context/Dhis2ContextHolder.java b/src/main/java/org/hisp/dhis/security/context/Dhis2ContextHolder.java index c37e3fbe..09230b98 100644 --- a/src/main/java/org/hisp/dhis/security/context/Dhis2ContextHolder.java +++ b/src/main/java/org/hisp/dhis/security/context/Dhis2ContextHolder.java @@ -28,9 +28,12 @@ package org.hisp.dhis.security.context; import java.util.Objects; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; /** Thread-local holder for {@link Dhis2Context}. */ -public class Dhis2ContextHolder { +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class Dhis2ContextHolder { private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>(); /** From 83c1f9270a29eba12c6532fb8de292c8c28bd436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 18 Nov 2025 17:19:32 +0100 Subject: [PATCH 112/119] fix: Update code --- .../org/hisp/dhis/security/context/Dhis2Context.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/org/hisp/dhis/security/context/Dhis2Context.java b/src/main/java/org/hisp/dhis/security/context/Dhis2Context.java index 7b3936c4..0e965edd 100644 --- a/src/main/java/org/hisp/dhis/security/context/Dhis2Context.java +++ b/src/main/java/org/hisp/dhis/security/context/Dhis2Context.java @@ -51,4 +51,14 @@ public Dhis2Context(String sessionId, Me user) { this.user = user; Objects.requireNonNull(sessionId); } + + /** Indicates whether a valid session identifier is present. */ + public boolean hasSessionId() { + return sessionId != null && sessionId.trim().length() > 16; + } + + /** Indicates whether an authenticated user is present. */ + public boolean hasUser() { + return user != null; + } } From 2ee2ea0bd1711353818b7de0cdb6e00fbf2168ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 18 Nov 2025 17:47:21 +0100 Subject: [PATCH 113/119] fix: Update code --- .../dhis/security/context/Dhis2Context.java | 23 ++++---- .../context/Dhis2ContextHolderTest.java | 54 +++++++++++++++++++ 2 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 src/test/java/org/hisp/dhis/security/context/Dhis2ContextHolderTest.java diff --git a/src/main/java/org/hisp/dhis/security/context/Dhis2Context.java b/src/main/java/org/hisp/dhis/security/context/Dhis2Context.java index 0e965edd..2d4017c7 100644 --- a/src/main/java/org/hisp/dhis/security/context/Dhis2Context.java +++ b/src/main/java/org/hisp/dhis/security/context/Dhis2Context.java @@ -27,9 +27,10 @@ */ package org.hisp.dhis.security.context; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + import java.util.Objects; import lombok.Getter; -import org.hisp.dhis.model.Me; /** Represents the security context for a DHIS2 session. */ @Getter @@ -37,28 +38,28 @@ public class Dhis2Context { /** The DHIS2 session identifier. */ private final String sessionId; - /** The currently authenticated user as {@link Me}. */ - private final Me user; + /** The username of the currently authenticated user. */ + private final String username; /** * Constructor. * * @param sessionId the session identifier. - * @param user the currently authenticated user as {@link Me}. + * @param username the username of the currently authenticated user. */ - public Dhis2Context(String sessionId, Me user) { + public Dhis2Context(String sessionId, String username) { this.sessionId = sessionId; - this.user = user; + this.username = username; Objects.requireNonNull(sessionId); } - /** Indicates whether a valid session identifier is present. */ + /** Indicates whether a session identifier is present. */ public boolean hasSessionId() { - return sessionId != null && sessionId.trim().length() > 16; + return isNotBlank(sessionId); } - /** Indicates whether an authenticated user is present. */ - public boolean hasUser() { - return user != null; + /** Indicates whether a username is present. */ + public boolean hasUsername() { + return isNotBlank(username); } } diff --git a/src/test/java/org/hisp/dhis/security/context/Dhis2ContextHolderTest.java b/src/test/java/org/hisp/dhis/security/context/Dhis2ContextHolderTest.java new file mode 100644 index 00000000..429c5dd4 --- /dev/null +++ b/src/test/java/org/hisp/dhis/security/context/Dhis2ContextHolderTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2004-2025, 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.security.context; + +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 org.junit.jupiter.api.Test; + +class Dhis2ContextHolderTest { + @Test + void testSetContext() { + String sessionId = "64E50FE921A40ECDC45ECA695B68CD1F"; + String username = "system"; + + Dhis2Context context = new Dhis2Context(sessionId, username); + + Dhis2ContextHolder.setContext(context); + + assertNotNull(Dhis2ContextHolder.getContext()); + assertEquals(sessionId, Dhis2ContextHolder.getContext().getSessionId()); + assertEquals(username, Dhis2ContextHolder.getContext().getUsername()); + + Dhis2ContextHolder.clearContext(); + + assertNull(Dhis2ContextHolder.getContext()); + } +} From 9399b0f7ab791a147787c8aacb1b649aefeb0bbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Tue, 18 Nov 2025 17:48:17 +0100 Subject: [PATCH 114/119] fix: Update code --- .../org/hisp/dhis/security/context/Dhis2Context.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/org/hisp/dhis/security/context/Dhis2Context.java b/src/main/java/org/hisp/dhis/security/context/Dhis2Context.java index 2d4017c7..a26d346e 100644 --- a/src/main/java/org/hisp/dhis/security/context/Dhis2Context.java +++ b/src/main/java/org/hisp/dhis/security/context/Dhis2Context.java @@ -41,6 +41,17 @@ public class Dhis2Context { /** The username of the currently authenticated user. */ private final String username; + /** + * Constructor. + * + * @param sessionId the session identifier. + */ + public Dhis2Context(String sessionId) { + this.sessionId = sessionId; + this.username = null; + Objects.requireNonNull(sessionId); + } + /** * Constructor. * From 44bcdbf1804565d141b8949167bc1fbdaa1514e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Thu, 4 Dec 2025 00:13:14 +0100 Subject: [PATCH 115/119] fix: Update code --- src/main/java/org/hisp/dhis/BaseDhis2.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/hisp/dhis/BaseDhis2.java b/src/main/java/org/hisp/dhis/BaseDhis2.java index f756d37b..db4aa495 100644 --- a/src/main/java/org/hisp/dhis/BaseDhis2.java +++ b/src/main/java/org/hisp/dhis/BaseDhis2.java @@ -1183,7 +1183,12 @@ private void handleErrorsForGet(ClassicHttpResponse response, String url) * @throws IOException if reading failed. */ protected T readValue(String content, Class type) throws IOException { - return objectMapper.readValue(content, type); + try { + return objectMapper.readValue(content, type); + } catch (IOException ex) { + log.error("JSON deserialization error for content: {}", content); + throw ex; + } } /** From 817b89cbd9a0e8d45e911166f76ae065c1f9c735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Thu, 4 Dec 2025 00:15:22 +0100 Subject: [PATCH 116/119] fix: Update code --- src/test/java/org/hisp/dhis/ProgramApiTest.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/hisp/dhis/ProgramApiTest.java b/src/test/java/org/hisp/dhis/ProgramApiTest.java index f3371a77..2bd1f0dd 100644 --- a/src/test/java/org/hisp/dhis/ProgramApiTest.java +++ b/src/test/java/org/hisp/dhis/ProgramApiTest.java @@ -41,7 +41,6 @@ import org.hisp.dhis.model.FeatureType; import org.hisp.dhis.model.OrgUnit; import org.hisp.dhis.model.Program; -import org.hisp.dhis.model.ProgramAccessLevel; import org.hisp.dhis.model.ProgramIndicator; import org.hisp.dhis.model.ProgramObjects; import org.hisp.dhis.model.ProgramSection; @@ -167,7 +166,7 @@ void testSaveGetRemoveProgramObjects() { assertNotNull(pr.getDisplayFrontPageList()); assertNotNull(pr.getMinAttributesRequiredToSearch()); assertNotNull(pr.getMaxTeiCountToReturn()); - assertEquals(ProgramAccessLevel.OPEN, pr.getAccessLevel()); + assertNotNull(pr.getAccessLevel()); ProgramTrackedEntityAttribute ptea = pr.getProgramTrackedEntityAttributes().get(0); @@ -292,7 +291,7 @@ void testGetProgramExtChildProgramme() { assertNotNull(pr.getCreated()); assertNotNull(pr.getLastUpdated()); assertEquals(ProgramType.WITH_REGISTRATION, pr.getProgramType()); - assertEquals(ProgramAccessLevel.OPEN, pr.getAccessLevel()); + assertNotNull(pr.getAccessLevel()); assertEquals(FeatureType.POINT, pr.getFeatureType()); assertNotNull(pr.getCategoryCombo()); assertNotEmpty(pr.getOrganisationUnits()); @@ -406,7 +405,7 @@ void testGetProgramStdChildProgramme() { assertNotNull(pr.getCreated()); assertNotNull(pr.getLastUpdated()); assertEquals(ProgramType.WITH_REGISTRATION, pr.getProgramType()); - assertEquals(ProgramAccessLevel.OPEN, pr.getAccessLevel()); + assertNotNull(pr.getAccessLevel()); assertEquals(FeatureType.POINT, pr.getFeatureType()); assertNotNull(pr.getCategoryCombo()); assertEmpty(pr.getOrganisationUnits()); From b7ec01418d05e5b16a545da834e54a7840eeb0f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Sun, 21 Dec 2025 14:00:29 +0100 Subject: [PATCH 117/119] fix: Update code --- src/main/java/org/hisp/dhis/util/DateTimeUtils.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/org/hisp/dhis/util/DateTimeUtils.java b/src/main/java/org/hisp/dhis/util/DateTimeUtils.java index dc2c4e15..e3f6b03c 100644 --- a/src/main/java/org/hisp/dhis/util/DateTimeUtils.java +++ b/src/main/java/org/hisp/dhis/util/DateTimeUtils.java @@ -99,6 +99,16 @@ public static Date toDate(LocalDateTime dateTime) { return Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant()); } + /** + * Returns a {@link java.util.Date} from a {@link java.time.Instant}. + * + * @param instant the {@link java.time.Instant}. + * @return the {@link java.util.Date}. + */ + public static Date toDate(Instant instant) { + return Date.from(instant); + } + /** * Returns a {@link java.util.Date} based on the given string. The string must be on the format * yyyy-MM-dd'T'HH:mm:ss.SSS. From f4f437920c2076991d5fe6a44f8716c95f1948ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Thu, 25 Dec 2025 09:28:47 +0100 Subject: [PATCH 118/119] fix: Update code --- src/main/java/org/hisp/dhis/BaseDhis2.java | 5 ++ src/main/java/org/hisp/dhis/Dhis2.java | 12 +++++ .../org/hisp/dhis/auth/Authentication.java | 10 ++++ .../org/hisp/dhis/auth/NoAuthentication.java | 47 +++++++++++++++++++ .../java/org/hisp/dhis/util/HttpUtils.java | 6 ++- .../org/hisp/dhis/util/HttpUtilsTest.java | 12 +++++ 6 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/hisp/dhis/auth/NoAuthentication.java diff --git a/src/main/java/org/hisp/dhis/BaseDhis2.java b/src/main/java/org/hisp/dhis/BaseDhis2.java index db4aa495..1287cd8d 100644 --- a/src/main/java/org/hisp/dhis/BaseDhis2.java +++ b/src/main/java/org/hisp/dhis/BaseDhis2.java @@ -167,6 +167,11 @@ public class BaseDhis2 { protected final CloseableHttpClient httpClient; + /** + * Constructor. + * + * @param config the {@link Dhis2Config}. + */ public BaseDhis2(Dhis2Config config) { Objects.requireNonNull(config, "Config must be specified"); this.config = config; diff --git a/src/main/java/org/hisp/dhis/Dhis2.java b/src/main/java/org/hisp/dhis/Dhis2.java index 3ff8aa99..f025dc0f 100644 --- a/src/main/java/org/hisp/dhis/Dhis2.java +++ b/src/main/java/org/hisp/dhis/Dhis2.java @@ -73,6 +73,7 @@ import org.hisp.dhis.auth.AccessTokenAuthentication; import org.hisp.dhis.auth.BasicAuthentication; import org.hisp.dhis.auth.CookieAuthentication; +import org.hisp.dhis.auth.NoAuthentication; import org.hisp.dhis.model.AnalyticsTableHook; import org.hisp.dhis.model.Attribute; import org.hisp.dhis.model.Category; @@ -240,6 +241,17 @@ public static Dhis2 withCookieAuth(String url, String sessionId) { return new Dhis2(new Dhis2Config(url, new CookieAuthentication(sessionId))); } + /** + * Creates a {@link Dhis2} instance with no authentication. + * + * @param url the URL to the DHIS2 instance, do not include the {@code /api} part or a trailing + * {@code /}. + * @return a {@link Dhis2} instance. + */ + public static Dhis2 withoutAuth(String url) { + return new Dhis2(new Dhis2Config(url, new NoAuthentication())); + } + // ------------------------------------------------------------------------- // Log level // ------------------------------------------------------------------------- diff --git a/src/main/java/org/hisp/dhis/auth/Authentication.java b/src/main/java/org/hisp/dhis/auth/Authentication.java index 8e57394c..764fb250 100644 --- a/src/main/java/org/hisp/dhis/auth/Authentication.java +++ b/src/main/java/org/hisp/dhis/auth/Authentication.java @@ -28,6 +28,7 @@ package org.hisp.dhis.auth; import java.io.Serializable; +import org.apache.commons.lang3.StringUtils; /** Authentication interface. */ public interface Authentication extends Serializable { @@ -44,4 +45,13 @@ public interface Authentication extends Serializable { * @return the value of the HTTP header to use for authentication. */ String getHttpHeaderAuthValue(); + + /** + * Indicates whether authentication details are present. + * + * @return true if authentication details are present, false otherwise. + */ + default boolean hasAuth() { + return StringUtils.isNotEmpty(getHttpHeaderAuthName()); + } } diff --git a/src/main/java/org/hisp/dhis/auth/NoAuthentication.java b/src/main/java/org/hisp/dhis/auth/NoAuthentication.java new file mode 100644 index 00000000..9d494b4e --- /dev/null +++ b/src/main/java/org/hisp/dhis/auth/NoAuthentication.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2004-2025, 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.auth; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; + +/** Class representing no authentication. */ +@Getter +@RequiredArgsConstructor +public class NoAuthentication implements Authentication { + @Override + public String getHttpHeaderAuthName() { + return StringUtils.EMPTY; + } + + @Override + public String getHttpHeaderAuthValue() { + return StringUtils.EMPTY; + } +} diff --git a/src/main/java/org/hisp/dhis/util/HttpUtils.java b/src/main/java/org/hisp/dhis/util/HttpUtils.java index be8a4dbc..afc42583 100644 --- a/src/main/java/org/hisp/dhis/util/HttpUtils.java +++ b/src/main/java/org/hisp/dhis/util/HttpUtils.java @@ -65,7 +65,11 @@ public class HttpUtils { */ public static T withAuth(T request, Dhis2Config config) { Authentication auth = config.getAuthentication(); - request.setHeader(auth.getHttpHeaderAuthName(), auth.getHttpHeaderAuthValue()); + + if (auth.hasAuth()) { + request.setHeader(auth.getHttpHeaderAuthName(), auth.getHttpHeaderAuthValue()); + } + return request; } diff --git a/src/test/java/org/hisp/dhis/util/HttpUtilsTest.java b/src/test/java/org/hisp/dhis/util/HttpUtilsTest.java index 81a36c6c..57099679 100644 --- a/src/test/java/org/hisp/dhis/util/HttpUtilsTest.java +++ b/src/test/java/org/hisp/dhis/util/HttpUtilsTest.java @@ -28,6 +28,7 @@ package org.hisp.dhis.util; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import java.net.URI; import java.util.ArrayList; @@ -39,6 +40,7 @@ import org.hisp.dhis.auth.AccessTokenAuthentication; import org.hisp.dhis.auth.BasicAuthentication; import org.hisp.dhis.auth.CookieAuthentication; +import org.hisp.dhis.auth.NoAuthentication; import org.hisp.dhis.support.TestTags; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -102,6 +104,16 @@ void testWithCookieAuth() throws Exception { assertEquals("JSESSIONID=KJH8KJ24fRD3FK491", post.getHeader(HttpHeaders.COOKIE).getValue()); } + @Test + void testWithoutAuth() throws Exception { + HttpPost post = new HttpPost(DEV_URL); + + HttpUtils.withAuth(post, new Dhis2Config(DEV_URL, new NoAuthentication())); + + assertNull(post.getHeader(HttpHeaders.AUTHORIZATION)); + assertNull(post.getHeader(HttpHeaders.COOKIE)); + } + @Test void testGetBasicAuthString() { assertEquals("Basic YWRtaW46ZGlzdHJpY3Q=", HttpUtils.getBasicAuthString("admin", "district")); From c746e81832ea0dc69990fa3812e3d53f9bd2b588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Helge=20=C3=98verland?= Date: Thu, 25 Dec 2025 09:31:22 +0100 Subject: [PATCH 119/119] fix: Update code --- src/test/java/org/hisp/dhis/AuthTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/test/java/org/hisp/dhis/AuthTest.java b/src/test/java/org/hisp/dhis/AuthTest.java index 3cf4c9c4..5f41df3b 100644 --- a/src/test/java/org/hisp/dhis/AuthTest.java +++ b/src/test/java/org/hisp/dhis/AuthTest.java @@ -48,10 +48,17 @@ void testWithCookieAuth() { assertNotNull(dhis2); } + @Test void testWithAccessTokenAuth() { Dhis2 dhis2 = Dhis2.withAccessTokenAuth( "https://play.dhis2.org/demo", "d2pat_2bBQecgNcxrS4EPhBJuRlQkwiLr2ATnC2557514242"); assertNotNull(dhis2); } + + @Test + void testWithoutAuth() { + Dhis2 dhis2 = Dhis2.withoutAuth("https://play.dhis2.org/demo"); + assertNotNull(dhis2); + } }