Skip to content

Commit

Permalink
Merge branch 'master' into sp/periodic-sync-separate-screen
Browse files Browse the repository at this point in the history
  • Loading branch information
Santosh Pingle committed Oct 14, 2024
2 parents fc01f54 + e702d60 commit 697a10f
Show file tree
Hide file tree
Showing 19 changed files with 140 additions and 165 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class HomeFragment : Fragment(R.layout.fragment_home) {
findNavController().navigate(HomeFragmentDirections.actionHomeFragmentToPatientList())
}
requireView().findViewById<CardView>(R.id.item_sync).setOnClickListener {
findNavController().navigate(HomeFragmentDirections.actionHomeFragmentToManualSyncFragment())
findNavController().navigate(HomeFragmentDirections.actionHomeFragmentToSyncFragment())
}
requireView().findViewById<CardView>(R.id.item_periodic_sync).setOnClickListener {
findNavController()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,41 +75,48 @@ class PeriodicSyncFragment : Fragment() {
private fun periodicSyncJobStatus(periodicSyncJobStatus: PeriodicSyncJobStatus) {
// Handle last sync job status and update UI
periodicSyncJobStatus.lastSyncJobStatus?.let { lastStatus ->
val lastSyncStatusValue = when (lastStatus) {
is LastSyncJobStatus.Succeeded -> getString(R.string.last_sync_status, LastSyncJobStatus.Succeeded::class.java.simpleName)
is LastSyncJobStatus.Failed -> getString(R.string.last_sync_status, LastSyncJobStatus.Failed::class.java.simpleName)
else -> null
}
val lastSyncStatusValue =
when (lastStatus) {
is LastSyncJobStatus.Succeeded ->
getString(R.string.last_sync_status, LastSyncJobStatus.Succeeded::class.java.simpleName)
is LastSyncJobStatus.Failed ->
getString(R.string.last_sync_status, LastSyncJobStatus.Failed::class.java.simpleName)
else -> null
}

lastSyncStatusValue?.let { statusText ->
requireView().findViewById<TextView>(R.id.last_sync_status).text = statusText
requireView().findViewById<TextView>(R.id.last_sync_time).text = getString(
R.string.last_sync_timestamp,
syncFragmentViewModel.formatSyncTimestamp(lastStatus.timestamp)
)
requireView().findViewById<TextView>(R.id.last_sync_time).text =
getString(
R.string.last_sync_timestamp,
syncFragmentViewModel.formatSyncTimestamp(lastStatus.timestamp),
)
}
}

// Set current sync status
val currentSyncStatusTextView = requireView().findViewById<TextView>(R.id.current_sync_status)
currentSyncStatusTextView.text = getString(
R.string.current_status,
periodicSyncJobStatus.currentSyncJobStatus::class.java.simpleName
)
currentSyncStatusTextView.text =
getString(
R.string.current_status,
periodicSyncJobStatus.currentSyncJobStatus::class.java.simpleName,
)

// Update progress indicator visibility based on current sync status
val syncIndicator = requireView().findViewById<ProgressBar>(R.id.sync_indicator)
syncIndicator.visibility = if (periodicSyncJobStatus.currentSyncJobStatus is CurrentSyncJobStatus.Running) {
View.VISIBLE
} else {
View.GONE
}
syncIndicator.visibility =
if (periodicSyncJobStatus.currentSyncJobStatus is CurrentSyncJobStatus.Running) {
View.VISIBLE
} else {
View.GONE
}

// Control the visibility of the current sync status text view
currentSyncStatusTextView.visibility = when (periodicSyncJobStatus.currentSyncJobStatus) {
is CurrentSyncJobStatus.Failed, is CurrentSyncJobStatus.Succeeded -> View.GONE
else -> View.VISIBLE
}
currentSyncStatusTextView.visibility =
when (periodicSyncJobStatus.currentSyncJobStatus) {
is CurrentSyncJobStatus.Failed,
is CurrentSyncJobStatus.Succeeded, -> View.GONE
else -> View.VISIBLE
}
}

}
7 changes: 3 additions & 4 deletions demo/src/main/res/navigation/reference_nav_graph.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
app:destination="@id/patient_list"
/>
<action
android:id="@+id/action_home_fragment_to_manual_sync_fragment"
app:destination="@id/manualSyncFragment"
android:id="@+id/action_home_fragment_to_sync_fragment"
app:destination="@id/syncFragment"
/>
<action
android:id="@+id/action_home_fragment_to_periodic_sync_fragment"
Expand Down Expand Up @@ -89,7 +89,7 @@
</fragment>

<fragment
android:id="@+id/manualSyncFragment"
android:id="@+id/syncFragment"
android:name="com.google.android.fhir.demo.SyncFragment"
android:label="fragment_manual_sync"
tools:layout="@layout/sync"
Expand All @@ -101,5 +101,4 @@
android:label="fragment_periodic_sync"
tools:layout="@layout/periodic_sync"
/>

</navigation>
Original file line number Diff line number Diff line change
Expand Up @@ -559,8 +559,8 @@ class DatabaseImplTest {
// Delete the patient created in setup as we only want to upload the patient in this test
database.deleteUpdates(listOf(TEST_PATIENT_1))
services.fhirEngine
.syncUpload(AllChangesSquashedBundlePut) {
it
.syncUpload(AllChangesSquashedBundlePut) { lcs, _ ->
lcs
.first { it.resourceId == "remote-patient-3" }
.let {
flowOf(
Expand Down
6 changes: 5 additions & 1 deletion engine/src/main/java/com/google/android/fhir/FhirEngine.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.google.android.fhir

import com.google.android.fhir.db.LocalChangeResourceReference
import com.google.android.fhir.db.ResourceNotFoundException
import com.google.android.fhir.search.Search
import com.google.android.fhir.sync.ConflictResolver
Expand Down Expand Up @@ -121,7 +122,10 @@ interface FhirEngine {
@Deprecated("To be deprecated.")
suspend fun syncUpload(
uploadStrategy: UploadStrategy,
upload: (suspend (List<LocalChange>) -> Flow<UploadRequestResult>),
upload:
(suspend (List<LocalChange>, List<LocalChangeResourceReference>) -> Flow<
UploadRequestResult,
>),
): Flow<SyncUploadProgress>

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ internal data class ResourceWithUUID<R>(
val resource: R,
)

internal data class LocalChangeResourceReference(
data class LocalChangeResourceReference(
val localChangeId: Long,
val resourceReferenceValue: String,
val resourceReferencePath: String?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.google.android.fhir.FhirEngineProvider
import com.google.android.fhir.LocalChange
import com.google.android.fhir.SearchResult
import com.google.android.fhir.db.Database
import com.google.android.fhir.db.LocalChangeResourceReference
import com.google.android.fhir.logicalId
import com.google.android.fhir.search.Search
import com.google.android.fhir.search.count
Expand Down Expand Up @@ -124,7 +125,10 @@ internal class FhirEngineImpl(private val database: Database, private val contex

override suspend fun syncUpload(
uploadStrategy: UploadStrategy,
upload: (suspend (List<LocalChange>) -> Flow<UploadRequestResult>),
upload:
(suspend (List<LocalChange>, List<LocalChangeResourceReference>) -> Flow<
UploadRequestResult,
>),
): Flow<SyncUploadProgress> = flow {
val resourceConsolidator =
ResourceConsolidatorFactory.byHttpVerb(uploadStrategy.requestGeneratorMode, database)
Expand All @@ -140,8 +144,10 @@ internal class FhirEngineImpl(private val database: Database, private val contex

while (localChangeFetcher.hasNext()) {
val localChanges = localChangeFetcher.next()
val localChangeReferences =
database.getLocalChangeResourceReferences(localChanges.flatMap { it.token.ids })
val uploadRequestResult =
upload(localChanges)
upload(localChanges, localChangeReferences)
.onEach { result ->
resourceConsolidator.consolidate(result)
val newProgress =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,7 @@ abstract class FhirSyncWorker(appContext: Context, workerParams: WorkerParameter
uploader =
Uploader(
dataSource = dataSource,
patchGenerator =
PatchGeneratorFactory.byMode(
getUploadStrategy().patchGeneratorMode,
FhirEngineProvider.getFhirDatabase(applicationContext),
),
patchGenerator = PatchGeneratorFactory.byMode(getUploadStrategy().patchGeneratorMode),
requestGenerator =
UploadRequestGeneratorFactory.byMode(getUploadStrategy().requestGeneratorMode),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.google.android.fhir.sync.upload

import com.google.android.fhir.LocalChange
import com.google.android.fhir.db.LocalChangeResourceReference
import com.google.android.fhir.sync.DataSource
import com.google.android.fhir.sync.ResourceSyncException
import com.google.android.fhir.sync.upload.patch.PatchGenerator
Expand Down Expand Up @@ -49,9 +50,12 @@ internal class Uploader(
private val patchGenerator: PatchGenerator,
private val requestGenerator: UploadRequestGenerator,
) {
suspend fun upload(localChanges: List<LocalChange>) =
suspend fun upload(
localChanges: List<LocalChange>,
localChangesReferences: List<LocalChangeResourceReference>,
) =
localChanges
.let { patchGenerator.generate(it) }
.let { patchGenerator.generate(it, localChangesReferences) }
.let { requestGenerator.generateUploadRequests(it) }
.asFlow()
.transformWhile {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package com.google.android.fhir.sync.upload.patch

import com.google.android.fhir.LocalChange
import com.google.android.fhir.db.Database
import com.google.android.fhir.db.LocalChangeResourceReference

/**
* Generates [Patch]es from [LocalChange]s and output [List<[StronglyConnectedPatchMappings]>] to
Expand All @@ -35,17 +35,17 @@ internal interface PatchGenerator {
* NOTE: different implementations may have requirements on the size of [localChanges] and output
* certain numbers of [Patch]es.
*/
suspend fun generate(localChanges: List<LocalChange>): List<StronglyConnectedPatchMappings>
suspend fun generate(
localChanges: List<LocalChange>,
localChangesReferences: List<LocalChangeResourceReference>,
): List<StronglyConnectedPatchMappings>
}

internal object PatchGeneratorFactory {
fun byMode(
mode: PatchGeneratorMode,
database: Database,
): PatchGenerator =
fun byMode(mode: PatchGeneratorMode): PatchGenerator =
when (mode) {
is PatchGeneratorMode.PerChange -> PerChangePatchGenerator
is PatchGeneratorMode.PerResource -> PerResourcePatchGenerator.with(database)
is PatchGeneratorMode.PerResource -> PerResourcePatchGenerator
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package com.google.android.fhir.sync.upload.patch

import androidx.annotation.VisibleForTesting
import com.google.android.fhir.db.Database
import com.google.android.fhir.db.LocalChangeResourceReference

/** Represents a resource e.g. 'Patient/123' , 'Encounter/123'. */
Expand Down Expand Up @@ -68,27 +67,27 @@ internal object PatchOrdering {
get() = "${generatedPatch.resourceType}/${generatedPatch.resourceId}"

/**
* Order the [PatchMapping] so that if the resource A has outgoing references {B,C} (CREATE) and
* {D} (UPDATE), then B,C needs to go before the resource A so that referential integrity is
* retained. Order of D shouldn't matter for the purpose of referential integrity.
* Orders the list of [PatchMapping]s to maintain referential integrity.
*
* @return A ordered list of the [StronglyConnectedPatchMappings] containing:
* - [StronglyConnectedPatchMappings] with single value for the [PatchMapping] based on the
* references to other [PatchMapping] if the mappings are acyclic
* - [StronglyConnectedPatchMappings] with multiple values for [PatchMapping]s based on the
* references to other [PatchMapping]s if the mappings are cyclic.
* This function ensures that if resource A has a CREATE reference to resources B and C, then B
* and C appear before A in the ordered list. UPDATE references are not considered as they do not
* impact referential integrity.
*
* The function uses Strongly Connected Components (SCC) to handle cyclic dependencies.
*
* @return A list of [StronglyConnectedPatchMappings]:
* - Each [StronglyConnectedPatchMappings] object represents an SCC.
* - If the graph of references is acyclic, each [StronglyConnectedPatchMappings] will contain
* a single [PatchMapping].
* - If the graph has cycles, a [StronglyConnectedPatchMappings] object will contain multiple
* [PatchMapping]s involved in the cycle.
*/
suspend fun List<PatchMapping>.sccOrderByReferences(
database: Database,
fun List<PatchMapping>.sccOrderByReferences(
localChangeResourceReferences: List<LocalChangeResourceReference>,
): List<StronglyConnectedPatchMappings> {
val resourceIdToPatchMapping = associateBy { patchMapping -> patchMapping.resourceTypeAndId }
/* Get LocalChangeResourceReferences for all the local changes. A single LocalChange may have
multiple LocalChangeResourceReference, one for each resource reference in the
LocalChange.payload.*/
val localChangeIdToResourceReferenceMap: Map<Long, List<LocalChangeResourceReference>> =
database
.getLocalChangeResourceReferences(flatMap { it.localChanges.flatMap { it.token.ids } })
.groupBy { it.localChangeId }
val localChangeIdToResourceReferenceMap =
localChangeResourceReferences.groupBy { it.localChangeId }

val adjacencyList = createAdjacencyListForCreateReferences(localChangeIdToResourceReferenceMap)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.google.android.fhir.sync.upload.patch

import com.google.android.fhir.LocalChange
import com.google.android.fhir.db.LocalChangeResourceReference

/**
* Generates a [Patch] for each [LocalChange].
Expand All @@ -27,6 +28,7 @@ import com.google.android.fhir.LocalChange
internal object PerChangePatchGenerator : PatchGenerator {
override suspend fun generate(
localChanges: List<LocalChange>,
localChangesReferences: List<LocalChangeResourceReference>,
): List<StronglyConnectedPatchMappings> =
localChanges
.map {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import com.github.fge.jackson.JsonLoader
import com.github.fge.jsonpatch.JsonPatch
import com.google.android.fhir.LocalChange
import com.google.android.fhir.LocalChange.Type
import com.google.android.fhir.db.Database
import com.google.android.fhir.db.LocalChangeResourceReference
import com.google.android.fhir.sync.upload.patch.PatchOrdering.sccOrderByReferences

/**
Expand All @@ -32,13 +32,13 @@ import com.google.android.fhir.sync.upload.patch.PatchOrdering.sccOrderByReferen
* maintain an audit trail, but instead, multiple changes made to the same FHIR resource on the
* client can be recorded as a single change on the server.
*/
internal class PerResourcePatchGenerator private constructor(val database: Database) :
PatchGenerator {
internal object PerResourcePatchGenerator : PatchGenerator {

override suspend fun generate(
localChanges: List<LocalChange>,
localChangesReferences: List<LocalChangeResourceReference>,
): List<StronglyConnectedPatchMappings> {
return generateSquashedChangesMapping(localChanges).sccOrderByReferences(database)
return generateSquashedChangesMapping(localChanges).sccOrderByReferences(localChangesReferences)
}

@androidx.annotation.VisibleForTesting
Expand Down Expand Up @@ -147,20 +147,4 @@ internal class PerResourcePatchGenerator private constructor(val database: Datab
mergedOperations.values.flatten().forEach(mergedNode::add)
return objectMapper.writeValueAsString(mergedNode)
}

companion object {

@Volatile private lateinit var _instance: PerResourcePatchGenerator

@Synchronized
fun with(database: Database): PerResourcePatchGenerator {
if (!::_instance.isInitialized) {
_instance = PerResourcePatchGenerator(database)
} else if (_instance.database != database) {
_instance = PerResourcePatchGenerator(database)
}

return _instance
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.google.android.fhir.FhirEngine
import com.google.android.fhir.LocalChange
import com.google.android.fhir.LocalChangeToken
import com.google.android.fhir.SearchResult
import com.google.android.fhir.db.LocalChangeResourceReference
import com.google.android.fhir.search.Search
import com.google.android.fhir.sync.ConflictResolver
import com.google.android.fhir.sync.DataSource
Expand Down Expand Up @@ -158,10 +159,11 @@ internal object TestFhirEngineImpl : FhirEngine {

override suspend fun syncUpload(
uploadStrategy: UploadStrategy,
upload: suspend (List<LocalChange>) -> Flow<UploadRequestResult>,
upload:
suspend (List<LocalChange>, List<LocalChangeResourceReference>) -> Flow<UploadRequestResult>,
): Flow<SyncUploadProgress> = flow {
emit(SyncUploadProgress(1, 1))
upload(getLocalChanges(ResourceType.Patient, "123")).collect {
upload(getLocalChanges(ResourceType.Patient, "123"), emptyList()).collect {
when (it) {
is UploadRequestResult.Success -> emit(SyncUploadProgress(0, 1))
is UploadRequestResult.Failure -> emit(SyncUploadProgress(1, 1, it.uploadError))
Expand Down
Loading

0 comments on commit 697a10f

Please sign in to comment.