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

Resource consolidation after using the AllChangesSquashedBundlePost upload strategy for resource creation. #2509

Merged
merged 38 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
3028c9d
draft singleresourcepost
Mar 5, 2024
dcecb04
Remove dead code.
Mar 5, 2024
133cc61
resource consolidation after post http verb request
Mar 11, 2024
ddb6387
Remove local changes.
Mar 14, 2024
9e194e3
fix unit tests.
Mar 14, 2024
1440793
Merge branch 'master' into sp/post-resource-creation
Mar 15, 2024
7f22636
unit tests
Mar 15, 2024
2ad3d27
Update kotlin api docs.
Mar 18, 2024
7a4cc0c
revert local changes.
Mar 18, 2024
a88125e
Resource consolidation as per http verb
Mar 29, 2024
d7694d7
address review comments.
Apr 3, 2024
5eeb2fa
Merge branch 'master' into sp/post-resource-creation
santosh-pingle Apr 3, 2024
05c2c58
order of arguments
Apr 3, 2024
ee2c41c
code to string conversion
Apr 3, 2024
efaa122
AllChangesBundlePost upload strategy.
Apr 8, 2024
9de86d3
remove localchange reference updates code.
Apr 8, 2024
7f01f0b
unit tests
Apr 10, 2024
8cd9e08
Address review comments.
Apr 11, 2024
065e0ac
Fix unit test.
Apr 12, 2024
df04add
Merge branch 'sp/post-resource-creation' into sp/bundle-post
Apr 12, 2024
912d8d6
rename tests.
Apr 12, 2024
e467f7e
Code cleaning.
Apr 12, 2024
366e3d5
code cleaning.
Apr 15, 2024
5d5509d
Merge branch 'master' into sp/bundle-post
May 28, 2024
043b674
Merge branch 'master' into sp/bundle-post
Jun 19, 2024
507b294
Address review comments.
Aug 28, 2024
0f959ef
Address review comments.
Aug 28, 2024
7cadc8c
Address review comments.
Aug 28, 2024
b49b8c4
Merge branch 'master' into sp/bundle-post
santosh-pingle Aug 28, 2024
dd35279
Merge branch 'master' into sp/bundle-post
santosh-pingle Aug 29, 2024
6d52a62
spotless apply.
Aug 29, 2024
43c3854
build failure.
Aug 29, 2024
991685e
cleanup.
Aug 30, 2024
01517ef
Address review comments.
Sep 4, 2024
b99daee
Merge branch 'master' into sp/bundle-post
santosh-pingle Sep 4, 2024
18eb48e
Address review comments.
Sep 4, 2024
30b9ceb
Address review comments.
Sep 6, 2024
927865e
Merge branch 'master' into sp/bundle-post
santosh-pingle Sep 6, 2024
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 @@ -20,12 +20,15 @@ import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import ca.uhn.fhir.context.FhirContext
import ca.uhn.fhir.context.FhirVersionEnum
import com.google.android.fhir.FhirServices
import com.google.android.fhir.db.Database
import com.google.android.fhir.db.ResourceNotFoundException
import com.google.android.fhir.logicalId
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.runBlocking
import org.hl7.fhir.r4.model.Bundle
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent
import org.hl7.fhir.r4.model.DomainResource
import org.hl7.fhir.r4.model.InstantType
import org.hl7.fhir.r4.model.Observation
Expand Down Expand Up @@ -228,4 +231,230 @@ class HttpPostResourceConsolidatorTest {
)
.isEqualTo("Patient/patient2")
}

