Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix loading related resources data for LIST #3539

Merged
merged 14 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ package org.smartregister.fhircore.engine.configuration

import android.content.Context
import android.database.SQLException
import android.graphics.Bitmap
import androidx.compose.runtime.mutableStateMapOf
import ca.uhn.fhir.context.ConfigurationException
import ca.uhn.fhir.context.FhirContext
import ca.uhn.fhir.parser.DataFormatException
Expand Down Expand Up @@ -98,7 +96,6 @@ constructor(

val configsJsonMap = mutableMapOf<String, String>()
val configCacheMap = mutableMapOf<String, Configuration>()
val decodedImageMap = mutableStateMapOf<String, Bitmap>()
val localizationHelper: LocalizationHelper by lazy { LocalizationHelper(this) }
private val supportedFileExtensions = listOf("json", "properties")
private var _isNonProxy = BuildConfig.IS_NON_PROXY_APK
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ data class ButtonProperties(
override val fillMaxHeight: Boolean = false,
override val clickable: String = "false",
override val visible: String = "true",
override val opacity: Float? = null,
val contentColor: String? = null,
val enabled: String = "true",
val text: String? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ data class CardViewProperties(
override val fillMaxHeight: Boolean = false,
override val clickable: String = "true",
override val visible: String = "true",
override val opacity: Float? = null,
val content: List<ViewProperties> = emptyList(),
val elevation: Int = 5,
val cornerSize: Int = 6,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ data class ColumnProperties(
override val fillMaxHeight: Boolean = false,
override val clickable: String = "false",
override val visible: String = "true",
override val opacity: Float? = null,
val spacedBy: Int = 8,
val wrapContent: Boolean = false,
val arrangement: ColumnArrangement? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ data class CompoundTextProperties(
override val alignment: ViewAlignment = ViewAlignment.NONE,
override val fillMaxWidth: Boolean = false,
override val fillMaxHeight: Boolean = false,
override val opacity: Float? = null,
override val clickable: String = "false",
override val visible: String = "true",
val primaryText: String? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ data class DividerProperties(
override val fillMaxHeight: Boolean = false,
override val clickable: String = "false",
override val visible: String = "true",
override val opacity: Float? = null,
val thickness: Float = 0.5f,
) : ViewProperties(), Parcelable {
override fun interpolate(computedValuesMap: Map<String, Any>): DividerProperties {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ data class ImageProperties(
override val fillMaxHeight: Boolean = false,
override val clickable: String = "false",
override val visible: String = "true",
override val opacity: Float? = null,
val tint: String? = null,
val text: String? = null,
val imageConfig: ImageConfig? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,13 @@ data class ListProperties(
override val fillMaxHeight: Boolean = false,
override val clickable: String = "false",
override val visible: String = "true",
override val opacity: Float? = null,
val id: String = "listId",
val registerCard: RegisterCardConfig,
val showDivider: Boolean = true,
val emptyList: NoResultsConfig? = null,
val orientation: ListOrientation = ListOrientation.VERTICAL,
val resources: List<ListResource> = emptyList(),
val resources: List<ListResourceConfig> = emptyList(),
) : ViewProperties(), Parcelable {
override fun interpolate(computedValuesMap: Map<String, Any>): ListProperties {
return this.copy(
Expand All @@ -61,12 +62,13 @@ enum class ListOrientation {

@Serializable
@Parcelize
data class ListResource(
data class ListResourceConfig(
val id: String? = null,
val relatedResourceId: String? = null,
val resourceType: ResourceType,
val conditionalFhirPathExpression: String? = null,
val sortConfig: SortConfig? = null,
val fhirPathExpression: String? = null,
val relatedResources: List<ListResource> = emptyList(),
val relatedResources: List<ListResourceConfig> = emptyList(),
val isRevInclude: Boolean = true,
) : Parcelable, java.io.Serializable
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ data class PersonalDataProperties(
override val fillMaxHeight: Boolean = false,
override val clickable: String = "false",
override val visible: String = "true",
override val opacity: Float? = null,
val personalDataItems: List<PersonalDataItem> = emptyList(),
) : ViewProperties(), Parcelable {
override fun interpolate(computedValuesMap: Map<String, Any>): PersonalDataProperties {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ data class RowProperties(
override val fillMaxHeight: Boolean = false,
override val clickable: String = "false",
override val visible: String = "true",
override val opacity: Float? = null,
val spacedBy: Int = 8,
val arrangement: RowArrangement? = null,
val wrapContent: Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ data class ServiceCardProperties(
override val fillMaxHeight: Boolean = false,
override val clickable: String = "true",
override val visible: String = "true",
override val opacity: Float? = null,
val details: List<CompoundTextProperties> = emptyList(),
val showVerticalDivider: Boolean = false,
val serviceMemberIcons: String? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ data class SpacerProperties(
override val alignment: ViewAlignment = ViewAlignment.NONE,
override val fillMaxWidth: Boolean = false,
override val fillMaxHeight: Boolean = false,
override val opacity: Float? = null,
override val clickable: String = "false",
override val visible: String = "true",
val height: Float? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,16 @@
data class StackViewProperties(
override val viewType: ViewType = ViewType.STACK,
override val weight: Float = 0f,
override val backgroundColor: String? = "#FFFFFF",
override val backgroundColor: String? = null,

Check warning on line 30 in android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/StackViewProperties.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/StackViewProperties.kt#L30

Added line #L30 was not covered by tests
override val padding: Int = 0,
override val borderRadius: Int = 0,
override val alignment: ViewAlignment = ViewAlignment.NONE,
override val fillMaxWidth: Boolean = false,
override val fillMaxHeight: Boolean = false,
override val clickable: String = "false",
override val visible: String = "true",
val opacity: Float = 0f,
val size: Int? = 0,
override val opacity: Float? = null,
val size: Int = 0,

Check warning on line 39 in android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/StackViewProperties.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/StackViewProperties.kt#L38-L39

Added lines #L38 - L39 were not covered by tests
val children: List<ViewProperties> = emptyList(),
) : ViewProperties(), Parcelable {
override fun interpolate(computedValuesMap: Map<String, Any>): StackViewProperties {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ abstract class ViewProperties : java.io.Serializable {
abstract val fillMaxHeight: Boolean
abstract val clickable: String
abstract val visible: String
abstract val opacity: Float?

abstract fun interpolate(computedValuesMap: Map<String, Any>): ViewProperties
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@
import org.hl7.fhir.r4.model.Resource
import org.jeasy.rules.api.Facts
import org.smartregister.fhircore.engine.configuration.view.ListProperties
import org.smartregister.fhircore.engine.configuration.view.ListResource
import org.smartregister.fhircore.engine.configuration.view.ListResourceConfig
import org.smartregister.fhircore.engine.domain.model.RepositoryResourceData
import org.smartregister.fhircore.engine.domain.model.ResourceData
import org.smartregister.fhircore.engine.domain.model.RuleConfig
import org.smartregister.fhircore.engine.domain.model.ViewType
import org.smartregister.fhircore.engine.util.extension.extractLogicalIdUuid
import org.smartregister.fhircore.engine.util.extension.interpolate

/**
* This class is used to fire rules used to extract and manipulate data from FHIR resources.
Expand Down Expand Up @@ -73,17 +74,18 @@
listResourceDataStateMap: SnapshotStateMap<String, SnapshotStateList<ResourceData>>,
) {
listProperties.resources.forEach { listResource ->
// Initialize to be updated incrementally as resources are transformed into ResourceData
// A new list is required on each iteration
val resourceDataSnapshotStateList = mutableStateListOf<ResourceData>()
listResourceDataStateMap[listProperties.id] = resourceDataSnapshotStateList

filteredListResources(relatedResourcesMap, listResource)
filteredListResources(relatedResourcesMap, listResource, computedValuesMap)
.mapToResourceData(
listResource = listResource,
listResourceConfig = listResource,
relatedResourcesMap = relatedResourcesMap,
ruleConfigs = listProperties.registerCard.rules,
computedValuesMap = computedValuesMap,
resourceDataSnapshotStateList = resourceDataSnapshotStateList,
listResourceDataStateMap = listResourceDataStateMap,
)
}
}
Expand All @@ -107,36 +109,64 @@
}

private fun List<Resource>.mapToResourceData(
listResource: ListResource,
listResourceConfig: ListResourceConfig,
relatedResourcesMap: Map<String, List<Resource>>,
ruleConfigs: List<RuleConfig>,
computedValuesMap: Map<String, Any>,
resourceDataSnapshotStateList: SnapshotStateList<ResourceData>,
listResourceDataStateMap: SnapshotStateMap<String, SnapshotStateList<ResourceData>>,
) {
this.forEach { resource ->
this.forEach { baseListResource ->
val relatedResourcesQueue =
ArrayDeque<Pair<Resource, List<ListResourceConfig>>>().apply {
addFirst(Pair(baseListResource, listResourceConfig.relatedResources))
}

val listItemRelatedResources = mutableMapOf<String, List<Resource>>()
listResource.relatedResources.forEach { relatedListResource ->
val retrieveRelatedResources: List<Resource>? =
relatedListResource.fhirPathExpression.let {
while (relatedResourcesQueue.isNotEmpty()) {
val (currentResource, currentListResourceConfig) = relatedResourcesQueue.removeFirst()
currentListResourceConfig.forEach { relatedListResourceConfig ->
val retrievedRelatedResources: List<Resource> =
rulesFactory.rulesEngineService.retrieveRelatedResources(
resource = resource,
resource = currentResource,
relatedResourceKey =
relatedListResource.relatedResourceId ?: relatedListResource.resourceType.name,
referenceFhirPathExpression = it,
relatedListResourceConfig.relatedResourceId
?: relatedListResourceConfig.resourceType.name,
referenceFhirPathExpression = relatedListResourceConfig.fhirPathExpression,
relatedResourcesMap = relatedResourcesMap,
)
}
if (!retrieveRelatedResources.isNullOrEmpty()) {
listItemRelatedResources[
relatedListResource.id ?: relatedListResource.resourceType.name,
] =
if (!relatedListResource.conditionalFhirPathExpression.isNullOrEmpty()) {
rulesFactory.rulesEngineService.filterResources(
retrieveRelatedResources,
relatedListResource.conditionalFhirPathExpression,
)
} else {
retrieveRelatedResources

val interpolatedConditionalFhirPathExpression =
relatedListResourceConfig.conditionalFhirPathExpression?.interpolate(computedValuesMap)

rulesFactory.rulesEngineService
.filterResources(
resources = retrievedRelatedResources,
conditionalFhirPathExpression = interpolatedConditionalFhirPathExpression,
)
.also { filteredResources ->
// Add to queue for processing
filteredResources.forEach {
relatedResourcesQueue.addLast(Pair(it, relatedListResourceConfig.relatedResources))
}

// Apply configurable sorting to related resources
val sortConfig = relatedListResourceConfig.sortConfig
if (sortConfig == null || sortConfig.fhirPathExpression.isBlank()) {
listItemRelatedResources[
relatedListResourceConfig.id ?: relatedListResourceConfig.resourceType.name,
] = filteredResources
} else {
listItemRelatedResources[

Check warning on line 160 in android/engine/src/main/java/org/smartregister/fhircore/engine/rulesengine/ResourceDataRulesExecutor.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/rulesengine/ResourceDataRulesExecutor.kt#L160

Added line #L160 was not covered by tests
relatedListResourceConfig.id ?: relatedListResourceConfig.resourceType.name,
] =
rulesFactory.rulesEngineService.sortResources(
resources = filteredResources,
fhirPathExpression = sortConfig.fhirPathExpression,
dataType = sortConfig.dataType.name,
order = sortConfig.order.name,
) ?: filteredResources

Check warning on line 168 in android/engine/src/main/java/org/smartregister/fhircore/engine/rulesengine/ResourceDataRulesExecutor.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/rulesengine/ResourceDataRulesExecutor.kt#L164-L168

Added lines #L164 - L168 were not covered by tests
}
}
}
}
Expand All @@ -146,19 +176,19 @@
ruleConfigs = ruleConfigs,
repositoryResourceData =
RepositoryResourceData(
resourceRulesEngineFactId = null,
resource = resource,
resourceRulesEngineFactId = listResourceConfig.id,
resource = baseListResource,
relatedResourcesMap = listItemRelatedResources,
),
params = emptyMap(),
)

resourceDataSnapshotStateList.add(
ResourceData(
baseResourceId = resource.logicalId.extractLogicalIdUuid(),
baseResourceType = resource.resourceType,
computedValuesMap =
computedValuesMap.plus(listComputedValuesMap), // Reuse computed values
baseResourceId = baseListResource.logicalId,
baseResourceType = baseListResource.resourceType,
computedValuesMap = computedValuesMap.plus(listComputedValuesMap),
listResourceDataMap = listResourceDataStateMap,
),
)
}
Expand All @@ -167,41 +197,36 @@
/**
* This function returns a list of filtered resources. The required list is obtained from
* [relatedResourceMap], then a filter is applied based on the condition returned from the
* extraction of the [ListResource] conditional FHIR path expression
* extraction of the [ListResourceConfig] conditional FHIR path expression. The list is sorted if
* configurations for sorting are provided.
*/
private fun filteredListResources(
relatedResourceMap: Map<String, List<Resource>>,
listResource: ListResource,
listResource: ListResourceConfig,
computedValuesMap: Map<String, Any>,
): List<Resource> {
val relatedResourceKey = listResource.relatedResourceId ?: listResource.resourceType.name
val newListRelatedResources = relatedResourceMap[relatedResourceKey]
val interpolatedConditionalFhirPathExpression =
listResource.conditionalFhirPathExpression?.interpolate(computedValuesMap)

// conditionalFhirPath expression e.g. "Task.status == 'ready'" to filter tasks that are due
// Filter by condition derived from fhirPathExpression otherwise return original or empty list
val resources =
if (
newListRelatedResources != null &&
!listResource.conditionalFhirPathExpression.isNullOrEmpty()
) {
rulesFactory.rulesEngineService.filterResources(
resources = newListRelatedResources,
conditionalFhirPathExpression = listResource.conditionalFhirPathExpression,
)
} else {
newListRelatedResources ?: listOf()
}
rulesFactory.rulesEngineService.filterResources(
resources = relatedResourceMap[relatedResourceKey],
conditionalFhirPathExpression = interpolatedConditionalFhirPathExpression,
)

// Sort resources if valid sort configuration is provided
val sortConfig = listResource.sortConfig

// Sort resources if sort configuration is provided
return if (sortConfig != null && sortConfig.fhirPathExpression.isNotEmpty()) {
return if (sortConfig == null || sortConfig.fhirPathExpression.isEmpty()) {
resources
} else {
rulesFactory.rulesEngineService.sortResources(
resources = resources,
fhirPathExpression = sortConfig.fhirPathExpression,
dataType = sortConfig.dataType.name,
order = sortConfig.order.name,
) ?: resources
} else {
resources
}
}
}
Loading
Loading