Skip to content

Commit

Permalink
Update map data loading to fetch locations using lineage location tags (
Browse files Browse the repository at this point in the history
#3708)

* Fetch locations using lineage location tags

* Add unit test

* Run spotless apply

* Remove unused comments

* Add docs for filterDataByLocationLineage config

---------

Co-authored-by: Benjamin Mwalimu <[email protected]>
  • Loading branch information
Rkareko and dubdabasoduba authored Feb 3, 2025
1 parent 4359a24 commit a790c9d
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ data class GeoWidgetConfiguration(
val actions: List<ActionConfig>? = emptyList(),
val noResults: NoResultsConfig? = null,
val filterDataByRelatedEntityLocation: Boolean? = null,
val filterDataByLocationLineage: Boolean? = null,
) : Configuration()

@Serializable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,7 @@ constructor(
configComputedRuleValues = configComputedRuleValues,
activeResourceFilters = null,
filterByRelatedEntityLocationMetaTag = false,
filterDataByLocationLineageMetaTag = false,
currentPage = null,
pageSize = null,
)
Expand Down Expand Up @@ -743,13 +744,14 @@ constructor(
configComputedRuleValues: Map<String, Any>,
activeResourceFilters: List<ActiveResourceFilterConfig>?,
filterByRelatedEntityLocationMetaTag: Boolean,
filterDataByLocationLineageMetaTag: Boolean = false,
currentPage: Int?,
pageSize: Int?,
): List<RepositoryResourceData> {
// Important!! Used LinkedHashMap to maintain the order of insertion.
val resultsDataMap = LinkedHashMap<String, RepositoryResourceData>()
if (filterByRelatedEntityLocationMetaTag) {
val syncLocationIds = retrieveRelatedEntitySyncLocationIds()
val syncLocationIds = retrieveRelatedEntitySyncLocationIds(filterDataByLocationLineageMetaTag)
if (currentPage != null && pageSize != null) {
val requiredSize = (currentPage + 1) * pageSize
var page = 0
Expand Down Expand Up @@ -1305,19 +1307,34 @@ constructor(
return null
}

protected suspend fun retrieveRelatedEntitySyncLocationIds(): HashSet<String> =
protected suspend fun retrieveRelatedEntitySyncLocationIds(
filterDataByLocationLineageMetaTag: Boolean = false,
): HashSet<String> =
withContext(dispatcherProvider.io()) {
context
.retrieveRelatedEntitySyncLocationState(MultiSelectViewAction.FILTER_DATA)
.asSequence()
.chunked(SQL_WHERE_CLAUSE_LIMIT)
.map { it.map { state -> state.locationId } }
.flatMapTo(HashSet()) { retrieveFlattenedSubLocationIds(it) }
.flatMapTo(HashSet()) {
retrieveFlattenedSubLocationIds(it, filterDataByLocationLineageMetaTag)
}
}

suspend fun retrieveFlattenedSubLocationIds(locationIds: List<String>): HashSet<String> {
suspend fun retrieveFlattenedSubLocationIds(
locationIds: List<String>,
filterDataByLocationLineageMetaTag: Boolean = false,
): HashSet<String> {
val locations = HashSet<String>(locationIds)
val queue = ArrayDeque<List<String>>()
if (filterDataByLocationLineageMetaTag) {
locations.addAll(
retrieveSubLocationsByMetaTags(
locationIds,
),
)
return locations
}
val subLocations = retrieveSubLocations(locationIds)
if (subLocations.isNotEmpty()) {
locations.addAll(subLocations)
Expand Down Expand Up @@ -1346,6 +1363,19 @@ constructor(
.map { it.logicalId }
}

private suspend fun retrieveSubLocationsByMetaTags(locationIds: List<String>): List<String> {
val search =
Search(type = ResourceType.Location).apply {
val filters =
locationIds.map {
val apply: TokenParamFilterCriterion.() -> Unit = { value = of(it) }
apply
}
filter(TokenClientParam(TAG), *filters.toTypedArray())
}
return fhirEngine.search<Location>(search).map { it.resource.logicalId }
}

/**
* This function searches and returns the latest [QuestionnaireResponse] for the given
* [resourceId] that was extracted from the [Questionnaire] identified as [questionnaireId].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import org.hl7.fhir.r4.model.Enumerations
import org.hl7.fhir.r4.model.Group
import org.hl7.fhir.r4.model.HumanName
import org.hl7.fhir.r4.model.Location
import org.hl7.fhir.r4.model.Meta
import org.hl7.fhir.r4.model.Organization
import org.hl7.fhir.r4.model.Patient
import org.hl7.fhir.r4.model.Period
Expand Down Expand Up @@ -1753,4 +1754,64 @@ class DefaultRepositoryTest : RobolectricTest() {
Assert.assertNotNull(latestQuestionnaireResponse)
Assert.assertEquals("QuestionnaireResponse/qr1", latestQuestionnaireResponse?.id)
}

@Test
fun testRetrieveFlattenedSubLocationsShouldReturnCorrectLocationIdsWhenFilterDataByLocationLineageMetaTagIsTrue() =
runTest(timeout = 120.seconds) {
val location1 = Location().apply { id = "loc1" }
val location1LineageTag =
Coding().apply {
system = "http://example.org/location-lineage"
code = location1.logicalId
display = "Location 1"
}
val location2 =
Location().apply {
id = "loc2"
meta = Meta().apply { tag.add(location1LineageTag) }
partOf = location1.asReference()
}
val location2LineageTag =
Coding().apply {
system = "http://example.org/location-lineage"
code = location2.logicalId
display = "Location 2"
}
val location3 =
Location().apply {
id = "loc3"
meta = Meta().apply { tag.add(location1LineageTag) }
partOf = location2.asReference()
}
val location4 =
Location().apply {
id = "loc4"
partOf = location3.asReference()
}
val location5 =
Location().apply {
id = "loc5"
meta = Meta().apply { tag.add(location2LineageTag) }
partOf = location4.asReference()
}

fhirEngine.create(location1, location2, location3, location4, location5, isLocalOnly = true)

val location1SubLocations =
defaultRepository.retrieveFlattenedSubLocationIds(
listOf(location1.logicalId),
filterDataByLocationLineageMetaTag = true,
)
Assert.assertEquals(3, location1SubLocations.size)
Assert.assertTrue(location1SubLocations.contains(location2.logicalId))
Assert.assertTrue(location1SubLocations.contains(location3.logicalId))

val location2SubLocations =
defaultRepository.retrieveFlattenedSubLocationIds(
listOf(location2.logicalId),
filterDataByLocationLineageMetaTag = true,
)
Assert.assertEquals(2, location2SubLocations.size)
Assert.assertEquals(location5.logicalId, location2SubLocations.last())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ constructor(
activeResourceFilters = null,
filterByRelatedEntityLocationMetaTag =
geoWidgetConfig.filterDataByRelatedEntityLocation == true,
filterDataByLocationLineageMetaTag =
geoWidgetConfig.filterDataByLocationLineage == true,
currentPage = null,
pageSize = null,
)
Expand Down
4 changes: 3 additions & 1 deletion docs/engineering/app/configuring/config-types/geowidget.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ There can be multiple instances of this configuration type in the application; e
"saveButtonText": "ADD FAMILY",
"setPractitionerDetails": true,
"setOrganizationDetails": true
}
},
"filterDataByLocationLineage": true
}
```

Expand All @@ -37,3 +38,4 @@ configType | Type of configuration | Yes | `geoWidget` |
id | A unique identifier for this multi-config type | Yes | |
profileId | The identifier for the profile to be opened when a point on the map (representing a household) is clicked | Yes | |
registrationQuestionnaire | Configuration for the register questionnaire | Yes | |
filterDataByLocationLineage | Fetch locations to load on the map using lineage location tags | No | false |

0 comments on commit a790c9d

Please sign in to comment.