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

Fix handling sync status by harmonizing SDK and custom sync statuses #3650

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -25,17 +25,23 @@
import com.google.android.fhir.db.ResourceNotFoundException
import com.google.android.fhir.get
import com.google.android.fhir.knowledge.KnowledgeManager
import com.google.android.fhir.sync.CurrentSyncJobStatus
import com.google.android.fhir.sync.SyncJobStatus
import com.google.android.fhir.sync.SyncOperation
import com.google.android.fhir.sync.SyncDataParams.LAST_UPDATED_KEY
import com.google.android.fhir.sync.download.ResourceSearchParams
import dagger.hilt.android.qualifiers.ApplicationContext
import java.io.FileNotFoundException
import java.io.InputStreamReader
import java.net.UnknownHostException
import java.time.OffsetDateTime
import java.util.Locale
import java.util.PropertyResourceBundle
import java.util.ResourceBundle
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import okhttp3.RequestBody.Companion.toRequestBody
Expand Down Expand Up @@ -102,6 +108,10 @@
val localizationHelper: LocalizationHelper by lazy { LocalizationHelper(this) }
private val supportedFileExtensions = listOf("json", "properties")
private var _isNonProxy = BuildConfig.IS_NON_PROXY_APK
private val _syncState = MutableSharedFlow<CurrentSyncJobStatus>()
val syncState: SharedFlow<CurrentSyncJobStatus> = _syncState

suspend fun setSyncState(state: CurrentSyncJobStatus) = _syncState.emit(state)

