From 4143968ee3dcad3d784673c8054ed8f15888bd00 Mon Sep 17 00:00:00 2001 From: Maikel Arabori <51713408+maikelarabori@users.noreply.github.com> Date: Thu, 19 Dec 2024 10:28:24 +0100 Subject: [PATCH 01/13] fix: Broken test in master [DHIS2-18665] (#19532) --- .../org/hisp/dhis/program/ProgramIndicatorServiceTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/program/ProgramIndicatorServiceTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/program/ProgramIndicatorServiceTest.java index 27f87bd1af07..f7c124b61627 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/program/ProgramIndicatorServiceTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/program/ProgramIndicatorServiceTest.java @@ -551,14 +551,14 @@ void testBooleanAsNumeric() { @Test void testBooleanAsBoolean() { assertEquals( - "coalesce(case when ax.\"ps\" = 'ProgrmStagA' then \"DataElmentG\" else null end::numeric!=0,false)", + "coalesce(case when ax.\"ps\" = 'ProgrmStagA' then \"DataElmentG\" else null end::numeric != 0,false)", filter("#{ProgrmStagA.DataElmentG}")); } @Test void testBooleanAsBooleanWithinIf() { assertEquals( - " case when coalesce(case when ax.\"ps\" = 'ProgrmStagA' then \"DataElmentG\" else null end::numeric!=0,false) then 4 else 5 end", + " case when coalesce(case when ax.\"ps\" = 'ProgrmStagA' then \"DataElmentG\" else null end::numeric != 0,false) then 4 else 5 end", sql("if(#{ProgrmStagA.DataElmentG},4,5)")); } From 4f4fa5c24c59edfbf87489e4068d058bdbdf1211 Mon Sep 17 00:00:00 2001 From: radnov Date: Thu, 19 Dec 2024 12:35:05 +0200 Subject: [PATCH 02/13] ci: remove boolean type from var definition [skip ci] (#19524) Using a type like `boolean` in the variable definition scopes it to a stage, like `def` would, but we need `isHotfix` in multiple stages. Signed-off-by: Rado --- jenkinsfiles/stable | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jenkinsfiles/stable b/jenkinsfiles/stable index 321b5040a9a9..0018d4142124 100644 --- a/jenkinsfiles/stable +++ b/jenkinsfiles/stable @@ -122,7 +122,7 @@ pipeline { oldImageTag = env.DOCKER_IMAGE_TAG.replace("-rc", "") // If version contains more than 2 dots... It's a hotfix. - boolean isHotfix = oldImageTag.length() - oldImageTag.replace(".", "").length() > 2 + isHotfix = oldImageTag.length() - oldImageTag.replace(".", "").length() > 2 if (!isHotfix) { oldImageTag = "$oldImageTag.0" From 11ce320bfdcc2715855dc9a69057d971b7b02f3e Mon Sep 17 00:00:00 2001 From: marc Date: Thu, 19 Dec 2024 11:42:13 +0100 Subject: [PATCH 03/13] feat: Sort data elements and event fields as single unit [DHIS2-18012] (#19512) * feat: Sort data elements and event fields as single unit [DHIS2-18012] * feat: Sort data elements and event fields as single unit [DHIS2-18012] * feat: Sort data elements and event fields as single unit [DHIS2-18012] * feat: Sort data elements and event fields as single unit [DHIS2-18012] --- .../event/HibernateEventChangeLogStore.java | 12 ++-- .../resources/tracker/simple_metadata.json | 2 + .../OrderAndFilterEventChangeLogTest.java | 55 ++++++++++--------- 3 files changed, 36 insertions(+), 33 deletions(-) diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/HibernateEventChangeLogStore.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/HibernateEventChangeLogStore.java index 7f762d67f06c..53766ba38842 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/HibernateEventChangeLogStore.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/HibernateEventChangeLogStore.java @@ -57,23 +57,21 @@ public class HibernateEventChangeLogStore { private static final String COLUMN_CHANGELOG_USER = "ecl.createdByUsername"; private static final String COLUMN_CHANGELOG_DATA_ELEMENT = "d.uid"; private static final String COLUMN_CHANGELOG_FIELD = "ecl.eventField"; - + private static final String ORDER_CHANGE_EXPRESSION = + "CONCAT(COALESCE(d.formName, ''), COALESCE(" + COLUMN_CHANGELOG_FIELD + ", ''))"; private static final String DEFAULT_ORDER = COLUMN_CHANGELOG_CREATED + " " + SortDirection.DESC.getValue(); /** * Event change logs can be ordered by given fields which correspond to fields on {@link - * EventChangeLog}. Maps fields to DB columns. The order implementation for change logs is - * different from other tracker exporters {@link EventChangeLog} is the view which is already - * returned from the service/store. Tracker exporter services return a representation we have to - * map to a view model. This mapping is not necessary for change logs. + * EventChangeLog}. Maps fields to DB columns, except when sorting by 'change'. In that case we + * need to sort by concatenation, to treat the dataElement and eventField as a single entity. */ private static final Map ORDERABLE_FIELDS = Map.ofEntries( entry("createdAt", COLUMN_CHANGELOG_CREATED), entry("username", COLUMN_CHANGELOG_USER), - entry("dataElement", COLUMN_CHANGELOG_DATA_ELEMENT), - entry("field", COLUMN_CHANGELOG_FIELD)); + entry("change", ORDER_CHANGE_EXPRESSION)); private static final Map>, String> FILTERABLE_FIELDS = Map.ofEntries( diff --git a/dhis-2/dhis-support/dhis-support-test/src/main/resources/tracker/simple_metadata.json b/dhis-2/dhis-support/dhis-support-test/src/main/resources/tracker/simple_metadata.json index ef773fcfb09e..2b61abbb750d 100644 --- a/dhis-2/dhis-support/dhis-support-test/src/main/resources/tracker/simple_metadata.json +++ b/dhis-2/dhis-support/dhis-support-test/src/main/resources/tracker/simple_metadata.json @@ -529,6 +529,7 @@ "lastUpdated": "2015-03-31T11:22:51.642", "name": "Height in cm", "shortName": "Height in cm", + "formName": "Height in cm", "url": "", "valueType": "NUMBER" }, @@ -549,6 +550,7 @@ "lastUpdated": "2015-03-31T11:22:51.642", "name": "Height in mm", "shortName": "Height in mm", + "formName": "Height in mm", "url": "", "valueType": "NUMBER" } diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/event/OrderAndFilterEventChangeLogTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/event/OrderAndFilterEventChangeLogTest.java index 943e3318264c..c01879abbcb2 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/event/OrderAndFilterEventChangeLogTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/event/OrderAndFilterEventChangeLogTest.java @@ -153,55 +153,58 @@ void shouldSortChangeLogsWhenOrderingByCreatedAtAsc() void shouldSortChangeLogsWhenOrderingByDataElementAsc() throws ForbiddenException, NotFoundException { EventChangeLogOperationParams params = - EventChangeLogOperationParams.builder().orderBy("dataElement", SortDirection.ASC).build(); + EventChangeLogOperationParams.builder().orderBy("change", SortDirection.ASC).build(); Event event = getEvent("kWjSezkXHVp"); updateDataValues(event, "GieVkTxp4HH", "20", "25"); updateDataValues(event, "GieVkTxp4HG", "20"); List changeLogs = - getDataElementChangeLogs( - eventChangeLogService.getEventChangeLog( - UID.of("kWjSezkXHVp"), params, defaultPageParams)); + eventChangeLogService + .getEventChangeLog(UID.of("kWjSezkXHVp"), params, defaultPageParams) + .getItems(); - assertNumberOfChanges(5, changeLogs); + assertNumberOfChanges(7, changeLogs); assertAll( - () -> assertDataElementUpdate("GieVkTxp4HG", "10", "20", changeLogs.get(0)), - () -> assertDataElementCreate("GieVkTxp4HG", "10", changeLogs.get(1)), - () -> assertDataElementUpdate("GieVkTxp4HH", "20", "25", changeLogs.get(2)), - () -> assertDataElementUpdate("GieVkTxp4HH", "15", "20", changeLogs.get(3)), - () -> assertDataElementCreate("GieVkTxp4HH", "15", changeLogs.get(4))); + () -> assertDataElementUpdate("GieVkTxp4HH", "20", "25", changeLogs.get(0)), + () -> assertDataElementUpdate("GieVkTxp4HH", "15", "20", changeLogs.get(1)), + () -> assertDataElementCreate("GieVkTxp4HH", "15", changeLogs.get(2)), + () -> assertDataElementUpdate("GieVkTxp4HG", "10", "20", changeLogs.get(3)), + () -> assertDataElementCreate("GieVkTxp4HG", "10", changeLogs.get(4)), + () -> assertFieldCreate("occurredAt", "2022-04-22 06:00:38.343", changeLogs.get(5)), + () -> assertFieldCreate("scheduledAt", "2022-04-26 06:00:34.323", changeLogs.get(6))); } @Test - void shouldSortChangeLogsWhenOrderingByDataElementDesc() - throws ForbiddenException, NotFoundException { + void shouldSortChangeLogsWhenOrderingByChangeDesc() throws ForbiddenException, NotFoundException { EventChangeLogOperationParams params = - EventChangeLogOperationParams.builder().orderBy("dataElement", SortDirection.DESC).build(); + EventChangeLogOperationParams.builder().orderBy("change", SortDirection.DESC).build(); Event event = getEvent("kWjSezkXHVp"); updateDataValues(event, "GieVkTxp4HH", "20", "25"); updateDataValues(event, "GieVkTxp4HG", "20"); List changeLogs = - getDataElementChangeLogs( - eventChangeLogService.getEventChangeLog( - UID.of("kWjSezkXHVp"), params, defaultPageParams)); + eventChangeLogService + .getEventChangeLog(UID.of("kWjSezkXHVp"), params, defaultPageParams) + .getItems(); - assertNumberOfChanges(5, changeLogs); + assertNumberOfChanges(7, changeLogs); assertAll( - () -> assertDataElementUpdate("GieVkTxp4HH", "20", "25", changeLogs.get(0)), - () -> assertDataElementUpdate("GieVkTxp4HH", "15", "20", changeLogs.get(1)), - () -> assertDataElementCreate("GieVkTxp4HH", "15", changeLogs.get(2)), - () -> assertDataElementUpdate("GieVkTxp4HG", "10", "20", changeLogs.get(3)), - () -> assertDataElementCreate("GieVkTxp4HG", "10", changeLogs.get(4))); + () -> assertFieldCreate("scheduledAt", "2022-04-26 06:00:34.323", changeLogs.get(0)), + () -> assertFieldCreate("occurredAt", "2022-04-22 06:00:38.343", changeLogs.get(1)), + () -> assertDataElementUpdate("GieVkTxp4HG", "10", "20", changeLogs.get(2)), + () -> assertDataElementCreate("GieVkTxp4HG", "10", changeLogs.get(3)), + () -> assertDataElementUpdate("GieVkTxp4HH", "20", "25", changeLogs.get(4)), + () -> assertDataElementUpdate("GieVkTxp4HH", "15", "20", changeLogs.get(5)), + () -> assertDataElementCreate("GieVkTxp4HH", "15", changeLogs.get(6))); } @Test - void shouldSortChangeLogsWhenOrderingByFieldAsc() + void shouldSortChangeLogsWhenOrderingByChangeAscAndChangesOnlyToEventFields() throws ForbiddenException, NotFoundException, IOException { EventChangeLogOperationParams params = - EventChangeLogOperationParams.builder().orderBy("field", SortDirection.ASC).build(); + EventChangeLogOperationParams.builder().orderBy("change", SortDirection.ASC).build(); UID event = UID.of("QRYjLTiJTrA"); LocalDateTime currentTime = LocalDateTime.now(); @@ -232,10 +235,10 @@ void shouldSortChangeLogsWhenOrderingByFieldAsc() } @Test - void shouldSortChangeLogsWhenOrderingByFieldDesc() + void shouldSortChangeLogsWhenOrderingByChangeDescAndChangesOnlyToEventFields() throws ForbiddenException, NotFoundException, IOException { EventChangeLogOperationParams params = - EventChangeLogOperationParams.builder().orderBy("field", SortDirection.DESC).build(); + EventChangeLogOperationParams.builder().orderBy("change", SortDirection.DESC).build(); UID event = UID.of("QRYjLTiJTrA"); LocalDateTime currentTime = LocalDateTime.now(); From 0c43892018fdbd116f9a5335628d247d35c9510d Mon Sep 17 00:00:00 2001 From: Maikel Arabori <51713408+maikelarabori@users.noreply.github.com> Date: Thu, 19 Dec 2024 12:04:50 +0100 Subject: [PATCH 04/13] feat: Add defaults to optionSetItem [DHIS2-18643] (#19536) * feat: Add defaults to optionSetItem [DHIS2-18643] * test: For absent aggregation [DHIS2-18643] --- .../hisp/dhis/common/DataDimensionItem.java | 35 ++++-- .../org/hisp/dhis/common/OptionSetItem.java | 14 +++ .../VisualizationControllerTest.java | 100 ++++++++++++++++++ 3 files changed, 142 insertions(+), 7 deletions(-) diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/DataDimensionItem.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/DataDimensionItem.java index 87a1317a19fd..e0d189e0ad5f 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/DataDimensionItem.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/DataDimensionItem.java @@ -27,6 +27,8 @@ */ package org.hisp.dhis.common; +import static org.hisp.dhis.analytics.Aggregation.AGGREGATED; + import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonSerialize; @@ -40,6 +42,7 @@ import java.util.stream.Collectors; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; +import org.hisp.dhis.analytics.Aggregation; import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.dataelement.DataElementOperand; import org.hisp.dhis.expressiondimensionitem.ExpressionDimensionItem; @@ -113,16 +116,31 @@ public class DataDimensionItem { @AllArgsConstructor public static class Attributes implements Serializable { /** The option item for this dimension item. * */ - private OptionSetItem optionItem; + private OptionSetItem optionSetItem; @JsonProperty @JacksonXmlProperty(namespace = DxfNamespaces.DXF_2_0) public OptionSetItem getOptionSetItem() { - return optionItem; + return optionSetItem; + } + + /** + * This method ensure that existing persisted items will return default values, case the current + * {@link OptionSetItem} is null or does not have an {@link Aggregation} defined. + * + * @return the correct version of an {@link OptionSetItem}. + */ + public OptionSetItem getOptionSetItemOrDefault() { + if (optionSetItem != null) { + return new OptionSetItem( + optionSetItem.getOptions(), optionSetItem.getAggregationOrDefault()); + } + + return new OptionSetItem(Set.of(), AGGREGATED); } - public void setOptionSetItem(OptionSetItem optionItem) { - this.optionItem = optionItem; + public void setOptionSetItem(OptionSetItem optionSetItem) { + this.optionSetItem = optionSetItem; } } @@ -232,14 +250,17 @@ public DimensionalItemObject getDimensionalItemObject() { } /** - * Simply loads the internal attributes into the given item object. + * Simply loads the internal attributes into the given item object. Some objects, when null, will + * be loaded with their respective defaults. * * @param itemObject the {@link BaseDimensionalItemObject}. */ private void loadAttributes(BaseDimensionalItemObject itemObject) { - if (attributes != null) { - itemObject.setOptionSetItem(attributes.getOptionSetItem()); + if (attributes == null) { + attributes = new Attributes(); } + + itemObject.setOptionSetItem(attributes.getOptionSetItemOrDefault()); } @JsonProperty diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/OptionSetItem.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/OptionSetItem.java index 2a15915de4c1..166085a45c8a 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/OptionSetItem.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/OptionSetItem.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis.common; +import static org.hisp.dhis.analytics.Aggregation.AGGREGATED; import static org.hisp.dhis.common.DxfNamespaces.DXF_2_0; import com.fasterxml.jackson.annotation.JsonProperty; @@ -71,4 +72,17 @@ public Aggregation getAggregation() { public void setAggregation(Aggregation aggregation) { this.aggregation = aggregation; } + + /** + * Returns the current {@link Aggregation} or default. + * + * @return the respective {@link Aggregation} object. + */ + public Aggregation getAggregationOrDefault() { + if (aggregation == null) { + return AGGREGATED; + } + + return aggregation; + } } diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/VisualizationControllerTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/VisualizationControllerTest.java index 0c17eeb5c6f7..5a4e7c5c2fab 100644 --- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/VisualizationControllerTest.java +++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/VisualizationControllerTest.java @@ -436,4 +436,104 @@ void testPostOptionSetItemInDataElement() { assertEquals( "[\"BFhv3jQZ8Cw\"]", itemsNode.get("optionSetItem").get("options").value().toString()); } + + @Test + void testPostWithoutOptionSetItemAndLoadDefaults() { + // Given + DataElement dataElement = createDataElement('A'); + manager.save(dataElement); + + String dataElementUid = dataElement.getUid(); + String jsonBody = + """ +{ + "type": "PIE", + "columns": [ + { + "dimension": "dx", + "items": [ + { + "id": "${dataElement}", + "name": "Data Element - OptionSet", + "dimensionItemType": "DATA_ELEMENT" + } + ] + } + ], + "name": "OptionSetItem - Test" +} +""" + .replace("${dataElement}", dataElementUid); + + // When + String uid = assertStatus(CREATED, POST("/visualizations/", jsonBody)); + + // Then + String getParams = "?fields=columns[:all,items[:all,optionSetItem[options,aggregation]]"; + JsonObject response = GET("/visualizations/" + uid + getParams).content(); + + JsonNode columnNode = response.get("columns").node().element(0); + JsonNode itemsNode = columnNode.get("items").elementOrNull(0); + + assertEquals("DATA_X", columnNode.get("dimensionType").value()); + assertTrue((boolean) columnNode.get("dataDimension").value()); + assertEquals("DATA_ELEMENT", itemsNode.get("dimensionItemType").value()); + assertEquals("SUM", itemsNode.get("aggregationType").value()); + assertEquals(dataElementUid, itemsNode.get("dimensionItem").value()); + assertEquals("AGGREGATED", itemsNode.get("optionSetItem").get("aggregation").value()); + assertEquals("[]", itemsNode.get("optionSetItem").get("options").value().toString()); + } + + @Test + void testPostOptionSetItemWithNoAggregation() { + // Given + DataElement dataElement = createDataElement('A'); + manager.save(dataElement); + + String dataElementUid = dataElement.getUid(); + String jsonBody = + """ +{ + "type": "PIE", + "columns": [ + { + "dimension": "dx", + "items": [ + { + "id": "${dataElement}", + "name": "Data Element - OptionSet", + "dimensionItemType": "DATA_ELEMENT", + "optionSetItem": { + "options": [ + "BFhv3jQZ8Cw" + ] + } + } + ] + } + ], + "name": "OptionSetItem - Test" +} +""" + .replace("${dataElement}", dataElementUid); + + // When + String uid = assertStatus(CREATED, POST("/visualizations/", jsonBody)); + + // Then + String getParams = "?fields=columns[:all,items[:all,optionSetItem[options,aggregation]]"; + JsonObject response = GET("/visualizations/" + uid + getParams).content(); + + JsonNode columnNode = response.get("columns").node().element(0); + JsonNode itemsNode = columnNode.get("items").elementOrNull(0); + + assertEquals("DATA_X", columnNode.get("dimensionType").value()); + assertTrue((boolean) columnNode.get("dataDimension").value()); + assertEquals("DATA_ELEMENT", itemsNode.get("dimensionItemType").value()); + assertEquals("SUM", itemsNode.get("aggregationType").value()); + assertEquals(dataElementUid, itemsNode.get("dimensionItem").value()); + assertEquals("AGGREGATED", itemsNode.get("optionSetItem").get("aggregation").value()); + assertEquals( + "[\"BFhv3jQZ8Cw\"]", itemsNode.get("optionSetItem").get("options").value().toString()); + } } From 724b9d0ac078538e275067e61accbec81cd38479 Mon Sep 17 00:00:00 2001 From: Enrico Colasante Date: Fri, 20 Dec 2024 04:17:31 -0300 Subject: [PATCH 05/13] chore: Make dependabot upgrade rule-engine library (#19543) Rule-engine doesn't have circular dependency anymore and it can be managed by dependabot as any other dependency --- .github/dependabot.yml | 12 ------------ dhis-2/pom.xml | 10 ---------- 2 files changed, 22 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b9a55d7720e4..333fec034948 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -52,12 +52,6 @@ updates: - dependency-name: "org.springframework.ldap:*" # Spring ldap 3.x requires Spring 6 (see above) versions: - ">= 3.0" - - dependency-name: "org.hisp.dhis.parser:*" # Antlr parser must be upgraded manually due to circular dependency with rule engine - versions: - - ">= 1.0" - - dependency-name: "org.hisp.dhis.rules:*" # Rule engine must be upgraded manually due to circular dependency with ANTLR parser - versions: - - ">= 2.0" - dependency-name: "org.slf4j:slf4j-api" # will update in https://dhis2.atlassian.net/browse/DHIS2-16504 versions: - ">= 2.0" @@ -138,12 +132,6 @@ updates: - dependency-name: "org.springframework.ldap:*" # Spring ldap 3.x requires Spring 6 (see above) versions: - ">= 3.0" - - dependency-name: "org.hisp.dhis.parser:*" # Antlr parser must be upgraded manually due to circular dependency with rule engine - versions: - - ">= 1.0" - - dependency-name: "org.hisp.dhis.rules:*" # Rule engine must be upgraded manually due to circular dependency with ANTLR parser - versions: - - ">= 2.0" - dependency-name: "org.flywaydb:flyway-core" # It requires Postgres version to be >= 11 versions: - "> 9.22.3" diff --git a/dhis-2/pom.xml b/dhis-2/pom.xml index ead83aebaee0..a379992088b6 100644 --- a/dhis-2/pom.xml +++ b/dhis-2/pom.xml @@ -428,16 +428,6 @@ org.hisp.dhis.rules rule-engine-jvm ${dhis2-rule-engine.version} - - - commons-logging - commons-logging - - - org.slf4j - slf4j-simple - - From 73fa91e6c07fe396db105cd310224ee6402fb2bc Mon Sep 17 00:00:00 2001 From: Martina Kraus Date: Fri, 20 Dec 2024 09:50:41 +0100 Subject: [PATCH 06/13] feat: integrate dependency track (#19412) --- .github/workflows/generate-merge-boms.yml | 51 +++++++++++++++++++++++ dhis-2/pom.xml | 22 ++++++++++ 2 files changed, 73 insertions(+) create mode 100644 .github/workflows/generate-merge-boms.yml diff --git a/.github/workflows/generate-merge-boms.yml b/.github/workflows/generate-merge-boms.yml new file mode 100644 index 000000000000..50b23f38f675 --- /dev/null +++ b/.github/workflows/generate-merge-boms.yml @@ -0,0 +1,51 @@ +name: Generate and Merge SBOMs and Upload them to DependencyTrack every night + +on: + schedule: + - cron: "0 0 * * *" # Run every day at midnight + +concurrency: + group: ${{ github.workflow}}-${{ github.ref }} + cancel-in-progress: true + +jobs: + create-boms: + runs-on: ubuntu-latest + defaults: + run: + working-directory: dhis-2/ + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: temurin + cache: maven + + - name: Install CycloneDX CLI + run: | + curl -s https://api.github.com/repos/CycloneDX/cyclonedx-cli/releases/latest | grep "browser_download_url.*linux.x64" | cut -d '"' -f 4 | wget -i - + sudo mv cyclonedx-linux-x64 /usr/local/bin/ + sudo chmod +x /usr/local/bin/cyclonedx-linux-x64 + + - name: Generate BOMs + run: mvn cyclonedx:makeBom + + - name: Merge BOMs + run: cyclonedx-linux-x64 merge --input-files $(find . -name 'dxbom.json') --input-format json --output-file target/merged-bom.json --output-format json + + - name: Upload SBOM to DependencyTrack + env: + DEPENDENCY_TRACK_API: "https://dt.security.dhis2.org/api/v1/bom" + run: | + curl -X POST "$DEPENDENCY_TRACK_API" \ + --fail-with-body \ + -H "Content-Type: multipart/form-data" \ + -H "X-Api-Key: ${{ secrets.DEPENDENCYTRACK_APIKEY }}" \ + -F "project=56383704-d5a2-4a35-ad6a-081f80f5d6d3" \ + -F "bom=@target/merged-bom.json" diff --git a/dhis-2/pom.xml b/dhis-2/pom.xml index a379992088b6..6c723e47141c 100644 --- a/dhis-2/pom.xml +++ b/dhis-2/pom.xml @@ -1872,6 +1872,28 @@ sonar-maven-plugin 5.0.0.4389 + + org.cyclonedx + cyclonedx-maven-plugin + + library + 1.6 + true + true + true + true + true + false + false + true + all + dxbom + ${project.build.directory} + + false + + + org.codehaus.mojo versions-maven-plugin From 826a04921f77f4fbd9147203d44a616ff729fcf9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 07:57:58 -0300 Subject: [PATCH 07/13] chore(deps): bump org.springframework.session:spring-session-core (#19546) Bumps [org.springframework.session:spring-session-core](https://github.com/spring-projects/spring-session) from 3.4.0 to 3.4.1. - [Release notes](https://github.com/spring-projects/spring-session/releases) - [Changelog](https://github.com/spring-projects/spring-session/blob/main/RELEASE.adoc) - [Commits](https://github.com/spring-projects/spring-session/compare/3.4.0...3.4.1) --- updated-dependencies: - dependency-name: org.springframework.session:spring-session-core dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dhis-2/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dhis-2/pom.xml b/dhis-2/pom.xml index 6c723e47141c..5b9734d4bd45 100644 --- a/dhis-2/pom.xml +++ b/dhis-2/pom.xml @@ -104,7 +104,7 @@ 6.1.12 - 3.4.0 + 3.4.1 2.7.18 2.7.4 1.1.5.RELEASE From 900b01c73537a5686095d7492c013c1145308a47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 07:58:51 -0300 Subject: [PATCH 08/13] chore(deps-dev): bump org.hisp.dhis.rules:rule-engine-jvm in /dhis-2 (#19548) Bumps [org.hisp.dhis.rules:rule-engine-jvm](https://github.com/dhis2/dhis2-rule-engine) from 3.1.0 to 3.2.1. - [Release notes](https://github.com/dhis2/dhis2-rule-engine/releases) - [Commits](https://github.com/dhis2/dhis2-rule-engine/commits) --- updated-dependencies: - dependency-name: org.hisp.dhis.rules:rule-engine-jvm dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dhis-2/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dhis-2/pom.xml b/dhis-2/pom.xml index 5b9734d4bd45..164856cbd652 100644 --- a/dhis-2/pom.xml +++ b/dhis-2/pom.xml @@ -85,7 +85,7 @@ - 3.1.0 + 3.2.1 0.6.1 From 6cb0d1fdd63acfe76f5217afb7d7c35e6c77d525 Mon Sep 17 00:00:00 2001 From: Enrico Colasante Date: Fri, 20 Dec 2024 10:57:32 -0300 Subject: [PATCH 09/13] fix: Always add default order as last order param in tracker exporters [DHIS2-18659] (#19551) * fix: Always add default order as last order param for events [DHIS2-18659] * fix: Always add default order as last order param for enrollments [DHIS2-18659] * fix: Always add default order as last order param for relationships [DHIS2-18659] * fix: Always add default order as last order param for tracked entities [DHIS2-18659] --- .../enrollment/HibernateEnrollmentStore.java | 2 +- .../tracker/export/event/JdbcEventStore.java | 2 +- .../HibernateRelationshipStore.java | 8 +- .../HibernateTrackedEntityStore.java | 4 +- .../OrderAndPaginationExporterTest.java | 94 +++++++++++++++++++ .../tracker/event_and_enrollment.json | 16 ++++ 6 files changed, 120 insertions(+), 6 deletions(-) diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/enrollment/HibernateEnrollmentStore.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/enrollment/HibernateEnrollmentStore.java index 49f165a22cc2..d67e20076ef6 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/enrollment/HibernateEnrollmentStore.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/enrollment/HibernateEnrollmentStore.java @@ -279,7 +279,7 @@ private static String orderBy(List orders) { orderJoiner.add( order.getField() + " " + (order.getDirection().isAscending() ? "asc" : "desc")); } - return " order by " + orderJoiner; + return " order by " + orderJoiner + ", " + DEFAULT_ORDER; } @Getter diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/JdbcEventStore.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/JdbcEventStore.java index 09c6e1c9a542..2b5d9f11e8b6 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/JdbcEventStore.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/JdbcEventStore.java @@ -1612,7 +1612,7 @@ private String getOrderQuery(EventQueryParams params) { } if (!orderFields.isEmpty()) { - return "order by " + StringUtils.join(orderFields, ',') + " "; + return "order by " + StringUtils.join(orderFields, ',') + ", " + DEFAULT_ORDER + " "; } else { return "order by " + DEFAULT_ORDER + " "; } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/relationship/HibernateRelationshipStore.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/relationship/HibernateRelationshipStore.java index 05801268d0c3..90c1715f9e8c 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/relationship/HibernateRelationshipStore.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/relationship/HibernateRelationshipStore.java @@ -41,6 +41,7 @@ import java.util.Set; import java.util.function.Function; import java.util.function.LongSupplier; +import java.util.stream.Stream; import javax.annotation.Nonnull; import org.apache.commons.collections4.CollectionUtils; import org.hisp.dhis.common.IdentifiableObject; @@ -264,10 +265,13 @@ private String getRelationshipEntityType(T entity private List orderBy( RelationshipQueryParams queryParams, CriteriaBuilder builder, Root root) { + List defaultOrder = orderBy(List.of(DEFAULT_ORDER), builder, root); if (!queryParams.getOrder().isEmpty()) { - return orderBy(queryParams.getOrder(), builder, root); + return Stream.concat( + orderBy(queryParams.getOrder(), builder, root).stream(), defaultOrder.stream()) + .toList(); } else { - return orderBy(List.of(DEFAULT_ORDER), builder, root); + return defaultOrder; } } diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/HibernateTrackedEntityStore.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/HibernateTrackedEntityStore.java index 8aa3221956e8..948d63004212 100644 --- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/HibernateTrackedEntityStore.java +++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/HibernateTrackedEntityStore.java @@ -1007,10 +1007,10 @@ private String getQueryOrderBy(TrackedEntityQueryParams params, boolean innerOrd } if (!orderFields.isEmpty()) { - return "ORDER BY " + StringUtils.join(orderFields, ',') + SPACE; + return "ORDER BY " + StringUtils.join(orderFields, ',') + ", " + DEFAULT_ORDER + SPACE; } - return "ORDER BY " + DEFAULT_ORDER + " "; + return "ORDER BY " + DEFAULT_ORDER + SPACE; } /** diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/OrderAndPaginationExporterTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/OrderAndPaginationExporterTest.java index b925005fca34..3106c4c8b009 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/OrderAndPaginationExporterTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/OrderAndPaginationExporterTest.java @@ -207,6 +207,32 @@ void shouldReturnPaginatedTrackedEntitiesGivenNonDefaultPageSizeAndTotalPages() trackedEntityService.getTrackedEntities(params, new PageParams(3, 3, true)).getItems()); } + @Test + void shouldOrderTrackedEntitiesByInactiveAndByDefaultOrder() + throws ForbiddenException, BadRequestException, NotFoundException { + List expected = + Stream.of( + get(TrackedEntity.class, "QesgJkTyTCk"), + get(TrackedEntity.class, "dUE514NMOlo"), + get(TrackedEntity.class, "mHWCacsGYYn")) + .sorted(Comparator.comparing(TrackedEntity::getId).reversed()) // reversed = desc + .map(TrackedEntity::getUid) + .toList(); + + TrackedEntityOperationParams params = + TrackedEntityOperationParams.builder() + .organisationUnits(orgUnit) + .orgUnitMode(SELECTED) + .trackedEntities(UID.of("mHWCacsGYYn", "QesgJkTyTCk", "dUE514NMOlo")) + .trackedEntityType(trackedEntityType) + .orderBy("inactive", SortDirection.ASC) + .build(); + + List trackedEntities = getTrackedEntities(params); + + assertEquals(expected, trackedEntities); + } + @Test void shouldOrderTrackedEntitiesByPrimaryKeyDescByDefault() throws ForbiddenException, BadRequestException, NotFoundException { @@ -566,6 +592,28 @@ void shouldOrderTrackedEntitiesByInactiveAsc() assertEquals(List.of("dUE514NMOlo", "QS6w44flWAf"), trackedEntities); } + @Test + void shouldOrderEnrollmentsByStatusAndByDefaultOrder() + throws ForbiddenException, BadRequestException { + List expected = + Stream.of(get(Enrollment.class, "HDWTYSYkICe"), get(Enrollment.class, "GYWSSZunTLk")) + .sorted(Comparator.comparing(Enrollment::getId).reversed()) // reversed = desc + .map(Enrollment::getUid) + .toList(); + + EnrollmentOperationParams operationParams = + EnrollmentOperationParams.builder() + .orgUnits(get(OrganisationUnit.class, "DiszpKrYNg8")) + .orgUnitMode(SELECTED) + .enrollments(UID.of("HDWTYSYkICe", "GYWSSZunTLk")) + .orderBy("status", SortDirection.DESC) + .build(); + + List actual = getEnrollments(operationParams); + + assertEquals(expected, actual); + } + @Test void shouldReturnPaginatedEnrollmentsGivenNonDefaultPageSize() throws ForbiddenException, BadRequestException { @@ -677,6 +725,29 @@ void shouldOrderEnrollmentsByEnrolledAtDesc() throws ForbiddenException, BadRequ assertEquals(List.of("TvctPPhpD8z", "nxP7UnKhomJ"), enrollments); } + @Test + void shouldOrderEventsByStatusAndByDefaultOrder() throws ForbiddenException, BadRequestException { + List expected = + Stream.of( + get(Event.class, "ck7DzdxqLqA"), + get(Event.class, "kWjSezkXHVp"), + get(Event.class, "OTmjvJDn0Fu")) + .sorted(Comparator.comparing(Event::getId).reversed()) // reversed = desc + .map(Event::getUid) + .toList(); + + EventOperationParams operationParams = + eventParamsBuilder + .orgUnit(get(OrganisationUnit.class, "DiszpKrYNg8")) + .events(UID.of("ck7DzdxqLqA", "kWjSezkXHVp", "OTmjvJDn0Fu")) + .orderBy("status", SortDirection.DESC) + .build(); + + List actual = getEvents(operationParams); + + assertEquals(expected, actual); + } + @Test void shouldReturnPaginatedEventsWithNotesGivenNonDefaultPageSize() throws ForbiddenException, BadRequestException { @@ -1274,6 +1345,29 @@ void shouldOrderByFieldInAscendingOrderWhenModeDescendants( assertEquals(List.of(firstEvent, secondEvent), events); } + @Test + void shouldOrderRelationshipsByCreatedAtClientAndByDefaultOrder() + throws ForbiddenException, BadRequestException, NotFoundException { + Relationship oLT07jKRu9e = get(Relationship.class, "fHn74P5T3r1"); + Relationship yZxjxJli9mO = get(Relationship.class, "yZxjxJli9mO"); + List expected = + Stream.of(oLT07jKRu9e, yZxjxJli9mO) + .sorted(Comparator.comparing(Relationship::getId).reversed()) // reversed = desc + .map(Relationship::getUid) + .toList(); + + RelationshipOperationParams params = + RelationshipOperationParams.builder() + .type(TrackerType.TRACKED_ENTITY) + .identifier(UID.of("dUE514NMOlo")) + .orderBy("createdAtClient", SortDirection.DESC) + .build(); + + List relationships = getRelationships(params); + + assertEquals(expected, relationships); + } + @Test void shouldOrderRelationshipsByPrimaryKeyDescByDefault() throws ForbiddenException, BadRequestException, NotFoundException { diff --git a/dhis-2/dhis-test-integration/src/test/resources/tracker/event_and_enrollment.json b/dhis-2/dhis-test-integration/src/test/resources/tracker/event_and_enrollment.json index d5ea8bc6e580..27db31bb7b87 100644 --- a/dhis-2/dhis-test-integration/src/test/resources/tracker/event_and_enrollment.json +++ b/dhis-2/dhis-test-integration/src/test/resources/tracker/event_and_enrollment.json @@ -1309,6 +1309,22 @@ "to": { "event": "pTzf9KYMk72" } + }, + { + "relationship": "fHn74P5T3r1", + "relationshipType": { + "idScheme": "UID", + "identifier": "TV9oB9LT3sh" + }, + "createdAtClient": "2018-11-01T13:24:37.118", + "bidirectional": false, + "deleted": false, + "from": { + "trackedEntity": "dUE514NMOlo" + }, + "to": { + "event": "D9PbzJY8bJM" + } } ], "username": "system-process" From 08018e16e268203696f20fd58512e757c5b9b278 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 07:51:30 -0300 Subject: [PATCH 10/13] chore(deps-dev): bump com.nimbusds:nimbus-jose-jwt in /dhis-2 (#19556) Bumps [com.nimbusds:nimbus-jose-jwt](https://bitbucket.org/connect2id/nimbus-jose-jwt) from 9.47 to 9.48. - [Changelog](https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/CHANGELOG.txt) - [Commits](https://bitbucket.org/connect2id/nimbus-jose-jwt/branches/compare/9.48..9.47) --- updated-dependencies: - dependency-name: com.nimbusds:nimbus-jose-jwt dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dhis-2/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dhis-2/pom.xml b/dhis-2/pom.xml index 164856cbd652..e8a3165411d3 100644 --- a/dhis-2/pom.xml +++ b/dhis-2/pom.xml @@ -98,7 +98,7 @@ 1.1.1.RELEASE 1.70 1.9.3 - 9.47 + 9.48 3.5.3 3.5.3 From 59dd5fd9292058011197bb64bb269dc91c5bfb0d Mon Sep 17 00:00:00 2001 From: Chisom Chima <87203527+Chisomchima@users.noreply.github.com> Date: Mon, 23 Dec 2024 12:51:48 +0100 Subject: [PATCH 11/13] feat: login fallback error (#19537) * feat: dynamically get url to make api calls * feat: dynamically get url to make api calls * fix: use relative path instead of dynamic url * feat: update relative path * chore: add relative paths for images --- .../resources/org/hisp/dhis/webapi/servlet/login.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dhis-2/dhis-web-api/src/main/resources/org/hisp/dhis/webapi/servlet/login.html b/dhis-2/dhis-web-api/src/main/resources/org/hisp/dhis/webapi/servlet/login.html index eb6c3f156269..a9775e915edd 100644 --- a/dhis-2/dhis-web-api/src/main/resources/org/hisp/dhis/webapi/servlet/login.html +++ b/dhis-2/dhis-web-api/src/main/resources/org/hisp/dhis/webapi/servlet/login.html @@ -250,7 +250,7 @@ @@ -328,7 +328,7 @@

Log in

// Fetch the login configuration dynamically async function fetchLoginConfig() { try { - const response = await fetch("../api/loginConfig"); + const response = await fetch("./api/loginConfig"); if (!response.ok) { let errorMessage = "Login failed: " + response.status; @@ -351,7 +351,7 @@

Log in

config.applicationDescription || "Welcome to the DHIS2 application"; if (config.countryFlag) { const flag = document.getElementById("flag"); - flag.src = `/dhis-web-commons/flags/${config.countryFlag}.png`; + flag.src = `./dhis-web-commons/flags/${config.countryFlag}.png`; flag.style.display = "block"; } } catch (error) { @@ -400,7 +400,7 @@

Log in

} try { - const response = await fetch("../api/auth/login", { + const response = await fetch("./api/auth/login", { method: "POST", headers: { "Content-Type": "application/json", @@ -425,7 +425,7 @@

Log in

const user = await response.json(); if (user.loginStatus === "SUCCESS") { - const redirectUrl = user.redirectUrl || "/"; + const redirectUrl = user.redirectUrl || "./"; window.location.href = redirectUrl; } else { throw new Error("Login failed. Status: " + user.loginStatus); From c3cb5c2d7edd3d58307b72efa413749bc1867863 Mon Sep 17 00:00:00 2001 From: Maikel Arabori <51713408+maikelarabori@users.noreply.github.com> Date: Mon, 23 Dec 2024 14:53:56 +0100 Subject: [PATCH 12/13] feat: Add optionSetId into items returned by /dataItems [DHIS2-18644] (#19552) * feat: Add optionSetId into items returned by /dataItems [DHIS2-18644] * chore: Removed unused class [DHIS2-18644] * fix: Missing left join [DHIS2-18644] --- .../java/org/hisp/dhis/dataitem/DataItem.java | 4 + .../dhis/dataitem/query/DataElementQuery.java | 46 +++- .../dhis/dataitem/query/DataItemQuery.java | 2 + .../dhis/dataitem/query/DataSetQuery.java | 26 ++- .../query/ExpressionDimensionItemQuery.java | 33 +-- .../dhis/dataitem/query/IndicatorQuery.java | 25 ++- .../dhis/dataitem/query/OptionSetQuery.java | 208 ------------------ .../dataitem/query/ProgramAttributeQuery.java | 37 +++- .../dataitem/query/ProgramIndicatorQuery.java | 25 ++- .../query/ProgramStageDataElementQuery.java | 34 ++- .../dataitem/query/QueryableDataItem.java | 4 +- .../dhis/dataitem/query/ResultProcessor.java | 7 +- .../query/shared/FilteringStatement.java | 17 ++ .../dataitem/query/shared/QueryParam.java | 2 + .../dataitems/DataItemsAnalyticsTest.java | 59 ++++- .../webapi/controller/dataitem/Filter.java | 2 + .../dataitem/helper/FilteringHelper.java | 6 + 17 files changed, 265 insertions(+), 272 deletions(-) delete mode 100644 dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/OptionSetQuery.java diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataitem/DataItem.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataitem/DataItem.java index 964e89c41ef7..693d8b6c89a4 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataitem/DataItem.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataitem/DataItem.java @@ -100,6 +100,10 @@ public class DataItem implements Serializable { @JacksonXmlProperty(namespace = DXF_2_0) private String expression; + @JsonProperty + @JacksonXmlProperty(namespace = DXF_2_0) + private String optionSetId; + public ValueType getSimplifiedValueType() { return valueType != null ? valueType.toSimplifiedValueType() : null; } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/DataElementQuery.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/DataElementQuery.java index 928b0836cd32..14735f3b322c 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/DataElementQuery.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/DataElementQuery.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis.dataitem.query; +import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toSet; import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.hisp.dhis.common.ValueType.getAggregatables; @@ -37,6 +38,7 @@ import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.ifAny; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.ifSet; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.nameFiltering; +import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.optionSetIdFiltering; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.rootJunction; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.shortNameFiltering; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.uidFiltering; @@ -54,8 +56,10 @@ import static org.hisp.dhis.dataitem.query.shared.UserAccessStatement.READ_ACCESS; import static org.hisp.dhis.dataitem.query.shared.UserAccessStatement.sharingConditions; +import java.util.List; import java.util.Set; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.tuple.Pair; import org.hisp.dhis.common.BaseIdentifiableObject; import org.hisp.dhis.dataitem.query.shared.OptionalFilterBuilder; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; @@ -70,12 +74,28 @@ @Component public class DataElementQuery implements DataItemQuery { private static final String COMMON_COLUMNS = - "cast (null as text) as program_name, cast (null as text) as program_uid," - + " cast (null as text) as program_shortname, dataelement.uid as item_uid, dataelement.name as item_name," - + " dataelement.shortname as item_shortname, dataelement.valuetype as item_valuetype," - + " dataelement.code as item_code, dataelement.sharing as item_sharing, dataelement.domaintype as item_domaintype," - + " cast ('DATA_ELEMENT' as text) as item_type," - + " cast (null as text) as expression"; + List.of( + Pair.of("program_name", CAST_NULL_AS_TEXT), + Pair.of("program_uid", CAST_NULL_AS_TEXT), + Pair.of("program_shortname", CAST_NULL_AS_TEXT), + Pair.of("item_uid", "dataelement.uid"), + Pair.of("item_name", "dataelement.name"), + Pair.of("item_shortname", "dataelement.shortname"), + Pair.of("item_valuetype", "dataelement.valuetype"), + Pair.of("item_code", "dataelement.code"), + Pair.of("item_sharing", "dataelement.sharing"), + Pair.of("item_domaintype", "dataelement.domaintype"), + Pair.of("item_type", "cast ('DATA_ELEMENT' as text)"), + Pair.of("expression", CAST_NULL_AS_TEXT), + Pair.of("optionset_uid", "optionset.uid")) + .stream() + .map(pair -> pair.getRight() + " as " + pair.getLeft()) + .collect(joining(", ")); + + private static final String JOINS = + "left join optionset on dataelement.optionsetid = optionset.optionsetid"; + + private static final String SPACED_FROM_DATA_ELEMENT = " from dataelement "; @Override public String getStatement(MapSqlParameterSource paramsMap) { @@ -95,7 +115,7 @@ public String getStatement(MapSqlParameterSource paramsMap) { } sql.append( - " group by item_name, item_uid, item_valuetype, item_code, item_domaintype, item_sharing, item_shortname," + " group by optionset.uid, item_name, item_uid, item_valuetype, item_code, item_domaintype, item_sharing, item_shortname," + " i18n_first_name, i18n_first_shortname, i18n_second_name, i18n_second_shortname"); // Closing the temp table. @@ -131,6 +151,7 @@ public String getStatement(MapSqlParameterSource paramsMap) { optionalFilters.append(ifSet(nameFiltering("t.item_name", paramsMap))); optionalFilters.append(ifSet(shortNameFiltering("t.item_shortname", paramsMap))); optionalFilters.append(ifSet(uidFiltering("t.item_uid", paramsMap))); + optionalFilters.append(ifSet(optionSetIdFiltering("t.optionset_uid", paramsMap))); sql.append(ifAny(optionalFilters.toString())); String identifiableStatement = @@ -180,9 +201,11 @@ public Class getRootEntity() { private String selectRowsContainingTranslatedName() { StringBuilder sql = new StringBuilder(); - sql.append(SPACED_SELECT + COMMON_COLUMNS).append(translationNamesColumnsFor("dataelement")); - - sql.append(" from dataelement ").append(translationNamesJoinsOn("dataelement")); + sql.append(SPACED_SELECT + COMMON_COLUMNS) + .append(translationNamesColumnsFor("dataelement")) + .append(SPACED_FROM_DATA_ELEMENT) + .append(JOINS) + .append(translationNamesJoinsOn("dataelement")); return sql.toString(); } @@ -193,7 +216,8 @@ private String selectAllRowsIgnoringAnyTranslation() { .append(", dataelement.name as i18n_first_name, cast (null as text) as i18n_second_name") .append( ", dataelement.shortname as i18n_first_shortname, cast (null as text) as i18n_second_shortname") - .append(" from dataelement ") + .append(SPACED_FROM_DATA_ELEMENT) + .append(JOINS) .toString(); } } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/DataItemQuery.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/DataItemQuery.java index 3c277c13f6df..e821aed51b07 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/DataItemQuery.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/DataItemQuery.java @@ -37,6 +37,8 @@ * @author maikel arabori */ public interface DataItemQuery { + String CAST_NULL_AS_TEXT = "cast (null as text)"; + /** * Builds and returns the SQL statement required by the implementation. * diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/DataSetQuery.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/DataSetQuery.java index c36cd23f7e85..4aadb4ce8aa2 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/DataSetQuery.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/DataSetQuery.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis.dataitem.query; +import static java.util.stream.Collectors.joining; import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.always; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.displayNameFiltering; @@ -50,7 +51,9 @@ import static org.hisp.dhis.dataitem.query.shared.UserAccessStatement.READ_ACCESS; import static org.hisp.dhis.dataitem.query.shared.UserAccessStatement.sharingConditions; +import java.util.List; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.tuple.Pair; import org.hisp.dhis.common.BaseIdentifiableObject; import org.hisp.dhis.dataitem.query.shared.OptionalFilterBuilder; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; @@ -65,12 +68,23 @@ @Component public class DataSetQuery implements DataItemQuery { private static final String COMMON_COLUMNS = - "cast (null as text) as program_name, cast (null as text) as program_uid," - + " cast (null as text) as program_shortname, dataset.uid as item_uid, dataset.name as item_name," - + " dataset.shortname as item_shortname, cast (null as text) as item_valuetype, dataset.code as item_code," - + " dataset.sharing as item_sharing, cast (null as text) as item_domaintype," - + " cast('REPORTING_RATE' as text) as item_type," - + " cast (null as text) as expression"; + List.of( + Pair.of("program_name", CAST_NULL_AS_TEXT), + Pair.of("program_uid", CAST_NULL_AS_TEXT), + Pair.of("program_shortname", CAST_NULL_AS_TEXT), + Pair.of("item_uid", "dataset.uid"), + Pair.of("item_name", "dataset.name"), + Pair.of("item_shortname", "dataset.shortname"), + Pair.of("item_valuetype", CAST_NULL_AS_TEXT), + Pair.of("item_code", "dataset.code"), + Pair.of("item_sharing", "dataset.sharing"), + Pair.of("item_domaintype", CAST_NULL_AS_TEXT), + Pair.of("item_type", "cast ('REPORTING_RATE' as text)"), + Pair.of("expression", CAST_NULL_AS_TEXT), + Pair.of("optionset_uid", CAST_NULL_AS_TEXT)) + .stream() + .map(pair -> pair.getRight() + " as " + pair.getLeft()) + .collect(joining(", ")); @Override public String getStatement(MapSqlParameterSource paramsMap) { diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/ExpressionDimensionItemQuery.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/ExpressionDimensionItemQuery.java index 839517841508..2d43c35ffd34 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/ExpressionDimensionItemQuery.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/ExpressionDimensionItemQuery.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis.dataitem.query; +import static java.util.stream.Collectors.joining; import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.always; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.displayNameFiltering; @@ -51,9 +52,9 @@ import static org.hisp.dhis.dataitem.query.shared.StatementUtil.SPACED_WHERE; import static org.hisp.dhis.dataitem.query.shared.UserAccessStatement.checkOwnerConditions; -import java.util.ArrayList; import java.util.List; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.tuple.Pair; import org.hisp.dhis.common.BaseIdentifiableObject; import org.hisp.dhis.dataitem.query.shared.OptionalFilterBuilder; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; @@ -66,18 +67,24 @@ @Slf4j @Component public class ExpressionDimensionItemQuery implements DataItemQuery { - private static final List COMMON_COLUMNS = new ArrayList<>(); - - static { - COMMON_COLUMNS.add("cast (null as text) as program_name, cast (null as text) as program_uid"); - COMMON_COLUMNS.add( - "cast (null as text) as program_shortname, expressiondimensionitem.uid as item_uid, expressiondimensionitem.name as item_name"); - COMMON_COLUMNS.add( - "expressiondimensionitem.shortname as item_shortname, cast (null as text) as item_valuetype, expressiondimensionitem.code as item_code"); - COMMON_COLUMNS.add( - "expressiondimensionitem.sharing as item_sharing, cast (null as text) as item_domaintype, cast ('EXPRESSION_DIMENSION_ITEM' as text) as item_type"); - COMMON_COLUMNS.add("expressiondimensionitem.expression"); - } + private static final String COMMON_COLUMNS = + List.of( + Pair.of("program_name", CAST_NULL_AS_TEXT), + Pair.of("program_uid", CAST_NULL_AS_TEXT), + Pair.of("program_shortname", CAST_NULL_AS_TEXT), + Pair.of("item_uid", "expressiondimensionitem.uid"), + Pair.of("item_name", "expressiondimensionitem.name"), + Pair.of("item_shortname", "expressiondimensionitem.shortname"), + Pair.of("item_valuetype", CAST_NULL_AS_TEXT), + Pair.of("item_code", "expressiondimensionitem.code"), + Pair.of("item_sharing", "expressiondimensionitem.sharing"), + Pair.of("item_domaintype", CAST_NULL_AS_TEXT), + Pair.of("item_type", "cast ('EXPRESSION_DIMENSION_ITEM' as text)"), + Pair.of("expression", "expressiondimensionitem.expression"), + Pair.of("optionset_uid", CAST_NULL_AS_TEXT)) + .stream() + .map(pair -> pair.getRight() + " as " + pair.getLeft()) + .collect(joining(", ")); /** * Builds and returns the SQL statement required by the implementation. diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/IndicatorQuery.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/IndicatorQuery.java index 98350c677d4e..74655b42c231 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/IndicatorQuery.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/IndicatorQuery.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis.dataitem.query; +import static java.util.stream.Collectors.joining; import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.hisp.dhis.common.ValueType.NUMBER; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.always; @@ -52,7 +53,9 @@ import static org.hisp.dhis.dataitem.query.shared.UserAccessStatement.READ_ACCESS; import static org.hisp.dhis.dataitem.query.shared.UserAccessStatement.sharingConditions; +import java.util.List; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.tuple.Pair; import org.hisp.dhis.common.BaseIdentifiableObject; import org.hisp.dhis.dataitem.query.shared.OptionalFilterBuilder; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; @@ -67,11 +70,23 @@ @Component public class IndicatorQuery implements DataItemQuery { private static final String COMMON_COLUMNS = - "cast (null as text) as program_name, cast (null as text) as program_uid," - + " cast (null as text) as program_shortname, indicator.uid as item_uid, indicator.name as item_name," - + " indicator.shortname as item_shortname, cast (null as text) as item_valuetype, indicator.code as item_code," - + " indicator.sharing as item_sharing, cast (null as text) as item_domaintype, cast ('INDICATOR' as text) as item_type," - + " cast (null as text) as expression"; + List.of( + Pair.of("program_name", CAST_NULL_AS_TEXT), + Pair.of("program_uid", CAST_NULL_AS_TEXT), + Pair.of("program_shortname", CAST_NULL_AS_TEXT), + Pair.of("item_uid", "indicator.uid"), + Pair.of("item_name", "indicator.name"), + Pair.of("item_shortname", "indicator.shortname"), + Pair.of("item_valuetype", CAST_NULL_AS_TEXT), + Pair.of("item_code", "indicator.code"), + Pair.of("item_sharing", "indicator.sharing"), + Pair.of("item_domaintype", CAST_NULL_AS_TEXT), + Pair.of("item_type", "cast ('INDICATOR' as text)"), + Pair.of("expression", CAST_NULL_AS_TEXT), + Pair.of("optionset_uid", CAST_NULL_AS_TEXT)) + .stream() + .map(pair -> pair.getRight() + " as " + pair.getLeft()) + .collect(joining(", ")); @Override public String getStatement(MapSqlParameterSource paramsMap) { diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/OptionSetQuery.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/OptionSetQuery.java deleted file mode 100644 index 2d8d88d552af..000000000000 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/OptionSetQuery.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (c) 2004-2022, University of Oslo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * Neither the name of the HISP project nor the names of its contributors may - * be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package org.hisp.dhis.dataitem.query; - -import static org.apache.commons.lang3.StringUtils.isNotBlank; -import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.always; -import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.displayNameFiltering; -import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.displayShortNameFiltering; -import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.identifiableTokenFiltering; -import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.ifAny; -import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.ifSet; -import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.nameFiltering; -import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.rootJunction; -import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.shortNameFiltering; -import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.uidFiltering; -import static org.hisp.dhis.dataitem.query.shared.LimitStatement.maxLimit; -import static org.hisp.dhis.dataitem.query.shared.NameTranslationStatement.translationNamesColumnsFor; -import static org.hisp.dhis.dataitem.query.shared.NameTranslationStatement.translationNamesJoinsOn; -import static org.hisp.dhis.dataitem.query.shared.OrderingStatement.ordering; -import static org.hisp.dhis.dataitem.query.shared.ParamPresenceChecker.hasNonBlankStringPresence; -import static org.hisp.dhis.dataitem.query.shared.QueryParam.LOCALE; -import static org.hisp.dhis.dataitem.query.shared.StatementUtil.SPACED_FROM; -import static org.hisp.dhis.dataitem.query.shared.StatementUtil.SPACED_LEFT_PARENTHESIS; -import static org.hisp.dhis.dataitem.query.shared.StatementUtil.SPACED_RIGHT_PARENTHESIS; -import static org.hisp.dhis.dataitem.query.shared.StatementUtil.SPACED_SELECT; -import static org.hisp.dhis.dataitem.query.shared.StatementUtil.SPACED_WHERE; -import static org.hisp.dhis.dataitem.query.shared.UserAccessStatement.checkOwnerConditions; - -import java.util.List; -import java.util.stream.Collectors; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.tuple.Pair; -import org.hisp.dhis.common.BaseIdentifiableObject; -import org.hisp.dhis.dataitem.query.shared.OptionalFilterBuilder; -import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; -import org.springframework.stereotype.Component; - -/** - * This component is responsible for providing query capabilities on top of ExpressionDimensionItem - * objects. - */ -@Slf4j -@Component -public class OptionSetQuery implements DataItemQuery { - public static final String CAST_NULL_AS_TEXT = "cast (null as text)"; - - private static final String COMMON_COLUMNS = - List.of( - Pair.of("program_name", CAST_NULL_AS_TEXT), - Pair.of("program_uid", CAST_NULL_AS_TEXT), - Pair.of("program_shortname", CAST_NULL_AS_TEXT), - Pair.of("item_uid", "optionset.uid"), - Pair.of("item_name", "optionset.name"), - Pair.of("item_shortname", CAST_NULL_AS_TEXT), - Pair.of("item_valuetype", CAST_NULL_AS_TEXT), - Pair.of("item_code", "optionset.code"), - Pair.of("item_sharing", "optionset.sharing"), - Pair.of("item_domaintype", CAST_NULL_AS_TEXT), - Pair.of("item_type", "cast ('OPTION_SET' as text)"), - Pair.of("expression", CAST_NULL_AS_TEXT)) - .stream() - .map(pair -> pair.getRight() + " as " + pair.getLeft()) - .collect(Collectors.joining(", ")); - - /** - * Builds and returns the SQL statement required by the implementation. - * - * @param paramsMap - * @return the full SQL statement - */ - @Override - public String getStatement(MapSqlParameterSource paramsMap) { - StringBuilder sql = new StringBuilder(); - - sql.append(SPACED_LEFT_PARENTHESIS); - - // Creating a temp translated table to be queried. - sql.append(SPACED_SELECT + "*" + SPACED_FROM + SPACED_LEFT_PARENTHESIS); - - if (hasNonBlankStringPresence(paramsMap, LOCALE)) { - // Selecting translated names. - sql.append(selectRowsContainingTranslatedName()); - } else { - // Retrieving all rows ignoring translation as no locale is defined. - sql.append(selectAllRowsIgnoringAnyTranslation()); - } - - sql.append( - " group by item_name, item_uid, item_code, item_sharing, item_shortname," - + " i18n_first_name, i18n_first_shortname, i18n_second_name, i18n_second_shortname, expression"); - - // Closing the temp table. - sql.append(SPACED_RIGHT_PARENTHESIS + " t"); - - sql.append(SPACED_WHERE); - - // Applying filters, ordering and limits. - - // Mandatory filters. They do not respect the root junction filtering. - sql.append(always(checkOwnerConditions("t.item_sharing"))); - - // Optional filters, based on the current root junction. - OptionalFilterBuilder optionalFilters = new OptionalFilterBuilder(paramsMap); - optionalFilters.append(ifSet(displayNameFiltering("t.i18n_first_name", paramsMap))); - optionalFilters.append(ifSet(displayShortNameFiltering("t.i18n_first_shortname", paramsMap))); - optionalFilters.append(ifSet(nameFiltering("t.item_name", paramsMap))); - optionalFilters.append(ifSet(shortNameFiltering("t.item_shortname", paramsMap))); - optionalFilters.append(ifSet(uidFiltering("t.item_uid", paramsMap))); - sql.append(ifAny(optionalFilters.toString())); - - String identifiableStatement = - identifiableTokenFiltering( - "t.item_uid", "t.item_code", "t.i18n_first_name", null, paramsMap); - - if (isNotBlank(identifiableStatement)) { - sql.append(rootJunction(paramsMap)); - sql.append(identifiableStatement); - } - - sql.append( - ifSet( - ordering( - "t.i18n_first_name, t.i18n_second_name, t.item_uid", - "t.item_name, t.item_uid", - "t.i18n_first_shortname, t.i18n_second_shortname, t.item_uid", - "t.item_shortname, t.item_uid", - paramsMap))); - sql.append(ifSet(maxLimit(paramsMap))); - sql.append(SPACED_RIGHT_PARENTHESIS); - - String fullStatement = sql.toString(); - - log.trace("Full SQL: " + fullStatement); - - return fullStatement; - } - - /** - * Checks if the query rules match the required conditions so the query can be executed. This - * implementation must return always true. - * - * @param paramsMap - * @return true if matches, false otherwise - */ - @Override - public boolean matchQueryRules(MapSqlParameterSource paramsMap) { - return true; - } - - /** - * Simply returns the entity associated with the respective interface/query implementation. - * - * @return the entity associated to the interface implementation - */ - @Override - public Class getRootEntity() { - return QueryableDataItem.OPTION_SET.getEntity(); - } - - private String selectRowsContainingTranslatedName() { - StringBuilder sql = new StringBuilder(); - - sql.append(SPACED_SELECT) - .append(COMMON_COLUMNS) - .append(translationNamesColumnsFor("optionset", false, false)); - - sql.append(" from optionset ").append(translationNamesJoinsOn("optionset")); - - return sql.toString(); - } - - private String selectAllRowsIgnoringAnyTranslation() { - return new StringBuilder() - .append(SPACED_SELECT + COMMON_COLUMNS) - .append(", optionset.name as i18n_first_name, cast (null as text) as i18n_second_name") - .append( - ", " - + CAST_NULL_AS_TEXT - + " as i18n_first_shortname, cast (null as text) as i18n_second_shortname") - .append(" from optionset ") - .toString(); - } -} diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/ProgramAttributeQuery.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/ProgramAttributeQuery.java index bfd3f19cddbf..e27c918eec94 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/ProgramAttributeQuery.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/ProgramAttributeQuery.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis.dataitem.query; +import static java.util.stream.Collectors.joining; import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.always; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.displayNameFiltering; @@ -35,6 +36,7 @@ import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.ifAny; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.ifSet; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.nameFiltering; +import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.optionSetIdFiltering; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.programIdFiltering; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.rootJunction; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.shortNameFiltering; @@ -51,7 +53,9 @@ import static org.hisp.dhis.dataitem.query.shared.UserAccessStatement.READ_ACCESS; import static org.hisp.dhis.dataitem.query.shared.UserAccessStatement.sharingConditions; +import java.util.List; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.tuple.Pair; import org.hisp.dhis.common.BaseIdentifiableObject; import org.hisp.dhis.dataitem.query.shared.OptionalFilterBuilder; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; @@ -67,19 +71,31 @@ @Component public class ProgramAttributeQuery implements DataItemQuery { private static final String COMMON_COLUMNS = - "program.name as program_name, program.uid as program_uid," - + " program.shortname as program_shortname, trackedentityattribute.uid as item_uid," - + " trackedentityattribute.name as item_name, trackedentityattribute.shortname as item_shortname," - + " trackedentityattribute.valuetype as item_valuetype, trackedentityattribute.code as item_code," - + " trackedentityattribute.sharing as item_sharing, cast (null as text) as item_domaintype," - + " cast ('PROGRAM_ATTRIBUTE' as text) as item_type," - + " cast (null as text) as expression"; - - private static final String COMMON_UIDS = "program.uid, trackedentityattribute.uid"; + List.of( + Pair.of("program_name", "program.name"), + Pair.of("program_uid", "program.uid"), + Pair.of("program_shortname", "program.shortname"), + Pair.of("item_uid", "trackedentityattribute.uid"), + Pair.of("item_name", "trackedentityattribute.name"), + Pair.of("item_shortname", "trackedentityattribute.shortname"), + Pair.of("item_valuetype", "trackedentityattribute.valuetype"), + Pair.of("item_code", "trackedentityattribute.code"), + Pair.of("item_sharing", "trackedentityattribute.sharing"), + Pair.of("item_domaintype", CAST_NULL_AS_TEXT), + Pair.of("item_type", "cast ('PROGRAM_ATTRIBUTE' as text)"), + Pair.of("expression", CAST_NULL_AS_TEXT), + Pair.of("optionset_uid", "optionset.uid")) + .stream() + .map(pair -> pair.getRight() + " as " + pair.getLeft()) + .collect(joining(", ")); + + private static final String COMMON_UIDS = + "program.uid, trackedentityattribute.uid, optionset.uid"; private static final String JOINS = " join program_attributes on program_attributes.trackedentityattributeid = trackedentityattribute.trackedentityattributeid" - + " join program on program_attributes.programid = program.programid"; + + " join program on program_attributes.programid = program.programid" + + " left join optionset on trackedentityattribute.optionsetid = optionset.optionsetid"; private static final String SPACED_FROM_TRACKED_ENTITY_ATTRIBUTE = " from trackedentityattribute "; @@ -132,6 +148,7 @@ public String getStatement(MapSqlParameterSource paramsMap) { ifSet(shortNameFiltering("t.program_shortname", "t.item_shortname", paramsMap))); optionalFilters.append(ifSet(programIdFiltering("t.program_uid", paramsMap))); optionalFilters.append(ifSet(uidFiltering("t.item_uid", paramsMap))); + optionalFilters.append(ifSet(optionSetIdFiltering("t.optionset_uid", paramsMap))); sql.append(ifAny(optionalFilters.toString())); String identifiableStatement = diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/ProgramIndicatorQuery.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/ProgramIndicatorQuery.java index 6f1f85d83ac7..efeb9e404a11 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/ProgramIndicatorQuery.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/ProgramIndicatorQuery.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis.dataitem.query; +import static java.util.stream.Collectors.joining; import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.hisp.dhis.common.ValueType.NUMBER; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.always; @@ -52,7 +53,9 @@ import static org.hisp.dhis.dataitem.query.shared.UserAccessStatement.READ_ACCESS; import static org.hisp.dhis.dataitem.query.shared.UserAccessStatement.sharingConditions; +import java.util.List; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.tuple.Pair; import org.hisp.dhis.common.BaseIdentifiableObject; import org.hisp.dhis.dataitem.query.shared.OptionalFilterBuilder; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; @@ -68,11 +71,23 @@ @Component public class ProgramIndicatorQuery implements DataItemQuery { private static final String COMMON_COLUMNS = - "cast (null as text) as program_name, program.uid as program_uid," - + " cast (null as text) as program_shortname, programindicator.uid as item_uid, programindicator.name as item_name," - + " programindicator.shortname as item_shortname, cast (null as text) as item_valuetype, programindicator.code as item_code," - + " programindicator.sharing as item_sharing, cast (null as text) as item_domaintype, cast ('PROGRAM_INDICATOR' as text) as item_type," - + " cast (null as text) as expression"; + List.of( + Pair.of("program_name", CAST_NULL_AS_TEXT), + Pair.of("program_uid", "program.uid"), + Pair.of("program_shortname", CAST_NULL_AS_TEXT), + Pair.of("item_uid", "programindicator.uid"), + Pair.of("item_name", "programindicator.name"), + Pair.of("item_shortname", "programindicator.shortname"), + Pair.of("item_valuetype", CAST_NULL_AS_TEXT), + Pair.of("item_code", "programindicator.code"), + Pair.of("item_sharing", "programindicator.sharing"), + Pair.of("item_domaintype", CAST_NULL_AS_TEXT), + Pair.of("item_type", "cast ('PROGRAM_INDICATOR' as text)"), + Pair.of("expression", CAST_NULL_AS_TEXT), + Pair.of("optionset_uid", CAST_NULL_AS_TEXT)) + .stream() + .map(pair -> pair.getRight() + " as " + pair.getLeft()) + .collect(joining(", ")); private static final String COMMON_UIDS = "program.uid, programindicator.uid"; diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/ProgramStageDataElementQuery.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/ProgramStageDataElementQuery.java index 2c7d2754288e..33acb04c217c 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/ProgramStageDataElementQuery.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/ProgramStageDataElementQuery.java @@ -27,6 +27,7 @@ */ package org.hisp.dhis.dataitem.query; +import static java.util.stream.Collectors.joining; import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.always; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.displayNameFiltering; @@ -35,6 +36,7 @@ import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.ifAny; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.ifSet; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.nameFiltering; +import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.optionSetIdFiltering; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.programIdFiltering; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.rootJunction; import static org.hisp.dhis.dataitem.query.shared.FilteringStatement.shortNameFiltering; @@ -51,7 +53,9 @@ import static org.hisp.dhis.dataitem.query.shared.UserAccessStatement.READ_ACCESS; import static org.hisp.dhis.dataitem.query.shared.UserAccessStatement.sharingConditions; +import java.util.List; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.tuple.Pair; import org.hisp.dhis.common.BaseIdentifiableObject; import org.hisp.dhis.dataitem.query.shared.OptionalFilterBuilder; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; @@ -67,18 +71,31 @@ @Component public class ProgramStageDataElementQuery implements DataItemQuery { private static final String COMMON_COLUMNS = - "program.name as program_name, program.uid as program_uid," - + " program.shortname as program_shortname, dataelement.uid as item_uid, dataelement.name as item_name," - + " dataelement.shortname as item_shortname, dataelement.valuetype as item_valuetype, dataelement.code as item_code," - + " dataelement.sharing as item_sharing, cast (null as text) as item_domaintype, cast ('PROGRAM_DATA_ELEMENT' as text) as item_type," - + " cast (null as text) as expression"; - - private static final String COMMON_UIDS = "program.uid, dataelement.uid"; + List.of( + Pair.of("program_name", "program.name"), + Pair.of("program_uid", "program.uid"), + Pair.of("program_shortname", "program.shortname"), + Pair.of("item_uid", "dataelement.uid"), + Pair.of("item_name", "dataelement.name"), + Pair.of("item_shortname", "dataelement.shortname"), + Pair.of("item_valuetype", "dataelement.valuetype"), + Pair.of("item_code", "dataelement.code"), + Pair.of("item_sharing", "dataelement.sharing"), + Pair.of("item_domaintype", CAST_NULL_AS_TEXT), + Pair.of("item_type", "cast ('PROGRAM_DATA_ELEMENT' as text)"), + Pair.of("expression", CAST_NULL_AS_TEXT), + Pair.of("optionset_uid", "optionset.uid")) + .stream() + .map(pair -> pair.getRight() + " as " + pair.getLeft()) + .collect(joining(", ")); + + private static final String COMMON_UIDS = "program.uid, dataelement.uid, optionset.uid"; private static final String JOINS = "join programstagedataelement on programstagedataelement.dataelementid = dataelement.dataelementid" + " join programstage on programstagedataelement.programstageid = programstage.programstageid" - + " join program on program.programid = programstage.programid"; + + " join program on program.programid = programstage.programid" + + " left join optionset on dataelement.optionsetid = optionset.optionsetid"; private static final String SPACED_FROM_DATA_ELEMENT = " from dataelement "; @@ -130,6 +147,7 @@ public String getStatement(MapSqlParameterSource paramsMap) { ifSet(shortNameFiltering("t.program_shortname", "t.item_shortname", paramsMap))); optionalFilters.append(ifSet(programIdFiltering("t.program_uid", paramsMap))); optionalFilters.append(ifSet(uidFiltering("t.item_uid", paramsMap))); + optionalFilters.append(ifSet(optionSetIdFiltering("t.optionset_uid", paramsMap))); sql.append(ifAny(optionalFilters.toString())); String identifiableStatement = diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/QueryableDataItem.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/QueryableDataItem.java index d8ac362e61f3..cd8077fd1257 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/QueryableDataItem.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/QueryableDataItem.java @@ -36,7 +36,6 @@ import org.hisp.dhis.dataset.DataSet; import org.hisp.dhis.expressiondimensionitem.ExpressionDimensionItem; import org.hisp.dhis.indicator.Indicator; -import org.hisp.dhis.option.OptionSet; import org.hisp.dhis.program.ProgramDataElementDimensionItem; import org.hisp.dhis.program.ProgramIndicator; import org.hisp.dhis.program.ProgramTrackedEntityAttributeDimensionItem; @@ -53,8 +52,7 @@ public enum QueryableDataItem { PROGRAM_INDICATOR(ProgramIndicator.class), PROGRAM_DATA_ELEMENT(ProgramDataElementDimensionItem.class), PROGRAM_ATTRIBUTE(ProgramTrackedEntityAttributeDimensionItem.class), - EXPRESSION_DIMENSION_ITEM(ExpressionDimensionItem.class), - OPTION_SET(OptionSet.class); + EXPRESSION_DIMENSION_ITEM(ExpressionDimensionItem.class); private Class entity; diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/ResultProcessor.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/ResultProcessor.java index 5d7f13145314..b6bf3da1d14d 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/ResultProcessor.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/ResultProcessor.java @@ -52,8 +52,12 @@ class ResultProcessor { private static final String PROGRAM_UID = "program_uid"; + private static final String ITEM_CODE = "item_code"; + private static final String EXPRESSION = "expression"; + private static final String OPTION_SET_UID = "optionset_uid"; + private ResultProcessor() {} /** @@ -74,11 +78,12 @@ static List process(SqlRowSet rowSet) { .id(getUid(rowSet)) .shortName(getShortName(rowSet)) .displayShortName(getDisplayShortName(rowSet)) - .code(rowSet.getString("item_code")) + .code(rowSet.getString(ITEM_CODE)) .dimensionItemType(getItemType(rowSet)) .programId(rowSet.getString(PROGRAM_UID)) .valueType(getValueType(rowSet)) .expression(rowSet.getString(EXPRESSION)) + .optionSetId(rowSet.getString(OPTION_SET_UID)) .build()); } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/shared/FilteringStatement.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/shared/FilteringStatement.java index fefa11990c1e..938cfd62542f 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/shared/FilteringStatement.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/shared/FilteringStatement.java @@ -38,6 +38,7 @@ import static org.hisp.dhis.dataitem.query.shared.QueryParam.DISPLAY_SHORT_NAME; import static org.hisp.dhis.dataitem.query.shared.QueryParam.IDENTIFIABLE_TOKEN_COMPARISON; import static org.hisp.dhis.dataitem.query.shared.QueryParam.NAME; +import static org.hisp.dhis.dataitem.query.shared.QueryParam.OPTION_SET_ID; import static org.hisp.dhis.dataitem.query.shared.QueryParam.PROGRAM_ID; import static org.hisp.dhis.dataitem.query.shared.QueryParam.ROOT_JUNCTION; import static org.hisp.dhis.dataitem.query.shared.QueryParam.SHORT_NAME; @@ -95,6 +96,22 @@ public static String programIdFiltering(String column, MapSqlParameterSource par return EMPTY; } + /** + * Returns a SQL string related to optionSetId equality to be reused as part of data items + * optionSetId filtering. + * + * @param column the uid column + * @param paramsMap + * @return the uid SQL comparison + */ + public static String optionSetIdFiltering(String column, MapSqlParameterSource paramsMap) { + if (hasNonBlankStringPresence(paramsMap, OPTION_SET_ID)) { + return equalsFiltering(column, OPTION_SET_ID); + } + + return EMPTY; + } + /** * Returns a SQL string related to 'name' "ilike" comparison to be reused as part of data items * 'name' filtering. diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/shared/QueryParam.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/shared/QueryParam.java index 4ec6fc3a09a0..43d68a8512db 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/shared/QueryParam.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataitem/query/shared/QueryParam.java @@ -56,6 +56,8 @@ public class QueryParam { public static final String PROGRAM_ID = "programId"; + public static final String OPTION_SET_ID = "optionSetId"; + public static final String MAX_LIMIT = "maxLimit"; public static final String NAME_ORDER = "nameOrder"; diff --git a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/dataitems/DataItemsAnalyticsTest.java b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/dataitems/DataItemsAnalyticsTest.java index be1bbcfba11c..471abfffcbb2 100644 --- a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/dataitems/DataItemsAnalyticsTest.java +++ b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/dataitems/DataItemsAnalyticsTest.java @@ -29,6 +29,7 @@ import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; import org.hisp.dhis.helpers.extensions.ConfigurationExtension; import org.hisp.dhis.test.e2e.actions.LoginActions; @@ -59,7 +60,7 @@ public static void beforeAll() { } @Test - void testDataItemsContainsOptionSets() { + void testDataItemsDoesNotContainOptionSets() { // Given QueryParamsBuilder params = new QueryParamsBuilder().add("paging=false").add("order=name:asc"); @@ -72,6 +73,60 @@ void testDataItemsContainsOptionSets() { .statusCode(equalTo(200)) .body("dataItems.dimensionItemType", hasItem("PROGRAM_INDICATOR")) .body("dataItems.dimensionItemType", hasItem("DATA_ELEMENT")) - .body("dataItems.dimensionItemType", hasItem("OPTION_SET")); + .body("dataItems.dimensionItemType", not(hasItem("OPTION_SET"))); + } + + @Test + void testDataItemsContainsOptionSetId() { + // Given + QueryParamsBuilder params = new QueryParamsBuilder().add("paging=false").add("order=name:asc"); + + // When + ApiResponse response = dataItemsActions.get(params); + + // Then + response + .validate() + .statusCode(equalTo(200)) + .body("dataItems.optionSetId", hasItem("SokRAajDrRz")); + } + + @Test + void testDataItemsFiltersNonExistingOptionSetId() { + // Given + QueryParamsBuilder params = + new QueryParamsBuilder() + .add("paging=false") + .add("order=name:asc") + .add("filter=optionSetId:eq:ABCD"); + + // When + ApiResponse response = dataItemsActions.get(params); + + // Then + response + .validate() + .statusCode(equalTo(200)) + .body("dataItems.optionSetId", not(hasItem("SokRAajDrRz"))) + .body("dataItems.optionSetId", not(hasItem("ABCD"))); + } + + @Test + void testDataItemsFiltersExistingOptionSetId() { + // Given + QueryParamsBuilder params = + new QueryParamsBuilder() + .add("paging=false") + .add("order=name:asc") + .add("filter=optionSetId:eq:SokRAajDrRz"); + + // When + ApiResponse response = dataItemsActions.get(params); + + // Then + response + .validate() + .statusCode(equalTo(200)) + .body("dataItems.optionSetId", hasItem("SokRAajDrRz")); } } diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataitem/Filter.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataitem/Filter.java index 69de7cb52f3a..0866a625aa99 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataitem/Filter.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataitem/Filter.java @@ -41,6 +41,7 @@ public enum Attribute { SHORT_NAME("shortName"), DISPLAY_SHORT_NAME("displayShortName"), PROGRAM_ID("programId"), + OPTION_SET_ID("optionSetId"), ID("id"); private String name; @@ -107,6 +108,7 @@ public enum Combination { SHORT_NAME_ILIKE("shortName:ilike:"), DISPLAY_SHORT_NAME_ILIKE("displayShortName:ilike:"), PROGRAM_ID_EQUAL("programId:eq:"), + OPTION_SET_ID_EQUAL("optionSetId:eq:"), ID_EQUAL("id:eq:"), IDENTIFIABLE_TOKEN("identifiable:token:"); diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataitem/helper/FilteringHelper.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataitem/helper/FilteringHelper.java index 65b2d9f6b387..193dcb5b6d59 100644 --- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataitem/helper/FilteringHelper.java +++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataitem/helper/FilteringHelper.java @@ -49,6 +49,7 @@ import static org.hisp.dhis.dataitem.query.shared.QueryParam.IDENTIFIABLE_TOKEN_COMPARISON; import static org.hisp.dhis.dataitem.query.shared.QueryParam.LOCALE; import static org.hisp.dhis.dataitem.query.shared.QueryParam.NAME; +import static org.hisp.dhis.dataitem.query.shared.QueryParam.OPTION_SET_ID; import static org.hisp.dhis.dataitem.query.shared.QueryParam.PROGRAM_ID; import static org.hisp.dhis.dataitem.query.shared.QueryParam.ROOT_JUNCTION; import static org.hisp.dhis.dataitem.query.shared.QueryParam.SHORT_NAME; @@ -66,6 +67,7 @@ import static org.hisp.dhis.webapi.controller.dataitem.Filter.Combination.IDENTIFIABLE_TOKEN; import static org.hisp.dhis.webapi.controller.dataitem.Filter.Combination.ID_EQUAL; import static org.hisp.dhis.webapi.controller.dataitem.Filter.Combination.NAME_ILIKE; +import static org.hisp.dhis.webapi.controller.dataitem.Filter.Combination.OPTION_SET_ID_EQUAL; import static org.hisp.dhis.webapi.controller.dataitem.Filter.Combination.PROGRAM_ID_EQUAL; import static org.hisp.dhis.webapi.controller.dataitem.Filter.Combination.SHORT_NAME_ILIKE; import static org.hisp.dhis.webapi.controller.dataitem.Filter.Combination.VALUE_TYPE_EQUAL; @@ -298,6 +300,10 @@ public static void setFilteringParams( String programId = extractValueFromFilter(filters, PROGRAM_ID_EQUAL, true); addIfNotBlank(paramsMap, PROGRAM_ID, programId); + // Add optionSet id filtering id, if present. + String optionSetId = extractValueFromFilter(filters, OPTION_SET_ID_EQUAL, true); + addIfNotBlank(paramsMap, OPTION_SET_ID, optionSetId); + // Add user group filtering, when present. if (currentUser != null && CollectionUtils.isNotEmpty(currentUser.getGroups())) { Set userGroupUids = From a7dc1d03de69f167a51b93dcdf7fc062312a2876 Mon Sep 17 00:00:00 2001 From: Maikel Arabori <51713408+maikelarabori@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:14:46 +0100 Subject: [PATCH 13/13] test: New ev/en analytics query tests [DHIS2-18695] (#19560) --- .../query/EnrollmentsQuery5AutoTest.java | 155 +++++++++++++ .../event/query/EventsQuery4AutoTest.java | 204 ++++++++++++++++++ .../generator/scenarios/enroll-query.json | 18 +- .../generator/scenarios/event-query.json | 23 +- 4 files changed, 397 insertions(+), 3 deletions(-) diff --git a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/enrollment/query/EnrollmentsQuery5AutoTest.java b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/enrollment/query/EnrollmentsQuery5AutoTest.java index 38109115c723..d48118d3f4ed 100644 --- a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/enrollment/query/EnrollmentsQuery5AutoTest.java +++ b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/enrollment/query/EnrollmentsQuery5AutoTest.java @@ -30,8 +30,10 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.hisp.dhis.analytics.ValidationHelper.validateHeader; +import static org.hisp.dhis.analytics.ValidationHelper.validateRow; import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; +import java.util.List; import java.util.Map; import org.hisp.dhis.AnalyticsApiTest; import org.hisp.dhis.test.e2e.actions.analytics.AnalyticsEnrollmentsActions; @@ -104,4 +106,157 @@ public void financialYear2022Sep() throws JSONException { // Assert rows. } + + @Test + public void programIndicatorWeightGainBetweenBirthAndLastPostnatal() throws JSONException { + // Given + QueryParamsBuilder params = + new QueryParamsBuilder() + .add("includeMetadataDetails=true") + .add("headers=ouname,lastupdated,qhTkqwAJLMv") + .add("lastUpdated=LAST_6_MONTHS") + .add("displayProperty=NAME") + .add("totalPages=false") + .add("rowContext=true") + .add("pageSize=10") + .add("outputType=ENROLLMENT") + .add("page=1") + .add("dimension=ou:USER_ORGUNIT,qhTkqwAJLMv:EQ:NV") + .add("desc=lastupdated") + .add("relativePeriodDate=2019-02-01"); + + // When + ApiResponse response = actions.query().get("IpHINAT79UW", JSON, JSON, params); + + // Then + response + .validate() + .statusCode(200) + .body("headers", hasSize(equalTo(3))) + .body("rows", hasSize(equalTo(10))) + .body("height", equalTo(10)) + .body("width", equalTo(3)) + .body("headerWidth", equalTo(3)); + + // Assert metaData. + String expectedMetaData = + "{\"pager\":{\"page\":1,\"pageSize\":10,\"isLastPage\":false},\"items\":{\"ImspTQPwCqd\":{\"uid\":\"ImspTQPwCqd\",\"code\":\"OU_525\",\"name\":\"Sierra Leone\",\"dimensionItemType\":\"ORGANISATION_UNIT\",\"valueType\":\"TEXT\",\"totalAggregationType\":\"SUM\"},\"LAST_6_MONTHS\":{\"name\":\"Last 6 months\"},\"IpHINAT79UW\":{\"uid\":\"IpHINAT79UW\",\"name\":\"Child Programme\"},\"ZzYYXq4fJie\":{\"uid\":\"ZzYYXq4fJie\",\"name\":\"Baby Postnatal\",\"description\":\"Baby Postnatal\"},\"USER_ORGUNIT\":{\"organisationUnits\":[\"ImspTQPwCqd\"]},\"ou\":{\"uid\":\"ou\",\"name\":\"Organisation unit\",\"dimensionType\":\"ORGANISATION_UNIT\"},\"A03MvHHogjR\":{\"uid\":\"A03MvHHogjR\",\"name\":\"Birth\",\"description\":\"Birth of the baby\"},\"qhTkqwAJLMv\":{\"uid\":\"qhTkqwAJLMv\",\"name\":\"Weight gain(in g) between birth and last postnatal\",\"description\":\"The average number of grams the baby has gained through the postnatal period. Only counted among the babies that has completed the postnatal period.\",\"dimensionItemType\":\"PROGRAM_INDICATOR\",\"valueType\":\"NUMBER\",\"aggregationType\":\"AVERAGE\",\"totalAggregationType\":\"SUM\"}},\"dimensions\":{\"pe\":[],\"ou\":[\"ImspTQPwCqd\"],\"qhTkqwAJLMv\":[]}}"; + String actualMetaData = new JSONObject((Map) response.extract("metaData")).toString(); + assertEquals(expectedMetaData, actualMetaData, false); + + // Assert headers. + validateHeader( + response, 0, "ouname", "Organisation unit name", "TEXT", "java.lang.String", false, true); + validateHeader( + response, + 1, + "lastupdated", + "Last updated on", + "DATETIME", + "java.time.LocalDateTime", + false, + true); + validateHeader( + response, + 2, + "qhTkqwAJLMv", + "Weight gain(in g) between birth and last postnatal", + "NUMBER", + "java.lang.Double", + false, + true); + + // Assert rows. + validateRow(response, 0, List.of("Elshadai Clinic", "2018-10-11 17:04:24.953", "")); + validateRow(response, 1, List.of("Tonko Maternity Clinic", "2018-08-07 15:47:29.301", "")); + validateRow(response, 2, List.of("Banka Makuloh MCHP", "2018-08-07 15:47:29.3", "")); + validateRow(response, 3, List.of("Yonibana MCHP", "2018-08-07 15:47:29.299", "")); + validateRow(response, 4, List.of("Kindoyal Hospital", "2018-08-07 15:47:29.298", "")); + validateRow(response, 5, List.of("Baoma-Peje CHP", "2018-08-07 15:47:29.297", "")); + validateRow(response, 6, List.of("Bontiwo MCHP", "2018-08-07 15:47:29.296", "")); + validateRow(response, 7, List.of("Konia MCHP", "2018-08-07 15:47:29.295", "")); + validateRow(response, 8, List.of("Kania MCHP", "2018-08-07 15:47:29.294", "")); + validateRow(response, 9, List.of("Taninahun (Malen) CHP", "2018-08-07 15:47:29.293", "")); + } + + @Test + public void programIndicatorWomenWithLowHbVisitWithinOneYear() throws JSONException { + // Given + QueryParamsBuilder params = + new QueryParamsBuilder() + .add("includeMetadataDetails=true") + .add("headers=ouname,lastupdated,EDp0b79Rbx9,programstatus,enrollmentdate") + .add("lastUpdated=LAST_12_MONTHS") + .add("displayProperty=NAME") + .add("totalPages=false") + .add("rowContext=true") + .add("pageSize=100") + .add("outputType=ENROLLMENT") + .add("page=1") + .add("dimension=EDp0b79Rbx9,ou:USER_ORGUNIT") + .add("relativePeriodDate=2019-02-01") + .add("desc=lastupdated"); + + // When + ApiResponse response = actions.query().get("WSGAb5XwJ3Y", JSON, JSON, params); + + // Then + response + .validate() + .statusCode(200) + .body("headers", hasSize(equalTo(5))) + .body("rows", hasSize(equalTo(2))) + .body("height", equalTo(2)) + .body("width", equalTo(5)) + .body("headerWidth", equalTo(5)); + + // Assert metaData. + String expectedMetaData = + "{\"pager\":{\"page\":1,\"pageSize\":100,\"isLastPage\":true},\"items\":{\"EDp0b79Rbx9\":{\"uid\":\"EDp0b79Rbx9\",\"name\":\"Number of women with at least one low hemoglobin visit within one year of the reporting period end\",\"description\":\"Counts the number of women enrolled in the reporting period, that has had at least one low-hemoglobin visit within the end of the reporting period.\",\"dimensionItemType\":\"PROGRAM_INDICATOR\",\"valueType\":\"NUMBER\",\"aggregationType\":\"COUNT\",\"totalAggregationType\":\"SUM\"},\"ImspTQPwCqd\":{\"uid\":\"ImspTQPwCqd\",\"code\":\"OU_525\",\"name\":\"Sierra Leone\",\"dimensionItemType\":\"ORGANISATION_UNIT\",\"valueType\":\"TEXT\",\"totalAggregationType\":\"SUM\"},\"PFDfvmGpsR3\":{\"uid\":\"PFDfvmGpsR3\",\"name\":\"Care at birth\",\"description\":\"Intrapartum care / Childbirth / Labour and delivery\"},\"bbKtnxRZKEP\":{\"uid\":\"bbKtnxRZKEP\",\"name\":\"Postpartum care visit\",\"description\":\"Provision of care for the mother for some weeks after delivery\"},\"USER_ORGUNIT\":{\"organisationUnits\":[\"ImspTQPwCqd\"]},\"ou\":{\"uid\":\"ou\",\"name\":\"Organisation unit\",\"dimensionType\":\"ORGANISATION_UNIT\"},\"PUZaKR0Jh2k\":{\"uid\":\"PUZaKR0Jh2k\",\"name\":\"Previous deliveries\",\"description\":\"Table for recording earlier deliveries\"},\"edqlbukwRfQ\":{\"uid\":\"edqlbukwRfQ\",\"name\":\"Second antenatal care visit\",\"description\":\"Antenatal care visit\"},\"WZbXY0S00lP\":{\"uid\":\"WZbXY0S00lP\",\"name\":\"First antenatal care visit\",\"description\":\"First antenatal care visit\"},\"WSGAb5XwJ3Y\":{\"uid\":\"WSGAb5XwJ3Y\",\"name\":\"WHO RMNCH Tracker\"},\"LAST_12_MONTHS\":{\"name\":\"Last 12 months\"}},\"dimensions\":{\"EDp0b79Rbx9\":[],\"pe\":[],\"ou\":[\"ImspTQPwCqd\"]}}"; + String actualMetaData = new JSONObject((Map) response.extract("metaData")).toString(); + assertEquals(expectedMetaData, actualMetaData, false); + + // Assert headers. + validateHeader( + response, 0, "ouname", "Organisation unit name", "TEXT", "java.lang.String", false, true); + validateHeader( + response, + 1, + "lastupdated", + "Last updated on", + "DATETIME", + "java.time.LocalDateTime", + false, + true); + validateHeader( + response, + 2, + "EDp0b79Rbx9", + "Number of women with at least one low hemoglobin visit within one year of the reporting period end", + "NUMBER", + "java.lang.Double", + false, + true); + validateHeader( + response, 3, "programstatus", "Program status", "TEXT", "java.lang.String", false, true); + validateHeader( + response, + 4, + "enrollmentdate", + "Date of first visit", + "DATETIME", + "java.time.LocalDateTime", + false, + true); + + // Assert rows. + validateRow( + response, + 0, + List.of("Ngelehun CHC", "2018-05-22 22:34:09.668", "0", "ACTIVE", "2023-10-14 02:00:00.0")); + validateRow( + response, + 1, + List.of("Ngelehun CHC", "2018-02-20 18:47:35.335", "0", "ACTIVE", "2023-10-15 02:00:00.0")); + } } diff --git a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/event/query/EventsQuery4AutoTest.java b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/event/query/EventsQuery4AutoTest.java index a0fb87dd9645..ffe664c11699 100644 --- a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/event/query/EventsQuery4AutoTest.java +++ b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/event/query/EventsQuery4AutoTest.java @@ -30,8 +30,10 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.hisp.dhis.analytics.ValidationHelper.validateHeader; +import static org.hisp.dhis.analytics.ValidationHelper.validateRow; import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; +import java.util.List; import java.util.Map; import org.hisp.dhis.AnalyticsApiTest; import org.hisp.dhis.test.e2e.actions.analytics.AnalyticsEventActions; @@ -104,4 +106,206 @@ public void financialYear2023Sep() throws JSONException { // Assert rows. } + + @Test + public void programIndicatorMalariaCaseMicroscopyPv514YearsFemale() throws JSONException { + // Given + QueryParamsBuilder params = + new QueryParamsBuilder() + .add("includeMetadataDetails=true") + .add("headers=ouname,TEmaOXaSPe7") + .add("stage=hYyB7FUS5eR") + .add("displayProperty=NAME") + .add("totalPages=false") + .add("enrollmentDate=MONTHS_THIS_YEAR") + .add("pageSize=10") + .add("outputType=EVENT") + .add("page=1") + .add("dimension=TEmaOXaSPe7,ou:USER_ORGUNIT") + .add("relativePeriodDate=2022-07-01") + .add("desc=lastupdated"); + + // When + ApiResponse response = actions.query().get("qDkgAbB5Jlk", JSON, JSON, params); + + // Then + response + .validate() + .statusCode(200) + .body("headers", hasSize(equalTo(2))) + .body("rows", hasSize(equalTo(10))) + .body("height", equalTo(10)) + .body("width", equalTo(2)) + .body("headerWidth", equalTo(2)); + + // Assert metaData. + String expectedMetaData = + "{\"pager\":{\"page\":1,\"pageSize\":10,\"isLastPage\":false},\"items\":{\"ImspTQPwCqd\":{\"uid\":\"ImspTQPwCqd\",\"code\":\"OU_525\",\"name\":\"Sierra Leone\",\"dimensionItemType\":\"ORGANISATION_UNIT\",\"valueType\":\"TEXT\",\"totalAggregationType\":\"SUM\"},\"TEmaOXaSPe7\":{\"uid\":\"TEmaOXaSPe7\",\"name\":\"Malaria case - microscopy Pv. 5-14 years female\",\"dimensionItemType\":\"PROGRAM_INDICATOR\",\"valueType\":\"NUMBER\",\"aggregationType\":\"COUNT\",\"totalAggregationType\":\"SUM\"},\"qDkgAbB5Jlk\":{\"uid\":\"qDkgAbB5Jlk\",\"name\":\"Malaria case diagnosis, treatment and investigation\",\"description\":\"All cases in an elimination setting should be registered in this program. Includes relevant case identifiers/details including the ID, Name, Index, Age, Gender, Location,etc..\"},\"USER_ORGUNIT\":{\"organisationUnits\":[\"ImspTQPwCqd\"]},\"ou\":{\"uid\":\"ou\",\"name\":\"Organisation unit\",\"dimensionType\":\"ORGANISATION_UNIT\"},\"MONTHS_THIS_YEAR\":{\"name\":\"Months this year\"},\"hYyB7FUS5eR\":{\"uid\":\"hYyB7FUS5eR\",\"name\":\"Diagnosis & treatment\",\"description\":\"This stage is used to identify initial diagnosis and treatment. This includes the method of case detection, information about the case include travel history, method of diagnosis, malaria species type and treatment details. \"}},\"dimensions\":{\"TEmaOXaSPe7\":[],\"pe\":[],\"ou\":[\"ImspTQPwCqd\"]}}"; + String actualMetaData = new JSONObject((Map) response.extract("metaData")).toString(); + assertEquals(expectedMetaData, actualMetaData, false); + + // Assert headers. + validateHeader( + response, 0, "ouname", "Organisation unit name", "TEXT", "java.lang.String", false, true); + validateHeader( + response, + 1, + "TEmaOXaSPe7", + "Malaria case - microscopy Pv. 5-14 years female", + "NUMBER", + "java.lang.Double", + false, + true); + + // Assert rows. + validateRow(response, 0, List.of("Ngelehun CHC", "1")); + validateRow(response, 1, List.of("Ngelehun CHC", "0")); + validateRow(response, 2, List.of("Ngelehun CHC", "0")); + validateRow(response, 3, List.of("Ngelehun CHC", "1")); + validateRow(response, 4, List.of("Ngelehun CHC", "0")); + validateRow(response, 5, List.of("Ngelehun CHC", "0")); + validateRow(response, 6, List.of("Ngelehun CHC", "0")); + validateRow(response, 7, List.of("Ngelehun CHC", "0")); + validateRow(response, 8, List.of("Ngelehun CHC", "0")); + validateRow(response, 9, List.of("Ngelehun CHC", "0")); + } + + @Test + public void programIndicatorNumberOfOrgUnitsWithOngoingArv() throws JSONException { + // Given + QueryParamsBuilder params = + new QueryParamsBuilder() + .add("includeMetadataDetails=true") + .add("headers=ouname,rxNjqzJ7dkK,lastupdated") + .add("lastUpdated=LAST_YEAR") + .add("stage=PUZaKR0Jh2k") + .add("displayProperty=NAME") + .add("totalPages=false") + .add("outputType=EVENT") + .add("pageSize=10") + .add("page=1") + .add("dimension=ou:USER_ORGUNIT,rxNjqzJ7dkK:GE:0") + .add("relativePeriodDate=2019-07-01") + .add("desc=lastupdated"); + + // When + ApiResponse response = actions.query().get("WSGAb5XwJ3Y", JSON, JSON, params); + + // Then + response + .validate() + .statusCode(200) + .body("headers", hasSize(equalTo(3))) + .body("rows", hasSize(equalTo(10))) + .body("height", equalTo(10)) + .body("width", equalTo(3)) + .body("headerWidth", equalTo(3)); + + // Assert metaData. + String expectedMetaData = + "{\"pager\":{\"page\":1,\"pageSize\":10,\"isLastPage\":false},\"items\":{\"ImspTQPwCqd\":{\"uid\":\"ImspTQPwCqd\",\"code\":\"OU_525\",\"name\":\"Sierra Leone\",\"dimensionItemType\":\"ORGANISATION_UNIT\",\"valueType\":\"TEXT\",\"totalAggregationType\":\"SUM\"},\"USER_ORGUNIT\":{\"organisationUnits\":[\"ImspTQPwCqd\"]},\"ou\":{\"uid\":\"ou\",\"name\":\"Organisation unit\",\"dimensionType\":\"ORGANISATION_UNIT\"},\"PUZaKR0Jh2k\":{\"uid\":\"PUZaKR0Jh2k\",\"name\":\"Previous deliveries\",\"description\":\"Table for recording earlier deliveries\"},\"WSGAb5XwJ3Y\":{\"uid\":\"WSGAb5XwJ3Y\",\"name\":\"WHO RMNCH Tracker\"},\"rxNjqzJ7dkK\":{\"uid\":\"rxNjqzJ7dkK\",\"name\":\"Number of organisation units with ongoing arv treatment\",\"dimensionItemType\":\"PROGRAM_INDICATOR\",\"valueType\":\"NUMBER\",\"aggregationType\":\"COUNT\",\"totalAggregationType\":\"SUM\"},\"LAST_YEAR\":{\"name\":\"Last year\"}},\"dimensions\":{\"pe\":[],\"ou\":[\"ImspTQPwCqd\"],\"rxNjqzJ7dkK\":[]}}"; + String actualMetaData = new JSONObject((Map) response.extract("metaData")).toString(); + assertEquals(expectedMetaData, actualMetaData, false); + + // Assert headers. + validateHeader( + response, 0, "ouname", "Organisation unit name", "TEXT", "java.lang.String", false, true); + validateHeader( + response, + 1, + "rxNjqzJ7dkK", + "Number of organisation units with ongoing arv treatment", + "NUMBER", + "java.lang.Double", + false, + true); + validateHeader( + response, + 2, + "lastupdated", + "Last updated on", + "DATETIME", + "java.time.LocalDateTime", + false, + true); + + // Assert rows. + validateRow(response, 0, List.of("Ngelehun CHC", "0", "2018-10-14 14:14:36.922")); + validateRow(response, 1, List.of("Kondewakoro CHP", "0", "2018-01-26 13:48:13.368")); + validateRow(response, 2, List.of("Mathen MCHP", "0", "2018-01-26 13:48:13.366")); + validateRow(response, 3, List.of("Mbokie CHP", "0", "2018-01-26 13:48:13.363")); + validateRow(response, 4, List.of("Grey Bush CHC", "0", "2018-01-26 13:48:13.359")); + validateRow(response, 5, List.of("Bangoma MCHP", "0", "2018-01-26 13:48:13.353")); + validateRow(response, 6, List.of("Blessed Mokaka East Clinic", "0", "2018-01-26 13:48:13.351")); + validateRow(response, 7, List.of("MCH Static/U5", "0", "2018-01-26 13:48:13.349")); + validateRow(response, 8, List.of("Konjo MCHP", "0", "2018-01-26 13:48:13.345")); + validateRow(response, 9, List.of("Lakka/Ogoo Farm CHC", "0", "2018-01-26 13:48:13.341")); + } + + @Test + public void programIndicatorBMIFemaleUnder5y() throws JSONException { + // Given + QueryParamsBuilder params = + new QueryParamsBuilder() + .add("includeMetadataDetails=true") + .add("headers=ouname,lastupdated,hlPt8H4bUOQ") + .add("lastUpdated=LAST_12_MONTHS") + .add("stage=Zj7UnCAulEk") + .add("displayProperty=NAME") + .add("totalPages=false") + .add("pageSize=5") + .add("outputType=EVENT") + .add("page=1") + .add("dimension=hlPt8H4bUOQ,ou:USER_ORGUNIT") + .add("relativePeriodDate=2019-02-01") + .add("desc=lastupdated"); + + // When + ApiResponse response = actions.query().get("eBAyeGv0exc", JSON, JSON, params); + + // Then + response + .validate() + .statusCode(200) + .body("headers", hasSize(equalTo(3))) + .body("rows", hasSize(equalTo(5))) + .body("height", equalTo(5)) + .body("width", equalTo(3)) + .body("headerWidth", equalTo(3)); + + // Assert metaData. + String expectedMetaData = + "{\"pager\":{\"page\":1,\"pageSize\":5,\"isLastPage\":false},\"items\":{\"ImspTQPwCqd\":{\"uid\":\"ImspTQPwCqd\",\"code\":\"OU_525\",\"name\":\"Sierra Leone\",\"dimensionItemType\":\"ORGANISATION_UNIT\",\"valueType\":\"TEXT\",\"totalAggregationType\":\"SUM\"},\"hlPt8H4bUOQ\":{\"uid\":\"hlPt8H4bUOQ\",\"name\":\"BMI female under 5 y\",\"dimensionItemType\":\"PROGRAM_INDICATOR\",\"valueType\":\"NUMBER\",\"aggregationType\":\"AVERAGE\",\"totalAggregationType\":\"SUM\"},\"eBAyeGv0exc\":{\"uid\":\"eBAyeGv0exc\",\"name\":\"Inpatient morbidity and mortality\"},\"USER_ORGUNIT\":{\"organisationUnits\":[\"ImspTQPwCqd\"]},\"ou\":{\"uid\":\"ou\",\"name\":\"Organisation unit\",\"dimensionType\":\"ORGANISATION_UNIT\"},\"Zj7UnCAulEk\":{\"uid\":\"Zj7UnCAulEk\",\"name\":\"Inpatient morbidity and mortality\",\"description\":\"Anonymous and ICD-10 coded inpatient data\"},\"LAST_12_MONTHS\":{\"name\":\"Last 12 months\"}},\"dimensions\":{\"hlPt8H4bUOQ\":[],\"pe\":[],\"ou\":[\"ImspTQPwCqd\"]}}"; + String actualMetaData = new JSONObject((Map) response.extract("metaData")).toString(); + assertEquals(expectedMetaData, actualMetaData, false); + + // Assert headers. + validateHeader( + response, 0, "ouname", "Organisation unit name", "TEXT", "java.lang.String", false, true); + validateHeader( + response, + 1, + "lastupdated", + "Last updated on", + "DATETIME", + "java.time.LocalDateTime", + false, + true); + validateHeader( + response, + 2, + "hlPt8H4bUOQ", + "BMI female under 5 y", + "NUMBER", + "java.lang.Double", + false, + true); + + // Assert rows. + validateRow(response, 0, List.of("Ngelehun CHC", "2018-08-04 15:19:36.473", "")); + validateRow(response, 1, List.of("Ngelehun CHC", "2018-08-04 15:18:06.085", "")); + validateRow(response, 2, List.of("Ngelehun CHC", "2018-08-04 15:17:39.87", "")); + validateRow(response, 3, List.of("Ngelehun CHC", "2018-08-04 15:17:10.722", "")); + validateRow(response, 4, List.of("Ngelehun CHC", "2018-08-04 15:15:50.672", "")); + } } diff --git a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/generator/scenarios/enroll-query.json b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/generator/scenarios/enroll-query.json index 0523d2077d1f..9f6af3e8d67e 100644 --- a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/generator/scenarios/enroll-query.json +++ b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/generator/scenarios/enroll-query.json @@ -2,17 +2,31 @@ "scenarios": [ { "name": "someDimensionsWithFilter", - "query": "/api/analytics/enrollments/query/ur1Edk5Oe2n?dimension=ou:O6uvpzGd5pu,J5jldMd8OHv,w75KJ2mc4zz,cejWyOfXge6:IN:Female,GUOBQt5K2WI:LIKE:Cape,ZkbAXlQUYJG.U5ubm6PPYrM,lZGmxYbs97q,OvY4VVhSDeJ:LE:45:NE:NV:!EQ:44&headers=ouname,J5jldMd8OHv,w75KJ2mc4zz,cejWyOfXge6,GUOBQt5K2WI,ZkbAXlQUYJG.U5ubm6PPYrM,lZGmxYbs97q,OvY4VVhSDeJ,lastupdated,createdbydisplayname,lastupdatedbydisplayname,enrollmentdate,programstatus&totalPages=false&rowContext=true&lastUpdated=LAST_10_YEARS&programStatus=ACTIVE&displayProperty=NAME&pageSize=100&page=1&includeMetadataDetails=true&outputType=ENROLLMENT&asc=OvY4VVhSDeJ&relativePeriodDate=2022-07-01", + "query": "/api/analytics/enrollments/query/ur1Edk5Oe2n.json?dimension=ou:O6uvpzGd5pu,J5jldMd8OHv,w75KJ2mc4zz,cejWyOfXge6:IN:Female,GUOBQt5K2WI:LIKE:Cape,ZkbAXlQUYJG.U5ubm6PPYrM,lZGmxYbs97q,OvY4VVhSDeJ:LE:45:NE:NV:!EQ:44&headers=ouname,J5jldMd8OHv,w75KJ2mc4zz,cejWyOfXge6,GUOBQt5K2WI,ZkbAXlQUYJG.U5ubm6PPYrM,lZGmxYbs97q,OvY4VVhSDeJ,lastupdated,createdbydisplayname,lastupdatedbydisplayname,enrollmentdate,programstatus&totalPages=false&rowContext=true&lastUpdated=LAST_10_YEARS&programStatus=ACTIVE&displayProperty=NAME&pageSize=100&page=1&includeMetadataDetails=true&outputType=ENROLLMENT&asc=OvY4VVhSDeJ&relativePeriodDate=2022-07-01", "version": { "min": 39 } }, { "name": "financialYear2022Sep", - "query": "/api/42/analytics/enrollments/query/IpHINAT79UW?dimension=ou:USER_ORGUNIT,GxdhnY5wmHq&headers=ouname,GxdhnY5wmHq,lastupdated&totalPages=false&rowContext=true&lastUpdated=2022Sep&displayProperty=NAME&pageSize=100&page=1&includeMetadataDetails=true&outputType=ENROLLMENT", + "query": "/api/42/analytics/enrollments/query/IpHINAT79UW.json?dimension=ou:USER_ORGUNIT,GxdhnY5wmHq&headers=ouname,GxdhnY5wmHq,lastupdated&totalPages=false&rowContext=true&lastUpdated=2022Sep&displayProperty=NAME&pageSize=100&page=1&includeMetadataDetails=true&outputType=ENROLLMENT", "version": { "min": 42 } + }, + { + "name": "programIndicatorWeightGainBetweenBirthAndLastPostnatal", + "query": "/api/42/analytics/enrollments/query/IpHINAT79UW.json?dimension=ou:USER_ORGUNIT,qhTkqwAJLMv:EQ:NV&headers=ouname,lastupdated,qhTkqwAJLMv&totalPages=false&rowContext=true&lastUpdated=LAST_6_MONTHS&displayProperty=NAME&pageSize=10&page=1&includeMetadataDetails=true&outputType=ENROLLMENT&desc=lastupdated&relativePeriodDate=2019-02-01", + "version": { + "min": 41 + } + }, + { + "name": "programIndicatorWomenWithLowHbVisitWithinOneYear", + "query": "/api/42/analytics/enrollments/query/WSGAb5XwJ3Y.json?dimension=EDp0b79Rbx9,ou:USER_ORGUNIT&headers=ouname,lastupdated,EDp0b79Rbx9,programstatus,enrollmentdate&totalPages=false&rowContext=true&lastUpdated=LAST_12_MONTHS&displayProperty=NAME&pageSize=100&page=1&includeMetadataDetails=true&outputType=ENROLLMENT&relativePeriodDate=2019-02-01&desc=lastupdated", + "version": { + "min": 41 + } } ] } diff --git a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/generator/scenarios/event-query.json b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/generator/scenarios/event-query.json index 337fedb6e0b0..1f203fe9dfd2 100644 --- a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/generator/scenarios/event-query.json +++ b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/generator/scenarios/event-query.json @@ -16,10 +16,31 @@ }, { "name": "financialYear2023Sep", - "query": "/api/42/analytics/events/query/IpHINAT79UW?dimension=ou:USER_ORGUNIT,GxdhnY5wmHq&headers=ouname,GxdhnY5wmHq,lastupdated&totalPages=false&lastUpdated=2023Sep&displayProperty=NAME&pageSize=100&page=1&includeMetadataDetails=true&outputType=EVENT&stage=A03MvHHogjR", + "query": "/api/analytics/events/query/IpHINAT79UW.json?dimension=ou:USER_ORGUNIT,GxdhnY5wmHq&headers=ouname,GxdhnY5wmHq,lastupdated&totalPages=false&lastUpdated=2023Sep&displayProperty=NAME&pageSize=100&page=1&includeMetadataDetails=true&outputType=EVENT&stage=A03MvHHogjR", "version": { "min": 42 } + }, + { + "name": "programIndicatorMalariaCaseMicroscopyPv5_14YearsFemale", + "query": "/api/analytics/events/query/qDkgAbB5Jlk.json?dimension=TEmaOXaSPe7,ou:USER_ORGUNIT&headers=ouname,TEmaOXaSPe7&totalPages=false&displayProperty=NAME&pageSize=10&page=1&includeMetadataDetails=true&outputType=EVENT&stage=hYyB7FUS5eR&enrollmentDate=MONTHS_THIS_YEAR&relativePeriodDate=2022-07-01&desc=lastupdated", + "version": { + "min": 41 + } + }, + { + "name": "programIndicatorNumberOfOrgUnitsWithOngoingArv", + "query": "/api/analytics/events/query/WSGAb5XwJ3Y.json?dimension=ou:USER_ORGUNIT,rxNjqzJ7dkK:GE:0&headers=ouname,rxNjqzJ7dkK,lastupdated&totalPages=false&displayProperty=NAME&includeMetadataDetails=true&outputType=EVENT&stage=PUZaKR0Jh2k&lastUpdated=LAST_YEAR&pageSize=10&page=1&relativePeriodDate=2019-07-01&desc=lastupdated", + "version": { + "min": 41 + } + }, + { + "name": "programIndicatorBMIFemaleUnder5y", + "query": "/api/analytics/events/query/eBAyeGv0exc.json?dimension=hlPt8H4bUOQ,ou:USER_ORGUNIT&headers=ouname,lastupdated,hlPt8H4bUOQ&totalPages=false&lastUpdated=LAST_12_MONTHS&displayProperty=NAME&pageSize=5&page=1&includeMetadataDetails=true&outputType=EVENT&stage=Zj7UnCAulEk&relativePeriodDate=2019-02-01&desc=lastupdated", + "version": { + "min": 41 + } } ] }