Skip to content

Commit

Permalink
Upgrade FHIR SDK dependencies ⬆️ (#3423)
Browse files Browse the repository at this point in the history
* Upgrade FHIR SDK dependencies ⬆️
* Replace JWT token parser library
* Update Kujaku library version
* Upgrade 3rd party dependencies
* Refactor Knowledge Manager Resources Persistance
* Refactor Cancel previous worflow to use native commands
* Upgrade CI API level to 34
* Clean up gradle dependencies configuration
* Move measure reporting evaluation to BG thread
* Remove unrecommended forced portrait format
* Clean up TOML catalog file
* Refactor usage of FHIR JSONParser to support concurrency
* Fix code coverage reporting
* Fix build 💚
  • Loading branch information
ndegwamartin committed Sep 25, 2024
1 parent 3cb71f0 commit 53ca669
Show file tree
Hide file tree
Showing 46 changed files with 583 additions and 522 deletions.
37 changes: 15 additions & 22 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ on:
merge_group:
branches: [ main ]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
FHIRCORE_USERNAME: ${{ secrets.FHIRCORE_USERNAME }}
FHIRCORE_ACCESS_TOKEN: ${{ secrets.FHIRCORE_ACCESS_TOKEN }}
Expand All @@ -21,13 +25,9 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
api-level: [30]
api-level: [34]

steps:
- name: Cancel Previous workflow runs
uses: styfle/cancel-workflow-action@0.9.1
with:
access_token: ${{ github.token }}

- name: Checkout 🛎️
uses: actions/checkout@v4

Expand Down Expand Up @@ -106,21 +106,17 @@ jobs:


- name: Upload Engine module test coverage report to Codecov
if: matrix.api-level == 30 # Only upload coverage on API level 30
if: matrix.api-level == 34 # Only upload coverage on API level 34
working-directory: android
run: bash <(curl -s https://codecov.io/bash) -F engine -f "engine/build/reports/jacoco/fhircoreJacocoReport/fhircoreJacocoReport.xml"

geowidget-tests:
runs-on: ubuntu-latest
strategy:
matrix:
api-level: [30]
api-level: [34]

steps:
- name: Cancel Previous workflow runs
uses: styfle/cancel-workflow-action@0.9.1
with:
access_token: ${{ github.token }}

- name: Checkout 🛎️
uses: actions/checkout@v4

Expand Down Expand Up @@ -198,20 +194,17 @@ jobs:
path: android/geowidget/build/reports

- name: Upload Geowidget module test coverage report to Codecov
if: matrix.api-level == 30 # Only upload coverage on API level 30
if: matrix.api-level == 34 # Only upload coverage on API level 34
working-directory: android
run: bash <(curl -s https://codecov.io/bash) -F geowidget -f "geowidget/build/reports/jacoco/fhircoreJacocoReport/fhircoreJacocoReport.xml"

quest-tests:
runs-on: ubuntu-latest
strategy:
matrix:
api-level: [30]
steps:
- name: Cancel Previous workflow runs
uses: styfle/cancel-workflow-action@0.9.1
with:
access_token: ${{ github.token }}
api-level: [34]

steps:
- name: Checkout 🛎️
uses: actions/checkout@v4

Expand Down Expand Up @@ -315,6 +308,6 @@ jobs:
path: android/quest/build/reports

- name: Upload Quest module test coverage report to Codecov
if: matrix.api-level == 30 # Only upload coverage on API level 30
if: matrix.api-level == 34 # Only upload coverage on API level 34
working-directory: android
run: bash <(curl -s https://codecov.io/bash) -F quest -f "quest/build/reports/jacoco/fhircoreJacocoReport/fhircoreJacocoReport.xml"
run: bash <(curl -s https://codecov.io/bash) -F quest -f "quest/build/reports/jacoco/fhircoreJacocoReport/fhircoreJacocoReport.xml"
12 changes: 4 additions & 8 deletions android/engine/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import org.gradle.api.tasks.testing.logging.TestLogEvent

plugins {
`jacoco-report`
`ktlint`
ktlint
id("com.android.library")
id("kotlin-android")
id("kotlin-kapt")
Expand Down Expand Up @@ -162,7 +162,7 @@ dependencies {
api(libs.glide)
api(libs.knowledge) { exclude(group = "org.slf4j", module = "jcl-over-slf4j") }
api(libs.p2p.lib)
api(libs.jjwt)
api(libs.java.jwt)
api(libs.fhir.common.utils) { exclude(group = "org.slf4j", module = "jcl-over-slf4j") }
api(libs.runtime.livedata)
api(libs.foundation)
Expand All @@ -180,8 +180,8 @@ dependencies {
api(libs.data.capture) {
isTransitive = true
exclude(group = "ca.uhn.hapi.fhir")
exclude(group = "com.google.android.fhir", module = "engine")
exclude(group = "com.google.android.fhir", module = "common")
exclude(group = "org.smartregister", module = "common")
exclude(group = "org.slf4j", module = "jcl-over-slf4j")
}
api(libs.cqf.fhir.cr) {
Expand All @@ -194,23 +194,19 @@ dependencies {
exclude(group = "xerces")
exclude(group = "com.github.java-json-tools")
exclude(group = "org.codehaus.woodstox")
exclude(group = "com.google.android.fhir", module = "common")
exclude(group = "com.google.android.fhir", module = "engine")
exclude(group = "org.smartregister", module = "engine")
exclude(group = "com.github.ben-manes.caffeine")
}
api(libs.contrib.barcode) {
isTransitive = true
exclude(group = "org.smartregister", module = "data-capture")
exclude(group = "ca.uhn.hapi.fhir")
exclude(group = "com.google.android.fhir", module = "common")
exclude(group = "com.google.android.fhir", module = "engine")
}
api(libs.contrib.locationwidget) {
isTransitive = true
exclude(group = "org.smartregister", module = "data-capture")
exclude(group = "ca.uhn.hapi.fhir")
exclude(group = "com.google.android.fhir", module = "common")
exclude(group = "com.google.android.fhir", module = "engine")
}
api(libs.fhir.engine) {
isTransitive = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.google.android.fhir.FhirEngineProvider
import com.google.android.fhir.search.search
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.hl7.fhir.r4.model.Patient
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.Resource
Expand Down Expand Up @@ -57,19 +58,15 @@ class FhirEngineExtensionKtTest {
}

@Test
fun test_search_time_searches_sequentially_and_short_running_query_waits() {
fun test_search_time_searches_sequentially_and_short_running_query_waits() = runTest {
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 patients = fhirEngine.search<Patient> {}.map { it.resource }
fetchedResources += patients

val questionnaires = fhirEngine.search<Questionnaire> {}.map { it.resource }
fetchedResources += questionnaires

val indexOfResultOfShortQuery =
fetchedResources.indexOfFirst { it.resourceType == ResourceType.Questionnaire }
val indexOfResultOfLongQuery =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ constructor(
private var _isNonProxy = BuildConfig.IS_NON_PROXY_APK
private val fhirContext = FhirContext.forR4Cached()
private val authConfiguration = configService.provideAuthConfiguration()
private val jsonParser = fhirContext.newJsonParser()

/**
* Retrieve configuration for the provided [ConfigType]. The JSON retrieved from [configsJsonMap]
Expand Down Expand Up @@ -629,9 +628,14 @@ constructor(
resource.idElement.idPart
}

return File(context.filesDir, "$fileName.json").apply {
writeText(jsonParser.encodeResourceToString(resource))
}
return File(
context.filesDir,
"$KNOWLEDGE_MANAGER_ASSETS_SUBFOLDER/${resource.resourceType}/$fileName.json",
)
.apply {
this.parentFile?.mkdirs()
writeText(fhirContext.newJsonParser().encodeResourceToString(resource))
}
}

/**
Expand Down Expand Up @@ -813,6 +817,7 @@ constructor(
const val PAGINATION_NEXT = "next"
const val RESOURCES_PATH = "resources/"
const val SYNC_LOCATION_IDS = "_syncLocations"
const val KNOWLEDGE_MANAGER_ASSETS_SUBFOLDER = "km"

/**
* The list of resources whose types can be synced down as part of the Composition configs.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,18 +114,15 @@ constructor(
@ApplicationContext open val context: Context,
) {

suspend inline fun <reified T : Resource> loadResource(resourceId: String): T? {
return withContext(dispatcherProvider.io()) { fhirEngine.loadResource(resourceId) }
}
suspend inline fun <reified T : Resource> loadResource(resourceId: String): T? =
fhirEngine.loadResource(resourceId)

suspend fun loadResource(resourceId: String, resourceType: ResourceType): Resource =
withContext(dispatcherProvider.io()) { fhirEngine.get(resourceType, resourceId) }
fhirEngine.get(resourceType, resourceId)

suspend fun loadResource(reference: Reference) =
withContext(dispatcherProvider.io()) {
IdType(reference.reference).let {
fhirEngine.get(ResourceType.fromCode(it.resourceType), it.idPart)
}
IdType(reference.reference).let {
fhirEngine.get(ResourceType.fromCode(it.resourceType), it.idPart)
}

suspend inline fun <reified T : Resource> searchResourceFor(
Expand All @@ -135,19 +132,17 @@ constructor(
dataQueries: List<DataQuery> = listOf(),
configComputedRuleValues: Map<String, Any>,
): List<T> =
withContext(dispatcherProvider.io()) {
fhirEngine
.batchedSearch<T> {
filterByResourceTypeId(token, subjectType, subjectId)
dataQueries.forEach {
filterBy(
dataQuery = it,
configComputedRuleValues = configComputedRuleValues,
)
}
fhirEngine
.batchedSearch<T> {
filterByResourceTypeId(token, subjectType, subjectId)
dataQueries.forEach {
filterBy(
dataQuery = it,
configComputedRuleValues = configComputedRuleValues,
)
}
.map { it.resource }
}
}
.map { it.resource }

suspend inline fun <reified R : Resource> search(search: Search) =
fhirEngine.batchedSearch<R>(search).map { it.resource }
Expand All @@ -162,17 +157,13 @@ constructor(
* param [addResourceTags]
*/
suspend fun create(addResourceTags: Boolean = true, vararg resource: Resource): List<String> {
return withContext(dispatcherProvider.io()) {
preProcessResources(addResourceTags, *resource)
fhirEngine.create(*resource)
}
preProcessResources(addResourceTags, *resource)
return fhirEngine.create(*resource)
}

suspend fun createRemote(addResourceTags: Boolean = true, vararg resource: Resource) {
return withContext(dispatcherProvider.io()) {
preProcessResources(addResourceTags, *resource)
fhirEngine.create(*resource, isLocalOnly = true)
}
preProcessResources(addResourceTags, *resource)
fhirEngine.create(*resource, isLocalOnly = true)
}

private fun preProcessResources(addResourceTags: Boolean, vararg resource: Resource) {
Expand All @@ -198,23 +189,19 @@ constructor(
resourceId: String,
softDelete: Boolean = false,
) {
withContext(dispatcherProvider.io()) {
if (softDelete) {
val resource = fhirEngine.get(resourceType, resourceId)
softDelete(resource)
} else {
fhirEngine.delete(resourceType, resourceId)
}
if (softDelete) {
val resource = fhirEngine.get(resourceType, resourceId)
softDelete(resource)
} else {
fhirEngine.delete(resourceType, resourceId)
}
}

suspend fun delete(resource: Resource, softDelete: Boolean = false) {
withContext(dispatcherProvider.io()) {
if (softDelete) {
softDelete(resource)
} else {
fhirEngine.delete(resource.resourceType, resource.logicalId)
}
if (softDelete) {
softDelete(resource)
} else {
fhirEngine.delete(resource.resourceType, resource.logicalId)
}
}

Expand Down Expand Up @@ -243,24 +230,20 @@ constructor(
* param [addMandatoryTags]
*/
suspend fun <R : Resource> addOrUpdate(addMandatoryTags: Boolean = true, resource: R) {
return withContext(dispatcherProvider.io()) {
resource.updateLastUpdated()
try {
fhirEngine.get(resource.resourceType, resource.logicalId).run {
val updateFrom = updateFrom(resource)
fhirEngine.update(updateFrom)
}
} catch (resourceNotFoundException: ResourceNotFoundException) {
create(addMandatoryTags, resource)
resource.updateLastUpdated()
try {
fhirEngine.get(resource.resourceType, resource.logicalId).run {
val updateFrom = updateFrom(resource)
fhirEngine.update(updateFrom)
}
} catch (resourceNotFoundException: ResourceNotFoundException) {
create(addMandatoryTags, resource)
}
}

suspend fun <R : Resource> update(resource: R) {
return withContext(dispatcherProvider.io()) {
resource.updateLastUpdated()
fhirEngine.update(resource)
}
resource.updateLastUpdated()
fhirEngine.update(resource)
}

suspend fun loadManagingEntity(group: Group) =
Expand Down Expand Up @@ -904,7 +887,7 @@ constructor(
val updatedResource =
parser.parseResource(resourceDefinition, updatedResourceDocument.jsonString())
updatedResource.setId(updatedResource.idElement.idPart)
withContext(dispatcherProvider.io()) { fhirEngine.update(updatedResource as Resource) }
fhirEngine.update(updatedResource as Resource)
}

private fun getJsonContent(jsonElement: JsonElement): Any? {
Expand Down Expand Up @@ -935,9 +918,7 @@ constructor(

suspend fun purge(resource: Resource, forcePurge: Boolean) {
try {
withContext(dispatcherProvider.io()) {
fhirEngine.purge(resource.resourceType, resource.logicalId, forcePurge)
}
fhirEngine.purge(resource.resourceType, resource.logicalId, forcePurge)
} catch (resourceNotFoundException: ResourceNotFoundException) {
Timber.e(
"Purge failed -> Resource with ID ${resource.logicalId} does not exist",
Expand Down
Loading

0 comments on commit 53ca669

Please sign in to comment.