/**
* Retrieve configuration for the provided [ConfigType]. The JSON retrieved from [configsJsonMap]
Expand Down Expand Up @@ -565,7 +575,82 @@
return resultBundle
}

suspend fun fetchResources(
suspend fun fetchCustomResources(
gatewayModeHeaderValue: String? = null,

Check warning on line 579 in android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt#L578-L579

Added lines #L578 - L579 were not covered by tests
url: String,
totalCustomRecords: Int = 0,
completedRecords: Int = 0,

Check warning on line 582 in android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt#L581-L582

Added lines #L581 - L582 were not covered by tests
) {
if (completedRecords == 0) {
Timber.d("Setting state: Started")
setSyncState(
CurrentSyncJobStatus.Running(
SyncJobStatus.Started(),

Check warning on line 588 in android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt#L585-L588

Added lines #L585 - L588 were not covered by tests
),
)
}

runCatching {
Timber.d("Setting state: Running")
setSyncState(
CurrentSyncJobStatus.Running(
SyncJobStatus.InProgress(
syncOperation = SyncOperation.DOWNLOAD,
total = totalCustomRecords,
completed = completedRecords,

Check warning on line 600 in android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt#L593-L600

Added lines #L593 - L600 were not covered by tests
),
),
)
Timber.d("Fetching page with URL: $url")

Check warning on line 604 in android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt#L604

Added line #L604 was not covered by tests
if (gatewayModeHeaderValue.isNullOrEmpty()) {
fhirResourceDataSource.getResource(url)

Check warning on line 606 in android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt#L606

Added line #L606 was not covered by tests
} else {
fhirResourceDataSource.getResourceWithGatewayModeHeader(gatewayModeHeaderValue, url)

Check warning on line 608 in android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt#L608

Added line #L608 was not covered by tests
}
}
.onFailure { throwable ->
Timber.e("Error occurred while retrieving resource via URL $url", throwable)
Timber.d("Setting state: Failed")
setSyncState(
CurrentSyncJobStatus.Failed(OffsetDateTime.now()),

Check warning on line 615 in android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt#L612-L615

Added lines #L612 - L615 were not covered by tests
)
return

Check warning on line 617 in android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt#L617

Added line #L617 was not covered by tests
}
.onSuccess { resultBundle ->
processResultBundleEntries(resultBundle.entry)
val newCompletedRecords = completedRecords + resultBundle.entry.size

Check warning on line 621 in android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt#L620-L621

Added lines #L620 - L621 were not covered by tests

Timber.d("Updating state: Running")
setSyncState(
CurrentSyncJobStatus.Running(
SyncJobStatus.InProgress(
syncOperation = SyncOperation.DOWNLOAD,
total = totalCustomRecords,
completed = newCompletedRecords,

Check warning on line 629 in android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt#L623-L629

Added lines #L623 - L629 were not covered by tests
),
),
)

val nextPageUrl = resultBundle.getLink(PAGINATION_NEXT)?.url

if (!nextPageUrl.isNullOrEmpty()) {
fetchCustomResources(
gatewayModeHeaderValue = gatewayModeHeaderValue,
url = nextPageUrl,
totalCustomRecords = totalCustomRecords,
completedRecords = newCompletedRecords, // Pass the new value

Check warning on line 641 in android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt#L637-L641

Added lines #L637 - L641 were not covered by tests
)
} else {
Timber.d("No more pages to fetch. Fetching completed.")
Timber.d("Setting state: Succeeded")
setSyncState(
CurrentSyncJobStatus.Succeeded(OffsetDateTime.now()),

Check warning on line 647 in android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt#L644-L647

Added lines #L644 - L647 were not covered by tests
)
}
}
}

private suspend fun fetchResources(
gatewayModeHeaderValue: String? = null,
url: String,
) {
Expand All @@ -581,11 +666,8 @@
Timber.e("Error occurred while retrieving resource via URL $url", throwable)
}
.getOrThrow()

val nextPageUrl = resultBundle.getLink(PAGINATION_NEXT)?.url

processResultBundleEntries(resultBundle.entry)

if (!nextPageUrl.isNullOrEmpty()) {
fetchResources(
gatewayModeHeaderValue = gatewayModeHeaderValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@
import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.google.android.fhir.sync.CurrentSyncJobStatus
import com.google.android.fhir.sync.concatParams
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import java.net.UnknownHostException
import java.time.OffsetDateTime
import kotlinx.coroutines.withContext
import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry
import org.smartregister.fhircore.engine.data.remote.fhir.resource.FhirResourceDataSource
Expand All @@ -42,22 +44,34 @@
val dispatcherProvider: DispatcherProvider,
val fhirResourceDataSource: FhirResourceDataSource,
) : CoroutineWorker(appContext, workerParams) {

override suspend fun doWork(): Result {
return withContext(dispatcherProvider.io()) {
try {
with(configurationRegistry) {
val (resourceSearchParams, _) = loadResourceSearchParams()
Timber.i("Custom resource sync parameters $resourceSearchParams")
resourceSearchParams
.asIterable()
.filter { it.value.isNotEmpty() }
.map { "${it.key}?${it.value.concatParams()}" }
.forEach { url ->
fetchResources(
gatewayModeHeaderValue = ConfigurationRegistry.FHIR_GATEWAY_MODE_HEADER_VALUE,
url = url,
)
}

// Process resource URLs
val resourceUrls =
resourceSearchParams
.asIterable()
.filter { it.value.isNotEmpty() }
.map { "${it.key}?${it.value.concatParams()}" }

// Fetch summary count first

val summaryCount = fetchSummaryCount(resourceUrls).values.sumOf { it ?: 0 }
Timber.d("Fetched summary count: $summaryCount")

// Fetch resources
resourceUrls.forEach { url ->
fetchCustomResources(
gatewayModeHeaderValue = ConfigurationRegistry.FHIR_GATEWAY_MODE_HEADER_VALUE,
url = url,
totalCustomRecords = summaryCount,

Check warning on line 72 in android/engine/src/main/java/org/smartregister/fhircore/engine/sync/CustomSyncWorker.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/sync/CustomSyncWorker.kt#L69-L72

Added lines #L69 - L72 were not covered by tests
)
}
}
Result.success()
} catch (httpException: HttpException) {
Expand All @@ -72,11 +86,32 @@
Result.failure()
} catch (exception: Exception) {
Timber.e(exception)
configurationRegistry.setSyncState(
CurrentSyncJobStatus.Failed(OffsetDateTime.now()),
)
Result.failure()
}
}
}

/** Fetch summary counts for the provided URLs with summary=count query added. */
private suspend fun fetchSummaryCount(resourceUrls: List<String>): Map<String, Int?> =
resourceUrls
.associate { url ->
// Modify URL to include 'summary=count'
val summaryUrl = "$url&summary=count"
val total: Int? =
runCatching {

Check warning on line 104 in android/engine/src/main/java/org/smartregister/fhircore/engine/sync/CustomSyncWorker.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/sync/CustomSyncWorker.kt#L102-L104

Added lines #L102 - L104 were not covered by tests
// Explicitly fetch resource and ensure result can be cast to Bundle
fhirResourceDataSource.getResource(summaryUrl)

Check warning on line 106 in android/engine/src/main/java/org/smartregister/fhircore/engine/sync/CustomSyncWorker.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/sync/CustomSyncWorker.kt#L106

Added line #L106 was not covered by tests
}
.onFailure { Timber.e(it, "Failed to fetch summary for $summaryUrl") }
.getOrNull()
?.total
summaryUrl to total

Check warning on line 111 in android/engine/src/main/java/org/smartregister/fhircore/engine/sync/CustomSyncWorker.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/sync/CustomSyncWorker.kt#L111

Added line #L111 was not covered by tests
}
.also { summaries -> Timber.i("Summary fetch results: $summaries") }

