diff --git a/.idea/gradle.xml b/.idea/gradle.xml index fdc5465974..830a0eeb1d 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,17 +4,28 @@ diff --git a/Jenkinsfile b/Jenkinsfile index fe0e0853db..c0d8ae698e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -3,6 +3,10 @@ pipeline { label "ec2-android" } + options { + disableConcurrentBuilds(abortPrevious: true) + } + stages{ stage('Change to JAVA 17') { steps { diff --git a/build.gradle.kts b/build.gradle.kts index 34cf03a06a..60d5c01dee 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,12 +12,12 @@ buildscript { classpath(libs.gradle) classpath(libs.kotlin) classpath(libs.ktlint) - classpath(libs.jacoco) } } plugins { alias(libs.plugins.sonarqube) + alias(libs.plugins.dokka) } sonarqube { @@ -53,12 +53,13 @@ allprojects { } tasks.register("clean", Delete::class) { - delete(rootProject.buildDir) + delete(rootProject.layout.buildDirectory) } subprojects { apply(plugin = "org.jlleitschuh.gradle.ktlint") + apply(plugin = "org.jetbrains.dokka") //group = GROUP //version = VERSION_NAME diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 0000000000..7d59c3d9e9 --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +plugins { + `kotlin-dsl` +} + +repositories { + mavenCentral() + gradlePluginPortal() +} \ No newline at end of file diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts new file mode 100644 index 0000000000..95aeb2385c --- /dev/null +++ b/buildSrc/settings.gradle.kts @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +dependencyResolutionManagement { + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Props.kt b/buildSrc/src/main/kotlin/Props.kt new file mode 100644 index 0000000000..fe472edfc6 --- /dev/null +++ b/buildSrc/src/main/kotlin/Props.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +object Props { + val GROUP = "org.hisp.dhis" + val POM_NAME = "Core" + val POM_ARTIFACT_ID = "android-core" + val POM_PACKAGING = "aar" + val POM_DESCRIPTION = "Android SDK for DHIS 2." + val POM_URL = "https://github.com/dhis2/dhis2-android-sdk" + val POM_SCM_URL = "https://github.com/dhis2/dhis2-android-sdk" + val POM_SCM_CONNECTION = "scm:git:git://github.com/dhis2/dhis2-android-sdk.git" + val POM_SCM_DEV_CONNECTION = "scm:git:ssh://git@github.com/dhis2/dhis2-android-sdk.git" + val POM_LICENCE_NAME = "BSD" + val POM_LICENCE_URL = "https://opensource.org/licenses/BSD-3-Clause" + val POM_LICENCE_DIST = "repo" + val POM_DEVELOPER_ID = "DHIS 2" + val POM_DEVELOPER_NAME = "DHIS 2" +} diff --git a/core/plugins/jacoco.gradle.kts b/buildSrc/src/main/kotlin/jacoco-conventions.gradle.kts similarity index 62% rename from core/plugins/jacoco.gradle.kts rename to buildSrc/src/main/kotlin/jacoco-conventions.gradle.kts index 98ea614d64..b2d450ee5b 100644 --- a/core/plugins/jacoco.gradle.kts +++ b/buildSrc/src/main/kotlin/jacoco-conventions.gradle.kts @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import org.gradle.testing.jacoco.tasks.JacocoReport + /* * Copyright (c) 2004-2022, University of Oslo * All rights reserved. @@ -26,7 +56,13 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -apply(plugin = "jacoco") +plugins { + jacoco +} + +jacoco { + toolVersion = "0.8.11" +} tasks.register("jacocoReport", JacocoReport::class) { group = "Coverage" @@ -34,7 +70,7 @@ tasks.register("jacocoReport", JacocoReport::class) { sourceDirectories.setFrom("${project.projectDir}/src/main/java") - val excludes = mutableSetOf( + val excludes = mutableSetOf( // data binding "android/databinding/**/*.class", "**/android/databinding/*Binding.class", @@ -84,10 +120,10 @@ tasks.register("jacocoReport", JacocoReport::class) { "**/*AutoValue_*.*" ) - val javaClasses = fileTree("${buildDir}/intermediates/javac/debug") { + val javaClasses = fileTree(layout.buildDirectory.file("intermediates/javac/debug")) { exclude(excludes) } - val kotlinClasses = fileTree("${buildDir}/tmp/kotlin-classes/debug") { + val kotlinClasses = fileTree(layout.buildDirectory.file("tmp/kotlin-classes/debug")) { exclude(excludes) } @@ -100,10 +136,10 @@ tasks.register("jacocoReport", JacocoReport::class) { ) ) - val unitTestsData = fileTree("${buildDir}/jacoco") { + val unitTestsData = fileTree(layout.buildDirectory.file("jacoco")) { include("*.exec") } - val androidTestsData = fileTree("${buildDir}/outputs/code_coverage") { + val androidTestsData = fileTree(layout.buildDirectory.file("outputs/code_coverage")) { include(listOf("**/*.ec")) } @@ -118,10 +154,10 @@ tasks.register("jacocoReport", JacocoReport::class) { fun JacocoReportsContainer.reports() { xml.required.set(true) - xml.outputLocation.set(file("${buildDir}/coverage-report/jacocoTestReport.xml")) + xml.outputLocation.set(layout.buildDirectory.file("coverage-report/jacocoTestReport.xml")) html.required.set(true) - html.outputLocation.set(file("${buildDir}/coverage-report")) + html.outputLocation.set(layout.buildDirectory.file("coverage-report").get().asFile) } reports { diff --git a/buildSrc/src/main/kotlin/maven-publish-conventions.gradle.kts b/buildSrc/src/main/kotlin/maven-publish-conventions.gradle.kts new file mode 100644 index 0000000000..3180cb0905 --- /dev/null +++ b/buildSrc/src/main/kotlin/maven-publish-conventions.gradle.kts @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import java.net.URI + +plugins { + `maven-publish` + signing +} + +val VERSION_NAME: String by project + +fun isReleaseBuild(): Boolean { + return !VERSION_NAME.contains("SNAPSHOT") +} + +val releaseRepositoryUrl: String = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" + +val snapshotRepositoryUrl: String = "https://oss.sonatype.org/content/repositories/snapshots/" + +fun getRepositoryUsername(): String? { + return System.getenv("NEXUS_USERNAME") +} + +fun getRepositoryPassword(): String? { + return System.getenv("NEXUS_PASSWORD") +} + +fun gpgKeyId(): String? { + return System.getenv("GPG_KEY_ID") +} + +fun gpgKeyLocation(): String? { + return System.getenv("GPG_KEY_LOCATION") +} + +fun gpgPassphrase(): String? { + return System.getenv("GPG_PASSPHRASE") +} + +gradle.taskGraph.whenReady(closureOf { + if (gradle.taskGraph.allTasks.any { it is Sign }) { + allprojects { ext["signing.keyId"] = gpgKeyId() } + allprojects { ext["signing.secretKeyRingFile"] = gpgKeyLocation() } + allprojects { ext["signing.password"] = gpgPassphrase() } + } +}) + +val androidJavadocsJar = tasks.register("androidJavadocsJar", Jar::class) { + archiveClassifier.set("javadoc") + + val dokkaHtml = tasks.findByName("dokkaJavadoc")!! + from(dokkaHtml.outputs) +} + +afterEvaluate { + publishing { + publications { + create("maven") { + from(components["release"]) + artifact(androidJavadocsJar) + + groupId = Props.GROUP + artifactId = Props.POM_ARTIFACT_ID + version = VERSION_NAME + + pom { + name = Props.POM_NAME + packaging = Props.POM_PACKAGING + description = Props.POM_DESCRIPTION + url = Props.POM_URL + licenses { + license { + name = Props.POM_LICENCE_NAME + url = Props.POM_LICENCE_URL + distribution = Props.POM_LICENCE_DIST + } + } + developers { + developer { + id = Props.POM_DEVELOPER_ID + name = Props.POM_DEVELOPER_NAME + } + } + scm { + connection = Props.POM_SCM_CONNECTION + developerConnection = Props.POM_SCM_DEV_CONNECTION + url = Props.POM_SCM_URL + } + } + } + + repositories { + maven { + url = if (isReleaseBuild()) URI(releaseRepositoryUrl) else URI(snapshotRepositoryUrl) + + credentials { + username = getRepositoryUsername() + password = getRepositoryPassword() + } + } + } + } + + signing { + setRequired({ isReleaseBuild() && gradle.taskGraph.hasTask("publishing") }) + sign(publishing.publications) + } + } +} \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 8516146216..5628fd60b4 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -31,14 +31,13 @@ plugins { id("com.google.devtools.ksp") version "${libs.versions.kotlin.get()}-1.0.16" id("kotlin-android") id("kotlin-kapt") + id("maven-publish-conventions") + id("jacoco-conventions") alias(libs.plugins.detekt) - alias(libs.plugins.dokka) apply false } apply(from = project.file("plugins/android-checkstyle.gradle")) apply(from = project.file("plugins/android-pmd.gradle")) -apply(from = project.file("plugins/jacoco.gradle.kts")) -apply(from = project.file("plugins/gradle-mvn-push.gradle")) repositories { mavenCentral() @@ -53,7 +52,7 @@ android { defaultConfig { minSdk = libs.versions.minSdkVersion.get().toInt() - targetSdk = libs.versions.targetSdkVersion.get().toInt() + testOptions.targetSdk = libs.versions.targetSdkVersion.get().toInt() testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" multiDexEnabled = true vectorDrawables.useSupportLibrary = true @@ -123,7 +122,7 @@ dependencies { // AndroidX api(libs.androidx.annotation) - api(libs.androidx.paging) + api(libs.androidx.paging.runtime) // Auto Value api(libs.google.auto.value.annotation) @@ -162,6 +161,8 @@ dependencies { // From SQLCipher 4.5.5, it depends on androidx.sqlite:sqlite api(libs.sqlite) + implementation(libs.zip4j) + implementation(libs.openid.appauth) implementation(libs.listenablefuture.empty) @@ -188,6 +189,7 @@ dependencies { exclude(group = "junit") // Android has JUnit built in. } androidTestImplementation(libs.kotlinx.coroutines.test) + androidTestImplementation(libs.androidx.paging.testing) debugImplementation(libs.facebook.soloader) debugImplementation(libs.facebook.flipper.core) @@ -204,3 +206,16 @@ detekt { parallel = true buildUponDefaultConfig = false } + +tasks.dokkaJavadoc.configure { + dependsOn("kaptReleaseKotlin") + + dokkaSourceSets { + configureEach { + perPackageOption { + matchingRegex.set(".*.internal.*") + suppress.set(true) + } + } + } +} diff --git a/core/gradle.properties b/core/gradle.properties index 0454f9d41a..9f9e03be39 100644 --- a/core/gradle.properties +++ b/core/gradle.properties @@ -29,24 +29,5 @@ # Properties which are consumed by plugins/gradle-mvn-push.gradle plugin. # They are used for publishing artifact to snapshot repository. -VERSION_NAME=1.9.1 -VERSION_CODE=291 - -GROUP=org.hisp.dhis - -POM_NAME=Core -POM_ARTIFACT_ID=android-core -POM_PACKAGING=aar - -POM_DESCRIPTION=Android SDK for DHIS 2. -POM_URL=https://github.com/dhis2/dhis2-android-sdk -POM_SCM_URL=https://github.com/dhis2/dhis2-android-sdk -POM_SCM_CONNECTION=scm:git:git://github.com/dhis2/dhis2-android-sdk.git -POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/dhis2/dhis2-android-sdk.git -POM_LICENCE_NAME=BSD -POM_LICENCE_URL=https://opensource.org/licenses/BSD-3-Clause -POM_LICENCE_DIST=repo -POM_DEVELOPER_ID=DHIS 2 -POM_DEVELOPER_NAME=DHIS 2 -android.useAndroidX=true -android.enableJetifier=true +VERSION_NAME=1.10.0 +VERSION_CODE=292 diff --git a/core/plugins/gradle-mvn-push.gradle b/core/plugins/gradle-mvn-push.gradle deleted file mode 100644 index 78056fbda0..0000000000 --- a/core/plugins/gradle-mvn-push.gradle +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2013 Chris Banes - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -apply plugin: "maven-publish" -apply plugin: "signing" -apply plugin: "org.jetbrains.dokka" - - -def isReleaseBuild() { - return !VERSION_NAME.contains("SNAPSHOT") -} - -def getReleaseRepositoryUrl() { - return hasProperty("RELEASE_REPOSITORY_URL") ? RELEASE_REPOSITORY_URL - : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" -} - -def getSnapshotRepositoryUrl() { - return hasProperty("SNAPSHOT_REPOSITORY_URL") ? SNAPSHOT_REPOSITORY_URL - : "https://oss.sonatype.org/content/repositories/snapshots/" -} - -static def getRepositoryUsername() { - return System.getenv("NEXUS_USERNAME") -} - -static def getRepositoryPassword() { - return System.getenv("NEXUS_PASSWORD") -} - -static def gpgKeyId() { - return System.getenv("GPG_KEY_ID") -} - -static def gpgKeyLocation() { - return System.getenv("GPG_KEY_LOCATION") -} - -static def gpgPassphrase() { - return System.getenv("GPG_PASSPHRASE") -} - -gradle.taskGraph.whenReady { taskGraph -> - if (taskGraph.allTasks.any { it instanceof Sign }) { - allprojects { ext."signing.keyId" = gpgKeyId() } - allprojects { ext."signing.secretKeyRingFile" = gpgKeyLocation() } - allprojects { ext."signing.password" = gpgPassphrase() } - } -} - -tasks.named("dokkaJavadoc") { - dependsOn("kaptReleaseKotlin") - outputDirectory = file("$buildDir/dokkaJavadoc") - - dokkaSourceSets { - configureEach { - perPackageOption { - matchingRegex.set(".*.internal.*") - suppress.set(true) - } - } - } -} - -task androidJavadocsJar(type: Jar, dependsOn: dokkaJavadoc) { - archiveClassifier.set('javadoc') - from dokkaJavadoc.outputDirectory -} - -afterEvaluate { - publishing { - publications { - release(MavenPublication) { - from components.release - - // Artifacts - artifact androidJavadocsJar - - groupId = GROUP - artifactId = POM_ARTIFACT_ID - version = VERSION_NAME - - pom { - name = POM_NAME - packaging = POM_PACKAGING - description = POM_DESCRIPTION - url = POM_URL - licenses { - license { - name = POM_LICENCE_NAME - url = POM_LICENCE_URL - distribution = POM_LICENCE_DIST - } - } - developers { - developer { - id = POM_DEVELOPER_ID - name = POM_DEVELOPER_NAME - } - } - scm { - connection = POM_SCM_CONNECTION - developerConnection = POM_SCM_DEV_CONNECTION - url = POM_SCM_URL - } - } - } - } - - repositories { - maven { - def releasesRepoUrl = getReleaseRepositoryUrl() - def snapshotsRepoUrl = getSnapshotRepositoryUrl() - url = isReleaseBuild() ? releasesRepoUrl : snapshotsRepoUrl - - credentials { - username = getRepositoryUsername() - password = getRepositoryPassword() - } - } - } - } - - signing { - required { isReleaseBuild() && gradle.taskGraph.hasTask("publishing") } - sign publishing.publications.release - } -} \ No newline at end of file diff --git a/core/src/androidTest/assets/databases/corrupted-database.zip b/core/src/androidTest/assets/databases/corrupted-database.zip new file mode 100644 index 0000000000..985beee22f Binary files /dev/null and b/core/src/androidTest/assets/databases/corrupted-database.zip differ diff --git a/core/src/androidTest/assets/databases/export-database.zip b/core/src/androidTest/assets/databases/export-database.zip new file mode 100644 index 0000000000..eba4248243 Binary files /dev/null and b/core/src/androidTest/assets/databases/export-database.zip differ diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/MockIntegrationTestObjects.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/MockIntegrationTestObjects.kt index 57a23fd391..ebf069b900 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/MockIntegrationTestObjects.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/MockIntegrationTestObjects.kt @@ -41,6 +41,7 @@ import java.util.* class MockIntegrationTestObjects( val d2: D2, val content: MockIntegrationTestDatabaseContent, + port: Int, ) { val databaseAdapter: DatabaseAdapter = d2.databaseAdapter() @@ -49,7 +50,7 @@ class MockIntegrationTestObjects( @JvmField internal val d2DIComponent: D2DIComponent = d2.d2DIComponent - val dhis2MockServer: Dhis2MockServer = Dhis2MockServer(0) + val dhis2MockServer: Dhis2MockServer = Dhis2MockServer(port) @Throws(IOException::class) fun tearDown() { diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/analytics/aggregated/internal/evaluator/BaseEvaluatorIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/analytics/aggregated/internal/evaluator/BaseEvaluatorIntegrationShould.kt index 5652ca1ce8..3f66b6de7b 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/analytics/aggregated/internal/evaluator/BaseEvaluatorIntegrationShould.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/analytics/aggregated/internal/evaluator/BaseEvaluatorIntegrationShould.kt @@ -30,6 +30,7 @@ package org.hisp.dhis.android.core.analytics.aggregated.internal.evaluator import org.hisp.dhis.android.core.analytics.aggregated.MetadataItem import org.hisp.dhis.android.core.analytics.aggregated.internal.evaluator.BaseEvaluatorSamples.attribute import org.hisp.dhis.android.core.analytics.aggregated.internal.evaluator.BaseEvaluatorSamples.attribute1 +import org.hisp.dhis.android.core.analytics.aggregated.internal.evaluator.BaseEvaluatorSamples.attribute2 import org.hisp.dhis.android.core.analytics.aggregated.internal.evaluator.BaseEvaluatorSamples.attributeAttributeComboLink import org.hisp.dhis.android.core.analytics.aggregated.internal.evaluator.BaseEvaluatorSamples.attributeAttributeOptionLink import org.hisp.dhis.android.core.analytics.aggregated.internal.evaluator.BaseEvaluatorSamples.attributeCombo @@ -248,6 +249,7 @@ internal open class BaseEvaluatorIntegrationShould : BaseMockIntegrationTestEmpt trackedEntityTypeStore.insert(trackedEntityType) trackedEntityAttributeStore.insert(attribute1) + trackedEntityAttributeStore.insert(attribute2) programStore.insert(program) programStageStore.insert(programStage1) programStageStore.insert(programStage2) diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/analytics/aggregated/internal/evaluator/BaseEvaluatorSamples.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/analytics/aggregated/internal/evaluator/BaseEvaluatorSamples.kt index ec74e947b6..808bc475e2 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/analytics/aggregated/internal/evaluator/BaseEvaluatorSamples.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/analytics/aggregated/internal/evaluator/BaseEvaluatorSamples.kt @@ -220,6 +220,12 @@ object BaseEvaluatorSamples { .valueType(ValueType.INTEGER) .build() + val attribute2 = TrackedEntityAttribute.builder() + .uid(generator.generate()) + .displayName("Attribute 2") + .valueType(ValueType.TEXT) + .build() + val period2019SunW25: Period = Period.builder() .periodId("2019SunW25") .periodType(PeriodType.WeeklySunday) @@ -275,6 +281,8 @@ object BaseEvaluatorSamples { val program: Program = Program.builder() .uid(generator.generate()) + .name("Tracker program") + .displayName("Tracker program") .trackedEntityType(trackedEntityType) .categoryCombo(ObjectWithUid.create(categoryCombo.uid())) .build() @@ -282,12 +290,15 @@ object BaseEvaluatorSamples { val programStage1: ProgramStage = ProgramStage.builder() .uid(generator.generate()) .name("Program stage 1") + .displayName("Program stage 1") .program(ObjectWithUid.create(program.uid())) .formType(FormType.DEFAULT) .build() val programStage2: ProgramStage = ProgramStage.builder() .uid(generator.generate()) + .name("Program stage 2") + .displayName("Program stage 2") .program(ObjectWithUid.create(program.uid())) .formType(FormType.DEFAULT) .build() diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/analytics/trackerlinelist/TrackerLineListRepositoryEvaluatorShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/analytics/trackerlinelist/TrackerLineListRepositoryEvaluatorShould.kt new file mode 100644 index 0000000000..c0cb0d5938 --- /dev/null +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/analytics/trackerlinelist/TrackerLineListRepositoryEvaluatorShould.kt @@ -0,0 +1,411 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist + +import com.google.common.truth.Truth.assertThat +import org.hisp.dhis.android.core.analytics.aggregated.internal.evaluator.BaseEvaluatorIntegrationShould +import org.hisp.dhis.android.core.analytics.aggregated.internal.evaluator.BaseEvaluatorSamples.attribute1 +import org.hisp.dhis.android.core.analytics.aggregated.internal.evaluator.BaseEvaluatorSamples.attribute2 +import org.hisp.dhis.android.core.analytics.aggregated.internal.evaluator.BaseEvaluatorSamples.dataElement1 +import org.hisp.dhis.android.core.analytics.aggregated.internal.evaluator.BaseEvaluatorSamples.generator +import org.hisp.dhis.android.core.analytics.aggregated.internal.evaluator.BaseEvaluatorSamples.orgunitChild1 +import org.hisp.dhis.android.core.analytics.aggregated.internal.evaluator.BaseEvaluatorSamples.orgunitChild2 +import org.hisp.dhis.android.core.analytics.aggregated.internal.evaluator.BaseEvaluatorSamples.period201911 +import org.hisp.dhis.android.core.analytics.aggregated.internal.evaluator.BaseEvaluatorSamples.period201912 +import org.hisp.dhis.android.core.analytics.aggregated.internal.evaluator.BaseEvaluatorSamples.period202001 +import org.hisp.dhis.android.core.analytics.aggregated.internal.evaluator.BaseEvaluatorSamples.program +import org.hisp.dhis.android.core.analytics.aggregated.internal.evaluator.BaseEvaluatorSamples.programStage1 +import org.hisp.dhis.android.core.analytics.aggregated.internal.evaluator.BaseEvaluatorSamples.trackedEntity1 +import org.hisp.dhis.android.core.analytics.aggregated.internal.evaluator.BaseEvaluatorSamples.trackedEntity2 +import org.hisp.dhis.android.core.analytics.aggregated.internal.evaluator.BaseEvaluatorSamples.trackedEntityType +import org.hisp.dhis.android.core.arch.helpers.DateUtils +import org.hisp.dhis.android.core.arch.repositories.paging.PageConfig +import org.hisp.dhis.android.core.common.AnalyticsType +import org.hisp.dhis.android.core.enrollment.EnrollmentStatus +import org.hisp.dhis.android.core.event.EventStatus +import org.hisp.dhis.android.core.program.programindicatorengine.BaseTrackerDataIntegrationHelper +import org.hisp.dhis.android.core.utils.runner.D2JunitRunner +import org.junit.Test +import org.junit.runner.RunWith +import java.util.Date + +@RunWith(D2JunitRunner::class) +internal class TrackerLineListRepositoryEvaluatorShould : BaseEvaluatorIntegrationShould() { + + private val helper = BaseTrackerDataIntegrationHelper(databaseAdapter) + + @Test + fun evaluate_program_attributes() { + helper.createTrackedEntity(trackedEntity1.uid(), orgunitChild1.uid(), trackedEntityType.uid()) + val enrollment1 = generator.generate() + createDefaultEnrollment(trackedEntity1.uid(), enrollment1) + helper.insertTrackedEntityAttributeValue(trackedEntity1.uid(), attribute1.uid(), "45") + + val result = d2.analyticsModule().trackerLineList() + .withEnrollmentOutput(program.uid()) + .withColumn(TrackerLineListItem.OrganisationUnitItem()) + .withColumn( + TrackerLineListItem.ProgramAttribute( + uid = attribute1.uid(), + filters = listOf( + DataFilter.GreaterThan("40"), + DataFilter.LowerThan("50"), + ), + ), + ) + .blockingEvaluate() + + assertThat(result.getOrThrow().rows.size).isEqualTo(1) + } + + @Test + fun evaluate_repeated_data_elements() { + helper.createTrackedEntity(trackedEntity1.uid(), orgunitChild1.uid(), trackedEntityType.uid()) + val enrollment1 = generator.generate() + createDefaultEnrollment(trackedEntity1.uid(), enrollment1) + val event1 = generator.generate() + createDefaultTrackerEvent(event1, enrollment1, eventDate = period202001.startDate()) + val event2 = generator.generate() + createDefaultTrackerEvent(event2, enrollment1, eventDate = period201912.startDate()) + val event3 = generator.generate() + createDefaultTrackerEvent(event3, enrollment1, eventDate = period201911.startDate()) + + helper.insertTrackedEntityDataValue(event1, dataElement1.uid(), "8") + helper.insertTrackedEntityDataValue(event2, dataElement1.uid(), "19") + helper.insertTrackedEntityDataValue(event3, dataElement1.uid(), "2") + + val result1 = d2.analyticsModule().trackerLineList() + .withEnrollmentOutput(program.uid()) + .withColumn( + TrackerLineListItem.ProgramDataElement( + dataElement = dataElement1.uid(), + programStage = programStage1.uid(), + filters = listOf( + DataFilter.GreaterThan("15"), + ), + repetitionIndexes = listOf(1, 2, 0, -1), + ), + ) + .blockingEvaluate() + + val rows = result1.getOrThrow().rows + assertThat(rows.size).isEqualTo(1) + + val row = rows.first() + assertThat(row.size).isEqualTo(4) + + rows.first().forEachIndexed { index, value -> + when (index) { + 0 -> assertThat(value.value).isEqualTo("2") + 1 -> assertThat(value.value).isEqualTo("19") + 2 -> assertThat(value.value).isEqualTo("19") + 3 -> assertThat(value.value).isEqualTo("8") + } + } + } + + @Test + fun should_filter_by_date() { + helper.createTrackedEntity(trackedEntity1.uid(), orgunitChild1.uid(), trackedEntityType.uid()) + val enrollment1 = generator.generate() + createDefaultEnrollment(trackedEntity1.uid(), enrollment1, enrollmentDate = period202001.startDate()) + helper.insertTrackedEntityAttributeValue(trackedEntity1.uid(), attribute1.uid(), "123") + + helper.createTrackedEntity(trackedEntity2.uid(), orgunitChild1.uid(), trackedEntityType.uid()) + val enrollment2 = generator.generate() + createDefaultEnrollment(trackedEntity2.uid(), enrollment2, enrollmentDate = period201912.startDate()) + helper.insertTrackedEntityAttributeValue(trackedEntity2.uid(), attribute1.uid(), "789") + + // Filter by absolute value + val result1 = d2.analyticsModule().trackerLineList() + .withEnrollmentOutput(program.uid()) + .withColumn(TrackerLineListItem.ProgramAttribute(attribute1.uid())) + .withColumn( + TrackerLineListItem.EnrollmentDate( + filters = listOf(DateFilter.Absolute("2020")), + ), + ) + .blockingEvaluate() + + val rows1 = result1.getOrThrow().rows + assertThat(rows1.size).isEqualTo(1) + assertThat(rows1[0][0].value).isEqualTo("123") + assertThat(rows1[0][1].value).isEqualTo("2020-01-01T00:00:00.000") + + // Filter by range + val result2 = d2.analyticsModule().trackerLineList() + .withEnrollmentOutput(program.uid()) + .withColumn(TrackerLineListItem.ProgramAttribute(attribute1.uid())) + .withColumn( + TrackerLineListItem.EnrollmentDate( + filters = listOf( + DateFilter.Range( + startDate = DateUtils.DATE_FORMAT.format(period201911.startDate()!!), + endDate = DateUtils.DATE_FORMAT.format(period201912.endDate()!!), + ), + ), + ), + ) + .blockingEvaluate() + + val rows2 = result2.getOrThrow().rows + assertThat(rows2.size).isEqualTo(1) + assertThat(rows2[0][0].value).isEqualTo("789") + assertThat(rows2[0][1].value).isEqualTo("2019-12-01T00:00:00.000") + } + + @Test + fun evaluate_program_indicator() { + helper.createTrackedEntity(trackedEntity1.uid(), orgunitChild1.uid(), trackedEntityType.uid()) + val enrollment1 = generator.generate() + createDefaultEnrollment(trackedEntity1.uid(), enrollment1, enrollmentDate = period202001.startDate()) + helper.insertTrackedEntityAttributeValue(trackedEntity1.uid(), attribute1.uid(), "123") + + val event1 = generator.generate() + createDefaultTrackerEvent(event1, enrollment1, eventDate = period202001.startDate()) + val event2 = generator.generate() + createDefaultTrackerEvent(event2, enrollment1, eventDate = period201912.startDate()) + + helper.insertTrackedEntityDataValue(event1, dataElement1.uid(), "5") + helper.insertTrackedEntityDataValue(event2, dataElement1.uid(), "10") + + val programIndicator = generator.generate() + helper.setProgramIndicatorExpression( + programIndicator, + program.uid(), + expression = "A{${attribute1.uid()}} + #{${programStage1.uid()}.${dataElement1.uid()}}", + analyticsType = AnalyticsType.EVENT, + ) + + val result = d2.analyticsModule().trackerLineList() + .withEventOutput(programStage1.uid()) + .withColumn(TrackerLineListItem.EventDate()) + .withColumn(TrackerLineListItem.ProgramIndicator(programIndicator)) + .blockingEvaluate() + + val rows = result.getOrThrow().rows + assertThat(rows.size).isEqualTo(2) + assertThat(rows[0][0].value).isEqualTo(DateUtils.DATE_FORMAT.format(period202001.startDate()!!)) + assertThat(rows[0][1].value).isEqualTo("128") + assertThat(rows[1][0].value).isEqualTo(DateUtils.DATE_FORMAT.format(period201912.startDate()!!)) + assertThat(rows[1][1].value).isEqualTo("133") + } + + @Test + fun evaluate_contains_functions() { + helper.createTrackedEntity(trackedEntity1.uid(), orgunitChild1.uid(), trackedEntityType.uid()) + val enrollment1 = generator.generate() + createDefaultEnrollment(trackedEntity1.uid(), enrollment1, enrollmentDate = period202001.startDate()) + helper.insertTrackedEntityAttributeValue(trackedEntity1.uid(), attribute2.uid(), "ALLERGY,LATEX") + + fun evaluateExpression(expression: String, expected: String) { + val programIndicator = generator.generate() + helper.setProgramIndicatorExpression( + programIndicator, + program.uid(), + expression = expression, + analyticsType = AnalyticsType.ENROLLMENT, + ) + + val result = d2.analyticsModule().trackerLineList() + .withEnrollmentOutput(program.uid()) + .withColumn(TrackerLineListItem.ProgramIndicator(programIndicator)) + .blockingEvaluate() + + val rows = result.getOrThrow().rows + assertThat(rows.size).isEqualTo(1) + assertThat(rows.first().first().value).isEqualTo(expected) + } + + evaluateExpression("if(contains(A{${attribute2.uid()}}, 'LATEX', 'ALLERGY'), '1', '0')", "1") + evaluateExpression("if(contains(A{${attribute2.uid()}}, 'GY,LA'), '1', '0')", "1") + evaluateExpression("if(contains(A{${attribute2.uid()}}, 'xy', 'ALLERGY'), '1', '0')", "0") + + evaluateExpression("if(containsItems(A{${attribute2.uid()}}, 'LATEX', 'ALLERGY'), '1', '0')", "1") + evaluateExpression("if(containsItems(A{${attribute2.uid()}}, 'GY,LA'), '1', '0')", "0") + evaluateExpression("if(containsItems(A{${attribute2.uid()}}, 'xy', 'ALLERGY'), '1', '0')", "0") + } + + @Test + fun evaluate_orgunit_filters() { + val event1 = generator.generate() + helper.createSingleEvent(event1, program.uid(), programStage1.uid(), orgunitChild1.uid()) + val event2 = generator.generate() + helper.createSingleEvent(event2, program.uid(), programStage1.uid(), orgunitChild2.uid()) + + val result = d2.analyticsModule().trackerLineList() + .withEventOutput(programStage1.uid()) + .withColumn( + TrackerLineListItem.OrganisationUnitItem( + filters = listOf( + OrganisationUnitFilter.Like(orgunitChild1.displayName()!!), + ), + ), + ) + .blockingEvaluate() + + val rows = result.getOrThrow().rows + assertThat(rows.size).isEqualTo(1) + assertThat(rows.first().first().value).isEqualTo(orgunitChild1.displayName()) + } + + @Test + fun evaluate_single_events() { + val event1 = generator.generate() + helper.createSingleEvent(event1, program.uid(), programStage1.uid(), orgunitChild1.uid()) + val event2 = generator.generate() + helper.createSingleEvent(event2, program.uid(), programStage1.uid(), orgunitChild1.uid()) + + helper.insertTrackedEntityDataValue(event1, dataElement1.uid(), "5") + helper.insertTrackedEntityDataValue(event2, dataElement1.uid(), "10") + + val result = d2.analyticsModule().trackerLineList() + .withEventOutput(programStage1.uid()) + .withColumn(TrackerLineListItem.ProgramDataElement(dataElement1.uid(), programStage1.uid())) + .blockingEvaluate() + + val rows = result.getOrThrow().rows + assertThat(rows.size).isEqualTo(2) + assertThat(rows.flatten().map { it.value }).containsExactly("5", "10") + } + + @Test + fun evaluate_shared_properties() { + val event1 = generator.generate() + helper.createSingleEvent( + event1, + program.uid(), + programStage1.uid(), + orgunitChild1.uid(), + lastUpdated = DateUtils.SIMPLE_DATE_FORMAT.parse("2024-01-04"), + ) + + val result = d2.analyticsModule().trackerLineList() + .withEventOutput(programStage1.uid()) + .withColumn( + TrackerLineListItem.LastUpdated( + filters = listOf( + DateFilter.Range(startDate = "2024-01-01", endDate = "2024-01-31"), + ), + ), + ) + .withColumn(TrackerLineListItem.LastUpdatedBy) + .withColumn(TrackerLineListItem.CreatedBy) + .withColumn(TrackerLineListItem.OrganisationUnitItem()) + .blockingEvaluate() + + val rows = result.getOrThrow().rows + assertThat(rows.size).isEqualTo(1) + } + + @Test + fun evaluate_paging() { + val event1 = generator.generate() + helper.createSingleEvent(event1, program.uid(), programStage1.uid(), orgunitChild1.uid()) + val event2 = generator.generate() + helper.createSingleEvent(event2, program.uid(), programStage1.uid(), orgunitChild2.uid()) + + val baseRepo = d2.analyticsModule().trackerLineList() + .withEventOutput(programStage1.uid()) + .withColumn(TrackerLineListItem.OrganisationUnitItem()) + + // Page 1 + val resultPage1 = baseRepo + .withPageConfig(PageConfig.Paging(1, 1)) + .blockingEvaluate() + + assertThat(resultPage1.getOrThrow().rows.size).isEqualTo(1) + assertThat(resultPage1.getOrThrow().rows.first().first().value).isEqualTo(orgunitChild1.displayName()) + + // Page 2 + val resultPage2 = baseRepo + .withPageConfig(PageConfig.Paging(2, 1)) + .blockingEvaluate() + + assertThat(resultPage2.getOrThrow().rows.size).isEqualTo(1) + assertThat(resultPage2.getOrThrow().rows.first().first().value).isEqualTo(orgunitChild2.displayName()) + + // No paging + val resultNoPaging = baseRepo + .withPageConfig(PageConfig.NoPaging) + .blockingEvaluate() + + assertThat(resultNoPaging.getOrThrow().rows.size).isEqualTo(2) + } + + private fun createDefaultEnrollment( + teiUid: String, + enrollmentUid: String, + programUid: String = program.uid(), + orgunitUid: String = orgunitChild1.uid(), + enrollmentDate: Date? = null, + incidentDate: Date? = null, + created: Date? = null, + lastUpdated: Date? = null, + status: EnrollmentStatus? = EnrollmentStatus.ACTIVE, + ) { + helper.createEnrollment( + teiUid, + enrollmentUid, + programUid, + orgunitUid, + enrollmentDate, + incidentDate, + created, + lastUpdated, + status, + ) + } + + private fun createDefaultTrackerEvent( + eventUid: String, + enrollmentUid: String, + programUid: String = program.uid(), + programStageUid: String = programStage1.uid(), + orgunitUid: String = orgunitChild1.uid(), + deleted: Boolean = false, + eventDate: Date? = null, + created: Date? = null, + lastUpdated: Date? = null, + status: EventStatus? = EventStatus.ACTIVE, + ) { + helper.createTrackerEvent( + eventUid, + enrollmentUid, + programUid, + programStageUid, + orgunitUid, + deleted, + eventDate, + created, + lastUpdated, + status, + ) + } +} diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/analytics/trackerlinelist/TrackerLineListRepositoryIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/analytics/trackerlinelist/TrackerLineListRepositoryIntegrationShould.kt new file mode 100644 index 0000000000..ffb54dc7ba --- /dev/null +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/analytics/trackerlinelist/TrackerLineListRepositoryIntegrationShould.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist + +import com.google.common.truth.Truth.assertThat +import org.hisp.dhis.android.core.utils.integration.mock.BaseMockIntegrationTestFullDispatcher +import org.hisp.dhis.android.core.utils.runner.D2JunitRunner +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(D2JunitRunner::class) +internal class TrackerLineListRepositoryIntegrationShould : BaseMockIntegrationTestFullDispatcher() { + + @Test + fun evaluate_tracker_visualization() { + val result = d2.analyticsModule().trackerLineList() + .withTrackerVisualization("s85urBIkN0z") + .blockingEvaluate() + + val rows = result.getOrThrow().rows + assertThat(rows.size).isEqualTo(2) + } +} diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/arch/db/access/internal/DatabaseImportExportFromDatabaseAssetsMockIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/arch/db/access/internal/DatabaseImportExportFromDatabaseAssetsMockIntegrationShould.kt index 0ceef2c025..9ad75f5f0d 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/arch/db/access/internal/DatabaseImportExportFromDatabaseAssetsMockIntegrationShould.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/arch/db/access/internal/DatabaseImportExportFromDatabaseAssetsMockIntegrationShould.kt @@ -27,118 +27,172 @@ */ package org.hisp.dhis.android.core.arch.db.access.internal -// @RunWith(D2JunitRunner::class) -class DatabaseImportExportFromDatabaseAssetsMockIntegrationShould { +import com.google.common.truth.Truth.assertThat +import org.hisp.dhis.android.core.maintenance.D2Error +import org.hisp.dhis.android.core.maintenance.D2ErrorCode +import org.hisp.dhis.android.core.util.deleteIfExists +import org.hisp.dhis.android.core.utils.integration.mock.BaseMockIntegrationTest +import org.hisp.dhis.android.core.utils.integration.mock.MockIntegrationTestDatabaseContent +import org.hisp.dhis.android.core.utils.runner.D2JunitRunner +import org.junit.AfterClass +import org.junit.Assert.fail +import org.junit.Before +import org.junit.BeforeClass +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(D2JunitRunner::class) +class DatabaseImportExportFromDatabaseAssetsMockIntegrationShould : BaseMockIntegrationTest() { + + companion object { + + const val username = "android" + const val password = "Android123" + const val host = "localhost" + const val port = 60809 + const val serverUrl = "http://$host:$port" - /*companion object LocalAnalyticsAggregatedLargeDataMockIntegrationShould { - - val context = InstrumentationRegistry.getInstrumentation().context - val server = Dhis2MockServer(60809) val importer = TestDatabaseImporter() - const val expectedDatabaseName = "localhost-60809_android_unencrypted.db" - const val serverUrl = "http://localhost:60809/" - @BeforeClass @JvmStatic fun setUpClass() { - server.setRequestDispatcher() + setUpClass(MockIntegrationTestDatabaseContent.DatabaseImportExport, port) + dhis2MockServer.setRequestDispatcher() } @AfterClass @JvmStatic fun tearDownClass() { - server.shutdown() + dhis2MockServer.shutdown() } } - @After - fun tearDown() { - context.deleteDatabase(expectedDatabaseName) - context.databaseList().forEach { dbName -> context.deleteDatabase(dbName) } + @Before + fun setUp() { + if (d2.userModule().blockingIsLogged()) { + d2.userModule().blockingLogOut() + } } @Test - fun import_database_when_not_logged() { - importer.copyDatabaseFromAssets() - - val d2 = D2Factory.forNewDatabase() - - d2.maintenanceModule().databaseImportExport().importDatabase(importer.databaseFile(context)) - - d2.userModule().blockingLogIn("android", "Android123", serverUrl) - - assertThat(d2.programModule().programs().blockingCount()).isEqualTo(2) - } - - @Test(expected = D2Error::class) fun import_fail_when_logged_in() { - importer.copyDatabaseFromAssets() - - val d2 = D2Factory.forNewDatabase() - - d2.userModule().blockingLogIn("other", "Pw1010", serverUrl) - - d2.maintenanceModule().databaseImportExport().importDatabase(importer.databaseFile(context)) + d2.userModule().blockingLogIn("other_user", "other_password", serverUrl) + + try { + d2.maintenanceModule().databaseImportExport().importDatabase(importer.validDatabaseFile(d2.context())) + fail("It should throw an error") + } catch (e: D2Error) { + assertThat(e.errorCode()).isEqualTo(D2ErrorCode.DATABASE_IMPORT_LOGOUT_FIRST) + } finally { + d2.userModule().accountManager().deleteCurrentAccount() + } } - @Test(expected = D2Error::class) - fun import_fail_when_database_exists() { - importer.copyDatabaseFromAssets(expectedDatabaseName) - - val d2 = D2Factory.forNewDatabase() + @Test + fun import_fail_when_account_exists() { + d2.userModule().blockingLogIn(username, password, serverUrl) + d2.userModule().blockingLogOut() - d2.maintenanceModule().databaseImportExport().importDatabase(importer.databaseFile(context, expectedDatabaseName)) + try { + d2.maintenanceModule().databaseImportExport().importDatabase(importer.validDatabaseFile(d2.context())) + fail("It should throw an error") + } catch (e: D2Error) { + assertThat(e.errorCode()).isEqualTo(D2ErrorCode.DATABASE_IMPORT_ALREADY_EXISTS) + } finally { + d2.userModule().blockingLogIn(username, password, serverUrl) + d2.userModule().accountManager().deleteCurrentAccount() + } } @Test - fun export_when_logged() { - val d2 = D2Factory.forNewDatabase() + fun import_fail_when_invalid_database_file() { + d2.userModule().blockingLogIn(username, password, serverUrl) + d2.userModule().blockingLogOut() - d2.userModule().blockingLogIn("android", "Pw1010", serverUrl) + try { + d2.maintenanceModule().databaseImportExport().importDatabase(importer.invalidDatabaseFile(d2.context())) + fail("It should throw an error") + } catch (e: D2Error) { + assertThat(e.errorCode()).isEqualTo(D2ErrorCode.DATABASE_IMPORT_INVALID_FILE) + } finally { + d2.userModule().blockingLogIn(username, password, serverUrl) + d2.userModule().accountManager().deleteCurrentAccount() + } + } - val exportedFile = d2.maintenanceModule().databaseImportExport().exportLoggedUserDatabase() + @Test + fun import_fail_when_no_zip_file() { + d2.userModule().blockingLogIn(username, password, serverUrl) + d2.userModule().blockingLogOut() - assertThat(exportedFile.path).isEqualTo("/data/user/0/org.hisp.dhis.android.test/databases/export-database.db") + try { + d2.maintenanceModule().databaseImportExport().importDatabase(importer.noZipFile(d2.context())) + fail("It should throw an error") + } catch (e: D2Error) { + assertThat(e.errorCode()).isEqualTo(D2ErrorCode.DATABASE_IMPORT_FAILED) + } finally { + d2.userModule().blockingLogIn(username, password, serverUrl) + d2.userModule().accountManager().deleteCurrentAccount() + } } - @Test(expected = D2Error::class) + @Test fun export_fail_when_not_logged() { - val d2 = D2Factory.forNewDatabase() - d2.maintenanceModule().databaseImportExport().exportLoggedUserDatabase() + try { + d2.maintenanceModule().databaseImportExport().exportLoggedUserDatabase() + fail("It should throw an error") + } catch (e: D2Error) { + assertThat(e.errorCode()).isEqualTo(D2ErrorCode.DATABASE_EXPORT_LOGIN_FIRST) + } } @Test fun export_and_reimport() { - var d2 = D2Factory.forNewDatabase() + test_export_and_reimport(beforeExport = {}) + } - d2.userModule().blockingLogIn("android", "Android123", serverUrl) + @Test + fun export_and_reimport_encrypted() { + test_export_and_reimport(beforeExport = { + // Change encryption + d2.d2DIComponent.multiUserDatabaseManager.changeEncryptionIfRequired( + d2.d2DIComponent.credentialsSecureStore.get(), + encrypt = true, + ) + }) + } + private fun test_export_and_reimport(beforeExport: () -> Unit) { + d2.userModule().blockingLogIn(username, password, serverUrl) d2.metadataModule().blockingDownload() - assertThat(d2.programModule().programs().blockingCount()).isEqualTo(2) + assertThat(d2.programModule().programs().blockingCount()).isEqualTo(3) - val systemInfoWithExpectedContextPath = d2.systemInfoModule().systemInfo().blockingGet() - .toBuilder().contextPath(serverUrl).build() + beforeExport() - d2.databaseAdapter().delete(SystemInfoTableInfo.TABLE_INFO.name()) - d2.databaseAdapter().insert(SystemInfoTableInfo.TABLE_INFO.name(), null, - systemInfoWithExpectedContextPath.toContentValues()) + val exportedFile = d2.maintenanceModule().databaseImportExport().exportLoggedUserDatabase() + d2.userModule().accountManager().deleteCurrentAccount() - val exportedFile = d2.maintenanceModule().databaseImportExport().exportLoggedUserDatabase() + val fileMetadata = d2.maintenanceModule().databaseImportExport().importDatabase(exportedFile) - d2.userModule().blockingLogOut() + assertThat(fileMetadata.username).isEqualTo(username) + assertThat(fileMetadata.serverUrl).isEqualTo(serverUrl) - context.deleteDatabase(expectedDatabaseName) + try { + d2.userModule().blockingLogIn(username, "other-password", serverUrl) + fail("It should throw an error") + } catch (e: RuntimeException) { + assertThat((e.cause as D2Error).errorCode()).isEqualTo(D2ErrorCode.BAD_CREDENTIALS) + } - // We won't need to create a new D2 when we support database deletion (multi-user) - d2 = D2Factory.forNewDatabase() + d2.userModule().blockingLogIn(username, password, serverUrl) - d2.maintenanceModule().databaseImportExport().importDatabase(exportedFile) + assertThat(d2.programModule().programs().blockingCount()).isEqualTo(3) - d2.userModule().blockingLogIn("android", "Android123", serverUrl) + d2.userModule().accountManager().deleteCurrentAccount() - assertThat(d2.programModule().programs().blockingCount()).isEqualTo(2) - }*/ + exportedFile.deleteIfExists() + } } diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/arch/db/access/internal/TestDatabaseImporter.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/arch/db/access/internal/TestDatabaseImporter.kt index 38c596a871..4ea14052e8 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/arch/db/access/internal/TestDatabaseImporter.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/arch/db/access/internal/TestDatabaseImporter.kt @@ -28,44 +28,36 @@ package org.hisp.dhis.android.core.arch.db.access.internal import android.content.Context -import androidx.test.platform.app.InstrumentationRegistry -import java.io.* +import java.io.File class TestDatabaseImporter { - fun copyDatabaseFromAssets(filename: String = FILESYSTEM_DB_NAME) { - val context = InstrumentationRegistry.getInstrumentation().context - val databasePath = context.applicationInfo?.dataDir + "/databases" - val outputFile = databaseFile(context, filename) - if (outputFile.exists()) { - return - } - val inputStream = context.assets.open("databases/$ASSETS_DB_NAME") - val outputStream = FileOutputStream("$databasePath/$filename") - writeExtractedFileToDisk(inputStream, outputStream) + fun validDatabaseFile(context: Context): File { + return copyAssetsFileToFilesDir(context, VALID_DATABASE_FILE) } - @Throws(IOException::class) - private fun writeExtractedFileToDisk(input: InputStream, output: OutputStream) { - val buffer = ByteArray(1024) - var length: Int - length = input.read(buffer) - while (length > 0) { - output.write(buffer, 0, length) - length = input.read(buffer) - } - output.flush() - output.close() - input.close() + fun invalidDatabaseFile(context: Context): File { + return copyAssetsFileToFilesDir(context, INVALID_DATABASE_FILE) } - fun databaseFile(context: Context, filename: String = FILESYSTEM_DB_NAME): File { - val databasePath = context.applicationInfo?.dataDir + "/databases" - return File("$databasePath/$filename") + fun noZipFile(context: Context): File { + return copyAssetsFileToFilesDir(context, NO_ZIP_FILE) } + private fun copyAssetsFileToFilesDir(context: Context, filename: String): File { + val outputFile = context.filesDir.resolve(filename).also { it.delete() } + + context.assets.open("databases/$filename").use { fis -> + outputFile.outputStream().use { fos -> + fis.copyTo(fos) + } + } + + return outputFile + } companion object { - const val ASSETS_DB_NAME = "test-database.db" - const val FILESYSTEM_DB_NAME = "test-database.db" + const val VALID_DATABASE_FILE = "export-database.zip" + const val INVALID_DATABASE_FILE = "corrupted-database.zip" + const val NO_ZIP_FILE = "test-database.db" } } diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/arch/repositories/collection/PagingMockIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/arch/repositories/collection/PagingMockIntegrationShould.kt index 46dc2839fe..5b73236362 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/arch/repositories/collection/PagingMockIntegrationShould.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/arch/repositories/collection/PagingMockIntegrationShould.kt @@ -29,18 +29,22 @@ package org.hisp.dhis.android.core.arch.repositories.collection import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.paging.PagedList +import androidx.paging.testing.asSnapshot import com.jraska.livedata.TestObserver +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.hisp.dhis.android.core.arch.db.querybuilders.internal.OrderByClauseBuilder import org.hisp.dhis.android.core.arch.repositories.scope.RepositoryScope import org.hisp.dhis.android.core.category.CategoryOption import org.hisp.dhis.android.core.category.internal.CategoryOptionStore import org.hisp.dhis.android.core.utils.integration.mock.BaseMockIntegrationTestFullDispatcher +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.TestRule +@OptIn(ExperimentalCoroutinesApi::class) class PagingMockIntegrationShould : BaseMockIntegrationTestFullDispatcher() { @get:Rule @@ -152,4 +156,88 @@ class PagingMockIntegrationShould : BaseMockIntegrationTestFullDispatcher() { .assertValue { pagedList: PagedList -> pagedList[4]!!.displayName() == "Male" } .assertValue { pagedList: PagedList -> pagedList[5]!!.displayName() == "MCH Aides" } } + + @Test + fun get_pagingData_with_initial_objects_with_default_order_considering_prefetch_distance() = runTest { + val items = d2.categoryModule().categoryOptions().getPagingData(2) + + val itemsSnapshot: List = items.asSnapshot() + + assertEquals(itemsSnapshot.size, 6) + assertEquals(itemsSnapshot[0], allValues[0]) + assertEquals(itemsSnapshot[1], allValues[1]) + assertEquals(itemsSnapshot[2], allValues[2]) + assertEquals(itemsSnapshot[3], allValues[3]) + assertEquals(itemsSnapshot[4], allValues[4]) + assertEquals(itemsSnapshot[5], allValues[5]) + } + + @Test + fun get_pagingData_with_initial_objects_ordered_by_display_name_asc() = runTest { + val items = d2.categoryModule().categoryOptions() + .orderByDisplayName(RepositoryScope.OrderByDirection.ASC) + .getPagingData(2) + + val snapshot = items.asSnapshot() + + assertEquals(snapshot.size, 6) + assertEquals(snapshot[0].displayName(), "At PHU") + assertEquals(snapshot[1].displayName(), "Female") + assertEquals(snapshot[2].displayName(), "In Community") + assertEquals(snapshot[3].displayName(), "MCH Aides") + assertEquals(snapshot[4].displayName(), "Male") + assertEquals(snapshot[5].displayName(), "SECHN") + } + + @Test + fun get_pagingData_with_initial_objects_ordered_by_display_name_desc() = runTest { + val items = d2.categoryModule().categoryOptions() + .orderByDisplayName(RepositoryScope.OrderByDirection.DESC) + .getPagingData(2) + + val snapshot = items.asSnapshot() + + assertEquals(snapshot.size, 6) + assertEquals(snapshot[0].displayName(), "default display name") + assertEquals(snapshot[1].displayName(), "Trained TBA") + assertEquals(snapshot[2].displayName(), "SECHN") + assertEquals(snapshot[3].displayName(), "Male") + assertEquals(snapshot[4].displayName(), "MCH Aides") + assertEquals(snapshot[5].displayName(), "In Community") + } + + @Test + fun get_pagingData_with_initial_objects_ordered_by_description_desc() = runTest { + val items = d2.categoryModule().categoryOptions() + .orderByDescription(RepositoryScope.OrderByDirection.DESC) + .getPagingData(2) + + val snapshot = items.asSnapshot() + + assertEquals(snapshot.size, 6) + assertEquals(snapshot[0].displayName(), "default display name") + assertEquals(snapshot[1].displayName(), "Female") + assertEquals(snapshot[2].displayName(), "Male") + assertEquals(snapshot[3].displayName(), "In Community") + assertEquals(snapshot[4].displayName(), "At PHU") + assertEquals(snapshot[5].displayName(), "MCH Aides") + } + + @Test + fun get_pagingData_with_initial_objects_ordered_by_description_and_display_name_desc() = runTest { + val items = d2.categoryModule().categoryOptions() + .orderByDescription(RepositoryScope.OrderByDirection.DESC) + .orderByDisplayName(RepositoryScope.OrderByDirection.ASC) + .getPagingData(2) + + val snapshot = items.asSnapshot() + + assertEquals(snapshot.size, 6) + assertEquals(snapshot[0].displayName(), "default display name") + assertEquals(snapshot[1].displayName(), "At PHU") + assertEquals(snapshot[2].displayName(), "Female") + assertEquals(snapshot[3].displayName(), "In Community") + assertEquals(snapshot[4].displayName(), "Male") + assertEquals(snapshot[5].displayName(), "MCH Aides") + } } diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/arch/repositories/collection/ReadOnlyIdentifiableCollectionRepositoryImplIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/arch/repositories/collection/ReadOnlyIdentifiableCollectionRepositoryImplIntegrationShould.kt index 12c2fc1f05..58a3fbcb34 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/arch/repositories/collection/ReadOnlyIdentifiableCollectionRepositoryImplIntegrationShould.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/arch/repositories/collection/ReadOnlyIdentifiableCollectionRepositoryImplIntegrationShould.kt @@ -83,6 +83,38 @@ class ReadOnlyIdentifiableCollectionRepositoryImplIntegrationShould : BaseMockIn .isEqualTo(RelationshipTypeSamples.TET_FOR_RELATIONSHIP_3_UID) } + @Test + fun get_tracker_data_view_from_relationship_constraint() { + val relationshipType = relationshipTypeCollectionRepository + .byConstraint( + RelationshipEntityType.TRACKED_ENTITY_INSTANCE, + RelationshipTypeSamples.TET_FOR_RELATIONSHIP_3_UID, + RelationshipConstraintType.FROM, + ) + .withConstraints() + .blockingGet() + + val fromTrackerDataView = relationshipType[0].fromConstraint()?.trackerDataView() + val toTrackerDataView = relationshipType[0].toConstraint()?.trackerDataView() + + assertThat(fromTrackerDataView?.attributes()?.get(0)) + .isEqualTo(RelationshipTypeSamples.ATTRIBUTE_1) + assertThat(fromTrackerDataView?.attributes()?.get(1)) + .isEqualTo(RelationshipTypeSamples.ATTRIBUTE_2) + assertThat(fromTrackerDataView?.attributes()?.get(2)) + .isEqualTo(RelationshipTypeSamples.ATTRIBUTE_3) + + assertThat(fromTrackerDataView?.dataElements()?.get(0)) + .isEqualTo(RelationshipTypeSamples.DATA_ELEMENT_1) + assertThat(fromTrackerDataView?.dataElements()?.get(1)) + .isEqualTo(RelationshipTypeSamples.DATA_ELEMENT_2) + + assertThat(toTrackerDataView?.attributes()?.get(0)) + .isEqualTo(RelationshipTypeSamples.ATTRIBUTE_3) + + assertThat(toTrackerDataView?.dataElements()?.isEmpty()).isTrue() + } + @Test fun get_relationship_2_from_object_repository_without_children() { val relationshipType = diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/arch/repositories/collection/RelationshipTypeAsserts.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/arch/repositories/collection/RelationshipTypeAsserts.kt index ea3c3fa493..9ab2cababa 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/arch/repositories/collection/RelationshipTypeAsserts.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/arch/repositories/collection/RelationshipTypeAsserts.kt @@ -52,14 +52,34 @@ internal object RelationshipTypeAsserts : BaseRealIntegrationTest() { fun assertTypeWithConstraints(target: RelationshipType, reference: RelationshipType) { val prunedTarget = target.toBuilder() .id(null) - .toConstraint(target.toConstraint()?.toBuilder()?.id(null)?.build()) - .fromConstraint(target.fromConstraint()?.toBuilder()?.id(null)?.build()) + .toConstraint( + target.toConstraint()?.toBuilder()?.id(null) + ?.trackerDataView( + target.toConstraint()?.trackerDataView()?.toBuilder()?.id(null)?.build(), + )?.build(), + ) + .fromConstraint( + target.fromConstraint()?.toBuilder()?.id(null) + ?.trackerDataView( + target.fromConstraint()?.trackerDataView()?.toBuilder()?.id(null)?.build(), + )?.build(), + ) .build() val prunedReference = reference.toBuilder() .id(null) - .toConstraint(reference.toConstraint()?.toBuilder()?.id(null)?.build()) - .fromConstraint(reference.fromConstraint()?.toBuilder()?.id(null)?.build()) + .toConstraint( + reference.toConstraint()?.toBuilder()?.id(null) + ?.trackerDataView( + reference.toConstraint()?.trackerDataView()?.toBuilder()?.id(null)?.build(), + )?.build(), + ) + .fromConstraint( + reference.fromConstraint()?.toBuilder()?.id(null) + ?.trackerDataView( + reference.fromConstraint()?.trackerDataView()?.toBuilder()?.id(null)?.build(), + )?.build(), + ) .build() assertThat(prunedTarget).isEqualTo(prunedReference) diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/common/objectstyle/internal/TablesWithStyleShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/common/objectstyle/internal/TablesWithStyleShould.kt new file mode 100644 index 0000000000..e794b4ecc9 --- /dev/null +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/common/objectstyle/internal/TablesWithStyleShould.kt @@ -0,0 +1,95 @@ +/* + * 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.common.objectstyle.internal + +import android.database.Cursor +import org.hisp.dhis.android.core.common.NameableWithStyleColumns +import org.hisp.dhis.android.core.utils.integration.mock.TestDatabaseAdapterFactory +import org.hisp.dhis.android.core.utils.runner.D2JunitRunner +import org.junit.Assert.fail +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(D2JunitRunner::class) +class TablesWithStyleShould { + + private val databaseAdapter = TestDatabaseAdapterFactory.get() + + private val excludedTables: List = listOf() + + @Test + fun check_content_of_styled_tables() { + val tableListCursor = databaseAdapter.rawQuery("SELECT name FROM sqlite_master WHERE type='table'") + val tableList = readStringColumn(tableListCursor, "name") + + val tablesWithStyle = tableList + .filterNot { excludedTables.contains(it) } + .filter { table -> + val columnListCursor = databaseAdapter.rawQuery("PRAGMA table_info($table);") + val columns = readStringColumn(columnListCursor, "name") + + columns.contains(NameableWithStyleColumns.ICON) + } + + val missingTablesInList = tablesWithStyle.minus(TableWithObjectStyle.allTableNames.toSet()) + val exceedingTablesInList = TableWithObjectStyle.allTableNames.minus(tablesWithStyle.toSet()) + + if (missingTablesInList.isNotEmpty() || exceedingTablesInList.isNotEmpty()) { + missingTablesInList.forEach { + println( + "Table $it is not in TableWithStyle list. " + + "Add it to the list or to the excluded tables in the test", + ) + } + + exceedingTablesInList.forEach { + println( + "Table $it is in the TableWithStyle list but has no style column. " + + "Remove it from the list.", + ) + } + + fail("Tables with style don't match with tables in TableWithStyle list.") + } + } + + private fun readStringColumn(cursor: Cursor, column: String): List { + val result = mutableListOf() + cursor.use { c -> + if (c.count > 0) { + c.moveToFirst() + do { + val tableIdx = c.getColumnIndex(column) + val tableName = c.getString(tableIdx) + result.add(tableName) + } while (c.moveToNext()) + } + } + return result + } +} diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/domain/metadata/MetadataCallMockIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/domain/metadata/MetadataCallMockIntegrationShould.kt index 09efa1d909..9b9bf758e5 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/domain/metadata/MetadataCallMockIntegrationShould.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/domain/metadata/MetadataCallMockIntegrationShould.kt @@ -33,6 +33,7 @@ import org.hisp.dhis.android.core.category.Category import org.hisp.dhis.android.core.constant.Constant import org.hisp.dhis.android.core.dataset.DataSet import org.hisp.dhis.android.core.expressiondimensionitem.ExpressionDimensionItem +import org.hisp.dhis.android.core.icon.CustomIcon import org.hisp.dhis.android.core.indicator.Indicator import org.hisp.dhis.android.core.legendset.LegendSet import org.hisp.dhis.android.core.organisationunit.OrganisationUnit @@ -45,6 +46,7 @@ import org.hisp.dhis.android.core.usecase.stock.StockUseCase import org.hisp.dhis.android.core.user.User import org.hisp.dhis.android.core.utils.integration.mock.BaseMockIntegrationTestEmptyDispatcher import org.hisp.dhis.android.core.utils.runner.D2JunitRunner +import org.hisp.dhis.android.core.visualization.TrackerVisualization import org.hisp.dhis.android.core.visualization.Visualization import org.junit.After import org.junit.Test @@ -64,7 +66,7 @@ class MetadataCallMockIntegrationShould : BaseMockIntegrationTestEmptyDispatcher testObserver.awaitTerminalEvent() - testObserver.assertValueCount(16) + testObserver.assertValueCount(18) val values = testObserver.values() @@ -87,11 +89,13 @@ class MetadataCallMockIntegrationShould : BaseMockIntegrationTestEmptyDispatcher DataSet::class, Category::class, Visualization::class, + TrackerVisualization::class, ProgramIndicator::class, Indicator::class, LegendSet::class, Attribute::class, ExpressionDimensionItem::class, + CustomIcon::class, ).map { it.java.simpleName }, ) diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/fileresource/internal/BaseFileResourceRoutineIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/fileresource/internal/BaseFileResourceRoutineIntegrationShould.kt index 680e10ecad..4fa5bb59aa 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/fileresource/internal/BaseFileResourceRoutineIntegrationShould.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/fileresource/internal/BaseFileResourceRoutineIntegrationShould.kt @@ -31,6 +31,7 @@ package org.hisp.dhis.android.core.fileresource.internal import org.hisp.dhis.android.core.category.internal.CategoryComboStoreImpl import org.hisp.dhis.android.core.dataelement.internal.DataElementStoreImpl import org.hisp.dhis.android.core.event.internal.EventStoreImpl +import org.hisp.dhis.android.core.icon.internal.CustomIconStoreImpl import org.hisp.dhis.android.core.option.internal.OptionSetStoreImpl import org.hisp.dhis.android.core.organisationunit.internal.OrganisationUnitStoreImpl import org.hisp.dhis.android.core.program.internal.ProgramStageStoreImpl @@ -51,6 +52,7 @@ internal open class BaseFileResourceRoutineIntegrationShould : BaseMockIntegrati protected val trackedEntityDataValueStore = TrackedEntityDataValueStoreImpl(databaseAdapter) protected val trackedEntityAttributeValueStore = TrackedEntityAttributeValueStoreImpl(databaseAdapter) protected val fileResourceStore = FileResourceStoreImpl(d2.databaseAdapter()) + protected val customIconStore = CustomIconStoreImpl(d2.databaseAdapter()) private val optionSetStore = OptionSetStoreImpl(d2.databaseAdapter()) // Metadata stores diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceCallRealIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceCallRealIntegrationShould.kt index 8d6b843e06..1a07e1286a 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceCallRealIntegrationShould.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceCallRealIntegrationShould.kt @@ -33,7 +33,7 @@ import org.hisp.dhis.android.core.common.State import org.hisp.dhis.android.core.common.ValueType import org.hisp.dhis.android.core.data.server.RealServerMother import org.hisp.dhis.android.core.event.EventCreateProjection -import org.hisp.dhis.android.core.fileresource.FileResourceDomainType +import org.hisp.dhis.android.core.fileresource.FileResourceDataDomainType import org.hisp.dhis.android.core.fileresource.FileResourceElementType import java.io.File import java.util.* @@ -122,7 +122,7 @@ class FileResourceCallRealIntegrationShould : BaseRealIntegrationTest() { .byProgramUid("eBAyeGv0exc").limit(5).blockingDownload() d2.fileResourceModule().fileResourceDownloader() - .byDomainType().eq(FileResourceDomainType.TRACKER) + .byDataDomainType().eq(FileResourceDataDomainType.TRACKER) .byElementType().eq(FileResourceElementType.DATA_ELEMENT) .blockingDownload() diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceRoutineShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceRoutineShould.kt index 9cf8b5a7e6..a77ac8c8ef 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceRoutineShould.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceRoutineShould.kt @@ -44,6 +44,7 @@ internal class FileResourceRoutineShould : BaseFileResourceRoutineIntegrationSho dataValueCollectionRepository = d2.dataValueModule().dataValues(), fileResourceCollectionRepository = d2.fileResourceModule().fileResources(), fileResourceStore = fileResourceStore, + customIconStore = customIconStore, trackedEntityAttributeCollectionRepository = d2.trackedEntityModule().trackedEntityAttributes(), trackedEntityAttributeValueCollectionRepository = d2.trackedEntityModule().trackedEntityAttributeValues(), trackedEntityDataValueCollectionRepository = d2.trackedEntityModule().trackedEntityDataValues(), diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/icon/internal/CustomIconStoreIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/icon/internal/CustomIconStoreIntegrationShould.kt new file mode 100644 index 0000000000..0bf7b94298 --- /dev/null +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/icon/internal/CustomIconStoreIntegrationShould.kt @@ -0,0 +1,55 @@ +/* + * 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.icon.internal + +import org.hisp.dhis.android.core.common.ObjectWithUid +import org.hisp.dhis.android.core.data.database.ObjectWithoutUidStoreAbstractIntegrationShould +import org.hisp.dhis.android.core.data.icon.CustomIconSamples +import org.hisp.dhis.android.core.icon.CustomIcon +import org.hisp.dhis.android.core.icon.CustomIconTableInfo +import org.hisp.dhis.android.core.utils.integration.mock.TestDatabaseAdapterFactory +import org.hisp.dhis.android.core.utils.runner.D2JunitRunner +import org.junit.runner.RunWith + +@RunWith(D2JunitRunner::class) +class CustomIconStoreIntegrationShould : + ObjectWithoutUidStoreAbstractIntegrationShould( + CustomIconStoreImpl(TestDatabaseAdapterFactory.get()), + CustomIconTableInfo.TABLE_INFO, + TestDatabaseAdapterFactory.get(), + ) { + override fun buildObject(): CustomIcon { + return CustomIconSamples.getCustomIcon() + } + + override fun buildObjectToUpdate(): CustomIcon { + return CustomIconSamples.getCustomIcon().toBuilder() + .fileResource(ObjectWithUid.create("otherResourceUid")) + .build() + } +} diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/program/programindicatorengine/BaseTrackerDataIntegrationHelper.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/program/programindicatorengine/BaseTrackerDataIntegrationHelper.kt index 76253ad9be..f986e2dbe8 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/program/programindicatorengine/BaseTrackerDataIntegrationHelper.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/program/programindicatorengine/BaseTrackerDataIntegrationHelper.kt @@ -29,6 +29,7 @@ package org.hisp.dhis.android.core.program.programindicatorengine import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter import org.hisp.dhis.android.core.common.AggregationType +import org.hisp.dhis.android.core.common.AnalyticsType import org.hisp.dhis.android.core.common.ObjectWithUid import org.hisp.dhis.android.core.enrollment.Enrollment import org.hisp.dhis.android.core.enrollment.EnrollmentStatus @@ -151,8 +152,9 @@ open class BaseTrackerDataIntegrationHelper(private val databaseAdapter: Databas programIndicatorUid: String, programUid: String, expression: String, + analyticsType: AnalyticsType, ) { - insertProgramIndicator(programIndicatorUid, programUid, expression, AggregationType.AVERAGE) + insertProgramIndicator(programIndicatorUid, programUid, expression, AggregationType.AVERAGE, analyticsType) } fun insertProgramIndicator( @@ -160,9 +162,16 @@ open class BaseTrackerDataIntegrationHelper(private val databaseAdapter: Databas programUid: String, expression: String, aggregationType: AggregationType, + analyticsType: AnalyticsType = AnalyticsType.ENROLLMENT, ) { - val programIndicator = ProgramIndicator.builder().uid(programIndicatorUid) - .program(ObjectWithUid.create(programUid)).expression(expression).aggregationType(aggregationType).build() + val programIndicator = ProgramIndicator.builder() + .uid(programIndicatorUid) + .name(programIndicatorUid) + .displayName(programIndicatorUid) + .analyticsType(analyticsType) + .program(ObjectWithUid.create(programUid)).expression(expression) + .aggregationType(aggregationType) + .build() setProgramIndicator(programIndicator) } diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/relationship/internal/RelationshipConstraintStoreIntegrationShould.java b/core/src/androidTest/java/org/hisp/dhis/android/core/relationship/internal/RelationshipConstraintStoreIntegrationShould.kt similarity index 64% rename from core/src/androidTest/java/org/hisp/dhis/android/core/relationship/internal/RelationshipConstraintStoreIntegrationShould.java rename to core/src/androidTest/java/org/hisp/dhis/android/core/relationship/internal/RelationshipConstraintStoreIntegrationShould.kt index 7862a08607..6dcbaab026 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/relationship/internal/RelationshipConstraintStoreIntegrationShould.java +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/relationship/internal/RelationshipConstraintStoreIntegrationShould.kt @@ -25,36 +25,31 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +package org.hisp.dhis.android.core.relationship.internal -package org.hisp.dhis.android.core.relationship.internal; +import org.hisp.dhis.android.core.data.database.ObjectWithoutUidStoreAbstractIntegrationShould +import org.hisp.dhis.android.core.data.relationship.RelationshipConstraintSamples.relationshipConstraint +import org.hisp.dhis.android.core.relationship.RelationshipConstraint +import org.hisp.dhis.android.core.relationship.RelationshipConstraintTableInfo +import org.hisp.dhis.android.core.relationship.RelationshipEntityType +import org.hisp.dhis.android.core.utils.integration.mock.TestDatabaseAdapterFactory +import org.hisp.dhis.android.core.utils.runner.D2JunitRunner +import org.junit.runner.RunWith -import org.hisp.dhis.android.core.data.database.ObjectWithoutUidStoreAbstractIntegrationShould; -import org.hisp.dhis.android.core.data.relationship.RelationshipConstraintSamples; -import org.hisp.dhis.android.core.relationship.RelationshipConstraint; -import org.hisp.dhis.android.core.relationship.RelationshipConstraintTableInfo; -import org.hisp.dhis.android.core.relationship.RelationshipEntityType; -import org.hisp.dhis.android.core.utils.integration.mock.TestDatabaseAdapterFactory; -import org.hisp.dhis.android.core.utils.runner.D2JunitRunner; -import org.junit.runner.RunWith; - -@RunWith(D2JunitRunner.class) -public class RelationshipConstraintStoreIntegrationShould extends - ObjectWithoutUidStoreAbstractIntegrationShould { - - public RelationshipConstraintStoreIntegrationShould() { - super(new RelationshipConstraintStoreImpl(TestDatabaseAdapterFactory.get()), - RelationshipConstraintTableInfo.TABLE_INFO, TestDatabaseAdapterFactory.get()); - } - - @Override - protected RelationshipConstraint buildObject() { - return RelationshipConstraintSamples.getRelationshipConstraint(); +@RunWith(D2JunitRunner::class) +class RelationshipConstraintStoreIntegrationShould : + ObjectWithoutUidStoreAbstractIntegrationShould( + RelationshipConstraintStoreImpl(TestDatabaseAdapterFactory.get()), + RelationshipConstraintTableInfo.TABLE_INFO, + TestDatabaseAdapterFactory.get(), + ) { + override fun buildObject(): RelationshipConstraint { + return relationshipConstraint } - @Override - protected RelationshipConstraint buildObjectToUpdate() { - return RelationshipConstraintSamples.getRelationshipConstraint().toBuilder() - .relationshipEntity(RelationshipEntityType.PROGRAM_INSTANCE) - .build(); + override fun buildObjectToUpdate(): RelationshipConstraint { + return relationshipConstraint.toBuilder() + .relationshipEntity(RelationshipEntityType.PROGRAM_INSTANCE) + .build() } -} \ No newline at end of file +} diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/settings/SettingsModuleMockIntegrationShould.java b/core/src/androidTest/java/org/hisp/dhis/android/core/settings/SettingsModuleMockIntegrationShould.java index 66ea9e05e7..637746339d 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/settings/SettingsModuleMockIntegrationShould.java +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/settings/SettingsModuleMockIntegrationShould.java @@ -43,7 +43,7 @@ public class SettingsModuleMockIntegrationShould extends BaseMockIntegrationTest @Test public void allow_access_to_system_setting() { List systemSettings = d2.settingModule().systemSetting().blockingGet(); - assertThat(systemSettings.size()).isEqualTo(2); + assertThat(systemSettings.size()).isEqualTo(3); } @Test @@ -56,6 +56,10 @@ public void allow_access_to_system_setting_filtered_by_key() { List systemSettingsStyle = d2.settingModule().systemSetting().byKey() .eq(SystemSetting.SystemSettingKey.STYLE).blockingGet(); assertThat(systemSettingsStyle.get(0).value()).isEqualTo("light_blue/light_blue.css"); + + List systemSettingsDefaultBaseMap = d2.settingModule().systemSetting().byKey() + .eq(SystemSetting.SystemSettingKey.DEFAULT_BASE_MAP).blockingGet(); + assertThat(systemSettingsDefaultBaseMap.get(0).value()).isEqualTo("keyDefaultBaseMap"); } @Test @@ -68,6 +72,11 @@ public void allow_access_to_system_setting_filtered_by_value() { List systemSettingsStyle = d2.settingModule().systemSetting().byValue() .eq("light_blue/light_blue.css").blockingGet(); assertThat(systemSettingsStyle.get(0).key()).isEqualTo(SystemSetting.SystemSettingKey.STYLE); + + List systemSettingsDefaultBaseMap = d2.settingModule().systemSetting().byValue() + .eq("keyDefaultBaseMap").blockingGet(); + assertThat(systemSettingsDefaultBaseMap.get(0).key()) + .isEqualTo(SystemSetting.SystemSettingKey.DEFAULT_BASE_MAP); } @Test @@ -83,4 +92,11 @@ public void allow_access_to_style_settings() { assertThat(systemSetting.key()).isEqualTo(SystemSetting.SystemSettingKey.STYLE); assertThat(systemSetting.value()).isEqualTo("light_blue/light_blue.css"); } + + @Test + public void allow_access_to_default_base_map_settings() { + SystemSetting systemSetting = d2.settingModule().systemSetting().defaultBaseMap().blockingGet(); + assertThat(systemSetting.key()).isEqualTo(SystemSetting.SystemSettingKey.DEFAULT_BASE_MAP); + assertThat(systemSetting.value()).isEqualTo("keyDefaultBaseMap"); + } } \ No newline at end of file diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/systeminfo/SystemInfoModuleMockIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/systeminfo/SystemInfoModuleMockIntegrationShould.kt index 171a05913f..ae73aa6289 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/systeminfo/SystemInfoModuleMockIntegrationShould.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/systeminfo/SystemInfoModuleMockIntegrationShould.kt @@ -35,13 +35,13 @@ class SystemInfoModuleMockIntegrationShould : BaseMockIntegrationTestFullDispatc @Test fun allow_access_to_system_info_user() { val systemInfo = d2.systemInfoModule().systemInfo().blockingGet()!! - assertThat(systemInfo.version()).isEqualTo("2.40.0") + assertThat(systemInfo.version()).isEqualTo("2.41.0") assertThat(systemInfo.systemName()).isEqualTo("DHIS 2 Demo - Sierra Leone") } @Test fun allow_access_to_version_manager() { val version = d2.systemInfoModule().versionManager().getVersion() - assertThat(version).isEqualTo(DHISVersion.V2_40) + assertThat(version).isEqualTo(DHISVersion.V2_41) } } diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/api/BreakTheGlassAPIShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/api/BreakTheGlassAPIShould.kt index d02d2b6be8..73d45169a8 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/api/BreakTheGlassAPIShould.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/api/BreakTheGlassAPIShould.kt @@ -176,7 +176,13 @@ class BreakTheGlassAPIShould : BaseRealIntegrationTest() { } val glassResponse: HttpMessageResponse = - executor.wrap { ownershipService.breakGlass(tei.uid(), program, "Sync") }.getOrThrow() + executor.wrap { + ownershipService.breakGlass( + mapOf(OwnershipService.TRACKED_ENTITY to tei.uid()), + program, + "Sync", + ) + }.getOrThrow() val response2 = postTrackedEntities(tei) assertThat(response2.response()!!.status()).isEqualTo(ImportStatus.SUCCESS) diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceCallBaseMockIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceCallBaseMockIntegrationShould.kt index c5285d2806..b85fcc49c3 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceCallBaseMockIntegrationShould.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceCallBaseMockIntegrationShould.kt @@ -62,6 +62,7 @@ abstract class TrackedEntityInstanceCallBaseMockIntegrationShould : BaseMockInte abstract val teiSingleFile: String abstract val teiWithRemovedDataFile: String abstract val teiWithRelationshipFile: String + abstract val teiAsRelationshipFile: String private lateinit var initSyncParams: SynchronizationSettings private val syncStore = SynchronizationSettingStoreImpl(databaseAdapter) @@ -156,7 +157,7 @@ abstract class TrackedEntityInstanceCallBaseMockIntegrationShould : BaseMockInte fun downloadAndPersistRelatedItems() { dhis2MockServer.enqueueSystemInfoResponse() dhis2MockServer.enqueueMockResponse(teiWithRelationshipFile) - dhis2MockServer.enqueueMockResponse(teiCollectionFile) + dhis2MockServer.enqueueMockResponse(teiAsRelationshipFile) d2.trackedEntityModule().trackedEntityInstanceDownloader() .byProgramUid("IpHINAT79UW") diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceCallNewMockIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceCallNewMockIntegrationShould.kt index 5ed9880fac..51854863a2 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceCallNewMockIntegrationShould.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceCallNewMockIntegrationShould.kt @@ -44,10 +44,11 @@ class TrackedEntityInstanceCallNewMockIntegrationShould : TrackedEntityInstanceC override val exporterVersion = TrackerExporterVersion.V2 override val teiFile = "trackedentity/new_tracker_importer_tracked_entity.json" override val teiCollectionFile = "trackedentity/new_tracker_importer_tracked_entity_collection.json" - override val teiSingleFile = "trackedentity/new_tracker_importer_tracked_entity_single.json" + override val teiSingleFile = teiFile override val teiWithRemovedDataFile = "trackedentity/new_tracker_importer_tracked_entity_with_removed_data_single.json" override val teiWithRelationshipFile = "trackedentity/new_tracker_importer_tracked_entity_with_relationship.json" + override val teiAsRelationshipFile = teiFile override fun parseTrackedEntityInstance(file: String): TrackedEntityInstance { val expectedEventsResponseJson = ResourcesFileReader().getStringFromFile(file) diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceCallOldMockIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceCallOldMockIntegrationShould.kt index e850187165..e7912467f9 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceCallOldMockIntegrationShould.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceCallOldMockIntegrationShould.kt @@ -46,6 +46,7 @@ class TrackedEntityInstanceCallOldMockIntegrationShould : TrackedEntityInstanceC override val teiSingleFile = "trackedentity/tracked_entity_instance_single.json" override val teiWithRemovedDataFile = "trackedentity/tracked_entity_instance_with_removed_data_single.json" override val teiWithRelationshipFile = "trackedentity/tracked_entity_instances_with_relationship.json" + override val teiAsRelationshipFile = teiCollectionFile override fun parseTrackedEntityInstance(file: String): TrackedEntityInstance { val expectedEventsResponseJson = ResourcesFileReader().getStringFromFile(file) diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceDownloadCallEnqueableMockIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceDownloadCallEnqueableMockIntegrationShould.kt index 3d5868147c..38fdab27d7 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceDownloadCallEnqueableMockIntegrationShould.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceDownloadCallEnqueableMockIntegrationShould.kt @@ -46,12 +46,10 @@ class TrackedEntityInstanceDownloadCallEnqueableMockIntegrationShould : BaseMock @Test fun should_continue_on_page_error() { val programTeis = "trackedentity/new_tracker_importer_tracked_entities.json" - val relationshipTei = "trackedentity/new_tracker_importer_tracked_entity_collection.json" dhis2MockServer.enqueueSystemInfoResponse() dhis2MockServer.enqueueMockResponse(403) dhis2MockServer.enqueueMockResponse(programTeis) - dhis2MockServer.enqueueMockResponse(relationshipTei) d2.trackedEntityModule().trackedEntityInstanceDownloader().blockingDownload() diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryCollectionRepositoryMockIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryCollectionRepositoryMockIntegrationShould.kt index 7a9d3545e2..7f6cc8423b 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryCollectionRepositoryMockIntegrationShould.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryCollectionRepositoryMockIntegrationShould.kt @@ -29,9 +29,15 @@ package org.hisp.dhis.android.core.trackedentity.search import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.paging.PagedList +import androidx.paging.PagingData +import androidx.paging.testing.asSnapshot import com.jraska.livedata.TestObserver +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.test.runTest import org.hisp.dhis.android.core.trackedentity.TrackedEntityInstance import org.hisp.dhis.android.core.utils.integration.mock.BaseMockIntegrationTestFullDispatcher +import org.junit.Assert.assertEquals import org.junit.Rule import org.junit.Test import org.junit.rules.TestRule @@ -50,4 +56,15 @@ class TrackedEntityInstanceQueryCollectionRepositoryMockIntegrationShould : Base .assertHasValue() .assertValue { pagedList: PagedList -> pagedList.size == 2 } } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun get_PagingData_with_offline_initial_objects() = runTest { + val items: Flow> = d2.trackedEntityModule().trackedEntityInstanceQuery() + .offlineOnly().getPagingData(2) + + val snapshot = items.asSnapshot() + + assertEquals(snapshot.size, 2) + } } diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/user/UserModuleMockIntegrationShould.java b/core/src/androidTest/java/org/hisp/dhis/android/core/user/UserModuleMockIntegrationShould.java index 37fdaaa6b8..fd62bcb638 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/user/UserModuleMockIntegrationShould.java +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/user/UserModuleMockIntegrationShould.java @@ -62,6 +62,14 @@ public void allow_access_to_user_role() { assertThat(userRole.get(0).displayName()).isEqualTo("Superuser"); } + @Test + public void allow_access_to_user_group() { + List userGroups = d2.userModule().userGroups().blockingGet(); + assertThat(userGroups.get(0).uid()).isEqualTo("Kk12LkEWtXp"); + assertThat(userGroups.get(0).name()).isEqualTo("_PROGRAM_TB program"); + assertThat(userGroups.get(0).displayName()).isEqualTo("_PROGRAM_TB program"); + } + @Test public void allow_access_to_user() { User user = d2.userModule().user().blockingGet(); diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/user/internal/UserGroupStoreIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/user/internal/UserGroupStoreIntegrationShould.kt new file mode 100644 index 0000000000..c6b66a20a8 --- /dev/null +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/user/internal/UserGroupStoreIntegrationShould.kt @@ -0,0 +1,53 @@ +/* + * 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.user.internal + +import org.hisp.dhis.android.core.data.database.IdentifiableObjectStoreAbstractIntegrationShould +import org.hisp.dhis.android.core.data.user.UserGroupSamples +import org.hisp.dhis.android.core.user.UserGroup +import org.hisp.dhis.android.core.user.UserGroupTableInfo +import org.hisp.dhis.android.core.utils.integration.mock.TestDatabaseAdapterFactory +import org.hisp.dhis.android.core.utils.runner.D2JunitRunner +import org.junit.runner.RunWith + +@RunWith(D2JunitRunner::class) +class UserGroupStoreIntegrationShould : IdentifiableObjectStoreAbstractIntegrationShould( + UserGroupStoreImpl(TestDatabaseAdapterFactory.get()), + UserGroupTableInfo.TABLE_INFO, + TestDatabaseAdapterFactory.get(), +) { + override fun buildObject(): UserGroup { + return UserGroupSamples.getUserGroup() + } + + override fun buildObjectToUpdate(): UserGroup { + return UserGroupSamples.getUserGroup().toBuilder() + .name("new_name") + .build() + } +} diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/utils/integration/mock/BaseMockIntegrationTest.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/utils/integration/mock/BaseMockIntegrationTest.kt index 2ad13ea217..5d7958eb42 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/utils/integration/mock/BaseMockIntegrationTest.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/utils/integration/mock/BaseMockIntegrationTest.kt @@ -55,8 +55,8 @@ abstract class BaseMockIntegrationTest { lateinit var databaseAdapter: DatabaseAdapter @JvmStatic - fun setUpClass(content: MockIntegrationTestDatabaseContent): Boolean { - val tuple = MockIntegrationTestObjectsFactory.getObjects(content) + fun setUpClass(content: MockIntegrationTestDatabaseContent, port: Int? = null): Boolean { + val tuple = MockIntegrationTestObjectsFactory.getObjects(content, port ?: 0) tuple.objects.let { objs -> objects = objs d2 = objs.d2 diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/utils/integration/mock/MockIntegrationTestDatabaseContent.java b/core/src/androidTest/java/org/hisp/dhis/android/core/utils/integration/mock/MockIntegrationTestDatabaseContent.java index c2c41e1823..e2f276dfea 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/utils/integration/mock/MockIntegrationTestDatabaseContent.java +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/utils/integration/mock/MockIntegrationTestDatabaseContent.java @@ -37,5 +37,6 @@ public enum MockIntegrationTestDatabaseContent { MethodScopedEmptyEnqueable, LocalAnalyticsDefaultDispatcher, LocalAnalyticsLargeDispatcher, - LocalAnalyticsSuperLargeDispatcher + LocalAnalyticsSuperLargeDispatcher, + DatabaseImportExport, } \ No newline at end of file diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/utils/integration/mock/MockIntegrationTestObjectsFactory.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/utils/integration/mock/MockIntegrationTestObjectsFactory.kt index b75c879b35..31440f9a26 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/core/utils/integration/mock/MockIntegrationTestObjectsFactory.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/utils/integration/mock/MockIntegrationTestObjectsFactory.kt @@ -44,12 +44,12 @@ internal object MockIntegrationTestObjectsFactory { d2.userModule().accountManager().setMaxAccounts(MultiUserDatabaseManager.DefaultTestMaxAccounts) } - fun getObjects(content: MockIntegrationTestDatabaseContent): IntegrationTestObjectsWithIsNewInstance { + fun getObjects(content: MockIntegrationTestDatabaseContent, port: Int): IntegrationTestObjectsWithIsNewInstance { val instance = instances[content] return if (instance != null) { IntegrationTestObjectsWithIsNewInstance(instance, false) } else { - val newInstance = MockIntegrationTestObjects(d2, content) + val newInstance = MockIntegrationTestObjects(d2, content, port) instances[content] = newInstance IntegrationTestObjectsWithIsNewInstance(newInstance, true) } diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationDimensionStoreIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationDimensionStoreIntegrationShould.kt new file mode 100644 index 0000000000..87c78a25c1 --- /dev/null +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationDimensionStoreIntegrationShould.kt @@ -0,0 +1,58 @@ +/* + * 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.visualization.internal + +import org.hisp.dhis.android.core.data.database.LinkStoreAbstractIntegrationShould +import org.hisp.dhis.android.core.data.visualization.TrackerVisualizationDimensionSamples +import org.hisp.dhis.android.core.utils.integration.mock.TestDatabaseAdapterFactory +import org.hisp.dhis.android.core.utils.runner.D2JunitRunner +import org.hisp.dhis.android.core.visualization.TrackerVisualizationDimension +import org.hisp.dhis.android.core.visualization.TrackerVisualizationDimensionTableInfo +import org.junit.runner.RunWith + +@RunWith(D2JunitRunner::class) +class TrackerVisualizationDimensionStoreIntegrationShould : + LinkStoreAbstractIntegrationShould( + TrackerVisualizationDimensionStoreImpl(TestDatabaseAdapterFactory.get()), + TrackerVisualizationDimensionTableInfo.TABLE_INFO, + TestDatabaseAdapterFactory.get(), + ) { + override fun addMasterUid(): String { + return "tracker_visualization_uid" + } + + override fun buildObject(): TrackerVisualizationDimension { + return TrackerVisualizationDimensionSamples.trackerVisualizationDimension() + } + + override fun buildObjectWithOtherMasterUid(): TrackerVisualizationDimension { + return TrackerVisualizationDimensionSamples.trackerVisualizationDimension().toBuilder() + .trackerVisualization("tracker_visualization_uid_2") + .build() + } +} diff --git a/core/src/androidTest/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationStoreIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationStoreIntegrationShould.kt new file mode 100644 index 0000000000..7319a62014 --- /dev/null +++ b/core/src/androidTest/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationStoreIntegrationShould.kt @@ -0,0 +1,55 @@ +/* + * 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.visualization.internal + +import org.hisp.dhis.android.core.data.database.IdentifiableObjectStoreAbstractIntegrationShould +import org.hisp.dhis.android.core.data.visualization.TrackerVisualizationSamples +import org.hisp.dhis.android.core.utils.integration.mock.TestDatabaseAdapterFactory +import org.hisp.dhis.android.core.utils.runner.D2JunitRunner +import org.hisp.dhis.android.core.visualization.TrackerVisualization +import org.hisp.dhis.android.core.visualization.TrackerVisualizationTableInfo +import org.hisp.dhis.android.core.visualization.TrackerVisualizationType +import org.junit.runner.RunWith + +@RunWith(D2JunitRunner::class) +class TrackerVisualizationStoreIntegrationShould : + IdentifiableObjectStoreAbstractIntegrationShould( + TrackerVisualizationStoreImpl(TestDatabaseAdapterFactory.get()), + TrackerVisualizationTableInfo.TABLE_INFO, + TestDatabaseAdapterFactory.get(), + ) { + override fun buildObject(): TrackerVisualization { + return TrackerVisualizationSamples.trackerVisualization() + } + + override fun buildObjectToUpdate(): TrackerVisualization { + return TrackerVisualizationSamples.trackerVisualization().toBuilder() + .type(TrackerVisualizationType.LINE) + .build() + } +} diff --git a/core/src/androidTest/java/org/hisp/dhis/android/testapp/icon/IconCollectionRepositoryMockIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/testapp/icon/IconCollectionRepositoryMockIntegrationShould.kt new file mode 100644 index 0000000000..47fcdabc93 --- /dev/null +++ b/core/src/androidTest/java/org/hisp/dhis/android/testapp/icon/IconCollectionRepositoryMockIntegrationShould.kt @@ -0,0 +1,69 @@ +/* + * 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.testapp.icon + +import com.google.common.truth.Truth.assertThat +import org.hisp.dhis.android.core.icon.Icon +import org.hisp.dhis.android.core.utils.integration.mock.BaseMockIntegrationTestFullDispatcher +import org.junit.Assert.fail +import org.junit.Test + +class IconCollectionRepositoryMockIntegrationShould : BaseMockIntegrationTestFullDispatcher() { + @Test + fun find_default_icon() { + val icon = d2.iconModule().icons() + .key("2g_negative") + .blockingGet() + + when (icon) { + is Icon.Default -> + assertThat(icon.key).isEqualTo("2g_negative") + + else -> + fail("Unexpected icon type") + } + } + + @Test + fun find_custom_icon() { + val icon = d2.iconModule().icons() + .key("antenatal_icon") + .blockingGet() + + when (icon) { + is Icon.Custom -> { + assertThat(icon.key).isEqualTo("antenatal_icon") + assertThat(icon.path).isNull() + } + + else -> + fail("Unexpected icon type") + } + } +} diff --git a/core/src/androidTest/java/org/hisp/dhis/android/testapp/maintenance/D2ErrorCollectionRepositoryMockIntegrationShould.java b/core/src/androidTest/java/org/hisp/dhis/android/testapp/maintenance/D2ErrorCollectionRepositoryMockIntegrationShould.java index be6983ecef..9ec810760b 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/testapp/maintenance/D2ErrorCollectionRepositoryMockIntegrationShould.java +++ b/core/src/androidTest/java/org/hisp/dhis/android/testapp/maintenance/D2ErrorCollectionRepositoryMockIntegrationShould.java @@ -67,7 +67,7 @@ public void filter_d2_error_by_d2_error_code() { public void filter_d2_error_by_d2_error_component() { List d2Errors = d2.maintenanceModule().d2Errors() .byD2ErrorComponent().eq(D2ErrorComponent.Server).blockingGet(); - assertThat(d2Errors.size()).isEqualTo(1); + assertThat(d2Errors.size()).isEqualTo(3); } @Test @@ -105,6 +105,6 @@ public void filter_d2_error_by_created() { List d2Errors = d2.maintenanceModule().d2Errors() .byCreated().inPeriods(Lists.newArrayList(todayPeriod)).blockingGet(); - assertThat(d2Errors.size()).isEqualTo(2); + assertThat(d2Errors.size()).isEqualTo(4); } } \ No newline at end of file diff --git a/core/src/androidTest/java/org/hisp/dhis/android/testapp/maintenance/MaintenanceMockIntegrationShould.java b/core/src/androidTest/java/org/hisp/dhis/android/testapp/maintenance/MaintenanceMockIntegrationShould.java index 0159095b4f..38c770f359 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/testapp/maintenance/MaintenanceMockIntegrationShould.java +++ b/core/src/androidTest/java/org/hisp/dhis/android/testapp/maintenance/MaintenanceMockIntegrationShould.java @@ -83,6 +83,6 @@ public void get_vulnerabilities_for_low_threshold() { @Test public void allow_access_to_d2_errors() { List d2Errors = d2.maintenanceModule().d2Errors().blockingGet(); - assertThat(d2Errors.size()).isEqualTo(2); + assertThat(d2Errors.size()).isEqualTo(4); } } \ No newline at end of file diff --git a/core/src/androidTest/java/org/hisp/dhis/android/testapp/map/layer/MapLayerCollectionRepositoryMockIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/testapp/map/layer/MapLayerCollectionRepositoryMockIntegrationShould.kt index 3a3e1e90fd..02fd5b6ed9 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/testapp/map/layer/MapLayerCollectionRepositoryMockIntegrationShould.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/testapp/map/layer/MapLayerCollectionRepositoryMockIntegrationShould.kt @@ -29,7 +29,9 @@ package org.hisp.dhis.android.testapp.map.layer import com.google.common.truth.Truth.assertThat +import org.hisp.dhis.android.core.map.layer.ImageFormat import org.hisp.dhis.android.core.map.layer.MapLayerPosition +import org.hisp.dhis.android.core.map.layer.MapService import org.hisp.dhis.android.core.map.layer.internal.bing.BingBasemaps import org.hisp.dhis.android.core.utils.integration.mock.BaseMockIntegrationTestEmptyEnqueable import org.hisp.dhis.android.core.utils.runner.D2JunitRunner @@ -45,7 +47,7 @@ class MapLayerCollectionRepositoryMockIntegrationShould : BaseMockIntegrationTes val mapLayers = d2.mapsModule().mapLayers() .blockingGet() - assertThat(mapLayers.size).isEqualTo(5) + assertThat(mapLayers.size).isEqualTo(9) } @Test @@ -75,6 +77,15 @@ class MapLayerCollectionRepositoryMockIntegrationShould : BaseMockIntegrationTes assertThat(mapLayers.size).isEqualTo(1) } + @Test + fun filter_by_code() { + val mapLayers = d2.mapsModule().mapLayers() + .byCode().eq("DARK_BASEMAP") + .blockingGet() + + assertThat(mapLayers.size).isEqualTo(1) + } + @Test fun filter_by_external() { val mapLayers = d2.mapsModule().mapLayers() @@ -90,7 +101,7 @@ class MapLayerCollectionRepositoryMockIntegrationShould : BaseMockIntegrationTes .byMapLayerPosition().eq(MapLayerPosition.BASEMAP) .blockingGet() - assertThat(mapLayers.size).isEqualTo(5) + assertThat(mapLayers.size).isEqualTo(7) } @Test @@ -112,6 +123,33 @@ class MapLayerCollectionRepositoryMockIntegrationShould : BaseMockIntegrationTes assertThat(mapLayers.first().imageryProviders()).isNotEmpty() } + @Test + fun filter_by_map_service() { + val mapLayers = d2.mapsModule().mapLayers() + .byMapService().eq(MapService.WMS) + .blockingGet() + + assertThat(mapLayers.size).isEqualTo(2) + } + + @Test + fun filter_by_image_format() { + val mapLayers = d2.mapsModule().mapLayers() + .byImageFormat().eq(ImageFormat.JPG) + .blockingGet() + + assertThat(mapLayers.size).isEqualTo(1) + } + + @Test + fun filter_by_layers() { + val mapLayers = d2.mapsModule().mapLayers() + .byLayers().eq("layer_test") + .blockingGet() + + assertThat(mapLayers.size).isEqualTo(1) + } + companion object { @BeforeClass @JvmStatic @@ -123,6 +161,7 @@ class MapLayerCollectionRepositoryMockIntegrationShould : BaseMockIntegrationTes dhis2MockServer.enqueueMockResponse("map/layer/bing/bing_server_response.json") dhis2MockServer.enqueueMockResponse(401) dhis2MockServer.enqueueMockResponse("map/layer/bing/bing_server_response.json") + dhis2MockServer.enqueueMockResponse("map/layer/externalmap/external_map_layers.json") d2.mapsModule().mapLayersDownloader().downloadMetadata().blockingAwait() } diff --git a/core/src/androidTest/java/org/hisp/dhis/android/testapp/program/ProgramCollectionRepositoryMockIntegrationShould.java b/core/src/androidTest/java/org/hisp/dhis/android/testapp/program/ProgramCollectionRepositoryMockIntegrationShould.java index 534f468396..879ad821f4 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/testapp/program/ProgramCollectionRepositoryMockIntegrationShould.java +++ b/core/src/androidTest/java/org/hisp/dhis/android/testapp/program/ProgramCollectionRepositoryMockIntegrationShould.java @@ -195,6 +195,70 @@ public void filter_by_access_level() { assertThat(programs.size()).isEqualTo(1); } + @Test + public void filter_by_enrollment_label() { + List programs = d2.programModule().programs() + .byEnrollmentLabel().eq("Enrollment Label") + .blockingGet(); + assertThat(programs.size()).isEqualTo(1); + } + + @Test + public void filter_by_follow_up_label() { + List programs = d2.programModule().programs() + .byFollowUpLabel().eq("Follow up Label") + .blockingGet(); + assertThat(programs.size()).isEqualTo(1); + } + + @Test + public void filter_by_org_unit_label() { + List programs = d2.programModule().programs() + .byOrgUnitLabel().eq("OrgUnit Label") + .blockingGet(); + assertThat(programs.size()).isEqualTo(1); + } + + @Test + public void filter_by_relationship_label() { + List programs = d2.programModule().programs() + .byRelationshipLabel().eq("Relationship Label") + .blockingGet(); + assertThat(programs.size()).isEqualTo(1); + } + + @Test + public void filter_by_note_label() { + List programs = d2.programModule().programs() + .byNoteLabel().eq("Note Label") + .blockingGet(); + assertThat(programs.size()).isEqualTo(1); + } + + @Test + public void filter_by_tracked_entity_attribute_label() { + List programs = d2.programModule().programs() + .byTrackedEntityAttributeLabel().eq("TrackedEntityAttribute Label") + .blockingGet(); + assertThat(programs.size()).isEqualTo(1); + } + + @Test + public void filter_by_program_stage_label() { + List programs = d2.programModule().programs() + .byProgramStageLabel().eq("ProgramStage Label") + .blockingGet(); + assertThat(programs.size()).isEqualTo(1); + } + + @Test + public void filter_by_event_label() { + List programs = d2.programModule().programs() + .byEventLabel().eq("Event Label") + .blockingGet(); + assertThat(programs.size()).isEqualTo(1); + } + @Test public void filter_by_field_color() { List programs = d2.programModule().programs() @@ -206,7 +270,7 @@ public void filter_by_field_color() { @Test public void filter_by_field_icon() { List programs = d2.programModule().programs() - .byIcon().eq("program-icon") + .byIcon().eq("antenatal_icon") .blockingGet(); assertThat(programs.size()).isEqualTo(1); } diff --git a/core/src/androidTest/java/org/hisp/dhis/android/testapp/program/ProgramStageCollectionRepositoryMockIntegrationShould.java b/core/src/androidTest/java/org/hisp/dhis/android/testapp/program/ProgramStageCollectionRepositoryMockIntegrationShould.java index ad08986350..f6774168e7 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/testapp/program/ProgramStageCollectionRepositoryMockIntegrationShould.java +++ b/core/src/androidTest/java/org/hisp/dhis/android/testapp/program/ProgramStageCollectionRepositoryMockIntegrationShould.java @@ -330,6 +330,26 @@ public void filter_by_validation_strategy() { assertThat(programStages.size()).isEqualTo(1); } + @Test + public void filter_by_program_stage_label() { + List programStages = + d2.programModule().programStages() + .byProgramStageLabel().eq("ProgramStage Label") + .blockingGet(); + + assertThat(programStages.size()).isEqualTo(1); + } + + @Test + public void filter_by_event_label() { + List programStages = + d2.programModule().programStages() + .byEventLabel().eq("Event Label") + .blockingGet(); + + assertThat(programStages.size()).isEqualTo(1); + } + @Test public void filter_by_field_color() { List programStages = d2.programModule().programStages() @@ -341,7 +361,7 @@ public void filter_by_field_color() { @Test public void filter_by_field_icon() { List programStages = d2.programModule().programStages() - .byIcon().eq("program-stage-icon") + .byIcon().eq("visit_icon") .blockingGet(); assertThat(programStages.size()).isEqualTo(1); } diff --git a/core/src/androidTest/java/org/hisp/dhis/android/testapp/settings/AnalyticsDhisVisualizationsSettingObjectRepositoryMockIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/testapp/settings/AnalyticsDhisVisualizationsSettingObjectRepositoryMockIntegrationShould.kt index 3dc0d1845a..e2244d05b7 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/testapp/settings/AnalyticsDhisVisualizationsSettingObjectRepositoryMockIntegrationShould.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/testapp/settings/AnalyticsDhisVisualizationsSettingObjectRepositoryMockIntegrationShould.kt @@ -46,6 +46,7 @@ class AnalyticsDhisVisualizationsSettingObjectRepositoryMockIntegrationShould : assertThat( analyticsDhisVisualizationsSetting.home().first().visualizations().first().name(), ).isNotEmpty() + assertThat(analyticsDhisVisualizationsSetting.home().first().visualizations().size).isEqualTo(3) assertThat(analyticsDhisVisualizationsSetting.program().size).isEqualTo(1) assertThat(analyticsDhisVisualizationsSetting.dataSet().size).isEqualTo(1) @@ -60,7 +61,7 @@ class AnalyticsDhisVisualizationsSettingObjectRepositoryMockIntegrationShould : .blockingGetByProgram("IpHINAT79UW") assertThat(programSettings?.size).isEqualTo(1) - assertThat(programSettings?.first()?.visualizations()?.size).isEqualTo(2) + assertThat(programSettings?.first()?.visualizations()?.size).isEqualTo(1) assertThat( programSettings?.first()?.visualizations()?.first()?.uid(), ).isEqualTo("PYBH8ZaAQnC") diff --git a/core/src/androidTest/java/org/hisp/dhis/android/testapp/settings/AnalyticsSettingsObjectRepositoryMockIntegrationShould.java b/core/src/androidTest/java/org/hisp/dhis/android/testapp/settings/AnalyticsSettingsObjectRepositoryMockIntegrationShould.java index 80e13b6cc1..47e918b937 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/testapp/settings/AnalyticsSettingsObjectRepositoryMockIntegrationShould.java +++ b/core/src/androidTest/java/org/hisp/dhis/android/testapp/settings/AnalyticsSettingsObjectRepositoryMockIntegrationShould.java @@ -68,7 +68,7 @@ public void find_analytics_settings() { for (AnalyticsDhisVisualizationsGroup analyticsDhisVisualizationsGroup : analyticsSettings.dhisVisualizations().home()) { if (analyticsDhisVisualizationsGroup.id().equals("12345678910")) { - assertThat(analyticsDhisVisualizationsGroup.visualizations().size()).isEqualTo(2); + assertThat(analyticsDhisVisualizationsGroup.visualizations().size()).isEqualTo(3); } else if (analyticsDhisVisualizationsGroup.id().equals("12345678911")) { assertThat(analyticsDhisVisualizationsGroup.visualizations().size()).isEqualTo(1); } diff --git a/core/src/androidTest/java/org/hisp/dhis/android/testapp/settings/GeneralSettingsObjectRepositoryMockIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/testapp/settings/GeneralSettingsObjectRepositoryMockIntegrationShould.kt index 578451e4f4..effc83a043 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/testapp/settings/GeneralSettingsObjectRepositoryMockIntegrationShould.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/testapp/settings/GeneralSettingsObjectRepositoryMockIntegrationShould.kt @@ -41,6 +41,7 @@ class GeneralSettingsObjectRepositoryMockIntegrationShould : BaseMockIntegration fun find_android_setting() { val generalSettings = d2.settingModule().generalSetting().blockingGet() assertThat(generalSettings!!.dataSync()).isEqualTo(DataSyncPeriod.EVERY_24_HOURS) + assertThat(generalSettings!!.bypassDHIS2VersionCheck()).isTrue() } @Test diff --git a/core/src/androidTest/java/org/hisp/dhis/android/testapp/settings/LatestAppVersionObjectRepositoryMockIntegrationShould.java b/core/src/androidTest/java/org/hisp/dhis/android/testapp/settings/LatestAppVersionObjectRepositoryMockIntegrationShould.kt similarity index 66% rename from core/src/androidTest/java/org/hisp/dhis/android/testapp/settings/LatestAppVersionObjectRepositoryMockIntegrationShould.java rename to core/src/androidTest/java/org/hisp/dhis/android/testapp/settings/LatestAppVersionObjectRepositoryMockIntegrationShould.kt index 8a5959b081..af26ac97af 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/testapp/settings/LatestAppVersionObjectRepositoryMockIntegrationShould.java +++ b/core/src/androidTest/java/org/hisp/dhis/android/testapp/settings/LatestAppVersionObjectRepositoryMockIntegrationShould.kt @@ -25,25 +25,22 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +package org.hisp.dhis.android.testapp.settings -package org.hisp.dhis.android.testapp.settings; - -import static com.google.common.truth.Truth.assertThat; - -import org.hisp.dhis.android.core.settings.LatestAppVersion; -import org.hisp.dhis.android.core.utils.integration.mock.BaseMockIntegrationTestFullDispatcher; -import org.hisp.dhis.android.core.utils.runner.D2JunitRunner; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(D2JunitRunner.class) -public class LatestAppVersionObjectRepositoryMockIntegrationShould extends BaseMockIntegrationTestFullDispatcher { +import com.google.common.truth.Truth +import org.hisp.dhis.android.core.utils.integration.mock.BaseMockIntegrationTestFullDispatcher +import org.hisp.dhis.android.core.utils.runner.D2JunitRunner +import org.junit.Test +import org.junit.runner.RunWith +@RunWith(D2JunitRunner::class) +class LatestAppVersionObjectRepositoryMockIntegrationShould : BaseMockIntegrationTestFullDispatcher() { @Test - public void find_user_settings() { - LatestAppVersion latestAppVersion = d2.settingModule().latestAppVersion().blockingGet(); - assertThat(latestAppVersion.downloadURL()).isEqualTo( - "https://github.com/dhis2/dhis2-android-capture-app/releases/download/2.7.1.1/dhis2-v2.7.1.1.apk"); - assertThat(latestAppVersion.version()).isEqualTo("v2.7.1.1"); + fun find_latest_app_version() { + val latestAppVersion = d2.settingModule().latestAppVersion().blockingGet() + Truth.assertThat(latestAppVersion?.version()).isEqualTo("40.2") + Truth.assertThat(latestAppVersion?.downloadURL()).isEqualTo( + "https://github.com/dhis2/dhis2-android-capture-app/releases/download/40.2/dhis2-40.2.apk", + ) } -} \ No newline at end of file +} diff --git a/core/src/androidTest/java/org/hisp/dhis/android/testapp/user/AccountManagerMockIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/testapp/user/AccountManagerMockIntegrationShould.kt index 1ce17450f0..94753ff0f2 100644 --- a/core/src/androidTest/java/org/hisp/dhis/android/testapp/user/AccountManagerMockIntegrationShould.kt +++ b/core/src/androidTest/java/org/hisp/dhis/android/testapp/user/AccountManagerMockIntegrationShould.kt @@ -69,6 +69,31 @@ class AccountManagerMockIntegrationShould : BaseMockIntegrationTestEmptyEnqueabl loginAndDeleteAccount(user1, pass1, dhis2MockServer) } + @Test + fun find_current_account_after_login() { + if (d2.userModule().blockingIsLogged()) { + d2.userModule().blockingLogOut() + } + dhis2MockServer.enqueueLoginResponses() + d2.userModule().blockingLogIn(user1, pass1, dhis2MockServer.baseEndpoint) + + val currentAccount = d2.userModule().accountManager().getCurrentAccount() + assertThat(currentAccount?.username()).isEqualTo(user1) + assertThat(currentAccount?.syncState()).isNotNull() + + loginAndDeleteAccount(user1, pass1, dhis2MockServer) + } + + @Test + fun cannot_find_current_account_after_logout() { + if (d2.userModule().blockingIsLogged()) { + d2.userModule().blockingLogOut() + } + + val currentAccount = d2.userModule().accountManager().getCurrentAccount() + assertThat(currentAccount).isNull() + } + @Test fun can_change_max_accounts() { d2.userModule().accountManager().setMaxAccounts(5) diff --git a/core/src/androidTest/java/org/hisp/dhis/android/testapp/visualization/TrackerVisualizationCollectionRepositoryMockIntegrationShould.kt b/core/src/androidTest/java/org/hisp/dhis/android/testapp/visualization/TrackerVisualizationCollectionRepositoryMockIntegrationShould.kt new file mode 100644 index 0000000000..7ed6a6af6c --- /dev/null +++ b/core/src/androidTest/java/org/hisp/dhis/android/testapp/visualization/TrackerVisualizationCollectionRepositoryMockIntegrationShould.kt @@ -0,0 +1,129 @@ +/* + * 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.android.testapp.visualization + +import com.google.common.truth.Truth.assertThat +import org.hisp.dhis.android.core.utils.integration.mock.BaseMockIntegrationTestFullDispatcher +import org.hisp.dhis.android.core.visualization.TrackerVisualizationOutputType +import org.hisp.dhis.android.core.visualization.TrackerVisualizationType +import org.junit.Test + +class TrackerVisualizationCollectionRepositoryMockIntegrationShould : + BaseMockIntegrationTestFullDispatcher() { + @Test + fun find_all() { + val visualizations = d2.visualizationModule().trackerVisualizations() + .blockingGet() + + assertThat(visualizations.size).isEqualTo(1) + } + + @Test + fun find_uids() { + val visualizationUids = d2.visualizationModule().trackerVisualizations() + .blockingGetUids() + + assertThat(visualizationUids.size).isEqualTo(1) + assertThat(visualizationUids.contains("s85urBIkN0z")).isTrue() + } + + @Test + fun find_by_description() { + val visualizations = d2.visualizationModule().trackerVisualizations() + .byDescription().eq("Child line list description") + .blockingGet() + + assertThat(visualizations.size).isEqualTo(1) + } + + @Test + fun find_by_display_description() { + val visualizations = d2.visualizationModule().trackerVisualizations() + .byDisplayDescription().eq("Child line list description") + .blockingGet() + + assertThat(visualizations.size).isEqualTo(1) + } + + @Test + fun find_by_type() { + val visualizations = d2.visualizationModule().trackerVisualizations() + .byType().eq(TrackerVisualizationType.LINE_LIST) + .blockingGet() + + assertThat(visualizations.size).isEqualTo(1) + } + + @Test + fun find_by_output_type() { + val visualizations = d2.visualizationModule().trackerVisualizations() + .byOutputType().eq(TrackerVisualizationOutputType.ENROLLMENT) + .blockingGet() + + assertThat(visualizations.size).isEqualTo(1) + } + + @Test + fun find_by_program() { + val visualizations = d2.visualizationModule().trackerVisualizations() + .byProgram().eq("IpHINAT79UW") + .blockingGet() + + assertThat(visualizations.size).isEqualTo(1) + } + + @Test + fun find_by_program_stage() { + val visualizations = d2.visualizationModule().trackerVisualizations() + .byProgramStage().eq("IpHINAT79UW") + .blockingGet() + + assertThat(visualizations.size).isEqualTo(0) + } + + @Test + fun include_columns_and_filters_as_children() { + val visualization = d2.visualizationModule().trackerVisualizations() + .withColumnsAndFilters() + .uid("s85urBIkN0z") + .blockingGet()!! + + assertThat(visualization.columns()!!.size).isEqualTo(3) + assertThat(visualization.columns()!![0].dimension()).isEqualTo("ou") + assertThat(visualization.columns()!![0].dimensionType()).isEqualTo("ORGANISATION_UNIT") + assertThat(visualization.columns()!![0].items()!!.size).isEqualTo(1) + assertThat(visualization.columns()!![0].items()!![0].uid()).isEqualTo("USER_ORGUNIT") + + assertThat(visualization.filters()!!.size).isEqualTo(1) + assertThat(visualization.filters()!![0].dimension()).isEqualTo("enrollmentDate") + assertThat(visualization.filters()!![0].dimensionType()).isEqualTo("PERIOD") + assertThat(visualization.filters()!![0].items()!!.size).isEqualTo(2) + assertThat(visualization.filters()!![0].items()!![0].uid()).isEqualTo("2018") + assertThat(visualization.filters()!![0].items()!![1].uid()).isEqualTo("2019") + } +} diff --git a/core/src/main/AndroidManifest.xml b/core/src/main/AndroidManifest.xml index adb13dbea3..5db899e2d5 100644 --- a/core/src/main/AndroidManifest.xml +++ b/core/src/main/AndroidManifest.xml @@ -29,10 +29,10 @@ - + diff --git a/core/src/main/assets/migrations/156.sql b/core/src/main/assets/migrations/156.sql new file mode 100644 index 0000000000..7c3d52a33e --- /dev/null +++ b/core/src/main/assets/migrations/156.sql @@ -0,0 +1,4 @@ +# Add trackerDataView to RelationshipConstraint (ANDROSDK-1695) + +ALTER TABLE RelationshipConstraint ADD COLUMN trackerDataViewAttributes TEXT; +ALTER TABLE RelationshipConstraint ADD COLUMN trackerDataViewDataElements TEXT; \ No newline at end of file diff --git a/core/src/main/assets/migrations/157.sql b/core/src/main/assets/migrations/157.sql new file mode 100644 index 0000000000..7167d8c2f8 --- /dev/null +++ b/core/src/main/assets/migrations/157.sql @@ -0,0 +1,13 @@ +# Add tracker terminology (ANDROSDK-1806) + +ALTER TABLE Program ADD COLUMN enrollmentLabel TEXT; +ALTER TABLE Program ADD COLUMN followUpLabel TEXT; +ALTER TABLE Program ADD COLUMN orgUnitLabel TEXT; +ALTER TABLE Program ADD COLUMN relationshipLabel TEXT; +ALTER TABLE Program ADD COLUMN noteLabel TEXT; +ALTER TABLE Program ADD COLUMN trackedEntityAttributeLabel TEXT; +ALTER TABLE Program ADD COLUMN programStageLabel TEXT; +ALTER TABLE Program ADD COLUMN eventLabel TEXT; + +ALTER TABLE ProgramStage ADD COLUMN programStageLabel TEXT; +ALTER TABLE ProgramStage ADD COLUMN eventLabel TEXT; \ No newline at end of file diff --git a/core/src/main/assets/migrations/158.sql b/core/src/main/assets/migrations/158.sql new file mode 100644 index 0000000000..b804f08508 --- /dev/null +++ b/core/src/main/assets/migrations/158.sql @@ -0,0 +1,4 @@ +# Add TrackerVisualization model (ANDROSDK-1810) + +CREATE TABLE TrackerVisualization(_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL UNIQUE, code TEXT, name TEXT, displayName TEXT, created TEXT, lastUpdated TEXT, description TEXT, displayDescription TEXT, type TEXT, outputType TEXT, program TEXT, programStage TEXT, trackedEntityType TEXT, FOREIGN KEY (program) REFERENCES Program (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (programStage) REFERENCES ProgramStage (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (trackedEntityType) REFERENCES TrackedEntityType (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); +CREATE TABLE TrackerVisualizationDimension(_id INTEGER PRIMARY KEY AUTOINCREMENT, trackerVisualization TEXT NOT NULL, position TEXT NOT NULL, dimension TEXT NOT NULL, dimensionType TEXT, program TEXT, programStage TEXT, items TEXT, filter TEXT, repetition TEXT, FOREIGN KEY (trackerVisualization) REFERENCES TrackerVisualization (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (program) REFERENCES Program (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (programStage) REFERENCES ProgramStage (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); diff --git a/core/src/main/assets/migrations/159.sql b/core/src/main/assets/migrations/159.sql new file mode 100644 index 0000000000..c671021e79 --- /dev/null +++ b/core/src/main/assets/migrations/159.sql @@ -0,0 +1,24 @@ +# Add external map layers (ANDROSDK-1800) and migrate map layer keys (ANDROSDK-1803) + +ALTER TABLE MapLayer ADD COLUMN code TEXT; +ALTER TABLE MapLayer ADD COLUMN mapService TEXT; +ALTER TABLE MapLayer ADD COLUMN imageFormat TEXT; +ALTER TABLE MapLayer ADD COLUMN layers TEXT; + +UPDATE MapLayer SET uid = 'osmLight' WHERE uid = 'l7rimUxoQu4'; +UPDATE MapLayerImageryProvider SET mapLayer = 'osmLight' WHERE mapLayer = 'l7rimUxoQu4'; + +UPDATE MapLayer SET uid = 'openStreetMap' WHERE uid = 'k6QEWMytadd'; +UPDATE MapLayerImageryProvider SET mapLayer = 'openStreetMap' WHERE mapLayer = 'k6QEWMytadd'; + +UPDATE MapLayer SET uid = 'bingLight' WHERE uid = 'ql5jVkAL1iy'; +UPDATE MapLayerImageryProvider SET mapLayer = 'bingLight' WHERE mapLayer = 'ql5jVkAL1iy'; + +UPDATE MapLayer SET uid = 'bingDark' WHERE uid = 'PwJ1fQoTthh'; +UPDATE MapLayerImageryProvider SET mapLayer = 'bingDark' WHERE mapLayer = 'PwJ1fQoTthh'; + +UPDATE MapLayer SET uid = 'bingAerial' WHERE uid = 'kKJNmY2yYtM'; +UPDATE MapLayerImageryProvider SET mapLayer = 'bingAerial' WHERE mapLayer = 'kKJNmY2yYtM'; + +UPDATE MapLayer SET uid = 'bingHybrid' WHERE uid = 'TfK2zM71AHJ'; +UPDATE MapLayerImageryProvider SET mapLayer = 'bingHybrid' WHERE mapLayer = 'TfK2zM71AHJ'; \ No newline at end of file diff --git a/core/src/main/assets/migrations/160.sql b/core/src/main/assets/migrations/160.sql new file mode 100644 index 0000000000..9a6e739630 --- /dev/null +++ b/core/src/main/assets/migrations/160.sql @@ -0,0 +1,3 @@ +# Add CustomIcon model (ANDROSDK-1630) + +CREATE TABLE CustomIcon(_id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT NOT NULL, fileResource TEXT NOT NULL, href TEXT NOT NULL); \ No newline at end of file diff --git a/core/src/main/assets/migrations/161.sql b/core/src/main/assets/migrations/161.sql new file mode 100644 index 0000000000..d6d6ec623e --- /dev/null +++ b/core/src/main/assets/migrations/161.sql @@ -0,0 +1,6 @@ +# Add TrackerVisualization to ASWA (ANDROSDK-1811) + +ALTER TABLE AnalyticsDhisVisualization RENAME TO AnalyticsDhisVisualization_Old; +CREATE TABLE AnalyticsDhisVisualization (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL, scopeUid TEXT, scope TEXT, groupUid TEXT, groupName TEXT, timestamp TEXT, name TEXT, type TEXT NOT NULL); +INSERT INTO AnalyticsDhisVisualization(_id, uid, scopeUid, scope, groupUid, groupName, timestamp, name, type) SELECT _id, uid, scopeUid, scope, groupUid, groupName, timestamp, name, 'VISUALIZATION' FROM AnalyticsDhisVisualization_Old; +DROP TABLE IF EXISTS AnalyticsDhisVisualization_Old; \ No newline at end of file diff --git a/core/src/main/assets/migrations/162.sql b/core/src/main/assets/migrations/162.sql new file mode 100644 index 0000000000..03cf8d0aae --- /dev/null +++ b/core/src/main/assets/migrations/162.sql @@ -0,0 +1,3 @@ +# Add UserGroup (ANDROSDK-1817) + +CREATE TABLE UserGroup (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL UNIQUE, code TEXT, name TEXT, displayName TEXT, created TEXT, lastUpdated TEXT); diff --git a/core/src/main/assets/migrations/163.sql b/core/src/main/assets/migrations/163.sql new file mode 100644 index 0000000000..33e44bf8fd --- /dev/null +++ b/core/src/main/assets/migrations/163.sql @@ -0,0 +1,3 @@ +# Add bypassDHIS2VersionCheck to General Settings (ANDROSDK-1750) + +ALTER TABLE GeneralSetting ADD COLUMN bypassDHIS2VersionCheck INTEGER; \ No newline at end of file diff --git a/core/src/main/assets/snapshots/snapshot.sql b/core/src/main/assets/snapshots/snapshot.sql index 291cbaca5e..115ac6003b 100644 --- a/core/src/main/assets/snapshots/snapshot.sql +++ b/core/src/main/assets/snapshots/snapshot.sql @@ -40,7 +40,7 @@ CREATE TABLE Section (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL U CREATE TABLE SectionDataElementLink (_id INTEGER PRIMARY KEY AUTOINCREMENT, section TEXT NOT NULL, dataElement TEXT NOT NULL, sortOrder INTEGER, FOREIGN KEY (section) REFERENCES Section (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (dataElement) REFERENCES DataElement (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, UNIQUE (section, dataElement)); CREATE TABLE DataSetCompulsoryDataElementOperandsLink (_id INTEGER PRIMARY KEY AUTOINCREMENT, dataSet TEXT NOT NULL, dataElementOperand TEXT NOT NULL, FOREIGN KEY (dataSet) REFERENCES DataSet (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (dataElementOperand) REFERENCES DataElementOperand (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, UNIQUE (dataSet, dataElementOperand)); CREATE TABLE DataInputPeriod (_id INTEGER PRIMARY KEY AUTOINCREMENT, dataSet TEXT NOT NULL, period TEXT NOT NULL, openingDate TEXT, closingDate TEXT, FOREIGN KEY (dataSet) REFERENCES DataSet (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); -CREATE TABLE RelationshipConstraint (_id INTEGER PRIMARY KEY AUTOINCREMENT, relationshipType TEXT NOT NULL, constraintType TEXT NOT NULL, relationshipEntity TEXT, trackedEntityType TEXT, program TEXT, programStage TEXT, FOREIGN KEY (trackedEntityType) REFERENCES TrackedEntityType (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (program) REFERENCES Program (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (programStage) REFERENCES ProgramStage (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, UNIQUE (relationshipType, constraintType)); +CREATE TABLE RelationshipConstraint (_id INTEGER PRIMARY KEY AUTOINCREMENT, relationshipType TEXT NOT NULL, constraintType TEXT NOT NULL, relationshipEntity TEXT, trackedEntityType TEXT, program TEXT, programStage TEXT, trackerDataViewAttributes TEXT, trackerDataViewDataElements TEXT, FOREIGN KEY (trackedEntityType) REFERENCES TrackedEntityType (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (program) REFERENCES Program (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (programStage) REFERENCES ProgramStage (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, UNIQUE (relationshipType, constraintType)); CREATE TABLE RelationshipItem (_id INTEGER PRIMARY KEY AUTOINCREMENT, relationship TEXT NOT NULL, relationshipItemType TEXT NOT NULL, trackedEntityInstance TEXT, enrollment TEXT, event TEXT, FOREIGN KEY (relationship) REFERENCES Relationship (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED FOREIGN KEY (trackedEntityInstance) REFERENCES TrackedEntityInstance (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (enrollment) REFERENCES Enrollment (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (event) REFERENCES Event (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); CREATE TABLE OrganisationUnitGroup (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL UNIQUE, code TEXT, name TEXT, displayName TEXT, created TEXT, lastUpdated TEXT, shortName TEXT, displayShortName TEXT); CREATE TABLE OrganisationUnitOrganisationUnitGroupLink (_id INTEGER PRIMARY KEY AUTOINCREMENT, organisationUnit TEXT NOT NULL, organisationUnitGroup TEXT NOT NULL, FOREIGN KEY (organisationUnit) REFERENCES OrganisationUnit (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (organisationUnitGroup) REFERENCES OrganisationUnitGroup (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, UNIQUE (organisationUnit, organisationUnitGroup)); @@ -65,8 +65,8 @@ CREATE TABLE TrackerImportConflict (_id INTEGER PRIMARY KEY AUTOINCREMENT, confl CREATE TABLE DataSetOrganisationUnitLink (_id INTEGER PRIMARY KEY AUTOINCREMENT, dataSet TEXT NOT NULL, organisationUnit TEXT NOT NULL, FOREIGN KEY (dataSet) REFERENCES DataSet (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (organisationUnit) REFERENCES OrganisationUnit (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, UNIQUE (organisationUnit, dataSet)); CREATE TABLE UserOrganisationUnit (_id INTEGER PRIMARY KEY AUTOINCREMENT, user TEXT NOT NULL, organisationUnit TEXT NOT NULL, organisationUnitScope TEXT NOT NULL, root INTEGER, userAssigned INTEGER, FOREIGN KEY (user) REFERENCES User (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, UNIQUE (organisationUnitScope, user, organisationUnit)); CREATE TABLE RelationshipType (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL UNIQUE, code TEXT, name TEXT, displayName TEXT, created TEXT, lastUpdated TEXT, fromToName TEXT, toFromName TEXT, bidirectional INTEGER, accessDataWrite INTEGER ); -CREATE TABLE ProgramStage (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL UNIQUE, code TEXT, name TEXT, displayName TEXT, created TEXT, lastUpdated TEXT, executionDateLabel TEXT, allowGenerateNextVisit INTEGER, validCompleteOnly INTEGER, reportDateToUse TEXT, openAfterEnrollment INTEGER, repeatable INTEGER, formType TEXT, displayGenerateEventBox INTEGER, generatedByEnrollmentDate INTEGER, autoGenerateEvent INTEGER, sortOrder INTEGER, hideDueDate INTEGER, blockEntryForm INTEGER, minDaysFromStart INTEGER, standardInterval INTEGER, program TEXT NOT NULL, periodType TEXT, accessDataWrite INTEGER, remindCompleted INTEGER, description TEXT, displayDescription TEXT, featureType TEXT, color TEXT, icon TEXT, enableUserAssignment INTEGER, dueDateLabel TEXT, validationStrategy TEXT, FOREIGN KEY (program) REFERENCES Program (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); -CREATE TABLE Program (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL UNIQUE, code TEXT, name TEXT, displayName TEXT, created TEXT, lastUpdated TEXT, shortName TEXT, displayShortName TEXT, description TEXT, displayDescription TEXT, version INTEGER, onlyEnrollOnce INTEGER, enrollmentDateLabel TEXT, displayIncidentDate INTEGER, incidentDateLabel TEXT, registration INTEGER, selectEnrollmentDatesInFuture INTEGER, dataEntryMethod INTEGER, ignoreOverdueEvents INTEGER, selectIncidentDatesInFuture INTEGER, useFirstStageDuringRegistration INTEGER, displayFrontPageList INTEGER, programType TEXT, relatedProgram TEXT, trackedEntityType TEXT, categoryCombo TEXT, accessDataWrite INTEGER, expiryDays INTEGER, completeEventsExpiryDays INTEGER, expiryPeriodType TEXT, minAttributesRequiredToSearch INTEGER, maxTeiCountToReturn INTEGER, featureType TEXT, accessLevel TEXT, color TEXT, icon TEXT, FOREIGN KEY (trackedEntityType) REFERENCES TrackedEntityType (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (categoryCombo) REFERENCES CategoryCombo (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); +CREATE TABLE ProgramStage (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL UNIQUE, code TEXT, name TEXT, displayName TEXT, created TEXT, lastUpdated TEXT, executionDateLabel TEXT, allowGenerateNextVisit INTEGER, validCompleteOnly INTEGER, reportDateToUse TEXT, openAfterEnrollment INTEGER, repeatable INTEGER, formType TEXT, displayGenerateEventBox INTEGER, generatedByEnrollmentDate INTEGER, autoGenerateEvent INTEGER, sortOrder INTEGER, hideDueDate INTEGER, blockEntryForm INTEGER, minDaysFromStart INTEGER, standardInterval INTEGER, program TEXT NOT NULL, periodType TEXT, accessDataWrite INTEGER, remindCompleted INTEGER, description TEXT, displayDescription TEXT, featureType TEXT, color TEXT, icon TEXT, enableUserAssignment INTEGER, dueDateLabel TEXT, validationStrategy TEXT, programStageLabel TEXT, eventLabel TEXT, FOREIGN KEY (program) REFERENCES Program (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); +CREATE TABLE Program (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL UNIQUE, code TEXT, name TEXT, displayName TEXT, created TEXT, lastUpdated TEXT, shortName TEXT, displayShortName TEXT, description TEXT, displayDescription TEXT, version INTEGER, onlyEnrollOnce INTEGER, enrollmentDateLabel TEXT, displayIncidentDate INTEGER, incidentDateLabel TEXT, registration INTEGER, selectEnrollmentDatesInFuture INTEGER, dataEntryMethod INTEGER, ignoreOverdueEvents INTEGER, selectIncidentDatesInFuture INTEGER, useFirstStageDuringRegistration INTEGER, displayFrontPageList INTEGER, programType TEXT, relatedProgram TEXT, trackedEntityType TEXT, categoryCombo TEXT, accessDataWrite INTEGER, expiryDays INTEGER, completeEventsExpiryDays INTEGER, expiryPeriodType TEXT, minAttributesRequiredToSearch INTEGER, maxTeiCountToReturn INTEGER, featureType TEXT, accessLevel TEXT, color TEXT, icon TEXT, enrollmentLabel TEXT, followUpLabel TEXT, orgUnitLabel TEXT, relationshipLabel TEXT, noteLabel TEXT, trackedEntityAttributeLabel TEXT, programStageLabel TEXT, eventLabel TEXT, FOREIGN KEY (trackedEntityType) REFERENCES TrackedEntityType (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (categoryCombo) REFERENCES CategoryCombo (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); CREATE TABLE TrackedEntityInstance (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL UNIQUE, created TEXT, lastUpdated TEXT, createdAtClient TEXT, lastUpdatedAtClient TEXT, organisationUnit TEXT, trackedEntityType TEXT, geometryType TEXT, geometryCoordinates TEXT, syncState TEXT, aggregatedSyncState TEXT, deleted INTEGER, FOREIGN KEY (organisationUnit) REFERENCES OrganisationUnit (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (trackedEntityType) REFERENCES TrackedEntityType (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); CREATE TABLE Enrollment (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL UNIQUE, created TEXT, lastUpdated TEXT, createdAtClient TEXT, lastUpdatedAtClient TEXT, organisationUnit TEXT NOT NULL, program TEXT NOT NULL, enrollmentDate TEXT, incidentDate TEXT, followup INTEGER, status TEXT, trackedEntityInstance TEXT NOT NULL, syncState TEXT, aggregatedSyncState TEXT, geometryType TEXT, geometryCoordinates TEXT, deleted INTEGER, completedDate TEXT, FOREIGN KEY (organisationUnit) REFERENCES OrganisationUnit (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (program) REFERENCES Program (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (trackedEntityInstance) REFERENCES TrackedEntityInstance (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); CREATE TABLE Event (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL UNIQUE, enrollment TEXT, created TEXT, lastUpdated TEXT, createdAtClient TEXT, lastUpdatedAtClient TEXT, status TEXT, geometryType TEXT, geometryCoordinates TEXT, program TEXT NOT NULL, programStage TEXT NOT NULL, organisationUnit TEXT NOT NULL, eventDate TEXT, completedDate TEXT, dueDate TEXT, syncState TEXT, aggregatedSyncState TEXT, attributeOptionCombo TEXT, deleted INTEGER, assignedUser TEXT, completedBy TEXT, FOREIGN KEY (program) REFERENCES Program (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (programStage) REFERENCES ProgramStage (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (enrollment) REFERENCES Enrollment (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (organisationUnit) REFERENCES OrganisationUnit (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (attributeOptionCombo) REFERENCES CategoryOptionCombo (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); @@ -79,7 +79,7 @@ CREATE TABLE SectionGreyedFieldsLink (_id INTEGER PRIMARY KEY AUTOINCREMENT, sec CREATE TABLE AuthenticatedUser (_id INTEGER PRIMARY KEY AUTOINCREMENT, user TEXT NOT NULL UNIQUE, hash TEXT, FOREIGN KEY (user) REFERENCES User (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); CREATE UNIQUE INDEX event_data_element ON TrackedEntityDataValue(event, dataElement); CREATE UNIQUE INDEX tracked_entity_instance_attribute ON TrackedEntityAttributeValue(trackedEntityInstance, trackedEntityAttribute); -CREATE TABLE GeneralSetting (_id INTEGER PRIMARY KEY AUTOINCREMENT, encryptDB INTEGER, lastUpdated TEXT, reservedValues INTEGER, smsGateway TEXT, smsResultSender TEXT, matomoID INTEGER, matomoURL TEXT, allowScreenCapture INTEGER, messageOfTheDay TEXT, experimentalFeatures TEXT); +CREATE TABLE GeneralSetting (_id INTEGER PRIMARY KEY AUTOINCREMENT, encryptDB INTEGER, lastUpdated TEXT, reservedValues INTEGER, smsGateway TEXT, smsResultSender TEXT, matomoID INTEGER, matomoURL TEXT, allowScreenCapture INTEGER, messageOfTheDay TEXT, experimentalFeatures TEXT, bypassDHIS2VersionCheck INTEGER); CREATE TABLE DataSetSetting (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT UNIQUE, name TEXT, lastUpdated TEXT, periodDSDownload INTEGER, periodDSDBTrimming INTEGER, FOREIGN KEY (uid) REFERENCES DataSet (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); CREATE TABLE ProgramSetting (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT UNIQUE, name TEXT, lastUpdated TEXT, teiDownload INTEGER, teiDBTrimming INTEGER, eventsDownload INTEGER, eventsDBTrimming INTEGER, updateDownload TEXT, updateDBTrimming TEXT, settingDownload TEXT, settingDBTrimming TEXT, enrollmentDownload TEXT, enrollmentDBTrimming TEXT, eventDateDownload TEXT, eventDateDBTrimming TEXT, enrollmentDateDownload TEXT, enrollmentDateDBTrimming TEXT, FOREIGN KEY (uid) REFERENCES Program (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); CREATE TABLE SynchronizationSetting (_id INTEGER PRIMARY KEY AUTOINCREMENT, dataSync TEXT, metadataSync TEXT, trackerImporterVersion TEXT, trackerExporterVersion TEXT, fileMaxLengthBytes INTEGER); @@ -109,7 +109,7 @@ CREATE TABLE DataElementAttributeValueLink (_id INTEGER PRIMARY KEY AUTOINCREMEN CREATE TABLE ProgramAttributeValueLink (_id INTEGER PRIMARY KEY AUTOINCREMENT, program TEXT NOT NULL, attribute TEXT NOT NULL, value TEXT, FOREIGN KEY (program) REFERENCES Program (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (attribute) REFERENCES Attribute (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, UNIQUE (program, attribute)); CREATE TABLE TrackerJobObject (_id INTEGER PRIMARY KEY AUTOINCREMENT, trackerType TEXT NOT NULL, objectUid TEXT NOT NULL, jobUid TEXT NOT NULL, lastUpdated TEXT NOT NULL, fileResources TEXT); CREATE TABLE DataValueConflict (_id INTEGER PRIMARY KEY AUTOINCREMENT, conflict TEXT, value TEXT, attributeOptionCombo TEXT, categoryOptionCombo TEXT, dataElement TEXT, period TEXT, orgUnit TEXT, errorCode TEXT, status TEXT, created TEXT, displayDescription TEXT); -CREATE TABLE AnalyticsDhisVisualization (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL, scopeUid TEXT, scope TEXT, groupUid TEXT, groupName TEXT, timestamp TEXT, name TEXT, FOREIGN KEY (uid) REFERENCES Visualization (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); +CREATE TABLE AnalyticsDhisVisualization (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL, scopeUid TEXT, scope TEXT, groupUid TEXT, groupName TEXT, timestamp TEXT, name TEXT, type TEXT NOT NULL); CREATE TABLE Visualization (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL UNIQUE, code TEXT, name TEXT, displayName TEXT, created TEXT, lastUpdated TEXT, description TEXT, displayDescription TEXT, displayFormName TEXT, title TEXT, displayTitle TEXT, subtitle TEXT, displaySubtitle TEXT, type TEXT, hideTitle INTEGER, hideSubtitle INTEGER, hideEmptyColumns INTEGER, hideEmptyRows INTEGER, hideEmptyRowItems TEXT, hideLegend INTEGER, showHierarchy INTEGER, rowTotals INTEGER, rowSubTotals INTEGER, colTotals INTEGER, colSubTotals INTEGER, showDimensionLabels INTEGER, percentStackedValues INTEGER, noSpaceBetweenColumns INTEGER, skipRounding INTEGER, displayDensity TEXT, digitGroupSeparator TEXT, legendShowKey TEXT, legendStyle TEXT, legendSetId TEXT, legendStrategy TEXT, aggregationType TEXT); CREATE TABLE VisualizationDimensionItem(_id INTEGER PRIMARY KEY AUTOINCREMENT, visualization TEXT NOT NULL, position TEXT NOT NULL, dimension TEXT NOT NULL, dimensionItem TEXT, dimensionItemType TEXT, FOREIGN KEY (visualization) REFERENCES Visualization (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); CREATE TABLE LocalDataStore (_id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT NOT NULL UNIQUE, value TEXT); @@ -124,9 +124,13 @@ CREATE TABLE TrackedEntityAttributeLegendSetLink (_id INTEGER PRIMARY KEY AUTOIN CREATE TABLE ItemFilter (_id INTEGER PRIMARY KEY AUTOINCREMENT, eventFilter TEXT, dataItem TEXT, trackedEntityInstanceFilter TEXT, attribute TEXT, programStageWorkingList TEXT, sw TEXT, ew TEXT, le TEXT, ge TEXT, gt TEXT, lt TEXT, eq TEXT, inProperty TEXT, like TEXT, dateFilter TEXT, FOREIGN KEY (eventFilter) REFERENCES EventFilter (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (trackedEntityInstanceFilter) REFERENCES TrackedEntityInstanceFilter (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (programStageWorkingList) REFERENCES ProgramStageWorkingList (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); CREATE TABLE StockUseCase (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL UNIQUE, itemCode TEXT, itemDescription TEXT, programType TEXT, description TEXT, stockOnHand TEXT, FOREIGN KEY (uid) REFERENCES Program (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); CREATE TABLE StockUseCaseTransaction (_id INTEGER PRIMARY KEY AUTOINCREMENT, programUid TEXT NOT NULL, sortOrder INTEGER, transactionType TEXT, distributedTo TEXT, stockDistributed TEXT, stockDiscarded TEXT, stockCount TEXT, FOREIGN KEY (programUid) REFERENCES StockUseCase (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); -CREATE TABLE MapLayer (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL UNIQUE, name TEXT NOT NULL, displayName TEXT NOT NULL, external INTEGER, mapLayerPosition TEXT NOT NULL, style TEXT, imageUrl TEXT NOT NULL, subdomains TEXT, subdomainPlaceholder TEXT); +CREATE TABLE MapLayer (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL UNIQUE, name TEXT NOT NULL, displayName TEXT NOT NULL, external INTEGER, mapLayerPosition TEXT NOT NULL, style TEXT, imageUrl TEXT NOT NULL, subdomains TEXT, subdomainPlaceholder TEXT, code TEXT, mapService TEXT, imageFormat TEXT, layers TEXT); CREATE TABLE MapLayerImageryProvider (_id INTEGER PRIMARY KEY AUTOINCREMENT, mapLayer TEXT NOT NULL, attribution TEXT NOT NULL, coverageAreas TEXT, FOREIGN KEY (mapLayer) REFERENCES MapLayer (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); CREATE TABLE DataStore (_id INTEGER PRIMARY KEY AUTOINCREMENT, namespace TEXT NOT NULL, key TEXT NOT NULL, value TEXT, syncState TEXT, deleted INTEGER, UNIQUE(namespace, key)); CREATE TABLE ProgramStageWorkingList (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL UNIQUE, code TEXT, name TEXT, displayName TEXT, created TEXT, lastUpdated TEXT, description TEXT, program TEXT NOT NULL, programStage TEXT NOT NULL, eventStatus TEXT, eventCreatedAt TEXT, eventOccurredAt TEXT, eventScheduledAt TEXT, enrollmentStatus TEXT, enrolledAt TEXT, enrollmentOccurredAt TEXT, orderProperty TEXT, displayColumnOrder TEXT, orgUnit TEXT, ouMode TEXT, assignedUserMode TEXT, FOREIGN KEY (program) REFERENCES Program (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (programStage) REFERENCES ProgramStage (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (orgUnit) REFERENCES OrganisationUnit (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); CREATE TABLE LatestAppVersion (_id INTEGER PRIMARY KEY AUTOINCREMENT, downloadURL TEXT, version TEXT); CREATE TABLE ExpressionDimensionItem (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL UNIQUE, code TEXT, name TEXT, displayName TEXT, created TEXT, lastUpdated TEXT, expression TEXT); +CREATE TABLE TrackerVisualization(_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL UNIQUE, code TEXT, name TEXT, displayName TEXT, created TEXT, lastUpdated TEXT, description TEXT, displayDescription TEXT, type TEXT, outputType TEXT, program TEXT, programStage TEXT, trackedEntityType TEXT, FOREIGN KEY (program) REFERENCES Program (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (programStage) REFERENCES ProgramStage (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (trackedEntityType) REFERENCES TrackedEntityType (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); +CREATE TABLE TrackerVisualizationDimension(_id INTEGER PRIMARY KEY AUTOINCREMENT, trackerVisualization TEXT NOT NULL, position TEXT NOT NULL, dimension TEXT NOT NULL, dimensionType TEXT, program TEXT, programStage TEXT, items TEXT, filter TEXT, repetition TEXT, FOREIGN KEY (trackerVisualization) REFERENCES TrackerVisualization (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (program) REFERENCES Program (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY (programStage) REFERENCES ProgramStage (uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); +CREATE TABLE CustomIcon(_id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT NOT NULL, fileResource TEXT NOT NULL, href TEXT NOT NULL); +CREATE TABLE UserGroup (_id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL UNIQUE, code TEXT, name TEXT, displayName TEXT, created TEXT, lastUpdated TEXT); \ No newline at end of file diff --git a/core/src/main/java/org/hisp/dhis/android/core/D2.kt b/core/src/main/java/org/hisp/dhis/android/core/D2.kt index 29b5575227..c15ec42296 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/D2.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/D2.kt @@ -47,6 +47,7 @@ import org.hisp.dhis.android.core.enrollment.EnrollmentModule import org.hisp.dhis.android.core.event.EventModule import org.hisp.dhis.android.core.expressiondimensionitem.ExpressionDimensionItemModule import org.hisp.dhis.android.core.fileresource.FileResourceModule +import org.hisp.dhis.android.core.icon.IconModule import org.hisp.dhis.android.core.imports.internal.ImportModule import org.hisp.dhis.android.core.indicator.IndicatorModule import org.hisp.dhis.android.core.legendset.LegendSetModule @@ -169,6 +170,10 @@ class D2 internal constructor(internal val d2DIComponent: D2DIComponent) { return modules.fileResource } + fun iconModule(): IconModule { + return modules.icon + } + fun importModule(): ImportModule { return modules.importModule } diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/AnalyticsDIModule.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/AnalyticsDIModule.kt index cbaaa1ed55..d9998cdcec 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/analytics/AnalyticsDIModule.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/AnalyticsDIModule.kt @@ -31,6 +31,7 @@ package org.hisp.dhis.android.core.analytics import org.hisp.dhis.android.core.analytics.aggregated.internal.AnalyticsRepositoryParams import org.hisp.dhis.android.core.analytics.aggregated.internal.AnalyticsVisualizationsRepositoryParams import org.hisp.dhis.android.core.analytics.linelist.EventLineListParams +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.TrackerLineListParams import org.koin.core.annotation.ComponentScan import org.koin.core.annotation.Module import org.koin.core.annotation.Singleton @@ -52,4 +53,9 @@ internal class AnalyticsDIModule { fun emptyAnalyticsVisualizationsParam(): AnalyticsVisualizationsRepositoryParams { return AnalyticsVisualizationsRepositoryParams(null, null, null) } + + @Singleton + fun emptyTrackerLineListParams(): TrackerLineListParams { + return TrackerLineListParams(null, null, null, null, emptyList(), emptyList()) + } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/AnalyticsException.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/AnalyticsException.kt index 77be65b168..ce0fd65fe7 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/analytics/AnalyticsException.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/AnalyticsException.kt @@ -38,6 +38,7 @@ sealed class AnalyticsException(message: String) : Throwable(message) { class InvalidDataElementOperand(val uid: String) : AnalyticsException("Missing DataElementOperand $uid") class InvalidProgramIndicator(val uid: String) : AnalyticsException("Missing ProgramIndicator $uid") class InvalidProgram(val uid: String) : AnalyticsException("Missing Program $uid") + class InvalidProgramStage(val uid: String) : AnalyticsException("Missing ProgramStage $uid") class InvalidIndicator(val uid: String) : AnalyticsException("Missing Indicator $uid") class InvalidExpressionDimensionItem(val uid: String) : AnalyticsException("Missing ExpressionDimensionItem $uid") class InvalidOrganisationUnit(val uid: String) : AnalyticsException("Missing organisation unit $uid") diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/AnalyticsModule.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/AnalyticsModule.kt index d9a32380fc..4caa013929 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/analytics/AnalyticsModule.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/AnalyticsModule.kt @@ -30,6 +30,7 @@ package org.hisp.dhis.android.core.analytics import org.hisp.dhis.android.core.analytics.aggregated.AnalyticsRepository import org.hisp.dhis.android.core.analytics.aggregated.AnalyticsVisualizationsRepository import org.hisp.dhis.android.core.analytics.linelist.EventLineListRepository +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListRepository interface AnalyticsModule { @@ -38,4 +39,6 @@ interface AnalyticsModule { fun analytics(): AnalyticsRepository fun visualizations(): AnalyticsVisualizationsRepository + + fun trackerLineList(): TrackerLineListRepository } diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/AnalyticsModuleImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/AnalyticsModuleImpl.kt index 4f78431fc2..10b8c07672 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/analytics/AnalyticsModuleImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/AnalyticsModuleImpl.kt @@ -30,6 +30,7 @@ package org.hisp.dhis.android.core.analytics import org.hisp.dhis.android.core.analytics.aggregated.AnalyticsRepository import org.hisp.dhis.android.core.analytics.aggregated.AnalyticsVisualizationsRepository import org.hisp.dhis.android.core.analytics.linelist.EventLineListRepository +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListRepository import org.koin.core.annotation.Singleton @Singleton @@ -37,6 +38,7 @@ internal class AnalyticsModuleImpl( private val eventLineListRepository: EventLineListRepository, private val analyticsRepository: AnalyticsRepository, private val analyticsVisualizationsRepository: AnalyticsVisualizationsRepository, + private val trackerLineListRepository: TrackerLineListRepository, ) : AnalyticsModule { override fun eventLineList(): EventLineListRepository = eventLineListRepository @@ -44,4 +46,6 @@ internal class AnalyticsModuleImpl( override fun analytics(): AnalyticsRepository = analyticsRepository override fun visualizations(): AnalyticsVisualizationsRepository = analyticsVisualizationsRepository + + override fun trackerLineList(): TrackerLineListRepository = trackerLineListRepository } diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/aggregated/AnalyticsModel.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/aggregated/AnalyticsModel.kt index e625db63c4..59e197394d 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/analytics/aggregated/AnalyticsModel.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/aggregated/AnalyticsModel.kt @@ -43,6 +43,7 @@ import org.hisp.dhis.android.core.organisationunit.OrganisationUnitLevel import org.hisp.dhis.android.core.period.Period import org.hisp.dhis.android.core.program.Program import org.hisp.dhis.android.core.program.ProgramIndicator +import org.hisp.dhis.android.core.program.ProgramStage import org.hisp.dhis.android.core.trackedentity.TrackedEntityAttribute sealed class MetadataItem(val id: String, val displayName: String) { @@ -50,8 +51,12 @@ sealed class MetadataItem(val id: String, val displayName: String) { class DataElementOperandItem(val item: DataElementOperand, dataElementName: String, cocName: String?) : MetadataItem(item.uid()!!, "$dataElementName $cocName") + class TrackedEntityAttributeItem(val item: TrackedEntityAttribute) : MetadataItem(item.uid(), item.displayName()!!) + class IndicatorItem(val item: Indicator) : MetadataItem(item.uid(), item.displayName()!!) class ProgramIndicatorItem(val item: ProgramIndicator) : MetadataItem(item.uid(), item.displayName()!!) + class ProgramItem(val item: Program) : MetadataItem(item.uid(), item.displayName()!!) + class ProgramStageItem(val item: ProgramStage) : MetadataItem(item.uid(), item.displayName()!!) class EventDataElementItem(val item: DataElement, val program: Program) : MetadataItem("${program.uid()}.${item.uid()}", "${program.displayName()} ${item.displayName()}") class EventAttributeItem(val item: TrackedEntityAttribute, val program: Program) : diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/aggregated/internal/AnalyticsVisualizationsServiceDimensionHelper.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/aggregated/internal/AnalyticsVisualizationsServiceDimensionHelper.kt index 6037834850..d0732c90fc 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/analytics/aggregated/internal/AnalyticsVisualizationsServiceDimensionHelper.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/aggregated/internal/AnalyticsVisualizationsServiceDimensionHelper.kt @@ -32,6 +32,9 @@ import org.hisp.dhis.android.core.analytics.AnalyticsException import org.hisp.dhis.android.core.analytics.aggregated.Dimension import org.hisp.dhis.android.core.analytics.aggregated.DimensionItem import org.hisp.dhis.android.core.analytics.aggregated.GridDimension +import org.hisp.dhis.android.core.analytics.internal.AnalyticsRegex.composedUidOperandRegex +import org.hisp.dhis.android.core.analytics.internal.AnalyticsRegex.orgunitLevelRegex +import org.hisp.dhis.android.core.analytics.internal.AnalyticsRegex.uidRegex import org.hisp.dhis.android.core.arch.db.querybuilders.internal.WhereClauseBuilder import org.hisp.dhis.android.core.category.internal.CategoryCategoryOptionLinkStore import org.hisp.dhis.android.core.category.internal.CategoryStore @@ -54,9 +57,6 @@ internal class AnalyticsVisualizationsServiceDimensionHelper( private val dataDimension = "dx" private val orgUnitDimension = "ou" private val periodDimension = "pe" - private val uidRegex = "^\\w{11}\$".toRegex() - private val composedUidOperandRegex = "^(\\w{11})\\.(\\w{11})\$".toRegex() - private val orgunitLevelRegex = "LEVEL-(\\d+)".toRegex() fun getDimensionItems(dimensions: List?): List { return dimensions?.map { dimension -> diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/internal/AnalyticsModelHelper.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/internal/AnalyticsModelHelper.kt new file mode 100644 index 0000000000..030ccbb187 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/internal/AnalyticsModelHelper.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.internal + +internal object AnalyticsModelHelper { + fun eventDataElementId(programStage: String, dataElement: String): String { + return "$programStage.$dataElement" + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/internal/AnalyticsRegex.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/internal/AnalyticsRegex.kt new file mode 100644 index 0000000000..6410f656be --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/internal/AnalyticsRegex.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.internal + +internal object AnalyticsRegex { + val uidRegex = "^\\w{11}\$".toRegex() + val composedUidOperandRegex = "^(\\w{11})\\.(\\w{11})\$".toRegex() + val orgunitLevelRegex = "^LEVEL-(\\d+)\$".toRegex() + val orgunitGroupRegex = "^OU_GROUP-(\\w{11})\$".toRegex() + val dateRangeRegex = "^(\\d{4}-\\d{1,2}-\\d{1,2})_(\\d{4}-\\d{1,2}-\\d{1,2})\$".toRegex() +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/TrackerLineListModel.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/TrackerLineListModel.kt new file mode 100644 index 0000000000..7f79dee3b2 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/TrackerLineListModel.kt @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist + +import org.hisp.dhis.android.core.analytics.internal.AnalyticsModelHelper.eventDataElementId +import org.hisp.dhis.android.core.common.RelativeOrganisationUnit +import org.hisp.dhis.android.core.common.RelativePeriod +import org.hisp.dhis.android.core.enrollment.EnrollmentStatus +import org.hisp.dhis.android.core.event.EventStatus + +sealed class TrackerLineListItem(val id: String) { + + data class OrganisationUnitItem(val filters: List = emptyList()) : + TrackerLineListItem(Label.OrganisationUnit) + + data class LastUpdated(override val filters: List = emptyList()) : + TrackerLineListItem(Label.LastUpdated), DateItem + + data class IncidentDate(override val filters: List = emptyList()) : + TrackerLineListItem(Label.IncidentDate), DateItem + + data class EnrollmentDate(override val filters: List = emptyList()) : + TrackerLineListItem(Label.EnrollmentDate), DateItem + + data class ScheduledDate(override val filters: List = emptyList()) : + TrackerLineListItem(Label.ScheduledDate), DateItem + + data class EventDate(override val filters: List = emptyList()) : + TrackerLineListItem(Label.EventDate), DateItem + + data class ProgramIndicator(val uid: String, val filters: List = emptyList()) : + TrackerLineListItem(uid) + + data class ProgramAttribute(val uid: String, val filters: List = emptyList()) : + TrackerLineListItem(uid) + + data class ProgramDataElement( + val dataElement: String, + val programStage: String, + val filters: List = emptyList(), + val repetitionIndexes: List? = null, + ) : TrackerLineListItem( + eventDataElementId(programStage, dataElement) + + (repetitionIndexes?.joinToString { it.toString() } ?: ""), + ) { + + val stageDataElementIdx = eventDataElementId(programStage, dataElement) + } + + data class ProgramStatusItem(val filters: List> = emptyList()) : + TrackerLineListItem(Label.ProgramStatus) + + data class EventStatusItem(val filters: List> = emptyList()) : + TrackerLineListItem(Label.EventStatus) + + object CreatedBy : TrackerLineListItem(Label.CreatedBy) + + object LastUpdatedBy : TrackerLineListItem(Label.LastUpdatedBy) +} + +internal interface DateItem { + val id: String + val filters: List +} + +sealed class OrganisationUnitFilter { + data class Absolute(val uid: String) : OrganisationUnitFilter() + data class Relative(val relative: RelativeOrganisationUnit) : OrganisationUnitFilter() + data class Level(val uid: String) : OrganisationUnitFilter() + data class Group(val uid: String) : OrganisationUnitFilter() + data class EqualTo(val orgunitName: String, val ignoreCase: Boolean = false) : OrganisationUnitFilter() + data class NotEqualTo(val orgunitName: String, val ignoreCase: Boolean = false) : OrganisationUnitFilter() + data class Like(val orgunitName: String, val ignoreCase: Boolean = true) : OrganisationUnitFilter() + data class NotLike(val orgunitName: String, val ignoreCase: Boolean = true) : OrganisationUnitFilter() +} + +sealed class DateFilter { + data class Relative(val relative: RelativePeriod) : DateFilter() + data class Absolute(val uid: String) : DateFilter() + data class Range(val startDate: String, val endDate: String) : DateFilter() + data class EqualTo(val timestamp: String, val ignoreCase: Boolean = false) : DateFilter() + data class NotEqualTo(val timestamp: String, val ignoreCase: Boolean = false) : DateFilter() + data class Like(val timestamp: String, val ignoreCase: Boolean = true) : DateFilter() + data class NotLike(val timestamp: String, val ignoreCase: Boolean = true) : DateFilter() +} + +sealed class EnumFilter { + data class EqualTo(val value: String, val ignoreCase: Boolean = false) : EnumFilter() + data class NotEqualTo(val value: String, val ignoreCase: Boolean = false) : EnumFilter() + data class Like(val value: String, val ignoreCase: Boolean = true) : EnumFilter() + data class NotLike(val value: String, val ignoreCase: Boolean = true) : EnumFilter() + data class In(val values: List) : EnumFilter() +} + +sealed class DataFilter { + data class EqualTo(val value: String, val ignoreCase: Boolean = false) : DataFilter() + data class NotEqualTo(val value: String, val ignoreCase: Boolean = false) : DataFilter() + data class GreaterThan(val value: String) : DataFilter() + data class GreaterThanOrEqualTo(val value: String) : DataFilter() + data class LowerThan(val value: String) : DataFilter() + data class LowerThanOrEqualTo(val value: String) : DataFilter() + data class Like(val value: String, val ignoreCase: Boolean = true) : DataFilter() + data class NotLike(val value: String, val ignoreCase: Boolean = true) : DataFilter() + data class In(val values: List) : DataFilter() +} + +internal object Label { + const val OrganisationUnit = "ouItem" + const val LastUpdated = "lastUpdatedItem" + const val IncidentDate = "incidentDateItem" + const val EnrollmentDate = "enrollmentDateItem" + const val ScheduledDate = "scheduledDateItem" + const val EventDate = "eventDateItem" + const val CreatedBy = "createdByItem" + const val LastUpdatedBy = "lastUpdatedByItem" + const val ProgramStatus = "programStatusItem" + const val EventStatus = "eventStatusItem" +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/TrackerLineListRepository.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/TrackerLineListRepository.kt new file mode 100644 index 0000000000..a5fae75944 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/TrackerLineListRepository.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist + +import io.reactivex.Single +import org.hisp.dhis.android.core.analytics.AnalyticsException +import org.hisp.dhis.android.core.arch.helpers.Result +import org.hisp.dhis.android.core.arch.repositories.paging.PageConfig + +interface TrackerLineListRepository { + + fun withEventOutput(programStageId: String): TrackerLineListRepository + + fun withEnrollmentOutput(programId: String): TrackerLineListRepository + + fun withColumn(column: TrackerLineListItem): TrackerLineListRepository + + fun withFilter(filter: TrackerLineListItem): TrackerLineListRepository + + fun withTrackerVisualization(trackerVisualization: String): TrackerLineListRepository + + fun withPageConfig(pageConfig: PageConfig): TrackerLineListRepository + + fun evaluate(): Single> + + fun blockingEvaluate(): Result +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/TrackerLineListResponse.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/TrackerLineListResponse.kt new file mode 100644 index 0000000000..00d5d5a09d --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/TrackerLineListResponse.kt @@ -0,0 +1,43 @@ +/* + * 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.analytics.trackerlinelist + +import org.hisp.dhis.android.core.analytics.aggregated.MetadataItem + +data class TrackerLineListResponse( + val metadata: Map, + val headers: List, + val filters: List, + val rows: List>, +) + +data class TrackerLineListValue( + val id: String, + val value: String?, +) diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListContext.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListContext.kt new file mode 100644 index 0000000000..b2c74b131f --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListContext.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal + +import org.hisp.dhis.android.core.analytics.aggregated.MetadataItem +import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter + +data class TrackerLineListContext( + val metadata: Map, + val databaseAdapter: DatabaseAdapter, +) diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListOutputType.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListOutputType.kt new file mode 100644 index 0000000000..001fbff602 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListOutputType.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal + +internal enum class TrackerLineListOutputType { + EVENT, + ENROLLMENT, +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListParams.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListParams.kt new file mode 100644 index 0000000000..f0ca6a8da9 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListParams.kt @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal + +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListItem +import org.hisp.dhis.android.core.arch.repositories.paging.PageConfig +import org.hisp.dhis.android.core.util.replaceOrPush + +internal data class TrackerLineListParams( + val trackerVisualization: String?, + val outputType: TrackerLineListOutputType?, + val programId: String?, + val programStageId: String?, + val columns: List, + val filters: List, + val pageConfig: PageConfig = DefaultPaging, +) { + val allItems = columns + filters + + operator fun plus(other: TrackerLineListParams): TrackerLineListParams { + return copy( + outputType = other.outputType ?: outputType, + programId = other.programId ?: programId, + programStageId = other.programStageId ?: programStageId, + ).run { + other.columns.fold(this) { params, item -> params.updateInColumns(item) } + }.run { + other.filters.fold(this) { params, item -> params.updateInFilters(item) } + } + } + + fun updateInColumns(item: TrackerLineListItem): TrackerLineListParams { + return copy( + columns = columns.replaceOrPush(item) { it.id == item.id }, + filters = filters.filterNot { it.id == item.id }, + ) + } + + fun updateInFilters(item: TrackerLineListItem): TrackerLineListParams { + return copy( + columns = columns.filterNot { it.id == item.id }, + filters = filters.replaceOrPush(item) { it.id == item.id }, + ) + } + + fun hasOrgunit(): Boolean { + return (columns + filters).any { it is TrackerLineListItem.OrganisationUnitItem } + } + + fun flattenRepeatedDataElements(): TrackerLineListParams { + return this.copy( + columns = flattenRepeatedDataElements(this.columns), + filters = flattenRepeatedDataElements(this.filters), + ) + } + + private fun flattenRepeatedDataElements(items: List): List { + return items.map { item -> + when (item) { + is TrackerLineListItem.ProgramDataElement -> flattenDataElement(item) + else -> listOf(item) + } + }.flatten() + } + + private fun flattenDataElement(item: TrackerLineListItem.ProgramDataElement): List { + return if (item.repetitionIndexes.isNullOrEmpty()) { + listOf(item) + } else { + sortIndexes(item.repetitionIndexes).map { idx -> item.copy(repetitionIndexes = listOf(idx)) } + } + } + + private fun sortIndexes(indexes: List): List { + val (positive, negativeOrZero) = indexes.sorted().partition { it > 0 } + return positive + negativeOrZero + } + + companion object { + val DefaultPaging = PageConfig.Paging(1, 500) + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListRepositoryImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListRepositoryImpl.kt new file mode 100644 index 0000000000..f6197ecdc4 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListRepositoryImpl.kt @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal + +import io.reactivex.Single +import org.hisp.dhis.android.core.analytics.AnalyticsException +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListItem +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListRepository +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListResponse +import org.hisp.dhis.android.core.arch.helpers.Result +import org.hisp.dhis.android.core.arch.repositories.paging.PageConfig +import org.koin.core.annotation.Singleton + +@Singleton +internal class TrackerLineListRepositoryImpl( + private val params: TrackerLineListParams, + private val service: TrackerLineListService, +) : TrackerLineListRepository { + + override fun withEventOutput(programStageId: String): TrackerLineListRepositoryImpl { + return updateParams { + params.copy( + outputType = TrackerLineListOutputType.EVENT, + programStageId = programStageId, + ) + } + } + + override fun withEnrollmentOutput(programId: String): TrackerLineListRepositoryImpl { + return updateParams { + params.copy( + outputType = TrackerLineListOutputType.ENROLLMENT, + programId = programId, + ) + } + } + + override fun withColumn(column: TrackerLineListItem): TrackerLineListRepositoryImpl { + return updateParams { params.updateInColumns(column) } + } + + override fun withFilter(filter: TrackerLineListItem): TrackerLineListRepositoryImpl { + return updateParams { params.updateInFilters(filter) } + } + + override fun withTrackerVisualization(trackerVisualization: String): TrackerLineListRepositoryImpl { + return updateParams { params.copy(trackerVisualization = trackerVisualization) } + } + + override fun withPageConfig(pageConfig: PageConfig): TrackerLineListRepository { + return updateParams { params.copy(pageConfig = pageConfig) } + } + + override fun evaluate(): Single> { + return Single.fromCallable { blockingEvaluate() } + } + + override fun blockingEvaluate(): Result { + return service.evaluate(params) + } + + private fun updateParams( + func: (params: TrackerLineListParams) -> TrackerLineListParams, + ): TrackerLineListRepositoryImpl { + return TrackerLineListRepositoryImpl(func(params), service) + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListService.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListService.kt new file mode 100644 index 0000000000..d81993e1ed --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListService.kt @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal + +import org.hisp.dhis.android.core.analytics.AnalyticsException +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListItem +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListResponse +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.TrackerLineListServiceHelper.mapCursorToColumns +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator.TrackerLineListEvaluatorMapper +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator.TrackerLineListSQLLabel.EnrollmentAlias +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator.TrackerLineListSQLLabel.EventAlias +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator.TrackerLineListSQLLabel.OrgunitAlias +import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.arch.helpers.Result +import org.hisp.dhis.android.core.arch.repositories.paging.PageConfig +import org.hisp.dhis.android.core.enrollment.EnrollmentTableInfo +import org.hisp.dhis.android.core.event.EventTableInfo +import org.hisp.dhis.android.core.organisationunit.OrganisationUnitTableInfo +import org.hisp.dhis.android.core.visualization.TrackerVisualization +import org.hisp.dhis.android.core.visualization.TrackerVisualizationCollectionRepository +import org.koin.core.annotation.Singleton +import java.lang.RuntimeException + +@Singleton +internal class TrackerLineListService( + private val databaseAdapter: DatabaseAdapter, + private val trackerVisualizationCollectionRepository: TrackerVisualizationCollectionRepository, + private val metadataHelper: TrackerLineListServiceMetadataHelper, + private val trackerVisualizationMapper: TrackerVisualizationMapper, +) { + @Suppress("TooGenericExceptionCaught") + fun evaluate(params: TrackerLineListParams): Result { + return try { + val evaluatedParams = evaluateParams(params) + + if (evaluatedParams.outputType == null) { + throw AnalyticsException.InvalidArguments("Output type cannot be empty.") + } + + val metadata = metadataHelper.getMetadata(evaluatedParams) + val context = TrackerLineListContext(metadata, databaseAdapter) + + val sqlClause = when (evaluatedParams.outputType) { + TrackerLineListOutputType.EVENT -> getEventSqlClause(evaluatedParams, context) + TrackerLineListOutputType.ENROLLMENT -> getEnrollmentSqlClause(evaluatedParams, context) + } + + val cursor = databaseAdapter.rawQuery(sqlClause) + val values = mapCursorToColumns(evaluatedParams, cursor) + + Result.Success( + TrackerLineListResponse( + metadata = metadata, + headers = evaluatedParams.columns, + filters = evaluatedParams.filters, + rows = values, + ), + ) + } catch (e: AnalyticsException) { + Result.Failure(e) + } catch (e: RuntimeException) { + Result.Failure(AnalyticsException.SQLException(e.message ?: "")) + } + } + + private fun evaluateParams(params: TrackerLineListParams): TrackerLineListParams { + return params + .run { + if (this.trackerVisualization != null) { + val visualization = getTrackerVisualization(this.trackerVisualization) + ?: throw AnalyticsException.InvalidVisualization(this.trackerVisualization) + + trackerVisualizationMapper.toTrackerLineListParams(visualization) + this + } else { + this + } + }.run { + this.flattenRepeatedDataElements() + } + } + + private fun getTrackerVisualization(trackerVisualization: String): TrackerVisualization? { + return trackerVisualizationCollectionRepository + .withColumnsAndFilters() + .uid(trackerVisualization) + .blockingGet() + } + + private fun getEventSqlClause(params: TrackerLineListParams, context: TrackerLineListContext): String { + return "SELECT " + + "${getEventSelectColumns(params, context)} " + + "FROM ${EventTableInfo.TABLE_INFO.name()} $EventAlias " + + "LEFT JOIN ${EnrollmentTableInfo.TABLE_INFO.name()} $EnrollmentAlias " + + "ON $EventAlias.${EventTableInfo.Columns.ENROLLMENT} = " + + "$EnrollmentAlias.${EnrollmentTableInfo.Columns.UID} " + + if (params.hasOrgunit()) { + "LEFT JOIN ${OrganisationUnitTableInfo.TABLE_INFO.name()} $OrgunitAlias " + + "ON $EventAlias.${EventTableInfo.Columns.ORGANISATION_UNIT} = " + + "$OrgunitAlias.${OrganisationUnitTableInfo.Columns.UID} " + } else { + "" + } + + "WHERE " + + "$EventAlias.${EventTableInfo.Columns.PROGRAM_STAGE} = '${params.programStageId!!}' AND " + + "${getEventWhereClause(params, context)} " + + appendPaging(params.pageConfig) + } + + private fun getEnrollmentSqlClause(params: TrackerLineListParams, context: TrackerLineListContext): String { + return "SELECT " + + "${getEnrollmentSelectColumns(params, context)} " + + "FROM ${EnrollmentTableInfo.TABLE_INFO.name()} $EnrollmentAlias " + + if (params.hasOrgunit()) { + "LEFT JOIN ${OrganisationUnitTableInfo.TABLE_INFO.name()} $OrgunitAlias " + + "ON $EnrollmentAlias.${EnrollmentTableInfo.Columns.ORGANISATION_UNIT} = " + + "$OrgunitAlias.${OrganisationUnitTableInfo.Columns.UID} " + } else { + "" + } + + "WHERE " + + "$EnrollmentAlias.${EnrollmentTableInfo.Columns.PROGRAM} = '${params.programId!!}' AND " + + "${getEnrollmentWhereClause(params, context)} " + + appendPaging(params.pageConfig) + } + + private fun getEventSelectColumns(params: TrackerLineListParams, context: TrackerLineListContext): String { + return params.allItems.joinToString(", ") { + "(${TrackerLineListEvaluatorMapper.getEvaluator(it, context).getSelectSQLForEvent()}) '${it.id}'" + } + } + + private fun getEventWhereClause(params: TrackerLineListParams, context: TrackerLineListContext): String { + return params.allItems.joinToString(" AND ") { + TrackerLineListEvaluatorMapper.getEvaluator(it, context).getWhereSQLForEvent() + } + } + + private fun getEnrollmentSelectColumns(params: TrackerLineListParams, context: TrackerLineListContext): String { + return params.allItems.joinToString(", ") { + "(${TrackerLineListEvaluatorMapper.getEvaluator(it, context).getSelectSQLForEnrollment()}) '${it.id}'" + } + } + + private fun getEnrollmentWhereClause(params: TrackerLineListParams, context: TrackerLineListContext): String { + val unflattenedRepeatedDataElements = params.allItems.groupBy { item -> + when (item) { + is TrackerLineListItem.ProgramDataElement -> item.stageDataElementIdx + else -> item.id + } + } + + return unflattenedRepeatedDataElements.values.joinToString(" AND ") { items -> + val orClause = items.joinToString(" OR ") { + TrackerLineListEvaluatorMapper.getEvaluator(it, context).getWhereSQLForEnrollment() + } + "($orClause)" + } + } + + private fun appendPaging(pageConfig: PageConfig): String { + return when (pageConfig) { + is PageConfig.NoPaging -> "" + is PageConfig.Paging -> "LIMIT ${pageConfig.pageSize} OFFSET ${pageConfig.pageSize * (pageConfig.page - 1)}" + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListServiceHelper.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListServiceHelper.kt new file mode 100644 index 0000000000..2f3ac504cc --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListServiceHelper.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal + +import android.database.Cursor +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListItem +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListValue + +internal object TrackerLineListServiceHelper { + fun mapCursorToColumns(params: TrackerLineListParams, cursor: Cursor): List> { + val rows: MutableList> = mutableListOf() + cursor.use { c -> + if (c.count > 0) { + c.moveToFirst() + do { + val row = mapRowValues(cursor, params.columns) + rows.add(row) + } while (c.moveToNext()) + } + } + return rows + } + + private fun mapRowValues(cursor: Cursor, columns: List): List { + val row: MutableList = mutableListOf() + columns.forEach { item -> + val columnIndex = cursor.columnNames.indexOf(item.id) + row.add(TrackerLineListValue(item.id, cursor.getString(columnIndex))) + } + return row + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListServiceMetadataHelper.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListServiceMetadataHelper.kt new file mode 100644 index 0000000000..7205f2a82b --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListServiceMetadataHelper.kt @@ -0,0 +1,122 @@ +/* + * 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.analytics.trackerlinelist.internal + +import org.hisp.dhis.android.core.analytics.AnalyticsException +import org.hisp.dhis.android.core.analytics.aggregated.MetadataItem +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListItem +import org.hisp.dhis.android.core.dataelement.internal.DataElementStore +import org.hisp.dhis.android.core.program.Program +import org.hisp.dhis.android.core.program.ProgramStage +import org.hisp.dhis.android.core.program.internal.ProgramIndicatorStore +import org.hisp.dhis.android.core.program.internal.ProgramStageStore +import org.hisp.dhis.android.core.program.internal.ProgramStore +import org.hisp.dhis.android.core.trackedentity.internal.TrackedEntityAttributeStore +import org.koin.core.annotation.Singleton + +@Singleton +internal class TrackerLineListServiceMetadataHelper( + private val trackedEntityAttributeStore: TrackedEntityAttributeStore, + private val dataElementStore: DataElementStore, + private val programIndicatorStore: ProgramIndicatorStore, + private val programStore: ProgramStore, + private val programStageStore: ProgramStageStore, +) { + + fun getMetadata(params: TrackerLineListParams): Map { + val metadata: MutableMap = mutableMapOf() + + (params.columns + params.filters).forEach { item -> + metadata += getMetadata(item) + } + + return metadata + } + + private fun getMetadata(item: TrackerLineListItem): Map { + val metadata: MutableMap = mutableMapOf() + + if (!metadata.containsKey(item.id)) { + val metadataItems = when (item) { + is TrackerLineListItem.ProgramAttribute -> getProgramAttributeItems(item) + is TrackerLineListItem.ProgramDataElement -> getProgramDataElement(item) + is TrackerLineListItem.ProgramIndicator -> getProgramIndicator(item) + else -> emptyList() + } + val metadataItemsMap = metadataItems.associateBy { it.id } + + metadata += metadataItemsMap + } + + return metadata + } + + private fun getProgramAttributeItems(item: TrackerLineListItem.ProgramAttribute): List { + val attribute = trackedEntityAttributeStore.selectByUid(item.uid) + ?: throw AnalyticsException.InvalidTrackedEntityAttribute(item.uid) + + return listOf( + MetadataItem.TrackedEntityAttributeItem(attribute), + ) + } + + private fun getProgramIndicator(item: TrackerLineListItem.ProgramIndicator): List { + val programIndicator = programIndicatorStore.selectByUid(item.uid) + ?: throw AnalyticsException.InvalidProgramIndicator(item.uid) + + return listOf( + MetadataItem.ProgramIndicatorItem(programIndicator), + ) + } + + private fun getProgramDataElement(item: TrackerLineListItem.ProgramDataElement): List { + val dataElement = dataElementStore.selectByUid(item.dataElement) + ?.let { MetadataItem.DataElementItem(it) } + ?: throw AnalyticsException.InvalidDataElement(item.dataElement) + + val programStage = getProgramStage(item.programStage) + .let { MetadataItem.ProgramStageItem(it) } + + val program = programStage.item.program()?.uid()?.let { getProgram(it) } + ?.let { MetadataItem.ProgramItem(it) } + ?: throw AnalyticsException.InvalidArguments("ProgramStage ${programStage.item.uid()} has no program") + + return listOf(dataElement, program, programStage) + } + + private fun getProgram(programId: String): Program { + return programStore.selectByUid(programId) + ?: throw AnalyticsException.InvalidProgram(programId) + } + + private fun getProgramStage(programStageId: String): ProgramStage { + return programStageStore.selectByUid(programStageId) + ?: throw AnalyticsException.InvalidProgramStage(programStageId) + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerVisualizationMapper.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerVisualizationMapper.kt new file mode 100644 index 0000000000..42512917b5 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerVisualizationMapper.kt @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal + +import org.hisp.dhis.android.core.analytics.AnalyticsException +import org.hisp.dhis.android.core.analytics.internal.AnalyticsRegex.dateRangeRegex +import org.hisp.dhis.android.core.analytics.internal.AnalyticsRegex.orgunitGroupRegex +import org.hisp.dhis.android.core.analytics.internal.AnalyticsRegex.orgunitLevelRegex +import org.hisp.dhis.android.core.analytics.internal.AnalyticsRegex.uidRegex +import org.hisp.dhis.android.core.analytics.trackerlinelist.DataFilter +import org.hisp.dhis.android.core.analytics.trackerlinelist.DateFilter +import org.hisp.dhis.android.core.analytics.trackerlinelist.EnumFilter +import org.hisp.dhis.android.core.analytics.trackerlinelist.OrganisationUnitFilter +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListItem +import org.hisp.dhis.android.core.arch.db.querybuilders.internal.WhereClauseBuilder +import org.hisp.dhis.android.core.common.RelativeOrganisationUnit +import org.hisp.dhis.android.core.common.RelativePeriod +import org.hisp.dhis.android.core.enrollment.EnrollmentStatus +import org.hisp.dhis.android.core.event.EventStatus +import org.hisp.dhis.android.core.organisationunit.OrganisationUnitLevelTableInfo +import org.hisp.dhis.android.core.organisationunit.internal.OrganisationUnitLevelStore +import org.hisp.dhis.android.core.visualization.TrackerVisualization +import org.hisp.dhis.android.core.visualization.TrackerVisualizationDimension +import org.hisp.dhis.android.core.visualization.TrackerVisualizationOutputType +import org.koin.core.annotation.Singleton + +@Singleton +@Suppress("TooManyFunctions") +internal class TrackerVisualizationMapper( + private val organisationUnitLevelStore: OrganisationUnitLevelStore, +) { + fun toTrackerLineListParams(trackerVisualization: TrackerVisualization): TrackerLineListParams { + return TrackerLineListParams( + trackerVisualization = trackerVisualization.uid(), + programId = trackerVisualization.program()?.uid(), + programStageId = trackerVisualization.programStage()?.uid(), + outputType = mapOutputType(trackerVisualization.outputType()), + columns = mapDimensions(trackerVisualization.columns(), trackerVisualization), + filters = mapDimensions(trackerVisualization.filters(), trackerVisualization), + ) + } + + private fun mapOutputType(type: TrackerVisualizationOutputType?): TrackerLineListOutputType? { + return when (type) { + TrackerVisualizationOutputType.ENROLLMENT -> TrackerLineListOutputType.ENROLLMENT + TrackerVisualizationOutputType.EVENT -> TrackerLineListOutputType.EVENT + else -> null + } + } + + private fun mapDimensions( + dimensions: List?, + trackerVisualization: TrackerVisualization, + ): List { + return dimensions?.mapNotNull { item -> + when (item.dimensionType()) { + "ORGANISATION_UNIT" -> mapOrganisationUnit(item) + "PERIOD" -> mapPeriod(item) + "PROGRAM_INDICATOR" -> mapProgramIndicator(item) + "PROGRAM_ATTRIBUTE" -> mapProgramAttribute(item) + "PROGRAM_DATA_ELEMENT" -> mapProgramDataElement(item, trackerVisualization) + "DATA_X" -> mapDataX(item) + "ORGANISATION_UNIT_GROUP_SET" -> + throw AnalyticsException.InvalidArguments("Dimension ORGANISATION_UNIT_GROUP_SET IS not supported") + + else -> null + } + } ?: emptyList() + } + + private fun mapOrganisationUnit(item: TrackerVisualizationDimension): TrackerLineListItem? { + return TrackerLineListItem.OrganisationUnitItem( + filters = item.items()?.mapNotNull { it.uid() }?.mapNotNull { uid -> + val relativeOrgunit = RelativeOrganisationUnit.entries.find { it.name == uid } + + when { + relativeOrgunit != null -> { + OrganisationUnitFilter.Relative(relativeOrgunit) + } + + orgunitLevelRegex.matches(uid) -> { + val (levelNumber) = orgunitLevelRegex.find(uid)!!.destructured + val level = organisationUnitLevelStore.selectOneWhere( + WhereClauseBuilder() + .appendKeyNumberValue(OrganisationUnitLevelTableInfo.Columns.LEVEL, levelNumber.toInt()) + .build(), + ) ?: throw AnalyticsException.InvalidOrganisationUnitLevel(levelNumber) + OrganisationUnitFilter.Level(level.uid()) + } + + orgunitGroupRegex.matches(uid) -> { + val (groupUid) = orgunitGroupRegex.find(uid)!!.destructured + OrganisationUnitFilter.Group(groupUid) + } + + uidRegex.matches(uid) -> { + OrganisationUnitFilter.Absolute(uid) + } + + else -> null + } + } ?: emptyList(), + ) + } + + private fun mapPeriod(item: TrackerVisualizationDimension): TrackerLineListItem? { + return when (item.dimension()) { + "lastUpdated" -> TrackerLineListItem.LastUpdated(mapDateFilters(item)) + "incidentDate" -> TrackerLineListItem.IncidentDate(mapDateFilters(item)) + "enrollmentDate" -> TrackerLineListItem.EnrollmentDate(mapDateFilters(item)) + "scheduledDate" -> TrackerLineListItem.ScheduledDate(mapDateFilters(item)) + "eventDate" -> TrackerLineListItem.EventDate(mapDateFilters(item)) + else -> null + } + } + + private fun mapProgramIndicator(item: TrackerVisualizationDimension): TrackerLineListItem? { + return item.dimension()?.let { uid -> + TrackerLineListItem.ProgramIndicator(uid, mapDataFilters(item)) + } + } + + private fun mapProgramAttribute(item: TrackerVisualizationDimension): TrackerLineListItem? { + return item.dimension()?.let { uid -> + TrackerLineListItem.ProgramAttribute(uid, mapDataFilters(item)) + } + } + + private fun mapProgramDataElement( + item: TrackerVisualizationDimension, + trackerVisualization: TrackerVisualization, + ): TrackerLineListItem? { + return item.dimension()?.let { uid -> + TrackerLineListItem.ProgramDataElement( + uid, + item.programStage()?.uid() ?: trackerVisualization.programStage()!!.uid(), + mapDataFilters(item), + item.repetition()?.indexes(), + ) + } + } + + internal fun mapDataX(item: TrackerVisualizationDimension): TrackerLineListItem? { + return when (item.dimension()) { + "createdBy" -> TrackerLineListItem.CreatedBy + "lastUpdatedBy" -> TrackerLineListItem.LastUpdatedBy + "programStatus" -> TrackerLineListItem.ProgramStatusItem( + filters = item.items()?.mapNotNull { e -> EnrollmentStatus.entries.find { it.name == e.uid() } } + .takeIf { !it.isNullOrEmpty() } + ?.let { statuses -> listOf(EnumFilter.In(statuses)) } + ?: emptyList(), + ) + + "eventStatus" -> TrackerLineListItem.EventStatusItem( + filters = item.items()?.mapNotNull { e -> EventStatus.entries.find { it.name == e.uid() } } + .takeIf { !it.isNullOrEmpty() } + ?.let { statuses -> listOf(EnumFilter.In(statuses)) } + ?: emptyList(), + ) + + else -> null + } + } + + @Suppress("ComplexMethod") + internal fun mapDataFilters(item: TrackerVisualizationDimension): List { + return if (item.filter().isNullOrEmpty()) { + emptyList() + } else { + val filterPairs = item.filter()!!.split(":").chunked(2) + + filterPairs.mapNotNull { filterPair -> + val operator = filterPair.getOrNull(0) + val value = filterPair.getOrNull(1) + if (operator != null && value != null) { + when (operator) { + "EQ" -> DataFilter.EqualTo(value, ignoreCase = false) + "!EQ" -> DataFilter.NotEqualTo(value, ignoreCase = false) + "IEQ" -> DataFilter.EqualTo(value, ignoreCase = true) + "!IEQ" -> DataFilter.NotEqualTo(value, ignoreCase = true) + "GT" -> DataFilter.GreaterThan(value) + "GE" -> DataFilter.GreaterThanOrEqualTo(value) + "LT" -> DataFilter.LowerThan(value) + "LE" -> DataFilter.LowerThanOrEqualTo(value) + "NE" -> DataFilter.NotEqualTo(value) + "LIKE" -> DataFilter.Like(value, ignoreCase = false) + "!LIKE" -> DataFilter.NotLike(value, ignoreCase = false) + "ILIKE" -> DataFilter.Like(value, ignoreCase = true) + "!ILIKE" -> DataFilter.NotLike(value, ignoreCase = true) + "IN" -> DataFilter.In(value.split(";")) + else -> null + } + } else { + null + } + } + } + } + + internal fun mapDateFilters(item: TrackerVisualizationDimension): List { + return item.items()?.mapNotNull { it.uid() }?.map { uid -> + val relativePeriod = RelativePeriod.entries.find { it.name == uid } + + when { + relativePeriod != null -> { + DateFilter.Relative(relativePeriod) + } + + dateRangeRegex.matches(uid) -> { + val (start, end) = dateRangeRegex.find(uid)!!.destructured + DateFilter.Range(start, end) + } + + else -> { + DateFilter.Absolute(uid) + } + } + } ?: emptyList() + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/BaseDateEvaluator.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/BaseDateEvaluator.kt new file mode 100644 index 0000000000..8b5af2e665 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/BaseDateEvaluator.kt @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator + +import org.hisp.dhis.android.core.analytics.trackerlinelist.DateFilter +import org.hisp.dhis.android.core.analytics.trackerlinelist.DateItem +import org.hisp.dhis.android.core.arch.helpers.DateUtils +import org.hisp.dhis.android.core.period.PeriodType +import org.hisp.dhis.android.core.period.internal.CalendarProviderFactory +import org.hisp.dhis.android.core.period.internal.ParentPeriodGeneratorImpl +import org.hisp.dhis.android.core.period.internal.PeriodParser +import java.util.Date + +internal abstract class BaseDateEvaluator( + private val item: DateItem, +) : TrackerLineListEvaluator() { + + private val parentPeriodGenerator = ParentPeriodGeneratorImpl.create(CalendarProviderFactory.calendarProvider) + private val periodParser = PeriodParser(CalendarProviderFactory.calendarProvider) + + fun getDateWhereClause(): String { + return if (item.filters.isEmpty()) { + "1" + } else { + return item.filters.joinToString(" OR ") { "(${getFilterWhereClause(it)})" } + } + } + + private fun getFilterWhereClause(filter: DateFilter): String { + val filterHelper = FilterHelper(item.id) + return when (filter) { + is DateFilter.Absolute -> { + val periodType = PeriodType.periodTypeFromPeriodId(filter.uid) + val date = periodParser.parse(filter.uid) + val period = parentPeriodGenerator.generatePeriod(periodType, date, 0)!! + + betweenDates(period.startDate()!!, period.endDate()!!) + } + + is DateFilter.Relative -> { + val periods = parentPeriodGenerator.generateRelativePeriods(filter.relative) + + betweenDates(periods.first().startDate()!!, periods.last().endDate()!!) + } + + is DateFilter.Range -> { + betweenDates(filter.startDate, filter.endDate) + } + + is DateFilter.EqualTo -> filterHelper.equalTo(filter.timestamp, filter.ignoreCase) + is DateFilter.NotEqualTo -> filterHelper.notEqualTo(filter.timestamp, filter.ignoreCase) + is DateFilter.Like -> filterHelper.like(filter.timestamp, filter.ignoreCase) + is DateFilter.NotLike -> filterHelper.notLike(filter.timestamp, filter.ignoreCase) + } + } + + private fun betweenDates(startDate: Date, endDate: Date): String { + return betweenDates( + startDate = DateUtils.DATE_FORMAT.format(startDate), + endDate = DateUtils.DATE_FORMAT.format(endDate), + ) + } + + private fun betweenDates(startDate: String, endDate: String): String { + return "julianday(${item.id}) >= julianday('$startDate') AND julianday(${item.id}) <= julianday('$endDate')" + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/BaseEnumEvaluator.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/BaseEnumEvaluator.kt new file mode 100644 index 0000000000..0f0229618e --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/BaseEnumEvaluator.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator + +import org.hisp.dhis.android.core.analytics.trackerlinelist.EnumFilter + +internal abstract class BaseEnumEvaluator>( + private val itemId: String, + private val filters: List>, +) : TrackerLineListEvaluator() { + + fun getWhereClause(): String { + return if (filters.isEmpty()) { + "1" + } else { + return filters.joinToString(" OR ") { "(${getFilterWhereClause(it)})" } + } + } + + private fun getFilterWhereClause(filter: EnumFilter): String { + val filterHelper = FilterHelper(itemId) + return when (filter) { + is EnumFilter.EqualTo -> filterHelper.equalTo(filter.value, filter.ignoreCase) + is EnumFilter.NotEqualTo -> filterHelper.notEqualTo(filter.value, filter.ignoreCase) + is EnumFilter.Like -> filterHelper.like(filter.value, filter.ignoreCase) + is EnumFilter.NotLike -> filterHelper.notLike(filter.value, filter.ignoreCase) + is EnumFilter.In -> filterHelper.inValues(filter.values.map { it.name }) + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/DataFilterHelper.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/DataFilterHelper.kt new file mode 100644 index 0000000000..8ba62a6b09 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/DataFilterHelper.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator + +import org.hisp.dhis.android.core.analytics.trackerlinelist.DataFilter + +internal object DataFilterHelper { + fun getWhereClause(itemId: String, filters: List): String { + return if (filters.isEmpty()) { + "1" + } else { + val filterHelper = FilterHelper(itemId) + return filters.joinToString(" AND ") { getSqlOperator(filterHelper, it) } + } + } + + private fun getSqlOperator(helper: FilterHelper, filter: DataFilter): String { + return when (filter) { + is DataFilter.EqualTo -> helper.equalTo(filter.value, filter.ignoreCase) + is DataFilter.NotEqualTo -> helper.notEqualTo(filter.value, filter.ignoreCase) + is DataFilter.GreaterThan -> helper.greaterThan(filter.value) + is DataFilter.GreaterThanOrEqualTo -> helper.greaterThanOrEqualTo(filter.value) + is DataFilter.LowerThan -> helper.lowerThan(filter.value) + is DataFilter.LowerThanOrEqualTo -> helper.lowerThanOrEqualTo(filter.value) + is DataFilter.Like -> helper.like(filter.value, filter.ignoreCase) + is DataFilter.NotLike -> helper.notLike(filter.value, filter.ignoreCase) + is DataFilter.In -> helper.inValues(filter.values) + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/EnrollmentDateEvaluator.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/EnrollmentDateEvaluator.kt new file mode 100644 index 0000000000..7807af42d4 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/EnrollmentDateEvaluator.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator + +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListItem +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator.TrackerLineListSQLLabel.EnrollmentAlias +import org.hisp.dhis.android.core.enrollment.EnrollmentTableInfo + +internal class EnrollmentDateEvaluator( + item: TrackerLineListItem.EnrollmentDate, +) : BaseDateEvaluator(item) { + + override fun getCommonSelectSQL(): String { + return "$EnrollmentAlias.${EnrollmentTableInfo.Columns.ENROLLMENT_DATE}" + } + + override fun getCommonWhereSQL(): String { + return getDateWhereClause() + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/EventDateEvaluator.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/EventDateEvaluator.kt new file mode 100644 index 0000000000..66e3d674e5 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/EventDateEvaluator.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator + +import org.hisp.dhis.android.core.analytics.AnalyticsException +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListItem +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator.TrackerLineListSQLLabel.EventAlias +import org.hisp.dhis.android.core.event.EventTableInfo + +internal class EventDateEvaluator( + item: TrackerLineListItem.EventDate, +) : BaseDateEvaluator(item) { + + override fun getSelectSQLForEvent(): String { + return "$EventAlias.${EventTableInfo.Columns.EVENT_DATE}" + } + + override fun getWhereSQLForEnrollment(): String { + throw AnalyticsException.InvalidArguments("EventDate is not supported in ENROLLMENT output type") + } + + override fun getWhereSQLForEvent(): String { + return getDateWhereClause() + } + + override fun getSelectSQLForEnrollment(): String { + throw AnalyticsException.InvalidArguments("EventDate is not supported in ENROLLMENT output type") + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/EventStatusEvaluator.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/EventStatusEvaluator.kt new file mode 100644 index 0000000000..84a83112fb --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/EventStatusEvaluator.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator + +import org.hisp.dhis.android.core.analytics.AnalyticsException +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListItem +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator.TrackerLineListSQLLabel.EventAlias +import org.hisp.dhis.android.core.event.EventStatus +import org.hisp.dhis.android.core.event.EventTableInfo + +internal class EventStatusEvaluator( + item: TrackerLineListItem.EventStatusItem, +) : BaseEnumEvaluator(item.id, item.filters) { + + override fun getSelectSQLForEvent(): String { + return "$EventAlias.${EventTableInfo.Columns.STATUS}" + } + + override fun getSelectSQLForEnrollment(): String { + throw AnalyticsException.InvalidArguments("EventStatus is not supported in ENROLLMENT output type") + } + + override fun getWhereSQLForEvent(): String { + return getWhereClause() + } + + override fun getWhereSQLForEnrollment(): String { + throw AnalyticsException.InvalidArguments("EventStatus is not supported in ENROLLMENT output type") + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/FilterHelper.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/FilterHelper.kt new file mode 100644 index 0000000000..6c9cfaa923 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/FilterHelper.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator + +@Suppress("TooManyFunctions") +internal class FilterHelper(private val itemId: String) { + fun equalTo(value: String, ignoreCase: Boolean): String = itemTo("= '$value'${case(ignoreCase)}") + fun notEqualTo(value: String, ignoreCase: Boolean): String = itemTo("!= '$value'${case(ignoreCase)}") + fun greaterThan(value: String): String = itemTo("> $value") + fun greaterThanOrEqualTo(value: String): String = itemTo(">= $value") + fun lowerThan(value: String): String = itemTo("< $value") + fun lowerThanOrEqualTo(value: String): String = itemTo("<= $value") + fun like(value: String, ignoreCase: Boolean): String = itemTo("LIKE '%$value%'${case(ignoreCase)}") + fun notLike(value: String, ignoreCase: Boolean): String = itemTo("NOT LIKE '%$value%'${case(ignoreCase)}") + fun inValues(values: List): String = itemTo("IN (${values.joinToString(", ") { "'$it'" }})") + + private fun itemTo(comparison: String) = "\"$itemId\" $comparison" + + private fun case(ignoreCase: Boolean) = if (ignoreCase) " COLLATE NOCASE" else "" +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/IncidentDateEvaluator.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/IncidentDateEvaluator.kt new file mode 100644 index 0000000000..3c88014ded --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/IncidentDateEvaluator.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator + +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListItem +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator.TrackerLineListSQLLabel.EnrollmentAlias +import org.hisp.dhis.android.core.enrollment.EnrollmentTableInfo + +internal class IncidentDateEvaluator( + item: TrackerLineListItem.IncidentDate, +) : BaseDateEvaluator(item) { + + override fun getCommonSelectSQL(): String { + return "$EnrollmentAlias.${EnrollmentTableInfo.Columns.INCIDENT_DATE}" + } + + override fun getCommonWhereSQL(): String { + return getDateWhereClause() + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/LastUpdatedEvaluator.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/LastUpdatedEvaluator.kt new file mode 100644 index 0000000000..90fabe017f --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/LastUpdatedEvaluator.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator + +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListItem +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator.TrackerLineListSQLLabel.EnrollmentAlias +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator.TrackerLineListSQLLabel.EventAlias +import org.hisp.dhis.android.core.enrollment.EnrollmentTableInfo +import org.hisp.dhis.android.core.event.EventTableInfo + +internal class LastUpdatedEvaluator( + item: TrackerLineListItem.LastUpdated, +) : BaseDateEvaluator(item) { + + override fun getSelectSQLForEvent(): String { + return "$EventAlias.${EventTableInfo.Columns.LAST_UPDATED}" + } + + override fun getSelectSQLForEnrollment(): String { + return "$EnrollmentAlias.${EnrollmentTableInfo.Columns.LAST_UPDATED}" + } + + override fun getCommonWhereSQL(): String { + return getDateWhereClause() + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/NotSupportedEvaluator.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/NotSupportedEvaluator.kt new file mode 100644 index 0000000000..3855aa6519 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/NotSupportedEvaluator.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator + +internal class NotSupportedEvaluator : TrackerLineListEvaluator() { + + override fun getCommonSelectSQL(): String { + return "'Not supported'" + } + + override fun getCommonWhereSQL(): String { + return "1" + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/OrganisationUnitEvaluator.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/OrganisationUnitEvaluator.kt new file mode 100644 index 0000000000..eb2794f713 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/OrganisationUnitEvaluator.kt @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator + +import org.hisp.dhis.android.core.analytics.trackerlinelist.OrganisationUnitFilter +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListItem +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.TrackerLineListContext +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator.TrackerLineListSQLLabel.OrgunitAlias +import org.hisp.dhis.android.core.arch.db.querybuilders.internal.WhereClauseBuilder +import org.hisp.dhis.android.core.common.RelativeOrganisationUnit +import org.hisp.dhis.android.core.organisationunit.OrganisationUnit +import org.hisp.dhis.android.core.organisationunit.OrganisationUnitOrganisationUnitGroupLinkTableInfo +import org.hisp.dhis.android.core.organisationunit.OrganisationUnitTableInfo +import org.hisp.dhis.android.core.organisationunit.internal.OrganisationUnitLevelStoreImpl +import org.hisp.dhis.android.core.organisationunit.internal.OrganisationUnitOrganisationUnitGroupLinkStoreImpl +import org.hisp.dhis.android.core.organisationunit.internal.OrganisationUnitStoreImpl +import org.hisp.dhis.android.core.user.internal.UserOrganisationUnitLinkStoreImpl + +internal class OrganisationUnitEvaluator( + private val item: TrackerLineListItem.OrganisationUnitItem, + context: TrackerLineListContext, +) : TrackerLineListEvaluator() { + + private val userOrganisationUnitLinkStore = UserOrganisationUnitLinkStoreImpl(context.databaseAdapter) + private val organisationUnitStore = OrganisationUnitStoreImpl(context.databaseAdapter) + private val orgunitLevelStore = OrganisationUnitLevelStoreImpl(context.databaseAdapter) + private val orgunitGroupLinkStore = OrganisationUnitOrganisationUnitGroupLinkStoreImpl(context.databaseAdapter) + + override fun getCommonSelectSQL(): String { + return "$OrgunitAlias.${OrganisationUnitTableInfo.Columns.DISPLAY_NAME}" + } + + override fun getCommonWhereSQL(): String { + return if (item.filters.isEmpty()) { + "1" + } else { + return item.filters.joinToString(" AND ") { getFilterWhereClause(it) } + } + } + + private fun getFilterWhereClause(filter: OrganisationUnitFilter): String { + val filterHelper = FilterHelper(item.id) + return when (filter) { + is OrganisationUnitFilter.Absolute -> inPathOf(filter.uid) + + is OrganisationUnitFilter.Relative -> { + val userAssignedOrgunits = userOrganisationUnitLinkStore + .queryAssignedOrganisationUnitUidsByScope(OrganisationUnit.Scope.SCOPE_DATA_CAPTURE) + + when (filter.relative) { + RelativeOrganisationUnit.USER_ORGUNIT -> { + inPathOfAny(userAssignedOrgunits) + } + + RelativeOrganisationUnit.USER_ORGUNIT_CHILDREN -> { + val children = getChildren(userAssignedOrgunits) + + inPathOfAny(children) + } + + RelativeOrganisationUnit.USER_ORGUNIT_GRANDCHILDREN -> { + val children = getChildren(userAssignedOrgunits) + val grandChildren = getChildren(children) + + inPathOfAny(grandChildren) + } + } + } + + is OrganisationUnitFilter.Level -> { + val level = orgunitLevelStore.selectByUid(filter.uid) + val orgunits = orgunitLevelStore.selectUidsWhere( + WhereClauseBuilder() + .appendKeyStringValue(OrganisationUnitTableInfo.Columns.LEVEL, level?.level()?.toString()) + .build(), + ) + + inPathOfAny(orgunits) + } + + is OrganisationUnitFilter.Group -> { + val orgunits = orgunitGroupLinkStore.selectWhere( + WhereClauseBuilder() + .appendKeyStringValue( + OrganisationUnitOrganisationUnitGroupLinkTableInfo.Columns.ORGANISATION_UNIT, + filter.uid, + ) + .build(), + ) + + inPathOfAny(orgunits.mapNotNull { it.organisationUnit() }) + } + + is OrganisationUnitFilter.EqualTo -> filterHelper.equalTo(filter.orgunitName, filter.ignoreCase) + is OrganisationUnitFilter.NotEqualTo -> filterHelper.notEqualTo(filter.orgunitName, filter.ignoreCase) + is OrganisationUnitFilter.Like -> filterHelper.like(filter.orgunitName, filter.ignoreCase) + is OrganisationUnitFilter.NotLike -> filterHelper.notLike(filter.orgunitName, filter.ignoreCase) + } + } + + private fun inPathOfAny(orgunits: List): String { + val orClauses = orgunits.joinToString(" OR ") { ou -> inPathOf(ou) } + return "($orClauses)" + } + + private fun inPathOf(orgunit: String): String { + return "$OrgunitAlias.${OrganisationUnitTableInfo.Columns.PATH} LIKE '%$orgunit%'" + } + + private fun getChildren(orgunits: List): List { + return organisationUnitStore.selectUidsWhere( + WhereClauseBuilder() + .appendInKeyStringValues(OrganisationUnitTableInfo.Columns.PARENT, orgunits) + .build(), + ) + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/ProgramAttributeEvaluator.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/ProgramAttributeEvaluator.kt new file mode 100644 index 0000000000..fc18b5c48f --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/ProgramAttributeEvaluator.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator + +import org.hisp.dhis.android.core.analytics.AnalyticsException +import org.hisp.dhis.android.core.analytics.aggregated.MetadataItem +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListItem +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator.TrackerLineListSQLLabel.EnrollmentAlias +import org.hisp.dhis.android.core.enrollment.EnrollmentTableInfo +import org.hisp.dhis.android.core.trackedentity.TrackedEntityAttributeValueTableInfo +import org.hisp.dhis.android.core.util.SqlUtils.getColumnValueCast + +internal class ProgramAttributeEvaluator( + private val item: TrackerLineListItem.ProgramAttribute, + private val metadata: Map, +) : TrackerLineListEvaluator() { + override fun getCommonSelectSQL(): String { + return "SELECT ${getColumnSql()} " + + "FROM ${TrackedEntityAttributeValueTableInfo.TABLE_INFO.name()} " + + "WHERE ${TrackedEntityAttributeValueTableInfo.Columns.TRACKED_ENTITY_INSTANCE} = " + + "$EnrollmentAlias.${EnrollmentTableInfo.Columns.TRACKED_ENTITY_INSTANCE} " + + "AND ${TrackedEntityAttributeValueTableInfo.Columns.TRACKED_ENTITY_ATTRIBUTE} = '${item.id}'" + } + + override fun getCommonWhereSQL(): String { + return DataFilterHelper.getWhereClause(item.id, item.filters) + } + + private fun getColumnSql(): String { + val attributeMetadata = metadata[item.id] ?: throw AnalyticsException.InvalidTrackedEntityAttribute(item.id) + val attribute = ((attributeMetadata) as MetadataItem.TrackedEntityAttributeItem).item + + return getColumnValueCast( + column = TrackedEntityAttributeValueTableInfo.Columns.VALUE, + valueType = attribute.valueType(), + ) + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/ProgramDataElementEvaluator.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/ProgramDataElementEvaluator.kt new file mode 100644 index 0000000000..c3e925461f --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/ProgramDataElementEvaluator.kt @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator + +import org.hisp.dhis.android.core.analytics.AnalyticsException +import org.hisp.dhis.android.core.analytics.aggregated.MetadataItem +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListItem +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator.TrackerLineListSQLLabel.EnrollmentAlias +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator.TrackerLineListSQLLabel.EventAlias +import org.hisp.dhis.android.core.enrollment.EnrollmentTableInfo +import org.hisp.dhis.android.core.event.EventTableInfo +import org.hisp.dhis.android.core.trackedentity.TrackedEntityDataValueTableInfo +import org.hisp.dhis.android.core.util.SqlUtils.getColumnValueCast + +internal class ProgramDataElementEvaluator( + private val item: TrackerLineListItem.ProgramDataElement, + private val metadata: Map, +) : TrackerLineListEvaluator() { + override fun getSelectSQLForEvent(): String { + val selectEventClause = "= $EventAlias.${EventTableInfo.Columns.UID} " + + return getSelectClause(selectEventClause) + } + + override fun getSelectSQLForEnrollment(): String { + /** eventIdx meaning: + * -> 0: newest event + * -> -1: newest event - 1 (second newest event) + * -> 1: oldest event + * -> 2: oldest event - 1 (second oldest event + */ + val eventIdx = item.repetitionIndexes?.firstOrNull() ?: 0 + + val eventSelectClause = "IN (SELECT ${EventTableInfo.Columns.UID} " + + "FROM ${EventTableInfo.TABLE_INFO.name()} " + + "WHERE ${EventTableInfo.Columns.ENROLLMENT} = $EnrollmentAlias.${EnrollmentTableInfo.Columns.UID} " + + (item.programStage?.let { "AND ${EventTableInfo.Columns.PROGRAM_STAGE} = '$it' " } ?: "") + + "ORDER BY ${EventTableInfo.Columns.EVENT_DATE} ${if (eventIdx <= 0) "DESC" else "ASC"} " + + "LIMIT 1 " + + "OFFSET ${ + if (eventIdx <= 0) { + -eventIdx + } else { + eventIdx - 1 + } + })" + + return getSelectClause(eventSelectClause) + } + + override fun getCommonWhereSQL(): String { + return DataFilterHelper.getWhereClause(item.id, item.filters) + } + + private fun getSelectClause(selectEventClause: String): String { + return "SELECT ${getColumnSql()} " + + "FROM ${TrackedEntityDataValueTableInfo.TABLE_INFO.name()} " + + "WHERE ${TrackedEntityDataValueTableInfo.Columns.EVENT} $selectEventClause " + + "AND ${TrackedEntityDataValueTableInfo.Columns.DATA_ELEMENT} = '${item.dataElement}'" + } + + private fun getColumnSql(): String { + val dataElementMetadata = metadata[item.dataElement] + ?: throw AnalyticsException.InvalidDataElement(item.id) + val dataElement = ((dataElementMetadata) as MetadataItem.DataElementItem).item + + return getColumnValueCast( + column = TrackedEntityDataValueTableInfo.Columns.VALUE, + valueType = dataElement.valueType(), + ) + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/ProgramIndicatorEvaluator.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/ProgramIndicatorEvaluator.kt new file mode 100644 index 0000000000..85f4e9c70c --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/ProgramIndicatorEvaluator.kt @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator + +import org.hisp.dhis.android.core.analytics.AnalyticsException +import org.hisp.dhis.android.core.analytics.aggregated.MetadataItem +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListItem +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.TrackerLineListContext +import org.hisp.dhis.android.core.arch.helpers.UidsHelper +import org.hisp.dhis.android.core.constant.Constant +import org.hisp.dhis.android.core.constant.internal.ConstantStoreImpl +import org.hisp.dhis.android.core.dataelement.internal.DataElementStoreImpl +import org.hisp.dhis.android.core.parser.internal.expression.CommonExpressionVisitor +import org.hisp.dhis.android.core.parser.internal.expression.CommonExpressionVisitorScope +import org.hisp.dhis.android.core.parser.internal.expression.CommonParser +import org.hisp.dhis.android.core.parser.internal.expression.ExpressionItemMethod +import org.hisp.dhis.android.core.parser.internal.expression.ParserUtils +import org.hisp.dhis.android.core.program.ProgramIndicator +import org.hisp.dhis.android.core.program.programindicatorengine.internal.ProgramIndicatorItemIdsCollector +import org.hisp.dhis.android.core.program.programindicatorengine.internal.ProgramIndicatorParserUtils +import org.hisp.dhis.android.core.program.programindicatorengine.internal.ProgramIndicatorSQLContext +import org.hisp.dhis.android.core.program.programindicatorengine.internal.literal.ProgramIndicatorSQLLiteral +import org.hisp.dhis.android.core.trackedentity.internal.TrackedEntityAttributeStoreImpl +import org.hisp.dhis.antlr.Parser + +internal class ProgramIndicatorEvaluator( + private val item: TrackerLineListItem.ProgramIndicator, + private val context: TrackerLineListContext, +) : TrackerLineListEvaluator() { + + private val constantStore = ConstantStoreImpl(context.databaseAdapter) + private val dataElementStore = DataElementStoreImpl(context.databaseAdapter) + private val trackedEntityAttributeStore = TrackedEntityAttributeStoreImpl(context.databaseAdapter) + + override fun getCommonSelectSQL(): String { + val programIndicator = getProgramIndicator() + + val context = ProgramIndicatorSQLContext( + programIndicator = programIndicator, + periods = emptyList(), + ) + + val collector = ProgramIndicatorItemIdsCollector() + Parser.listen(programIndicator.expression(), collector) + + val sqlVisitor = newVisitor(ParserUtils.ITEM_GET_SQL, context) + sqlVisitor.itemIds = collector.itemIds.toMutableSet() + sqlVisitor.setExpressionLiteral(ProgramIndicatorSQLLiteral()) + + val selectExpression = CommonParser.visit(programIndicator.expression(), sqlVisitor) + + val filterExpression = when (programIndicator.filter()?.trim()) { + "true", "", null -> "1" + else -> CommonParser.visit(programIndicator.filter(), sqlVisitor) + } + + return "SELECT CASE ($filterExpression) " + + "WHEN 1 THEN ($selectExpression) " + + "ELSE '' " + + "END" + } + + override fun getCommonWhereSQL(): String { + return DataFilterHelper.getWhereClause(item.id, item.filters) + } + + private fun getProgramIndicator(): ProgramIndicator { + val programIndicatorMetadata = context.metadata[item.id] + ?: throw AnalyticsException.InvalidProgramIndicator(item.id) + + return ((programIndicatorMetadata) as MetadataItem.ProgramIndicatorItem).item + } + + private fun constantMap(): Map { + val constants = constantStore.selectAll() + return UidsHelper.mapByUid(constants) + } + + private fun newVisitor( + itemMethod: ExpressionItemMethod, + context: ProgramIndicatorSQLContext, + ): CommonExpressionVisitor { + return CommonExpressionVisitor( + CommonExpressionVisitorScope.ProgramSQLIndicator( + itemMap = ProgramIndicatorParserUtils.PROGRAM_INDICATOR_SQL_EXPRESSION_ITEMS, + itemMethod = itemMethod, + constantMap = constantMap(), + programIndicatorSQLContext = context, + dataElementStore = dataElementStore, + trackedEntityAttributeStore = trackedEntityAttributeStore, + ), + ) + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/ProgramStatusEvaluator.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/ProgramStatusEvaluator.kt new file mode 100644 index 0000000000..7b14c1a8a8 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/ProgramStatusEvaluator.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator + +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListItem +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator.TrackerLineListSQLLabel.EnrollmentAlias +import org.hisp.dhis.android.core.enrollment.EnrollmentStatus +import org.hisp.dhis.android.core.enrollment.EnrollmentTableInfo + +internal class ProgramStatusEvaluator( + private val item: TrackerLineListItem.ProgramStatusItem, +) : BaseEnumEvaluator(item.id, item.filters) { + + override fun getCommonSelectSQL(): String { + return "$EnrollmentAlias.${EnrollmentTableInfo.Columns.STATUS}" + } + + override fun getCommonWhereSQL(): String { + return getWhereClause() + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/ScheduledDateEvaluator.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/ScheduledDateEvaluator.kt new file mode 100644 index 0000000000..bbbd8c4be9 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/ScheduledDateEvaluator.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator + +import org.hisp.dhis.android.core.analytics.AnalyticsException +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListItem +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator.TrackerLineListSQLLabel.EventAlias +import org.hisp.dhis.android.core.event.EventTableInfo + +internal class ScheduledDateEvaluator( + item: TrackerLineListItem.ScheduledDate, +) : BaseDateEvaluator(item) { + + override fun getSelectSQLForEvent(): String { + return "$EventAlias.${EventTableInfo.Columns.DUE_DATE}" + } + + override fun getWhereSQLForEnrollment(): String { + throw AnalyticsException.InvalidArguments("ScheduledDate is not supported in ENROLLMENT output type") + } + + override fun getWhereSQLForEvent(): String { + return getDateWhereClause() + } + + override fun getSelectSQLForEnrollment(): String { + throw AnalyticsException.InvalidArguments("ScheduledDate is not supported in ENROLLMENT output type") + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/TrackerLineListEvaluator.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/TrackerLineListEvaluator.kt new file mode 100644 index 0000000000..3567d0ecda --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/TrackerLineListEvaluator.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator + +import org.hisp.dhis.android.core.analytics.AnalyticsException + +internal open class TrackerLineListEvaluator { + open fun getSelectSQLForEvent(): String { + return getCommonSelectSQL() + } + open fun getWhereSQLForEvent(): String { + return getCommonWhereSQL() + } + open fun getSelectSQLForEnrollment(): String { + return getCommonSelectSQL() + } + open fun getWhereSQLForEnrollment(): String { + return getCommonWhereSQL() + } + + protected open fun getCommonSelectSQL(): String { + throw AnalyticsException.SQLException("SELECT clause is not implemented for this evaluator") + } + + protected open fun getCommonWhereSQL(): String { + throw AnalyticsException.SQLException("WHERE clause is not implemented for this evaluator") + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/TrackerLineListEvaluatorMapper.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/TrackerLineListEvaluatorMapper.kt new file mode 100644 index 0000000000..54557bea8d --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/TrackerLineListEvaluatorMapper.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator + +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListItem +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.TrackerLineListContext + +internal object TrackerLineListEvaluatorMapper { + fun getEvaluator(item: TrackerLineListItem, context: TrackerLineListContext): TrackerLineListEvaluator { + return when (item) { + is TrackerLineListItem.ProgramAttribute -> ProgramAttributeEvaluator(item, context.metadata) + is TrackerLineListItem.ProgramDataElement -> ProgramDataElementEvaluator(item, context.metadata) + is TrackerLineListItem.ProgramIndicator -> ProgramIndicatorEvaluator(item, context) + + is TrackerLineListItem.OrganisationUnitItem -> OrganisationUnitEvaluator(item, context) + + is TrackerLineListItem.ProgramStatusItem -> ProgramStatusEvaluator(item) + is TrackerLineListItem.EventStatusItem -> EventStatusEvaluator(item) + + is TrackerLineListItem.LastUpdated -> LastUpdatedEvaluator(item) + is TrackerLineListItem.IncidentDate -> IncidentDateEvaluator(item) + is TrackerLineListItem.EnrollmentDate -> EnrollmentDateEvaluator(item) + is TrackerLineListItem.ScheduledDate -> ScheduledDateEvaluator(item) + is TrackerLineListItem.EventDate -> EventDateEvaluator(item) + + is TrackerLineListItem.CreatedBy -> NotSupportedEvaluator() + is TrackerLineListItem.LastUpdatedBy -> NotSupportedEvaluator() + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/TrackerLineListSQLLabel.kt b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/TrackerLineListSQLLabel.kt new file mode 100644 index 0000000000..69683e38bd --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/evaluator/TrackerLineListSQLLabel.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.trackerlinelist.internal.evaluator + +import org.hisp.dhis.android.core.program.programindicatorengine.internal.ProgramIndicatorSQLUtils + +internal object TrackerLineListSQLLabel { + const val EventAlias = ProgramIndicatorSQLUtils.event + const val EnrollmentAlias = ProgramIndicatorSQLUtils.enrollment + const val OrgunitAlias = "ou" +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/api/internal/OkHttpClientFactory.java b/core/src/main/java/org/hisp/dhis/android/core/arch/api/internal/OkHttpClientFactory.java deleted file mode 100644 index 64935c314a..0000000000 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/api/internal/OkHttpClientFactory.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) 2004-2023, University of Oslo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * Neither the name of the HISP project nor the names of its contributors may - * be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.hisp.dhis.android.core.arch.api.internal; - -import android.os.Build; -import android.util.Log; - -import org.hisp.dhis.android.BuildConfig; -import org.hisp.dhis.android.core.D2Configuration; - -import java.security.KeyStore; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; - -import okhttp3.CipherSuite; -import okhttp3.ConnectionSpec; -import okhttp3.Interceptor; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.TlsVersion; - -final class OkHttpClientFactory { - - static OkHttpClient okHttpClient(D2Configuration d2Configuration, - Interceptor authenticator) { - - OkHttpClient.Builder client = new OkHttpClient.Builder() - .addInterceptor(new DynamicServerURLInterceptor()) - .addInterceptor(new ServerURLVersionRedirectionInterceptor()) - .addInterceptor(authenticator) - .addInterceptor(new PreventURLDecodeInterceptor()) - .addInterceptor(chain -> { - Request originalRequest = chain.request(); - Request withUserAgent = originalRequest.newBuilder() - .header("User-Agent", getUserAgent(d2Configuration)) - .build(); - return chain.proceed(withUserAgent); - }) - .readTimeout(d2Configuration.readTimeoutInSeconds(), TimeUnit.SECONDS) - .connectTimeout(d2Configuration.connectTimeoutInSeconds(), TimeUnit.SECONDS) - .writeTimeout(d2Configuration.writeTimeoutInSeconds(), TimeUnit.SECONDS) - .followRedirects(false); - - for (Interceptor interceptor : d2Configuration.networkInterceptors()) { - client.addNetworkInterceptor(interceptor); - } - - for (Interceptor interceptor : d2Configuration.interceptors()) { - client.addInterceptor(interceptor); - } - - setTLSParameters(client); - - return client.build(); - } - - private static String getUserAgent(D2Configuration d2Configuration) { - return String.format("%s/%s/%s/Android_%s", - d2Configuration.appName(), - BuildConfig.VERSION_NAME, //SDK version - d2Configuration.appVersion(), - Build.VERSION.SDK_INT //Android Version - ); - } - - private static void setTLSParameters(OkHttpClient.Builder client) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - try { - - SSLContext sc = SSLContext.getInstance(TlsVersion.TLS_1_2.javaName()); - sc.init(null, null, null); - - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( - TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init((KeyStore) null); - TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); - - if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { - throw new IllegalStateException("Unexpected default trust managers:" - + Arrays.toString(trustManagers)); - } - - X509TrustManager trustManager = (X509TrustManager) trustManagers[0]; - client.sslSocketFactory(new TLSSocketFactory(sc.getSocketFactory()), trustManager); - - ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) - .tlsVersions(TlsVersion.TLS_1_0, TlsVersion.TLS_1_1, TlsVersion.TLS_1_2) - .cipherSuites( - CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) - .build(); - - List specs = new ArrayList<>(); - specs.add(cs); - specs.add(ConnectionSpec.COMPATIBLE_TLS); - specs.add(ConnectionSpec.CLEARTEXT); - - client.connectionSpecs(specs); - - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - Log.e(OkHttpClientFactory.class.getSimpleName(), e.getMessage(), e); - } - } - } - - private OkHttpClientFactory() { - } -} \ No newline at end of file diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/api/internal/OkHttpClientFactory.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/api/internal/OkHttpClientFactory.kt new file mode 100644 index 0000000000..40f3910db4 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/api/internal/OkHttpClientFactory.kt @@ -0,0 +1,86 @@ +/* + * 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.arch.api.internal + +import android.os.Build +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import org.hisp.dhis.android.BuildConfig +import org.hisp.dhis.android.core.D2Configuration +import java.util.concurrent.TimeUnit + +internal object OkHttpClientFactory { + fun okHttpClient( + d2Configuration: D2Configuration, + authenticator: Interceptor, + ): OkHttpClient { + val client = OkHttpClient.Builder() + .addInterceptor(DynamicServerURLInterceptor()) + .addInterceptor(ServerURLVersionRedirectionInterceptor()) + .addInterceptor(authenticator) + .addInterceptor(PreventURLDecodeInterceptor()) + .addInterceptor( + Interceptor { chain: Interceptor.Chain -> + val originalRequest = chain.request() + val withUserAgent = originalRequest.newBuilder() + .header("User-Agent", getUserAgent(d2Configuration)) + .build() + chain.proceed(withUserAgent) + }, + ) + .readTimeout(d2Configuration.readTimeoutInSeconds().toLong(), TimeUnit.SECONDS) + .connectTimeout(d2Configuration.connectTimeoutInSeconds().toLong(), TimeUnit.SECONDS) + .writeTimeout(d2Configuration.writeTimeoutInSeconds().toLong(), TimeUnit.SECONDS) + .followRedirects(false) + + for (interceptor in d2Configuration.networkInterceptors()) { + client.addNetworkInterceptor(interceptor) + } + + for (interceptor in d2Configuration.interceptors()) { + client.addInterceptor(interceptor) + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + val (socketFactory, trustManager) = TrustFactory.getTrustFactoryManager(d2Configuration.context()) + client.sslSocketFactory(socketFactory, trustManager) + } + + return client.build() + } + + private fun getUserAgent(d2Configuration: D2Configuration): String { + return String.format( + "%s/%s/%s/Android_%s", + d2Configuration.appName(), + BuildConfig.VERSION_NAME, // SDK version + d2Configuration.appVersion(), + Build.VERSION.SDK_INT, // Android Version + ) + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/api/internal/ServicesDIModule.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/api/internal/ServicesDIModule.kt index 019ce788df..9e8a124b4f 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/api/internal/ServicesDIModule.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/api/internal/ServicesDIModule.kt @@ -15,10 +15,12 @@ import org.hisp.dhis.android.core.event.internal.EventFilterService import org.hisp.dhis.android.core.event.internal.EventService import org.hisp.dhis.android.core.expressiondimensionitem.internal.ExpressionDimensionItemService import org.hisp.dhis.android.core.fileresource.internal.FileResourceService +import org.hisp.dhis.android.core.icon.internal.IconService import org.hisp.dhis.android.core.indicator.internal.IndicatorService import org.hisp.dhis.android.core.indicator.internal.IndicatorTypeService import org.hisp.dhis.android.core.legendset.internal.LegendSetService import org.hisp.dhis.android.core.map.layer.internal.bing.BingService +import org.hisp.dhis.android.core.map.layer.internal.externalmap.ExternalMapLayerService import org.hisp.dhis.android.core.option.internal.OptionGroupService import org.hisp.dhis.android.core.option.internal.OptionService import org.hisp.dhis.android.core.option.internal.OptionSetService @@ -47,6 +49,7 @@ import org.hisp.dhis.android.core.usecase.stock.internal.StockUseCaseService import org.hisp.dhis.android.core.user.internal.AuthorityService import org.hisp.dhis.android.core.user.internal.UserService import org.hisp.dhis.android.core.validation.internal.ValidationRuleService +import org.hisp.dhis.android.core.visualization.internal.TrackerVisualizationService import org.hisp.dhis.android.core.visualization.internal.VisualizationService import org.koin.dsl.module import retrofit2.Retrofit @@ -68,7 +71,9 @@ internal val servicesDIModule = module { single { get().create(EventFilterService::class.java) } single { get().create(EventService::class.java) } single { get().create(ExpressionDimensionItemService::class.java) } + single { get().create(ExternalMapLayerService::class.java) } single { get().create(FileResourceService::class.java) } + single { get().create(IconService::class.java) } single { get().create(IndicatorService::class.java) } single { get().create(IndicatorTypeService::class.java) } single { get().create(LegendSetService::class.java) } @@ -97,6 +102,7 @@ internal val servicesDIModule = module { single { get().create(TrackedEntityTypeService::class.java) } single { get().create(TrackerExporterService::class.java) } single { get().create(TrackerImporterService::class.java) } + single { get().create(TrackerVisualizationService::class.java) } single { get().create(UserService::class.java) } single { get().create(ValidationRuleService::class.java) } single { get().create(VisualizationService::class.java) } diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/api/internal/TrustFactory.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/api/internal/TrustFactory.kt new file mode 100644 index 0000000000..c78542511f --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/api/internal/TrustFactory.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.arch.api.internal + +import android.content.Context +import okhttp3.TlsVersion +import org.hisp.dhis.android.R +import java.security.KeyStore +import java.security.cert.Certificate +import java.security.cert.CertificateFactory +import javax.net.ssl.SSLContext +import javax.net.ssl.SSLSocketFactory +import javax.net.ssl.TrustManagerFactory +import javax.net.ssl.X509TrustManager + +internal object TrustFactory { + fun getTrustFactoryManager(context: Context): Pair { + val cf = CertificateFactory.getInstance("X.509") + + val isrgRoot1Input = context.resources.openRawResource(R.raw.isrgrootx1) + val isrgRoot1Certificate: Certificate = isrgRoot1Input.use { + cf.generateCertificate(it) + } + + val isrgRoot2Input = context.resources.openRawResource(R.raw.isrgrootx2) + val isrgRoot2Certificate: Certificate = isrgRoot2Input.use { + cf.generateCertificate(it) + } + + val keyStoreType = KeyStore.getDefaultType() + val keyStore = KeyStore.getInstance(keyStoreType).apply { + load(null, null) + setCertificateEntry("isrgrootx1", isrgRoot1Certificate) + setCertificateEntry("isrgrootx2", isrgRoot2Certificate) + } + + val tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm() + val tmf = TrustManagerFactory.getInstance(tmfAlgorithm).apply { + init(keyStore) + } + + val sslContext = SSLContext.getInstance(TlsVersion.TLS_1_2.javaName).apply { + init(null, tmf.trustManagers, null) + } + + return Pair(sslContext.socketFactory, tmf.trustManagers[0] as X509TrustManager) + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/api/payload/internal/NTIPayload.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/api/payload/internal/TrackerPager.kt similarity index 91% rename from core/src/main/java/org/hisp/dhis/android/core/arch/api/payload/internal/NTIPayload.kt rename to core/src/main/java/org/hisp/dhis/android/core/arch/api/payload/internal/TrackerPager.kt index fbd7e96ad0..21afed63a9 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/api/payload/internal/NTIPayload.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/api/payload/internal/TrackerPager.kt @@ -27,8 +27,9 @@ */ package org.hisp.dhis.android.core.arch.api.payload.internal -data class NTIPayload( - val page: Int, - val pageSize: Int, - val instances: List, +import com.fasterxml.jackson.annotation.JsonProperty + +internal data class TrackerPager( + @JsonProperty val page: Int, + @JsonProperty val pageSize: Int, ) diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/api/payload/internal/TrackerPayload.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/api/payload/internal/TrackerPayload.kt new file mode 100644 index 0000000000..97d7c47eb6 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/api/payload/internal/TrackerPayload.kt @@ -0,0 +1,59 @@ +/* + * 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.arch.api.payload.internal + +import com.fasterxml.jackson.annotation.JsonAnySetter +import com.fasterxml.jackson.annotation.JsonIgnore +import com.fasterxml.jackson.annotation.JsonProperty + +internal data class TrackerPayload( + @JsonProperty private val pager: TrackerPager?, + @JsonProperty private val page: Int?, + @JsonProperty private val pageSize: Int?, + @JsonIgnore private var items: List = emptyList(), +) { + + @JsonAnySetter + @Suppress("unused") + private fun processItems(key: String?, values: List) { + items = values + } + + fun pager(): TrackerPager? { + return pager + ?: if (page != null && pageSize != null) { + TrackerPager(page = page, pageSize = pageSize) + } else { + null + } + } + + fun items(): List { + return items + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/call/queries/internal/BaseQuery.java b/core/src/main/java/org/hisp/dhis/android/core/arch/call/queries/internal/BaseQuery.java index 9c73e4d5bb..3560c9a6e8 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/call/queries/internal/BaseQuery.java +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/call/queries/internal/BaseQuery.java @@ -44,7 +44,7 @@ boolean isValid() { return true; } - protected static abstract class Builder { + protected abstract static class Builder { public abstract T page(int page); public abstract T pageSize(int pageSize); diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/cleaners/internal/BaseCollectionCleaner.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/cleaners/internal/BaseCollectionCleaner.kt new file mode 100644 index 0000000000..0fcc66220c --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/cleaners/internal/BaseCollectionCleaner.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.arch.cleaners.internal + +import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.arch.helpers.CollectionsHelper + +internal open class BaseCollectionCleaner( + private val tableName: String, + private val databaseAdapter: DatabaseAdapter, + private val key: String, +) { + fun deleteNotPresentByKey(uids: Collection): Boolean { + val objectUids = CollectionsHelper.commaAndSpaceSeparatedCollectionValues(uids.map { "'$it'" }) + val clause = "$key NOT IN ($objectUids);" + return databaseAdapter.delete(tableName, clause, null) > 0 + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/cleaners/internal/CollectionCleanerImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/cleaners/internal/CollectionCleanerImpl.kt index e4f069bcf7..73e354cb38 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/cleaners/internal/CollectionCleanerImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/cleaners/internal/CollectionCleanerImpl.kt @@ -28,25 +28,27 @@ package org.hisp.dhis.android.core.arch.cleaners.internal import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter -import org.hisp.dhis.android.core.arch.helpers.CollectionsHelper import org.hisp.dhis.android.core.common.IdentifiableColumns import org.hisp.dhis.android.core.common.ObjectWithUidInterface internal open class CollectionCleanerImpl

( - private val tableName: String, - private val databaseAdapter: DatabaseAdapter, -) : CollectionCleaner

{ + tableName: String, + databaseAdapter: DatabaseAdapter, +) : CollectionCleaner

, + BaseCollectionCleaner( + tableName = tableName, + databaseAdapter = databaseAdapter, + key = IdentifiableColumns.UID, + ) { override fun deleteNotPresent(objects: Collection

?): Boolean { if (objects == null) { return false } - return deleteNotPresentByUid(objects.map { it.uid() }) + return deleteNotPresentByKey(objects.map { it.uid() }) } override fun deleteNotPresentByUid(uids: Collection): Boolean { - val objectUids = CollectionsHelper.commaAndSpaceSeparatedCollectionValues(uids.map { "'$it'" }) - val clause = IdentifiableColumns.UID + " NOT IN (" + objectUids + ");" - return databaseAdapter.delete(tableName, clause, null) > 0 + return deleteNotPresentByKey(uids) } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/cleaners/internal/LinkCleanerImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/cleaners/internal/LinkCleanerImpl.kt index 81fc9f81dd..9fc22f79f9 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/cleaners/internal/LinkCleanerImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/cleaners/internal/LinkCleanerImpl.kt @@ -29,23 +29,25 @@ package org.hisp.dhis.android.core.arch.cleaners.internal import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter import org.hisp.dhis.android.core.arch.db.stores.internal.ObjectStore -import org.hisp.dhis.android.core.arch.helpers.UidsHelper.commaSeparatedUidsWithSingleQuotationMarks import org.hisp.dhis.android.core.common.ObjectWithUidInterface internal open class LinkCleanerImpl

( - private val tableName: String, - private val applicableColumn: String, + tableName: String, + applicableColumn: String, + databaseAdapter: DatabaseAdapter, private val parentStore: ObjectStore

, - private val databaseAdapter: DatabaseAdapter, -) : LinkCleaner

{ +) : LinkCleaner

, + BaseCollectionCleaner( + tableName = tableName, + databaseAdapter = databaseAdapter, + key = applicableColumn, + ) { override fun deleteNotPresent(objects: Collection

?): Boolean { if (objects == null) { return false } - val objectUids = commaSeparatedUidsWithSingleQuotationMarks(objects) - val clause = "$applicableColumn NOT IN ($objectUids);" - return databaseAdapter.delete(tableName, clause, null) > 0 + return deleteNotPresentByKey(objects.map { it.uid() }) } override fun deleteNotPresentInDb(): Boolean { diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/d2/internal/D2DIComponent.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/d2/internal/D2DIComponent.kt index 9b38382770..c281119023 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/d2/internal/D2DIComponent.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/d2/internal/D2DIComponent.kt @@ -33,6 +33,7 @@ import org.hisp.dhis.android.core.arch.api.executors.internal.CoroutineAPICallEx import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter import org.hisp.dhis.android.core.arch.storage.internal.* import org.hisp.dhis.android.core.category.internal.CategoryOptionStore +import org.hisp.dhis.android.core.configuration.internal.MultiUserDatabaseManager import org.hisp.dhis.android.core.configuration.internal.MultiUserDatabaseManagerForD2Manager import org.hisp.dhis.android.core.dataelement.internal.DataElementEndpointCallFactory import org.hisp.dhis.android.core.dataset.internal.DataSetEndpointCallFactory @@ -108,4 +109,7 @@ internal class D2DIComponent( @get:VisibleForTesting val interpreterSelector: InterpreterSelector, + + @get:VisibleForTesting + val multiUserDatabaseManager: MultiUserDatabaseManager, ) diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/d2/internal/D2DIComponentFactory.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/d2/internal/D2DIComponentFactory.kt index e717822123..7d810bde6e 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/d2/internal/D2DIComponentFactory.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/d2/internal/D2DIComponentFactory.kt @@ -50,6 +50,7 @@ import org.hisp.dhis.android.core.enrollment.EnrollmentDIModule import org.hisp.dhis.android.core.event.EventDIModule import org.hisp.dhis.android.core.expressiondimensionitem.ExpressionDimensionItemDIModule import org.hisp.dhis.android.core.fileresource.FileResourceDIModule +import org.hisp.dhis.android.core.icon.IconDIModule import org.hisp.dhis.android.core.imports.ImportsDIModule import org.hisp.dhis.android.core.indicator.IndicatorDIModule import org.hisp.dhis.android.core.legendset.LegendSetDIModule @@ -109,6 +110,7 @@ internal object D2DIComponentFactory { EventDIModule().module, ExpressionDimensionItemDIModule().module, FileResourceDIModule().module, + IconDIModule().module, ImportsDIModule().module, IndicatorDIModule().module, LegendSetDIModule().module, diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/d2/internal/D2Modules.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/d2/internal/D2Modules.kt index d91afe6f78..5f553d1a76 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/d2/internal/D2Modules.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/d2/internal/D2Modules.kt @@ -39,6 +39,7 @@ import org.hisp.dhis.android.core.enrollment.EnrollmentModule import org.hisp.dhis.android.core.event.EventModule import org.hisp.dhis.android.core.expressiondimensionitem.ExpressionDimensionItemModule import org.hisp.dhis.android.core.fileresource.FileResourceModule +import org.hisp.dhis.android.core.icon.IconModule import org.hisp.dhis.android.core.imports.internal.ImportModule import org.hisp.dhis.android.core.indicator.IndicatorModule import org.hisp.dhis.android.core.legendset.LegendSetModule @@ -76,6 +77,7 @@ internal class D2Modules( val expressionDimensionItem: ExpressionDimensionItemModule, val fileResource: FileResourceModule, val importModule: ImportModule, + val icon: IconModule, val indicator: IndicatorModule, val legendSet: LegendSetModule, val dataStore: DataStoreModule, diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/d2/internal/JavaDIClasses.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/d2/internal/JavaDIClasses.kt index 67f6c6d7cf..8a3da2cc76 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/d2/internal/JavaDIClasses.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/d2/internal/JavaDIClasses.kt @@ -30,7 +30,6 @@ package org.hisp.dhis.android.core.arch.d2.internal import org.hisp.dhis.android.core.arch.call.executors.internal.D2CallExecutor import org.hisp.dhis.android.core.arch.db.access.internal.DatabaseAdapterFactory -import org.hisp.dhis.android.core.arch.db.access.internal.DatabaseExport import org.hisp.dhis.android.core.configuration.internal.DatabaseEncryptionPasswordGenerator import org.hisp.dhis.android.core.configuration.internal.DatabaseEncryptionPasswordManager import org.hisp.dhis.android.core.maintenance.internal.ForeignKeyCleaner @@ -38,7 +37,6 @@ import org.hisp.dhis.android.core.maintenance.internal.ForeignKeyCleanerImpl import org.hisp.dhis.android.core.note.internal.NoteUniquenessManager import org.hisp.dhis.android.core.period.internal.PeriodHelper import org.hisp.dhis.android.core.period.internal.PeriodParser -import org.hisp.dhis.android.core.relationship.internal.RelationshipDHISVersionManager import org.hisp.dhis.android.core.sms.data.localdbrepository.internal.DataSetsStore import org.hisp.dhis.android.core.sms.data.localdbrepository.internal.FileResourceCleaner import org.hisp.dhis.android.core.trackedentity.TrackedEntityInstanceService @@ -54,8 +52,6 @@ internal val javaDIClasses = module { single { DatabaseEncryptionPasswordGenerator() } single { TrackedEntityInstanceService(get(), get(), get(), get()) } single { DatabaseAdapterFactory(get(), get()) } - single { DatabaseExport(get(), get(), get()) } single { D2CallExecutor(get(), get()) } - single { RelationshipDHISVersionManager(get()) } single { DataSetsStore(get(), get(), get(), get()) } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/db/access/DatabaseExportMetadata.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/db/access/DatabaseExportMetadata.kt new file mode 100644 index 0000000000..1fe5ea5cd9 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/db/access/DatabaseExportMetadata.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.arch.db.access + +data class DatabaseExportMetadata( + val version: Int, + val date: String, + val serverUrl: String, + val username: String, + val encrypted: Boolean, +) diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/db/access/DatabaseImportExport.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/db/access/DatabaseImportExport.kt index 81b37bf5aa..34ee5233cf 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/db/access/DatabaseImportExport.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/db/access/DatabaseImportExport.kt @@ -30,6 +30,6 @@ package org.hisp.dhis.android.core.arch.db.access import java.io.File interface DatabaseImportExport { - fun importDatabase(file: File) + fun importDatabase(file: File): DatabaseExportMetadata fun exportLoggedUserDatabase(): File } diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/db/access/internal/BaseDatabaseOpenHelper.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/db/access/internal/BaseDatabaseOpenHelper.kt index 7d8aadd20a..2624a312ae 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/db/access/internal/BaseDatabaseOpenHelper.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/db/access/internal/BaseDatabaseOpenHelper.kt @@ -59,6 +59,6 @@ internal class BaseDatabaseOpenHelper(context: Context, targetVersion: Int) { } companion object { - const val VERSION = 155 + const val VERSION = 163 } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/db/access/internal/DatabaseExport.java b/core/src/main/java/org/hisp/dhis/android/core/arch/db/access/internal/DatabaseExport.java deleted file mode 100644 index 127b3d8498..0000000000 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/db/access/internal/DatabaseExport.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (c) 2004-2023, University of Oslo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * Neither the name of the HISP project nor the names of its contributors may - * be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.hisp.dhis.android.core.arch.db.access.internal; - -import android.content.Context; -import android.util.Log; - -import net.zetetic.database.sqlcipher.SQLiteDatabase; -import net.zetetic.database.sqlcipher.SQLiteDatabaseHook; - -import org.hisp.dhis.android.core.common.internal.NativeLibraryLoader; -import org.hisp.dhis.android.core.configuration.internal.DatabaseAccount; -import org.hisp.dhis.android.core.configuration.internal.DatabaseConfigurationHelper; -import org.hisp.dhis.android.core.configuration.internal.DatabaseEncryptionPasswordManager; - -import java.io.File; - -import io.reactivex.functions.Action; - -@SuppressWarnings("PMD.ExcessiveImports") -public class DatabaseExport { - - private final Context context; - private final DatabaseEncryptionPasswordManager passwordManager; - private final DatabaseConfigurationHelper configurationHelper; - - public DatabaseExport(Context context, DatabaseEncryptionPasswordManager passwordManager, - DatabaseConfigurationHelper configurationHelper) { - this.context = context; - this.passwordManager = passwordManager; - this.configurationHelper = configurationHelper; - } - - public void encrypt(String serverUrl, DatabaseAccount oldConfiguration) { - DatabaseAccount newConfiguration = configurationHelper.changeEncryption(serverUrl, oldConfiguration); - export(oldConfiguration, newConfiguration, null, - passwordManager.getPassword(newConfiguration.databaseName()), "Encrypt", null, - EncryptedDatabaseOpenHelper.hook); - } - - public void decrypt(String serverUrl, DatabaseAccount oldConfiguration) { - DatabaseAccount newConfiguration = configurationHelper.changeEncryption(serverUrl, oldConfiguration); - export(oldConfiguration, newConfiguration, passwordManager.getPassword(oldConfiguration.databaseName()), - "", "Decrypt", EncryptedDatabaseOpenHelper.hook, null); - } - - private void export(DatabaseAccount oldConfiguration, - DatabaseAccount newConfiguration, String oldPassword, String newPassword, String tag, - SQLiteDatabaseHook oldHook, SQLiteDatabaseHook newHook) { - wrapAction(() -> { - File oldDatabaseFile = context.getDatabasePath(oldConfiguration.databaseName()); - File newDatabaseFile = context.getDatabasePath(newConfiguration.databaseName()); - - NativeLibraryLoader.INSTANCE.loadSQLCipher(); - SQLiteDatabase oldDatabase = SQLiteDatabase.openOrCreateDatabase(oldDatabaseFile, oldPassword, null, - null, oldHook); - oldDatabase.rawExecSQL(String.format( - "ATTACH DATABASE '%s' as alias KEY '%s';", newDatabaseFile.getAbsolutePath(), newPassword)); - if (newHook != null) { - oldDatabase.rawExecSQL("PRAGMA alias.cipher_page_size = 16384;"); - oldDatabase.rawExecSQL("PRAGMA alias.cipher_memory_security = OFF;"); - } - oldDatabase.rawExecSQL("SELECT sqlcipher_export('alias');"); - oldDatabase.rawExecSQL("DETACH DATABASE alias;"); - - int version = oldDatabase.getVersion(); - SQLiteDatabase newDatabase = SQLiteDatabase.openOrCreateDatabase(newDatabaseFile, newPassword, null, - null, newHook); - newDatabase.setVersion(version); - - newDatabase.close(); - oldDatabase.close(); - }, tag); - } - - @SuppressWarnings({"PMD.PrematureDeclaration", "PMD.PreserveStackTrace"}) - private void wrapAction(Action action, String tag) { - long startMillis = System.currentTimeMillis(); - try { - action.run(); - } catch (Exception e) { - throw new RuntimeException("Exception thrown during database export action: " + tag); - } - long endMillis = System.currentTimeMillis(); - - Log.e("DatabaseExport", tag + ": " + (endMillis - startMillis) + "ms"); - } -} \ No newline at end of file diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/db/access/internal/DatabaseExport.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/db/access/internal/DatabaseExport.kt new file mode 100644 index 0000000000..839473e7cc --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/db/access/internal/DatabaseExport.kt @@ -0,0 +1,153 @@ +/* + * 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.arch.db.access.internal + +import android.content.Context +import android.util.Log +import io.reactivex.functions.Action +import net.zetetic.database.sqlcipher.SQLiteDatabase +import net.zetetic.database.sqlcipher.SQLiteDatabaseHook +import org.hisp.dhis.android.core.common.internal.NativeLibraryLoader.loadSQLCipher +import org.hisp.dhis.android.core.configuration.internal.DatabaseAccount +import org.hisp.dhis.android.core.configuration.internal.DatabaseConfigurationHelper +import org.hisp.dhis.android.core.configuration.internal.DatabaseEncryptionPasswordManager +import org.koin.core.annotation.Singleton +import java.io.File + +@Singleton +internal class DatabaseExport( + private val context: Context, + private val passwordManager: DatabaseEncryptionPasswordManager, + private val configurationHelper: DatabaseConfigurationHelper, +) { + fun encrypt(serverUrl: String, oldConfiguration: DatabaseAccount) { + val newConfiguration = configurationHelper.changeEncryption(serverUrl, oldConfiguration) + export( + oldDatabaseFile = context.getDatabasePath(oldConfiguration.databaseName()), + newDatabaseFile = context.getDatabasePath(newConfiguration.databaseName()), + oldPassword = null, + newPassword = passwordManager.getPassword(newConfiguration.databaseName()), + tag = "Encrypt", + oldHook = null, + newHook = EncryptedDatabaseOpenHelper.hook, + ) + } + + fun encryptAndCopyTo(newConfiguration: DatabaseAccount, sourceFile: File, targetFile: File) { + export( + oldDatabaseFile = sourceFile, + newDatabaseFile = targetFile, + oldPassword = null, + newPassword = passwordManager.getPassword(newConfiguration.databaseName()), + tag = "Encrypt", + oldHook = null, + newHook = EncryptedDatabaseOpenHelper.hook, + ) + } + + fun decrypt(serverUrl: String, oldConfiguration: DatabaseAccount) { + val newConfiguration = configurationHelper.changeEncryption(serverUrl, oldConfiguration) + export( + oldDatabaseFile = context.getDatabasePath(oldConfiguration.databaseName()), + newDatabaseFile = context.getDatabasePath(newConfiguration.databaseName()), + oldPassword = passwordManager.getPassword(oldConfiguration.databaseName()), + newPassword = "", + tag = "Decrypt", + oldHook = EncryptedDatabaseOpenHelper.hook, + newHook = null, + ) + } + + fun decryptAndCopyTo(account: DatabaseAccount, destinationFile: File) { + export( + oldDatabaseFile = context.getDatabasePath(account.databaseName()), + newDatabaseFile = destinationFile, + oldPassword = passwordManager.getPassword(account.databaseName()), + newPassword = "", + tag = "Decrypt", + oldHook = EncryptedDatabaseOpenHelper.hook, + newHook = null, + ) + } + + @Suppress("LongParameterList") + private fun export( + oldDatabaseFile: File, + newDatabaseFile: File, + oldPassword: String?, + newPassword: String, + tag: String, + oldHook: SQLiteDatabaseHook?, + newHook: SQLiteDatabaseHook?, + ) { + wrapAction({ + loadSQLCipher() + + val oldDatabase = SQLiteDatabase.openOrCreateDatabase(oldDatabaseFile, oldPassword, null, null, oldHook) + oldDatabase.rawExecSQL( + String.format( + "ATTACH DATABASE '%s' as alias KEY '%s';", + newDatabaseFile.absolutePath, + newPassword, + ), + ) + + if (newHook != null) { + oldDatabase.rawExecSQL("PRAGMA alias.cipher_page_size = 16384;") + oldDatabase.rawExecSQL("PRAGMA alias.cipher_memory_security = OFF;") + } + oldDatabase.rawExecSQL("SELECT sqlcipher_export('alias');") + oldDatabase.rawExecSQL("DETACH DATABASE alias;") + + val version = oldDatabase.version + val newDatabase = SQLiteDatabase.openOrCreateDatabase( + newDatabaseFile, + newPassword, + null, + null, + newHook, + ) + newDatabase.version = version + + newDatabase.close() + oldDatabase.close() + }, tag) + } + + @Suppress("TooGenericExceptionCaught", "TooGenericExceptionThrown") + private fun wrapAction(action: Action, tag: String) { + val startMillis = System.currentTimeMillis() + try { + action.run() + } catch (e: Exception) { + throw RuntimeException("Exception thrown during database export action: $tag") + } + val endMillis = System.currentTimeMillis() + Log.e("DatabaseExport", tag + ": " + (endMillis - startMillis) + "ms") + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/db/access/internal/DatabaseImportExportImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/db/access/internal/DatabaseImportExportImpl.kt index 668551d8e6..7154bf068a 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/db/access/internal/DatabaseImportExportImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/db/access/internal/DatabaseImportExportImpl.kt @@ -28,45 +28,46 @@ package org.hisp.dhis.android.core.arch.db.access.internal import android.content.Context -import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.arch.db.access.DatabaseExportMetadata import org.hisp.dhis.android.core.arch.db.access.DatabaseImportExport +import org.hisp.dhis.android.core.arch.helpers.DateUtils.getCurrentTimeAndDate +import org.hisp.dhis.android.core.arch.json.internal.ObjectMapperFactory.objectMapper import org.hisp.dhis.android.core.arch.storage.internal.CredentialsSecureStore -import org.hisp.dhis.android.core.configuration.internal.DatabaseConfigurationHelper -import org.hisp.dhis.android.core.configuration.internal.DatabaseConfigurationInsecureStore -import org.hisp.dhis.android.core.configuration.internal.DatabaseNameGenerator -import org.hisp.dhis.android.core.configuration.internal.DatabaseRenamer +import org.hisp.dhis.android.core.configuration.internal.DatabaseAccount import org.hisp.dhis.android.core.configuration.internal.MultiUserDatabaseManager -import org.hisp.dhis.android.core.configuration.internal.ServerUrlParser import org.hisp.dhis.android.core.maintenance.D2Error import org.hisp.dhis.android.core.maintenance.D2ErrorCode import org.hisp.dhis.android.core.maintenance.D2ErrorComponent -import org.hisp.dhis.android.core.systeminfo.internal.SystemInfoStoreImpl import org.hisp.dhis.android.core.user.UserModule -import org.hisp.dhis.android.core.user.internal.UserStoreImpl +import org.hisp.dhis.android.core.util.CipherUtil +import org.hisp.dhis.android.core.util.FileUtils +import org.hisp.dhis.android.core.util.deleteIfExists +import org.hisp.dhis.android.core.util.simpleDateFormat import org.koin.core.annotation.Singleton import java.io.File +import java.util.Date @Singleton internal class DatabaseImportExportImpl( private val context: Context, - private val nameGenerator: DatabaseNameGenerator, private val multiUserDatabaseManager: MultiUserDatabaseManager, private val userModule: UserModule, private val credentialsStore: CredentialsSecureStore, - private val databaseConfigurationSecureStore: DatabaseConfigurationInsecureStore, - private val databaseRenamer: DatabaseRenamer, - private val databaseAdapter: DatabaseAdapter, + private val databaseExport: DatabaseExport, ) : DatabaseImportExport { companion object { - const val TmpDatabase = "tmp-database.db" const val ExportDatabase = "export-database.db" + const val ExportDatabaseProtected = "export-database-protected.db.zip" + const val ExportMetadata = "export-metadata.json" + const val ExportZip = "-database.zip" } private val d2ErrorBuilder = D2Error.builder() .errorComponent(D2ErrorComponent.SDK) - override fun importDatabase(file: File) { + @Suppress("TooGenericExceptionCaught") + override fun importDatabase(file: File): DatabaseExportMetadata { if (userModule.blockingIsLogged()) { throw d2ErrorBuilder .errorDescription("Please log out to import database") @@ -74,52 +75,63 @@ internal class DatabaseImportExportImpl( .build() } - var databaseAdapter: DatabaseAdapter? = null - try { - context.deleteDatabase(TmpDatabase) - val tmpDatabase = context.getDatabasePath(TmpDatabase) - file.copyTo(tmpDatabase) + val importMetadataFile = getWorkingDir().resolve(ExportMetadata).also { it.deleteIfExists() } + val importDatabaseFile = getWorkingDir().resolve(ExportDatabaseProtected).also { it.deleteIfExists() } - val openHelper = UnencryptedDatabaseOpenHelper(context, TmpDatabase, BaseDatabaseOpenHelper.VERSION) - val database = openHelper.readableDatabase - databaseAdapter = UnencryptedDatabaseAdapter(database, openHelper.databaseName) + return try { + FileUtils.unzipFiles(file, getWorkingDir()) - if (database.version > BaseDatabaseOpenHelper.VERSION) { + if (!importMetadataFile.exists() || !importDatabaseFile.exists()) { throw d2ErrorBuilder - .errorDescription("Import database version higher than supported") - .errorCode(D2ErrorCode.DATABASE_IMPORT_VERSION_HIGHER_THAN_SUPPORTED) + .errorDescription("Import file is not valid") + .errorCode(D2ErrorCode.DATABASE_IMPORT_INVALID_FILE) .build() } - val userStore = UserStoreImpl(databaseAdapter) - val username = userStore.selectFirst()!!.username() - - val systemInfoStore = SystemInfoStoreImpl(databaseAdapter) - val contextPath = systemInfoStore.selectFirst()!!.contextPath()!! - val serverUrl = ServerUrlParser.parse(contextPath).toString() - - // TODO What to do if username is null? - val databaseName = nameGenerator.getDatabaseName(serverUrl, username!!, false) - - if (!context.databaseList().contains(databaseName)) { - val destDatabase = context.getDatabasePath(databaseName) - file.copyTo(destDatabase) - - multiUserDatabaseManager.createNew(serverUrl, username, false) - } else { - throw d2ErrorBuilder - .errorDescription("Import database already exists") - .errorCode(D2ErrorCode.DATABASE_IMPORT_ALREADY_EXISTS) - .build() + val metadataContent = importMetadataFile.readText(Charsets.UTF_8) + val metadata = objectMapper().readValue(metadataContent, DatabaseExportMetadata::class.java) + + when { + metadata.version > BaseDatabaseOpenHelper.VERSION -> + throw d2ErrorBuilder + .errorDescription("Import database version higher than supported") + .errorCode(D2ErrorCode.DATABASE_IMPORT_VERSION_HIGHER_THAN_SUPPORTED) + .build() + + getExistingAccountForMetadata(metadata) != null -> + throw d2ErrorBuilder + .errorDescription("Import database already exists") + .errorCode(D2ErrorCode.DATABASE_IMPORT_ALREADY_EXISTS) + .build() + + else -> { + val databaseAccount = multiUserDatabaseManager.createNewPendingToImport(metadata) + val destDatabase = context.getDatabasePath(databaseAccount.importDB()!!.protectedDbName()) + importDatabaseFile.copyTo(destDatabase) + + metadata + } + } + } catch (e: Exception) { + when (e) { + is D2Error -> throw e + else -> + throw d2ErrorBuilder + .errorDescription("Import database failed") + .errorCode(D2ErrorCode.DATABASE_IMPORT_FAILED) + .originalException(e) + .build() } } finally { - databaseAdapter?.close() - context.deleteDatabase(TmpDatabase) + importMetadataFile.deleteIfExists() + importDatabaseFile.deleteIfExists() } } override fun exportLoggedUserDatabase(): File { - context.deleteDatabase(ExportDatabase) + val exportMetadataFile = getWorkingDir().resolve(ExportMetadata).also { it.deleteIfExists() } + val copiedDatabase = getWorkingDir().resolve(ExportDatabase).also { it.deleteIfExists() } + val protectedDatabase = getWorkingDir().resolve(ExportDatabaseProtected).also { it.deleteIfExists() } if (!userModule.blockingIsLogged()) { throw d2ErrorBuilder @@ -129,23 +141,65 @@ internal class DatabaseImportExportImpl( } val credentials = credentialsStore.get() - val databasesConfiguration = databaseConfigurationSecureStore.get() - val userConfiguration = DatabaseConfigurationHelper.getLoggedAccount( - databasesConfiguration, - credentials.serverUrl, - credentials.username, - ) + val userConfiguration = multiUserDatabaseManager.getAccount( + username = credentials.username, + serverUrl = credentials.serverUrl, + )!! + + val databaseName = userConfiguration.databaseName() + val databaseFile = getDatabaseFile(databaseName) if (userConfiguration.encrypted()) { - throw d2ErrorBuilder - .errorDescription("Database export of encrypted database not supported") - .errorCode(D2ErrorCode.DATABASE_EXPORT_ENCRYPTED_NOT_SUPPORTED) - .build() + databaseExport.decryptAndCopyTo(userConfiguration, copiedDatabase) + } else { + databaseFile.copyTo(copiedDatabase) } - databaseAdapter.close() + CipherUtil.createEncryptedZipFile( + input = copiedDatabase, + output = protectedDatabase, + password = credentials.password!!, + ) - val databaseName = userConfiguration.databaseName() - return databaseRenamer.copyDatabase(databaseName, ExportDatabase) + val metadata = DatabaseExportMetadata( + version = BaseDatabaseOpenHelper.VERSION, + date = Date().simpleDateFormat()!!, + serverUrl = userConfiguration.serverUrl(), + username = userConfiguration.username(), + encrypted = userConfiguration.encrypted(), + ) + + exportMetadataFile.bufferedWriter(Charsets.UTF_8).use { + it.write(objectMapper().writeValueAsString(metadata)) + } + + val zipName = credentials.username + '-' + getCurrentTimeAndDate() + '-' + ExportZip + val zipFile = getWorkingDir().resolve(zipName).also { it.deleteIfExists() } + + FileUtils.zipFiles( + files = listOf(exportMetadataFile, protectedDatabase), + zipFile = zipFile, + ) + + exportMetadataFile.deleteIfExists() + copiedDatabase.deleteIfExists() + protectedDatabase.deleteIfExists() + + return zipFile + } + + private fun getWorkingDir(): File { + return context.filesDir + } + + private fun getDatabaseFile(dbName: String): File { + return context.getDatabasePath(dbName) + } + + private fun getExistingAccountForMetadata(metadata: DatabaseExportMetadata): DatabaseAccount? { + return multiUserDatabaseManager.getAccount( + metadata.serverUrl, + metadata.username, + ) } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/custom/internal/TrackerDataViewColumnAdapter.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/custom/internal/TrackerDataViewColumnAdapter.kt new file mode 100644 index 0000000000..c60559545a --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/custom/internal/TrackerDataViewColumnAdapter.kt @@ -0,0 +1,42 @@ +/* + * 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.arch.db.adapters.custom.internal + +import android.content.ContentValues +import android.database.Cursor +import com.gabrielittner.auto.value.cursor.ColumnTypeAdapter +import org.hisp.dhis.android.core.relationship.TrackerDataView +class TrackerDataViewColumnAdapter : ColumnTypeAdapter { + override fun fromCursor(cursor: Cursor, columnName: String): TrackerDataView? { + return TrackerDataView.create(cursor) + } + + override fun toContentValues(values: ContentValues, columnName: String, value: TrackerDataView?) { + value?.toContentValues() + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/custom/internal/TrackerVisualizationDimensionRepetitionColumnAdapter.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/custom/internal/TrackerVisualizationDimensionRepetitionColumnAdapter.kt new file mode 100644 index 0000000000..7c1056dd75 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/custom/internal/TrackerVisualizationDimensionRepetitionColumnAdapter.kt @@ -0,0 +1,51 @@ +/* + * 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.arch.db.adapters.custom.internal + +import org.hisp.dhis.android.core.arch.json.internal.ObjectMapperFactory +import org.hisp.dhis.android.core.visualization.TrackerVisualizationDimensionRepetition + +internal class TrackerVisualizationDimensionRepetitionColumnAdapter : + JSONObjectColumnAdapter() { + + override fun getEnumClass(): Class { + return TrackerVisualizationDimensionRepetition::class.java + } + + override fun serialize(o: TrackerVisualizationDimensionRepetition?): String? { + return TrackerVisualizationDimensionRepetitionColumnAdapter.serialize(o) + } + + companion object { + fun serialize(o: TrackerVisualizationDimensionRepetition?): String? { + return o?.let { + ObjectMapperFactory.objectMapper().writeValueAsString(it) + } + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/enums/internal/AnalyticsDhisVisualizationScopeColumnAdapter.java b/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/enums/internal/AnalyticsDhisVisualizationScopeColumnAdapter.kt similarity index 86% rename from core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/enums/internal/AnalyticsDhisVisualizationScopeColumnAdapter.java rename to core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/enums/internal/AnalyticsDhisVisualizationScopeColumnAdapter.kt index 4c5afef0fa..0573c292b8 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/enums/internal/AnalyticsDhisVisualizationScopeColumnAdapter.java +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/enums/internal/AnalyticsDhisVisualizationScopeColumnAdapter.kt @@ -25,14 +25,12 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +package org.hisp.dhis.android.core.arch.db.adapters.enums.internal -package org.hisp.dhis.android.core.arch.db.adapters.enums.internal; +import org.hisp.dhis.android.core.settings.AnalyticsDhisVisualizationScope -import org.hisp.dhis.android.core.settings.AnalyticsDhisVisualizationScope; - -public class AnalyticsDhisVisualizationScopeColumnAdapter extends EnumColumnAdapter { - @Override - protected Class getEnumClass() { - return AnalyticsDhisVisualizationScope.class; +internal class AnalyticsDhisVisualizationScopeColumnAdapter : EnumColumnAdapter() { + override fun getEnumClass(): Class { + return AnalyticsDhisVisualizationScope::class.java } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/enums/internal/AnalyticsDhisVisualizationTypeColumnAdapter.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/enums/internal/AnalyticsDhisVisualizationTypeColumnAdapter.kt new file mode 100644 index 0000000000..56ae10991f --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/enums/internal/AnalyticsDhisVisualizationTypeColumnAdapter.kt @@ -0,0 +1,36 @@ +/* + * 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.arch.db.adapters.enums.internal + +import org.hisp.dhis.android.core.settings.AnalyticsDhisVisualizationType + +internal class AnalyticsDhisVisualizationTypeColumnAdapter : EnumColumnAdapter() { + override fun getEnumClass(): Class { + return AnalyticsDhisVisualizationType::class.java + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/enums/internal/ImageFormatColumnAdapter.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/enums/internal/ImageFormatColumnAdapter.kt new file mode 100644 index 0000000000..7ea017a3fe --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/enums/internal/ImageFormatColumnAdapter.kt @@ -0,0 +1,36 @@ +/* + * 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.arch.db.adapters.enums.internal + +import org.hisp.dhis.android.core.map.layer.ImageFormat + +internal class ImageFormatColumnAdapter : EnumColumnAdapter() { + override fun getEnumClass(): Class { + return ImageFormat::class.java + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/enums/internal/MapServiceColumnAdapter.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/enums/internal/MapServiceColumnAdapter.kt new file mode 100644 index 0000000000..196ab4754d --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/enums/internal/MapServiceColumnAdapter.kt @@ -0,0 +1,36 @@ +/* + * 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.arch.db.adapters.enums.internal + +import org.hisp.dhis.android.core.map.layer.MapService + +internal class MapServiceColumnAdapter : EnumColumnAdapter() { + override fun getEnumClass(): Class { + return MapService::class.java + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/enums/internal/TrackerVisualizationOutputTypeColumnAdapter.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/enums/internal/TrackerVisualizationOutputTypeColumnAdapter.kt new file mode 100644 index 0000000000..2427c364ec --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/enums/internal/TrackerVisualizationOutputTypeColumnAdapter.kt @@ -0,0 +1,36 @@ +/* + * 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.arch.db.adapters.enums.internal + +import org.hisp.dhis.android.core.visualization.TrackerVisualizationOutputType + +class TrackerVisualizationOutputTypeColumnAdapter : EnumColumnAdapter() { + override fun getEnumClass(): Class { + return TrackerVisualizationOutputType::class.java + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/enums/internal/TrackerVisualizationTypeColumnAdapter.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/enums/internal/TrackerVisualizationTypeColumnAdapter.kt new file mode 100644 index 0000000000..9cde39b30d --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/enums/internal/TrackerVisualizationTypeColumnAdapter.kt @@ -0,0 +1,36 @@ +/* + * 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.arch.db.adapters.enums.internal + +import org.hisp.dhis.android.core.visualization.TrackerVisualizationType + +class TrackerVisualizationTypeColumnAdapter : EnumColumnAdapter() { + override fun getEnumClass(): Class { + return TrackerVisualizationType::class.java + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/identifiable/internal/ObjectWithUidListColumnAdapter.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/identifiable/internal/ObjectWithUidListColumnAdapter.kt new file mode 100644 index 0000000000..b2737fcb56 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/identifiable/internal/ObjectWithUidListColumnAdapter.kt @@ -0,0 +1,72 @@ +/* + * 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.arch.db.adapters.identifiable.internal + +import android.content.ContentValues +import android.database.Cursor +import com.fasterxml.jackson.core.JsonProcessingException +import com.fasterxml.jackson.databind.JsonMappingException +import com.gabrielittner.auto.value.cursor.ColumnTypeAdapter +import org.hisp.dhis.android.core.arch.json.internal.ObjectMapperFactory +import org.hisp.dhis.android.core.common.ObjectWithUid + +internal class ObjectWithUidListColumnAdapter : ColumnTypeAdapter> { + + override fun fromCursor(cursor: Cursor, columnName: String): List { + val columnIndex = cursor.getColumnIndex(columnName) + val str = cursor.getString(columnIndex) + return try { + val idList = ObjectMapperFactory.objectMapper().readValue(str, ArrayList().javaClass) + idList.map { ObjectWithUid.create(it) } + } catch (e: JsonProcessingException) { + listOf() + } catch (e: JsonMappingException) { + listOf() + } catch (e: IllegalArgumentException) { + listOf() + } catch (e: IllegalStateException) { + listOf() + } + } + + override fun toContentValues(values: ContentValues, columnName: String, value: List?) { + try { + values.put(columnName, serialize(value)) + } catch (e: JsonProcessingException) { + e.printStackTrace() + } + } + + companion object { + fun serialize(o: List?): String? { + return o?.map { it.uid() }.let { + ObjectMapperFactory.objectMapper().writeValueAsString(it) + } + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/maintenance/MaintenanceModule.java b/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/ignore/internal/IgnoreTrackerVisualizationDimensionListColumnAdapter.java similarity index 77% rename from core/src/main/java/org/hisp/dhis/android/core/maintenance/MaintenanceModule.java rename to core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/ignore/internal/IgnoreTrackerVisualizationDimensionListColumnAdapter.java index 7fc4e5bdea..b8abcc88e5 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/maintenance/MaintenanceModule.java +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/ignore/internal/IgnoreTrackerVisualizationDimensionListColumnAdapter.java @@ -25,14 +25,13 @@ * (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.maintenance; -public interface MaintenanceModule { - ForeignKeyViolationCollectionRepository foreignKeyViolations(); - D2ErrorCollectionRepository d2Errors(); - PerformanceHintsService getPerformanceHintsService(int organisationUnitThreshold, - int programRulesPerProgramThreshold); +package org.hisp.dhis.android.core.arch.db.adapters.ignore.internal; +import org.hisp.dhis.android.core.visualization.TrackerVisualizationDimension; - // TODO restore when finished DatabaseImportExport databaseImportExport(); +import java.util.List; + +public final class IgnoreTrackerVisualizationDimensionListColumnAdapter + extends IgnoreColumnAdapter> { } \ No newline at end of file diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/ignore/internal/IgnoreUserGroupListColumnAdapter.java b/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/ignore/internal/IgnoreUserGroupListColumnAdapter.java new file mode 100644 index 0000000000..ee9e851544 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/db/adapters/ignore/internal/IgnoreUserGroupListColumnAdapter.java @@ -0,0 +1,36 @@ +/* + * 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.arch.db.adapters.ignore.internal; + +import org.hisp.dhis.android.core.user.UserGroup; + +import java.util.List; + +public final class IgnoreUserGroupListColumnAdapter extends IgnoreColumnAdapter> { +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/db/uidseeker/internal/BaseUidsSeeker.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/db/uidseeker/internal/BaseUidsSeeker.kt index da243f6167..ce5803d542 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/db/uidseeker/internal/BaseUidsSeeker.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/db/uidseeker/internal/BaseUidsSeeker.kt @@ -30,7 +30,7 @@ package org.hisp.dhis.android.core.arch.db.uidseeker.internal import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter -internal open class BaseUidsSeeker constructor(private val databaseAdapter: DatabaseAdapter) { +internal open class BaseUidsSeeker(private val databaseAdapter: DatabaseAdapter) { fun readSingleColumnResults(query: String): Set { val cursor = databaseAdapter.rawQuery(query) diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/fields/internal/FieldsHelper.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/fields/internal/FieldsHelper.kt index b434ac5ebe..7e0500996b 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/fields/internal/FieldsHelper.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/fields/internal/FieldsHelper.kt @@ -36,7 +36,7 @@ import org.hisp.dhis.android.core.common.ObjectWithUid @Suppress("TooManyFunctions") internal class FieldsHelper { - fun field(fieldName: String): Property { + fun field(fieldName: String): Field { return Field.create(fieldName) } diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/handlers/internal/IdentifiableDataHandler.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/handlers/internal/IdentifiableDataHandler.kt index 3c546a7838..b82fbf8601 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/handlers/internal/IdentifiableDataHandler.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/handlers/internal/IdentifiableDataHandler.kt @@ -31,7 +31,7 @@ import org.hisp.dhis.android.core.common.DeletableDataObject import org.hisp.dhis.android.core.common.ObjectWithUidInterface import org.hisp.dhis.android.core.relationship.internal.RelationshipItemRelatives -interface IdentifiableDataHandler where O : DeletableDataObject, O : ObjectWithUidInterface { +internal interface IdentifiableDataHandler where O : DeletableDataObject, O : ObjectWithUidInterface { @JvmSuppressWildcards fun handleMany( oCollection: Collection?, diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/handlers/internal/IdentifiableDataHandlerImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/handlers/internal/IdentifiableDataHandlerImpl.kt index 1ae357f55a..ee3073f305 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/handlers/internal/IdentifiableDataHandlerImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/handlers/internal/IdentifiableDataHandlerImpl.kt @@ -141,7 +141,6 @@ internal abstract class IdentifiableDataHandlerImpl( ownedRelationships, parent.uid(), relatives, - relationshipHandler, ) } relationshipHandler.handleMany( diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/handlers/internal/ObjectWithoutUidHandlerImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/handlers/internal/ObjectWithoutUidHandlerImpl.kt index cbdef7bca5..afd2048a1e 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/handlers/internal/ObjectWithoutUidHandlerImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/handlers/internal/ObjectWithoutUidHandlerImpl.kt @@ -30,7 +30,7 @@ package org.hisp.dhis.android.core.arch.handlers.internal import org.hisp.dhis.android.core.arch.db.stores.internal.ObjectWithoutUidStore import org.hisp.dhis.android.core.common.CoreObject -internal open class ObjectWithoutUidHandlerImpl(protected val store: ObjectWithoutUidStore) : +internal open class ObjectWithoutUidHandlerImpl(protected val store: ObjectWithoutUidStore) : HandlerBaseImpl() { override fun deleteOrPersist(o: O): HandleAction { diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/helpers/DateUtils.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/helpers/DateUtils.kt index eef30bd5eb..0c4ae84d92 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/helpers/DateUtils.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/helpers/DateUtils.kt @@ -27,6 +27,9 @@ */ package org.hisp.dhis.android.core.arch.helpers +import kotlinx.datetime.Clock +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime import org.hisp.dhis.android.core.arch.dateformat.internal.SafeDateFormat import org.hisp.dhis.android.core.period.Period import org.hisp.dhis.android.core.period.PeriodType @@ -95,4 +98,18 @@ object DateUtils { c.add(Calendar.MONTH, amount) return c.time } + + private fun Int.zeroPrefixed(length: Int = 2): String = this.toString().padStart(length, '0') + internal fun getCurrentTimeAndDate(): String { + val dateTime = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) + + val year = dateTime.year + val month = dateTime.monthNumber.zeroPrefixed() + val day = dateTime.dayOfMonth.zeroPrefixed() + val hour = dateTime.hour.zeroPrefixed() + val minute = dateTime.minute.zeroPrefixed() + val seconds = dateTime.second.zeroPrefixed() + + return "$year$month$day-$hour$minute$seconds" + } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/repositories/collection/ReadOnlyCollectionRepository.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/repositories/collection/ReadOnlyCollectionRepository.kt index d242f752e6..6fc97e38a9 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/repositories/collection/ReadOnlyCollectionRepository.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/repositories/collection/ReadOnlyCollectionRepository.kt @@ -29,7 +29,9 @@ package org.hisp.dhis.android.core.arch.repositories.collection import androidx.lifecycle.LiveData import androidx.paging.PagedList +import androidx.paging.PagingData import io.reactivex.Single +import kotlinx.coroutines.flow.Flow import org.hisp.dhis.android.core.arch.repositories.`object`.ReadOnlyObjectRepository interface ReadOnlyCollectionRepository : BaseRepository { @@ -57,6 +59,13 @@ interface ReadOnlyCollectionRepository : BaseRepository { @Deprecated(message = "Use {@link #getPagingData()} instead}", replaceWith = ReplaceWith("getPagingData()")) fun getPaged(pageSize: Int): LiveData> + /** + * Uses Paging3 library and return a Flow + * @param pageSize Length of the page + * @return a Flow of PagingData elements + */ + fun getPagingData(pageSize: Int): Flow> + /** * Get the count of elements in an asynchronous way, returning a `Single`. * @return A `Single` object with the element count diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/repositories/collection/internal/ReadOnlyCollectionRepositoryImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/repositories/collection/internal/ReadOnlyCollectionRepositoryImpl.kt index 665b2743cd..2992f48403 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/repositories/collection/internal/ReadOnlyCollectionRepositoryImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/repositories/collection/internal/ReadOnlyCollectionRepositoryImpl.kt @@ -31,7 +31,12 @@ import androidx.lifecycle.LiveData import androidx.paging.DataSource import androidx.paging.LivePagedListBuilder import androidx.paging.PagedList +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.PagingSource import io.reactivex.Single +import kotlinx.coroutines.flow.Flow import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter import org.hisp.dhis.android.core.arch.db.querybuilders.internal.OrderByClauseBuilder import org.hisp.dhis.android.core.arch.db.querybuilders.internal.WhereClauseBuilder @@ -42,10 +47,12 @@ import org.hisp.dhis.android.core.arch.repositories.collection.ReadOnlyCollectio import org.hisp.dhis.android.core.arch.repositories.filters.internal.FilterConnectorFactory import org.hisp.dhis.android.core.arch.repositories.`object`.ReadOnlyOneObjectRepositoryFinalImpl import org.hisp.dhis.android.core.arch.repositories.paging.internal.RepositoryDataSource +import org.hisp.dhis.android.core.arch.repositories.paging.internal.RepositoryPagingSource import org.hisp.dhis.android.core.arch.repositories.scope.RepositoryScope import org.hisp.dhis.android.core.arch.repositories.scope.internal.WhereClauseFromScopeBuilder import org.hisp.dhis.android.core.common.CoreObject +@Suppress("TooManyFunctions") open class ReadOnlyCollectionRepositoryImpl> internal constructor( private val store: ReadableStore, internal val databaseAdapter: DatabaseAdapter, @@ -113,10 +120,25 @@ open class ReadOnlyCollectionRepositoryImpl> { + return getPager(pageSize).flow + } + + fun getPager(pageSize: Int): Pager { + return Pager( + config = PagingConfig(pageSize = pageSize), + ) { + pagingSource + } + } + @Deprecated("Use {@link #getPagingData()} instead}", replaceWith = ReplaceWith("getPagingData()")) val dataSource: DataSource get() = RepositoryDataSource(store, databaseAdapter, scope, childrenAppenders) + private val pagingSource: PagingSource + get() = RepositoryPagingSource(store, databaseAdapter, scope, childrenAppenders) + /** * Get the count of elements in an asynchronous way, returning a `Single`. * @return A `Single` object with the element count diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/repositories/collection/internal/ReadOnlyWithTransformerCollectionRepositoryImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/repositories/collection/internal/ReadOnlyWithTransformerCollectionRepositoryImpl.kt index 2e3c34d678..29c3469661 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/arch/repositories/collection/internal/ReadOnlyWithTransformerCollectionRepositoryImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/repositories/collection/internal/ReadOnlyWithTransformerCollectionRepositoryImpl.kt @@ -31,7 +31,12 @@ import androidx.lifecycle.LiveData import androidx.paging.DataSource import androidx.paging.LivePagedListBuilder import androidx.paging.PagedList +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.PagingSource import io.reactivex.Single +import kotlinx.coroutines.flow.Flow import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter import org.hisp.dhis.android.core.arch.db.querybuilders.internal.OrderByClauseBuilder import org.hisp.dhis.android.core.arch.db.querybuilders.internal.WhereClauseBuilder @@ -44,10 +49,12 @@ import org.hisp.dhis.android.core.arch.repositories.filters.internal.FilterConne import org.hisp.dhis.android.core.arch.repositories.`object`.ReadOnlyObjectRepository import org.hisp.dhis.android.core.arch.repositories.`object`.internal.ReadOnlyWithTransformerObjectRepositoryImpl import org.hisp.dhis.android.core.arch.repositories.paging.internal.RepositoryDataSourceWithTransformer +import org.hisp.dhis.android.core.arch.repositories.paging.internal.RepositoryPagingSourceWithTransformer import org.hisp.dhis.android.core.arch.repositories.scope.RepositoryScope import org.hisp.dhis.android.core.arch.repositories.scope.internal.WhereClauseFromScopeBuilder import org.hisp.dhis.android.core.common.CoreObject +@Suppress("TooManyFunctions") internal open class ReadOnlyWithTransformerCollectionRepositoryImpl< M : CoreObject, T : Any, @@ -126,9 +133,28 @@ internal open class ReadOnlyWithTransformerCollectionRepositoryImpl< return LivePagedListBuilder(factory, pageSize).build() } + override fun getPagingData(pageSize: Int): Flow> { + return Pager( + config = PagingConfig(pageSize = pageSize), + ) { + pagingSource + }.flow + } + + fun getPager(pageSize: Int): Pager { + return Pager( + config = PagingConfig(pageSize = pageSize), + ) { + pagingSource + } + } + val dataSource: DataSource get() = RepositoryDataSourceWithTransformer(store, databaseAdapter, scope, childrenAppenders, transformer) + private val pagingSource: PagingSource + get() = RepositoryPagingSourceWithTransformer(store, databaseAdapter, scope, childrenAppenders, transformer) + /** * Get the count of elements in an asynchronous way, returning a `Single`. * @return A `Single` object with the element count diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/repositories/paging/PageConfig.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/repositories/paging/PageConfig.kt new file mode 100644 index 0000000000..d3c819a24b --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/repositories/paging/PageConfig.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.arch.repositories.paging + +sealed class PageConfig { + data object NoPaging : PageConfig() + data class Paging(val page: Int, val pageSize: Int) : PageConfig() +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/repositories/paging/internal/RepositoryPagingSource.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/repositories/paging/internal/RepositoryPagingSource.kt new file mode 100644 index 0000000000..bd908852fd --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/repositories/paging/internal/RepositoryPagingSource.kt @@ -0,0 +1,93 @@ +/* + * 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.arch.repositories.paging.internal + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.arch.db.querybuilders.internal.OrderByClauseBuilder +import org.hisp.dhis.android.core.arch.db.querybuilders.internal.WhereClauseBuilder +import org.hisp.dhis.android.core.arch.db.stores.internal.ReadableStore +import org.hisp.dhis.android.core.arch.repositories.children.internal.ChildrenAppenderExecutor.appendInObjectCollection +import org.hisp.dhis.android.core.arch.repositories.children.internal.ChildrenAppenderGetter +import org.hisp.dhis.android.core.arch.repositories.scope.RepositoryScope +import org.hisp.dhis.android.core.arch.repositories.scope.internal.WhereClauseFromScopeBuilder +import org.hisp.dhis.android.core.common.CoreObject +import java.io.IOException + +class RepositoryPagingSource internal constructor( + private val store: ReadableStore, + private val databaseAdapter: DatabaseAdapter, + private val scope: RepositoryScope, + private val childrenAppenders: ChildrenAppenderGetter, +) : PagingSource() { + + override fun getRefreshKey(state: PagingState): M? { + return state.anchorPosition?.let { state.closestPageToPosition(it)?.prevKey } + } + + override suspend fun load(params: LoadParams): LoadResult { + try { + val whereClauseBuilder = WhereClauseBuilder() + + params.key?.let { key -> + val reverse = when (params) { + is LoadParams.Prepend -> true + else -> false + } + + OrderByClauseBuilder.addSortingClauses( + whereClauseBuilder, + scope.orderBy(), + key.toContentValues(), + reverse, + scope.pagingKey(), + ) + } + + val whereClause = WhereClauseFromScopeBuilder(whereClauseBuilder).getWhereClause( + scope, + ) + val withoutChildren = store.selectWhere( + whereClause, + OrderByClauseBuilder.orderByFromItems(scope.orderBy(), scope.pagingKey()), + params.loadSize, + ) + + val items = appendInObjectCollection(withoutChildren, databaseAdapter, childrenAppenders, scope.children()) + return LoadResult.Page( + data = items, + prevKey = items.firstOrNull(), + nextKey = items.getOrNull(params.loadSize - 1), + ) + } catch (e: IOException) { + return LoadResult.Error(e) + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/arch/repositories/paging/internal/RepositoryPagingSourceWithTransformer.kt b/core/src/main/java/org/hisp/dhis/android/core/arch/repositories/paging/internal/RepositoryPagingSourceWithTransformer.kt new file mode 100644 index 0000000000..f91b42b159 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/arch/repositories/paging/internal/RepositoryPagingSourceWithTransformer.kt @@ -0,0 +1,97 @@ +/* + * 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.arch.repositories.paging.internal + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.arch.db.querybuilders.internal.OrderByClauseBuilder +import org.hisp.dhis.android.core.arch.db.querybuilders.internal.WhereClauseBuilder +import org.hisp.dhis.android.core.arch.db.stores.internal.ReadableStore +import org.hisp.dhis.android.core.arch.handlers.internal.TwoWayTransformer +import org.hisp.dhis.android.core.arch.repositories.children.internal.ChildrenAppenderExecutor +import org.hisp.dhis.android.core.arch.repositories.children.internal.ChildrenAppenderGetter +import org.hisp.dhis.android.core.arch.repositories.scope.RepositoryScope +import org.hisp.dhis.android.core.arch.repositories.scope.internal.WhereClauseFromScopeBuilder +import org.hisp.dhis.android.core.common.CoreObject +import java.io.IOException + +internal class RepositoryPagingSourceWithTransformer internal constructor( + private val store: ReadableStore, + private val databaseAdapter: DatabaseAdapter, + private val scope: RepositoryScope, + private val childrenAppenders: ChildrenAppenderGetter, + private val transformer: TwoWayTransformer, +) : PagingSource() { + override fun getRefreshKey(state: PagingState): M? { + return state.anchorPosition?.let { state.closestPageToPosition(it)?.prevKey } + } + + override suspend fun load(params: LoadParams): LoadResult { + try { + val whereClauseBuilder = WhereClauseBuilder() + + params.key?.let { key -> + val reverse = when (params) { + is LoadParams.Prepend -> true + else -> false + } + + OrderByClauseBuilder.addSortingClauses( + whereClauseBuilder, + scope.orderBy(), + key.toContentValues(), + reverse, + scope.pagingKey(), + ) + } + + val whereClause = WhereClauseFromScopeBuilder(whereClauseBuilder).getWhereClause(scope) + val withoutChildren = store.selectWhere( + whereClause, + OrderByClauseBuilder.orderByFromItems(scope.orderBy(), scope.pagingKey()), + params.loadSize, + ) + val items = ChildrenAppenderExecutor.appendInObjectCollection( + withoutChildren, + databaseAdapter, + childrenAppenders, + scope.children(), + ) + + return LoadResult.Page( + data = items.map { transformer.transform(it) }, + prevKey = items.firstOrNull(), + nextKey = items.getOrNull(params.loadSize - 1), + ) + } catch (e: IOException) { + return LoadResult.Error(e) + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/attribute/DataElementAttributeValueLink.java b/core/src/main/java/org/hisp/dhis/android/core/attribute/DataElementAttributeValueLink.java index 219d1770a4..c36792513f 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/attribute/DataElementAttributeValueLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/attribute/DataElementAttributeValueLink.java @@ -60,7 +60,7 @@ public static Builder builder() { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); public abstract Builder dataElement(String dataElement); diff --git a/core/src/main/java/org/hisp/dhis/android/core/attribute/ProgramAttributeValueLink.java b/core/src/main/java/org/hisp/dhis/android/core/attribute/ProgramAttributeValueLink.java index fb7a14f7bf..aa6cccc8c4 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/attribute/ProgramAttributeValueLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/attribute/ProgramAttributeValueLink.java @@ -60,7 +60,7 @@ public static Builder builder() { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); public abstract Builder program(String program); diff --git a/core/src/main/java/org/hisp/dhis/android/core/attribute/ProgramStageAttributeValueLink.java b/core/src/main/java/org/hisp/dhis/android/core/attribute/ProgramStageAttributeValueLink.java index 679d6eb034..6b73887969 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/attribute/ProgramStageAttributeValueLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/attribute/ProgramStageAttributeValueLink.java @@ -60,7 +60,7 @@ public static Builder builder() { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); public abstract Builder programStage(String programStage); diff --git a/core/src/main/java/org/hisp/dhis/android/core/attribute/internal/AttributeValuesFields.kt b/core/src/main/java/org/hisp/dhis/android/core/attribute/internal/AttributeValuesFields.kt new file mode 100644 index 0000000000..588c26631c --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/attribute/internal/AttributeValuesFields.kt @@ -0,0 +1,46 @@ +/* + * 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.attribute.internal + +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper +import org.hisp.dhis.android.core.attribute.AttributeValue +import org.hisp.dhis.android.core.common.ObjectWithUid + +internal object AttributeValuesFields { + const val VALUE = "value" + const val ATTRIBUTE = "attribute" + + private val fh = FieldsHelper() + + val allFields: Fields = Fields.builder() + .fields( + fh.field(VALUE), + fh.nestedField(ATTRIBUTE).with(ObjectWithUid.uid), + ).build() +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/category/Category.java b/core/src/main/java/org/hisp/dhis/android/core/category/Category.java index d4b21479a0..bac8207357 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/category/Category.java +++ b/core/src/main/java/org/hisp/dhis/android/core/category/Category.java @@ -69,7 +69,7 @@ public static Builder builder() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseIdentifiableObject.Builder { + public abstract static class Builder extends BaseIdentifiableObject.Builder { public abstract Builder id(Long id); public abstract Builder categoryOptions(@Nullable List categoryOptions); diff --git a/core/src/main/java/org/hisp/dhis/android/core/category/CategoryCategoryComboLink.java b/core/src/main/java/org/hisp/dhis/android/core/category/CategoryCategoryComboLink.java index 29b275a5ac..a728de20c9 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/category/CategoryCategoryComboLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/category/CategoryCategoryComboLink.java @@ -60,7 +60,7 @@ public static CategoryCategoryComboLink create(Cursor cursor) { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/category/CategoryCategoryOptionLink.java b/core/src/main/java/org/hisp/dhis/android/core/category/CategoryCategoryOptionLink.java index d54053f64a..be4ac31993 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/category/CategoryCategoryOptionLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/category/CategoryCategoryOptionLink.java @@ -60,7 +60,7 @@ public static CategoryCategoryOptionLink create(Cursor cursor) { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/category/CategoryCombo.java b/core/src/main/java/org/hisp/dhis/android/core/category/CategoryCombo.java index 0cea339861..e20af41e85 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/category/CategoryCombo.java +++ b/core/src/main/java/org/hisp/dhis/android/core/category/CategoryCombo.java @@ -77,7 +77,7 @@ public static Builder builder() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseIdentifiableObject.Builder { + public abstract static class Builder extends BaseIdentifiableObject.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/category/CategoryOption.java b/core/src/main/java/org/hisp/dhis/android/core/category/CategoryOption.java index 5d9d7439e4..6b4298d9b7 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/category/CategoryOption.java +++ b/core/src/main/java/org/hisp/dhis/android/core/category/CategoryOption.java @@ -88,7 +88,7 @@ public static Builder builder() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseNameableObject.Builder { + public abstract static class Builder extends BaseNameableObject.Builder { public abstract Builder id(Long id); public abstract Builder startDate(@Nullable Date startDate); diff --git a/core/src/main/java/org/hisp/dhis/android/core/category/CategoryOptionCombo.java b/core/src/main/java/org/hisp/dhis/android/core/category/CategoryOptionCombo.java index 7526563aed..dd5e289241 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/category/CategoryOptionCombo.java +++ b/core/src/main/java/org/hisp/dhis/android/core/category/CategoryOptionCombo.java @@ -72,7 +72,7 @@ public static Builder builder() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseIdentifiableObject.Builder { + public abstract static class Builder extends BaseIdentifiableObject.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/category/CategoryOptionComboCategoryOptionLink.java b/core/src/main/java/org/hisp/dhis/android/core/category/CategoryOptionComboCategoryOptionLink.java index 436228c2d5..8e4eb9e2e5 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/category/CategoryOptionComboCategoryOptionLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/category/CategoryOptionComboCategoryOptionLink.java @@ -57,7 +57,7 @@ public static CategoryOptionComboCategoryOptionLink create(Cursor cursor) { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/category/CategoryOptionOrganisationUnitLink.java b/core/src/main/java/org/hisp/dhis/android/core/category/CategoryOptionOrganisationUnitLink.java index 551ad86b02..fe8fc321b1 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/category/CategoryOptionOrganisationUnitLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/category/CategoryOptionOrganisationUnitLink.java @@ -60,7 +60,7 @@ public static CategoryOptionOrganisationUnitLink create(Cursor cursor) { } @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); public abstract Builder categoryOption(String categoryOption); diff --git a/core/src/main/java/org/hisp/dhis/android/core/common/BaseDataObject.java b/core/src/main/java/org/hisp/dhis/android/core/common/BaseDataObject.java index b713ed08f9..a761893045 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/common/BaseDataObject.java +++ b/core/src/main/java/org/hisp/dhis/android/core/common/BaseDataObject.java @@ -57,7 +57,7 @@ public State state() { public abstract State syncState(); @JsonPOJOBuilder(withPrefix = "") - protected static abstract class Builder extends BaseObject.Builder { + protected abstract static class Builder extends BaseObject.Builder { public abstract T syncState(@Nullable State syncState); /** diff --git a/core/src/main/java/org/hisp/dhis/android/core/common/BaseDeletableDataObject.java b/core/src/main/java/org/hisp/dhis/android/core/common/BaseDeletableDataObject.java index eac0a783b2..1c2ae17488 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/common/BaseDeletableDataObject.java +++ b/core/src/main/java/org/hisp/dhis/android/core/common/BaseDeletableDataObject.java @@ -43,7 +43,7 @@ public abstract class BaseDeletableDataObject extends BaseDataObject implements public abstract Boolean deleted(); @JsonPOJOBuilder(withPrefix = "") - protected static abstract class Builder extends BaseDataObject.Builder { + protected abstract static class Builder extends BaseDataObject.Builder { public abstract T deleted(@Nullable Boolean deleted); } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/common/BaseIdentifiableObject.java b/core/src/main/java/org/hisp/dhis/android/core/common/BaseIdentifiableObject.java index f26ba9e568..215be21c5d 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/common/BaseIdentifiableObject.java +++ b/core/src/main/java/org/hisp/dhis/android/core/common/BaseIdentifiableObject.java @@ -107,7 +107,7 @@ public static String dateToDateStr(Date date) { } @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder { + public abstract static class Builder { @JsonProperty(UID) @JsonAlias({UUID}) // Introduced in 2.38 due to changes in userCredentials model DHIS2-12577 diff --git a/core/src/main/java/org/hisp/dhis/android/core/common/BaseNameableObject.java b/core/src/main/java/org/hisp/dhis/android/core/common/BaseNameableObject.java index 8c404082c6..f574ddd7e3 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/common/BaseNameableObject.java +++ b/core/src/main/java/org/hisp/dhis/android/core/common/BaseNameableObject.java @@ -55,7 +55,7 @@ public abstract class BaseNameableObject extends BaseIdentifiableObject implemen public abstract String displayDescription(); @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseIdentifiableObject.Builder { + public abstract static class Builder extends BaseIdentifiableObject.Builder { public abstract T shortName(@Nullable String shortName); diff --git a/core/src/main/java/org/hisp/dhis/android/core/common/BaseObject.java b/core/src/main/java/org/hisp/dhis/android/core/common/BaseObject.java index 69f1416890..a9ec7b279a 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/common/BaseObject.java +++ b/core/src/main/java/org/hisp/dhis/android/core/common/BaseObject.java @@ -31,7 +31,7 @@ @SuppressWarnings("PMD.EmptyMethodInAbstractClassShouldBeAbstract") public abstract class BaseObject implements CoreObject { - public static abstract class Builder { + public abstract static class Builder { public abstract T id(Long id); } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/common/FilterOperators.java b/core/src/main/java/org/hisp/dhis/android/core/common/FilterOperators.java index 64bff4d31f..abffbb26dc 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/common/FilterOperators.java +++ b/core/src/main/java/org/hisp/dhis/android/core/common/FilterOperators.java @@ -103,7 +103,7 @@ public abstract class FilterOperators { public abstract DateFilterPeriod dateFilter(); @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder { + public abstract static class Builder { public abstract T le(String le); diff --git a/core/src/main/java/org/hisp/dhis/android/core/common/FilterQueryCriteria.java b/core/src/main/java/org/hisp/dhis/android/core/common/FilterQueryCriteria.java index bd13010a94..ea94c18a45 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/common/FilterQueryCriteria.java +++ b/core/src/main/java/org/hisp/dhis/android/core/common/FilterQueryCriteria.java @@ -91,7 +91,7 @@ public abstract class FilterQueryCriteria { public abstract DateFilterPeriod lastUpdatedDate(); @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder { + public abstract static class Builder { public abstract T followUp(Boolean followUp); diff --git a/core/src/main/java/org/hisp/dhis/android/core/common/internal/DataAccessFields.java b/core/src/main/java/org/hisp/dhis/android/core/common/internal/DataAccessFields.kt similarity index 74% rename from core/src/main/java/org/hisp/dhis/android/core/common/internal/DataAccessFields.java rename to core/src/main/java/org/hisp/dhis/android/core/common/internal/DataAccessFields.kt index af33b21dab..ca5cf71b29 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/common/internal/DataAccessFields.java +++ b/core/src/main/java/org/hisp/dhis/android/core/common/internal/DataAccessFields.kt @@ -25,26 +25,22 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +package org.hisp.dhis.android.core.common.internal -package org.hisp.dhis.android.core.common.internal; +import org.hisp.dhis.android.core.arch.api.fields.internal.Field +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.common.DataAccess -import org.hisp.dhis.android.core.arch.api.fields.internal.Field; -import org.hisp.dhis.android.core.arch.api.fields.internal.Fields; -import org.hisp.dhis.android.core.common.DataAccess; +internal object DataAccessFields { + private const val READ = "read" + private const val WRITE = "write" -public final class DataAccessFields { + val read: Field = Field.create(READ) - private static final String READ = "read"; - private static final String WRITE = "write"; + val write: Field = Field.create(WRITE) - public static final Field read = Field.create(READ); - public static final Field write = Field.create(WRITE); - - public static final Fields allFields = Fields.builder().fields( - read, - write - ).build(); - - private DataAccessFields() { - } + val allFields: Fields = Fields.builder().fields( + read, + write, + ).build() } diff --git a/core/src/main/java/org/hisp/dhis/android/core/common/objectstyle/internal/TableWithObjectStyle.kt b/core/src/main/java/org/hisp/dhis/android/core/common/objectstyle/internal/TableWithObjectStyle.kt new file mode 100644 index 0000000000..996542df8c --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/common/objectstyle/internal/TableWithObjectStyle.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.common.objectstyle.internal + +import org.hisp.dhis.android.core.dataelement.DataElementTableInfo +import org.hisp.dhis.android.core.dataset.DataSetTableInfo +import org.hisp.dhis.android.core.indicator.IndicatorTableInfo +import org.hisp.dhis.android.core.option.OptionTableInfo +import org.hisp.dhis.android.core.program.ProgramSectionTableInfo +import org.hisp.dhis.android.core.program.ProgramStageTableInfo +import org.hisp.dhis.android.core.program.ProgramTableInfo +import org.hisp.dhis.android.core.trackedentity.TrackedEntityAttributeTableInfo +import org.hisp.dhis.android.core.trackedentity.TrackedEntityInstanceFilterTableInfo +import org.hisp.dhis.android.core.trackedentity.TrackedEntityTypeTableInfo + +internal object TableWithObjectStyle { + val allTableNames: List = setOf( + DataElementTableInfo.TABLE_INFO, + DataSetTableInfo.TABLE_INFO, + IndicatorTableInfo.TABLE_INFO, + OptionTableInfo.TABLE_INFO, + ProgramSectionTableInfo.TABLE_INFO, + ProgramStageTableInfo.TABLE_INFO, + ProgramTableInfo.TABLE_INFO, + TrackedEntityAttributeTableInfo.TABLE_INFO, + TrackedEntityInstanceFilterTableInfo.TABLE_INFO, + TrackedEntityTypeTableInfo.TABLE_INFO, + ).map { + it.name() + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/configuration/internal/DatabaseAccount.java b/core/src/main/java/org/hisp/dhis/android/core/configuration/internal/DatabaseAccount.java index 032192d5f7..36112df0fb 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/configuration/internal/DatabaseAccount.java +++ b/core/src/main/java/org/hisp/dhis/android/core/configuration/internal/DatabaseAccount.java @@ -64,6 +64,10 @@ public abstract class DatabaseAccount { @Nullable public abstract State syncState(); + @JsonProperty() + @Nullable + public abstract DatabaseAccountImportDB importDB(); + public abstract Builder toBuilder(); public static Builder builder() { @@ -84,6 +88,8 @@ public abstract static class Builder { public abstract Builder databaseCreationDate(String databaseCreationDate); + public abstract Builder importDB(DatabaseAccountImportDB importDB); + public abstract Builder syncState(State syncState); public abstract DatabaseAccount build(); diff --git a/core/src/main/java/org/hisp/dhis/android/core/configuration/internal/DatabaseAccountImportDB.java b/core/src/main/java/org/hisp/dhis/android/core/configuration/internal/DatabaseAccountImportDB.java new file mode 100644 index 0000000000..2e39a8c7f0 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/configuration/internal/DatabaseAccountImportDB.java @@ -0,0 +1,65 @@ +/* + * 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.configuration.internal; + +import androidx.annotation.NonNull; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; + +@AutoValue +@JsonDeserialize(builder = AutoValue_DatabaseAccountImportDB.Builder.class) +public abstract class DatabaseAccountImportDB { + + @JsonProperty() + @NonNull + public abstract DatabaseAccountImportStatus status(); + + @JsonProperty + @NonNull + public abstract String protectedDbName(); + + public abstract Builder toBuilder(); + + public static Builder builder() { + return new AutoValue_DatabaseAccountImportDB.Builder(); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "") + public abstract static class Builder { + + public abstract Builder status(DatabaseAccountImportStatus importStatus); + + public abstract Builder protectedDbName(String protectedDbName); + + public abstract DatabaseAccountImportDB build(); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/hisp/dhis/android/core/configuration/internal/DatabaseAccountImportStatus.kt b/core/src/main/java/org/hisp/dhis/android/core/configuration/internal/DatabaseAccountImportStatus.kt new file mode 100644 index 0000000000..29ef296726 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/configuration/internal/DatabaseAccountImportStatus.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.configuration.internal + +enum class DatabaseAccountImportStatus { + PENDING_TO_IMPORT, + IMPORTED, +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/configuration/internal/DatabaseConfigurationHelper.kt b/core/src/main/java/org/hisp/dhis/android/core/configuration/internal/DatabaseConfigurationHelper.kt index a0ca76bbf8..23194c1763 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/configuration/internal/DatabaseConfigurationHelper.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/configuration/internal/DatabaseConfigurationHelper.kt @@ -48,26 +48,42 @@ internal class DatabaseConfigurationHelper( .build() } - fun addAccount( + fun addOrUpdateAccount( configuration: DatabasesConfiguration?, serverUrl: String, username: String, encrypt: Boolean, + importStatus: DatabaseAccountImportStatus? = null, ): DatabasesConfiguration { + val dbName = databaseNameGenerator.getDatabaseName(serverUrl, username, encrypt) + val importDb = importStatus?.let { + DatabaseAccountImportDB.builder() + .status(importStatus) + .protectedDbName("$dbName.protected") + .build() + } val newAccount = DatabaseAccount.builder() .username(username) .serverUrl(serverUrl) - .databaseName(databaseNameGenerator.getDatabaseName(serverUrl, username, encrypt)) + .databaseName(dbName) .encrypted(encrypt) .databaseCreationDate(dateProvider.dateStr) + .importDB(importDb) .build() + return addOrUpdateAccount(configuration, newAccount) + } + + fun addOrUpdateAccount( + configuration: DatabasesConfiguration?, + account: DatabaseAccount, + ): DatabasesConfiguration { val otherAccounts = configuration?.accounts()?.filterNot { - equalsIgnoreProtocol(it.serverUrl(), serverUrl) && it.username() == username + equalsIgnoreProtocol(it.serverUrl(), account.serverUrl()) && it.username() == account.username() } ?: emptyList() return (configuration?.toBuilder() ?: DatabasesConfiguration.builder()) - .accounts(otherAccounts + newAccount) + .accounts(otherAccounts + account) .build() } diff --git a/core/src/main/java/org/hisp/dhis/android/core/configuration/internal/MultiUserDatabaseManager.kt b/core/src/main/java/org/hisp/dhis/android/core/configuration/internal/MultiUserDatabaseManager.kt index d890eea172..b31c02e212 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/configuration/internal/MultiUserDatabaseManager.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/configuration/internal/MultiUserDatabaseManager.kt @@ -30,13 +30,17 @@ package org.hisp.dhis.android.core.configuration.internal import android.content.Context import android.util.Log import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.arch.db.access.DatabaseExportMetadata import org.hisp.dhis.android.core.arch.db.access.internal.DatabaseAdapterFactory import org.hisp.dhis.android.core.arch.db.access.internal.DatabaseExport import org.hisp.dhis.android.core.arch.helpers.FileResourceDirectoryHelper import org.hisp.dhis.android.core.arch.storage.internal.Credentials +import org.hisp.dhis.android.core.util.CipherUtil +import org.hisp.dhis.android.core.util.deleteIfExists import org.koin.core.annotation.Singleton @Singleton +@Suppress("TooManyFunctions") internal class MultiUserDatabaseManager( private val context: Context, private val databaseAdapter: DatabaseAdapter, @@ -76,26 +80,21 @@ internal class MultiUserDatabaseManager( } fun createNew(serverUrl: String, username: String, encrypt: Boolean) { - val configuration = databaseConfigurationSecureStore.get() - - configuration?.maxAccounts()?.let { maxAccounts -> - val exceedingAccounts = DatabaseConfigurationHelper - .getOldestAccounts(configuration.accounts(), maxAccounts - 1) - - val updatedConfiguration = - DatabaseConfigurationHelper.removeAccount(configuration, exceedingAccounts) - - databaseConfigurationSecureStore.set(updatedConfiguration) - exceedingAccounts.forEach { - FileResourceDirectoryHelper.deleteFileResourceDirectories(context, it) - databaseAdapterFactory.deleteDatabase(it) - } - } - - val userConfiguration = addNewAccountInternal(serverUrl, username, encrypt) + removeExceedingAccounts() + val userConfiguration = addOrUpdateAccountInternal(serverUrl, username, encrypt) databaseAdapterFactory.createOrOpenDatabase(databaseAdapter, userConfiguration) } + fun createNewPendingToImport(metadata: DatabaseExportMetadata): DatabaseAccount { + removeExceedingAccounts() + return addOrUpdateAccountInternal( + metadata.serverUrl, + metadata.username, + metadata.encrypted, + importStatus = DatabaseAccountImportStatus.PENDING_TO_IMPORT, + ) + } + fun changeEncryptionIfRequired(credentials: Credentials, encrypt: Boolean) { loadExistingChangingEncryptionIfRequired( credentials.serverUrl, @@ -126,6 +125,37 @@ internal class MultiUserDatabaseManager( } } + @Suppress("TooGenericExceptionCaught") + fun importAndLoadDb(account: DatabaseAccount, password: String) { + val protectedDbPath = context.getDatabasePath(account.importDB()!!.protectedDbName()) + val dbPath = context.getDatabasePath(account.databaseName()) + val tempDbPath = context.filesDir.resolve("temp.db").also { it.deleteIfExists() } + try { + CipherUtil.extractEncryptedZipFile(protectedDbPath, tempDbPath, password) + protectedDbPath.deleteIfExists() + + if (account.encrypted()) { + databaseExport.encryptAndCopyTo(account, sourceFile = tempDbPath, targetFile = dbPath) + } else { + tempDbPath.copyTo(dbPath) + } + databaseAdapterFactory.createOrOpenDatabase(databaseAdapter, account) + val importedAccount = account.toBuilder() + .importDB( + account.importDB()!!.toBuilder() + .status(DatabaseAccountImportStatus.IMPORTED) + .build(), + ) + .build() + addOrUpdatedAccountInternal(importedAccount) + } catch (e: Exception) { + dbPath.deleteIfExists() + throw e + } finally { + tempDbPath.deleteIfExists() + } + } + private fun loadExistingChangingEncryptionIfRequired( serverUrl: String, username: String, @@ -136,7 +166,7 @@ internal class MultiUserDatabaseManager( val encrypt = encryptionExtractor(existingAccount) changeEncryptionIfRequired(serverUrl, existingAccount, encrypt) if (encrypt != existingAccount.encrypted() || alsoOpenWhenEncryptionDoesntChange) { - val updatedAccount = addNewAccountInternal( + val updatedAccount = addOrUpdateAccountInternal( serverUrl, username, encrypt, @@ -165,26 +195,54 @@ internal class MultiUserDatabaseManager( } } - private fun getAccount(serverUrl: String, username: String): DatabaseAccount? { + fun getAccount(serverUrl: String, username: String): DatabaseAccount? { val configuration = databaseConfigurationSecureStore.get() return DatabaseConfigurationHelper.getAccount(configuration, serverUrl, username) } - private fun addNewAccountInternal( + private fun addOrUpdateAccountInternal( serverUrl: String, username: String, encrypt: Boolean, + importStatus: DatabaseAccountImportStatus? = null, ): DatabaseAccount { - val updatedAccount = configurationHelper.addAccount( + val updatedAccount = configurationHelper.addOrUpdateAccount( databaseConfigurationSecureStore.get(), serverUrl, username, encrypt, + importStatus, ) databaseConfigurationSecureStore.set(updatedAccount) return DatabaseConfigurationHelper.getLoggedAccount(updatedAccount, username, serverUrl) } + private fun addOrUpdatedAccountInternal(account: DatabaseAccount) { + val updatedAccount = configurationHelper.addOrUpdateAccount( + databaseConfigurationSecureStore.get(), + account, + ) + databaseConfigurationSecureStore.set(updatedAccount) + } + + private fun removeExceedingAccounts() { + val configuration = databaseConfigurationSecureStore.get() + + configuration?.maxAccounts()?.let { maxAccounts -> + val exceedingAccounts = DatabaseConfigurationHelper + .getOldestAccounts(configuration.accounts(), maxAccounts - 1) + + val updatedConfiguration = + DatabaseConfigurationHelper.removeAccount(configuration, exceedingAccounts) + + databaseConfigurationSecureStore.set(updatedConfiguration) + exceedingAccounts.forEach { + FileResourceDirectoryHelper.deleteFileResourceDirectories(context, it) + databaseAdapterFactory.deleteDatabase(it) + } + } + } + companion object { const val DefaultMaxAccounts = 1 internal val DefaultTestMaxAccounts = null diff --git a/core/src/main/java/org/hisp/dhis/android/core/configuration/internal/MultiUserDatabaseManagerForD2Manager.kt b/core/src/main/java/org/hisp/dhis/android/core/configuration/internal/MultiUserDatabaseManagerForD2Manager.kt index 3a4887caa5..907ab65f4b 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/configuration/internal/MultiUserDatabaseManagerForD2Manager.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/configuration/internal/MultiUserDatabaseManagerForD2Manager.kt @@ -63,6 +63,7 @@ internal class MultiUserDatabaseManagerForD2Manager( .serverUrl(serverUrl) .databaseCreationDate(DateUtils.DATE_FORMAT.format(Date())) .build() + ServerURLWrapper.setServerUrl(serverUrl) databaseAdapterFactory.createOrOpenDatabase(databaseAdapter, config) } diff --git a/core/src/main/java/org/hisp/dhis/android/core/dataelement/DataElement.java b/core/src/main/java/org/hisp/dhis/android/core/dataelement/DataElement.java index 2c0bf39df6..630e96ccb3 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/dataelement/DataElement.java +++ b/core/src/main/java/org/hisp/dhis/android/core/dataelement/DataElement.java @@ -120,7 +120,7 @@ public static DataElement create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseNameableObject.Builder + public abstract static class Builder extends BaseNameableObject.Builder implements ObjectWithStyle.Builder { public abstract DataElement.Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/dataelement/DataElementOperand.java b/core/src/main/java/org/hisp/dhis/android/core/dataelement/DataElementOperand.java index d7e339308c..20027e33cb 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/dataelement/DataElementOperand.java +++ b/core/src/main/java/org/hisp/dhis/android/core/dataelement/DataElementOperand.java @@ -85,7 +85,7 @@ public static Builder builder() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { @JsonProperty(UID) public abstract Builder uid(String uid); diff --git a/core/src/main/java/org/hisp/dhis/android/core/dataelement/internal/DataElementFields.java b/core/src/main/java/org/hisp/dhis/android/core/dataelement/internal/DataElementFields.java deleted file mode 100644 index 3cf682f402..0000000000 --- a/core/src/main/java/org/hisp/dhis/android/core/dataelement/internal/DataElementFields.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2004-2023, University of Oslo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * Neither the name of the HISP project nor the names of its contributors may - * be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.hisp.dhis.android.core.dataelement.internal; - -import org.hisp.dhis.android.core.arch.api.fields.internal.Field; -import org.hisp.dhis.android.core.arch.api.fields.internal.Fields; -import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper; -import org.hisp.dhis.android.core.attribute.AttributeValue; -import org.hisp.dhis.android.core.attribute.internal.AttributeValuesFields; -import org.hisp.dhis.android.core.common.Access; -import org.hisp.dhis.android.core.common.ObjectStyle; -import org.hisp.dhis.android.core.common.ObjectWithUid; -import org.hisp.dhis.android.core.common.ValueType; -import org.hisp.dhis.android.core.common.internal.AccessFields; -import org.hisp.dhis.android.core.common.objectstyle.internal.ObjectStyleFields; -import org.hisp.dhis.android.core.dataelement.DataElement; -import org.hisp.dhis.android.core.dataelement.DataElementTableInfo; -import org.hisp.dhis.android.core.legendset.LegendSet; -import org.hisp.dhis.android.core.legendset.internal.LegendSetFields; - -public final class DataElementFields { - - private final static String STYLE = "style"; - private final static String ACCESS = "access"; - public static final String LEGEND_SETS = "legendSets"; - public static final String ATTRIBUTE_VALUES = "attributeValues"; - - private static final FieldsHelper fh = new FieldsHelper<>(); - - public static final Field uid = fh.uid(); - - static final Field lastUpdated = fh.lastUpdated(); - - public static final Fields allFields = Fields.builder() - .fields(fh.getNameableFields()) - .fields( - fh.field(DataElementTableInfo.Columns.VALUE_TYPE), - fh.field(DataElementTableInfo.Columns.ZERO_IS_SIGNIFICANT), - fh.field(DataElementTableInfo.Columns.AGGREGATION_TYPE), - fh.field(DataElementTableInfo.Columns.FORM_NAME), - fh.field(DataElementTableInfo.Columns.DOMAIN_TYPE), - fh.field(DataElementTableInfo.Columns.DISPLAY_FORM_NAME), - fh.nestedField(DataElementTableInfo.Columns.OPTION_SET) - .with(ObjectWithUid.uid), - fh.nestedField(DataElementTableInfo.Columns.CATEGORY_COMBO) - .with(ObjectWithUid.uid), - fh.field(DataElementTableInfo.Columns.FIELD_MASK), - fh.nestedField(STYLE) - .with(ObjectStyleFields.allFields), - fh.nestedField(ACCESS) - .with(AccessFields.read), - fh.nestedField(LEGEND_SETS).with(LegendSetFields.uid), - fh.nestedField(ATTRIBUTE_VALUES).with(AttributeValuesFields.allFields) - ).build(); - - private DataElementFields() { - } -} \ No newline at end of file diff --git a/core/src/main/java/org/hisp/dhis/android/core/dataelement/internal/DataElementFields.kt b/core/src/main/java/org/hisp/dhis/android/core/dataelement/internal/DataElementFields.kt new file mode 100644 index 0000000000..9bf61f7e1b --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/dataelement/internal/DataElementFields.kt @@ -0,0 +1,77 @@ +/* + * 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.dataelement.internal + +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper +import org.hisp.dhis.android.core.attribute.AttributeValue +import org.hisp.dhis.android.core.attribute.internal.AttributeValuesFields +import org.hisp.dhis.android.core.common.Access +import org.hisp.dhis.android.core.common.ObjectStyle +import org.hisp.dhis.android.core.common.ObjectWithUid +import org.hisp.dhis.android.core.common.ValueType +import org.hisp.dhis.android.core.common.internal.AccessFields +import org.hisp.dhis.android.core.common.objectstyle.internal.ObjectStyleFields +import org.hisp.dhis.android.core.dataelement.DataElement +import org.hisp.dhis.android.core.dataelement.DataElementTableInfo +import org.hisp.dhis.android.core.legendset.LegendSet +import org.hisp.dhis.android.core.legendset.internal.LegendSetFields + +internal object DataElementFields { + private const val STYLE = "style" + private const val ACCESS = "access" + const val LEGEND_SETS = "legendSets" + const val ATTRIBUTE_VALUES = "attributeValues" + + private val fh = FieldsHelper() + val uid = fh.uid() + + val lastUpdated = fh.lastUpdated() + + val allFields: Fields = Fields.builder() + .fields(fh.getNameableFields()) + .fields( + fh.field(DataElementTableInfo.Columns.VALUE_TYPE), + fh.field(DataElementTableInfo.Columns.ZERO_IS_SIGNIFICANT), + fh.field(DataElementTableInfo.Columns.AGGREGATION_TYPE), + fh.field(DataElementTableInfo.Columns.FORM_NAME), + fh.field(DataElementTableInfo.Columns.DOMAIN_TYPE), + fh.field(DataElementTableInfo.Columns.DISPLAY_FORM_NAME), + fh.nestedField(DataElementTableInfo.Columns.OPTION_SET) + .with(ObjectWithUid.uid), + fh.nestedField(DataElementTableInfo.Columns.CATEGORY_COMBO) + .with(ObjectWithUid.uid), + fh.field(DataElementTableInfo.Columns.FIELD_MASK), + fh.nestedField(STYLE) + .with(ObjectStyleFields.allFields), + fh.nestedField(ACCESS) + .with(AccessFields.read), + fh.nestedField(LEGEND_SETS).with(LegendSetFields.uid), + fh.nestedField(ATTRIBUTE_VALUES).with(AttributeValuesFields.allFields), + ).build() +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/dataset/DataSet.java b/core/src/main/java/org/hisp/dhis/android/core/dataset/DataSet.java index 6ebdf297a6..a5e948f755 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/dataset/DataSet.java +++ b/core/src/main/java/org/hisp/dhis/android/core/dataset/DataSet.java @@ -173,7 +173,7 @@ public static DataSet create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseNameableObject.Builder + public abstract static class Builder extends BaseNameableObject.Builder implements ObjectWithStyle.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/dataset/DataSetCompulsoryDataElementOperandLink.java b/core/src/main/java/org/hisp/dhis/android/core/dataset/DataSetCompulsoryDataElementOperandLink.java index 4c84a24254..4b379ca6eb 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/dataset/DataSetCompulsoryDataElementOperandLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/dataset/DataSetCompulsoryDataElementOperandLink.java @@ -57,7 +57,7 @@ public static DataSetCompulsoryDataElementOperandLink create(Cursor cursor) { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/dataset/DataSetElement.java b/core/src/main/java/org/hisp/dhis/android/core/dataset/DataSetElement.java index 8e8d69adce..d27d5c1ca4 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/dataset/DataSetElement.java +++ b/core/src/main/java/org/hisp/dhis/android/core/dataset/DataSetElement.java @@ -75,7 +75,7 @@ public static DataSetElement create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); public abstract Builder dataSet(ObjectWithUid dataSet); diff --git a/core/src/main/java/org/hisp/dhis/android/core/dataset/DataSetOrganisationUnitLink.java b/core/src/main/java/org/hisp/dhis/android/core/dataset/DataSetOrganisationUnitLink.java index 0a84a7d782..19c4f713ad 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/dataset/DataSetOrganisationUnitLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/dataset/DataSetOrganisationUnitLink.java @@ -56,7 +56,7 @@ public static DataSetOrganisationUnitLink create(Cursor cursor) { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/dataset/Section.java b/core/src/main/java/org/hisp/dhis/android/core/dataset/Section.java index 0fd362cb5e..8199c9dd93 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/dataset/Section.java +++ b/core/src/main/java/org/hisp/dhis/android/core/dataset/Section.java @@ -103,7 +103,7 @@ public static Section create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseIdentifiableObject.Builder { + public abstract static class Builder extends BaseIdentifiableObject.Builder { public abstract Builder id(Long id); public abstract Builder description(String description); diff --git a/core/src/main/java/org/hisp/dhis/android/core/dataset/SectionDataElementLink.java b/core/src/main/java/org/hisp/dhis/android/core/dataset/SectionDataElementLink.java index ebc863dcc1..4dc5ab1060 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/dataset/SectionDataElementLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/dataset/SectionDataElementLink.java @@ -59,7 +59,7 @@ public static SectionDataElementLink create(Cursor cursor) { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/dataset/SectionGreyedFieldsLink.java b/core/src/main/java/org/hisp/dhis/android/core/dataset/SectionGreyedFieldsLink.java index c6d63c2b84..a5ba88774f 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/dataset/SectionGreyedFieldsLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/dataset/SectionGreyedFieldsLink.java @@ -60,7 +60,7 @@ public static SectionGreyedFieldsLink create(Cursor cursor) { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/dataset/internal/DataSetFields.java b/core/src/main/java/org/hisp/dhis/android/core/dataset/internal/DataSetFields.java deleted file mode 100644 index 584b4f9f61..0000000000 --- a/core/src/main/java/org/hisp/dhis/android/core/dataset/internal/DataSetFields.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2004-2023, University of Oslo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * Neither the name of the HISP project nor the names of its contributors may - * be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.hisp.dhis.android.core.dataset.internal; - -import org.hisp.dhis.android.core.arch.api.fields.internal.Field; -import org.hisp.dhis.android.core.arch.api.fields.internal.Fields; -import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper; -import org.hisp.dhis.android.core.common.Access; -import org.hisp.dhis.android.core.common.ObjectStyle; -import org.hisp.dhis.android.core.common.internal.AccessFields; -import org.hisp.dhis.android.core.common.internal.DataAccessFields; -import org.hisp.dhis.android.core.common.objectstyle.internal.ObjectStyleFields; -import org.hisp.dhis.android.core.dataelement.DataElementOperand; -import org.hisp.dhis.android.core.dataelement.internal.DataElementOperandFields; -import org.hisp.dhis.android.core.dataset.DataInputPeriod; -import org.hisp.dhis.android.core.dataset.DataSet; -import org.hisp.dhis.android.core.dataset.DataSetElement; -import org.hisp.dhis.android.core.dataset.Section; -import org.hisp.dhis.android.core.period.PeriodType; - -import static org.hisp.dhis.android.core.dataset.DataSetTableInfo.Columns; - -public final class DataSetFields { - - public static final String DATA_SET_ELEMENTS = "dataSetElements"; - public static final String INDICATORS = "indicators"; - public static final String SECTIONS = "sections"; - public static final String COMPULSORY_DATA_ELEMENT_OPERANDS = "compulsoryDataElementOperands"; - public static final String DATA_INPUT_PERIODS = "dataInputPeriods"; - private static final String ACCESS = "access"; - private static final String STYLE = "style"; - - private static FieldsHelper fh = new FieldsHelper<>(); - - public static final Field uid = fh.uid(); - - static final Fields allFields = Fields.builder() - .fields(fh.getNameableFields()) - .fields( - fh.field(Columns.PERIOD_TYPE), - fh.nestedFieldWithUid(Columns.CATEGORY_COMBO), - fh.field(Columns.MOBILE), - fh.field(Columns.VERSION), - fh.field(Columns.EXPIRY_DAYS), - fh.field(Columns.TIMELY_DAYS), - fh.field(Columns.NOTIFY_COMPLETING_USER), - fh.field(Columns.OPEN_FUTURE_PERIODS), - fh.field(Columns.FIELD_COMBINATION_REQUIRED), - fh.field(Columns.VALID_COMPLETE_ONLY), - fh.field(Columns.NO_VALUE_REQUIRES_COMMENT), - fh.field(Columns.SKIP_OFFLINE), - fh.field(Columns.DATA_ELEMENT_DECORATION), - fh.field(Columns.RENDER_AS_TABS), - fh.field(Columns.RENDER_HORIZONTALLY), - fh.nestedFieldWithUid(Columns.WORKFLOW), - fh.nestedField(DATA_SET_ELEMENTS).with(DataSetElementFields.allFields), - fh.nestedFieldWithUid(INDICATORS), - fh.

nestedField(SECTIONS).with(SectionFields.allFields), - - fh.nestedField(COMPULSORY_DATA_ELEMENT_OPERANDS) - .with(DataElementOperandFields.allFields), - fh.nestedField(DATA_INPUT_PERIODS).with(DataInputPeriodFields.allFields), - fh.nestedField(ACCESS).with(AccessFields.data.with(DataAccessFields.write)), - fh.nestedField(STYLE).with(ObjectStyleFields.allFields) - - ).build(); - - private DataSetFields() {} - -} \ No newline at end of file diff --git a/core/src/main/java/org/hisp/dhis/android/core/dataset/internal/DataSetFields.kt b/core/src/main/java/org/hisp/dhis/android/core/dataset/internal/DataSetFields.kt new file mode 100644 index 0000000000..a232191c4d --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/dataset/internal/DataSetFields.kt @@ -0,0 +1,87 @@ +/* + * 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.dataset.internal + +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper +import org.hisp.dhis.android.core.common.Access +import org.hisp.dhis.android.core.common.ObjectStyle +import org.hisp.dhis.android.core.common.internal.AccessFields +import org.hisp.dhis.android.core.common.internal.DataAccessFields +import org.hisp.dhis.android.core.common.objectstyle.internal.ObjectStyleFields +import org.hisp.dhis.android.core.dataelement.DataElementOperand +import org.hisp.dhis.android.core.dataelement.internal.DataElementOperandFields +import org.hisp.dhis.android.core.dataset.DataInputPeriod +import org.hisp.dhis.android.core.dataset.DataSet +import org.hisp.dhis.android.core.dataset.DataSetElement +import org.hisp.dhis.android.core.dataset.DataSetTableInfo +import org.hisp.dhis.android.core.dataset.Section +import org.hisp.dhis.android.core.period.PeriodType + +internal object DataSetFields { + const val DATA_SET_ELEMENTS = "dataSetElements" + const val INDICATORS = "indicators" + const val SECTIONS = "sections" + const val COMPULSORY_DATA_ELEMENT_OPERANDS = "compulsoryDataElementOperands" + const val DATA_INPUT_PERIODS = "dataInputPeriods" + private const val ACCESS = "access" + private const val STYLE = "style" + + private val fh = FieldsHelper() + + val uid = fh.uid() + + val allFields: Fields = Fields.builder() + .fields(fh.getNameableFields()) + .fields( + fh.field(DataSetTableInfo.Columns.PERIOD_TYPE), + fh.nestedFieldWithUid(DataSetTableInfo.Columns.CATEGORY_COMBO), + fh.field(DataSetTableInfo.Columns.MOBILE), + fh.field(DataSetTableInfo.Columns.VERSION), + fh.field(DataSetTableInfo.Columns.EXPIRY_DAYS), + fh.field(DataSetTableInfo.Columns.TIMELY_DAYS), + fh.field(DataSetTableInfo.Columns.NOTIFY_COMPLETING_USER), + fh.field(DataSetTableInfo.Columns.OPEN_FUTURE_PERIODS), + fh.field(DataSetTableInfo.Columns.FIELD_COMBINATION_REQUIRED), + fh.field(DataSetTableInfo.Columns.VALID_COMPLETE_ONLY), + fh.field(DataSetTableInfo.Columns.NO_VALUE_REQUIRES_COMMENT), + fh.field(DataSetTableInfo.Columns.SKIP_OFFLINE), + fh.field(DataSetTableInfo.Columns.DATA_ELEMENT_DECORATION), + fh.field(DataSetTableInfo.Columns.RENDER_AS_TABS), + fh.field(DataSetTableInfo.Columns.RENDER_HORIZONTALLY), + fh.nestedFieldWithUid(DataSetTableInfo.Columns.WORKFLOW), + fh.nestedField(DATA_SET_ELEMENTS).with(DataSetElementFields.allFields), + fh.nestedFieldWithUid(INDICATORS), + fh.nestedField
(SECTIONS).with(SectionFields.allFields), + fh.nestedField(COMPULSORY_DATA_ELEMENT_OPERANDS) + .with(DataElementOperandFields.allFields), + fh.nestedField(DATA_INPUT_PERIODS).with(DataInputPeriodFields.allFields), + fh.nestedField(ACCESS).with(AccessFields.data.with(DataAccessFields.write)), + fh.nestedField(STYLE).with(ObjectStyleFields.allFields), + ).build() +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/dataset/internal/SectionIndicatorLink.java b/core/src/main/java/org/hisp/dhis/android/core/dataset/internal/SectionIndicatorLink.java index 88085c6a04..063f7d960a 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/dataset/internal/SectionIndicatorLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/dataset/internal/SectionIndicatorLink.java @@ -57,7 +57,7 @@ public static SectionIndicatorLink create(Cursor cursor) { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/datastore/KeyValuePair.java b/core/src/main/java/org/hisp/dhis/android/core/datastore/KeyValuePair.java index 57e38b1f47..844ee743cd 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/datastore/KeyValuePair.java +++ b/core/src/main/java/org/hisp/dhis/android/core/datastore/KeyValuePair.java @@ -56,7 +56,7 @@ public static Builder builder() { } @AutoValue.Builder - public static abstract class Builder { + public abstract static class Builder { public abstract Builder id(Long id); public abstract Builder key(String key); diff --git a/core/src/main/java/org/hisp/dhis/android/core/datavalue/DataValueConflict.java b/core/src/main/java/org/hisp/dhis/android/core/datavalue/DataValueConflict.java index b8a6aedaae..55a7eb59ec 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/datavalue/DataValueConflict.java +++ b/core/src/main/java/org/hisp/dhis/android/core/datavalue/DataValueConflict.java @@ -93,7 +93,7 @@ public static Builder builder() { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder conflict(String conflict); public abstract Builder value(String value); diff --git a/core/src/main/java/org/hisp/dhis/android/core/datavalue/internal/DataValueFileResourcePostCall.kt b/core/src/main/java/org/hisp/dhis/android/core/datavalue/internal/DataValueFileResourcePostCall.kt index a642b2e675..fbea355287 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/datavalue/internal/DataValueFileResourcePostCall.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/datavalue/internal/DataValueFileResourcePostCall.kt @@ -28,7 +28,7 @@ package org.hisp.dhis.android.core.datavalue.internal import org.hisp.dhis.android.core.datavalue.DataValue -import org.hisp.dhis.android.core.fileresource.FileResourceDomainType +import org.hisp.dhis.android.core.fileresource.FileResourceDataDomainType import org.hisp.dhis.android.core.fileresource.internal.FileResourceHelper import org.hisp.dhis.android.core.fileresource.internal.FileResourcePostCall import org.hisp.dhis.android.core.fileresource.internal.FileResourceValue @@ -62,7 +62,7 @@ internal class DataValueFileResourcePostCall( } fun updateFileResourceStates(fileResources: List) { - fileResourceHelper.updateFileResourceStates(fileResources, FileResourceDomainType.AGGREGATED) + fileResourceHelper.updateFileResourceStates(fileResources, FileResourceDataDomainType.AGGREGATED) } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/domain/aggregated/data/internal/AggregatedDataSync.java b/core/src/main/java/org/hisp/dhis/android/core/domain/aggregated/data/internal/AggregatedDataSync.java index 4439fec072..89371436dd 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/domain/aggregated/data/internal/AggregatedDataSync.java +++ b/core/src/main/java/org/hisp/dhis/android/core/domain/aggregated/data/internal/AggregatedDataSync.java @@ -84,7 +84,7 @@ static Builder builder() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - static abstract class Builder extends BaseObject.Builder { + abstract static class Builder extends BaseObject.Builder { abstract Builder dataSet(String dataSet); diff --git a/core/src/main/java/org/hisp/dhis/android/core/domain/metadata/MetadataCall.kt b/core/src/main/java/org/hisp/dhis/android/core/domain/metadata/MetadataCall.kt index ebb68b02e7..a8f5a05c93 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/domain/metadata/MetadataCall.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/domain/metadata/MetadataCall.kt @@ -48,6 +48,8 @@ import org.hisp.dhis.android.core.dataset.DataSet import org.hisp.dhis.android.core.dataset.internal.DataSetModuleDownloader import org.hisp.dhis.android.core.expressiondimensionitem.ExpressionDimensionItem import org.hisp.dhis.android.core.expressiondimensionitem.internal.ExpressionDimensionItemModuleDownloader +import org.hisp.dhis.android.core.icon.CustomIcon +import org.hisp.dhis.android.core.icon.internal.CustomIconModuleDownloader import org.hisp.dhis.android.core.indicator.Indicator import org.hisp.dhis.android.core.indicator.internal.IndicatorModuleDownloader import org.hisp.dhis.android.core.legendset.LegendSet @@ -69,7 +71,9 @@ import org.hisp.dhis.android.core.usecase.UseCaseModuleDownloader import org.hisp.dhis.android.core.usecase.stock.StockUseCase import org.hisp.dhis.android.core.user.User import org.hisp.dhis.android.core.user.internal.UserModuleDownloader +import org.hisp.dhis.android.core.visualization.TrackerVisualization import org.hisp.dhis.android.core.visualization.Visualization +import org.hisp.dhis.android.core.visualization.internal.TrackerVisualizationModuleDownloader import org.hisp.dhis.android.core.visualization.internal.VisualizationModuleDownloader import org.koin.core.annotation.Singleton @@ -86,6 +90,7 @@ internal class MetadataCall( private val organisationUnitModuleDownloader: OrganisationUnitModuleDownloader, private val dataSetDownloader: DataSetModuleDownloader, private val visualizationDownloader: VisualizationModuleDownloader, + private val trackerVisualizationDownloader: TrackerVisualizationModuleDownloader, private val constantModuleDownloader: ConstantModuleDownloader, private val indicatorModuleDownloader: IndicatorModuleDownloader, private val programIndicatorModuleDownloader: ProgramIndicatorModuleDownloader, @@ -97,10 +102,11 @@ internal class MetadataCall( private val legendSetModuleDownloader: LegendSetModuleDownloader, private val attributeModuleDownloader: AttributeModuleDownloader, private val expressionDimensionItemModuleDownloader: ExpressionDimensionItemModuleDownloader, + private val customIconDownloader: CustomIconModuleDownloader, ) { companion object { - const val CALLS_COUNT = 15 + const val CALLS_COUNT = 17 } @Suppress("TooGenericExceptionCaught") @@ -159,6 +165,9 @@ internal class MetadataCall( visualizationDownloader.downloadMetadata() emit(progressManager.increaseProgress(Visualization::class.java, false)) + trackerVisualizationDownloader.downloadMetadata() + emit(progressManager.increaseProgress(TrackerVisualization::class.java, false)) + programIndicatorModuleDownloader.downloadMetadata() emit(progressManager.increaseProgress(ProgramIndicator::class.java, false)) @@ -172,7 +181,10 @@ internal class MetadataCall( emit(progressManager.increaseProgress(Attribute::class.java, false)) expressionDimensionItemModuleDownloader.downloadMetadata() - emit(progressManager.increaseProgress(ExpressionDimensionItem::class.java, true)) + emit(progressManager.increaseProgress(ExpressionDimensionItem::class.java, false)) + + customIconDownloader.downloadMetadata() + emit(progressManager.increaseProgress(CustomIcon::class.java, true)) } private suspend fun changeEncryptionIfRequiredCoroutines() { diff --git a/core/src/main/java/org/hisp/dhis/android/core/enrollment/internal/EnrollmentEndpointCallFactory.kt b/core/src/main/java/org/hisp/dhis/android/core/enrollment/internal/EnrollmentEndpointCallFactory.kt index 2cf0628095..77a9095673 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/enrollment/internal/EnrollmentEndpointCallFactory.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/enrollment/internal/EnrollmentEndpointCallFactory.kt @@ -28,7 +28,8 @@ package org.hisp.dhis.android.core.enrollment.internal import org.hisp.dhis.android.core.enrollment.Enrollment +import org.hisp.dhis.android.core.relationship.internal.RelationshipItemRelative internal interface EnrollmentEndpointCallFactory { - suspend fun getRelationshipEntityCall(uid: String): Enrollment + suspend fun getRelationshipEntityCall(item: RelationshipItemRelative): Enrollment } diff --git a/core/src/main/java/org/hisp/dhis/android/core/enrollment/internal/NewEnrollmentEndpointCallFactory.kt b/core/src/main/java/org/hisp/dhis/android/core/enrollment/internal/NewEnrollmentEndpointCallFactory.kt index 03dbf2f46b..75eb0d6e66 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/enrollment/internal/NewEnrollmentEndpointCallFactory.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/enrollment/internal/NewEnrollmentEndpointCallFactory.kt @@ -29,6 +29,7 @@ package org.hisp.dhis.android.core.enrollment.internal import org.hisp.dhis.android.core.enrollment.Enrollment import org.hisp.dhis.android.core.enrollment.NewTrackerImporterEnrollmentTransformer +import org.hisp.dhis.android.core.relationship.internal.RelationshipItemRelative import org.hisp.dhis.android.core.tracker.exporter.TrackerExporterService import org.koin.core.annotation.Singleton @@ -36,10 +37,10 @@ import org.koin.core.annotation.Singleton internal class NewEnrollmentEndpointCallFactory( private val service: TrackerExporterService, ) : EnrollmentEndpointCallFactory { - override suspend fun getRelationshipEntityCall(uid: String): Enrollment { + override suspend fun getRelationshipEntityCall(item: RelationshipItemRelative): Enrollment { return service.getEnrollmentSingle( - uid, - NewEnrollmentFields.asRelationshipFields, + enrollmentUid = item.itemUid, + fields = NewEnrollmentFields.asRelationshipFields, ).let { NewTrackerImporterEnrollmentTransformer.deTransform(it) } } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/enrollment/internal/OldEnrollmentEndpointCallFactory.kt b/core/src/main/java/org/hisp/dhis/android/core/enrollment/internal/OldEnrollmentEndpointCallFactory.kt index 8383e5421c..130008a7aa 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/enrollment/internal/OldEnrollmentEndpointCallFactory.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/enrollment/internal/OldEnrollmentEndpointCallFactory.kt @@ -28,16 +28,17 @@ package org.hisp.dhis.android.core.enrollment.internal import org.hisp.dhis.android.core.enrollment.Enrollment +import org.hisp.dhis.android.core.relationship.internal.RelationshipItemRelative import org.koin.core.annotation.Singleton @Singleton internal class OldEnrollmentEndpointCallFactory( private val service: EnrollmentService, ) : EnrollmentEndpointCallFactory { - override suspend fun getRelationshipEntityCall(uid: String): Enrollment { + override suspend fun getRelationshipEntityCall(item: RelationshipItemRelative): Enrollment { return service.getEnrollmentSingle( - uid, - EnrollmentFields.asRelationshipFields, + enrollmentUid = item.itemUid, + fields = EnrollmentFields.asRelationshipFields, ) } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/event/EventDataFilter.java b/core/src/main/java/org/hisp/dhis/android/core/event/EventDataFilter.java index 8920c6a7a6..9f5076703a 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/event/EventDataFilter.java +++ b/core/src/main/java/org/hisp/dhis/android/core/event/EventDataFilter.java @@ -70,7 +70,7 @@ public static EventDataFilter create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends FilterOperators.Builder { + public abstract static class Builder extends FilterOperators.Builder { public abstract Builder id(Long id); public abstract Builder eventFilter(String eventFilter); diff --git a/core/src/main/java/org/hisp/dhis/android/core/event/EventDownloader.kt b/core/src/main/java/org/hisp/dhis/android/core/event/EventDownloader.kt index 73a712f078..271220a49b 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/event/EventDownloader.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/event/EventDownloader.kt @@ -71,18 +71,18 @@ class EventDownloader internal constructor( } fun byProgramUid(programUid: String): EventDownloader { - return cf.baseString(QueryParams.PROGRAM).eq(programUid)!! + return cf.baseString(QueryParams.PROGRAM).eq(programUid) } fun limitByOrgunit(limitByOrgunit: Boolean): EventDownloader { - return cf.bool(QueryParams.LIMIT_BY_ORGUNIT).eq(limitByOrgunit)!! + return cf.bool(QueryParams.LIMIT_BY_ORGUNIT).eq(limitByOrgunit) } fun limitByProgram(limitByProgram: Boolean): EventDownloader { - return cf.bool(QueryParams.LIMIT_BY_PROGRAM).eq(limitByProgram)!! + return cf.bool(QueryParams.LIMIT_BY_PROGRAM).eq(limitByProgram) } fun limit(limit: Int): EventDownloader { - return cf.integer(QueryParams.LIMIT).eq(limit)!! + return cf.integer(QueryParams.LIMIT).eq(limit) } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/event/EventFilter.java b/core/src/main/java/org/hisp/dhis/android/core/event/EventFilter.java index 75f6b96b20..81042a1dc9 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/event/EventFilter.java +++ b/core/src/main/java/org/hisp/dhis/android/core/event/EventFilter.java @@ -75,7 +75,7 @@ public static EventFilter create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseIdentifiableObject.Builder { + public abstract static class Builder extends BaseIdentifiableObject.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/event/EventQueryCriteria.java b/core/src/main/java/org/hisp/dhis/android/core/event/EventQueryCriteria.java index b863d6cd4a..7d3a1eba2d 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/event/EventQueryCriteria.java +++ b/core/src/main/java/org/hisp/dhis/android/core/event/EventQueryCriteria.java @@ -83,7 +83,7 @@ public static EventQueryCriteria create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends FilterQueryCriteria.Builder { + public abstract static class Builder extends FilterQueryCriteria.Builder { public abstract Builder id(Long id); public abstract Builder dataFilters(List dataFilters); diff --git a/core/src/main/java/org/hisp/dhis/android/core/event/internal/EventDateUtils.kt b/core/src/main/java/org/hisp/dhis/android/core/event/internal/EventDateUtils.kt index dcaa926a3f..a1976d7733 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/event/internal/EventDateUtils.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/event/internal/EventDateUtils.kt @@ -81,17 +81,20 @@ class EventDateUtils( false } - val expiredBecauseOfPeriod = programPeriodType?.let { periodType -> - var nextPeriod = periodHelper - .blockingGetPeriodForPeriodTypeAndDate(periodType, event.eventDate()!!, 1).startDate()!! - val currentDate: Date = getCalendar().time - if (expiryDays > 0) { - val calendar: Calendar = getCalendar() - calendar.time = nextPeriod - calendar.add(Calendar.DAY_OF_YEAR, expiryDays) - nextPeriod = calendar.time + val expiredBecauseOfPeriod = (event.eventDate() ?: event.dueDate())?.let { eventDateOrDueDate -> + programPeriodType?.let { periodType -> + + var nextPeriod = periodHelper + .blockingGetPeriodForPeriodTypeAndDate(periodType, eventDateOrDueDate, 1).startDate()!! + val currentDate: Date = getCalendar().time + if (expiryDays > 0) { + val calendar: Calendar = getCalendar() + calendar.time = nextPeriod + calendar.add(Calendar.DAY_OF_YEAR, expiryDays) + nextPeriod = calendar.time + } + nextPeriod <= currentDate } - nextPeriod <= currentDate } ?: false return expiredBecauseOfCompletion || expiredBecauseOfPeriod diff --git a/core/src/main/java/org/hisp/dhis/android/core/event/internal/EventEndpointCallFactory.kt b/core/src/main/java/org/hisp/dhis/android/core/event/internal/EventEndpointCallFactory.kt index 46818056ca..babada4742 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/event/internal/EventEndpointCallFactory.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/event/internal/EventEndpointCallFactory.kt @@ -29,13 +29,14 @@ package org.hisp.dhis.android.core.event.internal import org.hisp.dhis.android.core.arch.api.payload.internal.Payload import org.hisp.dhis.android.core.event.Event +import org.hisp.dhis.android.core.relationship.internal.RelationshipItemRelative import org.hisp.dhis.android.core.tracker.exporter.TrackerAPIQuery internal abstract class EventEndpointCallFactory { abstract suspend fun getCollectionCall(eventQuery: TrackerAPIQuery): Payload - abstract suspend fun getRelationshipEntityCall(uid: String): Payload + abstract suspend fun getRelationshipEntityCall(item: RelationshipItemRelative): Payload protected fun getUidStr(query: TrackerAPIQuery): String? { return if (query.uids.isEmpty()) null else query.uids.joinToString(";") diff --git a/core/src/main/java/org/hisp/dhis/android/core/event/internal/EventService.kt b/core/src/main/java/org/hisp/dhis/android/core/event/internal/EventService.kt index ff661be3f6..1e57e8792f 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/event/internal/EventService.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/event/internal/EventService.kt @@ -58,8 +58,8 @@ internal interface EventService { @Query(ORDER) order: String? = null, @Query(ASSIGNED_USER_MODE) assignedUserMode: String? = null, @Query(PAGING) paging: Boolean, - @Query(PAGE) page: Int, - @Query(PAGE_SIZE) pageSize: Int, + @Query(PAGE) page: Int?, + @Query(PAGE_SIZE) pageSize: Int?, @Query(LAST_UPDATED_START_DATE) lastUpdatedStartDate: String? = null, @Query(LAST_UPDATED_END_DATE) lastUpdatedEndDate: String? = null, @Query(INCLUDE_DELETED) includeDeleted: Boolean, diff --git a/core/src/main/java/org/hisp/dhis/android/core/event/internal/EventSync.java b/core/src/main/java/org/hisp/dhis/android/core/event/internal/EventSync.java index 06283adc78..b84e436240 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/event/internal/EventSync.java +++ b/core/src/main/java/org/hisp/dhis/android/core/event/internal/EventSync.java @@ -55,7 +55,7 @@ static Builder builder() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - static abstract class Builder extends TrackerBaseSync.Builder { + abstract static class Builder extends TrackerBaseSync.Builder { abstract EventSync build(); } } \ No newline at end of file diff --git a/core/src/main/java/org/hisp/dhis/android/core/event/internal/NewEventEndpointCallFactory.kt b/core/src/main/java/org/hisp/dhis/android/core/event/internal/NewEventEndpointCallFactory.kt index 9f5a6f59cc..20093bef27 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/event/internal/NewEventEndpointCallFactory.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/event/internal/NewEventEndpointCallFactory.kt @@ -27,26 +27,29 @@ */ package org.hisp.dhis.android.core.event.internal -import org.hisp.dhis.android.core.arch.api.payload.internal.NTIPayload import org.hisp.dhis.android.core.arch.api.payload.internal.Payload +import org.hisp.dhis.android.core.arch.api.payload.internal.TrackerPayload import org.hisp.dhis.android.core.event.Event import org.hisp.dhis.android.core.event.NewTrackerImporterEvent import org.hisp.dhis.android.core.event.NewTrackerImporterEventTransformer import org.hisp.dhis.android.core.organisationunit.OrganisationUnitMode +import org.hisp.dhis.android.core.relationship.internal.RelationshipItemRelative import org.hisp.dhis.android.core.tracker.exporter.TrackerAPIQuery +import org.hisp.dhis.android.core.tracker.exporter.TrackerExporterParameterManager import org.hisp.dhis.android.core.tracker.exporter.TrackerExporterService import org.koin.core.annotation.Singleton @Singleton internal class NewEventEndpointCallFactory( private val service: TrackerExporterService, + private val parameterManager: TrackerExporterParameterManager, ) : EventEndpointCallFactory() { override suspend fun getCollectionCall(eventQuery: TrackerAPIQuery): Payload { return service.getEvents( fields = NewEventFields.allFields, orgUnit = eventQuery.orgUnit, - orgUnitMode = eventQuery.commonParams.ouMode.name, + orgUnitMode = parameterManager.getOrgunitModeParameter(eventQuery.commonParams.ouMode), program = eventQuery.commonParams.program, occurredAfter = getEventStartDate(eventQuery), paging = true, @@ -54,20 +57,20 @@ internal class NewEventEndpointCallFactory( pageSize = eventQuery.pageSize, updatedAfter = eventQuery.lastUpdatedStr, includeDeleted = true, - eventUid = getUidStr(eventQuery), + eventUid = parameterManager.getEventsParameter(eventQuery.uids), ).let { mapPayload(it) } } - override suspend fun getRelationshipEntityCall(uid: String): Payload { + override suspend fun getRelationshipEntityCall(item: RelationshipItemRelative): Payload { return service.getEventSingle( - eventUid = uid, + eventUid = parameterManager.getEventsParameter(listOf(item.itemUid)), fields = NewEventFields.asRelationshipFields, - orgUnitMode = OrganisationUnitMode.ACCESSIBLE.name, + orgUnitMode = parameterManager.getOrgunitModeParameter(OrganisationUnitMode.ACCESSIBLE), ).let { mapPayload(it) } } - private fun mapPayload(payload: NTIPayload): Payload { - val newItems = payload.instances.map { t -> NewTrackerImporterEventTransformer.deTransform(t) } + private fun mapPayload(payload: TrackerPayload): Payload { + val newItems = payload.items().map { t -> NewTrackerImporterEventTransformer.deTransform(t) } return Payload(newItems) } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/event/internal/OldEventEndpointCallFactory.kt b/core/src/main/java/org/hisp/dhis/android/core/event/internal/OldEventEndpointCallFactory.kt index 8d5bc99f39..116c82eefd 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/event/internal/OldEventEndpointCallFactory.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/event/internal/OldEventEndpointCallFactory.kt @@ -30,7 +30,9 @@ package org.hisp.dhis.android.core.event.internal import org.hisp.dhis.android.core.arch.api.payload.internal.Payload import org.hisp.dhis.android.core.event.Event import org.hisp.dhis.android.core.organisationunit.OrganisationUnitMode +import org.hisp.dhis.android.core.relationship.internal.RelationshipItemRelative import org.hisp.dhis.android.core.tracker.exporter.TrackerAPIQuery +import org.hisp.dhis.android.core.tracker.exporter.TrackerQueryHelper.getOrgunits import org.koin.core.annotation.Singleton @Singleton @@ -41,7 +43,7 @@ internal class OldEventEndpointCallFactory( override suspend fun getCollectionCall(eventQuery: TrackerAPIQuery): Payload { return service.getEvents( fields = EventFields.allFields, - orgUnit = eventQuery.orgUnit, + orgUnit = getOrgunits(eventQuery)?.firstOrNull(), orgUnitMode = eventQuery.commonParams.ouMode.name, program = eventQuery.commonParams.program, startDate = getEventStartDate(eventQuery), @@ -54,9 +56,9 @@ internal class OldEventEndpointCallFactory( ) } - override suspend fun getRelationshipEntityCall(uid: String): Payload { + override suspend fun getRelationshipEntityCall(item: RelationshipItemRelative): Payload { return service.getEventSingle( - eventUid = uid, + eventUid = item.itemUid, fields = EventFields.asRelationshipFields, orgUnitMode = OrganisationUnitMode.ACCESSIBLE.name, ) diff --git a/core/src/main/java/org/hisp/dhis/android/core/event/search/EventQueryCollectionRepository.kt b/core/src/main/java/org/hisp/dhis/android/core/event/search/EventQueryCollectionRepository.kt index 74ab69e752..923be7cad8 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/event/search/EventQueryCollectionRepository.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/event/search/EventQueryCollectionRepository.kt @@ -30,7 +30,10 @@ package org.hisp.dhis.android.core.event.search import androidx.lifecycle.LiveData import androidx.paging.DataSource import androidx.paging.PagedList +import androidx.paging.Pager +import androidx.paging.PagingData import io.reactivex.Single +import kotlinx.coroutines.flow.Flow import org.hisp.dhis.android.core.arch.repositories.collection.ReadOnlyWithUidCollectionRepository import org.hisp.dhis.android.core.arch.repositories.filters.internal.EqFilterConnector import org.hisp.dhis.android.core.arch.repositories.filters.internal.EventDataFilterConnector @@ -271,6 +274,14 @@ class EventQueryCollectionRepository internal constructor( return eventCollectionRepository.getPaged(pageSize) } + override fun getPagingData(pageSize: Int): Flow> { + return eventCollectionRepository.getPagingData(pageSize) + } + + fun getPager(pageSize: Int): Pager { + return eventCollectionRepository.getPager(pageSize) + } + val dataSource: DataSource get() = eventCollectionRepository.dataSource diff --git a/core/src/main/java/org/hisp/dhis/android/core/fileresource/FileResourceDomain.kt b/core/src/main/java/org/hisp/dhis/android/core/fileresource/FileResourceDomain.kt index 3ef3dae1c3..07a9e5f056 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/fileresource/FileResourceDomain.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/fileresource/FileResourceDomain.kt @@ -34,4 +34,6 @@ enum class FileResourceDomain { MESSAGE_ATTACHMENT, USER_AVATAR, ORG_UNIT, + ICON, + JOB_DATA, } diff --git a/core/src/main/java/org/hisp/dhis/android/core/fileresource/FileResourceDownloadConst.kt b/core/src/main/java/org/hisp/dhis/android/core/fileresource/FileResourceDownloadConst.kt index d1a024be63..f6b6bfbb75 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/fileresource/FileResourceDownloadConst.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/fileresource/FileResourceDownloadConst.kt @@ -39,7 +39,12 @@ enum class FileResourceElementType { TRACED_ENTITY_ATTRIBUTE, } -enum class FileResourceDomainType { +enum class FileResourceDataDomainType { AGGREGATED, TRACKER, } + +enum class FileResourceDomainType(internal val domainType: FileResourceDomain) { + DATA_VALUE(FileResourceDomain.DATA_VALUE), + ICON(FileResourceDomain.ICON), +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/fileresource/FileResourceDownloader.kt b/core/src/main/java/org/hisp/dhis/android/core/fileresource/FileResourceDownloader.kt index 7440d0432d..062ce75bbe 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/fileresource/FileResourceDownloader.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/fileresource/FileResourceDownloader.kt @@ -71,6 +71,10 @@ class FileResourceDownloader internal constructor( return connectorFactory.listConnector { list -> params.copy(elementTypes = list) } } + fun byDataDomainType(): ListFilterConnector { + return connectorFactory.listConnector { list -> params.copy(dataDomainTypes = list) } + } + fun byDomainType(): ListFilterConnector { return connectorFactory.listConnector { list -> params.copy(domainTypes = list) } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/fileresource/FileResourceRoutine.kt b/core/src/main/java/org/hisp/dhis/android/core/fileresource/FileResourceRoutine.kt index 3de9eede42..1e4f4c780f 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/fileresource/FileResourceRoutine.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/fileresource/FileResourceRoutine.kt @@ -35,6 +35,7 @@ import org.hisp.dhis.android.core.dataelement.DataElementCollectionRepository import org.hisp.dhis.android.core.datavalue.DataValue import org.hisp.dhis.android.core.datavalue.DataValueCollectionRepository import org.hisp.dhis.android.core.fileresource.internal.FileResourceStore +import org.hisp.dhis.android.core.icon.internal.CustomIconStore import org.hisp.dhis.android.core.trackedentity.TrackedEntityAttribute import org.hisp.dhis.android.core.trackedentity.TrackedEntityAttributeCollectionRepository import org.hisp.dhis.android.core.trackedentity.TrackedEntityAttributeValue @@ -54,6 +55,7 @@ internal class FileResourceRoutine( private val trackedEntityAttributeCollectionRepository: TrackedEntityAttributeCollectionRepository, private val trackedEntityDataValueCollectionRepository: TrackedEntityDataValueCollectionRepository, private val fileResourceStore: FileResourceStore, + private val customIconStore: CustomIconStore, private val trackedEntityAttributeValueCollectionRepository: TrackedEntityAttributeValueCollectionRepository, ) { fun deleteOutdatedFileResources(after: Date? = null): Completable { @@ -84,16 +86,19 @@ internal class FileResourceRoutine( .byDataElementUid().`in`(dataElementsUids) .blockingGet() + val customIcons = customIconStore.selectAll() + val fileResourceUids = dataValues.map(DataValue::value) + trackedEntityAttributeValues.map(TrackedEntityAttributeValue::value) + - trackedEntityDataValues.map(TrackedEntityDataValue::value) + trackedEntityDataValues.map(TrackedEntityDataValue::value) + + customIcons.map { it.fileResource().uid() } val calendar = Calendar.getInstance().apply { add(Calendar.HOUR_OF_DAY, -2) } val fileResources = fileResourceCollectionRepository .byUid().notIn(fileResourceUids.mapNotNull { it }) - .byDomain().eq(FileResourceDomain.DATA_VALUE) + .byDomain().`in`(FileResourceDomain.DATA_VALUE, FileResourceDomain.ICON) .byLastUpdated().before(after ?: calendar.time) .blockingGet() 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 4267623b55..b4b7b1d824 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 @@ -39,15 +39,17 @@ import org.hisp.dhis.android.core.arch.helpers.FileResizerHelper import org.hisp.dhis.android.core.common.State import org.hisp.dhis.android.core.common.ValueType import org.hisp.dhis.android.core.fileresource.FileResource +import org.hisp.dhis.android.core.fileresource.FileResourceDataDomainType import org.hisp.dhis.android.core.fileresource.FileResourceDomainType import org.hisp.dhis.android.core.fileresource.FileResourceElementType import org.hisp.dhis.android.core.fileresource.FileResourceInternalAccessor import org.hisp.dhis.android.core.fileresource.FileResourceRoutine +import org.hisp.dhis.android.core.icon.CustomIcon import org.hisp.dhis.android.core.maintenance.D2Error import org.hisp.dhis.android.core.settings.internal.SynchronizationSettingStore import org.koin.core.annotation.Singleton -@SuppressWarnings("LongParameterList") +@SuppressWarnings("LongParameterList", "MagicNumber") @Singleton internal class FileResourceDownloadCall( private val fileResourceStore: FileResourceStore, @@ -61,7 +63,7 @@ internal class FileResourceDownloadCall( ) { fun download(params: FileResourceDownloadParams): Flow = flow { - val progressManager = D2ProgressManager(2) + val progressManager = D2ProgressManager(4) val existingFileResources = fileResourceStore.selectUids() val paramsWithCorrectedMaxContentLength = params.copy( @@ -75,14 +77,21 @@ internal class FileResourceDownloadCall( downloadTrackerValues(paramsWithCorrectedMaxContentLength, existingFileResources) emit(progressManager.increaseProgress(FileResource::class.java, isComplete = false)) + + downloadCustomIcons(paramsWithCorrectedMaxContentLength, existingFileResources) + emit(progressManager.increaseProgress(FileResource::class.java, isComplete = false)) + fileResourceRoutine.blockingDeleteOutdatedFileResources() + emit(progressManager.increaseProgress(FileResource::class.java, isComplete = true)) } private suspend fun downloadAggregatedValues( params: FileResourceDownloadParams, existingFileResources: List, ) { - if (params.domainTypes.contains(FileResourceDomainType.AGGREGATED)) { + if (params.domainTypes.contains(FileResourceDomainType.DATA_VALUE) && + params.dataDomainTypes.contains(FileResourceDataDomainType.AGGREGATED) + ) { val dataValues = helper.getMissingAggregatedDataValues(params, existingFileResources) downloadAndPersistFiles( @@ -103,7 +112,9 @@ internal class FileResourceDownloadCall( } private suspend fun downloadTrackerValues(params: FileResourceDownloadParams, existingFileResources: List) { - if (params.domainTypes.contains(FileResourceDomainType.TRACKER)) { + if (params.domainTypes.contains(FileResourceDomainType.DATA_VALUE) && + params.dataDomainTypes.contains(FileResourceDataDomainType.TRACKER) + ) { if (params.elementTypes.contains(FileResourceElementType.TRACED_ENTITY_ATTRIBUTE)) { val attributeDataValues = helper.getMissingTrackerAttributeValues(params, existingFileResources) @@ -151,6 +162,21 @@ internal class FileResourceDownloadCall( } } + private suspend fun downloadCustomIcons(params: FileResourceDownloadParams, existingFileResources: List) { + if (params.domainTypes.contains(FileResourceDomainType.ICON)) { + val iconKeys: List = helper.getMissingCustomIcons(existingFileResources) + + downloadAndPersistFiles( + values = iconKeys, + maxContentLength = params.maxContentLength, + download = { v -> + fileResourceService.getCustomIcon(v.href()) + }, + getUid = { v -> v.fileResource().uid() }, + ) + } + } + private suspend fun downloadAndPersistFiles( values: List, maxContentLength: Int?, 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 0061c75476..197f76810f 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 @@ -34,6 +34,9 @@ import org.hisp.dhis.android.core.datavalue.DataValue import org.hisp.dhis.android.core.datavalue.DataValueTableInfo import org.hisp.dhis.android.core.datavalue.internal.DataValueStore import org.hisp.dhis.android.core.fileresource.FileResourceValueType +import org.hisp.dhis.android.core.icon.CustomIcon +import org.hisp.dhis.android.core.icon.CustomIconTableInfo +import org.hisp.dhis.android.core.icon.internal.CustomIconStore import org.hisp.dhis.android.core.systeminfo.DHISVersion import org.hisp.dhis.android.core.systeminfo.DHISVersionManager import org.hisp.dhis.android.core.trackedentity.TrackedEntityAttributeTableInfo @@ -52,6 +55,7 @@ internal class FileResourceDownloadCallHelper( private val trackedEntityAttributeStore: TrackedEntityAttributeStore, private val trackedEntityDataValueStore: TrackedEntityDataValueStore, private val dataValueStore: DataValueStore, + private val customIconStore: CustomIconStore, private val dhisVersionManager: DHISVersionManager, ) { @@ -117,4 +121,13 @@ internal class FileResourceDownloadCallHelper( .build() return dataValueStore.selectWhere(dataValuesWhereClause) } + + fun getMissingCustomIcons( + existingFileResources: List, + ): List { + val customIconsWhereClause = WhereClauseBuilder() + .appendNotInKeyStringValues(CustomIconTableInfo.Columns.FILE_RESOURCE, existingFileResources) + .build() + return customIconStore.selectWhere(customIconsWhereClause) + } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceDownloadParams.kt b/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceDownloadParams.kt index 58c9c2c060..d694f56f67 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceDownloadParams.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceDownloadParams.kt @@ -28,13 +28,15 @@ package org.hisp.dhis.android.core.fileresource.internal import org.hisp.dhis.android.core.arch.repositories.scope.BaseScope +import org.hisp.dhis.android.core.fileresource.FileResourceDataDomainType import org.hisp.dhis.android.core.fileresource.FileResourceDomainType import org.hisp.dhis.android.core.fileresource.FileResourceElementType import org.hisp.dhis.android.core.fileresource.FileResourceValueType internal data class FileResourceDownloadParams( - val valueTypes: List = FileResourceValueType.values().asList(), - val elementTypes: List = FileResourceElementType.values().asList(), - val domainTypes: List = FileResourceDomainType.values().asList(), + val valueTypes: List = FileResourceValueType.entries, + val elementTypes: List = FileResourceElementType.entries, + val dataDomainTypes: List = FileResourceDataDomainType.entries, + val domainTypes: List = FileResourceDomainType.entries, val maxContentLength: Int? = null, ) : BaseScope diff --git a/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceHelper.kt b/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceHelper.kt index 012f4989d1..d3a0b530d3 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceHelper.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceHelper.kt @@ -36,7 +36,7 @@ import org.hisp.dhis.android.core.datavalue.internal.DataValueStore import org.hisp.dhis.android.core.event.Event import org.hisp.dhis.android.core.event.internal.EventStore import org.hisp.dhis.android.core.fileresource.FileResource -import org.hisp.dhis.android.core.fileresource.FileResourceDomainType +import org.hisp.dhis.android.core.fileresource.FileResourceDataDomainType import org.hisp.dhis.android.core.trackedentity.* import org.hisp.dhis.android.core.trackedentity.internal.TrackedEntityAttributeStore import org.hisp.dhis.android.core.trackedentity.internal.TrackedEntityAttributeValueStore @@ -121,7 +121,7 @@ internal class FileResourceHelper( } } - fun updateFileResourceStates(fileResources: List, domainType: FileResourceDomainType) { + fun updateFileResourceStates(fileResources: List, domainType: FileResourceDataDomainType) { fileResources.forEach { fr -> val relatedState = getRelatedResourceState(fr, domainType) val state = if (relatedState == State.SYNCED) State.SYNCED else State.TO_POST @@ -129,13 +129,13 @@ internal class FileResourceHelper( } } - private fun getRelatedResourceState(fileResourceUid: String, domain: FileResourceDomainType): State { + private fun getRelatedResourceState(fileResourceUid: String, domain: FileResourceDataDomainType): State { return when (domain) { - FileResourceDomainType.TRACKER -> + FileResourceDataDomainType.TRACKER -> getRelatedEvent(fileResourceUid)?.syncState() ?: getRelatedTei(fileResourceUid)?.syncState() ?: State.TO_POST - FileResourceDomainType.AGGREGATED -> + FileResourceDataDomainType.AGGREGATED -> getRelatedDataValue(fileResourceUid)?.syncState() ?: State.TO_POST } diff --git a/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceModuleImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceModuleImpl.kt index a0331c9e20..c5b41c4079 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceModuleImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceModuleImpl.kt @@ -30,6 +30,7 @@ package org.hisp.dhis.android.core.fileresource.internal import io.reactivex.Observable import org.hisp.dhis.android.core.arch.call.D2Progress import org.hisp.dhis.android.core.fileresource.FileResourceCollectionRepository +import org.hisp.dhis.android.core.fileresource.FileResourceDataDomainType import org.hisp.dhis.android.core.fileresource.FileResourceDomainType import org.hisp.dhis.android.core.fileresource.FileResourceDownloader import org.hisp.dhis.android.core.fileresource.FileResourceModule @@ -46,7 +47,8 @@ internal class FileResourceModuleImpl( "Replace with fileResourceDownloader()", replaceWith = ReplaceWith( expression = "fileResourceDownloader()\n" + - " .byDomainType().eq(FileResourceDomainType.TRACKER)\n" + + " .byDomainType().eq(FileResourceDomainType.DATA_VALUE)\n" + + " .byDataDomainType().eq(FileResourceDataDomainType.TRACKER)\n" + " .byValueType().eq(FileResourceValueType.IMAGE)\n" + " .download()", "org.hisp.dhis.android.core.fileresource.FileResourceDomainType", @@ -55,7 +57,8 @@ internal class FileResourceModuleImpl( ) override fun download(): Observable { return fileResourceDownloader() - .byDomainType().eq(FileResourceDomainType.TRACKER) + .byDomainType().eq(FileResourceDomainType.DATA_VALUE) + .byDataDomainType().eq(FileResourceDataDomainType.TRACKER) .byValueType().eq(FileResourceValueType.IMAGE) .download() } @@ -64,7 +67,8 @@ internal class FileResourceModuleImpl( "Replace with fileResourceDownloader()", replaceWith = ReplaceWith( expression = "fileResourceDownloader()\n" + - " .byDomainType().eq(FileResourceDomainType.TRACKER)\n" + + " .byDomainType().eq(FileResourceDomainType.DATA_VALUE)\n" + + " .byDataDomainType().eq(FileResourceDataDomainType.TRACKER)\n" + " .byValueType().eq(FileResourceValueType.IMAGE)\n" + " .blockingDownload()", "org.hisp.dhis.android.core.fileresource.FileResourceDomainType", diff --git a/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceService.kt b/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceService.kt index 6691dd86ab..fde6358bc0 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceService.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/fileresource/internal/FileResourceService.kt @@ -61,6 +61,11 @@ internal interface FileResourceService { @Query("dimension") dimension: String, ): ResponseBody + @GET + suspend fun getCustomIcon( + @Url customIconHref: String, + ): ResponseBody + @GET("$DATA_VALUES/files") suspend fun getFileFromDataValue( @Query("de") dataElement: String, diff --git a/core/src/main/java/org/hisp/dhis/android/core/icon/CustomIcon.java b/core/src/main/java/org/hisp/dhis/android/core/icon/CustomIcon.java new file mode 100644 index 0000000000..0ef6827c4e --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/icon/CustomIcon.java @@ -0,0 +1,87 @@ +/* + * 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.icon; + +import android.database.Cursor; + +import androidx.annotation.NonNull; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.gabrielittner.auto.value.cursor.ColumnAdapter; +import com.google.auto.value.AutoValue; + +import org.hisp.dhis.android.core.arch.db.adapters.identifiable.internal.ObjectWithUidColumnAdapter; +import org.hisp.dhis.android.core.common.CoreObject; +import org.hisp.dhis.android.core.common.ObjectWithUid; + +@AutoValue +@JsonDeserialize(builder = $$AutoValue_CustomIcon.Builder.class) +public abstract class CustomIcon implements CoreObject { + + @NonNull + @JsonProperty() + public abstract String key(); + + @NonNull + @JsonProperty + @ColumnAdapter(ObjectWithUidColumnAdapter.class) + public abstract ObjectWithUid fileResource(); + + @NonNull + @JsonProperty + public abstract String href(); + + @NonNull + public static CustomIcon create(Cursor cursor) { + return $AutoValue_CustomIcon.createFromCursor(cursor); + } + + public abstract Builder toBuilder(); + + public static Builder builder() { + return new AutoValue_CustomIcon.Builder(); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "") + public abstract static class Builder { + + public abstract Builder id(Long id); + + public abstract Builder key(String key); + + public abstract Builder fileResource(ObjectWithUid fileResource); + + public abstract Builder href(String href); + + public abstract CustomIcon build(); + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/icon/CustomIconTableInfo.kt b/core/src/main/java/org/hisp/dhis/android/core/icon/CustomIconTableInfo.kt new file mode 100644 index 0000000000..bb1b6d4438 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/icon/CustomIconTableInfo.kt @@ -0,0 +1,71 @@ +/* + * 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.icon + +import org.hisp.dhis.android.core.arch.db.tableinfos.TableInfo +import org.hisp.dhis.android.core.arch.helpers.CollectionsHelper +import org.hisp.dhis.android.core.common.CoreColumns + +object CustomIconTableInfo { + + @JvmField + val TABLE_INFO: TableInfo = object : TableInfo() { + override fun name(): String { + return "CustomIcon" + } + + override fun columns(): CoreColumns { + return Columns() + } + } + + class Columns : CoreColumns() { + override fun all(): Array { + return CollectionsHelper.appendInNewArray( + super.all(), + KEY, + FILE_RESOURCE, + HREF, + ) + } + + override fun whereUpdate(): Array { + return CollectionsHelper.appendInNewArray( + super.all(), + KEY, + ) + } + + companion object { + const val KEY = "key" + const val FILE_RESOURCE = "fileResource" + const val HREF = "href" + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/icon/DefaultIcon.kt b/core/src/main/java/org/hisp/dhis/android/core/icon/DefaultIcon.kt new file mode 100644 index 0000000000..adf09fb292 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/icon/DefaultIcon.kt @@ -0,0 +1,935 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.icon + +@Suppress("LargeClass") +internal object DefaultIcon { + + const val mimeType = "application/xml" + + val all = listOf( + "2g_negative", + "2g_outline", + "2g_positive", + "3g_negative", + "3g_outline", + "3g_positive", + "4x4_negative", + "4x4_outline", + "4x4_positive", + "agriculture_negative", + "agriculture_outline", + "agriculture_positive", + "agriculture_worker_negative", + "agriculture_worker_outline", + "agriculture_worker_positive", + "alert_circle_negative", + "alert_circle_outline", + "alert_circle_positive", + "alert_negative", + "alert_outline", + "alert_positive", + "alert_triangle_negative", + "alert_triangle_outline", + "alert_triangle_positive", + "ambulance_negative", + "ambulance_outline", + "ambulance_positive", + "ambulatory_clinic_negative", + "ambulatory_clinic_outline", + "ambulatory_clinic_positive", + "ancv_negative", + "ancv_outline", + "ancv_positive", + "baby_female_0203m_negative", + "baby_female_0203m_outline", + "baby_female_0203m_positive", + "baby_female_0306m_negative", + "baby_female_0306m_outline", + "baby_female_0306m_positive", + "baby_female_0609m_negative", + "baby_female_0609m_outline", + "baby_female_0609m_positive", + "baby_male_0203m_negative", + "baby_male_0203m_outline", + "baby_male_0203m_positive", + "baby_male_0306m_negative", + "baby_male_0306m_outline", + "baby_male_0306m_positive", + "baby_male_0609m_negative", + "baby_male_0609m_outline", + "baby_male_0609m_positive", + "basic_motorcycle_negative", + "basic_motorcycle_outline", + "basic_motorcycle_positive", + "bike_negative", + "bike_outline", + "bike_positive", + "bills_negative", + "bills_outline", + "bills_positive", + "blister_pills_oval_x14_negative", + "blister_pills_oval_x14_outline", + "blister_pills_oval_x14_positive", + "blister_pills_oval_x16_negative", + "blister_pills_oval_x16_outline", + "blister_pills_oval_x16_positive", + "blister_pills_oval_x1_negative", + "blister_pills_oval_x1_outline", + "blister_pills_oval_x1_positive", + "blister_pills_oval_x4_negative", + "blister_pills_oval_x4_outline", + "blister_pills_oval_x4_positive", + "blister_pills_round_x14_negative", + "blister_pills_round_x14_outline", + "blister_pills_round_x14_positive", + "blister_pills_round_x16_negative", + "blister_pills_round_x16_outline", + "blister_pills_round_x16_positive", + "blister_pills_round_x1_negative", + "blister_pills_round_x1_outline", + "blister_pills_round_x1_positive", + "blister_pills_round_x4_negative", + "blister_pills_round_x4_outline", + "blister_pills_round_x4_positive", + "blood_a_n_negative", + "blood_a_n_outline", + "blood_a_n_positive", + "blood_a_p_negative", + "blood_a_p_outline", + "blood_a_p_positive", + "blood_ab_n_negative", + "blood_ab_n_outline", + "blood_ab_n_positive", + "blood_ab_p_negative", + "blood_ab_p_outline", + "blood_ab_p_positive", + "blood_b_n_negative", + "blood_b_n_outline", + "blood_b_n_positive", + "blood_b_p_negative", + "blood_b_p_outline", + "blood_b_p_positive", + "blood_o_n_negative", + "blood_o_n_outline", + "blood_o_n_positive", + "blood_o_p_negative", + "blood_o_p_outline", + "blood_o_p_positive", + "blood_pressure_2_negative", + "blood_pressure_2_outline", + "blood_pressure_2_positive", + "blood_pressure_monitor_negative", + "blood_pressure_monitor_outline", + "blood_pressure_monitor_positive", + "blood_pressure_negative", + "blood_pressure_outline", + "blood_pressure_positive", + "blood_rh_n_negative", + "blood_rh_n_outline", + "blood_rh_n_positive", + "blood_rh_p_negative", + "blood_rh_p_outline", + "blood_rh_p_positive", + "boy_0105y_negative", + "boy_0105y_outline", + "boy_0105y_positive", + "boy_1015y_negative", + "boy_1015y_outline", + "boy_1015y_positive", + "breeding_sites_negative", + "breeding_sites_outline", + "breeding_sites_positive", + "calendar_negative", + "calendar_outline", + "calendar_positive", + "cardiogram_e_negative", + "cardiogram_e_outline", + "cardiogram_e_positive", + "cardiogram_negative", + "cardiogram_outline", + "cardiogram_positive", + "cervical_cancer_negative", + "cervical_cancer_outline", + "cervical_cancer_positive", + "child_care_negative", + "child_care_outline", + "child_care_positive", + "child_program_negative", + "child_program_outline", + "child_program_positive", + "chills_negative", + "chills_outline", + "chills_positive", + "cholera_negative", + "cholera_outline", + "cholera_positive", + "church_negative", + "church_outline", + "church_positive", + "circle_large_negative", + "circle_large_outline", + "circle_large_positive", + "circle_medium_negative", + "circle_medium_outline", + "circle_medium_positive", + "circle_small_negative", + "circle_small_outline", + "circle_small_positive", + "city_negative", + "city_outline", + "city_positive", + "city_worker_negative", + "city_worker_outline", + "city_worker_positive", + "clean_hands_negative", + "clean_hands_outline", + "clean_hands_positive", + "clinical_a_negative", + "clinical_a_outline", + "clinical_a_positive", + "clinical_f_negative", + "clinical_f_outline", + "clinical_f_positive", + "clinical_fe_negative", + "clinical_fe_outline", + "clinical_fe_positive", + "coins_negative", + "coins_outline", + "coins_positive", + "cold_chain_negative", + "cold_chain_outline", + "cold_chain_positive", + "communication_negative", + "communication_outline", + "communication_positive", + "cone_test_on_nets_negative", + "cone_test_on_nets_outline", + "cone_test_on_nets_positive", + "cone_test_on_walls_negative", + "cone_test_on_walls_outline", + "cone_test_on_walls_positive", + "construction_negative", + "construction_outline", + "construction_positive", + "construction_worker_negative", + "construction_worker_outline", + "construction_worker_positive", + "contact_support_negative", + "contact_support_outline", + "contact_support_positive", + "contraceptive_diaphragm_negative", + "contraceptive_diaphragm_outline", + "contraceptive_diaphragm_positive", + "contraceptive_injection_negative", + "contraceptive_injection_outline", + "contraceptive_injection_positive", + "contraceptive_patch_negative", + "contraceptive_patch_outline", + "contraceptive_patch_positive", + "contraceptive_voucher_negative", + "contraceptive_voucher_outline", + "contraceptive_voucher_positive", + "copper_iud_negative", + "copper_iud_outline", + "copper_iud_positive", + "coughing_negative", + "coughing_outline", + "coughing_positive", + "credit_card_negative", + "credit_card_outline", + "credit_card_positive", + "cross_country_motorcycle_negative", + "cross_country_motorcycle_outline", + "cross_country_motorcycle_positive", + "default_negative", + "default_outline", + "default_positive", + "dhis2_logo_negative", + "dhis2_logo_outline", + "dhis2_logo_positive", + "diarrhea_negative", + "diarrhea_outline", + "diarrhea_positive", + "discriminating_concentration_bioassays_negative", + "discriminating_concentration_bioassays_outline", + "discriminating_concentration_bioassays_positive", + "doctor_negative", + "doctor_outline", + "doctor_positive", + "domestic_worker_negative", + "domestic_worker_outline", + "domestic_worker_positive", + "donkey_negative", + "donkey_outline", + "donkey_positive", + "drone_negative", + "drone_outline", + "drone_positive", + "eco_care_negative", + "eco_care_outline", + "eco_care_positive", + "elderly_negative", + "elderly_outline", + "elderly_positive", + "electricity_negative", + "electricity_outline", + "electricity_positive", + "emergency_post_negative", + "emergency_post_outline", + "emergency_post_positive", + "expectorate_negative", + "expectorate_outline", + "expectorate_positive", + "factory_worker_negative", + "factory_worker_outline", + "factory_worker_positive", + "family_planning_negative", + "family_planning_outline", + "family_planning_positive", + "female_and_male_negative", + "female_and_male_outline", + "female_and_male_positive", + "female_condom_negative", + "female_condom_outline", + "female_condom_positive", + "female_sex_worker_negative", + "female_sex_worker_outline", + "female_sex_worker_positive", + "fetus_negative", + "fetus_outline", + "fetus_positive", + "fever_2_negative", + "fever_2_outline", + "fever_2_positive", + "fever_chills_negative", + "fever_chills_outline", + "fever_chills_positive", + "fever_negative", + "fever_outline", + "fever_positive", + "forest_negative", + "forest_outline", + "forest_persons_negative", + "forest_persons_outline", + "forest_persons_positive", + "forest_positive", + "forum_negative", + "forum_outline", + "forum_positive", + "girl_0105y_negative", + "girl_0105y_outline", + "girl_0105y_positive", + "girl_1015y_negative", + "girl_1015y_outline", + "girl_1015y_positive", + "group_discussion_meeting_negative", + "group_discussion_meeting_outline", + "group_discussion_meeting_positive", + "group_discussion_meetingx3_negative", + "group_discussion_meetingx3_outline", + "group_discussion_meetingx3_positive", + "happy_negative", + "happy_outline", + "happy_positive", + "hazardous_negative", + "hazardous_outline", + "hazardous_positive", + "headache_negative", + "headache_outline", + "headache_positive", + "health_worker_form_negative", + "health_worker_form_outline", + "health_worker_form_positive", + "health_worker_negative", + "health_worker_outline", + "health_worker_positive", + "heart_cardiogram_negative", + "heart_cardiogram_outline", + "heart_cardiogram_positive", + "heart_negative", + "heart_outline", + "heart_positive", + "helicopter_negative", + "helicopter_outline", + "helicopter_positive", + "high_bars_negative", + "high_bars_outline", + "high_bars_positive", + "high_level_negative", + "high_level_outline", + "high_level_positive", + "hiv_ind_negative", + "hiv_ind_outline", + "hiv_ind_positive", + "hiv_neg_negative", + "hiv_neg_outline", + "hiv_neg_positive", + "hiv_pos_negative", + "hiv_pos_outline", + "hiv_pos_positive", + "hiv_self_test_negative", + "hiv_self_test_outline", + "hiv_self_test_positive", + "home_negative", + "home_outline", + "home_positive", + "hormonal_ring_negative", + "hormonal_ring_outline", + "hormonal_ring_positive", + "hospital_negative", + "hospital_outline", + "hospital_positive", + "hospitalized_negative", + "hospitalized_outline", + "hospitalized_positive", + "hot_meal_negative", + "hot_meal_outline", + "hot_meal_positive", + "hpv_negative", + "hpv_outline", + "hpv_positive", + "i_certificate_paper_negative", + "i_certificate_paper_outline", + "i_certificate_paper_positive", + "i_documents_accepted_negative", + "i_documents_accepted_outline", + "i_documents_accepted_positive", + "i_documents_denied_negative", + "i_documents_denied_outline", + "i_documents_denied_positive", + "i_exam_multiple_choice_negative", + "i_exam_multiple_choice_outline", + "i_exam_multiple_choice_positive", + "i_exam_qualification_negative", + "i_exam_qualification_outline", + "i_exam_qualification_positive", + "i_groups_perspective_crowd_negative", + "i_groups_perspective_crowd_outline", + "i_groups_perspective_crowd_positive", + "i_note_action_negative", + "i_note_action_outline", + "i_note_action_positive", + "i_schedule_school_date_time_negative", + "i_schedule_school_date_time_outline", + "i_schedule_school_date_time_positive", + "i_training_class_negative", + "i_training_class_outline", + "i_training_class_positive", + "i_utensils_negative", + "i_utensils_outline", + "i_utensils_positive", + "imm_negative", + "imm_outline", + "imm_positive", + "implant_negative", + "implant_outline", + "implant_positive", + "info_negative", + "info_outline", + "info_positive", + "information_campaign_negative", + "information_campaign_outline", + "information_campaign_positive", + "inpatient_negative", + "inpatient_outline", + "inpatient_positive", + "insecticide_resistance_negative", + "insecticide_resistance_outline", + "insecticide_resistance_positive", + "intensity_concentration_bioassays_negative", + "intensity_concentration_bioassays_outline", + "intensity_concentration_bioassays_positive", + "iud_negative", + "iud_outline", + "iud_positive", + "justice_negative", + "justice_outline", + "justice_positive", + "lactation_negative", + "lactation_outline", + "lactation_positive", + "letrina_negative", + "letrina_outline", + "letrina_positive", + "llin_negative", + "llin_outline", + "llin_positive", + "low_bars_negative", + "low_bars_outline", + "low_bars_positive", + "low_level_negative", + "low_level_outline", + "low_level_positive", + "machinery_negative", + "machinery_outline", + "machinery_positive", + "magnifying_glass_negative", + "magnifying_glass_outline", + "magnifying_glass_positive", + "malaria_mixed_microscope_negative", + "malaria_mixed_microscope_outline", + "malaria_mixed_microscope_positive", + "malaria_negative_microscope_negative", + "malaria_negative_microscope_outline", + "malaria_negative_microscope_positive", + "malaria_outbreak_negative", + "malaria_outbreak_outline", + "malaria_outbreak_positive", + "malaria_pf_microscope_negative", + "malaria_pf_microscope_outline", + "malaria_pf_microscope_positive", + "malaria_pv_microscope_negative", + "malaria_pv_microscope_outline", + "malaria_pv_microscope_positive", + "malaria_testing_negative", + "malaria_testing_outline", + "malaria_testing_positive", + "male_and_female_negative", + "male_and_female_outline", + "male_and_female_positive", + "male_condom_negative", + "male_condom_outline", + "male_condom_positive", + "male_sex_worker_negative", + "male_sex_worker_outline", + "male_sex_worker_positive", + "man_negative", + "man_outline", + "man_positive", + "market_stall_negative", + "market_stall_outline", + "market_stall_positive", + "mask_negative", + "mask_outline", + "mask_positive", + "measles_negative", + "measles_outline", + "measles_positive", + "medicines_negative", + "medicines_outline", + "medicines_positive", + "medium_bars_negative", + "medium_bars_outline", + "medium_bars_positive", + "medium_level_negative", + "medium_level_outline", + "medium_level_positive", + "megaphone_negative", + "megaphone_outline", + "megaphone_positive", + "mental_disorders_negative", + "mental_disorders_outline", + "mental_disorders_positive", + "microscope_negative", + "microscope_outline", + "microscope_positive", + "military_worker_negative", + "military_worker_outline", + "military_worker_positive", + "miner_worker_negative", + "miner_worker_outline", + "miner_worker_positive", + "mobile_clinic_negative", + "mobile_clinic_outline", + "mobile_clinic_positive", + "money_bag_negative", + "money_bag_outline", + "money_bag_positive", + "mosquito_collection_negative", + "mosquito_collection_outline", + "mosquito_collection_positive", + "mosquito_negative", + "mosquito_outline", + "mosquito_positive", + "msm_negative", + "msm_outline", + "msm_positive", + "nausea_negative", + "nausea_outline", + "nausea_positive", + "negative_negative", + "negative_outline", + "negative_positive", + "network_4g_negative", + "network_4g_outline", + "network_4g_positive", + "network_5g_negative", + "network_5g_outline", + "network_5g_positive", + "neurology_negative", + "neurology_outline", + "neurology_positive", + "neutral_negative", + "neutral_outline", + "neutral_positive", + "no_negative", + "no_outline", + "no_positive", + "not_ok_negative", + "not_ok_outline", + "not_ok_positive", + "nurse_negative", + "nurse_outline", + "nurse_positive", + "observation_negative", + "observation_outline", + "observation_positive", + "odontology_implant_negative", + "odontology_implant_outline", + "odontology_implant_positive", + "odontology_negative", + "odontology_outline", + "odontology_positive", + "officer_negative", + "officer_outline", + "officer_positive", + "ok_negative", + "ok_outline", + "ok_positive", + "old_man_negative", + "old_man_outline", + "old_man_positive", + "old_woman_negative", + "old_woman_outline", + "old_woman_positive", + "oral_contraception_pillsx21_negative", + "oral_contraception_pillsx21_outline", + "oral_contraception_pillsx21_positive", + "oral_contraception_pillsx28_negative", + "oral_contraception_pillsx28_outline", + "oral_contraception_pillsx28_positive", + "outpatient_negative", + "outpatient_outline", + "outpatient_positive", + "overweight_negative", + "overweight_outline", + "overweight_positive", + "palm_branches_roof_negative", + "palm_branches_roof_outline", + "palm_branches_roof_positive", + "pave_road_negative", + "pave_road_outline", + "pave_road_positive", + "peace_negative", + "peace_outline", + "peace_positive", + "people_negative", + "people_outline", + "people_positive", + "person_negative", + "person_outline", + "person_positive", + "phone_negative", + "phone_outline", + "phone_positive", + "pill_1_negative", + "pill_1_outline", + "pill_1_positive", + "pills_2_negative", + "pills_2_outline", + "pills_2_positive", + "pills_3_negative", + "pills_3_outline", + "pills_3_positive", + "pills_4_negative", + "pills_4_outline", + "pills_4_positive", + "plantation_worker_negative", + "plantation_worker_outline", + "plantation_worker_positive", + "polygon_negative", + "polygon_outline", + "polygon_positive", + "positive_negative", + "positive_outline", + "positive_positive", + "pregnant_0812w_negative", + "pregnant_0812w_outline", + "pregnant_0812w_positive", + "pregnant_2426w_negative", + "pregnant_2426w_outline", + "pregnant_2426w_positive", + "pregnant_32w_negative", + "pregnant_32w_outline", + "pregnant_32w_positive", + "pregnant_3638w_negative", + "pregnant_3638w_outline", + "pregnant_3638w_positive", + "pregnant_negative", + "pregnant_outline", + "pregnant_positive", + "prisoner_negative", + "prisoner_outline", + "prisoner_positive", + "proper_roof_negative", + "proper_roof_outline", + "proper_roof_positive", + "provider_fst_negative", + "provider_fst_outline", + "provider_fst_positive", + "pwid_negative", + "pwid_outline", + "pwid_positive", + "question_circle_negative", + "question_circle_outline", + "question_circle_positive", + "question_negative", + "question_outline", + "question_positive", + "question_triangle_negative", + "question_triangle_outline", + "question_triangle_positive", + "rdt_result_invalid_negative", + "rdt_result_invalid_outline", + "rdt_result_invalid_positive", + "rdt_result_mixed_invalid_negative", + "rdt_result_mixed_invalid_outline", + "rdt_result_mixed_invalid_positive", + "rdt_result_mixed_invalid_rectangular_negative", + "rdt_result_mixed_invalid_rectangular_outline", + "rdt_result_mixed_invalid_rectangular_positive", + "rdt_result_mixed_negative", + "rdt_result_mixed_outline", + "rdt_result_mixed_positive", + "rdt_result_mixed_rectangular_negative", + "rdt_result_mixed_rectangular_outline", + "rdt_result_mixed_rectangular_positive", + "rdt_result_neg_invalid_negative", + "rdt_result_neg_invalid_outline", + "rdt_result_neg_invalid_positive", + "rdt_result_neg_invalid_rectangular_negative", + "rdt_result_neg_invalid_rectangular_outline", + "rdt_result_neg_invalid_rectangular_positive", + "rdt_result_neg_negative", + "rdt_result_neg_outline", + "rdt_result_neg_positive", + "rdt_result_neg_rectangular_negative", + "rdt_result_neg_rectangular_outline", + "rdt_result_neg_rectangular_positive", + "rdt_result_negative_negative", + "rdt_result_negative_outline", + "rdt_result_negative_positive", + "rdt_result_no_test_negative", + "rdt_result_no_test_outline", + "rdt_result_no_test_positive", + "rdt_result_out_stock_negative", + "rdt_result_out_stock_outline", + "rdt_result_out_stock_positive", + "rdt_result_pf_invalid_negative", + "rdt_result_pf_invalid_outline", + "rdt_result_pf_invalid_positive", + "rdt_result_pf_invalid_rectangular_negative", + "rdt_result_pf_invalid_rectangular_outline", + "rdt_result_pf_invalid_rectangular_positive", + "rdt_result_pf_negative", + "rdt_result_pf_outline", + "rdt_result_pf_positive", + "rdt_result_pf_rectangular_negative", + "rdt_result_pf_rectangular_outline", + "rdt_result_pf_rectangular_positive", + "rdt_result_positive_negative", + "rdt_result_positive_outline", + "rdt_result_positive_positive", + "rdt_result_pv_invalid_negative", + "rdt_result_pv_invalid_outline", + "rdt_result_pv_invalid_positive", + "rdt_result_pv_invalid_rectangular_negative", + "rdt_result_pv_invalid_rectangular_outline", + "rdt_result_pv_invalid_rectangular_positive", + "rdt_result_pv_negative", + "rdt_result_pv_outline", + "rdt_result_pv_positive", + "rdt_result_pv_rectangular_negative", + "rdt_result_pv_rectangular_outline", + "rdt_result_pv_rectangular_positive", + "referral_negative", + "referral_outline", + "referral_positive", + "refused_negative", + "refused_outline", + "refused_positive", + "ribbon_negative", + "ribbon_outline", + "ribbon_positive", + "rmnh_negative", + "rmnh_outline", + "rmnh_positive", + "running_water_negative", + "running_water_outline", + "running_water_positive", + "rural_post_negative", + "rural_post_outline", + "rural_post_positive", + "sad_negative", + "sad_outline", + "sad_positive", + "sanitizer_negative", + "sanitizer_outline", + "sanitizer_positive", + "sayana_press_negative", + "sayana_press_outline", + "sayana_press_positive", + "security_worker_negative", + "security_worker_outline", + "security_worker_positive", + "sexual_reproductive_health_negative", + "sexual_reproductive_health_outline", + "sexual_reproductive_health_positive", + "small_plane_negative", + "small_plane_outline", + "small_plane_positive", + "social_distancing_negative", + "social_distancing_outline", + "social_distancing_positive", + "spraying_negative", + "spraying_outline", + "spraying_positive", + "square_large_negative", + "square_large_outline", + "square_large_positive", + "square_medium_negative", + "square_medium_outline", + "square_medium_positive", + "square_small_negative", + "square_small_outline", + "square_small_positive", + "star_large_negative", + "star_large_outline", + "star_large_positive", + "star_medium_negative", + "star_medium_outline", + "star_medium_positive", + "star_small_negative", + "star_small_outline", + "star_small_positive", + "stethoscope_negative", + "stethoscope_outline", + "stethoscope_positive", + "sti_negative", + "sti_outline", + "sti_positive", + "stock_out_negative", + "stock_out_outline", + "stock_out_positive", + "stop_negative", + "stop_outline", + "stop_positive", + "surgical_sterilization_negative", + "surgical_sterilization_outline", + "surgical_sterilization_positive", + "sweating_negative", + "sweating_outline", + "sweating_positive", + "symptom_negative", + "symptom_outline", + "symptom_positive", + "synergist_insecticide_bioassays_negative", + "synergist_insecticide_bioassays_outline", + "synergist_insecticide_bioassays_positive", + "syringe_negative", + "syringe_outline", + "syringe_positive", + "tac_negative", + "tac_outline", + "tac_positive", + "tb_negative", + "tb_outline", + "tb_positive", + "transgender_negative", + "transgender_outline", + "transgender_positive", + "traumatism_negative", + "traumatism_outline", + "traumatism_positive", + "travel_negative", + "travel_outline", + "travel_positive", + "treated_water_negative", + "treated_water_outline", + "treated_water_positive", + "triangle_large_negative", + "triangle_large_outline", + "triangle_large_positive", + "triangle_medium_negative", + "triangle_medium_outline", + "triangle_medium_positive", + "triangle_small_negative", + "triangle_small_outline", + "triangle_small_positive", + "truck_driver_negative", + "truck_driver_outline", + "truck_driver_positive", + "un_pave_road_negative", + "un_pave_road_outline", + "un_pave_road_positive", + "underweight_negative", + "underweight_outline", + "underweight_positive", + "vespa_motorcycle_negative", + "vespa_motorcycle_outline", + "vespa_motorcycle_positive", + "vih_aids_negative", + "vih_aids_outline", + "vih_aids_positive", + "virus_negative", + "virus_outline", + "virus_positive", + "vomiting_negative", + "vomiting_outline", + "vomiting_positive", + "war_negative", + "war_outline", + "war_positive", + "wash_hands_negative", + "wash_hands_outline", + "wash_hands_positive", + "water_sanitation_negative", + "water_sanitation_outline", + "water_sanitation_positive", + "water_treatment_negative", + "water_treatment_outline", + "water_treatment_positive", + "weight_negative", + "weight_outline", + "weight_positive", + "wold_care_negative", + "wold_care_outline", + "wold_care_positive", + "woman_negative", + "woman_outline", + "woman_positive", + "yes_negative", + "yes_outline", + "yes_positive", + "young_people_negative", + "young_people_outline", + "young_people_positive", + ) +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/icon/Icon.kt b/core/src/main/java/org/hisp/dhis/android/core/icon/Icon.kt new file mode 100644 index 0000000000..52116bf2d3 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/icon/Icon.kt @@ -0,0 +1,38 @@ +/* + * 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.icon + +sealed class Icon(open val key: String) { + data class Default(override val key: String) : Icon(key) + + data class Custom( + override val key: String, + val fileResourceUid: String, + val path: String?, + ) : Icon(key) +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/icon/IconCollectionRepository.kt b/core/src/main/java/org/hisp/dhis/android/core/icon/IconCollectionRepository.kt new file mode 100644 index 0000000000..49d06d8f14 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/icon/IconCollectionRepository.kt @@ -0,0 +1,69 @@ +/* + * 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.icon + +import io.reactivex.Single +import org.hisp.dhis.android.core.arch.repositories.`object`.ReadOnlyObjectRepository +import org.hisp.dhis.android.core.fileresource.internal.FileResourceStore +import org.hisp.dhis.android.core.icon.internal.CustomIconStore +import org.koin.core.annotation.Singleton + +@Singleton +@Suppress("TooManyFunctions") +class IconCollectionRepository internal constructor( + private val customIconStore: CustomIconStore, + private val fileResourceStore: FileResourceStore, +) { + + fun key(key: String): ReadOnlyObjectRepository { + return object : ReadOnlyObjectRepository { + override fun get(): Single { + return Single.fromCallable { blockingGet() } + } + + override fun blockingGet(): Icon? { + return if (DefaultIcon.all.contains(key)) { + Icon.Default(key) + } else { + customIconStore.selectByKey(key)?.let { customIcon -> + val fileResource = fileResourceStore.selectByUid(customIcon.fileResource().uid()) + Icon.Custom(customIcon.key(), customIcon.fileResource().uid(), fileResource?.path()) + } + } + } + + override fun exists(): Single { + return Single.fromCallable { blockingExists() } + } + + override fun blockingExists(): Boolean { + return blockingGet() != null + } + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/icon/IconDIModule.kt b/core/src/main/java/org/hisp/dhis/android/core/icon/IconDIModule.kt new file mode 100644 index 0000000000..8f0b5ab017 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/icon/IconDIModule.kt @@ -0,0 +1,35 @@ +/* + * 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.icon + +import org.koin.core.annotation.ComponentScan +import org.koin.core.annotation.Module + +@Module +@ComponentScan +internal class IconDIModule diff --git a/core/src/main/java/org/hisp/dhis/android/core/icon/IconModule.kt b/core/src/main/java/org/hisp/dhis/android/core/icon/IconModule.kt new file mode 100644 index 0000000000..5ba2ee5b4b --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/icon/IconModule.kt @@ -0,0 +1,32 @@ +/* + * 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.icon + +interface IconModule { + fun icons(): IconCollectionRepository +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/icon/IconType.kt b/core/src/main/java/org/hisp/dhis/android/core/icon/IconType.kt new file mode 100644 index 0000000000..26be9e2994 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/icon/IconType.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.icon + +enum class IconType { + CUSTOM, + DEFAULT, +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconCall.kt b/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconCall.kt new file mode 100644 index 0000000000..1b9294e8dd --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconCall.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.icon.internal + +import org.hisp.dhis.android.core.arch.api.executors.internal.APIDownloader +import org.hisp.dhis.android.core.arch.api.payload.internal.Payload +import org.hisp.dhis.android.core.arch.call.factories.internal.UidsCallCoroutines +import org.hisp.dhis.android.core.icon.CustomIcon +import org.hisp.dhis.android.core.icon.IconType +import org.hisp.dhis.android.core.systeminfo.DHISVersion +import org.hisp.dhis.android.core.systeminfo.DHISVersionManager +import org.koin.core.annotation.Singleton + +@Singleton +internal class CustomIconCall( + private val handler: CustomIconHandler, + private val service: IconService, + private val dhis2VersionManager: DHISVersionManager, + private val apiDownloader: APIDownloader, +) : UidsCallCoroutines { + + companion object { + private const val MAX_UID_LIST_SIZE = 50 + } + + override suspend fun download(uids: Set): List { + return if (dhis2VersionManager.isGreaterOrEqualThan(DHISVersion.V2_41)) { + apiDownloader.downloadPartitioned( + uids, + MAX_UID_LIST_SIZE, + handler, + ) { partitionKeys: Set -> + try { + service.getCustomIcons( + fields = CustomIconFields.allFields, + keys = partitionKeys.joinToString(","), + type = IconType.CUSTOM.name, + paging = false, + ) + } catch (ignored: Exception) { + Payload() + } + } + } else { + emptyList() + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconCollectionCleaner.kt b/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconCollectionCleaner.kt new file mode 100644 index 0000000000..a5da20f7d0 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconCollectionCleaner.kt @@ -0,0 +1,42 @@ +/* + * 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.icon.internal + +import org.hisp.dhis.android.core.arch.cleaners.internal.BaseCollectionCleaner +import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.icon.CustomIconTableInfo +import org.koin.core.annotation.Singleton + +@Singleton +internal class CustomIconCollectionCleaner( + databaseAdapter: DatabaseAdapter, +) : BaseCollectionCleaner( + tableName = CustomIconTableInfo.TABLE_INFO.name(), + databaseAdapter = databaseAdapter, + key = CustomIconTableInfo.Columns.KEY, +) diff --git a/core/src/main/java/org/hisp/dhis/android/core/attribute/internal/AttributeValuesFields.java b/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconFields.kt similarity index 71% rename from core/src/main/java/org/hisp/dhis/android/core/attribute/internal/AttributeValuesFields.java rename to core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconFields.kt index a9b121f6b9..7658d5e994 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/attribute/internal/AttributeValuesFields.java +++ b/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconFields.kt @@ -25,26 +25,25 @@ * (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.icon.internal -package org.hisp.dhis.android.core.attribute.internal; +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper +import org.hisp.dhis.android.core.icon.CustomIcon -import org.hisp.dhis.android.core.arch.api.fields.internal.Fields; -import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper; -import org.hisp.dhis.android.core.attribute.AttributeValue; -import org.hisp.dhis.android.core.common.ObjectWithUid; +internal object CustomIconFields { + private const val KEY = "key" + private const val FILE_RESOURCE = "fileResource" + private const val HREF = "href" -public final class AttributeValuesFields { - public static final String VALUE = "value"; - public static final String ATTRIBUTE = "attribute"; + private val fh = FieldsHelper() - private static final FieldsHelper fh = new FieldsHelper<>(); - - public static final Fields allFields = Fields.builder() + val allFields: Fields = + Fields.builder() .fields( - fh.field(VALUE), - fh.nestedField(ATTRIBUTE).with(ObjectWithUid.uid) - ).build(); - - private AttributeValuesFields() { - } -} \ No newline at end of file + fh.field(KEY), + fh.nestedFieldWithUid(FILE_RESOURCE), + fh.field(HREF), + ) + .build() +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconHandler.kt b/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconHandler.kt new file mode 100644 index 0000000000..052f236f8b --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconHandler.kt @@ -0,0 +1,38 @@ +/* + * 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.icon.internal + +import org.hisp.dhis.android.core.arch.handlers.internal.ObjectWithoutUidHandlerImpl +import org.hisp.dhis.android.core.icon.CustomIcon +import org.koin.core.annotation.Singleton + +@Singleton +internal class CustomIconHandler( + store: CustomIconStore, +) : ObjectWithoutUidHandlerImpl(store) diff --git a/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconModuleDownloader.kt b/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconModuleDownloader.kt new file mode 100644 index 0000000000..085bde78f8 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconModuleDownloader.kt @@ -0,0 +1,49 @@ +/* + * 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.icon.internal + +import org.hisp.dhis.android.core.arch.modules.internal.UntypedSuspendModuleDownloader +import org.hisp.dhis.android.core.icon.CustomIconTableInfo +import org.koin.core.annotation.Singleton + +@Singleton +internal class CustomIconModuleDownloader internal constructor( + private val customIconCall: CustomIconCall, + private val customIconSeeker: CustomIconSeeker, + private val customIconStore: CustomIconStore, + private val customIconCleaner: CustomIconCollectionCleaner, +) : UntypedSuspendModuleDownloader { + + override suspend fun downloadMetadata() { + val customIcons = customIconSeeker.seekUids() + val existingCustomIcons = customIconStore.selectStringColumnsWhereClause(CustomIconTableInfo.Columns.KEY, "1") + val customIconsToDownload = customIcons.minus(existingCustomIcons.toSet()) + customIconCall.download(customIconsToDownload) + customIconCleaner.deleteNotPresentByKey(customIcons) + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconSeeker.kt b/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconSeeker.kt new file mode 100644 index 0000000000..531de650e9 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconSeeker.kt @@ -0,0 +1,50 @@ +/* + * 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.icon.internal + +import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.arch.db.querybuilders.internal.MultipleTableQueryBuilder +import org.hisp.dhis.android.core.arch.db.uidseeker.internal.BaseUidsSeeker +import org.hisp.dhis.android.core.common.NameableWithStyleColumns +import org.hisp.dhis.android.core.common.objectstyle.internal.TableWithObjectStyle +import org.hisp.dhis.android.core.icon.DefaultIcon +import org.koin.core.annotation.Singleton + +@Singleton +internal class CustomIconSeeker( + databaseAdapter: DatabaseAdapter, +) : BaseUidsSeeker(databaseAdapter) { + fun seekUids(): Set { + val query = MultipleTableQueryBuilder() + .generateQuery(NameableWithStyleColumns.ICON, TableWithObjectStyle.allTableNames).build() + + val allIcons = readSingleColumnResults(query) + + return allIcons.filterNot { DefaultIcon.all.contains(it) }.toSet() + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconStore.kt b/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconStore.kt new file mode 100644 index 0000000000..24137481d8 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconStore.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.icon.internal + +import org.hisp.dhis.android.core.arch.db.stores.internal.ObjectWithoutUidStore +import org.hisp.dhis.android.core.icon.CustomIcon + +internal interface CustomIconStore : ObjectWithoutUidStore { + fun selectByKey(key: String): CustomIcon? +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconStoreImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconStoreImpl.kt new file mode 100644 index 0000000000..eb47622532 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/icon/internal/CustomIconStoreImpl.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.icon.internal + +import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.arch.db.querybuilders.internal.WhereClauseBuilder +import org.hisp.dhis.android.core.arch.db.stores.binders.internal.StatementBinder +import org.hisp.dhis.android.core.arch.db.stores.binders.internal.StatementWrapper +import org.hisp.dhis.android.core.arch.db.stores.binders.internal.WhereStatementBinder +import org.hisp.dhis.android.core.arch.db.stores.internal.ObjectWithoutUidStoreImpl +import org.hisp.dhis.android.core.arch.helpers.UidsHelper.getUidOrNull +import org.hisp.dhis.android.core.icon.CustomIcon +import org.hisp.dhis.android.core.icon.CustomIconTableInfo +import org.koin.core.annotation.Singleton + +@Singleton +internal class CustomIconStoreImpl( + databaseAdapter: DatabaseAdapter, +) : CustomIconStore, + ObjectWithoutUidStoreImpl( + databaseAdapter, + CustomIconTableInfo.TABLE_INFO, + BINDER, + WHERE_UPDATE_BINDER, + WHERE_DELETE_BINDER, + CustomIcon::create, + ) { + + override fun selectByKey(key: String): CustomIcon? { + val whereClause = WhereClauseBuilder() + .appendKeyStringValue(CustomIconTableInfo.Columns.KEY, key) + .build() + + return selectOneWhere(whereClause) + } + + companion object { + private val BINDER = StatementBinder { o: CustomIcon, w: StatementWrapper -> + w.bind(1, o.key()) + w.bind(2, getUidOrNull(o.fileResource())) + w.bind(3, o.href()) + } + + private val WHERE_UPDATE_BINDER = WhereStatementBinder { o: CustomIcon, w: StatementWrapper -> + w.bind(4, o.key()) + } + + private val WHERE_DELETE_BINDER = WhereStatementBinder { o: CustomIcon, w: StatementWrapper -> + w.bind(1, o.key()) + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/icon/internal/IconModuleImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/icon/internal/IconModuleImpl.kt new file mode 100644 index 0000000000..a91ff9be56 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/icon/internal/IconModuleImpl.kt @@ -0,0 +1,41 @@ +/* + * 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.icon.internal + +import org.hisp.dhis.android.core.icon.IconCollectionRepository +import org.hisp.dhis.android.core.icon.IconModule +import org.koin.core.annotation.Singleton + +@Singleton +internal class IconModuleImpl( + private val icons: IconCollectionRepository, +) : IconModule { + override fun icons(): IconCollectionRepository { + return icons + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/icon/internal/IconModuleWiper.kt b/core/src/main/java/org/hisp/dhis/android/core/icon/internal/IconModuleWiper.kt new file mode 100644 index 0000000000..d0237b6ea0 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/icon/internal/IconModuleWiper.kt @@ -0,0 +1,47 @@ +/* + * 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.icon.internal + +import org.hisp.dhis.android.core.icon.CustomIconTableInfo +import org.hisp.dhis.android.core.wipe.internal.ModuleWiper +import org.hisp.dhis.android.core.wipe.internal.TableWiper +import org.koin.core.annotation.Singleton + +@Singleton +internal class IconModuleWiper( + private val tableWiper: TableWiper, +) : ModuleWiper { + + override fun wipeMetadata() { + tableWiper.wipeTable(CustomIconTableInfo.TABLE_INFO) + } + + override fun wipeData() { + // No data to wipe + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/icon/internal/IconService.kt b/core/src/main/java/org/hisp/dhis/android/core/icon/internal/IconService.kt new file mode 100644 index 0000000000..9046c07c68 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/icon/internal/IconService.kt @@ -0,0 +1,50 @@ +/* + * 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.icon.internal + +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.arch.api.filters.internal.Which +import org.hisp.dhis.android.core.arch.api.payload.internal.Payload +import org.hisp.dhis.android.core.icon.CustomIcon +import retrofit2.http.GET +import retrofit2.http.Query + +internal interface IconService { + + @GET(ICONS) + suspend fun getCustomIcons( + @Query("fields") @Which fields: Fields, + @Query("keys") keys: String, + @Query("type") type: String?, + @Query("paging") paging: Boolean, + ): Payload + + companion object { + const val ICONS = "icons" + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/imports/TrackerImportConflict.java b/core/src/main/java/org/hisp/dhis/android/core/imports/TrackerImportConflict.java index b82e647b1b..cf8e97eec4 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/imports/TrackerImportConflict.java +++ b/core/src/main/java/org/hisp/dhis/android/core/imports/TrackerImportConflict.java @@ -95,7 +95,7 @@ public static Builder builder() { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder conflict(String conflict); public abstract Builder value(String value); diff --git a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/BaseImportSummaries.java b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/BaseImportSummaries.java index 29c9922c53..9ae1eadb94 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/BaseImportSummaries.java +++ b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/BaseImportSummaries.java @@ -70,7 +70,7 @@ public abstract class BaseImportSummaries { public abstract Integer ignored(); @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder { + public abstract static class Builder { public abstract T status(ImportStatus status); diff --git a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/BaseImportSummary.java b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/BaseImportSummary.java index 411da508a8..c936c74f3c 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/BaseImportSummary.java +++ b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/BaseImportSummary.java @@ -73,7 +73,7 @@ public abstract class BaseImportSummary implements ImportSummary { public abstract String description(); @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder { + public abstract static class Builder { public abstract T importCount(ImportCount importCount); diff --git a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/EnrollmentImportSummaries.java b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/EnrollmentImportSummaries.java index 4d566c0e21..15948b7f70 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/EnrollmentImportSummaries.java +++ b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/EnrollmentImportSummaries.java @@ -50,7 +50,7 @@ public abstract class EnrollmentImportSummaries extends BaseImportSummaries impl @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseImportSummaries.Builder { + public abstract static class Builder extends BaseImportSummaries.Builder { public abstract Builder importSummaries(List importSummaries); diff --git a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/EnrollmentImportSummary.java b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/EnrollmentImportSummary.java index 6c4cdcc43a..12a6c77158 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/EnrollmentImportSummary.java +++ b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/EnrollmentImportSummary.java @@ -47,7 +47,7 @@ public abstract class EnrollmentImportSummary extends BaseImportSummary { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseImportSummary.Builder { + public abstract static class Builder extends BaseImportSummary.Builder { public abstract Builder events(EventImportSummaries events); diff --git a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/EventImportSummaries.java b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/EventImportSummaries.java index 2f92b28f37..b79f668b44 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/EventImportSummaries.java +++ b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/EventImportSummaries.java @@ -50,7 +50,7 @@ public abstract class EventImportSummaries extends BaseImportSummaries implement @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseImportSummaries.Builder { + public abstract static class Builder extends BaseImportSummaries.Builder { public abstract Builder importSummaries(List importSummaries); diff --git a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/EventImportSummary.java b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/EventImportSummary.java index ed70a7a82a..94aa3efd30 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/EventImportSummary.java +++ b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/EventImportSummary.java @@ -38,7 +38,7 @@ public abstract class EventImportSummary extends BaseImportSummary { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseImportSummary.Builder { + public abstract static class Builder extends BaseImportSummary.Builder { public abstract EventImportSummary build(); } diff --git a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/EventWebResponse.java b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/EventWebResponse.java index 727c3d4b2f..c92b58d1e9 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/EventWebResponse.java +++ b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/EventWebResponse.java @@ -60,7 +60,7 @@ public static EventWebResponse empty() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends WebResponse.Builder { + public abstract static class Builder extends WebResponse.Builder { public abstract Builder response(EventImportSummaries response); public abstract EventWebResponse build(); diff --git a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/HttpMessageResponse.java b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/HttpMessageResponse.java index 5ea97e5cbe..b59bf68c93 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/HttpMessageResponse.java +++ b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/HttpMessageResponse.java @@ -38,7 +38,7 @@ public abstract class HttpMessageResponse extends WebResponse { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends WebResponse.Builder { + public abstract static class Builder extends WebResponse.Builder { public abstract HttpMessageResponse build(); diff --git a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/RelationshipDeleteSummary.java b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/RelationshipDeleteSummary.java index 30fd0f628c..9ced593a0e 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/RelationshipDeleteSummary.java +++ b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/RelationshipDeleteSummary.java @@ -38,7 +38,7 @@ public abstract class RelationshipDeleteSummary extends BaseImportSummary { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseImportSummary.Builder { + public abstract static class Builder extends BaseImportSummary.Builder { public abstract RelationshipDeleteSummary build(); diff --git a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/RelationshipDeleteWebResponse.java b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/RelationshipDeleteWebResponse.java index d87efaf548..c73484a137 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/RelationshipDeleteWebResponse.java +++ b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/RelationshipDeleteWebResponse.java @@ -60,7 +60,7 @@ public static RelationshipDeleteWebResponse empty() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends WebResponse.Builder { + public abstract static class Builder extends WebResponse.Builder { public abstract Builder response(RelationshipDeleteSummary response); public abstract RelationshipDeleteWebResponse build(); diff --git a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/RelationshipImportSummaries.java b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/RelationshipImportSummaries.java index 4b23aaff94..90103088dc 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/RelationshipImportSummaries.java +++ b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/RelationshipImportSummaries.java @@ -50,7 +50,7 @@ public abstract class RelationshipImportSummaries @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseImportSummaries.Builder { + public abstract static class Builder extends BaseImportSummaries.Builder { public abstract Builder importSummaries(List importSummaries); diff --git a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/RelationshipImportSummary.java b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/RelationshipImportSummary.java index 083d6db127..4204d373b6 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/RelationshipImportSummary.java +++ b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/RelationshipImportSummary.java @@ -38,7 +38,7 @@ public abstract class RelationshipImportSummary extends BaseImportSummary { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseImportSummary.Builder { + public abstract static class Builder extends BaseImportSummary.Builder { public abstract RelationshipImportSummary build(); } diff --git a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/RelationshipWebResponse.java b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/RelationshipWebResponse.java index a7351474e2..d578211bce 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/RelationshipWebResponse.java +++ b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/RelationshipWebResponse.java @@ -58,7 +58,7 @@ public static RelationshipWebResponse empty() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends WebResponse.Builder { + public abstract static class Builder extends WebResponse.Builder { public abstract Builder response(RelationshipImportSummaries response); public abstract RelationshipWebResponse build(); diff --git a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/TEIImportSummaries.java b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/TEIImportSummaries.java index 05d93a6e11..c78b219832 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/TEIImportSummaries.java +++ b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/TEIImportSummaries.java @@ -50,7 +50,7 @@ public abstract class TEIImportSummaries extends BaseImportSummaries implements @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseImportSummaries.Builder { + public abstract static class Builder extends BaseImportSummaries.Builder { public abstract Builder importSummaries(List importSummaries); diff --git a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/TEIImportSummary.java b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/TEIImportSummary.java index 54a369acd6..de462a1c27 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/TEIImportSummary.java +++ b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/TEIImportSummary.java @@ -47,7 +47,7 @@ public abstract class TEIImportSummary extends BaseImportSummary { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseImportSummary.Builder { + public abstract static class Builder extends BaseImportSummary.Builder { public abstract Builder enrollments(EnrollmentImportSummaries enrollments); diff --git a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/TEIWebResponse.java b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/TEIWebResponse.java index e5e91b30f7..f49ccc15a8 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/TEIWebResponse.java +++ b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/TEIWebResponse.java @@ -60,7 +60,7 @@ public static TEIWebResponse empty() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends WebResponse.Builder { + public abstract static class Builder extends WebResponse.Builder { public abstract Builder response(TEIImportSummaries response); public abstract TEIWebResponse build(); diff --git a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/WebResponse.java b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/WebResponse.java index 3c4390786c..92ee12d21f 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/imports/internal/WebResponse.java +++ b/core/src/main/java/org/hisp/dhis/android/core/imports/internal/WebResponse.java @@ -47,7 +47,7 @@ public abstract class WebResponse { public abstract String message(); @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder { + public abstract static class Builder { public abstract T httpStatus(String httpStatus); public abstract T httpStatusCode(Integer httpStatusCode); diff --git a/core/src/main/java/org/hisp/dhis/android/core/indicator/Indicator.java b/core/src/main/java/org/hisp/dhis/android/core/indicator/Indicator.java index 915a38d8d0..393c09cc65 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/indicator/Indicator.java +++ b/core/src/main/java/org/hisp/dhis/android/core/indicator/Indicator.java @@ -103,7 +103,7 @@ public static Indicator create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseNameableObject.Builder + public abstract static class Builder extends BaseNameableObject.Builder implements ObjectWithStyle.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/indicator/IndicatorType.java b/core/src/main/java/org/hisp/dhis/android/core/indicator/IndicatorType.java index 518432be56..8be34c124d 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/indicator/IndicatorType.java +++ b/core/src/main/java/org/hisp/dhis/android/core/indicator/IndicatorType.java @@ -64,7 +64,7 @@ public static IndicatorType create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseIdentifiableObject.Builder { + public abstract static class Builder extends BaseIdentifiableObject.Builder { public abstract Builder id(Long id); public abstract Builder number(Boolean number); diff --git a/core/src/main/java/org/hisp/dhis/android/core/legendset/DataElementLegendSetLink.java b/core/src/main/java/org/hisp/dhis/android/core/legendset/DataElementLegendSetLink.java index dd3cde16cf..bd4318e80c 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/legendset/DataElementLegendSetLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/legendset/DataElementLegendSetLink.java @@ -60,7 +60,7 @@ public static Builder builder() { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); public abstract Builder dataElement(String dataElement); diff --git a/core/src/main/java/org/hisp/dhis/android/core/legendset/IndicatorLegendSetLink.java b/core/src/main/java/org/hisp/dhis/android/core/legendset/IndicatorLegendSetLink.java index 6d3a65fda0..e371779ac7 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/legendset/IndicatorLegendSetLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/legendset/IndicatorLegendSetLink.java @@ -59,7 +59,7 @@ public static Builder builder() { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); public abstract Builder indicator(String indicator); diff --git a/core/src/main/java/org/hisp/dhis/android/core/legendset/ProgramIndicatorLegendSetLink.java b/core/src/main/java/org/hisp/dhis/android/core/legendset/ProgramIndicatorLegendSetLink.java index c2a593d0e0..d7b133d92d 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/legendset/ProgramIndicatorLegendSetLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/legendset/ProgramIndicatorLegendSetLink.java @@ -60,7 +60,7 @@ public static Builder builder() { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); public abstract Builder programIndicator(String programIndicator); diff --git a/core/src/main/java/org/hisp/dhis/android/core/maintenance/D2Error.java b/core/src/main/java/org/hisp/dhis/android/core/maintenance/D2Error.java index c24fafef36..e81a54672f 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/maintenance/D2Error.java +++ b/core/src/main/java/org/hisp/dhis/android/core/maintenance/D2Error.java @@ -91,7 +91,7 @@ public boolean isOffline() { } @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder url(String url); diff --git a/core/src/main/java/org/hisp/dhis/android/core/maintenance/D2ErrorCode.java b/core/src/main/java/org/hisp/dhis/android/core/maintenance/D2ErrorCode.java index b62192d085..2eafbfa6fc 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/maintenance/D2ErrorCode.java +++ b/core/src/main/java/org/hisp/dhis/android/core/maintenance/D2ErrorCode.java @@ -45,6 +45,8 @@ public enum D2ErrorCode { DATABASE_EXPORT_LOGIN_FIRST, DATABASE_EXPORT_ENCRYPTED_NOT_SUPPORTED, DATABASE_IMPORT_ALREADY_EXISTS, + DATABASE_IMPORT_FAILED, + DATABASE_IMPORT_INVALID_FILE, DATABASE_IMPORT_LOGOUT_FIRST, DATABASE_IMPORT_VERSION_HIGHER_THAN_SUPPORTED, FILE_NOT_FOUND, diff --git a/core/src/main/java/org/hisp/dhis/android/core/maintenance/ForeignKeyViolation.java b/core/src/main/java/org/hisp/dhis/android/core/maintenance/ForeignKeyViolation.java index 4dad86990c..40e20baedf 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/maintenance/ForeignKeyViolation.java +++ b/core/src/main/java/org/hisp/dhis/android/core/maintenance/ForeignKeyViolation.java @@ -81,7 +81,7 @@ public static Builder builder() { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder fromTable(String fromTable); diff --git a/core/src/main/java/org/hisp/dhis/android/core/maintenance/MaintenanceModule.kt b/core/src/main/java/org/hisp/dhis/android/core/maintenance/MaintenanceModule.kt new file mode 100644 index 0000000000..28e84ce27b --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/maintenance/MaintenanceModule.kt @@ -0,0 +1,41 @@ +/* + * 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.maintenance + +import org.hisp.dhis.android.core.arch.db.access.DatabaseImportExport + +interface MaintenanceModule { + fun foreignKeyViolations(): ForeignKeyViolationCollectionRepository + fun d2Errors(): D2ErrorCollectionRepository + fun getPerformanceHintsService( + organisationUnitThreshold: Int, + programRulesPerProgramThreshold: Int, + ): PerformanceHintsService + + fun databaseImportExport(): DatabaseImportExport +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/maintenance/internal/MaintenanceModuleImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/maintenance/internal/MaintenanceModuleImpl.kt index a388f4c777..8d0f516acb 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/maintenance/internal/MaintenanceModuleImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/maintenance/internal/MaintenanceModuleImpl.kt @@ -28,6 +28,7 @@ package org.hisp.dhis.android.core.maintenance.internal import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.arch.db.access.DatabaseImportExport import org.hisp.dhis.android.core.maintenance.D2ErrorCollectionRepository import org.hisp.dhis.android.core.maintenance.ForeignKeyViolationCollectionRepository import org.hisp.dhis.android.core.maintenance.MaintenanceModule @@ -39,6 +40,7 @@ internal class MaintenanceModuleImpl( private val databaseAdapter: DatabaseAdapter, private val foreignKeyViolations: ForeignKeyViolationCollectionRepository, private val d2Errors: D2ErrorCollectionRepository, + private val databaseImportExport: DatabaseImportExport, ) : MaintenanceModule { override fun getPerformanceHintsService( organisationUnitThreshold: Int, @@ -58,4 +60,8 @@ internal class MaintenanceModuleImpl( override fun d2Errors(): D2ErrorCollectionRepository { return d2Errors } + + override fun databaseImportExport(): DatabaseImportExport { + return databaseImportExport + } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/AccessLevel.java b/core/src/main/java/org/hisp/dhis/android/core/map/layer/ImageFormat.kt similarity index 93% rename from core/src/main/java/org/hisp/dhis/android/core/program/AccessLevel.java rename to core/src/main/java/org/hisp/dhis/android/core/map/layer/ImageFormat.kt index 9f34313260..310e7c8031 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/program/AccessLevel.java +++ b/core/src/main/java/org/hisp/dhis/android/core/map/layer/ImageFormat.kt @@ -26,8 +26,8 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.hisp.dhis.android.core.program; +package org.hisp.dhis.android.core.map.layer -public enum AccessLevel { - OPEN, AUDITED, PROTECTED, CLOSED -} \ No newline at end of file +enum class ImageFormat { + PNG, JPG +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/map/layer/MapLayer.java b/core/src/main/java/org/hisp/dhis/android/core/map/layer/MapLayer.java index 4ca7bacfe7..0d89ada03b 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/map/layer/MapLayer.java +++ b/core/src/main/java/org/hisp/dhis/android/core/map/layer/MapLayer.java @@ -37,7 +37,9 @@ import com.google.auto.value.AutoValue; import org.hisp.dhis.android.core.arch.db.adapters.custom.internal.StringListColumnAdapter; +import org.hisp.dhis.android.core.arch.db.adapters.enums.internal.ImageFormatColumnAdapter; import org.hisp.dhis.android.core.arch.db.adapters.enums.internal.MapLayerPositionColumnAdapter; +import org.hisp.dhis.android.core.arch.db.adapters.enums.internal.MapServiceColumnAdapter; import org.hisp.dhis.android.core.arch.db.adapters.ignore.internal.IgnoreMapLayerImageryProviderColumnAdapter; import org.hisp.dhis.android.core.common.BaseObject; import org.hisp.dhis.android.core.common.ObjectWithUidInterface; @@ -83,6 +85,20 @@ public abstract class MapLayer extends BaseObject implements ObjectWithUidInterf @ColumnAdapter(IgnoreMapLayerImageryProviderColumnAdapter.class) public abstract List imageryProviders(); + @Nullable + public abstract String code(); + + @Nullable + @ColumnAdapter(MapServiceColumnAdapter.class) + public abstract MapService mapService(); + + @Nullable + @ColumnAdapter(ImageFormatColumnAdapter.class) + public abstract ImageFormat imageFormat(); + + @Nullable + public abstract String layers(); + public static MapLayer create(Cursor cursor) { return $AutoValue_MapLayer.createFromCursor(cursor); } @@ -102,6 +118,8 @@ public abstract static class Builder extends BaseObject.Builder { public abstract Builder displayName(String displayName); + public abstract Builder code(String code); + public abstract Builder external(Boolean external); public abstract Builder mapLayerPosition(MapLayerPosition mapLayerPosition); @@ -116,6 +134,12 @@ public abstract static class Builder extends BaseObject.Builder { public abstract Builder imageryProviders(List imageryProviders); + public abstract Builder mapService(MapService mapService); + + public abstract Builder imageFormat(ImageFormat imageFormat); + + public abstract Builder layers(String layers); + public abstract MapLayer build(); } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/map/layer/MapLayerCollectionRepository.kt b/core/src/main/java/org/hisp/dhis/android/core/map/layer/MapLayerCollectionRepository.kt index f45fad005a..3682237eea 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/map/layer/MapLayerCollectionRepository.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/map/layer/MapLayerCollectionRepository.kt @@ -39,6 +39,7 @@ import org.hisp.dhis.android.core.map.layer.internal.MapLayerImagerProviderChild import org.hisp.dhis.android.core.map.layer.internal.MapLayerStore import org.koin.core.annotation.Singleton +@Suppress("TooManyFunctions") @Singleton class MapLayerCollectionRepository internal constructor( store: MapLayerStore, @@ -66,6 +67,10 @@ class MapLayerCollectionRepository internal constructor( return cf.string(MapLayerTableInfo.Columns.DISPLAY_NAME) } + fun byCode(): StringFilterConnector { + return cf.string(MapLayerTableInfo.Columns.CODE) + } + fun byExternal(): BooleanFilterConnector { return cf.bool(MapLayerTableInfo.Columns.EXTERNAL) } @@ -86,6 +91,18 @@ class MapLayerCollectionRepository internal constructor( return cf.withChild(MapLayer.IMAGERY_PROVIDERS) } + fun byMapService(): EnumFilterConnector { + return cf.enumC(MapLayerTableInfo.Columns.MAP_SERVICE) + } + + fun byImageFormat(): EnumFilterConnector { + return cf.enumC(MapLayerTableInfo.Columns.IMAGE_FORMAT) + } + + fun byLayers(): StringFilterConnector { + return cf.string(MapLayerTableInfo.Columns.LAYERS) + } + internal companion object { val childrenAppenders: ChildrenAppenderGetter = mapOf( MapLayer.IMAGERY_PROVIDERS to ::MapLayerImagerProviderChildrenAppender, diff --git a/core/src/main/java/org/hisp/dhis/android/core/map/layer/MapLayerTableInfo.kt b/core/src/main/java/org/hisp/dhis/android/core/map/layer/MapLayerTableInfo.kt index 4104a06da3..0a98400fff 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/map/layer/MapLayerTableInfo.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/map/layer/MapLayerTableInfo.kt @@ -56,6 +56,10 @@ object MapLayerTableInfo { IMAGE_URL, SUBDOMAINS, SUBDOMAIN_PLACEHOLDER, + CODE, + MAP_SERVICE, + IMAGE_FORMAT, + LAYERS, ) } @@ -69,6 +73,10 @@ object MapLayerTableInfo { const val IMAGE_URL = "imageUrl" const val SUBDOMAINS = "subdomains" const val SUBDOMAIN_PLACEHOLDER = "subdomainPlaceholder" + const val CODE = "code" + const val MAP_SERVICE = "mapService" + const val IMAGE_FORMAT = "imageFormat" + const val LAYERS = "layers" } } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/map/layer/MapService.kt b/core/src/main/java/org/hisp/dhis/android/core/map/layer/MapService.kt new file mode 100644 index 0000000000..aa8c74f234 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/map/layer/MapService.kt @@ -0,0 +1,33 @@ +/* + * 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.map.layer + +enum class MapService { + TMS, XYZ, WMS, VECTOR_STYLE +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/MapLayerCallFactory.kt b/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/MapLayerCallFactory.kt index 6831154055..93e86561a3 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/MapLayerCallFactory.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/MapLayerCallFactory.kt @@ -31,6 +31,7 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toList import org.hisp.dhis.android.core.map.layer.MapLayer import org.hisp.dhis.android.core.map.layer.internal.bing.BingCallFactory +import org.hisp.dhis.android.core.map.layer.internal.externalmap.ExternalMapLayerCallFactory import org.hisp.dhis.android.core.map.layer.internal.osm.OSMCallFactory import org.koin.core.annotation.Singleton @@ -38,10 +39,14 @@ import org.koin.core.annotation.Singleton internal class MapLayerCallFactory( private val osmCallFactory: OSMCallFactory, private val bingCallFactory: BingCallFactory, + private val externalMapLayerCallFactory: ExternalMapLayerCallFactory, + private val mapLayerCollectionCleaner: MapLayerCollectionCleaner, ) { suspend fun downloadMetadata(): List { - return flowOf(osmCallFactory.download(), bingCallFactory.download()).toList() + return flowOf(osmCallFactory.download(), bingCallFactory.download(), externalMapLayerCallFactory.download()) + .toList() .flatten() + .also { mapLayerCollectionCleaner.deleteNotPresent(it) } } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/MapLayerCollectionCleaner.kt b/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/MapLayerCollectionCleaner.kt new file mode 100644 index 0000000000..5969ab9995 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/MapLayerCollectionCleaner.kt @@ -0,0 +1,42 @@ +/* + * 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.map.layer.internal + +import org.hisp.dhis.android.core.arch.cleaners.internal.CollectionCleanerImpl +import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.map.layer.MapLayer +import org.hisp.dhis.android.core.map.layer.MapLayerTableInfo +import org.koin.core.annotation.Singleton + +@Singleton +internal class MapLayerCollectionCleaner( + databaseAdapter: DatabaseAdapter, +) : CollectionCleanerImpl( + tableName = MapLayerTableInfo.TABLE_INFO.name(), + databaseAdapter = databaseAdapter, +) diff --git a/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/MapLayerStoreImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/MapLayerStoreImpl.kt index 7fc34ad18c..d85079890f 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/MapLayerStoreImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/MapLayerStoreImpl.kt @@ -60,6 +60,10 @@ internal class MapLayerStoreImpl( w.bind(7, o.imageUrl()) w.bind(8, StringListColumnAdapter.serialize(o.subdomains())) w.bind(9, o.subdomainPlaceholder()) + w.bind(10, o.code()) + w.bind(11, o.mapService()) + w.bind(12, o.imageFormat()) + w.bind(13, o.layers()) } } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/bing/BingBasemaps.kt b/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/bing/BingBasemaps.kt index 61dd599ede..d6bce4ecef 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/bing/BingBasemaps.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/bing/BingBasemaps.kt @@ -31,22 +31,22 @@ package org.hisp.dhis.android.core.map.layer.internal.bing internal object BingBasemaps { val list: List = listOf( BingBasemap( - id = "ql5jVkAL1iy", + id = "bingLight", name = "Bing Road", style = "CanvasLight", ), BingBasemap( - id = "PwJ1fQoTthh", + id = "bingDark", name = "Bing Dark", style = "CanvasDark", ), BingBasemap( - id = "kKJNmY2yYtM", + id = "bingAerial", name = "Bing Aerial", style = "Aerial", ), BingBasemap( - id = "TfK2zM71AHJ", + id = "bingHybrid", name = "Bing Aerial Labels", style = "AerialWithLabelsOnDemand", ), diff --git a/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/externalmap/ExternalMapLayer.kt b/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/externalmap/ExternalMapLayer.kt new file mode 100644 index 0000000000..d6ecbba57f --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/externalmap/ExternalMapLayer.kt @@ -0,0 +1,46 @@ +/* + * 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.map.layer.internal.externalmap + +import org.hisp.dhis.android.core.map.layer.ImageFormat +import org.hisp.dhis.android.core.map.layer.MapLayerPosition +import org.hisp.dhis.android.core.map.layer.MapService + +internal data class ExternalMapLayer( + val id: String, + val name: String, + val displayName: String, + val code: String? = null, + val url: String, + val attribution: String? = null, + val mapService: MapService, + val imageFormat: ImageFormat? = null, + val layers: String? = null, + val mapLayerPosition: MapLayerPosition, +) diff --git a/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/externalmap/ExternalMapLayerCallFactory.kt b/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/externalmap/ExternalMapLayerCallFactory.kt new file mode 100644 index 0000000000..4a51f6120b --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/externalmap/ExternalMapLayerCallFactory.kt @@ -0,0 +1,82 @@ +/* + * 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.map.layer.internal.externalmap + +import org.hisp.dhis.android.core.arch.api.executors.internal.APIDownloader +import org.hisp.dhis.android.core.map.layer.MapLayer +import org.hisp.dhis.android.core.map.layer.MapLayerImageryProvider +import org.hisp.dhis.android.core.map.layer.MapLayerPosition +import org.hisp.dhis.android.core.map.layer.internal.MapLayerHandler +import org.koin.core.annotation.Singleton + +@Singleton +internal class ExternalMapLayerCallFactory( + private val mapLayerHandler: MapLayerHandler, + val apiDownloader: APIDownloader, + val service: ExternalMapLayerService, +) { + + suspend fun download(): List { + val mapLayers = getExternalMapLayers() + mapLayerHandler.handleMany(mapLayers) + return mapLayers + } + + private suspend fun getExternalMapLayers(): List { + return service.getExternalMapLayers( + ExternalMapLayerFields.allFields, + ExternalMapLayerFields.mapLayerPosition.eq(MapLayerPosition.BASEMAP), + false, + ).items() + .map { externalMapLayer -> + MapLayer.builder() + .uid(externalMapLayer.id) + .name(externalMapLayer.name) + .displayName(externalMapLayer.displayName) + .code(externalMapLayer.code) + .mapLayerPosition(externalMapLayer.mapLayerPosition) + .mapService(externalMapLayer.mapService) + .imageFormat(externalMapLayer.imageFormat) + .layers(externalMapLayer.layers) + .external(true) + .imageUrl(externalMapLayer.url) + .imageryProviders( + externalMapLayer.attribution?.let { attribution -> + listOf( + MapLayerImageryProvider.builder() + .mapLayer(externalMapLayer.id) + .attribution(attribution) + .build(), + ) + } ?: emptyList(), + ) + .build() + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/externalmap/ExternalMapLayerFields.kt b/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/externalmap/ExternalMapLayerFields.kt new file mode 100644 index 0000000000..0793629a5f --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/externalmap/ExternalMapLayerFields.kt @@ -0,0 +1,57 @@ +/* + * 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.map.layer.internal.externalmap + +import org.hisp.dhis.android.core.arch.api.fields.internal.Field +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper +import org.hisp.dhis.android.core.map.layer.MapLayerImageryProviderTableInfo +import org.hisp.dhis.android.core.map.layer.MapLayerPosition +import org.hisp.dhis.android.core.map.layer.MapLayerTableInfo + +internal object ExternalMapLayerFields { + + private const val URL = "url" + + private val fh = FieldsHelper() + val uid = fh.uid() + + val mapLayerPosition: Field = + Field.create(MapLayerTableInfo.Columns.MAP_LAYER_POSITION) + + val allFields: Fields = Fields.builder() + .fields(fh.getIdentifiableFields()) + .fields( + fh.field(URL), + fh.field(MapLayerImageryProviderTableInfo.Columns.ATTRIBUTION), + fh.field(MapLayerTableInfo.Columns.MAP_SERVICE), + fh.field(MapLayerTableInfo.Columns.IMAGE_FORMAT), + fh.field(MapLayerTableInfo.Columns.LAYERS), + fh.field(MapLayerTableInfo.Columns.MAP_LAYER_POSITION), + ).build() +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/externalmap/ExternalMapLayerService.kt b/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/externalmap/ExternalMapLayerService.kt new file mode 100644 index 0000000000..54de345fde --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/externalmap/ExternalMapLayerService.kt @@ -0,0 +1,48 @@ +/* + * 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.map.layer.internal.externalmap + +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.arch.api.filters.internal.Filter +import org.hisp.dhis.android.core.arch.api.filters.internal.Where +import org.hisp.dhis.android.core.arch.api.filters.internal.Which +import org.hisp.dhis.android.core.arch.api.payload.internal.Payload +import org.hisp.dhis.android.core.map.layer.MapLayerPosition +import retrofit2.http.GET +import retrofit2.http.Query + +internal fun interface ExternalMapLayerService { + + @GET("externalMapLayers") + suspend fun getExternalMapLayers( + @Query("fields") @Which fields: Fields, + @Query("filter") @Where mapLayerPosition: Filter, + @Query("paging") paging: Boolean, + ): Payload +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/osm/OSMBaseMaps.kt b/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/osm/OSMBaseMaps.kt index e644ead7ea..b343ebcc9e 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/osm/OSMBaseMaps.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/map/layer/internal/osm/OSMBaseMaps.kt @@ -31,7 +31,7 @@ package org.hisp.dhis.android.core.map.layer.internal.osm object OSMBaseMaps { val list: List = listOf( OSMBaseMap( - id = "l7rimUxoQu4", + id = "osmLight", name = "OSM Light", imageUrl = "https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}@2x.png", subdomains = listOf("a", "b", "c", "d"), @@ -40,7 +40,7 @@ object OSMBaseMaps { " © Carto", ), OSMBaseMap( - id = "k6QEWMytadd", + id = "openStreetMap", name = "OSM Detailed", imageUrl = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", subdomains = listOf("a", "b", "c", "d"), diff --git a/core/src/main/java/org/hisp/dhis/android/core/mockwebserver/Dhis2MockServer.java b/core/src/main/java/org/hisp/dhis/android/core/mockwebserver/Dhis2MockServer.java index b86b31288b..22384f690c 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/mockwebserver/Dhis2MockServer.java +++ b/core/src/main/java/org/hisp/dhis/android/core/mockwebserver/Dhis2MockServer.java @@ -63,8 +63,9 @@ public class Dhis2MockServer { private static final String PROGRAM_SETTINGS_JSON = "settings/program_settings.json"; private static final String SYNCHRONIZATION_SETTTINGS_JSON = "settings/synchronization_settings.json"; private static final String APPEARANCE_SETTINGS_JSON = "settings/appearance_settings_v2.json"; - private static final String ANALYTICS_SETTINGS_JSON = "settings/analytics_settings_v2.json"; + private static final String ANALYTICS_SETTINGS_JSON = "settings/analytics_settings_v3.json"; private static final String USER_SETTINGS_JSON = "settings/user_settings.json"; + private static final String VERSIONS_JSON = "settings/versions.json"; private static final String LATEST_APP_VERSION_JSON = "settings/latest_app_version.json"; private static final String PROGRAMS_JSON = "program/programs.json"; private static final String PROGRAMS_INDICATORS_JSON = "program/program_indicators.json"; @@ -95,6 +96,7 @@ public class Dhis2MockServer { private static final String CATEGORY_OPTION_ORGUNITS_JSON = "category/category_option_orgunits.json"; private static final String VISUALIZATIONS_1_JSON = "visualization/visualizations_1.json"; private static final String VISUALIZATIONS_2_JSON = "visualization/visualizations_2.json"; + private static final String TRACKER_VISUALIZATIONS_1_JSON = "visualization/tracker_visualizations_1.json"; private static final String ORGANISATION_UNIT_LEVELS_JSON = "organisationunit/organisation_unit_levels.json"; private static final String CONSTANTS_JSON = "constant/constants.json"; private static final String USER_JSON = "user/user38.json"; @@ -102,6 +104,7 @@ public class Dhis2MockServer { private static final String NEW_EVENTS_JSON = "event/new_tracker_importer_events.json"; private static final String LEGEND_SETS_JSON = "legendset/legend_sets.json"; private static final String EXPRESSION_DIMENSION_ITEMS = "expressiondimensionitem/expression_dimension_items.json"; + private static final String CUSTOM_ICONS_JSON = "icon/custom_icons.json"; private static final String TRACKED_ENTITY_INSTANCES_JSON = "trackedentity/tracked_entity_instances.json"; private static final String NEW_TRACKED_ENTITY_INSTANCES_JSON = "trackedentity/new_tracker_importer_tracked_entities.json"; @@ -220,7 +223,9 @@ public MockResponse dispatch(RecordedRequest request) { return createMockResponse(ANALYTICS_SETTINGS_JSON); } else if (path.startsWith("/api/userSettings?")) { return createMockResponse(USER_SETTINGS_JSON); - } else if (path.startsWith("/api/dataStore/APK_DISTRIBUTION/latestVersion")) { + } else if (path.startsWith("/api/dataStore/APK_DISTRIBUTION/versions")) { + return createMockResponse(VERSIONS_JSON); + } else if (path.startsWith("/api/dataStore/APK_DISTRIBUTION/latestVersion")) { return createMockResponse(LATEST_APP_VERSION_JSON); } else if (path.startsWith("/api/programs?")) { return createMockResponse(PROGRAMS_JSON); @@ -274,6 +279,8 @@ public MockResponse dispatch(RecordedRequest request) { return createMockResponse(VISUALIZATIONS_1_JSON); } else if (path.startsWith("/api/visualizations/FAFa11yFeFe?")) { return createMockResponse(VISUALIZATIONS_2_JSON); + } else if (path.startsWith("/api/eventVisualizations/s85urBIkN0z?")) { + return createMockResponse(TRACKER_VISUALIZATIONS_1_JSON); } else if (path.startsWith("/api/organisationUnits?")) { return createMockResponse(ORGANISATION_UNITS_JSON); } else if (path.startsWith("/api/organisationUnitLevels?")) { @@ -298,6 +305,8 @@ public MockResponse dispatch(RecordedRequest request) { return createMockResponse(LEGEND_SETS_JSON); } else if (path.startsWith("/api/expressionDimensionItems?")) { return createMockResponse(EXPRESSION_DIMENSION_ITEMS); + } else if (path.startsWith("/api/icons?")) { + return createMockResponse(CUSTOM_ICONS_JSON); } else if (path.startsWith("/api/trackedEntityAttributes/aejWyOfXge6/generateAndReserve")) { return createMockResponse(RESERVE_VALUES_JSON); } else if (path.startsWith("/api/metadata")) { @@ -343,7 +352,7 @@ public void enqueueMetadataResponses() { enqueueMockResponse(ANALYTICS_SETTINGS_JSON); enqueueMockResponse(USER_SETTINGS_JSON); enqueueMockResponse(SYSTEM_SETTINGS_JSON); - enqueueMockResponse(LATEST_APP_VERSION_JSON); + enqueueMockResponse(VERSIONS_JSON); enqueueMockResponse(STOCK_USE_CASES_JSON); enqueueMockResponse(CONSTANTS_JSON); enqueueMockResponse(USER_JSON); @@ -374,6 +383,8 @@ public void enqueueMetadataResponses() { enqueueMockResponse(CATEGORY_OPTION_ORGUNITS_JSON); enqueueMockResponse(VISUALIZATIONS_1_JSON); enqueueMockResponse(VISUALIZATIONS_2_JSON); + enqueueMockResponse(404); + enqueueMockResponse(TRACKER_VISUALIZATIONS_1_JSON); enqueueMockResponse(PROGRAMS_INDICATORS_JSON); enqueueMockResponse(PROGRAMS_INDICATORS_JSON); enqueueMockResponse(INDICATORS_JSON); @@ -381,6 +392,7 @@ public void enqueueMetadataResponses() { enqueueMockResponse(LEGEND_SETS_JSON); enqueueMockResponse(ATTRIBUTES_JSON); enqueueMockResponse(EXPRESSION_DIMENSION_ITEMS); + enqueueMockResponse(CUSTOM_ICONS_JSON); } private MockResponse createMockResponse(String fileName) { diff --git a/core/src/main/java/org/hisp/dhis/android/core/note/NewTrackerImporterNote.java b/core/src/main/java/org/hisp/dhis/android/core/note/NewTrackerImporterNote.java index b80dce910f..53f09361c6 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/note/NewTrackerImporterNote.java +++ b/core/src/main/java/org/hisp/dhis/android/core/note/NewTrackerImporterNote.java @@ -90,7 +90,7 @@ public static NewTrackerImporterNote create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseDeletableDataObject.Builder { + public abstract static class Builder extends BaseDeletableDataObject.Builder { public abstract Builder id(Long id); @JsonProperty(NoteFields.UID) diff --git a/core/src/main/java/org/hisp/dhis/android/core/note/Note.java b/core/src/main/java/org/hisp/dhis/android/core/note/Note.java index d5d3e07dfd..01f2127828 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/note/Note.java +++ b/core/src/main/java/org/hisp/dhis/android/core/note/Note.java @@ -94,7 +94,7 @@ public static Note create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseDeletableDataObject.Builder { + public abstract static class Builder extends BaseDeletableDataObject.Builder { public abstract Builder id(Long id); @JsonProperty(NoteFields.UID) diff --git a/core/src/main/java/org/hisp/dhis/android/core/note/NoteCreateProjection.java b/core/src/main/java/org/hisp/dhis/android/core/note/NoteCreateProjection.java index 0ec8ab9ee6..8b446a990e 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/note/NoteCreateProjection.java +++ b/core/src/main/java/org/hisp/dhis/android/core/note/NoteCreateProjection.java @@ -90,7 +90,7 @@ public static Builder builder() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder { + public abstract static class Builder { public abstract Builder noteType(Note.NoteType noteType); public abstract Builder event(String event); diff --git a/core/src/main/java/org/hisp/dhis/android/core/option/Option.java b/core/src/main/java/org/hisp/dhis/android/core/option/Option.java index aba82b4bf7..f5f9001210 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/option/Option.java +++ b/core/src/main/java/org/hisp/dhis/android/core/option/Option.java @@ -71,7 +71,7 @@ public static Option create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseIdentifiableObject.Builder + public abstract static class Builder extends BaseIdentifiableObject.Builder implements ObjectWithStyle.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/option/OptionGroup.java b/core/src/main/java/org/hisp/dhis/android/core/option/OptionGroup.java index 3b8455fed6..0eacbdbc5e 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/option/OptionGroup.java +++ b/core/src/main/java/org/hisp/dhis/android/core/option/OptionGroup.java @@ -72,7 +72,7 @@ public static OptionGroup create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseIdentifiableObject.Builder { + public abstract static class Builder extends BaseIdentifiableObject.Builder { public abstract Builder id(Long id); public abstract Builder optionSet(@Nullable ObjectWithUid optionSet); diff --git a/core/src/main/java/org/hisp/dhis/android/core/option/OptionGroupOptionLink.java b/core/src/main/java/org/hisp/dhis/android/core/option/OptionGroupOptionLink.java index d7673e749a..29db4b235c 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/option/OptionGroupOptionLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/option/OptionGroupOptionLink.java @@ -57,7 +57,7 @@ public static Builder builder() { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); public abstract Builder optionGroup(String optionGroup); diff --git a/core/src/main/java/org/hisp/dhis/android/core/option/OptionSet.java b/core/src/main/java/org/hisp/dhis/android/core/option/OptionSet.java index 55ce2d7847..52c03cff83 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/option/OptionSet.java +++ b/core/src/main/java/org/hisp/dhis/android/core/option/OptionSet.java @@ -68,7 +68,7 @@ public static OptionSet create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseIdentifiableObject.Builder { + public abstract static class Builder extends BaseIdentifiableObject.Builder { public abstract Builder id(Long id); public abstract Builder version(@Nullable Integer version); diff --git a/core/src/main/java/org/hisp/dhis/android/core/organisationunit/OrganisationUnitOrganisationUnitGroupLink.java b/core/src/main/java/org/hisp/dhis/android/core/organisationunit/OrganisationUnitOrganisationUnitGroupLink.java index a128926199..22857b2d8c 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/organisationunit/OrganisationUnitOrganisationUnitGroupLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/organisationunit/OrganisationUnitOrganisationUnitGroupLink.java @@ -57,7 +57,7 @@ public static OrganisationUnitOrganisationUnitGroupLink create(Cursor cursor) { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/organisationunit/OrganisationUnitProgramLink.java b/core/src/main/java/org/hisp/dhis/android/core/organisationunit/OrganisationUnitProgramLink.java index c64c2aca79..0b03d8ed16 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/organisationunit/OrganisationUnitProgramLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/organisationunit/OrganisationUnitProgramLink.java @@ -56,7 +56,7 @@ public static OrganisationUnitProgramLink create(Cursor cursor) { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/parser/internal/expression/ParserUtils.kt b/core/src/main/java/org/hisp/dhis/android/core/parser/internal/expression/ParserUtils.kt index dbe9b1c711..dc4e29a58f 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/parser/internal/expression/ParserUtils.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/parser/internal/expression/ParserUtils.kt @@ -93,6 +93,8 @@ internal object ParserUtils { ExpressionParser.LEAST to FunctionLeast(), ExpressionParser.LOG to FunctionLog(), ExpressionParser.LOG10 to FunctionLog10(), + ExpressionParser.CONTAINS to FunctionContains(), + ExpressionParser.CONTAINS_ITEMS to FunctionContainsItems(), // Common variables ExpressionParser.OUG_BRACE to ItemOrgUnitGroup(), diff --git a/core/src/main/java/org/hisp/dhis/android/core/parser/internal/expression/function/FunctionContains.kt b/core/src/main/java/org/hisp/dhis/android/core/parser/internal/expression/function/FunctionContains.kt new file mode 100644 index 0000000000..293f00f486 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/parser/internal/expression/function/FunctionContains.kt @@ -0,0 +1,53 @@ +/* + * 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.parser.internal.expression.function + +import org.hisp.dhis.android.core.parser.internal.expression.CommonExpressionVisitor +import org.hisp.dhis.android.core.parser.internal.expression.ExpressionItem +import org.hisp.dhis.parser.expression.antlr.ExpressionParser.ExprContext + +internal class FunctionContains : ExpressionItem { + + override fun evaluate(ctx: ExprContext, visitor: CommonExpressionVisitor): Any { + val values = ctx.expr().map(visitor::castStringVisit) + + val targetValue = values[0] + val containedValues = values.drop(1) + + return containedValues.all { targetValue.contains(it) } + } + + override fun getSql(ctx: ExprContext, visitor: CommonExpressionVisitor): Any { + val values = ctx.expr().map(visitor::castStringVisit) + + val targetValue = values[0] + val containedValues = values.drop(1).map { it.trimStart('\'').trimEnd('\'') } + + return "(${containedValues.joinToString(" AND ") { "$targetValue LIKE '%$it%'" }})" + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipItemRelatives.java b/core/src/main/java/org/hisp/dhis/android/core/parser/internal/expression/function/FunctionContainsItems.kt similarity index 58% rename from core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipItemRelatives.java rename to core/src/main/java/org/hisp/dhis/android/core/parser/internal/expression/function/FunctionContainsItems.kt index 970ea9b1e3..2ecc24c7b7 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipItemRelatives.java +++ b/core/src/main/java/org/hisp/dhis/android/core/parser/internal/expression/function/FunctionContainsItems.kt @@ -25,45 +25,32 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +package org.hisp.dhis.android.core.parser.internal.expression.function -package org.hisp.dhis.android.core.relationship.internal; +import org.hisp.dhis.android.core.parser.internal.expression.CommonExpressionVisitor +import org.hisp.dhis.android.core.parser.internal.expression.ExpressionItem +import org.hisp.dhis.parser.expression.antlr.ExpressionParser.ExprContext -import java.util.HashSet; -import java.util.Set; +internal class FunctionContainsItems : ExpressionItem { -public class RelationshipItemRelatives { + override fun evaluate(ctx: ExprContext, visitor: CommonExpressionVisitor): Any { + val values = ctx.expr().map(visitor::castStringVisit) - private final Set relativeTrackedEntityInstanceUids; - private final Set relativeEnrollmentUids; - private final Set relativeEventUids; + val targetValues = values[0].split(",") + val containedValues = values.drop(1) - public RelationshipItemRelatives() { - this.relativeTrackedEntityInstanceUids = new HashSet<>(); - this.relativeEnrollmentUids = new HashSet<>(); - this.relativeEventUids = new HashSet<>(); + return containedValues.all { targetValues.contains(it) } } - public void addTrackedEntityInstance(String uid) { - this.relativeTrackedEntityInstanceUids.add(uid); - } - - public void addEnrollment(String uid) { - this.relativeEnrollmentUids.add(uid); - } - - public void addEvent(String uid) { - this.relativeEventUids.add(uid); - } - - public Set getRelativeTrackedEntityInstanceUids() { - return relativeTrackedEntityInstanceUids; - } + override fun getSql(ctx: ExprContext, visitor: CommonExpressionVisitor): Any { + val values = ctx.expr().map(visitor::castStringVisit) - public Set getRelativeEnrollmentUids() { - return relativeEnrollmentUids; - } + val targetValue = values[0] + val containedValues = values.drop(1).map { it.trimStart('\'').trimEnd('\'') } - public Set getRelativeEventUids() { - return relativeEventUids; + val sql = "(${containedValues.joinToString(" AND ") { + "($targetValue LIKE '%,$it' OR $targetValue LIKE '$it,%' OR $targetValue LIKE '%,$it,%')" + }})" + return sql } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/parser/internal/service/ExpressionService.kt b/core/src/main/java/org/hisp/dhis/android/core/parser/internal/service/ExpressionService.kt index fadf434e17..9036864131 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/parser/internal/service/ExpressionService.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/parser/internal/service/ExpressionService.kt @@ -67,13 +67,17 @@ internal class ExpressionService( ) } - fun getDimensionalItemIds(expression: String?): Set { + private fun getDimensionalItemIds(expression: String?): Set { return if (expression == null) { emptySet() } else { - val visitor = newVisitor(ITEM_GET_IDS, emptyMap()) - visit(expression, visitor) - visitor.itemIds + try { + val visitor = newVisitor(ITEM_GET_IDS, emptyMap()) + visit(expression, visitor) + visitor.itemIds + } catch (e: ParserException) { + emptySet() + } } } @@ -95,10 +99,14 @@ internal class ExpressionService( return if (expression == null) { "" } else { - val visitor = newVisitor(ITEM_GET_DESCRIPTIONS, constantMap) - visit(expression, visitor) - visitor.itemDescriptions.entries.fold(expression) { acc, (key, value) -> - acc.replace(key, value) + try { + val visitor = newVisitor(ITEM_GET_DESCRIPTIONS, constantMap) + visit(expression, visitor) + visitor.itemDescriptions.entries.fold(expression) { acc, (key, value) -> + acc.replace(key, value) + } + } catch (e: ParserException) { + "" } } } @@ -115,6 +123,7 @@ internal class ExpressionService( expression: String?, context: ExpressionServiceContext, missingValueStrategy: MissingValueStrategy, + ignoreParseErrors: Boolean = true, ): Any? { return expression?.let { val visitor = newVisitor( @@ -130,7 +139,11 @@ internal class ExpressionService( val value = try { visit(expression, visitor) } catch (e: ParserException) { - null + if (ignoreParseErrors) { + null + } else { + throw e + } } val itemsFound = visitor.state.itemsFound @@ -144,6 +157,7 @@ internal class ExpressionService( getHandledValue(value) } } + MissingValueStrategy.SKIP_IF_ALL_VALUES_MISSING -> { if (itemsFound != 0 && itemValuesFound == 0) { null @@ -151,6 +165,7 @@ internal class ExpressionService( getHandledValue(value) } } + MissingValueStrategy.NEVER_SKIP -> getHandledValue(value) } } @@ -163,17 +178,21 @@ internal class ExpressionService( return if (expression == null) { "" } else { - val visitor = newVisitor( - ITEM_REGENERATE, - context.constantMap, - ) - - val itemValueMap = context.valueMap.map { it.key.dimensionItem to it.value }.toMap() - visitor.itemValueMap = itemValueMap - visitor.orgUnitCountMap = context.orgUnitCountMap - visitor.setExpressionLiteral(RegenerateLiteral()) - visitor.days = context.days?.toDouble() - visit(expression, visitor) as String + try { + val visitor = newVisitor( + ITEM_REGENERATE, + context.constantMap, + ) + + val itemValueMap = context.valueMap.map { it.key.dimensionItem to it.value }.toMap() + visitor.itemValueMap = itemValueMap + visitor.orgUnitCountMap = context.orgUnitCountMap + visitor.setExpressionLiteral(RegenerateLiteral()) + visitor.days = context.days?.toDouble() + visit(expression, visitor) as String + } catch (e: ParserException) { + "" + } } } // ------------------------------------------------------------------------- diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/AccessLevel.kt b/core/src/main/java/org/hisp/dhis/android/core/program/AccessLevel.kt new file mode 100644 index 0000000000..1af476f148 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/program/AccessLevel.kt @@ -0,0 +1,35 @@ +/* + * 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.program + +enum class AccessLevel { + OPEN, + AUDITED, + PROTECTED, + CLOSED, +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/Program.java b/core/src/main/java/org/hisp/dhis/android/core/program/Program.java index 7a59dd61ef..145c6c3a52 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/program/Program.java +++ b/core/src/main/java/org/hisp/dhis/android/core/program/Program.java @@ -203,6 +203,38 @@ public String categoryComboUid() { @ColumnAdapter(AccessLevelColumnAdapter.class) public abstract AccessLevel accessLevel(); + @Nullable + @JsonProperty() + public abstract String enrollmentLabel(); + + @Nullable + @JsonProperty() + public abstract String followUpLabel(); + + @Nullable + @JsonProperty() + public abstract String orgUnitLabel(); + + @Nullable + @JsonProperty() + public abstract String relationshipLabel(); + + @Nullable + @JsonProperty() + public abstract String noteLabel(); + + @Nullable + @JsonProperty() + public abstract String trackedEntityAttributeLabel(); + + @Nullable + @JsonProperty() + public abstract String programStageLabel(); + + @Nullable + @JsonProperty() + public abstract String eventLabel(); + @Nullable @JsonProperty() @ColumnAdapter(IgnoreAttributeValuesListAdapter.class) @@ -220,7 +252,7 @@ public static Program create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseNameableObject.Builder + public abstract static class Builder extends BaseNameableObject.Builder implements ObjectWithStyle.Builder { public abstract Builder id(Long id); @@ -286,6 +318,22 @@ abstract Builder programTrackedEntityAttributes(List attributeValues); abstract Program autoBuild(); diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/ProgramCollectionRepository.kt b/core/src/main/java/org/hisp/dhis/android/core/program/ProgramCollectionRepository.kt index 795b056105..4a8f475cb8 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/program/ProgramCollectionRepository.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/program/ProgramCollectionRepository.kt @@ -165,6 +165,38 @@ class ProgramCollectionRepository internal constructor( return cf.enumC(ProgramTableInfo.Columns.ACCESS_LEVEL) } + fun byEnrollmentLabel(): StringFilterConnector { + return cf.string(ProgramTableInfo.Columns.ENROLLMENT_LABEL) + } + + fun byFollowUpLabel(): StringFilterConnector { + return cf.string(ProgramTableInfo.Columns.FOLLOW_UP_LABEL) + } + + fun byOrgUnitLabel(): StringFilterConnector { + return cf.string(ProgramTableInfo.Columns.ORG_UNIT_LABEL) + } + + fun byRelationshipLabel(): StringFilterConnector { + return cf.string(ProgramTableInfo.Columns.RELATIONSHIP_LABEL) + } + + fun byNoteLabel(): StringFilterConnector { + return cf.string(ProgramTableInfo.Columns.NOTE_LABEL) + } + + fun byTrackedEntityAttributeLabel(): StringFilterConnector { + return cf.string(ProgramTableInfo.Columns.TRACKED_ENTITY_ATTRIBUTE_LABEL) + } + + fun byProgramStageLabel(): StringFilterConnector { + return cf.string(ProgramTableInfo.Columns.PROGRAM_STAGE_LABEL) + } + + fun byEventLabel(): StringFilterConnector { + return cf.string(ProgramTableInfo.Columns.EVENT_LABEL) + } + fun byColor(): StringFilterConnector { return cf.string(ProgramTableInfo.Columns.COLOR) } diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/ProgramRule.java b/core/src/main/java/org/hisp/dhis/android/core/program/ProgramRule.java index 2f072bf34d..302a0034ab 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/program/ProgramRule.java +++ b/core/src/main/java/org/hisp/dhis/android/core/program/ProgramRule.java @@ -85,7 +85,7 @@ public static Builder builder() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseIdentifiableObject.Builder { + public abstract static class Builder extends BaseIdentifiableObject.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/ProgramSectionAttributeLink.java b/core/src/main/java/org/hisp/dhis/android/core/program/ProgramSectionAttributeLink.java index c688553157..9186063073 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/program/ProgramSectionAttributeLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/program/ProgramSectionAttributeLink.java @@ -59,7 +59,7 @@ public static ProgramSectionAttributeLink create(Cursor cursor) { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/ProgramStage.java b/core/src/main/java/org/hisp/dhis/android/core/program/ProgramStage.java index 5fa3b9b3d9..e71fd0f262 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/program/ProgramStage.java +++ b/core/src/main/java/org/hisp/dhis/android/core/program/ProgramStage.java @@ -193,6 +193,14 @@ public abstract class ProgramStage extends BaseIdentifiableObject @ColumnAdapter(ValidationStrategyColumnAdapter.class) public abstract ValidationStrategy validationStrategy(); + @Nullable + @JsonProperty + public abstract String programStageLabel(); + + @Nullable + @JsonProperty + public abstract String eventLabel(); + @Nullable @JsonProperty() @ColumnAdapter(IgnoreAttributeValuesListAdapter.class) @@ -210,7 +218,7 @@ public static ProgramStage create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseIdentifiableObject.Builder + public abstract static class Builder extends BaseIdentifiableObject.Builder implements ObjectWithStyle.Builder { public abstract Builder id(Long id); @@ -274,6 +282,10 @@ public static abstract class Builder extends BaseIdentifiableObject.Builder attributeValues); abstract ProgramStage autoBuild(); diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/ProgramStageCollectionRepository.kt b/core/src/main/java/org/hisp/dhis/android/core/program/ProgramStageCollectionRepository.kt index 9b27c2806b..078fa17a46 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/program/ProgramStageCollectionRepository.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/program/ProgramStageCollectionRepository.kt @@ -167,6 +167,14 @@ class ProgramStageCollectionRepository internal constructor( return cf.enumC(ProgramStageTableInfo.Columns.VALIDATION_STRATEGY) } + fun byProgramStageLabel(): StringFilterConnector { + return cf.string(ProgramStageTableInfo.Columns.PROGRAM_STAGE_LABEL) + } + + fun byEventLabel(): StringFilterConnector { + return cf.string(ProgramStageTableInfo.Columns.EVENT_LABEL) + } + fun byColor(): StringFilterConnector { return cf.string(ProgramStageTableInfo.Columns.COLOR) } diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/ProgramStageSectionDataElementLink.java b/core/src/main/java/org/hisp/dhis/android/core/program/ProgramStageSectionDataElementLink.java index 723b15f7a3..12a96df842 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/program/ProgramStageSectionDataElementLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/program/ProgramStageSectionDataElementLink.java @@ -59,7 +59,7 @@ public static ProgramStageSectionDataElementLink create(Cursor cursor) { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/ProgramStageSectionProgramIndicatorLink.java b/core/src/main/java/org/hisp/dhis/android/core/program/ProgramStageSectionProgramIndicatorLink.java index d22a3f9dfe..1f00f2214e 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/program/ProgramStageSectionProgramIndicatorLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/program/ProgramStageSectionProgramIndicatorLink.java @@ -57,7 +57,7 @@ public static ProgramStageSectionProgramIndicatorLink create(Cursor cursor) { @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/ProgramStageTableInfo.java b/core/src/main/java/org/hisp/dhis/android/core/program/ProgramStageTableInfo.java index 7eef0836ff..997b2841ef 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/program/ProgramStageTableInfo.java +++ b/core/src/main/java/org/hisp/dhis/android/core/program/ProgramStageTableInfo.java @@ -77,6 +77,8 @@ public static class Columns extends IdentifiableWithStyleColumns { public static final String VALIDATION_STRATEGY = "validationStrategy"; public static final String ACCESS_DATA_WRITE = "accessDataWrite"; public static final String ENABLE_USER_ASSIGNMENT = "enableUserAssignment"; + public static final String PROGRAM_STAGE_LABEL = "programStageLabel"; + public static final String EVENT_LABEL = "eventLabel"; @Override public String[] all() { @@ -105,7 +107,9 @@ public String[] all() { REMIND_COMPLETED, FEATURE_TYPE, ENABLE_USER_ASSIGNMENT, - VALIDATION_STRATEGY + VALIDATION_STRATEGY, + PROGRAM_STAGE_LABEL, + EVENT_LABEL ); } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/ProgramTableInfo.java b/core/src/main/java/org/hisp/dhis/android/core/program/ProgramTableInfo.java index 90852e5b78..de8073df57 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/program/ProgramTableInfo.java +++ b/core/src/main/java/org/hisp/dhis/android/core/program/ProgramTableInfo.java @@ -75,6 +75,14 @@ public static class Columns extends NameableWithStyleColumns { public static final String MAX_TEI_COUNT_TO_RETURN = "maxTeiCountToReturn"; public static final String FEATURE_TYPE = "featureType"; public static final String ACCESS_LEVEL = "accessLevel"; + public static final String ENROLLMENT_LABEL = "enrollmentLabel"; + public static final String FOLLOW_UP_LABEL = "followUpLabel"; + public static final String ORG_UNIT_LABEL = "orgUnitLabel"; + public static final String RELATIONSHIP_LABEL = "relationshipLabel"; + public static final String NOTE_LABEL = "noteLabel"; + public static final String TRACKED_ENTITY_ATTRIBUTE_LABEL = "trackedEntityAttributeLabel"; + public static final String PROGRAM_STAGE_LABEL = "programStageLabel"; + public static final String EVENT_LABEL = "eventLabel"; @Override public String[] all() { @@ -102,7 +110,15 @@ public String[] all() { MIN_ATTRIBUTES_REQUIRED_TO_SEARCH, MAX_TEI_COUNT_TO_RETURN, FEATURE_TYPE, - ACCESS_LEVEL + ACCESS_LEVEL, + ENROLLMENT_LABEL, + FOLLOW_UP_LABEL, + ORG_UNIT_LABEL, + RELATIONSHIP_LABEL, + NOTE_LABEL, + TRACKED_ENTITY_ATTRIBUTE_LABEL, + PROGRAM_STAGE_LABEL, + EVENT_LABEL ); } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramFields.java b/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramFields.java deleted file mode 100644 index bedd1a6b75..0000000000 --- a/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramFields.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) 2004-2023, University of Oslo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * Neither the name of the HISP project nor the names of its contributors may - * be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.hisp.dhis.android.core.program.internal; - -import org.hisp.dhis.android.core.arch.api.fields.internal.Field; -import org.hisp.dhis.android.core.arch.api.fields.internal.Fields; -import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper; -import org.hisp.dhis.android.core.attribute.AttributeValue; -import org.hisp.dhis.android.core.attribute.internal.AttributeValuesFields; -import org.hisp.dhis.android.core.common.Access; -import org.hisp.dhis.android.core.common.FeatureType; -import org.hisp.dhis.android.core.common.ObjectStyle; -import org.hisp.dhis.android.core.common.internal.AccessFields; -import org.hisp.dhis.android.core.common.internal.DataAccessFields; -import org.hisp.dhis.android.core.common.objectstyle.internal.ObjectStyleFields; -import org.hisp.dhis.android.core.period.PeriodType; -import org.hisp.dhis.android.core.program.AccessLevel; -import org.hisp.dhis.android.core.program.Program; -import org.hisp.dhis.android.core.program.ProgramRuleVariable; -import org.hisp.dhis.android.core.program.ProgramSection; -import org.hisp.dhis.android.core.program.ProgramTableInfo.Columns; -import org.hisp.dhis.android.core.program.ProgramTrackedEntityAttribute; -import org.hisp.dhis.android.core.program.ProgramType; - -public final class ProgramFields { - public static final String PROGRAM_TRACKED_ENTITY_ATTRIBUTES = "programTrackedEntityAttributes"; - private static final String CAPTURE_COORDINATES = "captureCoordinates"; - public static final String PROGRAM_RULE_VARIABLES = "programRuleVariables"; - private static final String ACCESS = "access"; - private static final String STYLE = "style"; - public static final String PROGRAM_SECTIONS = "programSections"; - public static final String ATTRIBUTE_VALUES = "attributeValues"; - - private static FieldsHelper fh = new FieldsHelper<>(); - - public static final Field uid = fh.uid(); - - static final Fields allFields = Fields.builder() - .fields(fh.getNameableFields()) - .fields( - fh.field(Columns.VERSION), - fh.field(Columns.ONLY_ENROLL_ONCE), - fh.field(Columns.ENROLLMENT_DATE_LABEL), - fh.field(Columns.DISPLAY_INCIDENT_DATE), - fh.field(Columns.INCIDENT_DATE_LABEL), - fh.field(Columns.REGISTRATION), - fh.field(Columns.SELECT_ENROLLMENT_DATES_IN_FUTURE), - fh.field(Columns.DATA_ENTRY_METHOD), - fh.field(Columns.IGNORE_OVERDUE_EVENTS), - fh.field(Columns.SELECT_INCIDENT_DATES_IN_FUTURE), - fh.field(CAPTURE_COORDINATES), - fh.field(Columns.USE_FIRST_STAGE_DURING_REGISTRATION), - fh.field(Columns.DISPLAY_FRONT_PAGE_LIST), - fh.field(Columns.PROGRAM_TYPE), - fh.nestedField(PROGRAM_TRACKED_ENTITY_ATTRIBUTES).with( - ProgramTrackedEntityAttributeFields.allFields), - fh.nestedFieldWithUid(Columns.RELATED_PROGRAM), - fh.nestedFieldWithUid(Columns.TRACKED_ENTITY_TYPE), - fh.nestedFieldWithUid(Columns.CATEGORY_COMBO), - fh.nestedField(ACCESS).with(AccessFields.data.with(DataAccessFields.write)), - fh.nestedField(PROGRAM_RULE_VARIABLES) - .with(ProgramRuleVariableFields.allFields), - fh.nestedField(STYLE).with(ObjectStyleFields.allFields), - fh.field(Columns.EXPIRY_DAYS), - fh.field(Columns.COMPLETE_EVENTS_EXPIRY_DAYS), - fh.field(Columns.EXPIRY_PERIOD_TYPE), - fh.field(Columns.MIN_ATTRIBUTES_REQUIRED_TO_SEARCH), - fh.field(Columns.MAX_TEI_COUNT_TO_RETURN), - fh.field(Columns.FEATURE_TYPE), - fh.field(Columns.ACCESS_LEVEL), - fh.nestedField(PROGRAM_SECTIONS).with(ProgramSectionFields.allFields), - fh.nestedField(ATTRIBUTE_VALUES).with(AttributeValuesFields.allFields) - - ).build(); - - private ProgramFields() { - } -} \ No newline at end of file diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramFields.kt b/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramFields.kt new file mode 100644 index 0000000000..89b232d859 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramFields.kt @@ -0,0 +1,107 @@ +/* + * 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.program.internal + +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper +import org.hisp.dhis.android.core.attribute.AttributeValue +import org.hisp.dhis.android.core.attribute.internal.AttributeValuesFields +import org.hisp.dhis.android.core.common.Access +import org.hisp.dhis.android.core.common.FeatureType +import org.hisp.dhis.android.core.common.ObjectStyle +import org.hisp.dhis.android.core.common.internal.AccessFields +import org.hisp.dhis.android.core.common.internal.DataAccessFields +import org.hisp.dhis.android.core.common.objectstyle.internal.ObjectStyleFields +import org.hisp.dhis.android.core.period.PeriodType +import org.hisp.dhis.android.core.program.AccessLevel +import org.hisp.dhis.android.core.program.Program +import org.hisp.dhis.android.core.program.ProgramRuleVariable +import org.hisp.dhis.android.core.program.ProgramSection +import org.hisp.dhis.android.core.program.ProgramTableInfo +import org.hisp.dhis.android.core.program.ProgramTrackedEntityAttribute +import org.hisp.dhis.android.core.program.ProgramType + +internal object ProgramFields { + const val PROGRAM_TRACKED_ENTITY_ATTRIBUTES = "programTrackedEntityAttributes" + private const val CAPTURE_COORDINATES = "captureCoordinates" + const val PROGRAM_RULE_VARIABLES = "programRuleVariables" + private const val ACCESS = "access" + private const val STYLE = "style" + const val PROGRAM_SECTIONS = "programSections" + const val ATTRIBUTE_VALUES = "attributeValues" + + private val fh = FieldsHelper() + + val uid = fh.uid() + + val allFields: Fields = Fields.builder() + .fields(fh.getNameableFields()) + .fields( + fh.field(ProgramTableInfo.Columns.VERSION), + fh.field(ProgramTableInfo.Columns.ONLY_ENROLL_ONCE), + fh.field(ProgramTableInfo.Columns.ENROLLMENT_DATE_LABEL), + fh.field(ProgramTableInfo.Columns.DISPLAY_INCIDENT_DATE), + fh.field(ProgramTableInfo.Columns.INCIDENT_DATE_LABEL), + fh.field(ProgramTableInfo.Columns.REGISTRATION), + fh.field(ProgramTableInfo.Columns.SELECT_ENROLLMENT_DATES_IN_FUTURE), + fh.field(ProgramTableInfo.Columns.DATA_ENTRY_METHOD), + fh.field(ProgramTableInfo.Columns.IGNORE_OVERDUE_EVENTS), + fh.field(ProgramTableInfo.Columns.SELECT_INCIDENT_DATES_IN_FUTURE), + fh.field(CAPTURE_COORDINATES), + fh.field(ProgramTableInfo.Columns.USE_FIRST_STAGE_DURING_REGISTRATION), + fh.field(ProgramTableInfo.Columns.DISPLAY_FRONT_PAGE_LIST), + fh.field(ProgramTableInfo.Columns.PROGRAM_TYPE), + fh.nestedField(PROGRAM_TRACKED_ENTITY_ATTRIBUTES).with( + ProgramTrackedEntityAttributeFields.allFields, + ), + fh.nestedFieldWithUid(ProgramTableInfo.Columns.RELATED_PROGRAM), + fh.nestedFieldWithUid(ProgramTableInfo.Columns.TRACKED_ENTITY_TYPE), + fh.nestedFieldWithUid(ProgramTableInfo.Columns.CATEGORY_COMBO), + fh.nestedField(ACCESS).with(AccessFields.data.with(DataAccessFields.write)), + fh.nestedField(PROGRAM_RULE_VARIABLES) + .with(ProgramRuleVariableFields.allFields), + fh.nestedField(STYLE).with(ObjectStyleFields.allFields), + fh.field(ProgramTableInfo.Columns.EXPIRY_DAYS), + fh.field(ProgramTableInfo.Columns.COMPLETE_EVENTS_EXPIRY_DAYS), + fh.field(ProgramTableInfo.Columns.EXPIRY_PERIOD_TYPE), + fh.field(ProgramTableInfo.Columns.MIN_ATTRIBUTES_REQUIRED_TO_SEARCH), + fh.field(ProgramTableInfo.Columns.MAX_TEI_COUNT_TO_RETURN), + fh.field(ProgramTableInfo.Columns.FEATURE_TYPE), + fh.field(ProgramTableInfo.Columns.ACCESS_LEVEL), + fh.nestedField(PROGRAM_SECTIONS).with(ProgramSectionFields.allFields), + fh.nestedField(ATTRIBUTE_VALUES).with(AttributeValuesFields.allFields), + fh.field(ProgramTableInfo.Columns.ENROLLMENT_LABEL), + fh.field(ProgramTableInfo.Columns.FOLLOW_UP_LABEL), + fh.field(ProgramTableInfo.Columns.ORG_UNIT_LABEL), + fh.field(ProgramTableInfo.Columns.RELATIONSHIP_LABEL), + fh.field(ProgramTableInfo.Columns.NOTE_LABEL), + fh.field(ProgramTableInfo.Columns.TRACKED_ENTITY_ATTRIBUTE_LABEL), + fh.field(ProgramTableInfo.Columns.PROGRAM_STAGE_LABEL), + fh.field(ProgramTableInfo.Columns.EVENT_LABEL), + ).build() +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramRuleVariableFields.java b/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramRuleVariableFields.kt similarity index 64% rename from core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramRuleVariableFields.java rename to core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramRuleVariableFields.kt index 9a0289aef6..d8e569dbb1 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramRuleVariableFields.java +++ b/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramRuleVariableFields.kt @@ -25,29 +25,27 @@ * (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.program.internal -package org.hisp.dhis.android.core.program.internal; +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper +import org.hisp.dhis.android.core.program.ProgramRuleVariable +import org.hisp.dhis.android.core.program.ProgramRuleVariableSourceType +import org.hisp.dhis.android.core.program.ProgramRuleVariableTableInfo -import org.hisp.dhis.android.core.arch.api.fields.internal.Fields; -import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper; -import org.hisp.dhis.android.core.program.ProgramRuleVariable; -import org.hisp.dhis.android.core.program.ProgramRuleVariableSourceType; -import org.hisp.dhis.android.core.program.ProgramRuleVariableTableInfo.Columns; +internal object ProgramRuleVariableFields { + private val fh = FieldsHelper() -final class ProgramRuleVariableFields { - - private static FieldsHelper fh = new FieldsHelper<>(); - static final Fields allFields = Fields.builder() - .fields(fh.getIdentifiableFields()) - .fields( - fh.field(Columns.USE_CODE_FOR_OPTION_SET), - fh.nestedFieldWithUid(Columns.PROGRAM), - fh.nestedFieldWithUid(Columns.PROGRAM_STAGE), - fh.nestedFieldWithUid(Columns.DATA_ELEMENT), - fh.nestedFieldWithUid(Columns.TRACKED_ENTITY_ATTRIBUTE), - fh.field(Columns.PROGRAM_RULE_VARIABLE_SOURCE_TYPE) - ).build(); - - private ProgramRuleVariableFields() { - } -} \ No newline at end of file + val allFields: Fields = Fields.builder() + .fields(fh.getIdentifiableFields()) + .fields( + fh.field(ProgramRuleVariableTableInfo.Columns.USE_CODE_FOR_OPTION_SET), + fh.nestedFieldWithUid(ProgramRuleVariableTableInfo.Columns.PROGRAM), + fh.nestedFieldWithUid(ProgramRuleVariableTableInfo.Columns.PROGRAM_STAGE), + fh.nestedFieldWithUid(ProgramRuleVariableTableInfo.Columns.DATA_ELEMENT), + fh.nestedFieldWithUid(ProgramRuleVariableTableInfo.Columns.TRACKED_ENTITY_ATTRIBUTE), + fh.field( + ProgramRuleVariableTableInfo.Columns.PROGRAM_RULE_VARIABLE_SOURCE_TYPE, + ), + ).build() +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramSectionFields.java b/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramSectionFields.kt similarity index 54% rename from core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramSectionFields.java rename to core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramSectionFields.kt index e750ed3cff..2989844be5 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramSectionFields.java +++ b/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramSectionFields.kt @@ -25,42 +25,35 @@ * (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.program.internal -package org.hisp.dhis.android.core.program.internal; +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper +import org.hisp.dhis.android.core.common.ObjectStyle +import org.hisp.dhis.android.core.common.objectstyle.internal.ObjectStyleFields +import org.hisp.dhis.android.core.program.ProgramSection +import org.hisp.dhis.android.core.program.ProgramSectionTableInfo +import org.hisp.dhis.android.core.program.SectionRendering -import org.hisp.dhis.android.core.arch.api.fields.internal.Fields; -import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper; -import org.hisp.dhis.android.core.common.ObjectStyle; -import org.hisp.dhis.android.core.common.objectstyle.internal.ObjectStyleFields; -import org.hisp.dhis.android.core.program.ProgramSection; -import org.hisp.dhis.android.core.program.ProgramSectionTableInfo.Columns; -import org.hisp.dhis.android.core.program.SectionRendering; +internal object ProgramSectionFields { -public final class ProgramSectionFields { + @Deprecated("In version 2.33 and later, use {@link #TRACKED_ENTITY_ATTRIBUTES} instead.") + const val ATTRIBUTES = "programTrackedEntityAttribute" + const val TRACKED_ENTITY_ATTRIBUTES = "trackedEntityAttributes" + private const val STYLE = "style" + private const val RENDER_TYPE = "renderType" + private val fh = FieldsHelper() - /** - * @deprecated In version 2.33 and later, use {@link #TRACKED_ENTITY_ATTRIBUTES} instead. - */ - public static final String ATTRIBUTES = "programTrackedEntityAttribute"; - public static final String TRACKED_ENTITY_ATTRIBUTES = "trackedEntityAttributes"; - private static final String STYLE = "style"; - private static final String RENDER_TYPE = "renderType"; - - private static FieldsHelper fh = new FieldsHelper<>(); - - public static final Fields allFields = Fields.builder() - .fields(fh.getIdentifiableFields()) - .fields( - fh.field(Columns.DESCRIPTION), - fh.nestedFieldWithUid(Columns.PROGRAM), - fh.nestedFieldWithUid(ATTRIBUTES), - fh.nestedFieldWithUid(TRACKED_ENTITY_ATTRIBUTES), - fh.field(Columns.SORT_ORDER), - fh.nestedField(STYLE).with(ObjectStyleFields.allFields), - fh.field(Columns.FORM_NAME), - fh.nestedField(RENDER_TYPE) - ).build(); - - private ProgramSectionFields() { - } -} \ No newline at end of file + val allFields: Fields = Fields.builder() + .fields(fh.getIdentifiableFields()) + .fields( + fh.field(ProgramSectionTableInfo.Columns.DESCRIPTION), + fh.nestedFieldWithUid(ProgramSectionTableInfo.Columns.PROGRAM), + fh.nestedFieldWithUid(ATTRIBUTES), + fh.nestedFieldWithUid(TRACKED_ENTITY_ATTRIBUTES), + fh.field(ProgramSectionTableInfo.Columns.SORT_ORDER), + fh.nestedField(STYLE).with(ObjectStyleFields.allFields), + fh.field(ProgramSectionTableInfo.Columns.FORM_NAME), + fh.nestedField(RENDER_TYPE), + ).build() +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStageDataElementFields.java b/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStageDataElementFields.kt similarity index 58% rename from core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStageDataElementFields.java rename to core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStageDataElementFields.kt index 3d2a6fa85f..7b36caebc5 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStageDataElementFields.java +++ b/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStageDataElementFields.kt @@ -25,38 +25,34 @@ * (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.program.internal; - -import org.hisp.dhis.android.core.arch.api.fields.internal.Fields; -import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper; -import org.hisp.dhis.android.core.common.ObjectWithUid; -import org.hisp.dhis.android.core.common.ValueTypeRendering; -import org.hisp.dhis.android.core.dataelement.DataElement; -import org.hisp.dhis.android.core.dataelement.internal.DataElementFields; -import org.hisp.dhis.android.core.program.ProgramStageDataElement; -import org.hisp.dhis.android.core.program.ProgramStageDataElementTableInfo.Columns; - -public final class ProgramStageDataElementFields { - public static final String RENDER_TYPE = "renderType"; - - private static FieldsHelper fh = new FieldsHelper<>(); - - static final Fields allFields = Fields.builder() - .fields(fh.getIdentifiableFields()) - .fields( - fh.field(Columns.DISPLAY_IN_REPORTS), - fh.nestedField(Columns.DATA_ELEMENT).with(DataElementFields.allFields), - fh.field(Columns.COMPULSORY), - fh.field(Columns.ALLOW_PROVIDED_ELSEWHERE), - fh.field(Columns.SORT_ORDER), - fh.field(Columns.ALLOW_FUTURE_DATE), - fh.field(RENDER_TYPE), - fh.nestedField(Columns.PROGRAM_STAGE).with(ObjectWithUid.uid) - ).build(); - - - private ProgramStageDataElementFields() { - } - +package org.hisp.dhis.android.core.program.internal + +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper +import org.hisp.dhis.android.core.common.ObjectWithUid +import org.hisp.dhis.android.core.common.ValueTypeRendering +import org.hisp.dhis.android.core.dataelement.DataElement +import org.hisp.dhis.android.core.dataelement.internal.DataElementFields +import org.hisp.dhis.android.core.program.ProgramStageDataElement +import org.hisp.dhis.android.core.program.ProgramStageDataElementTableInfo + +object ProgramStageDataElementFields { + const val RENDER_TYPE = "renderType" + + private val fh = FieldsHelper() + + val allFields: Fields = Fields.builder() + .fields(fh.getIdentifiableFields()) + .fields( + fh.field(ProgramStageDataElementTableInfo.Columns.DISPLAY_IN_REPORTS), + fh.nestedField(ProgramStageDataElementTableInfo.Columns.DATA_ELEMENT) + .with(DataElementFields.allFields), + fh.field(ProgramStageDataElementTableInfo.Columns.COMPULSORY), + fh.field(ProgramStageDataElementTableInfo.Columns.ALLOW_PROVIDED_ELSEWHERE), + fh.field(ProgramStageDataElementTableInfo.Columns.SORT_ORDER), + fh.field(ProgramStageDataElementTableInfo.Columns.ALLOW_FUTURE_DATE), + fh.field(RENDER_TYPE), + fh.nestedField(ProgramStageDataElementTableInfo.Columns.PROGRAM_STAGE) + .with(ObjectWithUid.uid), + ).build() } diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStageFields.java b/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStageFields.java deleted file mode 100644 index da87cb9c87..0000000000 --- a/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStageFields.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2004-2023, University of Oslo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * Neither the name of the HISP project nor the names of its contributors may - * be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.hisp.dhis.android.core.program.internal; - -import org.hisp.dhis.android.core.arch.api.fields.internal.Field; -import org.hisp.dhis.android.core.arch.api.fields.internal.Fields; -import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper; -import org.hisp.dhis.android.core.attribute.AttributeValue; -import org.hisp.dhis.android.core.attribute.internal.AttributeValuesFields; -import org.hisp.dhis.android.core.common.Access; -import org.hisp.dhis.android.core.common.FeatureType; -import org.hisp.dhis.android.core.common.FormType; -import org.hisp.dhis.android.core.common.ObjectStyle; -import org.hisp.dhis.android.core.common.ObjectWithUid; -import org.hisp.dhis.android.core.common.ValidationStrategy; -import org.hisp.dhis.android.core.common.internal.AccessFields; -import org.hisp.dhis.android.core.common.internal.DataAccessFields; -import org.hisp.dhis.android.core.common.objectstyle.internal.ObjectStyleFields; -import org.hisp.dhis.android.core.period.PeriodType; -import org.hisp.dhis.android.core.program.ProgramStage; -import org.hisp.dhis.android.core.program.ProgramStageDataElement; -import org.hisp.dhis.android.core.program.ProgramStageSection; -import org.hisp.dhis.android.core.program.ProgramStageTableInfo.Columns; - -public final class ProgramStageFields { - - private static final String PROGRAM_STAGE_DATA_ELEMENTS = "programStageDataElements"; - private static final String CAPTURE_COORDINATES = "captureCoordinates"; - private static final String STYLE = "style"; - static final String PROGRAM_STAGE_SECTIONS = "programStageSections"; - public static final String ATTRIBUTE_VALUES = "attributeValues"; - private static final String ACCESS = "access"; - - private static FieldsHelper fh = new FieldsHelper<>(); - - static final Field uid = fh.uid(); - static final Fields allFields = Fields.builder() - .fields(fh.getIdentifiableFields()) - .fields( - fh.field(Columns.DESCRIPTION), - fh.field(Columns.DISPLAY_DESCRIPTION), - fh.field(Columns.EXECUTION_DATE_LABEL), - fh.field(Columns.DUE_DATE_LABEL), - fh.field(Columns.ALLOW_GENERATE_NEXT_VISIT), - fh.field(Columns.VALID_COMPLETE_ONLY), - fh.field(Columns.REPORT_DATE_TO_USE), - fh.field(Columns.OPEN_AFTER_ENROLLMENT), - fh.field(Columns.REPEATABLE), - fh.field(CAPTURE_COORDINATES), - fh.field(Columns.FEATURE_TYPE), - fh.field(Columns.FORM_TYPE), - fh.field(Columns.DISPLAY_GENERATE_EVENT_BOX), - fh.field(Columns.GENERATED_BY_ENROLMENT_DATE), - fh.field(Columns.AUTO_GENERATE_EVENT), - fh.field(Columns.SORT_ORDER), - fh.field(Columns.HIDE_DUE_DATE), - fh.field(Columns.BLOCK_ENTRY_FORM), - fh.field(Columns.MIN_DAYS_FROM_START), - fh.field(Columns.STANDARD_INTERVAL), - fh.nestedField(PROGRAM_STAGE_SECTIONS) - .with(ProgramStageSectionFields.allFields), - fh.nestedField(PROGRAM_STAGE_DATA_ELEMENTS) - .with(ProgramStageDataElementFields.allFields), - fh.nestedField(STYLE).with(ObjectStyleFields.allFields), - fh.field(Columns.PERIOD_TYPE), - fh.field(Columns.PROGRAM), - fh.nestedField(ACCESS).with(AccessFields.data.with(DataAccessFields.write)), - fh.field(Columns.REMIND_COMPLETED), - fh.field(Columns.VALIDATION_STRATEGY), - fh.field(Columns.ENABLE_USER_ASSIGNMENT), - fh.nestedField(ATTRIBUTE_VALUES).with(AttributeValuesFields.allFields) - ).build(); - - private ProgramStageFields() { - } -} \ No newline at end of file diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStageFields.kt b/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStageFields.kt new file mode 100644 index 0000000000..949b15c4fb --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStageFields.kt @@ -0,0 +1,99 @@ +/* + * 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.program.internal + +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper +import org.hisp.dhis.android.core.attribute.AttributeValue +import org.hisp.dhis.android.core.attribute.internal.AttributeValuesFields +import org.hisp.dhis.android.core.common.Access +import org.hisp.dhis.android.core.common.FeatureType +import org.hisp.dhis.android.core.common.FormType +import org.hisp.dhis.android.core.common.ObjectStyle +import org.hisp.dhis.android.core.common.ObjectWithUid +import org.hisp.dhis.android.core.common.ValidationStrategy +import org.hisp.dhis.android.core.common.internal.AccessFields +import org.hisp.dhis.android.core.common.internal.DataAccessFields +import org.hisp.dhis.android.core.common.objectstyle.internal.ObjectStyleFields +import org.hisp.dhis.android.core.period.PeriodType +import org.hisp.dhis.android.core.program.ProgramStage +import org.hisp.dhis.android.core.program.ProgramStageDataElement +import org.hisp.dhis.android.core.program.ProgramStageSection +import org.hisp.dhis.android.core.program.ProgramStageTableInfo + +internal object ProgramStageFields { + private const val PROGRAM_STAGE_DATA_ELEMENTS = "programStageDataElements" + private const val CAPTURE_COORDINATES = "captureCoordinates" + private const val STYLE = "style" + const val PROGRAM_STAGE_SECTIONS = "programStageSections" + const val ATTRIBUTE_VALUES = "attributeValues" + private const val ACCESS = "access" + + private val fh = FieldsHelper() + + val uid = fh.uid() + + val allFields: Fields = Fields.builder() + .fields(fh.getIdentifiableFields()) + .fields( + fh.field(ProgramStageTableInfo.Columns.DESCRIPTION), + fh.field(ProgramStageTableInfo.Columns.DISPLAY_DESCRIPTION), + fh.field(ProgramStageTableInfo.Columns.EXECUTION_DATE_LABEL), + fh.field(ProgramStageTableInfo.Columns.DUE_DATE_LABEL), + fh.field(ProgramStageTableInfo.Columns.ALLOW_GENERATE_NEXT_VISIT), + fh.field(ProgramStageTableInfo.Columns.VALID_COMPLETE_ONLY), + fh.field(ProgramStageTableInfo.Columns.REPORT_DATE_TO_USE), + fh.field(ProgramStageTableInfo.Columns.OPEN_AFTER_ENROLLMENT), + fh.field(ProgramStageTableInfo.Columns.REPEATABLE), + fh.field(CAPTURE_COORDINATES), + fh.field(ProgramStageTableInfo.Columns.FEATURE_TYPE), + fh.field(ProgramStageTableInfo.Columns.FORM_TYPE), + fh.field(ProgramStageTableInfo.Columns.DISPLAY_GENERATE_EVENT_BOX), + fh.field(ProgramStageTableInfo.Columns.GENERATED_BY_ENROLMENT_DATE), + fh.field(ProgramStageTableInfo.Columns.AUTO_GENERATE_EVENT), + fh.field(ProgramStageTableInfo.Columns.SORT_ORDER), + fh.field(ProgramStageTableInfo.Columns.HIDE_DUE_DATE), + fh.field(ProgramStageTableInfo.Columns.BLOCK_ENTRY_FORM), + fh.field(ProgramStageTableInfo.Columns.MIN_DAYS_FROM_START), + fh.field(ProgramStageTableInfo.Columns.STANDARD_INTERVAL), + fh.nestedField(PROGRAM_STAGE_SECTIONS) + .with(ProgramStageSectionFields.allFields), + fh.nestedField(PROGRAM_STAGE_DATA_ELEMENTS) + .with(ProgramStageDataElementFields.allFields), + fh.nestedField(STYLE).with(ObjectStyleFields.allFields), + fh.field(ProgramStageTableInfo.Columns.PERIOD_TYPE), + fh.field(ProgramStageTableInfo.Columns.PROGRAM), + fh.nestedField(ACCESS).with(AccessFields.data.with(DataAccessFields.write)), + fh.field(ProgramStageTableInfo.Columns.REMIND_COMPLETED), + fh.field(ProgramStageTableInfo.Columns.VALIDATION_STRATEGY), + fh.field(ProgramStageTableInfo.Columns.ENABLE_USER_ASSIGNMENT), + fh.nestedField(ATTRIBUTE_VALUES).with(AttributeValuesFields.allFields), + fh.field(ProgramStageTableInfo.Columns.PROGRAM_STAGE_LABEL), + fh.field(ProgramStageTableInfo.Columns.EVENT_LABEL), + ).build() +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStageSectionFields.java b/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStageSectionFields.kt similarity index 60% rename from core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStageSectionFields.java rename to core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStageSectionFields.kt index d8aa1a4756..2514b021bf 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStageSectionFields.java +++ b/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStageSectionFields.kt @@ -25,34 +25,29 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +package org.hisp.dhis.android.core.program.internal -package org.hisp.dhis.android.core.program.internal; +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper +import org.hisp.dhis.android.core.program.ProgramStageSection +import org.hisp.dhis.android.core.program.ProgramStageSectionTableInfo +import org.hisp.dhis.android.core.program.SectionRendering -import org.hisp.dhis.android.core.arch.api.fields.internal.Fields; -import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper; -import org.hisp.dhis.android.core.program.ProgramStageSection; -import org.hisp.dhis.android.core.program.SectionRendering; -import org.hisp.dhis.android.core.program.ProgramStageSectionTableInfo.Columns; +internal object ProgramStageSectionFields { + const val PROGRAM_INDICATORS = "programIndicators" + const val DATA_ELEMENTS = "dataElements" + private const val RENDER_TYPE = "renderType" -public final class ProgramStageSectionFields { + private val fh = FieldsHelper() - public static final String PROGRAM_INDICATORS = "programIndicators"; - public static final String DATA_ELEMENTS = "dataElements"; - private static final String RENDER_TYPE = "renderType"; - - private static final FieldsHelper fh = new FieldsHelper<>(); - - public static final Fields allFields = Fields.builder() - .fields(fh.getIdentifiableFields()) - .fields( - fh.field(Columns.SORT_ORDER), - fh.nestedFieldWithUid(PROGRAM_INDICATORS), - fh.nestedFieldWithUid(DATA_ELEMENTS), - fh.nestedField(RENDER_TYPE), - fh.field(Columns.DESCRIPTION), - fh.field(Columns.DISPLAY_DESCRIPTION) - ).build(); - - private ProgramStageSectionFields() { - } -} \ No newline at end of file + val allFields: Fields = Fields.builder() + .fields(fh.getIdentifiableFields()) + .fields( + fh.field(ProgramStageSectionTableInfo.Columns.SORT_ORDER), + fh.nestedFieldWithUid(PROGRAM_INDICATORS), + fh.nestedFieldWithUid(DATA_ELEMENTS), + fh.nestedField(RENDER_TYPE), + fh.field(ProgramStageSectionTableInfo.Columns.DESCRIPTION), + fh.field(ProgramStageSectionTableInfo.Columns.DISPLAY_DESCRIPTION), + ).build() +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStageStoreImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStageStoreImpl.kt index d1b6e976f6..2470e3bb87 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStageStoreImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStageStoreImpl.kt @@ -80,6 +80,8 @@ internal class ProgramStageStoreImpl( w.bind(31, o.featureType()) w.bind(32, o.enableUserAssignment()) w.bind(33, o.validationStrategy()) + w.bind(34, o.programStageLabel()) + w.bind(35, o.eventLabel()) } } val CHILD_PROJECTION = SingleParentChildProjection( diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStoreImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStoreImpl.kt index 1fb5c4dbcf..03d989552c 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStoreImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramStoreImpl.kt @@ -87,6 +87,14 @@ internal class ProgramStoreImpl( w.bind(34, o.maxTeiCountToReturn()) w.bind(35, o.featureType()) w.bind(36, o.accessLevel()) + w.bind(37, o.enrollmentLabel()) + w.bind(38, o.followUpLabel()) + w.bind(39, o.orgUnitLabel()) + w.bind(40, o.relationshipLabel()) + w.bind(41, o.noteLabel()) + w.bind(42, o.trackedEntityAttributeLabel()) + w.bind(43, o.programStageLabel()) + w.bind(44, o.eventLabel()) } } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramTrackedEntityAttributeFields.java b/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramTrackedEntityAttributeFields.kt similarity index 60% rename from core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramTrackedEntityAttributeFields.java rename to core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramTrackedEntityAttributeFields.kt index 8c65c21282..e68eba2ac1 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramTrackedEntityAttributeFields.java +++ b/core/src/main/java/org/hisp/dhis/android/core/program/internal/ProgramTrackedEntityAttributeFields.kt @@ -25,34 +25,28 @@ * (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.program.internal -package org.hisp.dhis.android.core.program.internal; +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper +import org.hisp.dhis.android.core.common.ValueTypeRendering +import org.hisp.dhis.android.core.program.ProgramTrackedEntityAttribute +import org.hisp.dhis.android.core.program.ProgramTrackedEntityAttributeTableInfo -import org.hisp.dhis.android.core.arch.api.fields.internal.Fields; -import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper; -import org.hisp.dhis.android.core.common.ValueTypeRendering; -import org.hisp.dhis.android.core.program.ProgramTrackedEntityAttribute; -import org.hisp.dhis.android.core.program.ProgramTrackedEntityAttributeTableInfo.Columns; +internal object ProgramTrackedEntityAttributeFields { + const val RENDER_TYPE = "renderType" + private val fh = FieldsHelper() -public final class ProgramTrackedEntityAttributeFields { - public static final String RENDER_TYPE = "renderType"; - - private static FieldsHelper fh = new FieldsHelper<>(); - - public static final Fields allFields - = Fields.builder() - .fields(fh.getNameableFields()) - .fields( - fh.field(Columns.MANDATORY), - fh.nestedFieldWithUid(Columns.PROGRAM), - fh.field(Columns.ALLOW_FUTURE_DATE), - fh.field(Columns.DISPLAY_IN_LIST), - fh.field(Columns.SORT_ORDER), - fh.field(Columns.SEARCHABLE), - fh.nestedFieldWithUid(Columns.TRACKED_ENTITY_ATTRIBUTE), - fh.field(RENDER_TYPE) - ).build(); - - private ProgramTrackedEntityAttributeFields() { - } -} \ No newline at end of file + val allFields: Fields = Fields.builder() + .fields(fh.getNameableFields()) + .fields( + fh.field(ProgramTrackedEntityAttributeTableInfo.Columns.MANDATORY), + fh.nestedFieldWithUid(ProgramTrackedEntityAttributeTableInfo.Columns.PROGRAM), + fh.field(ProgramTrackedEntityAttributeTableInfo.Columns.ALLOW_FUTURE_DATE), + fh.field(ProgramTrackedEntityAttributeTableInfo.Columns.DISPLAY_IN_LIST), + fh.field(ProgramTrackedEntityAttributeTableInfo.Columns.SORT_ORDER), + fh.field(ProgramTrackedEntityAttributeTableInfo.Columns.SEARCHABLE), + fh.nestedFieldWithUid(ProgramTrackedEntityAttributeTableInfo.Columns.TRACKED_ENTITY_ATTRIBUTE), + fh.field(RENDER_TYPE), + ).build() +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/programindicatorengine/internal/ProgramIndicatorSQLUtils.kt b/core/src/main/java/org/hisp/dhis/android/core/program/programindicatorengine/internal/ProgramIndicatorSQLUtils.kt index 1cec991795..5ec6b75b01 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/program/programindicatorengine/internal/ProgramIndicatorSQLUtils.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/program/programindicatorengine/internal/ProgramIndicatorSQLUtils.kt @@ -127,20 +127,6 @@ internal object ProgramIndicatorSQLUtils { } } - fun getColumnValueCast( - column: String, - valueType: ValueType?, - ): String { - return when { - valueType?.isNumeric == true -> - "CAST($column AS NUMERIC)" - valueType?.isBoolean == true -> - "CASE WHEN $column = 'true' THEN 1 ELSE 0 END" - else -> - column - } - } - fun getDefaultValue( valueType: ValueType?, ): String { diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/programindicatorengine/internal/dataitem/ProgramItemAttribute.kt b/core/src/main/java/org/hisp/dhis/android/core/program/programindicatorengine/internal/dataitem/ProgramItemAttribute.kt index 60cf5aa9d6..d0230f2e01 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/program/programindicatorengine/internal/dataitem/ProgramItemAttribute.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/program/programindicatorengine/internal/dataitem/ProgramItemAttribute.kt @@ -33,10 +33,10 @@ import org.hisp.dhis.android.core.parser.internal.service.dataitem.DimensionalIt import org.hisp.dhis.android.core.program.programindicatorengine.internal.ProgramExpressionItem import org.hisp.dhis.android.core.program.programindicatorengine.internal.ProgramIndicatorParserUtils.assumeProgramAttributeSyntax import org.hisp.dhis.android.core.program.programindicatorengine.internal.ProgramIndicatorSQLUtils -import org.hisp.dhis.android.core.program.programindicatorengine.internal.ProgramIndicatorSQLUtils.getColumnValueCast import org.hisp.dhis.android.core.program.programindicatorengine.internal.ProgramIndicatorSQLUtils.getDefaultValue import org.hisp.dhis.android.core.trackedentity.TrackedEntityAttribute import org.hisp.dhis.android.core.trackedentity.TrackedEntityAttributeValueTableInfo +import org.hisp.dhis.android.core.util.SqlUtils.getColumnValueCast import org.hisp.dhis.parser.expression.antlr.ExpressionParser.ExprContext internal class ProgramItemAttribute : ProgramExpressionItem() { diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/programindicatorengine/internal/dataitem/ProgramItemStageElement.kt b/core/src/main/java/org/hisp/dhis/android/core/program/programindicatorengine/internal/dataitem/ProgramItemStageElement.kt index af04e1ac7e..3a28391ef5 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/program/programindicatorengine/internal/dataitem/ProgramItemStageElement.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/program/programindicatorengine/internal/dataitem/ProgramItemStageElement.kt @@ -35,10 +35,10 @@ import org.hisp.dhis.android.core.parser.internal.service.dataitem.DimensionalIt import org.hisp.dhis.android.core.program.programindicatorengine.internal.ProgramExpressionItem import org.hisp.dhis.android.core.program.programindicatorengine.internal.ProgramIndicatorParserUtils.assumeStageElementSyntax import org.hisp.dhis.android.core.program.programindicatorengine.internal.ProgramIndicatorSQLUtils -import org.hisp.dhis.android.core.program.programindicatorengine.internal.ProgramIndicatorSQLUtils.getColumnValueCast import org.hisp.dhis.android.core.program.programindicatorengine.internal.ProgramIndicatorSQLUtils.getDefaultValue import org.hisp.dhis.android.core.trackedentity.TrackedEntityDataValue import org.hisp.dhis.android.core.trackedentity.TrackedEntityDataValueTableInfo +import org.hisp.dhis.android.core.util.SqlUtils.getColumnValueCast import org.hisp.dhis.parser.expression.antlr.ExpressionParser.ExprContext internal class ProgramItemStageElement : ProgramExpressionItem() { diff --git a/core/src/main/java/org/hisp/dhis/android/core/program/programindicatorengine/internal/function/ProgramCountFunction.kt b/core/src/main/java/org/hisp/dhis/android/core/program/programindicatorengine/internal/function/ProgramCountFunction.kt index 27b368b1bb..382db8ac99 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/program/programindicatorengine/internal/function/ProgramCountFunction.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/program/programindicatorengine/internal/function/ProgramCountFunction.kt @@ -30,10 +30,10 @@ package org.hisp.dhis.android.core.program.programindicatorengine.internal.funct import org.hisp.dhis.android.core.event.EventTableInfo import org.hisp.dhis.android.core.parser.internal.expression.CommonExpressionVisitor import org.hisp.dhis.android.core.program.programindicatorengine.internal.ProgramExpressionItem -import org.hisp.dhis.android.core.program.programindicatorengine.internal.ProgramIndicatorSQLUtils.getColumnValueCast import org.hisp.dhis.android.core.program.programindicatorengine.internal.ProgramIndicatorSQLUtils.getDataValueEventWhereClause import org.hisp.dhis.android.core.program.programindicatorengine.internal.dataitem.ProgramItemStageElement import org.hisp.dhis.android.core.trackedentity.TrackedEntityDataValueTableInfo +import org.hisp.dhis.android.core.util.SqlUtils.getColumnValueCast import org.hisp.dhis.antlr.ParserExceptionWithoutContext import org.hisp.dhis.parser.expression.antlr.ExpressionParser.ExprContext diff --git a/core/src/main/java/org/hisp/dhis/android/core/programstageworkinglist/ProgramStageQueryCriteria.java b/core/src/main/java/org/hisp/dhis/android/core/programstageworkinglist/ProgramStageQueryCriteria.java index c70e874e4a..eceaeaf342 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/programstageworkinglist/ProgramStageQueryCriteria.java +++ b/core/src/main/java/org/hisp/dhis/android/core/programstageworkinglist/ProgramStageQueryCriteria.java @@ -143,7 +143,7 @@ public static ProgramStageQueryCriteria create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder { + public abstract static class Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/programstageworkinglist/ProgramStageWorkingList.java b/core/src/main/java/org/hisp/dhis/android/core/programstageworkinglist/ProgramStageWorkingList.java index a8e0d436b2..bc3759b83d 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/programstageworkinglist/ProgramStageWorkingList.java +++ b/core/src/main/java/org/hisp/dhis/android/core/programstageworkinglist/ProgramStageWorkingList.java @@ -77,7 +77,7 @@ public static ProgramStageWorkingList create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseIdentifiableObject.Builder { + public abstract static class Builder extends BaseIdentifiableObject.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/programstageworkinglist/ProgramStageWorkingListAttributeValueFilter.java b/core/src/main/java/org/hisp/dhis/android/core/programstageworkinglist/ProgramStageWorkingListAttributeValueFilter.java index bad4a12604..4f4f6cbbf9 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/programstageworkinglist/ProgramStageWorkingListAttributeValueFilter.java +++ b/core/src/main/java/org/hisp/dhis/android/core/programstageworkinglist/ProgramStageWorkingListAttributeValueFilter.java @@ -84,7 +84,7 @@ public static ProgramStageWorkingListAttributeValueFilter create(Cursor cursor) @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends FilterOperators.Builder { + public abstract static class Builder extends FilterOperators.Builder { public abstract Builder id(Long id); public abstract Builder programStageWorkingList(String programStageWorkingList); diff --git a/core/src/main/java/org/hisp/dhis/android/core/programstageworkinglist/ProgramStageWorkingListEventDataFilter.java b/core/src/main/java/org/hisp/dhis/android/core/programstageworkinglist/ProgramStageWorkingListEventDataFilter.java index 09c581675c..c59e6b9c3d 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/programstageworkinglist/ProgramStageWorkingListEventDataFilter.java +++ b/core/src/main/java/org/hisp/dhis/android/core/programstageworkinglist/ProgramStageWorkingListEventDataFilter.java @@ -70,7 +70,7 @@ public static ProgramStageWorkingListEventDataFilter create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends FilterOperators.Builder { + public abstract static class Builder extends FilterOperators.Builder { public abstract Builder id(Long id); public abstract Builder programStageWorkingList(String programStageWorkingList); diff --git a/core/src/main/java/org/hisp/dhis/android/core/relationship/RelationshipConstraint.java b/core/src/main/java/org/hisp/dhis/android/core/relationship/RelationshipConstraint.java index e951cdd8f3..3aead715e5 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/relationship/RelationshipConstraint.java +++ b/core/src/main/java/org/hisp/dhis/android/core/relationship/RelationshipConstraint.java @@ -38,6 +38,7 @@ import com.gabrielittner.auto.value.cursor.ColumnAdapter; import com.google.auto.value.AutoValue; +import org.hisp.dhis.android.core.arch.db.adapters.custom.internal.TrackerDataViewColumnAdapter; import org.hisp.dhis.android.core.arch.db.adapters.enums.internal.RelationshipConstraintTypeColumnAdapter; import org.hisp.dhis.android.core.arch.db.adapters.enums.internal.RelationshipEntityTypeColumnAdapter; import org.hisp.dhis.android.core.arch.db.adapters.identifiable.internal.ObjectWithUidColumnAdapter; @@ -74,6 +75,10 @@ public abstract class RelationshipConstraint extends BaseObject { @ColumnAdapter(ObjectWithUidColumnAdapter.class) public abstract ObjectWithUid programStage(); + @Nullable + @ColumnAdapter(TrackerDataViewColumnAdapter.class) + public abstract TrackerDataView trackerDataView(); + public static RelationshipConstraint create(Cursor cursor) { return AutoValue_RelationshipConstraint.createFromCursor(cursor); } @@ -86,7 +91,7 @@ public static Builder builder() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder relationshipType(ObjectWithUid relationshipType); @@ -100,6 +105,8 @@ public static abstract class Builder extends BaseObject.Builder { public abstract Builder programStage(ObjectWithUid programStage); + public abstract Builder trackerDataView(TrackerDataView trackerDataView); + public abstract RelationshipConstraint build(); } } \ No newline at end of file diff --git a/core/src/main/java/org/hisp/dhis/android/core/relationship/RelationshipConstraintTableInfo.java b/core/src/main/java/org/hisp/dhis/android/core/relationship/RelationshipConstraintTableInfo.java index 31e17f4aac..9133e00bd2 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/relationship/RelationshipConstraintTableInfo.java +++ b/core/src/main/java/org/hisp/dhis/android/core/relationship/RelationshipConstraintTableInfo.java @@ -57,6 +57,8 @@ public static class Columns extends CoreColumns { public static final String TRACKED_ENTITY_TYPE = "trackedEntityType"; public static final String PROGRAM = "program"; public static final String PROGRAM_STAGE = "programStage"; + public static final String TRACKER_DATA_VIEW_ATTRIBUTES = "trackerDataViewAttributes"; + public static final String TRACKER_DATA_VIEW_DATA_ELEMENTS = "trackerDataViewDataElements"; @Override public String[] all() { @@ -66,7 +68,9 @@ public String[] all() { RELATIONSHIP_ENTITY, TRACKED_ENTITY_TYPE, PROGRAM, - PROGRAM_STAGE + PROGRAM_STAGE, + TRACKER_DATA_VIEW_ATTRIBUTES, + TRACKER_DATA_VIEW_DATA_ELEMENTS ); } diff --git a/core/src/main/java/org/hisp/dhis/android/core/relationship/RelationshipType.java b/core/src/main/java/org/hisp/dhis/android/core/relationship/RelationshipType.java index 28b0b745c1..2c1b7733f3 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/relationship/RelationshipType.java +++ b/core/src/main/java/org/hisp/dhis/android/core/relationship/RelationshipType.java @@ -99,7 +99,7 @@ public static RelationshipType create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseIdentifiableObject.Builder { + public abstract static class Builder extends BaseIdentifiableObject.Builder { public abstract Builder id(Long id); abstract Builder bIsToA(String bIsToA); diff --git a/core/src/main/java/org/hisp/dhis/android/core/relationship/TrackerDataView.java b/core/src/main/java/org/hisp/dhis/android/core/relationship/TrackerDataView.java new file mode 100644 index 0000000000..96fef02cd4 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/relationship/TrackerDataView.java @@ -0,0 +1,104 @@ +/* + * 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.relationship; + +import android.database.Cursor; + +import androidx.annotation.Nullable; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.gabrielittner.auto.value.cursor.ColumnAdapter; +import com.gabrielittner.auto.value.cursor.ColumnName; +import com.google.auto.value.AutoValue; + +import org.hisp.dhis.android.core.arch.db.adapters.custom.internal.StringListColumnAdapter; +import org.hisp.dhis.android.core.common.BaseObject; + +import java.util.Collections; +import java.util.List; + +@AutoValue +@JsonDeserialize(builder = AutoValue_TrackerDataView.Builder.class) +public abstract class TrackerDataView extends BaseObject { + + @Nullable + @ColumnName(RelationshipConstraintTableInfo.Columns.TRACKER_DATA_VIEW_ATTRIBUTES) + @ColumnAdapter(StringListColumnAdapter.class) + public abstract List attributes(); + + @Nullable + @ColumnName(RelationshipConstraintTableInfo.Columns.TRACKER_DATA_VIEW_DATA_ELEMENTS) + @ColumnAdapter(StringListColumnAdapter.class) + public abstract List dataElements(); + + public static TrackerDataView create(Cursor cursor) { + return $AutoValue_TrackerDataView.createFromCursor(cursor); + } + + public static Builder builder() { + return new AutoValue_TrackerDataView.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "") + public abstract static class Builder extends BaseObject.Builder { + + public abstract Builder attributes(List attributes); + + public abstract Builder dataElements(List dataElements); + + abstract TrackerDataView autoBuild(); + + //Auxiliary fields + abstract List attributes(); + + abstract List dataElements(); + + public TrackerDataView build() { + + try { + attributes(); + } catch (IllegalStateException e) { + attributes(Collections.emptyList()); + } + + try { + dataElements(); + } catch (IllegalStateException e) { + dataElements(Collections.emptyList()); + } + + return autoBuild(); + } + + } +} \ No newline at end of file diff --git a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipConstraintFields.java b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipConstraintFields.java index 5a3dc80e01..d96455dec7 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipConstraintFields.java +++ b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipConstraintFields.java @@ -33,8 +33,10 @@ import org.hisp.dhis.android.core.relationship.RelationshipConstraint; import org.hisp.dhis.android.core.relationship.RelationshipConstraintTableInfo.Columns; import org.hisp.dhis.android.core.relationship.RelationshipEntityType; +import org.hisp.dhis.android.core.relationship.TrackerDataView; final class RelationshipConstraintFields { + private static final String TRACKER_DATA_VIEW = "trackerDataView"; private static final FieldsHelper fh = new FieldsHelper<>(); @@ -44,7 +46,9 @@ final class RelationshipConstraintFields { fh.field(Columns.RELATIONSHIP_ENTITY), fh.nestedFieldWithUid(Columns.TRACKED_ENTITY_TYPE), fh.nestedFieldWithUid(Columns.PROGRAM), - fh.nestedFieldWithUid(Columns.PROGRAM_STAGE) + fh.nestedFieldWithUid(Columns.PROGRAM_STAGE), + fh.nestedField(TRACKER_DATA_VIEW) + .with(TrackerDataViewFields.INSTANCE.getAllFields()) ).build(); private RelationshipConstraintFields() { diff --git a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipConstraintStoreImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipConstraintStoreImpl.kt index 19b9b54e2f..c580e2ff37 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipConstraintStoreImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipConstraintStoreImpl.kt @@ -29,6 +29,7 @@ package org.hisp.dhis.android.core.relationship.internal import android.database.Cursor import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.arch.db.adapters.custom.internal.StringListColumnAdapter import org.hisp.dhis.android.core.arch.db.stores.binders.internal.StatementBinder import org.hisp.dhis.android.core.arch.db.stores.binders.internal.StatementWrapper import org.hisp.dhis.android.core.arch.db.stores.binders.internal.WhereStatementBinder @@ -59,10 +60,12 @@ internal class RelationshipConstraintStoreImpl( w.bind(4, getUidOrNull(o.trackedEntityType())) w.bind(5, getUidOrNull(o.program())) w.bind(6, getUidOrNull(o.programStage())) + w.bind(7, StringListColumnAdapter.serialize(o.trackerDataView()?.attributes())) + w.bind(8, StringListColumnAdapter.serialize(o.trackerDataView()?.dataElements())) } private val WHERE_UPDATE_BINDER = WhereStatementBinder { o: RelationshipConstraint, w: StatementWrapper -> - w.bind(7, getUidOrNull(o.relationshipType())) - w.bind(8, o.constraintType()) + w.bind(9, getUidOrNull(o.relationshipType())) + w.bind(10, o.constraintType()) } private val WHERE_DELETE_BINDER = WhereStatementBinder { o: RelationshipConstraint, w: StatementWrapper -> w.bind(1, getUidOrNull(o.relationshipType())) diff --git a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipDHISVersionManager.java b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipDHISVersionManager.java deleted file mode 100644 index 71229dc69f..0000000000 --- a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipDHISVersionManager.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2004-2023, University of Oslo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * Neither the name of the HISP project nor the names of its contributors may - * be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.hisp.dhis.android.core.relationship.internal; - -import org.hisp.dhis.android.core.relationship.BaseRelationship; -import org.hisp.dhis.android.core.relationship.Relationship; -import org.hisp.dhis.android.core.relationship.RelationshipHelper; -import org.hisp.dhis.android.core.relationship.RelationshipItem; -import org.hisp.dhis.android.core.relationship.RelationshipItemTableInfo; -import org.hisp.dhis.android.core.relationship.RelationshipType; -import org.hisp.dhis.android.core.trackedentity.TrackedEntityInstance; -import org.hisp.dhis.android.core.trackedentity.TrackedEntityInstanceInternalAccessor; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -public class RelationshipDHISVersionManager { - - private final RelationshipTypeStore relationshipTypeStore; - - public RelationshipDHISVersionManager(RelationshipTypeStore relationshipTypeStore) { - this.relationshipTypeStore = relationshipTypeStore; - } - - public List getOwnedRelationships(Collection relationships, String elementUid) { - List ownedRelationships = new ArrayList<>(); - for (Relationship relationship : relationships) { - RelationshipItem fromItem = relationship.from(); - if (isBidirectional(relationship) || - fromItem != null && fromItem.elementUid() != null && fromItem.elementUid().equals(elementUid)) { - ownedRelationships.add(relationship); - } - } - return ownedRelationships; - } - - private boolean isBidirectional(Relationship relationship) { - if (relationship.relationshipType() == null) { - return false; - } else { - RelationshipType relationshipType = relationshipTypeStore.selectByUid(relationship.relationshipType()); - return relationshipType != null && relationshipType.bidirectional(); - } - } - - public TrackedEntityInstance getRelativeTei(Relationship relationship, String teiUid) { - return getRelativeTEI230(relationship, teiUid); - } - - public TrackedEntityInstance getRelativeTEI230(BaseRelationship baseRelationship, String teiUid) { - String fromTEIUid = RelationshipHelper.getTeiUid(baseRelationship.from()); - String toTEIUid = RelationshipHelper.getTeiUid(baseRelationship.to()); - - if (fromTEIUid == null || toTEIUid == null) { - return null; - } - - String relatedTEIUid = teiUid.equals(fromTEIUid) ? toTEIUid : fromTEIUid; - - return TrackedEntityInstanceInternalAccessor.insertRelationships( - TrackedEntityInstance.builder(), Collections.emptyList()) - .uid(relatedTEIUid) - .deleted(false) - .build(); - } - - public RelationshipItem getRelatedRelationshipItem(BaseRelationship baseRelationship, String parentUid) { - String fromUid = baseRelationship.from() == null ? null : baseRelationship.from().elementUid(); - String toUid = baseRelationship.to() == null ? null : baseRelationship.to().elementUid(); - - if (fromUid == null || toUid == null) { - return null; - } - - return parentUid.equals(fromUid) ? baseRelationship.to() : baseRelationship.from(); - } - - public void saveRelativesIfNotExist(Collection relationships, - String parentUid, - RelationshipItemRelatives relatives, - RelationshipHandler relationshipHandler) { - for (BaseRelationship relationship : relationships) { - RelationshipItem item = getRelatedRelationshipItem(relationship, parentUid); - if (item != null && !relationshipHandler.doesRelationshipItemExist(item)) { - switch (item.elementType()) { - case RelationshipItemTableInfo.Columns.TRACKED_ENTITY_INSTANCE: - relatives.addTrackedEntityInstance(item.elementUid()); - break; - case RelationshipItemTableInfo.Columns.ENROLLMENT: - relatives.addEnrollment(item.elementUid()); - break; - case RelationshipItemTableInfo.Columns.EVENT: - relatives.addEvent(item.elementUid()); - break; - default: - break; - } - } - } - } -} \ No newline at end of file diff --git a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipDHISVersionManager.kt b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipDHISVersionManager.kt new file mode 100644 index 0000000000..9cd60baecf --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipDHISVersionManager.kt @@ -0,0 +1,100 @@ +/* + * 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.relationship.internal + +import org.hisp.dhis.android.core.common.ObjectWithUid +import org.hisp.dhis.android.core.relationship.BaseRelationship +import org.hisp.dhis.android.core.relationship.Relationship +import org.hisp.dhis.android.core.relationship.RelationshipConstraintType +import org.hisp.dhis.android.core.relationship.RelationshipItem +import org.hisp.dhis.android.core.relationship.RelationshipItemTableInfo.Columns.ENROLLMENT +import org.hisp.dhis.android.core.relationship.RelationshipItemTableInfo.Columns.EVENT +import org.hisp.dhis.android.core.relationship.RelationshipItemTableInfo.Columns.TRACKED_ENTITY_INSTANCE +import org.koin.core.annotation.Singleton + +@Singleton +internal class RelationshipDHISVersionManager( + private val relationshipTypeStore: RelationshipTypeStore, +) { + fun getOwnedRelationships(relationships: Collection, elementUid: String): List { + return relationships.filter { relationship -> + val fromItem = relationship.from() + isBidirectional(relationship) || fromItem?.elementUid() == elementUid + } + } + + private fun isBidirectional(relationship: Relationship): Boolean { + return relationship.relationshipType()?.let { relationshipTypeUid -> + relationshipTypeStore.selectByUid(relationshipTypeUid)?.bidirectional() + } ?: false + } + + private fun getRelatedRelationshipItem(baseRelationship: BaseRelationship, parentUid: String): RelationshipItem? { + val fromUid = baseRelationship.from()?.elementUid() + val toUid = baseRelationship.to()?.elementUid() + + val itemBuilder = when { + parentUid == fromUid -> + baseRelationship.to()?.toBuilder() + ?.relationshipItemType(RelationshipConstraintType.TO) + parentUid == toUid -> + baseRelationship.from()?.toBuilder() + ?.relationshipItemType(RelationshipConstraintType.FROM) + else -> + null + } + + return itemBuilder + ?.relationship(ObjectWithUid.create(baseRelationship.uid())) + ?.build() + } + + fun saveRelativesIfNotExist( + relationships: Collection, + parentUid: String, + relatives: RelationshipItemRelatives, + ) { + for (relationship in relationships) { + val item = getRelatedRelationshipItem(relationship, parentUid) + if (item != null && relationship.relationshipType() != null && item.relationshipItemType() != null) { + val relationshipItem = RelationshipItemRelative( + itemUid = item.elementUid(), + itemType = item.elementType(), + relationshipTypeUid = relationship.relationshipType()!!, + constraintType = item.relationshipItemType()!!, + ) + when (item.elementType()) { + TRACKED_ENTITY_INSTANCE -> relatives.addTrackedEntityInstance(relationshipItem) + ENROLLMENT -> relatives.addEnrollment(relationshipItem) + EVENT -> relatives.addEvent(relationshipItem) + else -> {} + } + } + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipDownloadAndPersistCallFactory.kt b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipDownloadAndPersistCallFactory.kt index fea875c513..9044b42a10 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipDownloadAndPersistCallFactory.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipDownloadAndPersistCallFactory.kt @@ -32,7 +32,12 @@ import org.hisp.dhis.android.core.enrollment.Enrollment import org.hisp.dhis.android.core.enrollment.internal.EnrollmentPersistenceCallFactory import org.hisp.dhis.android.core.event.Event import org.hisp.dhis.android.core.event.internal.EventPersistenceCallFactory -import org.hisp.dhis.android.core.relationship.* +import org.hisp.dhis.android.core.relationship.Relationship +import org.hisp.dhis.android.core.relationship.RelationshipItem +import org.hisp.dhis.android.core.relationship.RelationshipItemEnrollment +import org.hisp.dhis.android.core.relationship.RelationshipItemEvent +import org.hisp.dhis.android.core.relationship.RelationshipItemTableInfo +import org.hisp.dhis.android.core.relationship.RelationshipItemTrackedEntityInstance import org.hisp.dhis.android.core.trackedentity.TrackedEntityInstance import org.hisp.dhis.android.core.trackedentity.internal.TrackedEntityInstancePersistenceCallFactory import org.hisp.dhis.android.core.trackedentity.internal.TrackerParentCallFactory @@ -46,6 +51,7 @@ internal class RelationshipDownloadAndPersistCallFactory( private val enrollmentPersistenceCallFactory: EnrollmentPersistenceCallFactory, private val eventPersistenceCallFactory: EventPersistenceCallFactory, private val coroutineAPICallExecutor: CoroutineAPICallExecutor, + private val relationshipItemStoreSelector: RelationshipItemElementStoreSelector, ) { suspend fun downloadAndPersist(relatives: RelationshipItemRelatives) { downloadRelativeEvents(relatives) @@ -57,21 +63,30 @@ internal class RelationshipDownloadAndPersistCallFactory( val events: MutableList = mutableListOf() val failedEvents: MutableList = mutableListOf() - for (uid in relatives.relativeEventUids) { - coroutineAPICallExecutor.wrap(storeError = true) { - trackerParentCallFactory.getEventCall().getRelationshipEntityCall(uid) - }.fold( - onSuccess = { eventPayload -> events.addAll(eventPayload.items()) }, - onFailure = { failedEvents.add(uid) }, - ) + for (item in relatives.getRelativeEvents()) { + if (itemDoesNotExist(item)) { + coroutineAPICallExecutor.wrap(storeError = true) { + trackerParentCallFactory.getEventCall().getRelationshipEntityCall(item) + }.fold( + onSuccess = { eventPayload -> + events.addAll(eventPayload.items()) + eventPayload.items().mapNotNull { it.enrollment() }.forEach { enrollment -> + val relativeEnrollment = RelationshipItemRelative( + itemUid = enrollment, + itemType = RelationshipItemTableInfo.Columns.ENROLLMENT, + relationshipTypeUid = item.relationshipTypeUid, + constraintType = item.constraintType, + ) + relatives.addEnrollment(relativeEnrollment) + } + }, + onFailure = { failedEvents.add(item.itemUid) }, + ) + } } eventPersistenceCallFactory.persistAsRelationships(events) - events - .mapNotNull { it.enrollment() } - .forEach { relatives.addEnrollment(it) } - cleanFailedRelationships(failedEvents, RelationshipItemTableInfo.Columns.EVENT) } @@ -79,21 +94,30 @@ internal class RelationshipDownloadAndPersistCallFactory( val enrollments: MutableList = mutableListOf() val failedEnrollments: MutableList = mutableListOf() - for (uid in relatives.relativeEnrollmentUids) { - coroutineAPICallExecutor.wrap(storeError = true) { - trackerParentCallFactory.getEnrollmentCall().getRelationshipEntityCall(uid) - }.fold( - onSuccess = { enrollment -> enrollments.add(enrollment) }, - onFailure = { failedEnrollments.add(uid) }, - ) + for (item in relatives.getRelativeEnrollments()) { + if (itemDoesNotExist(item)) { + coroutineAPICallExecutor.wrap(storeError = true) { + trackerParentCallFactory.getEnrollmentCall().getRelationshipEntityCall(item) + }.fold( + onSuccess = { enrollment -> + enrollments.add(enrollment) + enrollment.trackedEntityInstance()?.let { tei -> + val relativeTei = RelationshipItemRelative( + itemUid = tei, + itemType = RelationshipItemTableInfo.Columns.TRACKED_ENTITY_INSTANCE, + relationshipTypeUid = item.relationshipTypeUid, + constraintType = item.constraintType, + ) + relatives.addTrackedEntityInstance(relativeTei) + } + }, + onFailure = { failedEnrollments.add(item.itemUid) }, + ) + } } enrollmentPersistenceCallFactory.persistAsRelationships(enrollments).blockingAwait() - enrollments - .mapNotNull { it.trackedEntityInstance() } - .forEach { relatives.addTrackedEntityInstance(it) } - cleanFailedRelationships(failedEnrollments, RelationshipItemTableInfo.Columns.ENROLLMENT) } @@ -101,13 +125,15 @@ internal class RelationshipDownloadAndPersistCallFactory( val teis: MutableList = mutableListOf() val failedTeis: MutableList = mutableListOf() - for (uid in relatives.relativeTrackedEntityInstanceUids) { - coroutineAPICallExecutor.wrap(storeError = true) { - trackerParentCallFactory.getTrackedEntityCall().getRelationshipEntityCall(uid) - }.fold( - onSuccess = { teiPayload -> teis.addAll(teiPayload.items()) }, - onFailure = { failedTeis.add(uid) }, - ) + for (item in relatives.getRelativeTrackedEntityInstances()) { + if (itemDoesNotExist(item)) { + coroutineAPICallExecutor.wrap(storeError = true) { + trackerParentCallFactory.getTrackedEntityCall().getRelationshipEntityCall(item) + }.fold( + onSuccess = { teiPayload -> teis.addAll(teiPayload.items()) }, + onFailure = { failedTeis.add(item.itemUid) }, + ) + } } teiPersistenceCallFactory.persistRelationships(teis) @@ -141,4 +167,8 @@ internal class RelationshipDownloadAndPersistCallFactory( relationshipStore.deleteById(r) } } + + private fun itemDoesNotExist(item: RelationshipItemRelative): Boolean { + return !relationshipItemStoreSelector.getElementStore(item).exists(item.itemUid) + } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipHandler.kt b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipHandler.kt index d5db5e2796..d2a5068137 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipHandler.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipHandler.kt @@ -32,7 +32,6 @@ import org.hisp.dhis.android.core.arch.handlers.internal.IdentifiableHandlerImpl import org.hisp.dhis.android.core.common.ObjectWithUid import org.hisp.dhis.android.core.relationship.Relationship import org.hisp.dhis.android.core.relationship.RelationshipConstraintType -import org.hisp.dhis.android.core.relationship.RelationshipItem import org.koin.core.annotation.Singleton @Singleton @@ -40,7 +39,6 @@ internal class RelationshipHandler( relationshipStore: RelationshipStore, private val relationshipItemStore: RelationshipItemStore, private val relationshipItemHandler: RelationshipItemHandler, - private val storeSelector: RelationshipItemElementStoreSelector, ) : IdentifiableHandlerImpl(relationshipStore) { override fun afterObjectHandled(o: Relationship, action: HandleAction) { @@ -60,10 +58,6 @@ internal class RelationshipHandler( return getExistingRelationshipUid(relationship) != null } - fun doesRelationshipItemExist(item: RelationshipItem): Boolean { - return storeSelector.getElementStore(item).exists(item.elementUid()) - } - fun deleteLinkedRelationships(entityUid: String) { relationshipItemStore.getByEntityUid(entityUid) .mapNotNull { it.relationship()?.uid() } diff --git a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipItemElementStoreSelector.kt b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipItemElementStoreSelector.kt index 2e1ddcf668..1dbec40ef8 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipItemElementStoreSelector.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipItemElementStoreSelector.kt @@ -32,4 +32,6 @@ import org.hisp.dhis.android.core.relationship.RelationshipItem internal interface RelationshipItemElementStoreSelector { fun getElementStore(item: RelationshipItem?): StoreWithState<*> + + fun getElementStore(item: RelationshipItemRelative): StoreWithState<*> } diff --git a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipItemElementStoreSelectorImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipItemElementStoreSelectorImpl.kt index 92ab65d988..44c7205a70 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipItemElementStoreSelectorImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipItemElementStoreSelectorImpl.kt @@ -31,6 +31,7 @@ import org.hisp.dhis.android.core.arch.db.stores.internal.StoreWithState import org.hisp.dhis.android.core.enrollment.internal.EnrollmentStore import org.hisp.dhis.android.core.event.internal.EventStore import org.hisp.dhis.android.core.relationship.RelationshipItem +import org.hisp.dhis.android.core.relationship.RelationshipItemTableInfo import org.hisp.dhis.android.core.trackedentity.internal.TrackedEntityInstanceStore import org.koin.core.annotation.Singleton @@ -41,12 +42,21 @@ internal class RelationshipItemElementStoreSelectorImpl( private val eventStore: EventStore, ) : RelationshipItemElementStoreSelector { override fun getElementStore(item: RelationshipItem?): StoreWithState<*> { - return if (item!!.hasTrackedEntityInstance()) { - trackedEntityInstanceStore - } else if (item.hasEnrollment()) { - enrollmentStore - } else { - eventStore + return getElementStoreByElementType(item?.elementType()) + } + + override fun getElementStore(item: RelationshipItemRelative): StoreWithState<*> { + return getElementStoreByElementType(item.itemType) + } + + private fun getElementStoreByElementType(elementType: String?): StoreWithState<*> { + return when (elementType) { + RelationshipItemTableInfo.Columns.TRACKED_ENTITY_INSTANCE -> + trackedEntityInstanceStore + RelationshipItemTableInfo.Columns.ENROLLMENT -> + enrollmentStore + else -> + eventStore } } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipItemRelative.kt b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipItemRelative.kt new file mode 100644 index 0000000000..076b0958bb --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipItemRelative.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.relationship.internal + +import org.hisp.dhis.android.core.relationship.RelationshipConstraintType + +internal data class RelationshipItemRelative( + val itemUid: String, + val itemType: String, + val relationshipTypeUid: String, + val constraintType: RelationshipConstraintType, +) diff --git a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipItemRelatives.kt b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipItemRelatives.kt new file mode 100644 index 0000000000..a57aa985bf --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipItemRelatives.kt @@ -0,0 +1,58 @@ +/* + * 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.relationship.internal + +internal class RelationshipItemRelatives { + private val relativeTrackedEntityInstanceItems: MutableSet = mutableSetOf() + private val relativeEnrollmentItems: MutableSet = mutableSetOf() + private val relativeEventItems: MutableSet = mutableSetOf() + + fun addTrackedEntityInstance(uid: RelationshipItemRelative) { + relativeTrackedEntityInstanceItems.add(uid) + } + + fun addEnrollment(uid: RelationshipItemRelative) { + relativeEnrollmentItems.add(uid) + } + + fun addEvent(uid: RelationshipItemRelative) { + relativeEventItems.add(uid) + } + + fun getRelativeTrackedEntityInstances(): Set { + return relativeTrackedEntityInstanceItems + } + + fun getRelativeEnrollments(): Set { + return relativeEnrollmentItems + } + + fun getRelativeEvents(): Set { + return relativeEventItems + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipTypeFields.java b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipTypeFields.kt similarity index 52% rename from core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipTypeFields.java rename to core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipTypeFields.kt index 3b86021076..e47d5f60d2 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipTypeFields.java +++ b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipTypeFields.kt @@ -25,49 +25,43 @@ * (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.relationship.internal -package org.hisp.dhis.android.core.relationship.internal; +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper +import org.hisp.dhis.android.core.common.Access +import org.hisp.dhis.android.core.common.internal.AccessFields +import org.hisp.dhis.android.core.common.internal.DataAccessFields +import org.hisp.dhis.android.core.relationship.RelationshipConstraint +import org.hisp.dhis.android.core.relationship.RelationshipType +import org.hisp.dhis.android.core.relationship.RelationshipTypeTableInfo -import org.hisp.dhis.android.core.arch.api.fields.internal.Field; -import org.hisp.dhis.android.core.arch.api.fields.internal.Fields; -import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper; -import org.hisp.dhis.android.core.common.Access; -import org.hisp.dhis.android.core.common.internal.AccessFields; -import org.hisp.dhis.android.core.common.internal.DataAccessFields; -import org.hisp.dhis.android.core.relationship.RelationshipConstraint; -import org.hisp.dhis.android.core.relationship.RelationshipType; -import org.hisp.dhis.android.core.relationship.RelationshipTypeTableInfo.Columns; - -public final class RelationshipTypeFields { - - private static final String B_IS_TO_A = "bIsToA"; - private static final String A_IS_TO_B = "aIsToB"; - private static final String FROM_CONSTRAINT = "fromConstraint"; - private static final String TO_CONSTRAINT = "toConstraint"; - private static final String ACCESS = "access"; +internal object RelationshipTypeFields { + private const val B_IS_TO_A = "bIsToA" + private const val A_IS_TO_B = "aIsToB" + private const val FROM_CONSTRAINT = "fromConstraint" + private const val TO_CONSTRAINT = "toConstraint" + private const val ACCESS = "access" // Used only for children appending, can't be used in query - public static final String CONSTRAINTS = "constraints"; - - private static final FieldsHelper fh = new FieldsHelper<>(); + const val CONSTRAINTS = "constraints" - static final Field lastUpdated = fh.lastUpdated(); + private val fh = FieldsHelper() - static final Fields allFields = Fields.builder() - .fields(fh.getIdentifiableFields()) - .fields( - fh.field(B_IS_TO_A), - fh.field(A_IS_TO_B), - fh.field(Columns.FROM_TO_NAME), - fh.field(Columns.TO_FROM_NAME), - fh.field(Columns.BIDIRECTIONAL), - fh.nestedField(FROM_CONSTRAINT) - .with(RelationshipConstraintFields.allFields), - fh.nestedField(TO_CONSTRAINT) - .with(RelationshipConstraintFields.allFields), - fh.nestedField(ACCESS).with(AccessFields.data.with(DataAccessFields.allFields)) - ).build(); + val lastUpdated = fh.lastUpdated() - private RelationshipTypeFields() { - } -} \ No newline at end of file + val allFields: Fields = Fields.builder() + .fields(fh.getIdentifiableFields()) + .fields( + fh.field(B_IS_TO_A), + fh.field(A_IS_TO_B), + fh.field(RelationshipTypeTableInfo.Columns.FROM_TO_NAME), + fh.field(RelationshipTypeTableInfo.Columns.TO_FROM_NAME), + fh.field(RelationshipTypeTableInfo.Columns.BIDIRECTIONAL), + fh.nestedField(FROM_CONSTRAINT) + .with(RelationshipConstraintFields.allFields), + fh.nestedField(TO_CONSTRAINT) + .with(RelationshipConstraintFields.allFields), + fh.nestedField(ACCESS).with(AccessFields.data.with(DataAccessFields.allFields)), + ).build() +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/TrackerDataViewFields.kt b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/TrackerDataViewFields.kt new file mode 100644 index 0000000000..2b52e30393 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/TrackerDataViewFields.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.hisp.dhis.android.core.relationship.internal + +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper +import org.hisp.dhis.android.core.relationship.TrackerDataView + +internal object TrackerDataViewFields { + private const val ATTRIBUTES = "attributes" + private const val DATA_ELEMENTS = "dataElements" + + private val fh = FieldsHelper() + + val allFields: Fields = Fields.builder() + .fields( + fh.field(ATTRIBUTES), + fh.field(DATA_ELEMENTS), + ).build() +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/settings/AnalyticsDhisVisualization.java b/core/src/main/java/org/hisp/dhis/android/core/settings/AnalyticsDhisVisualization.java index b4516ea181..c06f310a32 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/settings/AnalyticsDhisVisualization.java +++ b/core/src/main/java/org/hisp/dhis/android/core/settings/AnalyticsDhisVisualization.java @@ -40,6 +40,7 @@ import com.google.auto.value.AutoValue; import org.hisp.dhis.android.core.arch.db.adapters.enums.internal.AnalyticsDhisVisualizationScopeColumnAdapter; +import org.hisp.dhis.android.core.arch.db.adapters.enums.internal.AnalyticsDhisVisualizationTypeColumnAdapter; import org.hisp.dhis.android.core.common.CoreObject; import org.hisp.dhis.android.core.common.ObjectWithUidInterface; @@ -67,11 +68,18 @@ public abstract class AnalyticsDhisVisualization implements CoreObject, ObjectWi public abstract String uid(); @Nullable + @JsonProperty public abstract String name(); @Nullable + @JsonProperty public abstract String timestamp(); + @NonNull + @JsonProperty + @ColumnAdapter(AnalyticsDhisVisualizationTypeColumnAdapter.class) + public abstract AnalyticsDhisVisualizationType type(); + public static AnalyticsDhisVisualization create(Cursor cursor) { return AutoValue_AnalyticsDhisVisualization.createFromCursor(cursor); } @@ -101,9 +109,21 @@ public abstract static class Builder { public abstract Builder name(String name); - @JsonProperty("timestamp") public abstract Builder timestamp(String timestamp); - public abstract AnalyticsDhisVisualization build(); + public abstract Builder type(AnalyticsDhisVisualizationType type); + + abstract AnalyticsDhisVisualization autoBuild(); + + abstract AnalyticsDhisVisualizationType type(); + + public AnalyticsDhisVisualization build() { + try { + type(); + } catch (IllegalStateException e) { + type(AnalyticsDhisVisualizationType.VISUALIZATION); + } + return autoBuild(); + } } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/settings/AnalyticsDhisVisualizationTableInfo.kt b/core/src/main/java/org/hisp/dhis/android/core/settings/AnalyticsDhisVisualizationTableInfo.kt index bdf6151182..1710b3b9b8 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/settings/AnalyticsDhisVisualizationTableInfo.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/settings/AnalyticsDhisVisualizationTableInfo.kt @@ -57,6 +57,7 @@ object AnalyticsDhisVisualizationTableInfo { GROUP_NAME, NAME, TIME_STAMP, + TYPE, ) } @@ -68,6 +69,7 @@ object AnalyticsDhisVisualizationTableInfo { const val GROUP_NAME = "groupName" const val NAME = "name" const val TIME_STAMP = "timestamp" + const val TYPE = "type" } } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/settings/AnalyticsDhisVisualizationType.kt b/core/src/main/java/org/hisp/dhis/android/core/settings/AnalyticsDhisVisualizationType.kt new file mode 100644 index 0000000000..a0025b1422 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/settings/AnalyticsDhisVisualizationType.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.settings + +enum class AnalyticsDhisVisualizationType { + VISUALIZATION, + TRACKER_VISUALIZATION, +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/settings/GeneralSettingTableInfo.java b/core/src/main/java/org/hisp/dhis/android/core/settings/GeneralSettingTableInfo.java deleted file mode 100644 index 0baa3ce83d..0000000000 --- a/core/src/main/java/org/hisp/dhis/android/core/settings/GeneralSettingTableInfo.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2004-2023, University of Oslo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * Neither the name of the HISP project nor the names of its contributors may - * be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.hisp.dhis.android.core.settings; - -import org.hisp.dhis.android.core.arch.db.tableinfos.TableInfo; -import org.hisp.dhis.android.core.arch.helpers.CollectionsHelper; -import org.hisp.dhis.android.core.common.CoreColumns; - -public final class GeneralSettingTableInfo { - - private GeneralSettingTableInfo() { - } - - public static final TableInfo TABLE_INFO = new TableInfo() { - - @Override - public String name() { - return "GeneralSetting"; - } - - @Override - public CoreColumns columns() { - return new Columns(); - } - }; - - public static class Columns extends CoreColumns { - public static final String ENCRYPT_DB = "encryptDB"; - public static final String LAST_UPDATED = "lastUpdated"; - public static final String RESERVED_VALUES = "reservedValues"; - public static final String SMS_GATEWAY = "smsGateway"; - public static final String SMS_RESULT_SENDER = "smsResultSender"; - public static final String MATOMO_ID = "matomoID"; - public static final String MATOMO_URL = "matomoURL"; - public static final String ALLOW_SCREEN_CAPTURE = "allowScreenCapture"; - public static final String MESSAGE_OF_THE_DAY = "messageOfTheDay"; - public static final String EXPERIMENTAL_FEATURES = "experimentalFeatures"; - - @Override - public String[] all() { - return CollectionsHelper.appendInNewArray(super.all(), - ENCRYPT_DB, - LAST_UPDATED, - RESERVED_VALUES, - SMS_GATEWAY, - SMS_RESULT_SENDER, - MATOMO_ID, - MATOMO_URL, - ALLOW_SCREEN_CAPTURE, - MESSAGE_OF_THE_DAY, - EXPERIMENTAL_FEATURES - ); - } - } -} \ No newline at end of file diff --git a/core/src/main/java/org/hisp/dhis/android/core/settings/GeneralSettingTableInfo.kt b/core/src/main/java/org/hisp/dhis/android/core/settings/GeneralSettingTableInfo.kt new file mode 100644 index 0000000000..b3051e9af1 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/settings/GeneralSettingTableInfo.kt @@ -0,0 +1,77 @@ +/* + * 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.settings + +import org.hisp.dhis.android.core.arch.db.tableinfos.TableInfo +import org.hisp.dhis.android.core.arch.helpers.CollectionsHelper +import org.hisp.dhis.android.core.common.CoreColumns + +object GeneralSettingTableInfo { + val TABLE_INFO: TableInfo = object : TableInfo() { + override fun name(): String { + return "GeneralSetting" + } + + override fun columns(): CoreColumns { + return Columns() + } + } + + class Columns : CoreColumns() { + override fun all(): Array { + return CollectionsHelper.appendInNewArray( + super.all(), + ENCRYPT_DB, + LAST_UPDATED, + RESERVED_VALUES, + SMS_GATEWAY, + SMS_RESULT_SENDER, + MATOMO_ID, + MATOMO_URL, + ALLOW_SCREEN_CAPTURE, + MESSAGE_OF_THE_DAY, + EXPERIMENTAL_FEATURES, + BYPASS_DHIS2_VERSION_CHECK, + ) + } + + companion object { + const val ENCRYPT_DB = "encryptDB" + const val LAST_UPDATED = "lastUpdated" + const val RESERVED_VALUES = "reservedValues" + const val SMS_GATEWAY = "smsGateway" + const val SMS_RESULT_SENDER = "smsResultSender" + const val MATOMO_ID = "matomoID" + const val MATOMO_URL = "matomoURL" + const val ALLOW_SCREEN_CAPTURE = "allowScreenCapture" + const val MESSAGE_OF_THE_DAY = "messageOfTheDay" + const val EXPERIMENTAL_FEATURES = "experimentalFeatures" + const val BYPASS_DHIS2_VERSION_CHECK = "bypassDHIS2VersionCheck" + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/settings/GeneralSettings.java b/core/src/main/java/org/hisp/dhis/android/core/settings/GeneralSettings.java index 2acd5b1f5a..4891b1992b 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/settings/GeneralSettings.java +++ b/core/src/main/java/org/hisp/dhis/android/core/settings/GeneralSettings.java @@ -116,6 +116,9 @@ public String numberSmsConfirmation() { @ColumnAdapter(StringListColumnAdapter.class) public abstract List experimentalFeatures(); + @Nullable + public abstract Boolean bypassDHIS2VersionCheck(); + public static GeneralSettings create(Cursor cursor) { return $AutoValue_GeneralSettings.createFromCursor(cursor); } @@ -158,6 +161,8 @@ public abstract static class Builder { public abstract Builder experimentalFeatures(List experimentalFeatures); + public abstract Builder bypassDHIS2VersionCheck(Boolean bypassDHIS2VersionCheck); + public abstract GeneralSettings build(); } } \ No newline at end of file diff --git a/core/src/main/java/org/hisp/dhis/android/core/settings/LatestAppVersion.java b/core/src/main/java/org/hisp/dhis/android/core/settings/LatestAppVersion.java index be7a2b8b8c..2ac7c4c570 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/settings/LatestAppVersion.java +++ b/core/src/main/java/org/hisp/dhis/android/core/settings/LatestAppVersion.java @@ -37,6 +37,7 @@ import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; import com.google.auto.value.AutoValue; +import org.hisp.dhis.android.core.common.BaseObject; import org.hisp.dhis.android.core.common.CoreObject; @AutoValue @@ -63,7 +64,7 @@ public static Builder builder() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public abstract static class Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); public abstract Builder version(String version); diff --git a/core/src/main/java/org/hisp/dhis/android/core/settings/SystemSetting.java b/core/src/main/java/org/hisp/dhis/android/core/settings/SystemSetting.java index e82fb9eabe..d21a73a038 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/settings/SystemSetting.java +++ b/core/src/main/java/org/hisp/dhis/android/core/settings/SystemSetting.java @@ -44,7 +44,8 @@ public abstract class SystemSetting implements CoreObject { public enum SystemSettingKey { FLAG, - STYLE + STYLE, + DEFAULT_BASE_MAP, } @Nullable diff --git a/core/src/main/java/org/hisp/dhis/android/core/settings/SystemSettingCollectionRepository.kt b/core/src/main/java/org/hisp/dhis/android/core/settings/SystemSettingCollectionRepository.kt index c6cd919f98..1907d0a216 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/settings/SystemSettingCollectionRepository.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/settings/SystemSettingCollectionRepository.kt @@ -75,6 +75,10 @@ class SystemSettingCollectionRepository internal constructor( return byKey().eq(SystemSettingKey.STYLE).one() } + fun defaultBaseMap(): ReadOnlyOneObjectRepositoryFinalImpl { + return byKey().eq(SystemSettingKey.DEFAULT_BASE_MAP).one() + } + internal companion object { val childrenAppenders: ChildrenAppenderGetter = emptyMap() } diff --git a/core/src/main/java/org/hisp/dhis/android/core/settings/SystemSettings.kt b/core/src/main/java/org/hisp/dhis/android/core/settings/SystemSettings.kt index 23625291a5..d33eb1fb8c 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/settings/SystemSettings.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/settings/SystemSettings.kt @@ -30,5 +30,6 @@ package org.hisp.dhis.android.core.settings data class SystemSettings( val keyFlag: String?, val keyStyle: String?, + val keyDefaultBaseMap: String?, val keyBingMapsApiKey: String?, ) diff --git a/core/src/main/java/org/hisp/dhis/android/core/settings/internal/AnalyticsDhisVisualizationCleaner.kt b/core/src/main/java/org/hisp/dhis/android/core/settings/internal/AnalyticsDhisVisualizationCleaner.kt new file mode 100644 index 0000000000..e6e4b022db --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/settings/internal/AnalyticsDhisVisualizationCleaner.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.settings.internal + +import org.hisp.dhis.android.core.arch.db.querybuilders.internal.WhereClauseBuilder +import org.hisp.dhis.android.core.settings.AnalyticsDhisVisualizationTableInfo +import org.hisp.dhis.android.core.settings.AnalyticsDhisVisualizationType +import org.koin.core.annotation.Singleton + +@Singleton +internal class AnalyticsDhisVisualizationCleaner( + private val store: AnalyticsDhisVisualizationStore, +) { + + fun deleteNotPresent(uids: List, type: AnalyticsDhisVisualizationType) { + val whereClause = WhereClauseBuilder() + .appendKeyStringValue(AnalyticsDhisVisualizationTableInfo.Columns.TYPE, type.name) + .appendNotInKeyStringValues(AnalyticsDhisVisualizationTableInfo.Columns.UID, uids) + .build() + + store.deleteWhere(whereClause) + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/settings/internal/AnalyticsDhisVisualizationStoreImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/settings/internal/AnalyticsDhisVisualizationStoreImpl.kt index 46b703c7aa..8b645ef2b0 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/settings/internal/AnalyticsDhisVisualizationStoreImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/settings/internal/AnalyticsDhisVisualizationStoreImpl.kt @@ -61,6 +61,7 @@ internal class AnalyticsDhisVisualizationStoreImpl( w.bind(5, o.groupName()) w.bind(6, o.name()) w.bind(7, o.timestamp()) + w.bind(8, o.type()) } private val WHERE_UPDATE_BINDER = WhereStatementBinder { _: AnalyticsDhisVisualization, _: StatementWrapper -> diff --git a/core/src/main/java/org/hisp/dhis/android/core/settings/internal/ApkDistributionVersion.kt b/core/src/main/java/org/hisp/dhis/android/core/settings/internal/ApkDistributionVersion.kt new file mode 100644 index 0000000000..7aced03d1f --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/settings/internal/ApkDistributionVersion.kt @@ -0,0 +1,36 @@ +/* + * 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.settings.internal + +internal data class ApkDistributionVersion( + val version: String?, + val downloadURL: String?, + val isDefault: Boolean?, + val userGroups: List?, +) diff --git a/core/src/main/java/org/hisp/dhis/android/core/settings/internal/GeneralSettingCall.kt b/core/src/main/java/org/hisp/dhis/android/core/settings/internal/GeneralSettingCall.kt index 42bbc74cb6..3e6a85a922 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/settings/internal/GeneralSettingCall.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/settings/internal/GeneralSettingCall.kt @@ -31,6 +31,7 @@ import org.hisp.dhis.android.core.arch.api.executors.internal.CoroutineAPICallEx import org.hisp.dhis.android.core.arch.helpers.Result import org.hisp.dhis.android.core.maintenance.D2Error import org.hisp.dhis.android.core.settings.GeneralSettings +import org.hisp.dhis.android.core.systeminfo.internal.DHISVersionManagerImpl import org.koin.core.annotation.Singleton @Singleton @@ -39,6 +40,7 @@ internal class GeneralSettingCall( private val settingAppService: SettingAppService, private val appVersionManager: SettingsAppInfoManager, coroutineAPICallExecutor: CoroutineAPICallExecutor, + private val versionManager: DHISVersionManagerImpl, ) : BaseSettingCall(coroutineAPICallExecutor) { private var cachedValue: GeneralSettings? = null @@ -59,6 +61,7 @@ internal class GeneralSettingCall( override fun process(item: GeneralSettings?) { cachedValue = item val generalSettingsList = listOfNotNull(item) + versionManager.setBypassVersion(item?.bypassDHIS2VersionCheck()) generalSettingHandler.handleMany(generalSettingsList) } @@ -67,6 +70,6 @@ internal class GeneralSettingCall( appVersionManager.updateAppVersion() return coroutineAPICallExecutor.wrap(storeError = false) { settingAppService.generalSettings(appVersionManager.getDataStoreVersion()) - }.getOrThrow().encryptDB() + }.getOrThrow().also { versionManager.setBypassVersion(it.bypassDHIS2VersionCheck()) }.encryptDB() } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/settings/internal/GeneralSettingStoreImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/settings/internal/GeneralSettingStoreImpl.kt index 0328bd1ac1..4d230d9ff1 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/settings/internal/GeneralSettingStoreImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/settings/internal/GeneralSettingStoreImpl.kt @@ -64,6 +64,7 @@ internal class GeneralSettingStoreImpl( w.bind(8, o.allowScreenCapture()) w.bind(9, o.messageOfTheDay()) w.bind(10, StringListColumnAdapter.serialize(o.experimentalFeatures())) + w.bind(11, o.bypassDHIS2VersionCheck()) } private val WHERE_UPDATE_BINDER = WhereStatementBinder { diff --git a/core/src/main/java/org/hisp/dhis/android/core/settings/internal/LatestAppVersionCall.kt b/core/src/main/java/org/hisp/dhis/android/core/settings/internal/LatestAppVersionCall.kt index 3a129961ed..368184040b 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/settings/internal/LatestAppVersionCall.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/settings/internal/LatestAppVersionCall.kt @@ -32,17 +32,36 @@ import org.hisp.dhis.android.core.arch.api.executors.internal.CoroutineAPICallEx import org.hisp.dhis.android.core.arch.helpers.Result import org.hisp.dhis.android.core.maintenance.D2Error import org.hisp.dhis.android.core.settings.LatestAppVersion +import org.hisp.dhis.android.core.user.UserModule import org.koin.core.annotation.Singleton @Singleton internal class LatestAppVersionCall( private val latestAppVersionHandler: LatestAppVersionHandler, private val settingAppService: SettingAppService, + private val userModule: UserModule, + private val versionComparator: LatestAppVersionComparator, coroutineAPICallExecutor: CoroutineAPICallExecutor, ) : BaseSettingCall(coroutineAPICallExecutor) { override suspend fun tryFetch(storeError: Boolean): Result { - return coroutineAPICallExecutor.wrap(storeError = storeError) { settingAppService.latestAppVersion() } + return coroutineAPICallExecutor.wrap(storeError = storeError) { + val userGroupUids = userModule.userGroups().blockingGetUids() + + val versions = settingAppService.versions().items() + + val filteredVersions = versions.filter { version -> + version.userGroups?.any { userGroupUid -> + userGroupUids.contains(userGroupUid) + } ?: false + } + + val version = filteredVersions.maxWithOrNull(versionComparator.comparator) + ?: versions.find { it.isDefault == true } + + version?.let { LatestAppVersion.builder().version(it.version).downloadURL(it.downloadURL).build() } + ?: settingAppService.latestAppVersion() + } } override fun process(item: LatestAppVersion?) { diff --git a/core/src/main/java/org/hisp/dhis/android/core/settings/internal/LatestAppVersionComparator.kt b/core/src/main/java/org/hisp/dhis/android/core/settings/internal/LatestAppVersionComparator.kt new file mode 100644 index 0000000000..ff4a0caff5 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/settings/internal/LatestAppVersionComparator.kt @@ -0,0 +1,48 @@ +/* + * 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.settings.internal + +import org.koin.core.annotation.Singleton + +@Singleton +internal class LatestAppVersionComparator { + val comparator: Comparator = Comparator { v1, v2 -> + val partsV1 = v1.version?.split(".")?.map { it.toIntOrNull() ?: 0 } ?: emptyList() + val partsV2 = v2.version?.split(".")?.map { it.toIntOrNull() ?: 0 } ?: emptyList() + var result = 0 + val maxLength = maxOf(partsV1.size, partsV2.size) + for (i in 0 until maxLength) { + val partV1 = partsV1.getOrElse(i) { 0 } + val partV2 = partsV2.getOrElse(i) { 0 } + result = partV1.compareTo(partV2) + if (result != 0) break + } + result + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/settings/internal/SettingAppService.kt b/core/src/main/java/org/hisp/dhis/android/core/settings/internal/SettingAppService.kt index 31d96802de..e4a1912b85 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/settings/internal/SettingAppService.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/settings/internal/SettingAppService.kt @@ -27,6 +27,7 @@ */ package org.hisp.dhis.android.core.settings.internal +import org.hisp.dhis.android.core.arch.api.payload.internal.Payload import org.hisp.dhis.android.core.settings.AnalyticsSettings import org.hisp.dhis.android.core.settings.AppearanceSettings import org.hisp.dhis.android.core.settings.DataSetSettings @@ -79,6 +80,10 @@ internal class SettingAppService( return settingService.latestAppVersion("$APK_DISTRIBUTION_NAMESPACE/latestVersion") } + suspend fun versions(): Payload { + return settingService.versions("$APK_DISTRIBUTION_NAMESPACE/versions") + } + private fun getNamespace(version: SettingsAppDataStoreVersion): String { return when (version) { SettingsAppDataStoreVersion.V1_1 -> ANDROID_APP_NAMESPACE_V1 diff --git a/core/src/main/java/org/hisp/dhis/android/core/settings/internal/SettingService.kt b/core/src/main/java/org/hisp/dhis/android/core/settings/internal/SettingService.kt index 9c53bdf686..46279b27b5 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/settings/internal/SettingService.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/settings/internal/SettingService.kt @@ -29,6 +29,7 @@ package org.hisp.dhis.android.core.settings.internal import org.hisp.dhis.android.core.arch.api.fields.internal.Fields import org.hisp.dhis.android.core.arch.api.filters.internal.Which +import org.hisp.dhis.android.core.arch.api.payload.internal.Payload import org.hisp.dhis.android.core.settings.* import retrofit2.http.GET import retrofit2.http.Query @@ -69,4 +70,7 @@ internal interface SettingService { @GET suspend fun latestAppVersion(@Url url: String): LatestAppVersion + + @GET + suspend fun versions(@Url url: String): Payload } diff --git a/core/src/main/java/org/hisp/dhis/android/core/settings/internal/SystemSettingsFields.kt b/core/src/main/java/org/hisp/dhis/android/core/settings/internal/SystemSettingsFields.kt index 2e313d2fdb..a5939e0cf7 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/settings/internal/SystemSettingsFields.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/settings/internal/SystemSettingsFields.kt @@ -34,6 +34,7 @@ import org.hisp.dhis.android.core.settings.SystemSettings internal object SystemSettingsFields { private const val KEY_FLAG = "keyFlag" private const val KEY_STYLE = "keyStyle" + private const val KEY_DEFAULT_BASE_MAP = "keyDefaultBaseMap" private const val KEY_BING_MAPS_API_KEY = "keyBingMapsApiKey" private val fh = FieldsHelper() @@ -42,6 +43,7 @@ internal object SystemSettingsFields { .fields( fh.field(KEY_FLAG), fh.field(KEY_STYLE), + fh.field(KEY_DEFAULT_BASE_MAP), ).build() val bingApiKey: Fields = Fields.builder() diff --git a/core/src/main/java/org/hisp/dhis/android/core/settings/internal/SystemSettingsSplitter.kt b/core/src/main/java/org/hisp/dhis/android/core/settings/internal/SystemSettingsSplitter.kt index 1fc6606b85..518d80d0b0 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/settings/internal/SystemSettingsSplitter.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/settings/internal/SystemSettingsSplitter.kt @@ -42,7 +42,11 @@ internal class SystemSettingsSplitter { .key(SystemSetting.SystemSettingKey.STYLE) .value(settings.keyStyle) .build() + val keyDefaultBaseMap = SystemSetting.builder() + .key(SystemSetting.SystemSettingKey.DEFAULT_BASE_MAP) + .value(settings.keyDefaultBaseMap) + .build() - return listOf(flag, style) + return listOf(flag, style, keyDefaultBaseMap) } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/sms/data/localdbrepository/internal/SMSMetadataId.java b/core/src/main/java/org/hisp/dhis/android/core/sms/data/localdbrepository/internal/SMSMetadataId.java index 0ce5747c22..79d4bf82ec 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/sms/data/localdbrepository/internal/SMSMetadataId.java +++ b/core/src/main/java/org/hisp/dhis/android/core/sms/data/localdbrepository/internal/SMSMetadataId.java @@ -60,7 +60,7 @@ public static SMSMetadataId create(Cursor cursor) { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder { + public abstract static class Builder { public abstract Builder id(Long id); public abstract Builder type(SMSConsts.MetadataType smsMetadataIdType); diff --git a/core/src/main/java/org/hisp/dhis/android/core/sms/data/localdbrepository/internal/SMSOngoingSubmission.java b/core/src/main/java/org/hisp/dhis/android/core/sms/data/localdbrepository/internal/SMSOngoingSubmission.java index d64198c046..392fc48c97 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/sms/data/localdbrepository/internal/SMSOngoingSubmission.java +++ b/core/src/main/java/org/hisp/dhis/android/core/sms/data/localdbrepository/internal/SMSOngoingSubmission.java @@ -60,7 +60,7 @@ public static SMSOngoingSubmission create(Cursor cursor) { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder { + public abstract static class Builder { public abstract Builder id(Long id); public abstract Builder submissionId(Integer submissionId); diff --git a/core/src/main/java/org/hisp/dhis/android/core/sms/data/webapirepository/internal/MetadataResponse.java b/core/src/main/java/org/hisp/dhis/android/core/sms/data/webapirepository/internal/MetadataResponse.java index d7c636121a..b76cf37510 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/sms/data/webapirepository/internal/MetadataResponse.java +++ b/core/src/main/java/org/hisp/dhis/android/core/sms/data/webapirepository/internal/MetadataResponse.java @@ -70,7 +70,7 @@ public abstract static class MetadataSystemInfo { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder { + public abstract static class Builder { public abstract MetadataSystemInfo.Builder date(Date date); @@ -85,7 +85,7 @@ public abstract static class MetadataId { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder { + public abstract static class Builder { public abstract MetadataId.Builder id(String id); @@ -95,7 +95,7 @@ public static abstract class Builder { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder { + public abstract static class Builder { public abstract MetadataResponse.Builder system(MetadataSystemInfo systemInfo); diff --git a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/DHISPatchVersion.kt b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/DHISPatchVersion.kt index 28cbefc438..54e39897ec 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/DHISPatchVersion.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/DHISPatchVersion.kt @@ -69,12 +69,15 @@ enum class DHISPatchVersion(val majorVersion: DHISVersion, val strValue: String, V2_40_0(DHISVersion.V2_40, "2.40.0", SMSVersion.V2), V2_41_0(DHISVersion.V2_41, "2.41.0", SMSVersion.V2), + + UNKNOWN(DHISVersion.UNKNOWN, "UNKNOWN", SMSVersion.V2), ; companion object { @JvmStatic - fun getValue(versionStr: String): DHISPatchVersion? { - return values().find { versionStr == it.strValue || versionStr.startsWith(it.strValue + "-") } + fun getValue(versionStr: String, bypassDHIS2VersionCheck: Boolean? = false): DHISPatchVersion? { + return entries.find { versionStr == it.strValue || versionStr.startsWith(it.strValue + "-") } + ?: bypassDHIS2VersionCheck.takeIf { it == true }?.let { UNKNOWN } } } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/DHISVersion.kt b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/DHISVersion.kt index f885f73313..d8fd0117a1 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/DHISVersion.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/DHISVersion.kt @@ -40,23 +40,25 @@ enum class DHISVersion(internal val prefix: String, internal val supported: Bool V2_38("2.38"), V2_39("2.39"), V2_40("2.40"), - V2_41("2.41", false), + V2_41("2.41"), + UNKNOWN("UNKNOWN", false), ; companion object { @JvmStatic - fun getValue(versionStr: String): DHISVersion? { - return values().find { versionStr.startsWith(it.prefix).and(it.supported) } + fun getValue(versionStr: String, bypassDHIS2VersionCheck: Boolean? = false): DHISVersion? { + return entries.find { versionStr.startsWith(it.prefix).and(it.supported) } + ?: bypassDHIS2VersionCheck.takeIf { it == true }?.let { UNKNOWN } } @JvmStatic - fun isAllowedVersion(versionStr: String): Boolean { - return getValue(versionStr) != null + fun isAllowedVersion(versionStr: String, bypassDHIS2VersionCheck: Boolean? = false): Boolean { + return getValue(versionStr, bypassDHIS2VersionCheck) != null } @JvmStatic fun allowedVersionsAsStr(): Array { - return values().filter { it.supported } + return entries.filter { it.supported } .map { it.prefix } .toTypedArray() } diff --git a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/DHISVersionManager.kt b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/DHISVersionManager.kt index 4c12f2d6cc..d569ed5e69 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/DHISVersionManager.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/DHISVersionManager.kt @@ -31,6 +31,7 @@ interface DHISVersionManager { fun getVersion(): DHISVersion fun getPatchVersion(): DHISPatchVersion? fun getSmsVersion(): SMSVersion? + fun getBypassVersion(): Boolean? /** * Check if the current version is equal to the version passed as parameter. @@ -55,4 +56,6 @@ interface DHISVersionManager { * @return True if current version is greater or equal than the parameter. */ fun isGreaterOrEqualThan(version: DHISVersion): Boolean + + fun setBypassVersion(bypassDHIS2VersionCheck: Boolean?) } diff --git a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/Ping.kt b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/Ping.kt new file mode 100644 index 0000000000..501f6f434a --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/Ping.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.hisp.dhis.android.core.systeminfo + +import io.reactivex.Single +import org.hisp.dhis.android.core.maintenance.D2Error + +interface Ping { + + fun get(): Single + + @Throws(D2Error::class) + fun blockingGet(): String +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/SMSVersion.kt b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/SMSVersion.kt index 43e26d0e24..e743b650db 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/SMSVersion.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/SMSVersion.kt @@ -34,18 +34,18 @@ enum class SMSVersion(val intValue: Int) { companion object { @JvmStatic - fun getValue(versionStr: String): SMSVersion? { - val patchVersion = DHISPatchVersion.getValue(versionStr) + fun getValue(versionStr: String, bypassDHIS2VersionCheck: Boolean? = false): SMSVersion? { + val patchVersion = DHISPatchVersion.getValue(versionStr, bypassDHIS2VersionCheck) return if (patchVersion == null) { - DHISVersion.getValue(versionStr)?.let { getLatestInDHISVersion(it) } + DHISVersion.getValue(versionStr, bypassDHIS2VersionCheck)?.let { getLatestInDHISVersion(it) } } else { patchVersion.smsVersion } } private fun getLatestInDHISVersion(dhisVersion: DHISVersion): SMSVersion? { - return DHISPatchVersion.values() + return DHISPatchVersion.entries .filter { it.majorVersion == dhisVersion && it.smsVersion != null } .fold(null) { latest: SMSVersion?, version -> if (latest == null || latest.intValue < version.smsVersion!!.intValue) { diff --git a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/SystemInfo.java b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/SystemInfo.java index d552c09698..49a1ade426 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/SystemInfo.java +++ b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/SystemInfo.java @@ -76,7 +76,7 @@ public static Builder builder() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder serverDate(Date serverDate); public abstract Builder dateFormat(String dateFormat); diff --git a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/SystemInfoModule.kt b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/SystemInfoModule.kt index e72c1337f0..a2fe3ab7bf 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/SystemInfoModule.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/SystemInfoModule.kt @@ -30,4 +30,5 @@ package org.hisp.dhis.android.core.systeminfo interface SystemInfoModule { fun versionManager(): DHISVersionManager fun systemInfo(): SystemInfoObjectRepository + fun ping(): Ping } diff --git a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/DHISVersionManagerImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/DHISVersionManagerImpl.kt index 74b0115a17..59ce60ca5a 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/DHISVersionManagerImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/DHISVersionManagerImpl.kt @@ -43,11 +43,12 @@ internal class DHISVersionManagerImpl internal constructor( private var version: DHISVersion? = null private var patchVersion: DHISPatchVersion? = null private var smsVersion: SMSVersion? = null + private var bypassDHIS2Version: Boolean? = null override fun getVersion(): DHISVersion { return version ?: systemInfoStore.selectFirst()?.let { systemInfo -> - systemInfo.version()?.let { DHISVersion.getValue(it) } + systemInfo.version()?.let { DHISVersion.getValue(it, getBypassVersion()) } .also { dhisVersion -> version = dhisVersion } } ?: throw D2Error.builder() @@ -60,7 +61,7 @@ internal class DHISVersionManagerImpl internal constructor( override fun getPatchVersion(): DHISPatchVersion? { return patchVersion ?: systemInfoStore.selectFirst()?.let { systemInfo -> - systemInfo.version()?.let { DHISPatchVersion.getValue(it) } + systemInfo.version()?.let { DHISPatchVersion.getValue(it, getBypassVersion()) } .also { patch -> patchVersion = patch } } } @@ -68,11 +69,15 @@ internal class DHISVersionManagerImpl internal constructor( override fun getSmsVersion(): SMSVersion? { return smsVersion ?: systemInfoStore.selectFirst()?.let { systemInfo -> - systemInfo.version()?.let { SMSVersion.getValue(it) } + systemInfo.version()?.let { SMSVersion.getValue(it, getBypassVersion()) } .also { sms -> smsVersion = sms } } } + override fun getBypassVersion(): Boolean? { + return bypassDHIS2Version + } + override fun isVersion(version: DHISVersion): Boolean { return version === getVersion() } @@ -86,8 +91,12 @@ internal class DHISVersionManagerImpl internal constructor( } internal fun setVersion(versionStr: String) { - version = DHISVersion.getValue(versionStr) - patchVersion = DHISPatchVersion.getValue(versionStr) - smsVersion = SMSVersion.getValue(versionStr) + version = DHISVersion.getValue(versionStr, getBypassVersion()) + patchVersion = DHISPatchVersion.getValue(versionStr, getBypassVersion()) + smsVersion = SMSVersion.getValue(versionStr, getBypassVersion()) + } + + override fun setBypassVersion(bypassDHIS2VersionCheck: Boolean?) { + bypassDHIS2Version = bypassDHIS2VersionCheck } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/PingCall.kt b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/PingCall.kt index 361404e417..3d9ba005e7 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/PingCall.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/PingCall.kt @@ -32,7 +32,7 @@ import org.hisp.dhis.android.core.arch.call.internal.DownloadProvider import org.koin.core.annotation.Singleton @Singleton -class PingCall internal constructor( +internal class PingCall internal constructor( private val pingService: PingService, private val coroutineAPICallExecutor: CoroutineAPICallExecutor, ) : DownloadProvider { diff --git a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/PingImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/PingImpl.kt new file mode 100644 index 0000000000..c57c4fadcb --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/PingImpl.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.hisp.dhis.android.core.systeminfo.internal + +import io.reactivex.Single +import kotlinx.coroutines.rx2.rxSingle +import org.hisp.dhis.android.core.maintenance.D2Error +import org.hisp.dhis.android.core.maintenance.D2ErrorCode +import org.hisp.dhis.android.core.maintenance.D2ErrorComponent +import org.hisp.dhis.android.core.systeminfo.Ping +import org.koin.core.annotation.Singleton +import java.io.IOException + +@Singleton +class PingImpl internal constructor( + private val pingService: PingService, +) : Ping { + + override fun get(): Single { + return rxSingle { checkPing() } + } + + @Throws(D2Error::class) + override fun blockingGet(): String { + return get().blockingGet() + } + + @Suppress("TooGenericExceptionCaught") + private suspend fun checkPing(): String { + try { + val response = pingService.getPing() + return response.takeIf { it.isSuccessful }?.body()?.string().takeIf { it == "pong" } + ?: throw IOException("Ping to the server failed with status code: ${response.code()}") + } catch (e: Exception) { + throw toD2Error(e) + } + } + + private fun toD2Error(e: Exception): D2Error { + return D2Error.builder() + .originalException(e) + .errorCode(D2ErrorCode.API_UNSUCCESSFUL_RESPONSE) + .errorDescription("Unable to ping the server.") + .errorComponent(D2ErrorComponent.Server) + .build() + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/PingService.kt b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/PingService.kt index ab97b7b41a..44849eae8f 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/PingService.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/PingService.kt @@ -27,10 +27,12 @@ */ package org.hisp.dhis.android.core.systeminfo.internal +import okhttp3.ResponseBody +import retrofit2.Response import retrofit2.http.GET internal interface PingService { @GET("system/ping") - suspend fun getPing(): String + suspend fun getPing(): Response } diff --git a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/SystemInfoCall.kt b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/SystemInfoCall.kt index e76ee45e50..03c4c3ecfd 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/SystemInfoCall.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/SystemInfoCall.kt @@ -55,7 +55,7 @@ class SystemInfoCall internal constructor( }.fold( onSuccess = { systemInfo -> val version = systemInfo.version() - if (version != null && isAllowedVersion(version)) { + if (version != null && isAllowedVersion(version, versionManager.getBypassVersion())) { versionManager.setVersion(version) } else { throw D2Error.builder() diff --git a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/SystemInfoModuleImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/SystemInfoModuleImpl.kt index 31d14d589b..477443cdfc 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/SystemInfoModuleImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/systeminfo/internal/SystemInfoModuleImpl.kt @@ -28,6 +28,7 @@ package org.hisp.dhis.android.core.systeminfo.internal import org.hisp.dhis.android.core.systeminfo.DHISVersionManager +import org.hisp.dhis.android.core.systeminfo.Ping import org.hisp.dhis.android.core.systeminfo.SystemInfoModule import org.hisp.dhis.android.core.systeminfo.SystemInfoObjectRepository import org.koin.core.annotation.Singleton @@ -36,6 +37,7 @@ import org.koin.core.annotation.Singleton internal class SystemInfoModuleImpl( private val versionManager: DHISVersionManager, private val systemInfo: SystemInfoObjectRepository, + private val ping: PingImpl, ) : SystemInfoModule { override fun versionManager(): DHISVersionManager { return versionManager @@ -44,4 +46,8 @@ internal class SystemInfoModuleImpl( override fun systemInfo(): SystemInfoObjectRepository { return systemInfo } + + override fun ping(): Ping { + return ping + } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/AttributeValueFilter.java b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/AttributeValueFilter.java index cda74fa9a9..741082f4fc 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/AttributeValueFilter.java +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/AttributeValueFilter.java @@ -84,7 +84,7 @@ public static AttributeValueFilter create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends FilterOperators.Builder { + public abstract static class Builder extends FilterOperators.Builder { public abstract Builder id(Long id); public abstract Builder trackedEntityInstanceFilter(String trackedEntityInstanceFilter); diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/EntityQueryCriteria.java b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/EntityQueryCriteria.java index 9a8ba117e2..d8b0602ec6 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/EntityQueryCriteria.java +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/EntityQueryCriteria.java @@ -98,7 +98,7 @@ public static EntityQueryCriteria create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends FilterQueryCriteria.Builder { + public abstract static class Builder extends FilterQueryCriteria.Builder { public abstract Builder id(Long id); public abstract Builder programStage(String programStage); diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/TrackedEntityAttributeLegendSetLink.java b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/TrackedEntityAttributeLegendSetLink.java index 49edb1136b..87189f8813 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/TrackedEntityAttributeLegendSetLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/TrackedEntityAttributeLegendSetLink.java @@ -60,7 +60,7 @@ public static Builder builder() { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); public abstract Builder trackedEntityAttribute(String trackedEntityAttribute); diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/TrackedEntityInstanceFilter.java b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/TrackedEntityInstanceFilter.java index d68c094bd7..61155a8a9f 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/TrackedEntityInstanceFilter.java +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/TrackedEntityInstanceFilter.java @@ -125,7 +125,7 @@ public static TrackedEntityInstanceFilter create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseIdentifiableObject.Builder + public abstract static class Builder extends BaseIdentifiableObject.Builder implements ObjectWithStyle.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/TrackedEntityType.java b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/TrackedEntityType.java index cc6e52848d..127e1f3250 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/TrackedEntityType.java +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/TrackedEntityType.java @@ -82,7 +82,7 @@ public static TrackedEntityType create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseNameableObject.Builder + public abstract static class Builder extends BaseNameableObject.Builder implements ObjectWithStyle.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/TrackedEntityTypeAttribute.java b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/TrackedEntityTypeAttribute.java index b4410f69eb..c6c04446b5 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/TrackedEntityTypeAttribute.java +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/TrackedEntityTypeAttribute.java @@ -82,7 +82,7 @@ public static TrackedEntityTypeAttribute create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder { + public abstract static class Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/NewTrackedEntityEndpointCallFactory.kt b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/NewTrackedEntityEndpointCallFactory.kt index 30c0794e05..c5da0add1e 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/NewTrackedEntityEndpointCallFactory.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/NewTrackedEntityEndpointCallFactory.kt @@ -28,11 +28,14 @@ package org.hisp.dhis.android.core.trackedentity.internal import org.hisp.dhis.android.core.arch.api.executors.internal.CoroutineAPICallExecutor -import org.hisp.dhis.android.core.arch.api.payload.internal.NTIPayload import org.hisp.dhis.android.core.arch.api.payload.internal.Payload +import org.hisp.dhis.android.core.arch.api.payload.internal.TrackerPayload import org.hisp.dhis.android.core.event.NewTrackerImporterEvent import org.hisp.dhis.android.core.event.internal.NewEventFields import org.hisp.dhis.android.core.organisationunit.OrganisationUnitMode +import org.hisp.dhis.android.core.relationship.RelationshipConstraintType +import org.hisp.dhis.android.core.relationship.RelationshipTypeCollectionRepository +import org.hisp.dhis.android.core.relationship.internal.RelationshipItemRelative import org.hisp.dhis.android.core.trackedentity.NewTrackerImporterTrackedEntity import org.hisp.dhis.android.core.trackedentity.NewTrackerImporterTrackedEntityTransformer import org.hisp.dhis.android.core.trackedentity.TrackedEntityInstance @@ -44,22 +47,27 @@ import org.hisp.dhis.android.core.trackedentity.search.TrackedEntityInstanceQuer import org.hisp.dhis.android.core.trackedentity.search.TrackerQueryResult import org.hisp.dhis.android.core.tracker.TrackerExporterVersion import org.hisp.dhis.android.core.tracker.exporter.TrackerAPIQuery +import org.hisp.dhis.android.core.tracker.exporter.TrackerExporterParameterManager import org.hisp.dhis.android.core.tracker.exporter.TrackerExporterService +import org.hisp.dhis.android.core.tracker.exporter.TrackerQueryHelper.getOrgunits import org.hisp.dhis.android.core.util.simpleDateFormat import org.koin.core.annotation.Singleton @Singleton +@Suppress("TooManyFunctions") internal class NewTrackedEntityEndpointCallFactory( private val trackedExporterService: TrackerExporterService, private val coroutineAPICallExecutor: CoroutineAPICallExecutor, + private val relationshipTypeRepository: RelationshipTypeCollectionRepository, + private val parameterManager: TrackerExporterParameterManager, ) : TrackedEntityEndpointCallFactory() { override suspend fun getCollectionCall(query: TrackerAPIQuery): Payload { return trackedExporterService.getTrackedEntityInstances( fields = NewTrackedEntityInstanceFields.allFields, - trackedEntityInstances = getUidStr(query), - orgUnits = query.orgUnit, - orgUnitMode = query.commonParams.ouMode.name, + trackedEntityInstances = parameterManager.getTrackedEntitiesParameter(query.uids), + orgUnits = parameterManager.getOrgunitsParameter(getOrgunits(query)), + orgUnitMode = parameterManager.getOrgunitModeParameter(query.commonParams.ouMode), program = query.commonParams.program, programStatus = getProgramStatus(query), programStartDate = getProgramStartDate(query), @@ -68,7 +76,6 @@ internal class NewTrackedEntityEndpointCallFactory( page = query.page, pageSize = query.pageSize, lastUpdatedStartDate = query.lastUpdatedStr, - includeAllAttributes = true, includeDeleted = true, ).let { mapPayload(it) } } @@ -77,23 +84,24 @@ internal class NewTrackedEntityEndpointCallFactory( return trackedExporterService.getSingleTrackedEntityInstance( fields = NewTrackedEntityInstanceFields.allFields, trackedEntityInstanceUid = uid, - orgUnitMode = query.commonParams.ouMode.name, + orgUnitMode = parameterManager.getOrgunitModeParameter(query.commonParams.ouMode), program = query.commonParams.program, programStatus = getProgramStatus(query), programStartDate = getProgramStartDate(query), - includeAllAttributes = true, includeDeleted = true, ).let { NewTrackerImporterTrackedEntityTransformer.deTransform(it) } } - override suspend fun getRelationshipEntityCall(uid: String): Payload { - return trackedExporterService.getTrackedEntityInstance( - trackedEntityInstance = uid, + override suspend fun getRelationshipEntityCall(item: RelationshipItemRelative): Payload { + return trackedExporterService.getSingleTrackedEntityInstance( fields = NewTrackedEntityInstanceFields.asRelationshipFields, - orgUnitMode = OrganisationUnitMode.ACCESSIBLE.name, - includeAllAttributes = true, + trackedEntityInstanceUid = item.itemUid, + orgUnitMode = parameterManager.getOrgunitModeParameter(OrganisationUnitMode.ACCESSIBLE), + program = getRelatedProgramUid(item), + programStatus = null, + programStartDate = null, includeDeleted = true, - ).let { mapPayload(it) } + ).let { Payload(listOf(NewTrackerImporterTrackedEntityTransformer.deTransform(it))) } } override suspend fun getQueryCall(query: TrackedEntityInstanceQueryOnline): TrackerQueryResult { @@ -109,14 +117,14 @@ internal class NewTrackedEntityEndpointCallFactory( val instances = getTrackedEntityQuery(teiQuery) TrackerQueryResult( trackedEntities = instances, - exhausted = events.size < query.pageSize, + exhausted = events.size < query.pageSize || !query.paging, ) } } else { val instances = getTrackedEntityQuery(query) TrackerQueryResult( trackedEntities = instances, - exhausted = instances.size < query.pageSize, + exhausted = instances.size < query.pageSize || !query.paging, ) } } @@ -139,7 +147,7 @@ internal class NewTrackedEntityEndpointCallFactory( trackedExporterService.getEvents( fields = NewEventFields.teiQueryFields, orgUnit = orgunit, - orgUnitMode = query.orgUnitMode?.toString(), + orgUnitMode = parameterManager.getOrgunitModeParameter(query.orgUnitMode), status = query.eventStatus?.toString(), program = query.program, programStage = query.programStage, @@ -158,26 +166,24 @@ internal class NewTrackedEntityEndpointCallFactory( order = toAPIOrderFormat(query.order, TrackerExporterVersion.V2), assignedUserMode = query.assignedUserMode?.toString(), paging = query.paging, - pageSize = query.pageSize, - page = query.page, + pageSize = query.pageSize.takeIf { query.paging }, + page = query.page.takeIf { query.paging }, updatedAfter = query.lastUpdatedStartDate.simpleDateFormat(), updatedBefore = query.lastUpdatedEndDate.simpleDateFormat(), includeDeleted = query.includeDeleted, ) - }.getOrThrow().instances + }.getOrThrow().items() } private suspend fun getTrackedEntityQuery(query: TrackedEntityInstanceQueryOnline): List { return coroutineAPICallExecutor.wrap( errorCatcher = TrackedEntityInstanceQueryErrorCatcher(), ) { - val uidsStr = query.uids?.joinToString(";") - val payload = trackedExporterService.getTrackedEntityInstances( fields = NewTrackedEntityInstanceFields.asRelationshipFields, - trackedEntityInstances = uidsStr, - orgUnits = getOrgunits(query.orgUnits), - orgUnitMode = query.orgUnitMode?.toString(), + trackedEntityInstances = parameterManager.getTrackedEntitiesParameter(query.uids), + orgUnits = parameterManager.getOrgunitsParameter(getOrgunits(query)), + orgUnitMode = parameterManager.getOrgunitModeParameter(query.orgUnitMode), program = query.program, programStage = query.programStage, programStartDate = query.programStartDate.simpleDateFormat(), @@ -190,16 +196,14 @@ internal class NewTrackedEntityEndpointCallFactory( eventEndDate = query.eventEndDate.simpleDateFormat(), eventStatus = query.eventStatus?.toString(), trackedEntityType = query.trackedEntityType, - query = query.query, filter = toAPIFilterFormat(query.attributeFilter, upper = true), assignedUserMode = query.assignedUserMode?.toString(), lastUpdatedStartDate = query.lastUpdatedStartDate.simpleDateFormat(), lastUpdatedEndDate = query.lastUpdatedEndDate.simpleDateFormat(), order = toAPIOrderFormat(query.order, TrackerExporterVersion.V2), paging = query.paging, - page = query.page, - pageSize = query.pageSize, - includeAllAttributes = true, + page = query.page.takeIf { query.paging }, + pageSize = query.pageSize.takeIf { query.paging }, ) mapPayload(payload) @@ -218,24 +222,28 @@ internal class NewTrackedEntityEndpointCallFactory( orgUnitMode = query.orgUnitMode, program = query.program, uids = events.mapNotNull { it.trackedEntity() }.distinct(), - query = query.query, - attributeFilter = query.attributeFilter, order = query.order, trackedEntityType = query.trackedEntityType, includeDeleted = query.includeDeleted, ) } - private fun mapPayload(payload: NTIPayload): Payload { - val newItems = payload.instances.map { t -> NewTrackerImporterTrackedEntityTransformer.deTransform(t) } + private fun mapPayload(payload: TrackerPayload): Payload { + val newItems = payload.items().map { t -> NewTrackerImporterTrackedEntityTransformer.deTransform(t) } return Payload(newItems) } - private fun getOrgunits(orgUnits: List): String? { - return if (orgUnits.isEmpty()) { - null - } else { - orgUnits.joinToString(";") + private fun getRelatedProgramUid(item: RelationshipItemRelative): String? { + val relationshipType = relationshipTypeRepository + .withConstraints() + .uid(item.relationshipTypeUid) + .blockingGet() + + val constraint = when (item.constraintType) { + RelationshipConstraintType.FROM -> relationshipType?.fromConstraint() + RelationshipConstraintType.TO -> relationshipType?.toConstraint() } + + return constraint?.program()?.uid() } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/ObjectWithUidWebResponse.java b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/ObjectWithUidWebResponse.java index 03723fc70e..c6e05c48f2 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/ObjectWithUidWebResponse.java +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/ObjectWithUidWebResponse.java @@ -50,7 +50,7 @@ public static Builder builder() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends WebResponse.Builder { + public abstract static class Builder extends WebResponse.Builder { public abstract Builder response(ObjectWithUid response); public abstract ObjectWithUidWebResponse build(); diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/OldTrackedEntityEndpointCallFactory.kt b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/OldTrackedEntityEndpointCallFactory.kt index 0144eaace3..bdf22cde87 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/OldTrackedEntityEndpointCallFactory.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/OldTrackedEntityEndpointCallFactory.kt @@ -29,6 +29,7 @@ package org.hisp.dhis.android.core.trackedentity.internal import org.hisp.dhis.android.core.arch.api.payload.internal.Payload import org.hisp.dhis.android.core.organisationunit.OrganisationUnitMode +import org.hisp.dhis.android.core.relationship.internal.RelationshipItemRelative import org.hisp.dhis.android.core.trackedentity.TrackedEntityInstance import org.hisp.dhis.android.core.trackedentity.search.TrackedEntityInstanceQueryCallFactory import org.hisp.dhis.android.core.trackedentity.search.TrackedEntityInstanceQueryOnline @@ -48,7 +49,7 @@ internal class OldTrackedEntityEndpointCallFactory( return trackedEntityInstanceService.getTrackedEntityInstances( fields = TrackedEntityInstanceFields.allFields, trackedEntityInstances = getUidStr(query), - orgUnits = query.orgUnit, + orgUnits = getOrgunitStr(query), orgUnitMode = query.commonParams.ouMode.name, program = query.commonParams.program, programStatus = getProgramStatus(query), @@ -76,10 +77,10 @@ internal class OldTrackedEntityEndpointCallFactory( ) } - override suspend fun getRelationshipEntityCall(uid: String): Payload { + override suspend fun getRelationshipEntityCall(item: RelationshipItemRelative): Payload { return trackedEntityInstanceService.getTrackedEntityInstance( fields = TrackedEntityInstanceFields.asRelationshipFields, - trackedEntityInstance = uid, + trackedEntityInstance = item.itemUid, orgUnitMode = OrganisationUnitMode.ACCESSIBLE.name, includeAllAttributes = true, includeDeleted = true, diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/OldTrackerImporterFileResourcesPostCall.kt b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/OldTrackerImporterFileResourcesPostCall.kt index 23580db8f9..7196990546 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/OldTrackerImporterFileResourcesPostCall.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/OldTrackerImporterFileResourcesPostCall.kt @@ -31,7 +31,7 @@ import org.hisp.dhis.android.core.enrollment.Enrollment import org.hisp.dhis.android.core.enrollment.EnrollmentInternalAccessor import org.hisp.dhis.android.core.event.Event import org.hisp.dhis.android.core.fileresource.FileResource -import org.hisp.dhis.android.core.fileresource.FileResourceDomainType +import org.hisp.dhis.android.core.fileresource.FileResourceDataDomainType import org.hisp.dhis.android.core.fileresource.internal.FileResourceHelper import org.hisp.dhis.android.core.fileresource.internal.FileResourcePostCall import org.hisp.dhis.android.core.fileresource.internal.FileResourceValue @@ -150,7 +150,7 @@ internal class OldTrackerImporterFileResourcesPostCall internal constructor( } fun updateFileResourceStates(fileResources: List) { - fileResourceHelper.updateFileResourceStates(fileResources, FileResourceDomainType.TRACKER) + fileResourceHelper.updateFileResourceStates(fileResources, FileResourceDataDomainType.TRACKER) } @Suppress("TooGenericExceptionCaught") diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityEndpointCallFactory.kt b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityEndpointCallFactory.kt index 96b323f0ba..93875c2da3 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityEndpointCallFactory.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityEndpointCallFactory.kt @@ -28,11 +28,12 @@ package org.hisp.dhis.android.core.trackedentity.internal import org.hisp.dhis.android.core.arch.api.payload.internal.Payload -import org.hisp.dhis.android.core.arch.helpers.CollectionsHelper +import org.hisp.dhis.android.core.relationship.internal.RelationshipItemRelative import org.hisp.dhis.android.core.trackedentity.TrackedEntityInstance import org.hisp.dhis.android.core.trackedentity.search.TrackedEntityInstanceQueryOnline import org.hisp.dhis.android.core.trackedentity.search.TrackerQueryResult import org.hisp.dhis.android.core.tracker.exporter.TrackerAPIQuery +import org.hisp.dhis.android.core.tracker.exporter.TrackerQueryHelper internal abstract class TrackedEntityEndpointCallFactory { @@ -40,12 +41,16 @@ internal abstract class TrackedEntityEndpointCallFactory { abstract suspend fun getEntityCall(uid: String, query: TrackerAPIQuery): TrackedEntityInstance - abstract suspend fun getRelationshipEntityCall(uid: String): Payload + abstract suspend fun getRelationshipEntityCall(item: RelationshipItemRelative): Payload abstract suspend fun getQueryCall(query: TrackedEntityInstanceQueryOnline): TrackerQueryResult protected fun getUidStr(query: TrackerAPIQuery): String? { - return if (query.uids.isEmpty()) null else CollectionsHelper.joinCollectionWithSeparator(query.uids, ";") + return if (query.uids.isEmpty()) null else query.uids.joinToString(";") + } + + protected fun getOrgunitStr(query: TrackerAPIQuery): String? { + return TrackerQueryHelper.getOrgunits(query)?.joinToString(";") } protected fun getProgramStatus(query: TrackerAPIQuery): String? { diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceCallErrorCatcher.kt b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceCallErrorCatcher.kt index a7db2d45ea..b47c12f421 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceCallErrorCatcher.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceCallErrorCatcher.kt @@ -33,6 +33,7 @@ import org.hisp.dhis.android.core.imports.internal.HttpMessageResponse import org.hisp.dhis.android.core.maintenance.D2ErrorCode import retrofit2.Response import java.io.IOException +import javax.net.ssl.HttpsURLConnection import kotlin.Throws internal class TrackedEntityInstanceCallErrorCatcher : APICallErrorCatcher { @@ -45,7 +46,11 @@ internal class TrackedEntityInstanceCallErrorCatcher : APICallErrorCatcher { val parsed = objectMapper().readValue(errorBody, HttpMessageResponse::class.java) @Suppress("MagicNumber") - return if (parsed.httpStatusCode() == 401) { + return if ( + parsed.httpStatusCode() == HttpsURLConnection.HTTP_UNAUTHORIZED || + parsed.httpStatusCode() == HttpsURLConnection.HTTP_CONFLICT || + parsed.httpStatusCode() == HttpsURLConnection.HTTP_FORBIDDEN + ) { when (parsed.message()) { "OWNERSHIP_ACCESS_DENIED" -> D2ErrorCode.OWNERSHIP_ACCESS_DENIED "PROGRAM_ACCESS_CLOSED" -> D2ErrorCode.PROGRAM_ACCESS_CLOSED diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceDownloader.kt b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceDownloader.kt index c495746796..9d6dd81a9d 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceDownloader.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceDownloader.kt @@ -76,23 +76,23 @@ class TrackedEntityInstanceDownloader internal constructor( } fun byProgramUid(programUid: String): TrackedEntityInstanceDownloader { - return cf.baseString(QueryParams.PROGRAM).eq(programUid)!! + return cf.baseString(QueryParams.PROGRAM).eq(programUid) } fun limitByOrgunit(limitByOrgunit: Boolean): TrackedEntityInstanceDownloader { - return cf.bool(QueryParams.LIMIT_BY_ORGUNIT).eq(limitByOrgunit)!! + return cf.bool(QueryParams.LIMIT_BY_ORGUNIT).eq(limitByOrgunit) } fun limitByProgram(limitByProgram: Boolean): TrackedEntityInstanceDownloader { - return cf.bool(QueryParams.LIMIT_BY_PROGRAM).eq(limitByProgram)!! + return cf.bool(QueryParams.LIMIT_BY_PROGRAM).eq(limitByProgram) } fun limit(limit: Int): TrackedEntityInstanceDownloader { - return cf.integer(QueryParams.LIMIT).eq(limit)!! + return cf.integer(QueryParams.LIMIT).eq(limit) } fun byProgramStatus(status: EnrollmentScope): TrackedEntityInstanceDownloader { - return cf.baseString(QueryParams.PROGRAM_STATUS).eq(status.toString())!! + return cf.baseString(QueryParams.PROGRAM_STATUS).eq(status.toString()) } /** @@ -103,6 +103,6 @@ class TrackedEntityInstanceDownloader internal constructor( * @return the new repository */ fun overwrite(overwrite: Boolean): TrackedEntityInstanceDownloader { - return cf.bool(QueryParams.OVERWRITE).eq(overwrite)!! + return cf.bool(QueryParams.OVERWRITE).eq(overwrite) } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceImportHandler.kt b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceImportHandler.kt index d6414359e5..7f356f8c0e 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceImportHandler.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceImportHandler.kt @@ -38,16 +38,12 @@ import org.hisp.dhis.android.core.imports.internal.TEIImportSummary import org.hisp.dhis.android.core.imports.internal.TEIWebResponseHandlerSummary import org.hisp.dhis.android.core.imports.internal.TrackerImportConflictParser import org.hisp.dhis.android.core.imports.internal.TrackerImportConflictStore -import org.hisp.dhis.android.core.relationship.RelationshipCollectionRepository -import org.hisp.dhis.android.core.relationship.RelationshipHelper -import org.hisp.dhis.android.core.relationship.internal.RelationshipDHISVersionManager -import org.hisp.dhis.android.core.relationship.internal.RelationshipStore import org.hisp.dhis.android.core.trackedentity.TrackedEntityInstance import org.hisp.dhis.android.core.trackedentity.TrackedEntityInstanceInternalAccessor import org.hisp.dhis.android.core.trackedentity.TrackedEntityInstanceTableInfo import org.hisp.dhis.android.core.tracker.importer.internal.JobReportTrackedEntityHandler import org.koin.core.annotation.Singleton -import java.util.* +import java.util.Date @Singleton internal class TrackedEntityInstanceImportHandler internal constructor( @@ -55,10 +51,7 @@ internal class TrackedEntityInstanceImportHandler internal constructor( private val enrollmentImportHandler: EnrollmentImportHandler, private val trackerImportConflictStore: TrackerImportConflictStore, private val trackerImportConflictParser: TrackerImportConflictParser, - private val relationshipStore: RelationshipStore, private val dataStatePropagator: DataStatePropagator, - private val relationshipDHISVersionManager: RelationshipDHISVersionManager, - private val relationshipRepository: RelationshipCollectionRepository, private val jobReportTrackedEntityHandler: JobReportTrackedEntityHandler, ) { @@ -89,7 +82,6 @@ internal class TrackedEntityInstanceImportHandler internal constructor( resetNestedDataStates(instance) instance?.let { summary.teis.error.add(it) } } else { - setRelationshipsState(teiUid, State.SYNCED) instance?.let { summary.teis.success.add(it) } } @@ -152,17 +144,6 @@ internal class TrackedEntityInstanceImportHandler internal constructor( trackerImportConflicts.forEach { trackerImportConflictStore.insert(it) } } - // Legacy code for <= 2.29 - private fun setRelationshipsState(trackedEntityInstanceUid: String?, state: State) { - val dbRelationships = - relationshipRepository.getByItem(RelationshipHelper.teiItem(trackedEntityInstanceUid), true, false) - val ownedRelationships = relationshipDHISVersionManager - .getOwnedRelationships(dbRelationships, trackedEntityInstanceUid) - for (relationship in ownedRelationships) { - relationshipStore.setSyncStateOrDelete(relationship.uid()!!, state) - } - } - private fun processIgnoredTEIs( processedTEIs: List, instances: List, @@ -177,7 +158,6 @@ internal class TrackedEntityInstanceImportHandler internal constructor( private fun resetNestedDataStates(instance: TrackedEntityInstance?) { instance?.let { dataStatePropagator.resetUploadingEnrollmentAndEventStates(instance.uid()) - setRelationshipsState(instance.uid(), State.TO_UPDATE) } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceService.kt b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceService.kt index e43f059bab..860d6daf69 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceService.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceService.kt @@ -75,8 +75,8 @@ internal interface TrackedEntityInstanceService { @Query(FIELDS) @Which fields: Fields, @Query(ORDER) order: String?, @Query(PAGING) paging: Boolean, - @Query(PAGE) page: Int, - @Query(PAGE_SIZE) pageSize: Int, + @Query(PAGE) page: Int?, + @Query(PAGE_SIZE) pageSize: Int?, @Query(LAST_UPDATED_START_DATE) lastUpdatedStartDate: String?, @Query(INCLUDE_ALL_ATTRIBUTES) includeAllAttributes: Boolean, @Query(INCLUDE_DELETED) includeDeleted: Boolean, @@ -99,15 +99,14 @@ internal interface TrackedEntityInstanceService { @Query(EVENT_END_DATE) eventEndDate: String?, @Query(EVENT_STATUS) eventStatus: String?, @Query(TRACKED_ENTITY_TYPE) trackedEntityType: String?, - @Query(QUERY) query: String?, @Query(FILTER) filter: List?, @Query(ASSIGNED_USER_MODE) assignedUserMode: String?, @Query(LAST_UPDATED_START_DATE) lastUpdatedStartDate: String?, @Query(LAST_UPDATED_END_DATE) lastUpdatedEndDate: String?, @Query(ORDER) order: String?, @Query(PAGING) paging: Boolean, - @Query(PAGE) page: Int, - @Query(PAGE_SIZE) pageSize: Int, + @Query(PAGE) page: Int?, + @Query(PAGE_SIZE) pageSize: Int?, ): SearchGrid companion object { @@ -116,7 +115,6 @@ internal interface TrackedEntityInstanceService { const val OU = "ou" const val OU_MODE = "ouMode" const val FIELDS = "fields" - const val QUERY = "query" const val PAGING = "paging" const val PAGE = "page" const val PAGE_SIZE = "pageSize" diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceSync.java b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceSync.java index 6d66997bb9..490a183fee 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceSync.java +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceSync.java @@ -53,7 +53,7 @@ static Builder builder() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - static abstract class Builder extends TrackerBaseSync.Builder { + abstract static class Builder extends TrackerBaseSync.Builder { abstract TrackedEntityInstanceSync build(); } diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityTypeFields.java b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityTypeFields.kt similarity index 56% rename from core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityTypeFields.java rename to core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityTypeFields.kt index f9adc10df2..6a6385ac83 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityTypeFields.java +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityTypeFields.kt @@ -25,44 +25,37 @@ * (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.trackedentity.internal; - -import org.hisp.dhis.android.core.arch.api.fields.internal.Field; -import org.hisp.dhis.android.core.arch.api.fields.internal.Fields; -import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper; -import org.hisp.dhis.android.core.common.Access; -import org.hisp.dhis.android.core.common.FeatureType; -import org.hisp.dhis.android.core.common.ObjectStyle; -import org.hisp.dhis.android.core.common.internal.AccessFields; -import org.hisp.dhis.android.core.common.internal.DataAccessFields; -import org.hisp.dhis.android.core.common.objectstyle.internal.ObjectStyleFields; -import org.hisp.dhis.android.core.trackedentity.TrackedEntityType; -import org.hisp.dhis.android.core.trackedentity.TrackedEntityTypeAttribute; -import org.hisp.dhis.android.core.trackedentity.TrackedEntityTypeTableInfo.Columns; - -public final class TrackedEntityTypeFields { - - private final static String STYLE = "style"; - public final static String TRACKED_ENTITY_TYPE_ATTRIBUTES = "trackedEntityTypeAttributes"; - private static final String ACCESS = "access"; - - private static final FieldsHelper fh = new FieldsHelper<>(); - - public static final Field uid = fh.uid(); - - static final Field lastUpdated = fh.lastUpdated(); - - public static final Fields allFields = Fields.builder() - .fields(fh.getNameableFields()) - .fields( - fh.nestedField(TRACKED_ENTITY_TYPE_ATTRIBUTES) - .with(TrackedEntityTypeAttributeFields.allFields), - fh.nestedField(STYLE).with(ObjectStyleFields.allFields), - fh.field(Columns.FEATURE_TYPE), - fh.nestedField(ACCESS).with(AccessFields.data.with(DataAccessFields.allFields)) - ).build(); - - private TrackedEntityTypeFields() { - } -} \ No newline at end of file +package org.hisp.dhis.android.core.trackedentity.internal + +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper +import org.hisp.dhis.android.core.common.Access +import org.hisp.dhis.android.core.common.FeatureType +import org.hisp.dhis.android.core.common.ObjectStyle +import org.hisp.dhis.android.core.common.internal.AccessFields +import org.hisp.dhis.android.core.common.internal.DataAccessFields +import org.hisp.dhis.android.core.common.objectstyle.internal.ObjectStyleFields +import org.hisp.dhis.android.core.trackedentity.TrackedEntityType +import org.hisp.dhis.android.core.trackedentity.TrackedEntityTypeAttribute +import org.hisp.dhis.android.core.trackedentity.TrackedEntityTypeTableInfo + +internal object TrackedEntityTypeFields { + private const val STYLE = "style" + const val TRACKED_ENTITY_TYPE_ATTRIBUTES = "trackedEntityTypeAttributes" + private const val ACCESS = "access" + private val fh = FieldsHelper() + + val uid = fh.uid() + + val lastUpdated = fh.lastUpdated() + + val allFields: Fields = Fields.builder() + .fields(fh.getNameableFields()) + .fields( + fh.nestedField(TRACKED_ENTITY_TYPE_ATTRIBUTES) + .with(TrackedEntityTypeAttributeFields.allFields), + fh.nestedField(STYLE).with(ObjectStyleFields.allFields), + fh.field(TrackedEntityTypeTableInfo.Columns.FEATURE_TYPE), + fh.nestedField(ACCESS).with(AccessFields.data.with(DataAccessFields.allFields)), + ).build() +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackerBaseSync.java b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackerBaseSync.java index fc97d7b477..03768cf895 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackerBaseSync.java +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/internal/TrackerBaseSync.java @@ -55,7 +55,7 @@ public abstract class TrackerBaseSync extends BaseObject { public abstract Date lastUpdated(); @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract T program(String program); diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/ownership/OwnershipManagerImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/ownership/OwnershipManagerImpl.kt index 66c9d985ad..399f823958 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/ownership/OwnershipManagerImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/ownership/OwnershipManagerImpl.kt @@ -40,6 +40,7 @@ import org.hisp.dhis.android.core.imports.internal.HttpMessageResponse import org.hisp.dhis.android.core.maintenance.D2Error import org.hisp.dhis.android.core.maintenance.D2ErrorCode import org.hisp.dhis.android.core.maintenance.D2ErrorComponent +import org.hisp.dhis.android.core.tracker.exporter.TrackerExporterParameterManager import org.koin.core.annotation.Singleton import java.util.* @@ -50,6 +51,7 @@ internal class OwnershipManagerImpl( private val dataStatePropagator: DataStatePropagator, private val programTempOwnerStore: ProgramTempOwnerStore, private val programOwnerStore: ProgramOwnerStore, + private val parameterManager: TrackerExporterParameterManager, ) : OwnershipManager { override fun breakGlass(trackedEntityInstance: String, program: String, reason: String): Completable { @@ -132,7 +134,11 @@ internal class OwnershipManagerImpl( reason: String, ): Result { return coroutineAPICallExecutor.wrap(storeError = true) { - ownershipService.breakGlass(trackedEntityInstance, program, reason) + ownershipService.breakGlass( + parameterManager.getTrackedEntityForOwnershipParameter(trackedEntityInstance), + program, + reason, + ) } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/ownership/OwnershipService.kt b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/ownership/OwnershipService.kt index 7bd9e3f442..b1caa1ec6f 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/ownership/OwnershipService.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/ownership/OwnershipService.kt @@ -31,19 +31,20 @@ import org.hisp.dhis.android.core.imports.internal.HttpMessageResponse import retrofit2.http.POST import retrofit2.http.PUT import retrofit2.http.Query +import retrofit2.http.QueryMap internal interface OwnershipService { @POST("$OWNERSHIP_URL/override") suspend fun breakGlass( - @Query(TRACKED_ENTITY_INSTACE) trackedEntityInstance: String, + @QueryMap trackedEntity: Map, @Query(PROGRAM) program: String, @Query(REASON) reason: String, ): HttpMessageResponse @PUT("$OWNERSHIP_URL/transfer") suspend fun transfer( - @Query(TRACKED_ENTITY_INSTACE) trackedEntityInstance: String, + @QueryMap trackedEntity: Map, @Query(PROGRAM) program: String, @Query(ORG_UNIT) ou: String, ): HttpMessageResponse @@ -52,6 +53,7 @@ internal interface OwnershipService { const val OWNERSHIP_URL = "tracker/ownership" const val TRACKED_ENTITY_INSTACE = "trackedEntityInstance" + const val TRACKED_ENTITY = "trackedEntity" const val PROGRAM = "program" const val REASON = "reason" const val ORG_UNIT = "ou" diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/ownership/ProgramOwnerPostCall.kt b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/ownership/ProgramOwnerPostCall.kt index de7d6226a8..34d996103a 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/ownership/ProgramOwnerPostCall.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/ownership/ProgramOwnerPostCall.kt @@ -30,6 +30,7 @@ package org.hisp.dhis.android.core.trackedentity.ownership import org.hisp.dhis.android.core.arch.api.executors.internal.CoroutineAPICallExecutor import org.hisp.dhis.android.core.common.State import org.hisp.dhis.android.core.common.internal.DataStatePropagator +import org.hisp.dhis.android.core.tracker.exporter.TrackerExporterParameterManager import org.koin.core.annotation.Singleton @Singleton @@ -38,12 +39,13 @@ internal class ProgramOwnerPostCall( private val coroutineAPICallExecutor: CoroutineAPICallExecutor, private val programOwnerStore: ProgramOwnerStore, private val dataStatePropagator: DataStatePropagator, + private val parameterManager: TrackerExporterParameterManager, ) { suspend fun uploadProgramOwner(programOwner: ProgramOwner) { val response = coroutineAPICallExecutor.wrap(storeError = true) { ownershipService.transfer( - programOwner.trackedEntityInstance(), + parameterManager.getTrackedEntityForOwnershipParameter(programOwner.trackedEntityInstance()), programOwner.program(), programOwner.ownerOrgUnit(), ) diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryCallFactory.kt b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryCallFactory.kt index c6c6bc4a77..83e1aba551 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryCallFactory.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryCallFactory.kt @@ -43,6 +43,7 @@ import org.hisp.dhis.android.core.trackedentity.internal.TrackedEntityInstanceSe import org.hisp.dhis.android.core.trackedentity.search.TrackedEntityInstanceQueryOnlineHelper.Companion.toAPIFilterFormat import org.hisp.dhis.android.core.trackedentity.search.TrackedEntityInstanceQueryOnlineHelper.Companion.toAPIOrderFormat import org.hisp.dhis.android.core.tracker.TrackerExporterVersion +import org.hisp.dhis.android.core.tracker.exporter.TrackerQueryHelper.getOrgunits import org.hisp.dhis.android.core.util.simpleDateFormat import org.koin.core.annotation.Singleton import java.text.ParseException @@ -101,7 +102,7 @@ internal class TrackedEntityInstanceQueryCallFactory( return coroutineAPICallExecutor.wrap(storeError = false) { eventService.getEvents( fields = EventFields.teiQueryFields, - orgUnit = orgunit, + orgUnit = getOrgunits(orgunit, query.orgUnitMode)?.firstOrNull(), orgUnitMode = query.orgUnitMode?.toString(), status = query.eventStatus?.toString(), program = query.program, @@ -116,8 +117,8 @@ internal class TrackedEntityInstanceQueryCallFactory( order = toAPIOrderFormat(query.order, TrackerExporterVersion.V1), assignedUserMode = query.assignedUserMode?.toString(), paging = query.paging, - pageSize = query.pageSize, - page = query.page, + pageSize = query.pageSize.takeIf { query.paging }, + page = query.page.takeIf { query.paging }, lastUpdatedStartDate = query.lastUpdatedStartDate.simpleDateFormat(), lastUpdatedEndDate = query.lastUpdatedEndDate.simpleDateFormat(), includeDeleted = query.includeDeleted, @@ -126,16 +127,14 @@ internal class TrackedEntityInstanceQueryCallFactory( } private suspend fun getTrackedEntityQuery(query: TrackedEntityInstanceQueryOnline): List { - val uidsStr = query.uids?.joinToString(";") - return try { coroutineAPICallExecutor.wrap( storeError = false, errorCatcher = TrackedEntityInstanceQueryErrorCatcher(), ) { trackedEntityService.query( - trackedEntityInstance = uidsStr, - orgUnit = getOrgunits(query.orgUnits), + trackedEntityInstance = query.uids?.joinToString(";"), + orgUnit = getOrgunits(query)?.joinToString(";"), orgUnitMode = query.orgUnitMode?.toString(), program = query.program, programStage = query.programStage, @@ -149,15 +148,14 @@ internal class TrackedEntityInstanceQueryCallFactory( eventEndDate = query.eventEndDate.simpleDateFormat(), eventStatus = getEventStatus(query), trackedEntityType = query.trackedEntityType, - query = query.query, filter = toAPIFilterFormat(query.attributeFilter, upper = true), assignedUserMode = query.assignedUserMode?.toString(), lastUpdatedStartDate = query.lastUpdatedStartDate.simpleDateFormat(), lastUpdatedEndDate = query.lastUpdatedEndDate.simpleDateFormat(), order = toAPIOrderFormat(query.order, TrackerExporterVersion.V1), paging = query.paging, - pageSize = query.pageSize, - page = query.page, + pageSize = query.pageSize.takeIf { query.paging }, + page = query.page.takeIf { query.paging }, ) }.getOrThrow().let { mapper.transform(it) } } catch (pe: ParseException) { @@ -180,14 +178,6 @@ internal class TrackedEntityInstanceQueryCallFactory( } } - private fun getOrgunits(orgUnits: List): String? { - return if (orgUnits.isEmpty()) { - null - } else { - orgUnits.joinToString(";") - } - } - companion object { internal fun getPostEventTeiQuery( query: TrackedEntityInstanceQueryOnline, diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryCollectionRepository.kt b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryCollectionRepository.kt index 6dcaccfc44..7a6b6dabb6 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryCollectionRepository.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryCollectionRepository.kt @@ -31,7 +31,12 @@ import androidx.lifecycle.LiveData import androidx.paging.DataSource import androidx.paging.LivePagedListBuilder import androidx.paging.PagedList +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.PagingSource import io.reactivex.Single +import kotlinx.coroutines.flow.Flow import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter import org.hisp.dhis.android.core.arch.handlers.internal.Transformer import org.hisp.dhis.android.core.arch.helpers.Result @@ -120,6 +125,7 @@ class TrackedEntityInstanceQueryCollectionRepository internal constructor( } } + @Deprecated("Use {@link #getPagingData()} instead}", replaceWith = ReplaceWith("getPagingData()")) override fun getPaged(pageSize: Int): LiveData> { val factory: DataSource.Factory = object : DataSource.Factory() { @@ -130,9 +136,33 @@ class TrackedEntityInstanceQueryCollectionRepository internal constructor( return LivePagedListBuilder(factory, pageSize).build() } + override fun getPagingData(pageSize: Int): Flow> { + return getPager(pageSize).flow + } + + fun getPager(pageSize: Int): Pager { + return Pager( + config = PagingConfig(pageSize = pageSize), + ) { + pagingSource + } + } + val dataSource: DataSource get() = TrackedEntityInstanceQueryDataSource(getDataFetcher()) + val pagingSource: PagingSource + get() = TrackedEntityInstanceQueryPagingSource( + store, + databaseAdapter, + trackerParentCallFactory, + scope, + childrenAppenders, + onlineCache, + onlineHelper, + localQueryHelper, + ) + @Deprecated("use getPagingdata") val resultDataSource: DataSource> get() = TrackedEntityInstanceQueryDataSourceResult(getDataFetcher()) diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryErrorCatcher.kt b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryErrorCatcher.kt index 23a1640440..678865aff8 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryErrorCatcher.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryErrorCatcher.kt @@ -59,7 +59,11 @@ internal class TrackedEntityInstanceQueryErrorCatcher : APICallErrorCatcher { private fun parseErrorMessage(errorBody: String): D2ErrorCode { return try { val parsed = objectMapper().readValue(errorBody, HttpMessageResponse::class.java) - if (parsed.httpStatusCode() == HttpsURLConnection.HTTP_CONFLICT) { + if ( + parsed.httpStatusCode() == HttpsURLConnection.HTTP_UNAUTHORIZED || + parsed.httpStatusCode() == HttpsURLConnection.HTTP_CONFLICT || + parsed.httpStatusCode() == HttpsURLConnection.HTTP_FORBIDDEN + ) { when { parsed.message() == "maxteicountreached" -> D2ErrorCode.MAX_TEI_COUNT_REACHED OutOfSearchScope.containsMatchIn(parsed.message()) -> D2ErrorCode.ORGUNIT_NOT_IN_SEARCH_SCOPE diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryOnline.kt b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryOnline.kt index 0244c00c12..5bf2ece3d4 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryOnline.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryOnline.kt @@ -43,7 +43,6 @@ internal data class TrackedEntityInstanceQueryOnline( val orgUnitMode: OrganisationUnitMode? = null, val program: String? = null, val programStage: String? = null, - val query: String? = null, val attributeFilter: List = emptyList(), val dataValueFilter: List = emptyList(), val programStartDate: Date? = null, diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryOnlineHelper.kt b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryOnlineHelper.kt index 0ef9c9aac2..efe36f87c5 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryOnlineHelper.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryOnlineHelper.kt @@ -83,8 +83,6 @@ internal class TrackedEntityInstanceQueryOnlineHelper( private fun getBaseQuery( scope: TrackedEntityInstanceQueryRepositoryScope, ): TrackedEntityInstanceQueryOnline { - val query = scope.query()?.let { query -> query.operator().apiUpperOperator + ":" + query.value() } - // EnrollmentStatus does not accepts a list of status but a single value in web API. val enrollmentStatus = scope.enrollmentStatus()?.getOrNull(0) @@ -95,7 +93,6 @@ internal class TrackedEntityInstanceQueryOnlineHelper( orgUnits = scope.orgUnits(), orgUnitMode = scope.orgUnitMode(), program = scope.program(), - query = query, attributeFilter = scope.filter(), dataValueFilter = scope.dataValue(), lastUpdatedStartDate = scope.lastUpdatedDate()?.let { dateFilterPeriodHelper.getStartDate(it) }, diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryPagingSource.kt b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryPagingSource.kt new file mode 100644 index 0000000000..9a8f1e91d7 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryPagingSource.kt @@ -0,0 +1,104 @@ +/* + * 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.trackedentity.search + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.arch.helpers.Result +import org.hisp.dhis.android.core.arch.repositories.children.internal.ChildrenAppenderGetter +import org.hisp.dhis.android.core.trackedentity.TrackedEntityInstance +import org.hisp.dhis.android.core.trackedentity.internal.TrackedEntityInstanceStore +import org.hisp.dhis.android.core.trackedentity.internal.TrackerParentCallFactory + +internal class TrackedEntityInstanceQueryPagingSource( + store: TrackedEntityInstanceStore, + databaseAdapter: DatabaseAdapter, + trackerParentCallFactory: TrackerParentCallFactory, + scope: TrackedEntityInstanceQueryRepositoryScope, + childrenAppenders: ChildrenAppenderGetter, + onlineCache: TrackedEntityInstanceOnlineCache, + onlineHelper: TrackedEntityInstanceQueryOnlineHelper, + localQueryHelper: TrackedEntityInstanceLocalQueryHelper, +) : PagingSource() { + + private val dataFetcher = TrackedEntityInstanceQueryDataFetcher( + store, + databaseAdapter, + trackerParentCallFactory, + scope, + childrenAppenders, + onlineCache, + onlineHelper, + localQueryHelper, + ) + + override fun getRefreshKey( + state: PagingState, + ): TrackedEntityInstance? { + return state.anchorPosition?.let { state.closestPageToPosition(it)?.prevKey } + } + + override suspend fun load( + params: LoadParams, + ): LoadResult { + return when (params) { + is LoadParams.Refresh -> { + dataFetcher.refresh() + loadPages(params.loadSize) + } + is LoadParams.Append -> { + loadPages(params.loadSize) + } + is LoadParams.Prepend -> { + emptyPage() + } + } + } + + private fun loadPages(loadSize: Int): LoadResult { + val pages = dataFetcher.loadPages(loadSize) + + return pages.firstOrNull { it is Result.Failure }?.let { + LoadResult.Error((it as Result.Failure).failure) + } ?: LoadResult.Page( + data = pages.map { it.getOrThrow() }, + prevKey = null, // Only paging forward + nextKey = pages.getOrNull(loadSize - 1)?.getOrThrow(), + ) + } + + private fun emptyPage(): LoadResult { + return LoadResult.Page( + data = emptyList(), + prevKey = null, + nextKey = null, + ) + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntitySearchCollectionRepository.kt b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntitySearchCollectionRepository.kt index 11eb47415c..e4c30a3e5c 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntitySearchCollectionRepository.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntitySearchCollectionRepository.kt @@ -32,7 +32,12 @@ import androidx.lifecycle.LiveData import androidx.paging.DataSource import androidx.paging.LivePagedListBuilder import androidx.paging.PagedList +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.PagingSource import io.reactivex.Single +import kotlinx.coroutines.flow.Flow import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter import org.hisp.dhis.android.core.arch.handlers.internal.Transformer import org.hisp.dhis.android.core.arch.helpers.Result @@ -98,9 +103,34 @@ class TrackedEntitySearchCollectionRepository internal constructor( return LivePagedListBuilder(factory, pageSize).build() } + override fun getPagingData(pageSize: Int): Flow> { + return getPager(pageSize).flow + } + + fun getPager(pageSize: Int): Pager { + return Pager( + config = PagingConfig(pageSize = pageSize), + ) { + pagingSource + } + } + val dataSource: DataSource get() = TrackedEntitySearchDataSource(getDataFetcher()) + val pagingSource: PagingSource + get() = TrackedEntitySearchPagingSource( + store, + databaseAdapter, + trackerParentCallFactory, + scope, + childrenAppenders, + onlineCache, + onlineHelper, + localQueryHelper, + searchDataFetcherHelper, + ) + val resultDataSource: DataSource> get() = TrackedEntitySearchDataSourceResult(getDataFetcher()) diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntitySearchDataSource.kt b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntitySearchDataSource.kt index ea8183c5a7..52514aaea9 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntitySearchDataSource.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntitySearchDataSource.kt @@ -30,7 +30,7 @@ package org.hisp.dhis.android.core.trackedentity.search import androidx.paging.ItemKeyedDataSource import org.hisp.dhis.android.core.arch.helpers.Result -internal class TrackedEntitySearchDataSource constructor( +internal class TrackedEntitySearchDataSource( private val dataFetcher: TrackedEntitySearchDataFetcher, ) : ItemKeyedDataSource() { diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntitySearchOperators.kt b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntitySearchOperators.kt index e0ff967c51..4b41e78973 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntitySearchOperators.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntitySearchOperators.kt @@ -111,19 +111,7 @@ abstract class TrackedEntitySearchOperators internal constru }.eq(null) } - /** - * Add an "attribute" filter to the query. If this method is called several times, conditions are appended with - * AND connector. - * - * - * For example, - *


.byAttribute("uid1").eq("value1")

.byAttribute("uid2").eq("value2")

- * means that the instance must have attribute "uid1" with value "value1" **AND** attribute "uid2" with - * value "value2". - * - * @param attributeId Attribute uid to use in the filter - * @return Repository connector - */ + @Deprecated(message = "Use byFilter()", replaceWith = ReplaceWith("byFilter(attributeId)")) fun byAttribute(attributeId: String): EqLikeItemFilterConnector { return byFilter(attributeId) } @@ -147,11 +135,10 @@ abstract class TrackedEntitySearchOperators internal constru } } - /** - * Search tracked entity instances with **any** attribute matching the query. - * - * @return Repository connector - */ + @Deprecated( + message = "This property is ignored for online queries and will be ignored in offline queries soon. " + + "Please use byFilter to achieve a similar functionality.", + ) fun byQuery(): EqLikeItemFilterConnector { return connectorFactory.eqLikeItemC("") { filterItem: RepositoryScopeFilterItem -> scope.toBuilder().query(filterItem).build() diff --git a/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntitySearchPagingSource.kt b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntitySearchPagingSource.kt new file mode 100644 index 0000000000..f489e0b9f3 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntitySearchPagingSource.kt @@ -0,0 +1,106 @@ +/* + * 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.trackedentity.search + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.arch.helpers.Result +import org.hisp.dhis.android.core.arch.repositories.children.internal.ChildrenAppenderGetter +import org.hisp.dhis.android.core.trackedentity.TrackedEntityInstance +import org.hisp.dhis.android.core.trackedentity.internal.TrackedEntityInstanceStore +import org.hisp.dhis.android.core.trackedentity.internal.TrackerParentCallFactory + +internal class TrackedEntitySearchPagingSource( + store: TrackedEntityInstanceStore, + databaseAdapter: DatabaseAdapter, + trackerParentCallFactory: TrackerParentCallFactory, + scope: TrackedEntityInstanceQueryRepositoryScope, + childrenAppenders: ChildrenAppenderGetter, + onlineCache: TrackedEntityInstanceOnlineCache, + onlineHelper: TrackedEntityInstanceQueryOnlineHelper, + localQueryHelper: TrackedEntityInstanceLocalQueryHelper, + helper: TrackedEntitySearchDataFetcherHelper, +) : PagingSource() { + + private val dataFetcher = TrackedEntitySearchDataFetcher( + store, + databaseAdapter, + trackerParentCallFactory, + scope, + childrenAppenders, + onlineCache, + onlineHelper, + localQueryHelper, + helper, + ) + + override fun getRefreshKey( + state: PagingState, + ): TrackedEntitySearchItem? { + return state.anchorPosition?.let { state.closestPageToPosition(it)?.prevKey } + } + + override suspend fun load( + params: LoadParams, + ): LoadResult { + return when (params) { + is LoadParams.Refresh -> { + dataFetcher.refresh() + loadPages(params.loadSize) + } + is LoadParams.Append -> { + loadPages(params.loadSize) + } + is LoadParams.Prepend -> { + emptyPage() + } + } + } + + private fun loadPages(loadSize: Int): LoadResult { + val pages = dataFetcher.loadPages(loadSize) + + return pages.firstOrNull { it is Result.Failure }?.let { + LoadResult.Error((it as Result.Failure).failure) + } ?: LoadResult.Page( + data = pages.map { it.getOrThrow() }, + prevKey = null, // Only paging forward + nextKey = pages.getOrNull(loadSize - 1)?.getOrThrow(), + ) + } + + private fun emptyPage(): LoadResult { + return LoadResult.Page( + data = emptyList(), + prevKey = null, + nextKey = null, + ) + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/tracker/exporter/TrackerExporterParameterManager.kt b/core/src/main/java/org/hisp/dhis/android/core/tracker/exporter/TrackerExporterParameterManager.kt new file mode 100644 index 0000000000..18cbc34bd6 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/tracker/exporter/TrackerExporterParameterManager.kt @@ -0,0 +1,87 @@ +/* + * 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.tracker.exporter + +import org.hisp.dhis.android.core.organisationunit.OrganisationUnitMode +import org.hisp.dhis.android.core.systeminfo.DHISVersion +import org.hisp.dhis.android.core.systeminfo.DHISVersionManager +import org.hisp.dhis.android.core.trackedentity.ownership.OwnershipService +import org.koin.core.annotation.Singleton + +@Singleton +internal class TrackerExporterParameterManager( + private val dhisVersionManager: DHISVersionManager, +) { + fun getTrackedEntitiesParameter(uids: Collection?): Map { + return if (uids.isNullOrEmpty()) { + emptyMap() + } else if (dhisVersionManager.isGreaterOrEqualThan(DHISVersion.V2_41)) { + mapOf(TrackerExporterService.TRACKED_ENTITIES to uids.joinToString(",")) + } else { + mapOf(TrackerExporterService.TRACKED_ENTITY to uids.joinToString(";")) + } + } + + fun getEventsParameter(uids: Collection?): Map { + return if (uids.isNullOrEmpty()) { + emptyMap() + } else if (dhisVersionManager.isGreaterOrEqualThan(DHISVersion.V2_41)) { + mapOf(TrackerExporterService.EVENTS to uids.joinToString(",")) + } else { + mapOf(TrackerExporterService.EVENT to uids.joinToString(";")) + } + } + + fun getOrgunitModeParameter(mode: OrganisationUnitMode?): Map { + return if (mode == null) { + emptyMap() + } else if (dhisVersionManager.isGreaterOrEqualThan(DHISVersion.V2_41)) { + mapOf(TrackerExporterService.OU_MODE to mode.name) + } else { + mapOf(TrackerExporterService.OU_MODE_BELOW_41 to mode.name) + } + } + + fun getOrgunitsParameter(uids: Collection?): Map { + return if (uids.isNullOrEmpty()) { + emptyMap() + } else if (dhisVersionManager.isGreaterOrEqualThan(DHISVersion.V2_41)) { + mapOf(TrackerExporterService.ORG_UNITS to uids.joinToString(",")) + } else { + mapOf(TrackerExporterService.ORG_UNIT to uids.joinToString(";")) + } + } + + fun getTrackedEntityForOwnershipParameter(uid: String): Map { + return if (dhisVersionManager.isGreaterOrEqualThan(DHISVersion.V2_41)) { + mapOf(OwnershipService.TRACKED_ENTITY to uid) + } else { + mapOf(OwnershipService.TRACKED_ENTITY_INSTACE to uid) + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/tracker/exporter/TrackerExporterService.kt b/core/src/main/java/org/hisp/dhis/android/core/tracker/exporter/TrackerExporterService.kt index cd493a39d6..58599f6a92 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/tracker/exporter/TrackerExporterService.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/tracker/exporter/TrackerExporterService.kt @@ -29,7 +29,7 @@ package org.hisp.dhis.android.core.tracker.exporter import org.hisp.dhis.android.core.arch.api.fields.internal.Fields import org.hisp.dhis.android.core.arch.api.filters.internal.Which -import org.hisp.dhis.android.core.arch.api.payload.internal.NTIPayload +import org.hisp.dhis.android.core.arch.api.payload.internal.TrackerPayload import org.hisp.dhis.android.core.enrollment.NewTrackerImporterEnrollment import org.hisp.dhis.android.core.event.NewTrackerImporterEvent import org.hisp.dhis.android.core.trackedentity.NewTrackerImporterTrackedEntity @@ -37,33 +37,23 @@ import retrofit2.http.* @Suppress("LongParameterList") internal interface TrackerExporterService { - @GET(TRACKED_ENTITY_INSTANCES) - suspend fun getTrackedEntityInstance( - @Query(FIELDS) @Which fields: Fields, - @Query(TRACKED_ENTITY_INSTACE) trackedEntityInstance: String?, - @Query(OU_MODE) orgUnitMode: String?, - @Query(INCLUDE_ALL_ATTRIBUTES) includeAllAttributes: Boolean, - @Query(INCLUDE_DELETED) includeDeleted: Boolean, - ): NTIPayload - - @GET("$TRACKED_ENTITY_INSTANCES/{$TRACKED_ENTITY_INSTACE}") + @GET("$TRACKED_ENTITIES_API/{$TRACKED_ENTITY}") suspend fun getSingleTrackedEntityInstance( - @Path(TRACKED_ENTITY_INSTACE) trackedEntityInstanceUid: String, + @Path(TRACKED_ENTITY) trackedEntityInstanceUid: String, @Query(FIELDS) @Which fields: Fields, - @Query(OU_MODE) orgUnitMode: String?, + @QueryMap orgUnitMode: Map = emptyMap(), @Query(PROGRAM) program: String?, @Query(PROGRAM_STATUS) programStatus: String?, @Query(ENROLLMENT_ENROLLED_AFTER) programStartDate: String?, - @Query(INCLUDE_ALL_ATTRIBUTES) includeAllAttributes: Boolean, @Query(INCLUDE_DELETED) includeDeleted: Boolean, ): NewTrackerImporterTrackedEntity - @GET(TRACKED_ENTITY_INSTANCES) + @GET(TRACKED_ENTITIES_API) suspend fun getTrackedEntityInstances( @Query(FIELDS) @Which fields: Fields, - @Query(TRACKED_ENTITY_INSTACE) trackedEntityInstances: String? = null, - @Query(OU) orgUnits: String? = null, - @Query(OU_MODE) orgUnitMode: String? = null, + @QueryMap trackedEntityInstances: Map = emptyMap(), + @QueryMap orgUnits: Map = emptyMap(), + @QueryMap orgUnitMode: Map = emptyMap(), @Query(PROGRAM) program: String? = null, @Query(PROGRAM_STAGE) programStage: String? = null, @Query(ENROLLMENT_ENROLLED_AFTER) programStartDate: String? = null, @@ -76,30 +66,28 @@ internal interface TrackerExporterService { @Query(EVENT_END_DATE) eventEndDate: String? = null, @Query(EVENT_STATUS) eventStatus: String? = null, @Query(TRACKED_ENTITY_TYPE) trackedEntityType: String? = null, - @Query(QUERY) query: String? = null, @Query(FILTER) filter: List? = null, @Query(ASSIGNED_USER_MODE) assignedUserMode: String? = null, @Query(UPDATED_AFTER) lastUpdatedStartDate: String? = null, @Query(UPDATED_BEFORE) lastUpdatedEndDate: String? = null, @Query(ORDER) order: String? = null, @Query(PAGING) paging: Boolean, - @Query(PAGE) page: Int, - @Query(PAGE_SIZE) pageSize: Int, - @Query(INCLUDE_ALL_ATTRIBUTES) includeAllAttributes: Boolean, + @Query(PAGE) page: Int?, + @Query(PAGE_SIZE) pageSize: Int?, @Query(INCLUDE_DELETED) includeDeleted: Boolean = false, - ): NTIPayload + ): TrackerPayload - @GET("$ENROLLMENTS/{$ENROLLMENT}") + @GET("$ENROLLMENTS_API/{$ENROLLMENT}") suspend fun getEnrollmentSingle( @Path(ENROLLMENT) enrollmentUid: String, @Query(FIELDS) @Which fields: Fields, ): NewTrackerImporterEnrollment - @GET(EVENTS) + @GET(EVENTS_API) suspend fun getEvents( @Query(FIELDS) @Which fields: Fields, - @Query(OU) orgUnit: String?, - @Query(OU_MODE) orgUnitMode: String?, + @Query(ORG_UNIT) orgUnit: String?, + @QueryMap orgUnitMode: Map = emptyMap(), @Query(STATUS) status: String? = null, @Query(PROGRAM) program: String?, @Query(PROGRAM_STAGE) programStage: String? = null, @@ -118,32 +106,35 @@ internal interface TrackerExporterService { @Query(ORDER) order: String? = null, @Query(ASSIGNED_USER_MODE) assignedUserMode: String? = null, @Query(PAGING) paging: Boolean, - @Query(PAGE) page: Int, - @Query(PAGE_SIZE) pageSize: Int, + @Query(PAGE) page: Int?, + @Query(PAGE_SIZE) pageSize: Int?, @Query(UPDATED_AFTER) updatedAfter: String?, @Query(UPDATED_BEFORE) updatedBefore: String? = null, @Query(INCLUDE_DELETED) includeDeleted: Boolean, - @Query(EVENT) eventUid: String? = null, - ): NTIPayload + @QueryMap eventUid: Map = emptyMap(), + ): TrackerPayload - @GET(EVENTS) + @GET(EVENTS_API) suspend fun getEventSingle( @Query(FIELDS) @Which fields: Fields, - @Query(EVENT) eventUid: String, - @Query(OU_MODE) orgUnitMode: String, - ): NTIPayload + @QueryMap eventUid: Map? = null, + @QueryMap orgUnitMode: Map? = null, + ): TrackerPayload companion object { - const val TRACKED_ENTITY_INSTANCES = "tracker/trackedEntities" - const val ENROLLMENTS = "tracker/enrollments" - const val EVENTS = "tracker/events" - const val TRACKED_ENTITY_INSTACE = "trackedEntity" + const val TRACKED_ENTITIES_API = "tracker/trackedEntities" + const val ENROLLMENTS_API = "tracker/enrollments" + const val EVENTS_API = "tracker/events" + const val TRACKED_ENTITY = "trackedEntity" + const val TRACKED_ENTITIES = "trackedEntities" const val ENROLLMENT = "enrollment" const val EVENT = "event" - const val OU = "orgUnit" - const val OU_MODE = "ouMode" + const val EVENTS = "events" + const val ORG_UNIT = "orgUnit" + const val ORG_UNITS = "orgUnits" + const val OU_MODE = "orgUnitMode" + const val OU_MODE_BELOW_41 = "ouMode" const val FIELDS = "fields" - const val QUERY = "query" const val PAGING = "paging" const val PAGE = "page" const val PAGE_SIZE = "pageSize" @@ -164,7 +155,6 @@ internal interface TrackerExporterService { const val SCHEDULED_AFTER = "scheduledAfter" const val SCHEDULED_BEFORE = "scheduledBefore" const val TRACKED_ENTITY_TYPE = "trackedEntityType" - const val INCLUDE_ALL_ATTRIBUTES = "includeAllAttributes" const val FILTER = "filter" const val FILTER_ATTRIBUTES = "filterAttributes" const val UPDATED_AFTER = "updatedAfter" diff --git a/core/src/main/java/org/hisp/dhis/android/core/tracker/exporter/TrackerQueryHelper.kt b/core/src/main/java/org/hisp/dhis/android/core/tracker/exporter/TrackerQueryHelper.kt new file mode 100644 index 0000000000..adb4e869cf --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/tracker/exporter/TrackerQueryHelper.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.tracker.exporter + +import org.hisp.dhis.android.core.organisationunit.OrganisationUnitMode +import org.hisp.dhis.android.core.trackedentity.search.TrackedEntityInstanceQueryOnline + +internal object TrackerQueryHelper { + fun getOrgunits(query: TrackerAPIQuery): List? { + return getOrgunits(query.orgUnit?.let { listOf(it) }, query.commonParams.ouMode) + } + + fun getOrgunits(query: TrackedEntityInstanceQueryOnline): List? { + return getOrgunits(query.orgUnits, query.orgUnitMode) + } + + fun getOrgunits(orgunit: String?, mode: OrganisationUnitMode?): List? { + return getOrgunits(orgunit?.let { listOf(it) }, mode) + } + + private fun getOrgunits(orgunits: List?, mode: OrganisationUnitMode?): List? { + return if (orgunits.isNullOrEmpty()) { + null + } else if ( + mode == OrganisationUnitMode.ALL || + mode == OrganisationUnitMode.ACCESSIBLE || + mode == OrganisationUnitMode.CAPTURE + ) { + null + } else { + orgunits + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/tracker/importer/internal/JobReport.kt b/core/src/main/java/org/hisp/dhis/android/core/tracker/importer/internal/JobReport.kt index b4bea7f608..ce480baec3 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/tracker/importer/internal/JobReport.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/tracker/importer/internal/JobReport.kt @@ -50,7 +50,6 @@ internal data class JobValidationReport( internal data class JobObjectReport( val errorReports: List, - val index: Int, val trackerType: TrackerImporterObjectType, val uid: String, ) diff --git a/core/src/main/java/org/hisp/dhis/android/core/tracker/importer/internal/JobReportFileResourceHandler.kt b/core/src/main/java/org/hisp/dhis/android/core/tracker/importer/internal/JobReportFileResourceHandler.kt index df58c91c59..7d7b3802ab 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/tracker/importer/internal/JobReportFileResourceHandler.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/tracker/importer/internal/JobReportFileResourceHandler.kt @@ -29,7 +29,7 @@ package org.hisp.dhis.android.core.tracker.importer.internal import org.hisp.dhis.android.core.arch.call.internal.D2ProgressManager import org.hisp.dhis.android.core.fileresource.FileResource -import org.hisp.dhis.android.core.fileresource.FileResourceDomainType +import org.hisp.dhis.android.core.fileresource.FileResourceDataDomainType import org.hisp.dhis.android.core.fileresource.internal.FileResourceHelper import org.koin.core.annotation.Singleton @@ -42,7 +42,7 @@ internal class JobReportFileResourceHandler internal constructor( val fileResources = jobObjects.flatMap { it.fileResources() } - fileResourceHelper.updateFileResourceStates(fileResources, FileResourceDomainType.TRACKER) + fileResourceHelper.updateFileResourceStates(fileResources, FileResourceDataDomainType.TRACKER) progress.increaseProgress(FileResource::class.java, false) } diff --git a/core/src/main/java/org/hisp/dhis/android/core/usecase/stock/InternalStockUseCase.java b/core/src/main/java/org/hisp/dhis/android/core/usecase/stock/InternalStockUseCase.java index 091f0fc387..b392ec3234 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/usecase/stock/InternalStockUseCase.java +++ b/core/src/main/java/org/hisp/dhis/android/core/usecase/stock/InternalStockUseCase.java @@ -93,7 +93,7 @@ public static Builder builder() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); @JsonProperty("programUid") diff --git a/core/src/main/java/org/hisp/dhis/android/core/usecase/stock/InternalStockUseCaseTransaction.java b/core/src/main/java/org/hisp/dhis/android/core/usecase/stock/InternalStockUseCaseTransaction.java index 604f0657aa..ecb9d2d96c 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/usecase/stock/InternalStockUseCaseTransaction.java +++ b/core/src/main/java/org/hisp/dhis/android/core/usecase/stock/InternalStockUseCaseTransaction.java @@ -85,7 +85,7 @@ public static Builder builder() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { @JsonProperty(UID) public abstract Builder programUid(String programUid); diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/AccountManager.kt b/core/src/main/java/org/hisp/dhis/android/core/user/AccountManager.kt index c9d465a812..498a17bef7 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/user/AccountManager.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/user/AccountManager.kt @@ -36,6 +36,8 @@ import kotlin.jvm.Throws interface AccountManager { fun getAccounts(): List + fun getCurrentAccount(): DatabaseAccount? + fun setMaxAccounts(maxAccounts: Int?) fun getMaxAccounts(): Int? diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/AuthenticatedUser.java b/core/src/main/java/org/hisp/dhis/android/core/user/AuthenticatedUser.java index 17891df69e..f137c0151b 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/user/AuthenticatedUser.java +++ b/core/src/main/java/org/hisp/dhis/android/core/user/AuthenticatedUser.java @@ -58,7 +58,7 @@ public static AuthenticatedUser create(Cursor cursor) { @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/Authority.java b/core/src/main/java/org/hisp/dhis/android/core/user/Authority.java index 2d03d11c57..5ff85365aa 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/user/Authority.java +++ b/core/src/main/java/org/hisp/dhis/android/core/user/Authority.java @@ -61,7 +61,7 @@ public static Builder builder() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/User.java b/core/src/main/java/org/hisp/dhis/android/core/user/User.java index ad47f00aee..ae0f7c7bfc 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/user/User.java +++ b/core/src/main/java/org/hisp/dhis/android/core/user/User.java @@ -38,6 +38,7 @@ import org.hisp.dhis.android.core.arch.db.adapters.ignore.internal.IgnoreOrganisationUnitListAdapter; import org.hisp.dhis.android.core.arch.db.adapters.ignore.internal.IgnoreUserCredentialsAdapter; +import org.hisp.dhis.android.core.arch.db.adapters.ignore.internal.IgnoreUserGroupListColumnAdapter; import org.hisp.dhis.android.core.arch.db.adapters.ignore.internal.IgnoreUserRoleListColumnAdapter; import org.hisp.dhis.android.core.common.BaseIdentifiableObject; import org.hisp.dhis.android.core.common.CoreObject; @@ -111,6 +112,11 @@ public abstract class User extends BaseIdentifiableObject implements CoreObject @ColumnAdapter(IgnoreUserRoleListColumnAdapter.class) public abstract List userRoles(); + @Nullable + @JsonProperty() + @ColumnAdapter(IgnoreUserGroupListColumnAdapter.class) + public abstract List userGroups(); + public abstract Builder toBuilder(); public static Builder builder() { @@ -124,7 +130,7 @@ public static User create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseIdentifiableObject.Builder { + public abstract static class Builder extends BaseIdentifiableObject.Builder { public abstract Builder id(Long id); @@ -164,6 +170,8 @@ public static abstract class Builder extends BaseIdentifiableObject.Builder userRoles); + public abstract Builder userGroups(List userGroups); + public abstract User build(); } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/UserCredentials.java b/core/src/main/java/org/hisp/dhis/android/core/user/UserCredentials.java index a4f4b1b274..251167611e 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/user/UserCredentials.java +++ b/core/src/main/java/org/hisp/dhis/android/core/user/UserCredentials.java @@ -65,7 +65,7 @@ public static Builder builder() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder { + public abstract static class Builder { public abstract Builder username(String username); public abstract Builder name(String username); diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/UserGroup.java b/core/src/main/java/org/hisp/dhis/android/core/user/UserGroup.java new file mode 100644 index 0000000000..c0f69c38ec --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/user/UserGroup.java @@ -0,0 +1,61 @@ +/* + * 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.user; + +import android.database.Cursor; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; + +import org.hisp.dhis.android.core.common.BaseIdentifiableObject; +import org.hisp.dhis.android.core.common.CoreObject; + +@AutoValue +@JsonDeserialize(builder = $$AutoValue_UserGroup.Builder.class) +public abstract class UserGroup extends BaseIdentifiableObject implements CoreObject { + + public static Builder builder() { + return new $$AutoValue_UserGroup.Builder(); + } + + public static UserGroup create(Cursor cursor) { + return $AutoValue_UserGroup.createFromCursor(cursor); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "") + public abstract static class Builder extends BaseIdentifiableObject.Builder { + public abstract Builder id(Long id); + + public abstract UserGroup build(); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/UserGroupCollectionRepository.kt b/core/src/main/java/org/hisp/dhis/android/core/user/UserGroupCollectionRepository.kt new file mode 100644 index 0000000000..5441c2ef8d --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/user/UserGroupCollectionRepository.kt @@ -0,0 +1,61 @@ +/* + * 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.user + +import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.arch.repositories.children.internal.ChildrenAppenderGetter +import org.hisp.dhis.android.core.arch.repositories.collection.internal.ReadOnlyIdentifiableCollectionRepositoryImpl +import org.hisp.dhis.android.core.arch.repositories.filters.internal.FilterConnectorFactory +import org.hisp.dhis.android.core.arch.repositories.scope.RepositoryScope +import org.hisp.dhis.android.core.user.internal.UserGroupStore +import org.koin.core.annotation.Singleton + +@Singleton +class UserGroupCollectionRepository internal constructor( + store: UserGroupStore, + databaseAdapter: DatabaseAdapter, + scope: RepositoryScope, +) : ReadOnlyIdentifiableCollectionRepositoryImpl( + store, + databaseAdapter, + childrenAppenders, + scope, + FilterConnectorFactory( + scope, + ) { s: RepositoryScope -> + UserGroupCollectionRepository( + store, + databaseAdapter, + s, + ) + }, +) { + internal companion object { + val childrenAppenders: ChildrenAppenderGetter = emptyMap() + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/UserGroupTableInfo.kt b/core/src/main/java/org/hisp/dhis/android/core/user/UserGroupTableInfo.kt new file mode 100644 index 0000000000..081c084415 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/user/UserGroupTableInfo.kt @@ -0,0 +1,48 @@ +/* + * 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.user + +import org.hisp.dhis.android.core.arch.db.tableinfos.TableInfo +import org.hisp.dhis.android.core.common.CoreColumns +import org.hisp.dhis.android.core.common.IdentifiableColumns + +object UserGroupTableInfo { + + @JvmField + val TABLE_INFO: TableInfo = object : TableInfo() { + override fun name(): String { + return "UserGroup" + } + + override fun columns(): CoreColumns { + return Columns() + } + } + + class Columns : IdentifiableColumns() +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/UserInfo.java b/core/src/main/java/org/hisp/dhis/android/core/user/UserInfo.java index 2d357384d0..953b1d4d22 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/user/UserInfo.java +++ b/core/src/main/java/org/hisp/dhis/android/core/user/UserInfo.java @@ -66,7 +66,7 @@ public static UserInfo create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder { + public abstract static class Builder { public abstract Builder uid(String uid); diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/UserModule.kt b/core/src/main/java/org/hisp/dhis/android/core/user/UserModule.kt index 2c07b6846a..87f11420ae 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/user/UserModule.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/user/UserModule.kt @@ -35,6 +35,7 @@ import org.hisp.dhis.android.core.user.openid.OpenIDConnectHandler interface UserModule { fun authenticatedUser(): AuthenticatedUserObjectRepository fun userRoles(): UserRoleCollectionRepository + fun userGroups(): UserGroupCollectionRepository fun authorities(): AuthorityCollectionRepository fun user(): UserObjectRepository fun accountManager(): AccountManager diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/UserOrganisationUnitLink.java b/core/src/main/java/org/hisp/dhis/android/core/user/UserOrganisationUnitLink.java index 4a9964580e..dfda6f8b6b 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/user/UserOrganisationUnitLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/user/UserOrganisationUnitLink.java @@ -59,7 +59,7 @@ public static UserOrganisationUnitLink create(Cursor cursor) { public abstract Builder toBuilder(); @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/internal/AccountManagerImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/user/internal/AccountManagerImpl.kt index e9b69d877c..3397d4d09f 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/user/internal/AccountManagerImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/user/internal/AccountManagerImpl.kt @@ -36,6 +36,7 @@ import org.hisp.dhis.android.core.arch.helpers.FileResourceDirectoryHelper import org.hisp.dhis.android.core.arch.storage.internal.Credentials import org.hisp.dhis.android.core.arch.storage.internal.CredentialsSecureStore import org.hisp.dhis.android.core.configuration.internal.DatabaseAccount +import org.hisp.dhis.android.core.configuration.internal.DatabaseAccountImportStatus import org.hisp.dhis.android.core.configuration.internal.DatabaseConfigurationHelper import org.hisp.dhis.android.core.configuration.internal.DatabaseConfigurationInsecureStore import org.hisp.dhis.android.core.configuration.internal.MultiUserDatabaseManager @@ -48,7 +49,7 @@ import org.koin.core.annotation.Singleton @Singleton @Suppress("TooManyFunctions") -internal class AccountManagerImpl constructor( +internal class AccountManagerImpl( private val databasesConfigurationStore: DatabaseConfigurationInsecureStore, private val multiUserDatabaseManager: MultiUserDatabaseManager, private val databaseAdapterFactory: DatabaseAdapterFactory, @@ -62,6 +63,12 @@ internal class AccountManagerImpl constructor( return databasesConfigurationStore.get()?.accounts()?.map { updateSyncState(it) } ?: emptyList() } + override fun getCurrentAccount(): DatabaseAccount? { + return credentialsSecureStore.get() + ?.let { multiUserDatabaseManager.getAccount(it.serverUrl, it.username) } + ?.let { updateSyncState(it) } + } + override fun setMaxAccounts(maxAccounts: Int?) { multiUserDatabaseManager.setMaxAccounts(maxAccounts) } @@ -128,12 +135,16 @@ internal class AccountManagerImpl constructor( } private fun updateSyncState(account: DatabaseAccount): DatabaseAccount { - val databaseAdapter = databaseAdapterFactory.getDatabaseAdapter(account) - val syncState = AccountManagerHelper.getSyncState(databaseAdapter) + return if (account.importDB()?.status() != DatabaseAccountImportStatus.PENDING_TO_IMPORT) { + val databaseAdapter = databaseAdapterFactory.getDatabaseAdapter(account) + val syncState = AccountManagerHelper.getSyncState(databaseAdapter) - return account.toBuilder() - .syncState(syncState) - .build() + account.toBuilder() + .syncState(syncState) + .build() + } else { + account + } } override fun accountDeletionObservable(): Observable { diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/internal/LogInCall.kt b/core/src/main/java/org/hisp/dhis/android/core/user/internal/LogInCall.kt index 29cc2ea43c..8d8ebeba62 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/user/internal/LogInCall.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/user/internal/LogInCall.kt @@ -36,7 +36,6 @@ import org.hisp.dhis.android.core.arch.storage.internal.UserIdInMemoryStore import org.hisp.dhis.android.core.configuration.internal.ServerUrlParser import org.hisp.dhis.android.core.maintenance.D2Error import org.hisp.dhis.android.core.maintenance.D2ErrorCode -import org.hisp.dhis.android.core.systeminfo.DHISVersionManager import org.hisp.dhis.android.core.systeminfo.internal.SystemInfoCall import org.hisp.dhis.android.core.user.AccountDeletionReason import org.hisp.dhis.android.core.user.AuthenticatedUser @@ -58,7 +57,6 @@ internal class LogInCall( private val databaseManager: LogInDatabaseManager, private val exceptions: LogInExceptions, private val accountManager: AccountManagerImpl, - private val versionManager: DHISVersionManager, private val apiCallErrorCatcher: UserAuthenticateCallErrorCatcher, ) { suspend fun logIn(username: String?, password: String?, serverUrl: String?): User { @@ -79,13 +77,17 @@ internal class LogInCall( val credentials = Credentials(username!!, trimmedServerUrl!!, password, null) return try { - val user = coroutineAPICallExecutor.wrap(errorCatcher = apiCallErrorCatcher) { - userService.authenticate( - okhttp3.Credentials.basic(username, password!!), - UserFields.allFieldsWithoutOrgUnit(null), - ) - }.getOrThrow() - loginOnline(user, credentials) + if (databaseManager.isPendingToImportDB(trimmedServerUrl, username)) { + importDB(trimmedServerUrl, credentials) + } else { + val user = coroutineAPICallExecutor.wrap(errorCatcher = apiCallErrorCatcher) { + userService.authenticate( + okhttp3.Credentials.basic(username, password!!), + UserFields.allFieldsWithoutOrgUnit, + ) + }.getOrThrow() + loginOnline(user, credentials) + } } catch (d2Error: D2Error) { if (d2Error.isOffline) { tryLoginOffline(credentials, d2Error) @@ -160,6 +162,19 @@ internal class LogInCall( return userStore.selectByUid(existingUser.user()!!)!! } + @Suppress("TooGenericExceptionCaught") + private fun importDB(serverUrl: String, credentials: Credentials): User { + try { + databaseManager.importDB(serverUrl, credentials) + credentialsSecureStore.set(credentials) + val existingUser = authenticatedUserStore.selectFirst() ?: throw exceptions.noUserOfflineError() + userIdStore.set(existingUser.user()!!) + return userStore.selectByUid(existingUser.user()!!)!! + } catch (e: Exception) { + throw exceptions.badCredentialsError() + } + } + @Throws(D2Error::class) suspend fun blockingLogInOpenIDConnect(serverUrl: String, openIDConnectState: AuthState): User { val trimmedServerUrl = ServerUrlParser.trimAndRemoveTrailingSlash(serverUrl) @@ -172,7 +187,7 @@ internal class LogInCall( val user = coroutineAPICallExecutor.wrap(errorCatcher = apiCallErrorCatcher) { userService.authenticate( "Bearer ${openIDConnectState.idToken}", - UserFields.allFieldsWithoutOrgUnit(versionManager.getVersion()), + UserFields.allFieldsWithoutOrgUnit, ) }.getOrThrow() credentials = getOpenIdConnectCredentials(user, trimmedServerUrl!!, openIDConnectState) diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/internal/LogInDatabaseManager.kt b/core/src/main/java/org/hisp/dhis/android/core/user/internal/LogInDatabaseManager.kt index c99f61d65b..5345ae6e35 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/user/internal/LogInDatabaseManager.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/user/internal/LogInDatabaseManager.kt @@ -27,6 +27,8 @@ */ package org.hisp.dhis.android.core.user.internal +import org.hisp.dhis.android.core.arch.storage.internal.Credentials +import org.hisp.dhis.android.core.configuration.internal.DatabaseAccountImportStatus import org.hisp.dhis.android.core.configuration.internal.MultiUserDatabaseManager import org.hisp.dhis.android.core.settings.internal.GeneralSettingCall import org.koin.core.annotation.Singleton @@ -60,4 +62,14 @@ internal class LogInDatabaseManager( username, ) } + + fun isPendingToImportDB(serverUrl: String, username: String): Boolean { + val existingAccount = multiUserDatabaseManager.getAccount(serverUrl, username) + return existingAccount?.importDB()?.status() == DatabaseAccountImportStatus.PENDING_TO_IMPORT + } + + fun importDB(serverUrl: String, credentials: Credentials) { + val existingAccount = multiUserDatabaseManager.getAccount(serverUrl, credentials.username)!! + multiUserDatabaseManager.importAndLoadDb(existingAccount, credentials.password!!) + } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserFields.kt b/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserFields.kt index b995cd96e1..063dd38fed 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserFields.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserFields.kt @@ -36,6 +36,7 @@ import org.hisp.dhis.android.core.organisationunit.internal.OrganisationUnitFiel import org.hisp.dhis.android.core.systeminfo.DHISVersion import org.hisp.dhis.android.core.user.User import org.hisp.dhis.android.core.user.UserCredentials +import org.hisp.dhis.android.core.user.UserGroup import org.hisp.dhis.android.core.user.UserRole object UserFields { @@ -55,6 +56,7 @@ object UserFields { const val NATIONALITY = "nationality" const val USER_CREDENTIALS = "userCredentials" const val USER_ROLES = "userRoles" + const val USER_GROUPS = "userGroups" private const val ORGANISATION_UNITS = "organisationUnits" private const val TEI_SEARCH_ORGANISATION_UNITS = "teiSearchOrganisationUnits" @@ -83,11 +85,13 @@ object UserFields { private val organisationUnits = NestedField.create(ORGANISATION_UNITS) private val teiSearchOrganisationUnits = NestedField.create(TEI_SEARCH_ORGANISATION_UNITS) private val userRoles = NestedField.create(USER_ROLES) + private val userGroups = NestedField.create(USER_GROUPS) private fun commonFields(): Fields.Builder { return Fields.builder().fields( uid, code, name, displayName, created, lastUpdated, birthday, education, gender, jobTitle, surname, firstName, introduction, employer, interests, languages, email, phoneNumber, nationality, deleted, + userGroups.with(UserGroupFields.allFields), ) } @@ -115,9 +119,7 @@ object UserFields { } } - fun allFieldsWithoutOrgUnit(version: DHISVersion?): Fields { - return getBaseFields(version).build() - } + val allFieldsWithoutOrgUnit: Fields = getBaseFields(null).build() fun allFieldsWithOrgUnit(version: DHISVersion?): Fields { return getBaseFields(version) diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserGroupChildrenAppender.kt b/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserGroupChildrenAppender.kt new file mode 100644 index 0000000000..437eca2457 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserGroupChildrenAppender.kt @@ -0,0 +1,46 @@ +/* + * 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.user.internal + +import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.arch.repositories.children.internal.ChildrenAppender +import org.hisp.dhis.android.core.user.User +import org.koin.core.annotation.Singleton + +@Singleton +internal class UserGroupChildrenAppender( + databaseAdapter: DatabaseAdapter, +) : ChildrenAppender() { + private val store = UserGroupStoreImpl(databaseAdapter) + + override fun appendChildren(user: User): User { + val builder = user.toBuilder() + builder.userGroups(store.selectAll()) + return builder.build() + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserGroupCollectionCleaner.kt b/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserGroupCollectionCleaner.kt new file mode 100644 index 0000000000..420d1f6478 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserGroupCollectionCleaner.kt @@ -0,0 +1,42 @@ +/* + * 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.user.internal + +import org.hisp.dhis.android.core.arch.cleaners.internal.CollectionCleanerImpl +import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.user.UserGroup +import org.hisp.dhis.android.core.user.UserGroupTableInfo +import org.koin.core.annotation.Singleton + +@Singleton +internal class UserGroupCollectionCleaner( + databaseAdapter: DatabaseAdapter, +) : CollectionCleanerImpl( + tableName = UserGroupTableInfo.TABLE_INFO.name(), + databaseAdapter = databaseAdapter, +) diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserGroupFields.kt b/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserGroupFields.kt new file mode 100644 index 0000000000..4ff46e01bf --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserGroupFields.kt @@ -0,0 +1,39 @@ +/* + * 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.user.internal + +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper +import org.hisp.dhis.android.core.user.UserGroup + +internal object UserGroupFields { + private val fh = FieldsHelper() + val allFields: Fields = Fields.builder() + .fields(fh.getIdentifiableFields()) + .build() +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserGroupHandler.kt b/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserGroupHandler.kt new file mode 100644 index 0000000000..79bb683912 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserGroupHandler.kt @@ -0,0 +1,38 @@ +/* + * 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.user.internal + +import org.hisp.dhis.android.core.arch.handlers.internal.IdentifiableHandlerImpl +import org.hisp.dhis.android.core.user.UserGroup +import org.koin.core.annotation.Singleton + +@Singleton +internal class UserGroupHandler( + store: UserGroupStore, +) : IdentifiableHandlerImpl(store) diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserGroupStore.kt b/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserGroupStore.kt new file mode 100644 index 0000000000..cddcc80f4d --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserGroupStore.kt @@ -0,0 +1,34 @@ +/* + * 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.user.internal + +import org.hisp.dhis.android.core.arch.db.stores.internal.IdentifiableObjectStore +import org.hisp.dhis.android.core.user.UserGroup + +internal interface UserGroupStore : IdentifiableObjectStore diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserGroupStoreImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserGroupStoreImpl.kt new file mode 100644 index 0000000000..df6ec22e4c --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserGroupStoreImpl.kt @@ -0,0 +1,52 @@ +/* + * 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.user.internal + +import android.database.Cursor +import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.arch.db.stores.binders.internal.IdentifiableStatementBinder +import org.hisp.dhis.android.core.arch.db.stores.binders.internal.StatementBinder +import org.hisp.dhis.android.core.arch.db.stores.internal.IdentifiableObjectStoreImpl +import org.hisp.dhis.android.core.user.UserGroup +import org.hisp.dhis.android.core.user.UserGroupTableInfo +import org.koin.core.annotation.Singleton + +@Singleton +internal class UserGroupStoreImpl( + databaseAdapter: DatabaseAdapter, +) : UserGroupStore, + IdentifiableObjectStoreImpl( + databaseAdapter, + UserGroupTableInfo.TABLE_INFO, + BINDER, + { cursor: Cursor -> UserGroup.create(cursor) }, + ) { + companion object { + private val BINDER: StatementBinder = object : IdentifiableStatementBinder() {} + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserHandler.kt b/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserHandler.kt index 40227e8e9c..91f4c9b8e8 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserHandler.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserHandler.kt @@ -38,6 +38,8 @@ internal class UserHandler( userStore: UserStore, private val userRoleHandler: UserRoleHandler, private val userRoleCollectionCleaner: UserRoleCollectionCleaner, + private val userGroupHandler: UserGroupHandler, + private val userGroupCollectionCleaner: UserGroupCollectionCleaner, ) : IdentifiableHandlerImpl(userStore) { override fun beforeObjectHandled(o: User): User { @@ -58,5 +60,7 @@ internal class UserHandler( override fun afterObjectHandled(o: User, action: HandleAction) { userRoleCollectionCleaner.deleteNotPresent(o.userRoles()) userRoleHandler.handleMany(o.userRoles()) + userGroupCollectionCleaner.deleteNotPresent(o.userGroups()) + userGroupHandler.handleMany(o.userGroups()) } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserModuleImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserModuleImpl.kt index 6189a705e7..7c3cd7deab 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserModuleImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserModuleImpl.kt @@ -43,6 +43,7 @@ internal class UserModuleImpl( private val logInCall: LogInCall, private val authenticatedUser: AuthenticatedUserObjectRepository, private val userRoles: UserRoleCollectionRepository, + private val userGroups: UserGroupCollectionRepository, private val authorities: AuthorityCollectionRepository, private val userCredentials: UserCredentialsObjectRepository, private val user: UserObjectRepository, @@ -58,6 +59,10 @@ internal class UserModuleImpl( return userRoles } + override fun userGroups(): UserGroupCollectionRepository { + return userGroups + } + override fun authorities(): AuthorityCollectionRepository { return authorities } diff --git a/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserModuleWiper.kt b/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserModuleWiper.kt index 33e21fe9b9..63d768eb29 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserModuleWiper.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/user/internal/UserModuleWiper.kt @@ -29,6 +29,7 @@ package org.hisp.dhis.android.core.user.internal import org.hisp.dhis.android.core.user.AuthenticatedUserTableInfo import org.hisp.dhis.android.core.user.AuthorityTableInfo +import org.hisp.dhis.android.core.user.UserGroupTableInfo import org.hisp.dhis.android.core.user.UserOrganisationUnitLinkTableInfo import org.hisp.dhis.android.core.user.UserRoleTableInfo import org.hisp.dhis.android.core.user.UserTableInfo @@ -47,6 +48,7 @@ internal class UserModuleWiper( AuthenticatedUserTableInfo.TABLE_INFO, AuthorityTableInfo.TABLE_INFO, UserRoleTableInfo.TABLE_INFO, + UserGroupTableInfo.TABLE_INFO, ) } diff --git a/core/src/main/java/org/hisp/dhis/android/core/util/CipherUtil.kt b/core/src/main/java/org/hisp/dhis/android/core/util/CipherUtil.kt new file mode 100644 index 0000000000..d951b707f4 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/util/CipherUtil.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.util + +import net.lingala.zip4j.ZipFile +import net.lingala.zip4j.model.ZipParameters +import net.lingala.zip4j.model.enums.AesKeyStrength +import net.lingala.zip4j.model.enums.EncryptionMethod +import org.hisp.dhis.android.core.maintenance.D2Error +import org.hisp.dhis.android.core.maintenance.D2ErrorCode +import org.hisp.dhis.android.core.maintenance.D2ErrorComponent +import java.io.File + +internal object CipherUtil { + fun createEncryptedZipFile(input: File, output: File, password: String) { + val zipParameters = ZipParameters() + zipParameters.isEncryptFiles = true + zipParameters.encryptionMethod = EncryptionMethod.AES + zipParameters.aesKeyStrength = AesKeyStrength.KEY_STRENGTH_256 + + val filesToAdd = listOf(input) + + val zipFile = ZipFile(output, password.toCharArray()) + zipFile.addFiles(filesToAdd, zipParameters) + } + + fun extractEncryptedZipFile(input: File, output: File, password: String) { + val zipFile = ZipFile(input, password.toCharArray()) + val allFiles = zipFile.fileHeaders + + if (allFiles.size == 1) { + val targetFile = allFiles.first() + zipFile.extractFile(targetFile, output.parent, output.name) + } else { + throw D2Error.builder() + .errorComponent(D2ErrorComponent.SDK) + .errorDescription("Database zip file must contain a single file") + .errorCode(D2ErrorCode.DATABASE_IMPORT_INVALID_FILE) + .build() + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/util/CollectionExtensions.kt b/core/src/main/java/org/hisp/dhis/android/core/util/CollectionExtensions.kt new file mode 100644 index 0000000000..49423ea57d --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/util/CollectionExtensions.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.util + +internal fun Collection.replace(index: Int, element: E): List { + return this.mapIndexed { i, item -> if (i == index) element else item } +} + +internal fun Collection.replaceOrPush(element: E, predicate: (E) -> Boolean): List { + val itemIndex = this.indexOfFirst(predicate) + + return if (itemIndex >= 0) { + this.replace(itemIndex, element) + } else { + this + element + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/util/FileExtensions.kt b/core/src/main/java/org/hisp/dhis/android/core/util/FileExtensions.kt new file mode 100644 index 0000000000..ecf02895fa --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/util/FileExtensions.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.util + +import org.hisp.dhis.android.core.maintenance.D2Error +import org.hisp.dhis.android.core.maintenance.D2ErrorCode +import org.hisp.dhis.android.core.maintenance.D2ErrorComponent +import java.io.File + +internal fun File.deleteIfExists() { + if (exists()) { + val deleted = delete() + if (!deleted) { + throw D2Error.builder() + .errorComponent(D2ErrorComponent.SDK) + .errorDescription("File $path exists and cannot be deleted") + .errorCode(D2ErrorCode.UNEXPECTED) + .build() + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/util/FileUtils.kt b/core/src/main/java/org/hisp/dhis/android/core/util/FileUtils.kt new file mode 100644 index 0000000000..f53a37cadd --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/util/FileUtils.kt @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.util + +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipFile +import java.util.zip.ZipInputStream +import java.util.zip.ZipOutputStream + +@Suppress("NestedBlockDepth") +internal object FileUtils { + + private const val bufferSize = 512 + + @Suppress("TooGenericExceptionCaught") + fun zipFiles(files: List, zipFile: File) { + val buffer = ByteArray(bufferSize) + + try { + val fos = FileOutputStream(zipFile.path) + val zos = ZipOutputStream(fos) + + files.forEach { file -> + val ze = ZipEntry(file.name) + zos.putNextEntry(ze) + val inStream = FileInputStream(file) + while (true) { + val len = inStream.read(buffer) + if (len <= 0) break + zos.write(buffer, 0, len) + } + + zos.closeEntry() + inStream.close() + } + + zos.close() + fos.close() + } catch (e: Exception) { + e.printStackTrace() + } + } + + fun unzipFiles(zipFile: File, unzipDirectory: File) { + val buffer = ByteArray(bufferSize) + + val zip = ZipFile(zipFile) + val enum = zip.entries() + while (enum.hasMoreElements()) { + val entry = enum.nextElement() + val entryName = entry.name + val fis = FileInputStream(zip.name) + val zis = ZipInputStream(fis) + + while (true) { + val nextEntry = zis.nextEntry ?: break + if (nextEntry.name == entryName) { + val fout = FileOutputStream(File(unzipDirectory, nextEntry.name)) + while (true) { + val len = zis.read(buffer) + if (len <= 0) break + fout.write(buffer, 0, len) + } + + zis.closeEntry() + fout.close() + } + } + + zis.close() + fis.close() + } + zip.close() + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/util/SqlUtils.kt b/core/src/main/java/org/hisp/dhis/android/core/util/SqlUtils.kt new file mode 100644 index 0000000000..a87825a924 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/util/SqlUtils.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.util + +import org.hisp.dhis.android.core.common.ValueType + +internal object SqlUtils { + fun getColumnValueCast( + column: String, + valueType: ValueType?, + ): String { + return when { + valueType?.isNumeric == true -> + "CAST($column AS NUMERIC)" + valueType?.isBoolean == true -> + "CASE WHEN $column = 'true' THEN 1 ELSE 0 END" + else -> + column + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/validation/engine/internal/ValidationExecutor.kt b/core/src/main/java/org/hisp/dhis/android/core/validation/engine/internal/ValidationExecutor.kt index 771f42db28..73e3cf4906 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/validation/engine/internal/ValidationExecutor.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/validation/engine/internal/ValidationExecutor.kt @@ -37,6 +37,7 @@ import org.hisp.dhis.android.core.validation.ValidationRuleExpression import org.hisp.dhis.android.core.validation.ValidationRuleOperator import org.hisp.dhis.android.core.validation.engine.ValidationResultSideEvaluation import org.hisp.dhis.android.core.validation.engine.ValidationResultViolation +import org.hisp.dhis.antlr.ParserException import org.koin.core.annotation.Singleton @Singleton @@ -51,34 +52,39 @@ internal class ValidationExecutor(private val expressionService: ExpressionServi return if (shouldSkipOrgunitLevel(rule, organisationUnit)) { null } else { - val leftSideValue = expressionService.getExpressionValue( - rule.leftSide().expression(), - context, - rule.leftSide().missingValueStrategy(), - ) as Double? - val rightSideValue = expressionService.getExpressionValue( - rule.rightSide().expression(), - context, - rule.rightSide().missingValueStrategy(), - ) as Double? + try { + val leftSideValue = evaluateSide(rule.leftSide(), context) + val rightSideValue = evaluateSide(rule.rightSide(), context) - if (isViolation(rule, leftSideValue, rightSideValue)) { - val leftSide = buildSideResult(leftSideValue, rule.leftSide(), context) - val rightSide = buildSideResult(rightSideValue, rule.rightSide(), context) - ValidationResultViolation.builder() - .period(period.periodId()) - .organisationUnitUid(organisationUnit!!.uid()) - .attributeOptionComboUid(attributeOptionComboId) - .validationRule(rule) - .leftSideEvaluation(leftSide) - .rightSideEvaluation(rightSide) - .build() - } else { + if (isViolation(rule, leftSideValue, rightSideValue)) { + val leftSide = buildSideResult(leftSideValue, rule.leftSide(), context) + val rightSide = buildSideResult(rightSideValue, rule.rightSide(), context) + ValidationResultViolation.builder() + .period(period.periodId()) + .organisationUnitUid(organisationUnit!!.uid()) + .attributeOptionComboUid(attributeOptionComboId) + .validationRule(rule) + .leftSideEvaluation(leftSide) + .rightSideEvaluation(rightSide) + .build() + } else { + null + } + } catch (e: ParserException) { null } } } + private fun evaluateSide(side: ValidationRuleExpression, context: ExpressionServiceContext): Double? { + return expressionService.getExpressionValue( + expression = side.expression(), + context = context, + missingValueStrategy = side.missingValueStrategy(), + ignoreParseErrors = false, + ) as Double? + } + private fun isViolation(rule: ValidationRule, leftSide: Double?, rightSide: Double?): Boolean { return when (rule.operator()) { ValidationRuleOperator.compulsory_pair -> leftSide == null != (rightSide == null) diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/CategoryDimension.java b/core/src/main/java/org/hisp/dhis/android/core/visualization/CategoryDimension.java index ce758338b8..5c8101d46b 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/visualization/CategoryDimension.java +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/CategoryDimension.java @@ -59,7 +59,7 @@ public static Builder builder() { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder { + public abstract static class Builder { public abstract Builder category(ObjectWithUid category); diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualization.java b/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualization.java new file mode 100644 index 0000000000..1b0b416c8a --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualization.java @@ -0,0 +1,134 @@ +/* + * 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.visualization; + +import android.database.Cursor; + +import androidx.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.gabrielittner.auto.value.cursor.ColumnAdapter; +import com.google.auto.value.AutoValue; + +import org.hisp.dhis.android.core.arch.db.adapters.enums.internal.TrackerVisualizationOutputTypeColumnAdapter; +import org.hisp.dhis.android.core.arch.db.adapters.enums.internal.TrackerVisualizationTypeColumnAdapter; +import org.hisp.dhis.android.core.arch.db.adapters.identifiable.internal.ObjectWithUidColumnAdapter; +import org.hisp.dhis.android.core.arch.db.adapters.ignore.internal.IgnoreTrackerVisualizationDimensionListColumnAdapter; +import org.hisp.dhis.android.core.common.BaseIdentifiableObject; +import org.hisp.dhis.android.core.common.CoreObject; +import org.hisp.dhis.android.core.common.ObjectWithUid; + +import java.util.List; + +@AutoValue +@JsonDeserialize(builder = $$AutoValue_TrackerVisualization.Builder.class) +public abstract class TrackerVisualization extends BaseIdentifiableObject implements CoreObject { + + @Nullable + @JsonProperty() + public abstract String description(); + + @Nullable + @JsonProperty() + public abstract String displayDescription(); + + @Nullable + @JsonProperty() + @ColumnAdapter(TrackerVisualizationTypeColumnAdapter.class) + public abstract TrackerVisualizationType type(); + + @Nullable + @JsonProperty() + @ColumnAdapter(TrackerVisualizationOutputTypeColumnAdapter.class) + public abstract TrackerVisualizationOutputType outputType(); + + @Nullable + @JsonProperty + @ColumnAdapter(ObjectWithUidColumnAdapter.class) + public abstract ObjectWithUid program(); + + @Nullable + @JsonProperty + @ColumnAdapter(ObjectWithUidColumnAdapter.class) + public abstract ObjectWithUid programStage(); + + @Nullable + @JsonProperty + @ColumnAdapter(ObjectWithUidColumnAdapter.class) + public abstract ObjectWithUid trackedEntityType(); + + @Nullable + @JsonProperty() + @ColumnAdapter(IgnoreTrackerVisualizationDimensionListColumnAdapter.class) + public abstract List columns(); + + @Nullable + @JsonProperty() + @ColumnAdapter(IgnoreTrackerVisualizationDimensionListColumnAdapter.class) + public abstract List filters(); + + public static Builder builder() { + return new $$AutoValue_TrackerVisualization.Builder(); + } + + public static TrackerVisualization create(Cursor cursor) { + return $AutoValue_TrackerVisualization.createFromCursor(cursor); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "") + public abstract static class Builder extends BaseIdentifiableObject.Builder { + + public abstract Builder id(Long id); + + public abstract Builder description(String description); + + public abstract Builder displayDescription(String displayDescription); + + public abstract Builder type(TrackerVisualizationType type); + + public abstract Builder outputType(TrackerVisualizationOutputType type); + + public abstract Builder program(ObjectWithUid program); + + public abstract Builder programStage(ObjectWithUid programStage); + + public abstract Builder trackedEntityType(ObjectWithUid trackedEntityType); + + public abstract Builder columns(List columns); + + public abstract Builder filters(List filters); + + public abstract TrackerVisualization build(); + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationCollectionRepository.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationCollectionRepository.kt new file mode 100644 index 0000000000..21fa8c6bc6 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationCollectionRepository.kt @@ -0,0 +1,100 @@ +/* + * 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.visualization + +import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.arch.repositories.children.internal.ChildrenAppenderGetter +import org.hisp.dhis.android.core.arch.repositories.collection.internal.ReadOnlyIdentifiableCollectionRepositoryImpl +import org.hisp.dhis.android.core.arch.repositories.filters.internal.EnumFilterConnector +import org.hisp.dhis.android.core.arch.repositories.filters.internal.FilterConnectorFactory +import org.hisp.dhis.android.core.arch.repositories.filters.internal.StringFilterConnector +import org.hisp.dhis.android.core.arch.repositories.scope.RepositoryScope +import org.hisp.dhis.android.core.visualization.internal.TrackerVisualizationColumnsFiltersChildrenAppender +import org.hisp.dhis.android.core.visualization.internal.TrackerVisualizationFields +import org.hisp.dhis.android.core.visualization.internal.TrackerVisualizationStore +import org.hisp.dhis.android.core.visualization.internal.VisualizationFields +import org.koin.core.annotation.Singleton + +@Singleton +@Suppress("TooManyFunctions") +class TrackerVisualizationCollectionRepository internal constructor( + store: TrackerVisualizationStore, + databaseAdapter: DatabaseAdapter, + scope: RepositoryScope, +) : ReadOnlyIdentifiableCollectionRepositoryImpl( + store, + databaseAdapter, + childrenAppenders, + scope, + FilterConnectorFactory( + scope, + ) { s: RepositoryScope -> + TrackerVisualizationCollectionRepository( + store, + databaseAdapter, + s, + ) + }, +) { + fun byDescription(): StringFilterConnector { + return cf.string(TrackerVisualizationTableInfo.Columns.DESCRIPTION) + } + + fun byDisplayDescription(): StringFilterConnector { + return cf.string(TrackerVisualizationTableInfo.Columns.DISPLAY_DESCRIPTION) + } + + fun byType(): EnumFilterConnector { + return cf.enumC(TrackerVisualizationTableInfo.Columns.TYPE) + } + + fun byOutputType(): EnumFilterConnector { + return cf.enumC(TrackerVisualizationTableInfo.Columns.OUTPUT_TYPE) + } + + fun byProgram(): StringFilterConnector { + return cf.string(TrackerVisualizationTableInfo.Columns.PROGRAM) + } + + fun byProgramStage(): StringFilterConnector { + return cf.string(TrackerVisualizationTableInfo.Columns.PROGRAM_STAGE) + } + + fun byTrackedEntityType(): StringFilterConnector { + return cf.string(TrackerVisualizationTableInfo.Columns.TRACKED_ENTITY_TYPE) + } + fun withColumnsAndFilters(): TrackerVisualizationCollectionRepository { + return cf.withChild(VisualizationFields.ITEMS) + } + + internal companion object { + val childrenAppenders: ChildrenAppenderGetter = mapOf( + TrackerVisualizationFields.ITEMS to TrackerVisualizationColumnsFiltersChildrenAppender::create, + ) + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationDimension.java b/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationDimension.java new file mode 100644 index 0000000000..7557ca128b --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationDimension.java @@ -0,0 +1,130 @@ +/* + * 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.visualization; + +import android.database.Cursor; + +import androidx.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.gabrielittner.auto.value.cursor.ColumnAdapter; +import com.google.auto.value.AutoValue; + +import org.hisp.dhis.android.core.arch.db.adapters.custom.internal.TrackerVisualizationDimensionRepetitionColumnAdapter; +import org.hisp.dhis.android.core.arch.db.adapters.enums.internal.LayoutPositionColumnAdapter; +import org.hisp.dhis.android.core.arch.db.adapters.identifiable.internal.ObjectWithUidColumnAdapter; +import org.hisp.dhis.android.core.arch.db.adapters.identifiable.internal.ObjectWithUidListColumnAdapter; +import org.hisp.dhis.android.core.common.CoreObject; +import org.hisp.dhis.android.core.common.ObjectWithUid; + +import java.util.List; + +@AutoValue +@JsonDeserialize(builder = AutoValue_TrackerVisualizationDimension.Builder.class) +public abstract class TrackerVisualizationDimension implements CoreObject { + + @Nullable + public abstract String trackerVisualization(); + + @Nullable + @ColumnAdapter(LayoutPositionColumnAdapter.class) + public abstract LayoutPosition position(); + + @Nullable + @JsonProperty() + public abstract String dimension(); + + @Nullable + @JsonProperty() + public abstract String dimensionType(); + + @Nullable + @JsonProperty() + @ColumnAdapter(ObjectWithUidColumnAdapter.class) + public abstract ObjectWithUid program(); + + @Nullable + @JsonProperty() + @ColumnAdapter(ObjectWithUidColumnAdapter.class) + public abstract ObjectWithUid programStage(); + + @Nullable + @JsonProperty() + @ColumnAdapter(ObjectWithUidListColumnAdapter.class) + public abstract List items(); + + @Nullable + @JsonProperty() + public abstract String filter(); + + @Nullable + @JsonProperty() + @ColumnAdapter(TrackerVisualizationDimensionRepetitionColumnAdapter.class) + public abstract TrackerVisualizationDimensionRepetition repetition(); + + + public static Builder builder() { + return new AutoValue_TrackerVisualizationDimension.Builder(); + } + + public static TrackerVisualizationDimension create(Cursor cursor) { + return $AutoValue_TrackerVisualizationDimension.createFromCursor(cursor); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "") + public abstract static class Builder { + + public abstract Builder id(Long id); + + public abstract Builder trackerVisualization(String trackerVisualization); + + public abstract Builder position(LayoutPosition position); + + public abstract Builder dimension(String dimension); + + public abstract Builder dimensionType(String dimensionType); + + public abstract Builder program(ObjectWithUid program); + + public abstract Builder programStage(ObjectWithUid programStage); + + public abstract Builder items(List items); + + public abstract Builder filter(String filter); + + public abstract Builder repetition(TrackerVisualizationDimensionRepetition repetition); + + public abstract TrackerVisualizationDimension build(); + } +} \ No newline at end of file diff --git a/core/src/sharedTest/java/org/hisp/dhis/android/core/data/relationship/RelationshipConstraintSamples.java b/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationDimensionRepetition.java similarity index 61% rename from core/src/sharedTest/java/org/hisp/dhis/android/core/data/relationship/RelationshipConstraintSamples.java rename to core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationDimensionRepetition.java index 8331c9304e..7ca98f2e42 100644 --- a/core/src/sharedTest/java/org/hisp/dhis/android/core/data/relationship/RelationshipConstraintSamples.java +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationDimensionRepetition.java @@ -26,25 +26,37 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.hisp.dhis.android.core.data.relationship; - -import org.hisp.dhis.android.core.common.ObjectWithUid; -import org.hisp.dhis.android.core.relationship.RelationshipConstraint; -import org.hisp.dhis.android.core.relationship.RelationshipConstraintType; -import org.hisp.dhis.android.core.relationship.RelationshipEntityType; - - -public class RelationshipConstraintSamples { - - public static RelationshipConstraint getRelationshipConstraint() { - return RelationshipConstraint.builder() - .id(1L) - .relationshipType(ObjectWithUid.create("relationship_type_uid")) - .constraintType(RelationshipConstraintType.FROM) - .relationshipEntity(RelationshipEntityType.TRACKED_ENTITY_INSTANCE) - .trackedEntityType(ObjectWithUid.create("tracked_entity_type_uid")) - .program(ObjectWithUid.create("program_uid")) - .programStage(ObjectWithUid.create("program_stage_uid")) - .build(); +package org.hisp.dhis.android.core.visualization; + +import androidx.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; + +import java.util.List; + +@AutoValue +@JsonDeserialize(builder = AutoValue_TrackerVisualizationDimensionRepetition.Builder.class) +public abstract class TrackerVisualizationDimensionRepetition { + + @Nullable + @JsonProperty() + public abstract List indexes(); + + public static Builder builder() { + return new AutoValue_TrackerVisualizationDimensionRepetition.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "") + public abstract static class Builder { + + public abstract Builder indexes(List indexes); + + public abstract TrackerVisualizationDimensionRepetition build(); } } \ No newline at end of file diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationDimensionTableInfo.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationDimensionTableInfo.kt new file mode 100644 index 0000000000..f1172a1261 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationDimensionTableInfo.kt @@ -0,0 +1,86 @@ +/* + * 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.visualization + +import org.hisp.dhis.android.core.arch.db.tableinfos.TableInfo +import org.hisp.dhis.android.core.arch.helpers.CollectionsHelper +import org.hisp.dhis.android.core.common.CoreColumns + +object TrackerVisualizationDimensionTableInfo { + + @JvmField + val TABLE_INFO: TableInfo = object : TableInfo() { + override fun name(): String { + return "TrackerVisualizationDimension" + } + + override fun columns(): CoreColumns { + return Columns() + } + } + + class Columns : CoreColumns() { + override fun all(): Array { + return CollectionsHelper.appendInNewArray( + super.all(), + TRACKER_VISUALIZATION, + POSITION, + DIMENSION, + DIMENSION_TYPE, + PROGRAM, + PROGRAM_STAGE, + ITEMS, + FILTER, + REPETITION, + ) + } + + override fun whereUpdate(): Array { + return CollectionsHelper.appendInNewArray( + super.all(), + TRACKER_VISUALIZATION, + POSITION, + DIMENSION, + DIMENSION_TYPE, + ) + } + + companion object { + const val TRACKER_VISUALIZATION = "trackerVisualization" + const val POSITION = "position" + const val DIMENSION = "dimension" + const val DIMENSION_TYPE = "dimensionType" + const val PROGRAM = "program" + const val PROGRAM_STAGE = "programStage" + const val ITEMS = "items" + const val FILTER = "filter" + const val REPETITION = "repetition" + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationOutputType.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationOutputType.kt new file mode 100644 index 0000000000..ec0ed8f92d --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationOutputType.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.visualization + +enum class TrackerVisualizationOutputType { + EVENT, + ENROLLMENT, + TRACKED_ENTITY_INSTANCE, +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationTableInfo.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationTableInfo.kt new file mode 100644 index 0000000000..f80ffef08b --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationTableInfo.kt @@ -0,0 +1,73 @@ +/* + * 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.visualization + +import org.hisp.dhis.android.core.arch.db.tableinfos.TableInfo +import org.hisp.dhis.android.core.arch.helpers.CollectionsHelper +import org.hisp.dhis.android.core.common.CoreColumns +import org.hisp.dhis.android.core.common.IdentifiableColumns + +object TrackerVisualizationTableInfo { + + @JvmField + val TABLE_INFO: TableInfo = object : TableInfo() { + override fun name(): String { + return "TrackerVisualization" + } + + override fun columns(): CoreColumns { + return Columns() + } + } + + class Columns : IdentifiableColumns() { + override fun all(): Array { + return CollectionsHelper.appendInNewArray( + super.all(), + DESCRIPTION, + DISPLAY_DESCRIPTION, + TYPE, + OUTPUT_TYPE, + PROGRAM, + PROGRAM_STAGE, + TRACKED_ENTITY_TYPE, + ) + } + + companion object { + const val DESCRIPTION = "description" + const val DISPLAY_DESCRIPTION = "displayDescription" + const val TYPE = "type" + const val OUTPUT_TYPE = "outputType" + const val PROGRAM = "program" + const val PROGRAM_STAGE = "programStage" + const val TRACKED_ENTITY_TYPE = "trackedEntityType" + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationType.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationType.kt new file mode 100644 index 0000000000..f4a3c7b431 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationType.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.visualization + +enum class TrackerVisualizationType { + COLUMN, + STACKED_COLUMN, + BAR, + STACKED_BAR, + LINE, + LINE_LIST, + AREA, + STACKED_AREA, + PIE, + RADAR, + GAUGE, + YEAR_OVER_YEAR_LINE, + YEAR_OVER_YEAR_COLUMN, + SINGLE_VALUE, + PIVOT_TABLE, + SCATTER, + BUBBLE, +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/Visualization.java b/core/src/main/java/org/hisp/dhis/android/core/visualization/Visualization.java index 6b43b1a44e..3879335b9e 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/visualization/Visualization.java +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/Visualization.java @@ -197,7 +197,7 @@ public static Visualization create(Cursor cursor) { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "") - public static abstract class Builder extends BaseIdentifiableObject.Builder { + public abstract static class Builder extends BaseIdentifiableObject.Builder { public abstract Builder id(Long id); diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/VisualizationCategoryDimensionLink.java b/core/src/main/java/org/hisp/dhis/android/core/visualization/VisualizationCategoryDimensionLink.java index 91f521c19e..c34c39cc32 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/visualization/VisualizationCategoryDimensionLink.java +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/VisualizationCategoryDimensionLink.java @@ -61,7 +61,7 @@ public static VisualizationCategoryDimensionLink create(Cursor cursor) { } @AutoValue.Builder - public static abstract class Builder extends BaseObject.Builder { + public abstract static class Builder extends BaseObject.Builder { public abstract Builder id(Long id); public abstract Builder visualization(String visualization); diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/VisualizationModule.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/VisualizationModule.kt index 06627c886b..1a70429432 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/visualization/VisualizationModule.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/VisualizationModule.kt @@ -29,4 +29,6 @@ package org.hisp.dhis.android.core.visualization interface VisualizationModule { fun visualizations(): VisualizationCollectionRepository + + fun trackerVisualizations(): TrackerVisualizationCollectionRepository } diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationCall.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationCall.kt new file mode 100644 index 0000000000..a6c7ae9101 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationCall.kt @@ -0,0 +1,77 @@ +/* + * 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.visualization.internal + +import org.hisp.dhis.android.core.arch.api.executors.internal.APIDownloader +import org.hisp.dhis.android.core.arch.api.payload.internal.Payload +import org.hisp.dhis.android.core.arch.call.factories.internal.UidsCallCoroutines +import org.hisp.dhis.android.core.common.internal.AccessFields +import org.hisp.dhis.android.core.systeminfo.DHISVersion +import org.hisp.dhis.android.core.systeminfo.DHISVersionManager +import org.hisp.dhis.android.core.visualization.TrackerVisualization +import org.koin.core.annotation.Singleton + +@Singleton +internal class TrackerVisualizationCall( + private val handler: TrackerVisualizationHandler, + private val service: TrackerVisualizationService, + private val dhis2VersionManager: DHISVersionManager, + private val apiDownloader: APIDownloader, +) : UidsCallCoroutines { + + companion object { + // Workaround for DHIS2-16746. Force queries to entity endpoint instead of list endpoint. + private const val MAX_UID_LIST_SIZE = 1 + } + + override suspend fun download(uids: Set): List { + val accessFilter = "access." + AccessFields.read.eq(true).generateString() + + return if (dhis2VersionManager.isGreaterOrEqualThan(DHISVersion.V2_38)) { + apiDownloader.downloadPartitioned( + uids, + MAX_UID_LIST_SIZE, + handler, + ) { partitionUids: Set -> + try { + val visualization = service.getSingleTrackerVisualization( + partitionUids.first(), + TrackerVisualizationFields.allFields, + accessFilter = accessFilter, + paging = false, + ) + Payload(listOf(visualization)) + } catch (ignored: Exception) { + Payload() + } + } + } else { + emptyList() + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationCollectionCleaner.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationCollectionCleaner.kt new file mode 100644 index 0000000000..f9ce80f3c6 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationCollectionCleaner.kt @@ -0,0 +1,42 @@ +/* + * 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.visualization.internal + +import org.hisp.dhis.android.core.arch.cleaners.internal.CollectionCleanerImpl +import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.visualization.TrackerVisualization +import org.hisp.dhis.android.core.visualization.TrackerVisualizationTableInfo +import org.koin.core.annotation.Singleton + +@Singleton +internal class TrackerVisualizationCollectionCleaner( + databaseAdapter: DatabaseAdapter, +) : CollectionCleanerImpl( + tableName = TrackerVisualizationTableInfo.TABLE_INFO.name(), + databaseAdapter = databaseAdapter, +) diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationColumnsFiltersChildrenAppender.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationColumnsFiltersChildrenAppender.kt new file mode 100644 index 0000000000..f9b0590e61 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationColumnsFiltersChildrenAppender.kt @@ -0,0 +1,70 @@ +/* + * 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.visualization.internal + +import android.database.Cursor +import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.arch.db.stores.internal.SingleParentChildStore +import org.hisp.dhis.android.core.arch.db.stores.internal.StoreFactory.singleParentChildStore +import org.hisp.dhis.android.core.arch.db.stores.projections.internal.SingleParentChildProjection +import org.hisp.dhis.android.core.arch.repositories.children.internal.ChildrenAppender +import org.hisp.dhis.android.core.visualization.LayoutPosition +import org.hisp.dhis.android.core.visualization.TrackerVisualization +import org.hisp.dhis.android.core.visualization.TrackerVisualizationDimension +import org.hisp.dhis.android.core.visualization.TrackerVisualizationDimensionTableInfo + +internal class TrackerVisualizationColumnsFiltersChildrenAppender private constructor( + private val linkChildStore: SingleParentChildStore, +) : ChildrenAppender() { + override fun appendChildren(m: TrackerVisualization): TrackerVisualization { + val items = linkChildStore.getChildren(m) + val groupedByPosition = items + .groupBy { it.position() } + + return m.toBuilder() + .columns(groupedByPosition[LayoutPosition.COLUMN] ?: emptyList()) + .filters(groupedByPosition[LayoutPosition.FILTER] ?: emptyList()) + .build() + } + + companion object { + private val CHILD_PROJECTION = SingleParentChildProjection( + TrackerVisualizationDimensionTableInfo.TABLE_INFO, + TrackerVisualizationDimensionTableInfo.Columns.TRACKER_VISUALIZATION, + ) + + fun create(databaseAdapter: DatabaseAdapter): ChildrenAppender { + return TrackerVisualizationColumnsFiltersChildrenAppender( + singleParentChildStore( + databaseAdapter, + CHILD_PROJECTION, + ) { cursor: Cursor -> TrackerVisualizationDimension.create(cursor) }, + ) + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationDimensionFields.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationDimensionFields.kt new file mode 100644 index 0000000000..512eca9515 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationDimensionFields.kt @@ -0,0 +1,54 @@ +/* + * 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.visualization.internal + +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper +import org.hisp.dhis.android.core.visualization.TrackerVisualizationDimension +import org.hisp.dhis.android.core.visualization.TrackerVisualizationDimensionRepetition +import org.hisp.dhis.android.core.visualization.TrackerVisualizationDimensionTableInfo + +internal object TrackerVisualizationDimensionFields { + private val fh = FieldsHelper() + + val allFields: Fields = + Fields.builder() + .fields( + fh.field(TrackerVisualizationDimensionTableInfo.Columns.DIMENSION), + fh.field(TrackerVisualizationDimensionTableInfo.Columns.DIMENSION_TYPE), + fh.field(TrackerVisualizationDimensionTableInfo.Columns.PROGRAM), + fh.field(TrackerVisualizationDimensionTableInfo.Columns.PROGRAM_STAGE), + fh.nestedFieldWithUid(TrackerVisualizationDimensionTableInfo.Columns.ITEMS), + fh.field(TrackerVisualizationDimensionTableInfo.Columns.FILTER), + fh.nestedField( + TrackerVisualizationDimensionTableInfo.Columns.REPETITION, + ) + .with(TrackerVisualizationDimensionRepetitionFields.allFields), + ) + .build() +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationDimensionHandler.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationDimensionHandler.kt new file mode 100644 index 0000000000..cc8d979962 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationDimensionHandler.kt @@ -0,0 +1,38 @@ +/* + * 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.visualization.internal + +import org.hisp.dhis.android.core.arch.handlers.internal.LinkHandlerImpl +import org.hisp.dhis.android.core.visualization.TrackerVisualizationDimension +import org.koin.core.annotation.Singleton + +@Singleton +internal class TrackerVisualizationDimensionHandler( + store: TrackerVisualizationDimensionStore, +) : LinkHandlerImpl(store) diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationDimensionRepetitionFields.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationDimensionRepetitionFields.kt new file mode 100644 index 0000000000..e1cd496319 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationDimensionRepetitionFields.kt @@ -0,0 +1,45 @@ +/* + * 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.visualization.internal + +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper +import org.hisp.dhis.android.core.visualization.TrackerVisualizationDimensionRepetition + +internal object TrackerVisualizationDimensionRepetitionFields { + private const val INDEXES = "indexes" + + private val fh = FieldsHelper() + + val allFields: Fields = + Fields.builder() + .fields( + fh.field(INDEXES), + ) + .build() +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationDimensionStore.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationDimensionStore.kt new file mode 100644 index 0000000000..3f3ae075b2 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationDimensionStore.kt @@ -0,0 +1,34 @@ +/* + * 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.visualization.internal + +import org.hisp.dhis.android.core.arch.db.stores.internal.LinkStore +import org.hisp.dhis.android.core.visualization.TrackerVisualizationDimension + +internal interface TrackerVisualizationDimensionStore : LinkStore diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationDimensionStoreImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationDimensionStoreImpl.kt new file mode 100644 index 0000000000..d3a329a089 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationDimensionStoreImpl.kt @@ -0,0 +1,66 @@ +/* + * 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.visualization.internal + +import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.arch.db.adapters.custom.internal.TrackerVisualizationDimensionRepetitionColumnAdapter +import org.hisp.dhis.android.core.arch.db.adapters.identifiable.internal.ObjectWithUidListColumnAdapter +import org.hisp.dhis.android.core.arch.db.stores.binders.internal.StatementBinder +import org.hisp.dhis.android.core.arch.db.stores.binders.internal.StatementWrapper +import org.hisp.dhis.android.core.arch.db.stores.internal.LinkStoreImpl +import org.hisp.dhis.android.core.arch.helpers.UidsHelper +import org.hisp.dhis.android.core.visualization.TrackerVisualizationDimension +import org.hisp.dhis.android.core.visualization.TrackerVisualizationDimensionTableInfo +import org.koin.core.annotation.Singleton + +@Singleton +@Suppress("MagicNumber") +internal class TrackerVisualizationDimensionStoreImpl( + databaseAdapter: DatabaseAdapter, +) : TrackerVisualizationDimensionStore, + LinkStoreImpl( + databaseAdapter, + TrackerVisualizationDimensionTableInfo.TABLE_INFO, + TrackerVisualizationDimensionTableInfo.Columns.TRACKER_VISUALIZATION, + BINDER, + { TrackerVisualizationDimension.create(it) }, + ) { + companion object { + private val BINDER = StatementBinder { o: TrackerVisualizationDimension, w: StatementWrapper -> + w.bind(1, o.trackerVisualization()) + w.bind(2, o.position()) + w.bind(3, o.dimension()) + w.bind(4, o.dimensionType()) + w.bind(5, UidsHelper.getUidOrNull(o.program())) + w.bind(6, UidsHelper.getUidOrNull(o.programStage())) + w.bind(7, ObjectWithUidListColumnAdapter.serialize(o.items())) + w.bind(8, o.filter()) + w.bind(9, TrackerVisualizationDimensionRepetitionColumnAdapter.serialize(o.repetition())) + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationFields.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationFields.kt new file mode 100644 index 0000000000..c66c6a55b5 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationFields.kt @@ -0,0 +1,62 @@ +/* + * 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.visualization.internal + +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.arch.fields.internal.FieldsHelper +import org.hisp.dhis.android.core.visualization.TrackerVisualization +import org.hisp.dhis.android.core.visualization.TrackerVisualizationDimension +import org.hisp.dhis.android.core.visualization.TrackerVisualizationTableInfo + +internal object TrackerVisualizationFields { + private const val COLUMNS = "columns" + private const val FILTERS = "filters" + + internal const val ITEMS = "items" + + private val fh = FieldsHelper() + val uid = fh.uid() + + val allFields: Fields = + Fields.builder() + .fields(fh.getIdentifiableFields()) + .fields( + fh.field(TrackerVisualizationTableInfo.Columns.DESCRIPTION), + fh.field(TrackerVisualizationTableInfo.Columns.DISPLAY_DESCRIPTION), + fh.field(TrackerVisualizationTableInfo.Columns.TYPE), + fh.field(TrackerVisualizationTableInfo.Columns.OUTPUT_TYPE), + fh.nestedFieldWithUid(TrackerVisualizationTableInfo.Columns.PROGRAM), + fh.nestedFieldWithUid(TrackerVisualizationTableInfo.Columns.PROGRAM_STAGE), + fh.nestedFieldWithUid(TrackerVisualizationTableInfo.Columns.TRACKED_ENTITY_TYPE), + fh.nestedField(COLUMNS) + .with(TrackerVisualizationDimensionFields.allFields), + fh.nestedField(FILTERS) + .with(TrackerVisualizationDimensionFields.allFields), + ) + .build() +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationHandler.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationHandler.kt new file mode 100644 index 0000000000..65db11ccd8 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationHandler.kt @@ -0,0 +1,75 @@ +/* + * 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.visualization.internal + +import org.hisp.dhis.android.core.arch.handlers.internal.HandleAction +import org.hisp.dhis.android.core.arch.handlers.internal.IdentifiableHandlerImpl +import org.hisp.dhis.android.core.settings.AnalyticsDhisVisualizationType +import org.hisp.dhis.android.core.settings.internal.AnalyticsDhisVisualizationCleaner +import org.hisp.dhis.android.core.visualization.LayoutPosition +import org.hisp.dhis.android.core.visualization.TrackerVisualization +import org.hisp.dhis.android.core.visualization.TrackerVisualizationDimension +import org.koin.core.annotation.Singleton + +@Singleton +internal class TrackerVisualizationHandler( + store: TrackerVisualizationStore, + private val trackerVisualizationCollectionCleaner: TrackerVisualizationCollectionCleaner, + private val analyticsDhisVisualizationCleaner: AnalyticsDhisVisualizationCleaner, + private val dimensionHandler: TrackerVisualizationDimensionHandler, +) : IdentifiableHandlerImpl(store) { + + override fun afterObjectHandled(o: TrackerVisualization, action: HandleAction) { + val dimensions = + toDimensions(o.columns(), LayoutPosition.COLUMN) + + toDimensions(o.filters(), LayoutPosition.FILTER) + + dimensionHandler.handleMany(o.uid(), dimensions) { + it.toBuilder().trackerVisualization(o.uid()).build() + } + } + + override fun afterCollectionHandled(oCollection: Collection?) { + trackerVisualizationCollectionCleaner.deleteNotPresent(oCollection) + analyticsDhisVisualizationCleaner.deleteNotPresent( + uids = store.selectUids(), + type = AnalyticsDhisVisualizationType.TRACKER_VISUALIZATION, + ) + } + + private fun toDimensions( + dimensions: List?, + position: LayoutPosition, + ): List { + return dimensions?.map { dimension -> + dimension.toBuilder() + .position(position) + .build() + } ?: emptyList() + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationModuleDownloader.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationModuleDownloader.kt new file mode 100644 index 0000000000..6756855760 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationModuleDownloader.kt @@ -0,0 +1,50 @@ +/* + * 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.visualization.internal + +import org.hisp.dhis.android.core.arch.modules.internal.TypedModuleDownloader +import org.hisp.dhis.android.core.settings.AnalyticsDhisVisualizationType +import org.hisp.dhis.android.core.settings.internal.AnalyticsDhisVisualizationStore +import org.hisp.dhis.android.core.visualization.TrackerVisualization +import org.koin.core.annotation.Singleton + +@Singleton +internal class TrackerVisualizationModuleDownloader internal constructor( + private val visualizationCall: TrackerVisualizationCall, + private val analyticsDhisVisualizationStore: AnalyticsDhisVisualizationStore, +) : + TypedModuleDownloader> { + + override suspend fun downloadMetadata(): List { + val visualizations = analyticsDhisVisualizationStore.selectAll() + .filter { it.type() == AnalyticsDhisVisualizationType.TRACKER_VISUALIZATION } + .map { it.uid() }.toSet() + + return visualizationCall.download(visualizations) + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationService.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationService.kt new file mode 100644 index 0000000000..72caa546f0 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationService.kt @@ -0,0 +1,51 @@ +/* + * 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.visualization.internal + +import org.hisp.dhis.android.core.arch.api.fields.internal.Fields +import org.hisp.dhis.android.core.arch.api.filters.internal.Which +import org.hisp.dhis.android.core.visualization.TrackerVisualization +import retrofit2.http.GET +import retrofit2.http.Path +import retrofit2.http.Query + +internal interface TrackerVisualizationService { + + @GET("$TRACKER_VISUALIZATIONS/{$TRACKER_VISUALIZATION_UID}") + suspend fun getSingleTrackerVisualization( + @Path(TRACKER_VISUALIZATION_UID) uid: String, + @Query("fields") @Which fields: Fields, + @Query("filter") accessFilter: String, + @Query("paging") paging: Boolean, + ): TrackerVisualization + + companion object { + const val TRACKER_VISUALIZATIONS = "eventVisualizations" + const val TRACKER_VISUALIZATION_UID = "visualizationUid" + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationStore.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationStore.kt new file mode 100644 index 0000000000..faa084da03 --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationStore.kt @@ -0,0 +1,34 @@ +/* + * 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.visualization.internal + +import org.hisp.dhis.android.core.arch.db.stores.internal.IdentifiableObjectStore +import org.hisp.dhis.android.core.visualization.TrackerVisualization + +internal interface TrackerVisualizationStore : IdentifiableObjectStore diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationStoreImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationStoreImpl.kt new file mode 100644 index 0000000000..a0dca332db --- /dev/null +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationStoreImpl.kt @@ -0,0 +1,65 @@ +/* + * 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.visualization.internal + +import android.database.Cursor +import org.hisp.dhis.android.core.arch.db.access.DatabaseAdapter +import org.hisp.dhis.android.core.arch.db.stores.binders.internal.IdentifiableStatementBinder +import org.hisp.dhis.android.core.arch.db.stores.binders.internal.StatementWrapper +import org.hisp.dhis.android.core.arch.db.stores.internal.IdentifiableObjectStoreImpl +import org.hisp.dhis.android.core.arch.helpers.UidsHelper +import org.hisp.dhis.android.core.visualization.TrackerVisualization +import org.hisp.dhis.android.core.visualization.TrackerVisualizationTableInfo +import org.koin.core.annotation.Singleton + +@Singleton +@Suppress("MagicNumber") +internal class TrackerVisualizationStoreImpl( + databaseAdapter: DatabaseAdapter, +) : TrackerVisualizationStore, + IdentifiableObjectStoreImpl( + databaseAdapter, + TrackerVisualizationTableInfo.TABLE_INFO, + BINDER, + { cursor: Cursor -> TrackerVisualization.create(cursor) }, + ) { + companion object { + private val BINDER = object : IdentifiableStatementBinder() { + override fun bindToStatement(o: TrackerVisualization, w: StatementWrapper) { + super.bindToStatement(o, w) + w.bind(7, o.description()) + w.bind(8, o.displayDescription()) + w.bind(9, o.type()) + w.bind(10, o.outputType()) + w.bind(11, UidsHelper.getUidOrNull(o.program())) + w.bind(12, UidsHelper.getUidOrNull(o.programStage())) + w.bind(13, UidsHelper.getUidOrNull(o.trackedEntityType())) + } + } + } +} diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/VisualizationHandler.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/VisualizationHandler.kt index 807d2339d9..5a5c802cec 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/VisualizationHandler.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/VisualizationHandler.kt @@ -29,6 +29,8 @@ package org.hisp.dhis.android.core.visualization.internal import org.hisp.dhis.android.core.arch.handlers.internal.HandleAction import org.hisp.dhis.android.core.arch.handlers.internal.IdentifiableHandlerImpl +import org.hisp.dhis.android.core.settings.AnalyticsDhisVisualizationType +import org.hisp.dhis.android.core.settings.internal.AnalyticsDhisVisualizationCleaner import org.hisp.dhis.android.core.visualization.LayoutPosition import org.hisp.dhis.android.core.visualization.Visualization import org.hisp.dhis.android.core.visualization.VisualizationDimension @@ -39,6 +41,7 @@ import org.koin.core.annotation.Singleton internal class VisualizationHandler( store: VisualizationStore, private val visualizationCollectionCleaner: VisualizationCollectionCleaner, + private val analyticsDhisVisualizationCleaner: AnalyticsDhisVisualizationCleaner, private val itemHandler: VisualizationDimensionItemHandler, ) : IdentifiableHandlerImpl(store) { @@ -55,6 +58,10 @@ internal class VisualizationHandler( override fun afterCollectionHandled(oCollection: Collection?) { visualizationCollectionCleaner.deleteNotPresent(oCollection) + analyticsDhisVisualizationCleaner.deleteNotPresent( + uids = store.selectUids(), + type = AnalyticsDhisVisualizationType.VISUALIZATION, + ) } private fun toItems( diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/VisualizationModuleDownloader.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/VisualizationModuleDownloader.kt index 5c0a22049c..256e6e6572 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/VisualizationModuleDownloader.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/VisualizationModuleDownloader.kt @@ -28,6 +28,7 @@ package org.hisp.dhis.android.core.visualization.internal import org.hisp.dhis.android.core.arch.modules.internal.TypedModuleDownloader +import org.hisp.dhis.android.core.settings.AnalyticsDhisVisualizationType import org.hisp.dhis.android.core.settings.internal.AnalyticsDhisVisualizationStore import org.hisp.dhis.android.core.visualization.Visualization import org.koin.core.annotation.Singleton @@ -40,7 +41,10 @@ internal class VisualizationModuleDownloader internal constructor( TypedModuleDownloader> { override suspend fun downloadMetadata(): List { - val visualizations = analyticsDhisVisualizationStore.selectAll().map { it.uid() }.toSet() + val visualizations = analyticsDhisVisualizationStore.selectAll() + .filter { it.type() == AnalyticsDhisVisualizationType.VISUALIZATION } + .map { it.uid() }.toSet() + return visualizationCall.download(visualizations) } } diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/VisualizationModuleImpl.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/VisualizationModuleImpl.kt index 6971804aef..470ac76ea6 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/VisualizationModuleImpl.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/VisualizationModuleImpl.kt @@ -27,6 +27,7 @@ */ package org.hisp.dhis.android.core.visualization.internal +import org.hisp.dhis.android.core.visualization.TrackerVisualizationCollectionRepository import org.hisp.dhis.android.core.visualization.VisualizationCollectionRepository import org.hisp.dhis.android.core.visualization.VisualizationModule import org.koin.core.annotation.Singleton @@ -34,7 +35,10 @@ import org.koin.core.annotation.Singleton @Singleton internal class VisualizationModuleImpl( private val visualizations: VisualizationCollectionRepository, + private val trackerVisualizations: TrackerVisualizationCollectionRepository, ) : VisualizationModule { override fun visualizations(): VisualizationCollectionRepository = visualizations + + override fun trackerVisualizations(): TrackerVisualizationCollectionRepository = trackerVisualizations } diff --git a/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/VisualizationModuleWiper.kt b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/VisualizationModuleWiper.kt index c4f3801fed..79df93bd42 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/VisualizationModuleWiper.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/visualization/internal/VisualizationModuleWiper.kt @@ -27,6 +27,8 @@ */ package org.hisp.dhis.android.core.visualization.internal +import org.hisp.dhis.android.core.visualization.TrackerVisualizationDimensionTableInfo +import org.hisp.dhis.android.core.visualization.TrackerVisualizationTableInfo import org.hisp.dhis.android.core.visualization.VisualizationDimensionItemTableInfo import org.hisp.dhis.android.core.visualization.VisualizationTableInfo import org.hisp.dhis.android.core.wipe.internal.ModuleWiper @@ -37,6 +39,8 @@ import org.koin.core.annotation.Singleton class VisualizationModuleWiper internal constructor(private val tableWiper: TableWiper) : ModuleWiper { override fun wipeMetadata() { tableWiper.wipeTables( + TrackerVisualizationTableInfo.TABLE_INFO, + TrackerVisualizationDimensionTableInfo.TABLE_INFO, VisualizationTableInfo.TABLE_INFO, VisualizationDimensionItemTableInfo.TABLE_INFO, ) diff --git a/core/src/main/java/org/hisp/dhis/android/core/wipe/internal/D2ModuleWipers.kt b/core/src/main/java/org/hisp/dhis/android/core/wipe/internal/D2ModuleWipers.kt index 1c7be2a117..47b569a91e 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/wipe/internal/D2ModuleWipers.kt +++ b/core/src/main/java/org/hisp/dhis/android/core/wipe/internal/D2ModuleWipers.kt @@ -39,6 +39,7 @@ import org.hisp.dhis.android.core.enrollment.internal.EnrollmentModuleWiper import org.hisp.dhis.android.core.event.internal.EventModuleWiper import org.hisp.dhis.android.core.expressiondimensionitem.internal.ExpressionDimensionItemModuleWiper import org.hisp.dhis.android.core.fileresource.internal.FileResourceModuleWiper +import org.hisp.dhis.android.core.icon.internal.IconModuleWiper import org.hisp.dhis.android.core.imports.internal.ImportModuleWiper import org.hisp.dhis.android.core.indicator.internal.IndicatorModuleWiper import org.hisp.dhis.android.core.legendset.internal.LegendSetModuleWiper @@ -76,6 +77,7 @@ internal class D2ModuleWipers( event: EventModuleWiper, expressionDimensionItem: ExpressionDimensionItemModuleWiper, fileResource: FileResourceModuleWiper, + icon: IconModuleWiper, importModule: ImportModuleWiper, indicator: IndicatorModuleWiper, legendSet: LegendSetModuleWiper, @@ -114,6 +116,7 @@ internal class D2ModuleWipers( event, expressionDimensionItem, fileResource, + icon, importModule, indicator, legendSet, diff --git a/core/src/main/res/raw/isrgrootx1.pem b/core/src/main/res/raw/isrgrootx1.pem new file mode 100644 index 0000000000..b85c8037f6 --- /dev/null +++ b/core/src/main/res/raw/isrgrootx1.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- diff --git a/core/src/main/res/raw/isrgrootx2.pem b/core/src/main/res/raw/isrgrootx2.pem new file mode 100644 index 0000000000..7d903edc9e --- /dev/null +++ b/core/src/main/res/raw/isrgrootx2.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw +CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg +R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 +MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT +ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW ++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9 +ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI +zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW +tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 +/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- diff --git a/core/src/main/res/xml/network_security_configuration.xml b/core/src/main/res/xml/network_security_configuration.xml new file mode 100644 index 0000000000..b35ae4c942 --- /dev/null +++ b/core/src/main/res/xml/network_security_configuration.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + diff --git a/core/src/sharedTest/java/org/hisp/dhis/android/core/data/icon/CustomIconSamples.kt b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/icon/CustomIconSamples.kt new file mode 100644 index 0000000000..ea568c35d0 --- /dev/null +++ b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/icon/CustomIconSamples.kt @@ -0,0 +1,44 @@ +/* + * 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.data.icon + +import org.hisp.dhis.android.core.common.ObjectWithUid +import org.hisp.dhis.android.core.icon.CustomIcon + +object CustomIconSamples { + + fun getCustomIcon(): CustomIcon { + return CustomIcon.builder() + .id(1L) + .key("childIcon") + .fileResource(ObjectWithUid.create("lNrwSpIy1Q9")) + .href("https://play.im.dhis2.org/dev/api/icons/childIcon/icon") + .build() + } +} diff --git a/core/src/sharedTest/java/org/hisp/dhis/android/core/data/maps/MapLayerSamples.kt b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/maps/MapLayerSamples.kt index b9e373b835..0a98d43e7c 100644 --- a/core/src/sharedTest/java/org/hisp/dhis/android/core/data/maps/MapLayerSamples.kt +++ b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/maps/MapLayerSamples.kt @@ -27,8 +27,10 @@ */ package org.hisp.dhis.android.core.data.maps +import org.hisp.dhis.android.core.map.layer.ImageFormat import org.hisp.dhis.android.core.map.layer.MapLayer import org.hisp.dhis.android.core.map.layer.MapLayerPosition +import org.hisp.dhis.android.core.map.layer.MapService object MapLayerSamples { fun get(): MapLayer { @@ -36,6 +38,7 @@ object MapLayerSamples { .id(1L) .uid("map_layer_uid") .name("Map Layer") + .code("MAP_CODE") .displayName("Display map layer") .external(true) .mapLayerPosition(MapLayerPosition.BASEMAP) @@ -43,6 +46,9 @@ object MapLayerSamples { .imageUrl("https://provider-{s}.url") .subdomains(listOf("a", "b", "c")) .subdomainPlaceholder("{s}") + .imageFormat(ImageFormat.JPG) + .layers("layer") + .mapService(MapService.TMS) .build() } } diff --git a/core/src/sharedTest/java/org/hisp/dhis/android/core/data/program/ProgramSamples.java b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/program/ProgramSamples.java index 15519dfbd4..4fbe286813 100644 --- a/core/src/sharedTest/java/org/hisp/dhis/android/core/data/program/ProgramSamples.java +++ b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/program/ProgramSamples.java @@ -75,6 +75,14 @@ public static Program getProgram() { .maxTeiCountToReturn(2) .featureType(FeatureType.POINT) .accessLevel(AccessLevel.PROTECTED) + .enrollmentLabel("enrollmentLabel") + .followUpLabel("followUpLabel") + .orgUnitLabel("orgUnitLabel") + .relationshipLabel("relationshipLabel") + .noteLabel("noteLabel") + .trackedEntityAttributeLabel("trackedEntityAttributeLabel") + .programStageLabel("programStageLabel") + .eventLabel("eventLabel") .build(); return builder.build(); } @@ -105,7 +113,7 @@ public static Program getAntenatalProgram() { .categoryCombo(ObjectWithUid.create("m2jTvAj5kkm")) .access(Access.create(true, true, DataAccess.create(true, true))) .accessLevel(AccessLevel.PROTECTED) - .style(ObjectStyle.builder().color("#333").icon("program-icon").build()) + .style(ObjectStyle.builder().color("#333").icon("antenatal_icon").build()) .relatedProgram(ObjectWithUid.create("lxAQ7Zs9VYR")) .expiryDays(2) .completeEventsExpiryDays(4) @@ -113,6 +121,14 @@ public static Program getAntenatalProgram() { .minAttributesRequiredToSearch(7) .featureType(FeatureType.NONE) .maxTeiCountToReturn(20) + .enrollmentLabel("Enrollment Label") + .followUpLabel("Follow up Label") + .orgUnitLabel("OrgUnit Label") + .relationshipLabel("Relationship Label") + .noteLabel("Note Label") + .trackedEntityAttributeLabel("TrackedEntityAttribute Label") + .programStageLabel("ProgramStage Label") + .eventLabel("Event Label") .build(); } diff --git a/core/src/sharedTest/java/org/hisp/dhis/android/core/data/program/ProgramStageSamples.java b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/program/ProgramStageSamples.java index 7af8bbfcfd..be0ce2ac32 100644 --- a/core/src/sharedTest/java/org/hisp/dhis/android/core/data/program/ProgramStageSamples.java +++ b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/program/ProgramStageSamples.java @@ -73,6 +73,8 @@ public static ProgramStage getProgramStage() { .access(Access.create(false, false, DataAccess.create(true, true))) .remindCompleted(Boolean.FALSE) .validationStrategy(ValidationStrategy.ON_UPDATE_AND_INSERT) + .programStageLabel("programStageLabel") + .eventLabel("eventLabel") .build(); } diff --git a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipTypeBuilder.java b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/relationship/RelationshipConstraintSamples.kt similarity index 58% rename from core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipTypeBuilder.java rename to core/src/sharedTest/java/org/hisp/dhis/android/core/data/relationship/RelationshipConstraintSamples.kt index dd48bd6420..b4b31153db 100644 --- a/core/src/main/java/org/hisp/dhis/android/core/relationship/internal/RelationshipTypeBuilder.java +++ b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/relationship/RelationshipConstraintSamples.kt @@ -25,37 +25,30 @@ * (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.relationship.internal; - -import org.hisp.dhis.android.core.relationship.RelationshipConstraint; -import org.hisp.dhis.android.core.relationship.RelationshipConstraintType; -import org.hisp.dhis.android.core.relationship.RelationshipType; - -import java.util.Set; - -public class RelationshipTypeBuilder { - - private final Set constraints; - - RelationshipTypeBuilder(Set constraints) { - this.constraints = constraints; - } - - public RelationshipType typeWithConstraints(RelationshipType type) { - - RelationshipType.Builder typeBuilder = type.toBuilder(); - - for (RelationshipConstraint constraint : this.constraints) { - if (constraint.relationshipType().uid().equals(type.uid())) { - if (constraint.constraintType().equals(RelationshipConstraintType.FROM)) { - typeBuilder.fromConstraint(constraint); - } else if (constraint.constraintType().equals(RelationshipConstraintType.TO)) { - typeBuilder.toConstraint(constraint); - } - } - } - - return typeBuilder.build(); - } -} \ No newline at end of file +package org.hisp.dhis.android.core.data.relationship + +import org.hisp.dhis.android.core.common.ObjectWithUid +import org.hisp.dhis.android.core.relationship.RelationshipConstraint +import org.hisp.dhis.android.core.relationship.RelationshipConstraintType +import org.hisp.dhis.android.core.relationship.RelationshipEntityType +import org.hisp.dhis.android.core.relationship.TrackerDataView + +object RelationshipConstraintSamples { + @JvmStatic + val relationshipConstraint: RelationshipConstraint + get() = RelationshipConstraint.builder() + .id(1L) + .relationshipType(ObjectWithUid.create("relationship_type_uid")) + .constraintType(RelationshipConstraintType.FROM) + .relationshipEntity(RelationshipEntityType.TRACKED_ENTITY_INSTANCE) + .trackedEntityType(ObjectWithUid.create("tracked_entity_type_uid")) + .program(ObjectWithUid.create("program_uid")) + .programStage(ObjectWithUid.create("program_stage_uid")) + .trackerDataView( + TrackerDataView.builder() + .attributes(listOf("attribute_uid_1", "attribute_uid_3", "attribute_uid_3")) + .dataElements(listOf("data_element_uid_1", "data_element_uid_2", "data_element_uid_3")) + .build(), + ) + .build() +} diff --git a/core/src/sharedTest/java/org/hisp/dhis/android/core/data/relationship/RelationshipTypeSamples.kt b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/relationship/RelationshipTypeSamples.kt index f2c77705c7..23318cc93f 100644 --- a/core/src/sharedTest/java/org/hisp/dhis/android/core/data/relationship/RelationshipTypeSamples.kt +++ b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/relationship/RelationshipTypeSamples.kt @@ -32,35 +32,59 @@ import org.hisp.dhis.android.core.relationship.RelationshipConstraint import org.hisp.dhis.android.core.relationship.RelationshipConstraintType import org.hisp.dhis.android.core.relationship.RelationshipEntityType import org.hisp.dhis.android.core.relationship.RelationshipType +import org.hisp.dhis.android.core.relationship.TrackerDataView object RelationshipTypeSamples { var RELATIONSHIP_TYPE_UID_1 = "RELATIONSHIP_TYPE_UID_1" var RELATIONSHIP_TYPE_UID_2 = "RELATIONSHIP_TYPE_UID_2" var RELATIONSHIP_TYPE_UID_3 = "RELATIONSHIP_TYPE_UID_3" var TET_FOR_RELATIONSHIP_3_UID = "nEenWmSyUEp" + + var ATTRIBUTE_1 = "ATTRIBUTE_1" + var ATTRIBUTE_2 = "ATTRIBUTE_2" + var ATTRIBUTE_3 = "ATTRIBUTE_3" + var DATA_ELEMENT_1 = "DATA_ELEMENT_1" + var DATA_ELEMENT_2 = "DATA_ELEMENT_2" + + var TRACKER_DATA_VIEW_1 = TrackerDataView + .builder() + .attributes(listOf(ATTRIBUTE_1, ATTRIBUTE_2, ATTRIBUTE_3)) + .dataElements(listOf(DATA_ELEMENT_1, DATA_ELEMENT_2)) + .build() + + var TRACKER_DATA_VIEW_2 = TrackerDataView + .builder() + .attributes(listOf(ATTRIBUTE_3)) + .dataElements(emptyList()) + .build() + var FROM_CONSTRAINT_1 = RelationshipConstraint .builder() .id(100L) .constraintType(RelationshipConstraintType.FROM) .relationshipType(ObjectWithUid.create(RELATIONSHIP_TYPE_UID_1)) + .trackerDataView(TRACKER_DATA_VIEW_1) .build() var TO_CONSTRAINT_1 = RelationshipConstraint .builder() .id(200L) .constraintType(RelationshipConstraintType.TO) .relationshipType(ObjectWithUid.create(RELATIONSHIP_TYPE_UID_1)) + .trackerDataView(TRACKER_DATA_VIEW_2) .build() var FROM_CONSTRAINT_2 = RelationshipConstraint .builder() .id(300L) .constraintType(RelationshipConstraintType.FROM) .relationshipType(ObjectWithUid.create(RELATIONSHIP_TYPE_UID_2)) + .trackerDataView(TRACKER_DATA_VIEW_2) .build() var TO_CONSTRAINT_2 = RelationshipConstraint .builder() .id(400L) .constraintType(RelationshipConstraintType.TO) .relationshipType(ObjectWithUid.create(RELATIONSHIP_TYPE_UID_2)) + .trackerDataView(TRACKER_DATA_VIEW_1) .build() var FROM_CONSTRAINT_3 = RelationshipConstraint .builder() @@ -69,12 +93,14 @@ object RelationshipTypeSamples { .relationshipType(ObjectWithUid.create(RELATIONSHIP_TYPE_UID_3)) .relationshipEntity(RelationshipEntityType.TRACKED_ENTITY_INSTANCE) .trackedEntityType(ObjectWithUid.create(TET_FOR_RELATIONSHIP_3_UID)) + .trackerDataView(TRACKER_DATA_VIEW_1) .build() var TO_CONSTRAINT_3 = RelationshipConstraint .builder() .id(600L) .constraintType(RelationshipConstraintType.TO) .relationshipType(ObjectWithUid.create(RELATIONSHIP_TYPE_UID_3)) + .trackerDataView(TRACKER_DATA_VIEW_2) .build() var RELATIONSHIP_TYPE_1 = RelationshipType .builder() diff --git a/core/src/sharedTest/java/org/hisp/dhis/android/core/data/settings/AnalyticsSettingsSamples.kt b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/settings/AnalyticsSettingsSamples.kt index 50f5644d3b..19114e4c0b 100644 --- a/core/src/sharedTest/java/org/hisp/dhis/android/core/data/settings/AnalyticsSettingsSamples.kt +++ b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/settings/AnalyticsSettingsSamples.kt @@ -30,6 +30,7 @@ package org.hisp.dhis.android.core.data.settings import org.hisp.dhis.android.core.period.PeriodType import org.hisp.dhis.android.core.settings.AnalyticsDhisVisualization import org.hisp.dhis.android.core.settings.AnalyticsDhisVisualizationScope +import org.hisp.dhis.android.core.settings.AnalyticsDhisVisualizationType import org.hisp.dhis.android.core.settings.AnalyticsTeiAttribute import org.hisp.dhis.android.core.settings.AnalyticsTeiData import org.hisp.dhis.android.core.settings.AnalyticsTeiDataElement @@ -112,5 +113,6 @@ object AnalyticsSettingsSamples { .timestamp("2021-07-01T02:55:16.8770") .uid("PYBH8ZaAQnC") .name("Sample name") + .type(AnalyticsDhisVisualizationType.VISUALIZATION) .build() } diff --git a/core/src/sharedTest/java/org/hisp/dhis/android/core/data/settings/GeneralSettingsSamples.kt b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/settings/GeneralSettingsSamples.kt index 802de20ade..3737f290cf 100644 --- a/core/src/sharedTest/java/org/hisp/dhis/android/core/data/settings/GeneralSettingsSamples.kt +++ b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/settings/GeneralSettingsSamples.kt @@ -44,6 +44,7 @@ object GeneralSettingsSamples { .allowScreenCapture(true) .messageOfTheDay("Message of the day") .experimentalFeatures(listOf("newFormLayout")) + .bypassDHIS2VersionCheck(true) .build() } } diff --git a/core/src/sharedTest/java/org/hisp/dhis/android/core/data/systeminfo/SystemInfoSamples.java b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/systeminfo/SystemInfoSamples.java index ad1603b1f3..bfa3537417 100644 --- a/core/src/sharedTest/java/org/hisp/dhis/android/core/data/systeminfo/SystemInfoSamples.java +++ b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/systeminfo/SystemInfoSamples.java @@ -41,7 +41,7 @@ public static SystemInfo get1() { .id(1L) .serverDate(getDate("2017-11-29T11:27:46.935")) .dateFormat("yyyy-mm-dd") - .version("2.40.0") + .version("2.41.0") .contextPath("https://play.dhis2.org/android-current") .systemName("DHIS 2 Demo - Sierra Leone") .build(); diff --git a/core/src/sharedTest/java/org/hisp/dhis/android/core/data/user/UserGroupSamples.java b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/user/UserGroupSamples.java new file mode 100644 index 0000000000..cce8cb4753 --- /dev/null +++ b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/user/UserGroupSamples.java @@ -0,0 +1,45 @@ +/* + * 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.data.user; + +import static org.hisp.dhis.android.core.data.utils.FillPropertiesTestUtils.fillIdentifiableProperties; + +import org.hisp.dhis.android.core.user.UserGroup; + +public class UserGroupSamples { + + public static UserGroup getUserGroup() { + UserGroup.Builder builder = UserGroup.builder(); + + fillIdentifiableProperties(builder); + return builder + .id(1L) + .build(); + } +} \ No newline at end of file diff --git a/core/src/sharedTest/java/org/hisp/dhis/android/core/data/visualization/TrackerVisualizationDimensionSamples.kt b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/visualization/TrackerVisualizationDimensionSamples.kt new file mode 100644 index 0000000000..a402efdd7c --- /dev/null +++ b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/visualization/TrackerVisualizationDimensionSamples.kt @@ -0,0 +1,58 @@ +/* + * 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.data.visualization + +import org.hisp.dhis.android.core.common.ObjectWithUid +import org.hisp.dhis.android.core.visualization.LayoutPosition +import org.hisp.dhis.android.core.visualization.TrackerVisualizationDimension +import org.hisp.dhis.android.core.visualization.TrackerVisualizationDimensionRepetition + +object TrackerVisualizationDimensionSamples { + + fun trackerVisualizationDimension(): TrackerVisualizationDimension = + TrackerVisualizationDimension.builder() + .id(1L) + .trackerVisualization("tracker_visualization_uid") + .position(LayoutPosition.COLUMN) + .dimension("ou") + .dimensionType("ORGANISATION_UNIT") + .program(ObjectWithUid.create("program_uid")) + .programStage(ObjectWithUid.create("program_stage_uid")) + .items( + listOf( + ObjectWithUid.create("USER_ORGUNIT"), + ObjectWithUid.create("USER_ORGUNIT_CHILDREN"), + ), + ) + .repetition( + TrackerVisualizationDimensionRepetition.builder() + .indexes(listOf(-1, 1, 0)) + .build(), + ) + .build() +} diff --git a/core/src/sharedTest/java/org/hisp/dhis/android/core/data/visualization/TrackerVisualizationSamples.kt b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/visualization/TrackerVisualizationSamples.kt new file mode 100644 index 0000000000..9f9e50206e --- /dev/null +++ b/core/src/sharedTest/java/org/hisp/dhis/android/core/data/visualization/TrackerVisualizationSamples.kt @@ -0,0 +1,73 @@ +/* + * 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.data.visualization + +import org.hisp.dhis.android.core.common.ObjectWithUid +import org.hisp.dhis.android.core.data.utils.FillPropertiesTestUtils +import org.hisp.dhis.android.core.visualization.* + +internal object TrackerVisualizationSamples { + + private const val DATE_STR = "2021-06-16T14:26:50.195" + private val DATE = FillPropertiesTestUtils.parseDate(DATE_STR) + + @JvmStatic + fun trackerVisualization(): TrackerVisualization = TrackerVisualization.builder() + .id(1L) + .uid("PYBH8ZaAQnC") + .name("Android SDK Tracker Visualization sample") + .displayName("Android SDK Tracker Visualization sample") + .created(DATE) + .lastUpdated(DATE) + .description("Sample tracker visualization for the Android SDK") + .displayDescription("Sample tracker visualization for the Android SDK") + .type(TrackerVisualizationType.LINE_LIST) + .outputType(TrackerVisualizationOutputType.ENROLLMENT) + .program(ObjectWithUid.create("")) + .programStage(ObjectWithUid.create("")) + .trackedEntityType(ObjectWithUid.create("")) + .columns( + listOf( + TrackerVisualizationDimension.builder() + .dimension("ou") + .dimensionType("ORGANISATION_UNIT") + .items(listOf(ObjectWithUid.create("USER_ORGUNIT"))) + .build(), + ), + ) + .filters( + listOf( + TrackerVisualizationDimension.builder() + .dimension("enrollmentDate") + .dimensionType("PERIOD") + .items(listOf(ObjectWithUid.create("LAST_5_YEARS"))) + .build(), + ), + ) + .build() +} diff --git a/core/src/sharedTest/resources/event/new_tracker_importer_events_greater_equal_v41.json b/core/src/sharedTest/resources/event/new_tracker_importer_events_greater_equal_v41.json new file mode 100644 index 0000000000..1d4031b4fe --- /dev/null +++ b/core/src/sharedTest/resources/event/new_tracker_importer_events_greater_equal_v41.json @@ -0,0 +1,35 @@ +{ + "pager": { + "page": 1, + "pageSize": 50 + }, + "page": 1, + "pageSize": 50, + "events": [ + { + "attributeOptionCombo": "bRowv6yZOF2", + "programStage": "dBwrot7S420", + "orgUnit": "DiszpKrYNg8", + "scheduledAt": "2017-01-28T00:00:00.000", + "program": "lxAQ7Zs9VYR", + "event": "single1", + "status": "COMPLETED", + "occurredAt": "2018-02-27T00:00:00.000", + "dataValues": [ + ] + }, + { + "attributeOptionCombo": "bRowv6yZOF2", + "programStage": "dBwrot7S420", + "orgUnit": "DiszpKrYNg8", + "scheduledAt": "2018-02-28T00:00:00.000", + "trackedEntityInstance": "nWrB0TfWlvh", + "program": "lxAQ7Zs9VYR", + "event": "single2", + "status": "ACTIVE", + "occurredAt": "2017-02-27T00:00:00.000", + "dataValues": [ + ] + } + ] +} \ No newline at end of file diff --git a/core/src/sharedTest/resources/event/new_tracker_importer_events_lower_v41.json b/core/src/sharedTest/resources/event/new_tracker_importer_events_lower_v41.json new file mode 100644 index 0000000000..1ee0269c8f --- /dev/null +++ b/core/src/sharedTest/resources/event/new_tracker_importer_events_lower_v41.json @@ -0,0 +1,31 @@ +{ + "page": 1, + "pageSize": 50, + "instances": [ + { + "attributeOptionCombo": "bRowv6yZOF2", + "programStage": "dBwrot7S420", + "orgUnit": "DiszpKrYNg8", + "scheduledAt": "2017-01-28T00:00:00.000", + "program": "lxAQ7Zs9VYR", + "event": "single1", + "status": "COMPLETED", + "occurredAt": "2018-02-27T00:00:00.000", + "dataValues": [ + ] + }, + { + "attributeOptionCombo": "bRowv6yZOF2", + "programStage": "dBwrot7S420", + "orgUnit": "DiszpKrYNg8", + "scheduledAt": "2018-02-28T00:00:00.000", + "trackedEntityInstance": "nWrB0TfWlvh", + "program": "lxAQ7Zs9VYR", + "event": "single2", + "status": "ACTIVE", + "occurredAt": "2017-02-27T00:00:00.000", + "dataValues": [ + ] + } + ] +} \ No newline at end of file diff --git a/core/src/sharedTest/resources/icon/custom_icon.json b/core/src/sharedTest/resources/icon/custom_icon.json new file mode 100644 index 0000000000..ce4aa5dec5 --- /dev/null +++ b/core/src/sharedTest/resources/icon/custom_icon.json @@ -0,0 +1,7 @@ +{ + "key": "childIcon", + "fileResource": { + "id": "lNrwSpIy1Q9" + }, + "href": "https://play.im.dhis2.org/dev/api/icons/childIcon/icon" +} \ No newline at end of file diff --git a/core/src/sharedTest/resources/icon/custom_icons.json b/core/src/sharedTest/resources/icon/custom_icons.json new file mode 100644 index 0000000000..4bef38c95a --- /dev/null +++ b/core/src/sharedTest/resources/icon/custom_icons.json @@ -0,0 +1,24 @@ +{ + "pager": { + "page": 1, + "pageCount": 1, + "total": 2, + "pageSize": 50 + }, + "icons": [ + { + "key": "antenatal_icon", + "fileResource": { + "id": "lNrwSpIy1Q9" + }, + "href": "https://play.im.dhis2.org/dev/api/icons/antenatal_icon/icon" + }, + { + "key": "visit_icon", + "fileResource": { + "id": "yx5Vm0DBjFr" + }, + "href": "https://play.im.dhis2.org/dev/api/icons/visit_icon/icon" + } + ] +} \ No newline at end of file diff --git a/core/src/sharedTest/resources/map/layer/externalmap/external_map_layers.json b/core/src/sharedTest/resources/map/layer/externalmap/external_map_layers.json new file mode 100644 index 0000000000..270df62d44 --- /dev/null +++ b/core/src/sharedTest/resources/map/layer/externalmap/external_map_layers.json @@ -0,0 +1,59 @@ +{ + "pager": { + "page": 1, + "total": 4, + "pageSize": 50, + "pageCount": 1 + }, + "externalMapLayers": [ + { + "name": " Dark basemap", + "code": "DARK_BASEMAP", + "created": "2016-10-05T16:38:03.202", + "lastUpdated": "2024-02-09T10:20:17.044", + "mapService": "WMS", + "url": "https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png", + "attribution": "© OpenStreetMap, CARTO", + "layers": "layer_test", + "imageFormat": "JPG", + "mapLayerPosition": "OVERLAY", + "displayName": " Dark basemap", + "id": "LOw2p0kPwua" + }, + { + "name": "Aerial imagery of Dar-es-Salaam", + "created": "2016-10-05T16:28:27.586", + "lastUpdated": "2024-02-09T10:06:42.677", + "mapService": "XYZ", + "url": "https://a.tiles.mapbox.com/v4/worldbank-education.pebkgmlc/{z}/{x}/{y}.png?access_token=pk.eyJ1Ijoid29ybGRiYW5rLWVkdWNhdGlvbiIsImEiOiJIZ2VvODFjIn0.TDw5VdwGavwEsch53sAVxA", + "attribution": "OpenAerialMap / Tanzania Open Data Initiative", + "imageFormat": "PNG", + "mapLayerPosition": "BASEMAP", + "displayName": "Aerial imagery of Dar-es-Salaam", + "id": "ni2ZiTOZaPD" + }, + { + "name": "Labels overlay", + "created": "2016-10-05T16:40:08.241", + "lastUpdated": "2016-10-05T16:40:08.241", + "mapService": "XYZ", + "url": "https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_only_labels/{z}/{x}/{y}.png", + "attribution": "© OpenStreetMap, CARTO", + "imageFormat": "PNG", + "mapLayerPosition": "OVERLAY", + "displayName": "Labels overlay", + "id": "suB1SFdc6RD" + }, + { + "name": "Terrain basemap", + "created": "2016-10-05T16:45:21.378", + "lastUpdated": "2016-10-05T16:49:13.324", + "mapService": "WMS", + "url": "https://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}.png", + "imageFormat": "PNG", + "mapLayerPosition": "BASEMAP", + "displayName": "Terrain basemap", + "id": "wNIQ8pNvSQd" + } + ] +} \ No newline at end of file diff --git a/core/src/sharedTest/resources/program/program.json b/core/src/sharedTest/resources/program/program.json index d189fa8682..c59108030b 100644 --- a/core/src/sharedTest/resources/program/program.json +++ b/core/src/sharedTest/resources/program/program.json @@ -14,6 +14,14 @@ "version": 11, "selectIncidentDatesInFuture": true, "incidentDateLabel": "Date of incident", + "enrollmentLabel": "Enrollment Label", + "followUpLabel": "Follow up Label", + "orgUnitLabel": "OrgUnit Label", + "relationshipLabel": "Relationship Label", + "noteLabel": "Note Label", + "trackedEntityAttributeLabel": "TrackedEntityAttribute Label", + "programStageLabel": "ProgramStage Label", + "eventLabel": "Event Label", "selectEnrollmentDatesInFuture": false, "registration": true, "useFirstStageDuringRegistration": false, diff --git a/core/src/sharedTest/resources/program/program_stage.json b/core/src/sharedTest/resources/program/program_stage.json index ecbcf2786f..ce692cee7b 100644 --- a/core/src/sharedTest/resources/program/program_stage.json +++ b/core/src/sharedTest/resources/program/program_stage.json @@ -18,6 +18,8 @@ "captureCoordinates": true, "formType": "DEFAULT", "dueDateLabel": "Due date", + "programStageLabel": "ProgramStage Label", + "eventLabel": "Event Label", "remindCompleted": false, "displayGenerateEventBox": false, "validationStrategy": "ON_UPDATE_AND_INSERT", diff --git a/core/src/sharedTest/resources/program/program_stages.json b/core/src/sharedTest/resources/program/program_stages.json index a633ed0b74..154645b314 100644 --- a/core/src/sharedTest/resources/program/program_stages.json +++ b/core/src/sharedTest/resources/program/program_stages.json @@ -10,6 +10,8 @@ "allowGenerateNextVisit": false, "executionDateLabel": "Visit date", "dueDateLabel": "Due date", + "programStageLabel": "ProgramStage Label", + "eventLabel": "Event Label", "validCompleteOnly": true, "displayName": "Antenatal care visit - Program rules demo", "openAfterEnrollment": false, @@ -30,7 +32,7 @@ "reportDateToUse": "report_date_to_use", "style": { "color": "#444", - "icon": "program-stage-icon" + "icon": "visit_icon" }, "access": { "read": true, diff --git a/core/src/sharedTest/resources/program/programs.json b/core/src/sharedTest/resources/program/programs.json index 752ae83dc6..091e96d7ed 100644 --- a/core/src/sharedTest/resources/program/programs.json +++ b/core/src/sharedTest/resources/program/programs.json @@ -20,6 +20,14 @@ "version": 3, "selectIncidentDatesInFuture": false, "incidentDateLabel": "Incident Date", + "enrollmentLabel": "Enrollment Label", + "followUpLabel": "Follow up Label", + "orgUnitLabel": "OrgUnit Label", + "relationshipLabel": "Relationship Label", + "noteLabel": "Note Label", + "trackedEntityAttributeLabel": "TrackedEntityAttribute Label", + "programStageLabel": "ProgramStage Label", + "eventLabel": "Event Label", "displayIncidentDate": false, "selectEnrollmentDatesInFuture": false, "registration": false, @@ -31,7 +39,7 @@ "maxTeiCountToReturn": 20, "style": { "color": "#333", - "icon": "program-icon" + "icon": "antenatal_icon" }, "access": { "data": { diff --git a/core/src/sharedTest/resources/relationship/relationship_type_30.json b/core/src/sharedTest/resources/relationship/relationship_type_30.json index 1b087dd8d1..1c2d84650f 100644 --- a/core/src/sharedTest/resources/relationship/relationship_type_30.json +++ b/core/src/sharedTest/resources/relationship/relationship_type_30.json @@ -20,12 +20,29 @@ "relationshipEntity": "TRACKED_ENTITY_INSTANCE", "trackedEntityType": { "id": "nEenWmSyUEp" + }, + "trackerDataView": { + "attributes": [ + "b0vcadVrn08", + "qXS2NDUEAOS" + ], + "dataElements": [ + "ciWE5jde1ax", + "hB9F8vKFmlk", + "uFAQYm3UgBL" + ] } }, "toConstraint": { "relationshipEntity": "PROGRAM_INSTANCE", "program": { "id": "WSGAb5XwJ3Y" + }, + "trackerDataView": { + "attributes": [ + "b0vcadVrn08" + ], + "dataElements": [] } } } \ No newline at end of file diff --git a/core/src/sharedTest/resources/relationship/relationship_type_32.json b/core/src/sharedTest/resources/relationship/relationship_type_32.json index 88254731a5..aa324920ee 100644 --- a/core/src/sharedTest/resources/relationship/relationship_type_32.json +++ b/core/src/sharedTest/resources/relationship/relationship_type_32.json @@ -24,12 +24,29 @@ "relationshipEntity": "PROGRAM_INSTANCE", "program": { "id": "WSGAb5XwJ3Y" + }, + "trackerDataView": { + "attributes": [ + "b0vcadVrn08", + "qXS2NDUEAOS" + ], + "dataElements": [ + "ciWE5jde1ax", + "hB9F8vKFmlk", + "uFAQYm3UgBL" + ] } }, "fromConstraint": { "relationshipEntity": "TRACKED_ENTITY_INSTANCE", "trackedEntityType": { "id": "nEenWmSyUEp" + }, + "trackerDataView": { + "attributes": [ + "b0vcadVrn08" + ], + "dataElements": [] } } } \ No newline at end of file diff --git a/core/src/sharedTest/resources/settings/analytics_settings_v2.json b/core/src/sharedTest/resources/settings/analytics_settings_v2.json index bb7b0941d1..f12d30035c 100644 --- a/core/src/sharedTest/resources/settings/analytics_settings_v2.json +++ b/core/src/sharedTest/resources/settings/analytics_settings_v2.json @@ -75,6 +75,15 @@ { "id": "PYBH8ZaAQnC", "timestamp": "2021-07-01T03:02:16.8770" + }, + { + "id": "s85urBIkN0z", + "timestamp": "2021-07-01T03:02:16.8770" + }, + { + "id": "invalid_uid", + "type": "VISUALIZATION", + "timestamp": "2021-07-01T03:02:16.8770" } ] }, @@ -109,10 +118,6 @@ "id": "0000000001", "name": "default", "visualizations": [ - { - "id": "PYBH8ZaAQnC", - "timestamp": "2021-07-01T02:55:16.8770" - }, { "id": "PYBH8ZaAQnC", "timestamp": "2021-07-01T02:55:16.8770" diff --git a/core/src/sharedTest/resources/settings/analytics_settings_v3.json b/core/src/sharedTest/resources/settings/analytics_settings_v3.json new file mode 100644 index 0000000000..d3f16ec339 --- /dev/null +++ b/core/src/sharedTest/resources/settings/analytics_settings_v3.json @@ -0,0 +1,136 @@ +{ + "tei": [ + { + "uid": "fqEx2avRp1L", + "data": { + "dataElements": [ + "dBwrot7S420.sWoqcoByYmD", + "dBwrot7S421.Ok9OQpitjQr" + ] + }, + "name": "Height evolution", + "type": "LINE", + "period": "Monthly", + "program": "IpHINAT79UW", + "programStage": "dBwrot7S420", + "shortName": "H. evolution" + }, + { + "uid": "XQUhloISaQJ", + "data": { + "programIndicators": [ + "dBwrot7S420.GSae40Fyppf" + ], + "attributes": [ + "cejWyOfXge6" + ] + }, + "name": "Weight gain", + "type": "BAR", + "period": "Weekly", + "program": "lxAQ7Zs9VYR", + "shortName": "W. gain" + }, + { + "WHONutrition": { + "chartType": "WFH", + "gender": { + "attribute": "cejWyOfXge6", + "values": { + "female": "female", + "male": "male" + } + }, + "x": { + "dataElements": [ + "dBwrot7S420.sWoqcoByYmD" + ] + }, + "y": { + "programIndicators": [ + "GSae40Fyppf" + ] + } + }, + "name": "Who chart", + "program": "IpHINAT79UW", + "programStage": "dBwrot7S420", + "shortName": "Who chart", + "type": "WHO_NUTRITION", + "uid": "yEdtdG7ql9K" + } + ], + "lastUpdated": "2021-06-02T04:30:16.877Z", + "dhisVisualizations": { + "home": [ + { + "id": "12345678910", + "name": "Ejemplo", + "visualizations": [ + { + "id": "FAFa11yFeFe", + "name": "Sample title fro visualization", + "type": "VISUALIZATION", + "timestamp": "2021-07-01T03:01:16.8770" + }, + { + "id": "PYBH8ZaAQnC", + "type": "VISUALIZATION", + "timestamp": "2021-07-01T03:02:16.8770" + }, + { + "id": "s85urBIkN0z", + "type": "TRACKER_VISUALIZATION", + "timestamp": "2021-07-01T03:02:16.8770" + }, + { + "id": "invalid_uid", + "type": "VISUALIZATION", + "timestamp": "2021-07-01T03:02:16.8770" + } + ] + }, + { + "id": "12345678911", + "name": "Otro ejemplo", + "visualizations": [ + { + "id": "PYBH8ZaAQnC", + "type": "VISUALIZATION", + "timestamp": "2021-07-01T03:04:16.8770" + } + ] + } + ], + "dataSet": { + "BfMAe6Itzgt": [ + { + "id": "0000000001", + "name": "default", + "visualizations": [ + { + "id": "FAFa11yFeFe", + "type": "VISUALIZATION", + "timestamp": "2021-07-01T02:55:16.8770" + } + ] + } + ] + }, + "program": { + "IpHINAT79UW": [ + { + "id": "0000000001", + "name": "default", + "visualizations": [ + { + "id": "PYBH8ZaAQnC", + "type": "VISUALIZATION", + "timestamp": "2021-07-01T02:55:16.8770" + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/core/src/sharedTest/resources/settings/general_settings_v2.json b/core/src/sharedTest/resources/settings/general_settings_v2.json index d66244b75b..b481a5076b 100644 --- a/core/src/sharedTest/resources/settings/general_settings_v2.json +++ b/core/src/sharedTest/resources/settings/general_settings_v2.json @@ -9,5 +9,6 @@ "messageOfTheDay": "Message of the day", "experimentalFeatures": [ "newFormLayout" - ] + ], + "bypassDHIS2VersionCheck": true } \ No newline at end of file diff --git a/core/src/sharedTest/resources/settings/system_settings.json b/core/src/sharedTest/resources/settings/system_settings.json index 162df22f97..a639a34555 100644 --- a/core/src/sharedTest/resources/settings/system_settings.json +++ b/core/src/sharedTest/resources/settings/system_settings.json @@ -1,5 +1,6 @@ { "keyFlag": "sierra_leone", "keyStyle": "light_blue/light_blue.css", + "keyDefaultBaseMap": "keyDefaultBaseMap", "keyBingMapsApiKey": "keyBingMapsApiKey" } \ No newline at end of file diff --git a/core/src/sharedTest/resources/settings/version.json b/core/src/sharedTest/resources/settings/version.json new file mode 100644 index 0000000000..713e081e50 --- /dev/null +++ b/core/src/sharedTest/resources/settings/version.json @@ -0,0 +1,6 @@ +{ + "downloadURL": "https://github.com/dhis2/dhis2-android-capture-app/releases/download/40.1/dhis2-40.1.apk", + "version": "40.1", + "isDefault": true, + "userGroups": ["Kk12LkEWtXp"] +} \ No newline at end of file diff --git a/core/src/sharedTest/resources/settings/versions.json b/core/src/sharedTest/resources/settings/versions.json new file mode 100644 index 0000000000..92abe69e02 --- /dev/null +++ b/core/src/sharedTest/resources/settings/versions.json @@ -0,0 +1,45 @@ +{ + "versions": [ + { + "androidOSVersion": { + "min": "5", + "recommended": "7" + }, + "dhis2Version": { + "min": "2.29", + "recommended": "2.40" + }, + "downloadURL": "https://github.com/dhis2/dhis2-android-capture-app/releases/download/40.3/dhis2-40.3.apk", + "version": "40.3", + "isDefault": true + }, + { + "androidOSVersion": { + "min": "5", + "recommended": "7" + }, + "dhis2Version": { + "min": "2.29", + "recommended": "2.40" + }, + "downloadURL": "https://github.com/dhis2/dhis2-android-capture-app/releases/download/40.2/dhis2-40.2.apk", + "version": "40.2", + "isDefault": false, + "userGroups": ["Kk12LkEWtXp"] + }, + { + "androidOSVersion": { + "min": "5", + "recommended": "7" + }, + "dhis2Version": { + "min": "2.29", + "recommended": "2.40" + }, + "downloadURL": "https://github.com/dhis2/dhis2-android-capture-app/releases/download/40.1/dhis2-40.1.apk", + "version": "40.1", + "isDefault": false, + "userGroups": ["Kk12LkEWtXp"] + } + ] +} \ No newline at end of file diff --git a/core/src/sharedTest/resources/systeminfo/system_info.json b/core/src/sharedTest/resources/systeminfo/system_info.json index 8a0a22cbd4..dfec116046 100644 --- a/core/src/sharedTest/resources/systeminfo/system_info.json +++ b/core/src/sharedTest/resources/systeminfo/system_info.json @@ -7,7 +7,7 @@ "lastAnalyticsTableSuccess": "2017-11-29T03:32:45.861", "intervalSinceLastAnalyticsTableSuccess": "7 h, 55 m, 1 s", "lastAnalyticsTableRuntime": "7 m, 40 s", - "version": "2.40.0", + "version": "2.41.0", "revision": "585e4bd", "buildTime": "2017-10-12T06:22:05.000", "jasperReportsVersion": "6.3.1", diff --git a/core/src/sharedTest/resources/trackedentity/new_tracker_importer_tracked_entities_greater_equal_v41.json b/core/src/sharedTest/resources/trackedentity/new_tracker_importer_tracked_entities_greater_equal_v41.json new file mode 100644 index 0000000000..9d2d415a22 --- /dev/null +++ b/core/src/sharedTest/resources/trackedentity/new_tracker_importer_tracked_entities_greater_equal_v41.json @@ -0,0 +1,34 @@ +{ + "pager": { + "page": 1, + "pageSize": 50 + }, + "page": 1, + "pageSize": 50, + "trackedEntities": [ + { + "trackedEntityType": "nEenWmSyUEp", + "orgUnit": "DiszpKrYNg8", + "trackedEntity": "nWrB0TfWlvh", + "deleted": false, + "attributes": [ + { + "attribute": "cejWyOfXge6", + "value": "4081507" + } + ] + }, + { + "trackedEntityType": "nEenWmSyUEp", + "orgUnit": "DiszpKrYNg8", + "trackedEntity": "nWrB0TfWlvD", + "deleted": false, + "attributes": [ + { + "attribute": "cejWyOfXge6", + "value": "654321" + } + ] + } + ] +} \ No newline at end of file diff --git a/core/src/sharedTest/resources/trackedentity/new_tracker_importer_tracked_entities_lower_v41.json b/core/src/sharedTest/resources/trackedentity/new_tracker_importer_tracked_entities_lower_v41.json new file mode 100644 index 0000000000..6c64651c1e --- /dev/null +++ b/core/src/sharedTest/resources/trackedentity/new_tracker_importer_tracked_entities_lower_v41.json @@ -0,0 +1,30 @@ +{ + "page": 1, + "pageSize": 50, + "instances": [ + { + "trackedEntityType": "nEenWmSyUEp", + "orgUnit": "DiszpKrYNg8", + "trackedEntity": "nWrB0TfWlvh", + "deleted": false, + "attributes": [ + { + "attribute": "cejWyOfXge6", + "value": "4081507" + } + ] + }, + { + "trackedEntityType": "nEenWmSyUEp", + "orgUnit": "DiszpKrYNg8", + "trackedEntity": "nWrB0TfWlvD", + "deleted": false, + "attributes": [ + { + "attribute": "cejWyOfXge6", + "value": "654321" + } + ] + } + ] +} \ No newline at end of file diff --git a/core/src/sharedTest/resources/trackedentity/new_tracker_importer_tracked_entity_single.json b/core/src/sharedTest/resources/trackedentity/new_tracker_importer_tracked_entity_single.json deleted file mode 100644 index c38bc74823..0000000000 --- a/core/src/sharedTest/resources/trackedentity/new_tracker_importer_tracked_entity_single.json +++ /dev/null @@ -1,150 +0,0 @@ -{ - "updatedAt": "2015-10-15T11:32:27.242", - "createdAt": "2014-06-06T20:44:21.375", - "trackedEntityType": "nEenWmSyUEp", - "orgUnit": "DiszpKrYNg8", - "trackedEntity": "PgmUFEQYZdt", - "geometry": { - "type": "Point", - "coordinates": [ - 9.0, - 9.0 - ] - }, - "deleted": false, - "relationships": [], - "attributes": [ - { - "updatedAt": "2017-12-12T07:35:12.904", - "createdAt": "2017-12-12T07:35:11.366", - "attribute": "cejWyOfXge6", - "value": "1972-11-08" - } - ], - "enrollments": [ - { - "orgUnit": "DiszpKrYNg8", - "program": "lxAQ7Zs9VYR", - "enrollment": "p6xHz0sbDlx", - "trackedEntity": "PgmUFEQYZdt", - "enrolledAt": "2017-12-12T01:00:00.000", - "followUp": false, - "deleted": false, - "occurredAt": "2017-12-12T07:33:52.993", - "status": "ACTIVE", - "events": [ - { - "programStage": "dBwrot7S420", - "scheduledAt": "2017-12-12T00:00:00.000", - "orgUnit": "DiszpKrYNg8", - "program": "lxAQ7Zs9VYR", - "enrollment": "p6xHz0sbDlx", - "event": "yVTi4EG84wp", - "occurredAt": "2017-12-12T00:00:00.000", - "status": "SCHEDULE", - "createdAt": "2017-12-12T07:33:53.613", - "updatedAt": "2017-12-12T07:35:11.917", - "deleted": false, - "dataValues": [ - { - "updatedAt": "2017-12-12T07:35:12.167", - "storedBy": "android", - "createdAt": "2017-12-12T07:35:12.166", - "dataElement": "sWoqcoByYmD", - "value": "false", - "providedElsewhere": false - } - ] - } - ], - "notes": [] - }, - { - "orgUnit": "DiszpKrYNg8", - "program": "lxAQ7Zs9VYR", - "enrollment": "WKPoiZxZxNG", - "trackedEntity": "PgmUFEQYZdt", - "enrolledAt": "2017-01-20T00:00:00.000", - "followUp": false, - "deleted": false, - "occurredAt": "2017-01-20T00:00:00.000", - "status": "CANCELLED", - "events": [ - { - "attributeOptionCombo": "bRowv6yZOF2", - "programStage": "dBwrot7S420", - "scheduledAt": "2017-12-12T07:30:12.535", - "orgUnit": "DiszpKrYNg8", - "program": "lxAQ7Zs9VYR", - "enrollment": "WKPoiZxZxNG", - "event": "AUEQ24HuW4H", - "occurredAt": "2017-01-20T00:00:00.000", - "status": "ACTIVE", - "createdAt": "2017-01-20T12:14:46.389", - "updatedAt": "2017-12-12T07:30:12.536", - "deleted": false, - "dataValues": [ - { - "updatedAt": "2017-12-12T07:30:12.541", - "storedBy": "android", - "createdAt": "2017-12-12T07:30:12.541", - "dataElement": "sWoqcoByYmD", - "value": "medication 1", - "providedElsewhere": false - } - ] - }, - { - "attributeOptionCombo": "bRowv6yZOF2", - "programStage": "dBwrot7S420", - "scheduledAt": "2017-12-12T07:30:41.755", - "orgUnit": "DiszpKrYNg8", - "program": "lxAQ7Zs9VYR", - "enrollment": "WKPoiZxZxNG", - "event": "LN9rXOMdkDM", - "occurredAt": "2017-12-12T00:00:00.000", - "status": "ACTIVE", - "createdAt": "2017-12-12T07:30:16.658", - "updatedAt": "2017-12-12T07:30:41.756", - "deleted": false, - "dataValues": [ - { - "updatedAt": "2017-12-12T07:30:41.762", - "storedBy": "android", - "createdAt": "2017-12-12T07:30:41.762", - "dataElement": "sWoqcoByYmD", - "value": "Sufficiently immunized", - "providedElsewhere": false - } - ] - }, - { - "attributeOptionCombo": "bRowv6yZOF2", - "programStage": "dBwrot7S420", - "scheduledAt": "2017-12-12T07:32:16.006", - "orgUnit": "DiszpKrYNg8", - "program": "lxAQ7Zs9VYR", - "enrollment": "WKPoiZxZxNG", - "event": "S4OBgYm4bOP", - "occurredAt": "2017-12-12T00:00:00.000", - "status": "COMPLETED", - "createdAt": "2017-12-12T07:31:30.874", - "completedAt": "2017-12-12T00:00:00.000", - "updatedAt": "2017-12-12T07:32:16.012", - "deleted": false, - "dataValues": [ - { - "updatedAt": "2017-12-12T07:32:16.046", - "storedBy": "android", - "createdAt": "2017-12-12T07:31:58.340", - "dataElement": "sWoqcoByYmD", - "value": "false", - "providedElsewhere": false - } - ] - } - ], - "notes": [] - } - ] -} diff --git a/core/src/sharedTest/resources/user/user38.json b/core/src/sharedTest/resources/user/user38.json index 7cc893c83d..140ef82015 100644 --- a/core/src/sharedTest/resources/user/user38.json +++ b/core/src/sharedTest/resources/user/user38.json @@ -22,6 +22,23 @@ ] } ], + "userGroups": [ + { + "href": "https://play.dhis2.org/android-current/api/userGroups/Kk12LkEWtXp", + "name": "_PROGRAM_TB program", + "created": "2018-03-09T23:04:50.114", + "lastUpdated": "2024-02-07T14:00:59.251", + "createdBy": { + "id": "GOLswS44mh8", + "code": null, + "name": "Tom Wakiki", + "displayName": "Tom Wakiki", + "username": "system" + }, + "displayName": "_PROGRAM_TB program", + "id": "Kk12LkEWtXp" + } + ], "teiSearchOrganisationUnits": [], "organisationUnits": [ { diff --git a/core/src/sharedTest/resources/user/user_group.json b/core/src/sharedTest/resources/user/user_group.json new file mode 100644 index 0000000000..ca796788c7 --- /dev/null +++ b/core/src/sharedTest/resources/user/user_group.json @@ -0,0 +1,65 @@ +{ + "href": "https://play.dhis2.org/android-current/api/userGroups/Kk12LkEWtXp", + "name": "_PROGRAM_TB program", + "created": "2018-03-09T23:04:50.114", + "lastUpdated": "2024-02-07T14:00:59.251", + "translations": [ + ], + "externalAccess": false, + "publicAccess": "rw------", + "createdBy": { + "id": "GOLswS44mh8", + "code": null, + "name": "Tom Wakiki", + "displayName": "Tom Wakiki", + "username": "system" + }, + "userGroupAccesses": [ + ], + "userAccesses": [ + ], + "access": { + "manage": true, + "externalize": true, + "write": true, + "read": true, + "update": true, + "delete": true + }, + "favorites": [ + ], + "sharing": { + "owner": "GOLswS44mh8", + "external": false, + "users": { + }, + "userGroups": { + }, + "public": "rw------" + }, + "user": { + "id": "GOLswS44mh8", + "code": null, + "name": "Tom Wakiki", + "displayName": "Tom Wakiki", + "username": "system" + }, + "favorite": false, + "displayName": "_PROGRAM_TB program", + "id": "Kk12LkEWtXp", + "attributeValues": [ + ], + "users": [ + { + "id": "ERMxia28vpM", + "code": null, + "name": "Susan Barnes", + "displayName": "Susan Barnes", + "username": "android1" + } + ], + "managedGroups": [ + ], + "managedByGroups": [ + ] +} \ No newline at end of file diff --git a/core/src/sharedTest/resources/visualization/tracker_visualization.json b/core/src/sharedTest/resources/visualization/tracker_visualization.json new file mode 100644 index 0000000000..8593ea5dba --- /dev/null +++ b/core/src/sharedTest/resources/visualization/tracker_visualization.json @@ -0,0 +1,74 @@ +{ + "name": "TB program", + "created": "2024-02-07T07:37:41.116", + "lastUpdated": "2024-02-07T07:43:25.556", + "description": "Line list for TB program", + "type": "LINE_LIST", + "program": { + "id": "ur1Edk5Oe2n" + }, + "outputType": "ENROLLMENT", + "filters": [ + { + "dimensionType": "PERIOD", + "items": [ + { + "id": "LAST_5_YEARS" + } + ], + "dimension": "enrollmentDate" + } + ], + "columns": [ + { + "dimensionType": "ORGANISATION_UNIT", + "items": [ + { + "id": "USER_ORGUNIT" + } + ], + "dimension": "ou" + }, + { + "dimensionType": "PROGRAM_ATTRIBUTE", + "items": [], + "dimension": "w75KJ2mc4zz" + }, + { + "dimensionType": "DATA_X", + "items": [ + { + "id": "COMPLETED" + }, + { + "id": "ACTIVE" + } + ], + "dimension": "programStatus" + }, + { + "dimensionType": "PROGRAM_DATA_ELEMENT", + "items": [], + "programStage": { + "id": "EPEcjy3FWmI" + }, + "program": { + "id": "ur1Edk5Oe2n" + }, + "filter": "IN:1", + "dimension": "fCXKBdc27Bt", + "repetition": { + "indexes": [ + 1, + 2, + -2, + -1, + 0 + ] + } + } + ], + "displayName": "TB program", + "displayDescription": "Line list for TB program", + "id": "s85urBIkN0z" +} diff --git a/core/src/sharedTest/resources/visualization/tracker_visualizations_1.json b/core/src/sharedTest/resources/visualization/tracker_visualizations_1.json new file mode 100644 index 0000000000..f2c4a6015d --- /dev/null +++ b/core/src/sharedTest/resources/visualization/tracker_visualizations_1.json @@ -0,0 +1,56 @@ +{ + "name": "Child line list", + "created": "2024-02-07T07:37:41.116", + "lastUpdated": "2024-02-07T07:43:25.556", + "description": "Child line list description", + "type": "LINE_LIST", + "program": { + "id": "IpHINAT79UW" + }, + "outputType": "ENROLLMENT", + "filters": [ + { + "dimensionType": "PERIOD", + "items": [ + { + "id": "2018" + }, + { + "id": "2019" + } + ], + "dimension": "enrollmentDate" + } + ], + "columns": [ + { + "dimensionType": "ORGANISATION_UNIT", + "items": [ + { + "id": "USER_ORGUNIT" + } + ], + "dimension": "ou" + }, + { + "dimensionType": "PROGRAM_ATTRIBUTE", + "items": [], + "dimension": "cejWyOfXge6" + }, + { + "dimensionType": "DATA_X", + "items": [ + { + "id": "COMPLETED" + }, + { + "id": "ACTIVE" + } + ], + "dimension": "programStatus" + } + ], + "displayName": "Child line list", + "displayDescription": "Child line list description", + "id": "s85urBIkN0z" +} diff --git a/core/src/test/java/org/hisp/dhis/android/core/analytics/internal/AnalyticsRegexShould.kt b/core/src/test/java/org/hisp/dhis/android/core/analytics/internal/AnalyticsRegexShould.kt new file mode 100644 index 0000000000..1cce14733b --- /dev/null +++ b/core/src/test/java/org/hisp/dhis/android/core/analytics/internal/AnalyticsRegexShould.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.analytics.internal + +import com.google.common.truth.Truth.assertThat +import org.hisp.dhis.android.core.analytics.internal.AnalyticsRegex.dateRangeRegex +import org.hisp.dhis.android.core.analytics.internal.AnalyticsRegex.orgunitLevelRegex +import org.hisp.dhis.android.core.analytics.internal.AnalyticsRegex.uidRegex +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +class AnalyticsRegexShould { + + @Test + fun should_evaluate_orgunit_regex() { + assertThat(orgunitLevelRegex.matches("LEVEL-4")).isTrue() + assertThat(orgunitLevelRegex.matches("LEVEL-ajflkaaf")).isFalse() + + val (level) = orgunitLevelRegex.find("LEVEL-5")!!.destructured + assertThat(level).isEqualTo("5") + } + + @Test + fun should_evaluate_date_range() { + assertThat(dateRangeRegex.matches("2015-12-11_2024-01-04")).isTrue() + assertThat(dateRangeRegex.matches("2015-12-11")).isFalse() + + val (start, end) = dateRangeRegex.find("2015-12-11_2024-01-04")!!.destructured + assertThat(start).isEqualTo("2015-12-11") + assertThat(end).isEqualTo("2024-01-04") + } + + @Test + fun should_evaluate_uid() { + assertThat(uidRegex.matches("YuQRtpL10I")).isFalse() + assertThat(uidRegex.matches("YuQRtpLP10I")).isTrue() + assertThat(uidRegex.matches("YuQRtpL10Ier")).isFalse() + } +} diff --git a/core/src/test/java/org/hisp/dhis/android/core/analytics/trackerlinelist/TrackerLineListRepositoryShould.kt b/core/src/test/java/org/hisp/dhis/android/core/analytics/trackerlinelist/TrackerLineListRepositoryShould.kt new file mode 100644 index 0000000000..e38d7e63ba --- /dev/null +++ b/core/src/test/java/org/hisp/dhis/android/core/analytics/trackerlinelist/TrackerLineListRepositoryShould.kt @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2004-2022, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.hisp.dhis.android.core.analytics.trackerlinelist + +import com.google.common.truth.Truth.assertThat +import com.nhaarman.mockitokotlin2.argumentCaptor +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.verify +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.TrackerLineListParams +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.TrackerLineListRepositoryImpl +import org.hisp.dhis.android.core.analytics.trackerlinelist.internal.TrackerLineListService +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +class TrackerLineListRepositoryShould { + + private val service: TrackerLineListService = mock() + + private val initialParams = TrackerLineListParams(null, null, null, null, listOf(), listOf()) + + private val paramsCaptor = argumentCaptor() + + private val repository = TrackerLineListRepositoryImpl(initialParams, service) + + @Test + fun `Call service with overridden columns respecting initial order`() { + val de1_1 = TrackerLineListItem.ProgramDataElement("dataElement1", "programStage", listOf(), null) + val de2_1 = TrackerLineListItem.ProgramDataElement("dataElement2", "programStage", listOf(), null) + val de1_2 = de1_1.copy(filters = listOf(DataFilter.EqualTo("value"))) + + repository + .withColumn(de1_1) + .withColumn(de2_1) + .withColumn(de1_2) + .blockingEvaluate() + + verify(service).evaluate(paramsCaptor.capture()) + val columns = paramsCaptor.firstValue.columns + + assertThat(columns.size).isEqualTo(2) + + val dataElementColumns = columns.filterIsInstance() + + dataElementColumns.forEachIndexed { index, item -> + when (index) { + 0 -> { + assertThat(item.dataElement).isEqualTo("dataElement1") + assertThat(item.filters.size).isEqualTo(1) + } + 1 -> { + assertThat(item.dataElement).isEqualTo("dataElement2") + assertThat(item.filters.size).isEqualTo(0) + } + } + } + } +} diff --git a/core/src/test/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListParamsShould.kt b/core/src/test/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListParamsShould.kt new file mode 100644 index 0000000000..9ec80ad058 --- /dev/null +++ b/core/src/test/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerLineListParamsShould.kt @@ -0,0 +1,120 @@ +/* + * 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.android.core.analytics.trackerlinelist.internal + +import com.google.common.truth.Truth.assertThat +import org.hisp.dhis.android.core.analytics.trackerlinelist.DataFilter +import org.hisp.dhis.android.core.analytics.trackerlinelist.DateFilter +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListItem +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +class TrackerLineListParamsShould { + + @Test + fun should_add_two_params() { + val params1 = TrackerLineListParams( + trackerVisualization = null, + outputType = TrackerLineListOutputType.EVENT, + programId = null, + programStageId = "program_stage_uid", + columns = listOf( + TrackerLineListItem.ProgramAttribute("attribute", listOf(DataFilter.GreaterThan("5"))), + TrackerLineListItem.ProgramIndicator("indicator"), + TrackerLineListItem.EventDate(), + ), + filters = listOf(), + ) + + val params2 = TrackerLineListParams( + trackerVisualization = null, + outputType = null, + programId = "program_uid", + programStageId = null, + columns = listOf( + TrackerLineListItem.ProgramAttribute("attribute", listOf(DataFilter.NotEqualTo("10"))), + ), + filters = listOf( + TrackerLineListItem.EventDate(listOf(DateFilter.Absolute("202405"))), + ), + ) + + val params = params1 + params2 + + assertThat(params.trackerVisualization).isNull() + assertThat(params.outputType).isEqualTo(TrackerLineListOutputType.EVENT) + assertThat(params.programId).isEqualTo("program_uid") + assertThat(params.programStageId).isEqualTo("program_stage_uid") + assertThat(params.columns).containsExactly( + TrackerLineListItem.ProgramAttribute("attribute", listOf(DataFilter.NotEqualTo("10"))), + TrackerLineListItem.ProgramIndicator("indicator"), + ) + assertThat(params.filters).containsExactly( + TrackerLineListItem.EventDate(listOf(DateFilter.Absolute("202405"))), + ) + } + + @Test + fun should_flatten_repeated_data_elements() { + val params = TrackerLineListParams( + trackerVisualization = null, + outputType = TrackerLineListOutputType.ENROLLMENT, + programId = "programId", + programStageId = null, + columns = listOf( + TrackerLineListItem.ProgramDataElement( + "dataElement", + "programStage", + listOf(), + listOf(0, -1, -2, 1, 2), + ), + ), + filters = emptyList(), + ) + + val flattenedParams = params.flattenRepeatedDataElements() + + assertThat(flattenedParams.columns.size).isEqualTo(5) + + flattenedParams.columns.map { it as TrackerLineListItem.ProgramDataElement }.forEachIndexed { index, item -> + when (index) { + 0 -> assertIndex(item, 1) + 1 -> assertIndex(item, 2) + 2 -> assertIndex(item, -2) + 3 -> assertIndex(item, -1) + 4 -> assertIndex(item, 0) + } + } + } + + private fun assertIndex(item: TrackerLineListItem.ProgramDataElement, idx: Int) { + assertThat(item.repetitionIndexes!!.first()).isEqualTo(idx) + } +} diff --git a/core/src/test/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerVisualizationMapperShould.kt b/core/src/test/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerVisualizationMapperShould.kt new file mode 100644 index 0000000000..23eddebc25 --- /dev/null +++ b/core/src/test/java/org/hisp/dhis/android/core/analytics/trackerlinelist/internal/TrackerVisualizationMapperShould.kt @@ -0,0 +1,113 @@ +/* + * 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.android.core.analytics.trackerlinelist.internal + +import com.google.common.truth.Truth.assertThat +import com.nhaarman.mockitokotlin2.mock +import org.hisp.dhis.android.core.analytics.trackerlinelist.DataFilter +import org.hisp.dhis.android.core.analytics.trackerlinelist.DateFilter +import org.hisp.dhis.android.core.analytics.trackerlinelist.EnumFilter +import org.hisp.dhis.android.core.analytics.trackerlinelist.TrackerLineListItem +import org.hisp.dhis.android.core.common.ObjectWithUid +import org.hisp.dhis.android.core.enrollment.EnrollmentStatus +import org.hisp.dhis.android.core.organisationunit.internal.OrganisationUnitLevelStore +import org.hisp.dhis.android.core.visualization.TrackerVisualizationDimension +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +class TrackerVisualizationMapperShould { + + private val organisationUniLevelStore: OrganisationUnitLevelStore = mock() + + private val mapper = TrackerVisualizationMapper(organisationUniLevelStore) + + @Test + fun should_map_data_filters() { + val item = TrackerVisualizationDimension.builder() + .filter("GT:6:ILIKE:ar:NE:4") + .build() + + val dataFilters = mapper.mapDataFilters(item) + + assertThat(dataFilters).containsExactly( + DataFilter.GreaterThan("6"), + DataFilter.Like("ar", ignoreCase = true), + DataFilter.NotEqualTo("4", ignoreCase = false), + ) + } + + @Test + fun should_map_date_filters() { + val item = TrackerVisualizationDimension.builder() + .items( + listOf( + ObjectWithUid.create("202403"), + ObjectWithUid.create("2024-03-05_2024-04-01"), + ), + ) + .build() + + val dateFilters = mapper.mapDateFilters(item) + + assertThat(dateFilters).containsExactly( + DateFilter.Absolute("202403"), + DateFilter.Range("2024-03-05", "2024-04-01"), + ) + } + + @Test + fun should_map_program_status() { + val item = TrackerVisualizationDimension.builder() + .dimensionType("dataX") + .dimension("programStatus") + .items( + listOf( + ObjectWithUid.create(EnrollmentStatus.ACTIVE.name), + ObjectWithUid.create(EnrollmentStatus.CANCELLED.name), + ), + ) + .build() + + val programStatus = mapper.mapDataX(item) + + assertThat(programStatus).isEqualTo( + TrackerLineListItem.ProgramStatusItem( + listOf( + EnumFilter.In( + listOf( + EnrollmentStatus.ACTIVE, + EnrollmentStatus.CANCELLED, + ), + ), + ), + ), + ) + } +} diff --git a/core/src/test/java/org/hisp/dhis/android/core/configuration/internal/DatabasesConfigurationHelperShould.kt b/core/src/test/java/org/hisp/dhis/android/core/configuration/internal/DatabasesConfigurationHelperShould.kt index 292321b03c..bad191ce75 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/configuration/internal/DatabasesConfigurationHelperShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/configuration/internal/DatabasesConfigurationHelperShould.kt @@ -124,19 +124,19 @@ class DatabasesConfigurationHelperShould { @Test fun add_new_configuration_to_empty() { - val config = helper.addAccount(null, url1, username1, false) + val config = helper.addOrUpdateAccount(null, url1, username1, false) assertThat(config).isEqualTo(singleServerSingleUserConfig) } @Test fun add_new_configuration_to_single_server_single_user_in_same_server() { - val config = helper.addAccount(singleServerSingleUserConfig, url1, username2, false) + val config = helper.addOrUpdateAccount(singleServerSingleUserConfig, url1, username2, false) assertThat(config).isEqualTo(singleServer2UserConfig) } @Test fun add_new_configuration_to_single_server_single_user_in_other_server() { - val config = helper.addAccount(singleServerSingleUserConfig, url2, username2, false) + val config = helper.addOrUpdateAccount(singleServerSingleUserConfig, url2, username2, false) assertThat( DatabaseConfigurationHelper .getLoggedAccount(config, username2, url2), diff --git a/core/src/test/java/org/hisp/dhis/android/core/configuration/internal/MultiUserDatabaseManagerUnitShould.kt b/core/src/test/java/org/hisp/dhis/android/core/configuration/internal/MultiUserDatabaseManagerUnitShould.kt index 3ceeb14bc4..641b754a6f 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/configuration/internal/MultiUserDatabaseManagerUnitShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/configuration/internal/MultiUserDatabaseManagerUnitShould.kt @@ -96,7 +96,7 @@ class MultiUserDatabaseManagerUnitShould : BaseCallShould() { @Test fun create_new_db_when_no_previous_configuration_on_loadExistingChangingEncryptionIfRequiredOtherwiseCreateNew() { val encrypt = false - whenever(configurationHelper.addAccount(null, serverUrl, username, encrypt)) + whenever(configurationHelper.addOrUpdateAccount(null, serverUrl, username, encrypt)) .doReturn(unencryptedConfiguration) manager.loadExistingChangingEncryptionIfRequiredOtherwiseCreateNew(serverUrl, username, encrypt) @@ -108,7 +108,7 @@ class MultiUserDatabaseManagerUnitShould : BaseCallShould() { fun copy_database_when_changing_encryption_on_loadExistingChangingEncryptionIfRequiredOtherwiseCreateNew() { val encrypt = true whenever(databaseConfigurationSecureStore.get()).doReturn(unencryptedConfiguration) - whenever(configurationHelper.addAccount(unencryptedConfiguration, serverUrl, username, encrypt)) + whenever(configurationHelper.addOrUpdateAccount(unencryptedConfiguration, serverUrl, username, encrypt)) .doReturn(encryptedConfiguration) manager.loadExistingChangingEncryptionIfRequiredOtherwiseCreateNew(serverUrl, username, encrypt) @@ -127,7 +127,7 @@ class MultiUserDatabaseManagerUnitShould : BaseCallShould() { @Test fun open_database_when_existing_when_calling_loadExistingKeepingEncryption() { whenever(databaseConfigurationSecureStore.get()).doReturn(unencryptedConfiguration) - whenever(configurationHelper.addAccount(unencryptedConfiguration, serverUrl, username, false)) + whenever(configurationHelper.addOrUpdateAccount(unencryptedConfiguration, serverUrl, username, false)) .doReturn(unencryptedConfiguration) manager.loadExistingKeepingEncryption(serverUrl, username) @@ -154,7 +154,7 @@ class MultiUserDatabaseManagerUnitShould : BaseCallShould() { val newUsername = "new_username" val newServerUrl = "new_server_url" val newConfiguration = buildUserConfiguration(newUsername, "2021-06-01T00:01:04.000", newServerUrl) - whenever(configurationHelper.addAccount(configuration, newServerUrl, newUsername, false)) + whenever(configurationHelper.addOrUpdateAccount(configuration, newServerUrl, newUsername, false)) .doReturn(DatabasesConfiguration.builder().accounts(listOf(newConfiguration)).build()) manager.createNew(newServerUrl, newUsername, false) diff --git a/core/src/test/java/org/hisp/dhis/android/core/domain/metadata/MetadataCallShould.kt b/core/src/test/java/org/hisp/dhis/android/core/domain/metadata/MetadataCallShould.kt index 8061a1f6c4..7b002f467e 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/domain/metadata/MetadataCallShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/domain/metadata/MetadataCallShould.kt @@ -44,6 +44,7 @@ import org.hisp.dhis.android.core.configuration.internal.MultiUserDatabaseManage import org.hisp.dhis.android.core.constant.internal.ConstantModuleDownloader import org.hisp.dhis.android.core.dataset.internal.DataSetModuleDownloader import org.hisp.dhis.android.core.expressiondimensionitem.internal.ExpressionDimensionItemModuleDownloader +import org.hisp.dhis.android.core.icon.internal.CustomIconModuleDownloader import org.hisp.dhis.android.core.indicator.internal.IndicatorModuleDownloader import org.hisp.dhis.android.core.legendset.internal.LegendSetModuleDownloader import org.hisp.dhis.android.core.maintenance.D2Error @@ -60,6 +61,7 @@ import org.hisp.dhis.android.core.systeminfo.internal.SystemInfoModuleDownloader import org.hisp.dhis.android.core.usecase.UseCaseModuleDownloader import org.hisp.dhis.android.core.user.User import org.hisp.dhis.android.core.user.internal.UserModuleDownloader +import org.hisp.dhis.android.core.visualization.internal.TrackerVisualizationModuleDownloader import org.hisp.dhis.android.core.visualization.internal.VisualizationModuleDownloader import org.junit.Assert.fail import org.junit.Before @@ -81,6 +83,7 @@ class MetadataCallShould : BaseCallShould() { private val organisationUnitDownloader: OrganisationUnitModuleDownloader = mock() private val dataSetDownloader: DataSetModuleDownloader = mock() private val visualizationDownloader: VisualizationModuleDownloader = mock() + private val trackerVisualizationDownloader: TrackerVisualizationModuleDownloader = mock() private val constantDownloader: ConstantModuleDownloader = mock() private val indicatorDownloader: IndicatorModuleDownloader = mock() private val programIndicatorModuleDownloader: ProgramIndicatorModuleDownloader = mock() @@ -92,6 +95,7 @@ class MetadataCallShould : BaseCallShould() { private val legendSetModuleDownloader: LegendSetModuleDownloader = mock() private val attributeModuleDownloader: AttributeModuleDownloader = mock() private val expressionDimensIndicatorModuleDownloader: ExpressionDimensionItemModuleDownloader = mock() + private val customIconDownloader: CustomIconModuleDownloader = mock() private val networkError: D2Error = D2Error.builder() .errorCode(D2ErrorCode.UNKNOWN_HOST) @@ -136,6 +140,9 @@ class MetadataCallShould : BaseCallShould() { visualizationDownloader.stub { onBlocking { downloadMetadata() }.doReturn(emptyList()) } + trackerVisualizationDownloader.stub { + onBlocking { downloadMetadata() }.doReturn(emptyList()) + } legendSetModuleDownloader.stub { onBlocking { downloadMetadata() }.doReturn(Unit) } @@ -154,6 +161,9 @@ class MetadataCallShould : BaseCallShould() { categoryDownloader.stub { onBlocking { downloadMetadata() }.doReturn(Unit) } + customIconDownloader.stub { + onBlocking { downloadMetadata() }.doReturn(Unit) + } whenever(smsModule.configCase()).thenReturn(configCase) configCase.stub { onBlocking { refreshMetadataIdsCallable() }.doReturn(Unit) @@ -173,6 +183,7 @@ class MetadataCallShould : BaseCallShould() { organisationUnitDownloader, dataSetDownloader, visualizationDownloader, + trackerVisualizationDownloader, constantDownloader, indicatorDownloader, programIndicatorModuleDownloader, @@ -184,6 +195,7 @@ class MetadataCallShould : BaseCallShould() { legendSetModuleDownloader, attributeModuleDownloader, expressionDimensIndicatorModuleDownloader, + customIconDownloader, ) } diff --git a/core/src/test/java/org/hisp/dhis/android/core/event/NewTrackerImporterEventPayloadGreaterEqualV41Should.kt b/core/src/test/java/org/hisp/dhis/android/core/event/NewTrackerImporterEventPayloadGreaterEqualV41Should.kt new file mode 100644 index 0000000000..c37482eca7 --- /dev/null +++ b/core/src/test/java/org/hisp/dhis/android/core/event/NewTrackerImporterEventPayloadGreaterEqualV41Should.kt @@ -0,0 +1,52 @@ +/* + * 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.android.core.event + +import com.fasterxml.jackson.core.type.TypeReference +import com.google.common.truth.Truth.assertThat +import org.hisp.dhis.android.core.arch.api.payload.internal.TrackerPayload +import org.hisp.dhis.android.core.common.BaseObjectShould +import org.hisp.dhis.android.core.common.ObjectShould +import org.junit.Test + +class NewTrackerImporterEventPayloadGreaterEqualV41Should : + BaseObjectShould("event/new_tracker_importer_events_greater_equal_v41.json"), + ObjectShould { + + @Test + override fun map_from_json_string() { + val eventPayload = objectMapper.readValue( + jsonStream, + object : TypeReference>() {}, + ) + + assertThat(eventPayload.pager()?.page).isEqualTo(1) + assertThat(eventPayload.pager()?.pageSize).isEqualTo(50) + assertThat(eventPayload.items().size).isEqualTo(2) + } +} diff --git a/core/src/test/java/org/hisp/dhis/android/core/event/NewTrackerImporterEventPayloadLowerV41Should.kt b/core/src/test/java/org/hisp/dhis/android/core/event/NewTrackerImporterEventPayloadLowerV41Should.kt new file mode 100644 index 0000000000..7011904766 --- /dev/null +++ b/core/src/test/java/org/hisp/dhis/android/core/event/NewTrackerImporterEventPayloadLowerV41Should.kt @@ -0,0 +1,52 @@ +/* + * 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.android.core.event + +import com.fasterxml.jackson.core.type.TypeReference +import com.google.common.truth.Truth.assertThat +import org.hisp.dhis.android.core.arch.api.payload.internal.TrackerPayload +import org.hisp.dhis.android.core.common.BaseObjectShould +import org.hisp.dhis.android.core.common.ObjectShould +import org.junit.Test + +class NewTrackerImporterEventPayloadLowerV41Should : + BaseObjectShould("event/new_tracker_importer_events_lower_v41.json"), + ObjectShould { + + @Test + override fun map_from_json_string() { + val eventPayload = objectMapper.readValue( + jsonStream, + object : TypeReference>() {}, + ) + + assertThat(eventPayload.pager()?.page).isEqualTo(1) + assertThat(eventPayload.pager()?.pageSize).isEqualTo(50) + assertThat(eventPayload.items().size).isEqualTo(2) + } +} diff --git a/core/src/test/java/org/hisp/dhis/android/core/event/internal/EventDateUtilsShould.kt b/core/src/test/java/org/hisp/dhis/android/core/event/internal/EventDateUtilsShould.kt index 00e57e0a78..e0eee74870 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/event/internal/EventDateUtilsShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/event/internal/EventDateUtilsShould.kt @@ -96,6 +96,15 @@ class EventDateUtilsShould { assertThat(eventDateUtils.isEventExpired(event, 0, null, 2)).isFalse() } + @Test + fun `Should return is not expired if no event or due date provided`() { + whenever(event.status()) doReturn EventStatus.ACTIVE + whenever(event.eventDate()) doReturn null + whenever(event.dueDate()) doReturn null + + assertThat(eventDateUtils.isEventExpired(event, 0, null, 2)).isFalse() + } + private fun getCalendar(): Calendar { val calendar = Calendar.getInstance() calendar[Calendar.YEAR] = 2020 diff --git a/core/src/test/java/org/hisp/dhis/android/core/fileresource/FileResourceDownloaderShould.kt b/core/src/test/java/org/hisp/dhis/android/core/fileresource/FileResourceDownloaderShould.kt index fd2f08e080..037649b8e8 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/fileresource/FileResourceDownloaderShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/fileresource/FileResourceDownloaderShould.kt @@ -55,7 +55,7 @@ class FileResourceDownloaderShould { verify(call).download(paramsCapture.capture()) val params = paramsCapture.firstValue - assertThat(params.domainTypes).isNotEmpty() + assertThat(params.dataDomainTypes).isNotEmpty() assertThat(params.elementTypes).isNotEmpty() assertThat(params.valueTypes).isNotEmpty() assertThat(params.maxContentLength).isNull() @@ -64,7 +64,7 @@ class FileResourceDownloaderShould { @Test fun should_override_default_params() { downloader - .byDomainType().eq(FileResourceDomainType.TRACKER) + .byDataDomainType().eq(FileResourceDataDomainType.TRACKER) .byElementType().eq(FileResourceElementType.DATA_ELEMENT) .byValueType().eq(FileResourceValueType.IMAGE) .byMaxContentLength().eq(400) @@ -73,7 +73,7 @@ class FileResourceDownloaderShould { verify(call).download(paramsCapture.capture()) val params = paramsCapture.firstValue - assertThat(params.domainTypes).isEqualTo(listOf(FileResourceDomainType.TRACKER)) + assertThat(params.dataDomainTypes).isEqualTo(listOf(FileResourceDataDomainType.TRACKER)) assertThat(params.elementTypes).isEqualTo(listOf(FileResourceElementType.DATA_ELEMENT)) assertThat(params.valueTypes).isEqualTo(listOf(FileResourceValueType.IMAGE)) assertThat(params.maxContentLength).isEqualTo(400) diff --git a/core/src/test/java/org/hisp/dhis/android/core/icon/CustomIconShould.kt b/core/src/test/java/org/hisp/dhis/android/core/icon/CustomIconShould.kt new file mode 100644 index 0000000000..fd38a904d8 --- /dev/null +++ b/core/src/test/java/org/hisp/dhis/android/core/icon/CustomIconShould.kt @@ -0,0 +1,47 @@ +/* + * 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.android.core.icon + +import com.google.common.truth.Truth.assertThat +import org.hisp.dhis.android.core.common.BaseObjectShould +import org.hisp.dhis.android.core.common.ObjectShould +import org.junit.Test + +class CustomIconShould : + BaseObjectShould("icon/custom_icon.json"), + ObjectShould { + + @Test + override fun map_from_json_string() { + val icon = objectMapper.readValue(jsonStream, CustomIcon::class.java) + + assertThat(icon.key()).isEqualTo("childIcon") + assertThat(icon.fileResource().uid()).isEqualTo("lNrwSpIy1Q9") + assertThat(icon.href()).isEqualTo("https://play.im.dhis2.org/dev/api/icons/childIcon/icon") + } +} diff --git a/core/src/test/java/org/hisp/dhis/android/core/parser/internal/expression/ExpressionServiceShould.kt b/core/src/test/java/org/hisp/dhis/android/core/parser/internal/expression/ExpressionServiceShould.kt index 2cdffdcc04..cfb9e51963 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/parser/internal/expression/ExpressionServiceShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/parser/internal/expression/ExpressionServiceShould.kt @@ -273,6 +273,25 @@ class ExpressionServiceShould { assertThat(service.getExpressionValue("is('three' in 'one', 'two')")).isEqualTo(false) } + @Test + fun evaluate_contains() { + assertEqual("contains('ALLERGY,LATEX', 'ALLERGY')", true) + assertEqual("contains('ALLERGY,LATEX', 'LATEX', 'ALLERGY')", true) + assertEqual("contains('ALLERGY,LATEX', 'ALLE')", true) + assertEqual("contains('ALLERGY,LATEX', 'RGY,LAT')", true) + assertEqual("contains('abcdef', 'abcdef')", true) + assertEqual("contains('abcdef', 'bcd')", true) + assertEqual("contains('abcdef', 'xyz')", false) + + assertEqual("containsItems('ALLERGY,LATEX', 'ALLERGY')", true) + assertEqual("containsItems('ALLERGY,LATEX', 'LATEX', 'ALLERGY')", true) + assertEqual("containsItems('ALLERGY,LATEX', 'ALLE')", false) + assertEqual("containsItems('ALLERGY,LATEX', 'RGY,LAT')", false) + assertEqual("containsItems('abcdef', 'abcdef')", true) + assertEqual("containsItems('abcdef', 'bcd')", false) + assertEqual("containsItems('abcdef', 'xyz')", false) + } + @Test fun evaluate_divide_by_zero() { assertThat(service.getExpressionValue("4 / 0")).isEqualTo(null) @@ -396,6 +415,10 @@ class ExpressionServiceShould { assertThat(regeneratedExpression).isEqualTo("5.0 + " + de(dataElementId2)) } + private fun assertEqual(expression: String, result: Any) { + assertThat(service.getExpressionValue(expression)).isEqualTo(result) + } + private fun constant(uid: String): String { return "C{$uid}" } diff --git a/core/src/test/java/org/hisp/dhis/android/core/program/ProgramShould.java b/core/src/test/java/org/hisp/dhis/android/core/program/ProgramShould.java deleted file mode 100644 index 7acc2a357c..0000000000 --- a/core/src/test/java/org/hisp/dhis/android/core/program/ProgramShould.java +++ /dev/null @@ -1,98 +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.android.core.program; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.hisp.dhis.android.core.Inject; -import org.hisp.dhis.android.core.common.BaseIdentifiableObject; -import org.hisp.dhis.android.core.common.BaseObjectShould; -import org.hisp.dhis.android.core.common.FeatureType; -import org.hisp.dhis.android.core.common.ObjectShould; -import org.junit.Test; - -import java.io.IOException; -import java.text.ParseException; - -import static com.google.common.truth.Truth.assertThat; - -public class ProgramShould extends BaseObjectShould implements ObjectShould { - - public ProgramShould() { - super("program/program.json"); - } - - @Override - @Test - public void map_from_json_string() throws IOException, ParseException { - ObjectMapper objectMapper = Inject.objectMapper(); - Program program = objectMapper.readValue(jsonStream, Program.class); - - assertThat(program.lastUpdated()).isEqualTo( - BaseIdentifiableObject.DATE_FORMAT.parse("2015-10-15T11:32:27.242")); - assertThat(program.created()).isEqualTo( - BaseIdentifiableObject.DATE_FORMAT.parse("2014-06-06T20:44:21.375")); - - assertThat(program.uid()).isEqualTo("WSGAb5XwJ3Y"); - assertThat(program.name()).isEqualTo("WHO RMNCH Tracker"); - assertThat(program.displayName()).isEqualTo("WHO RMNCH Tracker"); - assertThat(program.shortName()).isEqualTo("WHO RMNCH Tracker"); - assertThat(program.displayShortName()).isEqualTo("WHO RMNCH Tracker"); - assertThat(program.ignoreOverdueEvents()).isFalse(); - assertThat(program.dataEntryMethod()).isFalse(); - assertThat(program.captureCoordinates()).isTrue(); - assertThat(program.enrollmentDateLabel()).isEqualTo("Date of first visit"); - assertThat(program.onlyEnrollOnce()).isFalse(); - assertThat(program.version()).isEqualTo(11); - assertThat(program.selectIncidentDatesInFuture()).isTrue(); - assertThat(program.incidentDateLabel()).isEqualTo("Date of incident"); - assertThat(program.selectEnrollmentDatesInFuture()).isFalse(); - assertThat(program.registration()).isTrue(); - assertThat(program.useFirstStageDuringRegistration()).isFalse(); - assertThat(program.minAttributesRequiredToSearch()).isEqualTo(3); - assertThat(program.maxTeiCountToReturn()).isEqualTo(2); - assertThat(program.featureType()).isEqualTo(FeatureType.MULTI_POLYGON); - assertThat(program.accessLevel()).isEqualTo(AccessLevel.PROTECTED); - - assertThat(program.displayFrontPageList()).isFalse(); - assertThat(program.programType()).isEqualTo(ProgramType.WITH_REGISTRATION); - assertThat(program.displayIncidentDate()).isFalse(); - assertThat(program.categoryCombo().uid()).isEqualTo("p0KPaWEg3cf"); - assertThat(program.trackedEntityType().uid()).isEqualTo("nEenWmSyUEp"); - assertThat(program.relatedProgram().uid()).isEqualTo("IpHINAT79UW"); - - assertThat(program.programRuleVariables().get(0).uid()).isEqualTo("varonrw1032"); - assertThat(program.programRuleVariables().get(1).uid()).isEqualTo("idLCptBEOF9"); - - assertThat(program.programTrackedEntityAttributes().get(0).uid()).isEqualTo("YGMlKXYa5xF"); - assertThat(program.programTrackedEntityAttributes().get(1).uid()).isEqualTo("WZWEBrkJSAm"); - - assertThat(program.programSections().get(0).uid()).isEqualTo("FdpWnXhl7c1"); - } -} \ No newline at end of file diff --git a/core/src/test/java/org/hisp/dhis/android/core/program/ProgramShould.kt b/core/src/test/java/org/hisp/dhis/android/core/program/ProgramShould.kt new file mode 100644 index 0000000000..6c2d6ebf0c --- /dev/null +++ b/core/src/test/java/org/hisp/dhis/android/core/program/ProgramShould.kt @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2004-2022, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.hisp.dhis.android.core.program + +import com.google.common.truth.Truth.assertThat +import org.hisp.dhis.android.core.arch.helpers.DateUtils +import org.hisp.dhis.android.core.common.BaseObjectShould +import org.hisp.dhis.android.core.common.FeatureType +import org.hisp.dhis.android.core.common.ObjectShould +import org.junit.Test + +class ProgramShould : BaseObjectShould("program/program.json"), ObjectShould { + @Test + override fun map_from_json_string() { + val program = objectMapper.readValue(jsonStream, Program::class.java) + + assertThat(program.lastUpdated()).isEqualTo(DateUtils.DATE_FORMAT.parse("2015-10-15T11:32:27.242")) + assertThat(program.created()).isEqualTo(DateUtils.DATE_FORMAT.parse("2014-06-06T20:44:21.375")) + assertThat(program.uid()).isEqualTo("WSGAb5XwJ3Y") + assertThat(program.name()).isEqualTo("WHO RMNCH Tracker") + assertThat(program.displayName()).isEqualTo("WHO RMNCH Tracker") + assertThat(program.shortName()).isEqualTo("WHO RMNCH Tracker") + assertThat(program.displayShortName()).isEqualTo("WHO RMNCH Tracker") + assertThat(program.ignoreOverdueEvents()).isFalse() + assertThat(program.dataEntryMethod()).isFalse() + assertThat(program.captureCoordinates()).isTrue() + assertThat(program.enrollmentDateLabel()).isEqualTo("Date of first visit") + assertThat(program.onlyEnrollOnce()).isFalse() + assertThat(program.version()).isEqualTo(11) + assertThat(program.selectIncidentDatesInFuture()).isTrue() + assertThat(program.incidentDateLabel()).isEqualTo("Date of incident") + assertThat(program.selectEnrollmentDatesInFuture()).isFalse() + assertThat(program.registration()).isTrue() + assertThat(program.useFirstStageDuringRegistration()).isFalse() + assertThat(program.minAttributesRequiredToSearch()).isEqualTo(3) + assertThat(program.maxTeiCountToReturn()).isEqualTo(2) + assertThat(program.featureType()).isEqualTo(FeatureType.MULTI_POLYGON) + assertThat(program.accessLevel()).isEqualTo(AccessLevel.PROTECTED) + assertThat(program.enrollmentLabel()).isEqualTo("Enrollment Label") + assertThat(program.followUpLabel()).isEqualTo("Follow up Label") + assertThat(program.orgUnitLabel()).isEqualTo("OrgUnit Label") + assertThat(program.relationshipLabel()).isEqualTo("Relationship Label") + assertThat(program.noteLabel()).isEqualTo("Note Label") + assertThat(program.trackedEntityAttributeLabel()).isEqualTo("TrackedEntityAttribute Label") + assertThat(program.programStageLabel()).isEqualTo("ProgramStage Label") + assertThat(program.eventLabel()).isEqualTo("Event Label") + assertThat(program.displayFrontPageList()).isFalse() + assertThat(program.programType()).isEqualTo(ProgramType.WITH_REGISTRATION) + assertThat(program.displayIncidentDate()).isFalse() + assertThat(program.categoryCombo()!!.uid()).isEqualTo("p0KPaWEg3cf") + assertThat(program.trackedEntityType()!!.uid()).isEqualTo("nEenWmSyUEp") + assertThat(program.relatedProgram()!!.uid()).isEqualTo("IpHINAT79UW") + assertThat(program.programRuleVariables()!![0].uid()).isEqualTo("varonrw1032") + assertThat(program.programRuleVariables()!![1].uid()).isEqualTo("idLCptBEOF9") + assertThat(program.programTrackedEntityAttributes()!![0].uid()).isEqualTo("YGMlKXYa5xF") + assertThat(program.programTrackedEntityAttributes()!![1].uid()).isEqualTo("WZWEBrkJSAm") + assertThat(program.programSections()!![0].uid()).isEqualTo("FdpWnXhl7c1") + } +} diff --git a/core/src/test/java/org/hisp/dhis/android/core/program/ProgramStageShould.java b/core/src/test/java/org/hisp/dhis/android/core/program/ProgramStageShould.java deleted file mode 100644 index 1be79d6f4f..0000000000 --- a/core/src/test/java/org/hisp/dhis/android/core/program/ProgramStageShould.java +++ /dev/null @@ -1,110 +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.android.core.program; - -import org.hisp.dhis.android.core.common.BaseIdentifiableObject; -import org.hisp.dhis.android.core.common.BaseObjectShould; -import org.hisp.dhis.android.core.common.FeatureType; -import org.hisp.dhis.android.core.common.FormType; -import org.hisp.dhis.android.core.common.ObjectShould; -import org.hisp.dhis.android.core.common.ValidationStrategy; -import org.hisp.dhis.android.core.period.PeriodType; -import org.junit.Test; - -import java.io.IOException; -import java.text.ParseException; -import java.util.List; - -import static com.google.common.truth.Truth.assertThat; - -public class ProgramStageShould extends BaseObjectShould implements ObjectShould { - - public ProgramStageShould() { - super("program/program_stage.json"); - } - - @Override - @Test - public void map_from_json_string() throws IOException, ParseException { - ProgramStage programStage = objectMapper.readValue(jsonStream, ProgramStage.class); - - assertThat(programStage.lastUpdated()).isEqualTo( - BaseIdentifiableObject.DATE_FORMAT.parse("2013-04-10T12:15:02.041")); - assertThat(programStage.created()).isEqualTo( - BaseIdentifiableObject.DATE_FORMAT.parse("2013-03-04T11:41:07.541")); - - assertThat(programStage.uid()).isEqualTo("eaDHS084uMp"); - assertThat(programStage.name()).isEqualTo("ANC 1st visit"); - assertThat(programStage.displayName()).isEqualTo("ANC 1st visit"); - assertThat(programStage.description()).isEqualTo("ANC 1st visit"); - assertThat(programStage.displayDescription()).isEqualTo("ANC 1st visit"); - assertThat(programStage.sortOrder()).isEqualTo(1); - assertThat(programStage.allowGenerateNextVisit()).isFalse(); - assertThat(programStage.autoGenerateEvent()).isTrue(); - assertThat(programStage.blockEntryForm()).isFalse(); - assertThat(programStage.captureCoordinates()).isTrue(); - assertThat(programStage.displayGenerateEventBox()).isFalse(); - assertThat(programStage.executionDateLabel()).isNull(); - assertThat(programStage.dueDateLabel()).isEqualTo("Due date"); - assertThat(programStage.formType()).isEqualTo(FormType.DEFAULT); - assertThat(programStage.generatedByEnrollmentDate()).isFalse(); - assertThat(programStage.hideDueDate()).isFalse(); - assertThat(programStage.minDaysFromStart()).isEqualTo(0); - assertThat(programStage.openAfterEnrollment()).isFalse(); - assertThat(programStage.repeatable()).isFalse(); - assertThat(programStage.reportDateToUse()).isEqualTo("false"); - assertThat(programStage.standardInterval()).isNull(); - assertThat(ProgramStageInternalAccessor.accessProgramStageSections(programStage)).isEmpty(); - assertThat(programStage.periodType()).isEqualTo(PeriodType.Monthly); - assertThat(programStage.remindCompleted()).isFalse(); - assertThat(programStage.validationStrategy()).isEqualTo(ValidationStrategy.ON_UPDATE_AND_INSERT); - assertThat(programStage.featureType()).isEqualTo(FeatureType.POINT); - assertThat(programStage.enableUserAssignment()).isTrue(); - - List dataElements = - ProgramStageInternalAccessor.accessProgramStageDataElements(programStage); - assertThat(dataElements.get(0).uid()).isEqualTo("EQCf1l2Mdr8"); - assertThat(dataElements.get(1).uid()).isEqualTo("muxw4SGzUwJ"); - assertThat(dataElements.get(2).uid()).isEqualTo("KWybjio9UZT"); - assertThat(dataElements.get(3).uid()).isEqualTo("ejm0g2hwHHc"); - assertThat(dataElements.get(4).uid()).isEqualTo("yvV3txhSCyc"); - assertThat(dataElements.get(5).uid()).isEqualTo("fzQrjBpbwQD"); - assertThat(dataElements.get(6).uid()).isEqualTo("BbvkNf9PCxX"); - assertThat(dataElements.get(7).uid()).isEqualTo("MbdCfd4HaMQ"); - assertThat(dataElements.get(8).uid()).isEqualTo("SBn4XCFRbyT"); - assertThat(dataElements.get(9).uid()).isEqualTo("F0PZ4nZ86vo"); - assertThat(dataElements.get(10).uid()).isEqualTo("gFBRqVFh60H"); - assertThat(dataElements.get(11).uid()).isEqualTo("ljAoyjH4GYA"); - assertThat(dataElements.get(12).uid()).isEqualTo("MAdsNY2gOlv"); - assertThat(dataElements.get(13).uid()).isEqualTo("IpVUTCDdlGW"); - assertThat(dataElements.get(14).uid()).isEqualTo("psBtdqepNVM"); - assertThat(dataElements.get(15).uid()).isEqualTo("UzB6pZxZ2Rb"); - assertThat(dataElements.get(16).uid()).isEqualTo("FQZEMbBVabW"); - } -} diff --git a/core/src/test/java/org/hisp/dhis/android/core/program/ProgramStageShould.kt b/core/src/test/java/org/hisp/dhis/android/core/program/ProgramStageShould.kt new file mode 100644 index 0000000000..7ab9d63894 --- /dev/null +++ b/core/src/test/java/org/hisp/dhis/android/core/program/ProgramStageShould.kt @@ -0,0 +1,96 @@ +/* + * 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.android.core.program + +import com.google.common.truth.Truth.assertThat +import org.hisp.dhis.android.core.arch.helpers.DateUtils +import org.hisp.dhis.android.core.common.BaseObjectShould +import org.hisp.dhis.android.core.common.FeatureType +import org.hisp.dhis.android.core.common.FormType +import org.hisp.dhis.android.core.common.ObjectShould +import org.hisp.dhis.android.core.common.ValidationStrategy +import org.hisp.dhis.android.core.period.PeriodType +import org.junit.Test + +class ProgramStageShould : BaseObjectShould("program/program_stage.json"), ObjectShould { + @Test + override fun map_from_json_string() { + val programStage = objectMapper.readValue(jsonStream, ProgramStage::class.java) + + assertThat(programStage.lastUpdated()).isEqualTo(DateUtils.DATE_FORMAT.parse("2013-04-10T12:15:02.041")) + assertThat(programStage.created()).isEqualTo(DateUtils.DATE_FORMAT.parse("2013-03-04T11:41:07.541")) + assertThat(programStage.uid()).isEqualTo("eaDHS084uMp") + assertThat(programStage.name()).isEqualTo("ANC 1st visit") + assertThat(programStage.displayName()).isEqualTo("ANC 1st visit") + assertThat(programStage.description()).isEqualTo("ANC 1st visit") + assertThat(programStage.displayDescription()).isEqualTo("ANC 1st visit") + assertThat(programStage.sortOrder()).isEqualTo(1) + assertThat(programStage.allowGenerateNextVisit()).isFalse() + assertThat(programStage.autoGenerateEvent()).isTrue() + assertThat(programStage.blockEntryForm()).isFalse() + assertThat(programStage.captureCoordinates()).isTrue() + assertThat(programStage.displayGenerateEventBox()).isFalse() + assertThat(programStage.executionDateLabel()).isNull() + assertThat(programStage.dueDateLabel()).isEqualTo("Due date") + assertThat(programStage.formType()).isEqualTo(FormType.DEFAULT) + assertThat(programStage.generatedByEnrollmentDate()).isFalse() + assertThat(programStage.hideDueDate()).isFalse() + assertThat(programStage.minDaysFromStart()).isEqualTo(0) + assertThat(programStage.openAfterEnrollment()).isFalse() + assertThat(programStage.repeatable()).isFalse() + assertThat(programStage.reportDateToUse()).isEqualTo("false") + assertThat(programStage.standardInterval()).isNull() + assertThat(ProgramStageInternalAccessor.accessProgramStageSections(programStage)).isEmpty() + assertThat(programStage.periodType()).isEqualTo(PeriodType.Monthly) + assertThat(programStage.remindCompleted()).isFalse() + assertThat(programStage.validationStrategy()).isEqualTo(ValidationStrategy.ON_UPDATE_AND_INSERT) + assertThat(programStage.featureType()).isEqualTo(FeatureType.POINT) + assertThat(programStage.enableUserAssignment()).isTrue() + assertThat(programStage.programStageLabel()).isEqualTo("ProgramStage Label") + assertThat(programStage.eventLabel()).isEqualTo("Event Label") + + val dataElements = ProgramStageInternalAccessor.accessProgramStageDataElements(programStage) + assertThat(dataElements[0].uid()).isEqualTo("EQCf1l2Mdr8") + assertThat(dataElements[1].uid()).isEqualTo("muxw4SGzUwJ") + assertThat(dataElements[2].uid()).isEqualTo("KWybjio9UZT") + assertThat(dataElements[3].uid()).isEqualTo("ejm0g2hwHHc") + assertThat(dataElements[4].uid()).isEqualTo("yvV3txhSCyc") + assertThat(dataElements[5].uid()).isEqualTo("fzQrjBpbwQD") + assertThat(dataElements[6].uid()).isEqualTo("BbvkNf9PCxX") + assertThat(dataElements[7].uid()).isEqualTo("MbdCfd4HaMQ") + assertThat(dataElements[8].uid()).isEqualTo("SBn4XCFRbyT") + assertThat(dataElements[9].uid()).isEqualTo("F0PZ4nZ86vo") + assertThat(dataElements[10].uid()).isEqualTo("gFBRqVFh60H") + assertThat(dataElements[11].uid()).isEqualTo("ljAoyjH4GYA") + assertThat(dataElements[12].uid()).isEqualTo("MAdsNY2gOlv") + assertThat(dataElements[13].uid()).isEqualTo("IpVUTCDdlGW") + assertThat(dataElements[14].uid()).isEqualTo("psBtdqepNVM") + assertThat(dataElements[15].uid()).isEqualTo("UzB6pZxZ2Rb") + assertThat(dataElements[16].uid()).isEqualTo("FQZEMbBVabW") + } +} diff --git a/core/src/test/java/org/hisp/dhis/android/core/relationship/RelationshipType30Should.java b/core/src/test/java/org/hisp/dhis/android/core/relationship/RelationshipType30Should.java index 72a1ed3e49..b514b84782 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/relationship/RelationshipType30Should.java +++ b/core/src/test/java/org/hisp/dhis/android/core/relationship/RelationshipType30Should.java @@ -63,9 +63,16 @@ public void map_from_json_string() throws IOException, ParseException { assertThat(relationshipType.fromConstraint()).isNotNull(); assertThat(relationshipType.fromConstraint().relationshipEntity()).isEqualTo(RelationshipEntityType.TRACKED_ENTITY_INSTANCE); assertThat(relationshipType.fromConstraint().trackedEntityType().uid()).isEqualTo("nEenWmSyUEp"); + assertThat(relationshipType.fromConstraint().trackerDataView().attributes().get(0)).isEqualTo("b0vcadVrn08"); + assertThat(relationshipType.fromConstraint().trackerDataView().attributes().get(1)).isEqualTo("qXS2NDUEAOS"); + assertThat(relationshipType.fromConstraint().trackerDataView().dataElements().get(0)).isEqualTo("ciWE5jde1ax"); + assertThat(relationshipType.fromConstraint().trackerDataView().dataElements().get(1)).isEqualTo("hB9F8vKFmlk"); + assertThat(relationshipType.fromConstraint().trackerDataView().dataElements().get(2)).isEqualTo("uFAQYm3UgBL"); assertThat(relationshipType.toConstraint()).isNotNull(); assertThat(relationshipType.toConstraint().relationshipEntity()).isEqualTo(RelationshipEntityType.PROGRAM_INSTANCE); assertThat(relationshipType.toConstraint().program().uid()).isEqualTo("WSGAb5XwJ3Y"); + assertThat(relationshipType.toConstraint().trackerDataView().attributes().get(0)).isEqualTo("b0vcadVrn08"); + assertThat(relationshipType.toConstraint().trackerDataView().dataElements().isEmpty()).isTrue(); assertThat(relationshipType.bidirectional()).isFalse(); assertThat(relationshipType.access().data().read()).isTrue(); assertThat(relationshipType.access().data().write()).isFalse(); diff --git a/core/src/test/java/org/hisp/dhis/android/core/relationship/RelationshipType32Should.java b/core/src/test/java/org/hisp/dhis/android/core/relationship/RelationshipType32Should.java index 3acae5177f..40a8df725a 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/relationship/RelationshipType32Should.java +++ b/core/src/test/java/org/hisp/dhis/android/core/relationship/RelationshipType32Should.java @@ -63,9 +63,16 @@ public void map_from_json_string() throws IOException, ParseException { assertThat(relationshipType.fromConstraint()).isNotNull(); assertThat(relationshipType.fromConstraint().relationshipEntity()).isEqualTo(RelationshipEntityType.TRACKED_ENTITY_INSTANCE); assertThat(relationshipType.fromConstraint().trackedEntityType().uid()).isEqualTo("nEenWmSyUEp"); + assertThat(relationshipType.fromConstraint().trackerDataView().attributes().get(0)).isEqualTo("b0vcadVrn08"); + assertThat(relationshipType.fromConstraint().trackerDataView().dataElements().isEmpty()).isTrue(); assertThat(relationshipType.toConstraint()).isNotNull(); assertThat(relationshipType.toConstraint().relationshipEntity()).isEqualTo(RelationshipEntityType.PROGRAM_INSTANCE); assertThat(relationshipType.toConstraint().program().uid()).isEqualTo("WSGAb5XwJ3Y"); + assertThat(relationshipType.toConstraint().trackerDataView().attributes().get(0)).isEqualTo("b0vcadVrn08"); + assertThat(relationshipType.toConstraint().trackerDataView().attributes().get(1)).isEqualTo("qXS2NDUEAOS"); + assertThat(relationshipType.toConstraint().trackerDataView().dataElements().get(0)).isEqualTo("ciWE5jde1ax"); + assertThat(relationshipType.toConstraint().trackerDataView().dataElements().get(1)).isEqualTo("hB9F8vKFmlk"); + assertThat(relationshipType.toConstraint().trackerDataView().dataElements().get(2)).isEqualTo("uFAQYm3UgBL"); assertThat(relationshipType.bidirectional()).isTrue(); assertThat(relationshipType.access().data().read()).isTrue(); assertThat(relationshipType.access().data().write()).isFalse(); diff --git a/core/src/test/java/org/hisp/dhis/android/core/relationship/internal/RelationshipHandlerShould.kt b/core/src/test/java/org/hisp/dhis/android/core/relationship/internal/RelationshipHandlerShould.kt index c10b3333ad..d67448cdf1 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/relationship/internal/RelationshipHandlerShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/relationship/internal/RelationshipHandlerShould.kt @@ -48,8 +48,6 @@ class RelationshipHandlerShould { private val relationshipItemHandler: RelationshipItemHandler = mock() - private val storeSelector: RelationshipItemElementStoreSelector = mock() - private val itemElementStore: StoreWithState<*> = mock() private val NEW_UID = "new-uid" @@ -71,9 +69,7 @@ class RelationshipHandlerShould { relationshipStore, relationshipItemStore, relationshipItemHandler, - storeSelector, ) - whenever(storeSelector.getElementStore(any())).thenReturn(itemElementStore) whenever(itemElementStore.exists(RelationshipSamples.FROM_UID)).thenReturn(true) whenever(itemElementStore.exists(RelationshipSamples.TO_UID)).thenReturn(true) whenever(itemElementStore.exists(TEI_3_UID)).thenReturn(true) diff --git a/core/src/test/java/org/hisp/dhis/android/core/settings/AnalyticsSettingAsserts.kt b/core/src/test/java/org/hisp/dhis/android/core/settings/AnalyticsSettingAsserts.kt new file mode 100644 index 0000000000..3f105c1221 --- /dev/null +++ b/core/src/test/java/org/hisp/dhis/android/core/settings/AnalyticsSettingAsserts.kt @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.settings + +import com.google.common.truth.Truth.assertThat +import org.hisp.dhis.android.core.period.PeriodType +import org.junit.Assert.fail + +object AnalyticsSettingAsserts { + fun assertTeiAnalytics(teis: List) { + assertThat(teis.size).isEqualTo(3) + + teis.forEach { tei -> + when (tei.uid()) { + "fqEx2avRp1L" -> { + assertThat(tei.name()).isEqualTo("Height evolution") + assertThat(tei.shortName()).isEqualTo("H. evolution") + assertThat(tei.program()).isEqualTo("IpHINAT79UW") + assertThat(tei.programStage()).isEqualTo("dBwrot7S420") + assertThat(tei.period()).isEquivalentAccordingToCompareTo(PeriodType.Monthly) + assertThat(tei.type()).isEquivalentAccordingToCompareTo(ChartType.LINE) + assertThat(tei.data()?.dataElements()?.size).isEqualTo(2) + + assertThat( + tei.data()?.dataElements()?.any { dataElement -> + dataElement.dataElement() == "sWoqcoByYmD" && dataElement.programStage() == "dBwrot7S420" + }, + ).isTrue() + + assertThat( + tei.data()?.dataElements()?.any { dataElement -> + dataElement.dataElement() == "Ok9OQpitjQr" && dataElement.programStage() == "dBwrot7S421" + }, + ).isTrue() + } + "XQUhloISaQJ" -> { + assertThat(tei.name()).isEqualTo("Weight gain") + + assertThat(tei.data()?.indicators()?.size).isEqualTo(1) + assertThat( + tei.data()?.indicators()?.first()?.let { + it.indicator() == "GSae40Fyppf" && it.programStage() == "dBwrot7S420" + }, + ).isTrue() + + assertThat(tei.data()?.attributes()?.size).isEqualTo(1) + assertThat(tei.data()?.attributes()?.first()?.attribute() == "cejWyOfXge6").isTrue() + } + "yEdtdG7ql9K" -> { + assertThat(tei.name()).isEqualTo("Who chart") + + assertThat(tei.whoNutritionData()).isNotNull() + assertThat(tei.whoNutritionData()?.chartType()) + .isEquivalentAccordingToCompareTo(WHONutritionChartType.WFH) + + assertThat(tei.whoNutritionData()?.gender()?.attribute()).isEqualTo("cejWyOfXge6") + assertThat(tei.whoNutritionData()?.gender()?.values()?.male()).isEqualTo("male") + assertThat(tei.whoNutritionData()?.gender()?.values()?.female()).isEqualTo("female") + + assertThat(tei.whoNutritionData()?.x()?.dataElements()?.size).isEqualTo(1) + assertThat(tei.whoNutritionData()?.x()?.indicators()?.size).isEqualTo(0) + assertThat(tei.whoNutritionData()?.y()?.dataElements()?.size).isEqualTo(0) + assertThat(tei.whoNutritionData()?.y()?.indicators()?.size).isEqualTo(1) + } + else -> fail("Unexpected tei uid") + } + } + } + + fun assertDhisVisualizations(dhisVisualizationSetting: AnalyticsDhisVisualizationsSetting) { + assertThat(dhisVisualizationSetting.home().size).isEqualTo(2) + dhisVisualizationSetting.home().forEach { group -> + when (group.id()) { + "12345678910" -> { + assertThat(group.name()).isEqualTo("Ejemplo") + assertThat(group.visualizations().size).isEqualTo(4) + } + "12345678911" -> { + assertThat(group.name()).isEqualTo("Otro ejemplo") + assertThat(group.visualizations().size).isEqualTo(1) + } + } + } + + assertThat(dhisVisualizationSetting.dataSet().size).isEqualTo(1) + dhisVisualizationSetting.dataSet().forEach { map -> + when (map.key) { + "BfMAe6Itzgt" -> { + assertThat(map.value.size).isEqualTo(1) + assertThat(map.value[0].visualizations().size).isEqualTo(1) + } + } + } + + assertThat(dhisVisualizationSetting.program().size).isEqualTo(1) + dhisVisualizationSetting.program().forEach { map -> + when (map.key) { + "IpHINAT79UW" -> { + assertThat(map.value.size).isEqualTo(1) + assertThat(map.value[0].visualizations().size).isEqualTo(1) + } + } + } + } +} diff --git a/core/src/test/java/org/hisp/dhis/android/core/settings/AnalyticsSettingV1Should.kt b/core/src/test/java/org/hisp/dhis/android/core/settings/AnalyticsSettingV1Should.kt index e0ed4fb101..57e255f85e 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/settings/AnalyticsSettingV1Should.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/settings/AnalyticsSettingV1Should.kt @@ -27,11 +27,8 @@ */ package org.hisp.dhis.android.core.settings -import com.google.common.truth.Truth.assertThat import org.hisp.dhis.android.core.common.BaseObjectShould import org.hisp.dhis.android.core.common.ObjectShould -import org.hisp.dhis.android.core.period.PeriodType -import org.junit.Assert.fail import org.junit.Test import java.io.IOException import java.text.ParseException @@ -43,62 +40,6 @@ class AnalyticsSettingV1Should : BaseObjectShould("settings/analytics_settings.j override fun map_from_json_string() { val analyticsSettings = objectMapper.readValue(jsonStream, AnalyticsSettings::class.java) - assertThat(analyticsSettings.tei().size).isEqualTo(3) - - analyticsSettings.tei().forEach { tei -> - when (tei.uid()) { - "fqEx2avRp1L" -> { - assertThat(tei.name()).isEqualTo("Height evolution") - assertThat(tei.shortName()).isEqualTo("H. evolution") - assertThat(tei.program()).isEqualTo("IpHINAT79UW") - assertThat(tei.programStage()).isEqualTo("dBwrot7S420") - assertThat(tei.period()).isEquivalentAccordingToCompareTo(PeriodType.Monthly) - assertThat(tei.type()).isEquivalentAccordingToCompareTo(ChartType.LINE) - assertThat(tei.data()?.dataElements()?.size).isEqualTo(2) - - assertThat( - tei.data()?.dataElements()?.any { dataElement -> - dataElement.dataElement() == "sWoqcoByYmD" && dataElement.programStage() == "dBwrot7S420" - }, - ).isTrue() - - assertThat( - tei.data()?.dataElements()?.any { dataElement -> - dataElement.dataElement() == "Ok9OQpitjQr" && dataElement.programStage() == "dBwrot7S421" - }, - ).isTrue() - } - "XQUhloISaQJ" -> { - assertThat(tei.name()).isEqualTo("Weight gain") - - assertThat(tei.data()?.indicators()?.size).isEqualTo(1) - assertThat( - tei.data()?.indicators()?.first()?.let { - it.indicator() == "GSae40Fyppf" && it.programStage() == "dBwrot7S420" - }, - ).isTrue() - - assertThat(tei.data()?.attributes()?.size).isEqualTo(1) - assertThat(tei.data()?.attributes()?.first()?.attribute() == "cejWyOfXge6").isTrue() - } - "yEdtdG7ql9K" -> { - assertThat(tei.name()).isEqualTo("Who chart") - - assertThat(tei.whoNutritionData()).isNotNull() - assertThat(tei.whoNutritionData()?.chartType()) - .isEquivalentAccordingToCompareTo(WHONutritionChartType.WFH) - - assertThat(tei.whoNutritionData()?.gender()?.attribute()).isEqualTo("cejWyOfXge6") - assertThat(tei.whoNutritionData()?.gender()?.values()?.male()).isEqualTo("male") - assertThat(tei.whoNutritionData()?.gender()?.values()?.female()).isEqualTo("female") - - assertThat(tei.whoNutritionData()?.x()?.dataElements()?.size).isEqualTo(1) - assertThat(tei.whoNutritionData()?.x()?.indicators()?.size).isEqualTo(0) - assertThat(tei.whoNutritionData()?.y()?.dataElements()?.size).isEqualTo(0) - assertThat(tei.whoNutritionData()?.y()?.indicators()?.size).isEqualTo(1) - } - else -> fail("Unexpected tei uid") - } - } + AnalyticsSettingAsserts.assertTeiAnalytics(analyticsSettings.tei()) } } diff --git a/core/src/test/java/org/hisp/dhis/android/core/settings/AnalyticsSettingV2Should.kt b/core/src/test/java/org/hisp/dhis/android/core/settings/AnalyticsSettingV2Should.kt index 6556258f95..ab6f7a6f7f 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/settings/AnalyticsSettingV2Should.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/settings/AnalyticsSettingV2Should.kt @@ -31,73 +31,21 @@ package org.hisp.dhis.android.core.settings import com.google.common.truth.Truth.assertThat import org.hisp.dhis.android.core.common.BaseObjectShould import org.hisp.dhis.android.core.common.ObjectShould -import org.hisp.dhis.android.core.period.PeriodType import org.junit.Test -import java.io.IOException -import java.text.ParseException class AnalyticsSettingV2Should : BaseObjectShould("settings/analytics_settings_v2.json"), ObjectShould { @Test - @Throws(IOException::class, ParseException::class) override fun map_from_json_string() { val analyticsSettings = objectMapper.readValue(jsonStream, AnalyticsSettings::class.java) - assertThat(analyticsSettings.tei().size).isEqualTo(3) + AnalyticsSettingAsserts.assertTeiAnalytics(analyticsSettings.tei()) - analyticsSettings.tei().forEach { tei -> - when (tei.uid()) { - "fqEx2avRp1L" -> { - assertThat(tei.name()).isEqualTo("Height evolution") - assertThat(tei.shortName()).isEqualTo("H. evolution") - assertThat(tei.program()).isEqualTo("IpHINAT79UW") - assertThat(tei.programStage()).isEqualTo("dBwrot7S420") - assertThat(tei.period()).isEquivalentAccordingToCompareTo(PeriodType.Monthly) - assertThat(tei.type()).isEquivalentAccordingToCompareTo(ChartType.LINE) - } - "XQUhloISaQJ" -> { - assertThat(tei.data()?.indicators()?.size).isEqualTo(1) - assertThat(tei.data()?.indicators()?.first()?.programStage()).isEqualTo("dBwrot7S420") - assertThat(tei.data()?.indicators()?.first()?.indicator()).isEqualTo("GSae40Fyppf") - } - "yEdtdG7ql9K" -> { - assertThat(tei.whoNutritionData()?.y()?.indicators()?.size).isEqualTo(1) - assertThat(tei.whoNutritionData()?.y()?.indicators()?.first()?.indicator()).isEqualTo("GSae40Fyppf") - } - } - } - - assertThat(analyticsSettings.dhisVisualizations().home().size).isEqualTo(2) - analyticsSettings.dhisVisualizations().home().forEach { group -> - when (group.id()) { - "12345678910" -> { - assertThat(group.name()).isEqualTo("Ejemplo") - assertThat(group.visualizations().size).isEqualTo(2) - } - "12345678911" -> { - assertThat(group.name()).isEqualTo("Otro ejemplo") - assertThat(group.visualizations().size).isEqualTo(1) - } - } - } - - assertThat(analyticsSettings.dhisVisualizations().dataSet().size).isEqualTo(1) - analyticsSettings.dhisVisualizations().dataSet().forEach { map -> - when (map.key) { - "BfMAe6Itzgt" -> { - assertThat(map.value.size).isEqualTo(1) - assertThat(map.value[0].visualizations().size).isEqualTo(1) - } - } - } + AnalyticsSettingAsserts.assertDhisVisualizations(analyticsSettings.dhisVisualizations()) - assertThat(analyticsSettings.dhisVisualizations().program().size).isEqualTo(1) - analyticsSettings.dhisVisualizations().program().forEach { map -> - when (map.key) { - "IpHINAT79UW" -> { - assertThat(map.value.size).isEqualTo(1) - assertThat(map.value[0].visualizations().size).isEqualTo(2) - } + analyticsSettings.dhisVisualizations().home().forEach { + it.visualizations().forEach { + assertThat(it.type()).isEqualTo(AnalyticsDhisVisualizationType.VISUALIZATION) } } } diff --git a/core/src/test/java/org/hisp/dhis/android/core/settings/AnalyticsSettingV3Should.kt b/core/src/test/java/org/hisp/dhis/android/core/settings/AnalyticsSettingV3Should.kt new file mode 100644 index 0000000000..a1ae6f5674 --- /dev/null +++ b/core/src/test/java/org/hisp/dhis/android/core/settings/AnalyticsSettingV3Should.kt @@ -0,0 +1,64 @@ +/* + * 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.android.core.settings + +import com.google.common.truth.Truth.assertThat +import org.hisp.dhis.android.core.common.BaseObjectShould +import org.hisp.dhis.android.core.common.ObjectShould +import org.junit.Test +import java.io.IOException +import java.text.ParseException + +class AnalyticsSettingV3Should : BaseObjectShould("settings/analytics_settings_v3.json"), ObjectShould { + + @Test + @Throws(IOException::class, ParseException::class) + override fun map_from_json_string() { + val analyticsSettings = objectMapper.readValue(jsonStream, AnalyticsSettings::class.java) + + AnalyticsSettingAsserts.assertTeiAnalytics(analyticsSettings.tei()) + + AnalyticsSettingAsserts.assertDhisVisualizations(analyticsSettings.dhisVisualizations()) + + analyticsSettings.dhisVisualizations().home().forEach { + it.visualizations().forEach { + when (it.uid()) { + "FAFa11yFeFe" -> + assertThat(it.type()).isEqualTo(AnalyticsDhisVisualizationType.VISUALIZATION) + + "PYBH8ZaAQnC" -> + assertThat(it.type()).isEqualTo(AnalyticsDhisVisualizationType.VISUALIZATION) + + "s85urBIkN0z" -> + assertThat(it.type()).isEqualTo(AnalyticsDhisVisualizationType.TRACKER_VISUALIZATION) + } + } + } + } +} diff --git a/core/src/test/java/org/hisp/dhis/android/core/settings/GeneralSettingsV2Should.java b/core/src/test/java/org/hisp/dhis/android/core/settings/GeneralSettingsV2Should.java index 6eaad1f740..8b7bdcf43b 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/settings/GeneralSettingsV2Should.java +++ b/core/src/test/java/org/hisp/dhis/android/core/settings/GeneralSettingsV2Should.java @@ -63,5 +63,6 @@ public void map_from_json_string() throws IOException, ParseException { assertThat(generalSettings.messageOfTheDay()).isEqualTo("Message of the day"); assertThat(generalSettings.experimentalFeatures().size()).isEqualTo(1); assertThat(generalSettings.experimentalFeatures().get(0)).isEqualTo("newFormLayout"); + assertThat(generalSettings.bypassDHIS2VersionCheck()).isTrue(); } } diff --git a/core/src/test/java/org/hisp/dhis/android/core/settings/SystemSettingsShould.kt b/core/src/test/java/org/hisp/dhis/android/core/settings/SystemSettingsShould.kt index ac303231b5..184dc15c2c 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/settings/SystemSettingsShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/settings/SystemSettingsShould.kt @@ -38,6 +38,7 @@ class SystemSettingsShould : BaseObjectShould("settings/system_settings.json"), val settings = objectMapper.readValue(jsonStream, SystemSettings::class.java) assertThat(settings.keyFlag).isEqualTo("sierra_leone") assertThat(settings.keyStyle).isEqualTo("light_blue/light_blue.css") + assertThat(settings.keyDefaultBaseMap).isEqualTo("keyDefaultBaseMap") assertThat(settings.keyBingMapsApiKey).isEqualTo("keyBingMapsApiKey") } } diff --git a/core/src/test/java/org/hisp/dhis/android/core/settings/VersionsSettingsShould.kt b/core/src/test/java/org/hisp/dhis/android/core/settings/VersionsSettingsShould.kt new file mode 100644 index 0000000000..e29babf4af --- /dev/null +++ b/core/src/test/java/org/hisp/dhis/android/core/settings/VersionsSettingsShould.kt @@ -0,0 +1,49 @@ +/* + * 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.android.core.settings + +import com.google.common.truth.Truth +import org.hisp.dhis.android.core.common.BaseObjectShould +import org.hisp.dhis.android.core.common.ObjectShould +import org.hisp.dhis.android.core.settings.internal.ApkDistributionVersion +import org.junit.Test + +class VersionsSettingsShould : BaseObjectShould("settings/version.json"), ObjectShould { + + @Test + override fun map_from_json_string() { + val version = objectMapper.readValue(jsonStream, ApkDistributionVersion::class.java) + + Truth.assertThat(version.version).isEqualTo("40.1") + Truth.assertThat(version.isDefault).isTrue() + Truth.assertThat(version.userGroups?.get(0)).isEqualTo("Kk12LkEWtXp") + Truth.assertThat(version.downloadURL).isEqualTo( + "https://github.com/dhis2/dhis2-android-capture-app/releases/download/40.1/dhis2-40.1.apk", + ) + } +} diff --git a/core/src/test/java/org/hisp/dhis/android/core/settings/internal/GeneralSettingCallShould.kt b/core/src/test/java/org/hisp/dhis/android/core/settings/internal/GeneralSettingCallShould.kt index 8bbcc5ebc5..43ba634a52 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/settings/internal/GeneralSettingCallShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/settings/internal/GeneralSettingCallShould.kt @@ -33,6 +33,7 @@ import kotlinx.coroutines.test.runTest import org.hisp.dhis.android.core.arch.api.executors.internal.CoroutineAPICallExecutorMock import org.hisp.dhis.android.core.maintenance.D2ErrorSamples import org.hisp.dhis.android.core.settings.GeneralSettings +import org.hisp.dhis.android.core.systeminfo.internal.DHISVersionManagerImpl import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -47,6 +48,7 @@ class GeneralSettingCallShould { private val generalSetting: GeneralSettings = mock() private val coroutineAPICallExecutor: CoroutineAPICallExecutorMock = CoroutineAPICallExecutorMock() private val appVersionManager: SettingsAppInfoManager = mock() + private val versionManager: DHISVersionManagerImpl = mock() private lateinit var generalSettingCall: GeneralSettingCall @@ -56,7 +58,10 @@ class GeneralSettingCallShould { onBlocking { getDataStoreVersion() } doReturn SettingsAppDataStoreVersion.V1_1 } whenAPICall { generalSetting } - generalSettingCall = GeneralSettingCall(handler, service, appVersionManager, coroutineAPICallExecutor) + whenever(generalSetting.bypassDHIS2VersionCheck()).thenReturn(true) + generalSettingCall = GeneralSettingCall( + handler, service, appVersionManager, coroutineAPICallExecutor, versionManager, + ) } private fun whenAPICall(answer: Answer) { @@ -73,4 +78,13 @@ class GeneralSettingCallShould { verify(handler).handleMany(emptyList()) verifyNoMoreInteractions(handler) } + + @Test + fun handle_general_setting_and_set_bypass_DHIS2_version() = runTest { + whenever(service.generalSettings(any())) doAnswer { generalSetting } + + generalSettingCall.download(false) + verify(handler).handleMany(listOfNotNull(generalSetting)) + verify(versionManager).setBypassVersion(true) + } } diff --git a/core/src/test/java/org/hisp/dhis/android/core/settings/internal/LatestAppVersionCallShould.kt b/core/src/test/java/org/hisp/dhis/android/core/settings/internal/LatestAppVersionCallShould.kt new file mode 100644 index 0000000000..b7088a3c0e --- /dev/null +++ b/core/src/test/java/org/hisp/dhis/android/core/settings/internal/LatestAppVersionCallShould.kt @@ -0,0 +1,93 @@ +/* + * 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.android.core.settings.internal + +import com.nhaarman.mockitokotlin2.* +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.hisp.dhis.android.core.arch.api.executors.internal.CoroutineAPICallExecutorMock +import org.hisp.dhis.android.core.maintenance.D2ErrorSamples +import org.hisp.dhis.android.core.settings.LatestAppVersion +import org.hisp.dhis.android.core.user.UserGroupCollectionRepository +import org.hisp.dhis.android.core.user.UserModule +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.stubbing.Answer + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(JUnit4::class) +class LatestAppVersionCallShould { + private val handler: LatestAppVersionHandler = mock() + private val service: SettingAppService = mock() + private val userModule: UserModule = mock() + private val versionComparator: LatestAppVersionComparator = mock() + private val versions: List = mock() + private val latestVersion: LatestAppVersion = mock() + private val userGroupCollectionRepository: UserGroupCollectionRepository = mock() + private val coroutineAPICallExecutor: CoroutineAPICallExecutorMock = CoroutineAPICallExecutorMock() + + private lateinit var latestAppVersionCall: LatestAppVersionCall + + @Before + fun setUp() { + whenVersionsAPICall { versions } + whenLatestVersionAPICall { latestVersion } + + whenever(userModule.userGroups()).thenReturn(userGroupCollectionRepository) + whenever(userGroupCollectionRepository.blockingGet()).thenReturn(emptyList()) + + latestAppVersionCall = LatestAppVersionCall( + handler, service, userModule, versionComparator, coroutineAPICallExecutor, + ) + } + + private fun whenVersionsAPICall(answer: Answer>) { + service.stub { + onBlocking { versions() }.doAnswer(answer) + } + } + + private fun whenLatestVersionAPICall(answer: Answer) { + service.stub { + onBlocking { latestAppVersion() }.doAnswer(answer) + } + } + + @Test + fun default_to_empty_collection_if_not_found() = runTest { + whenever(service.versions()) doAnswer { throw D2ErrorSamples.notFound() } + whenever(service.latestAppVersion()) doAnswer { throw D2ErrorSamples.notFound() } + + latestAppVersionCall.download(false) + + verify(handler).handleMany(emptyList()) + verifyNoMoreInteractions(handler) + } +} diff --git a/core/src/test/java/org/hisp/dhis/android/core/settings/internal/LatestAppVersionComparatorShould.kt b/core/src/test/java/org/hisp/dhis/android/core/settings/internal/LatestAppVersionComparatorShould.kt new file mode 100644 index 0000000000..abbbb5fcfb --- /dev/null +++ b/core/src/test/java/org/hisp/dhis/android/core/settings/internal/LatestAppVersionComparatorShould.kt @@ -0,0 +1,122 @@ +/* + * 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.android.core.settings.internal + +import com.google.common.truth.Truth.assertThat +import com.nhaarman.mockitokotlin2.* +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +class LatestAppVersionComparatorShould { + private val version1: ApkDistributionVersion = mock() + private val version2: ApkDistributionVersion = mock() + private val version3: ApkDistributionVersion = mock() + private val comparator = LatestAppVersionComparator().comparator + + @Test + fun correctly_compare_versions_where_one_is_greater() { + whenever(version1.version) doReturn "1.2.3" + whenever(version2.version) doReturn "1.2.4" + + val result = comparator.compare(version1, version2) + + assertThat(result).isLessThan(0) + } + + @Test + fun treat_versions_as_equal_when_they_are_the_same() { + whenever(version1.version) doReturn "1.2.3" + whenever(version2.version) doReturn "1.2.3" + + val result = comparator.compare(version1, version2) + + assertThat(result).isEqualTo(0) + } + + @Test + fun handle_versions_with_different_lengths_correctly() { + whenever(version1.version) doReturn "1.2" + whenever(version2.version) doReturn "1.2.1" + + val result = comparator.compare(version1, version2) + + assertThat(result).isLessThan(0) + } + + @Test + fun handle_non_numeric_parts_by_treating_them_as_0() { + whenever(version1.version) doReturn "1.2.x" + whenever(version2.version) doReturn "1.2.1" + + val result = comparator.compare(version1, version2) + + assertThat(result).isLessThan(0) + } + + @Test + fun return_the_greatest_version() { + whenever(version1.version) doReturn "1.2" + whenever(version2.version) doReturn "1.1.1" + whenever(version3.version) doReturn "1.1.0" + + val highestVersion = listOf(version1, version2, version3).maxWithOrNull(comparator) + + assertThat(highestVersion).isEqualTo(version1) + } + + @Test + fun return_the_first_in_list_when_two_greatest_versions() { + whenever(version1.version) doReturn "1.2.0" + whenever(version2.version) doReturn "1.2.1" + whenever(version3.version) doReturn "1.2.1" + + val highestVersion = listOf(version1, version2, version3).maxWithOrNull(comparator) + + assertThat(highestVersion).isEqualTo(version2) + } + + @Test + fun return_the_first_version_when_string_is_not_a_number() { + whenever(version1.version) doReturn "version_one" + whenever(version2.version) doReturn "version_two" + whenever(version3.version) doReturn "version_three" + + val highestVersion = listOf(version1, version2, version3).maxWithOrNull(comparator) + + assertThat(highestVersion).isEqualTo(version1) + } + + @Test + fun return_null_when_empty_list() { + val highestVersion = emptyList().maxWithOrNull(comparator) + + assertThat(highestVersion).isNull() + } +} diff --git a/core/src/test/java/org/hisp/dhis/android/core/settings/internal/SystemSettingSplitterShould.kt b/core/src/test/java/org/hisp/dhis/android/core/settings/internal/SystemSettingSplitterShould.kt index 2601fef651..a9c3740075 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/settings/internal/SystemSettingSplitterShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/settings/internal/SystemSettingSplitterShould.kt @@ -39,6 +39,7 @@ class SystemSettingSplitterShould { private val settings: SystemSettings = SystemSettings( keyFlag = "aFlag", keyStyle = "aStyle", + keyDefaultBaseMap = "aDefaultBaseMap", keyBingMapsApiKey = null, ) @@ -61,4 +62,13 @@ class SystemSettingSplitterShould { assertThat(style.value()).isEqualTo("aStyle") } } + + @Test + fun build_default_base_map_setting() { + val settingList = systemSettingsSplitter.splitSettings(settings) + settingList[2].let { style -> + assertThat(style.key()).isEqualTo(SystemSettingKey.DEFAULT_BASE_MAP) + assertThat(style.value()).isEqualTo("aDefaultBaseMap") + } + } } diff --git a/core/src/test/java/org/hisp/dhis/android/core/systeminfo/SMSVersionShould.kt b/core/src/test/java/org/hisp/dhis/android/core/systeminfo/SMSVersionShould.kt index 5ba5bf262c..4a4e2d7045 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/systeminfo/SMSVersionShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/systeminfo/SMSVersionShould.kt @@ -57,9 +57,21 @@ class SMSVersionShould { assertThat(smsVersion).isNull() } + @Test + fun return_unknown_version_if_patch_does_not_exist_but_bypass_is_true() { + val smsVersion = getValue("2.100.100", true) + assertThat(smsVersion).isEqualTo(DHISPatchVersion.UNKNOWN.smsVersion) + } + + @Test + fun return_null_if_patch_does_not_exist_but_bypass_is_false() { + val smsVersion = getValue("2.100.100", false) + assertThat(smsVersion).isNull() + } + @Test fun return_non_null_for_any_version_greater_than_2_32() { - DHISVersion.values() + DHISVersion.entries .filter { it > DHISVersion.V2_32 && it.supported } .forEach { assertThat(getValue(it.prefix + ".0")).isNotNull() diff --git a/core/src/test/java/org/hisp/dhis/android/core/systeminfo/SystemInfoShould.kt b/core/src/test/java/org/hisp/dhis/android/core/systeminfo/SystemInfoShould.kt index 71d0e3f45c..ed315111e6 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/systeminfo/SystemInfoShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/systeminfo/SystemInfoShould.kt @@ -41,7 +41,7 @@ class SystemInfoShould : BaseObjectShould("systeminfo/system_info.json"), Object assertThat(systemInfo.serverDate()).isEqualTo(DateUtils.DATE_FORMAT.parse("2017-11-29T11:27:46.935")) assertThat(systemInfo.dateFormat()).isEqualTo("yyyy-mm-dd") - assertThat(systemInfo.version()).isEqualTo("2.40.0") + assertThat(systemInfo.version()).isEqualTo("2.41.0") assertThat(systemInfo.contextPath()).isEqualTo("https://play.dhis2.org/android-current") assertThat(systemInfo.systemName()).isEqualTo("DHIS 2 Demo - Sierra Leone") } diff --git a/core/src/test/java/org/hisp/dhis/android/core/systeminfo/internal/DHISVersionManagerShould.kt b/core/src/test/java/org/hisp/dhis/android/core/systeminfo/internal/DHISVersionManagerShould.kt index 3ee9d335b6..665f043956 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/systeminfo/internal/DHISVersionManagerShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/systeminfo/internal/DHISVersionManagerShould.kt @@ -60,16 +60,36 @@ class DHISVersionManagerShould { assertThat(dhisVersionManager.isVersion(DHISVersion.V2_31)).isTrue() assertThat(dhisVersionManager.isVersion(DHISVersion.V2_32)).isFalse() assertThat(dhisVersionManager.isVersion(DHISVersion.V2_33)).isFalse() + assertThat(dhisVersionManager.isVersion(DHISVersion.UNKNOWN)).isFalse() assertThat(dhisVersionManager.isGreaterThan(DHISVersion.V2_30)).isTrue() assertThat(dhisVersionManager.isGreaterThan(DHISVersion.V2_31)).isFalse() assertThat(dhisVersionManager.isGreaterThan(DHISVersion.V2_32)).isFalse() assertThat(dhisVersionManager.isGreaterThan(DHISVersion.V2_33)).isFalse() + assertThat(dhisVersionManager.isGreaterThan(DHISVersion.UNKNOWN)).isFalse() assertThat(dhisVersionManager.isGreaterOrEqualThan(DHISVersion.V2_30)).isTrue() assertThat(dhisVersionManager.isGreaterOrEqualThan(DHISVersion.V2_31)).isTrue() assertThat(dhisVersionManager.isGreaterOrEqualThan(DHISVersion.V2_32)).isFalse() assertThat(dhisVersionManager.isGreaterOrEqualThan(DHISVersion.V2_33)).isFalse() + assertThat(dhisVersionManager.isGreaterOrEqualThan(DHISVersion.UNKNOWN)).isFalse() + } + + @Test + fun compare_version_when_unknown() { + dhisVersionManager.setBypassVersion(true) + whenever(systemInfo.version()).thenReturn(DHISVersion.UNKNOWN.name) + + assertThat(dhisVersionManager.isVersion(DHISVersion.V2_33)).isFalse() + assertThat(dhisVersionManager.isVersion(DHISVersion.UNKNOWN)).isTrue() + + assertThat(dhisVersionManager.isGreaterThan(DHISVersion.V2_32)).isTrue() + assertThat(dhisVersionManager.isGreaterThan(DHISVersion.V2_33)).isTrue() + assertThat(dhisVersionManager.isGreaterThan(DHISVersion.UNKNOWN)).isFalse() + + assertThat(dhisVersionManager.isGreaterOrEqualThan(DHISVersion.V2_32)).isTrue() + assertThat(dhisVersionManager.isGreaterOrEqualThan(DHISVersion.V2_33)).isTrue() + assertThat(dhisVersionManager.isGreaterOrEqualThan(DHISVersion.UNKNOWN)).isTrue() } @Test(expected = D2Error::class) @@ -90,6 +110,16 @@ class DHISVersionManagerShould { assertThat(dhisVersionManager.getPatchVersion()).isNull() } + @Test + fun return_unknown_if_unknown_patch_version_and_bypass_dhis2_version_is_true() { + dhisVersionManager.setBypassVersion(true) + whenever(systemInfo.version()).thenReturn("2.100.100") + assertThat(dhisVersionManager.getPatchVersion()).isEqualTo(DHISPatchVersion.UNKNOWN) + + whenever(systemInfo.version()).thenReturn("2.100.0") + assertThat(dhisVersionManager.getPatchVersion()).isEqualTo(DHISPatchVersion.UNKNOWN) + } + @Test fun should_return_sms_version() { whenever(systemInfo.version()).thenReturn("2.39.5.1") diff --git a/core/src/test/java/org/hisp/dhis/android/core/systeminfo/internal/DHISVersionShould.kt b/core/src/test/java/org/hisp/dhis/android/core/systeminfo/internal/DHISVersionShould.kt index 7930d06d40..264609a055 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/systeminfo/internal/DHISVersionShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/systeminfo/internal/DHISVersionShould.kt @@ -28,19 +28,38 @@ package org.hisp.dhis.android.core.systeminfo.internal -import com.google.common.truth.Truth +import com.google.common.truth.Truth.assertThat import org.hisp.dhis.android.core.systeminfo.DHISVersion import org.junit.Test class DHISVersionShould { @Test fun return_null_for_unsupported_versions() { - val supportedVersions = listOf("2.29", "2.41") - DHISVersion.values() - .filter { supportedVersions.contains(it.prefix) } + DHISVersion.entries .forEach { - Truth.assertThat(DHISVersion.getValue(it.prefix + ".0")).isNull() - Truth.assertThat(DHISVersion.getValue(it.prefix + ".9")).isNull() + if (it.supported) { + assertThat(DHISVersion.getValue(it.prefix + ".0")).isNotNull() + assertThat(DHISVersion.getValue(it.prefix + ".9")).isNotNull() + } else { + assertThat(DHISVersion.getValue(it.prefix + ".0")).isNull() + assertThat(DHISVersion.getValue(it.prefix + ".9")).isNull() + } + } + } + + @Test + fun return_unknown_when_bypassing_version_for_unsupported_versions() { + DHISVersion.entries + .forEach { + if (it.supported) { + assertThat(DHISVersion.getValue(it.prefix + ".0", true)).isNotNull() + assertThat(DHISVersion.getValue(it.prefix + ".9", true)).isNotNull() + } else { + assertThat(DHISVersion.getValue(it.prefix + ".0", true)) + .isEqualTo(DHISVersion.UNKNOWN) + assertThat(DHISVersion.getValue(it.prefix + ".9", true)) + .isEqualTo(DHISVersion.UNKNOWN) + } } } } diff --git a/core/src/test/java/org/hisp/dhis/android/core/systeminfo/internal/PingImplShould.kt b/core/src/test/java/org/hisp/dhis/android/core/systeminfo/internal/PingImplShould.kt new file mode 100644 index 0000000000..740ac3479d --- /dev/null +++ b/core/src/test/java/org/hisp/dhis/android/core/systeminfo/internal/PingImplShould.kt @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.hisp.dhis.android.core.systeminfo.internal + +import com.google.common.truth.Truth.assertThat +import io.reactivex.plugins.RxJavaPlugins +import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.runBlocking +import okhttp3.ResponseBody.Companion.toResponseBody +import org.hisp.dhis.android.core.maintenance.D2Error +import org.hisp.dhis.android.core.maintenance.D2ErrorCode +import org.junit.Assert.assertTrue +import org.junit.Assert.fail +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations +import retrofit2.HttpException +import retrofit2.Response +import java.net.HttpURLConnection + +@RunWith(JUnit4::class) +class PingImplShould { + + @Mock + internal lateinit var service: PingService + + private lateinit var ping: PingImpl + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() } + + ping = PingImpl(service) + } + + @Test + fun get_should_return_success_when_ping_service_succeeds() { + val expectedResponse = "pong" + val responseBody = expectedResponse.toResponseBody() + val response = Response.success(responseBody) + runBlocking { + `when`(service.getPing()).thenReturn(response) + } + + assertThat(ping.blockingGet()).isEqualTo(expectedResponse) + } + + @Test + fun get_should_return_D2Error_when_ping_service_fails() { + val errorCode = HttpURLConnection.HTTP_INTERNAL_ERROR + val httpException = HttpException(Response.error(errorCode, "Internal Server Error".toResponseBody())) + + runBlocking { + `when`(service.getPing()).thenThrow(httpException) + } + + try { + ping.blockingGet() + fail("D2Error was expected but not thrown.") + } catch (e: RuntimeException) { + val cause = e.cause + assertTrue("Cause of RuntimeException should be D2Error", cause is D2Error) + + val d2Error = cause as D2Error + assertThat(d2Error.errorCode()).isEqualTo(D2ErrorCode.API_UNSUCCESSFUL_RESPONSE) + assertThat(d2Error.errorDescription()).isEqualTo("Unable to ping the server.") + assertThat((d2Error.originalException() as HttpException).code()).isEqualTo(errorCode) + } + } +} diff --git a/core/src/test/java/org/hisp/dhis/android/core/systeminfo/internal/SystemInfoCallShould.kt b/core/src/test/java/org/hisp/dhis/android/core/systeminfo/internal/SystemInfoCallShould.kt index b14ab87523..ebd7af911e 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/systeminfo/internal/SystemInfoCallShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/systeminfo/internal/SystemInfoCallShould.kt @@ -128,6 +128,14 @@ class SystemInfoCallShould { verify(resourceHandler).handleResource(eq(Resource.Type.SYSTEM_INFO)) } + @Test + fun invoke_set_version_and_check_bypass_Version_after_successful_call() = runTest { + systemInfoSyncCall.download(true) + + verify(versionManager).getBypassVersion() + verify(versionManager).setVersion(any()) + } + @Test fun throw_d2_call_exception_when_system_version_not_supported() = runTest { whenever(systemInfo.version()).thenReturn("2.28") diff --git a/core/src/test/java/org/hisp/dhis/android/core/trackedentity/NewTrackerImporterTrackedEntityPayloadGreaterEqualV41Should.kt b/core/src/test/java/org/hisp/dhis/android/core/trackedentity/NewTrackerImporterTrackedEntityPayloadGreaterEqualV41Should.kt new file mode 100644 index 0000000000..e42163727f --- /dev/null +++ b/core/src/test/java/org/hisp/dhis/android/core/trackedentity/NewTrackerImporterTrackedEntityPayloadGreaterEqualV41Should.kt @@ -0,0 +1,52 @@ +/* + * 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.android.core.trackedentity + +import com.fasterxml.jackson.core.type.TypeReference +import com.google.common.truth.Truth.assertThat +import org.hisp.dhis.android.core.arch.api.payload.internal.TrackerPayload +import org.hisp.dhis.android.core.common.BaseObjectShould +import org.hisp.dhis.android.core.common.ObjectShould +import org.junit.Test + +class NewTrackerImporterTrackedEntityPayloadGreaterEqualV41Should : + BaseObjectShould("trackedentity/new_tracker_importer_tracked_entities_greater_equal_v41.json"), + ObjectShould { + + @Test + override fun map_from_json_string() { + val trackedEntityPayload = objectMapper.readValue( + jsonStream, + object : TypeReference>() {}, + ) + + assertThat(trackedEntityPayload.pager()?.page).isEqualTo(1) + assertThat(trackedEntityPayload.pager()?.pageSize).isEqualTo(50) + assertThat(trackedEntityPayload.items().size).isEqualTo(2) + } +} diff --git a/core/src/test/java/org/hisp/dhis/android/core/trackedentity/NewTrackerImporterTrackedEntityPayloadLowerV41Should.kt b/core/src/test/java/org/hisp/dhis/android/core/trackedentity/NewTrackerImporterTrackedEntityPayloadLowerV41Should.kt new file mode 100644 index 0000000000..1c8731b2ed --- /dev/null +++ b/core/src/test/java/org/hisp/dhis/android/core/trackedentity/NewTrackerImporterTrackedEntityPayloadLowerV41Should.kt @@ -0,0 +1,52 @@ +/* + * 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.android.core.trackedentity + +import com.fasterxml.jackson.core.type.TypeReference +import com.google.common.truth.Truth.assertThat +import org.hisp.dhis.android.core.arch.api.payload.internal.TrackerPayload +import org.hisp.dhis.android.core.common.BaseObjectShould +import org.hisp.dhis.android.core.common.ObjectShould +import org.junit.Test + +class NewTrackerImporterTrackedEntityPayloadLowerV41Should : + BaseObjectShould("trackedentity/new_tracker_importer_tracked_entities_lower_v41.json"), + ObjectShould { + + @Test + override fun map_from_json_string() { + val trackedEntityPayload = objectMapper.readValue( + jsonStream, + object : TypeReference>() {}, + ) + + assertThat(trackedEntityPayload.pager()?.page).isEqualTo(1) + assertThat(trackedEntityPayload.pager()?.pageSize).isEqualTo(50) + assertThat(trackedEntityPayload.items().size).isEqualTo(2) + } +} diff --git a/core/src/test/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceHandlerShould.kt b/core/src/test/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceHandlerShould.kt index afbc6eabb8..381edfab6a 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceHandlerShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceHandlerShould.kt @@ -182,7 +182,6 @@ class TrackedEntityInstanceHandlerShould { @Test fun invoke_relationship_handler_with_relationship_from_version_manager() { - whenever(relationshipVersionManager.getRelativeTei(relationship, TEI_UID)).doReturn(relative) whenever(relationshipVersionManager.getOwnedRelationships(listOf(relationship), TEI_UID)) .doReturn(listOf(relationship)) whenever(relative.toBuilder()).doReturn(relativeBuilder) @@ -197,14 +196,11 @@ class TrackedEntityInstanceHandlerShould { listOf(relationship), TEI_UID, relatives, - relationshipHandler, ) } @Test fun do_not_invoke_relationship_repository_when_no_relative() { - whenever(relationshipVersionManager.getRelativeTei(relationship, TEI_UID)).doReturn(null) - trackedEntityInstanceHandler.handleMany(listOf(trackedEntityInstance), params, relationshipItemRelatives) verify(relationshipHandler, never()).handle(any()) diff --git a/core/src/test/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceImportHandlerShould.kt b/core/src/test/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceImportHandlerShould.kt index 9723814c6f..1759c527b2 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceImportHandlerShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/trackedentity/internal/TrackedEntityInstanceImportHandlerShould.kt @@ -35,9 +35,6 @@ import org.hisp.dhis.android.core.common.internal.DataStatePropagator import org.hisp.dhis.android.core.enrollment.internal.EnrollmentImportHandler import org.hisp.dhis.android.core.imports.ImportStatus import org.hisp.dhis.android.core.imports.internal.* -import org.hisp.dhis.android.core.relationship.RelationshipCollectionRepository -import org.hisp.dhis.android.core.relationship.internal.RelationshipDHISVersionManager -import org.hisp.dhis.android.core.relationship.internal.RelationshipStore import org.hisp.dhis.android.core.trackedentity.TrackedEntityAttributeValue import org.hisp.dhis.android.core.trackedentity.TrackedEntityInstance import org.hisp.dhis.android.core.tracker.importer.internal.JobReportTrackedEntityHandler @@ -64,14 +61,8 @@ class TrackedEntityInstanceImportHandlerShould { private val trackerImportConflictParser: TrackerImportConflictParser = mock() - private val relationshipStore: RelationshipStore = mock() - private val dataStatePropagator: DataStatePropagator = mock() - private val relationshipDHISVersionManager: RelationshipDHISVersionManager = mock() - - private val relationshipCollectionRepository: RelationshipCollectionRepository = mock() - private val jobReportTrackedEntityHandler: JobReportTrackedEntityHandler = mock() private val trackedEntityInstance: TrackedEntityInstance = mock() @@ -87,8 +78,7 @@ class TrackedEntityInstanceImportHandlerShould { fun setUp() { trackedEntityInstanceImportHandler = TrackedEntityInstanceImportHandler( trackedEntityInstanceStore, enrollmentImportHandler, trackerImportConflictStore, - trackerImportConflictParser, relationshipStore, dataStatePropagator, relationshipDHISVersionManager, - relationshipCollectionRepository, jobReportTrackedEntityHandler, + trackerImportConflictParser, dataStatePropagator, jobReportTrackedEntityHandler, ) whenever(trackedEntityInstanceStore.setSyncStateOrDelete(any(), any())).doReturn(HandleAction.Update) diff --git a/core/src/test/java/org/hisp/dhis/android/core/trackedentity/ownership/OwnershipManagerShould.kt b/core/src/test/java/org/hisp/dhis/android/core/trackedentity/ownership/OwnershipManagerShould.kt index ae2847a208..e84c6bde0a 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/trackedentity/ownership/OwnershipManagerShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/trackedentity/ownership/OwnershipManagerShould.kt @@ -36,6 +36,7 @@ import org.hisp.dhis.android.core.arch.api.executors.internal.CoroutineAPICallEx import org.hisp.dhis.android.core.common.internal.DataStatePropagator import org.hisp.dhis.android.core.imports.internal.HttpMessageResponse import org.hisp.dhis.android.core.maintenance.D2Error +import org.hisp.dhis.android.core.tracker.exporter.TrackerExporterParameterManager import org.junit.Assert.fail import org.junit.Before import org.junit.Test @@ -51,6 +52,7 @@ class OwnershipManagerShould { private val dataStatePropagator: DataStatePropagator = mock() private val programTempOwnerStore: ProgramTempOwnerStore = mock() private val programOwnerStore: ProgramOwnerStore = mock() + private val parameterManager: TrackerExporterParameterManager = mock() private val httpResponse: HttpMessageResponse = mock() @@ -68,6 +70,7 @@ class OwnershipManagerShould { dataStatePropagator, programTempOwnerStore, programOwnerStore, + parameterManager, ) } diff --git a/core/src/test/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryCallShould.kt b/core/src/test/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryCallShould.kt index 49f628f6ef..c84e22e7a3 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryCallShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryCallShould.kt @@ -91,7 +91,7 @@ class TrackedEntityInstanceQueryCallShould : BaseCallShould() { query = TrackedEntityInstanceQueryOnline( uids = listOf("uid1", "uid2"), orgUnits = orgUnits, - orgUnitMode = OrganisationUnitMode.ACCESSIBLE, + orgUnitMode = OrganisationUnitMode.DESCENDANTS, program = "program", programStartDate = Date(), programEndDate = Date(), @@ -100,7 +100,6 @@ class TrackedEntityInstanceQueryCallShould : BaseCallShould() { incidentStartDate = Date(), incidentEndDate = Date(), trackedEntityType = "teiTypeStr", - query = "queryStr", attributeFilter = attribute, includeDeleted = false, lastUpdatedStartDate = Date(), @@ -233,7 +232,7 @@ class TrackedEntityInstanceQueryCallShould : BaseCallShould() { ) = runBlocking { verify(trackedEntityService).query( eq(query.uids?.joinToString(";")), - eq(query.orgUnits.joinToString(";")), + getExpectedOrgunit(query.orgUnits, query.orgUnitMode), eq(query.orgUnitMode.toString()), eq(query.program), eq(query.programStage), @@ -247,15 +246,14 @@ class TrackedEntityInstanceQueryCallShould : BaseCallShould() { eq(query.eventEndDate.simpleDateFormat()), eq(expectedStatus?.toString()), eq(query.trackedEntityType), - eq(query.query), any(), eq(query.assignedUserMode?.toString()), eq(query.lastUpdatedStartDate.simpleDateFormat()), eq(query.lastUpdatedEndDate.simpleDateFormat()), any(), eq(query.paging), - eq(query.page), - eq(query.pageSize), + eq(query.page.takeIf { query.paging }), + eq(query.pageSize.takeIf { query.paging }), ) } @@ -274,7 +272,7 @@ class TrackedEntityInstanceQueryCallShould : BaseCallShould() { private fun verifyEventServiceForOrgunit(query: TrackedEntityInstanceQueryOnline, orgunit: String?) = runBlocking { verify(eventService).getEvents( eq(EventFields.teiQueryFields), - eq(orgunit), + getExpectedOrgunit(orgunit?.let { listOf(it) }, query.orgUnitMode), eq(query.orgUnitMode?.toString()), eq(query.eventStatus?.toString()), eq(query.program), @@ -289,8 +287,8 @@ class TrackedEntityInstanceQueryCallShould : BaseCallShould() { any(), eq(query.assignedUserMode?.toString()), eq(query.paging), - eq(query.page), - eq(query.pageSize), + eq(query.page.takeIf { query.paging }), + eq(query.pageSize.takeIf { query.paging }), eq(query.lastUpdatedStartDate.simpleDateFormat()), eq(query.lastUpdatedEndDate.simpleDateFormat()), eq(query.includeDeleted), @@ -298,6 +296,17 @@ class TrackedEntityInstanceQueryCallShould : BaseCallShould() { ) } + private fun getExpectedOrgunit(orgUnits: List?, mode: OrganisationUnitMode?): String? { + return if (mode == OrganisationUnitMode.ALL || + mode == OrganisationUnitMode.ACCESSIBLE || + mode == OrganisationUnitMode.CAPTURE + ) { + eq(null) + } else { + eq(orgUnits?.joinToString(";")) + } + } + private fun whenServiceQuery(answer: (InvocationOnMock) -> SearchGrid?) { trackedEntityService.stub { onBlocking { @@ -325,7 +334,6 @@ class TrackedEntityInstanceQueryCallShould : BaseCallShould() { anyOrNull(), anyOrNull(), anyOrNull(), - anyOrNull(), ) }.doAnswer(answer) } diff --git a/core/src/test/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryErrorCatcherShould.kt b/core/src/test/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryErrorCatcherShould.kt index b815367080..997d182283 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryErrorCatcherShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryErrorCatcherShould.kt @@ -28,7 +28,7 @@ package org.hisp.dhis.android.core.trackedentity.search import com.google.common.truth.Truth.assertThat -import okhttp3.ResponseBody +import okhttp3.ResponseBody.Companion.toResponseBody import org.hisp.dhis.android.core.maintenance.D2ErrorCode import org.junit.Test import org.junit.runner.RunWith @@ -43,7 +43,7 @@ class TrackedEntityInstanceQueryErrorCatcherShould { @Test fun return_too_many_orgunits() { val response = - Response.error(HttpsURLConnection.HTTP_REQ_TOO_LONG, ResponseBody.create(null, "")) + Response.error(HttpsURLConnection.HTTP_REQ_TOO_LONG, "".toResponseBody(null)) assertThat(catcher.catchError(response, response.errorBody()!!.string())) .isEqualTo(D2ErrorCode.TOO_MANY_ORG_UNITS) @@ -59,7 +59,7 @@ class TrackedEntityInstanceQueryErrorCatcherShould { "message": "maxteicountreached" }""" - val response = Response.error(409, ResponseBody.create(null, responseError)) + val response = Response.error(409, responseError.toResponseBody(null)) assertThat(catcher.catchError(response, response.errorBody()!!.string())) .isEqualTo(D2ErrorCode.MAX_TEI_COUNT_REACHED) } @@ -74,7 +74,23 @@ class TrackedEntityInstanceQueryErrorCatcherShould { "message": "Organisation unit is not part of the search scope: O6uvpzGd5pu" }""" - val response = Response.error(409, ResponseBody.create(null, responseError)) + val response = Response.error(409, responseError.toResponseBody(null)) + assertThat(catcher.catchError(response, response.errorBody()!!.string())) + .isEqualTo(D2ErrorCode.ORGUNIT_NOT_IN_SEARCH_SCOPE) + } + + @Test + fun return_orgunit_not_in_search_scope_v41() { + val responseError = + """{ + "httpStatus": "Forbidden", + "httpStatusCode": 403, + "status": "ERROR", + "message": "Organisation unit is not part of the search scope: vWbkYPRmKyS", + "errorCode": "E1006" + }""" + + val response = Response.error(403, responseError.toResponseBody(null)) assertThat(catcher.catchError(response, response.errorBody()!!.string())) .isEqualTo(D2ErrorCode.ORGUNIT_NOT_IN_SEARCH_SCOPE) } @@ -89,7 +105,7 @@ class TrackedEntityInstanceQueryErrorCatcherShould { "message": "At least 1 attributes should be mentioned in the search criteria." }""" - val response = Response.error(409, ResponseBody.create(null, responseError)) + val response = Response.error(409, responseError.toResponseBody(null)) assertThat(catcher.catchError(response, response.errorBody()!!.string())) .isEqualTo(D2ErrorCode.MIN_SEARCH_ATTRIBUTES_REQUIRED) } diff --git a/core/src/test/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryOnlineHelperShould.kt b/core/src/test/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryOnlineHelperShould.kt index 98f62eab7b..e9e92f4c8a 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryOnlineHelperShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/trackedentity/search/TrackedEntityInstanceQueryOnlineHelperShould.kt @@ -53,19 +53,6 @@ class TrackedEntityInstanceQueryOnlineHelperShould { onlineHelper = TrackedEntityInstanceQueryOnlineHelper(periodHelper) } - @Test - fun parse_query_in_api_format() { - val scope = queryBuilder - .query( - RepositoryScopeFilterItem.builder().key("").operator(FilterItemOperator.LIKE).value("filter").build(), - ) - .build() - val onlineQueries = onlineHelper.fromScope(scope) - - assertThat(onlineQueries.size).isEqualTo(1) - assertThat(onlineQueries[0].query).isEqualTo("LIKE:filter") - } - @Test fun parse_filters_using_in_operator() { val list = listOf( diff --git a/core/src/test/java/org/hisp/dhis/android/core/tracker/importer/JobReportSuccessShould.kt b/core/src/test/java/org/hisp/dhis/android/core/tracker/importer/JobReportSuccessShould.kt index 77c7a1bc35..c79368d3f6 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/tracker/importer/JobReportSuccessShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/tracker/importer/JobReportSuccessShould.kt @@ -65,7 +65,7 @@ class JobReportSuccessShould : BaseObjectShould("tracker/importer/jobreport-succ JobTypeReport( "EVENT", JobImportCount(2, 2, 2, 2, 8), - listOf(JobObjectReport(emptyList(), 0, TrackerImporterObjectType.EVENT, "UavzrupW3lZ")), + listOf(JobObjectReport(emptyList(), TrackerImporterObjectType.EVENT, "UavzrupW3lZ")), ), ) assertThat(bundleReport.typeReportMap.relationship).isEqualTo( diff --git a/core/src/test/java/org/hisp/dhis/android/core/user/UserGroupShould.java b/core/src/test/java/org/hisp/dhis/android/core/user/UserGroupShould.java new file mode 100644 index 0000000000..6ffa47cc2d --- /dev/null +++ b/core/src/test/java/org/hisp/dhis/android/core/user/UserGroupShould.java @@ -0,0 +1,60 @@ +/* + * 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.android.core.user; + +import static com.google.common.truth.Truth.assertThat; + +import org.hisp.dhis.android.core.common.BaseIdentifiableObject; +import org.hisp.dhis.android.core.common.BaseObjectShould; +import org.hisp.dhis.android.core.common.ObjectShould; +import org.junit.Test; + +import java.io.IOException; +import java.text.ParseException; + +public class UserGroupShould extends BaseObjectShould implements ObjectShould { + + public UserGroupShould() { + super("user/user_group.json"); + } + + @Override + @Test + public void map_from_json_string() throws IOException, ParseException { + UserGroup userGroup = objectMapper.readValue(jsonStream, UserGroup.class); + + assertThat(userGroup.lastUpdated()).isEqualTo( + BaseIdentifiableObject.DATE_FORMAT.parse("2024-02-07T14:00:59.251")); + assertThat(userGroup.created()).isEqualTo( + BaseIdentifiableObject.DATE_FORMAT.parse("2018-03-09T23:04:50.114")); + assertThat(userGroup.uid()).isEqualTo("Kk12LkEWtXp"); + assertThat(userGroup.displayName()).isEqualTo("_PROGRAM_TB program"); + assertThat(userGroup.name()).isEqualTo("_PROGRAM_TB program"); + } +} diff --git a/core/src/test/java/org/hisp/dhis/android/core/user/internal/LogInCallUnitShould.kt b/core/src/test/java/org/hisp/dhis/android/core/user/internal/LogInCallUnitShould.kt index 00ae1f478e..1172a69e40 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/user/internal/LogInCallUnitShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/user/internal/LogInCallUnitShould.kt @@ -43,8 +43,6 @@ import org.hisp.dhis.android.core.configuration.internal.MultiUserDatabaseManage import org.hisp.dhis.android.core.maintenance.D2Error import org.hisp.dhis.android.core.maintenance.D2ErrorCode import org.hisp.dhis.android.core.settings.internal.GeneralSettingCall -import org.hisp.dhis.android.core.systeminfo.DHISVersion -import org.hisp.dhis.android.core.systeminfo.DHISVersionManager import org.hisp.dhis.android.core.systeminfo.SystemInfo import org.hisp.dhis.android.core.systeminfo.internal.SystemInfoCall import org.hisp.dhis.android.core.user.AuthenticatedUser @@ -80,7 +78,6 @@ class LogInCallUnitShould : BaseCallShould() { private val multiUserDatabaseManager: MultiUserDatabaseManager = mock() private val generalSettingCall: GeneralSettingCall = mock() private val accountManager: AccountManagerImpl = mock() - private val versionManager: DHISVersionManager = mock() @Before @Throws(Exception::class) @@ -107,8 +104,6 @@ class LogInCallUnitShould : BaseCallShould() { generalSettingCall.stub { onBlocking { isDatabaseEncrypted() }.doReturn(false) } - - whenever(versionManager.getVersion()).thenReturn(DHISVersion.V2_39) } private suspend fun login() = instantiateCall(USERNAME, PASSWORD, serverUrl) @@ -118,7 +113,7 @@ class LogInCallUnitShould : BaseCallShould() { coroutineAPICallExecutor, userService, credentialsSecureStore, userIdStore, userHandler, authenticatedUserStore, systemInfoCall, userStore, LogInDatabaseManager(multiUserDatabaseManager, generalSettingCall), - LogInExceptions(credentialsSecureStore), accountManager, versionManager, apiErrorCatcher, + LogInExceptions(credentialsSecureStore), accountManager, apiErrorCatcher, ).logIn(username, password, serverUrl) } diff --git a/core/src/test/java/org/hisp/dhis/android/core/user/internal/UserHandlerShould.kt b/core/src/test/java/org/hisp/dhis/android/core/user/internal/UserHandlerShould.kt index 6c1459d820..155149a70a 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/user/internal/UserHandlerShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/user/internal/UserHandlerShould.kt @@ -33,6 +33,7 @@ import org.hisp.dhis.android.core.arch.handlers.internal.HandleAction import org.hisp.dhis.android.core.arch.handlers.internal.IdentifiableHandlerImpl import org.hisp.dhis.android.core.user.User import org.hisp.dhis.android.core.user.UserCredentials +import org.hisp.dhis.android.core.user.UserGroup import org.hisp.dhis.android.core.user.UserRole import org.junit.Before import org.junit.Test @@ -44,8 +45,11 @@ class UserHandlerShould { private val userStore: UserStore = mock() private val userRoleHandler: UserRoleHandler = mock() private val userRoleCollectionCleaner: UserRoleCollectionCleaner = mock() + private val userGroupHandler: UserGroupHandler = mock() + private val userGroupCollectionCleaner: UserGroupCollectionCleaner = mock() private val userRoles: List = mock() + private val userGroups: List = mock() private lateinit var user: User private lateinit var userCredentials: UserCredentials @@ -55,7 +59,10 @@ class UserHandlerShould { @Before fun setUp() { - userHandler = UserHandler(userStore, userRoleHandler, userRoleCollectionCleaner) + userHandler = UserHandler( + userStore, userRoleHandler, userRoleCollectionCleaner, userGroupHandler, + userGroupCollectionCleaner, + ) userCredentials = UserCredentials.builder() .username("username") .userRoles(userRoles) @@ -63,6 +70,7 @@ class UserHandlerShould { user = User.builder() .uid("userUid") .userCredentials(userCredentials) + .userGroups(userGroups) .build() whenever(userStore.updateOrInsert(any())).thenReturn(HandleAction.Insert) @@ -71,16 +79,25 @@ class UserHandlerShould { @Test fun extend_identifiable_sync_handler_impl() { val genericHandler: IdentifiableHandlerImpl = - UserHandler(userStore, userRoleHandler, userRoleCollectionCleaner) + UserHandler( + userStore, + userRoleHandler, + userRoleCollectionCleaner, + userGroupHandler, + userGroupCollectionCleaner, + ) assertThat(genericHandler).isNotNull() } @Test - fun add_username_and_roles_from_credentials() { + fun add_username_groups_and_roles_from_credentials() { userHandler.handle(user) verify(userRoleCollectionCleaner, times(1)).deleteNotPresent(eq(userRoles)) verify(userRoleHandler, times(1)).handleMany(eq(userRoles)) + + verify(userGroupCollectionCleaner, times(1)).deleteNotPresent(eq(userGroups)) + verify(userGroupHandler, times(1)).handleMany(eq(userGroups)) } } diff --git a/core/src/test/java/org/hisp/dhis/android/core/validation/engine/internal/ValidationExecutorShould.kt b/core/src/test/java/org/hisp/dhis/android/core/validation/engine/internal/ValidationExecutorShould.kt new file mode 100644 index 0000000000..0329466d70 --- /dev/null +++ b/core/src/test/java/org/hisp/dhis/android/core/validation/engine/internal/ValidationExecutorShould.kt @@ -0,0 +1,103 @@ +/* + * 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.validation.engine.internal + +import com.google.common.truth.Truth.assertThat +import com.nhaarman.mockitokotlin2.mock +import org.hisp.dhis.android.core.arch.helpers.UidGeneratorImpl +import org.hisp.dhis.android.core.category.internal.CategoryOptionComboStore +import org.hisp.dhis.android.core.dataelement.internal.DataElementStore +import org.hisp.dhis.android.core.organisationunit.OrganisationUnit +import org.hisp.dhis.android.core.organisationunit.internal.OrganisationUnitGroupStore +import org.hisp.dhis.android.core.parser.internal.service.ExpressionService +import org.hisp.dhis.android.core.parser.internal.service.ExpressionServiceContext +import org.hisp.dhis.android.core.period.Period +import org.hisp.dhis.android.core.period.PeriodType +import org.hisp.dhis.android.core.program.internal.ProgramStageStore +import org.hisp.dhis.android.core.validation.MissingValueStrategy +import org.hisp.dhis.android.core.validation.ValidationRule +import org.hisp.dhis.android.core.validation.ValidationRuleExpression +import org.hisp.dhis.android.core.validation.ValidationRuleImportance +import org.hisp.dhis.android.core.validation.ValidationRuleOperator +import org.junit.Test + +class ValidationExecutorShould { + + private val dataElementStore = mock() + private val categoryOptionComboStore = mock() + private val organisationUnitGroupStore = mock() + private val programStageStore = mock() + + private val uidGenerator = UidGeneratorImpl() + private val samplePeriod = Period.builder().periodId("202405").build() + private val sampleOrgunit = OrganisationUnit.builder().uid(uidGenerator.generate()).build() + + private val expressionService = ExpressionService( + dataElementStore, + categoryOptionComboStore, + organisationUnitGroupStore, + programStageStore, + ) + private val executor = ValidationExecutor(expressionService) + + @Test + fun should_return_null_if_invalid_expression() { + val validationRule = ValidationRule.builder() + .uid(uidGenerator.generate()) + .operator(ValidationRuleOperator.less_than_or_equal_to) + .leftSide( + ValidationRuleExpression.builder() + .expression("5") + .missingValueStrategy(MissingValueStrategy.SKIP_IF_ANY_VALUE_MISSING) + .description("Valid expression") + .build(), + ) + .rightSide( + ValidationRuleExpression.builder() + .expression("AVG((#{eY5ehpbEsB7})*1.5)") + .missingValueStrategy(MissingValueStrategy.SKIP_IF_ANY_VALUE_MISSING) + .description("Invalid expression") + .build(), + ) + .importance(ValidationRuleImportance.HIGH) + .periodType(PeriodType.Daily) + .skipFormValidation(false) + .organisationUnitLevels(emptyList()) + .build() + + val result = executor.evaluateRule( + rule = validationRule, + organisationUnit = sampleOrgunit, + context = ExpressionServiceContext(), + period = samplePeriod, + attributeOptionComboId = uidGenerator.generate(), + ) + + assertThat(result).isNull() + } +} diff --git a/core/src/test/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationShould.kt b/core/src/test/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationShould.kt new file mode 100644 index 0000000000..b2b45d9226 --- /dev/null +++ b/core/src/test/java/org/hisp/dhis/android/core/visualization/TrackerVisualizationShould.kt @@ -0,0 +1,67 @@ +/* + * 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.android.core.visualization + +import com.google.common.truth.Truth.assertThat +import org.hisp.dhis.android.core.arch.helpers.DateUtils +import org.hisp.dhis.android.core.common.BaseObjectShould +import org.hisp.dhis.android.core.common.ObjectShould +import org.junit.Test + +class TrackerVisualizationShould : BaseObjectShould("visualization/tracker_visualization.json"), ObjectShould { + + @Test + override fun map_from_json_string() { + val visualization = objectMapper.readValue(jsonStream, TrackerVisualization::class.java) + + assertThat(visualization.uid()).isEqualTo("s85urBIkN0z") + assertThat(visualization.name()).isEqualTo("TB program") + assertThat(visualization.displayName()).isEqualTo("TB program") + assertThat(visualization.description()).isEqualTo("Line list for TB program") + assertThat(visualization.displayDescription()).isEqualTo("Line list for TB program") + assertThat(visualization.created()).isEqualTo(DateUtils.DATE_FORMAT.parse("2024-02-07T07:37:41.116")) + assertThat(visualization.lastUpdated()).isEqualTo(DateUtils.DATE_FORMAT.parse("2024-02-07T07:43:25.556")) + assertThat(visualization.type()).isEqualTo(TrackerVisualizationType.LINE_LIST) + assertThat(visualization.outputType()).isEqualTo(TrackerVisualizationOutputType.ENROLLMENT) + assertThat(visualization.program()!!.uid()).isEqualTo("ur1Edk5Oe2n") + + assertThat(visualization.columns()?.size).isEqualTo(4) + val dataElementColumn = visualization.columns()!!.find { it.dimensionType() == "PROGRAM_DATA_ELEMENT" }!! + assertThat(dataElementColumn.dimension()).isEqualTo("fCXKBdc27Bt") + assertThat(dataElementColumn.program()!!.uid()).isEqualTo("ur1Edk5Oe2n") + assertThat(dataElementColumn.programStage()!!.uid()).isEqualTo("EPEcjy3FWmI") + assertThat(dataElementColumn.filter()).isEqualTo("IN:1") + assertThat(dataElementColumn.repetition()!!.indexes()).isEqualTo(listOf(1, 2, -2, -1, 0)) + + assertThat(visualization.filters()?.size).isEqualTo(1) + assertThat(visualization.filters()!![0].dimension()).isEqualTo("enrollmentDate") + assertThat(visualization.filters()!![0].dimensionType()).isEqualTo("PERIOD") + assertThat(visualization.filters()!![0].items()!!.size).isEqualTo(1) + assertThat(visualization.filters()!![0].items()!![0].uid()).isEqualTo("LAST_5_YEARS") + } +} diff --git a/core/src/test/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationHandlerShould.kt b/core/src/test/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationHandlerShould.kt new file mode 100644 index 0000000000..3160c8c03d --- /dev/null +++ b/core/src/test/java/org/hisp/dhis/android/core/visualization/internal/TrackerVisualizationHandlerShould.kt @@ -0,0 +1,79 @@ +/* + * 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.android.core.visualization.internal + +import com.nhaarman.mockitokotlin2.* +import org.hisp.dhis.android.core.arch.handlers.internal.HandleAction +import org.hisp.dhis.android.core.settings.internal.AnalyticsDhisVisualizationCleaner +import org.hisp.dhis.android.core.visualization.TrackerVisualization +import org.hisp.dhis.android.core.visualization.TrackerVisualizationDimension +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +class TrackerVisualizationHandlerShould { + + private val store: TrackerVisualizationStore = mock() + private val collectionCleaner: TrackerVisualizationCollectionCleaner = mock() + private val dimensionHandler: TrackerVisualizationDimensionHandler = mock() + private val analyticsDhisVisualizationCleaner: AnalyticsDhisVisualizationCleaner = mock() + private val dimension: TrackerVisualizationDimension = TrackerVisualizationDimension.builder().build() + private val trackerVisualization: TrackerVisualization = mock() + + // object to test + private lateinit var trackerVisualizationHandler: TrackerVisualizationHandler + + @Before + fun setUp() { + trackerVisualizationHandler = TrackerVisualizationHandler( + store, + collectionCleaner, + analyticsDhisVisualizationCleaner, + dimensionHandler, + ) + + whenever(trackerVisualization.columns()).doReturn(listOf(dimension)) + whenever(trackerVisualization.filters()).doReturn(listOf(dimension)) + whenever(store.updateOrInsert(any())).doReturn(HandleAction.Insert) + whenever(trackerVisualization.uid()).doReturn("tracker_visualization_uid") + } + + @Test + fun call_items_handler() { + trackerVisualizationHandler.handleMany(listOf(trackerVisualization)) + verify(dimensionHandler).handleMany(any(), any(), any()) + } + + @Test + fun call_collection_cleaner() { + trackerVisualizationHandler.handleMany(listOf(trackerVisualization)) + verify(collectionCleaner).deleteNotPresent(any()) + } +} diff --git a/core/src/test/java/org/hisp/dhis/android/core/visualization/internal/VisualizationHandlerShould.kt b/core/src/test/java/org/hisp/dhis/android/core/visualization/internal/VisualizationHandlerShould.kt index f1e2e2dbbe..09339bb3ce 100644 --- a/core/src/test/java/org/hisp/dhis/android/core/visualization/internal/VisualizationHandlerShould.kt +++ b/core/src/test/java/org/hisp/dhis/android/core/visualization/internal/VisualizationHandlerShould.kt @@ -29,6 +29,7 @@ package org.hisp.dhis.android.core.visualization.internal import com.nhaarman.mockitokotlin2.* import org.hisp.dhis.android.core.arch.handlers.internal.HandleAction +import org.hisp.dhis.android.core.settings.internal.AnalyticsDhisVisualizationCleaner import org.hisp.dhis.android.core.visualization.Visualization import org.hisp.dhis.android.core.visualization.VisualizationDimension import org.junit.Before @@ -41,6 +42,7 @@ class VisualizationHandlerShould { private val visualizationStore: VisualizationStore = mock() private val visualizationCollectionCleaner: VisualizationCollectionCleaner = mock() + private val analyticsDhisVisualizationCleaner: AnalyticsDhisVisualizationCleaner = mock() private val visualizationDimensionItemHandler: VisualizationDimensionItemHandler = mock() private val visualizationDimension: VisualizationDimension = mock() private val visualization: Visualization = mock() @@ -53,6 +55,7 @@ class VisualizationHandlerShould { visualizationHandler = VisualizationHandler( visualizationStore, visualizationCollectionCleaner, + analyticsDhisVisualizationCleaner, visualizationDimensionItemHandler, ) diff --git a/core/src/test/java/org/hisp/dhis/android/testapp/user/UserGroupPublicAccessShould.java b/core/src/test/java/org/hisp/dhis/android/testapp/user/UserGroupPublicAccessShould.java new file mode 100644 index 0000000000..4d1765da71 --- /dev/null +++ b/core/src/test/java/org/hisp/dhis/android/testapp/user/UserGroupPublicAccessShould.java @@ -0,0 +1,59 @@ +/* + * 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.android.testapp.user; + +import org.hisp.dhis.android.core.user.UserGroup; +import org.hisp.dhis.android.testapp.arch.BasePublicAccessShould; +import org.mockito.Mock; + +public class UserGroupPublicAccessShould extends BasePublicAccessShould { + + @Mock + private UserGroup object; + + @Override + public UserGroup object() { + return object; + } + + @Override + public void has_public_create_method() { + UserGroup.create(null); + } + + @Override + public void has_public_builder_method() { + UserGroup.builder(); + } + + @Override + public void has_public_to_builder_method() { + object().toBuilder(); + } +} \ No newline at end of file diff --git a/docs/content/developer/analytics.md b/docs/content/developer/analytics.md index ef2a58d636..bf18045d1b 100644 --- a/docs/content/developer/analytics.md +++ b/docs/content/developer/analytics.md @@ -409,6 +409,8 @@ This table shows the functionality supported by the ProgramIndicator dimension i | | Log | Yes | No | | | Log10 | Yes | No | | | PeriodOffset | Yes | No | +| | Contains | Yes | Yes | +| | ContainsItems | Yes | Yes | | **D2 functions:** | D2AddDays | No | No | | | D2Ceil | No | No | | | D2Concatenate | No | No | @@ -503,11 +505,92 @@ This table shows the functionality supported by the ProgramIndicator dimension i | CUSTOM | No | | DEFAULT | No | +## Tracker line list { #android_sdk_tracker_line_list } + +This kind of analytics is similar to those obtained through the Line Listing web app. It allows to generate line lists at event or enrollment level. These line list might include data elements, attributes, program indicators, variables and optionally filter those entries by the values in the their columns. + +A common use-case is to generate a list of event or enrollments that meet a certain criteria, such as a value within a particular range, or a certain status, or a certain date range. + +For example, a query that contains all the ACTIVE enrollments in the program "fbfJHSPpUQD" whose attribute "p4mRWMtCxtB" has a value between 40 and 50 would look like this. Note that the status is included as a filter, so there is not an explicit column for it. + +```kt +d2.analyticsModule().trackerLineList() + .withEnrollmentOutput("fbfJHSPpUQD") + .withFilter( + TrackerLineListItem.ProgramStatusItem( + filters = listOf( + EnumFilter.EqualTo(EnrollmentStatus.ACTIVE.name) + ) + ) + ) + .withColumn(TrackerLineListItem.OrganisationUnitItem()) + .withColumn( + TrackerLineListItem.ProgramAttribute( + uid = "p4mRWMtCxtB", + filters = listOf( + DataFilter.GreaterThan("40"), + DataFilter.LowerThan("50"), + ), + ), + ) + .evaluate() +``` + +The response is a Result object of TrackerLineListResponse, which has the following structure: + +```kt +TrackerLineListResponse( + metadata = mapOf( + "p4mRWMtCxtB" to MetadataItem.TrackedEntityAttributeItem + ), + headers = listOf( + TrackerLineListItem.OrganisationUnitItem, + TrackerLineListItem.ProgramAttribute + ), + filters = listOf( + TrackerLineListItem.ProgramStatusItem + ), + rows = listOf( + listOf( + TrackerLineListValue(id = "ouItem", value = "Child 1"), + TrackerLineListValue(id = "p4mRWMtCxtB", value = "45") + ), + listOf( + TrackerLineListValue(id = "ouItem", value = "Child 2"), + TrackerLineListValue(id = "p4mRWMtCxtB", value = "49") + ), + listOf( + TrackerLineListValue(id = "ouItem", value = "Child 2"), + TrackerLineListValue(id = "p4mRWMtCxtB", value = "41") + ) + ) +) +``` + +Optionally, it is possible to use a TrackerVisualization object (called EventVisualization in the API) to evaluate a predefined line list. The TrackerVisualization must have the type LINE_LIST. It is also possible to override a specific value. + +For example, this query uses the configuration in the TrackerVisualization "s85urBIkN0z" and adds or overrides the column ProgramStatusItem filtering by ACTIVE. + +```kt +d2.analyticsModule().trackerLineList() + .withTrackerVisualization("s85urBIkN0z") + .withColumn( + TrackerLineListItem.ProgramStatusItem( + filters = listOf( + EnumFilter.EqualTo(EnrollmentStatus.ACTIVE.name) + ) + ) + ) + .evaluate() +``` + +In order to use the TrackerVisualization objects, they must be set in the "Analytics" section of the Android Settings webapp. Currently, it is not possible to download on-demand visualizations from the server, just to downloaded through the Android Settings webapp. + ## Event line list { #android_sdk_event_line_list } -They are event-based analytics. If you are familiar with web analytic tools, it is very similar to Event Reports (line list). +They are event-based analytics. If you are familiar with web analytic tools, it is very similar to Event Reports (line list). It is a simplified case of Tracker Line List. -A common use-case it to generate an event line list of a repeatable stage in the context of a particular TEI in order to show the evolution of a particular value across the events. +A common use-case is to generate an event line list of a repeatable stage in the context of a particular TEI in order to show the evolution of a particular value across the events. For example, let's suppose we have a repeatable stage with two dataElements (height and weight) and one indicator based on those values (BMI, Body Mass Index). We would like to show the evolution of those values across the events diff --git a/docs/content/developer/apk-distribution.md b/docs/content/developer/apk-distribution.md index a3d0fdfd7a..ea10db6a89 100644 --- a/docs/content/developer/apk-distribution.md +++ b/docs/content/developer/apk-distribution.md @@ -3,4 +3,20 @@ The web app [APK distribution](https://apps.dhis2.org/app/dff273fc-909e-48af-b15 This parameter is automatically used by the official Android Capture app, which will offer the update when a new version is identified. Other custom applications might use this parameter to control their own updates. This logic must be implemented at application level. +The APK distribution app allows the definition of different versions by user group. The SDK will internally handle this logic and determine the version that must be used by the current user. + +The app can get this version by using the LatestAppVersion repository: + +```kt +d2.settingModule().latestAppVersion().get() +``` + +This version is updated with each metadata sync. To check for updates without triggering a full metadata sync, this method can be used: + +```kt +d2.settingModule().latestAppVersion().download() +``` + +Once the download is completed, the version can be read from the database as usual. + Check more information about the app in the [docs](https://docs.dhis2.org/en/use/android-app/apk-distribution.html). \ No newline at end of file diff --git a/docs/content/developer/compatibility.md b/docs/content/developer/compatibility.md index 45836f9456..a148428fcf 100644 --- a/docs/content/developer/compatibility.md +++ b/docs/content/developer/compatibility.md @@ -2,15 +2,16 @@ Compatibility table between DHIS2 Android SDK library, DHIS2 core and Android SDK API. -| SDK | DHIS2 core | Android SDK | Rule engine | -|-|-|-|-| -| 1.0.X | 2.29 -> 2.33 | 19 - 28 | - | -| 1.1.X | 2.29 -> 2.34 | 19 - 28 | - | -| 1.2.X | 2.29 -> 2.34 | 19 - 28 | - | -| 1.3.X | 2.29 -> 2.35 | 19 - 29 | 2.0.10 - 2.0.15 | -| 1.4.X | 2.29 -> 2.36 | 19 - 29 | 2.0.17 - 2.0.20 | -| 1.5.X | 2.29 -> 2.37 | 19 - 29 | 2.0.25 - 2.0.27 | -| 1.6.X | 2.30 -> 2.38 | 21 - 31 | 2.0.34 - 2.0.35 | -| 1.7.X | 2.30 -> 2.39 | 21 - 31 | 2.0.42 - 2.0.43 | -| 1.8.X | 2.30 -> 2.40 | 21 - 33 | 2.0.47 - 2.0.48 | -| 1.9.X | 2.30 -> 2.40 | 21 - 34 | 2.0.47 - 2.0.48 | +| SDK | DHIS2 core | Android SDK | Rule engine | +|--------|--------------|-|-| +| 1.0.X | 2.29 -> 2.33 | 19 - 28 | - | +| 1.1.X | 2.29 -> 2.34 | 19 - 28 | - | +| 1.2.X | 2.29 -> 2.34 | 19 - 28 | - | +| 1.3.X | 2.29 -> 2.35 | 19 - 29 | 2.0.10 - 2.0.15 | +| 1.4.X | 2.29 -> 2.36 | 19 - 29 | 2.0.17 - 2.0.20 | +| 1.5.X | 2.29 -> 2.37 | 19 - 29 | 2.0.25 - 2.0.27 | +| 1.6.X | 2.30 -> 2.38 | 21 - 31 | 2.0.34 - 2.0.35 | +| 1.7.X | 2.30 -> 2.39 | 21 - 31 | 2.0.42 - 2.0.43 | +| 1.8.X | 2.30 -> 2.40 | 21 - 33 | 2.0.47 - 2.0.48 | +| 1.9.X | 2.30 -> 2.40 | 21 - 34 | 2.0.47 - 2.0.48 | +| 1.10.X | 2.30 -> 2.41 | 21 - 34 | 2.0.47 - 2.0.48 | diff --git a/docs/content/developer/database.md b/docs/content/developer/database.md index 69721ba839..9701d3cd4c 100644 --- a/docs/content/developer/database.md +++ b/docs/content/developer/database.md @@ -1,9 +1,9 @@ # Database { #android_sdk_database } -## Database scope +## Database scope { #android_sdk_database_scope } The SDK keeps the data of a [server, user] pair in an isolated database. As of version 1.6.0, the SDK supports multiple accounts (pairs [server, user]) and the information for each account is stored in an isolated database. The database is deleted only when the account is deleted. Databases are created automatically on a successful login. -## Encryption +## Encryption { #android_sdk_database_encryption } As of SDK version 1.1.0, it is possible to store the data in an encrypted database. The encryption key is generated randomly by the SDK and kept secure. @@ -18,4 +18,28 @@ if changed, will encrypt or decrypt the current database without data loss. ### Encryption performance - Database size: the database size is approximately the same, regardless of being encrypted or not. -- Speed: reads and writes are on average 5 to 10% slower using an encrypted database. \ No newline at end of file +- Speed: reads and writes are on average 5 to 10% slower using an encrypted database. + +## Import / export { #android_sdk_database_import_export } +The database can be exported and imported in a different device. + +One of the main use cases of this functionality is debugging: sometimes it is hard to know the reason for a sync problem or a bug, and it is very useful to replicate the issue in an emulator or a different device. + +```kt +// Export database +val database = d2.maintenanceModule().databaseImportExport().exportLoggedUserDatabase() + +// Import database +val metadata = d2.maintenanceModule().databaseImportExport().importDatabase(database) + +// The metadata object contains information about the database (serverUrl, username,...) + +// Once the database is imported, it is possible to login as usual +d2.userModule().login("username", "password", "serverUrl") +``` + +The export process encrypts the database using ZIP encryption, so the database file can't be read unless the right user credentials are provided. + +Things to consider: +- The exported file only contains the database, it does not contain file resources (images, icons, files,...). +- The receiver device must run an **SDK version that is equal or higher** than the SDK version used to export the database. \ No newline at end of file diff --git a/docs/content/developer/getting-started.md b/docs/content/developer/getting-started.md index 39faf63ff0..51aa430148 100644 --- a/docs/content/developer/getting-started.md +++ b/docs/content/developer/getting-started.md @@ -6,7 +6,7 @@ Include dependency in build.gradle. ```gradle dependencies { - implementation "org.hisp.dhis:android-core:1.9.1" + implementation "org.hisp.dhis:android-core:1.10.0" ... } ``` diff --git a/docs/content/developer/maps.md b/docs/content/developer/maps.md index c3409bec5a..77e500d3b3 100644 --- a/docs/content/developer/maps.md +++ b/docs/content/developer/maps.md @@ -1,9 +1,5 @@ # Maps { #android_sdk_maps } -> **Caution** -> -> This is an experimental feature and could change in future releases. - The SDK has a MapModule to download and access the information relative to maps in DHIS2. By default, DHIS2 has some basemaps: @@ -11,12 +7,18 @@ By default, DHIS2 has some basemaps: - OpenStreetMaps - Bing: it is required to setup a bing api key in the server to make them work. -These map layers are downloaded in a separate call: +Additionally, it is possible to define custom basemaps in the Maintenance app. + +All these map layers are downloaded in a separate call: ```java d2.mapsModule().mapLayersDownloader().downloadMetadata() ``` +> **Important** +> +> The SDK only downloads maps with type BASEMAP. + Then, map layers can be accessed by using the corresponding collection repository, as usual: ```java @@ -31,5 +33,6 @@ These map layers contain useful information to display them using a SDK for maps - imageUrl: it might look like `https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`. - subdomains: map providers usually offer a list of subdomains, for example `["a", "b", "c", "d"]`. - subdomainPlaceholder: the token that must be replaced in the imageUrl, in this case `{s}`. +- external: whether the map is user-defined (external) or built-in (bing, openstreetmaps). It is straightforward to get the list of imageUrls by iterating the subdomain list and using the placeholder to replace it in the url. diff --git a/docs/content/developer/object-style.md b/docs/content/developer/object-style.md index a6686a9b3b..51dc3aa247 100644 --- a/docs/content/developer/object-style.md +++ b/docs/content/developer/object-style.md @@ -10,14 +10,37 @@ This property is optional and it is not defined in most of the cases. An object ## Icon -The set of icons used in DHIS2 are included in the SDK. They are located within the resources, in the "drawable" folder. Currently they are predefined and cannot be customized. +DHIS2 includes a predefined set of icons. Those icons are included in the SDK and are located within the resources, in the "drawable" folder. -```java +Staring on version v41, it is possible to upload user-defined icons and assign them to metadata objects. The actual images of these icons are stored as a FileResource and must be explicitly downloaded in a separate query. -// Illustrative code to get the resource id -if (program.style().icon() != null) { - String iconName = program.style().icon(); - int resourceId = getResources().getIdentifier(iconName, "drawable", getPackageName()); +```kt +d2.fileResourceModule().fileResourceDownloader() + .byDomainType().eq(FileResourceDomainType.ICON) + .download() +``` + +You can get the information about a particular icon by using the IconCollectionRepository. It returns an Icon object, which is a sealed class with two possible values: Default or Custom. + +The way to render the actual image will depend on the Icon type. + +```kt + +val icon = d2.iconModule().icons().key("icon_key").blockingGet() + +icon?.let { + when (icon) { + is Icon.Custom -> { + val path = icon.path + val fileResourceUid = icon.fileResourceUid + // Use this information to load the file + } + + is Icon.Default -> { + val resourceName = icon.key + // Use this information to load the resource + } + } } ``` @@ -25,7 +48,6 @@ if (program.style().icon() != null) { It contains the Hex value for the color. It can be used to customize the background, text color, line headings, etc. -```java -program.style().color(); // For example #9C33FF - +```kt +program.style().color() // For example #9C33FF ``` diff --git a/docs/content/developer/settings.md b/docs/content/developer/settings.md index 15f1b57b02..d676af10a5 100644 --- a/docs/content/developer/settings.md +++ b/docs/content/developer/settings.md @@ -28,6 +28,7 @@ It gives additional information about app settings: - **Encrypt database**: whether or not to encrypt local database. - **Reserved values**: number of attribute values to reserve. It might be overridden by the app. +- **Bypass DHIS2 version**: if true, the SDK will try to connect to the instance no matter the DHIS2 version installed. - Mobile configuration: gateway number, result sender number. They must be consumed by the application and used to configure the SMS module in the SDK. - Matomo configuration: if you have your own Matomo instance, you can expose this information to the app in order to configure its Matomo client. - AllowScreenCapture: parameter to determine if the application should allow screen capture or not. diff --git a/docs/content/developer/workflow.md b/docs/content/developer/workflow.md index 26c9c94f90..c1a0cb53da 100644 --- a/docs/content/developer/workflow.md +++ b/docs/content/developer/workflow.md @@ -29,12 +29,15 @@ The number of maximum allowed accounts can be configured by the app (it defaults // Get the account list d2.userModule().accountManager().getAccounts(); -// Get/set the maximum number of accounts -d2.userModule().accountManager().getMaxAccounts(); -d2.userModule().accountManager().setMaxAccounts(); +// Get the account for current user, or null if the user is not authenticated yet +d2.userModule().accountManager().getCurrentAccount(); // Delete account for current user d2.userModule().accountManager().deleteCurrentAccount(); + +// Get/set the maximum number of accounts +d2.userModule().accountManager().getMaxAccounts(); +d2.userModule().accountManager().setMaxAccounts(); ``` The accountManager exposes an observable that emits an event when the current account is deleted. It includes the reason why the account was deleted. @@ -530,14 +533,6 @@ Data whose state is `ERROR` or `WARNING` cannot be uploaded. It is required to s As of version 2.37, a new tracker importer was introduced (`/api/tracker` endpoint). The default tracker importer is still the legacy one (`/api/trackedEntityInstances`), but you can opt-in to use this new tracker importer by using the Android Settings webapp (see [Synchronization](#android_sdk_synchronization_settings)). This is internal to the SDK; the API exposed to the app does not change. -File resources must be uploaded in a different post call before tracker data upload. The query to post file resources is: - -```java -d2.fileResourceModule().fileResources().upload(); -``` - -More information about file resources in the section [*Dealing with FileResources*](#android_sdk_file_resources). - #### Tracker conflicts Server response is parsed to ensure that data has been correctly uploaded to the server. In case the server response includes import conflicts, these conflicts are stored in the database, so the app can check them and take an action to solve them. @@ -625,6 +620,16 @@ d2.trackedEntityModule().trackedEntityInstanceService() .inheritAttributes("fromTeiUid", "toTeiUid", "programUid"); ``` +In order to access the `dataElements` and `attributes` associated to a `relationshipConstraint`, they can be accessed through the `trackerDataView` property as in the following examples: + +```java +relationshipType.toConstraint().trackerDataView().attributes(); +``` + +```java +relationshipType.toConstraint().trackerDataView().dataElements(); +``` + ## Aggregated data { #android_sdk_aggregated_data } ### Aggregated data download @@ -781,13 +786,14 @@ This module contains methods to download the file resources associated with the - **File resources download**. The `fileResourceDownloader()` offers methods to filter the fileResources we want to download. It will search for values that match the filters and whose file resource has not been previously downloaded. - ```java + ```kt d2.fileResourceModule().fileResourceDownloader() - .byDomainType().eq(FileResourceDomainType.TRACKER) - .byElementType().eq(FileResourceElementType.DATA_ELEMENT) - .byValueType().in(FileResourceValueType.IMAGE, FileResourceValueType.FILE_RESOURCE) - .byMaxContentLength().eq(2000000) - .download(); + .byDomainType().eq(FileResourceDomainType.DATA_VALUE) + .byDataDomainType().eq(FileResourceDataDomainType.TRACKER) + .byElementType().eq(FileResourceElementType.DATA_ELEMENT) + .byValueType().in(FileResourceValueType.IMAGE, FileResourceValueType.FILE_RESOURCE) + .byMaxContentLength().eq(2000000) + .download() ``` The SDK has a default maxContentLength of 6000000. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 70a7d9a925..7f1ebb1edc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,11 +1,10 @@ [versions] -gradle = "8.2.0" +gradle = "8.3.1" kotlin = "1.9.21" ktlint = "11.5.1" -jacoco = "0.8.10" sonarqube = "3.3" detekt = "1.18.0" -dokka = "1.9.10" +dokka = "1.9.20" targetSdkVersion = "34" minSdkVersion = "21" @@ -14,7 +13,7 @@ libraryDesugaring = "2.0.4" # android annotation = "1.6.0" -paging = "2.1.2" +paging = "3.2.1" # java jackson = "2.13.4" @@ -26,8 +25,9 @@ rxJava = "2.2.21" rxAndroid = "2.1.1" sqlCipher = "4.5.5" sqlLite = "2.2.0" +zip4j = "2.11.5" smsCompression = "0.2.0" -expressionParser = "1.0.33" +expressionParser = "1.0.36" # Kotlin kotlinxDatetime = "0.4.1" @@ -59,7 +59,6 @@ listenablefuture = "9999.0-empty-to-avoid-conflict-with-guava" gradle = { group = "com.android.tools.build", name = "gradle", version.ref = "gradle" } kotlin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } ktlint = { group = "org.jlleitschuh.gradle", name = "ktlint-gradle", version.ref = "ktlint" } -jacoco = { group = "org.jacoco", name = "org.jacoco.core", version.ref = "jacoco" } desugaring = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "libraryDesugaring" } @@ -67,7 +66,8 @@ rx-java = { group = "io.reactivex.rxjava2", name = "rxjava", version.ref = "rxJa rx-android = { group = "io.reactivex.rxjava2", name = "rxandroid", version.ref = "rxAndroid" } androidx-annotation = { group = "androidx.annotation", name = "annotation", version.ref = "annotation" } -androidx-paging = { group = "androidx.paging", name = "paging-runtime", version.ref = "paging" } +androidx-paging-runtime = { group = "androidx.paging", name = "paging-runtime", version.ref = "paging" } +androidx-paging-testing = { group = "androidx.paging", name = "paging-testing", version.ref = "paging" } google-auto-value-annotation = { group = "com.google.auto.value", name = "auto-value-annotations", version.ref = "autoValue" } google-auto-value = { group = "com.google.auto.value", name = "auto-value", version.ref = "autoValue" } @@ -101,6 +101,8 @@ dhis2-antlr-parser = { group = "org.hisp.dhis.parser", name = "dhis-antlr-expres sqlcipher = { group = "net.zetetic", name = "sqlcipher-android", version.ref = "sqlCipher" } sqlite = { group = "androidx.sqlite", name = "sqlite", version.ref = "sqlLite" } +zip4j = { group = "net.lingala.zip4j", name = "zip4j", version.ref = "zip4j"} + openid-appauth = { group = "net.openid", name = "appauth", version.ref = "appauth" } listenablefuture-empty = { group = "com.google.guava", name = "listenablefuture", version.ref = "listenablefuture" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a2a67a7b3d..7166abc811 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Thu Aug 31 00:09:15 CEST 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/instrumented-test-app/build.gradle.kts b/instrumented-test-app/build.gradle.kts index bdcbddc62e..82bca695f4 100644 --- a/instrumented-test-app/build.gradle.kts +++ b/instrumented-test-app/build.gradle.kts @@ -28,11 +28,10 @@ plugins { id("com.android.application") + id("jacoco-conventions") kotlin("android") } -apply(from = project.file("../core/plugins/jacoco.gradle.kts")) - android { compileSdk = libs.versions.targetSdkVersion.get().toInt()