opensrp / fhircore

FHIR Core / OpenSRP 2 is a Kotlin application for delivering offline-capable, mobile-first healthcare project implementations from local community to national and international scale using FHIR and WHO Smart Guidelines on Android.
https://smartregister.org
Apache License 2.0
53 stars 41 forks source link

Load FHIR Resources from configs in assets directory #3065

Closed Raynafs closed 3 months ago

Raynafs commented 6 months ago

IMPORTANT: Where possible all PRs must be linked to a Github issue

Fixes #3041

Engineer Checklist

Code Reviewer Checklist

codecov[bot] commented 6 months ago

Codecov Report

Attention: Patch coverage is 50.00000% with 12 lines in your changes are missing coverage. Please review.

Project coverage is 28.0%. Comparing base (ac82739) to head (7a05d5d). Report is 69 commits behind head on main.

Additional details and impacted files [![Impacted file tree graph](https://app.codecov.io/gh/opensrp/fhircore/pull/3065/graphs/tree.svg?width=650&height=150&src=pr&token=IJUTHZUGGH&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensrp)](https://app.codecov.io/gh/opensrp/fhircore/pull/3065?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensrp) ```diff @@ Coverage Diff @@ ## main #3065 +/- ## ========================================= - Coverage 29.6% 28.0% -1.7% - Complexity 658 693 +35 ========================================= Files 239 266 +27 Lines 11204 12472 +1268 Branches 1948 2172 +224 ========================================= + Hits 3323 3495 +172 - Misses 7447 8518 +1071 - Partials 434 459 +25 ``` | [Flag](https://app.codecov.io/gh/opensrp/fhircore/pull/3065/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensrp) | Coverage Δ | | |---|---|---| | [engine](https://app.codecov.io/gh/opensrp/fhircore/pull/3065/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensrp) | `64.7% <50.0%> (-1.5%)` | :arrow_down: | | [geowidget](https://app.codecov.io/gh/opensrp/fhircore/pull/3065/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensrp) | `18.7% <ø> (-28.5%)` | :arrow_down: | | [quest](https://app.codecov.io/gh/opensrp/fhircore/pull/3065/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensrp) | `5.2% <ø> (-0.3%)` | :arrow_down: | Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensrp#carryforward-flags-in-the-pull-request-comment) to find out more. | [Files](https://app.codecov.io/gh/opensrp/fhircore/pull/3065?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensrp) | Coverage Δ | | |---|---|---| | [...core/engine/configuration/ConfigurationRegistry.kt](https://app.codecov.io/gh/opensrp/fhircore/pull/3065?src=pr&el=tree&filepath=android%2Fengine%2Fsrc%2Fmain%2Fjava%2Forg%2Fsmartregister%2Ffhircore%2Fengine%2Fconfiguration%2FConfigurationRegistry.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensrp#diff-YW5kcm9pZC9lbmdpbmUvc3JjL21haW4vamF2YS9vcmcvc21hcnRyZWdpc3Rlci9maGlyY29yZS9lbmdpbmUvY29uZmlndXJhdGlvbi9Db25maWd1cmF0aW9uUmVnaXN0cnkua3Q=) | `65.9% <50.0%> (-2.1%)` | :arrow_down: | ... and [10 files with indirect coverage changes](https://app.codecov.io/gh/opensrp/fhircore/pull/3065/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensrp)
SebaMutuku commented 6 months ago

You can create your method in configurationsRegistry to retrieve any resource. Here's a sample method you can use

 inline fun <reified T : Resource> retrieveResourcesFromConfigMap(resourceId: String): T {
    return configsJsonMap.getValue(resourceId).decodeResourceFromString()
  }

and now invoke it whenever a resource is to be loaded from the configMap in debug mode. E.g Questionnaire, StructureMap, PlanDefinition, List.

Raynafs commented 6 months ago

Hi Seb. Yes sure. working on it

ellykits commented 5 months ago

@SebaMutuku Did you verify if the implementation follows the directory structure required by FHIRCore tools as proposed before?

SebaMutuku commented 5 months ago

@ellykits yes, the resources will be loaded from a folder inside the configs called resources. Inside this folder we will have sub-directorys e.g a sub-directory called questionnaire, structuremap, planDefinition which will hold corresponding resources

SebaMutuku commented 5 months ago

@Raynafs is there anything pending on this PR. Can we close?

Raynafs commented 5 months ago

Hey @SebaMutuku I believe I have completed the full implementation for this. Unless @ellykits would like me to add something.

Raynafs commented 5 months ago

@ellykits This is a video showing that the resources have been loaded from the resources folder when the app Id is app/debug

Screencast from 03-04-2024 11:01:01 ASUBUHI.webm

Here is a video showing that the questionnaire that is in assets actually appears in the app

Screencast from 03-04-2024 11:08:16 ASUBUHI.webm

SebaMutuku commented 3 months ago

@ellykits @pld this implementation is now complete. However we need to make changeds on FhirEngine at this function

 private suspend fun insertResource(resource: Resource, lastUpdatedLocal: Instant?): UUID {
    val resourceUuid = UUID.randomUUID()

    // Use the local UUID as the logical ID of the resource
    if (resource.id.isNullOrEmpty()) {
      resource.id = resourceUuid.toString()
    }

    val entity =
      ResourceEntity(
        id = 0,
        resourceType = resource.resourceType,
        resourceUuid = resourceUuid,
        resourceId = resource.logicalId,
        serializedResource = iParser.encodeResourceToString(resource),
        versionId = resource.versionId,
        lastUpdatedRemote = resource.lastUpdated,
        lastUpdatedLocal = lastUpdatedLocal,
      )
    insertResource(entity)

    val index =
      ResourceIndices.Builder(resourceIndexer.index(resource))
        .apply {
          lastUpdatedLocal?.let {
            addDateTimeIndex(
              createLocalLastUpdatedIndex(entity.resourceType, InstantType(Date.from(it))),
            )
          }
        }
        .build()

    updateIndicesForResource(index, resource.resourceType, resourceUuid)

    return entity.resourceUuid
  }

to reuse the same resourceUUID if it doesn't exist on the DB and the resource has a resourceID. Currently, the function above checks if the resource logicalID/resourceID exists in resourcesEntity table and if not generates a random UUID which is used to save the resource there.

Here's the function that checks that for both Remote and Local entries

  private suspend fun insertRemoteResource(resource: Resource): UUID {
    val existingResourceEntity = getResourceEntity(resource.logicalId, resource.resourceType)
    if (existingResourceEntity != null) {
      applyRemoteUpdate(resource)
      return existingResourceEntity.resourceUuid
    }
    return insertResource(resource, null)
  }

I can proceed to do that

cc @f-odhiambo

pld commented 3 months ago

nice, exciting!

SebaMutuku commented 3 months ago

@ellykits @pld this implementation is now complete. However we need to make changeds on FhirEngine at this function

 private suspend fun insertResource(resource: Resource, lastUpdatedLocal: Instant?): UUID {
    val resourceUuid = UUID.randomUUID()

    // Use the local UUID as the logical ID of the resource
    if (resource.id.isNullOrEmpty()) {
      resource.id = resourceUuid.toString()
    }

    val entity =
      ResourceEntity(
        id = 0,
        resourceType = resource.resourceType,
        resourceUuid = resourceUuid,
        resourceId = resource.logicalId,
        serializedResource = iParser.encodeResourceToString(resource),
        versionId = resource.versionId,
        lastUpdatedRemote = resource.lastUpdated,
        lastUpdatedLocal = lastUpdatedLocal,
      )
    insertResource(entity)

    val index =
      ResourceIndices.Builder(resourceIndexer.index(resource))
        .apply {
          lastUpdatedLocal?.let {
            addDateTimeIndex(
              createLocalLastUpdatedIndex(entity.resourceType, InstantType(Date.from(it))),
            )
          }
        }
        .build()

    updateIndicesForResource(index, resource.resourceType, resourceUuid)

    return entity.resourceUuid
  }

to reuse the same resourceUUID if it doesn't exist on the DB and the resource has a resourceID. Currently, the function above checks if the resource logicalID/resourceID exists in resourcesEntity table and if not generates a random UUID which is used to save the resource there.

Here's the function that checks that for both Remote and Local entries

  private suspend fun insertRemoteResource(resource: Resource): UUID {
    val existingResourceEntity = getResourceEntity(resource.logicalId, resource.resourceType)
    if (existingResourceEntity != null) {
      applyRemoteUpdate(resource)
      return existingResourceEntity.resourceUuid
    }
    return insertResource(resource, null)
  }

I can proceed to do that

cc @f-odhiambo

This was resolved

ellykits commented 3 months ago

I have refactored some lines of code. I've also corrected the file names for the resources directory. The directory should be inside the configs/appId.