@Test
fun consolidate_allChangesBundleSquashedPost_shouldUpdateResourceId() = runBlocking {
santosh-pingle marked this conversation as resolved.
Show resolved Hide resolved
val preSyncPatientJsonString =
"""
{
"resourceType": "Patient",
"id": "patient1"
}
"""
.trimIndent()
val preSyncPatient =
FhirContext.forCached(FhirVersionEnum.R4)
jingtang10 marked this conversation as resolved.
Show resolved Hide resolved
.newJsonParser()
.parseResource(preSyncPatientJsonString) as DomainResource
database.insert(preSyncPatient)
val localChanges =
database.getLocalChanges(preSyncPatient.resourceType, preSyncPatient.logicalId)
val bundleEntryComponentJsonString =
"""
{
"resourceType": "Bundle",
"id": "bundle1",
"type": "transaction-response",
"entry": [
{
"response": {
"status": "201 Created",
"location": "Patient/patient2/_history/1",
"etag": "1",
"lastModified": "2024-04-08T11:15:42.648+00:00",
"outcome": {
"resourceType": "OperationOutcome"
}
}
},
{
"response": {
"status": "201 Created",
"location": "Encounter/8055/_history/1",
"etag": "1",
"lastModified": "2024-04-08T11:15:42.648+00:00",
"outcome": {
"resourceType": "OperationOutcome"
}
}
}
]
}
"""
.trimIndent()

val postSyncResponseBundle =
FhirContext.forCached(FhirVersionEnum.R4)
.newJsonParser()
.parseResource(bundleEntryComponentJsonString) as Bundle

val patientResponseEntry =
jingtang10 marked this conversation as resolved.
Show resolved Hide resolved
(postSyncResponseBundle.entry.firstOrNull() as BundleEntryComponent).response
val secondResponseEntry = postSyncResponseBundle.entry[1]
santosh-pingle marked this conversation as resolved.
Show resolved Hide resolved

val uploadRequestResult =
UploadRequestResult.Success(
listOf(BundleComponentUploadResponseMapping(localChanges, patientResponseEntry)),
)

resourceConsolidator.consolidate(uploadRequestResult)

assertThat(database.select(ResourceType.Patient, "patient2").logicalId)
.isEqualTo(patientResponseEntry.resourceIdAndType?.first)

val exception =
assertThrows(ResourceNotFoundException::class.java) {
runBlocking { database.select(ResourceType.Patient, "patient1") }
}

assertThat(exception.message).isEqualTo("Resource not found with type Patient and id patient1!")
}

@Test
fun consolidate_allChangesBundleSquashedPost_dependentResources_shouldUpdateReferenceValue() =
runBlocking {
santosh-pingle marked this conversation as resolved.
Show resolved Hide resolved
val preSyncPatientJsonString =
"""
{
"resourceType": "Patient",
"id": "patient1"
}
"""
.trimIndent()
val preSyncPatient =
FhirContext.forCached(FhirVersionEnum.R4)
.newJsonParser()
.parseResource(preSyncPatientJsonString) as DomainResource
val preSyncObservationJsonString =
"""
{
"resourceType": "Observation",
"id": "observation1",
"subject": {
"reference": "Patient/patient1"
}
}
"""
.trimIndent()
val preSyncObservation =
FhirContext.forCached(FhirVersionEnum.R4)
.newJsonParser()
.parseResource(preSyncObservationJsonString) as DomainResource
database.insert(preSyncPatient, preSyncObservation)
val patientLocalChanges =
database.getLocalChanges(preSyncPatient.resourceType, preSyncPatient.logicalId)
val observationLocalChanges =
database.getLocalChanges(preSyncObservation.resourceType, preSyncObservation.logicalId)
val bundleEntryComponentJsonString =
"""
{
"resourceType": "Bundle",
"id": "bundle1",
"type": "transaction-response",
"entry": [
{
"response": {
"status": "201 Created",
"location": "Patient/patient2/_history/1",
"etag": "1",
"lastModified": "2024-04-08T11:15:42.648+00:00",
"outcome": {
"resourceType": "OperationOutcome"
}
}
},
{
"response": {
"status": "201 Created",
"location": "Observation/observation2/_history/1",
"etag": "1",
"lastModified": "2024-04-08T11:15:42.648+00:00",
"outcome": {
"resourceType": "OperationOutcome"
}
}
}
]
}
"""
.trimIndent()

val postSyncResponseBundle =
FhirContext.forCached(FhirVersionEnum.R4)
.newJsonParser()
.parseResource(bundleEntryComponentJsonString) as Bundle

val patientResponseEntry =
(postSyncResponseBundle.entry.firstOrNull() as BundleEntryComponent).response
val observationResponseEntry =
(postSyncResponseBundle.entry[1] as BundleEntryComponent).response

val uploadRequestResult =
UploadRequestResult.Success(
listOf(
BundleComponentUploadResponseMapping(patientLocalChanges, patientResponseEntry),
BundleComponentUploadResponseMapping(observationLocalChanges, observationResponseEntry),
),
)

resourceConsolidator.consolidate(uploadRequestResult)

assertThat(
(database.select(ResourceType.Observation, "observation2") as Observation)
.subject
.reference,
)
.isEqualTo("Patient/patient2")
}

