Skip to content

Commit

Permalink
Merge branch 'filter-data-by-selected-locations' into feature/eusm_de…
Browse files Browse the repository at this point in the history
…velop
  • Loading branch information
dubdabasoduba committed Jun 11, 2024
2 parents 385e7aa + c9dd2c0 commit 9891270
Show file tree
Hide file tree
Showing 23 changed files with 255 additions and 176 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,6 @@ data class RegisterConfiguration(
),
val configRules: List<RuleConfig>? = null,
val registerFilter: RegisterFilterConfig? = null,
val filterDataByRelatedEntityLocation: Boolean = false,
val topScreenSection: TopScreenSectionConfig? = null,
) : Configuration()
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@

package org.smartregister.fhircore.engine.data.local

import android.content.Context
import androidx.annotation.VisibleForTesting
import androidx.compose.ui.state.ToggleableState
import ca.uhn.fhir.context.FhirContext
import ca.uhn.fhir.parser.IParser
import ca.uhn.fhir.rest.gclient.DateClientParam
Expand All @@ -39,34 +41,40 @@ import com.jayway.jsonpath.Configuration
import com.jayway.jsonpath.JsonPath
import com.jayway.jsonpath.Option
import com.jayway.jsonpath.PathNotFoundException
import dagger.hilt.android.qualifiers.ApplicationContext
import java.util.LinkedList
import java.util.UUID
import javax.inject.Inject
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import org.hl7.fhir.instance.model.api.IBaseResource
import org.hl7.fhir.r4.model.Condition
import org.hl7.fhir.r4.model.DataRequirement
import org.hl7.fhir.r4.model.Coding
import org.hl7.fhir.r4.model.Enumerations
import org.hl7.fhir.r4.model.Group
import org.hl7.fhir.r4.model.IdType
import org.hl7.fhir.r4.model.Location
import org.hl7.fhir.r4.model.Patient
import org.hl7.fhir.r4.model.Reference
import org.hl7.fhir.r4.model.RelatedPerson
import org.hl7.fhir.r4.model.Resource
import org.hl7.fhir.r4.model.ResourceType
import org.smartregister.fhircore.engine.R
import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry
import org.smartregister.fhircore.engine.configuration.app.ConfigService
import org.smartregister.fhircore.engine.configuration.event.EventWorkflow
import org.smartregister.fhircore.engine.configuration.profile.ManagingEntityConfig
import org.smartregister.fhircore.engine.configuration.register.ActiveResourceFilterConfig
import org.smartregister.fhircore.engine.data.local.register.RegisterRepository
import org.smartregister.fhircore.engine.datastore.syncLocationIdsProtoStore
import org.smartregister.fhircore.engine.domain.model.Code
import org.smartregister.fhircore.engine.domain.model.DataQuery
import org.smartregister.fhircore.engine.domain.model.FhirResourceConfig
Expand Down Expand Up @@ -103,6 +111,7 @@ constructor(
open val configRulesExecutor: ConfigRulesExecutor,
open val fhirPathDataExtractor: FhirPathDataExtractor,
open val parser: IParser,
@ApplicationContext open val context: Context,
) {

suspend inline fun <reified T : Resource> loadResource(resourceId: String): T? {
Expand Down Expand Up @@ -140,20 +149,6 @@ constructor(
.map { it.resource }
}

suspend fun searchCondition(dataRequirement: DataRequirement) =
when (dataRequirement.type) {
Enumerations.ResourceType.CONDITION.toCode() ->
fhirEngine
.search<Condition> {
dataRequirement.codeFilter.forEach {
filter(TokenClientParam(it.path), { value = of(it.codeFirstRep) })
}
// TODO handle date filter
}
.map { it.resource }
else -> listOf()
}

suspend inline fun <reified R : Resource> search(search: Search) =
fhirEngine.search<R>(search).map { it.resource }

Expand Down Expand Up @@ -850,10 +845,10 @@ constructor(
): List<Resource> {
val resourceFilterExpressionForCurrentResourceType =
resourceFilterExpressions?.firstOrNull {
!resources.isNullOrEmpty() && (resources[0].resourceType == it.resourceType)
resources.isNotEmpty() && (resources[0].resourceType == it.resourceType)
}
return with(resourceFilterExpressionForCurrentResourceType) {
if ((this == null) || conditionalFhirPathExpressions.isNullOrEmpty()) {
if ((this == null) || conditionalFhirPathExpressions.isEmpty()) {
resources
} else {
resources.filter { resource ->
Expand All @@ -873,7 +868,7 @@ constructor(

@VisibleForTesting
suspend fun closeResource(resource: Resource, eventWorkflow: EventWorkflow) {
var conf: Configuration =
val conf: Configuration =
Configuration.defaultConfiguration().apply { addOptions(Option.DEFAULT_PATH_LEAF_TO_NULL) }
val jsonParse = JsonPath.using(conf).parse(resource.encodeResourceToString())

Expand All @@ -890,15 +885,15 @@ constructor(
// Expression stars with '$' (JSONPath) or ResourceType like in FHIRPath
if (
updateExpression.jsonPathExpression.startsWith("\$") &&
updateExpression.value != null
updateExpression.value != JsonNull
) {
set(updateExpression.jsonPathExpression, updateValue)
}
if (
updateExpression.jsonPathExpression.startsWith(
resource.resourceType.name,
ignoreCase = true,
) && updateExpression.value != null
) && updateExpression.value != JsonNull
) {
set(
updateExpression.jsonPathExpression.replace(
Expand Down Expand Up @@ -966,6 +961,7 @@ constructor(
}

suspend fun searchResourcesRecursively(
filterByRelatedEntityLocationMetaTag: Boolean,
filterActiveResources: List<ActiveResourceFilterConfig>?,
fhirResourceConfig: FhirResourceConfig,
secondaryResourceConfigs: List<FhirResourceConfig>?,
Expand All @@ -984,6 +980,10 @@ constructor(
sortData = true,
configComputedRuleValues = configComputedRuleValues,
)
applyFilterByRelatedEntityLocationMetaTag(
baseResourceConfig.resource,
filterByRelatedEntityLocationMetaTag,
)
if (currentPage != null && pageSize != null) {
count = pageSize
from = currentPage * pageSize
Expand Down Expand Up @@ -1046,12 +1046,56 @@ constructor(
filterActiveResources = filterActiveResources,
secondaryResourceConfigs = null,
configRules = null,
filterByRelatedEntityLocationMetaTag = false,
),
)
}
return secondaryRepositoryResourceDataLinkedList
}

suspend fun Search.applyFilterByRelatedEntityLocationMetaTag(
baseResourceType: ResourceType,
filterByRelatedEntityLocation: Boolean,
) {
runBlocking {
if (filterByRelatedEntityLocation) {
val system = context.getString(R.string.sync_strategy_related_entity_location_system)
val display = context.getString(R.string.sync_strategy_related_entity_location_display)
val locationIds =
context.syncLocationIdsProtoStore.data
.firstOrNull()
?.filter { it.toggleableState == ToggleableState.On }
?.map { it.locationId }
.takeIf { !it.isNullOrEmpty() }
val filters =
if (baseResourceType == ResourceType.Location) { // E.g where _id=uuid1,uuid2
locationIds?.map {
val apply: TokenParamFilterCriterion.() -> Unit = { value = of(it) }
apply
}
} else {
locationIds?.map { code -> // The RelatedEntityLocation is retrieved from meta tag
val apply: TokenParamFilterCriterion.() -> Unit = {
value = of(Coding(system, code, display))
}
apply
}
}

if (!filters.isNullOrEmpty()) {
this@applyFilterByRelatedEntityLocationMetaTag.filter(
if (baseResourceType == ResourceType.Location) {
Location.RES_ID
} else {
TokenClientParam(TAG)
},
*filters.toTypedArray(),
)
}
}
}
}

/**
* A wrapper data class to hold search results. All related resources are flattened into one Map
* including the nested related resources as required by the Rules Engine facts.
Expand All @@ -1065,7 +1109,6 @@ constructor(
const val SNOMED_SYSTEM = "http://hl7.org/fhir/R4B/valueset-condition-clinical.html"
const val PATIENT_CONDITION_RESOLVED_CODE = "resolved"
const val PATIENT_CONDITION_RESOLVED_DISPLAY = "Resolved"
const val PNC_CONDITION_TO_CLOSE_RESOURCE_ID = "pncConditionToClose"
const val SICK_CHILD_CONDITION_TO_CLOSE_RESOURCE_ID = "sickChildConditionToClose"
const val TAG = "_tag"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@

package org.smartregister.fhircore.engine.data.local.register

import android.content.Context
import ca.uhn.fhir.parser.IParser
import com.google.android.fhir.FhirEngine
import com.google.android.fhir.search.Search
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import kotlinx.coroutines.withContext
import org.hl7.fhir.r4.model.Resource
Expand Down Expand Up @@ -51,6 +53,7 @@ constructor(
override val configRulesExecutor: ConfigRulesExecutor,
override val fhirPathDataExtractor: FhirPathDataExtractor,
override val parser: IParser,
@ApplicationContext override val context: Context,
) :
Repository,
DefaultRepository(
Expand All @@ -62,6 +65,7 @@ constructor(
configRulesExecutor = configRulesExecutor,
fhirPathDataExtractor = fhirPathDataExtractor,
parser = parser,
context = context,
) {

override suspend fun loadRegisterData(
Expand All @@ -72,6 +76,8 @@ constructor(
): List<RepositoryResourceData> {
val registerConfiguration = retrieveRegisterConfiguration(registerId, paramsMap)
return searchResourcesRecursively(
filterByRelatedEntityLocationMetaTag =
registerConfiguration.filterDataByRelatedEntityLocation,
filterActiveResources = registerConfiguration.activeResourceFilters,
fhirResourceConfig = fhirResourceConfig ?: registerConfiguration.fhirResource,
secondaryResourceConfigs = registerConfiguration.secondaryResources,
Expand All @@ -91,6 +97,7 @@ constructor(
val fhirResource = fhirResourceConfig ?: registerConfiguration.fhirResource
val baseResourceConfig = fhirResource.baseResource
val configComputedRuleValues = registerConfiguration.configRules.configRulesComputedValues()
val filterByRelatedEntityLocation = registerConfiguration.filterDataByRelatedEntityLocation
val search =
Search(baseResourceConfig.resource).apply {
applyConfiguredSortAndFilters(
Expand All @@ -99,6 +106,10 @@ constructor(
filterActiveResources = registerConfiguration.activeResourceFilters,
configComputedRuleValues = configComputedRuleValues,
)
applyFilterByRelatedEntityLocationMetaTag(
baseResourceType = baseResourceConfig.resource,
filterByRelatedEntityLocation = filterByRelatedEntityLocation,
)
}
return search.count(
onFailure = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ class PreferenceDataStore @Inject constructor(@ApplicationContext val context: C
companion object Keys {
val APP_ID by lazy { stringPreferencesKey("appId") }
val LANG by lazy { stringPreferencesKey("lang") }
val SYNC_LOCATION_IDS by lazy { stringPreferencesKey("syncLocationIds") }
val MIGRATION_VERSION by lazy { intPreferencesKey("migrationVersion") }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ private const val LOCATION_COORDINATES_DATASTORE_JSON = "location_coordinates.js

private const val SYNC_LOCATION_IDS = "sync_location_ids.json"

private const val TAG = "Proto DataStore"

val Context.practitionerProtoStore: DataStore<PractitionerDetails> by
dataStore(
fileName = PRACTITIONER_DETAILS_DATASTORE_JSON,
Expand Down
Loading

0 comments on commit 9891270

Please sign in to comment.