From 487e14f6972c137413bf15f5c536a736bc3ebb86 Mon Sep 17 00:00:00 2001 From: danielTari Date: Thu, 3 Sep 2026 16:51:37 +0200 Subject: [PATCH 1/3] fix file download for aggregate data --- .../FileResourceNetworkHandlerImpl.kt | 16 +++++++++++++++- .../network/fileresource/FileResourceService.kt | 8 +++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/hisp/dhis/android/network/fileresource/FileResourceNetworkHandlerImpl.kt b/core/src/main/java/org/hisp/dhis/android/network/fileresource/FileResourceNetworkHandlerImpl.kt index f3fececbde..900e6db145 100644 --- a/core/src/main/java/org/hisp/dhis/android/network/fileresource/FileResourceNetworkHandlerImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/network/fileresource/FileResourceNetworkHandlerImpl.kt @@ -32,7 +32,10 @@ import io.ktor.client.request.forms.MultiPartFormDataContent import org.hisp.dhis.android.core.arch.api.HttpServiceClient import org.hisp.dhis.android.core.arch.api.payload.internal.Payload import org.hisp.dhis.android.core.arch.call.queries.internal.UidsQuery +import org.hisp.dhis.android.core.arch.helpers.CollectionsHelper import org.hisp.dhis.android.core.arch.helpers.FileResizerHelper.DimensionSize +import org.hisp.dhis.android.core.arch.helpers.UidsHelper.getUids +import org.hisp.dhis.android.core.category.CategoryOptionComboCollectionRepository import org.hisp.dhis.android.core.datavalue.DataValue import org.hisp.dhis.android.core.fileresource.FileResource import org.hisp.dhis.android.core.fileresource.internal.FileResourceNetworkHandler @@ -47,6 +50,7 @@ import org.koin.core.annotation.Singleton internal class FileResourceNetworkHandlerImpl( httpServiceClient: HttpServiceClient, private val dhis2VersionManager: DHISVersionManager, + private val categoryOptionComboCollectionRepository: CategoryOptionComboCollectionRepository, ) : FileResourceNetworkHandler { private val service = FileResourceService(httpServiceClient) override suspend fun uploadFile(filePart: MultiPartFormDataContent): FileResource { @@ -147,11 +151,21 @@ internal class FileResourceNetworkHandlerImpl( } override suspend fun getFileFromDataValue(v: DataValue, dimension: String): ByteArray { + val attributeOptionCombo = categoryOptionComboCollectionRepository + .withCategoryOptions() + .uid(v.attributeOptionCombo()) + .suspendGet() + return service.getFileFromDataValue( + v.sourceDataSet(), v.dataElement(), v.period(), v.organisationUnit(), - v.attributeOptionCombo(), + v.categoryOptionCombo(), + attributeOptionCombo?.categoryCombo()?.uid(), + attributeOptionCombo?.categoryOptions()?.let { + CollectionsHelper.semicolonSeparatedCollectionValues(getUids(it)) + }, dimension, ) } diff --git a/core/src/main/java/org/hisp/dhis/android/network/fileresource/FileResourceService.kt b/core/src/main/java/org/hisp/dhis/android/network/fileresource/FileResourceService.kt index 1a0af32b7c..00aa79c6f7 100644 --- a/core/src/main/java/org/hisp/dhis/android/network/fileresource/FileResourceService.kt +++ b/core/src/main/java/org/hisp/dhis/android/network/fileresource/FileResourceService.kt @@ -34,7 +34,7 @@ import org.hisp.dhis.android.core.fileresource.FileResource import org.hisp.dhis.android.network.common.fields.Fields import org.hisp.dhis.android.network.common.filters.Filter -@Suppress("TooManyFunctions") +@Suppress("TooManyFunctions", "LongParameterList") internal class FileResourceService(private val client: HttpServiceClient) { suspend fun uploadFile(filePart: MultiPartFormDataContent): FileResourceResponseDTO { @@ -161,19 +161,25 @@ internal class FileResourceService(private val client: HttpServiceClient) { } suspend fun getFileFromDataValue( + dataSet: String?, dataElement: String, period: String, organisationUnit: String, categoryOptionCombo: String, + attributeCategoryCombo: String?, + attributeCategoryOptions: String?, dimension: String, ): ByteArray { return client.get { url("$DATA_VALUES/files") parameters { + attribute("ds", dataSet) attribute("de", dataElement) attribute("pe", period) attribute("ou", organisationUnit) attribute("co", categoryOptionCombo) + attribute("cc", attributeCategoryCombo) + attribute("cp", attributeCategoryOptions) dimension.takeIf { it != DimensionSize.ORIGINAL_NAME }?.let { attribute("dimension", dimension) } } } From e4c81f505334f8db595c8551f1fb7901e4ec2066 Mon Sep 17 00:00:00 2001 From: danielTari Date: Thu, 3 Sep 2026 17:19:07 +0200 Subject: [PATCH 2/3] add wrapper for missing aggregated data value file resources --- .../internal/FileResourceDownloadCall.kt | 2 +- .../FileResourceDownloadCallHelper.kt | 31 ++++++++++++++-- .../internal/FileResourceNetworkHandler.kt | 3 +- .../internal/MissingAggregatedDataValue.kt | 37 +++++++++++++++++++ .../FileResourceNetworkHandlerImpl.kt | 29 +++++---------- 5 files changed, 76 insertions(+), 26 deletions(-) create mode 100644 core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/MissingAggregatedDataValue.kt diff --git a/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceDownloadCall.kt b/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceDownloadCall.kt index 1b723a7ef4..911b9bbde5 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceDownloadCall.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceDownloadCall.kt @@ -126,7 +126,7 @@ internal class FileResourceDownloadCall( values = dataValues, maxContentLength = params.maxContentLength, download = fileResourceNetworkHandlder::getFileFromDataValue, - getUid = { v -> v.value() }, + getUid = { v -> v.value.value() }, ) } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceDownloadCallHelper.kt b/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceDownloadCallHelper.kt index bd45f63f94..c6b54f3b96 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceDownloadCallHelper.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceDownloadCallHelper.kt @@ -27,9 +27,11 @@ */ package org.hisp.dhis.android.core.fileresource.internal +import org.hisp.dhis.android.core.arch.helpers.CollectionsHelper +import org.hisp.dhis.android.core.category.internal.CategoryOptionComboCategoryOptionLinkStore +import org.hisp.dhis.android.core.category.internal.CategoryOptionComboStore import org.hisp.dhis.android.core.dataelement.internal.DataElementStore import org.hisp.dhis.android.core.dataset.internal.DataSetElementStore -import org.hisp.dhis.android.core.datavalue.DataValue import org.hisp.dhis.android.core.datavalue.internal.DataValueStore import org.hisp.dhis.android.core.enrollment.internal.EnrollmentStore import org.hisp.dhis.android.core.event.internal.EventStore @@ -72,6 +74,8 @@ internal class FileResourceDownloadCallHelper( private val dataSetElementStore: DataSetElementStore, private val dataValueStore: DataValueStore, private val customIconStore: CustomIconStore, + private val categoryOptionComboStore: CategoryOptionComboStore, + private val categoryOptionComboCategoryOptionLinkStore: CategoryOptionComboCategoryOptionLinkStore, private val dhisVersionManager: DHISVersionManagerImpl, ) { @@ -225,7 +229,7 @@ internal class FileResourceDownloadCallHelper( suspend fun getMissingAggregatedDataValues( params: FileResourceDownloadParams, existingFileResources: List, - ): List { + ): List { val dataElementUidsWhereClause = WhereClauseBuilder() .appendInKeyEnumValues(DataElementTableInfo.Columns.VALUE_TYPE, params.valueTypes.map { it.valueType }) .appendKeyStringValue(DataElementTableInfo.Columns.DOMAIN_TYPE, "AGGREGATE") @@ -250,7 +254,28 @@ internal class FileResourceDownloadCallHelper( .appendNotInKeyStringValues(DataValueTableInfo.Columns.VALUE, existingFileResources) .build() - return dataValueStore.selectWhere(dataValuesWhereClause) + val dataValues = dataValueStore.selectWhere(dataValuesWhereClause) + val attributeOptionCombos = dataValues + .map { it.attributeOptionCombo() } + .distinct() + .associateWith { getAttributeOptionComboParams(it) } + + return dataValues.map { dataValue -> + val (categoryCombo, categoryOptions) = attributeOptionCombos.getValue(dataValue.attributeOptionCombo()) + MissingAggregatedDataValue(dataValue, categoryCombo, categoryOptions) + } + } + + private suspend fun getAttributeOptionComboParams(attributeOptionCombo: String): Pair { + val categoryCombo = categoryOptionComboStore.selectByUid(attributeOptionCombo)?.categoryCombo()?.uid() + + val categoryOptions = categoryOptionComboCategoryOptionLinkStore + .selectLinksForMasterUid(attributeOptionCombo) + .map { it.categoryOption() } + .takeIf { it.isNotEmpty() } + ?.let { CollectionsHelper.semicolonSeparatedCollectionValues(it) } + + return categoryCombo to categoryOptions } suspend fun getMissingCustomIcons( diff --git a/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceNetworkHandler.kt b/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceNetworkHandler.kt index 2f776ff4ba..1e771e86d3 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceNetworkHandler.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceNetworkHandler.kt @@ -31,7 +31,6 @@ package org.hisp.dhis.android.core.fileresource.internal import io.ktor.client.request.forms.MultiPartFormDataContent import org.hisp.dhis.android.core.arch.api.payload.internal.Payload import org.hisp.dhis.android.core.arch.call.queries.internal.UidsQuery -import org.hisp.dhis.android.core.datavalue.DataValue import org.hisp.dhis.android.core.fileresource.FileResource import org.hisp.dhis.android.core.icon.CustomIcon import org.hisp.dhis.android.core.trackedentity.TrackedEntityDataValue @@ -68,5 +67,5 @@ internal interface FileResourceNetworkHandler { v: CustomIcon, ): ByteArray - suspend fun getFileFromDataValue(v: DataValue, dimension: String): ByteArray + suspend fun getFileFromDataValue(v: MissingAggregatedDataValue, dimension: String): ByteArray } diff --git a/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/MissingAggregatedDataValue.kt b/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/MissingAggregatedDataValue.kt new file mode 100644 index 0000000000..3dd401dc2e --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/MissingAggregatedDataValue.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2004-2023, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.fileresource.internal + +import org.hisp.dhis.android.core.datavalue.DataValue + +internal data class MissingAggregatedDataValue( + val value: DataValue, + val attributeCategoryCombo: String?, + val attributeCategoryOptions: String?, +) diff --git a/core/src/main/java/org/hisp/dhis/android/network/fileresource/FileResourceNetworkHandlerImpl.kt b/core/src/main/java/org/hisp/dhis/android/network/fileresource/FileResourceNetworkHandlerImpl.kt index 900e6db145..4894f0ef16 100644 --- a/core/src/main/java/org/hisp/dhis/android/network/fileresource/FileResourceNetworkHandlerImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/network/fileresource/FileResourceNetworkHandlerImpl.kt @@ -32,13 +32,10 @@ import io.ktor.client.request.forms.MultiPartFormDataContent import org.hisp.dhis.android.core.arch.api.HttpServiceClient import org.hisp.dhis.android.core.arch.api.payload.internal.Payload import org.hisp.dhis.android.core.arch.call.queries.internal.UidsQuery -import org.hisp.dhis.android.core.arch.helpers.CollectionsHelper import org.hisp.dhis.android.core.arch.helpers.FileResizerHelper.DimensionSize -import org.hisp.dhis.android.core.arch.helpers.UidsHelper.getUids -import org.hisp.dhis.android.core.category.CategoryOptionComboCollectionRepository -import org.hisp.dhis.android.core.datavalue.DataValue import org.hisp.dhis.android.core.fileresource.FileResource import org.hisp.dhis.android.core.fileresource.internal.FileResourceNetworkHandler +import org.hisp.dhis.android.core.fileresource.internal.MissingAggregatedDataValue import org.hisp.dhis.android.core.fileresource.internal.MissingTrackerAttributeValue import org.hisp.dhis.android.core.icon.CustomIcon import org.hisp.dhis.android.core.systeminfo.DHISVersion @@ -50,7 +47,6 @@ import org.koin.core.annotation.Singleton internal class FileResourceNetworkHandlerImpl( httpServiceClient: HttpServiceClient, private val dhis2VersionManager: DHISVersionManager, - private val categoryOptionComboCollectionRepository: CategoryOptionComboCollectionRepository, ) : FileResourceNetworkHandler { private val service = FileResourceService(httpServiceClient) override suspend fun uploadFile(filePart: MultiPartFormDataContent): FileResource { @@ -150,22 +146,15 @@ internal class FileResourceNetworkHandlerImpl( return service.getCustomIcon(v.href()) } - override suspend fun getFileFromDataValue(v: DataValue, dimension: String): ByteArray { - val attributeOptionCombo = categoryOptionComboCollectionRepository - .withCategoryOptions() - .uid(v.attributeOptionCombo()) - .suspendGet() - + override suspend fun getFileFromDataValue(v: MissingAggregatedDataValue, dimension: String): ByteArray { return service.getFileFromDataValue( - v.sourceDataSet(), - v.dataElement(), - v.period(), - v.organisationUnit(), - v.categoryOptionCombo(), - attributeOptionCombo?.categoryCombo()?.uid(), - attributeOptionCombo?.categoryOptions()?.let { - CollectionsHelper.semicolonSeparatedCollectionValues(getUids(it)) - }, + v.value.sourceDataSet(), + v.value.dataElement(), + v.value.period(), + v.value.organisationUnit(), + v.value.categoryOptionCombo(), + v.attributeCategoryCombo, + v.attributeCategoryOptions, dimension, ) } From cbe8be9ce9805ef588bbbb65e379d15f73cea4c7 Mon Sep 17 00:00:00 2001 From: danielTari Date: Thu, 3 Sep 2026 17:44:11 +0200 Subject: [PATCH 3/3] fix test --- .../internal/FileResourceDownloadCallHelperShould.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/src/test/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceDownloadCallHelperShould.kt b/core/src/test/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceDownloadCallHelperShould.kt index e789c211ce..07ba745598 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceDownloadCallHelperShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceDownloadCallHelperShould.kt @@ -29,6 +29,8 @@ package org.hisp.dhis.android.core.fileresource.internal import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest +import org.hisp.dhis.android.core.category.internal.CategoryOptionComboCategoryOptionLinkStore +import org.hisp.dhis.android.core.category.internal.CategoryOptionComboStore import org.hisp.dhis.android.core.common.ObjectWithUid import org.hisp.dhis.android.core.common.ValueType import org.hisp.dhis.android.core.dataelement.internal.DataElementStore @@ -70,6 +72,8 @@ internal class FileResourceDownloadCallHelperShould { private val dataSetElementStore: DataSetElementStore = mock() private val dataValueStore: DataValueStore = mock() private val customIconStore: CustomIconStore = mock() + private val categoryOptionComboStore: CategoryOptionComboStore = mock() + private val categoryOptionComboCategoryOptionLinkStore: CategoryOptionComboCategoryOptionLinkStore = mock() private val dhisVersionManager: DHISVersionManagerImpl = mock() private lateinit var helper: FileResourceDownloadCallHelper @@ -93,6 +97,8 @@ internal class FileResourceDownloadCallHelperShould { dataSetElementStore, dataValueStore, customIconStore, + categoryOptionComboStore, + categoryOptionComboCategoryOptionLinkStore, dhisVersionManager, ) }