@Test
fun consolidate_allChangesBundleSquashedPost_shouldDiscardLocalChanges() = runBlocking {
val preSyncPatientJsonString =
"""
{
"resourceType": "Patient",
"id": "patient1"
}
"""
.trimIndent()
val preSyncPatient =
FhirContext.forCached(FhirVersionEnum.R4)
.newJsonParser()
.parseResource(preSyncPatientJsonString) as DomainResource
database.insert(preSyncPatient)
val localChanges =
database.getLocalChanges(preSyncPatient.resourceType, preSyncPatient.logicalId)
val bundleEntryComponentJsonString =
"""
{
"resourceType": "Bundle",
"id": "bundle1",
"type": "transaction-response",
"entry": [
{
"response": {
"status": "201 Created",
"location": "Patient/patient2/_history/1",
"etag": "1"
}
}
]
}
"""
.trimIndent()
val postSyncResponseBundle =
FhirContext.forCached(FhirVersionEnum.R4)
.newJsonParser()
.parseResource(bundleEntryComponentJsonString) as Bundle
val patientResponseEntry =
(postSyncResponseBundle.entry.firstOrNull() as BundleEntryComponent).response
val uploadRequestResult =
UploadRequestResult.Success(
listOf(BundleComponentUploadResponseMapping(localChanges, patientResponseEntry)),
)

resourceConsolidator.consolidate(uploadRequestResult)

assertThat(database.getAllLocalChanges()).isEmpty()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ interface FhirEngine {
* This function initiates multiple server calls to upload local changes. The results of each call
* are emitted as [UploadRequestResult] objects, which can be collected using a [Flow].
*
* @param localChangesFetchMode Specifies how to fetch local changes for upload.
* @param uploadStrategy Defines strategies for uploading FHIR resource.
* @param upload A suspending function that takes a list of [LocalChange] objects and returns a
* [Flow] of [UploadRequestResult] objects.
* @return A [Flow] that emits the progress of the synchronization process as [SyncUploadProgress]
Expand Down
39 changes: 39 additions & 0 deletions engine/src/main/java/com/google/android/fhir/db/Database.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,30 @@ internal interface Database {
lastUpdated: Instant,
)

/**
* Updates existing [Resource] present in the [ResourceEntity]. It updates [Resource.id],
* metadata, and reference values of the dependent resources. This method is more suitable if
* [preSyncResourceId] and post-sync resourceId [postSyncResource] are different. However, even if
* [preSyncResourceId] and post-sync resourceId are the same, it still updates the reference value
* of referring resources, which is just redundant.
santosh-pingle marked this conversation as resolved.
Show resolved Hide resolved
santosh-pingle marked this conversation as resolved.
Show resolved Hide resolved
*
* @param preSyncResourceId The [Resource.id] of the resource before synchronization.
* @param postSyncResourceID The [Resource.id] of the resource after synchronization.
* @param postSyncResourceVersionId The version id of the resource after synchronization.
* @param postSyncResourceLastUpdated The last modified time of the resource after
* synchronization.
* @param dependentResources The dependent resources for which the reference value will be
* changed.
*/
suspend fun updateResourcesPostSync(
preSyncResourceId: String,
postSyncResourceID: String,
santosh-pingle marked this conversation as resolved.
Show resolved Hide resolved
resourceType: ResourceType,
postSyncResourceVersionId: String,
postSyncResourceLastUpdated: Instant,
dependentResources: List<UUID> = emptyList(),
)

/**
* Selects the FHIR resource of type `clazz` with `id`.
*
Expand Down Expand Up @@ -194,6 +218,21 @@ internal interface Database {
suspend fun getLocalChangeResourceReferences(
localChangeIds: List<Long>,
): List<LocalChangeResourceReference>

/**
* Retrieves a list of UUIDs for resources that reference [preSyncResourceId]. [preSyncResourceId]
* can be referenced as the reference value in other resources, returning those resource UUIDs.
* Essentially, [LocalChangeResourceReference] contains
* [LocalChangeResourceReference.resourceReferenceValue] and
* [LocalChangeResourceReference.localChangeId]. [LocalChange] contains UUIDs for every resource.
santosh-pingle marked this conversation as resolved.
Show resolved Hide resolved
*
* @param preSyncResource The resource that is being referenced.
* @return A list of UUIDs of resources that reference [preSyncResource].
*/
suspend fun getResourceUuidsThatReferenceTheGivenResource(
preSyncResourceId: String,
santosh-pingle marked this conversation as resolved.
Show resolved Hide resolved
resourceType: ResourceType,
): List<UUID>
}

internal data class ResourceWithUUID<R>(
Expand Down
Loading
Loading