companion object {
const val WORK_ID = "CustomResourceSyncWorker"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import org.smartregister.fhircore.engine.configuration.ConfigType
import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry
import org.smartregister.fhircore.engine.configuration.geowidget.GeoWidgetConfiguration
import org.smartregister.fhircore.engine.domain.model.SnackBarMessageConfig
import org.smartregister.fhircore.engine.sync.OnSyncListener
import org.smartregister.fhircore.engine.sync.SyncListenerManager
import org.smartregister.fhircore.engine.ui.base.AlertDialogButton
Expand Down Expand Up @@ -210,9 +211,21 @@
}

override fun onSync(syncJobStatus: CurrentSyncJobStatus) {
onSync(syncJobStatus, isCustomSync = false)

Check warning on line 214 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt#L214

Added line #L214 was not covered by tests
}

private fun onSync(syncJobStatus: CurrentSyncJobStatus, isCustomSync: Boolean) {
when (syncJobStatus) {
is CurrentSyncJobStatus.Running -> {
if (syncJobStatus.inProgressSyncJob is SyncJobStatus.InProgress) {
if (syncJobStatus.inProgressSyncJob is SyncJobStatus.Started) {
lifecycleScope.launch {

Check warning on line 221 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt#L221

Added line #L221 was not covered by tests
if (isCustomSync) {
geoWidgetLauncherViewModel.emitSnackBarState(
SnackBarMessageConfig(message = getString(R.string.syncing_custom_resources_toast)),

Check warning on line 224 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt#L223-L224

Added lines #L223 - L224 were not covered by tests
)
}
}
} else {
val inProgressSyncJob = syncJobStatus.inProgressSyncJob as SyncJobStatus.InProgress
val isSyncUpload = inProgressSyncJob.syncOperation == SyncOperation.UPLOAD
val progressPercentage = appMainViewModel.calculatePercentageProgress(inProgressSyncJob)
Expand All @@ -223,12 +236,11 @@
)
}
}
is CurrentSyncJobStatus.Succeeded,
is CurrentSyncJobStatus.Failed, -> {
is CurrentSyncJobStatus.Succeeded -> {
appMainViewModel.updateAppDrawerUIState(currentSyncJobStatus = syncJobStatus)

Check warning on line 240 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt#L240

Added line #L240 was not covered by tests
}
is CurrentSyncJobStatus.Failed -> {
appMainViewModel.updateAppDrawerUIState(currentSyncJobStatus = syncJobStatus)
if (syncJobStatus is CurrentSyncJobStatus.Succeeded) {
geoWidgetLauncherViewModel.onEvent(GeoWidgetEvent.ClearMap)
}
geoWidgetLauncherViewModel.onEvent(
GeoWidgetEvent.RetrieveFeatures(
geoWidgetConfig = geoWidgetConfiguration,
Expand All @@ -244,26 +256,33 @@
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
eventBus.events
.getFor(MainNavigationScreen.GeoWidgetLauncher.eventId(navArgs.geoWidgetId))
.onEach { appEvent ->
when (appEvent) {
is AppEvent.RefreshData,
is AppEvent.OnSubmitQuestionnaire, -> {
appMainViewModel.countRegisterData()
geoWidgetLauncherViewModel.run {
onEvent(GeoWidgetEvent.ClearMap)
onEvent(
GeoWidgetEvent.RetrieveFeatures(
geoWidgetConfig = geoWidgetConfiguration,
searchQuery = searchViewModel.searchQuery.value,
),
)
launch {
configurationRegistry.syncState

Check warning on line 260 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt#L260

Added line #L260 was not covered by tests
.onEach { syncJobStatus -> onSync(syncJobStatus, true) }
.launchIn(this)

Check warning on line 262 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt#L262

Added line #L262 was not covered by tests
}
launch {
eventBus.events
.getFor(MainNavigationScreen.GeoWidgetLauncher.eventId(navArgs.geoWidgetId))

Check warning on line 266 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt#L265-L266

Added lines #L265 - L266 were not covered by tests
.onEach { appEvent ->
when (appEvent) {

Check warning on line 268 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt#L268

Added line #L268 was not covered by tests
is AppEvent.RefreshData,
is AppEvent.OnSubmitQuestionnaire, -> {
appMainViewModel.countRegisterData()
geoWidgetLauncherViewModel.run {
onEvent(GeoWidgetEvent.ClearMap)
onEvent(
GeoWidgetEvent.RetrieveFeatures(

Check warning on line 275 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt#L271-L275

Added lines #L271 - L275 were not covered by tests
geoWidgetConfig = geoWidgetConfiguration,
searchQuery = searchViewModel.searchQuery.value,

Check warning on line 277 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt#L277

Added line #L277 was not covered by tests
),
)
}
}
}
}
}
.launchIn(lifecycleScope)
.launchIn(this)

Check warning on line 284 in android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt

View check run for this annotation

Codecov / codecov/patch

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt#L284

Added line #L284 was not covered by tests
}
}
}
geoWidgetLauncherViewModel.noLocationFoundDialog.observe(viewLifecycleOwner) { show ->
Expand Down
Loading
Loading