Skip to content

Commit

Permalink
Refactor register filter with REL tags (#3488)
Browse files Browse the repository at this point in the history
* Refactor register filter with REL tags

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Update default pageSize to 15

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Fix loading locations on map

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Refactor data structure used on base resource search results

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Refactor code

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Delete unnecessary code

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Refactor retrieving related resources

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Optimize data structures and perform parallel processing

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Use recent version of rules engine library

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Refactor implementation for decoding image resources to bitmap

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Fix related resource count on register

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Fix loading related resources

This fix ensures all the nested related resources are loaded too.

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Batch related resource queries

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Map resources to RepositoryResourceData with async map

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Make infinite scroll the default register behavior

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Disable automatic intialization of emoji2

A lot of memory was used in heap during the allocation. Emojis are not
used in the app so intializing them automatically is unnecessary.

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* ⬆️ Update the map box and kujaku versions

* Run spotlessApply

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Update tests for displaying images (#3506)

* Refactor load images tests for different views.

Signed-off-by: Lentumunai-Mark <lentumunai.mark@students.jkuat.ac.ke>

* Remove unutilized imports.

Signed-off-by: Lentumunai-Mark <lentumunai.mark@students.jkuat.ac.ke>

* Run spotlessApply

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>
Signed-off-by: Lentumunai-Mark <lentumunai.mark@students.jkuat.ac.ke>

* Refactor load images tests for different views.

Signed-off-by: Lentumunai-Mark <lentumunai.mark@students.jkuat.ac.ke>

* Resolve conflicts.

Signed-off-by: Lentumunai-Mark <lentumunai.mark@students.jkuat.ac.ke>

---------

Signed-off-by: Lentumunai-Mark <lentumunai.mark@students.jkuat.ac.ke>
Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Refactor navigation to GeowidgetLauncher workflow

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Load map data in batches (#3511)

* Load map data in batches

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Update observer

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Deactivate infinite scroll by default

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

---------

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Fix failing tests

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Batch searching when list might be large or when count is not defined (#3456)

* Fetch search results in batches for when loading all

* Fix infinite loop for mocks with FhirEngine#search in tests

* Add comparable FhirEngine#search vs batchedSearch integration tests

* Run spotlessApply

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Fix Geowidget tests

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Fix spotlessCheck

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Add missing import

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Fix rules execution before map render

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* - Update android manifest to use exported values

* Prevent leaking map features via viewmodel

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Format code

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Only search map features via keyboard action

We need to reload all features when the search term is reset

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

---------

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>
Signed-off-by: Lentumunai-Mark <lentumunai.mark@students.jkuat.ac.ke>
Co-authored-by: Benjamin Mwalimu <dubdabasoduba@gmail.com>
Co-authored-by: Lentumunai Mark <90028422+Lentumunai-Mark@users.noreply.github.com>
Co-authored-by: L≡ZRS <12814349+LZRS@users.noreply.github.com>
  • Loading branch information
4 people committed Sep 20, 2024
1 parent 041762f commit 62c9e89
Show file tree
Hide file tree
Showing 76 changed files with 1,480 additions and 1,077 deletions.
6 changes: 1 addition & 5 deletions android/engine/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,7 @@ dependencies {
api(libs.timber)
api(libs.converter.gson)
api(libs.json.path)
api(libs.commons.jexl3) { exclude(group = "commons-logging", module = "commons-logging") }
api(libs.easy.rules.jexl) {
exclude(group = "commons-logging", module = "commons-logging")
exclude(group = "org.apache.commons", module = "commons-jexl3")
}
api(libs.easy.rules.jexl) { exclude(group = "commons-logging", module = "commons-logging") }
api(libs.data.capture) {
isTransitive = true
exclude(group = "ca.uhn.hapi.fhir")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2021-2024 Ona Systems, Inc
*
* 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.
*/

package org.smartregister.fhircore.engine.util.extension

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.MediumTest
import com.google.android.fhir.FhirEngine
import com.google.android.fhir.FhirEngineConfiguration
import com.google.android.fhir.FhirEngineProvider
import com.google.android.fhir.search.search
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.hl7.fhir.r4.model.Patient
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.Resource
import org.hl7.fhir.r4.model.ResourceType
import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Test

@MediumTest
class FhirEngineExtensionKtTest {

private val context = ApplicationProvider.getApplicationContext<Context>()
private lateinit var fhirEngine: FhirEngine

@Before
fun setUp() {
FhirEngineProvider.init(FhirEngineConfiguration(testMode = true))
fhirEngine = FhirEngineProvider.getInstance(context)

val patients = (0..1000).map { Patient().apply { id = "test-patient-$it" } }
val questionnaires = (0..3).map { Questionnaire().apply { id = "test-questionnaire-$it" } }
runBlocking { fhirEngine.create(*patients.toTypedArray(), *questionnaires.toTypedArray()) }
}

@After
fun tearDown() {
runBlocking { fhirEngine.clearDatabase() }
FhirEngineProvider.cleanup()
}

@Test
fun test_search_time_searches_sequentially_and_short_running_query_waits() {
val fetchedResources = mutableListOf<Resource>()
runBlocking {
launch {
val patients = fhirEngine.search<Patient> {}.map { it.resource }
fetchedResources += patients
}

launch {
val questionnaires = fhirEngine.search<Questionnaire> {}.map { it.resource }
fetchedResources += questionnaires
}
}
val indexOfResultOfShortQuery =
fetchedResources.indexOfFirst { it.resourceType == ResourceType.Questionnaire }
val indexOfResultOfLongQuery =
fetchedResources.indexOfFirst { it.resourceType == ResourceType.Patient }
Assert.assertTrue(indexOfResultOfShortQuery > indexOfResultOfLongQuery)
}

@Test
fun test_batchedSearch_returns_short_running_query_and_long_running_does_not_block() {
val fetchedResources = mutableListOf<Resource>()
runBlocking {
launch {
val patients = fhirEngine.batchedSearch<Patient> {}.map { it.resource }
fetchedResources += patients
}

launch {
val questionnaires = fhirEngine.search<Questionnaire> {}
fetchedResources + questionnaires
}
}

val indexOfResultOfShortQuery =
fetchedResources.indexOfFirst { it.resourceType == ResourceType.Questionnaire }
val indexOfResultOfLongQuery =
fetchedResources.indexOfFirst { it.resourceType == ResourceType.Patient }
Assert.assertTrue(indexOfResultOfShortQuery < indexOfResultOfLongQuery)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import java.io.File
import java.io.FileNotFoundException
import java.io.InputStreamReader
import java.net.UnknownHostException
import java.util.LinkedList
import java.util.Locale
import java.util.PropertyResourceBundle
import java.util.ResourceBundle
Expand Down Expand Up @@ -61,8 +60,6 @@ import org.smartregister.fhircore.engine.configuration.app.ApplicationConfigurat
import org.smartregister.fhircore.engine.configuration.app.ConfigService
import org.smartregister.fhircore.engine.data.remote.fhir.resource.FhirResourceDataSource
import org.smartregister.fhircore.engine.di.NetworkModule
import org.smartregister.fhircore.engine.domain.model.FhirResourceConfig
import org.smartregister.fhircore.engine.domain.model.ResourceConfig
import org.smartregister.fhircore.engine.util.DispatcherProvider
import org.smartregister.fhircore.engine.util.SharedPreferenceKey
import org.smartregister.fhircore.engine.util.SharedPreferencesHelper
Expand Down Expand Up @@ -376,19 +373,23 @@ constructor(
* @return A list of strings of config files.
*/
private fun retrieveAssetConfigs(context: Context, appId: String): MutableList<String> {
val filesQueue = LinkedList<String>()
val filesQueue = ArrayDeque<String>()
val configFiles = mutableListOf<String>()
context.assets.list(String.format(BASE_CONFIG_PATH, appId))?.onEach {
if (!supportedFileExtensions.contains(it.fileExtension)) {
filesQueue.addLast(String.format(BASE_CONFIG_PATH, appId) + "/$it")
} else configFiles.add(String.format(BASE_CONFIG_PATH, appId) + "/$it")
} else {
configFiles.add(String.format(BASE_CONFIG_PATH, appId) + "/$it")
}
}
while (filesQueue.isNotEmpty()) {
val currentPath = filesQueue.removeFirst()
context.assets.list(currentPath)?.onEach {
if (!supportedFileExtensions.contains(it.fileExtension)) {
filesQueue.addLast("$currentPath/$it")
} else configFiles.add("$currentPath/$it")
} else {
configFiles.add("$currentPath/$it")
}
}
}
return configFiles
Expand Down Expand Up @@ -510,13 +511,14 @@ constructor(
val resultBundle =
if (isNonProxy()) {
fhirResourceDataSourceGetBundle(resourceType, resourceIdList)
} else
} else {
fhirResourceDataSource.post(
requestBody =
generateRequestBundle(resourceType, resourceIdList)
.encodeResourceToString()
.toRequestBody(NetworkModule.JSON_MEDIA_TYPE),
)
}

processResultBundleEntries(resultBundle.entry)

Expand Down Expand Up @@ -730,16 +732,6 @@ constructor(
}
}

private fun FhirResourceConfig.dependentResourceTypes(target: MutableList<ResourceType>) {
this.baseResource.dependentResourceTypes(target)
this.relatedResources.forEach { it.dependentResourceTypes(target) }
}

private fun ResourceConfig.dependentResourceTypes(target: MutableList<ResourceType>) {
target.add(resource)
relatedResources.forEach { it.dependentResourceTypes(target) }
}

suspend fun loadResourceSearchParams():
Pair<Map<String, Map<String, String>>, ResourceSearchParams> {
val syncConfig = retrieveResourceConfiguration<Parameters>(ConfigType.Sync)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import kotlinx.serialization.Serializable
import org.smartregister.fhircore.engine.domain.model.ActionConfig
import org.smartregister.fhircore.engine.util.extension.interpolate

const val ICON_TYPE_LOCAL = "local"
const val ICON_TYPE_REMOTE = "remote"

@Serializable
@Parcelize
data class NavigationMenuConfig(
Expand Down Expand Up @@ -53,9 +56,6 @@ data class ImageConfig(
}
}

const val ICON_TYPE_LOCAL = "local"
const val ICON_TYPE_REMOTE = "remote"

enum class ImageType {
JPEG,
PNG,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ data class RegisterConfiguration(
val filterDataByRelatedEntityLocation: Boolean = false,
val topScreenSection: TopScreenSectionConfig? = null,
val onSearchByQrSingleResultActions: List<ActionConfig>? = null,
val infiniteScroll: Boolean = false,
) : Configuration() {
val onSearchByQrSingleResultValidActions =
onSearchByQrSingleResultActions?.filter { it.trigger == ActionTrigger.ON_SEARCH_SINGLE_RESULT }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ data class ButtonProperties(
val interpolated = this.status.interpolate(computedValuesMap)
return if (ServiceStatus.values().map { it.name }.contains(interpolated)) {
ServiceStatus.valueOf(interpolated)
} else ServiceStatus.UPCOMING
} else {
ServiceStatus.UPCOMING
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package org.smartregister.fhircore.engine.configuration.view

import java.util.LinkedList
import kotlinx.serialization.Serializable
import org.smartregister.fhircore.engine.domain.model.ViewType

Expand Down Expand Up @@ -47,18 +46,17 @@ abstract class ViewProperties : java.io.Serializable {
*/
fun List<ViewProperties>.retrieveListProperties(): List<ListProperties> {
val listProperties = mutableListOf<ListProperties>()
val viewPropertiesLinkedList: LinkedList<ViewProperties> = LinkedList(this)
while (viewPropertiesLinkedList.isNotEmpty()) {
val properties = viewPropertiesLinkedList.removeFirst()
val viewPropertiesQueue: ArrayDeque<ViewProperties> = ArrayDeque(this)
while (viewPropertiesQueue.isNotEmpty()) {
val properties = viewPropertiesQueue.removeFirst()
if (properties.viewType == ViewType.LIST) {
listProperties.add(properties as ListProperties)
}
when (properties.viewType) {
ViewType.COLUMN -> viewPropertiesLinkedList.addAll((properties as ColumnProperties).children)
ViewType.ROW -> viewPropertiesLinkedList.addAll((properties as RowProperties).children)
ViewType.CARD -> viewPropertiesLinkedList.addAll((properties as CardViewProperties).content)
ViewType.LIST ->
viewPropertiesLinkedList.addAll((properties as ListProperties).registerCard.views)
ViewType.COLUMN -> viewPropertiesQueue.addAll((properties as ColumnProperties).children)
ViewType.ROW -> viewPropertiesQueue.addAll((properties as RowProperties).children)
ViewType.CARD -> viewPropertiesQueue.addAll((properties as CardViewProperties).content)
ViewType.LIST -> viewPropertiesQueue.addAll((properties as ListProperties).registerCard.views)
else -> {}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ object ViewPropertiesSerializer :
JsonContentPolymorphicSerializer<ViewProperties>(ViewProperties::class) {
override fun selectDeserializer(
element: JsonElement,
): DeserializationStrategy<out ViewProperties> {
): DeserializationStrategy<ViewProperties> {
val jsonObject = element.jsonObject
val viewType = jsonObject[VIEW_TYPE]?.jsonPrimitive?.content
require(viewType != null && ViewType.values().contains(ViewType.valueOf(viewType))) {
require(
viewType != null && ViewType.entries.toTypedArray().contains(ViewType.valueOf(viewType)),
) {
"""Ensure that supported `viewType` property is included in your register view properties configuration.
Supported types: ${ViewType.values()}
Supported types: ${ViewType.entries.toTypedArray()}
Parsed JSON: $jsonObject
"""
.trimMargin()
Expand Down
Loading

0 comments on commit 62c9e89

Please sign in to comment.