From 2a8a38839b7d7f9a5c0d18ff7f4c7ebf7a4cffca Mon Sep 17 00:00:00 2001 From: Milen Marinov Date: Wed, 25 Oct 2023 18:53:30 +0300 Subject: [PATCH 001/197] [CORE-2976] Add a re-login button on sync card --- feature/dashboard/build.gradle.kts | 3 +- .../dashboard/main/sync/SyncFragment.kt | 17 +++++++++ .../dashboard/main/sync/SyncViewModel.kt | 36 ++++++++++++++++++- .../feature/dashboard/views/SyncCardState.kt | 4 +++ .../feature/dashboard/views/SyncCardView.kt | 21 +++++------ .../src/main/res/layout/layout_card_sync.xml | 26 ++++++++++++++ .../main/res/navigation/graph_dashboard.xml | 5 +++ .../resources/src/main/res/values/strings.xml | 2 ++ 8 files changed, 102 insertions(+), 12 deletions(-) diff --git a/feature/dashboard/build.gradle.kts b/feature/dashboard/build.gradle.kts index c062a54dfb..c8ec8d7b59 100644 --- a/feature/dashboard/build.gradle.kts +++ b/feature/dashboard/build.gradle.kts @@ -17,8 +17,9 @@ dependencies { implementation(project(":infra:auth-store")) implementation(project(":infra:auth-logic")) implementation(project(":infra:recent-user-activity")) - implementation(project(":feature:consent")) implementation(project(":infra:project-security-store")) + implementation(project(":feature:consent")) + implementation(project(":feature:login")) implementation(libs.fuzzywuzzy.core) diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncFragment.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncFragment.kt index b9bcdd43ca..a95ae81f0f 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncFragment.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncFragment.kt @@ -7,12 +7,16 @@ import android.view.View import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController +import com.simprints.core.livedata.LiveDataEventWithContentObserver import com.simprints.infra.uibase.viewbinding.viewBinding import com.simprints.feature.dashboard.R import com.simprints.feature.dashboard.databinding.FragmentDashboardCardSyncBinding import com.simprints.feature.dashboard.requestlogin.LogoutReason import com.simprints.feature.dashboard.requestlogin.RequestLoginFragmentArgs import com.simprints.infra.resources.R as IDR +import com.simprints.feature.login.LoginContract +import com.simprints.feature.login.LoginResult +import com.simprints.infra.uibase.navigation.handleResult import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -25,6 +29,12 @@ internal class SyncFragment : Fragment(R.layout.fragment_dashboard_card_sync) { super.onViewCreated(view, savedInstanceState) initViews() observeLiveData() + + findNavController().handleResult( + viewLifecycleOwner, + R.id.mainFragment, + LoginContract.LOGIN_DESTINATION_ID, + ) { result -> viewModel.handleLoginResult(result) } } private fun initViews() = with(binding.dashboardSyncCard) { @@ -32,6 +42,7 @@ internal class SyncFragment : Fragment(R.layout.fragment_dashboard_card_sync) { onOfflineButtonClick = { startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS)) } onSelectNoModulesButtonClick = { findNavController().navigate(R.id.action_mainFragment_to_moduleSelectionFragment) } + onLoginButtonClick = { viewModel.login() } } private fun observeLiveData() { @@ -55,5 +66,11 @@ internal class SyncFragment : Fragment(R.layout.fragment_dashboard_card_sync) { RequestLoginFragmentArgs(logoutReason = logoutReason).toBundle() ) } + viewModel.loginRequestedEventLiveData.observe(viewLifecycleOwner, LiveDataEventWithContentObserver { loginArgs -> + findNavController().navigate( + R.id.action_mainFragment_to_login, + loginArgs + ) + }) } } diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncViewModel.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncViewModel.kt index 5ef6378696..d16a05ffca 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncViewModel.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncViewModel.kt @@ -1,5 +1,6 @@ package com.simprints.feature.dashboard.main.sync +import android.os.Bundle import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData @@ -7,12 +8,15 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.simprints.core.ExternalScope import com.simprints.core.livedata.LiveDataEvent +import com.simprints.core.livedata.LiveDataEventWithContent +import com.simprints.core.livedata.send import com.simprints.core.tools.time.TimeHelper import com.simprints.feature.dashboard.views.SyncCardState import com.simprints.feature.dashboard.views.SyncCardState.SyncComplete import com.simprints.feature.dashboard.views.SyncCardState.SyncConnecting import com.simprints.feature.dashboard.views.SyncCardState.SyncDefault import com.simprints.feature.dashboard.views.SyncCardState.SyncFailed +import com.simprints.feature.dashboard.views.SyncCardState.SyncFailedSignInRequired import com.simprints.feature.dashboard.views.SyncCardState.SyncFailedBackendMaintenance import com.simprints.feature.dashboard.views.SyncCardState.SyncHasNoModules import com.simprints.feature.dashboard.views.SyncCardState.SyncOffline @@ -20,6 +24,8 @@ import com.simprints.feature.dashboard.views.SyncCardState.SyncPendingUpload import com.simprints.feature.dashboard.views.SyncCardState.SyncProgress import com.simprints.feature.dashboard.views.SyncCardState.SyncTooManyRequests import com.simprints.feature.dashboard.views.SyncCardState.SyncTryAgain +import com.simprints.feature.login.LoginContract +import com.simprints.feature.login.LoginResult import com.simprints.infra.authlogic.AuthManager import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.sync.ConfigManager @@ -34,6 +40,7 @@ import com.simprints.infra.eventsync.status.models.EventSyncWorkerState import com.simprints.infra.network.ConnectivityTracker import com.simprints.infra.projectsecuritystore.SecurityStateRepository import com.simprints.infra.projectsecuritystore.securitystate.models.SecurityState +import com.simprints.infra.recent.user.activity.RecentUserActivityManager import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob @@ -49,8 +56,9 @@ internal class SyncViewModel @Inject constructor( private val configManager: ConfigManager, private val timeHelper: TimeHelper, private val authStore: AuthStore, - private val securityStateRepository: SecurityStateRepository, private val authManager: AuthManager, + private val securityStateRepository: SecurityStateRepository, + private val recentUserActivityManager: RecentUserActivityManager, @ExternalScope private val externalScope: CoroutineScope, ) : ViewModel() { @@ -69,6 +77,9 @@ internal class SyncViewModel @Inject constructor( val signOutEventLiveData: LiveData get() = _signOutEventLiveData private val _signOutEventLiveData = MediatorLiveData() + val loginRequestedEventLiveData: LiveData> + get() = _loginRequestedEventLiveData + private val _loginRequestedEventLiveData = MutableLiveData>() private val upSyncCountLiveData = MutableLiveData(0) private val syncStateLiveData = eventSyncManager.getLastSyncState() @@ -109,6 +120,23 @@ internal class SyncViewModel @Inject constructor( eventSyncManager.sync() } + fun login() { + viewModelScope.launch { + val userId = recentUserActivityManager.getRecentUserActivity().lastUserUsed + val loginArgs = LoginContract.toArgs( + authStore.signedInProjectId, + userId + ) + _loginRequestedEventLiveData.send(loginArgs) + } + } + + fun handleLoginResult(result: LoginResult) { + if (result.isSuccess) { + sync() + } + } + private fun startInitialSyncIfRequired() { viewModelScope.launch { val lastUpdate = lastTimeSyncRun ?: eventSyncManager.getLastSyncTime() @@ -209,6 +237,9 @@ internal class SyncViewModel @Inject constructor( syncState.progress, syncState.total ) + isSyncFailedBecauseSignInRequired(allSyncStates) -> SyncFailedSignInRequired( + lastTimeSyncSucceed() + ) isSyncFailedBecauseTooManyRequests(allSyncStates) -> SyncTooManyRequests( lastTimeSyncSucceed() ) @@ -234,6 +265,9 @@ internal class SyncViewModel @Inject constructor( private fun isSyncConnecting(allSyncStates: List) = allSyncStates.any { it.state is EventSyncWorkerState.Enqueued } + private fun isSyncFailedBecauseSignInRequired(allSyncStates: List) = + allSyncStates.any { it.state is EventSyncWorkerState.Failed && (it.state as EventSyncWorkerState.Failed).failedBecauseSignInRequired } + private fun isSyncFailedBecauseTooManyRequests(allSyncStates: List) = allSyncStates.any { it.state is EventSyncWorkerState.Failed && (it.state as EventSyncWorkerState.Failed).failedBecauseTooManyRequest } diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/views/SyncCardState.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/views/SyncCardState.kt index f912517339..e5db5cdf70 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/views/SyncCardState.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/views/SyncCardState.kt @@ -27,6 +27,10 @@ internal sealed class SyncCardState(open val lastTimeSyncSucceed: String?) { override val lastTimeSyncSucceed: String?, ) : SyncCardState(lastTimeSyncSucceed) + data class SyncFailedSignInRequired( + override val lastTimeSyncSucceed: String?, + ) : SyncCardState(lastTimeSyncSucceed) + data class SyncFailedBackendMaintenance( override val lastTimeSyncSucceed: String?, val estimatedOutage: Long? = null diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/views/SyncCardView.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/views/SyncCardView.kt index 70fbc94648..949bcd72dd 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/views/SyncCardView.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/views/SyncCardView.kt @@ -26,6 +26,7 @@ internal class SyncCardView : MaterialCardView { var onSyncButtonClick: () -> Unit = {} var onSelectNoModulesButtonClick: () -> Unit = {} var onOfflineButtonClick: () -> Unit = {} + var onLoginButtonClick: () -> Unit = {} private val binding = LayoutCardSyncBinding.inflate(LayoutInflater.from(context), this) init { @@ -38,10 +39,8 @@ internal class SyncCardView : MaterialCardView { is SyncCardState.SyncDefault -> prepareSyncDefaultStateView() is SyncCardState.SyncPendingUpload -> prepareSyncDefaultStateView(state.itemsToUpSync) is SyncCardState.SyncFailed -> prepareSyncFailedStateView() - is SyncCardState.SyncFailedBackendMaintenance -> prepareSyncFailedBecauseBackendMaintenanceView( - state - ) - + is SyncCardState.SyncFailedSignInRequired -> prepareSyncFailedBecauseSignInRequired() + is SyncCardState.SyncFailedBackendMaintenance -> prepareSyncFailedBecauseBackendMaintenanceView(state) is SyncCardState.SyncTooManyRequests -> prepareSyncTooManyRequestsView() is SyncCardState.SyncTryAgain -> prepareTryAgainStateView() is SyncCardState.SyncHasNoModules -> prepareNoModulesStateView() @@ -60,6 +59,7 @@ internal class SyncCardView : MaterialCardView { binding.syncCardOffline.visibility = View.GONE binding.syncCardProgress.visibility = View.GONE binding.syncCardTryAgain.visibility = View.GONE + binding.syncCardReloginRequired.visibility = View.GONE } private fun prepareSyncDefaultStateView(itemsToSync: Int = 0) { @@ -82,6 +82,11 @@ internal class SyncCardView : MaterialCardView { resources.getString(R.string.dashboard_sync_card_failed_message) } + private fun prepareSyncFailedBecauseSignInRequired() { + binding.syncCardReloginRequired.visibility = View.VISIBLE + binding.syncCardReloginRequiredLoginButton.setOnClickListener { onLoginButtonClick() } + } + private fun prepareSyncFailedBecauseBackendMaintenanceView(state: SyncCardState.SyncFailedBackendMaintenance) { binding.syncCardFailedMessage.visibility = View.VISIBLE binding.syncCardFailedMessage.text = @@ -102,9 +107,7 @@ internal class SyncCardView : MaterialCardView { private fun prepareTryAgainStateView() { binding.syncCardTryAgain.visibility = View.VISIBLE - binding.syncCardTryAgainSyncButton.setOnClickListener { - onSyncButtonClick() - } + binding.syncCardTryAgainSyncButton.setOnClickListener { onSyncButtonClick() } } private fun prepareNoModulesStateView() { @@ -116,9 +119,7 @@ internal class SyncCardView : MaterialCardView { private fun prepareSyncOfflineView() { binding.syncCardOffline.visibility = View.VISIBLE - binding.syncCardOfflineButton.setOnClickListener { - onOfflineButtonClick() - } + binding.syncCardOfflineButton.setOnClickListener { onOfflineButtonClick() } } private fun prepareProgressView(state: SyncCardState.SyncProgress) { diff --git a/feature/dashboard/src/main/res/layout/layout_card_sync.xml b/feature/dashboard/src/main/res/layout/layout_card_sync.xml index 2d66248e64..fce2ea46f3 100644 --- a/feature/dashboard/src/main/res/layout/layout_card_sync.xml +++ b/feature/dashboard/src/main/res/layout/layout_card_sync.xml @@ -191,6 +191,32 @@ android:text="@string/dashboard_sync_card_try_again_button" /> + + + + + + + + + + Please turn on internet connection in settings Settings Too many modules have been downloaded. + You need to log in again in order to sync + Last sync: %1$s All records uploaded From 20109c5ef3e0b4647758d96482a94632fb792d44 Mon Sep 17 00:00:00 2001 From: Milen Marinov Date: Wed, 25 Oct 2023 20:20:38 +0300 Subject: [PATCH 002/197] [CORE-2976] Wire re-login flow in LogoutSyncFragment --- .../dashboard/logout/sync/LogoutSyncFragment.kt | 17 +++++++++++++++++ .../src/main/res/navigation/graph_dashboard.xml | 3 +++ 2 files changed, 20 insertions(+) diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/sync/LogoutSyncFragment.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/sync/LogoutSyncFragment.kt index 64004c264c..4b6084e4d0 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/sync/LogoutSyncFragment.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/sync/LogoutSyncFragment.kt @@ -9,12 +9,16 @@ import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController +import com.simprints.core.livedata.LiveDataEventWithContentObserver import com.simprints.infra.uibase.viewbinding.viewBinding import com.simprints.feature.dashboard.R import com.simprints.feature.dashboard.databinding.FragmentLogoutSyncBinding import com.simprints.feature.dashboard.logout.LogoutSyncViewModel import com.simprints.feature.dashboard.views.SyncCardState import com.simprints.feature.dashboard.main.sync.SyncViewModel +import com.simprints.feature.login.LoginContract +import com.simprints.feature.login.LoginResult +import com.simprints.infra.uibase.navigation.handleResult import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -28,6 +32,12 @@ class LogoutSyncFragment : Fragment(R.layout.fragment_logout_sync) { super.onViewCreated(view, savedInstanceState) initViews() observeLiveData() + + findNavController().handleResult( + viewLifecycleOwner, + R.id.logOutSyncFragment, + LoginContract.LOGIN_DESTINATION_ID, + ) { result -> syncViewModel.handleLoginResult(result) } } private fun initViews() = with(binding) { @@ -36,6 +46,7 @@ class LogoutSyncFragment : Fragment(R.layout.fragment_logout_sync) { { startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS)) } logoutSyncCard.onSelectNoModulesButtonClick = { findNavController().navigate(R.id.action_logoutSyncFragment_to_moduleSelectionFragment) } + logoutSyncCard.onLoginButtonClick = { syncViewModel.login() } logoutSyncToolbar.setNavigationOnClickListener { findNavController().popBackStack() } @@ -56,6 +67,12 @@ class LogoutSyncFragment : Fragment(R.layout.fragment_logout_sync) { logoutWithoutSyncButton.isVisible = isLogoutButtonVisible.not() logoutSyncInfo.isInvisible = isLogoutButtonVisible } + syncViewModel.loginRequestedEventLiveData.observe(viewLifecycleOwner, LiveDataEventWithContentObserver { loginArgs -> + findNavController().navigate( + R.id.action_logOutSyncFragment_to_login, + loginArgs + ) + }) } /** diff --git a/feature/dashboard/src/main/res/navigation/graph_dashboard.xml b/feature/dashboard/src/main/res/navigation/graph_dashboard.xml index 583d72b636..6335b2e94e 100644 --- a/feature/dashboard/src/main/res/navigation/graph_dashboard.xml +++ b/feature/dashboard/src/main/res/navigation/graph_dashboard.xml @@ -120,6 +120,9 @@ + Date: Thu, 26 Oct 2023 12:50:48 +0300 Subject: [PATCH 003/197] [CORE-2976] Add 'Clear Firebase Token' option in the Debug fragment --- .../simprints/feature/dashboard/debug/DebugFragment.kt | 5 +++++ feature/dashboard/src/main/res/layout/fragment_debug.xml | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/debug/DebugFragment.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/debug/DebugFragment.kt index ee2485d6df..5ff56f0ee8 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/debug/DebugFragment.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/debug/DebugFragment.kt @@ -90,6 +90,11 @@ internal class DebugFragment : Fragment(R.layout.fragment_debug) { eventSyncManager.scheduleSync() } + binding.clearFirebaseToken.setOnClickListener { + authStore.clearFirebaseToken() + binding.logs.append("\nFirebase token deleted") + } + binding.syncConfig.setOnClickListener { binding.logs.append("\nGetting Configs from BFSID") lifecycleScope.launch { diff --git a/feature/dashboard/src/main/res/layout/fragment_debug.xml b/feature/dashboard/src/main/res/layout/fragment_debug.xml index c558624a43..7d313dcf14 100644 --- a/feature/dashboard/src/main/res/layout/fragment_debug.xml +++ b/feature/dashboard/src/main/res/layout/fragment_debug.xml @@ -71,8 +71,15 @@ android:text="Schedule Event Sync" tools:ignore="HardcodedText" /> - + + From 065f9aa71a291ec660bbcad1177cca1e688f5796 Mon Sep 17 00:00:00 2001 From: Milen Marinov Date: Thu, 26 Oct 2023 14:03:41 +0300 Subject: [PATCH 004/197] [CORE-2976] Add relogin option in Sync Information screen --- .../dashboard/main/sync/SyncViewModel.kt | 61 +---- .../settings/syncinfo/SyncInfoFragment.kt | 54 ++++- .../settings/syncinfo/SyncInfoViewModel.kt | 45 +++- .../main/res/layout/fragment_sync_info.xml | 227 +++++++++++------- .../main/res/navigation/graph_dashboard.xml | 3 + .../eventsync/status/models/EventSyncState.kt | 37 ++- 6 files changed, 267 insertions(+), 160 deletions(-) diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncViewModel.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncViewModel.kt index d16a05ffca..8495a53453 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncViewModel.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncViewModel.kt @@ -36,7 +36,6 @@ import com.simprints.infra.config.store.models.isEventDownSyncAllowed import com.simprints.infra.events.event.domain.models.EventType import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.eventsync.status.models.EventSyncState -import com.simprints.infra.eventsync.status.models.EventSyncWorkerState import com.simprints.infra.network.ConnectivityTracker import com.simprints.infra.projectsecuritystore.SecurityStateRepository import com.simprints.infra.projectsecuritystore.securitystate.models.SecurityState @@ -90,7 +89,6 @@ internal class SyncViewModel @Inject constructor( } private var lastTimeSyncRun: Date? = null - private var estimatedOutage: Long? = null init { viewModelScope.launch { @@ -217,77 +215,38 @@ internal class SyncViewModel @Inject constructor( private suspend fun processRecentSyncState(syncState: EventSyncState, itemsToUpSync: Int): SyncCardState { - val downSyncStates = syncState.downSyncWorkersInfo - val upSyncStates = syncState.upSyncWorkersInfo - val allSyncStates = downSyncStates + upSyncStates - return when { - isThereNotSyncHistory(allSyncStates) -> SyncDefault(lastTimeSyncSucceed()) - isSyncCompleted(allSyncStates) -> { + syncState.isThereNotSyncHistory() -> SyncDefault(lastTimeSyncSucceed()) + syncState.isSyncCompleted() -> { if (itemsToUpSync == 0) SyncComplete(lastTimeSyncSucceed()) else SyncPendingUpload(lastTimeSyncSucceed(), itemsToUpSync) } - isSyncProcess(allSyncStates) -> SyncProgress( + syncState.isSyncInProgress() -> SyncProgress( lastTimeSyncSucceed(), syncState.progress, syncState.total ) - isSyncConnecting(allSyncStates) -> SyncConnecting( + syncState.isSyncConnecting() -> SyncConnecting( lastTimeSyncSucceed(), syncState.progress, syncState.total ) - isSyncFailedBecauseSignInRequired(allSyncStates) -> SyncFailedSignInRequired( + syncState.isSyncFailedBecauseSignInRequired() -> SyncFailedSignInRequired( lastTimeSyncSucceed() ) - isSyncFailedBecauseTooManyRequests(allSyncStates) -> SyncTooManyRequests( + syncState.isSyncFailedBecauseTooManyRequests() -> SyncTooManyRequests( lastTimeSyncSucceed() ) - isSyncFailedBecauseCloudIntegration(allSyncStates) -> SyncFailed(lastTimeSyncSucceed()) - isSyncFailedBecauseBackendMaintenance(allSyncStates) -> SyncFailedBackendMaintenance( + syncState.isSyncFailedBecauseCloudIntegration() -> SyncFailed(lastTimeSyncSucceed()) + syncState.isSyncFailedBecauseBackendMaintenance() -> SyncFailedBackendMaintenance( lastTimeSyncSucceed(), - estimatedOutage + syncState.getEstimatedBackendMaintenanceOutage() ) - isSyncFailed(allSyncStates) -> SyncTryAgain(lastTimeSyncSucceed()) + syncState.isSyncFailed() -> SyncTryAgain(lastTimeSyncSucceed()) else -> SyncProgress(lastTimeSyncSucceed(), syncState.progress, syncState.total) } } - private fun isThereNotSyncHistory(allSyncStates: List) = - allSyncStates.isEmpty() - - private fun isSyncCompleted(allSyncStates: List) = - allSyncStates.all { it.state is EventSyncWorkerState.Succeeded } - - private fun isSyncProcess(allSyncStates: List) = - allSyncStates.any { it.state is EventSyncWorkerState.Running } - - private fun isSyncConnecting(allSyncStates: List) = - allSyncStates.any { it.state is EventSyncWorkerState.Enqueued } - - private fun isSyncFailedBecauseSignInRequired(allSyncStates: List) = - allSyncStates.any { it.state is EventSyncWorkerState.Failed && (it.state as EventSyncWorkerState.Failed).failedBecauseSignInRequired } - - private fun isSyncFailedBecauseTooManyRequests(allSyncStates: List) = - allSyncStates.any { it.state is EventSyncWorkerState.Failed && (it.state as EventSyncWorkerState.Failed).failedBecauseTooManyRequest } - - private fun isSyncFailedBecauseCloudIntegration(allSyncStates: List) = - allSyncStates.any { it.state is EventSyncWorkerState.Failed && (it.state as EventSyncWorkerState.Failed).failedBecauseCloudIntegration } - - private fun isSyncFailedBecauseBackendMaintenance(allSyncStates: List): Boolean { - val isBackendMaintenance = - allSyncStates.any { it.state is EventSyncWorkerState.Failed && (it.state as EventSyncWorkerState.Failed).failedBecauseBackendMaintenance } - if (isBackendMaintenance) { - val syncWorkerInfo = - allSyncStates.find { it.state is EventSyncWorkerState.Failed && (it.state as EventSyncWorkerState.Failed).estimatedOutage != 0L } - val failedWorkerState = syncWorkerInfo?.state as EventSyncWorkerState.Failed? - estimatedOutage = failedWorkerState?.estimatedOutage - } - return isBackendMaintenance - } - - private fun isSyncFailed(allSyncStates: List) = - allSyncStates.any { it.state is EventSyncWorkerState.Failed || it.state is EventSyncWorkerState.Blocked || it.state is EventSyncWorkerState.Cancelled } private suspend fun isModuleSelectionRequired() = isDownSyncAllowed() && isSelectedModulesEmpty() && isModuleSync() diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoFragment.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoFragment.kt index ac2a7833c6..9316e530d3 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoFragment.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoFragment.kt @@ -2,20 +2,26 @@ package com.simprints.feature.dashboard.settings.syncinfo import android.os.Bundle import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE import android.widget.ProgressBar import android.widget.TextView import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController +import com.simprints.core.livedata.LiveDataEventWithContentObserver import com.simprints.infra.uibase.viewbinding.viewBinding import com.simprints.feature.dashboard.R import com.simprints.feature.dashboard.databinding.FragmentSyncInfoBinding import com.simprints.feature.dashboard.settings.syncinfo.modulecount.ModuleCount import com.simprints.feature.dashboard.settings.syncinfo.modulecount.ModuleCountAdapter +import com.simprints.feature.login.LoginContract +import com.simprints.feature.login.LoginResult import com.simprints.infra.config.store.models.ProjectConfiguration import com.simprints.infra.config.store.models.SynchronizationConfiguration import com.simprints.infra.config.store.models.canSyncDataToSimprints import com.simprints.infra.config.store.models.isEventDownSyncAllowed +import com.simprints.infra.uibase.navigation.handleResult import dagger.hilt.android.AndroidEntryPoint import com.simprints.infra.resources.R as IDR @@ -37,6 +43,12 @@ internal class SyncInfoFragment : Fragment(R.layout.fragment_sync_info) { setupClickListeners() observeUI() viewModel.refreshInformation() + + findNavController().handleResult( + viewLifecycleOwner, + R.id.syncInfoFragment, + LoginContract.LOGIN_DESTINATION_ID, + ) { result -> viewModel.handleLoginResult(result) } } private fun setupClickListeners() { @@ -54,6 +66,9 @@ internal class SyncInfoFragment : Fragment(R.layout.fragment_sync_info) { viewModel.forceSync() updateSyncButton(isSyncInProgress = true) } + binding.reloginRequiredLoginButton.setOnClickListener { + viewModel.login() + } } private fun observeUI() { @@ -97,6 +112,21 @@ internal class SyncInfoFragment : Fragment(R.layout.fragment_sync_info) { viewModel.isSyncAvailable.observe(viewLifecycleOwner) { binding.syncButton.isEnabled = it } + viewModel.isReloginRequired.observe(viewLifecycleOwner) { reloginRequired -> + if (reloginRequired) { + binding.reloginRequiredSection.visibility = VISIBLE + binding.syncButton.visibility = GONE + } else { + binding.reloginRequiredSection.visibility = GONE + binding.syncButton.visibility = VISIBLE + } + } + viewModel.loginRequestedEventLiveData.observe(viewLifecycleOwner, LiveDataEventWithContentObserver { loginArgs -> + findNavController().navigate( + R.id.action_syncInfoFragment_to_login, + loginArgs + ) + }) } private fun updateSyncButton(isSyncInProgress: Boolean) { @@ -108,33 +138,33 @@ internal class SyncInfoFragment : Fragment(R.layout.fragment_sync_info) { private fun enableModuleSelectionButtonAndTabsIfNecessary(synchronizationConfiguration: SynchronizationConfiguration) { if (viewModel.isModuleSyncAndModuleIdOptionsNotEmpty(synchronizationConfiguration)) { - binding.moduleSelectionButton.visibility = View.VISIBLE - binding.modulesTabHost.visibility = View.VISIBLE + binding.moduleSelectionButton.visibility = VISIBLE + binding.modulesTabHost.visibility = VISIBLE } else { - binding.moduleSelectionButton.visibility = View.GONE - binding.modulesTabHost.visibility = View.GONE + binding.moduleSelectionButton.visibility = GONE + binding.modulesTabHost.visibility = GONE } } private fun setupRecordsCountCards(configuration: ProjectConfiguration) { if (!configuration.isEventDownSyncAllowed()) { - binding.recordsToDownloadCardView.visibility = View.GONE - binding.recordsToDeleteCardView.visibility = View.GONE + binding.recordsToDownloadCardView.visibility = GONE + binding.recordsToDeleteCardView.visibility = GONE } if (!configuration.canSyncDataToSimprints()) { - binding.recordsToUploadCardView.visibility = View.GONE - binding.imagesToUploadCardView.visibility = View.GONE + binding.recordsToUploadCardView.visibility = GONE + binding.imagesToUploadCardView.visibility = GONE } } private fun setProgressBar(value: Int?, tv: TextView, pb: ProgressBar) { if (value == null) { - pb.visibility = View.VISIBLE - tv.visibility = View.GONE + pb.visibility = VISIBLE + tv.visibility = GONE } else { - pb.visibility = View.GONE - tv.visibility = View.VISIBLE + pb.visibility = GONE + tv.visibility = VISIBLE } } diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt index b90dd17619..9495d9df9a 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt @@ -1,17 +1,26 @@ package com.simprints.feature.dashboard.settings.syncinfo +import android.os.Bundle import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.simprints.core.domain.tokenization.TokenizableString +import com.simprints.core.livedata.LiveDataEventWithContent +import com.simprints.core.livedata.send import com.simprints.feature.dashboard.settings.syncinfo.modulecount.ModuleCount -import com.simprints.infra.config.sync.ConfigManager +import com.simprints.feature.login.LoginContract +import com.simprints.feature.login.LoginResult +import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.models.DownSynchronizationConfiguration import com.simprints.infra.config.store.models.ProjectConfiguration import com.simprints.infra.config.store.models.SynchronizationConfiguration +import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.config.store.models.isEventDownSyncAllowed +import com.simprints.infra.config.store.tokenization.TokenizationProcessor +import com.simprints.infra.config.sync.ConfigManager +import com.simprints.infra.enrolment.records.store.EnrolmentRecordRepository import com.simprints.infra.enrolment.records.store.domain.models.SubjectQuery import com.simprints.infra.events.event.domain.models.EventType import com.simprints.infra.eventsync.EventSyncManager @@ -20,10 +29,6 @@ import com.simprints.infra.eventsync.status.models.EventSyncState import com.simprints.infra.eventsync.status.models.EventSyncWorkerState import com.simprints.infra.images.ImageRepository import com.simprints.infra.logging.Simber -import com.simprints.infra.authstore.AuthStore -import com.simprints.infra.config.store.models.TokenKeyType -import com.simprints.infra.config.store.tokenization.TokenizationProcessor -import com.simprints.infra.enrolment.records.store.EnrolmentRecordRepository import com.simprints.infra.network.ConnectivityTracker import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.async @@ -71,7 +76,7 @@ internal class SyncInfoViewModel @Inject constructor( get() = _configuration private val _configuration = MutableLiveData() - val isConnected: LiveData = connectivityTracker.observeIsConnected() + private val isConnected: LiveData = connectivityTracker.observeIsConnected() val lastSyncState = eventSyncManager.getLastSyncState() private var lastKnownEventSyncState: EventSyncState? = null @@ -80,6 +85,14 @@ internal class SyncInfoViewModel @Inject constructor( get() = _isSyncAvailable private val _isSyncAvailable = MediatorLiveData() + val isReloginRequired: LiveData + get() = _isReloginRequired + private val _isReloginRequired = MediatorLiveData() + + val loginRequestedEventLiveData: LiveData> + get() = _loginRequestedEventLiveData + private val _loginRequestedEventLiveData = MutableLiveData>() + init { _isSyncAvailable.addSource(lastSyncState) { lastSyncStateValue -> _isSyncAvailable.postValue( @@ -108,6 +121,9 @@ internal class SyncInfoViewModel @Inject constructor( ) ) } + _isReloginRequired.addSource(lastSyncState) { lastSyncStateValue -> + _isReloginRequired.postValue(lastSyncStateValue.isSyncFailedBecauseSignInRequired()) + } } fun refreshInformation() { @@ -145,6 +161,23 @@ internal class SyncInfoViewModel @Inject constructor( } } + fun login() { + viewModelScope.launch { + val userId = recentUserActivityManager.getRecentUserActivity().lastUserUsed + val loginArgs = LoginContract.toArgs( + authStore.signedInProjectId, + userId + ) + _loginRequestedEventLiveData.send(loginArgs) + } + } + + fun handleLoginResult(result: LoginResult) { + if (result.isSuccess) { + forceSync() + } + } + private fun load() = viewModelScope.launch { val projectId = authStore.signedInProjectId diff --git a/feature/dashboard/src/main/res/layout/fragment_sync_info.xml b/feature/dashboard/src/main/res/layout/fragment_sync_info.xml index 55cef3bd59..fe35e5b744 100644 --- a/feature/dashboard/src/main/res/layout/fragment_sync_info.xml +++ b/feature/dashboard/src/main/res/layout/fragment_sync_info.xml @@ -26,25 +26,24 @@ + app:layout_constraintTop_toBottomOf="@+id/appBarLayout"> + android:padding="8dp"> + app:layout_constraintTop_toBottomOf="@+id/recordsInLocalCardView"> + android:orientation="vertical" + android:padding="8dp"> + tools:text="0" /> + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toEndOf="@+id/recordsToUploadCardView" + app:layout_constraintTop_toTopOf="@id/recordsToUploadCardView"> + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="8dp"> + tools:text="0" /> + app:layout_constraintTop_toBottomOf="@id/recordsToUploadCardView"> + android:orientation="horizontal" + android:padding="8dp"> + android:layout_weight="0.2" + android:gravity="center" + android:textSize="16sp" + tools:text="137" /> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/recordsToDownloadCardView"> + android:orientation="horizontal" + android:padding="8dp"> + android:layout_weight="0.2" + android:gravity="center" + android:textSize="16sp" + tools:text="137" /> + + + + + + + + + + + + + + Date: Thu, 26 Oct 2023 14:09:11 +0300 Subject: [PATCH 005/197] [CORE-2976] Rename SignInRequired to ReloginRequired in order to have uniform naming --- .../simprints/feature/dashboard/main/sync/SyncViewModel.kt | 4 ++-- .../feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt | 2 +- .../com/simprints/feature/dashboard/views/SyncCardState.kt | 2 +- .../com/simprints/feature/dashboard/views/SyncCardView.kt | 4 ++-- .../simprints/infra/eventsync/status/models/EventSyncState.kt | 4 ++-- .../infra/eventsync/status/models/EventSyncWorkerState.kt | 3 +++ .../simprints/infra/eventsync/sync/EventSyncStateProcessor.kt | 1 + .../com/simprints/infra/eventsync/sync/common/WorkInfo.ext.kt | 4 ++++ .../eventsync/sync/down/workers/EventDownSyncCountWorker.kt | 3 +++ .../sync/down/workers/EventDownSyncDownloaderWorker.kt | 3 +++ .../eventsync/sync/up/workers/EventUpSyncUploaderWorker.kt | 3 +++ 11 files changed, 25 insertions(+), 8 deletions(-) diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncViewModel.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncViewModel.kt index 8495a53453..60cd4c7ad7 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncViewModel.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncViewModel.kt @@ -16,7 +16,7 @@ import com.simprints.feature.dashboard.views.SyncCardState.SyncComplete import com.simprints.feature.dashboard.views.SyncCardState.SyncConnecting import com.simprints.feature.dashboard.views.SyncCardState.SyncDefault import com.simprints.feature.dashboard.views.SyncCardState.SyncFailed -import com.simprints.feature.dashboard.views.SyncCardState.SyncFailedSignInRequired +import com.simprints.feature.dashboard.views.SyncCardState.SyncFailedReloginRequired import com.simprints.feature.dashboard.views.SyncCardState.SyncFailedBackendMaintenance import com.simprints.feature.dashboard.views.SyncCardState.SyncHasNoModules import com.simprints.feature.dashboard.views.SyncCardState.SyncOffline @@ -231,7 +231,7 @@ internal class SyncViewModel @Inject constructor( syncState.progress, syncState.total ) - syncState.isSyncFailedBecauseSignInRequired() -> SyncFailedSignInRequired( + syncState.isSyncFailedBecauseReloginRequired() -> SyncFailedReloginRequired( lastTimeSyncSucceed() ) syncState.isSyncFailedBecauseTooManyRequests() -> SyncTooManyRequests( diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt index 9495d9df9a..9962c073b3 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt @@ -122,7 +122,7 @@ internal class SyncInfoViewModel @Inject constructor( ) } _isReloginRequired.addSource(lastSyncState) { lastSyncStateValue -> - _isReloginRequired.postValue(lastSyncStateValue.isSyncFailedBecauseSignInRequired()) + _isReloginRequired.postValue(lastSyncStateValue.isSyncFailedBecauseReloginRequired()) } } diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/views/SyncCardState.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/views/SyncCardState.kt index e5db5cdf70..993fceeae9 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/views/SyncCardState.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/views/SyncCardState.kt @@ -27,7 +27,7 @@ internal sealed class SyncCardState(open val lastTimeSyncSucceed: String?) { override val lastTimeSyncSucceed: String?, ) : SyncCardState(lastTimeSyncSucceed) - data class SyncFailedSignInRequired( + data class SyncFailedReloginRequired( override val lastTimeSyncSucceed: String?, ) : SyncCardState(lastTimeSyncSucceed) diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/views/SyncCardView.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/views/SyncCardView.kt index 949bcd72dd..e5e421b940 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/views/SyncCardView.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/views/SyncCardView.kt @@ -39,7 +39,7 @@ internal class SyncCardView : MaterialCardView { is SyncCardState.SyncDefault -> prepareSyncDefaultStateView() is SyncCardState.SyncPendingUpload -> prepareSyncDefaultStateView(state.itemsToUpSync) is SyncCardState.SyncFailed -> prepareSyncFailedStateView() - is SyncCardState.SyncFailedSignInRequired -> prepareSyncFailedBecauseSignInRequired() + is SyncCardState.SyncFailedReloginRequired -> prepareSyncFailedBecauseReloginRequired() is SyncCardState.SyncFailedBackendMaintenance -> prepareSyncFailedBecauseBackendMaintenanceView(state) is SyncCardState.SyncTooManyRequests -> prepareSyncTooManyRequestsView() is SyncCardState.SyncTryAgain -> prepareTryAgainStateView() @@ -82,7 +82,7 @@ internal class SyncCardView : MaterialCardView { resources.getString(R.string.dashboard_sync_card_failed_message) } - private fun prepareSyncFailedBecauseSignInRequired() { + private fun prepareSyncFailedBecauseReloginRequired() { binding.syncCardReloginRequired.visibility = View.VISIBLE binding.syncCardReloginRequiredLoginButton.setOnClickListener { onLoginButtonClick() } } diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/models/EventSyncState.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/models/EventSyncState.kt index 9d3d3a9071..5c23a218c4 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/models/EventSyncState.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/models/EventSyncState.kt @@ -31,8 +31,8 @@ data class EventSyncState( fun isSyncConnecting() = (upSyncWorkersInfo + downSyncWorkersInfo) .any { it.state is EventSyncWorkerState.Enqueued } - fun isSyncFailedBecauseSignInRequired() = (upSyncWorkersInfo + downSyncWorkersInfo) - .any { it.state is EventSyncWorkerState.Failed && it.state.failedBecauseSignInRequired } + fun isSyncFailedBecauseReloginRequired() = (upSyncWorkersInfo + downSyncWorkersInfo) + .any { it.state is EventSyncWorkerState.Failed && it.state.failedBecauseReloginRequired } fun isSyncFailedBecauseTooManyRequests() = (upSyncWorkersInfo + downSyncWorkersInfo) .any { it.state is EventSyncWorkerState.Failed && it.state.failedBecauseTooManyRequest } diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/models/EventSyncWorkerState.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/models/EventSyncWorkerState.kt index 51c9fa6035..2e0ac4e370 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/models/EventSyncWorkerState.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/models/EventSyncWorkerState.kt @@ -9,6 +9,7 @@ sealed class EventSyncWorkerState(val state: String) { object Running : EventSyncWorkerState("Running") object Succeeded : EventSyncWorkerState("Succeeded") class Failed( + val failedBecauseReloginRequired: Boolean = false, val failedBecauseCloudIntegration: Boolean = false, val failedBecauseBackendMaintenance: Boolean = false, val failedBecauseTooManyRequest: Boolean = false, @@ -22,6 +23,7 @@ sealed class EventSyncWorkerState(val state: String) { companion object { fun fromWorkInfo( state: WorkInfo.State, + failedBecauseReloginRequired: Boolean = false, failedBecauseCloudIntegration: Boolean = false, failedBecauseBackendMaintenance: Boolean = false, failedBecauseTooManyRequest: Boolean = false, @@ -32,6 +34,7 @@ sealed class EventSyncWorkerState(val state: String) { WorkInfo.State.RUNNING -> Running WorkInfo.State.SUCCEEDED -> Succeeded WorkInfo.State.FAILED -> Failed( + failedBecauseReloginRequired, failedBecauseCloudIntegration, failedBecauseBackendMaintenance, failedBecauseTooManyRequest, diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/EventSyncStateProcessor.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/EventSyncStateProcessor.kt index e18dacf89f..15a7037414 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/EventSyncStateProcessor.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/EventSyncStateProcessor.kt @@ -121,6 +121,7 @@ internal class EventSyncStateProcessor @Inject constructor( private fun WorkInfo.toEventSyncWorkerState(): EventSyncWorkerState = fromWorkInfo( state, + didFailBecauseReloginRequired(), didFailBecauseCloudIntegration(), didFailBecauseBackendMaintenance(), didFailBecauseTooManyRequests(), diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/common/WorkInfo.ext.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/common/WorkInfo.ext.kt index 266c0329f4..9333401154 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/common/WorkInfo.ext.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/common/WorkInfo.ext.kt @@ -2,11 +2,15 @@ package com.simprints.infra.eventsync.sync.common import androidx.work.WorkInfo +internal const val OUTPUT_FAILED_BECAUSE_RELOGIN_REQUIRED = "OUTPUT_FAILED_BECAUSE_RELOGIN_REQUIRED" internal const val OUTPUT_FAILED_BECAUSE_TOO_MANY_REQUESTS = "OUTPUT_FAILED_BECAUSE_TOO_MANY_REQUESTS" internal const val OUTPUT_FAILED_BECAUSE_CLOUD_INTEGRATION = "OUTPUT_FAILED_BECAUSE_CLOUD_INTEGRATION" internal const val OUTPUT_FAILED_BECAUSE_BACKEND_MAINTENANCE = "OUTPUT_FAILED_BECAUSE_BACKEND_MAINTENANCE" internal const val OUTPUT_ESTIMATED_MAINTENANCE_TIME = "OUTPUT_ESTIMATED_MAINTENANCE_TIME" +internal fun WorkInfo.didFailBecauseReloginRequired(): Boolean = + this.outputData.getBoolean(OUTPUT_FAILED_BECAUSE_RELOGIN_REQUIRED, false) + internal fun WorkInfo.didFailBecauseCloudIntegration(): Boolean = this.outputData.getBoolean(OUTPUT_FAILED_BECAUSE_CLOUD_INTEGRATION, false) diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncCountWorker.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncCountWorker.kt index 53e2273ad1..980e374b62 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncCountWorker.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncCountWorker.kt @@ -83,6 +83,9 @@ internal class EventDownSyncCountWorker @AssistedInject constructor( OUTPUT_ESTIMATED_MAINTENANCE_TIME to t.estimatedOutage ) ) + t is RemoteDbNotSignedInException -> { + fail(t, t.message, workDataOf(OUTPUT_FAILED_BECAUSE_RELOGIN_REQUIRED to true)) + } isSyncStillRunning() -> retry(t) else -> { Simber.d(t) diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncDownloaderWorker.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncDownloaderWorker.kt index 15b194f119..bf7d8a3507 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncDownloaderWorker.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncDownloaderWorker.kt @@ -100,6 +100,9 @@ internal class EventDownSyncDownloaderWorker @AssistedInject constructor( OUTPUT_FAILED_BECAUSE_TOO_MANY_REQUESTS to true ) ) + is RemoteDbNotSignedInException -> { + fail(t, t.message, workDataOf(OUTPUT_FAILED_BECAUSE_RELOGIN_REQUIRED to true)) + } else -> retry(t) } diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/workers/EventUpSyncUploaderWorker.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/workers/EventUpSyncUploaderWorker.kt index e6d1321e69..c27c505442 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/workers/EventUpSyncUploaderWorker.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/workers/EventUpSyncUploaderWorker.kt @@ -96,6 +96,9 @@ internal class EventUpSyncUploaderWorker @AssistedInject constructor( is SyncCloudIntegrationException -> { fail(t, t.message, workDataOf(OUTPUT_FAILED_BECAUSE_CLOUD_INTEGRATION to true)) } + is RemoteDbNotSignedInException -> { + fail(t, t.message, workDataOf(OUTPUT_FAILED_BECAUSE_RELOGIN_REQUIRED to true)) + } else -> { retry(t) } From 4617da8c6fd97f7acc016eb0331fa6f3e90b8479 Mon Sep 17 00:00:00 2001 From: Milen Marinov Date: Mon, 30 Oct 2023 17:59:51 +0200 Subject: [PATCH 006/197] [CORE-2976] Fix tests --- .../logout/sync/LogoutSyncFragmentTest.kt | 27 ++++++++--------- .../dashboard/main/MainFragmentTest.kt | 12 +++----- .../dashboard/main/sync/SyncFragmentTest.kt | 29 +++++++++---------- .../dashboard/main/sync/SyncViewModelTest.kt | 7 ++++- .../syncinfo/SyncInfoViewModelTest.kt | 1 + .../dashboard/tools/di/FakeCoreModule.kt | 15 ++++++---- 6 files changed, 49 insertions(+), 42 deletions(-) diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/logout/sync/LogoutSyncFragmentTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/logout/sync/LogoutSyncFragmentTest.kt index b65baafbea..499beb07a6 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/logout/sync/LogoutSyncFragmentTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/logout/sync/LogoutSyncFragmentTest.kt @@ -52,11 +52,12 @@ internal class LogoutSyncFragmentTest { internal val logoutSyncViewModel = mockk(relaxed = true) private val context = InstrumentationRegistry.getInstrumentation().context + private val navController = testNavController(R.navigation.graph_dashboard) @Test fun `should not hide the sync card view if it can't sync to BFSID`() { mockSyncToBFSIDAllowed(false) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) onView(withId(R.id.logoutSyncCard)).check(matches(isDisplayed())) } @@ -66,7 +67,7 @@ internal class LogoutSyncFragmentTest { mockSyncToBFSIDAllowed(true) mockSyncCardLiveData(SyncCardState.SyncDefault(LAST_SYNC_TIME)) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) checkHiddenViews( listOf( @@ -98,7 +99,7 @@ internal class LogoutSyncFragmentTest { mockSyncToBFSIDAllowed(true) mockSyncCardLiveData(SyncCardState.SyncPendingUpload(LAST_SYNC_TIME, 2)) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) checkHiddenViews( listOf( @@ -138,7 +139,7 @@ internal class LogoutSyncFragmentTest { mockSyncToBFSIDAllowed(true) mockSyncCardLiveData(SyncCardState.SyncFailed(LAST_SYNC_TIME)) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) checkHiddenViews( listOf( @@ -166,7 +167,7 @@ internal class LogoutSyncFragmentTest { mockSyncToBFSIDAllowed(true) mockSyncCardLiveData(SyncCardState.SyncFailedBackendMaintenance(LAST_SYNC_TIME)) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) checkHiddenViews( listOf( @@ -198,7 +199,7 @@ internal class LogoutSyncFragmentTest { ) ) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) checkHiddenViews( listOf( @@ -231,7 +232,7 @@ internal class LogoutSyncFragmentTest { mockSyncToBFSIDAllowed(true) mockSyncCardLiveData(SyncCardState.SyncTooManyRequests(LAST_SYNC_TIME)) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) checkHiddenViews( listOf( @@ -259,7 +260,7 @@ internal class LogoutSyncFragmentTest { mockSyncToBFSIDAllowed(true) mockSyncCardLiveData(SyncCardState.SyncTryAgain(LAST_SYNC_TIME)) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) checkHiddenViews( listOf( @@ -322,7 +323,7 @@ internal class LogoutSyncFragmentTest { mockSyncToBFSIDAllowed(true) mockSyncCardLiveData(SyncCardState.SyncProgress(LAST_SYNC_TIME, 20, 40)) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) checkHiddenViews( listOf( @@ -358,7 +359,7 @@ internal class LogoutSyncFragmentTest { mockSyncToBFSIDAllowed(true) mockSyncCardLiveData(SyncCardState.SyncConnecting(LAST_SYNC_TIME, 20, 40)) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) checkHiddenViews( listOf( @@ -391,7 +392,7 @@ internal class LogoutSyncFragmentTest { mockSyncToBFSIDAllowed(true) mockSyncCardLiveData(SyncCardState.SyncComplete(LAST_SYNC_TIME)) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) checkHiddenViews( listOf( @@ -439,7 +440,7 @@ internal class LogoutSyncFragmentTest { mockSyncToBFSIDAllowed(true) mockSyncCardLiveData(SyncCardState.SyncProgress(LAST_SYNC_TIME, 20, 40)) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) onView(withId(R.id.logoutButton)).check(matches(not(isDisplayed()))) onView(withId(R.id.logout_sync_info)).check(matches(isDisplayed())) onView(withId(R.id.logoutWithoutSyncButton)).check(matches(isDisplayed())) @@ -450,7 +451,7 @@ internal class LogoutSyncFragmentTest { mockSyncToBFSIDAllowed(true) mockSyncCardLiveData(SyncCardState.SyncComplete(LAST_SYNC_TIME)) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) onView(withId(R.id.logoutButton)).check(matches(isDisplayed())) onView(withId(R.id.logout_sync_info)).check(matches(not(isDisplayed()))) onView(withId(R.id.logoutWithoutSyncButton)).check(matches(not(isDisplayed()))) diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/MainFragmentTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/MainFragmentTest.kt index a6760d700a..c91ed1dc36 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/MainFragmentTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/MainFragmentTest.kt @@ -51,11 +51,13 @@ class MainFragmentTest { @JvmField internal val syncViewModel = mockk(relaxed = true) + private val navController = testNavController(R.navigation.graph_dashboard, R.id.mainFragment) + @Test fun `should hide the privacy notice menu if the consent is not required`() { mockConsentRequired(false) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) openContextualActionModeOverflowMenu() onView(withText("Privacy Notice")).check(doesNotExist()) @@ -65,7 +67,7 @@ class MainFragmentTest { fun `should display the privacy notice menu if the consent is required`() { mockConsentRequired(true) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) openContextualActionModeOverflowMenu() onView(withText("Privacy Notice")).check(matches(isDisplayed())) @@ -75,8 +77,6 @@ class MainFragmentTest { fun `should redirect to the settings fragment when clicking on settings`() { mockConsentRequired(true) - val navController = testNavController(R.navigation.graph_dashboard, R.id.mainFragment) - launchFragmentInHiltContainer(navController = navController) openContextualActionModeOverflowMenu() @@ -88,8 +88,6 @@ class MainFragmentTest { fun `should redirect to the privacy notices fragment when clicking on privacy notices`() { mockConsentRequired(true) - val navController = testNavController(R.navigation.graph_dashboard, R.id.mainFragment) - launchFragmentInHiltContainer(navController = navController) openContextualActionModeOverflowMenu() @@ -101,8 +99,6 @@ class MainFragmentTest { fun `should redirect to the debug fragment when clicking on debug`() { mockConsentRequired(true) - val navController = testNavController(R.navigation.graph_dashboard, R.id.mainFragment) - launchFragmentInHiltContainer(navController = navController) openContextualActionModeOverflowMenu() diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncFragmentTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncFragmentTest.kt index 5be2bdb4af..7d7d1f8d0a 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncFragmentTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncFragmentTest.kt @@ -43,12 +43,13 @@ class SyncFragmentTest { internal val viewModel = mockk(relaxed = true) private val context = InstrumentationRegistry.getInstrumentation().context + private val navController = testNavController(R.navigation.graph_dashboard, R.id.mainFragment) @Test fun `should hide the sync card view if it can't sync to BFSID`() { mockSyncToBFSIDAllowed(false) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) onView(withId(R.id.dashboardSyncCard)).check(matches(not(isDisplayed()))) } @@ -57,7 +58,7 @@ class SyncFragmentTest { fun `should display the sync card view if it can sync to BFSID`() { mockSyncToBFSIDAllowed(true) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) onView(withId(R.id.dashboardSyncCard)).check(matches(isDisplayed())) } @@ -67,7 +68,7 @@ class SyncFragmentTest { mockSyncToBFSIDAllowed(true) mockSyncCardLiveData(SyncCardState.SyncDefault(LAST_SYNC_TIME)) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) checkHiddenViews( listOf( @@ -94,7 +95,7 @@ class SyncFragmentTest { mockSyncToBFSIDAllowed(true) mockSyncCardLiveData(SyncCardState.SyncPendingUpload(LAST_SYNC_TIME, 2)) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) checkHiddenViews( listOf( @@ -122,7 +123,7 @@ class SyncFragmentTest { mockSyncToBFSIDAllowed(true) mockSyncCardLiveData(SyncCardState.SyncFailed(LAST_SYNC_TIME)) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) checkHiddenViews( listOf( @@ -144,7 +145,7 @@ class SyncFragmentTest { mockSyncToBFSIDAllowed(true) mockSyncCardLiveData(SyncCardState.SyncFailedBackendMaintenance(LAST_SYNC_TIME)) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) checkHiddenViews( listOf( @@ -170,7 +171,7 @@ class SyncFragmentTest { ) ) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) checkHiddenViews( listOf( @@ -194,7 +195,7 @@ class SyncFragmentTest { mockSyncToBFSIDAllowed(true) mockSyncCardLiveData(SyncCardState.SyncTooManyRequests(LAST_SYNC_TIME)) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) checkHiddenViews( listOf( @@ -216,7 +217,7 @@ class SyncFragmentTest { mockSyncToBFSIDAllowed(true) mockSyncCardLiveData(SyncCardState.SyncTryAgain(LAST_SYNC_TIME)) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) checkHiddenViews( listOf( @@ -241,8 +242,6 @@ class SyncFragmentTest { mockSyncToBFSIDAllowed(true) mockSyncCardLiveData(SyncCardState.SyncHasNoModules(LAST_SYNC_TIME)) - val navController = testNavController(R.navigation.graph_dashboard, R.id.mainFragment) - launchFragmentInHiltContainer(navController = navController) checkHiddenViews( @@ -269,7 +268,7 @@ class SyncFragmentTest { mockSyncToBFSIDAllowed(true) mockSyncCardLiveData(SyncCardState.SyncOffline(LAST_SYNC_TIME)) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) checkHiddenViews( listOf( @@ -293,7 +292,7 @@ class SyncFragmentTest { mockSyncToBFSIDAllowed(true) mockSyncCardLiveData(SyncCardState.SyncProgress(LAST_SYNC_TIME, 20, 40)) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) checkHiddenViews( listOf( @@ -323,7 +322,7 @@ class SyncFragmentTest { mockSyncToBFSIDAllowed(true) mockSyncCardLiveData(SyncCardState.SyncConnecting(LAST_SYNC_TIME, 20, 40)) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) checkHiddenViews( listOf( @@ -350,7 +349,7 @@ class SyncFragmentTest { mockSyncToBFSIDAllowed(true) mockSyncCardLiveData(SyncCardState.SyncComplete(LAST_SYNC_TIME)) - launchFragmentInHiltContainer() + launchFragmentInHiltContainer(navController = navController) checkHiddenViews( listOf( diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncViewModelTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncViewModelTest.kt index 36b86049b3..69cf852434 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncViewModelTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncViewModelTest.kt @@ -31,6 +31,7 @@ import com.simprints.infra.authstore.AuthStore import com.simprints.infra.network.ConnectivityTracker import com.simprints.infra.projectsecuritystore.SecurityStateRepository import com.simprints.infra.projectsecuritystore.securitystate.models.SecurityState +import com.simprints.infra.recent.user.activity.RecentUserActivityManager import com.simprints.testtools.common.coroutines.TestCoroutineRule import com.simprints.testtools.common.livedata.getOrAwaitValue import io.mockk.MockKAnnotations @@ -87,6 +88,9 @@ class SyncViewModelTest { @MockK lateinit var authManager: AuthManager + @MockK + lateinit var recentUserActivityManager: RecentUserActivityManager + @Before fun setUp() { MockKAnnotations.init(this, relaxed = true) @@ -386,8 +390,9 @@ class SyncViewModelTest { configManager = configManager, timeHelper = timeHelper, authStore = authStore, - securityStateRepository = securityStateRepository, authManager = authManager, + securityStateRepository = securityStateRepository, + recentUserActivityManager = recentUserActivityManager, externalScope = CoroutineScope(testCoroutineRule.testCoroutineDispatcher) ) } diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt index c5adcd0108..fa04b5061d 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt @@ -24,6 +24,7 @@ import com.simprints.infra.eventsync.status.models.EventSyncWorkerState import com.simprints.infra.eventsync.status.models.EventSyncWorkerType import com.simprints.infra.images.ImageRepository import com.simprints.infra.network.ConnectivityTracker +import com.simprints.infra.recent.user.activity.RecentUserActivityManager import com.simprints.testtools.common.coroutines.TestCoroutineRule import com.simprints.testtools.common.livedata.getOrAwaitValue import com.simprints.testtools.common.livedata.getOrAwaitValues diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/tools/di/FakeCoreModule.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/tools/di/FakeCoreModule.kt index 85735796a6..53161d5246 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/tools/di/FakeCoreModule.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/tools/di/FakeCoreModule.kt @@ -1,10 +1,7 @@ package com.simprints.feature.dashboard.tools.di -import com.simprints.core.CoreModule -import com.simprints.core.DeviceID -import com.simprints.core.DispatcherIO -import com.simprints.core.ExternalScope -import com.simprints.core.PackageVersionName +import com.simprints.core.* +import com.simprints.core.tools.json.JsonHelper import com.simprints.core.tools.time.TimeHelper import com.simprints.core.tools.utils.EncodingUtils import com.simprints.core.tools.utils.StringTokenizer @@ -36,6 +33,10 @@ object FakeCoreModule { @Singleton fun provideTimeHelper(): TimeHelper = mockk() + @Provides + @Singleton + fun provideJsonHelper(): JsonHelper = mockk() + @DeviceID @Provides fun provideDeviceId(): String = DEVICE_ID @@ -48,6 +49,10 @@ object FakeCoreModule { @Provides fun provideCoroutineDispatcher(): CoroutineDispatcher = StandardTestDispatcher() + @DispatcherBG + @Provides + fun provideCoroutineDispatcher2(): CoroutineDispatcher = StandardTestDispatcher() + @ExternalScope @Provides fun provideExternalScope(): CoroutineScope = CoroutineScope(Dispatchers.Main + Job()) From b8572c38b65607775054d07bd9d87edf480e2114 Mon Sep 17 00:00:00 2001 From: Milen Marinov Date: Mon, 30 Oct 2023 18:04:17 +0200 Subject: [PATCH 007/197] [CORE-2976] Apply PR review suggestions --- .../eventsync/status/models/EventSyncState.kt | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/models/EventSyncState.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/models/EventSyncState.kt index 5c23a218c4..87d5a5f035 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/models/EventSyncState.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/models/EventSyncState.kt @@ -16,41 +16,42 @@ data class EventSyncState( val state: EventSyncWorkerState, ) - fun isThereNotSyncHistory() = (upSyncWorkersInfo + downSyncWorkersInfo) + private val syncWorkersInfo: List + get() = upSyncWorkersInfo + downSyncWorkersInfo + + fun isThereNotSyncHistory() = syncWorkersInfo .isEmpty() - fun isSyncRunning() = (upSyncWorkersInfo + downSyncWorkersInfo) + fun isSyncRunning() = syncWorkersInfo .any { it.state is EventSyncWorkerState.Running || it.state is EventSyncWorkerState.Enqueued } - fun isSyncCompleted() = (upSyncWorkersInfo + downSyncWorkersInfo) + fun isSyncCompleted() = syncWorkersInfo .all { it.state is EventSyncWorkerState.Succeeded } - fun isSyncInProgress() = (upSyncWorkersInfo + downSyncWorkersInfo) + fun isSyncInProgress() = syncWorkersInfo .any { it.state is EventSyncWorkerState.Running } - fun isSyncConnecting() = (upSyncWorkersInfo + downSyncWorkersInfo) + fun isSyncConnecting() = syncWorkersInfo .any { it.state is EventSyncWorkerState.Enqueued } - fun isSyncFailedBecauseReloginRequired() = (upSyncWorkersInfo + downSyncWorkersInfo) + fun isSyncFailedBecauseReloginRequired() = syncWorkersInfo .any { it.state is EventSyncWorkerState.Failed && it.state.failedBecauseReloginRequired } - fun isSyncFailedBecauseTooManyRequests() = (upSyncWorkersInfo + downSyncWorkersInfo) + fun isSyncFailedBecauseTooManyRequests() = syncWorkersInfo .any { it.state is EventSyncWorkerState.Failed && it.state.failedBecauseTooManyRequest } - fun isSyncFailedBecauseCloudIntegration() = (upSyncWorkersInfo + downSyncWorkersInfo) + fun isSyncFailedBecauseCloudIntegration() = syncWorkersInfo .any { it.state is EventSyncWorkerState.Failed && it.state.failedBecauseCloudIntegration } - fun isSyncFailedBecauseBackendMaintenance() = (upSyncWorkersInfo + downSyncWorkersInfo) + fun isSyncFailedBecauseBackendMaintenance() = syncWorkersInfo .any { it.state is EventSyncWorkerState.Failed && it.state.failedBecauseBackendMaintenance } - fun getEstimatedBackendMaintenanceOutage(): Long? { - val syncWorkerInfo = (upSyncWorkersInfo + downSyncWorkersInfo) - .find { it.state is EventSyncWorkerState.Failed && it.state.estimatedOutage != 0L } - val failedWorkerState = syncWorkerInfo?.state as EventSyncWorkerState.Failed? - return failedWorkerState?.estimatedOutage - } + fun getEstimatedBackendMaintenanceOutage() = syncWorkersInfo + .find { it.state is EventSyncWorkerState.Failed && it.state.estimatedOutage != 0L } + ?.let { it.state as? EventSyncWorkerState.Failed } + ?.estimatedOutage - fun isSyncFailed() = (upSyncWorkersInfo + downSyncWorkersInfo) + fun isSyncFailed() = syncWorkersInfo .any { it.state is EventSyncWorkerState.Failed || it.state is EventSyncWorkerState.Blocked || it.state is EventSyncWorkerState.Cancelled } } From e030a6f559ada87705440bebdabc1e36f34df543 Mon Sep 17 00:00:00 2001 From: Milen Marinov Date: Tue, 31 Oct 2023 14:13:57 +0200 Subject: [PATCH 008/197] [CORE-2976] Add tests to increase coverage --- .../dashboard/main/sync/SyncFragmentTest.kt | 38 +++- .../dashboard/main/sync/SyncViewModelTest.kt | 51 +++++ .../syncinfo/SyncInfoViewModelTest.kt | 47 +++++ .../status/models/EventSyncStateTest.kt | 196 +++++++++++++++++- .../sync/down/tasks/EventDownSyncTaskTest.kt | 8 + .../workers/EventDownSyncCountWorkerTest.kt | 49 +++++ .../EventDownSyncDownloaderWorkerTest.kt | 18 ++ .../sync/up/tasks/EventUpSyncTaskTest.kt | 8 + .../workers/EventUpSyncUploaderWorkerTest.kt | 26 ++- 9 files changed, 432 insertions(+), 9 deletions(-) diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncFragmentTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncFragmentTest.kt index 7d7d1f8d0a..64ef081f71 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncFragmentTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncFragmentTest.kt @@ -77,6 +77,7 @@ class SyncFragmentTest { R.id.sync_card_offline, R.id.sync_card_progress, R.id.sync_card_try_again, + R.id.sync_card_relogin_required, ) ) val lastSyncText = context.getString(IDR.string.dashboard_sync_card_last_sync, LAST_SYNC_TIME) @@ -104,6 +105,7 @@ class SyncFragmentTest { R.id.sync_card_offline, R.id.sync_card_progress, R.id.sync_card_try_again, + R.id.sync_card_relogin_required, ) ) val lastSyncText = context.getString(IDR.string.dashboard_sync_card_last_sync, LAST_SYNC_TIME) @@ -117,7 +119,6 @@ class SyncFragmentTest { verify(exactly = 1) { viewModel.sync() } } - @Test fun `should display the correct sync card view for the SyncFailed state`() { mockSyncToBFSIDAllowed(true) @@ -132,6 +133,7 @@ class SyncFragmentTest { R.id.sync_card_offline, R.id.sync_card_progress, R.id.sync_card_try_again, + R.id.sync_card_relogin_required, ) ) @@ -140,6 +142,31 @@ class SyncFragmentTest { onView(withId(R.id.sync_card_failed_message)).check(matches(withText(IDR.string.dashboard_sync_card_failed_message))) } + @Test + fun `should display the correct sync card view for the SyncFailedReloginRequired state`() { + mockSyncToBFSIDAllowed(true) + mockSyncCardLiveData(SyncCardState.SyncFailedReloginRequired(LAST_SYNC_TIME)) + + launchFragmentInHiltContainer(navController = navController) + + checkHiddenViews( + listOf( + R.id.sync_card_default_state_sync_button, + R.id.sync_card_select_no_modules, + R.id.sync_card_offline, + R.id.sync_card_progress, + R.id.sync_card_try_again, + ) + ) + + val lastSyncText = context.getString(IDR.string.dashboard_card_sync_last_sync, LAST_SYNC_TIME) + onView(withId(R.id.sync_card_last_sync)).check(matches(withText(lastSyncText))) + onView(withId(R.id.sync_card_relogin_required)).check(matches(isDisplayed())) + onView(withId(R.id.sync_card_relogin_required_login_button)).check(matches(isDisplayed())) + .perform(click()) + verify(exactly = 1) { viewModel.login() } + } + @Test fun `should display the correct sync card view for the SyncFailedBackendMaintenance state without estimated outage`() { mockSyncToBFSIDAllowed(true) @@ -154,6 +181,7 @@ class SyncFragmentTest { R.id.sync_card_offline, R.id.sync_card_progress, R.id.sync_card_try_again, + R.id.sync_card_relogin_required, ) ) val lastSyncText = context.getString(IDR.string.dashboard_sync_card_last_sync, LAST_SYNC_TIME) @@ -180,6 +208,7 @@ class SyncFragmentTest { R.id.sync_card_offline, R.id.sync_card_progress, R.id.sync_card_try_again, + R.id.sync_card_relogin_required, ) ) @@ -204,6 +233,7 @@ class SyncFragmentTest { R.id.sync_card_offline, R.id.sync_card_progress, R.id.sync_card_try_again, + R.id.sync_card_relogin_required, ) ) @@ -226,6 +256,7 @@ class SyncFragmentTest { R.id.sync_card_select_no_modules, R.id.sync_card_offline, R.id.sync_card_progress, + R.id.sync_card_relogin_required, ) ) @@ -251,6 +282,7 @@ class SyncFragmentTest { R.id.sync_card_try_again, R.id.sync_card_offline, R.id.sync_card_progress, + R.id.sync_card_relogin_required, ) ) @@ -277,6 +309,7 @@ class SyncFragmentTest { R.id.sync_card_try_again, R.id.sync_card_select_no_modules_button, R.id.sync_card_progress, + R.id.sync_card_relogin_required, ) ) @@ -301,6 +334,7 @@ class SyncFragmentTest { R.id.sync_card_try_again, R.id.sync_card_select_no_modules_button, R.id.sync_card_offline, + R.id.sync_card_relogin_required, ) ) @@ -331,6 +365,7 @@ class SyncFragmentTest { R.id.sync_card_try_again, R.id.sync_card_select_no_modules_button, R.id.sync_card_offline, + R.id.sync_card_relogin_required, ) ) @@ -358,6 +393,7 @@ class SyncFragmentTest { R.id.sync_card_try_again, R.id.sync_card_select_no_modules_button, R.id.sync_card_offline, + R.id.sync_card_relogin_required, ) ) diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncViewModelTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncViewModelTest.kt index 69cf852434..9fc1246a94 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncViewModelTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncViewModelTest.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.MutableLiveData import com.google.common.truth.Truth.assertThat import com.simprints.core.domain.tokenization.asTokenizableEncrypted import com.simprints.core.tools.time.TimeHelper +import com.simprints.feature.dashboard.views.SyncCardState import com.simprints.feature.dashboard.views.SyncCardState.SyncComplete import com.simprints.feature.dashboard.views.SyncCardState.SyncConnecting import com.simprints.feature.dashboard.views.SyncCardState.SyncDefault @@ -16,6 +17,7 @@ import com.simprints.feature.dashboard.views.SyncCardState.SyncPendingUpload import com.simprints.feature.dashboard.views.SyncCardState.SyncProgress import com.simprints.feature.dashboard.views.SyncCardState.SyncTooManyRequests import com.simprints.feature.dashboard.views.SyncCardState.SyncTryAgain +import com.simprints.feature.login.LoginResult import com.simprints.infra.authlogic.AuthManager import com.simprints.infra.config.sync.ConfigManager import com.simprints.infra.config.store.models.DeviceConfiguration @@ -306,6 +308,55 @@ class SyncViewModelTest { assertThat(syncCardLiveData).isEqualTo(SyncFailed(DATE)) } + @Test + fun `should post a ReloginRequired card state if the sync fails with such problem`() { + coEvery { configManager.getDeviceConfiguration() } returns DeviceConfiguration( + "", + listOf("module 1"), + "" + ) + isConnected.value = true + syncState.value = EventSyncState( + "", 10, 40, listOf(), listOf( + EventSyncState.SyncWorkerInfo( + EventSyncWorkerType.DOWNLOADER, + EventSyncWorkerState.Failed(failedBecauseReloginRequired = true) + ) + ) + ) + val syncCardLiveData = initViewModel().syncCardLiveData.getOrAwaitValue() + + assertThat(syncCardLiveData).isEqualTo(SyncCardState.SyncFailedReloginRequired(DATE)) + } + + @Test + fun `calling login() sends respective event to the view`() { + val viewModel = initViewModel() + + viewModel.login() + + val loginRequestedEvent = viewModel.loginRequestedEventLiveData.getOrAwaitValue() + assertThat(loginRequestedEvent).isNotNull() + } + + @Test + fun `calling handleLoginResult() triggers sync if result is success`() { + val viewModel = initViewModel() + + viewModel.handleLoginResult(LoginResult(true)) + + verify(exactly = 1) { eventSyncManager.sync() } + } + + @Test + fun `calling handleLoginResult() does not trigger sync if result is not success`() { + val viewModel = initViewModel() + + viewModel.handleLoginResult(LoginResult(false)) + + verify(exactly = 0) { eventSyncManager.sync() } + } + @Test fun `should post a SyncFailedBackendMaintenance card state if the sync fails because of cloud maintenance`() { coEvery { configManager.getDeviceConfiguration() } returns deviceConfiguration diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt index fa04b5061d..fca6742aa4 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt @@ -6,6 +6,7 @@ import com.google.common.truth.Truth.assertThat import com.simprints.core.domain.tokenization.asTokenizableEncrypted import com.simprints.core.domain.tokenization.asTokenizableRaw import com.simprints.feature.dashboard.settings.syncinfo.modulecount.ModuleCount +import com.simprints.feature.login.LoginResult import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.sync.ConfigManager import com.simprints.infra.config.store.models.DownSynchronizationConfiguration @@ -419,6 +420,52 @@ class SyncInfoViewModelTest { assertThat(viewModel.isSyncAvailable.getOrAwaitValue()).isFalse() } + @Test + fun `emit ReloginRequired = false when lastSyncState updates with different status`() = runTest { + stateLiveData.value = EventSyncState("", 0, 0, listOf(), listOf( + EventSyncState.SyncWorkerInfo( + EventSyncWorkerType.DOWNLOADER, + EventSyncWorkerState.Failed(failedBecauseBackendMaintenance = true) + ) + )) + + assertThat(viewModel.isReloginRequired.getOrAwaitValue()).isFalse() + } + + @Test + fun `emit ReloginRequired = true when lastSyncState updates with such status`() = runTest { + stateLiveData.value = EventSyncState("", 0, 0, listOf(), listOf( + EventSyncState.SyncWorkerInfo( + EventSyncWorkerType.DOWNLOADER, + EventSyncWorkerState.Failed(failedBecauseReloginRequired = true) + ) + )) + + assertThat(viewModel.isReloginRequired.getOrAwaitValue()).isTrue() + } + + @Test + fun `calling login() sends respective event to the view`() { + viewModel.login() + + val loginRequestedEvent = viewModel.loginRequestedEventLiveData.getOrAwaitValue() + assertThat(loginRequestedEvent).isNotNull() + } + + @Test + fun `calling handleLoginResult() triggers sync if result is success`() { + viewModel.handleLoginResult(LoginResult(true)) + + verify(exactly = 1) { eventSyncManager.sync() } + } + + @Test + fun `calling handleLoginResult() does not trigger sync if result is not success`() { + viewModel.handleLoginResult(LoginResult(false)) + + verify(exactly = 0) { eventSyncManager.sync() } + } + private fun createMockDownSyncConfig( partitionType: DownSynchronizationConfiguration.PartitionType, modules: List = emptyList(), diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/status/models/EventSyncStateTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/status/models/EventSyncStateTest.kt index 0b867b8821..1619fa016b 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/status/models/EventSyncStateTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/status/models/EventSyncStateTest.kt @@ -8,7 +8,22 @@ import org.junit.Test class EventSyncStateTest { @Test - fun `is not running when there are no workers`() { + fun `isThereNotSyncHistory() is true when there are no workers`() { + assertThat(createState( + up = emptyList(), + down = emptyList(), + ).isThereNotSyncHistory()).isTrue() + } + @Test + fun `isThereNotSyncHistory() is false when there are workers`() { + assertThat(createState( + up = listOf(createWorker(Succeeded)), + down = emptyList(), + ).isThereNotSyncHistory()).isFalse() + } + + @Test + fun `isSyncRunning() is false when there are no workers`() { assertThat(createState( up = emptyList(), down = emptyList(), @@ -16,7 +31,7 @@ class EventSyncStateTest { } @Test - fun `is not running when when all workers completed`() { + fun `isSyncRunning() is false when when all workers completed`() { assertThat(createState( up = listOf(createWorker(Succeeded)), down = listOf(createWorker(Succeeded)), @@ -24,7 +39,7 @@ class EventSyncStateTest { } @Test - fun `is running when there are running workers`() { + fun `isSyncRunning() is true when there are running workers`() { assertThat(createState( up = listOf(createWorker(Running)), down = listOf(createWorker(Succeeded)), @@ -40,7 +55,7 @@ class EventSyncStateTest { } @Test - fun `is running when there are enqueued workers`() { + fun `isSyncRunning() is true when there are enqueued workers`() { assertThat(createState( up = listOf(createWorker(Enqueued)), down = listOf(createWorker(Succeeded)), @@ -55,6 +70,179 @@ class EventSyncStateTest { ).isSyncRunning()).isTrue() } + @Test + fun `isSyncCompleted() is true when all workers are completed`() { + assertThat(createState( + up = listOf(createWorker(Succeeded)), + down = listOf(createWorker(Succeeded)), + ).isSyncCompleted()).isTrue() + } + + @Test + fun `isSyncCompleted() is false when there are enqueued workers`() { + assertThat(createState( + up = listOf(createWorker(Enqueued)), + down = listOf(createWorker(Succeeded)), + ).isSyncCompleted()).isFalse() + } + + @Test + fun `isSyncCompleted() is false when there are running workers`() { + assertThat(createState( + up = listOf(createWorker(Running)), + down = listOf(createWorker(Succeeded)), + ).isSyncCompleted()).isFalse() + } + + @Test + fun `isSyncInProgress() is false when there are no running workers`() { + assertThat(createState( + up = listOf(createWorker(Enqueued)), + down = listOf(createWorker(Succeeded)), + ).isSyncInProgress()).isFalse() + } + + @Test + fun `isSyncInProgress() is true when there are running workers`() { + assertThat(createState( + up = listOf(createWorker(Running)), + down = listOf(createWorker(Succeeded), createWorker(Enqueued)), + ).isSyncInProgress()).isTrue() + } + + @Test + fun `isSyncConnecting() is false when there are no enqueued workers`() { + assertThat(createState( + up = listOf(createWorker(Running)), + down = listOf(createWorker(Succeeded)), + ).isSyncConnecting()).isFalse() + } + + @Test + fun `isSyncConnecting() is true when there are enqueued workers`() { + assertThat(createState( + up = listOf(createWorker(Running)), + down = listOf(createWorker(Succeeded), createWorker(Enqueued)), + ).isSyncConnecting()).isTrue() + } + + @Test + fun `isSyncFailedBecauseReloginRequired() is false when there are no workers with that status`() { + assertThat(createState( + up = listOf(createWorker(Failed())), + down = listOf(createWorker(Succeeded)), + ).isSyncFailedBecauseReloginRequired()).isFalse() + } + + @Test + fun `isSyncFailedBecauseReloginRequired() is true when there are workers with that status`() { + assertThat(createState( + up = listOf(createWorker(Failed(failedBecauseReloginRequired = true))), + down = listOf(createWorker(Succeeded), createWorker(Enqueued)), + ).isSyncFailedBecauseReloginRequired()).isTrue() + } + + @Test + fun `isSyncFailedBecauseTooManyRequests() is false when there are no workers with that status`() { + assertThat(createState( + up = listOf(createWorker(Failed())), + down = listOf(createWorker(Succeeded)), + ).isSyncFailedBecauseTooManyRequests()).isFalse() + } + + @Test + fun `isSyncFailedBecauseTooManyRequests() is true when there are workers with that status`() { + assertThat(createState( + up = listOf(createWorker(Failed(failedBecauseTooManyRequest = true))), + down = listOf(createWorker(Succeeded), createWorker(Enqueued)), + ).isSyncFailedBecauseTooManyRequests()).isTrue() + } + + @Test + fun `isSyncFailedBecauseCloudIntegration() is false when there are no workers with that status`() { + assertThat(createState( + up = listOf(createWorker(Failed())), + down = listOf(createWorker(Succeeded)), + ).isSyncFailedBecauseCloudIntegration()).isFalse() + } + + @Test + fun `isSyncFailedBecauseBackendMaintenance() is true when there are workers with that status`() { + assertThat(createState( + up = listOf(createWorker(Failed(failedBecauseBackendMaintenance = true))), + down = listOf(createWorker(Succeeded), createWorker(Enqueued)), + ).isSyncFailedBecauseBackendMaintenance()).isTrue() + } + + @Test + fun `isSyncFailedBecauseBackendMaintenance() is false when there are no workers with that status`() { + assertThat(createState( + up = listOf(createWorker(Failed())), + down = listOf(createWorker(Succeeded)), + ).isSyncFailedBecauseBackendMaintenance()).isFalse() + } + + @Test + fun `isSyncFailedBecauseCloudIntegration() is true when there are workers with that status`() { + assertThat(createState( + up = listOf(createWorker(Failed(failedBecauseCloudIntegration = true))), + down = listOf(createWorker(Succeeded), createWorker(Enqueued)), + ).isSyncFailedBecauseCloudIntegration()).isTrue() + } + + @Test + fun `isSyncFailed() is false when there are no Failed, Blocked or Cancelled workers`() { + assertThat(createState( + up = listOf(createWorker(Enqueued), createWorker(Running)), + down = listOf(createWorker(Succeeded)), + ).isSyncFailed()).isFalse() + } + + @Test + fun `isSyncFailed() is true when there are no Failed workers`() { + assertThat(createState( + up = listOf(createWorker(Enqueued), createWorker(Running)), + down = listOf(createWorker(Succeeded), createWorker(Failed())), + ).isSyncFailed()).isTrue() + } + + @Test + fun `isSyncFailed() is true when there are no Blocked workers`() { + assertThat(createState( + up = listOf(createWorker(Enqueued), createWorker(Running)), + down = listOf(createWorker(Succeeded), createWorker(Blocked)), + ).isSyncFailed()).isTrue() + } + + @Test + fun `isSyncFailed() is true when there are no Cancelled workers`() { + assertThat(createState( + up = listOf(createWorker(Enqueued), createWorker(Running)), + down = listOf(createWorker(Succeeded), createWorker(Cancelled)), + ).isSyncFailed()).isTrue() + } + + @Test + fun `getEstimatedBackendMaintenanceOutage() returns outage value when there is a worker with that status`() { + val outage: Long = 666 + assertThat(createState( + up = listOf( + createWorker(Enqueued), + createWorker(Running), + createWorker(Failed(failedBecauseBackendMaintenance = true, estimatedOutage = outage))) + , + down = listOf(createWorker(Succeeded), createWorker(Cancelled)), + ).getEstimatedBackendMaintenanceOutage()).isEqualTo(outage) + } + + @Test + fun `getEstimatedBackendMaintenanceOutage() returns null when there is no worker with that status`() { + assertThat(createState( + up = listOf(createWorker(Enqueued), createWorker(Running)), + down = listOf(createWorker(Succeeded), createWorker(Cancelled)), + ).getEstimatedBackendMaintenanceOutage()).isNull() + } + private fun createState( up: List, down: List, diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTaskTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTaskTest.kt index 9aae62d04c..febca353f8 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTaskTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTaskTest.kt @@ -3,6 +3,7 @@ package com.simprints.infra.eventsync.sync.down.tasks import com.google.common.truth.Truth.assertThat import com.simprints.core.domain.tokenization.asTokenizableRaw import com.simprints.core.tools.time.TimeHelper +import com.simprints.infra.authstore.exceptions.RemoteDbNotSignedInException import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.models.DeviceConfiguration import com.simprints.infra.enrolment.records.store.EnrolmentRecordRepository @@ -143,6 +144,13 @@ class EventDownSyncTaskTest { coVerify(exactly = 2) { eventDownSyncScopeRepository.insertOrUpdate(any()) } } + @Test(expected = RemoteDbNotSignedInException::class) + fun downSync_shouldThrowUpIfRemoteDbNotSignedInExceptionOccurs() = runTest { + coEvery { eventRemoteDataSource.getEvents(any(), any()) } throws RemoteDbNotSignedInException() + + eventDownSyncTask.downSync(this, projectOp).toList() + } + @Test fun downSync_shouldProcessRecordCreationEvent() = runTest { val event = ENROLMENT_RECORD_CREATION diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncCountWorkerTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncCountWorkerTest.kt index 9ff80fb51f..6a35497b28 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncCountWorkerTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncCountWorkerTest.kt @@ -8,15 +8,22 @@ import androidx.work.workDataOf import com.google.common.truth.Truth.assertThat import com.google.common.util.concurrent.ListenableFuture import com.simprints.core.tools.json.JsonHelper +import com.simprints.infra.authstore.exceptions.RemoteDbNotSignedInException import com.simprints.infra.events.event.domain.EventCount import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordEventType import com.simprints.infra.eventsync.SampleSyncScopes.projectDownSyncScope import com.simprints.infra.eventsync.status.models.EventSyncWorkerType import com.simprints.infra.eventsync.status.models.EventSyncWorkerType.Companion.tagForType +import com.simprints.infra.eventsync.sync.common.OUTPUT_ESTIMATED_MAINTENANCE_TIME +import com.simprints.infra.eventsync.sync.common.OUTPUT_FAILED_BECAUSE_BACKEND_MAINTENANCE +import com.simprints.infra.eventsync.sync.common.OUTPUT_FAILED_BECAUSE_CLOUD_INTEGRATION +import com.simprints.infra.eventsync.sync.common.OUTPUT_FAILED_BECAUSE_RELOGIN_REQUIRED import com.simprints.infra.eventsync.sync.common.TAG_MASTER_SYNC_ID import com.simprints.infra.eventsync.sync.down.tasks.EventDownSyncCountTask import com.simprints.infra.eventsync.sync.down.workers.EventDownSyncCountWorker.Companion.INPUT_COUNT_WORKER_DOWN import com.simprints.infra.eventsync.sync.down.workers.EventDownSyncCountWorker.Companion.OUTPUT_COUNT_WORKER_DOWN +import com.simprints.infra.network.exceptions.BackendMaintenanceException +import com.simprints.infra.network.exceptions.SyncCloudIntegrationException import com.simprints.testtools.common.coroutines.TestCoroutineRule import io.mockk.* import io.mockk.impl.annotations.MockK @@ -89,6 +96,48 @@ internal class EventDownSyncCountWorkerTest { assertThat(result).isEqualTo(ListenableWorker.Result.success(expectedSuccessfulOutput)) } + @Test + fun `when worker encounters SyncCloudIntegrationException then it should fail with CLOUD_INTEGRATION`() { + runTest { + coEvery { eventDownSyncCountTask.getCount(any()) } throws + SyncCloudIntegrationException(cause = Throwable()) + + val result = countWorker.doWork() + + val expectedFailureOutput = workDataOf(OUTPUT_FAILED_BECAUSE_CLOUD_INTEGRATION to true) + assertThat(result).isEqualTo(ListenableWorker.Result.failure(expectedFailureOutput)) + } + } + + @Test + fun `when worker encounters BackendMaintenanceException then it should fail with BACKEND_MAINTENANCE`() { + runTest { + val outage: Long = 666 + coEvery { eventDownSyncCountTask.getCount(any()) } throws + BackendMaintenanceException(estimatedOutage = outage) + + val result = countWorker.doWork() + + val expectedFailureOutput = workDataOf( + OUTPUT_FAILED_BECAUSE_BACKEND_MAINTENANCE to true, + OUTPUT_ESTIMATED_MAINTENANCE_TIME to outage + ) + assertThat(result).isEqualTo(ListenableWorker.Result.failure(expectedFailureOutput)) + } + } + + @Test + fun `when worker encounters RemoteDbNotSignedInException then it should fail with RELOGIN_REQUIRED`() { + runTest { + coEvery { eventDownSyncCountTask.getCount(any()) } throws RemoteDbNotSignedInException() + + val result = countWorker.doWork() + + val expectedFailureOutput = workDataOf(OUTPUT_FAILED_BECAUSE_RELOGIN_REQUIRED to true) + assertThat(result).isEqualTo(ListenableWorker.Result.failure(expectedFailureOutput)) + } + } + @Test fun countWorkerFailed_syncStillRunning_shouldRetry() = runTest { coEvery { eventDownSyncCountTask.getCount(any()) } throws Throwable("IO Error") diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncDownloaderWorkerTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncDownloaderWorkerTest.kt index 7445b4ffd6..a515432ba7 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncDownloaderWorkerTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncDownloaderWorkerTest.kt @@ -8,6 +8,7 @@ import androidx.work.WorkInfo.State.SUCCEEDED import androidx.work.workDataOf import com.google.common.truth.Truth.assertThat import com.simprints.core.tools.json.JsonHelper +import com.simprints.infra.authstore.exceptions.RemoteDbNotSignedInException import com.simprints.infra.eventsync.SampleSyncScopes.projectDownSyncScope import com.simprints.infra.eventsync.event.remote.exceptions.TooManyRequestsException import com.simprints.infra.eventsync.status.down.EventDownSyncScopeRepository @@ -144,6 +145,23 @@ internal class EventDownSyncDownloaderWorkerTest { ) } + @Test + fun worker_failForRemoteDbNotSignedInException_shouldFail() = runTest { + coEvery { + downSyncTask.downSync(any(), any()) + } throws RemoteDbNotSignedInException() + + val result = eventDownSyncDownloaderWorker.doWork() + + assertThat(result).isEqualTo( + ListenableWorker.Result.failure( + workDataOf( + OUTPUT_FAILED_BECAUSE_RELOGIN_REQUIRED to true + ) + ) + ) + } + @Test fun worker_failForNetworkIssue_shouldRetry() = runTest { coEvery { diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTaskTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTaskTest.kt index 5cb9bcbd93..c76a8285c2 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTaskTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTaskTest.kt @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonParseException import com.google.common.truth.Truth.assertThat import com.simprints.core.tools.time.TimeHelper import com.simprints.core.tools.utils.randomUUID +import com.simprints.infra.authstore.exceptions.RemoteDbNotSignedInException import com.simprints.infra.config.store.models.ProjectConfiguration import com.simprints.infra.config.store.models.SynchronizationConfiguration import com.simprints.infra.config.store.models.UpSynchronizationConfiguration @@ -321,6 +322,13 @@ internal class EventUpSyncTaskTest { coVerify(exactly = 1) { eventUpSyncScopeRepository.insertOrUpdate(any()) } } + @Test(expected = RemoteDbNotSignedInException::class) + fun `upSync should throw up if RemoteDbNotSignedInException occurs`() = runTest { + coEvery { eventRepo.getAllClosedSessionIds(any()) } throws RemoteDbNotSignedInException() + + eventUpSyncTask.upSync(operation).toList() + } + private fun setUpSyncKind(kind: UpSynchronizationConfiguration.UpSynchronizationKind) { every { synchronizationConfiguration.up.simprints.kind } returns kind } diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/workers/EventUpSyncUploaderWorkerTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/workers/EventUpSyncUploaderWorkerTest.kt index 5e9c868f1f..96f3e99edc 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/workers/EventUpSyncUploaderWorkerTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/workers/EventUpSyncUploaderWorkerTest.kt @@ -10,16 +10,18 @@ import androidx.work.testing.TestListenableWorkerBuilder import androidx.work.workDataOf import com.google.common.truth.Truth.assertThat import com.simprints.core.tools.json.JsonHelper +import com.simprints.infra.authstore.AuthStore +import com.simprints.infra.authstore.exceptions.RemoteDbNotSignedInException import com.simprints.infra.eventsync.status.up.domain.EventUpSyncOperation import com.simprints.infra.eventsync.status.up.domain.EventUpSyncScope import com.simprints.infra.eventsync.sync.common.EventSyncCache import com.simprints.infra.eventsync.sync.common.OUTPUT_ESTIMATED_MAINTENANCE_TIME import com.simprints.infra.eventsync.sync.common.OUTPUT_FAILED_BECAUSE_BACKEND_MAINTENANCE import com.simprints.infra.eventsync.sync.common.OUTPUT_FAILED_BECAUSE_CLOUD_INTEGRATION -import com.simprints.infra.eventsync.sync.up.tasks.EventUpSyncTask +import com.simprints.infra.eventsync.sync.common.OUTPUT_FAILED_BECAUSE_RELOGIN_REQUIRED import com.simprints.infra.eventsync.sync.up.EventUpSyncProgress +import com.simprints.infra.eventsync.sync.up.tasks.EventUpSyncTask import com.simprints.infra.eventsync.sync.up.workers.EventUpSyncUploaderWorker.Companion.INPUT_UP_SYNC -import com.simprints.infra.authstore.AuthStore import com.simprints.infra.logging.Simber import com.simprints.infra.network.exceptions.BackendMaintenanceException import com.simprints.infra.network.exceptions.SyncCloudIntegrationException @@ -136,7 +138,6 @@ class EventUpSyncUploaderWorkerTest { ) } - @Test fun worker_shouldSetFailCorrectlyIfCloudIntegrationError() = runTest { val eventUpSyncUploaderWorker = init(projectScope) @@ -156,6 +157,24 @@ class EventUpSyncUploaderWorkerTest { ) } + @Test + fun worker_shouldSetFailCorrectlyIfRemoteDbNotSignedInException() = runTest { + val eventUpSyncUploaderWorker = init(projectScope) + + coEvery { + upSyncTask.upSync(any()) + } throws RemoteDbNotSignedInException() + + val result = eventUpSyncUploaderWorker.doWork() + + assertThat(result).isEqualTo( + ListenableWorker.Result.failure( + workDataOf( + OUTPUT_FAILED_BECAUSE_RELOGIN_REQUIRED to true + ) + ) + ) + } @Test fun worker_shouldRetryIfNotBackendMaintenanceOrSyncIssue() = runTest { @@ -170,7 +189,6 @@ class EventUpSyncUploaderWorkerTest { assertThat(result).isEqualTo(ListenableWorker.Result.retry()) } - @Test fun eventUpSyncScope_canDeserializeOldFormat() { val jsonInput = """ From c028d8e30c1ed9799a2e688c00dbc00b7a05f641 Mon Sep 17 00:00:00 2001 From: Milen Marinov Date: Tue, 31 Oct 2023 14:20:28 +0200 Subject: [PATCH 009/197] [CORE-2976] Move actions inside fragments for better organization --- .../main/res/navigation/graph_dashboard.xml | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/feature/dashboard/src/main/res/navigation/graph_dashboard.xml b/feature/dashboard/src/main/res/navigation/graph_dashboard.xml index 79724bca49..04053eb3e8 100644 --- a/feature/dashboard/src/main/res/navigation/graph_dashboard.xml +++ b/feature/dashboard/src/main/res/navigation/graph_dashboard.xml @@ -5,6 +5,9 @@ android:id="@+id/dashboard_navigation" app:startDestination="@id/baseFragment"> + + + + + + - - - - - - - - + Date: Wed, 21 Feb 2024 13:14:19 +0200 Subject: [PATCH 010/197] Fix merge issues --- .../logout/sync/LogoutSyncFragment.kt | 6 +- .../dashboard/main/sync/SyncFragment.kt | 2 +- .../settings/syncinfo/SyncInfoFragment.kt | 2 +- .../settings/syncinfo/SyncInfoViewModel.kt | 4 +- .../main/res/layout/fragment_sync_info.xml | 195 ++++++++---------- .../src/main/res/layout/layout_card_sync.xml | 2 +- .../dashboard/main/sync/SyncFragmentTest.kt | 2 +- .../dashboard/main/sync/SyncViewModelTest.kt | 6 +- .../syncinfo/SyncInfoViewModelTest.kt | 6 +- .../infra/authstore/db/FirebaseAuthManager.kt | 2 +- .../sync/down/tasks/EventDownSyncTask.kt | 7 +- .../down/workers/EventDownSyncCountWorker.kt | 2 + .../workers/EventDownSyncDownloaderWorker.kt | 10 +- .../sync/up/tasks/EventUpSyncTask.kt | 5 + .../up/workers/EventUpSyncUploaderWorker.kt | 11 +- .../workers/EventDownSyncCountWorkerTest.kt | 34 --- 16 files changed, 139 insertions(+), 157 deletions(-) diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/sync/LogoutSyncFragment.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/sync/LogoutSyncFragment.kt index 4b6084e4d0..ed46d76baf 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/sync/LogoutSyncFragment.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/sync/LogoutSyncFragment.kt @@ -10,15 +10,15 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import com.simprints.core.livedata.LiveDataEventWithContentObserver -import com.simprints.infra.uibase.viewbinding.viewBinding import com.simprints.feature.dashboard.R import com.simprints.feature.dashboard.databinding.FragmentLogoutSyncBinding import com.simprints.feature.dashboard.logout.LogoutSyncViewModel -import com.simprints.feature.dashboard.views.SyncCardState import com.simprints.feature.dashboard.main.sync.SyncViewModel +import com.simprints.feature.dashboard.views.SyncCardState import com.simprints.feature.login.LoginContract import com.simprints.feature.login.LoginResult import com.simprints.infra.uibase.navigation.handleResult +import com.simprints.infra.uibase.viewbinding.viewBinding import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -36,7 +36,7 @@ class LogoutSyncFragment : Fragment(R.layout.fragment_logout_sync) { findNavController().handleResult( viewLifecycleOwner, R.id.logOutSyncFragment, - LoginContract.LOGIN_DESTINATION_ID, + LoginContract.DESTINATION, ) { result -> syncViewModel.handleLoginResult(result) } } diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncFragment.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncFragment.kt index a95ae81f0f..5a4d30744f 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncFragment.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncFragment.kt @@ -33,7 +33,7 @@ internal class SyncFragment : Fragment(R.layout.fragment_dashboard_card_sync) { findNavController().handleResult( viewLifecycleOwner, R.id.mainFragment, - LoginContract.LOGIN_DESTINATION_ID, + LoginContract.DESTINATION, ) { result -> viewModel.handleLoginResult(result) } } diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoFragment.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoFragment.kt index 9316e530d3..676d6ce1c2 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoFragment.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoFragment.kt @@ -47,7 +47,7 @@ internal class SyncInfoFragment : Fragment(R.layout.fragment_sync_info) { findNavController().handleResult( viewLifecycleOwner, R.id.syncInfoFragment, - LoginContract.LOGIN_DESTINATION_ID, + LoginContract.DESTINATION, ) { result -> viewModel.handleLoginResult(result) } } diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt index 9962c073b3..af80e1ee73 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt @@ -30,6 +30,7 @@ import com.simprints.infra.eventsync.status.models.EventSyncWorkerState import com.simprints.infra.images.ImageRepository import com.simprints.infra.logging.Simber import com.simprints.infra.network.ConnectivityTracker +import com.simprints.infra.recent.user.activity.RecentUserActivityManager import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -45,7 +46,8 @@ internal class SyncInfoViewModel @Inject constructor( private val authStore: AuthStore, private val imageRepository: ImageRepository, private val eventSyncManager: EventSyncManager, - private val tokenizationProcessor: TokenizationProcessor + private val tokenizationProcessor: TokenizationProcessor, + private val recentUserActivityManager: RecentUserActivityManager ) : ViewModel() { val recordsInLocal: LiveData diff --git a/feature/dashboard/src/main/res/layout/fragment_sync_info.xml b/feature/dashboard/src/main/res/layout/fragment_sync_info.xml index fe35e5b744..5fc70652e1 100644 --- a/feature/dashboard/src/main/res/layout/fragment_sync_info.xml +++ b/feature/dashboard/src/main/res/layout/fragment_sync_info.xml @@ -26,24 +26,25 @@ + app:layout_constraintTop_toBottomOf="@id/recordsToUploadCardView"> + android:padding="10dp"> + app:layout_constraintTop_toBottomOf="@+id/recordsToDownloadCardView"> + android:orientation="horizontal" + android:padding="10dp"> + android:layout_weight="0.2" + android:gravity="center" + android:textSize="16sp" + tools:text="137" /> - - - + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/appBarLayout"> + android:layout_height="wrap_content" + android:orientation="horizontal" + android:padding="10dp"> + android:layout_weight="0.2" + android:gravity="center" + tools:text="137" /> + app:layout_constraintTop_toBottomOf="@+id/recordsInLocalCardView"> + android:orientation="vertical" + android:padding="10dp"> + tools:text="0" /> + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toEndOf="@+id/recordsToUploadCardView" + app:layout_constraintTop_toTopOf="@id/recordsToUploadCardView"> + android:orientation="vertical" + android:padding="10dp"> + tools:text="0" /> + android:text="@string/login_log_in_button" /> @@ -349,13 +336,13 @@ android:id="@+id/modulesTabHost" android:layout_width="match_parent" android:layout_height="0dp" - android:layout_marginTop="16dp" + android:layout_marginTop="15dp" android:background="@color/cardview_light_background" android:orientation="vertical" app:layout_constraintBottom_toTopOf="@+id/moduleSelectionButton" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/sync_action_section" + app:layout_constraintTop_toBottomOf="@+id/syncButton" tools:ignore="SpeakableTextPresentCheck"> + android:text="@string/login_log_in_button" /> Date: Wed, 21 Feb 2024 13:02:03 +0000 Subject: [PATCH 011/197] MS-233 GitHub Actions jobs execution time capped at vars.JOB_TIMEOUT_MINUTES --- .github/workflows/deploy-to-firebase-distribution.yml | 1 + .github/workflows/deploy-to-internal.yml | 1 + .github/workflows/jira.yml | 1 + .github/workflows/promote-artifact.yml | 1 + .github/workflows/refresh-gradle-cache.yml | 1 + .github/workflows/run-unit-tests.yml | 1 + .github/workflows/sonar-scan.yml | 1 + 7 files changed, 7 insertions(+) diff --git a/.github/workflows/deploy-to-firebase-distribution.yml b/.github/workflows/deploy-to-firebase-distribution.yml index e51956b28f..b04984ec77 100644 --- a/.github/workflows/deploy-to-firebase-distribution.yml +++ b/.github/workflows/deploy-to-firebase-distribution.yml @@ -11,6 +11,7 @@ on: jobs: deploy-to-firebase: runs-on: ubuntu-latest + timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT_MINUTES) }} concurrency: group: firebase-${{ inputs.buildType }}-workflow #only one instance of this workflow can run at a time diff --git a/.github/workflows/deploy-to-internal.yml b/.github/workflows/deploy-to-internal.yml index 0bcc912ba5..2c26854d80 100644 --- a/.github/workflows/deploy-to-internal.yml +++ b/.github/workflows/deploy-to-internal.yml @@ -15,6 +15,7 @@ jobs: deploy-to-internal: runs-on: ubuntu-latest + timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT_MINUTES) }} concurrency: group: release-internal-workflow #only one instance of this workflow can run at a time diff --git a/.github/workflows/jira.yml b/.github/workflows/jira.yml index a615b94fd3..74de3b2e5e 100644 --- a/.github/workflows/jira.yml +++ b/.github/workflows/jira.yml @@ -14,6 +14,7 @@ jobs: sync: name: Sync Items runs-on: ubuntu-latest + timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT_MINUTES) }} steps: - name: Sync uses: mheap/github-action-issue-to-jira@v1 diff --git a/.github/workflows/promote-artifact.yml b/.github/workflows/promote-artifact.yml index 1a6532e822..da86531949 100644 --- a/.github/workflows/promote-artifact.yml +++ b/.github/workflows/promote-artifact.yml @@ -19,6 +19,7 @@ on: jobs: promote-artifact: runs-on: ubuntu-latest + timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT_MINUTES) }} environment: ${{inputs.deployment-track}} # Dynamically set the job environment based on the input diff --git a/.github/workflows/refresh-gradle-cache.yml b/.github/workflows/refresh-gradle-cache.yml index 3fdcb2b6b5..eebbf6b1c4 100644 --- a/.github/workflows/refresh-gradle-cache.yml +++ b/.github/workflows/refresh-gradle-cache.yml @@ -9,6 +9,7 @@ on: jobs: refresh-caches: runs-on: ubuntu-latest + timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT_MINUTES) }} env: GOOGLE_SERVICES_FILE: ${{ secrets.GOOGLE_SERVICES_FILE}} diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index 43c64193ef..f6e607b334 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -18,6 +18,7 @@ jobs: test-modules: runs-on: ubuntu-latest + timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT_MINUTES) }} env: GOOGLE_SERVICES_FILE: ${{ secrets.GOOGLE_SERVICES_FILE}} diff --git a/.github/workflows/sonar-scan.yml b/.github/workflows/sonar-scan.yml index 9e10ae1ea0..c4a560c4a9 100644 --- a/.github/workflows/sonar-scan.yml +++ b/.github/workflows/sonar-scan.yml @@ -10,6 +10,7 @@ jobs: GOOGLE_SERVICES_FILE: ${{ secrets.GOOGLE_SERVICES_FILE}} runs-on: ubuntu-latest + timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT_MINUTES) }} steps: - name: Checkout uses: actions/checkout@v4 From a2caaa4d33213ccd6e78d8adb94300c11e0798dc Mon Sep 17 00:00:00 2001 From: Milen Marinov Date: Mon, 23 Oct 2023 17:46:12 +0300 Subject: [PATCH 012/197] [CORE-2976] Add better logging for failed image upload attempts --- .../java/com/simprints/infra/images/ImageRepositoryImpl.kt | 3 ++- .../infra/images/remote/ImageRemoteDataSourceImpl.kt | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/infra/images/src/main/java/com/simprints/infra/images/ImageRepositoryImpl.kt b/infra/images/src/main/java/com/simprints/infra/images/ImageRepositoryImpl.kt index ad267970f5..6db5067cda 100644 --- a/infra/images/src/main/java/com/simprints/infra/images/ImageRepositoryImpl.kt +++ b/infra/images/src/main/java/com/simprints/infra/images/ImageRepositoryImpl.kt @@ -34,11 +34,12 @@ class ImageRepositoryImpl @Inject internal constructor( localDataSource.deleteImage(imageRef) } else { allImagesUploaded = false + Simber.e("Failed to upload image without exception") } } } catch (t: Throwable) { allImagesUploaded = false - Simber.d(t) + Simber.e(t) } } diff --git a/infra/images/src/main/java/com/simprints/infra/images/remote/ImageRemoteDataSourceImpl.kt b/infra/images/src/main/java/com/simprints/infra/images/remote/ImageRemoteDataSourceImpl.kt index ca57f5aacb..6dbb56ba22 100644 --- a/infra/images/src/main/java/com/simprints/infra/images/remote/ImageRemoteDataSourceImpl.kt +++ b/infra/images/src/main/java/com/simprints/infra/images/remote/ImageRemoteDataSourceImpl.kt @@ -24,8 +24,10 @@ internal class ImageRemoteDataSourceImpl @Inject constructor( return if (firebaseProjectName != null) { val projectId = authStore.signedInProjectId - if (projectId.isEmpty()) + if (projectId.isEmpty()) { + Simber.i("AuthStore projectId is empty") return UploadResult(imageRef, UploadResult.Status.FAILED) + } val bucketUrl = imageUrlProvider.getProject(projectId).imageBucket @@ -51,6 +53,7 @@ internal class ImageRemoteDataSourceImpl @Inject constructor( UploadResult(imageRef, status) } else { + Simber.i("Firebase projectId is null") UploadResult(imageRef, UploadResult.Status.FAILED) } } From 4843b1f030084d5d64b4df15d2e2c417fa2d4df9 Mon Sep 17 00:00:00 2001 From: Milen Marinov Date: Tue, 24 Oct 2023 16:48:41 +0300 Subject: [PATCH 013/197] [CORE-2976] Log and bubble up RemoteDbNotSignedInException in sync workers --- .../eventsync/status/models/EventSyncWorkerState.kt | 10 +++++----- .../infra/eventsync/sync/common/WorkInfo.ext.kt | 2 +- .../sync/down/workers/EventDownSyncCountWorker.kt | 9 ++++----- .../down/workers/EventDownSyncDownloaderWorker.kt | 5 +++-- .../infra/eventsync/sync/up/tasks/EventUpSyncTask.kt | 12 +++++++----- .../sync/up/workers/EventUpSyncUploaderWorker.kt | 4 ++-- 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/models/EventSyncWorkerState.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/models/EventSyncWorkerState.kt index 2e0ac4e370..d61eeea62f 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/models/EventSyncWorkerState.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/models/EventSyncWorkerState.kt @@ -5,9 +5,9 @@ import androidx.work.WorkInfo // val state: String is used for logs purpose only - otherwise any state.toString() would print the same output. sealed class EventSyncWorkerState(val state: String) { - object Enqueued : EventSyncWorkerState("Enqueued") - object Running : EventSyncWorkerState("Running") - object Succeeded : EventSyncWorkerState("Succeeded") + data object Enqueued : EventSyncWorkerState("Enqueued") + data object Running : EventSyncWorkerState("Running") + data object Succeeded : EventSyncWorkerState("Succeeded") class Failed( val failedBecauseReloginRequired: Boolean = false, val failedBecauseCloudIntegration: Boolean = false, @@ -17,8 +17,8 @@ sealed class EventSyncWorkerState(val state: String) { ) : EventSyncWorkerState("Failed") - object Blocked : EventSyncWorkerState("Blocked") - object Cancelled : EventSyncWorkerState("Cancelled") + data object Blocked : EventSyncWorkerState("Blocked") + data object Cancelled : EventSyncWorkerState("Cancelled") companion object { fun fromWorkInfo( diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/common/WorkInfo.ext.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/common/WorkInfo.ext.kt index 9333401154..0747c7e202 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/common/WorkInfo.ext.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/common/WorkInfo.ext.kt @@ -2,7 +2,7 @@ package com.simprints.infra.eventsync.sync.common import androidx.work.WorkInfo -internal const val OUTPUT_FAILED_BECAUSE_RELOGIN_REQUIRED = "OUTPUT_FAILED_BECAUSE_RELOGIN_REQUIRED" +internal const val OUTPUT_FAILED_BECAUSE_SIGN_IN_REQUIRED = "OUTPUT_FAILED_BECAUSE_SIGN_IN_REQUIRED" internal const val OUTPUT_FAILED_BECAUSE_TOO_MANY_REQUESTS = "OUTPUT_FAILED_BECAUSE_TOO_MANY_REQUESTS" internal const val OUTPUT_FAILED_BECAUSE_CLOUD_INTEGRATION = "OUTPUT_FAILED_BECAUSE_CLOUD_INTEGRATION" internal const val OUTPUT_FAILED_BECAUSE_BACKEND_MAINTENANCE = "OUTPUT_FAILED_BECAUSE_BACKEND_MAINTENANCE" diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncCountWorker.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncCountWorker.kt index 62a6e364e2..312c535c40 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncCountWorker.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncCountWorker.kt @@ -52,6 +52,7 @@ internal class EventDownSyncCountWorker @AssistedInject constructor( override val tag: String = EventDownSyncCountWorker::class.java.simpleName + private val downSyncScope by lazy { val jsonInput = inputData.getString(INPUT_COUNT_WORKER_DOWN) ?: throw IllegalArgumentException("input required") @@ -62,13 +63,13 @@ internal class EventDownSyncCountWorker @AssistedInject constructor( override suspend fun doWork(): Result = withContext(dispatcher) { Simber.tag(SYNC_LOG_TAG).d("[COUNT_DOWN] Started") try { - crashlyticsLog("Start") + crashlyticsLog("Start - Params: $downSyncScope") val downCount = eventDownSyncCountTask.getCount(downSyncScope) val output = jsonHelper.toJson(downCount) Simber.tag(SYNC_LOG_TAG).d("[COUNT_DOWN] Done $downCount") - success(workDataOf(OUTPUT_COUNT_WORKER_DOWN to output)) + success(workDataOf(OUTPUT_COUNT_WORKER_DOWN to output), output) } catch (t: Throwable) { Simber.tag(SYNC_LOG_TAG).d("[COUNT_DOWN] Failed. ${t.message}") @@ -78,8 +79,7 @@ internal class EventDownSyncCountWorker @AssistedInject constructor( fail(t, t.message, workDataOf(OUTPUT_FAILED_BECAUSE_CLOUD_INTEGRATION to true)) } t is BackendMaintenanceException -> fail( - t, - t.message, + t, t.message, workDataOf( OUTPUT_FAILED_BECAUSE_BACKEND_MAINTENANCE to true, OUTPUT_ESTIMATED_MAINTENANCE_TIME to t.estimatedOutage @@ -91,7 +91,6 @@ internal class EventDownSyncCountWorker @AssistedInject constructor( isSyncStillRunning() -> retry(t) else -> { Simber.d(t) - t.printStackTrace() success(message = "Succeed because count is not required any more.") } } diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncDownloaderWorker.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncDownloaderWorker.kt index 3c30799895..d6823d3b5d 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncDownloaderWorker.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncDownloaderWorker.kt @@ -82,7 +82,8 @@ internal class EventDownSyncDownloaderWorker @AssistedInject constructor( "Total downloaded: $0" ) } catch (t: Throwable) { - Simber.tag(SYNC_LOG_TAG).d("[DOWNLOADER] Failed") + Simber.d(t) + Simber.tag(SYNC_LOG_TAG).d("[DOWNLOADER] Failed ${t.message}") handleSyncException(t) } } @@ -109,7 +110,7 @@ internal class EventDownSyncDownloaderWorker @AssistedInject constructor( ) ) is RemoteDbNotSignedInException -> { - fail(t, t.message, workDataOf(OUTPUT_FAILED_BECAUSE_RELOGIN_REQUIRED to true)) + fail(t, t.message, workDataOf(OUTPUT_FAILED_BECAUSE_SIGN_IN_REQUIRED to true)) } else -> retry(t) } diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt index ce4e7cf4a4..0737773820 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt @@ -116,11 +116,12 @@ internal class EventUpSyncTask @Inject constructor( this.emit(it.size) } } catch (ex: Exception) { - if (ex is JsonParseException || ex is JsonMappingException) { - attemptInvalidEventUpload(projectId, sessionId)?.let { this.emit(it) } - } else { - Simber.i("Failed to un-marshal events for $sessionId") - Simber.e(ex) + Simber.i("Failed to upload events for $sessionId") + when (ex) { + is JsonParseException, is JsonMappingException -> + attemptInvalidEventUpload(projectId, sessionId)?.let { this.emit(it) } + is RemoteDbNotSignedInException -> throw ex + else -> Simber.e(ex) } } } @@ -176,6 +177,7 @@ internal class EventUpSyncTask @Inject constructor( is NetworkConnectionException -> Simber.i(t) // We don't need to report http exceptions as cloud logs all of them. is HttpException -> Simber.i(t) + is RemoteDbNotSignedInException -> throw t else -> Simber.e(t) } } diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/workers/EventUpSyncUploaderWorker.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/workers/EventUpSyncUploaderWorker.kt index ca86a91a0d..1fc03a69c7 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/workers/EventUpSyncUploaderWorker.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/workers/EventUpSyncUploaderWorker.kt @@ -84,11 +84,11 @@ internal class EventUpSyncUploaderWorker @AssistedInject constructor( } catch (t: Throwable) { Simber.d(t) Simber.tag(SYNC_LOG_TAG).d("[UPLOADER] Failed ${t.message}") - retryOrFailIfCloudIntegrationOrBackendMaintenanceError(t) + handleSyncException(t) } } - private fun retryOrFailIfCloudIntegrationOrBackendMaintenanceError(t: Throwable): Result { + private fun handleSyncException(t: Throwable): Result { return when (t) { is BackendMaintenanceException -> { fail( From 304049f64994feba4022ad9568a24c8c2a84f90e Mon Sep 17 00:00:00 2001 From: melad Date: Thu, 22 Feb 2024 12:23:56 +0200 Subject: [PATCH 014/197] Fix constant name --- .../com/simprints/infra/eventsync/sync/common/WorkInfo.ext.kt | 2 +- .../sync/down/workers/EventDownSyncDownloaderWorker.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/common/WorkInfo.ext.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/common/WorkInfo.ext.kt index 0747c7e202..9333401154 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/common/WorkInfo.ext.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/common/WorkInfo.ext.kt @@ -2,7 +2,7 @@ package com.simprints.infra.eventsync.sync.common import androidx.work.WorkInfo -internal const val OUTPUT_FAILED_BECAUSE_SIGN_IN_REQUIRED = "OUTPUT_FAILED_BECAUSE_SIGN_IN_REQUIRED" +internal const val OUTPUT_FAILED_BECAUSE_RELOGIN_REQUIRED = "OUTPUT_FAILED_BECAUSE_RELOGIN_REQUIRED" internal const val OUTPUT_FAILED_BECAUSE_TOO_MANY_REQUESTS = "OUTPUT_FAILED_BECAUSE_TOO_MANY_REQUESTS" internal const val OUTPUT_FAILED_BECAUSE_CLOUD_INTEGRATION = "OUTPUT_FAILED_BECAUSE_CLOUD_INTEGRATION" internal const val OUTPUT_FAILED_BECAUSE_BACKEND_MAINTENANCE = "OUTPUT_FAILED_BECAUSE_BACKEND_MAINTENANCE" diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncDownloaderWorker.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncDownloaderWorker.kt index d6823d3b5d..b3acfa0f3f 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncDownloaderWorker.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncDownloaderWorker.kt @@ -110,7 +110,7 @@ internal class EventDownSyncDownloaderWorker @AssistedInject constructor( ) ) is RemoteDbNotSignedInException -> { - fail(t, t.message, workDataOf(OUTPUT_FAILED_BECAUSE_SIGN_IN_REQUIRED to true)) + fail(t, t.message, workDataOf(OUTPUT_FAILED_BECAUSE_RELOGIN_REQUIRED to true)) } else -> retry(t) } From 18ec6b8531ee2c7571b18c99e1798c146ac9a6a6 Mon Sep 17 00:00:00 2001 From: melad Date: Thu, 22 Feb 2024 13:30:27 +0200 Subject: [PATCH 015/197] Fix merge issues --- .../eventsync/sync/down/workers/EventDownSyncCountWorker.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncCountWorker.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncCountWorker.kt index 312c535c40..df229ff50d 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncCountWorker.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/workers/EventDownSyncCountWorker.kt @@ -63,7 +63,7 @@ internal class EventDownSyncCountWorker @AssistedInject constructor( override suspend fun doWork(): Result = withContext(dispatcher) { Simber.tag(SYNC_LOG_TAG).d("[COUNT_DOWN] Started") try { - crashlyticsLog("Start - Params: $downSyncScope") + crashlyticsLog("Start") val downCount = eventDownSyncCountTask.getCount(downSyncScope) val output = jsonHelper.toJson(downCount) From 3d0f41c005fcf318c6a46b6dea3776f4795653c4 Mon Sep 17 00:00:00 2001 From: Milen Marinov Date: Thu, 22 Feb 2024 14:31:09 +0200 Subject: [PATCH 016/197] [CORE-2976] Use the signedInUserId (when available) for re-login --- .../com/simprints/feature/dashboard/main/sync/SyncViewModel.kt | 3 +-- .../feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncViewModel.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncViewModel.kt index 60cd4c7ad7..7062c8f119 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncViewModel.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncViewModel.kt @@ -120,10 +120,9 @@ internal class SyncViewModel @Inject constructor( fun login() { viewModelScope.launch { - val userId = recentUserActivityManager.getRecentUserActivity().lastUserUsed val loginArgs = LoginContract.toArgs( authStore.signedInProjectId, - userId + authStore.signedInUserId ?: recentUserActivityManager.getRecentUserActivity().lastUserUsed ) _loginRequestedEventLiveData.send(loginArgs) } diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt index af80e1ee73..3635a8b380 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt @@ -165,10 +165,9 @@ internal class SyncInfoViewModel @Inject constructor( fun login() { viewModelScope.launch { - val userId = recentUserActivityManager.getRecentUserActivity().lastUserUsed val loginArgs = LoginContract.toArgs( authStore.signedInProjectId, - userId + authStore.signedInUserId ?: recentUserActivityManager.getRecentUserActivity().lastUserUsed ) _loginRequestedEventLiveData.send(loginArgs) } From d841fb1e70ebab03bc790e365e317716210a0ea9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 04:16:25 +0000 Subject: [PATCH 017/197] Bump hilt_androidx_version from 1.1.0 to 1.2.0 Bumps `hilt_androidx_version` from 1.1.0 to 1.2.0. Updates `androidx.hilt:hilt-compiler` from 1.1.0 to 1.2.0 Updates `androidx.hilt:hilt-work` from 1.1.0 to 1.2.0 --- updated-dependencies: - dependency-name: androidx.hilt:hilt-compiler dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: androidx.hilt:hilt-work dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a1eef2f443..be74ed007b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,7 +26,7 @@ androidx_arch_core_version = "2.2.0" matertial_version = "1.11.0" hilt_version = "2.50" -hilt_androidx_version = "1.1.0" +hilt_androidx_version = "1.2.0" play_base_services_version = "18.3.0" play_location_services_version = "21.1.0" From 25fb88704a898e9dafca56d9e881f0e9f84b5177 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 04:16:35 +0000 Subject: [PATCH 018/197] Bump androidx.test.uiautomator:uiautomator from 2.2.0 to 2.3.0 Bumps androidx.test.uiautomator:uiautomator from 2.2.0 to 2.3.0. --- updated-dependencies: - dependency-name: androidx.test.uiautomator:uiautomator dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a1eef2f443..ed40df7c23 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -82,7 +82,7 @@ robolectric_version = "4.11.1" espresso_version = "3.5.1" espresso_accessibility_version = "4.1.0" barista_version = "4.3.0" -uiAutomator_version = "2.2.0" +uiAutomator_version = "2.3.0" jacoco_version = "0.8.11" sonar_plugin_version = "4.4.1.3373" From 5b3b96247c7d490f953f7381d9b66b155ceb09ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 04:17:24 +0000 Subject: [PATCH 019/197] Bump com.google.firebase:firebase-crashlytics from 18.6.1 to 18.6.2 Bumps [com.google.firebase:firebase-crashlytics](https://github.com/firebase/firebase-android-sdk) from 18.6.1 to 18.6.2. - [Changelog](https://github.com/firebase/firebase-android-sdk/blob/master/docs/make_release_notes.py) - [Commits](https://github.com/firebase/firebase-android-sdk/commits) --- updated-dependencies: - dependency-name: com.google.firebase:firebase-crashlytics dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a1eef2f443..a90a20fe04 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,7 +37,7 @@ play_barcode_version = "18.3.0" firebase_auth_version = "22.3.1" firebase_storage_version = "20.3.0" -firebase_crashlytics_version = "18.6.1" +firebase_crashlytics_version = "18.6.2" # Update analytics with caution as it might cause `NoClassDefFoundError: Failed resolution of: ImmutableSet;` firebase_analytics_version = "21.4.0" firebase_perf_version = "20.5.2" From dff8bb10504c38252cedd202ee1d4d3763a23002 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Tue, 20 Feb 2024 10:11:10 +0200 Subject: [PATCH 020/197] MS-205 Reconfigure config-sync module to sync.config --- feature/dashboard/build.gradle.kts | 2 +- .../simprints/feature/dashboard/debug/DebugFragment.kt | 2 +- .../feature/dashboard/logout/usecase/LogoutUseCase.kt | 2 +- feature/login-check/build.gradle.kts | 2 +- .../logincheck/usecases/CancelBackgroundSyncUseCase.kt | 2 +- .../usecases/StartBackgroundSyncIfNeededUseCase.kt | 2 +- .../usecases/CancelBackgroundSyncUseCaseTest.kt | 2 +- .../usecases/StartBackgroundSyncUseCaseTest.kt | 2 +- id/build.gradle.kts | 2 +- .../com/simprints/id/ScheduleBackgroundSyncUseCase.kt | 2 +- .../simprints/id/ScheduleBackgroundSyncUseCaseTest.kt | 2 +- infra/{config-sync => sync}/.gitignore | 0 infra/{config-sync => sync}/build.gradle.kts | 2 +- infra/{config-sync => sync}/consumer-rules.pro | 0 .../{config-sync => sync}/src/main/AndroidManifest.xml | 0 .../main/java/com/simprints/infra/sync/SyncModule.kt} | 6 ++++-- .../sync/config}/ProjectConfigurationScheduler.kt | 2 +- .../sync/config}/ProjectConfigurationSchedulerImpl.kt | 7 ++++--- .../sync/config}/usecase/HandleProjectStateUseCase.kt | 2 +- .../infra/sync/config}/usecase/LogoutUseCase.kt | 4 ++-- .../usecase/RescheduleWorkersIfConfigChangedUseCase.kt | 2 +- .../sync/config}/worker/DeviceConfigDownSyncWorker.kt | 4 ++-- .../sync/config}/worker/ProjectConfigDownSyncWorker.kt | 6 +++--- .../config}/ProjectConfigurationSchedulerImplTest.kt | 3 ++- .../simprints/infra/sync/config}/testtools/Models.kt | 2 +- .../config}/usecase/HandleProjectStateUseCaseTest.kt | 4 +++- .../infra/sync/config}/usecase/LogoutUseCaseTest.kt | 5 +++-- .../RescheduleWorkersIfConfigChangedUseCaseTest.kt | 7 ++++--- .../config}/worker/DeviceConfigDownSyncWorkerTest.kt | 5 +++-- .../config}/worker/ProjectConfigDownSyncWorkerTest.kt | 10 +++++----- settings.gradle.kts | 6 +++--- 31 files changed, 54 insertions(+), 45 deletions(-) rename infra/{config-sync => sync}/.gitignore (100%) rename infra/{config-sync => sync}/build.gradle.kts (95%) rename infra/{config-sync => sync}/consumer-rules.pro (100%) rename infra/{config-sync => sync}/src/main/AndroidManifest.xml (100%) rename infra/{config-sync/src/main/java/com/simprints/infra/config/sync/ConfigManagerModule.kt => sync/src/main/java/com/simprints/infra/sync/SyncModule.kt} (61%) rename infra/{config-sync/src/main/java/com/simprints/infra/config/sync => sync/src/main/java/com/simprints/infra/sync/config}/ProjectConfigurationScheduler.kt (82%) rename infra/{config-sync/src/main/java/com/simprints/infra/config/sync => sync/src/main/java/com/simprints/infra/sync/config}/ProjectConfigurationSchedulerImpl.kt (92%) rename infra/{config-sync/src/main/java/com/simprints/infra/config/sync => sync/src/main/java/com/simprints/infra/sync/config}/usecase/HandleProjectStateUseCase.kt (94%) rename infra/{config-sync/src/main/java/com/simprints/infra/config/sync => sync/src/main/java/com/simprints/infra/sync/config}/usecase/LogoutUseCase.kt (87%) rename infra/{config-sync/src/main/java/com/simprints/infra/config/sync => sync/src/main/java/com/simprints/infra/sync/config}/usecase/RescheduleWorkersIfConfigChangedUseCase.kt (94%) rename infra/{config-sync/src/main/java/com/simprints/infra/config/sync => sync/src/main/java/com/simprints/infra/sync/config}/worker/DeviceConfigDownSyncWorker.kt (94%) rename infra/{config-sync/src/main/java/com/simprints/infra/config/sync => sync/src/main/java/com/simprints/infra/sync/config}/worker/ProjectConfigDownSyncWorker.kt (91%) rename infra/{config-sync/src/test/java/com/simprints/infra/config/sync => sync/src/test/java/com/simprints/infra/sync/config}/ProjectConfigurationSchedulerImplTest.kt (95%) rename infra/{config-sync/src/test/java/com/simprints/infra/config/sync => sync/src/test/java/com/simprints/infra/sync/config}/testtools/Models.kt (98%) rename infra/{config-sync/src/test/java/com/simprints/infra/config/sync => sync/src/test/java/com/simprints/infra/sync/config}/usecase/HandleProjectStateUseCaseTest.kt (91%) rename infra/{config-sync/src/test/java/com/simprints/infra/config/sync => sync/src/test/java/com/simprints/infra/sync/config}/usecase/LogoutUseCaseTest.kt (89%) rename infra/{config-sync/src/test/java/com/simprints/infra/config/sync => sync/src/test/java/com/simprints/infra/sync/config}/usecase/RescheduleWorkersIfConfigChangedUseCaseTest.kt (89%) rename infra/{config-sync/src/test/java/com/simprints/infra/config/sync => sync/src/test/java/com/simprints/infra/sync/config}/worker/DeviceConfigDownSyncWorkerTest.kt (94%) rename infra/{config-sync/src/test/java/com/simprints/infra/config/sync => sync/src/test/java/com/simprints/infra/sync/config}/worker/ProjectConfigDownSyncWorkerTest.kt (91%) diff --git a/feature/dashboard/build.gradle.kts b/feature/dashboard/build.gradle.kts index d1f5dfcc12..3e85f0d001 100644 --- a/feature/dashboard/build.gradle.kts +++ b/feature/dashboard/build.gradle.kts @@ -11,7 +11,7 @@ dependencies { implementation(project(":infra:events")) implementation(project(":infra:event-sync")) implementation(project(":infra:config-store")) - implementation(project(":infra:config-sync")) + implementation(project(":infra:sync")) implementation(project(":infra:enrolment-records-store")) implementation(project(":infra:images")) implementation(project(":infra:auth-store")) diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/debug/DebugFragment.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/debug/DebugFragment.kt index b789f443b3..92edbcaf28 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/debug/DebugFragment.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/debug/DebugFragment.kt @@ -15,7 +15,7 @@ import com.simprints.feature.dashboard.R import com.simprints.feature.dashboard.databinding.FragmentDebugBinding import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.ConfigRepository -import com.simprints.infra.config.sync.ProjectConfigurationScheduler +import com.simprints.infra.sync.config.ProjectConfigurationScheduler import com.simprints.infra.enrolment.records.store.EnrolmentRecordRepository import com.simprints.infra.events.EventRepository import com.simprints.infra.eventsync.EventSyncManager diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCase.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCase.kt index 62f6a45942..1916d4f219 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCase.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCase.kt @@ -1,7 +1,7 @@ package com.simprints.feature.dashboard.logout.usecase import com.simprints.infra.authlogic.AuthManager -import com.simprints.infra.config.sync.ProjectConfigurationScheduler +import com.simprints.infra.sync.config.ProjectConfigurationScheduler import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.images.ImageUpSyncScheduler import javax.inject.Inject diff --git a/feature/login-check/build.gradle.kts b/feature/login-check/build.gradle.kts index dfd6b8370e..45e4981267 100644 --- a/feature/login-check/build.gradle.kts +++ b/feature/login-check/build.gradle.kts @@ -12,7 +12,7 @@ dependencies { implementation(project(":infra:orchestrator-data")) implementation(project(":infra:config-store")) - implementation(project(":infra:config-sync")) + implementation(project(":infra:sync")) implementation(project(":infra:events")) implementation(project(":infra:event-sync")) implementation(project(":infra:images")) diff --git a/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCase.kt b/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCase.kt index 3532814748..73b2a63641 100644 --- a/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCase.kt +++ b/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCase.kt @@ -1,6 +1,6 @@ package com.simprints.feature.logincheck.usecases -import com.simprints.infra.config.sync.ProjectConfigurationScheduler +import com.simprints.infra.sync.config.ProjectConfigurationScheduler import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.images.ImageUpSyncScheduler import javax.inject.Inject diff --git a/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncIfNeededUseCase.kt b/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncIfNeededUseCase.kt index 0495056746..f8331ce702 100644 --- a/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncIfNeededUseCase.kt +++ b/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncIfNeededUseCase.kt @@ -2,7 +2,7 @@ package com.simprints.feature.logincheck.usecases import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.models.SynchronizationConfiguration -import com.simprints.infra.config.sync.ProjectConfigurationScheduler +import com.simprints.infra.sync.config.ProjectConfigurationScheduler import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.images.ImageUpSyncScheduler import javax.inject.Inject diff --git a/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCaseTest.kt b/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCaseTest.kt index 469c03e70b..0a14232bc1 100644 --- a/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCaseTest.kt +++ b/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCaseTest.kt @@ -1,6 +1,6 @@ package com.simprints.feature.logincheck.usecases -import com.simprints.infra.config.sync.ProjectConfigurationScheduler +import com.simprints.infra.sync.config.ProjectConfigurationScheduler import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.images.ImageUpSyncScheduler import io.mockk.MockKAnnotations diff --git a/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCaseTest.kt b/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCaseTest.kt index cc960eb265..e4873f7a77 100644 --- a/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCaseTest.kt +++ b/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCaseTest.kt @@ -2,7 +2,7 @@ package com.simprints.feature.logincheck.usecases import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.models.SynchronizationConfiguration -import com.simprints.infra.config.sync.ProjectConfigurationScheduler +import com.simprints.infra.sync.config.ProjectConfigurationScheduler import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.images.ImageUpSyncScheduler import io.mockk.MockKAnnotations diff --git a/id/build.gradle.kts b/id/build.gradle.kts index d4dffaed8a..cbaea8d7ec 100644 --- a/id/build.gradle.kts +++ b/id/build.gradle.kts @@ -28,7 +28,7 @@ dependencies { implementation(project(":infra:core")) implementation(project(":infra:event-sync")) - implementation(project(":infra:config-sync")) + implementation(project(":infra:sync")) implementation(project(":infra:images")) implementation(project(":infra:auth-logic")) implementation(project(":infra:auth-store")) diff --git a/id/src/main/java/com/simprints/id/ScheduleBackgroundSyncUseCase.kt b/id/src/main/java/com/simprints/id/ScheduleBackgroundSyncUseCase.kt index 9fdbccfc4b..a3dbfe2b24 100644 --- a/id/src/main/java/com/simprints/id/ScheduleBackgroundSyncUseCase.kt +++ b/id/src/main/java/com/simprints/id/ScheduleBackgroundSyncUseCase.kt @@ -2,7 +2,7 @@ package com.simprints.id import com.simprints.fingerprint.infra.scanner.data.worker.FirmwareFileUpdateScheduler import com.simprints.infra.authstore.AuthStore -import com.simprints.infra.config.sync.ProjectConfigurationScheduler +import com.simprints.infra.sync.config.ProjectConfigurationScheduler import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.images.ImageUpSyncScheduler import javax.inject.Inject diff --git a/id/src/test/java/com/simprints/id/ScheduleBackgroundSyncUseCaseTest.kt b/id/src/test/java/com/simprints/id/ScheduleBackgroundSyncUseCaseTest.kt index 3db0720db5..4018465a37 100644 --- a/id/src/test/java/com/simprints/id/ScheduleBackgroundSyncUseCaseTest.kt +++ b/id/src/test/java/com/simprints/id/ScheduleBackgroundSyncUseCaseTest.kt @@ -2,7 +2,7 @@ package com.simprints.id import com.simprints.fingerprint.infra.scanner.data.worker.FirmwareFileUpdateScheduler import com.simprints.infra.authstore.AuthStore -import com.simprints.infra.config.sync.ProjectConfigurationScheduler +import com.simprints.infra.sync.config.ProjectConfigurationScheduler import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.images.ImageUpSyncScheduler import io.mockk.MockKAnnotations diff --git a/infra/config-sync/.gitignore b/infra/sync/.gitignore similarity index 100% rename from infra/config-sync/.gitignore rename to infra/sync/.gitignore diff --git a/infra/config-sync/build.gradle.kts b/infra/sync/build.gradle.kts similarity index 95% rename from infra/config-sync/build.gradle.kts rename to infra/sync/build.gradle.kts index 46d112bd95..20f7eb83ee 100644 --- a/infra/config-sync/build.gradle.kts +++ b/infra/sync/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.simprints.infra.config.sync" + namespace = "com.simprints.infra.sync" buildTypes { diff --git a/infra/config-sync/consumer-rules.pro b/infra/sync/consumer-rules.pro similarity index 100% rename from infra/config-sync/consumer-rules.pro rename to infra/sync/consumer-rules.pro diff --git a/infra/config-sync/src/main/AndroidManifest.xml b/infra/sync/src/main/AndroidManifest.xml similarity index 100% rename from infra/config-sync/src/main/AndroidManifest.xml rename to infra/sync/src/main/AndroidManifest.xml diff --git a/infra/config-sync/src/main/java/com/simprints/infra/config/sync/ConfigManagerModule.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncModule.kt similarity index 61% rename from infra/config-sync/src/main/java/com/simprints/infra/config/sync/ConfigManagerModule.kt rename to infra/sync/src/main/java/com/simprints/infra/sync/SyncModule.kt index 56fbcd26e2..c19916be2f 100644 --- a/infra/config-sync/src/main/java/com/simprints/infra/config/sync/ConfigManagerModule.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncModule.kt @@ -1,5 +1,7 @@ -package com.simprints.infra.config.sync +package com.simprints.infra.sync +import com.simprints.infra.sync.config.ProjectConfigurationScheduler +import com.simprints.infra.sync.config.ProjectConfigurationSchedulerImpl import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -7,7 +9,7 @@ import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) -abstract class ConfigManagerModule { +abstract class SyncModule { @Binds internal abstract fun provideConfigurationScheduler(configurationScheduler: ProjectConfigurationSchedulerImpl): ProjectConfigurationScheduler diff --git a/infra/config-sync/src/main/java/com/simprints/infra/config/sync/ProjectConfigurationScheduler.kt b/infra/sync/src/main/java/com/simprints/infra/sync/config/ProjectConfigurationScheduler.kt similarity index 82% rename from infra/config-sync/src/main/java/com/simprints/infra/config/sync/ProjectConfigurationScheduler.kt rename to infra/sync/src/main/java/com/simprints/infra/sync/config/ProjectConfigurationScheduler.kt index 50c5c85ea5..f1e3f15a91 100644 --- a/infra/config-sync/src/main/java/com/simprints/infra/config/sync/ProjectConfigurationScheduler.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/config/ProjectConfigurationScheduler.kt @@ -1,4 +1,4 @@ -package com.simprints.infra.config.sync +package com.simprints.infra.sync.config interface ProjectConfigurationScheduler { fun scheduleProjectSync() diff --git a/infra/config-sync/src/main/java/com/simprints/infra/config/sync/ProjectConfigurationSchedulerImpl.kt b/infra/sync/src/main/java/com/simprints/infra/sync/config/ProjectConfigurationSchedulerImpl.kt similarity index 92% rename from infra/config-sync/src/main/java/com/simprints/infra/config/sync/ProjectConfigurationSchedulerImpl.kt rename to infra/sync/src/main/java/com/simprints/infra/sync/config/ProjectConfigurationSchedulerImpl.kt index 83d49d16c9..d9ca34f66d 100644 --- a/infra/config-sync/src/main/java/com/simprints/infra/config/sync/ProjectConfigurationSchedulerImpl.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/config/ProjectConfigurationSchedulerImpl.kt @@ -1,4 +1,4 @@ -package com.simprints.infra.config.sync +package com.simprints.infra.sync.config import android.content.Context import androidx.work.Constraints @@ -8,8 +8,9 @@ import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkManager -import com.simprints.infra.config.sync.worker.DeviceConfigDownSyncWorker -import com.simprints.infra.config.sync.worker.ProjectConfigDownSyncWorker +import com.simprints.infra.sync.BuildConfig +import com.simprints.infra.sync.config.worker.DeviceConfigDownSyncWorker +import com.simprints.infra.sync.config.worker.ProjectConfigDownSyncWorker import dagger.hilt.android.qualifiers.ApplicationContext import java.util.concurrent.TimeUnit import javax.inject.Inject diff --git a/infra/config-sync/src/main/java/com/simprints/infra/config/sync/usecase/HandleProjectStateUseCase.kt b/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/HandleProjectStateUseCase.kt similarity index 94% rename from infra/config-sync/src/main/java/com/simprints/infra/config/sync/usecase/HandleProjectStateUseCase.kt rename to infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/HandleProjectStateUseCase.kt index 36d00a7ffa..2502a41906 100644 --- a/infra/config-sync/src/main/java/com/simprints/infra/config/sync/usecase/HandleProjectStateUseCase.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/HandleProjectStateUseCase.kt @@ -1,4 +1,4 @@ -package com.simprints.infra.config.sync.usecase +package com.simprints.infra.sync.config.usecase import com.simprints.infra.config.store.models.ProjectState import com.simprints.infra.eventsync.EventSyncManager diff --git a/infra/config-sync/src/main/java/com/simprints/infra/config/sync/usecase/LogoutUseCase.kt b/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/LogoutUseCase.kt similarity index 87% rename from infra/config-sync/src/main/java/com/simprints/infra/config/sync/usecase/LogoutUseCase.kt rename to infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/LogoutUseCase.kt index 4c2e7e365d..6e537aba2c 100644 --- a/infra/config-sync/src/main/java/com/simprints/infra/config/sync/usecase/LogoutUseCase.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/LogoutUseCase.kt @@ -1,7 +1,7 @@ -package com.simprints.infra.config.sync.usecase +package com.simprints.infra.sync.config.usecase import com.simprints.infra.authlogic.AuthManager -import com.simprints.infra.config.sync.ProjectConfigurationScheduler +import com.simprints.infra.sync.config.ProjectConfigurationScheduler import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.images.ImageUpSyncScheduler import javax.inject.Inject diff --git a/infra/config-sync/src/main/java/com/simprints/infra/config/sync/usecase/RescheduleWorkersIfConfigChangedUseCase.kt b/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCase.kt similarity index 94% rename from infra/config-sync/src/main/java/com/simprints/infra/config/sync/usecase/RescheduleWorkersIfConfigChangedUseCase.kt rename to infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCase.kt index 97e6022c4c..939cffd9e9 100644 --- a/infra/config-sync/src/main/java/com/simprints/infra/config/sync/usecase/RescheduleWorkersIfConfigChangedUseCase.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCase.kt @@ -1,4 +1,4 @@ -package com.simprints.infra.config.sync.usecase +package com.simprints.infra.sync.config.usecase import com.simprints.infra.config.store.models.ProjectConfiguration import com.simprints.infra.config.store.models.imagesUploadRequiresUnmeteredConnection diff --git a/infra/config-sync/src/main/java/com/simprints/infra/config/sync/worker/DeviceConfigDownSyncWorker.kt b/infra/sync/src/main/java/com/simprints/infra/sync/config/worker/DeviceConfigDownSyncWorker.kt similarity index 94% rename from infra/config-sync/src/main/java/com/simprints/infra/config/sync/worker/DeviceConfigDownSyncWorker.kt rename to infra/sync/src/main/java/com/simprints/infra/sync/config/worker/DeviceConfigDownSyncWorker.kt index 9de4b398ce..ec23e29fb9 100644 --- a/infra/config-sync/src/main/java/com/simprints/infra/config/sync/worker/DeviceConfigDownSyncWorker.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/config/worker/DeviceConfigDownSyncWorker.kt @@ -1,11 +1,11 @@ -package com.simprints.infra.config.sync.worker +package com.simprints.infra.sync.config.worker import android.content.Context import androidx.work.WorkerParameters import com.simprints.core.DispatcherBG import com.simprints.core.workers.SimCoroutineWorker import com.simprints.infra.config.store.ConfigRepository -import com.simprints.infra.config.sync.usecase.LogoutUseCase +import com.simprints.infra.sync.config.usecase.LogoutUseCase import com.simprints.infra.enrolment.records.sync.worker.EnrolmentRecordScheduler import dagger.assisted.Assisted import dagger.assisted.AssistedInject diff --git a/infra/config-sync/src/main/java/com/simprints/infra/config/sync/worker/ProjectConfigDownSyncWorker.kt b/infra/sync/src/main/java/com/simprints/infra/sync/config/worker/ProjectConfigDownSyncWorker.kt similarity index 91% rename from infra/config-sync/src/main/java/com/simprints/infra/config/sync/worker/ProjectConfigDownSyncWorker.kt rename to infra/sync/src/main/java/com/simprints/infra/sync/config/worker/ProjectConfigDownSyncWorker.kt index e1b57cf90c..2067da8957 100644 --- a/infra/config-sync/src/main/java/com/simprints/infra/config/sync/worker/ProjectConfigDownSyncWorker.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/config/worker/ProjectConfigDownSyncWorker.kt @@ -1,4 +1,4 @@ -package com.simprints.infra.config.sync.worker +package com.simprints.infra.sync.config.worker import android.content.Context import androidx.hilt.work.HiltWorker @@ -7,8 +7,8 @@ import com.simprints.core.DispatcherBG import com.simprints.core.workers.SimCoroutineWorker import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.ConfigRepository -import com.simprints.infra.config.sync.usecase.HandleProjectStateUseCase -import com.simprints.infra.config.sync.usecase.RescheduleWorkersIfConfigChangedUseCase +import com.simprints.infra.sync.config.usecase.HandleProjectStateUseCase +import com.simprints.infra.sync.config.usecase.RescheduleWorkersIfConfigChangedUseCase import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineDispatcher diff --git a/infra/config-sync/src/test/java/com/simprints/infra/config/sync/ProjectConfigurationSchedulerImplTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/config/ProjectConfigurationSchedulerImplTest.kt similarity index 95% rename from infra/config-sync/src/test/java/com/simprints/infra/config/sync/ProjectConfigurationSchedulerImplTest.kt rename to infra/sync/src/test/java/com/simprints/infra/sync/config/ProjectConfigurationSchedulerImplTest.kt index 10f1125dfc..5e7962ed88 100644 --- a/infra/config-sync/src/test/java/com/simprints/infra/config/sync/ProjectConfigurationSchedulerImplTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/config/ProjectConfigurationSchedulerImplTest.kt @@ -1,10 +1,11 @@ -package com.simprints.infra.config.sync +package com.simprints.infra.sync.config import android.content.Context import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingWorkPolicy import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager +import com.simprints.infra.sync.config.ProjectConfigurationSchedulerImpl import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic diff --git a/infra/config-sync/src/test/java/com/simprints/infra/config/sync/testtools/Models.kt b/infra/sync/src/test/java/com/simprints/infra/sync/config/testtools/Models.kt similarity index 98% rename from infra/config-sync/src/test/java/com/simprints/infra/config/sync/testtools/Models.kt rename to infra/sync/src/test/java/com/simprints/infra/sync/config/testtools/Models.kt index 82dd16e68b..3f19e7c079 100644 --- a/infra/config-sync/src/test/java/com/simprints/infra/config/sync/testtools/Models.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/config/testtools/Models.kt @@ -1,4 +1,4 @@ -package com.simprints.infra.config.sync.testtools +package com.simprints.infra.sync.config.testtools import com.simprints.core.domain.tokenization.asTokenizableEncrypted import com.simprints.infra.config.store.models.* diff --git a/infra/config-sync/src/test/java/com/simprints/infra/config/sync/usecase/HandleProjectStateUseCaseTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/HandleProjectStateUseCaseTest.kt similarity index 91% rename from infra/config-sync/src/test/java/com/simprints/infra/config/sync/usecase/HandleProjectStateUseCaseTest.kt rename to infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/HandleProjectStateUseCaseTest.kt index bf75731aa0..32be2a6c93 100644 --- a/infra/config-sync/src/test/java/com/simprints/infra/config/sync/usecase/HandleProjectStateUseCaseTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/HandleProjectStateUseCaseTest.kt @@ -1,7 +1,9 @@ -package com.simprints.infra.config.sync.usecase +package com.simprints.infra.sync.config.usecase import com.simprints.infra.config.store.models.ProjectState import com.simprints.infra.eventsync.EventSyncManager +import com.simprints.infra.sync.config.usecase.HandleProjectStateUseCase +import com.simprints.infra.sync.config.usecase.LogoutUseCase import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.coVerify diff --git a/infra/config-sync/src/test/java/com/simprints/infra/config/sync/usecase/LogoutUseCaseTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/LogoutUseCaseTest.kt similarity index 89% rename from infra/config-sync/src/test/java/com/simprints/infra/config/sync/usecase/LogoutUseCaseTest.kt rename to infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/LogoutUseCaseTest.kt index 86f854b66d..b8f9597f63 100644 --- a/infra/config-sync/src/test/java/com/simprints/infra/config/sync/usecase/LogoutUseCaseTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/LogoutUseCaseTest.kt @@ -1,9 +1,10 @@ -package com.simprints.infra.config.sync.usecase +package com.simprints.infra.sync.config.usecase import com.simprints.infra.authlogic.AuthManager -import com.simprints.infra.config.sync.ProjectConfigurationScheduler +import com.simprints.infra.sync.config.ProjectConfigurationScheduler import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.images.ImageUpSyncScheduler +import com.simprints.infra.sync.config.usecase.LogoutUseCase import io.mockk.MockKAnnotations import io.mockk.coVerify import io.mockk.impl.annotations.MockK diff --git a/infra/config-sync/src/test/java/com/simprints/infra/config/sync/usecase/RescheduleWorkersIfConfigChangedUseCaseTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCaseTest.kt similarity index 89% rename from infra/config-sync/src/test/java/com/simprints/infra/config/sync/usecase/RescheduleWorkersIfConfigChangedUseCaseTest.kt rename to infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCaseTest.kt index 86aeb50fc6..b91da79f3a 100644 --- a/infra/config-sync/src/test/java/com/simprints/infra/config/sync/usecase/RescheduleWorkersIfConfigChangedUseCaseTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCaseTest.kt @@ -1,8 +1,9 @@ -package com.simprints.infra.config.sync.usecase +package com.simprints.infra.sync.config.usecase -import com.simprints.infra.config.sync.testtools.projectConfiguration -import com.simprints.infra.config.sync.testtools.synchronizationConfiguration +import com.simprints.infra.sync.config.testtools.projectConfiguration +import com.simprints.infra.sync.config.testtools.synchronizationConfiguration import com.simprints.infra.images.ImageUpSyncScheduler +import com.simprints.infra.sync.config.usecase.RescheduleWorkersIfConfigChangedUseCase import io.mockk.MockKAnnotations import io.mockk.coVerify import io.mockk.impl.annotations.MockK diff --git a/infra/config-sync/src/test/java/com/simprints/infra/config/sync/worker/DeviceConfigDownSyncWorkerTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/config/worker/DeviceConfigDownSyncWorkerTest.kt similarity index 94% rename from infra/config-sync/src/test/java/com/simprints/infra/config/sync/worker/DeviceConfigDownSyncWorkerTest.kt rename to infra/sync/src/test/java/com/simprints/infra/sync/config/worker/DeviceConfigDownSyncWorkerTest.kt index ad45649701..547401978c 100644 --- a/infra/config-sync/src/test/java/com/simprints/infra/config/sync/worker/DeviceConfigDownSyncWorkerTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/config/worker/DeviceConfigDownSyncWorkerTest.kt @@ -1,12 +1,13 @@ -package com.simprints.infra.config.sync.worker +package com.simprints.infra.sync.config.worker import androidx.work.ListenableWorker import com.google.common.truth.Truth.assertThat import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.models.DeviceState import com.simprints.infra.config.store.models.UpSyncEnrolmentRecords -import com.simprints.infra.config.sync.usecase.LogoutUseCase +import com.simprints.infra.sync.config.usecase.LogoutUseCase import com.simprints.infra.enrolment.records.sync.worker.EnrolmentRecordScheduler +import com.simprints.infra.sync.config.worker.DeviceConfigDownSyncWorker import com.simprints.testtools.common.coroutines.TestCoroutineRule import io.mockk.MockKAnnotations import io.mockk.coEvery diff --git a/infra/config-sync/src/test/java/com/simprints/infra/config/sync/worker/ProjectConfigDownSyncWorkerTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/config/worker/ProjectConfigDownSyncWorkerTest.kt similarity index 91% rename from infra/config-sync/src/test/java/com/simprints/infra/config/sync/worker/ProjectConfigDownSyncWorkerTest.kt rename to infra/sync/src/test/java/com/simprints/infra/sync/config/worker/ProjectConfigDownSyncWorkerTest.kt index 67e653461b..74e1b2381e 100644 --- a/infra/config-sync/src/test/java/com/simprints/infra/config/sync/worker/ProjectConfigDownSyncWorkerTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/config/worker/ProjectConfigDownSyncWorkerTest.kt @@ -1,14 +1,14 @@ -package com.simprints.infra.config.sync.worker +package com.simprints.infra.sync.config.worker import androidx.work.ListenableWorker import com.google.common.truth.Truth.assertThat import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.models.ProjectWithConfig -import com.simprints.infra.config.sync.testtools.project -import com.simprints.infra.config.sync.testtools.projectConfiguration -import com.simprints.infra.config.sync.usecase.HandleProjectStateUseCase -import com.simprints.infra.config.sync.usecase.RescheduleWorkersIfConfigChangedUseCase +import com.simprints.infra.sync.config.testtools.project +import com.simprints.infra.sync.config.testtools.projectConfiguration +import com.simprints.infra.sync.config.usecase.HandleProjectStateUseCase +import com.simprints.infra.sync.config.usecase.RescheduleWorkersIfConfigChangedUseCase import com.simprints.testtools.common.coroutines.TestCoroutineRule import io.mockk.MockKAnnotations import io.mockk.coEvery diff --git a/settings.gradle.kts b/settings.gradle.kts index 20d022da3c..68c2ced543 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -117,10 +117,7 @@ include( ":infra:core", ":infra:test-tools", ":infra:events", - ":infra:event-sync", ":infra:config-store", - ":infra:config-sync", - ":infra:enrolment-records-sync", ":infra:enrolment-records-store", ":infra:images", ":infra:license", @@ -134,4 +131,7 @@ include( ":infra:security", ":infra:orchestrator-data", ":infra:ui-base", + ":infra:sync", + ":infra:event-sync", + ":infra:enrolment-records-sync", ) From 116399ac2095d1ac63e1cb266701f4123ee8bede Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Tue, 20 Feb 2024 11:21:43 +0200 Subject: [PATCH 021/197] MS-205 Route config sync scheduling thru sync orchestrator --- .../feature/dashboard/debug/DebugFragment.kt | 6 +- .../dashboard/logout/usecase/LogoutUseCase.kt | 8 +- .../usecases/CancelBackgroundSyncUseCase.kt | 9 +- .../StartBackgroundSyncIfNeededUseCase.kt | 7 +- .../CancelBackgroundSyncUseCaseTest.kt | 15 ++-- .../StartBackgroundSyncUseCaseTest.kt | 9 +- .../id/CleanupDeprecatedWorkersUseCase.kt | 2 + .../id/ScheduleBackgroundSyncUseCase.kt | 7 +- .../id/ScheduleBackgroundSyncUseCaseTest.kt | 12 ++- .../com/simprints/infra/sync/SyncConstants.kt | 16 ++++ .../com/simprints/infra/sync/SyncModule.kt | 21 ++++- .../simprints/infra/sync/SyncOrchestrator.kt | 10 +++ .../infra/sync/SyncOrchestratorImpl.kt | 37 +++++++++ .../config/ProjectConfigurationScheduler.kt | 10 --- .../ProjectConfigurationSchedulerImpl.kt | 82 ------------------ .../sync/config/usecase/LogoutUseCase.kt | 7 +- .../infra/sync/extensions/WorkManager.ext.kt | 37 +++++++++ .../infra/sync/SyncOrchestratorImplTest.kt | 80 ++++++++++++++++++ .../ProjectConfigurationSchedulerImplTest.kt | 83 ------------------- .../sync/config/usecase/LogoutUseCaseTest.kt | 10 +-- 20 files changed, 240 insertions(+), 228 deletions(-) create mode 100644 infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt create mode 100644 infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt create mode 100644 infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt delete mode 100644 infra/sync/src/main/java/com/simprints/infra/sync/config/ProjectConfigurationScheduler.kt delete mode 100644 infra/sync/src/main/java/com/simprints/infra/sync/config/ProjectConfigurationSchedulerImpl.kt create mode 100644 infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt create mode 100644 infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt delete mode 100644 infra/sync/src/test/java/com/simprints/infra/sync/config/ProjectConfigurationSchedulerImplTest.kt diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/debug/DebugFragment.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/debug/DebugFragment.kt index 92edbcaf28..0557e8a08e 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/debug/DebugFragment.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/debug/DebugFragment.kt @@ -15,11 +15,11 @@ import com.simprints.feature.dashboard.R import com.simprints.feature.dashboard.databinding.FragmentDebugBinding import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.ConfigRepository -import com.simprints.infra.sync.config.ProjectConfigurationScheduler import com.simprints.infra.enrolment.records.store.EnrolmentRecordRepository import com.simprints.infra.events.EventRepository import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.eventsync.status.models.EventSyncWorkerState +import com.simprints.infra.sync.SyncOrchestrator import com.simprints.infra.uibase.viewbinding.viewBinding import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineDispatcher @@ -38,7 +38,7 @@ internal class DebugFragment : Fragment(R.layout.fragment_debug) { lateinit var configRepository: ConfigRepository @Inject - lateinit var configScheduler: ProjectConfigurationScheduler + lateinit var syncOrchestrator: SyncOrchestrator @Inject lateinit var authStore: AuthStore @@ -105,7 +105,7 @@ internal class DebugFragment : Fragment(R.layout.fragment_debug) { } binding.syncDevice.setOnClickListener { - configScheduler.startDeviceSync() + syncOrchestrator.startDeviceSync() } binding.printRoomDb.setOnClickListener { diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCase.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCase.kt index 1916d4f219..28f57f2627 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCase.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCase.kt @@ -1,13 +1,13 @@ package com.simprints.feature.dashboard.logout.usecase import com.simprints.infra.authlogic.AuthManager -import com.simprints.infra.sync.config.ProjectConfigurationScheduler import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.images.ImageUpSyncScheduler +import com.simprints.infra.sync.SyncOrchestrator import javax.inject.Inject internal class LogoutUseCase @Inject constructor( - private val configScheduler: ProjectConfigurationScheduler, + private val syncOrchestrator: SyncOrchestrator, private val imageUpSyncScheduler: ImageUpSyncScheduler, private val eventSyncManager: EventSyncManager, private val authManager: AuthManager, @@ -17,9 +17,7 @@ internal class LogoutUseCase @Inject constructor( // Cancel all background sync eventSyncManager.cancelScheduledSync() imageUpSyncScheduler.cancelImageUpSync() - configScheduler.cancelProjectSync() - configScheduler.cancelDeviceSync() - + syncOrchestrator.cancelBackgroundWork() eventSyncManager.deleteSyncInfo() authManager.signOut() } diff --git a/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCase.kt b/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCase.kt index 73b2a63641..5ba58386ae 100644 --- a/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCase.kt +++ b/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCase.kt @@ -1,20 +1,19 @@ package com.simprints.feature.logincheck.usecases -import com.simprints.infra.sync.config.ProjectConfigurationScheduler import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.images.ImageUpSyncScheduler +import com.simprints.infra.sync.SyncOrchestrator import javax.inject.Inject internal class CancelBackgroundSyncUseCase @Inject constructor( private val eventSyncManager: EventSyncManager, private val imageUpSyncScheduler: ImageUpSyncScheduler, - private val configScheduler: ProjectConfigurationScheduler, + private val syncOrchestrator: SyncOrchestrator, ) { - operator fun invoke() { + suspend operator fun invoke() { eventSyncManager.cancelScheduledSync() imageUpSyncScheduler.cancelImageUpSync() - configScheduler.cancelProjectSync() - configScheduler.cancelDeviceSync() + syncOrchestrator.cancelBackgroundWork() } } diff --git a/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncIfNeededUseCase.kt b/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncIfNeededUseCase.kt index f8331ce702..de65bc9f07 100644 --- a/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncIfNeededUseCase.kt +++ b/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncIfNeededUseCase.kt @@ -2,23 +2,22 @@ package com.simprints.feature.logincheck.usecases import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.models.SynchronizationConfiguration -import com.simprints.infra.sync.config.ProjectConfigurationScheduler import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.images.ImageUpSyncScheduler +import com.simprints.infra.sync.SyncOrchestrator import javax.inject.Inject internal class StartBackgroundSyncUseCase @Inject constructor( + private val syncOrchestrator: SyncOrchestrator, private val eventSyncManager: EventSyncManager, private val imageUpSyncScheduler: ImageUpSyncScheduler, - private val configScheduler: ProjectConfigurationScheduler, private val configRepository: ConfigRepository, ) { suspend operator fun invoke() { eventSyncManager.scheduleSync() imageUpSyncScheduler.scheduleImageUpSync() - configScheduler.scheduleProjectSync() - configScheduler.scheduleDeviceSync() + syncOrchestrator.scheduleBackgroundWork() val frequency = configRepository.getProjectConfiguration().synchronization.frequency if (frequency == SynchronizationConfiguration.Frequency.PERIODICALLY_AND_ON_SESSION_START) { diff --git a/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCaseTest.kt b/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCaseTest.kt index 0a14232bc1..743f14a6f7 100644 --- a/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCaseTest.kt +++ b/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCaseTest.kt @@ -1,11 +1,13 @@ package com.simprints.feature.logincheck.usecases -import com.simprints.infra.sync.config.ProjectConfigurationScheduler import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.images.ImageUpSyncScheduler +import com.simprints.infra.sync.SyncOrchestrator import io.mockk.MockKAnnotations +import io.mockk.coVerify import io.mockk.impl.annotations.MockK import io.mockk.verify +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -18,7 +20,7 @@ class CancelBackgroundSyncUseCaseTest { lateinit var imageUpSyncScheduler: ImageUpSyncScheduler @MockK - lateinit var configScheduler: ProjectConfigurationScheduler + lateinit var syncOrchestrator: SyncOrchestrator private lateinit var useCase: CancelBackgroundSyncUseCase @@ -29,19 +31,20 @@ class CancelBackgroundSyncUseCaseTest { useCase = CancelBackgroundSyncUseCase( eventSyncManager, imageUpSyncScheduler, - configScheduler + syncOrchestrator ) } @Test - fun `Cancels all syncs when called`() { + fun `Cancels all syncs when called`() = runTest { useCase.invoke() verify { eventSyncManager.cancelScheduledSync() imageUpSyncScheduler.cancelImageUpSync() - configScheduler.cancelProjectSync() - configScheduler.cancelDeviceSync() + } + coVerify { + syncOrchestrator.cancelBackgroundWork() } } } diff --git a/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCaseTest.kt b/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCaseTest.kt index e4873f7a77..bbedfd0654 100644 --- a/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCaseTest.kt +++ b/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCaseTest.kt @@ -2,9 +2,9 @@ package com.simprints.feature.logincheck.usecases import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.models.SynchronizationConfiguration -import com.simprints.infra.sync.config.ProjectConfigurationScheduler import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.images.ImageUpSyncScheduler +import com.simprints.infra.sync.SyncOrchestrator import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.coVerify @@ -23,7 +23,7 @@ class StartBackgroundSyncUseCaseTest { lateinit var imageUpSyncScheduler: ImageUpSyncScheduler @MockK - lateinit var configScheduler: ProjectConfigurationScheduler + lateinit var syncOrchestrator: SyncOrchestrator @MockK lateinit var configRepository: ConfigRepository @@ -35,9 +35,9 @@ class StartBackgroundSyncUseCaseTest { MockKAnnotations.init(this, relaxed = true) useCase = StartBackgroundSyncUseCase( + syncOrchestrator, eventSyncManager, imageUpSyncScheduler, - configScheduler, configRepository, ) } @@ -50,10 +50,9 @@ class StartBackgroundSyncUseCaseTest { verify { eventSyncManager.scheduleSync() - configScheduler.scheduleProjectSync() - configScheduler.scheduleDeviceSync() } coVerify { + syncOrchestrator.scheduleBackgroundWork() imageUpSyncScheduler.scheduleImageUpSync() } } diff --git a/id/src/main/java/com/simprints/id/CleanupDeprecatedWorkersUseCase.kt b/id/src/main/java/com/simprints/id/CleanupDeprecatedWorkersUseCase.kt index ba98856431..bcae283da7 100644 --- a/id/src/main/java/com/simprints/id/CleanupDeprecatedWorkersUseCase.kt +++ b/id/src/main/java/com/simprints/id/CleanupDeprecatedWorkersUseCase.kt @@ -31,6 +31,8 @@ class CleanupDeprecatedWorkersUseCase @Inject constructor( "project-configuration-work", // 2024.1.1 "security-status-check-work-v2", // 2024.1.1 "security-status-check-work-one-time-v2", // 2024.1.1 + "project-sync-work", // 2024.1.1 + "device-sync-work", // 2024.1.1 ) private fun tagsForDeprecatedWorkers() = listOf( diff --git a/id/src/main/java/com/simprints/id/ScheduleBackgroundSyncUseCase.kt b/id/src/main/java/com/simprints/id/ScheduleBackgroundSyncUseCase.kt index a3dbfe2b24..1c4cbd7349 100644 --- a/id/src/main/java/com/simprints/id/ScheduleBackgroundSyncUseCase.kt +++ b/id/src/main/java/com/simprints/id/ScheduleBackgroundSyncUseCase.kt @@ -2,15 +2,15 @@ package com.simprints.id import com.simprints.fingerprint.infra.scanner.data.worker.FirmwareFileUpdateScheduler import com.simprints.infra.authstore.AuthStore -import com.simprints.infra.sync.config.ProjectConfigurationScheduler import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.images.ImageUpSyncScheduler +import com.simprints.infra.sync.SyncOrchestrator import javax.inject.Inject class ScheduleBackgroundSyncUseCase @Inject constructor( private val eventSyncManager: EventSyncManager, private val imageUpSyncScheduler: ImageUpSyncScheduler, - private val configScheduler: ProjectConfigurationScheduler, + private val syncOrchestrator: SyncOrchestrator, private val authStore: AuthStore, private val firmwareFileUpdateScheduler: FirmwareFileUpdateScheduler, ) { @@ -19,8 +19,7 @@ class ScheduleBackgroundSyncUseCase @Inject constructor( if (authStore.signedInProjectId.isNotEmpty()) { eventSyncManager.scheduleSync() imageUpSyncScheduler.scheduleImageUpSync() - configScheduler.scheduleProjectSync() - configScheduler.scheduleDeviceSync() + syncOrchestrator.scheduleBackgroundWork() firmwareFileUpdateScheduler.scheduleOrCancelWorkIfNecessary() } } diff --git a/id/src/test/java/com/simprints/id/ScheduleBackgroundSyncUseCaseTest.kt b/id/src/test/java/com/simprints/id/ScheduleBackgroundSyncUseCaseTest.kt index 4018465a37..c3a92f5a28 100644 --- a/id/src/test/java/com/simprints/id/ScheduleBackgroundSyncUseCaseTest.kt +++ b/id/src/test/java/com/simprints/id/ScheduleBackgroundSyncUseCaseTest.kt @@ -2,9 +2,9 @@ package com.simprints.id import com.simprints.fingerprint.infra.scanner.data.worker.FirmwareFileUpdateScheduler import com.simprints.infra.authstore.AuthStore -import com.simprints.infra.sync.config.ProjectConfigurationScheduler import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.images.ImageUpSyncScheduler +import com.simprints.infra.sync.SyncOrchestrator import io.mockk.MockKAnnotations import io.mockk.coVerify import io.mockk.every @@ -23,7 +23,7 @@ class ScheduleBackgroundSyncUseCaseTest { lateinit var imageUpSyncScheduler: ImageUpSyncScheduler @MockK - lateinit var configScheduler: ProjectConfigurationScheduler + lateinit var syncOrchestrator: SyncOrchestrator @MockK lateinit var authStore: AuthStore @@ -40,7 +40,7 @@ class ScheduleBackgroundSyncUseCaseTest { useCase = ScheduleBackgroundSyncUseCase( eventSyncManager, imageUpSyncScheduler, - configScheduler, + syncOrchestrator, authStore, firmwareFileUpdateScheduler, ) @@ -54,11 +54,10 @@ class ScheduleBackgroundSyncUseCaseTest { verify { eventSyncManager.scheduleSync() - configScheduler.scheduleProjectSync() - configScheduler.scheduleDeviceSync() firmwareFileUpdateScheduler.scheduleOrCancelWorkIfNecessary() } coVerify { + syncOrchestrator.scheduleBackgroundWork() imageUpSyncScheduler.scheduleImageUpSync() } } @@ -71,11 +70,10 @@ class ScheduleBackgroundSyncUseCaseTest { verify(exactly = 0) { eventSyncManager.scheduleSync() - configScheduler.scheduleProjectSync() - configScheduler.scheduleDeviceSync() firmwareFileUpdateScheduler.scheduleOrCancelWorkIfNecessary() } coVerify(exactly = 0) { + syncOrchestrator.scheduleBackgroundWork() imageUpSyncScheduler.scheduleImageUpSync() } } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt new file mode 100644 index 0000000000..a83e1fe228 --- /dev/null +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt @@ -0,0 +1,16 @@ +package com.simprints.infra.sync + +import java.util.concurrent.TimeUnit + +internal object SyncConstants { + + val SYNC_REPEAT_UNIT = TimeUnit.MINUTES + + const val PROJECT_SYNC_WORK_NAME = "project-sync-work-v2" + const val PROJECT_SYNC_REPEAT_INTERVAL = BuildConfig.SYNC_PERIODIC_WORKER_INTERVAL_MINUTES + + const val DEVICE_SYNC_WORK_NAME = "device-sync-work-v2" + const val DEVICE_SYNC_WORK_NAME_ONE_TIME = "device-sync-work-one-time" + const val DEVICE_SYNC_REPEAT_INTERVAL = BuildConfig.DEVICE_PERIODIC_WORKER_INTERVAL_MINUTES + +} diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncModule.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncModule.kt index c19916be2f..eee123ff07 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncModule.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncModule.kt @@ -1,17 +1,30 @@ package com.simprints.infra.sync -import com.simprints.infra.sync.config.ProjectConfigurationScheduler -import com.simprints.infra.sync.config.ProjectConfigurationSchedulerImpl +import android.content.Context +import androidx.work.WorkManager import dagger.Binds import dagger.Module +import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) -abstract class SyncModule { +object SyncModule { + + @Provides + fun provideWorkManager( + @ApplicationContext context: Context, + ): WorkManager = WorkManager.getInstance(context) + +} + +@Module +@InstallIn(SingletonComponent::class) +abstract class SyncOrchestratorModule { @Binds - internal abstract fun provideConfigurationScheduler(configurationScheduler: ProjectConfigurationSchedulerImpl): ProjectConfigurationScheduler + internal abstract fun provideSyncOrchestrator(syncOrchestratorImpl: SyncOrchestratorImpl): SyncOrchestrator } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt new file mode 100644 index 0000000000..f54c614f7c --- /dev/null +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt @@ -0,0 +1,10 @@ +package com.simprints.infra.sync + +interface SyncOrchestrator { + + suspend fun scheduleBackgroundWork() + suspend fun cancelBackgroundWork() + + fun startDeviceSync() + +} diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt new file mode 100644 index 0000000000..c7a48838ab --- /dev/null +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt @@ -0,0 +1,37 @@ +package com.simprints.infra.sync + +import androidx.work.WorkManager +import com.simprints.infra.authstore.AuthStore +import com.simprints.infra.sync.extensions.schedulePeriodicWorker +import com.simprints.infra.sync.extensions.startWorker +import com.simprints.infra.sync.config.worker.DeviceConfigDownSyncWorker +import com.simprints.infra.sync.config.worker.ProjectConfigDownSyncWorker +import javax.inject.Inject + +internal class SyncOrchestratorImpl @Inject constructor( + private val workManager: WorkManager, + private val authStore: AuthStore, +) : SyncOrchestrator { + + override suspend fun scheduleBackgroundWork() { + if (authStore.signedInProjectId.isNotEmpty()) { + workManager.schedulePeriodicWorker( + SyncConstants.PROJECT_SYNC_WORK_NAME, + SyncConstants.PROJECT_SYNC_REPEAT_INTERVAL + ) + workManager.schedulePeriodicWorker( + SyncConstants.DEVICE_SYNC_WORK_NAME, + SyncConstants.DEVICE_SYNC_REPEAT_INTERVAL + ) + } + } + + override suspend fun cancelBackgroundWork() { + workManager.cancelUniqueWork(SyncConstants.PROJECT_SYNC_WORK_NAME) + workManager.cancelUniqueWork(SyncConstants.DEVICE_SYNC_WORK_NAME) + } + + override fun startDeviceSync() { + workManager.startWorker(SyncConstants.DEVICE_SYNC_WORK_NAME_ONE_TIME) + } +} diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/config/ProjectConfigurationScheduler.kt b/infra/sync/src/main/java/com/simprints/infra/sync/config/ProjectConfigurationScheduler.kt deleted file mode 100644 index f1e3f15a91..0000000000 --- a/infra/sync/src/main/java/com/simprints/infra/sync/config/ProjectConfigurationScheduler.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.simprints.infra.sync.config - -interface ProjectConfigurationScheduler { - fun scheduleProjectSync() - fun cancelProjectSync() - - fun startDeviceSync() - fun scheduleDeviceSync() - fun cancelDeviceSync() -} diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/config/ProjectConfigurationSchedulerImpl.kt b/infra/sync/src/main/java/com/simprints/infra/sync/config/ProjectConfigurationSchedulerImpl.kt deleted file mode 100644 index d9ca34f66d..0000000000 --- a/infra/sync/src/main/java/com/simprints/infra/sync/config/ProjectConfigurationSchedulerImpl.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.simprints.infra.sync.config - -import android.content.Context -import androidx.work.Constraints -import androidx.work.ExistingPeriodicWorkPolicy -import androidx.work.ExistingWorkPolicy -import androidx.work.NetworkType -import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.PeriodicWorkRequestBuilder -import androidx.work.WorkManager -import com.simprints.infra.sync.BuildConfig -import com.simprints.infra.sync.config.worker.DeviceConfigDownSyncWorker -import com.simprints.infra.sync.config.worker.ProjectConfigDownSyncWorker -import dagger.hilt.android.qualifiers.ApplicationContext -import java.util.concurrent.TimeUnit -import javax.inject.Inject - -internal class ProjectConfigurationSchedulerImpl @Inject constructor(@ApplicationContext context: Context) : - ProjectConfigurationScheduler { - - companion object { - - internal const val PROJECT_SYNC_WORK_NAME = "project-sync-work" - private const val PROJECT_SYNC_REPEAT_INTERVAL = - BuildConfig.SYNC_PERIODIC_WORKER_INTERVAL_MINUTES - - internal const val DEVICE_SYNC_WORK_NAME = "device-sync-work" - internal const val DEVICE_SYNC_WORK_NAME_ONE_TIME = "device-sync-work-one-time" - private const val DEVICE_SYNC_REPEAT_INTERVAL = - BuildConfig.DEVICE_PERIODIC_WORKER_INTERVAL_MINUTES - - private val SYNC_REPEAT_UNIT = TimeUnit.MINUTES - } - - private val workManager = WorkManager.getInstance(context) - - override fun scheduleProjectSync() { - workManager.enqueueUniquePeriodicWork( - PROJECT_SYNC_WORK_NAME, - ExistingPeriodicWorkPolicy.UPDATE, - PeriodicWorkRequestBuilder( - PROJECT_SYNC_REPEAT_INTERVAL, - SYNC_REPEAT_UNIT - ).setConstraints(workerConstraints()).build() - ) - } - - override fun cancelProjectSync() { - workManager.cancelUniqueWork(PROJECT_SYNC_WORK_NAME) - } - - override fun startDeviceSync() { - workManager.enqueueUniqueWork( - DEVICE_SYNC_WORK_NAME_ONE_TIME, - ExistingWorkPolicy.KEEP, - OneTimeWorkRequestBuilder() - .setConstraints(workerConstraints()) - .build() - ) - } - - override fun scheduleDeviceSync() { - workManager.enqueueUniquePeriodicWork( - DEVICE_SYNC_WORK_NAME, - ExistingPeriodicWorkPolicy.UPDATE, - PeriodicWorkRequestBuilder( - DEVICE_SYNC_REPEAT_INTERVAL, - SYNC_REPEAT_UNIT - ).setConstraints(workerConstraints()).build() - ) - } - - override fun cancelDeviceSync() { - workManager.cancelUniqueWork(DEVICE_SYNC_WORK_NAME) - } - - private fun workerConstraints() = - Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .build() - -} diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/LogoutUseCase.kt b/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/LogoutUseCase.kt index 6e537aba2c..2b38fa5758 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/LogoutUseCase.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/LogoutUseCase.kt @@ -1,13 +1,13 @@ package com.simprints.infra.sync.config.usecase import com.simprints.infra.authlogic.AuthManager -import com.simprints.infra.sync.config.ProjectConfigurationScheduler import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.images.ImageUpSyncScheduler +import com.simprints.infra.sync.SyncOrchestrator import javax.inject.Inject internal class LogoutUseCase @Inject constructor( - private val configScheduler: ProjectConfigurationScheduler, + private val syncOrchestrator: SyncOrchestrator, private val imageUpSyncScheduler: ImageUpSyncScheduler, private val eventSyncManager: EventSyncManager, private val authManager: AuthManager, @@ -15,8 +15,7 @@ internal class LogoutUseCase @Inject constructor( suspend operator fun invoke() { imageUpSyncScheduler.cancelImageUpSync() - configScheduler.cancelProjectSync() - configScheduler.cancelDeviceSync() + syncOrchestrator.cancelBackgroundWork() eventSyncManager.cancelScheduledSync() eventSyncManager.deleteSyncInfo() authManager.signOut() diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt b/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt new file mode 100644 index 0000000000..72b4939128 --- /dev/null +++ b/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt @@ -0,0 +1,37 @@ +package com.simprints.infra.sync.extensions + +import androidx.work.* +import com.simprints.infra.sync.SyncConstants + + +internal fun defaultWorkerConstraints() = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + +internal inline fun WorkManager.schedulePeriodicWorker( + workName: String, + repeatInterval: Long, + constraints: Constraints = defaultWorkerConstraints(), + inputData: Data? = null, +) = enqueueUniquePeriodicWork( + workName, + ExistingPeriodicWorkPolicy.UPDATE, + PeriodicWorkRequestBuilder(repeatInterval, SyncConstants.SYNC_REPEAT_UNIT) + .setConstraints(constraints) + .let { if (inputData != null) it.setInputData(inputData) else it } + .build() +) + +internal inline fun WorkManager.startWorker( + workName: String, + constraints: Constraints = defaultWorkerConstraints(), + inputData: Data? = null, +) = this.enqueueUniqueWork( + workName, + ExistingWorkPolicy.KEEP, + OneTimeWorkRequestBuilder() + .setConstraints(constraints) + .let { if (inputData != null) it.setInputData(inputData) else it } + .build() +) + diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt new file mode 100644 index 0000000000..633870f2fc --- /dev/null +++ b/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt @@ -0,0 +1,80 @@ +package com.simprints.infra.sync + +import androidx.work.OneTimeWorkRequest +import androidx.work.WorkManager +import com.simprints.infra.authstore.AuthStore +import com.simprints.infra.sync.SyncConstants.DEVICE_SYNC_WORK_NAME +import com.simprints.infra.sync.SyncConstants.DEVICE_SYNC_WORK_NAME_ONE_TIME +import com.simprints.infra.sync.SyncConstants.PROJECT_SYNC_WORK_NAME +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.verify +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test + +class SyncOrchestratorImplTest { + + + @MockK + private lateinit var workManager: WorkManager + + @MockK + private lateinit var authStore: AuthStore + + private lateinit var syncOrchestrator: SyncOrchestratorImpl + + @Before + fun setup() { + MockKAnnotations.init(this, relaxed = true) + + syncOrchestrator = SyncOrchestratorImpl( + workManager, + authStore, + ) + } + + @Test + fun `does not schedules any workers if not logged in`() = runTest { + every { authStore.signedInProjectId } returns "" + + syncOrchestrator.scheduleBackgroundWork() + + verify(exactly = 0) { + workManager.enqueueUniquePeriodicWork(any(), any(), any()) + } + } + + @Test + fun `schedules all necessary background workers if logged in`() = runTest { + every { authStore.signedInProjectId } returns "projectId" + + syncOrchestrator.scheduleBackgroundWork() + + verify { + workManager.enqueueUniquePeriodicWork(PROJECT_SYNC_WORK_NAME, any(), any()) + workManager.enqueueUniquePeriodicWork(DEVICE_SYNC_WORK_NAME, any(), any()) + } + } + + @Test + fun `cancels all necessary background workers`() = runTest { + syncOrchestrator.cancelBackgroundWork() + + verify { + workManager.cancelUniqueWork(PROJECT_SYNC_WORK_NAME) + workManager.cancelUniqueWork(DEVICE_SYNC_WORK_NAME) + } + } + + @Test + fun `schedules device worker when requested`() = runTest { + syncOrchestrator.startDeviceSync() + + verify { + workManager.enqueueUniqueWork(DEVICE_SYNC_WORK_NAME_ONE_TIME, any(), any()) + } + } + +} diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/config/ProjectConfigurationSchedulerImplTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/config/ProjectConfigurationSchedulerImplTest.kt deleted file mode 100644 index 5e7962ed88..0000000000 --- a/infra/sync/src/test/java/com/simprints/infra/sync/config/ProjectConfigurationSchedulerImplTest.kt +++ /dev/null @@ -1,83 +0,0 @@ -package com.simprints.infra.sync.config - -import android.content.Context -import androidx.work.ExistingPeriodicWorkPolicy -import androidx.work.ExistingWorkPolicy -import androidx.work.OneTimeWorkRequest -import androidx.work.WorkManager -import com.simprints.infra.sync.config.ProjectConfigurationSchedulerImpl -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkStatic -import io.mockk.verify -import org.junit.Before -import org.junit.Test - -class ProjectConfigurationSchedulerImplTest { - - private val ctx = mockk() - private val workManager = mockk(relaxed = true) - private lateinit var configurationSchedulerImpl: ProjectConfigurationSchedulerImpl - - @Before - fun setup() { - mockkStatic(WorkManager::class) - every { WorkManager.getInstance(ctx) } returns workManager - - configurationSchedulerImpl = - ProjectConfigurationSchedulerImpl(ctx) - } - - @Test - fun `scheduleProjectSync should schedule the worker`() { - configurationSchedulerImpl.scheduleProjectSync() - - verify { - workManager.enqueueUniquePeriodicWork( - ProjectConfigurationSchedulerImpl.PROJECT_SYNC_WORK_NAME, - ExistingPeriodicWorkPolicy.UPDATE, - any(), - ) - } - } - - @Test - fun `cancelProjectSync should cancel the worker`() { - configurationSchedulerImpl.cancelProjectSync() - - verify { workManager.cancelUniqueWork(ProjectConfigurationSchedulerImpl.PROJECT_SYNC_WORK_NAME) } - } - - @Test - fun `startDeviceSync should schedule the worker`() { - configurationSchedulerImpl.startDeviceSync() - - verify { - workManager.enqueueUniqueWork( - ProjectConfigurationSchedulerImpl.DEVICE_SYNC_WORK_NAME_ONE_TIME, - ExistingWorkPolicy.KEEP, - any(), - ) - } - } - - @Test - fun `scheduleDeviceSync should schedule the worker`() { - configurationSchedulerImpl.scheduleDeviceSync() - - verify { - workManager.enqueueUniquePeriodicWork( - ProjectConfigurationSchedulerImpl.DEVICE_SYNC_WORK_NAME, - ExistingPeriodicWorkPolicy.UPDATE, - any(), - ) - } - } - - @Test - fun `cancelDeviceSync should cancel the worker`() { - configurationSchedulerImpl.cancelDeviceSync() - - verify { workManager.cancelUniqueWork(ProjectConfigurationSchedulerImpl.DEVICE_SYNC_WORK_NAME) } - } -} diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/LogoutUseCaseTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/LogoutUseCaseTest.kt index b8f9597f63..3543f5ccd0 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/LogoutUseCaseTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/LogoutUseCaseTest.kt @@ -1,10 +1,9 @@ package com.simprints.infra.sync.config.usecase import com.simprints.infra.authlogic.AuthManager -import com.simprints.infra.sync.config.ProjectConfigurationScheduler import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.images.ImageUpSyncScheduler -import com.simprints.infra.sync.config.usecase.LogoutUseCase +import com.simprints.infra.sync.SyncOrchestrator import io.mockk.MockKAnnotations import io.mockk.coVerify import io.mockk.impl.annotations.MockK @@ -16,7 +15,7 @@ import org.junit.Test class LogoutUseCaseTest { @MockK - private lateinit var configScheduler: ProjectConfigurationScheduler + private lateinit var syncOrchestrator: SyncOrchestrator @MockK private lateinit var imageUpSyncScheduler: ImageUpSyncScheduler @@ -34,7 +33,7 @@ class LogoutUseCaseTest { MockKAnnotations.init(this, relaxed = true) useCase = LogoutUseCase( - configScheduler = configScheduler, + syncOrchestrator = syncOrchestrator, imageUpSyncScheduler = imageUpSyncScheduler, eventSyncManager = eventSyncManager, authManager = authManager, @@ -47,11 +46,10 @@ class LogoutUseCaseTest { verify { imageUpSyncScheduler.cancelImageUpSync() - configScheduler.cancelProjectSync() - configScheduler.cancelDeviceSync() eventSyncManager.cancelScheduledSync() } coVerify { + syncOrchestrator.cancelBackgroundWork() authManager.signOut() eventSyncManager.deleteSyncInfo() } From 5004d58e8dd3c7d10ef4b2162097786731817622 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Tue, 20 Feb 2024 11:35:34 +0200 Subject: [PATCH 022/197] MS-205 Move code related to sync from id module to sync --- .github/workflows/pr-checks.yml | 1 - id/build.gradle.kts | 10 --- .../main/java/com/simprints/id/Application.kt | 10 +-- .../id/ScheduleBackgroundSyncUseCase.kt | 26 ------ .../id/ScheduleBackgroundSyncUseCaseTest.kt | 80 ------------------- .../simprints/infra/sync/SyncOrchestrator.kt | 1 + .../infra/sync/SyncOrchestratorImpl.kt | 10 +++ .../CleanupDeprecatedWorkersUseCase.kt | 14 ++-- .../infra/sync/SyncOrchestratorImplTest.kt | 11 +++ 9 files changed, 31 insertions(+), 132 deletions(-) delete mode 100644 id/src/main/java/com/simprints/id/ScheduleBackgroundSyncUseCase.kt delete mode 100644 id/src/test/java/com/simprints/id/ScheduleBackgroundSyncUseCaseTest.kt rename {id/src/main/java/com/simprints/id => infra/sync/src/main/java/com/simprints/infra/sync/usecase}/CleanupDeprecatedWorkersUseCase.kt (77%) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 4aaa02570f..9a3636d1b1 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -14,7 +14,6 @@ jobs: secrets: inherit with: modules: | - id infra:core infra:ui-base infra:network diff --git a/id/build.gradle.kts b/id/build.gradle.kts index cbaea8d7ec..3a5552fae6 100644 --- a/id/build.gradle.kts +++ b/id/build.gradle.kts @@ -29,21 +29,11 @@ dependencies { implementation(project(":infra:core")) implementation(project(":infra:event-sync")) implementation(project(":infra:sync")) - implementation(project(":infra:images")) - implementation(project(":infra:auth-logic")) - implementation(project(":infra:auth-store")) implementation(project(":feature:orchestrator")) implementation(project(":feature:dashboard")) - implementation(project(":fingerprint:infra:scanner")) - implementation(libs.androidX.core) implementation(libs.androidX.appcompat) implementation(libs.rxJava2.core) - - testImplementation(libs.testing.junit) - testImplementation(libs.testing.mockk.core) - testImplementation(libs.testing.coroutines) - testImplementation(project(":infra:test-tools")) } diff --git a/id/src/main/java/com/simprints/id/Application.kt b/id/src/main/java/com/simprints/id/Application.kt index c95db8b88a..8618871f38 100644 --- a/id/src/main/java/com/simprints/id/Application.kt +++ b/id/src/main/java/com/simprints/id/Application.kt @@ -11,6 +11,7 @@ import com.simprints.core.tools.utils.LanguageHelper import com.simprints.infra.logging.LoggingConstants.CrashReportingCustomKeys.DEVICE_ID import com.simprints.infra.logging.Simber import com.simprints.infra.logging.SimberBuilder +import com.simprints.infra.sync.SyncOrchestrator import dagger.hilt.android.HiltAndroidApp import io.reactivex.exceptions.UndeliverableException import io.reactivex.plugins.RxJavaPlugins @@ -26,10 +27,7 @@ open class Application : CoreApplication(), Configuration.Provider { lateinit var workerFactory: HiltWorkerFactory @Inject - lateinit var cleanupDeprecatedWorkers: CleanupDeprecatedWorkersUseCase - - @Inject - lateinit var scheduleBackgroundSync: ScheduleBackgroundSyncUseCase + lateinit var syncOrchestrator: SyncOrchestrator @AppScope @Inject @@ -57,8 +55,8 @@ open class Application : CoreApplication(), Configuration.Provider { SimberBuilder.initialize(this) Simber.tag(DEVICE_ID, true).i(deviceHardwareId) appScope.launch { - cleanupDeprecatedWorkers() - scheduleBackgroundSync() + syncOrchestrator.cleanupWorkers() + syncOrchestrator.scheduleBackgroundWork() } } diff --git a/id/src/main/java/com/simprints/id/ScheduleBackgroundSyncUseCase.kt b/id/src/main/java/com/simprints/id/ScheduleBackgroundSyncUseCase.kt deleted file mode 100644 index 1c4cbd7349..0000000000 --- a/id/src/main/java/com/simprints/id/ScheduleBackgroundSyncUseCase.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.simprints.id - -import com.simprints.fingerprint.infra.scanner.data.worker.FirmwareFileUpdateScheduler -import com.simprints.infra.authstore.AuthStore -import com.simprints.infra.eventsync.EventSyncManager -import com.simprints.infra.images.ImageUpSyncScheduler -import com.simprints.infra.sync.SyncOrchestrator -import javax.inject.Inject - -class ScheduleBackgroundSyncUseCase @Inject constructor( - private val eventSyncManager: EventSyncManager, - private val imageUpSyncScheduler: ImageUpSyncScheduler, - private val syncOrchestrator: SyncOrchestrator, - private val authStore: AuthStore, - private val firmwareFileUpdateScheduler: FirmwareFileUpdateScheduler, -) { - - suspend operator fun invoke() { - if (authStore.signedInProjectId.isNotEmpty()) { - eventSyncManager.scheduleSync() - imageUpSyncScheduler.scheduleImageUpSync() - syncOrchestrator.scheduleBackgroundWork() - firmwareFileUpdateScheduler.scheduleOrCancelWorkIfNecessary() - } - } -} diff --git a/id/src/test/java/com/simprints/id/ScheduleBackgroundSyncUseCaseTest.kt b/id/src/test/java/com/simprints/id/ScheduleBackgroundSyncUseCaseTest.kt deleted file mode 100644 index c3a92f5a28..0000000000 --- a/id/src/test/java/com/simprints/id/ScheduleBackgroundSyncUseCaseTest.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.simprints.id - -import com.simprints.fingerprint.infra.scanner.data.worker.FirmwareFileUpdateScheduler -import com.simprints.infra.authstore.AuthStore -import com.simprints.infra.eventsync.EventSyncManager -import com.simprints.infra.images.ImageUpSyncScheduler -import com.simprints.infra.sync.SyncOrchestrator -import io.mockk.MockKAnnotations -import io.mockk.coVerify -import io.mockk.every -import io.mockk.impl.annotations.MockK -import io.mockk.verify -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test - -class ScheduleBackgroundSyncUseCaseTest { - - @MockK - lateinit var eventSyncManager: EventSyncManager - - @MockK - lateinit var imageUpSyncScheduler: ImageUpSyncScheduler - - @MockK - lateinit var syncOrchestrator: SyncOrchestrator - - @MockK - lateinit var authStore: AuthStore - - @MockK - lateinit var firmwareFileUpdateScheduler: FirmwareFileUpdateScheduler - - private lateinit var useCase: ScheduleBackgroundSyncUseCase - - @Before - fun setUp() { - MockKAnnotations.init(this, relaxed = true) - - useCase = ScheduleBackgroundSyncUseCase( - eventSyncManager, - imageUpSyncScheduler, - syncOrchestrator, - authStore, - firmwareFileUpdateScheduler, - ) - } - - @Test - fun `If user is signed in - schedules all syncs when called`() = runTest { - every { authStore.signedInProjectId } returns "projectId" - - useCase.invoke() - - verify { - eventSyncManager.scheduleSync() - firmwareFileUpdateScheduler.scheduleOrCancelWorkIfNecessary() - } - coVerify { - syncOrchestrator.scheduleBackgroundWork() - imageUpSyncScheduler.scheduleImageUpSync() - } - } - - @Test - fun `If user is not signed in - does nothing`() = runTest { - every { authStore.signedInProjectId } returns "" - - useCase.invoke() - - verify(exactly = 0) { - eventSyncManager.scheduleSync() - firmwareFileUpdateScheduler.scheduleOrCancelWorkIfNecessary() - } - coVerify(exactly = 0) { - syncOrchestrator.scheduleBackgroundWork() - imageUpSyncScheduler.scheduleImageUpSync() - } - } -} diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt index f54c614f7c..a6fcd8acb6 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt @@ -7,4 +7,5 @@ interface SyncOrchestrator { fun startDeviceSync() + fun cleanupWorkers() } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt index c7a48838ab..e9743e576d 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt @@ -6,11 +6,13 @@ import com.simprints.infra.sync.extensions.schedulePeriodicWorker import com.simprints.infra.sync.extensions.startWorker import com.simprints.infra.sync.config.worker.DeviceConfigDownSyncWorker import com.simprints.infra.sync.config.worker.ProjectConfigDownSyncWorker +import com.simprints.infra.sync.usecase.CleanupDeprecatedWorkersUseCase import javax.inject.Inject internal class SyncOrchestratorImpl @Inject constructor( private val workManager: WorkManager, private val authStore: AuthStore, + private val cleanupDeprecatedWorkers: CleanupDeprecatedWorkersUseCase, ) : SyncOrchestrator { override suspend fun scheduleBackgroundWork() { @@ -23,6 +25,9 @@ internal class SyncOrchestratorImpl @Inject constructor( SyncConstants.DEVICE_SYNC_WORK_NAME, SyncConstants.DEVICE_SYNC_REPEAT_INTERVAL ) + // TODO eventSyncManager.scheduleSync() + // TODO imageUpSyncScheduler.scheduleImageUpSync() + // TODO firmwareFileUpdateScheduler.scheduleOrCancelWorkIfNecessary() } } @@ -34,4 +39,9 @@ internal class SyncOrchestratorImpl @Inject constructor( override fun startDeviceSync() { workManager.startWorker(SyncConstants.DEVICE_SYNC_WORK_NAME_ONE_TIME) } + + + override fun cleanupWorkers() { + cleanupDeprecatedWorkers() + } } diff --git a/id/src/main/java/com/simprints/id/CleanupDeprecatedWorkersUseCase.kt b/infra/sync/src/main/java/com/simprints/infra/sync/usecase/CleanupDeprecatedWorkersUseCase.kt similarity index 77% rename from id/src/main/java/com/simprints/id/CleanupDeprecatedWorkersUseCase.kt rename to infra/sync/src/main/java/com/simprints/infra/sync/usecase/CleanupDeprecatedWorkersUseCase.kt index bcae283da7..f964c2ca38 100644 --- a/id/src/main/java/com/simprints/id/CleanupDeprecatedWorkersUseCase.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/usecase/CleanupDeprecatedWorkersUseCase.kt @@ -1,18 +1,14 @@ -package com.simprints.id +package com.simprints.infra.sync.usecase -import android.content.Context import androidx.work.WorkManager import com.simprints.core.ExcludedFromGeneratedTestCoverageReports -import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject @ExcludedFromGeneratedTestCoverageReports("There is no complex business logic to test") -class CleanupDeprecatedWorkersUseCase @Inject constructor( - @ApplicationContext private val ctx: Context, +internal class CleanupDeprecatedWorkersUseCase @Inject constructor( + private val workManager: WorkManager, ) { - private val wm = WorkManager.getInstance(ctx) - /** * Removes deprecated workers from the work manager. * @@ -20,8 +16,8 @@ class CleanupDeprecatedWorkersUseCase @Inject constructor( * its tag or name should change with the old one added to respective list. */ operator fun invoke() { - namesForDeprecatedWorkers().forEach { wm.cancelUniqueWork(it) } - tagsForDeprecatedWorkers().forEach { wm.cancelAllWorkByTag(it) } + namesForDeprecatedWorkers().forEach { workManager.cancelUniqueWork(it) } + tagsForDeprecatedWorkers().forEach { workManager.cancelAllWorkByTag(it) } } private fun namesForDeprecatedWorkers() = listOf( diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt index 633870f2fc..2433159290 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt @@ -6,6 +6,7 @@ import com.simprints.infra.authstore.AuthStore import com.simprints.infra.sync.SyncConstants.DEVICE_SYNC_WORK_NAME import com.simprints.infra.sync.SyncConstants.DEVICE_SYNC_WORK_NAME_ONE_TIME import com.simprints.infra.sync.SyncConstants.PROJECT_SYNC_WORK_NAME +import com.simprints.infra.sync.usecase.CleanupDeprecatedWorkersUseCase import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK @@ -23,6 +24,9 @@ class SyncOrchestratorImplTest { @MockK private lateinit var authStore: AuthStore + @MockK + private lateinit var cleanupDeprecatedWorkers: CleanupDeprecatedWorkersUseCase + private lateinit var syncOrchestrator: SyncOrchestratorImpl @Before @@ -32,6 +36,7 @@ class SyncOrchestratorImplTest { syncOrchestrator = SyncOrchestratorImpl( workManager, authStore, + cleanupDeprecatedWorkers, ) } @@ -77,4 +82,10 @@ class SyncOrchestratorImplTest { } } + @Test + fun `delegates worker cleanup requests`() = runTest { + syncOrchestrator.cleanupWorkers() + verify { cleanupDeprecatedWorkers.invoke() } + } + } From b05ba2772a9849d43d28b13c6db4a822dd8f5165 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Tue, 20 Feb 2024 13:11:35 +0200 Subject: [PATCH 023/197] MS-205 Move image upload worker scheduling to sync module --- .../dashboard/logout/usecase/LogoutUseCase.kt | 3 - feature/login-check/build.gradle.kts | 1 - .../usecases/CancelBackgroundSyncUseCase.kt | 3 - .../StartBackgroundSyncIfNeededUseCase.kt | 3 - .../CancelBackgroundSyncUseCaseTest.kt | 6 - .../StartBackgroundSyncUseCaseTest.kt | 6 - infra/images/build.gradle.kts | 12 -- .../infra/images/ImageUpSyncScheduler.kt | 5 +- .../simprints/infra/images/ImagesModule.kt | 4 - .../images/worker/ImageUpSyncSchedulerImpl.kt | 68 ----------- .../worker/ImageUpSyncSchedulerImplTest.kt | 109 ------------------ infra/sync/build.gradle.kts | 9 +- .../com/simprints/infra/sync/SyncConstants.kt | 4 +- .../simprints/infra/sync/SyncOrchestrator.kt | 6 + .../infra/sync/SyncOrchestratorImpl.kt | 15 ++- .../sync/config/usecase/LogoutUseCase.kt | 3 - ...RescheduleWorkersIfConfigChangedUseCase.kt | 6 +- .../infra/sync/extensions/WorkManager.ext.kt | 3 +- .../infra/sync/images}/ImageUpSyncWorker.kt | 29 +++-- .../CleanupDeprecatedWorkersUseCase.kt | 1 + .../infra/sync/SyncOrchestratorImplTest.kt | 17 +++ .../sync/config/usecase/LogoutUseCaseTest.kt | 6 - ...heduleWorkersIfConfigChangedUseCaseTest.kt | 11 +- .../sync/images}/ImageUpSyncWorkerTest.kt | 2 +- 24 files changed, 73 insertions(+), 259 deletions(-) delete mode 100644 infra/images/src/main/java/com/simprints/infra/images/worker/ImageUpSyncSchedulerImpl.kt delete mode 100644 infra/images/src/test/java/com/simprints/infra/images/worker/ImageUpSyncSchedulerImplTest.kt rename infra/{images/src/main/java/com/simprints/infra/images/worker => sync/src/main/java/com/simprints/infra/sync/images}/ImageUpSyncWorker.kt (60%) rename infra/{images/src/test/java/com/simprints/infra/images/worker => sync/src/test/java/com/simprints/infra/sync/images}/ImageUpSyncWorkerTest.kt (97%) diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCase.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCase.kt index 28f57f2627..a35592d8a9 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCase.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCase.kt @@ -2,13 +2,11 @@ package com.simprints.feature.dashboard.logout.usecase import com.simprints.infra.authlogic.AuthManager import com.simprints.infra.eventsync.EventSyncManager -import com.simprints.infra.images.ImageUpSyncScheduler import com.simprints.infra.sync.SyncOrchestrator import javax.inject.Inject internal class LogoutUseCase @Inject constructor( private val syncOrchestrator: SyncOrchestrator, - private val imageUpSyncScheduler: ImageUpSyncScheduler, private val eventSyncManager: EventSyncManager, private val authManager: AuthManager, ) { @@ -16,7 +14,6 @@ internal class LogoutUseCase @Inject constructor( suspend operator fun invoke() { // Cancel all background sync eventSyncManager.cancelScheduledSync() - imageUpSyncScheduler.cancelImageUpSync() syncOrchestrator.cancelBackgroundWork() eventSyncManager.deleteSyncInfo() authManager.signOut() diff --git a/feature/login-check/build.gradle.kts b/feature/login-check/build.gradle.kts index 45e4981267..6063495f71 100644 --- a/feature/login-check/build.gradle.kts +++ b/feature/login-check/build.gradle.kts @@ -15,7 +15,6 @@ dependencies { implementation(project(":infra:sync")) implementation(project(":infra:events")) implementation(project(":infra:event-sync")) - implementation(project(":infra:images")) implementation(project(":infra:auth-store")) implementation(project(":infra:auth-logic")) implementation(project(":infra:recent-user-activity")) diff --git a/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCase.kt b/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCase.kt index 5ba58386ae..48d6f9adce 100644 --- a/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCase.kt +++ b/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCase.kt @@ -1,19 +1,16 @@ package com.simprints.feature.logincheck.usecases import com.simprints.infra.eventsync.EventSyncManager -import com.simprints.infra.images.ImageUpSyncScheduler import com.simprints.infra.sync.SyncOrchestrator import javax.inject.Inject internal class CancelBackgroundSyncUseCase @Inject constructor( private val eventSyncManager: EventSyncManager, - private val imageUpSyncScheduler: ImageUpSyncScheduler, private val syncOrchestrator: SyncOrchestrator, ) { suspend operator fun invoke() { eventSyncManager.cancelScheduledSync() - imageUpSyncScheduler.cancelImageUpSync() syncOrchestrator.cancelBackgroundWork() } } diff --git a/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncIfNeededUseCase.kt b/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncIfNeededUseCase.kt index de65bc9f07..f2ec37ebc3 100644 --- a/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncIfNeededUseCase.kt +++ b/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncIfNeededUseCase.kt @@ -3,20 +3,17 @@ package com.simprints.feature.logincheck.usecases import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.models.SynchronizationConfiguration import com.simprints.infra.eventsync.EventSyncManager -import com.simprints.infra.images.ImageUpSyncScheduler import com.simprints.infra.sync.SyncOrchestrator import javax.inject.Inject internal class StartBackgroundSyncUseCase @Inject constructor( private val syncOrchestrator: SyncOrchestrator, private val eventSyncManager: EventSyncManager, - private val imageUpSyncScheduler: ImageUpSyncScheduler, private val configRepository: ConfigRepository, ) { suspend operator fun invoke() { eventSyncManager.scheduleSync() - imageUpSyncScheduler.scheduleImageUpSync() syncOrchestrator.scheduleBackgroundWork() val frequency = configRepository.getProjectConfiguration().synchronization.frequency diff --git a/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCaseTest.kt b/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCaseTest.kt index 743f14a6f7..039914213f 100644 --- a/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCaseTest.kt +++ b/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCaseTest.kt @@ -1,7 +1,6 @@ package com.simprints.feature.logincheck.usecases import com.simprints.infra.eventsync.EventSyncManager -import com.simprints.infra.images.ImageUpSyncScheduler import com.simprints.infra.sync.SyncOrchestrator import io.mockk.MockKAnnotations import io.mockk.coVerify @@ -16,9 +15,6 @@ class CancelBackgroundSyncUseCaseTest { @MockK lateinit var eventSyncManager: EventSyncManager - @MockK - lateinit var imageUpSyncScheduler: ImageUpSyncScheduler - @MockK lateinit var syncOrchestrator: SyncOrchestrator @@ -30,7 +26,6 @@ class CancelBackgroundSyncUseCaseTest { useCase = CancelBackgroundSyncUseCase( eventSyncManager, - imageUpSyncScheduler, syncOrchestrator ) } @@ -41,7 +36,6 @@ class CancelBackgroundSyncUseCaseTest { verify { eventSyncManager.cancelScheduledSync() - imageUpSyncScheduler.cancelImageUpSync() } coVerify { syncOrchestrator.cancelBackgroundWork() diff --git a/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCaseTest.kt b/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCaseTest.kt index bbedfd0654..5d96627764 100644 --- a/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCaseTest.kt +++ b/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCaseTest.kt @@ -3,7 +3,6 @@ package com.simprints.feature.logincheck.usecases import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.models.SynchronizationConfiguration import com.simprints.infra.eventsync.EventSyncManager -import com.simprints.infra.images.ImageUpSyncScheduler import com.simprints.infra.sync.SyncOrchestrator import io.mockk.MockKAnnotations import io.mockk.coEvery @@ -19,9 +18,6 @@ class StartBackgroundSyncUseCaseTest { @MockK lateinit var eventSyncManager: EventSyncManager - @MockK - lateinit var imageUpSyncScheduler: ImageUpSyncScheduler - @MockK lateinit var syncOrchestrator: SyncOrchestrator @@ -37,7 +33,6 @@ class StartBackgroundSyncUseCaseTest { useCase = StartBackgroundSyncUseCase( syncOrchestrator, eventSyncManager, - imageUpSyncScheduler, configRepository, ) } @@ -53,7 +48,6 @@ class StartBackgroundSyncUseCaseTest { } coVerify { syncOrchestrator.scheduleBackgroundWork() - imageUpSyncScheduler.scheduleImageUpSync() } } diff --git a/infra/images/build.gradle.kts b/infra/images/build.gradle.kts index ff130498ee..96e1856e74 100644 --- a/infra/images/build.gradle.kts +++ b/infra/images/build.gradle.kts @@ -7,17 +7,6 @@ plugins { android { namespace = "com.simprints.infra.images" - buildTypes { - getByName("release") { - buildConfigField("long", "SYNC_PERIODIC_WORKER_INTERVAL_MINUTES", "60L") - } - getByName("staging") { - buildConfigField("long", "SYNC_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") - } - getByName("debug") { - buildConfigField("long", "SYNC_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") - } - } } dependencies { @@ -30,6 +19,5 @@ dependencies { implementation(libs.androidX.security) implementation(libs.kotlin.coroutinesPlayServices) - implementation(libs.workManager.work) } diff --git a/infra/images/src/main/java/com/simprints/infra/images/ImageUpSyncScheduler.kt b/infra/images/src/main/java/com/simprints/infra/images/ImageUpSyncScheduler.kt index 5875c58b74..6d8ed3542f 100644 --- a/infra/images/src/main/java/com/simprints/infra/images/ImageUpSyncScheduler.kt +++ b/infra/images/src/main/java/com/simprints/infra/images/ImageUpSyncScheduler.kt @@ -7,10 +7,7 @@ interface ImageUpSyncScheduler { */ suspend fun scheduleImageUpSync() - /** - * Fully reschedule the background worker. - * Should be used in when the configuration that affects scheduling has changed. - */ + suspend fun rescheduleImageUpSync() fun cancelImageUpSync() diff --git a/infra/images/src/main/java/com/simprints/infra/images/ImagesModule.kt b/infra/images/src/main/java/com/simprints/infra/images/ImagesModule.kt index 970bd933d4..8a45f9cfd3 100644 --- a/infra/images/src/main/java/com/simprints/infra/images/ImagesModule.kt +++ b/infra/images/src/main/java/com/simprints/infra/images/ImagesModule.kt @@ -4,7 +4,6 @@ import com.simprints.infra.images.local.ImageLocalDataSource import com.simprints.infra.images.local.ImageLocalDataSourceImpl import com.simprints.infra.images.remote.ImageRemoteDataSource import com.simprints.infra.images.remote.ImageRemoteDataSourceImpl -import com.simprints.infra.images.worker.ImageUpSyncSchedulerImpl import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -17,9 +16,6 @@ abstract class ImagesModule { @Binds internal abstract fun bindImageRepository(impl: ImageRepositoryImpl): ImageRepository - @Binds - internal abstract fun bindImageUpSyncScheduler(impl: ImageUpSyncSchedulerImpl): ImageUpSyncScheduler - @Binds internal abstract fun bindImageLocalDataSource(impl: ImageLocalDataSourceImpl): ImageLocalDataSource diff --git a/infra/images/src/main/java/com/simprints/infra/images/worker/ImageUpSyncSchedulerImpl.kt b/infra/images/src/main/java/com/simprints/infra/images/worker/ImageUpSyncSchedulerImpl.kt deleted file mode 100644 index f0f3e70d5f..0000000000 --- a/infra/images/src/main/java/com/simprints/infra/images/worker/ImageUpSyncSchedulerImpl.kt +++ /dev/null @@ -1,68 +0,0 @@ -package com.simprints.infra.images.worker - -import android.content.Context -import androidx.work.* -import androidx.work.WorkRequest.Companion.MIN_BACKOFF_MILLIS -import com.simprints.infra.config.store.ConfigRepository -import com.simprints.infra.config.store.models.imagesUploadRequiresUnmeteredConnection -import com.simprints.infra.images.BuildConfig -import com.simprints.infra.images.ImageUpSyncScheduler -import dagger.hilt.android.qualifiers.ApplicationContext -import java.util.concurrent.TimeUnit -import javax.inject.Inject - -internal class ImageUpSyncSchedulerImpl @Inject constructor( - @ApplicationContext context: Context, - private val configRepo: ConfigRepository, -) : ImageUpSyncScheduler { - - private val workManager = WorkManager.getInstance(context) - - override suspend fun scheduleImageUpSync() { - workManager.enqueueUniquePeriodicWork( - WORK_NAME, - ExistingPeriodicWorkPolicy.UPDATE, - buildWork() - ) - } - - override suspend fun rescheduleImageUpSync() { - workManager.enqueueUniquePeriodicWork( - WORK_NAME, - ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - buildWork() - ) - } - - override fun cancelImageUpSync() { - workManager.cancelUniqueWork(WORK_NAME) - } - - private suspend fun buildWork(): PeriodicWorkRequest { - val constraints = Constraints.Builder() - .setRequiredNetworkType( - if (configRepo.getProjectConfiguration().imagesUploadRequiresUnmeteredConnection()) NetworkType.UNMETERED - else NetworkType.CONNECTED - ) - .build() - - return PeriodicWorkRequestBuilder( - SYNC_REPEAT_INTERVAL, - SYNC_REPEAT_UNIT - ) - .setConstraints(constraints) - .setBackoffCriteria( - BackoffPolicy.EXPONENTIAL, - MIN_BACKOFF_MILLIS, - TimeUnit.MILLISECONDS - ).build() - } - - companion object { - - private const val WORK_NAME = "image-upsync-work-v2" - private const val SYNC_REPEAT_INTERVAL = - BuildConfig.SYNC_PERIODIC_WORKER_INTERVAL_MINUTES - private val SYNC_REPEAT_UNIT = TimeUnit.MINUTES - } -} diff --git a/infra/images/src/test/java/com/simprints/infra/images/worker/ImageUpSyncSchedulerImplTest.kt b/infra/images/src/test/java/com/simprints/infra/images/worker/ImageUpSyncSchedulerImplTest.kt deleted file mode 100644 index 2ef5f7979e..0000000000 --- a/infra/images/src/test/java/com/simprints/infra/images/worker/ImageUpSyncSchedulerImplTest.kt +++ /dev/null @@ -1,109 +0,0 @@ -package com.simprints.infra.images.worker - -import android.content.Context -import androidx.work.ExistingPeriodicWorkPolicy -import androidx.work.NetworkType -import androidx.work.PeriodicWorkRequest -import androidx.work.WorkManager -import com.simprints.infra.config.store.ConfigRepository -import io.mockk.MockKAnnotations -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every -import io.mockk.impl.annotations.MockK -import io.mockk.mockk -import io.mockk.mockkStatic -import io.mockk.unmockkAll -import kotlinx.coroutines.test.runTest -import org.junit.After -import org.junit.Assert.* - -import org.junit.Before -import org.junit.Test - -internal class ImageUpSyncSchedulerImplTest { - - @MockK - private lateinit var ctx: Context - - @MockK - private lateinit var workManager: WorkManager - - @MockK - private lateinit var configRepository: ConfigRepository - - private lateinit var scheduler: ImageUpSyncSchedulerImpl - - @Before - fun setUp() { - MockKAnnotations.init(this, relaxed = true) - mockkStatic(WorkManager::class) - every { WorkManager.getInstance(ctx) } returns workManager - - scheduler = ImageUpSyncSchedulerImpl(ctx, configRepository) - } - - @After - fun tearDown() { - unmockkAll() - } - - @Test - fun `should schedule worker to upload on any network`() = runTest { - coEvery { configRepository.getProjectConfiguration().synchronization.up.imagesRequireUnmeteredConnection } returns false - - scheduler.scheduleImageUpSync() - - coVerify { - workManager.enqueueUniquePeriodicWork( - any(), - any(), - match { - it.workSpec.constraints.requiredNetworkType == NetworkType.CONNECTED - } - ) - } - } - - @Test - fun `should schedule worker to upload only on unmetered network`() = runTest { - coEvery { configRepository.getProjectConfiguration().synchronization.up.imagesRequireUnmeteredConnection } returns true - - scheduler.scheduleImageUpSync() - - coVerify { - workManager.enqueueUniquePeriodicWork( - any(), - any(), - match { - it.workSpec.constraints.requiredNetworkType == NetworkType.UNMETERED - } - ) - } - } - - @Test - fun `should cancel and reschedule worker`() = runTest { - coEvery { configRepository.getProjectConfiguration().synchronization.up.imagesRequireUnmeteredConnection } returns true - - scheduler.rescheduleImageUpSync() - - coVerify { - workManager.enqueueUniquePeriodicWork( - any(), - ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - any() - ) - } - } - - @Test - fun `should cancel worker`() = runTest { - scheduler.cancelImageUpSync() - - coVerify { - workManager.cancelUniqueWork(any()) - } - } - -} diff --git a/infra/sync/build.gradle.kts b/infra/sync/build.gradle.kts index 20f7eb83ee..1d250cc7b4 100644 --- a/infra/sync/build.gradle.kts +++ b/infra/sync/build.gradle.kts @@ -8,16 +8,19 @@ android { buildTypes { getByName("release") { - buildConfigField("long", "SYNC_PERIODIC_WORKER_INTERVAL_MINUTES", "60L") + buildConfigField("long", "PROJECT_PERIODIC_WORKER_INTERVAL_MINUTES", "60L") buildConfigField("long", "DEVICE_PERIODIC_WORKER_INTERVAL_MINUTES", "30L") + buildConfigField("long", "IMAGE_PERIODIC_WORKER_INTERVAL_MINUTES", "60L") } getByName("staging") { - buildConfigField("long", "SYNC_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") + buildConfigField("long", "PROJECT_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") buildConfigField("long", "DEVICE_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") + buildConfigField("long", "IMAGE_PERIODIC_WORKER_INTERVAL_MINUTES", "15LL") } getByName("debug") { - buildConfigField("long", "SYNC_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") + buildConfigField("long", "PROJECT_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") buildConfigField("long", "DEVICE_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") + buildConfigField("long", "IMAGE_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") } } } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt index a83e1fe228..a575c5a52b 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt @@ -7,10 +7,12 @@ internal object SyncConstants { val SYNC_REPEAT_UNIT = TimeUnit.MINUTES const val PROJECT_SYNC_WORK_NAME = "project-sync-work-v2" - const val PROJECT_SYNC_REPEAT_INTERVAL = BuildConfig.SYNC_PERIODIC_WORKER_INTERVAL_MINUTES + const val PROJECT_SYNC_REPEAT_INTERVAL = BuildConfig.PROJECT_PERIODIC_WORKER_INTERVAL_MINUTES const val DEVICE_SYNC_WORK_NAME = "device-sync-work-v2" const val DEVICE_SYNC_WORK_NAME_ONE_TIME = "device-sync-work-one-time" const val DEVICE_SYNC_REPEAT_INTERVAL = BuildConfig.DEVICE_PERIODIC_WORKER_INTERVAL_MINUTES + const val IMAGE_UP_SYNC_WORK_NAME = "image-upsync-work-v3" + const val IMAGE_UP_SYNC_REPEAT_INTERVAL = BuildConfig.IMAGE_PERIODIC_WORKER_INTERVAL_MINUTES } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt index a6fcd8acb6..56e6221a32 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt @@ -7,5 +7,11 @@ interface SyncOrchestrator { fun startDeviceSync() + /** + * Fully reschedule the background worker. + * Should be used in when the configuration that affects scheduling has changed. + */ + suspend fun rescheduleImageUpSync() + fun cleanupWorkers() } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt index e9743e576d..22fe387de4 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt @@ -1,5 +1,6 @@ package com.simprints.infra.sync +import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.WorkManager import com.simprints.infra.authstore.AuthStore import com.simprints.infra.sync.extensions.schedulePeriodicWorker @@ -7,6 +8,7 @@ import com.simprints.infra.sync.extensions.startWorker import com.simprints.infra.sync.config.worker.DeviceConfigDownSyncWorker import com.simprints.infra.sync.config.worker.ProjectConfigDownSyncWorker import com.simprints.infra.sync.usecase.CleanupDeprecatedWorkersUseCase +import com.simprints.infra.sync.images.ImageUpSyncWorker import javax.inject.Inject internal class SyncOrchestratorImpl @Inject constructor( @@ -25,8 +27,11 @@ internal class SyncOrchestratorImpl @Inject constructor( SyncConstants.DEVICE_SYNC_WORK_NAME, SyncConstants.DEVICE_SYNC_REPEAT_INTERVAL ) + workManager.schedulePeriodicWorker( + SyncConstants.IMAGE_UP_SYNC_WORK_NAME, + SyncConstants.IMAGE_UP_SYNC_REPEAT_INTERVAL, + ) // TODO eventSyncManager.scheduleSync() - // TODO imageUpSyncScheduler.scheduleImageUpSync() // TODO firmwareFileUpdateScheduler.scheduleOrCancelWorkIfNecessary() } } @@ -34,12 +39,20 @@ internal class SyncOrchestratorImpl @Inject constructor( override suspend fun cancelBackgroundWork() { workManager.cancelUniqueWork(SyncConstants.PROJECT_SYNC_WORK_NAME) workManager.cancelUniqueWork(SyncConstants.DEVICE_SYNC_WORK_NAME) + workManager.cancelUniqueWork(SyncConstants.IMAGE_UP_SYNC_WORK_NAME) } override fun startDeviceSync() { workManager.startWorker(SyncConstants.DEVICE_SYNC_WORK_NAME_ONE_TIME) } + override suspend fun rescheduleImageUpSync() { + workManager.schedulePeriodicWorker( + SyncConstants.IMAGE_UP_SYNC_WORK_NAME, + SyncConstants.IMAGE_UP_SYNC_REPEAT_INTERVAL, + existingWorkPolicy = ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, + ) + } override fun cleanupWorkers() { cleanupDeprecatedWorkers() diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/LogoutUseCase.kt b/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/LogoutUseCase.kt index 2b38fa5758..0a36af713b 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/LogoutUseCase.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/LogoutUseCase.kt @@ -2,19 +2,16 @@ package com.simprints.infra.sync.config.usecase import com.simprints.infra.authlogic.AuthManager import com.simprints.infra.eventsync.EventSyncManager -import com.simprints.infra.images.ImageUpSyncScheduler import com.simprints.infra.sync.SyncOrchestrator import javax.inject.Inject internal class LogoutUseCase @Inject constructor( private val syncOrchestrator: SyncOrchestrator, - private val imageUpSyncScheduler: ImageUpSyncScheduler, private val eventSyncManager: EventSyncManager, private val authManager: AuthManager, ) { suspend operator fun invoke() { - imageUpSyncScheduler.cancelImageUpSync() syncOrchestrator.cancelBackgroundWork() eventSyncManager.cancelScheduledSync() eventSyncManager.deleteSyncInfo() diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCase.kt b/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCase.kt index 939cffd9e9..111b7ad794 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCase.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCase.kt @@ -2,11 +2,11 @@ package com.simprints.infra.sync.config.usecase import com.simprints.infra.config.store.models.ProjectConfiguration import com.simprints.infra.config.store.models.imagesUploadRequiresUnmeteredConnection -import com.simprints.infra.images.ImageUpSyncScheduler +import com.simprints.infra.sync.SyncOrchestrator import javax.inject.Inject internal class RescheduleWorkersIfConfigChangedUseCase @Inject constructor( - private val imageUpSyncScheduler: ImageUpSyncScheduler, + private val syncOrchestrator: SyncOrchestrator ) { suspend operator fun invoke( @@ -14,7 +14,7 @@ internal class RescheduleWorkersIfConfigChangedUseCase @Inject constructor( newConfig: ProjectConfiguration, ) { if (shouldRescheduleImageUpload(oldConfig, newConfig)) { - imageUpSyncScheduler.rescheduleImageUpSync() + syncOrchestrator.rescheduleImageUpSync() } } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt b/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt index 72b4939128..b2e7a32434 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt @@ -11,11 +11,12 @@ internal fun defaultWorkerConstraints() = Constraints.Builder() internal inline fun WorkManager.schedulePeriodicWorker( workName: String, repeatInterval: Long, + existingWorkPolicy: ExistingPeriodicWorkPolicy = ExistingPeriodicWorkPolicy.UPDATE, constraints: Constraints = defaultWorkerConstraints(), inputData: Data? = null, ) = enqueueUniquePeriodicWork( workName, - ExistingPeriodicWorkPolicy.UPDATE, + existingWorkPolicy, PeriodicWorkRequestBuilder(repeatInterval, SyncConstants.SYNC_REPEAT_UNIT) .setConstraints(constraints) .let { if (inputData != null) it.setInputData(inputData) else it } diff --git a/infra/images/src/main/java/com/simprints/infra/images/worker/ImageUpSyncWorker.kt b/infra/sync/src/main/java/com/simprints/infra/sync/images/ImageUpSyncWorker.kt similarity index 60% rename from infra/images/src/main/java/com/simprints/infra/images/worker/ImageUpSyncWorker.kt rename to infra/sync/src/main/java/com/simprints/infra/sync/images/ImageUpSyncWorker.kt index bf495987f5..50c1329b26 100644 --- a/infra/images/src/main/java/com/simprints/infra/images/worker/ImageUpSyncWorker.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/images/ImageUpSyncWorker.kt @@ -1,12 +1,12 @@ -package com.simprints.infra.images.worker +package com.simprints.infra.sync.images import android.content.Context import androidx.hilt.work.HiltWorker import androidx.work.WorkerParameters import com.simprints.core.DispatcherBG import com.simprints.core.workers.SimCoroutineWorker -import com.simprints.infra.images.ImageRepository import com.simprints.infra.authstore.AuthStore +import com.simprints.infra.images.ImageRepository import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineDispatcher @@ -18,24 +18,23 @@ internal class ImageUpSyncWorker @AssistedInject constructor( @Assisted params: WorkerParameters, private val imageRepository: ImageRepository, private val authStore: AuthStore, - @DispatcherBG private val dispatcher: CoroutineDispatcher + @DispatcherBG private val dispatcher: CoroutineDispatcher, ) : SimCoroutineWorker(context, params) { override val tag: String = ImageUpSyncWorker::class.java.simpleName - override suspend fun doWork(): Result = - withContext(dispatcher) { - crashlyticsLog("Start") - showProgressNotification() + override suspend fun doWork(): Result = withContext(dispatcher) { + crashlyticsLog("Image upload start") + showProgressNotification() - try { - if (imageRepository.uploadStoredImagesAndDelete(authStore.signedInProjectId)) { - success() - } else { - retry() - } - } catch (ex: Exception) { - retry(t = ex) + try { + if (imageRepository.uploadStoredImagesAndDelete(authStore.signedInProjectId)) { + success() + } else { + retry() } + } catch (ex: Exception) { + retry(t = ex) } + } } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/usecase/CleanupDeprecatedWorkersUseCase.kt b/infra/sync/src/main/java/com/simprints/infra/sync/usecase/CleanupDeprecatedWorkersUseCase.kt index f964c2ca38..2d51903dc0 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/usecase/CleanupDeprecatedWorkersUseCase.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/usecase/CleanupDeprecatedWorkersUseCase.kt @@ -29,6 +29,7 @@ internal class CleanupDeprecatedWorkersUseCase @Inject constructor( "security-status-check-work-one-time-v2", // 2024.1.1 "project-sync-work", // 2024.1.1 "device-sync-work", // 2024.1.1 + "image-upsync-work-v2", // 2024.1.1 ) private fun tagsForDeprecatedWorkers() = listOf( diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt index 2433159290..348fe21fde 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt @@ -1,10 +1,12 @@ package com.simprints.infra.sync +import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager import com.simprints.infra.authstore.AuthStore import com.simprints.infra.sync.SyncConstants.DEVICE_SYNC_WORK_NAME import com.simprints.infra.sync.SyncConstants.DEVICE_SYNC_WORK_NAME_ONE_TIME +import com.simprints.infra.sync.SyncConstants.IMAGE_UP_SYNC_WORK_NAME import com.simprints.infra.sync.SyncConstants.PROJECT_SYNC_WORK_NAME import com.simprints.infra.sync.usecase.CleanupDeprecatedWorkersUseCase import io.mockk.MockKAnnotations @@ -60,6 +62,7 @@ class SyncOrchestratorImplTest { verify { workManager.enqueueUniquePeriodicWork(PROJECT_SYNC_WORK_NAME, any(), any()) workManager.enqueueUniquePeriodicWork(DEVICE_SYNC_WORK_NAME, any(), any()) + workManager.enqueueUniquePeriodicWork(IMAGE_UP_SYNC_WORK_NAME, any(), any()) } } @@ -70,6 +73,7 @@ class SyncOrchestratorImplTest { verify { workManager.cancelUniqueWork(PROJECT_SYNC_WORK_NAME) workManager.cancelUniqueWork(DEVICE_SYNC_WORK_NAME) + workManager.cancelUniqueWork(IMAGE_UP_SYNC_WORK_NAME) } } @@ -82,6 +86,19 @@ class SyncOrchestratorImplTest { } } + @Test + fun `reschedules image worker when requested`() = runTest { + syncOrchestrator.rescheduleImageUpSync() + + verify { + workManager.enqueueUniquePeriodicWork( + IMAGE_UP_SYNC_WORK_NAME, + ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, + any(), + ) + } + } + @Test fun `delegates worker cleanup requests`() = runTest { syncOrchestrator.cleanupWorkers() diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/LogoutUseCaseTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/LogoutUseCaseTest.kt index 3543f5ccd0..0dabe88b18 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/LogoutUseCaseTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/LogoutUseCaseTest.kt @@ -2,7 +2,6 @@ package com.simprints.infra.sync.config.usecase import com.simprints.infra.authlogic.AuthManager import com.simprints.infra.eventsync.EventSyncManager -import com.simprints.infra.images.ImageUpSyncScheduler import com.simprints.infra.sync.SyncOrchestrator import io.mockk.MockKAnnotations import io.mockk.coVerify @@ -17,9 +16,6 @@ class LogoutUseCaseTest { @MockK private lateinit var syncOrchestrator: SyncOrchestrator - @MockK - private lateinit var imageUpSyncScheduler: ImageUpSyncScheduler - @MockK private lateinit var eventSyncManager: EventSyncManager @@ -34,7 +30,6 @@ class LogoutUseCaseTest { useCase = LogoutUseCase( syncOrchestrator = syncOrchestrator, - imageUpSyncScheduler = imageUpSyncScheduler, eventSyncManager = eventSyncManager, authManager = authManager, ) @@ -45,7 +40,6 @@ class LogoutUseCaseTest { useCase.invoke() verify { - imageUpSyncScheduler.cancelImageUpSync() eventSyncManager.cancelScheduledSync() } coVerify { diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCaseTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCaseTest.kt index b91da79f3a..487317ffdc 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCaseTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCaseTest.kt @@ -1,9 +1,8 @@ package com.simprints.infra.sync.config.usecase +import com.simprints.infra.sync.SyncOrchestrator import com.simprints.infra.sync.config.testtools.projectConfiguration import com.simprints.infra.sync.config.testtools.synchronizationConfiguration -import com.simprints.infra.images.ImageUpSyncScheduler -import com.simprints.infra.sync.config.usecase.RescheduleWorkersIfConfigChangedUseCase import io.mockk.MockKAnnotations import io.mockk.coVerify import io.mockk.impl.annotations.MockK @@ -14,7 +13,7 @@ import org.junit.Test class RescheduleWorkersIfConfigChangedUseCaseTest { @MockK - private lateinit var imageUpSyncScheduler: ImageUpSyncScheduler + private lateinit var syncOrchestrator: SyncOrchestrator private lateinit var useCase: RescheduleWorkersIfConfigChangedUseCase @@ -22,7 +21,7 @@ class RescheduleWorkersIfConfigChangedUseCaseTest { fun setUp() { MockKAnnotations.init(this, relaxed = true) - useCase = RescheduleWorkersIfConfigChangedUseCase(imageUpSyncScheduler) + useCase = RescheduleWorkersIfConfigChangedUseCase(syncOrchestrator) } @Test @@ -41,7 +40,7 @@ class RescheduleWorkersIfConfigChangedUseCaseTest { ), ) - coVerify(exactly = 0) { imageUpSyncScheduler.rescheduleImageUpSync() } + coVerify(exactly = 0) { syncOrchestrator.rescheduleImageUpSync() } } @Test @@ -60,7 +59,7 @@ class RescheduleWorkersIfConfigChangedUseCaseTest { ), ) - coVerify { imageUpSyncScheduler.rescheduleImageUpSync() } + coVerify { syncOrchestrator.rescheduleImageUpSync() } } } diff --git a/infra/images/src/test/java/com/simprints/infra/images/worker/ImageUpSyncWorkerTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/images/ImageUpSyncWorkerTest.kt similarity index 97% rename from infra/images/src/test/java/com/simprints/infra/images/worker/ImageUpSyncWorkerTest.kt rename to infra/sync/src/test/java/com/simprints/infra/sync/images/ImageUpSyncWorkerTest.kt index 143a1bc8e6..f3a49516e0 100644 --- a/infra/images/src/test/java/com/simprints/infra/images/worker/ImageUpSyncWorkerTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/images/ImageUpSyncWorkerTest.kt @@ -1,4 +1,4 @@ -package com.simprints.infra.images.worker +package com.simprints.infra.sync.images import androidx.work.ListenableWorker import com.google.common.truth.Truth.assertThat From c104fa6f9eb45528f30eb67348be78ccc6eecec2 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Tue, 20 Feb 2024 13:39:03 +0200 Subject: [PATCH 024/197] MS-205 Move record upload worker into sync module --- infra/enrolment-records-sync/.gitignore | 1 - infra/enrolment-records-sync/build.gradle.kts | 20 ------- .../src/main/AndroidManifest.xml | 4 -- .../sync/EnrolmentRecordsSyncModule.kt | 16 ----- .../sync/worker/EnrolmentRecordScheduler.kt | 6 -- .../worker/EnrolmentRecordSchedulerImpl.kt | 42 -------------- .../sync/worker/EnrolmentRecordWorker.kt | 45 -------------- .../records/EnrolmentRecordManagerImplTest.kt | 0 .../EnrolmentRecordSchedulerImplTest.kt | 58 ------------------- infra/sync/build.gradle.kts | 1 - .../com/simprints/infra/sync/SyncConstants.kt | 4 ++ .../simprints/infra/sync/SyncOrchestrator.kt | 5 ++ .../infra/sync/SyncOrchestratorImpl.kt | 12 ++++ .../worker/DeviceConfigDownSyncWorker.kt | 6 +- .../sync/enrolments/EnrolmentRecordWorker.kt | 45 ++++++++++++++ .../infra/sync/SyncOrchestratorImplTest.kt | 36 +++++++++++- .../worker/DeviceConfigDownSyncWorkerTest.kt | 13 ++--- .../enrolments}/EnrolmentRecordWorkerTest.kt | 17 +++--- settings.gradle.kts | 1 - 19 files changed, 120 insertions(+), 212 deletions(-) delete mode 100644 infra/enrolment-records-sync/.gitignore delete mode 100644 infra/enrolment-records-sync/build.gradle.kts delete mode 100644 infra/enrolment-records-sync/src/main/AndroidManifest.xml delete mode 100644 infra/enrolment-records-sync/src/main/java/com/simprints/infra/enrolment/records/sync/EnrolmentRecordsSyncModule.kt delete mode 100644 infra/enrolment-records-sync/src/main/java/com/simprints/infra/enrolment/records/sync/worker/EnrolmentRecordScheduler.kt delete mode 100644 infra/enrolment-records-sync/src/main/java/com/simprints/infra/enrolment/records/sync/worker/EnrolmentRecordSchedulerImpl.kt delete mode 100644 infra/enrolment-records-sync/src/main/java/com/simprints/infra/enrolment/records/sync/worker/EnrolmentRecordWorker.kt delete mode 100644 infra/enrolment-records-sync/src/test/java/com/simprints/infra/enrolment/records/EnrolmentRecordManagerImplTest.kt delete mode 100644 infra/enrolment-records-sync/src/test/java/com/simprints/infra/enrolment/records/worker/EnrolmentRecordSchedulerImplTest.kt create mode 100644 infra/sync/src/main/java/com/simprints/infra/sync/enrolments/EnrolmentRecordWorker.kt rename infra/{enrolment-records-sync/src/test/java/com/simprints/infra/enrolment/records/worker => sync/src/test/java/com/simprints/infra/sync/enrolments}/EnrolmentRecordWorkerTest.kt (77%) diff --git a/infra/enrolment-records-sync/.gitignore b/infra/enrolment-records-sync/.gitignore deleted file mode 100644 index 42afabfd2a..0000000000 --- a/infra/enrolment-records-sync/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/infra/enrolment-records-sync/build.gradle.kts b/infra/enrolment-records-sync/build.gradle.kts deleted file mode 100644 index 80456036a9..0000000000 --- a/infra/enrolment-records-sync/build.gradle.kts +++ /dev/null @@ -1,20 +0,0 @@ -plugins { - id("simprints.infra") - id("kotlin-parcelize") -} - -android { - namespace = "com.simprints.infra.enrolment.records.sync" -} - -dependencies { - implementation(project(":infra:config-store")) - implementation(project(":infra:enrolment-records-store")) - implementation(project(":infra:auth-store")) - implementation(project(":infra:realm")) - - implementation(libs.retrofit.core) - implementation(libs.jackson.core) - - implementation(libs.workManager.work) -} diff --git a/infra/enrolment-records-sync/src/main/AndroidManifest.xml b/infra/enrolment-records-sync/src/main/AndroidManifest.xml deleted file mode 100644 index e100076157..0000000000 --- a/infra/enrolment-records-sync/src/main/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/infra/enrolment-records-sync/src/main/java/com/simprints/infra/enrolment/records/sync/EnrolmentRecordsSyncModule.kt b/infra/enrolment-records-sync/src/main/java/com/simprints/infra/enrolment/records/sync/EnrolmentRecordsSyncModule.kt deleted file mode 100644 index 2898434bbd..0000000000 --- a/infra/enrolment-records-sync/src/main/java/com/simprints/infra/enrolment/records/sync/EnrolmentRecordsSyncModule.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.simprints.infra.enrolment.records.sync - -import com.simprints.infra.enrolment.records.sync.worker.EnrolmentRecordScheduler -import com.simprints.infra.enrolment.records.sync.worker.EnrolmentRecordSchedulerImpl -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent - -@Module -@InstallIn(SingletonComponent::class) -abstract class EnrolmentRecordsSyncModule { - - @Binds - internal abstract fun bindEnrolmentRecordScheduler(impl: EnrolmentRecordSchedulerImpl): EnrolmentRecordScheduler -} diff --git a/infra/enrolment-records-sync/src/main/java/com/simprints/infra/enrolment/records/sync/worker/EnrolmentRecordScheduler.kt b/infra/enrolment-records-sync/src/main/java/com/simprints/infra/enrolment/records/sync/worker/EnrolmentRecordScheduler.kt deleted file mode 100644 index 6a263a17ef..0000000000 --- a/infra/enrolment-records-sync/src/main/java/com/simprints/infra/enrolment/records/sync/worker/EnrolmentRecordScheduler.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.simprints.infra.enrolment.records.sync.worker - -interface EnrolmentRecordScheduler { - fun upload(id: String, subjectIds: List) -} - diff --git a/infra/enrolment-records-sync/src/main/java/com/simprints/infra/enrolment/records/sync/worker/EnrolmentRecordSchedulerImpl.kt b/infra/enrolment-records-sync/src/main/java/com/simprints/infra/enrolment/records/sync/worker/EnrolmentRecordSchedulerImpl.kt deleted file mode 100644 index 2694d0459a..0000000000 --- a/infra/enrolment-records-sync/src/main/java/com/simprints/infra/enrolment/records/sync/worker/EnrolmentRecordSchedulerImpl.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.simprints.infra.enrolment.records.sync.worker - -import android.content.Context -import androidx.work.* -import dagger.hilt.android.qualifiers.ApplicationContext -import javax.inject.Inject - -internal class EnrolmentRecordSchedulerImpl @Inject constructor(@ApplicationContext context: Context) : - EnrolmentRecordScheduler { - - companion object { - private const val WORK_NAME = "upload-enrolment-record-work-one-time" - const val INPUT_ID_NAME = "INPUT_ID_NAME" - const val INPUT_SUBJECT_IDS_NAME = "INPUT_SUBJECT_IDS_NAME" - } - - private val workManager = WorkManager.getInstance(context) - - override fun upload(id: String, subjectIds: List) { - workManager.enqueueUniqueWork( - WORK_NAME, - ExistingWorkPolicy.KEEP, - buildOneTimeRequest(id, subjectIds) - ) - } - - private fun buildOneTimeRequest(id: String, subjectIds: List): OneTimeWorkRequest = - OneTimeWorkRequestBuilder() - .setConstraints( - Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .build() - ) - .setInputData( - workDataOf( - INPUT_SUBJECT_IDS_NAME to subjectIds.toTypedArray(), - INPUT_ID_NAME to id - ) - ) - .build() -} - diff --git a/infra/enrolment-records-sync/src/main/java/com/simprints/infra/enrolment/records/sync/worker/EnrolmentRecordWorker.kt b/infra/enrolment-records-sync/src/main/java/com/simprints/infra/enrolment/records/sync/worker/EnrolmentRecordWorker.kt deleted file mode 100644 index 9de62520bb..0000000000 --- a/infra/enrolment-records-sync/src/main/java/com/simprints/infra/enrolment/records/sync/worker/EnrolmentRecordWorker.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.simprints.infra.enrolment.records.sync.worker - -import android.content.Context -import androidx.hilt.work.HiltWorker -import androidx.work.CoroutineWorker -import androidx.work.WorkerParameters -import com.simprints.core.DispatcherIO -import com.simprints.infra.config.store.ConfigRepository -import com.simprints.infra.enrolment.records.store.EnrolmentRecordRepository -import dagger.assisted.Assisted -import dagger.assisted.AssistedInject -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.withContext - -@HiltWorker -class EnrolmentRecordWorker @AssistedInject constructor( - @Assisted context: Context, - @Assisted params: WorkerParameters, - private val enrolmentRecordRepository: EnrolmentRecordRepository, - private val configRepository: ConfigRepository, - @DispatcherIO private val dispatcher: CoroutineDispatcher, -) : CoroutineWorker(context, params) { - - override suspend fun doWork(): Result = - withContext(dispatcher) { - try { - val instructionId = - inputData.getString(EnrolmentRecordSchedulerImpl.INPUT_ID_NAME) - ?: throw IllegalArgumentException("input required") - val subjectIds = - inputData.getStringArray(EnrolmentRecordSchedulerImpl.INPUT_SUBJECT_IDS_NAME) - ?: throw IllegalArgumentException("input required") - - enrolmentRecordRepository.uploadRecords(subjectIds.toList()) - - configRepository.updateDeviceConfiguration { - it.apply { it.lastInstructionId = instructionId } - } - - Result.success() - } catch (e: Exception) { - Result.retry() - } - } -} diff --git a/infra/enrolment-records-sync/src/test/java/com/simprints/infra/enrolment/records/EnrolmentRecordManagerImplTest.kt b/infra/enrolment-records-sync/src/test/java/com/simprints/infra/enrolment/records/EnrolmentRecordManagerImplTest.kt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/infra/enrolment-records-sync/src/test/java/com/simprints/infra/enrolment/records/worker/EnrolmentRecordSchedulerImplTest.kt b/infra/enrolment-records-sync/src/test/java/com/simprints/infra/enrolment/records/worker/EnrolmentRecordSchedulerImplTest.kt deleted file mode 100644 index ed1433a4b1..0000000000 --- a/infra/enrolment-records-sync/src/test/java/com/simprints/infra/enrolment/records/worker/EnrolmentRecordSchedulerImplTest.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.simprints.infra.enrolment.records.worker - -import android.content.Context -import androidx.work.ExistingWorkPolicy -import androidx.work.OneTimeWorkRequest -import androidx.work.WorkManager -import com.simprints.infra.enrolment.records.sync.worker.EnrolmentRecordSchedulerImpl -import io.mockk.coVerify -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkStatic -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test - -class EnrolmentRecordSchedulerImplTest { - - companion object { - private const val INSTRUCTION_ID = "id" - private const val SUBJECT_ID = "subjectId" - } - - private val ctx = mockk() - private val workManager = mockk(relaxed = true) - private lateinit var enrolmentRecordSchedulerImpl: EnrolmentRecordSchedulerImpl - - @Before - fun setup() { - mockkStatic(WorkManager::class) - every { WorkManager.getInstance(ctx) } returns workManager - - enrolmentRecordSchedulerImpl = EnrolmentRecordSchedulerImpl(ctx) - } - - @Test - fun `upload should schedule the worker with the correct data`() = runTest { - enrolmentRecordSchedulerImpl.upload(INSTRUCTION_ID, listOf(SUBJECT_ID)) - - coVerify(exactly = 1) { - workManager.enqueueUniqueWork( - any(), - ExistingWorkPolicy.KEEP, - match { oneTimeWorkRequest -> - val subjectIdsInput = oneTimeWorkRequest.workSpec.input.getStringArray( - EnrolmentRecordSchedulerImpl.INPUT_SUBJECT_IDS_NAME - ) - val instructionIdInput = - oneTimeWorkRequest.workSpec.input.getString(EnrolmentRecordSchedulerImpl.INPUT_ID_NAME) - instructionIdInput == INSTRUCTION_ID && subjectIdsInput.contentEquals( - arrayOf( - SUBJECT_ID - ) - ) - } - ) - } - } -} diff --git a/infra/sync/build.gradle.kts b/infra/sync/build.gradle.kts index 1d250cc7b4..1eac3680ba 100644 --- a/infra/sync/build.gradle.kts +++ b/infra/sync/build.gradle.kts @@ -30,7 +30,6 @@ dependencies { implementation(project(":infra:auth-logic")) implementation(project(":infra:config-store")) implementation(project(":infra:enrolment-records-store")) - implementation(project(":infra:enrolment-records-sync")) implementation(project(":infra:events")) implementation(project(":infra:event-sync")) implementation(project(":infra:images")) diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt index a575c5a52b..c969ef2ceb 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt @@ -15,4 +15,8 @@ internal object SyncConstants { const val IMAGE_UP_SYNC_WORK_NAME = "image-upsync-work-v3" const val IMAGE_UP_SYNC_REPEAT_INTERVAL = BuildConfig.IMAGE_PERIODIC_WORKER_INTERVAL_MINUTES + + const val RECORD_UPLOAD_WORK_NAME = "upload-enrolment-record-work-one-time" + const val RECORD_UPLOAD_INPUT_ID_NAME = "INPUT_ID_NAME" + const val RECORD_UPLOAD_INPUT_SUBJECT_IDS_NAME = "INPUT_SUBJECT_IDS_NAME" } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt index 56e6221a32..dcc2b731ff 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt @@ -13,5 +13,10 @@ interface SyncOrchestrator { */ suspend fun rescheduleImageUpSync() + /** + * Schedule a worker to upload subjects with IDs in the provided list. + */ + fun uploadEnrolmentRecords(id: String, subjectIds: List) + fun cleanupWorkers() } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt index 22fe387de4..3ae13c8c79 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt @@ -2,11 +2,13 @@ package com.simprints.infra.sync import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.WorkManager +import androidx.work.workDataOf import com.simprints.infra.authstore.AuthStore import com.simprints.infra.sync.extensions.schedulePeriodicWorker import com.simprints.infra.sync.extensions.startWorker import com.simprints.infra.sync.config.worker.DeviceConfigDownSyncWorker import com.simprints.infra.sync.config.worker.ProjectConfigDownSyncWorker +import com.simprints.infra.sync.enrolments.EnrolmentRecordWorker import com.simprints.infra.sync.usecase.CleanupDeprecatedWorkersUseCase import com.simprints.infra.sync.images.ImageUpSyncWorker import javax.inject.Inject @@ -54,6 +56,16 @@ internal class SyncOrchestratorImpl @Inject constructor( ) } + override fun uploadEnrolmentRecords(id: String, subjectIds: List) { + workManager.startWorker( + SyncConstants.RECORD_UPLOAD_WORK_NAME, + inputData = workDataOf( + SyncConstants.RECORD_UPLOAD_INPUT_ID_NAME to id, + SyncConstants.RECORD_UPLOAD_INPUT_SUBJECT_IDS_NAME to subjectIds.toTypedArray() + ), + ) + } + override fun cleanupWorkers() { cleanupDeprecatedWorkers() } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/config/worker/DeviceConfigDownSyncWorker.kt b/infra/sync/src/main/java/com/simprints/infra/sync/config/worker/DeviceConfigDownSyncWorker.kt index ec23e29fb9..6d17c81595 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/config/worker/DeviceConfigDownSyncWorker.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/config/worker/DeviceConfigDownSyncWorker.kt @@ -5,8 +5,8 @@ import androidx.work.WorkerParameters import com.simprints.core.DispatcherBG import com.simprints.core.workers.SimCoroutineWorker import com.simprints.infra.config.store.ConfigRepository +import com.simprints.infra.sync.SyncOrchestrator import com.simprints.infra.sync.config.usecase.LogoutUseCase -import com.simprints.infra.enrolment.records.sync.worker.EnrolmentRecordScheduler import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineDispatcher @@ -17,7 +17,7 @@ internal class DeviceConfigDownSyncWorker @AssistedInject constructor( @Assisted params: WorkerParameters, private val configRepository: ConfigRepository, private val logoutUseCase: LogoutUseCase, - private val enrolmentRecordScheduler: EnrolmentRecordScheduler, + private val syncOrchestrator: SyncOrchestrator, @DispatcherBG private val dispatcher: CoroutineDispatcher, ) : SimCoroutineWorker(context, params) { @@ -36,7 +36,7 @@ internal class DeviceConfigDownSyncWorker @AssistedInject constructor( } else if (state.recordsToUpSync != null) { state.recordsToUpSync?.let { records -> crashlyticsLog("subject ids ${records.subjectIds.size}") - enrolmentRecordScheduler.upload(records.id, records.subjectIds) + syncOrchestrator.uploadEnrolmentRecords(records.id, records.subjectIds) } } success() diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/enrolments/EnrolmentRecordWorker.kt b/infra/sync/src/main/java/com/simprints/infra/sync/enrolments/EnrolmentRecordWorker.kt new file mode 100644 index 0000000000..00124ee5e1 --- /dev/null +++ b/infra/sync/src/main/java/com/simprints/infra/sync/enrolments/EnrolmentRecordWorker.kt @@ -0,0 +1,45 @@ +package com.simprints.infra.sync.enrolments + +import android.content.Context +import androidx.hilt.work.HiltWorker +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import com.simprints.core.DispatcherIO +import com.simprints.infra.config.store.ConfigRepository +import com.simprints.infra.enrolment.records.store.EnrolmentRecordRepository +import com.simprints.infra.sync.SyncConstants +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext + +@HiltWorker +class EnrolmentRecordWorker @AssistedInject constructor( + @Assisted context: Context, + @Assisted params: WorkerParameters, + private val enrolmentRecordRepository: EnrolmentRecordRepository, + private val configRepository: ConfigRepository, + @DispatcherIO private val dispatcher: CoroutineDispatcher, +) : CoroutineWorker(context, params) { + + override suspend fun doWork(): Result = withContext(dispatcher) { + try { + val instructionId = + inputData.getString(SyncConstants.RECORD_UPLOAD_INPUT_ID_NAME) + ?: throw IllegalArgumentException("input required") + val subjectIds = + inputData.getStringArray(SyncConstants.RECORD_UPLOAD_INPUT_SUBJECT_IDS_NAME) + ?: throw IllegalArgumentException("input required") + + enrolmentRecordRepository.uploadRecords(subjectIds.toList()) + + configRepository.updateDeviceConfiguration { + it.apply { it.lastInstructionId = instructionId } + } + + Result.success() + } catch (e: Exception) { + Result.retry() + } + } +} diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt index 348fe21fde..1845a23e14 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt @@ -8,8 +8,11 @@ import com.simprints.infra.sync.SyncConstants.DEVICE_SYNC_WORK_NAME import com.simprints.infra.sync.SyncConstants.DEVICE_SYNC_WORK_NAME_ONE_TIME import com.simprints.infra.sync.SyncConstants.IMAGE_UP_SYNC_WORK_NAME import com.simprints.infra.sync.SyncConstants.PROJECT_SYNC_WORK_NAME +import com.simprints.infra.sync.SyncConstants.RECORD_UPLOAD_INPUT_ID_NAME +import com.simprints.infra.sync.SyncConstants.RECORD_UPLOAD_INPUT_SUBJECT_IDS_NAME import com.simprints.infra.sync.usecase.CleanupDeprecatedWorkersUseCase import io.mockk.MockKAnnotations +import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.verify @@ -82,7 +85,11 @@ class SyncOrchestratorImplTest { syncOrchestrator.startDeviceSync() verify { - workManager.enqueueUniqueWork(DEVICE_SYNC_WORK_NAME_ONE_TIME, any(), any()) + workManager.enqueueUniqueWork( + DEVICE_SYNC_WORK_NAME_ONE_TIME, + any(), + any() + ) } } @@ -99,10 +106,37 @@ class SyncOrchestratorImplTest { } } + @Test + fun `schedules record upload`() = runTest { + syncOrchestrator.uploadEnrolmentRecords(INSTRUCTION_ID, listOf(SUBJECT_ID)) + + coVerify(exactly = 1) { + workManager.enqueueUniqueWork( + any(), + any(), + match { oneTimeWorkRequest -> + val subjectIdsInput = oneTimeWorkRequest.workSpec.input.getStringArray( + RECORD_UPLOAD_INPUT_SUBJECT_IDS_NAME + ) + val instructionIdInput = oneTimeWorkRequest.workSpec.input.getString( + RECORD_UPLOAD_INPUT_ID_NAME + ) + instructionIdInput == INSTRUCTION_ID && + subjectIdsInput.contentEquals(arrayOf(SUBJECT_ID)) + } + ) + } + } + @Test fun `delegates worker cleanup requests`() = runTest { syncOrchestrator.cleanupWorkers() verify { cleanupDeprecatedWorkers.invoke() } } + companion object { + + private const val INSTRUCTION_ID = "id" + private const val SUBJECT_ID = "subjectId" + } } diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/config/worker/DeviceConfigDownSyncWorkerTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/config/worker/DeviceConfigDownSyncWorkerTest.kt index 547401978c..5a0f0edd1e 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/config/worker/DeviceConfigDownSyncWorkerTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/config/worker/DeviceConfigDownSyncWorkerTest.kt @@ -6,8 +6,7 @@ import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.models.DeviceState import com.simprints.infra.config.store.models.UpSyncEnrolmentRecords import com.simprints.infra.sync.config.usecase.LogoutUseCase -import com.simprints.infra.enrolment.records.sync.worker.EnrolmentRecordScheduler -import com.simprints.infra.sync.config.worker.DeviceConfigDownSyncWorker +import com.simprints.infra.sync.SyncOrchestrator import com.simprints.testtools.common.coroutines.TestCoroutineRule import io.mockk.MockKAnnotations import io.mockk.coEvery @@ -29,7 +28,7 @@ class DeviceConfigDownSyncWorkerTest { private lateinit var logoutUseCase: LogoutUseCase @MockK - private lateinit var enrolmentRecordScheduler: EnrolmentRecordScheduler + private lateinit var syncOrchestrator: SyncOrchestrator @get:Rule val testCoroutineRule = TestCoroutineRule() @@ -45,7 +44,7 @@ class DeviceConfigDownSyncWorkerTest { params = mockk(relaxed = true), configRepository = configRepository, logoutUseCase = logoutUseCase, - enrolmentRecordScheduler = enrolmentRecordScheduler, + syncOrchestrator = syncOrchestrator, dispatcher = testCoroutineRule.testCoroutineDispatcher, ) } @@ -61,7 +60,7 @@ class DeviceConfigDownSyncWorkerTest { assertThat(result).isEqualTo(ListenableWorker.Result.success()) coVerify(exactly = 0) { logoutUseCase.invoke() } - verify(exactly = 0) { enrolmentRecordScheduler.upload(any(), any()) } + verify(exactly = 0) { syncOrchestrator.uploadEnrolmentRecords(any(), any()) } } @Test @@ -76,7 +75,7 @@ class DeviceConfigDownSyncWorkerTest { assertThat(result).isEqualTo(ListenableWorker.Result.success()) coVerify { logoutUseCase.invoke() } - verify(exactly = 0) { enrolmentRecordScheduler.upload(any(), any()) } + verify(exactly = 0) { syncOrchestrator.uploadEnrolmentRecords(any(), any()) } } @Test @@ -91,6 +90,6 @@ class DeviceConfigDownSyncWorkerTest { assertThat(result).isEqualTo(ListenableWorker.Result.success()) coVerify(exactly = 0) { logoutUseCase.invoke() } - verify { enrolmentRecordScheduler.upload(any(), any()) } + verify { syncOrchestrator.uploadEnrolmentRecords(any(), any()) } } } diff --git a/infra/enrolment-records-sync/src/test/java/com/simprints/infra/enrolment/records/worker/EnrolmentRecordWorkerTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/enrolments/EnrolmentRecordWorkerTest.kt similarity index 77% rename from infra/enrolment-records-sync/src/test/java/com/simprints/infra/enrolment/records/worker/EnrolmentRecordWorkerTest.kt rename to infra/sync/src/test/java/com/simprints/infra/sync/enrolments/EnrolmentRecordWorkerTest.kt index 36077c24cb..3b2f04dfd3 100644 --- a/infra/enrolment-records-sync/src/test/java/com/simprints/infra/enrolment/records/worker/EnrolmentRecordWorkerTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/enrolments/EnrolmentRecordWorkerTest.kt @@ -1,4 +1,4 @@ -package com.simprints.infra.enrolment.records.worker +package com.simprints.infra.sync.enrolments import androidx.work.WorkerParameters import androidx.work.workDataOf @@ -6,9 +6,12 @@ import com.google.common.truth.Truth.assertThat import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.models.DeviceConfiguration import com.simprints.infra.enrolment.records.store.EnrolmentRecordRepository -import com.simprints.infra.enrolment.records.sync.worker.EnrolmentRecordSchedulerImpl -import com.simprints.infra.enrolment.records.sync.worker.EnrolmentRecordWorker -import io.mockk.* +import com.simprints.infra.sync.SyncConstants +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.slot import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Test @@ -24,11 +27,11 @@ class EnrolmentRecordWorkerTest { private val configRepository = mockk() private val params = mockk(relaxed = true) { every { inputData } returns workDataOf( - EnrolmentRecordSchedulerImpl.INPUT_ID_NAME to INSTRUCTION_ID, - EnrolmentRecordSchedulerImpl.INPUT_SUBJECT_IDS_NAME to arrayOf(SUBJECT_ID), + SyncConstants.RECORD_UPLOAD_INPUT_ID_NAME to INSTRUCTION_ID, + SyncConstants.RECORD_UPLOAD_INPUT_SUBJECT_IDS_NAME to arrayOf(SUBJECT_ID), ) } - private val worker = EnrolmentRecordWorker( + private val worker = com.simprints.infra.sync.enrolments.EnrolmentRecordWorker( mockk(relaxed = true), params, repository, diff --git a/settings.gradle.kts b/settings.gradle.kts index 68c2ced543..f28d94a576 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -133,5 +133,4 @@ include( ":infra:ui-base", ":infra:sync", ":infra:event-sync", - ":infra:enrolment-records-sync", ) From 6675b54dbc653ff248e40388666ca7b85a2fd405 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Tue, 20 Feb 2024 14:12:19 +0200 Subject: [PATCH 025/197] MS-205 Move firmware update worker into sync module --- fingerprint/infra/scanner/build.gradle.kts | 15 ---- .../worker/FirmwareFileUpdateScheduler.kt | 74 ---------------- .../worker/FirmwareFileUpdateSchedulerTest.kt | 87 ------------------- infra/sync/build.gradle.kts | 5 ++ .../com/simprints/infra/sync/SyncConstants.kt | 3 + .../infra/sync/SyncOrchestratorImpl.kt | 23 ++++- .../infra/sync/extensions/WorkManager.ext.kt | 3 + .../firmware}/FirmwareFileUpdateWorker.kt | 6 +- .../ShouldScheduleFirmwareUpdateUseCase.kt | 16 ++++ .../CleanupDeprecatedWorkersUseCase.kt | 1 + .../infra/sync/SyncOrchestratorImplTest.kt | 23 +++++ .../firmware}/FirmwareFileUpdateWorkerTest.kt | 2 +- ...ShouldScheduleFirmwareUpdateUseCaseTest.kt | 51 +++++++++++ 13 files changed, 125 insertions(+), 184 deletions(-) delete mode 100644 fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/data/worker/FirmwareFileUpdateScheduler.kt delete mode 100644 fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/data/worker/FirmwareFileUpdateSchedulerTest.kt rename {fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/data/worker => infra/sync/src/main/java/com/simprints/infra/sync/firmware}/FirmwareFileUpdateWorker.kt (87%) create mode 100644 infra/sync/src/main/java/com/simprints/infra/sync/firmware/ShouldScheduleFirmwareUpdateUseCase.kt rename {fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/data/worker => infra/sync/src/test/java/com/simprints/infra/sync/firmware}/FirmwareFileUpdateWorkerTest.kt (97%) create mode 100644 infra/sync/src/test/java/com/simprints/infra/sync/firmware/ShouldScheduleFirmwareUpdateUseCaseTest.kt diff --git a/fingerprint/infra/scanner/build.gradle.kts b/fingerprint/infra/scanner/build.gradle.kts index 6a0b8898ab..87ad85bff0 100644 --- a/fingerprint/infra/scanner/build.gradle.kts +++ b/fingerprint/infra/scanner/build.gradle.kts @@ -8,20 +8,6 @@ android { defaultConfig { consumerProguardFiles("consumer-rules.pro") } - - buildTypes { - getByName("release") { - buildConfigField("long", "FIRMWARE_UPDATE_WORKER_INTERVAL_MINUTES", "1440L") - } - - getByName("staging") { - buildConfigField("long", "FIRMWARE_UPDATE_WORKER_INTERVAL_MINUTES", "15L") - } - - getByName("debug") { - buildConfigField("long", "FIRMWARE_UPDATE_WORKER_INTERVAL_MINUTES", "15L") - } - } } dependencies { @@ -29,7 +15,6 @@ dependencies { implementation(libs.kotlin.reflect) implementation(libs.kotlin.coroutine.rx2.adapter) - implementation(libs.workManager.work) implementation(libs.retrofit.core) runtimeOnly(libs.jackson.core) diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/data/worker/FirmwareFileUpdateScheduler.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/data/worker/FirmwareFileUpdateScheduler.kt deleted file mode 100644 index 6f68a8da8b..0000000000 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/data/worker/FirmwareFileUpdateScheduler.kt +++ /dev/null @@ -1,74 +0,0 @@ -package com.simprints.fingerprint.infra.scanner.data.worker - -import android.content.Context -import androidx.work.Constraints -import androidx.work.ExistingPeriodicWorkPolicy -import androidx.work.NetworkType -import androidx.work.PeriodicWorkRequest -import androidx.work.PeriodicWorkRequestBuilder -import androidx.work.WorkManager -import com.simprints.core.ExternalScope -import com.simprints.fingerprint.infra.scanner.BuildConfig -import com.simprints.infra.config.store.ConfigRepository -import com.simprints.infra.config.store.models.FingerprintConfiguration -import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import java.util.concurrent.TimeUnit -import javax.inject.Inject - -/** - * This class is responsible for scheduling the worker [FirmwareFileUpdateWorker] that updates the - * firmware version, if any updates available, on the device. - * - * @property context the application context used for scheduling the worker - * @property configRepository the configuration manager for checking the version of the connected Vero scanner - */ -class FirmwareFileUpdateScheduler @Inject constructor( - @ApplicationContext private val context: Context, - private val configRepository: ConfigRepository, - @ExternalScope private val externalScope: CoroutineScope, -) { - - fun scheduleOrCancelWorkIfNecessary() { - externalScope.launch { - if (configRepository.getProjectConfiguration().fingerprint?.allowedScanners?.contains( - FingerprintConfiguration.VeroGeneration.VERO_2 - ) == true - ) { - scheduleWork() - } else { - cancelWork() - } - } - } - - private fun scheduleWork() { - WorkManager - .getInstance(context) - .enqueueUniquePeriodicWork(WORK_NAME, ExistingPeriodicWorkPolicy.UPDATE, buildWork()) - } - - private fun cancelWork() { - WorkManager - .getInstance(context) - .cancelUniqueWork(WORK_NAME) - } - - private fun buildWork(): PeriodicWorkRequest = - PeriodicWorkRequestBuilder(REPEAT_INTERVAL, REPEAT_INTERVAL_UNIT) - .setConstraints(getConstraints()) - .build() - - private fun getConstraints() = - Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .build() - - companion object { - - const val WORK_NAME = "firmware-file-update-work" - const val REPEAT_INTERVAL = BuildConfig.FIRMWARE_UPDATE_WORKER_INTERVAL_MINUTES - val REPEAT_INTERVAL_UNIT = TimeUnit.MINUTES - } -} diff --git a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/data/worker/FirmwareFileUpdateSchedulerTest.kt b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/data/worker/FirmwareFileUpdateSchedulerTest.kt deleted file mode 100644 index c08df94500..0000000000 --- a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/data/worker/FirmwareFileUpdateSchedulerTest.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.simprints.fingerprint.infra.scanner.data.worker - -import androidx.work.WorkManager -import com.simprints.infra.config.store.ConfigRepository -import com.simprints.infra.config.store.models.FingerprintConfiguration -import com.simprints.testtools.common.coroutines.TestCoroutineRule -import io.mockk.coEvery -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkStatic -import io.mockk.unmockkAll -import io.mockk.verifySequence -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.test.runTest -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test - -class FirmwareFileUpdateSchedulerTest { - - @get:Rule - val testCoroutineRule = TestCoroutineRule() - - private val workManagerMock = mockk() - private val fingerprintConfiguration = mockk() - private val configRepository = mockk { - coEvery { getProjectConfiguration() } returns mockk { - every { fingerprint } returns fingerprintConfiguration - } - } - - private val firmwareFileUpdateScheduler = - FirmwareFileUpdateScheduler( - mockk(), - configRepository, - CoroutineScope(testCoroutineRule.testCoroutineDispatcher), - ) - - @Before - fun setUp() { - mockkStatic(WorkManager::class) - every { WorkManager.getInstance(any()) } returns workManagerMock - } - - @Test - fun projectIsOnVero2Only_schedulesWork() = runTest { - every { fingerprintConfiguration.allowedScanners } returns listOf( - FingerprintConfiguration.VeroGeneration.VERO_2 - ) - every { workManagerMock.enqueueUniquePeriodicWork(any(), any(), any()) } returns mockk() - - firmwareFileUpdateScheduler.scheduleOrCancelWorkIfNecessary() - - verifySequence { workManagerMock.enqueueUniquePeriodicWork(any(), any(), any()) } - } - - @Test - fun projectIsBothVero1AndVero2_schedulesWork() = runTest { - every { fingerprintConfiguration.allowedScanners } returns listOf( - FingerprintConfiguration.VeroGeneration.VERO_1, - FingerprintConfiguration.VeroGeneration.VERO_2 - ) - every { workManagerMock.enqueueUniquePeriodicWork(any(), any(), any()) } returns mockk() - - firmwareFileUpdateScheduler.scheduleOrCancelWorkIfNecessary() - - verifySequence { workManagerMock.enqueueUniquePeriodicWork(any(), any(), any()) } - } - - @Test - fun projectIsOnVero1Only_cancelsScheduledWork() = runTest { - every { fingerprintConfiguration.allowedScanners } returns listOf( - FingerprintConfiguration.VeroGeneration.VERO_1 - ) - every { workManagerMock.cancelUniqueWork(any()) } returns mockk() - - firmwareFileUpdateScheduler.scheduleOrCancelWorkIfNecessary() - - verifySequence { workManagerMock.cancelUniqueWork(any()) } - } - - @After - fun tearDown() { - unmockkAll() - } -} diff --git a/infra/sync/build.gradle.kts b/infra/sync/build.gradle.kts index 1eac3680ba..39c3be5f7d 100644 --- a/infra/sync/build.gradle.kts +++ b/infra/sync/build.gradle.kts @@ -11,16 +11,19 @@ android { buildConfigField("long", "PROJECT_PERIODIC_WORKER_INTERVAL_MINUTES", "60L") buildConfigField("long", "DEVICE_PERIODIC_WORKER_INTERVAL_MINUTES", "30L") buildConfigField("long", "IMAGE_PERIODIC_WORKER_INTERVAL_MINUTES", "60L") + buildConfigField("long", "FIRMWARE_UPDATE_WORKER_INTERVAL_MINUTES", "1440L") } getByName("staging") { buildConfigField("long", "PROJECT_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") buildConfigField("long", "DEVICE_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") buildConfigField("long", "IMAGE_PERIODIC_WORKER_INTERVAL_MINUTES", "15LL") + buildConfigField("long", "FIRMWARE_UPDATE_WORKER_INTERVAL_MINUTES", "15LL") } getByName("debug") { buildConfigField("long", "PROJECT_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") buildConfigField("long", "DEVICE_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") buildConfigField("long", "IMAGE_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") + buildConfigField("long", "FIRMWARE_UPDATE_WORKER_INTERVAL_MINUTES", "15L") } } } @@ -34,5 +37,7 @@ dependencies { implementation(project(":infra:event-sync")) implementation(project(":infra:images")) + implementation(project(":fingerprint:infra:scanner")) + implementation(libs.workManager.work) } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt index c969ef2ceb..a15b7f9a9e 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt @@ -19,4 +19,7 @@ internal object SyncConstants { const val RECORD_UPLOAD_WORK_NAME = "upload-enrolment-record-work-one-time" const val RECORD_UPLOAD_INPUT_ID_NAME = "INPUT_ID_NAME" const val RECORD_UPLOAD_INPUT_SUBJECT_IDS_NAME = "INPUT_SUBJECT_IDS_NAME" + + const val FIRMWARE_UPDATE_WORK_NAME = "firmware-file-update-work-v2" + const val FIRMWARE_UPDATE_REPEAT_INTERVAL = BuildConfig.FIRMWARE_UPDATE_WORKER_INTERVAL_MINUTES } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt index 3ae13c8c79..a1e50410a9 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt @@ -9,13 +9,17 @@ import com.simprints.infra.sync.extensions.startWorker import com.simprints.infra.sync.config.worker.DeviceConfigDownSyncWorker import com.simprints.infra.sync.config.worker.ProjectConfigDownSyncWorker import com.simprints.infra.sync.enrolments.EnrolmentRecordWorker +import com.simprints.infra.sync.extensions.cancelWorkers +import com.simprints.infra.sync.firmware.FirmwareFileUpdateWorker import com.simprints.infra.sync.usecase.CleanupDeprecatedWorkersUseCase import com.simprints.infra.sync.images.ImageUpSyncWorker +import com.simprints.infra.sync.firmware.ShouldScheduleFirmwareUpdateUseCase import javax.inject.Inject internal class SyncOrchestratorImpl @Inject constructor( private val workManager: WorkManager, private val authStore: AuthStore, + private val shouldScheduleFirmwareUpdate: ShouldScheduleFirmwareUpdateUseCase, private val cleanupDeprecatedWorkers: CleanupDeprecatedWorkersUseCase, ) : SyncOrchestrator { @@ -34,14 +38,25 @@ internal class SyncOrchestratorImpl @Inject constructor( SyncConstants.IMAGE_UP_SYNC_REPEAT_INTERVAL, ) // TODO eventSyncManager.scheduleSync() - // TODO firmwareFileUpdateScheduler.scheduleOrCancelWorkIfNecessary() + + if (shouldScheduleFirmwareUpdate()) { + workManager.schedulePeriodicWorker( + SyncConstants.FIRMWARE_UPDATE_WORK_NAME, + SyncConstants.FIRMWARE_UPDATE_REPEAT_INTERVAL, + ) + } else { + workManager.cancelWorkers(SyncConstants.FIRMWARE_UPDATE_WORK_NAME) + } } } override suspend fun cancelBackgroundWork() { - workManager.cancelUniqueWork(SyncConstants.PROJECT_SYNC_WORK_NAME) - workManager.cancelUniqueWork(SyncConstants.DEVICE_SYNC_WORK_NAME) - workManager.cancelUniqueWork(SyncConstants.IMAGE_UP_SYNC_WORK_NAME) + workManager.cancelWorkers( + SyncConstants.PROJECT_SYNC_WORK_NAME, + SyncConstants.DEVICE_SYNC_WORK_NAME, + SyncConstants.IMAGE_UP_SYNC_WORK_NAME, + SyncConstants.FIRMWARE_UPDATE_WORK_NAME, + ) } override fun startDeviceSync() { diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt b/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt index b2e7a32434..5cb7047c36 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt @@ -36,3 +36,6 @@ internal inline fun WorkManager.startWorker( .build() ) +internal fun WorkManager.cancelWorkers(vararg workNames: String) { + workNames.forEach(this::cancelUniqueWork) +} diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/data/worker/FirmwareFileUpdateWorker.kt b/infra/sync/src/main/java/com/simprints/infra/sync/firmware/FirmwareFileUpdateWorker.kt similarity index 87% rename from fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/data/worker/FirmwareFileUpdateWorker.kt rename to infra/sync/src/main/java/com/simprints/infra/sync/firmware/FirmwareFileUpdateWorker.kt index 88cc829195..39745d17dd 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/data/worker/FirmwareFileUpdateWorker.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/firmware/FirmwareFileUpdateWorker.kt @@ -1,4 +1,4 @@ -package com.simprints.fingerprint.infra.scanner.data.worker +package com.simprints.infra.sync.firmware import android.content.Context import androidx.hilt.work.HiltWorker @@ -15,11 +15,11 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext /** - * This class is responsible for regularly downloading the latest versions of the firmware binaries, + * This class is responsible for downloading the latest versions of the firmware binaries, * if any update are available, ensuring that the latest versions are always on the phone. */ @HiltWorker -internal class FirmwareFileUpdateWorker @AssistedInject constructor( +class FirmwareFileUpdateWorker @AssistedInject constructor( @Assisted context: Context, @Assisted params: WorkerParameters, private val firmwareRepository: FirmwareRepository, diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/firmware/ShouldScheduleFirmwareUpdateUseCase.kt b/infra/sync/src/main/java/com/simprints/infra/sync/firmware/ShouldScheduleFirmwareUpdateUseCase.kt new file mode 100644 index 0000000000..f3f527c605 --- /dev/null +++ b/infra/sync/src/main/java/com/simprints/infra/sync/firmware/ShouldScheduleFirmwareUpdateUseCase.kt @@ -0,0 +1,16 @@ +package com.simprints.infra.sync.firmware + +import com.simprints.infra.config.store.ConfigRepository +import com.simprints.infra.config.store.models.FingerprintConfiguration +import javax.inject.Inject + +class ShouldScheduleFirmwareUpdateUseCase @Inject constructor( + private val configRepository: ConfigRepository, +) { + + suspend operator fun invoke(): Boolean = configRepository + .getProjectConfiguration() + .fingerprint + ?.allowedScanners + ?.contains(FingerprintConfiguration.VeroGeneration.VERO_2) == true +} diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/usecase/CleanupDeprecatedWorkersUseCase.kt b/infra/sync/src/main/java/com/simprints/infra/sync/usecase/CleanupDeprecatedWorkersUseCase.kt index 2d51903dc0..53fe44e54b 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/usecase/CleanupDeprecatedWorkersUseCase.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/usecase/CleanupDeprecatedWorkersUseCase.kt @@ -30,6 +30,7 @@ internal class CleanupDeprecatedWorkersUseCase @Inject constructor( "project-sync-work", // 2024.1.1 "device-sync-work", // 2024.1.1 "image-upsync-work-v2", // 2024.1.1 + "firmware-file-update-work", // 2024.1.1 ) private fun tagsForDeprecatedWorkers() = listOf( diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt index 1845a23e14..0cf8e961a3 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt @@ -6,12 +6,15 @@ import androidx.work.WorkManager import com.simprints.infra.authstore.AuthStore import com.simprints.infra.sync.SyncConstants.DEVICE_SYNC_WORK_NAME import com.simprints.infra.sync.SyncConstants.DEVICE_SYNC_WORK_NAME_ONE_TIME +import com.simprints.infra.sync.SyncConstants.FIRMWARE_UPDATE_WORK_NAME import com.simprints.infra.sync.SyncConstants.IMAGE_UP_SYNC_WORK_NAME import com.simprints.infra.sync.SyncConstants.PROJECT_SYNC_WORK_NAME import com.simprints.infra.sync.SyncConstants.RECORD_UPLOAD_INPUT_ID_NAME import com.simprints.infra.sync.SyncConstants.RECORD_UPLOAD_INPUT_SUBJECT_IDS_NAME +import com.simprints.infra.sync.firmware.ShouldScheduleFirmwareUpdateUseCase import com.simprints.infra.sync.usecase.CleanupDeprecatedWorkersUseCase import io.mockk.MockKAnnotations +import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK @@ -29,6 +32,9 @@ class SyncOrchestratorImplTest { @MockK private lateinit var authStore: AuthStore + @MockK + private lateinit var shouldScheduleFirmwareUpdate: ShouldScheduleFirmwareUpdateUseCase + @MockK private lateinit var cleanupDeprecatedWorkers: CleanupDeprecatedWorkersUseCase @@ -41,6 +47,7 @@ class SyncOrchestratorImplTest { syncOrchestrator = SyncOrchestratorImpl( workManager, authStore, + shouldScheduleFirmwareUpdate, cleanupDeprecatedWorkers, ) } @@ -48,6 +55,7 @@ class SyncOrchestratorImplTest { @Test fun `does not schedules any workers if not logged in`() = runTest { every { authStore.signedInProjectId } returns "" + coEvery { shouldScheduleFirmwareUpdate.invoke() } returns false syncOrchestrator.scheduleBackgroundWork() @@ -59,6 +67,7 @@ class SyncOrchestratorImplTest { @Test fun `schedules all necessary background workers if logged in`() = runTest { every { authStore.signedInProjectId } returns "projectId" + coEvery { shouldScheduleFirmwareUpdate.invoke() } returns true syncOrchestrator.scheduleBackgroundWork() @@ -66,6 +75,19 @@ class SyncOrchestratorImplTest { workManager.enqueueUniquePeriodicWork(PROJECT_SYNC_WORK_NAME, any(), any()) workManager.enqueueUniquePeriodicWork(DEVICE_SYNC_WORK_NAME, any(), any()) workManager.enqueueUniquePeriodicWork(IMAGE_UP_SYNC_WORK_NAME, any(), any()) + workManager.enqueueUniquePeriodicWork(FIRMWARE_UPDATE_WORK_NAME, any(), any()) + } + } + + @Test + fun `schedules cancel firmware update worker if no support for vero 2`() = runTest { + every { authStore.signedInProjectId } returns "projectId" + coEvery { shouldScheduleFirmwareUpdate.invoke() } returns false + + syncOrchestrator.scheduleBackgroundWork() + + verify { + workManager.cancelUniqueWork(FIRMWARE_UPDATE_WORK_NAME) } } @@ -77,6 +99,7 @@ class SyncOrchestratorImplTest { workManager.cancelUniqueWork(PROJECT_SYNC_WORK_NAME) workManager.cancelUniqueWork(DEVICE_SYNC_WORK_NAME) workManager.cancelUniqueWork(IMAGE_UP_SYNC_WORK_NAME) + workManager.cancelUniqueWork(FIRMWARE_UPDATE_WORK_NAME) } } diff --git a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/data/worker/FirmwareFileUpdateWorkerTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/firmware/FirmwareFileUpdateWorkerTest.kt similarity index 97% rename from fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/data/worker/FirmwareFileUpdateWorkerTest.kt rename to infra/sync/src/test/java/com/simprints/infra/sync/firmware/FirmwareFileUpdateWorkerTest.kt index fbb39e562c..ea87dfd5de 100644 --- a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/data/worker/FirmwareFileUpdateWorkerTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/firmware/FirmwareFileUpdateWorkerTest.kt @@ -1,4 +1,4 @@ -package com.simprints.fingerprint.infra.scanner.data.worker +package com.simprints.infra.sync.firmware import androidx.work.ListenableWorker.Result.Retry import androidx.work.ListenableWorker.Result.Success diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/firmware/ShouldScheduleFirmwareUpdateUseCaseTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/firmware/ShouldScheduleFirmwareUpdateUseCaseTest.kt new file mode 100644 index 0000000000..83c1aec91e --- /dev/null +++ b/infra/sync/src/test/java/com/simprints/infra/sync/firmware/ShouldScheduleFirmwareUpdateUseCaseTest.kt @@ -0,0 +1,51 @@ +package com.simprints.infra.sync.firmware + +import com.google.common.truth.Truth.assertThat +import com.simprints.infra.config.store.ConfigRepository +import com.simprints.infra.config.store.models.FingerprintConfiguration +import io.mockk.MockKAnnotations +import io.mockk.coEvery +import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test + +class ShouldScheduleFirmwareUpdateUseCaseTest { + + @MockK + private lateinit var configRepository: ConfigRepository + + private lateinit var useCase: ShouldScheduleFirmwareUpdateUseCase + + @Before + fun setup() { + MockKAnnotations.init(this, relaxed = true) + useCase = ShouldScheduleFirmwareUpdateUseCase(configRepository) + } + + @Test + fun `should return true if Vero 2 is allowed`() = runTest { + coEvery { + configRepository.getProjectConfiguration().fingerprint?.allowedScanners + } returns listOf(FingerprintConfiguration.VeroGeneration.VERO_2) + + assertThat(useCase()).isTrue() + } + + @Test + fun `should return false if only Vero 1 is allowed`() = runTest { + coEvery { + configRepository.getProjectConfiguration().fingerprint?.allowedScanners + } returns listOf(FingerprintConfiguration.VeroGeneration.VERO_1) + + assertThat(useCase()).isFalse() + } + + @Test + fun `should return false if no fingerprint config`() = runTest { + coEvery { configRepository.getProjectConfiguration().fingerprint } returns null + + assertThat(useCase()).isFalse() + } + +} From b0d54c6c06e91c9464a1ccdcea5309e5a41474d9 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Tue, 20 Feb 2024 16:03:29 +0200 Subject: [PATCH 026/197] MS-205 Reroute event sync worker scheduling thru the new sync module --- .../feature/dashboard/debug/DebugFragment.kt | 11 +- .../dashboard/logout/usecase/LogoutUseCase.kt | 5 +- .../dashboard/main/sync/SyncViewModel.kt | 4 +- .../settings/syncinfo/SyncInfoViewModel.kt | 4 +- .../ModuleSelectionViewModel.kt | 8 +- .../dashboard/main/sync/SyncViewModelTest.kt | 11 +- .../syncinfo/SyncInfoViewModelTest.kt | 7 +- .../ModuleSelectionViewModelTest.kt | 22 ++-- .../feature/logincheck/LoginCheckViewModel.kt | 14 +-- .../usecases/CancelBackgroundSyncUseCase.kt | 16 --- .../StartBackgroundSyncIfNeededUseCase.kt | 5 +- .../logincheck/LoginCheckViewModelTest.kt | 19 +-- .../CancelBackgroundSyncUseCaseTest.kt | 44 ------- .../StartBackgroundSyncUseCaseTest.kt | 12 +- .../events/down/EventDownSyncResetService.kt | 6 +- .../infra/eventsync/EventSyncManager.kt | 10 +- .../infra/eventsync/EventSyncManagerImpl.kt | 109 ++++-------------- .../sync/common/WorkRequestBuilder.ext.kt | 7 -- .../sync/master/EventSyncMasterWorker.kt | 2 +- .../infra/eventsync/EventSyncManagerTest.kt | 71 +----------- infra/sync/build.gradle.kts | 3 + .../com/simprints/infra/sync/SyncConstants.kt | 4 + .../simprints/infra/sync/SyncOrchestrator.kt | 6 + .../infra/sync/SyncOrchestratorImpl.kt | 39 ++++++- .../sync/config/usecase/LogoutUseCase.kt | 5 +- .../infra/sync/extensions/WorkManager.ext.kt | 4 + .../CleanupDeprecatedWorkersUseCase.kt | 2 + .../infra/sync/SyncOrchestratorImplTest.kt | 74 ++++++++++++ .../sync/config/usecase/LogoutUseCaseTest.kt | 11 +- 29 files changed, 224 insertions(+), 311 deletions(-) delete mode 100644 feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCase.kt delete mode 100644 feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCaseTest.kt diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/debug/DebugFragment.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/debug/DebugFragment.kt index 0557e8a08e..05ca056c16 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/debug/DebugFragment.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/debug/DebugFragment.kt @@ -79,15 +79,15 @@ internal class DebugFragment : Fragment(R.layout.fragment_debug) { } binding.syncStart.setOnClickListener { - eventSyncManager.sync() + syncOrchestrator.startEventSync() } binding.syncStop.setOnClickListener { - eventSyncManager.stop() + syncOrchestrator.stopEventSync() } binding.syncSchedule.setOnClickListener { - eventSyncManager.scheduleSync() + syncOrchestrator.rescheduleEventSync() } binding.syncConfig.setOnClickListener { @@ -125,8 +125,9 @@ internal class DebugFragment : Fragment(R.layout.fragment_debug) { binding.cleanAll.setOnClickListener { lifecycleScope.launch(dispatcher) { - eventSyncManager.cancelScheduledSync() - eventSyncManager.stop() + syncOrchestrator.stopEventSync() + syncOrchestrator.cancelEventSync() + eventRepository.deleteAll() eventSyncManager.resetDownSyncInfo() enrolmentRecordRepository.deleteAll() diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCase.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCase.kt index a35592d8a9..c206dac577 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCase.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCase.kt @@ -1,21 +1,18 @@ package com.simprints.feature.dashboard.logout.usecase import com.simprints.infra.authlogic.AuthManager -import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.sync.SyncOrchestrator import javax.inject.Inject internal class LogoutUseCase @Inject constructor( private val syncOrchestrator: SyncOrchestrator, - private val eventSyncManager: EventSyncManager, private val authManager: AuthManager, ) { suspend operator fun invoke() { // Cancel all background sync - eventSyncManager.cancelScheduledSync() syncOrchestrator.cancelBackgroundWork() - eventSyncManager.deleteSyncInfo() + syncOrchestrator.deleteEventSyncInfo() authManager.signOut() } } diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncViewModel.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncViewModel.kt index dfd71f8f7b..7cf3542674 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncViewModel.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/sync/SyncViewModel.kt @@ -33,6 +33,7 @@ import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.eventsync.status.models.EventSyncState import com.simprints.infra.eventsync.status.models.EventSyncWorkerState import com.simprints.infra.network.ConnectivityTracker +import com.simprints.infra.sync.SyncOrchestrator import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob @@ -44,6 +45,7 @@ import javax.inject.Inject @HiltViewModel internal class SyncViewModel @Inject constructor( private val eventSyncManager: EventSyncManager, + private val syncOrchestrator: SyncOrchestrator, private val connectivityTracker: ConnectivityTracker, private val configRepository: ConfigRepository, private val timeHelper: TimeHelper, @@ -108,7 +110,7 @@ internal class SyncViewModel @Inject constructor( fun sync() { _syncCardLiveData.postValue(SyncConnecting(null, 0, null)) - eventSyncManager.sync() + syncOrchestrator.startEventSync() } private fun startInitialSyncIfRequired() { diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt index c3cbb1a5a4..b5c8468e85 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt @@ -25,6 +25,7 @@ import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.config.store.tokenization.TokenizationProcessor import com.simprints.infra.enrolment.records.store.EnrolmentRecordRepository import com.simprints.infra.network.ConnectivityTracker +import com.simprints.infra.sync.SyncOrchestrator import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -40,6 +41,7 @@ internal class SyncInfoViewModel @Inject constructor( private val authStore: AuthStore, private val imageRepository: ImageRepository, private val eventSyncManager: EventSyncManager, + private val syncOrchestrator: SyncOrchestrator, private val tokenizationProcessor: TokenizationProcessor ) : ViewModel() { @@ -121,7 +123,7 @@ internal class SyncInfoViewModel @Inject constructor( } fun forceSync() { - eventSyncManager.sync() + syncOrchestrator.startEventSync() // There is a delay between starting sync and lastSyncState // reporting it so this prevents starting multiple syncs by accident _isSyncAvailable.postValue(false) diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/moduleselection/ModuleSelectionViewModel.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/moduleselection/ModuleSelectionViewModel.kt index 5685f2bd3c..229ebaba67 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/moduleselection/ModuleSelectionViewModel.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/moduleselection/ModuleSelectionViewModel.kt @@ -15,7 +15,7 @@ import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.models.SettingsPasswordConfig import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.config.store.tokenization.TokenizationProcessor -import com.simprints.infra.eventsync.EventSyncManager +import com.simprints.infra.sync.SyncOrchestrator import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -25,7 +25,7 @@ import javax.inject.Inject internal class ModuleSelectionViewModel @Inject constructor( private val authStore: AuthStore, private val moduleRepository: ModuleRepository, - private val eventSyncManager: EventSyncManager, + private val syncOrchestrator: SyncOrchestrator, private val configRepository: ConfigRepository, private val tokenizationProcessor: TokenizationProcessor, @ExternalScope private val externalScope: CoroutineScope, @@ -108,8 +108,8 @@ internal class ModuleSelectionViewModel @Inject constructor( module.copy(name = encryptedName) } moduleRepository.saveModules(modules) - eventSyncManager.stop() - eventSyncManager.sync() + syncOrchestrator.stopEventSync() + syncOrchestrator.startEventSync() } } diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncViewModelTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncViewModelTest.kt index 4a38191de7..19754dc4c8 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncViewModelTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncViewModelTest.kt @@ -30,6 +30,7 @@ import com.simprints.infra.eventsync.status.models.EventSyncState import com.simprints.infra.eventsync.status.models.EventSyncWorkerState import com.simprints.infra.eventsync.status.models.EventSyncWorkerType import com.simprints.infra.network.ConnectivityTracker +import com.simprints.infra.sync.SyncOrchestrator import com.simprints.testtools.common.coroutines.TestCoroutineRule import com.simprints.testtools.common.livedata.getOrAwaitValue import io.mockk.MockKAnnotations @@ -69,6 +70,9 @@ internal class SyncViewModelTest { @MockK lateinit var eventSyncManager: EventSyncManager + @MockK + lateinit var syncOrchestrator: SyncOrchestrator + @MockK lateinit var connectivityTracker: ConnectivityTracker @@ -117,7 +121,7 @@ internal class SyncViewModelTest { val viewModel = initViewModel() - verify(exactly = 1) { eventSyncManager.sync() } + verify(exactly = 1) { syncOrchestrator.startEventSync() } assertThat(viewModel.syncCardLiveData.value).isEqualTo(SyncConnecting(null, 0, null)) } @@ -129,7 +133,7 @@ internal class SyncViewModelTest { val viewModel = initViewModel() - verify(exactly = 1) { eventSyncManager.sync() } + verify(exactly = 1) { syncOrchestrator.startEventSync() } assertThat(viewModel.syncCardLiveData.value).isEqualTo(SyncConnecting(null, 0, null)) } @@ -147,7 +151,7 @@ internal class SyncViewModelTest { initViewModel() - verify(exactly = 0) { eventSyncManager.sync() } + verify(exactly = 0) { syncOrchestrator.startEventSync() } } @Test @@ -379,6 +383,7 @@ internal class SyncViewModelTest { private fun initViewModel(): SyncViewModel = SyncViewModel( eventSyncManager = eventSyncManager, + syncOrchestrator = syncOrchestrator, connectivityTracker = connectivityTracker, configRepository = configRepository, timeHelper = timeHelper, diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt index 42cd10010b..6ba3349417 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt @@ -24,6 +24,7 @@ import com.simprints.infra.eventsync.status.models.EventSyncWorkerState import com.simprints.infra.eventsync.status.models.EventSyncWorkerType import com.simprints.infra.images.ImageRepository import com.simprints.infra.network.ConnectivityTracker +import com.simprints.infra.sync.SyncOrchestrator import com.simprints.testtools.common.coroutines.TestCoroutineRule import com.simprints.testtools.common.livedata.getOrAwaitValue import com.simprints.testtools.common.livedata.getOrAwaitValues @@ -71,6 +72,9 @@ class SyncInfoViewModelTest { @MockK private lateinit var eventSyncManager: EventSyncManager + @MockK + private lateinit var syncOrchestrator: SyncOrchestrator + @MockK private lateinit var project: Project @@ -101,6 +105,7 @@ class SyncInfoViewModelTest { authStore = authStore, imageRepository = imageRepository, eventSyncManager = eventSyncManager, + syncOrchestrator = syncOrchestrator, tokenizationProcessor = tokenizationProcessor ) } @@ -295,7 +300,7 @@ class SyncInfoViewModelTest { fun `should invoke sync manager when sync is requested`() = runTest { viewModel.forceSync() - verify(exactly = 1) { eventSyncManager.sync() } + verify(exactly = 1) { syncOrchestrator.startEventSync() } assertThat(viewModel.isSyncAvailable.getOrAwaitValue()).isEqualTo(false) } diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/moduleselection/ModuleSelectionViewModelTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/moduleselection/ModuleSelectionViewModelTest.kt index 449998094c..e1b5742b5b 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/moduleselection/ModuleSelectionViewModelTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/moduleselection/ModuleSelectionViewModelTest.kt @@ -17,6 +17,7 @@ import com.simprints.infra.config.store.models.SettingsPasswordConfig import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.config.store.tokenization.TokenizationProcessor import com.simprints.infra.eventsync.EventSyncManager +import com.simprints.infra.sync.SyncOrchestrator import com.simprints.testtools.common.coroutines.TestCoroutineRule import com.simprints.testtools.common.livedata.getOrAwaitValue import com.simprints.testtools.common.syntax.assertThrows @@ -37,19 +38,22 @@ class ModuleSelectionViewModelTest { @get:Rule val testCoroutineRule = TestCoroutineRule() - @MockK(relaxed = true) + @MockK private lateinit var repository: ModuleRepository - @MockK(relaxed = true) + @MockK private lateinit var eventSyncManager: EventSyncManager - @MockK(relaxed = true) + @MockK + private lateinit var syncOrchestrator: SyncOrchestrator + + @MockK private lateinit var configRepository: ConfigRepository - @MockK(relaxed = true) + @MockK private lateinit var tokenizationProcessor: TokenizationProcessor - @MockK(relaxed = true) + @MockK private lateinit var authStore: AuthStore @MockK @@ -59,7 +63,7 @@ class ModuleSelectionViewModelTest { @Before fun setUp() { - MockKAnnotations.init(this) + MockKAnnotations.init(this, relaxed = true) val modulesDefault = listOf( Module("a".asTokenizableEncrypted(), false), @@ -87,7 +91,7 @@ class ModuleSelectionViewModelTest { viewModel = ModuleSelectionViewModel( authStore = authStore, moduleRepository = repository, - eventSyncManager = eventSyncManager, + syncOrchestrator = syncOrchestrator, configRepository = configRepository, tokenizationProcessor = tokenizationProcessor, externalScope = CoroutineScope(testCoroutineRule.testCoroutineDispatcher), @@ -180,8 +184,8 @@ class ModuleSelectionViewModelTest { viewModel.saveModules() coVerify(exactly = 1) { repository.saveModules(updatedModules) } - coVerify(exactly = 1) { eventSyncManager.stop() } - coVerify(exactly = 1) { eventSyncManager.sync() } + coVerify(exactly = 1) { syncOrchestrator.stopEventSync() } + coVerify(exactly = 1) { syncOrchestrator.startEventSync() } } @Test diff --git a/feature/login-check/src/main/java/com/simprints/feature/logincheck/LoginCheckViewModel.kt b/feature/login-check/src/main/java/com/simprints/feature/logincheck/LoginCheckViewModel.kt index 1aab827a99..7d318c15d3 100644 --- a/feature/login-check/src/main/java/com/simprints/feature/logincheck/LoginCheckViewModel.kt +++ b/feature/login-check/src/main/java/com/simprints/feature/logincheck/LoginCheckViewModel.kt @@ -10,24 +10,16 @@ import com.simprints.core.livedata.send import com.simprints.feature.login.LoginError import com.simprints.feature.login.LoginResult import com.simprints.feature.logincheck.usecases.* -import com.simprints.feature.logincheck.usecases.AddAuthorizationEventUseCase -import com.simprints.feature.logincheck.usecases.CancelBackgroundSyncUseCase -import com.simprints.feature.logincheck.usecases.ExtractCrashKeysUseCase -import com.simprints.feature.logincheck.usecases.ExtractParametersForAnalyticsUseCase -import com.simprints.feature.logincheck.usecases.IsUserSignedInUseCase import com.simprints.feature.logincheck.usecases.IsUserSignedInUseCase.SignedInState.MISMATCHED_PROJECT_ID import com.simprints.feature.logincheck.usecases.IsUserSignedInUseCase.SignedInState.NOT_SIGNED_IN import com.simprints.feature.logincheck.usecases.IsUserSignedInUseCase.SignedInState.SIGNED_IN -import com.simprints.feature.logincheck.usecases.ReportActionRequestEventsUseCase -import com.simprints.feature.logincheck.usecases.StartBackgroundSyncUseCase -import com.simprints.feature.logincheck.usecases.UpdateProjectInCurrentSessionUseCase -import com.simprints.feature.logincheck.usecases.UpdateSessionScopePayloadUseCase import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.models.ProjectState import com.simprints.infra.logging.Simber import com.simprints.infra.orchestration.data.ActionRequest import com.simprints.infra.security.SecurityManager import com.simprints.infra.security.exceptions.RootedDeviceException +import com.simprints.infra.sync.SyncOrchestrator import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -45,7 +37,7 @@ class LoginCheckViewModel @Inject internal constructor( private val isUserSignedIn: IsUserSignedInUseCase, private val configRepository: ConfigRepository, private val startBackgroundSync: StartBackgroundSyncUseCase, - private val cancelBackgroundSync: CancelBackgroundSyncUseCase, + private val syncOrchestrator: SyncOrchestrator, private val updateDatabaseCountsInCurrentSession: UpdateSessionScopePayloadUseCase, private val updateProjectInCurrentSession: UpdateProjectInCurrentSessionUseCase, private val updateStoredUserId: UpdateStoredUserIdUseCase, @@ -101,7 +93,7 @@ class LoginCheckViewModel @Inject internal constructor( cachedRequest = actionRequest loginAlreadyTried.set(true) - cancelBackgroundSync.invoke() + syncOrchestrator.cancelBackgroundWork() _showLoginFlow.send(actionRequest) } diff --git a/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCase.kt b/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCase.kt deleted file mode 100644 index 48d6f9adce..0000000000 --- a/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCase.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.simprints.feature.logincheck.usecases - -import com.simprints.infra.eventsync.EventSyncManager -import com.simprints.infra.sync.SyncOrchestrator -import javax.inject.Inject - -internal class CancelBackgroundSyncUseCase @Inject constructor( - private val eventSyncManager: EventSyncManager, - private val syncOrchestrator: SyncOrchestrator, -) { - - suspend operator fun invoke() { - eventSyncManager.cancelScheduledSync() - syncOrchestrator.cancelBackgroundWork() - } -} diff --git a/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncIfNeededUseCase.kt b/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncIfNeededUseCase.kt index f2ec37ebc3..cccb152678 100644 --- a/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncIfNeededUseCase.kt +++ b/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncIfNeededUseCase.kt @@ -2,23 +2,20 @@ package com.simprints.feature.logincheck.usecases import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.models.SynchronizationConfiguration -import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.sync.SyncOrchestrator import javax.inject.Inject internal class StartBackgroundSyncUseCase @Inject constructor( private val syncOrchestrator: SyncOrchestrator, - private val eventSyncManager: EventSyncManager, private val configRepository: ConfigRepository, ) { suspend operator fun invoke() { - eventSyncManager.scheduleSync() syncOrchestrator.scheduleBackgroundWork() val frequency = configRepository.getProjectConfiguration().synchronization.frequency if (frequency == SynchronizationConfiguration.Frequency.PERIODICALLY_AND_ON_SESSION_START) { - eventSyncManager.sync() + syncOrchestrator.startEventSync() } } } diff --git a/feature/login-check/src/test/java/com/simprints/feature/logincheck/LoginCheckViewModelTest.kt b/feature/login-check/src/test/java/com/simprints/feature/logincheck/LoginCheckViewModelTest.kt index e4034af61d..3a25230445 100644 --- a/feature/login-check/src/test/java/com/simprints/feature/logincheck/LoginCheckViewModelTest.kt +++ b/feature/login-check/src/test/java/com/simprints/feature/logincheck/LoginCheckViewModelTest.kt @@ -5,21 +5,12 @@ import com.google.common.truth.Truth.assertThat import com.jraska.livedata.test import com.simprints.feature.login.LoginError import com.simprints.feature.login.LoginResult -import com.simprints.feature.logincheck.usecases.ActionFactory -import com.simprints.feature.logincheck.usecases.AddAuthorizationEventUseCase -import com.simprints.feature.logincheck.usecases.CancelBackgroundSyncUseCase -import com.simprints.feature.logincheck.usecases.ExtractCrashKeysUseCase -import com.simprints.feature.logincheck.usecases.ExtractParametersForAnalyticsUseCase -import com.simprints.feature.logincheck.usecases.IsUserSignedInUseCase -import com.simprints.feature.logincheck.usecases.ReportActionRequestEventsUseCase -import com.simprints.feature.logincheck.usecases.StartBackgroundSyncUseCase -import com.simprints.feature.logincheck.usecases.UpdateProjectInCurrentSessionUseCase -import com.simprints.feature.logincheck.usecases.UpdateSessionScopePayloadUseCase -import com.simprints.feature.logincheck.usecases.UpdateStoredUserIdUseCase +import com.simprints.feature.logincheck.usecases.* import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.models.ProjectState import com.simprints.infra.security.SecurityManager import com.simprints.infra.security.exceptions.RootedDeviceException +import com.simprints.infra.sync.SyncOrchestrator import com.simprints.testtools.common.coroutines.TestCoroutineRule import io.mockk.MockKAnnotations import io.mockk.coEvery @@ -65,7 +56,7 @@ internal class LoginCheckViewModelTest { lateinit var startBackgroundSync: StartBackgroundSyncUseCase @MockK - lateinit var cancelBackgroundSync: CancelBackgroundSyncUseCase + lateinit var syncOrchestrator: SyncOrchestrator @MockK lateinit var updateSessionScopePayloadUseCase: UpdateSessionScopePayloadUseCase @@ -91,7 +82,7 @@ internal class LoginCheckViewModelTest { isUserSignedInUseCase, configRepository, startBackgroundSync, - cancelBackgroundSync, + syncOrchestrator, updateSessionScopePayloadUseCase, updateProjectStateUseCase, updateStoredUserIdUseCase, @@ -161,7 +152,7 @@ internal class LoginCheckViewModelTest { coVerify { addAuthorizationEventUseCase.invoke(any(), eq(false)) - cancelBackgroundSync.invoke() + syncOrchestrator.cancelBackgroundWork() } viewModel.showLoginFlow.test() .assertValue { it.peekContent() == ActionFactory.getFlowRequest() } diff --git a/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCaseTest.kt b/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCaseTest.kt deleted file mode 100644 index 039914213f..0000000000 --- a/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/CancelBackgroundSyncUseCaseTest.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.simprints.feature.logincheck.usecases - -import com.simprints.infra.eventsync.EventSyncManager -import com.simprints.infra.sync.SyncOrchestrator -import io.mockk.MockKAnnotations -import io.mockk.coVerify -import io.mockk.impl.annotations.MockK -import io.mockk.verify -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test - -class CancelBackgroundSyncUseCaseTest { - - @MockK - lateinit var eventSyncManager: EventSyncManager - - @MockK - lateinit var syncOrchestrator: SyncOrchestrator - - private lateinit var useCase: CancelBackgroundSyncUseCase - - @Before - fun setUp() { - MockKAnnotations.init(this, relaxed = true) - - useCase = CancelBackgroundSyncUseCase( - eventSyncManager, - syncOrchestrator - ) - } - - @Test - fun `Cancels all syncs when called`() = runTest { - useCase.invoke() - - verify { - eventSyncManager.cancelScheduledSync() - } - coVerify { - syncOrchestrator.cancelBackgroundWork() - } - } -} diff --git a/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCaseTest.kt b/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCaseTest.kt index 5d96627764..d995e4e301 100644 --- a/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCaseTest.kt +++ b/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCaseTest.kt @@ -2,7 +2,6 @@ package com.simprints.feature.logincheck.usecases import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.models.SynchronizationConfiguration -import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.sync.SyncOrchestrator import io.mockk.MockKAnnotations import io.mockk.coEvery @@ -15,9 +14,6 @@ import org.junit.Test class StartBackgroundSyncUseCaseTest { - @MockK - lateinit var eventSyncManager: EventSyncManager - @MockK lateinit var syncOrchestrator: SyncOrchestrator @@ -32,7 +28,6 @@ class StartBackgroundSyncUseCaseTest { useCase = StartBackgroundSyncUseCase( syncOrchestrator, - eventSyncManager, configRepository, ) } @@ -43,9 +38,6 @@ class StartBackgroundSyncUseCaseTest { useCase.invoke() - verify { - eventSyncManager.scheduleSync() - } coVerify { syncOrchestrator.scheduleBackgroundWork() } @@ -57,7 +49,7 @@ class StartBackgroundSyncUseCaseTest { useCase.invoke() - verify { eventSyncManager.sync() } + verify { syncOrchestrator.startEventSync() } } @Test @@ -66,7 +58,7 @@ class StartBackgroundSyncUseCaseTest { useCase.invoke() - verify(exactly = 0) { eventSyncManager.sync() } + verify(exactly = 0) { syncOrchestrator.startEventSync() } } } diff --git a/id/src/main/java/com/simprints/id/services/sync/events/down/EventDownSyncResetService.kt b/id/src/main/java/com/simprints/id/services/sync/events/down/EventDownSyncResetService.kt index 156986ec35..ada13f2e12 100644 --- a/id/src/main/java/com/simprints/id/services/sync/events/down/EventDownSyncResetService.kt +++ b/id/src/main/java/com/simprints/id/services/sync/events/down/EventDownSyncResetService.kt @@ -14,6 +14,7 @@ import com.simprints.core.ExternalScope import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.logging.LoggingConstants.CrashReportTag.SYNC import com.simprints.infra.logging.Simber +import com.simprints.infra.sync.SyncOrchestrator import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -28,6 +29,9 @@ class EventDownSyncResetService : Service() { @Inject lateinit var eventSyncManager: EventSyncManager + @Inject + lateinit var syncOrchestrator: SyncOrchestrator + @Inject @ExternalScope lateinit var externalScope: CoroutineScope @@ -39,7 +43,7 @@ class EventDownSyncResetService : Service() { // Reset current downsync state eventSyncManager.resetDownSyncInfo() // Trigger a new sync - eventSyncManager.sync() + syncOrchestrator.startEventSync() stopSelf() } diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/EventSyncManager.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/EventSyncManager.kt index 88b9cedbf9..9d87b63c1a 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/EventSyncManager.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/EventSyncManager.kt @@ -9,15 +9,13 @@ import java.util.Date interface EventSyncManager { + fun getPeriodicWorkTags(): List + fun getOneTimeWorkTags(): List + fun getAllWorkerTag(): String + suspend fun getLastSyncTime(): Date? fun getLastSyncState(): LiveData - fun sync() - fun stop() - - fun scheduleSync() - fun cancelScheduledSync() - suspend fun countEventsToUpload(type: EventType?): Flow suspend fun countEventsToDownload(): DownSyncCounts diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/EventSyncManagerImpl.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/EventSyncManagerImpl.kt index ef17377f3f..c1f6d7bb39 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/EventSyncManagerImpl.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/EventSyncManagerImpl.kt @@ -1,16 +1,9 @@ package com.simprints.infra.eventsync -import android.content.Context import androidx.lifecycle.LiveData -import androidx.work.Constraints -import androidx.work.ExistingPeriodicWorkPolicy -import androidx.work.ExistingWorkPolicy -import androidx.work.NetworkType -import androidx.work.OneTimeWorkRequest -import androidx.work.PeriodicWorkRequest -import androidx.work.WorkManager import com.simprints.core.DispatcherIO import com.simprints.core.domain.tokenization.values +import com.simprints.core.tools.time.TimeHelper import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.models.ProjectConfiguration import com.simprints.infra.events.EventRepository @@ -29,26 +22,18 @@ import com.simprints.infra.eventsync.sync.common.EventSyncCache import com.simprints.infra.eventsync.sync.common.MASTER_SYNC_SCHEDULERS import com.simprints.infra.eventsync.sync.common.MASTER_SYNC_SCHEDULER_ONE_TIME import com.simprints.infra.eventsync.sync.common.MASTER_SYNC_SCHEDULER_PERIODIC_TIME -import com.simprints.infra.eventsync.sync.common.SYNC_LOG_TAG -import com.simprints.infra.eventsync.sync.common.addTagForBackgroundSyncMasterWorker -import com.simprints.infra.eventsync.sync.common.addTagForOneTimeSyncMasterWorker -import com.simprints.infra.eventsync.sync.common.addTagForScheduledAtNow -import com.simprints.infra.eventsync.sync.common.addTagForSyncMasterWorkers -import com.simprints.infra.eventsync.sync.common.cancelAllSubjectsSyncWorkers +import com.simprints.infra.eventsync.sync.common.TAG_SCHEDULED_AT +import com.simprints.infra.eventsync.sync.common.TAG_SUBJECTS_SYNC_ALL_WORKERS import com.simprints.infra.eventsync.sync.down.tasks.EventDownSyncTask -import com.simprints.infra.eventsync.sync.master.EventSyncMasterWorker -import com.simprints.infra.logging.Simber -import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.toList import kotlinx.coroutines.withContext import java.util.Date -import java.util.concurrent.TimeUnit import javax.inject.Inject internal class EventSyncManagerImpl @Inject constructor( - @ApplicationContext private val ctx: Context, + private val timeHelper: TimeHelper, private val eventSyncStateProcessor: EventSyncStateProcessor, private val downSyncScopeRepository: EventDownSyncScopeRepository, private val eventRepository: EventRepository, @@ -60,75 +45,24 @@ internal class EventSyncManagerImpl @Inject constructor( @DispatcherIO private val dispatcher: CoroutineDispatcher, ) : EventSyncManager { - companion object { - private const val SYNC_REPEAT_INTERVAL = BuildConfig.SYNC_PERIODIC_WORKER_INTERVAL_MINUTES - val SYNC_REPEAT_UNIT = TimeUnit.MINUTES - } - - private val wm = WorkManager.getInstance(ctx) - override suspend fun getLastSyncTime(): Date? = eventSyncCache.readLastSuccessfulSyncTime() override fun getLastSyncState(): LiveData = eventSyncStateProcessor.getLastSyncState() - override fun sync() { - Simber.tag(SYNC_LOG_TAG).d("[SCHEDULER] One time events master worker") - - wm.beginUniqueWork( - MASTER_SYNC_SCHEDULER_ONE_TIME, - ExistingWorkPolicy.KEEP, - buildOneTimeRequest() - ).enqueue() - } - - override fun scheduleSync() { - Simber.tag(SYNC_LOG_TAG).d("[SCHEDULER] Periodic events master worker") - - wm.enqueueUniquePeriodicWork( - MASTER_SYNC_SCHEDULER_PERIODIC_TIME, - ExistingPeriodicWorkPolicy.UPDATE, - buildPeriodicRequest() - ) - } - - private fun buildOneTimeRequest(): OneTimeWorkRequest = - OneTimeWorkRequest.Builder(EventSyncMasterWorker::class.java) - .setConstraints(getDownSyncMasterWorkerConstraints()) - .addTagForSyncMasterWorkers() - .addTagForOneTimeSyncMasterWorker() - .addTagForScheduledAtNow() - .build() as OneTimeWorkRequest - - private fun buildPeriodicRequest(): PeriodicWorkRequest = - PeriodicWorkRequest.Builder( - EventSyncMasterWorker::class.java, - SYNC_REPEAT_INTERVAL, - SYNC_REPEAT_UNIT - ) - .setConstraints(getDownSyncMasterWorkerConstraints()) - .addTagForSyncMasterWorkers() - .addTagForBackgroundSyncMasterWorker() - .addTagForScheduledAtNow() - .build() as PeriodicWorkRequest - - private fun getDownSyncMasterWorkerConstraints() = - Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .build() - - override fun cancelScheduledSync() { - wm.cancelAllWorkByTag(MASTER_SYNC_SCHEDULERS) - stop() - } + override fun getPeriodicWorkTags(): List = listOf( + MASTER_SYNC_SCHEDULERS, + MASTER_SYNC_SCHEDULER_PERIODIC_TIME, + "$TAG_SCHEDULED_AT${timeHelper.now().ms}", + ) - override fun stop() { - wm.cancelAllSubjectsSyncWorkers() - } + override fun getOneTimeWorkTags(): List = listOf( + MASTER_SYNC_SCHEDULERS, + MASTER_SYNC_SCHEDULER_ONE_TIME, + "$TAG_SCHEDULED_AT${timeHelper.now().ms}", + ) - private fun cleanScheduledHistory() { - wm.pruneWork() - } + override fun getAllWorkerTag(): String = TAG_SUBJECTS_SYNC_ALL_WORKERS override suspend fun countEventsToUpload(type: EventType?): Flow = eventRepository.observeEventCount(type) @@ -165,11 +99,13 @@ internal class EventSyncManagerImpl @Inject constructor( subjectId: String, ): Unit = withContext(dispatcher) { val eventScope = eventRepository.createEventScope(EventScopeType.DOWN_SYNC) - val op = EventDownSyncOperation(RemoteEventQuery( - projectId = projectId, - subjectId = subjectId, - modes = getProjectModes(configRepository.getProjectConfiguration()), - )) + val op = EventDownSyncOperation( + RemoteEventQuery( + projectId = projectId, + subjectId = subjectId, + modes = getProjectModes(configRepository.getProjectConfiguration()), + ) + ) downSyncTask.downSync(this, op, eventScope).toList() } @@ -188,7 +124,6 @@ internal class EventSyncManagerImpl @Inject constructor( upSyncScopeRepo.deleteAll() eventSyncCache.clearProgresses() eventSyncCache.storeLastSuccessfulSyncTime(null) - cleanScheduledHistory() } override suspend fun resetDownSyncInfo() { diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/common/WorkRequestBuilder.ext.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/common/WorkRequestBuilder.ext.kt index eb9aa093c8..2033395383 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/common/WorkRequestBuilder.ext.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/common/WorkRequestBuilder.ext.kt @@ -65,12 +65,6 @@ internal fun WorkRequest.Builder<*, *>.addTagForEndSyncReporter(): WorkRequest.B internal fun WorkRequest.Builder<*, *>.addTagForStartSyncReporter(): WorkRequest.Builder<*, *> = this.addTag(tagForType(START_SYNC_REPORTER)) -// Master Worker tags -internal fun WorkRequest.Builder<*, *>.addTagForSyncMasterWorkers(): WorkRequest.Builder<*, *> = this.addTag(MASTER_SYNC_SCHEDULERS) - -internal fun WorkRequest.Builder<*, *>.addTagForOneTimeSyncMasterWorker(): WorkRequest.Builder<*, *> = this.addTag(MASTER_SYNC_SCHEDULER_ONE_TIME) -internal fun WorkRequest.Builder<*, *>.addTagForBackgroundSyncMasterWorker(): WorkRequest.Builder<*, *> = this.addTag(MASTER_SYNC_SCHEDULER_PERIODIC_TIME) - /** * Use tags */ @@ -87,6 +81,5 @@ internal fun List.filterByTags(vararg tagsToFilter: String) = } internal fun WorkManager.getAllSubjectsSyncWorkersInfo() = getWorkInfosByTag(TAG_SUBJECTS_SYNC_ALL_WORKERS) -internal fun WorkManager.cancelAllSubjectsSyncWorkers() = cancelAllWorkByTag(TAG_SUBJECTS_SYNC_ALL_WORKERS) internal fun MutableList.sortByScheduledTime() = sortBy { it -> it.tags.first { it.contains(TAG_SCHEDULED_AT) } } internal fun List.sortByScheduledTime() = sortedBy { it -> it.tags.first { it.contains(TAG_SCHEDULED_AT) } } diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorker.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorker.kt index 1571e079c3..83830bc093 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorker.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorker.kt @@ -26,7 +26,7 @@ import kotlinx.coroutines.withContext import java.util.* @HiltWorker -internal class EventSyncMasterWorker @AssistedInject constructor( +class EventSyncMasterWorker @AssistedInject internal constructor( @Assisted private val appContext: Context, @Assisted params: WorkerParameters, private val downSyncWorkerBuilder: EventDownSyncWorkersBuilder, diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/EventSyncManagerTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/EventSyncManagerTest.kt index 55f94f130d..6318411ce5 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/EventSyncManagerTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/EventSyncManagerTest.kt @@ -1,13 +1,10 @@ package com.simprints.infra.eventsync -import android.content.Context import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.work.ExistingPeriodicWorkPolicy -import androidx.work.ExistingWorkPolicy -import androidx.work.OneTimeWorkRequest -import androidx.work.WorkManager import com.google.common.truth.Truth.assertThat import com.simprints.core.domain.common.Partitioning +import com.simprints.core.tools.time.TimeHelper +import com.simprints.core.tools.time.Timestamp import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.events.EventRepository import com.simprints.infra.events.event.domain.EventCount @@ -32,7 +29,6 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock @RunWith(AndroidJUnit4::class) internal class EventSyncManagerTest { @@ -41,10 +37,7 @@ internal class EventSyncManagerTest { val testCoroutineRule = TestCoroutineRule() @MockK - lateinit var ctx: Context - - @MockK - lateinit var workManager: WorkManager + lateinit var timeHelper: TimeHelper @MockK lateinit var eventSyncStateProcessor: EventSyncStateProcessor @@ -79,16 +72,14 @@ internal class EventSyncManagerTest { fun setup() { MockKAnnotations.init(this, relaxed = true) - mockkStatic(WorkManager::class) - every { WorkManager.getInstance(ctx) } returns workManager - + every { timeHelper.now() } returns Timestamp(1) coEvery { configRepository.getProjectConfiguration() } returns mockk { every { general.modalities } returns listOf() every { synchronization.down.partitionType.toDomain() } returns Partitioning.MODULE } eventSyncManagerImpl = EventSyncManagerImpl( - ctx = ctx, + timeHelper = timeHelper, eventSyncStateProcessor = eventSyncStateProcessor, downSyncScopeRepository = eventDownSyncScopeRepository, eventRepository = eventRepository, @@ -113,57 +104,6 @@ internal class EventSyncManagerTest { verify { eventSyncStateProcessor.getLastSyncState() } } - @Test - fun `sync should enqueue a one time sync master worker`() = runTest { - eventSyncManagerImpl.sync() - - verify(exactly = 1) { - workManager.beginUniqueWork( - MASTER_SYNC_SCHEDULER_ONE_TIME, - ExistingWorkPolicy.KEEP, - match { req -> - assertThat(req.tags.firstOrNull { it.contains(TAG_SCHEDULED_AT) }).isNotNull() - assertThat(req.tags).contains(MASTER_SYNC_SCHEDULER_ONE_TIME) - assertThat(req.tags).contains(MASTER_SYNC_SCHEDULERS) - true - } - ) - } - } - - @Test - fun `sync should enqueue periodic sync master worker`() = runTest { - eventSyncManagerImpl.scheduleSync() - - verify(exactly = 1) { - workManager.enqueueUniquePeriodicWork( - MASTER_SYNC_SCHEDULER_PERIODIC_TIME, - ExistingPeriodicWorkPolicy.UPDATE, - match { req -> - assertThat(req.tags.firstOrNull { it.contains(TAG_SCHEDULED_AT) }).isNotNull() - assertThat(req.tags).contains(MASTER_SYNC_SCHEDULER_PERIODIC_TIME) - assertThat(req.tags).contains(MASTER_SYNC_SCHEDULERS) - true - } - ) - } - } - - @Test - fun `cancelScheduledSync should clear periodic master workers`() = runTest { - eventSyncManagerImpl.cancelScheduledSync() - - verify(exactly = 1) { workManager.cancelAllWorkByTag(MASTER_SYNC_SCHEDULERS) } - verify(exactly = 1) { workManager.cancelAllWorkByTag(TAG_SUBJECTS_SYNC_ALL_WORKERS) } - } - - @Test - fun `stop should stop all workers`() = runTest { - eventSyncManagerImpl.stop() - - verify(exactly = 1) { workManager.cancelAllWorkByTag(TAG_SUBJECTS_SYNC_ALL_WORKERS) } - } - @Test fun `countEventsToUpload should call event repo`() = runTest { eventSyncManagerImpl.countEventsToUpload(null).toList() @@ -247,7 +187,6 @@ internal class EventSyncManagerTest { coVerify(exactly = 1) { eventDownSyncScopeRepository.deleteAll() } coVerify(exactly = 1) { eventSyncCache.clearProgresses() } coVerify(exactly = 1) { eventSyncCache.storeLastSuccessfulSyncTime(null) } - verify(exactly = 1) { workManager.pruneWork() } } @Test diff --git a/infra/sync/build.gradle.kts b/infra/sync/build.gradle.kts index 39c3be5f7d..c869d2c9ef 100644 --- a/infra/sync/build.gradle.kts +++ b/infra/sync/build.gradle.kts @@ -12,18 +12,21 @@ android { buildConfigField("long", "DEVICE_PERIODIC_WORKER_INTERVAL_MINUTES", "30L") buildConfigField("long", "IMAGE_PERIODIC_WORKER_INTERVAL_MINUTES", "60L") buildConfigField("long", "FIRMWARE_UPDATE_WORKER_INTERVAL_MINUTES", "1440L") + buildConfigField("long", "EVENT_SYNC_WORKER_INTERVAL_MINUTES", "60L") } getByName("staging") { buildConfigField("long", "PROJECT_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") buildConfigField("long", "DEVICE_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") buildConfigField("long", "IMAGE_PERIODIC_WORKER_INTERVAL_MINUTES", "15LL") buildConfigField("long", "FIRMWARE_UPDATE_WORKER_INTERVAL_MINUTES", "15LL") + buildConfigField("long", "EVENT_SYNC_WORKER_INTERVAL_MINUTES", "15LL") } getByName("debug") { buildConfigField("long", "PROJECT_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") buildConfigField("long", "DEVICE_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") buildConfigField("long", "IMAGE_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") buildConfigField("long", "FIRMWARE_UPDATE_WORKER_INTERVAL_MINUTES", "15L") + buildConfigField("long", "EVENT_SYNC_WORKER_INTERVAL_MINUTES", "15L") } } } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt index a15b7f9a9e..b2c86209d4 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt @@ -22,4 +22,8 @@ internal object SyncConstants { const val FIRMWARE_UPDATE_WORK_NAME = "firmware-file-update-work-v2" const val FIRMWARE_UPDATE_REPEAT_INTERVAL = BuildConfig.FIRMWARE_UPDATE_WORKER_INTERVAL_MINUTES + + const val EVENT_SYNC_WORK_NAME = "event-sync-work" + const val EVENT_SYNC_WORK_NAME_ONE_TIME = "event-sync-work-one-time" + const val EVENT_SYNC_WORKER_INTERVAL = BuildConfig.EVENT_SYNC_WORKER_INTERVAL_MINUTES } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt index dcc2b731ff..289cd190c0 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt @@ -7,6 +7,11 @@ interface SyncOrchestrator { fun startDeviceSync() + fun rescheduleEventSync() + fun cancelEventSync() + fun startEventSync() + fun stopEventSync() + /** * Fully reschedule the background worker. * Should be used in when the configuration that affects scheduling has changed. @@ -18,5 +23,6 @@ interface SyncOrchestrator { */ fun uploadEnrolmentRecords(id: String, subjectIds: List) + suspend fun deleteEventSyncInfo() fun cleanupWorkers() } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt index a1e50410a9..b06b837dcf 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt @@ -4,6 +4,8 @@ import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.WorkManager import androidx.work.workDataOf import com.simprints.infra.authstore.AuthStore +import com.simprints.infra.eventsync.EventSyncManager +import com.simprints.infra.eventsync.sync.master.EventSyncMasterWorker import com.simprints.infra.sync.extensions.schedulePeriodicWorker import com.simprints.infra.sync.extensions.startWorker import com.simprints.infra.sync.config.worker.DeviceConfigDownSyncWorker @@ -19,6 +21,7 @@ import javax.inject.Inject internal class SyncOrchestratorImpl @Inject constructor( private val workManager: WorkManager, private val authStore: AuthStore, + private val eventSyncManager: EventSyncManager, private val shouldScheduleFirmwareUpdate: ShouldScheduleFirmwareUpdateUseCase, private val cleanupDeprecatedWorkers: CleanupDeprecatedWorkersUseCase, ) : SyncOrchestrator { @@ -37,8 +40,7 @@ internal class SyncOrchestratorImpl @Inject constructor( SyncConstants.IMAGE_UP_SYNC_WORK_NAME, SyncConstants.IMAGE_UP_SYNC_REPEAT_INTERVAL, ) - // TODO eventSyncManager.scheduleSync() - + rescheduleEventSync() if (shouldScheduleFirmwareUpdate()) { workManager.schedulePeriodicWorker( SyncConstants.FIRMWARE_UPDATE_WORK_NAME, @@ -55,14 +57,42 @@ internal class SyncOrchestratorImpl @Inject constructor( SyncConstants.PROJECT_SYNC_WORK_NAME, SyncConstants.DEVICE_SYNC_WORK_NAME, SyncConstants.IMAGE_UP_SYNC_WORK_NAME, + SyncConstants.EVENT_SYNC_WORK_NAME, SyncConstants.FIRMWARE_UPDATE_WORK_NAME, ) + stopEventSync() } override fun startDeviceSync() { workManager.startWorker(SyncConstants.DEVICE_SYNC_WORK_NAME_ONE_TIME) } + override fun rescheduleEventSync() { + workManager.schedulePeriodicWorker( + SyncConstants.EVENT_SYNC_WORK_NAME, + SyncConstants.EVENT_SYNC_WORKER_INTERVAL, + tags = eventSyncManager.getPeriodicWorkTags(), + ) + } + + override fun cancelEventSync() { + workManager.cancelWorkers(SyncConstants.EVENT_SYNC_WORK_NAME) + stopEventSync() + } + + override fun startEventSync() { + workManager.startWorker( + SyncConstants.EVENT_SYNC_WORK_NAME_ONE_TIME, + tags = eventSyncManager.getOneTimeWorkTags(), + ) + } + + override fun stopEventSync() { + workManager.cancelWorkers(SyncConstants.EVENT_SYNC_WORK_NAME_ONE_TIME) + // Event sync consists of multiple workers, so we cancel them all by tag + workManager.cancelAllWorkByTag(eventSyncManager.getAllWorkerTag()) + } + override suspend fun rescheduleImageUpSync() { workManager.schedulePeriodicWorker( SyncConstants.IMAGE_UP_SYNC_WORK_NAME, @@ -81,6 +111,11 @@ internal class SyncOrchestratorImpl @Inject constructor( ) } + override suspend fun deleteEventSyncInfo() { + eventSyncManager.deleteSyncInfo() + workManager.pruneWork() + } + override fun cleanupWorkers() { cleanupDeprecatedWorkers() } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/LogoutUseCase.kt b/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/LogoutUseCase.kt index 0a36af713b..0957050261 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/LogoutUseCase.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/LogoutUseCase.kt @@ -1,20 +1,17 @@ package com.simprints.infra.sync.config.usecase import com.simprints.infra.authlogic.AuthManager -import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.sync.SyncOrchestrator import javax.inject.Inject internal class LogoutUseCase @Inject constructor( private val syncOrchestrator: SyncOrchestrator, - private val eventSyncManager: EventSyncManager, private val authManager: AuthManager, ) { suspend operator fun invoke() { syncOrchestrator.cancelBackgroundWork() - eventSyncManager.cancelScheduledSync() - eventSyncManager.deleteSyncInfo() + syncOrchestrator.deleteEventSyncInfo() authManager.signOut() } } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt b/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt index 5cb7047c36..a5bd4e7808 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt @@ -13,6 +13,7 @@ internal inline fun WorkManager.schedulePeriodicW repeatInterval: Long, existingWorkPolicy: ExistingPeriodicWorkPolicy = ExistingPeriodicWorkPolicy.UPDATE, constraints: Constraints = defaultWorkerConstraints(), + tags: List = emptyList(), inputData: Data? = null, ) = enqueueUniquePeriodicWork( workName, @@ -20,12 +21,14 @@ internal inline fun WorkManager.schedulePeriodicW PeriodicWorkRequestBuilder(repeatInterval, SyncConstants.SYNC_REPEAT_UNIT) .setConstraints(constraints) .let { if (inputData != null) it.setInputData(inputData) else it } + .let { tags.fold(it) { builder, tag -> builder.addTag(tag) } } .build() ) internal inline fun WorkManager.startWorker( workName: String, constraints: Constraints = defaultWorkerConstraints(), + tags: List = emptyList(), inputData: Data? = null, ) = this.enqueueUniqueWork( workName, @@ -33,6 +36,7 @@ internal inline fun WorkManager.startWorker( OneTimeWorkRequestBuilder() .setConstraints(constraints) .let { if (inputData != null) it.setInputData(inputData) else it } + .let { tags.fold(it) { builder, tag -> builder.addTag(tag) } } .build() ) diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/usecase/CleanupDeprecatedWorkersUseCase.kt b/infra/sync/src/main/java/com/simprints/infra/sync/usecase/CleanupDeprecatedWorkersUseCase.kt index 53fe44e54b..4d9e0936a8 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/usecase/CleanupDeprecatedWorkersUseCase.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/usecase/CleanupDeprecatedWorkersUseCase.kt @@ -31,6 +31,8 @@ internal class CleanupDeprecatedWorkersUseCase @Inject constructor( "device-sync-work", // 2024.1.1 "image-upsync-work-v2", // 2024.1.1 "firmware-file-update-work", // 2024.1.1 + "TAG_MASTER_SYNC_SCHEDULER_PERIODIC_TIME", // 2024.1.1, tag was indeed used as worker name + "TAG_MASTER_SYNC_SCHEDULER_ONE_TIME", // 2024.1.1, tag was indeed used as worker name ) private fun tagsForDeprecatedWorkers() = listOf( diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt index 0cf8e961a3..1f31509eeb 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt @@ -4,8 +4,11 @@ import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager import com.simprints.infra.authstore.AuthStore +import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.sync.SyncConstants.DEVICE_SYNC_WORK_NAME import com.simprints.infra.sync.SyncConstants.DEVICE_SYNC_WORK_NAME_ONE_TIME +import com.simprints.infra.sync.SyncConstants.EVENT_SYNC_WORK_NAME +import com.simprints.infra.sync.SyncConstants.EVENT_SYNC_WORK_NAME_ONE_TIME import com.simprints.infra.sync.SyncConstants.FIRMWARE_UPDATE_WORK_NAME import com.simprints.infra.sync.SyncConstants.IMAGE_UP_SYNC_WORK_NAME import com.simprints.infra.sync.SyncConstants.PROJECT_SYNC_WORK_NAME @@ -32,6 +35,9 @@ class SyncOrchestratorImplTest { @MockK private lateinit var authStore: AuthStore + @MockK + private lateinit var eventSyncManager: EventSyncManager + @MockK private lateinit var shouldScheduleFirmwareUpdate: ShouldScheduleFirmwareUpdateUseCase @@ -47,6 +53,7 @@ class SyncOrchestratorImplTest { syncOrchestrator = SyncOrchestratorImpl( workManager, authStore, + eventSyncManager, shouldScheduleFirmwareUpdate, cleanupDeprecatedWorkers, ) @@ -75,6 +82,7 @@ class SyncOrchestratorImplTest { workManager.enqueueUniquePeriodicWork(PROJECT_SYNC_WORK_NAME, any(), any()) workManager.enqueueUniquePeriodicWork(DEVICE_SYNC_WORK_NAME, any(), any()) workManager.enqueueUniquePeriodicWork(IMAGE_UP_SYNC_WORK_NAME, any(), any()) + workManager.enqueueUniquePeriodicWork(EVENT_SYNC_WORK_NAME, any(), any()) workManager.enqueueUniquePeriodicWork(FIRMWARE_UPDATE_WORK_NAME, any(), any()) } } @@ -93,13 +101,19 @@ class SyncOrchestratorImplTest { @Test fun `cancels all necessary background workers`() = runTest { + every { eventSyncManager.getAllWorkerTag() } returns "syncWorkers" + syncOrchestrator.cancelBackgroundWork() verify { workManager.cancelUniqueWork(PROJECT_SYNC_WORK_NAME) workManager.cancelUniqueWork(DEVICE_SYNC_WORK_NAME) workManager.cancelUniqueWork(IMAGE_UP_SYNC_WORK_NAME) + workManager.cancelUniqueWork(EVENT_SYNC_WORK_NAME) workManager.cancelUniqueWork(FIRMWARE_UPDATE_WORK_NAME) + + // Explicitly cancel event sync sub-workers + workManager.cancelAllWorkByTag("syncWorkers") } } @@ -116,6 +130,60 @@ class SyncOrchestratorImplTest { } } + @Test + fun `reschedules event sync worker with correct tags`() = runTest { + every { eventSyncManager.getPeriodicWorkTags() } returns listOf("tag1", "tag2") + + syncOrchestrator.rescheduleEventSync() + + verify { + workManager.enqueueUniquePeriodicWork( + EVENT_SYNC_WORK_NAME, + any(), + match { it.tags.containsAll(setOf("tag1", "tag2")) }) + } + } + + @Test + fun `cancel event sync worker cancels correct worker`() = runTest { + every { eventSyncManager.getAllWorkerTag() } returns "syncWorkers" + + syncOrchestrator.cancelEventSync() + + verify { + workManager.cancelUniqueWork(EVENT_SYNC_WORK_NAME) + workManager.cancelUniqueWork(EVENT_SYNC_WORK_NAME_ONE_TIME) + workManager.cancelAllWorkByTag("syncWorkers") + } + } + + @Test + fun `start event sync worker with correct tags`() = runTest { + every { eventSyncManager.getOneTimeWorkTags() } returns listOf("tag1", "tag2") + + syncOrchestrator.startEventSync() + + verify { + workManager.enqueueUniqueWork( + EVENT_SYNC_WORK_NAME_ONE_TIME, + any(), + match { it.tags.containsAll(setOf("tag1", "tag2")) } + ) + } + } + + @Test + fun `stop event sync worker cancels correct worker`() = runTest { + every { eventSyncManager.getAllWorkerTag() } returns "syncWorkers" + + syncOrchestrator.cancelEventSync() + + verify { + workManager.cancelUniqueWork(EVENT_SYNC_WORK_NAME_ONE_TIME) + workManager.cancelAllWorkByTag("syncWorkers") + } + } + @Test fun `reschedules image worker when requested`() = runTest { syncOrchestrator.rescheduleImageUpSync() @@ -151,6 +219,12 @@ class SyncOrchestratorImplTest { } } + @Test + fun `delegates sync info deletion`() = runTest { + syncOrchestrator.deleteEventSyncInfo() + coVerify { eventSyncManager.deleteSyncInfo() } + } + @Test fun `delegates worker cleanup requests`() = runTest { syncOrchestrator.cleanupWorkers() diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/LogoutUseCaseTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/LogoutUseCaseTest.kt index 0dabe88b18..6afddc5960 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/LogoutUseCaseTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/LogoutUseCaseTest.kt @@ -1,12 +1,10 @@ package com.simprints.infra.sync.config.usecase import com.simprints.infra.authlogic.AuthManager -import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.sync.SyncOrchestrator import io.mockk.MockKAnnotations import io.mockk.coVerify import io.mockk.impl.annotations.MockK -import io.mockk.verify import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -16,9 +14,6 @@ class LogoutUseCaseTest { @MockK private lateinit var syncOrchestrator: SyncOrchestrator - @MockK - private lateinit var eventSyncManager: EventSyncManager - @MockK private lateinit var authManager: AuthManager @@ -30,7 +25,6 @@ class LogoutUseCaseTest { useCase = LogoutUseCase( syncOrchestrator = syncOrchestrator, - eventSyncManager = eventSyncManager, authManager = authManager, ) } @@ -39,13 +33,10 @@ class LogoutUseCaseTest { fun `Fully logs out when called`() = runTest { useCase.invoke() - verify { - eventSyncManager.cancelScheduledSync() - } coVerify { syncOrchestrator.cancelBackgroundWork() + syncOrchestrator.deleteEventSyncInfo() authManager.signOut() - eventSyncManager.deleteSyncInfo() } } } From ce8c3739f87dd87765d1ec2860320dbbffcf9542 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Tue, 20 Feb 2024 16:11:15 +0200 Subject: [PATCH 027/197] MS-205 Adjust build config field names to be consistent and more descriptive --- infra/auth-logic/build.gradle.kts | 13 ----------- infra/event-sync/build.gradle.kts | 12 ---------- infra/sync/build.gradle.kts | 22 +++++++++---------- .../com/simprints/infra/sync/SyncConstants.kt | 6 ++--- 4 files changed, 14 insertions(+), 39 deletions(-) diff --git a/infra/auth-logic/build.gradle.kts b/infra/auth-logic/build.gradle.kts index 6582a220a7..968a12aafa 100644 --- a/infra/auth-logic/build.gradle.kts +++ b/infra/auth-logic/build.gradle.kts @@ -5,19 +5,6 @@ plugins { android { namespace = "com.simprints.infra.authlogic" - - - buildTypes { - getByName("release") { - buildConfigField("long", "SECURITY_STATE_PERIODIC_WORKER_INTERVAL_MINUTES", "30L") - } - getByName("staging") { - buildConfigField("long", "SECURITY_STATE_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") - } - getByName("debug") { - buildConfigField("long", "SECURITY_STATE_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") - } - } } dependencies { diff --git a/infra/event-sync/build.gradle.kts b/infra/event-sync/build.gradle.kts index 3051507407..b0fb5e96d2 100644 --- a/infra/event-sync/build.gradle.kts +++ b/infra/event-sync/build.gradle.kts @@ -7,18 +7,6 @@ plugins { android { namespace = "com.simprints.infra.eventsync" - buildTypes { - getByName("release") { - buildConfigField("long", "SYNC_PERIODIC_WORKER_INTERVAL_MINUTES", "60L") - } - getByName("staging") { - buildConfigField("long", "SYNC_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") - } - getByName("debug") { - buildConfigField("long", "SYNC_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") - } - } - sourceSets { // Adds exported room schema location as test app assets. getByName("debug") { diff --git a/infra/sync/build.gradle.kts b/infra/sync/build.gradle.kts index c869d2c9ef..720b10563c 100644 --- a/infra/sync/build.gradle.kts +++ b/infra/sync/build.gradle.kts @@ -8,23 +8,23 @@ android { buildTypes { getByName("release") { - buildConfigField("long", "PROJECT_PERIODIC_WORKER_INTERVAL_MINUTES", "60L") - buildConfigField("long", "DEVICE_PERIODIC_WORKER_INTERVAL_MINUTES", "30L") - buildConfigField("long", "IMAGE_PERIODIC_WORKER_INTERVAL_MINUTES", "60L") + buildConfigField("long", "PROJECT_DOWN_SYNC_WORKER_INTERVAL_MINUTES", "60L") + buildConfigField("long", "DEVICE_DOWN_SYNC_WORKER_INTERVAL_MINUTES", "30L") + buildConfigField("long", "IMAGE_UP_SYNC_WORKER_INTERVAL_MINUTES", "60L") buildConfigField("long", "FIRMWARE_UPDATE_WORKER_INTERVAL_MINUTES", "1440L") buildConfigField("long", "EVENT_SYNC_WORKER_INTERVAL_MINUTES", "60L") } getByName("staging") { - buildConfigField("long", "PROJECT_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") - buildConfigField("long", "DEVICE_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") - buildConfigField("long", "IMAGE_PERIODIC_WORKER_INTERVAL_MINUTES", "15LL") - buildConfigField("long", "FIRMWARE_UPDATE_WORKER_INTERVAL_MINUTES", "15LL") - buildConfigField("long", "EVENT_SYNC_WORKER_INTERVAL_MINUTES", "15LL") + buildConfigField("long", "PROJECT_DOWN_SYNC_WORKER_INTERVAL_MINUTES", "15L") + buildConfigField("long", "DEVICE_DOWN_SYNC_WORKER_INTERVAL_MINUTES", "15L") + buildConfigField("long", "IMAGE_UP_SYNC_WORKER_INTERVAL_MINUTES", "15L") + buildConfigField("long", "FIRMWARE_UPDATE_WORKER_INTERVAL_MINUTES", "15L") + buildConfigField("long", "EVENT_SYNC_WORKER_INTERVAL_MINUTES", "15L") } getByName("debug") { - buildConfigField("long", "PROJECT_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") - buildConfigField("long", "DEVICE_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") - buildConfigField("long", "IMAGE_PERIODIC_WORKER_INTERVAL_MINUTES", "15L") + buildConfigField("long", "PROJECT_DOWN_SYNC_WORKER_INTERVAL_MINUTES", "15L") + buildConfigField("long", "DEVICE_DOWN_SYNC_WORKER_INTERVAL_MINUTES", "15L") + buildConfigField("long", "IMAGE_UP_SYNC_WORKER_INTERVAL_MINUTES", "15L") buildConfigField("long", "FIRMWARE_UPDATE_WORKER_INTERVAL_MINUTES", "15L") buildConfigField("long", "EVENT_SYNC_WORKER_INTERVAL_MINUTES", "15L") } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt index b2c86209d4..70abd5d3a3 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt @@ -7,14 +7,14 @@ internal object SyncConstants { val SYNC_REPEAT_UNIT = TimeUnit.MINUTES const val PROJECT_SYNC_WORK_NAME = "project-sync-work-v2" - const val PROJECT_SYNC_REPEAT_INTERVAL = BuildConfig.PROJECT_PERIODIC_WORKER_INTERVAL_MINUTES + const val PROJECT_SYNC_REPEAT_INTERVAL = BuildConfig.PROJECT_DOWN_SYNC_WORKER_INTERVAL_MINUTES const val DEVICE_SYNC_WORK_NAME = "device-sync-work-v2" const val DEVICE_SYNC_WORK_NAME_ONE_TIME = "device-sync-work-one-time" - const val DEVICE_SYNC_REPEAT_INTERVAL = BuildConfig.DEVICE_PERIODIC_WORKER_INTERVAL_MINUTES + const val DEVICE_SYNC_REPEAT_INTERVAL = BuildConfig.DEVICE_DOWN_SYNC_WORKER_INTERVAL_MINUTES const val IMAGE_UP_SYNC_WORK_NAME = "image-upsync-work-v3" - const val IMAGE_UP_SYNC_REPEAT_INTERVAL = BuildConfig.IMAGE_PERIODIC_WORKER_INTERVAL_MINUTES + const val IMAGE_UP_SYNC_REPEAT_INTERVAL = BuildConfig.IMAGE_UP_SYNC_WORKER_INTERVAL_MINUTES const val RECORD_UPLOAD_WORK_NAME = "upload-enrolment-record-work-one-time" const val RECORD_UPLOAD_INPUT_ID_NAME = "INPUT_ID_NAME" From ddad3d9eae082516020cfbc7752a0d7070c26bfa Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Tue, 20 Feb 2024 16:22:49 +0200 Subject: [PATCH 028/197] MS-205 Restore image upload unmetered connection constraint check --- .../infra/sync/SyncOrchestratorImpl.kt | 16 +++++++ .../infra/sync/SyncOrchestratorImplTest.kt | 43 +++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt index b06b837dcf..50f209613d 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt @@ -1,9 +1,13 @@ package com.simprints.infra.sync +import androidx.work.Constraints import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.NetworkType import androidx.work.WorkManager import androidx.work.workDataOf import com.simprints.infra.authstore.AuthStore +import com.simprints.infra.config.store.ConfigRepository +import com.simprints.infra.config.store.models.imagesUploadRequiresUnmeteredConnection import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.eventsync.sync.master.EventSyncMasterWorker import com.simprints.infra.sync.extensions.schedulePeriodicWorker @@ -21,6 +25,7 @@ import javax.inject.Inject internal class SyncOrchestratorImpl @Inject constructor( private val workManager: WorkManager, private val authStore: AuthStore, + private val configRepo: ConfigRepository, private val eventSyncManager: EventSyncManager, private val shouldScheduleFirmwareUpdate: ShouldScheduleFirmwareUpdateUseCase, private val cleanupDeprecatedWorkers: CleanupDeprecatedWorkersUseCase, @@ -39,6 +44,7 @@ internal class SyncOrchestratorImpl @Inject constructor( workManager.schedulePeriodicWorker( SyncConstants.IMAGE_UP_SYNC_WORK_NAME, SyncConstants.IMAGE_UP_SYNC_REPEAT_INTERVAL, + constraints = getImageUploadConstraints() ) rescheduleEventSync() if (shouldScheduleFirmwareUpdate()) { @@ -98,6 +104,7 @@ internal class SyncOrchestratorImpl @Inject constructor( SyncConstants.IMAGE_UP_SYNC_WORK_NAME, SyncConstants.IMAGE_UP_SYNC_REPEAT_INTERVAL, existingWorkPolicy = ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, + constraints = getImageUploadConstraints() ) } @@ -119,4 +126,13 @@ internal class SyncOrchestratorImpl @Inject constructor( override fun cleanupWorkers() { cleanupDeprecatedWorkers() } + + + private suspend fun getImageUploadConstraints(): Constraints { + val networkType = configRepo + .getProjectConfiguration() + .imagesUploadRequiresUnmeteredConnection() + .let { if (it) NetworkType.UNMETERED else NetworkType.CONNECTED } + return Constraints.Builder().setRequiredNetworkType(networkType).build() + } } diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt index 1f31509eeb..f2f5bfb147 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt @@ -1,9 +1,11 @@ package com.simprints.infra.sync import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.NetworkType import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager import com.simprints.infra.authstore.AuthStore +import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.sync.SyncConstants.DEVICE_SYNC_WORK_NAME import com.simprints.infra.sync.SyncConstants.DEVICE_SYNC_WORK_NAME_ONE_TIME @@ -35,6 +37,9 @@ class SyncOrchestratorImplTest { @MockK private lateinit var authStore: AuthStore + @MockK + private lateinit var configRepo: ConfigRepository + @MockK private lateinit var eventSyncManager: EventSyncManager @@ -53,6 +58,7 @@ class SyncOrchestratorImplTest { syncOrchestrator = SyncOrchestratorImpl( workManager, authStore, + configRepo, eventSyncManager, shouldScheduleFirmwareUpdate, cleanupDeprecatedWorkers, @@ -87,6 +93,43 @@ class SyncOrchestratorImplTest { } } + @Test + fun `schedules images with any connection if not specified`() = runTest { + coEvery { + configRepo.getProjectConfiguration().synchronization.up.imagesRequireUnmeteredConnection + } returns false + every { authStore.signedInProjectId } returns "projectId" + + syncOrchestrator.scheduleBackgroundWork() + + verify { + workManager.enqueueUniquePeriodicWork( + IMAGE_UP_SYNC_WORK_NAME, + any(), + match { it.workSpec.constraints.requiredNetworkType == NetworkType.CONNECTED } + ) + } + } + + @Test + fun `schedules images with unmetered constraint if requested`() = runTest { + coEvery { + configRepo.getProjectConfiguration().synchronization.up.imagesRequireUnmeteredConnection + } returns true + every { authStore.signedInProjectId } returns "projectId" + coEvery { shouldScheduleFirmwareUpdate.invoke() } returns false + + syncOrchestrator.scheduleBackgroundWork() + + verify { + workManager.enqueueUniquePeriodicWork( + IMAGE_UP_SYNC_WORK_NAME, + any(), + match { it.workSpec.constraints.requiredNetworkType == NetworkType.UNMETERED } + ) + } + } + @Test fun `schedules cancel firmware update worker if no support for vero 2`() = runTest { every { authStore.signedInProjectId } returns "projectId" From 0fbc3f51347a1532a165b6c4f52f0990bb7e4dd4 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Tue, 20 Feb 2024 17:10:38 +0200 Subject: [PATCH 029/197] MS-205 Fix github action setup --- .github/workflows/pr-checks.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 9a3636d1b1..efdfd37c3a 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -31,7 +31,7 @@ jobs: infra:realm infra:recent-user-activity infra:config-store - infra:config-sync + infra:sync infra:auth-store infra:auth-logic reportsId: infra1 @@ -45,7 +45,6 @@ jobs: infra:events infra:event-sync infra:enrolment-records-store - infra:enrolment-records-sync infra:license infra:images reportsId: infra2 From 71564cb64a93f7cebfe9ef3f4e560d42f5e644bb Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Mon, 26 Feb 2024 10:29:58 +0200 Subject: [PATCH 030/197] Minor cleanup --- .../simprints/infra/images/ImageUpSyncScheduler.kt | 14 -------------- .../sync/enrolments/EnrolmentRecordWorkerTest.kt | 2 +- 2 files changed, 1 insertion(+), 15 deletions(-) delete mode 100644 infra/images/src/main/java/com/simprints/infra/images/ImageUpSyncScheduler.kt diff --git a/infra/images/src/main/java/com/simprints/infra/images/ImageUpSyncScheduler.kt b/infra/images/src/main/java/com/simprints/infra/images/ImageUpSyncScheduler.kt deleted file mode 100644 index 6d8ed3542f..0000000000 --- a/infra/images/src/main/java/com/simprints/infra/images/ImageUpSyncScheduler.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.simprints.infra.images - -interface ImageUpSyncScheduler { - - /** - * Schedules the background worker if there is none. - */ - suspend fun scheduleImageUpSync() - - - suspend fun rescheduleImageUpSync() - fun cancelImageUpSync() - -} diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/enrolments/EnrolmentRecordWorkerTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/enrolments/EnrolmentRecordWorkerTest.kt index 3b2f04dfd3..98e8159b08 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/enrolments/EnrolmentRecordWorkerTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/enrolments/EnrolmentRecordWorkerTest.kt @@ -31,7 +31,7 @@ class EnrolmentRecordWorkerTest { SyncConstants.RECORD_UPLOAD_INPUT_SUBJECT_IDS_NAME to arrayOf(SUBJECT_ID), ) } - private val worker = com.simprints.infra.sync.enrolments.EnrolmentRecordWorker( + private val worker = EnrolmentRecordWorker( mockk(relaxed = true), params, repository, From 1903dd04f6bb67ae903f91f1832d0bc2d6c4edf1 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Wed, 21 Feb 2024 14:05:46 +0200 Subject: [PATCH 031/197] MS-205 Stop and reschedule image upload worker when event sync starts MS-205 Make sure that AppScope is closed on application termination MS-205 Add an explicit backoff configuration to worker scheduling --- .../dashboard/tools/di/FakeCoreModule.kt | 5 ++ .../main/java/com/simprints/id/Application.kt | 6 ++ .../core/workers/SimCoroutineWorker.kt | 2 +- .../com/simprints/infra/sync/SyncConstants.kt | 3 +- .../infra/sync/SyncOrchestratorImpl.kt | 31 ++++++-- .../infra/sync/extensions/WorkManager.ext.kt | 15 +++- .../infra/sync/images/ImageUpSyncWorker.kt | 1 - .../infra/sync/SyncOrchestratorImplTest.kt | 72 ++++++++++++++++--- 8 files changed, 118 insertions(+), 17 deletions(-) diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/tools/di/FakeCoreModule.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/tools/di/FakeCoreModule.kt index 3bd1635264..9c17dd5658 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/tools/di/FakeCoreModule.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/tools/di/FakeCoreModule.kt @@ -1,5 +1,6 @@ package com.simprints.feature.dashboard.tools.di +import com.simprints.core.AppScope import com.simprints.core.CoreModule import com.simprints.core.DeviceID import com.simprints.core.DispatcherIO @@ -53,6 +54,10 @@ object FakeCoreModule { @Provides fun provideExternalScope(): CoroutineScope = CoroutineScope(Dispatchers.Main + Job()) + @AppScope + @Provides + fun provideAppScope(): CoroutineScope = CoroutineScope(Dispatchers.Main + Job()) + @Provides @Singleton fun provideStringTokenizer(): StringTokenizer = mockk() diff --git a/id/src/main/java/com/simprints/id/Application.kt b/id/src/main/java/com/simprints/id/Application.kt index 8618871f38..b2b536ccb7 100644 --- a/id/src/main/java/com/simprints/id/Application.kt +++ b/id/src/main/java/com/simprints/id/Application.kt @@ -16,6 +16,7 @@ import dagger.hilt.android.HiltAndroidApp import io.reactivex.exceptions.UndeliverableException import io.reactivex.plugins.RxJavaPlugins import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import javax.inject.Inject @@ -44,6 +45,11 @@ open class Application : CoreApplication(), Configuration.Provider { initApplication() } + override fun onTerminate() { + super.onTerminate() + appScope.cancel() + } + override val workManagerConfiguration: Configuration get() = Configuration.Builder() .setWorkerFactory(workerFactory) diff --git a/infra/core/src/main/java/com/simprints/core/workers/SimCoroutineWorker.kt b/infra/core/src/main/java/com/simprints/core/workers/SimCoroutineWorker.kt index 43a1dbdfa3..3268b1c408 100644 --- a/infra/core/src/main/java/com/simprints/core/workers/SimCoroutineWorker.kt +++ b/infra/core/src/main/java/com/simprints/core/workers/SimCoroutineWorker.kt @@ -102,7 +102,7 @@ abstract class SimCoroutineWorker( } protected fun crashlyticsLog(message: String) { - Simber.tag(CrashReportTag.SYNC.name).i("$tag - $message") + Simber.tag(CrashReportTag.SYNC.name).i("$tag - $message".take(99)) } private fun logExceptionIfRequired(t: Throwable?) { diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt index 70abd5d3a3..f49ffcae91 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncConstants.kt @@ -4,7 +4,8 @@ import java.util.concurrent.TimeUnit internal object SyncConstants { - val SYNC_REPEAT_UNIT = TimeUnit.MINUTES + val SYNC_TIME_UNIT = TimeUnit.MINUTES + const val DEFAULT_BACKOFF_INTERVAL_MINUTES = 5L const val PROJECT_SYNC_WORK_NAME = "project-sync-work-v2" const val PROJECT_SYNC_REPEAT_INTERVAL = BuildConfig.PROJECT_DOWN_SYNC_WORKER_INTERVAL_MINUTES diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt index 50f209613d..15096edc94 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt @@ -4,24 +4,31 @@ import androidx.work.Constraints import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.NetworkType import androidx.work.WorkManager +import androidx.work.WorkQuery import androidx.work.workDataOf +import com.simprints.core.AppScope import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.models.imagesUploadRequiresUnmeteredConnection import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.eventsync.sync.master.EventSyncMasterWorker -import com.simprints.infra.sync.extensions.schedulePeriodicWorker -import com.simprints.infra.sync.extensions.startWorker import com.simprints.infra.sync.config.worker.DeviceConfigDownSyncWorker import com.simprints.infra.sync.config.worker.ProjectConfigDownSyncWorker import com.simprints.infra.sync.enrolments.EnrolmentRecordWorker +import com.simprints.infra.sync.extensions.anyRunning import com.simprints.infra.sync.extensions.cancelWorkers +import com.simprints.infra.sync.extensions.schedulePeriodicWorker +import com.simprints.infra.sync.extensions.startWorker import com.simprints.infra.sync.firmware.FirmwareFileUpdateWorker -import com.simprints.infra.sync.usecase.CleanupDeprecatedWorkersUseCase -import com.simprints.infra.sync.images.ImageUpSyncWorker import com.simprints.infra.sync.firmware.ShouldScheduleFirmwareUpdateUseCase +import com.simprints.infra.sync.images.ImageUpSyncWorker +import com.simprints.infra.sync.usecase.CleanupDeprecatedWorkersUseCase +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import javax.inject.Inject +import javax.inject.Singleton +@Singleton internal class SyncOrchestratorImpl @Inject constructor( private val workManager: WorkManager, private val authStore: AuthStore, @@ -29,8 +36,23 @@ internal class SyncOrchestratorImpl @Inject constructor( private val eventSyncManager: EventSyncManager, private val shouldScheduleFirmwareUpdate: ShouldScheduleFirmwareUpdateUseCase, private val cleanupDeprecatedWorkers: CleanupDeprecatedWorkersUseCase, + @AppScope private val appScope: CoroutineScope, ) : SyncOrchestrator { + init { + appScope.launch { + // Stop image upload when event sync starts + workManager.getWorkInfosFlow( + WorkQuery.fromUniqueWorkNames( + SyncConstants.EVENT_SYNC_WORK_NAME, + SyncConstants.EVENT_SYNC_WORK_NAME_ONE_TIME, + ) + ).collect { workInfoList -> + if (workInfoList.anyRunning()) rescheduleImageUpSync() + } + } + } + override suspend fun scheduleBackgroundWork() { if (authStore.signedInProjectId.isNotEmpty()) { workManager.schedulePeriodicWorker( @@ -103,6 +125,7 @@ internal class SyncOrchestratorImpl @Inject constructor( workManager.schedulePeriodicWorker( SyncConstants.IMAGE_UP_SYNC_WORK_NAME, SyncConstants.IMAGE_UP_SYNC_REPEAT_INTERVAL, + initialDelay = SyncConstants.DEFAULT_BACKOFF_INTERVAL_MINUTES, existingWorkPolicy = ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, constraints = getImageUploadConstraints() ) diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt b/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt index a5bd4e7808..6fc5422061 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt @@ -12,14 +12,18 @@ internal inline fun WorkManager.schedulePeriodicW workName: String, repeatInterval: Long, existingWorkPolicy: ExistingPeriodicWorkPolicy = ExistingPeriodicWorkPolicy.UPDATE, + initialDelay: Long = 0, + backoffInterval: Long = SyncConstants.DEFAULT_BACKOFF_INTERVAL_MINUTES, constraints: Constraints = defaultWorkerConstraints(), tags: List = emptyList(), inputData: Data? = null, ) = enqueueUniquePeriodicWork( workName, existingWorkPolicy, - PeriodicWorkRequestBuilder(repeatInterval, SyncConstants.SYNC_REPEAT_UNIT) + PeriodicWorkRequestBuilder(repeatInterval, SyncConstants.SYNC_TIME_UNIT) .setConstraints(constraints) + .setInitialDelay(initialDelay, SyncConstants.SYNC_TIME_UNIT) + .setBackoffCriteria(BackoffPolicy.LINEAR, backoffInterval, SyncConstants.SYNC_TIME_UNIT) .let { if (inputData != null) it.setInputData(inputData) else it } .let { tags.fold(it) { builder, tag -> builder.addTag(tag) } } .build() @@ -27,14 +31,19 @@ internal inline fun WorkManager.schedulePeriodicW internal inline fun WorkManager.startWorker( workName: String, + existingWorkPolicy: ExistingWorkPolicy = ExistingWorkPolicy.KEEP, + initialDelay: Long = 0, + backoffInterval: Long = SyncConstants.DEFAULT_BACKOFF_INTERVAL_MINUTES, constraints: Constraints = defaultWorkerConstraints(), tags: List = emptyList(), inputData: Data? = null, ) = this.enqueueUniqueWork( workName, - ExistingWorkPolicy.KEEP, + existingWorkPolicy, OneTimeWorkRequestBuilder() .setConstraints(constraints) + .setInitialDelay(initialDelay, SyncConstants.SYNC_TIME_UNIT) + .setBackoffCriteria(BackoffPolicy.LINEAR, backoffInterval, SyncConstants.SYNC_TIME_UNIT) .let { if (inputData != null) it.setInputData(inputData) else it } .let { tags.fold(it) { builder, tag -> builder.addTag(tag) } } .build() @@ -43,3 +52,5 @@ internal inline fun WorkManager.startWorker( internal fun WorkManager.cancelWorkers(vararg workNames: String) { workNames.forEach(this::cancelUniqueWork) } + +internal fun List.anyRunning() = any { it.state == WorkInfo.State.RUNNING } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/images/ImageUpSyncWorker.kt b/infra/sync/src/main/java/com/simprints/infra/sync/images/ImageUpSyncWorker.kt index 50c1329b26..74dd9ec18a 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/images/ImageUpSyncWorker.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/images/ImageUpSyncWorker.kt @@ -25,7 +25,6 @@ internal class ImageUpSyncWorker @AssistedInject constructor( override suspend fun doWork(): Result = withContext(dispatcher) { crashlyticsLog("Image upload start") - showProgressNotification() try { if (imageRepository.uploadStoredImagesAndDelete(authStore.signedInProjectId)) { diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt index f2f5bfb147..ccf3da6ce8 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt @@ -3,7 +3,9 @@ package com.simprints.infra.sync import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.NetworkType import androidx.work.OneTimeWorkRequest +import androidx.work.WorkInfo import androidx.work.WorkManager +import com.google.common.util.concurrent.ListenableFuture import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.eventsync.EventSyncManager @@ -18,18 +20,26 @@ import com.simprints.infra.sync.SyncConstants.RECORD_UPLOAD_INPUT_ID_NAME import com.simprints.infra.sync.SyncConstants.RECORD_UPLOAD_INPUT_SUBJECT_IDS_NAME import com.simprints.infra.sync.firmware.ShouldScheduleFirmwareUpdateUseCase import com.simprints.infra.sync.usecase.CleanupDeprecatedWorkersUseCase +import com.simprints.testtools.common.coroutines.TestCoroutineRule import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK +import io.mockk.mockk import io.mockk.verify +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.test.runTest import org.junit.Before +import org.junit.Rule import org.junit.Test +import java.util.UUID class SyncOrchestratorImplTest { + @get:Rule + val testCoroutineRule = TestCoroutineRule() @MockK private lateinit var workManager: WorkManager @@ -55,14 +65,7 @@ class SyncOrchestratorImplTest { fun setup() { MockKAnnotations.init(this, relaxed = true) - syncOrchestrator = SyncOrchestratorImpl( - workManager, - authStore, - configRepo, - eventSyncManager, - shouldScheduleFirmwareUpdate, - cleanupDeprecatedWorkers, - ) + syncOrchestrator = createSyncOrchestrator() } @Test @@ -274,6 +277,59 @@ class SyncOrchestratorImplTest { verify { cleanupDeprecatedWorkers.invoke() } } + @Test + fun `stops image worker when event sync starts`() = runTest { + val eventStartFlow = MutableSharedFlow>() + every { workManager.getWorkInfosFlow(any()) } returns eventStartFlow + every { + workManager.getWorkInfosForUniqueWork(IMAGE_UP_SYNC_WORK_NAME) + } returns mockFuture(createWorkInfo(WorkInfo.State.RUNNING)) + + // Recreating orchestrator with new mocks since the subscription is done in init + syncOrchestrator = createSyncOrchestrator() + eventStartFlow.emit(createWorkInfo(WorkInfo.State.RUNNING)) + + verify { + workManager.enqueueUniquePeriodicWork( + IMAGE_UP_SYNC_WORK_NAME, + ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, + any() + ) + } + } + + @Test + fun `does not stop image worker when event sync is not running`() = runTest { + val eventStartFlow = MutableSharedFlow>() + every { workManager.getWorkInfosFlow(any()) } returns eventStartFlow + + // Recreating orchestrator with new mocks since the subscription is done in init + syncOrchestrator = createSyncOrchestrator() + eventStartFlow.emit(createWorkInfo(WorkInfo.State.CANCELLED)) + + verify(exactly = 0) { + workManager.getWorkInfosForUniqueWork(IMAGE_UP_SYNC_WORK_NAME) + workManager.cancelWorkById(any()) + } + } + + private fun createSyncOrchestrator() = SyncOrchestratorImpl( + workManager, + authStore, + configRepo, + eventSyncManager, + shouldScheduleFirmwareUpdate, + cleanupDeprecatedWorkers, + CoroutineScope(testCoroutineRule.testCoroutineDispatcher), + ) + + private fun mockFuture(workInfo: List) = + mockk>> { every { get() } returns workInfo } + + private fun createWorkInfo(state: WorkInfo.State) = listOf( + WorkInfo(UUID.randomUUID(), state, emptySet()) + ) + companion object { private const val INSTRUCTION_ID = "id" From 582b824bed75f5d6e4bb8620e99bbf2fd902d1e4 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Thu, 22 Feb 2024 10:38:04 +0200 Subject: [PATCH 032/197] MS-205 Use custom base class for all workers --- .../infra/sync/enrolments/EnrolmentRecordWorker.kt | 6 +++++- .../infra/sync/firmware/FirmwareFileUpdateWorker.kt | 9 +++++---- .../com/simprints/infra/sync/images/ImageUpSyncWorker.kt | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/enrolments/EnrolmentRecordWorker.kt b/infra/sync/src/main/java/com/simprints/infra/sync/enrolments/EnrolmentRecordWorker.kt index 00124ee5e1..f4ea063b63 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/enrolments/EnrolmentRecordWorker.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/enrolments/EnrolmentRecordWorker.kt @@ -5,6 +5,7 @@ import androidx.hilt.work.HiltWorker import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import com.simprints.core.DispatcherIO +import com.simprints.core.workers.SimCoroutineWorker import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.enrolment.records.store.EnrolmentRecordRepository import com.simprints.infra.sync.SyncConstants @@ -20,9 +21,12 @@ class EnrolmentRecordWorker @AssistedInject constructor( private val enrolmentRecordRepository: EnrolmentRecordRepository, private val configRepository: ConfigRepository, @DispatcherIO private val dispatcher: CoroutineDispatcher, -) : CoroutineWorker(context, params) { +) : SimCoroutineWorker(context, params) { + + override val tag: String = "EnrolmentRecordWorker" override suspend fun doWork(): Result = withContext(dispatcher) { + crashlyticsLog("Enrolment record upload start") try { val instructionId = inputData.getString(SyncConstants.RECORD_UPLOAD_INPUT_ID_NAME) diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/firmware/FirmwareFileUpdateWorker.kt b/infra/sync/src/main/java/com/simprints/infra/sync/firmware/FirmwareFileUpdateWorker.kt index 39745d17dd..c87d1df2a0 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/firmware/FirmwareFileUpdateWorker.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/firmware/FirmwareFileUpdateWorker.kt @@ -2,9 +2,9 @@ package com.simprints.infra.sync.firmware import android.content.Context import androidx.hilt.work.HiltWorker -import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import com.simprints.core.DispatcherBG +import com.simprints.core.workers.SimCoroutineWorker import com.simprints.fingerprint.infra.scanner.data.FirmwareRepository import com.simprints.infra.logging.Simber import com.simprints.infra.network.exceptions.NetworkConnectionException @@ -24,16 +24,17 @@ class FirmwareFileUpdateWorker @AssistedInject constructor( @Assisted params: WorkerParameters, private val firmwareRepository: FirmwareRepository, @DispatcherBG private val dispatcher: CoroutineDispatcher, -) : CoroutineWorker(context, params) { +) : SimCoroutineWorker(context, params) { + override val tag: String = "FirmwareFileUpdateWorker" override suspend fun doWork(): Result = withContext(dispatcher) { + crashlyticsLog("FirmwareFileUpdateWorker started") try { - Simber.d("FirmwareFileUpdateWorker started") firmwareRepository.updateStoredFirmwareFilesWithLatest() firmwareRepository.cleanUpOldFirmwareFiles() - Simber.d("FirmwareFileUpdateWorker succeeded") + crashlyticsLog("FirmwareFileUpdateWorker succeeded") Result.success() } catch (e: Throwable) { when { diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/images/ImageUpSyncWorker.kt b/infra/sync/src/main/java/com/simprints/infra/sync/images/ImageUpSyncWorker.kt index 74dd9ec18a..0e456438af 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/images/ImageUpSyncWorker.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/images/ImageUpSyncWorker.kt @@ -21,7 +21,7 @@ internal class ImageUpSyncWorker @AssistedInject constructor( @DispatcherBG private val dispatcher: CoroutineDispatcher, ) : SimCoroutineWorker(context, params) { - override val tag: String = ImageUpSyncWorker::class.java.simpleName + override val tag: String = "ImageUpSyncWorker" override suspend fun doWork(): Result = withContext(dispatcher) { crashlyticsLog("Image upload start") From 4928afd81ac084e6cda7625f23f72554efcec27a Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Mon, 26 Feb 2024 10:05:52 +0200 Subject: [PATCH 033/197] MS-205 Exclude work manager wrapper from code coverage --- .../com/simprints/infra/sync/extensions/WorkManager.ext.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt b/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt index 6fc5422061..60a131e373 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/extensions/WorkManager.ext.kt @@ -1,6 +1,7 @@ package com.simprints.infra.sync.extensions import androidx.work.* +import com.simprints.core.ExcludedFromGeneratedTestCoverageReports import com.simprints.infra.sync.SyncConstants @@ -8,6 +9,7 @@ internal fun defaultWorkerConstraints() = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build() +@ExcludedFromGeneratedTestCoverageReports("Basic API wrapper to provide default values for most parameters") internal inline fun WorkManager.schedulePeriodicWorker( workName: String, repeatInterval: Long, @@ -29,6 +31,7 @@ internal inline fun WorkManager.schedulePeriodicW .build() ) +@ExcludedFromGeneratedTestCoverageReports("Basic API wrapper to provide default values for most parameters") internal inline fun WorkManager.startWorker( workName: String, existingWorkPolicy: ExistingWorkPolicy = ExistingWorkPolicy.KEEP, From 6bd076d201d617ba2c838cf49e819e275fc11580 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Thu, 22 Feb 2024 14:16:37 +0200 Subject: [PATCH 034/197] MS-229 Add batch sizes to sync configuration --- .../dashboard/main/sync/SyncViewModelTest.kt | 3 +- .../store/local/ConfigLocalDataSourceImpl.kt | 3 +- .../migrations/models/OldProjectConfig.kt | 7 ++++- .../models/UpSynchronizationConfiguration.kt | 12 +++++++- .../models/UpSynchronizationConfiguration.kt | 17 ++++++++++- .../models/ApiSynchronizationConfiguration.kt | 28 +++++++++++++++++-- .../src/main/proto/project_config.proto | 6 ++++ .../ProjectConfigSharedPrefsMigrationTest.kt | 4 +++ .../store/models/ProjectConfigurationTest.kt | 28 ++++++++++++++++--- .../infra/config/store/testtools/Models.kt | 14 ++++++++-- .../infra/sync/config/testtools/Models.kt | 3 +- 11 files changed, 110 insertions(+), 15 deletions(-) diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncViewModelTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncViewModelTest.kt index 19754dc4c8..b709c92ec3 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncViewModelTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncViewModelTest.kt @@ -23,6 +23,7 @@ import com.simprints.infra.config.store.models.DeviceConfiguration import com.simprints.infra.config.store.models.DownSynchronizationConfiguration import com.simprints.infra.config.store.models.ProjectState import com.simprints.infra.config.store.models.SynchronizationConfiguration +import com.simprints.infra.config.store.models.UpSynchronizationConfiguration import com.simprints.infra.config.store.models.UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration import com.simprints.infra.config.store.models.UpSynchronizationConfiguration.UpSynchronizationKind.ALL import com.simprints.infra.eventsync.EventSyncManager @@ -95,7 +96,7 @@ internal class SyncViewModelTest { every { eventSyncManager.getLastSyncState() } returns syncState every { connectivityTracker.observeIsConnected() } returns isConnected coEvery { configRepository.getProjectConfiguration().synchronization } returns mockk { - every { up.simprints } returns SimprintsUpSynchronizationConfiguration(kind = ALL) + every { up.simprints } returns SimprintsUpSynchronizationConfiguration(kind = ALL, batchSizes = UpSynchronizationConfiguration.UpSyncBatchSizes.default()) every { frequency } returns SynchronizationConfiguration.Frequency.PERIODICALLY_AND_ON_SESSION_START every { down.partitionType } returns DownSynchronizationConfiguration.PartitionType.MODULE } diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/ConfigLocalDataSourceImpl.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/ConfigLocalDataSourceImpl.kt index c4a06907d2..10429841b8 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/ConfigLocalDataSourceImpl.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/ConfigLocalDataSourceImpl.kt @@ -179,7 +179,8 @@ internal class ConfigLocalDataSourceImpl @Inject constructor( frequency = SynchronizationConfiguration.Frequency.PERIODICALLY, up = UpSynchronizationConfiguration( simprints = UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration( - kind = UpSynchronizationConfiguration.UpSynchronizationKind.NONE + kind = UpSynchronizationConfiguration.UpSynchronizationKind.NONE, + batchSizes = UpSynchronizationConfiguration.UpSyncBatchSizes.default(), ), coSync = UpSynchronizationConfiguration.CoSyncUpSynchronizationConfiguration( kind = UpSynchronizationConfiguration.UpSynchronizationKind.NONE diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/migrations/models/OldProjectConfig.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/migrations/models/OldProjectConfig.kt index fb320592b3..fe6b50e8bd 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/migrations/models/OldProjectConfig.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/migrations/models/OldProjectConfig.kt @@ -197,7 +197,12 @@ internal data class OldProjectConfig( UpSynchronizationConfiguration.UpSynchronizationKind.valueOf( simprintsSync ) - } + }, + batchSizes = UpSynchronizationConfiguration.UpSyncBatchSizes( + sessions = 1, + upSyncs = 1, + downSyncs = 1, + ), ), coSync = UpSynchronizationConfiguration.CoSyncUpSynchronizationConfiguration( kind = if (coSync == null) { diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/UpSynchronizationConfiguration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/UpSynchronizationConfiguration.kt index f0a3ae2b76..bb761afcbd 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/UpSynchronizationConfiguration.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/UpSynchronizationConfiguration.kt @@ -13,6 +13,7 @@ internal fun UpSynchronizationConfiguration.toProto(): ProtoUpSynchronizationCon internal fun UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration.toProto(): ProtoUpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration = ProtoUpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration.newBuilder() .setKind(kind.toProto()) + .setBatchSizes(batchSizes.toProto()) .build() internal fun UpSynchronizationConfiguration.CoSyncUpSynchronizationConfiguration.toProto(): ProtoUpSynchronizationConfiguration.CoSyncUpSynchronizationConfiguration = @@ -28,11 +29,17 @@ internal fun UpSynchronizationConfiguration.UpSynchronizationKind.toProto(): Pro UpSynchronizationConfiguration.UpSynchronizationKind.ALL -> ProtoUpSynchronizationConfiguration.UpSynchronizationKind.ALL } +internal fun UpSynchronizationConfiguration.UpSyncBatchSizes.toProto(): ProtoUpSyncBatchSizes = ProtoUpSyncBatchSizes.newBuilder() + .setSessions(sessions) + .setUpSyncs(upSyncs) + .setDownSyncs(downSyncs) + .build() + internal fun ProtoUpSynchronizationConfiguration.toDomain(): UpSynchronizationConfiguration = UpSynchronizationConfiguration(simprints.toDomain(), coSync.toDomain(), imagesRequireUnmeteredConnection) internal fun ProtoUpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration.toDomain(): UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration = - UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration(kind.toDomain()) + UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration(kind.toDomain(), batchSizes.toDomain()) internal fun ProtoUpSynchronizationConfiguration.CoSyncUpSynchronizationConfiguration.toDomain(): UpSynchronizationConfiguration.CoSyncUpSynchronizationConfiguration = UpSynchronizationConfiguration.CoSyncUpSynchronizationConfiguration(kind.toDomain()) @@ -47,3 +54,6 @@ internal fun ProtoUpSynchronizationConfiguration.UpSynchronizationKind.toDomain( "invalid UpSynchronizationKind $name" ) } + +internal fun ProtoUpSyncBatchSizes.toDomain(): UpSynchronizationConfiguration.UpSyncBatchSizes = + UpSynchronizationConfiguration.UpSyncBatchSizes(sessions, upSyncs, downSyncs) diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/models/UpSynchronizationConfiguration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/models/UpSynchronizationConfiguration.kt index 7c949560b4..1c80f5bc4d 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/models/UpSynchronizationConfiguration.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/models/UpSynchronizationConfiguration.kt @@ -6,10 +6,25 @@ data class UpSynchronizationConfiguration( val imagesRequireUnmeteredConnection: Boolean, ) { - data class SimprintsUpSynchronizationConfiguration(val kind: UpSynchronizationKind) + data class SimprintsUpSynchronizationConfiguration( + val kind: UpSynchronizationKind, + val batchSizes: UpSyncBatchSizes, + ) data class CoSyncUpSynchronizationConfiguration(val kind: UpSynchronizationKind) + data class UpSyncBatchSizes( + val sessions: Int, + val upSyncs: Int, + val downSyncs: Int, + ) { + + companion object { + + fun default() = UpSyncBatchSizes(1, 1, 1) + } + } + enum class UpSynchronizationKind { NONE, ONLY_ANALYTICS, diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiSynchronizationConfiguration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiSynchronizationConfiguration.kt index 23ef057178..4ce876ccc3 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiSynchronizationConfiguration.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiSynchronizationConfiguration.kt @@ -22,6 +22,7 @@ internal data class ApiSynchronizationConfiguration( @Keep enum class Frequency { + ONLY_PERIODICALLY_UP_SYNC, PERIODICALLY, PERIODICALLY_AND_ON_SESSION_START; @@ -49,19 +50,28 @@ internal data class ApiSynchronizationConfiguration( ) @Keep - data class ApiSimprintsUpSynchronizationConfiguration(val kind: UpSynchronizationKind) { + data class ApiSimprintsUpSynchronizationConfiguration( + val kind: UpSynchronizationKind, + val batchSizes: ApiUpSyncBatchSizes?, + ) { + fun toDomain(): UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration = - UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration(kind.toDomain()) + UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration( + kind.toDomain(), + batchSizes?.toDomain() ?: UpSynchronizationConfiguration.UpSyncBatchSizes.default() + ) } @Keep data class ApiCoSyncUpSynchronizationConfiguration(val kind: UpSynchronizationKind) { + fun toDomain(): UpSynchronizationConfiguration.CoSyncUpSynchronizationConfiguration = UpSynchronizationConfiguration.CoSyncUpSynchronizationConfiguration(kind.toDomain()) } @Keep enum class UpSynchronizationKind { + NONE, ONLY_ANALYTICS, ONLY_BIOMETRICS, @@ -75,13 +85,24 @@ internal data class ApiSynchronizationConfiguration( ALL -> UpSynchronizationConfiguration.UpSynchronizationKind.ALL } } + + @Keep + data class ApiUpSyncBatchSizes( + val sessions: Int, + val upSyncs: Int, + val downSyncs: Int, + ) { + + fun toDomain(): UpSynchronizationConfiguration.UpSyncBatchSizes = + UpSynchronizationConfiguration.UpSyncBatchSizes(sessions, upSyncs, downSyncs) + } } @Keep data class ApiDownSynchronizationConfiguration( val partitionType: PartitionType, val maxNbOfModules: Int, - val moduleOptions: List? + val moduleOptions: List?, ) { fun toDomain(): DownSynchronizationConfiguration = @@ -93,6 +114,7 @@ internal data class ApiSynchronizationConfiguration( @Keep enum class PartitionType { + PROJECT, MODULE, USER; diff --git a/infra/config-store/src/main/proto/project_config.proto b/infra/config-store/src/main/proto/project_config.proto index 158d6f46b3..3e318f2cd4 100644 --- a/infra/config-store/src/main/proto/project_config.proto +++ b/infra/config-store/src/main/proto/project_config.proto @@ -175,6 +175,7 @@ message ProtoUpSynchronizationConfiguration { message SimprintsUpSynchronizationConfiguration { UpSynchronizationKind kind = 1; + ProtoUpSyncBatchSizes batchSizes = 2; } message CoSyncUpSynchronizationConfiguration { @@ -189,6 +190,11 @@ message ProtoUpSynchronizationConfiguration { } } +message ProtoUpSyncBatchSizes { + int32 sessions = 1; + int32 upSyncs = 2; + int32 downSyncs = 3; +} message ProtoDecisionPolicy { int32 low = 1; diff --git a/infra/config-store/src/test/java/com/simprints/infra/config/store/local/migrations/ProjectConfigSharedPrefsMigrationTest.kt b/infra/config-store/src/test/java/com/simprints/infra/config/store/local/migrations/ProjectConfigSharedPrefsMigrationTest.kt index f1c381940e..dc772e0661 100644 --- a/infra/config-store/src/test/java/com/simprints/infra/config/store/local/migrations/ProjectConfigSharedPrefsMigrationTest.kt +++ b/infra/config-store/src/test/java/com/simprints/infra/config/store/local/migrations/ProjectConfigSharedPrefsMigrationTest.kt @@ -501,6 +501,7 @@ class ProjectConfigSharedPrefsMigrationTest { .setSimprints( ProtoUpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration.newBuilder() .setKind(ProtoUpSynchronizationConfiguration.UpSynchronizationKind.ALL) + .setBatchSizes(ProtoUpSyncBatchSizes.newBuilder().setSessions(1).setUpSyncs(1).setDownSyncs(1).build()) .build() ) .setCoSync( @@ -526,6 +527,7 @@ class ProjectConfigSharedPrefsMigrationTest { .setSimprints( ProtoUpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration.newBuilder() .setKind(ProtoUpSynchronizationConfiguration.UpSynchronizationKind.ALL) + .setBatchSizes(ProtoUpSyncBatchSizes.newBuilder().setSessions(1).setUpSyncs(1).setDownSyncs(1).build()) .build() ) .setCoSync( @@ -551,6 +553,7 @@ class ProjectConfigSharedPrefsMigrationTest { .setSimprints( ProtoUpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration.newBuilder() .setKind(ProtoUpSynchronizationConfiguration.UpSynchronizationKind.NONE) + .setBatchSizes(ProtoUpSyncBatchSizes.newBuilder().setSessions(1).setUpSyncs(1).setDownSyncs(1).build()) .build() ) .setCoSync( @@ -577,6 +580,7 @@ class ProjectConfigSharedPrefsMigrationTest { .setSimprints( ProtoUpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration.newBuilder() .setKind(ProtoUpSynchronizationConfiguration.UpSynchronizationKind.NONE) + .setBatchSizes(ProtoUpSyncBatchSizes.newBuilder().setSessions(1).setUpSyncs(1).setDownSyncs(1).build()) .build() ) .setCoSync( diff --git a/infra/config-store/src/test/java/com/simprints/infra/config/store/models/ProjectConfigurationTest.kt b/infra/config-store/src/test/java/com/simprints/infra/config/store/models/ProjectConfigurationTest.kt index 36104a673a..99951bd9b3 100644 --- a/infra/config-store/src/test/java/com/simprints/infra/config/store/models/ProjectConfigurationTest.kt +++ b/infra/config-store/src/test/java/com/simprints/infra/config/store/models/ProjectConfigurationTest.kt @@ -117,7 +117,12 @@ class ProjectConfigurationTest { synchronization = synchronizationConfiguration.copy( up = synchronizationConfiguration.up.copy( simprints = SimprintsUpSynchronizationConfiguration( - kind = it.key + kind = it.key, + batchSizes = UpSynchronizationConfiguration.UpSyncBatchSizes( + sessions = 1, + upSyncs = 1, + downSyncs = 1 + ), ) ) ) @@ -140,7 +145,12 @@ class ProjectConfigurationTest { synchronization = synchronizationConfiguration.copy( up = synchronizationConfiguration.up.copy( simprints = SimprintsUpSynchronizationConfiguration( - kind = it.key + kind = it.key, + batchSizes = UpSynchronizationConfiguration.UpSyncBatchSizes( + sessions = 1, + upSyncs = 1, + downSyncs = 1 + ), ) ) ) @@ -163,7 +173,12 @@ class ProjectConfigurationTest { synchronization = synchronizationConfiguration.copy( up = synchronizationConfiguration.up.copy( simprints = SimprintsUpSynchronizationConfiguration( - kind = it.key + kind = it.key, + batchSizes = UpSynchronizationConfiguration.UpSyncBatchSizes( + sessions = 1, + upSyncs = 1, + downSyncs = 1 + ), ) ) ) @@ -186,7 +201,12 @@ class ProjectConfigurationTest { synchronization = synchronizationConfiguration.copy( up = synchronizationConfiguration.up.copy( simprints = SimprintsUpSynchronizationConfiguration( - kind = it.key + kind = it.key, + batchSizes = UpSynchronizationConfiguration.UpSyncBatchSizes( + sessions = 1, + upSyncs = 1, + downSyncs = 1 + ), ) ) ) diff --git a/infra/config-store/src/test/java/com/simprints/infra/config/store/testtools/Models.kt b/infra/config-store/src/test/java/com/simprints/infra/config/store/testtools/Models.kt index 1a4b7c631e..8983170583 100644 --- a/infra/config-store/src/test/java/com/simprints/infra/config/store/testtools/Models.kt +++ b/infra/config-store/src/test/java/com/simprints/infra/config/store/testtools/Models.kt @@ -11,6 +11,7 @@ import com.simprints.infra.config.store.local.models.ProtoIdentificationConfigur import com.simprints.infra.config.store.local.models.ProtoProject import com.simprints.infra.config.store.local.models.ProtoProjectConfiguration import com.simprints.infra.config.store.local.models.ProtoSynchronizationConfiguration +import com.simprints.infra.config.store.local.models.ProtoUpSyncBatchSizes import com.simprints.infra.config.store.local.models.ProtoUpSynchronizationConfiguration import com.simprints.infra.config.store.local.models.ProtoVero2Configuration import com.simprints.infra.config.store.local.models.toProto @@ -200,7 +201,8 @@ internal val apiSynchronizationConfiguration = ApiSynchronizationConfiguration( ApiSynchronizationConfiguration.Frequency.PERIODICALLY, ApiSynchronizationConfiguration.ApiUpSynchronizationConfiguration( ApiSynchronizationConfiguration.ApiUpSynchronizationConfiguration.ApiSimprintsUpSynchronizationConfiguration( - ApiSynchronizationConfiguration.ApiUpSynchronizationConfiguration.UpSynchronizationKind.ALL + ApiSynchronizationConfiguration.ApiUpSynchronizationConfiguration.UpSynchronizationKind.ALL, + ApiSynchronizationConfiguration.ApiUpSynchronizationConfiguration.ApiUpSyncBatchSizes(1, 2, 3), ), ApiSynchronizationConfiguration.ApiUpSynchronizationConfiguration.ApiCoSyncUpSynchronizationConfiguration( ApiSynchronizationConfiguration.ApiUpSynchronizationConfiguration.UpSynchronizationKind.NONE @@ -218,7 +220,8 @@ internal val synchronizationConfiguration = SynchronizationConfiguration( SynchronizationConfiguration.Frequency.PERIODICALLY, UpSynchronizationConfiguration( UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration( - UpSynchronizationConfiguration.UpSynchronizationKind.ALL + UpSynchronizationConfiguration.UpSynchronizationKind.ALL, + UpSynchronizationConfiguration.UpSyncBatchSizes(1, 2, 3), ), UpSynchronizationConfiguration.CoSyncUpSynchronizationConfiguration( UpSynchronizationConfiguration.UpSynchronizationKind.NONE @@ -239,6 +242,13 @@ internal val protoSynchronizationConfiguration = ProtoSynchronizationConfigurati .setSimprints( ProtoUpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration.newBuilder() .setKind(ProtoUpSynchronizationConfiguration.UpSynchronizationKind.ALL) + .setBatchSizes( + ProtoUpSyncBatchSizes.newBuilder() + .setSessions(1) + .setUpSyncs(2) + .setDownSyncs(3) + .build() + ) .build() ) .setCoSync( diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/config/testtools/Models.kt b/infra/sync/src/test/java/com/simprints/infra/sync/config/testtools/Models.kt index 3f19e7c079..49c3733c09 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/config/testtools/Models.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/config/testtools/Models.kt @@ -63,7 +63,8 @@ internal val synchronizationConfiguration = SynchronizationConfiguration( SynchronizationConfiguration.Frequency.PERIODICALLY, UpSynchronizationConfiguration( UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration( - UpSynchronizationConfiguration.UpSynchronizationKind.ALL + UpSynchronizationConfiguration.UpSynchronizationKind.ALL, + UpSynchronizationConfiguration.UpSyncBatchSizes.default() ), UpSynchronizationConfiguration.CoSyncUpSynchronizationConfiguration( UpSynchronizationConfiguration.UpSynchronizationKind.NONE From 67fb945d5aaea75def79327535845b73e909bffb Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Thu, 22 Feb 2024 15:53:51 +0200 Subject: [PATCH 035/197] MS-229 Upload events in batches of provided size --- .../sync/up/tasks/EventUpSyncTask.kt | 169 +++++++++--------- .../sync/up/tasks/EventUpSyncTaskTest.kt | 48 +++-- .../events/sampledata/EventFactoryUtils.kt | 8 +- .../models/upsync/EventUpSyncRequestEvent.kt | 10 +- 4 files changed, 125 insertions(+), 110 deletions(-) diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt index 140dcfac02..6445ef2d33 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt @@ -62,32 +62,84 @@ internal class EventUpSyncTask @Inject constructor( val config = configRepository.getProjectConfiguration() var lastOperation = operation.copy() var count = 0 + var isUsefulUpload = false try { - lastOperation = - lastOperation.copy(lastState = RUNNING, lastSyncTime = timeHelper.now().ms) + lastOperation = lastOperation.copy( + lastState = RUNNING, + lastSyncTime = timeHelper.now().ms + ) - uploadSessionEvents(projectId = operation.projectId, config, eventScope).collect { + uploadEventScopeType( + eventScope = eventScope, + projectId = operation.projectId, + eventScopeTypeToUpload = EventScopeType.SESSION, + batchSize = config.synchronization.up.simprints.batchSizes.sessions, + eventFilter = { scopes -> + scopes.mapValues { (_, events) -> + events?.let { filterEventsToUpSync(it, config) } + } + }, + createUpSyncContentContent = { + isUsefulUpload = it > 0 + EventUpSyncRequestEvent.UpSyncContent(sessionCount = it) + }, + ).collect { count = it - lastOperation = - lastOperation.copy(lastState = RUNNING, lastSyncTime = timeHelper.now().ms) + lastOperation = lastOperation.copy( + lastState = RUNNING, + lastSyncTime = timeHelper.now().ms + ) emitProgress(lastOperation, count) } - uploadOutOfSessionEvents(operation.projectId, eventScope).collect { + uploadEventScopeType( + eventScope = eventScope, + projectId = operation.projectId, + eventScopeTypeToUpload = EventScopeType.DOWN_SYNC, + batchSize = config.synchronization.up.simprints.batchSizes.downSyncs, + createUpSyncContentContent = { + isUsefulUpload = it > 0 + EventUpSyncRequestEvent.UpSyncContent(eventDownSyncCount = it) + }, + ).collect { count = it - lastOperation = - lastOperation.copy(lastState = RUNNING, lastSyncTime = timeHelper.now().ms) + lastOperation = lastOperation.copy( + lastState = RUNNING, + lastSyncTime = timeHelper.now().ms + ) emitProgress(lastOperation, count) } + uploadEventScopeType( + eventScope = eventScope, + projectId = operation.projectId, + eventScopeTypeToUpload = EventScopeType.UP_SYNC, + batchSize = config.synchronization.up.simprints.batchSizes.upSyncs, + createUpSyncContentContent = { + // Only tracking up-sync if there have been ay events in other scopes. + EventUpSyncRequestEvent.UpSyncContent( + eventUpSyncCount = if (isUsefulUpload) it else 0 + ) + }, + ).collect { + count = it + lastOperation = lastOperation.copy( + lastState = RUNNING, + lastSyncTime = timeHelper.now().ms + ) + emitProgress(lastOperation, count) + } + lastOperation = lastOperation.copy( + lastState = COMPLETE, + lastSyncTime = timeHelper.now().ms + ) - lastOperation = - lastOperation.copy(lastState = COMPLETE, lastSyncTime = timeHelper.now().ms) emitProgress(lastOperation, count) } catch (t: Throwable) { Simber.e(t) - - lastOperation = - lastOperation.copy(lastState = FAILED, lastSyncTime = timeHelper.now().ms) + lastOperation = lastOperation.copy( + lastState = FAILED, + lastSyncTime = timeHelper.now().ms + ) emitProgress(lastOperation, count) } @@ -101,13 +153,16 @@ internal class EventUpSyncTask @Inject constructor( this.emit(EventUpSyncProgress(lastOperation, count)) } - private fun uploadSessionEvents( - projectId: String, - config: ProjectConfiguration, + private fun uploadEventScopeType( eventScope: EventScope, + projectId: String, + eventScopeTypeToUpload: EventScopeType, + batchSize: Int, + eventFilter: (Map?>) -> Map?> = { it }, + createUpSyncContentContent: (Int) -> EventUpSyncRequestEvent.UpSyncContent, ) = flow { - Simber.d("[EVENT_REPO] Uploading session scopes") - val sessionScopes = getClosedScopesForType(EventScopeType.SESSION) + Simber.d("[EVENT_REPO] Uploading event scope - $eventScopeTypeToUpload in batches of $batchSize") + val sessionScopes = getClosedScopesForType(eventScopeTypeToUpload) // Re-emitting the number of uploaded corrupted events attemptInvalidEventUpload( @@ -117,13 +172,11 @@ internal class EventUpSyncTask @Inject constructor( val scopesToUpload = sessionScopes .filterValues { it != null } - .mapValues { (_, events) -> - events?.let { filterEventsToUpSync(events, config) }.orEmpty() - } - .map { (scope, events) -> ApiEventScope.fromDomain(scope, events) } + .let(eventFilter) + .map { (scope, events) -> ApiEventScope.fromDomain(scope, events.orEmpty()) } val uploadedScopes = mutableListOf() - scopesToUpload.chunked(UPLOAD_BATCH_SIZE).forEach { scope -> + scopesToUpload.chunked(batchSize).forEach { scope -> val requestStartTime = timeHelper.now() try { val result = eventRemoteDataSource.post( @@ -134,7 +187,7 @@ internal class EventUpSyncTask @Inject constructor( eventScope = eventScope, startTime = requestStartTime, result = result, - uploadedSessionScopes = scope.size, + content = createUpSyncContentContent(scope.size), ) uploadedScopes.addAll(scope.map { it.id }) } catch (ex: Exception) { @@ -146,55 +199,6 @@ internal class EventUpSyncTask @Inject constructor( eventRepository.deleteEventScopes(uploadedScopes) } - private fun uploadOutOfSessionEvents(projectId: String, eventScope: EventScope) = flow { - Simber.d("[EVENT_REPO] Uploading event scopes") - - val upSyncScopes = getClosedScopesForType(EventScopeType.UP_SYNC) - val downSyncScopes = getClosedScopesForType(EventScopeType.DOWN_SYNC) - - // Re-emitting the number of uploaded corrupted events - attemptInvalidEventUpload( - projectId, - upSyncScopes.getCorruptedScopes() + downSyncScopes.getCorruptedScopes() - ).collect { emit(it) } - - val upSyncScopesToUpload = upSyncScopes - .filterValues { it != null } - .map { (scope, events) -> ApiEventScope.fromDomain(scope, events.orEmpty()) } - - val downSyncScopesToUpload = downSyncScopes - .filterValues { it != null } - .map { (scope, events) -> ApiEventScope.fromDomain(scope, events.orEmpty()) } - - if (upSyncScopesToUpload.isNotEmpty() || downSyncScopesToUpload.isNotEmpty()) { - val requestStartTime = timeHelper.now() - try { - val result = eventRemoteDataSource.post( - projectId, - ApiUploadEventsBody( - eventUpSyncs = upSyncScopesToUpload, - eventDownSyncs = downSyncScopesToUpload, - ) - ) - addRequestEvent( - eventScope = eventScope, - startTime = requestStartTime, - result = result, - uploadedUpSyncScopes = upSyncScopesToUpload.size, - uploadedDownSyncScopes = downSyncScopesToUpload.size, - ) - } catch (ex: Exception) { - handleFailedRequest(ex, eventScope, requestStartTime) - } - } - - val uploadedScopes = - upSyncScopesToUpload.map { it.id } + downSyncScopesToUpload.map { it.id } - Simber.d("[EVENT_REPO] Deleting ${uploadedScopes.size} event scopes") - uploadedScopes.forEach { eventRepository.deleteEventScope(it) } - } - - private fun Map?>.getCorruptedScopes() = filterValues { it == null }.keys @@ -225,23 +229,16 @@ internal class EventUpSyncTask @Inject constructor( eventScope: EventScope, startTime: Timestamp, result: EventUpSyncResult, - uploadedSessionScopes: Int = 0, - uploadedUpSyncScopes: Int = 0, - uploadedDownSyncScopes: Int = 0, + content: EventUpSyncRequestEvent.UpSyncContent, ) { - if (uploadedSessionScopes > 0 || uploadedDownSyncScopes > 0) { - // Not tracking cases when only up sync scopes are uploaded as it is likely - // to cause a feedback loop of up-syncing the previous up-sync event. - + if (content.sessionCount > 0 || content.eventDownSyncCount > 0 || content.eventUpSyncCount > 0) { eventRepository.addOrUpdateEvent( eventScope, EventUpSyncRequestEvent( createdAt = startTime, endedAt = timeHelper.now(), requestId = result.requestId, - sessionCount = uploadedSessionScopes, - eventUpSyncCount = uploadedUpSyncScopes, - eventDownSyncCount = uploadedDownSyncScopes, + content = content, responseStatus = result.status, ) ) @@ -330,10 +327,4 @@ internal class EventUpSyncTask @Inject constructor( } } - companion object { - - // TODO: This should be configurable via project configuration - private const val UPLOAD_BATCH_SIZE = 100 - } - } diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTaskTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTaskTest.kt index 21535b0a0a..8d51db2e87 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTaskTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTaskTest.kt @@ -83,6 +83,9 @@ internal class EventUpSyncTaskTest { every { timeHelper.now() } returns NOW every { authStore.signedInProjectId } returns DEFAULT_PROJECT_ID + every { synchronizationConfiguration.up.simprints.batchSizes } returns UpSynchronizationConfiguration.UpSyncBatchSizes( + 10, 10, 10 + ) every { projectConfiguration.synchronization } returns synchronizationConfiguration coEvery { configRepository.getProjectConfiguration() } returns projectConfiguration @@ -118,6 +121,28 @@ internal class EventUpSyncTaskTest { coVerify(exactly = 2) { eventRepo.getEventsFromScope(any()) } } + @Test + fun `upload events in batches of provided size`() = runTest { + setUpSyncKind(UpSynchronizationConfiguration.UpSynchronizationKind.ALL) + every { synchronizationConfiguration.up.simprints.batchSizes } returns UpSynchronizationConfiguration.UpSyncBatchSizes( + 2, 2, 2 + ) + + coEvery { eventRepo.getClosedEventScopes(any()) } returns emptyList() + coEvery { eventRepo.getClosedEventScopes(EventScopeType.SESSION) } returns listOf( + createSessionScope(GUID1), + createSessionScope(GUID2), + createSessionScope(GUID3) + ) + coEvery { + eventRepo.getEventsFromScope(any()) + } returns listOf(createEventWithSessionId(GUID1, GUID1)) + + eventUpSyncTask.upSync(operation, eventScope).toList() + + coVerify(exactly = 2) { eventRemoteDataSource.post(any(), any()) } + } + @Test fun `upload should not filter any session events on upload`() = runTest { setUpSyncKind(UpSynchronizationConfiguration.UpSynchronizationKind.ALL) @@ -219,7 +244,7 @@ internal class EventUpSyncTaskTest { fun `when upload succeeds it should delete session events`() = runTest { setUpSyncKind(UpSynchronizationConfiguration.UpSynchronizationKind.ALL) - coEvery { eventRepo.getClosedEventScopes(any()) } returns listOf( + coEvery { eventRepo.getClosedEventScopes(EventScopeType.SESSION) } returns listOf( createSessionScope(GUID1), createSessionScope(GUID2) ) @@ -233,8 +258,7 @@ internal class EventUpSyncTaskTest { eventUpSyncTask.upSync(operation, eventScope).toList() coVerify { - eventRepo.deleteEventScope(GUID1) - eventRepo.deleteEventScope(GUID2) + eventRepo.deleteEventScopes(listOf(GUID1, GUID2)) } } @@ -398,7 +422,7 @@ internal class EventUpSyncTaskTest { fun `upload should save request event for each upload request`() = runTest { setUpSyncKind(UpSynchronizationConfiguration.UpSynchronizationKind.ALL) - coEvery { eventRepo.getClosedEventScopes(any()) } returns listOf( + coEvery { eventRepo.getClosedEventScopes(EventScopeType.SESSION) } returns listOf( createSessionScope(GUID1), createSessionScope(GUID2) ) @@ -461,20 +485,24 @@ internal class EventUpSyncTaskTest { eventUpSyncTask.upSync(operation, eventScope).toList() + coVerify(exactly = 3) { eventRepo.getClosedEventScopes(any()) } + coVerify(exactly = 3) { eventRepo.addOrUpdateEvent(any(), any()) } + coVerify(exactly = 1) { eventRepo.addOrUpdateEvent(any(), match { - it is EventUpSyncRequestEvent && - it.payload.content == EventUpSyncRequestEvent.UpSyncContent(1, 0, 0) + it is EventUpSyncRequestEvent && it.payload.content.sessionCount == 1 + }) + eventRepo.addOrUpdateEvent(any(), match { + it is EventUpSyncRequestEvent && it.payload.content.eventUpSyncCount == 1 }) eventRepo.addOrUpdateEvent(any(), match { - it is EventUpSyncRequestEvent && - it.payload.content == EventUpSyncRequestEvent.UpSyncContent(0, 1, 1) + it is EventUpSyncRequestEvent && it.payload.content.eventDownSyncCount == 1 }) } } @Test - fun `upload does not reports events if no scopes to upload`() = runTest { + fun `upload does not reports events if only up-sync scopes are present to upload`() = runTest { setUpSyncKind(UpSynchronizationConfiguration.UpSynchronizationKind.ALL) coEvery { eventRepo.getClosedEventScopes(any()) } returns emptyList() coEvery { eventRepo.getClosedEventScopes(EventScopeType.UP_SYNC) } returns listOf( @@ -490,7 +518,7 @@ internal class EventUpSyncTaskTest { } @Test - fun `upload does not reports events if only up-sync scopes are present to upload`() = runTest { + fun `upload does not reports events if no scopes to upload`() = runTest { setUpSyncKind(UpSynchronizationConfiguration.UpSynchronizationKind.ALL) coEvery { eventRepo.getClosedEventScopes(any()) } returns emptyList() diff --git a/infra/events/src/debug/java/com/simprints/infra/events/sampledata/EventFactoryUtils.kt b/infra/events/src/debug/java/com/simprints/infra/events/sampledata/EventFactoryUtils.kt index dc8fb17140..b60260946e 100644 --- a/infra/events/src/debug/java/com/simprints/infra/events/sampledata/EventFactoryUtils.kt +++ b/infra/events/src/debug/java/com/simprints/infra/events/sampledata/EventFactoryUtils.kt @@ -404,9 +404,11 @@ fun createEventUpSyncRequestEvent() = EventUpSyncRequestEvent( createdAt = CREATED_AT, endedAt = ENDED_AT, requestId = GUID1, - sessionCount = 1, - eventUpSyncCount = 2, - eventDownSyncCount = 3, + content = EventUpSyncRequestEvent.UpSyncContent( + sessionCount = 1, + eventUpSyncCount = 2, + eventDownSyncCount = 3, + ), responseStatus = 200, errorType = "OK", ) diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/upsync/EventUpSyncRequestEvent.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/upsync/EventUpSyncRequestEvent.kt index 6a3f5eaf05..413072cb9a 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/upsync/EventUpSyncRequestEvent.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/upsync/EventUpSyncRequestEvent.kt @@ -22,9 +22,7 @@ data class EventUpSyncRequestEvent( createdAt: Timestamp, endedAt: Timestamp, requestId: String, - sessionCount: Int = 0, - eventUpSyncCount: Int = 0, - eventDownSyncCount: Int = 0, + content: UpSyncContent = UpSyncContent(), responseStatus: Int? = null, errorType: String? = null, ) : this( @@ -33,11 +31,7 @@ data class EventUpSyncRequestEvent( createdAt, endedAt, requestId, - UpSyncContent( - sessionCount, - eventUpSyncCount, - eventDownSyncCount, - ), + content, responseStatus, errorType, EVENT_VERSION, From 6246eea676f585281c4c2c41d7f9709410220850 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Mon, 26 Feb 2024 13:04:08 +0200 Subject: [PATCH 036/197] MS-229 Move unmetered connection flag into up.simprints configuration --- .../dashboard/main/sync/SyncViewModelTest.kt | 6 +++- .../store/local/ConfigLocalDataSourceImpl.kt | 2 +- .../migrations/models/OldProjectConfig.kt | 2 +- .../models/UpSynchronizationConfiguration.kt | 6 ++-- .../store/models/ProjectConfiguration.kt | 2 +- .../models/UpSynchronizationConfiguration.kt | 2 +- .../models/ApiSynchronizationConfiguration.kt | 9 ++--- .../src/main/proto/project_config.proto | 2 +- .../store/models/ProjectConfigurationTest.kt | 35 ++++++------------- .../infra/config/store/testtools/Models.kt | 14 ++++---- .../infra/sync/SyncOrchestratorImplTest.kt | 4 +-- .../infra/sync/config/testtools/Models.kt | 12 ++++--- ...heduleWorkersIfConfigChangedUseCaseTest.kt | 25 ++++++++++--- 13 files changed, 67 insertions(+), 54 deletions(-) diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncViewModelTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncViewModelTest.kt index b709c92ec3..8ad7172d3c 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncViewModelTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/sync/SyncViewModelTest.kt @@ -96,7 +96,11 @@ internal class SyncViewModelTest { every { eventSyncManager.getLastSyncState() } returns syncState every { connectivityTracker.observeIsConnected() } returns isConnected coEvery { configRepository.getProjectConfiguration().synchronization } returns mockk { - every { up.simprints } returns SimprintsUpSynchronizationConfiguration(kind = ALL, batchSizes = UpSynchronizationConfiguration.UpSyncBatchSizes.default()) + every { up.simprints } returns SimprintsUpSynchronizationConfiguration( + kind = ALL, + batchSizes = UpSynchronizationConfiguration.UpSyncBatchSizes.default(), + false, + ) every { frequency } returns SynchronizationConfiguration.Frequency.PERIODICALLY_AND_ON_SESSION_START every { down.partitionType } returns DownSynchronizationConfiguration.PartitionType.MODULE } diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/ConfigLocalDataSourceImpl.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/ConfigLocalDataSourceImpl.kt index 10429841b8..b944504582 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/ConfigLocalDataSourceImpl.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/ConfigLocalDataSourceImpl.kt @@ -181,11 +181,11 @@ internal class ConfigLocalDataSourceImpl @Inject constructor( simprints = UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration( kind = UpSynchronizationConfiguration.UpSynchronizationKind.NONE, batchSizes = UpSynchronizationConfiguration.UpSyncBatchSizes.default(), + imagesRequireUnmeteredConnection = false, ), coSync = UpSynchronizationConfiguration.CoSyncUpSynchronizationConfiguration( kind = UpSynchronizationConfiguration.UpSynchronizationKind.NONE ), - imagesRequireUnmeteredConnection = false, ), down = DownSynchronizationConfiguration( partitionType = DownSynchronizationConfiguration.PartitionType.USER, diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/migrations/models/OldProjectConfig.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/migrations/models/OldProjectConfig.kt index fe6b50e8bd..06414babaa 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/migrations/models/OldProjectConfig.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/migrations/models/OldProjectConfig.kt @@ -203,6 +203,7 @@ internal data class OldProjectConfig( upSyncs = 1, downSyncs = 1, ), + imagesRequireUnmeteredConnection = false, ), coSync = UpSynchronizationConfiguration.CoSyncUpSynchronizationConfiguration( kind = if (coSync == null) { @@ -217,7 +218,6 @@ internal data class OldProjectConfig( ) } ), - imagesRequireUnmeteredConnection = false, ), down = DownSynchronizationConfiguration( partitionType = DownSynchronizationConfiguration.PartitionType.valueOf( diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/UpSynchronizationConfiguration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/UpSynchronizationConfiguration.kt index bb761afcbd..2d3483c7fc 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/UpSynchronizationConfiguration.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/UpSynchronizationConfiguration.kt @@ -7,13 +7,13 @@ internal fun UpSynchronizationConfiguration.toProto(): ProtoUpSynchronizationCon ProtoUpSynchronizationConfiguration.newBuilder() .setSimprints(simprints.toProto()) .setCoSync(coSync.toProto()) - .setImagesRequireUnmeteredConnection(imagesRequireUnmeteredConnection) .build() internal fun UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration.toProto(): ProtoUpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration = ProtoUpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration.newBuilder() .setKind(kind.toProto()) .setBatchSizes(batchSizes.toProto()) + .setImagesRequireUnmeteredConnection(imagesRequireUnmeteredConnection) .build() internal fun UpSynchronizationConfiguration.CoSyncUpSynchronizationConfiguration.toProto(): ProtoUpSynchronizationConfiguration.CoSyncUpSynchronizationConfiguration = @@ -36,10 +36,10 @@ internal fun UpSynchronizationConfiguration.UpSyncBatchSizes.toProto(): ProtoUpS .build() internal fun ProtoUpSynchronizationConfiguration.toDomain(): UpSynchronizationConfiguration = - UpSynchronizationConfiguration(simprints.toDomain(), coSync.toDomain(), imagesRequireUnmeteredConnection) + UpSynchronizationConfiguration(simprints.toDomain(), coSync.toDomain()) internal fun ProtoUpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration.toDomain(): UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration = - UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration(kind.toDomain(), batchSizes.toDomain()) + UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration(kind.toDomain(), batchSizes.toDomain(), imagesRequireUnmeteredConnection) internal fun ProtoUpSynchronizationConfiguration.CoSyncUpSynchronizationConfiguration.toDomain(): UpSynchronizationConfiguration.CoSyncUpSynchronizationConfiguration = UpSynchronizationConfiguration.CoSyncUpSynchronizationConfiguration(kind.toDomain()) diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/models/ProjectConfiguration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/models/ProjectConfiguration.kt index 1c37c071d3..9d30491d8a 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/models/ProjectConfiguration.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/models/ProjectConfiguration.kt @@ -40,4 +40,4 @@ fun ProjectConfiguration.isEventDownSyncAllowed(): Boolean = synchronization.frequency != SynchronizationConfiguration.Frequency.ONLY_PERIODICALLY_UP_SYNC fun ProjectConfiguration.imagesUploadRequiresUnmeteredConnection(): Boolean = - synchronization.up.imagesRequireUnmeteredConnection + synchronization.up.simprints.imagesRequireUnmeteredConnection diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/models/UpSynchronizationConfiguration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/models/UpSynchronizationConfiguration.kt index 1c80f5bc4d..fbaa495ea7 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/models/UpSynchronizationConfiguration.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/models/UpSynchronizationConfiguration.kt @@ -3,12 +3,12 @@ package com.simprints.infra.config.store.models data class UpSynchronizationConfiguration( val simprints: SimprintsUpSynchronizationConfiguration, val coSync: CoSyncUpSynchronizationConfiguration, - val imagesRequireUnmeteredConnection: Boolean, ) { data class SimprintsUpSynchronizationConfiguration( val kind: UpSynchronizationKind, val batchSizes: UpSyncBatchSizes, + val imagesRequireUnmeteredConnection: Boolean, ) data class CoSyncUpSynchronizationConfiguration(val kind: UpSynchronizationKind) diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiSynchronizationConfiguration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiSynchronizationConfiguration.kt index 4ce876ccc3..709c814be6 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiSynchronizationConfiguration.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiSynchronizationConfiguration.kt @@ -39,26 +39,27 @@ internal data class ApiSynchronizationConfiguration( data class ApiUpSynchronizationConfiguration( val simprints: ApiSimprintsUpSynchronizationConfiguration, val coSync: ApiCoSyncUpSynchronizationConfiguration, - val imagesRequireUnmeteredConnection: Boolean, ) { fun toDomain(): UpSynchronizationConfiguration = UpSynchronizationConfiguration( simprints.toDomain(), coSync.toDomain(), - imagesRequireUnmeteredConnection ) @Keep data class ApiSimprintsUpSynchronizationConfiguration( val kind: UpSynchronizationKind, val batchSizes: ApiUpSyncBatchSizes?, + val imagesRequireUnmeteredConnection: Boolean?, ) { fun toDomain(): UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration = UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration( - kind.toDomain(), - batchSizes?.toDomain() ?: UpSynchronizationConfiguration.UpSyncBatchSizes.default() + kind = kind.toDomain(), + batchSizes = batchSizes?.toDomain() + ?: UpSynchronizationConfiguration.UpSyncBatchSizes.default(), + imagesRequireUnmeteredConnection = imagesRequireUnmeteredConnection ?: false, ) } diff --git a/infra/config-store/src/main/proto/project_config.proto b/infra/config-store/src/main/proto/project_config.proto index 3e318f2cd4..b9c685a439 100644 --- a/infra/config-store/src/main/proto/project_config.proto +++ b/infra/config-store/src/main/proto/project_config.proto @@ -171,11 +171,11 @@ message ProtoDownSynchronizationConfiguration { message ProtoUpSynchronizationConfiguration { SimprintsUpSynchronizationConfiguration simprints = 1; CoSyncUpSynchronizationConfiguration co_sync = 2; - bool imagesRequireUnmeteredConnection = 3; message SimprintsUpSynchronizationConfiguration { UpSynchronizationKind kind = 1; ProtoUpSyncBatchSizes batchSizes = 2; + bool imagesRequireUnmeteredConnection = 3; } message CoSyncUpSynchronizationConfiguration { diff --git a/infra/config-store/src/test/java/com/simprints/infra/config/store/models/ProjectConfigurationTest.kt b/infra/config-store/src/test/java/com/simprints/infra/config/store/models/ProjectConfigurationTest.kt index 99951bd9b3..d621ef59bd 100644 --- a/infra/config-store/src/test/java/com/simprints/infra/config/store/models/ProjectConfigurationTest.kt +++ b/infra/config-store/src/test/java/com/simprints/infra/config/store/models/ProjectConfigurationTest.kt @@ -6,6 +6,7 @@ import com.simprints.infra.config.store.models.UpSynchronizationConfiguration.Co import com.simprints.infra.config.store.models.UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration import com.simprints.infra.config.store.models.UpSynchronizationConfiguration.UpSynchronizationKind.* import com.simprints.infra.config.store.testtools.projectConfiguration +import com.simprints.infra.config.store.testtools.simprintsUpSyncConfigurationConfiguration import com.simprints.infra.config.store.testtools.synchronizationConfiguration import org.junit.Test @@ -116,13 +117,8 @@ class ProjectConfigurationTest { val config = projectConfiguration.copy( synchronization = synchronizationConfiguration.copy( up = synchronizationConfiguration.up.copy( - simprints = SimprintsUpSynchronizationConfiguration( + simprints = simprintsUpSyncConfigurationConfiguration.copy( kind = it.key, - batchSizes = UpSynchronizationConfiguration.UpSyncBatchSizes( - sessions = 1, - upSyncs = 1, - downSyncs = 1 - ), ) ) ) @@ -144,13 +140,8 @@ class ProjectConfigurationTest { val config = projectConfiguration.copy( synchronization = synchronizationConfiguration.copy( up = synchronizationConfiguration.up.copy( - simprints = SimprintsUpSynchronizationConfiguration( + simprints = simprintsUpSyncConfigurationConfiguration.copy( kind = it.key, - batchSizes = UpSynchronizationConfiguration.UpSyncBatchSizes( - sessions = 1, - upSyncs = 1, - downSyncs = 1 - ), ) ) ) @@ -174,11 +165,8 @@ class ProjectConfigurationTest { up = synchronizationConfiguration.up.copy( simprints = SimprintsUpSynchronizationConfiguration( kind = it.key, - batchSizes = UpSynchronizationConfiguration.UpSyncBatchSizes( - sessions = 1, - upSyncs = 1, - downSyncs = 1 - ), + batchSizes = UpSynchronizationConfiguration.UpSyncBatchSizes.default(), + imagesRequireUnmeteredConnection = false ) ) ) @@ -200,13 +188,8 @@ class ProjectConfigurationTest { val config = projectConfiguration.copy( synchronization = synchronizationConfiguration.copy( up = synchronizationConfiguration.up.copy( - simprints = SimprintsUpSynchronizationConfiguration( + simprints = simprintsUpSyncConfigurationConfiguration.copy( kind = it.key, - batchSizes = UpSynchronizationConfiguration.UpSyncBatchSizes( - sessions = 1, - upSyncs = 1, - downSyncs = 1 - ), ) ) ) @@ -240,7 +223,11 @@ class ProjectConfigurationTest { values.forEach { val config = projectConfiguration.copy( synchronization = synchronizationConfiguration.copy( - up = synchronizationConfiguration.up.copy(imagesRequireUnmeteredConnection = it) + up = synchronizationConfiguration.up.copy( + simprints = simprintsUpSyncConfigurationConfiguration.copy( + imagesRequireUnmeteredConnection = it + ), + ) ) ) assertThat(config.imagesUploadRequiresUnmeteredConnection()).isEqualTo(it) diff --git a/infra/config-store/src/test/java/com/simprints/infra/config/store/testtools/Models.kt b/infra/config-store/src/test/java/com/simprints/infra/config/store/testtools/Models.kt index 8983170583..6afd3d3cb2 100644 --- a/infra/config-store/src/test/java/com/simprints/infra/config/store/testtools/Models.kt +++ b/infra/config-store/src/test/java/com/simprints/infra/config/store/testtools/Models.kt @@ -203,11 +203,11 @@ internal val apiSynchronizationConfiguration = ApiSynchronizationConfiguration( ApiSynchronizationConfiguration.ApiUpSynchronizationConfiguration.ApiSimprintsUpSynchronizationConfiguration( ApiSynchronizationConfiguration.ApiUpSynchronizationConfiguration.UpSynchronizationKind.ALL, ApiSynchronizationConfiguration.ApiUpSynchronizationConfiguration.ApiUpSyncBatchSizes(1, 2, 3), + false, ), ApiSynchronizationConfiguration.ApiUpSynchronizationConfiguration.ApiCoSyncUpSynchronizationConfiguration( ApiSynchronizationConfiguration.ApiUpSynchronizationConfiguration.UpSynchronizationKind.NONE ), - false, ), ApiSynchronizationConfiguration.ApiDownSynchronizationConfiguration( ApiSynchronizationConfiguration.ApiDownSynchronizationConfiguration.PartitionType.PROJECT, @@ -216,17 +216,19 @@ internal val apiSynchronizationConfiguration = ApiSynchronizationConfiguration( ) ) +internal val simprintsUpSyncConfigurationConfiguration = UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration( + UpSynchronizationConfiguration.UpSynchronizationKind.ALL, + UpSynchronizationConfiguration.UpSyncBatchSizes(1, 2, 3), + false +) + internal val synchronizationConfiguration = SynchronizationConfiguration( SynchronizationConfiguration.Frequency.PERIODICALLY, UpSynchronizationConfiguration( - UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration( - UpSynchronizationConfiguration.UpSynchronizationKind.ALL, - UpSynchronizationConfiguration.UpSyncBatchSizes(1, 2, 3), - ), + simprintsUpSyncConfigurationConfiguration, UpSynchronizationConfiguration.CoSyncUpSynchronizationConfiguration( UpSynchronizationConfiguration.UpSynchronizationKind.NONE ), - false, ), DownSynchronizationConfiguration( DownSynchronizationConfiguration.PartitionType.PROJECT, diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt index ccf3da6ce8..a1c5ad4f45 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt @@ -99,7 +99,7 @@ class SyncOrchestratorImplTest { @Test fun `schedules images with any connection if not specified`() = runTest { coEvery { - configRepo.getProjectConfiguration().synchronization.up.imagesRequireUnmeteredConnection + configRepo.getProjectConfiguration().synchronization.up.simprints.imagesRequireUnmeteredConnection } returns false every { authStore.signedInProjectId } returns "projectId" @@ -117,7 +117,7 @@ class SyncOrchestratorImplTest { @Test fun `schedules images with unmetered constraint if requested`() = runTest { coEvery { - configRepo.getProjectConfiguration().synchronization.up.imagesRequireUnmeteredConnection + configRepo.getProjectConfiguration().synchronization.up.simprints.imagesRequireUnmeteredConnection } returns true every { authStore.signedInProjectId } returns "projectId" coEvery { shouldScheduleFirmwareUpdate.invoke() } returns false diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/config/testtools/Models.kt b/infra/sync/src/test/java/com/simprints/infra/sync/config/testtools/Models.kt index 49c3733c09..b39dc86746 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/config/testtools/Models.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/config/testtools/Models.kt @@ -59,17 +59,19 @@ internal val consentConfiguration = ConsentConfiguration( ), ) +internal val simprintsUpSyncConfigurationConfiguration = UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration( + UpSynchronizationConfiguration.UpSynchronizationKind.ALL, + UpSynchronizationConfiguration.UpSyncBatchSizes.default(), + false, +) + internal val synchronizationConfiguration = SynchronizationConfiguration( SynchronizationConfiguration.Frequency.PERIODICALLY, UpSynchronizationConfiguration( - UpSynchronizationConfiguration.SimprintsUpSynchronizationConfiguration( - UpSynchronizationConfiguration.UpSynchronizationKind.ALL, - UpSynchronizationConfiguration.UpSyncBatchSizes.default() - ), + simprintsUpSyncConfigurationConfiguration, UpSynchronizationConfiguration.CoSyncUpSynchronizationConfiguration( UpSynchronizationConfiguration.UpSynchronizationKind.NONE ), - false, ), DownSynchronizationConfiguration( DownSynchronizationConfiguration.PartitionType.PROJECT, diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCaseTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCaseTest.kt index 487317ffdc..65be78d44a 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCaseTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/RescheduleWorkersIfConfigChangedUseCaseTest.kt @@ -2,6 +2,7 @@ package com.simprints.infra.sync.config.usecase import com.simprints.infra.sync.SyncOrchestrator import com.simprints.infra.sync.config.testtools.projectConfiguration +import com.simprints.infra.sync.config.testtools.simprintsUpSyncConfigurationConfiguration import com.simprints.infra.sync.config.testtools.synchronizationConfiguration import io.mockk.MockKAnnotations import io.mockk.coVerify @@ -30,12 +31,20 @@ class RescheduleWorkersIfConfigChangedUseCaseTest { useCase( projectConfiguration.copy( synchronization = synchronizationConfiguration.copy( - up = synchronizationConfiguration.up.copy(imagesRequireUnmeteredConnection = true) + up = synchronizationConfiguration.up.copy( + simprints = simprintsUpSyncConfigurationConfiguration.copy( + imagesRequireUnmeteredConnection = true + ) + ) ) ), projectConfiguration.copy( synchronization = synchronizationConfiguration.copy( - up = synchronizationConfiguration.up.copy(imagesRequireUnmeteredConnection = true) + up = synchronizationConfiguration.up.copy( + simprints = simprintsUpSyncConfigurationConfiguration.copy( + imagesRequireUnmeteredConnection = true + ) + ) ) ), ) @@ -49,12 +58,20 @@ class RescheduleWorkersIfConfigChangedUseCaseTest { useCase( projectConfiguration.copy( synchronization = synchronizationConfiguration.copy( - up = synchronizationConfiguration.up.copy(imagesRequireUnmeteredConnection = false) + up = synchronizationConfiguration.up.copy( + simprints = simprintsUpSyncConfigurationConfiguration.copy( + imagesRequireUnmeteredConnection = false + ) + ) ) ), projectConfiguration.copy( synchronization = synchronizationConfiguration.copy( - up = synchronizationConfiguration.up.copy(imagesRequireUnmeteredConnection = true) + up = synchronizationConfiguration.up.copy( + simprints = simprintsUpSyncConfigurationConfiguration.copy( + imagesRequireUnmeteredConnection = true + ) + ) ) ), ) From 99ea87b24a1805f1a34d674a8eb8a4858d1ff5ff Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Mon, 26 Feb 2024 10:55:48 +0200 Subject: [PATCH 037/197] MS-215 Combine counts of records to download and delete into single field --- .../settings/syncinfo/SyncInfoFragment.kt | 6 - .../settings/syncinfo/SyncInfoViewModel.kt | 29 +- .../main/res/layout/fragment_sync_info.xml | 250 +++++++----------- .../syncinfo/SyncInfoViewModelTest.kt | 3 +- .../infra/events/event/domain/EventCount.kt | 13 +- .../events/event/domain/EventCountTest.kt | 14 + .../resources/src/main/res/values/strings.xml | 2 +- 7 files changed, 135 insertions(+), 182 deletions(-) create mode 100644 infra/events/src/test/java/com/simprints/infra/events/event/domain/EventCountTest.kt diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoFragment.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoFragment.kt index ac2a7833c6..783d519d05 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoFragment.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoFragment.kt @@ -81,11 +81,6 @@ internal class SyncInfoFragment : Fragment(R.layout.fragment_sync_info) { setProgressBar(it, binding.recordsToDownloadCount, binding.recordsToDownloadProgress) } - viewModel.recordsToDelete.observe(viewLifecycleOwner) { - binding.recordsToDeleteCount.text = it?.toString() ?: "" - setProgressBar(it, binding.recordsToDeleteCount, binding.recordsToDeleteProgress) - } - viewModel.moduleCounts.observe(viewLifecycleOwner) { updateModuleCounts(it) } @@ -119,7 +114,6 @@ internal class SyncInfoFragment : Fragment(R.layout.fragment_sync_info) { private fun setupRecordsCountCards(configuration: ProjectConfiguration) { if (!configuration.isEventDownSyncAllowed()) { binding.recordsToDownloadCardView.visibility = View.GONE - binding.recordsToDeleteCardView.visibility = View.GONE } if (!configuration.canSyncDataToSimprints()) { diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt index b5c8468e85..a782f1dad0 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt @@ -14,7 +14,6 @@ import com.simprints.infra.config.store.models.isEventDownSyncAllowed import com.simprints.infra.enrolment.records.store.domain.models.SubjectQuery import com.simprints.infra.events.event.domain.models.EventType import com.simprints.infra.eventsync.EventSyncManager -import com.simprints.infra.eventsync.status.models.DownSyncCounts import com.simprints.infra.eventsync.status.models.EventSyncState import com.simprints.infra.eventsync.status.models.EventSyncWorkerState import com.simprints.infra.images.ImageRepository @@ -42,7 +41,7 @@ internal class SyncInfoViewModel @Inject constructor( private val imageRepository: ImageRepository, private val eventSyncManager: EventSyncManager, private val syncOrchestrator: SyncOrchestrator, - private val tokenizationProcessor: TokenizationProcessor + private val tokenizationProcessor: TokenizationProcessor, ) : ViewModel() { val recordsInLocal: LiveData @@ -61,10 +60,6 @@ internal class SyncInfoViewModel @Inject constructor( get() = _recordsToDownSync private val _recordsToDownSync = MutableLiveData(null) - val recordsToDelete: LiveData - get() = _recordsToDelete - private val _recordsToDelete = MutableLiveData(null) - val moduleCounts: LiveData> get() = _moduleCounts private val _moduleCounts = MutableLiveData>() @@ -116,7 +111,6 @@ internal class SyncInfoViewModel @Inject constructor( _recordsInLocal.postValue(null) _recordsToUpSync.postValue(null) _recordsToDownSync.postValue(null) - _recordsToDelete.postValue(null) _imagesToUpload.postValue(null) _moduleCounts.postValue(listOf()) load() @@ -154,12 +148,7 @@ internal class SyncInfoViewModel @Inject constructor( async { _configuration.postValue(configRepository.getProjectConfiguration()) }, async { _recordsInLocal.postValue(getRecordsInLocal(projectId)) }, async { _recordsToUpSync.postValue(getRecordsToUpSync()) }, - async { - fetchRecordsToCreateAndDeleteCount().let { - _recordsToDownSync.postValue(it.toCreate) - _recordsToDelete.postValue(it.toDelete) - } - }, + async { _recordsToDownSync.postValue(fetchRecordsToCreateAndDeleteCount()) }, async { _imagesToUpload.postValue(imageRepository.getNumberOfImagesToUpload(projectId)) }, async { _moduleCounts.postValue(getModuleCounts(projectId)) } ) @@ -170,8 +159,8 @@ internal class SyncInfoViewModel @Inject constructor( isConnected: Boolean?, syncConfiguration: SynchronizationConfiguration? = configuration.value?.synchronization, ) = isConnected == true - && isSyncRunning == false - && syncConfiguration?.let { + && isSyncRunning == false + && syncConfiguration?.let { !isModuleSync(it.down) || isModuleSyncAndModuleIdOptionsNotEmpty( it ) @@ -191,19 +180,19 @@ internal class SyncInfoViewModel @Inject constructor( .firstOrNull() ?: 0 - private suspend fun fetchRecordsToCreateAndDeleteCount(): DownSyncCounts = + private suspend fun fetchRecordsToCreateAndDeleteCount(): Int = if (configRepository.getProjectConfiguration().isEventDownSyncAllowed()) { fetchAndUpdateRecordsToDownSyncAndDeleteCount() } else { - DownSyncCounts(0, 0) + 0 } - private suspend fun fetchAndUpdateRecordsToDownSyncAndDeleteCount(): DownSyncCounts = + private suspend fun fetchAndUpdateRecordsToDownSyncAndDeleteCount(): Int = try { - eventSyncManager.countEventsToDownload() + eventSyncManager.countEventsToDownload().let { it.toCreate + it.toDelete } } catch (t: Throwable) { Simber.d(t) - DownSyncCounts(0, 0) + 0 } private suspend fun getModuleCounts(projectId: String): List = diff --git a/feature/dashboard/src/main/res/layout/fragment_sync_info.xml b/feature/dashboard/src/main/res/layout/fragment_sync_info.xml index 55cef3bd59..5e408d5f5a 100644 --- a/feature/dashboard/src/main/res/layout/fragment_sync_info.xml +++ b/feature/dashboard/src/main/res/layout/fragment_sync_info.xml @@ -22,52 +22,49 @@ app:navigationContentDescription="back" app:navigationIcon="?android:attr/homeAsUpIndicator" app:title="@string/dashboard_sync_info_title" /> - + app:layout_constraintTop_toBottomOf="@+id/appBarLayout"> + android:padding="8dp"> + android:padding="4dp" + android:text="@string/dashboard_sync_info_total_records_on_device" /> - + app:layout_constraintTop_toBottomOf="@+id/recordsInLocalCardView"> + android:orientation="vertical" + android:padding="8dp"> - - - - - - - - - - - - - - - - + android:padding="4dp" + android:text="@string/dashboard_sync_info_records_to_upload" /> + tools:text="0" /> + app:layout_constraintStart_toEndOf="@+id/recordsToUploadCardView" + app:layout_constraintTop_toTopOf="@id/recordsToUploadCardView"> + android:padding="8dp"> + android:padding="4dp" + android:text="@string/dashboard_sync_info_images_to_upload" /> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/recordsToUploadCardView"> + android:orientation="horizontal" + android:padding="8dp"> + android:padding="4dp" + android:text="@string/dashboard_sync_info_records_to_download" /> + android:layout_weight="0.2" + android:gravity="center" + android:textSize="16sp" + tools:text="137" /> + + + app:layout_constraintTop_toBottomOf="@+id/recordsCardsBarrier" + tools:ignore="SpeakableTextPresentCheck" + tools:visibility="visible"> @@ -331,7 +279,7 @@ android:layout_height="match_parent" android:fadeScrollbars="false" android:orientation="vertical" - android:padding="10dp" + android:padding="8dp" android:scrollbars="vertical" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" tools:itemCount="100" @@ -343,21 +291,21 @@ android:id="@+id/moduleSelectionButton" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="10dp" - android:layout_marginTop="15dp" - android:layout_marginEnd="10dp" - android:layout_marginBottom="15dp" + android:layout_marginStart="8dp" + android:layout_marginTop="16dp" + android:layout_marginEnd="8dp" + android:layout_marginBottom="16dp" android:text="@string/dashboard_sync_info_select_modules_button" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@+id/modulesTabHost" app:layout_constraintStart_toStartOf="@+id/modulesTabHost" - app:layout_constraintTop_toBottomOf="@+id/modulesTabHost"/> + tools:visibility="visible" /> + app:constraint_referenced_ids="recordsToUploadCardView,recordsToDownloadCardView,recordsToDownloadCardView,syncButton" /> diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt index 6ba3349417..cf2ef77b6d 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt @@ -212,8 +212,7 @@ class SyncInfoViewModelTest { viewModel.refreshInformation() - assertThat(viewModel.recordsToDownSync.getOrAwaitValue()).isEqualTo(creationForModules) - assertThat(viewModel.recordsToDelete.getOrAwaitValue()).isEqualTo(deletionForModules) + assertThat(viewModel.recordsToDownSync.getOrAwaitValue()).isEqualTo(15) } @Test diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/EventCount.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/EventCount.kt index f9d015fdb9..e95db5382d 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/EventCount.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/EventCount.kt @@ -1,7 +1,16 @@ package com.simprints.infra.events.event.domain import androidx.annotation.Keep -import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordEventType @Keep -data class EventCount(val type: EnrolmentRecordEventType, val count: Int) +data class EventCount( + val count: Int, + val isLowerBound: Boolean, +) { + + /** + * Returns exact count or null if the count is a lower bound. + */ + val exactCount: Int? + get() = if (isLowerBound) null else count +} diff --git a/infra/events/src/test/java/com/simprints/infra/events/event/domain/EventCountTest.kt b/infra/events/src/test/java/com/simprints/infra/events/event/domain/EventCountTest.kt new file mode 100644 index 0000000000..31eccdf477 --- /dev/null +++ b/infra/events/src/test/java/com/simprints/infra/events/event/domain/EventCountTest.kt @@ -0,0 +1,14 @@ +package com.simprints.infra.events.event.domain + + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class EventCountTest { + + @Test + fun getExactCount() { + assertThat(EventCount(5, false).exactCount).isEqualTo(5) + assertThat(EventCount(5, true).exactCount).isNull() + } +} diff --git a/infra/resources/src/main/res/values/strings.xml b/infra/resources/src/main/res/values/strings.xml index 6c747d94b4..549c5f3cf4 100644 --- a/infra/resources/src/main/res/values/strings.xml +++ b/infra/resources/src/main/res/values/strings.xml @@ -365,7 +365,7 @@ Sync Information Select modules Records to upload - Records to download + Records to download or delete Records to delete Total records on device Selected modules From b2ef42062efb54e3b6bdc2e7bdd56970a7d4df2e Mon Sep 17 00:00:00 2001 From: Milen Marinov Date: Tue, 27 Feb 2024 21:13:58 +0200 Subject: [PATCH 038/197] [MS-241] Add -keep declarations for all classes saved in the OrchestratorCache --- .../feature/orchestrator/steps/Step.kt | 18 +++--------------- id/proguard-rules.pro | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/steps/Step.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/steps/Step.kt index 86840aad5f..f4d66d31ca 100644 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/steps/Step.kt +++ b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/steps/Step.kt @@ -29,26 +29,14 @@ import java.io.Serializable JsonSubTypes.Type(value = ConsentResult::class, name = "ConsentResult"), JsonSubTypes.Type(value = FingerprintConnectResult::class, name = "FingerprintConnectResult"), JsonSubTypes.Type(value = FingerprintCaptureResult::class, name = "FingerprintCaptureResult"), - JsonSubTypes.Type( - value = FingerprintCaptureResult.Item::class, - name = "FingerprintCaptureResult.Item" - ), - JsonSubTypes.Type( - value = FingerprintCaptureResult.Sample::class, - name = "FingerprintCaptureResult.Sample" - ), - + JsonSubTypes.Type(value = FingerprintCaptureResult.Item::class, name = "FingerprintCaptureResult.Item"), + JsonSubTypes.Type(value = FingerprintCaptureResult.Sample::class, name = "FingerprintCaptureResult.Sample"), JsonSubTypes.Type(value = FingerprintMatchResult::class, name = "FingerprintMatchResult"), - JsonSubTypes.Type( - value = FingerprintMatchResult.Item::class, - name = "FingerprintMatchResult.Item" - ), - + JsonSubTypes.Type(value = FingerprintMatchResult.Item::class, name = "FingerprintMatchResult.Item"), JsonSubTypes.Type(value = FaceConfigurationResult::class, name = "FaceConfigurationResult"), JsonSubTypes.Type(value = FaceCaptureResult::class, name = "FaceCaptureResult"), JsonSubTypes.Type(value = FaceCaptureResult.Item::class, name = "FaceCaptureResult.Item"), JsonSubTypes.Type(value = FaceCaptureResult.Sample::class, name = "FaceCaptureResult.Sample"), - JsonSubTypes.Type(value = FaceMatchResult::class, name = "FaceMatchResult"), JsonSubTypes.Type(value = FaceMatchResult.Item::class, name = "FaceMatchResult.Item"), JsonSubTypes.Type(value = EnrolLastBiometricResult::class, name = "EnrolLastBiometricResult"), diff --git a/id/proguard-rules.pro b/id/proguard-rules.pro index 933ddc1ae1..adab089201 100644 --- a/id/proguard-rules.pro +++ b/id/proguard-rules.pro @@ -52,6 +52,21 @@ -keep class com.simprints.infra.events.event.domain.models.** { *; } -keep class com.simprints.infra.events.event.local.** { *; } +-keep class com.simprints.feature.orchestrator.steps.** { *; } +-keep class com.simprints.face.capture.** { *; } +-keep class com.simprints.face.configuration.** { *; } +-keep class com.simprints.feature.alert.** { *; } +-keep class com.simprints.feature.consent.** { *; } +-keep class com.simprints.feature.enrollast.** { *; } +-keep class com.simprints.feature.exitform.** { *; } +-keep class com.simprints.feature.fetchsubject.** { *; } +-keep class com.simprints.feature.login.** { *; } +-keep class com.simprints.feature.selectsubject.** { *; } +-keep class com.simprints.feature.setup.** { *; } +-keep class com.simprints.fingerprint.capture.** { *; } +-keep class com.simprints.fingerprint.connect.** { *; } +-keep class com.simprints.matcher.** { *; } + # Deobfuscations for Crashlytics: # https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports -keepattributes SourceFile,LineNumberTable,*Annotation* From 5c475b5ee58b84a5bd0cc1e371b65a18f45be21a Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Wed, 28 Feb 2024 13:09:03 +0200 Subject: [PATCH 039/197] Apply correct Keep annotation for image ref and path models --- .../com/simprints/feature/orchestrator/OrchestratorViewModel.kt | 2 +- .../src/main/java/com/simprints/infra/images/model/Path.kt | 2 ++ .../java/com/simprints/infra/images/model/SecuredImageRef.kt | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorViewModel.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorViewModel.kt index 9903b041c5..344546233b 100644 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorViewModel.kt +++ b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorViewModel.kt @@ -70,7 +70,7 @@ internal class OrchestratorViewModel @Inject constructor( } fun handleResult(result: Serializable) { - Simber.i(result.toString()) + Simber.d(result.toString()) val errorResponse = mapRefusalOrErrorResult(result) if (errorResponse != null) { // Shortcut the flow execution if any refusal or error result is found diff --git a/infra/images/src/main/java/com/simprints/infra/images/model/Path.kt b/infra/images/src/main/java/com/simprints/infra/images/model/Path.kt index 2feeea5cad..8e4945a601 100644 --- a/infra/images/src/main/java/com/simprints/infra/images/model/Path.kt +++ b/infra/images/src/main/java/com/simprints/infra/images/model/Path.kt @@ -1,6 +1,7 @@ package com.simprints.infra.images.model import android.os.Parcelable +import androidx.annotation.Keep import kotlinx.parcelize.Parcelize import java.io.File @@ -12,6 +13,7 @@ import java.io.File * e.g.: for dir1/dir2/file.txt [parts] should be * @sample [arrayOf("dir1", "dir2", "file.txt")] */ +@Keep @Parcelize data class Path(val parts: Array) : Parcelable { diff --git a/infra/images/src/main/java/com/simprints/infra/images/model/SecuredImageRef.kt b/infra/images/src/main/java/com/simprints/infra/images/model/SecuredImageRef.kt index fbab87af8a..b2c2cac6ae 100644 --- a/infra/images/src/main/java/com/simprints/infra/images/model/SecuredImageRef.kt +++ b/infra/images/src/main/java/com/simprints/infra/images/model/SecuredImageRef.kt @@ -1,6 +1,6 @@ package com.simprints.infra.images.model -import com.google.errorprone.annotations.Keep +import androidx.annotation.Keep @Keep data class SecuredImageRef(override val relativePath: Path) : ImageRef(relativePath) From 3ff6a1c89d53bfaa001097538f28631e13a1d7d5 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Mon, 26 Feb 2024 14:39:24 +0200 Subject: [PATCH 040/197] MS-215 Use the events HEAD request to fetch down-sync count instead of dedicated endpoint --- .../settings/syncinfo/SyncInfoFragment.kt | 7 +++- .../settings/syncinfo/SyncInfoViewModel.kt | 25 ++++++------ .../syncinfo/SyncInfoViewModelTest.kt | 24 ++++++++--- .../infra/eventsync/EventSyncManagerImpl.kt | 21 +++------- .../event/remote/EventRemoteDataSource.kt | 29 +++++++++----- .../event/remote/EventRemoteInterface.kt | 6 +-- .../event/remote/models/ApiEventCount.kt | 11 ----- .../eventsync/status/models/DownSyncCounts.kt | 4 +- .../infra/eventsync/EventSyncManagerTest.kt | 40 ++----------------- .../event/remote/EventRemoteDataSourceTest.kt | 31 +++----------- 10 files changed, 76 insertions(+), 122 deletions(-) delete mode 100644 infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventCount.kt diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoFragment.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoFragment.kt index 783d519d05..33ad9341d1 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoFragment.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoFragment.kt @@ -23,6 +23,7 @@ import com.simprints.infra.resources.R as IDR internal class SyncInfoFragment : Fragment(R.layout.fragment_sync_info) { companion object { + private const val TOTAL_RECORDS_INDEX = 0 } @@ -77,8 +78,10 @@ internal class SyncInfoFragment : Fragment(R.layout.fragment_sync_info) { } viewModel.recordsToDownSync.observe(viewLifecycleOwner) { - binding.recordsToDownloadCount.text = it?.toString() ?: "" - setProgressBar(it, binding.recordsToDownloadCount, binding.recordsToDownloadProgress) + binding.recordsToDownloadCount.text = it?.let { + if (it.isLowerBound) "${it.count}+" else "${it.count}" + } ?: "" + setProgressBar(it?.count, binding.recordsToDownloadCount, binding.recordsToDownloadProgress) } viewModel.moduleCounts.observe(viewLifecycleOwner) { diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt index a782f1dad0..c3f96d66bb 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt @@ -7,22 +7,23 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.simprints.core.domain.tokenization.TokenizableString import com.simprints.feature.dashboard.settings.syncinfo.modulecount.ModuleCount +import com.simprints.infra.authstore.AuthStore +import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.models.DownSynchronizationConfiguration import com.simprints.infra.config.store.models.ProjectConfiguration import com.simprints.infra.config.store.models.SynchronizationConfiguration +import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.config.store.models.isEventDownSyncAllowed +import com.simprints.infra.config.store.tokenization.TokenizationProcessor +import com.simprints.infra.enrolment.records.store.EnrolmentRecordRepository import com.simprints.infra.enrolment.records.store.domain.models.SubjectQuery import com.simprints.infra.events.event.domain.models.EventType import com.simprints.infra.eventsync.EventSyncManager +import com.simprints.infra.eventsync.status.models.DownSyncCounts import com.simprints.infra.eventsync.status.models.EventSyncState import com.simprints.infra.eventsync.status.models.EventSyncWorkerState import com.simprints.infra.images.ImageRepository import com.simprints.infra.logging.Simber -import com.simprints.infra.authstore.AuthStore -import com.simprints.infra.config.store.ConfigRepository -import com.simprints.infra.config.store.models.TokenKeyType -import com.simprints.infra.config.store.tokenization.TokenizationProcessor -import com.simprints.infra.enrolment.records.store.EnrolmentRecordRepository import com.simprints.infra.network.ConnectivityTracker import com.simprints.infra.sync.SyncOrchestrator import dagger.hilt.android.lifecycle.HiltViewModel @@ -56,9 +57,9 @@ internal class SyncInfoViewModel @Inject constructor( get() = _imagesToUpload private val _imagesToUpload = MutableLiveData(null) - val recordsToDownSync: LiveData + val recordsToDownSync: LiveData get() = _recordsToDownSync - private val _recordsToDownSync = MutableLiveData(null) + private val _recordsToDownSync = MutableLiveData(null) val moduleCounts: LiveData> get() = _moduleCounts @@ -180,19 +181,19 @@ internal class SyncInfoViewModel @Inject constructor( .firstOrNull() ?: 0 - private suspend fun fetchRecordsToCreateAndDeleteCount(): Int = + private suspend fun fetchRecordsToCreateAndDeleteCount(): DownSyncCounts = if (configRepository.getProjectConfiguration().isEventDownSyncAllowed()) { fetchAndUpdateRecordsToDownSyncAndDeleteCount() } else { - 0 + DownSyncCounts(0, isLowerBound = false) } - private suspend fun fetchAndUpdateRecordsToDownSyncAndDeleteCount(): Int = + private suspend fun fetchAndUpdateRecordsToDownSyncAndDeleteCount(): DownSyncCounts = try { - eventSyncManager.countEventsToDownload().let { it.toCreate + it.toDelete } + eventSyncManager.countEventsToDownload() } catch (t: Throwable) { Simber.d(t) - 0 + DownSyncCounts(0, isLowerBound = false) } private suspend fun getModuleCounts(projectId: String): List = diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt index cf2ef77b6d..5623cedf58 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt @@ -198,21 +198,35 @@ class SyncInfoViewModelTest { } @Test - fun `should initialize the recordsToDownSync and recordsToDelete live data to the count otherwise`() = + fun `should initialize the recordsToDownSync live data to the count otherwise`() = runTest { val module1 = "module1".asTokenizableEncrypted() - val creationForModules = 10 - val deletionForModules = 5 coEvery { configRepo.getDeviceConfiguration() } returns mockk { every { selectedModules } returns listOf(module1) } coEvery { eventSyncManager.countEventsToDownload() - } returns DownSyncCounts(creationForModules, deletionForModules) + } returns DownSyncCounts(15, isLowerBound = false) viewModel.refreshInformation() - assertThat(viewModel.recordsToDownSync.getOrAwaitValue()).isEqualTo(15) + assertThat(viewModel.recordsToDownSync.getOrAwaitValue()?.count).isEqualTo(15) + } + + @Test + fun `should initialize the recordsToDownSync live data to the default count value if fetch fails`() = + runTest { + val module1 = "module1".asTokenizableEncrypted() + coEvery { configRepo.getDeviceConfiguration() } returns mockk { + every { selectedModules } returns listOf(module1) + } + coEvery { + eventSyncManager.countEventsToDownload() + } throws Exception() + + viewModel.refreshInformation() + + assertThat(viewModel.recordsToDownSync.getOrAwaitValue()?.count).isEqualTo(0) } @Test diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/EventSyncManagerImpl.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/EventSyncManagerImpl.kt index c1f6d7bb39..246b049b6b 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/EventSyncManagerImpl.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/EventSyncManagerImpl.kt @@ -9,7 +9,6 @@ import com.simprints.infra.config.store.models.ProjectConfiguration import com.simprints.infra.events.EventRepository import com.simprints.infra.events.event.domain.models.EventType import com.simprints.infra.events.event.domain.models.scope.EventScopeType -import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordEventType import com.simprints.infra.eventsync.event.remote.EventRemoteDataSource import com.simprints.infra.eventsync.status.down.EventDownSyncScopeRepository import com.simprints.infra.eventsync.status.down.domain.EventDownSyncOperation @@ -77,21 +76,13 @@ internal class EventSyncManagerImpl @Inject constructor( syncPartitioning = projectConfig.synchronization.down.partitionType.toDomain() ) - var creationsToDownload = 0 - var deletionsToDownload = 0 + val counts = downSyncScope.operations + .map { eventRemoteDataSource.count(it.queryEvent.fromDomainToApi()) } - downSyncScope.operations.forEach { syncOperation -> - val counts = eventRemoteDataSource.count(syncOperation.queryEvent.fromDomainToApi()) - - creationsToDownload += counts - .firstOrNull { it.type == EnrolmentRecordEventType.EnrolmentRecordCreation } - ?.count ?: 0 - deletionsToDownload += counts - .firstOrNull { it.type == EnrolmentRecordEventType.EnrolmentRecordDeletion } - ?.count ?: 0 - } - - return DownSyncCounts(creationsToDownload, deletionsToDownload) + return DownSyncCounts( + count = counts.sumOf { it.count }, + isLowerBound = counts.any { it.isLowerBound } + ) } override suspend fun downSyncSubject( diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/EventRemoteDataSource.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/EventRemoteDataSource.kt index acc9f2efc3..4633e370dd 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/EventRemoteDataSource.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/EventRemoteDataSource.kt @@ -10,7 +10,6 @@ import com.simprints.infra.authstore.AuthStore import com.simprints.infra.events.event.domain.EventCount import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordEvent import com.simprints.infra.eventsync.event.remote.exceptions.TooManyRequestsException -import com.simprints.infra.eventsync.event.remote.models.fromApiToDomain import com.simprints.infra.eventsync.event.remote.models.subject.ApiEnrolmentRecordEvent import com.simprints.infra.eventsync.event.remote.models.subject.fromApiToDomain import com.simprints.infra.eventsync.status.down.domain.EventDownSyncResult @@ -32,8 +31,8 @@ internal class EventRemoteDataSource @Inject constructor( private val jsonHelper: JsonHelper, ) { - suspend fun count(query: ApiRemoteEventQuery): List = - executeCall { eventsRemoteInterface -> + suspend fun count(query: ApiRemoteEventQuery) = try { + val response = executeCall { eventsRemoteInterface -> eventsRemoteInterface.countEvents( projectId = query.projectId, moduleId = query.moduleId, @@ -41,9 +40,23 @@ internal class EventRemoteDataSource @Inject constructor( subjectId = query.subjectId, modes = query.modes, lastEventId = query.lastEventId - ).map { it.fromApiToDomain() } + ) } + getEventCountFromHeader(response) + } catch (t: Throwable) { + if (t is SyncCloudIntegrationException && t.httpStatusCode() == TOO_MANY_REQUEST_STATUS) + throw TooManyRequestsException() + else + throw t + } + private fun getEventCountFromHeader(response: Response): EventCount { + val totalCount = response.headers()[COUNT_HEADER]?.toIntOrNull() ?: 0 + val isTotalLowerBound = response + .headers()[IS_COUNT_HEADER_LOWER_BOUND] + ?.toBoolean() ?: false // If not present, assume it's not lower bound + return EventCount(totalCount, isTotalLowerBound) + } suspend fun dumpInvalidEvents(projectId: String, events: List) { executeCall { remoteInterface -> @@ -57,17 +70,13 @@ internal class EventRemoteDataSource @Inject constructor( ): EventDownSyncResult { return try { val response = takeStreaming(query) - - val totalCount = response.headers()[COUNT_HEADER]?.toIntOrNull() ?: 0 - val isTotalLowerBound = response - .headers()[IS_COUNT_HEADER_LOWER_BOUND] - ?.toBoolean() ?: false // If not present, assume it's not lower bound + val eventCount = getEventCountFromHeader(response) val streaming = response.body()?.byteStream() ?: ByteArrayInputStream(byteArrayOf()) Simber.tag("SYNC").d("[EVENT_REMOTE_SOURCE] Stream taken") EventDownSyncResult( - totalCount = totalCount.takeUnless { isTotalLowerBound }, + totalCount = eventCount.exactCount, requestId = getRequestId(response), status = response.code(), eventStream = scope.produce(capacity = CHANNEL_CAPACITY_FOR_PROPAGATION) { diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/EventRemoteInterface.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/EventRemoteInterface.kt index 23a8695943..86644b9165 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/EventRemoteInterface.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/EventRemoteInterface.kt @@ -1,6 +1,5 @@ package com.simprints.infra.eventsync.event.remote -import com.simprints.infra.eventsync.event.remote.models.ApiEventCount import com.simprints.infra.network.SimRemoteInterface import okhttp3.ResponseBody import retrofit2.Response @@ -10,7 +9,8 @@ import retrofit2.http.* @JvmSuppressWildcards internal interface EventRemoteInterface : SimRemoteInterface { - @GET("projects/{projectId}/events/count") + @Headers("X-Force-Version: 2024.1.1") + @HEAD("projects/{projectId}/events") suspend fun countEvents( @Path("projectId") projectId: String, @Query("l_moduleId") moduleId: String?, @@ -18,7 +18,7 @@ internal interface EventRemoteInterface : SimRemoteInterface { @Query("l_subjectId") subjectId: String?, @Query("l_mode") modes: List, @Query("lastEventId") lastEventId: String? - ): List + ): Response @Headers( "Content-Encoding: gzip", diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventCount.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventCount.kt deleted file mode 100644 index 1ec8816822..0000000000 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventCount.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.simprints.infra.eventsync.event.remote.models - -import androidx.annotation.Keep -import com.simprints.infra.events.event.domain.EventCount -import com.simprints.infra.eventsync.event.remote.models.subject.ApiEnrolmentRecordPayloadType -import com.simprints.infra.eventsync.event.remote.models.subject.fromApiToDomain - -@Keep -internal data class ApiEventCount(val type: ApiEnrolmentRecordPayloadType, val count: Int) - -internal fun ApiEventCount.fromApiToDomain() = EventCount(type.fromApiToDomain(), count) diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/models/DownSyncCounts.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/models/DownSyncCounts.kt index b4d7aa69b3..ec836890c2 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/models/DownSyncCounts.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/models/DownSyncCounts.kt @@ -1,6 +1,6 @@ package com.simprints.infra.eventsync.status.models data class DownSyncCounts( - val toCreate: Int, - val toDelete: Int + val count: Int, + val isLowerBound: Boolean, ) diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/EventSyncManagerTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/EventSyncManagerTest.kt index 6318411ce5..cc2ee4c7f6 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/EventSyncManagerTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/EventSyncManagerTest.kt @@ -9,12 +9,12 @@ import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.events.EventRepository import com.simprints.infra.events.event.domain.EventCount import com.simprints.infra.events.event.domain.models.scope.EventScope -import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordEventType import com.simprints.infra.events.sampledata.SampleDefaults.DEFAULT_MODULE_ID import com.simprints.infra.events.sampledata.SampleDefaults.DEFAULT_MODULE_ID_2 import com.simprints.infra.events.sampledata.SampleDefaults.DEFAULT_PROJECT_ID import com.simprints.infra.eventsync.event.remote.EventRemoteDataSource import com.simprints.infra.eventsync.status.down.EventDownSyncScopeRepository +import com.simprints.infra.eventsync.status.models.DownSyncCounts import com.simprints.infra.eventsync.status.up.EventUpSyncScopeRepository import com.simprints.infra.eventsync.sync.EventSyncStateProcessor import com.simprints.infra.eventsync.sync.common.* @@ -118,14 +118,8 @@ internal class EventSyncManagerTest { } returns SampleSyncScopes.modulesDownSyncScope coEvery { eventRemoteDataSource.count(any()) } returnsMany listOf( - listOf( - EventCount(EnrolmentRecordEventType.EnrolmentRecordCreation, 3), - EventCount(EnrolmentRecordEventType.EnrolmentRecordDeletion, 5), - ), - listOf( - EventCount(EnrolmentRecordEventType.EnrolmentRecordCreation, 7), - EventCount(EnrolmentRecordEventType.EnrolmentRecordDeletion, 11), - ) + EventCount(8, false), + EventCount(18, true), ) coEvery { configRepository.getDeviceConfiguration() } returns mockk { every { selectedModules } returns listOf(DEFAULT_MODULE_ID, DEFAULT_MODULE_ID_2) @@ -133,33 +127,7 @@ internal class EventSyncManagerTest { val result = eventSyncManagerImpl.countEventsToDownload() - assertThat(result.toCreate).isEqualTo(10) - assertThat(result.toDelete).isEqualTo(16) - } - - @Test - fun `getDownSyncCounts does not count record move`() = runTest { - coEvery { - eventDownSyncScopeRepository.getDownSyncScope(any(), any(), any()) - } returns SampleSyncScopes.modulesDownSyncScope - - coEvery { eventRemoteDataSource.count(any()) } returnsMany listOf( - listOf( - EventCount(EnrolmentRecordEventType.EnrolmentRecordCreation, 3), - ), - listOf( - EventCount(EnrolmentRecordEventType.EnrolmentRecordMove, 7), - EventCount(EnrolmentRecordEventType.EnrolmentRecordDeletion, 5), - ) - ) - coEvery { configRepository.getDeviceConfiguration() } returns mockk { - every { selectedModules } returns listOf(DEFAULT_MODULE_ID, DEFAULT_MODULE_ID_2) - } - - val result = eventSyncManagerImpl.countEventsToDownload() - - assertThat(result.toCreate).isEqualTo(3) - assertThat(result.toDelete).isEqualTo(5) + assertThat(result).isEqualTo(DownSyncCounts(26, isLowerBound = true)) } @Test diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/EventRemoteDataSourceTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/EventRemoteDataSourceTest.kt index 48f0354949..788ed87d02 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/EventRemoteDataSourceTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/EventRemoteDataSourceTest.kt @@ -5,7 +5,6 @@ import com.simprints.core.tools.json.JsonHelper import com.simprints.infra.authstore.AuthStore import com.simprints.infra.events.event.domain.EventCount import com.simprints.infra.events.event.domain.models.Event -import com.simprints.infra.events.event.domain.models.scope.EventScopeType import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordEvent import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordEventType import com.simprints.infra.events.sampledata.SampleDefaults.DEFAULT_MODULE_ID @@ -16,9 +15,7 @@ import com.simprints.infra.events.sampledata.SampleDefaults.GUID2 import com.simprints.infra.events.sampledata.createAlertScreenEvent import com.simprints.infra.events.sampledata.createSessionScope import com.simprints.infra.eventsync.event.remote.exceptions.TooManyRequestsException -import com.simprints.infra.eventsync.event.remote.models.ApiEventCount import com.simprints.infra.eventsync.event.remote.models.session.ApiEventScope -import com.simprints.infra.eventsync.event.remote.models.subject.ApiEnrolmentRecordPayloadType import com.simprints.infra.network.SimNetwork import com.simprints.infra.network.exceptions.BackendMaintenanceException import com.simprints.infra.network.exceptions.SyncCloudIntegrationException @@ -30,8 +27,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.channels.produce import kotlinx.coroutines.test.runTest -import okhttp3.Headers -import okhttp3.Headers.Companion.headersOf import okhttp3.Headers.Companion.toHeaders import okhttp3.ResponseBody.Companion.toResponseBody import org.junit.After @@ -84,31 +79,15 @@ class EventRemoteDataSourceTest { @Test fun count_shouldMakeANetworkRequest() = runTest { coEvery { - eventRemoteInterface.countEvents( - any(), - any(), - any(), - any(), - any(), - any() - ) - } returns listOf( - ApiEventCount( - ApiEnrolmentRecordPayloadType.EnrolmentRecordCreation, - 1 - ) + eventRemoteInterface.countEvents(any(), any(), any(), any(), any(), any()) + } returns Response.success( + "".toResponseBody(null), + mapOf("x-event-count" to "6", "x-event-count-is-lower-bound" to "true").toHeaders() ) val count = eventRemoteDataSource.count(query) - assertThat(count).isEqualTo( - listOf( - EventCount( - EnrolmentRecordEventType.EnrolmentRecordCreation, - 1 - ) - ) - ) + assertThat(count).isEqualTo(EventCount(6, true)) coVerify(exactly = 1) { eventRemoteInterface.countEvents( projectId = DEFAULT_PROJECT_ID, From 0348e066727f43790ac4a7d6da9688c65d101a36 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 04:49:31 +0000 Subject: [PATCH 041/197] Bump hilt_version from 2.50 to 2.51 Bumps `hilt_version` from 2.50 to 2.51. Updates `com.google.dagger:hilt-android` from 2.50 to 2.51 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.50...dagger-2.51) Updates `com.google.dagger:hilt-compiler` from 2.50 to 2.51 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.50...dagger-2.51) Updates `com.google.dagger:hilt-android-testing` from 2.50 to 2.51 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.50...dagger-2.51) Updates `com.google.dagger:hilt-android-compiler` from 2.50 to 2.51 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.50...dagger-2.51) Updates `com.google.dagger:hilt-android-gradle-plugin` from 2.50 to 2.51 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.50...dagger-2.51) Updates `com.google.dagger.hilt.android` from 2.50 to 2.51 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.50...dagger-2.51) --- updated-dependencies: - dependency-name: com.google.dagger:hilt-android dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.google.dagger:hilt-compiler dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.google.dagger:hilt-android-testing dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.google.dagger:hilt-android-compiler dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.google.dagger:hilt-android-gradle-plugin dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.google.dagger.hilt.android dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 045c9ba721..a9389634b5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ androidx_annotation_version = "1.7.1" androidx_arch_core_version = "2.2.0" matertial_version = "1.11.0" -hilt_version = "2.50" +hilt_version = "2.51" hilt_androidx_version = "1.2.0" play_base_services_version = "18.3.0" From 8d22ef127ae4cb4b95a8bd69cda886889fa236f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 04:51:53 +0000 Subject: [PATCH 042/197] Bump mockk_version from 1.13.9 to 1.13.10 Bumps `mockk_version` from 1.13.9 to 1.13.10. Updates `io.mockk:mockk` from 1.13.9 to 1.13.10 - [Release notes](https://github.com/mockk/mockk/releases) - [Commits](https://github.com/mockk/mockk/compare/1.13.9...1.13.10) Updates `io.mockk:mockk-android` from 1.13.9 to 1.13.10 - [Release notes](https://github.com/mockk/mockk/releases) - [Commits](https://github.com/mockk/mockk/compare/1.13.9...1.13.10) --- updated-dependencies: - dependency-name: io.mockk:mockk dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.mockk:mockk-android dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 045c9ba721..bcaf7c94f8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -77,7 +77,7 @@ junit_version = "4.13.2" junit_ext_version = "1.1.5" truth_version = "1.4.1" livedata_testing_version = "1.3.0" -mockk_version = "1.13.9" +mockk_version = "1.13.10" robolectric_version = "4.11.1" espresso_version = "3.5.1" espresso_accessibility_version = "4.1.0" From a26b73719e9df673bdb83201d55918514ac5d59c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 04:53:49 +0000 Subject: [PATCH 043/197] Bump firebase_distrtibutionPlugin_version from 4.1.0 to 4.2.0 Bumps `firebase_distrtibutionPlugin_version` from 4.1.0 to 4.2.0. Updates `com.google.firebase:firebase-appdistribution-gradle` from 4.1.0 to 4.2.0 Updates `com.google.firebase.appdistribution` from 4.1.0 to 4.2.0 --- updated-dependencies: - dependency-name: com.google.firebase:firebase-appdistribution-gradle dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.google.firebase.appdistribution dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 045c9ba721..ece6b0f4c4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,7 +43,7 @@ firebase_analytics_version = "21.4.0" firebase_perf_version = "20.5.2" firebase_crashlyticsPlugin_version = "2.9.9" firebase_perfPlugin_version = "1.4.2" -firebase_distrtibutionPlugin_version = "4.1.0" +firebase_distrtibutionPlugin_version = "4.2.0" retrofit_version = "2.9.0" okttp_version = "4.12.0" From 79b436591ad192f43d65771efe4471c964dd6b59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 04:54:50 +0000 Subject: [PATCH 044/197] Bump com.google.truth:truth from 1.4.1 to 1.4.2 Bumps [com.google.truth:truth](https://github.com/google/truth) from 1.4.1 to 1.4.2. - [Release notes](https://github.com/google/truth/releases) - [Commits](https://github.com/google/truth/compare/v1.4.1...v1.4.2) --- updated-dependencies: - dependency-name: com.google.truth:truth dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 045c9ba721..7e85de85f0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -75,7 +75,7 @@ bitmapConverter_version = "0.2.0" junit_version = "4.13.2" junit_ext_version = "1.1.5" -truth_version = "1.4.1" +truth_version = "1.4.2" livedata_testing_version = "1.3.0" mockk_version = "1.13.9" robolectric_version = "4.11.1" From 6e287f53aefd9dfd29ee3237d0be5cbfe1eb2c24 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 04:50:31 +0000 Subject: [PATCH 045/197] Bump android_gradlePlugin_version from 8.2.2 to 8.3.0 Bumps `android_gradlePlugin_version` from 8.2.2 to 8.3.0. Updates `com.android.tools.build:gradle` from 8.2.2 to 8.3.0 Updates `com.android.application` from 8.2.2 to 8.3.0 Updates `com.android.library` from 8.2.2 to 8.3.0 Updates `com.android.test` from 8.2.2 to 8.3.0 --- updated-dependencies: - dependency-name: com.android.tools.build:gradle dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.android.application dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.android.library dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.android.test dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f4f8711f2c..98ad6dd044 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin_version = "1.9.22" kotlin_coroutine_version = "1.8.0" -android_gradlePlugin_version = "8.2.2" +android_gradlePlugin_version = "8.3.0" androidx_version = "1.5.2" androidx_core_version = "1.12.0" androidx_app_compat_version = "1.6.1" From fc461cb1a8df8979c208d34dd80a91c85ca7228e Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Mon, 4 Mar 2024 09:49:04 +0200 Subject: [PATCH 046/197] Fix convention plugin extensions for newest version --- .../convention/src/main/kotlin/common/KotlinAndroid.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build-logic/convention/src/main/kotlin/common/KotlinAndroid.kt b/build-logic/convention/src/main/kotlin/common/KotlinAndroid.kt index 3d0afd22ca..e55b1769df 100644 --- a/build-logic/convention/src/main/kotlin/common/KotlinAndroid.kt +++ b/build-logic/convention/src/main/kotlin/common/KotlinAndroid.kt @@ -9,7 +9,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions * Configure base Kotlin with Android options */ internal fun Project.configureKotlinAndroid( - commonExtension: CommonExtension<*, *, *, *, *>, + commonExtension: CommonExtension<*, *, *, *, *, *>, ) { commonExtension.apply { compileSdk = SdkVersions.TARGET @@ -39,6 +39,6 @@ internal fun Project.configureKotlinAndroid( } } -fun CommonExtension<*, *, *, *, *>.kotlinOptions(block: KotlinJvmOptions.() -> Unit) { +fun CommonExtension<*, *, *, *, *, *>.kotlinOptions(block: KotlinJvmOptions.() -> Unit) { (this as ExtensionAware).extensions.configure("kotlinOptions", block) } From 373d9c92b27850f115433568f20c11ffae2f22c0 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Wed, 28 Feb 2024 10:42:55 +0200 Subject: [PATCH 047/197] MS-157 Remove password toggle on useId field --- feature/login/src/main/res/layout/fragment_login_form.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/feature/login/src/main/res/layout/fragment_login_form.xml b/feature/login/src/main/res/layout/fragment_login_form.xml index 1cdbe6f2a4..b049a155a4 100644 --- a/feature/login/src/main/res/layout/fragment_login_form.xml +++ b/feature/login/src/main/res/layout/fragment_login_form.xml @@ -28,8 +28,7 @@ + android:layout_marginTop="8dp"> Date: Wed, 28 Feb 2024 10:56:06 +0200 Subject: [PATCH 048/197] MS-157 Disable project ID field editing --- .../feature/login/screens/form/LoginFormFragment.kt | 3 +++ feature/login/src/main/res/layout/fragment_login_form.xml | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormFragment.kt b/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormFragment.kt index 7b40a63b97..348ed5e96e 100644 --- a/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormFragment.kt +++ b/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormFragment.kt @@ -52,6 +52,7 @@ internal class LoginFormFragment : Fragment(R.layout.fragment_login_form) { private val viewModel by viewModels() private lateinit var checkForPlayServicesResultLauncher: ActivityResultLauncher + init { checkForPlayServicesResultLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { @@ -87,6 +88,8 @@ internal class LoginFormFragment : Fragment(R.layout.fragment_login_form) { private fun initUi() { binding.loginUserId.setText(args.loginParams.userId.value) + binding.loginProjectId.setText(args.loginParams.projectId) + binding.loginButtonScanQr.setOnClickListener { Simber.tag(LoggingConstants.CrashReportTag.LOGIN.name).i("Scan QR button clicked") findNavController().navigate(R.id.action_loginFormFragment_to_loginQrScanner) diff --git a/feature/login/src/main/res/layout/fragment_login_form.xml b/feature/login/src/main/res/layout/fragment_login_form.xml index b049a155a4..cdb1712e13 100644 --- a/feature/login/src/main/res/layout/fragment_login_form.xml +++ b/feature/login/src/main/res/layout/fragment_login_form.xml @@ -39,8 +39,6 @@ android:inputType="text" android:minWidth="150dp" tools:text="UserName" /> - - @@ -69,6 +68,8 @@ android:layout_height="wrap_content" android:hint="@string/login_secret_hint" android:inputType="textPassword" /> + + Date: Wed, 28 Feb 2024 10:57:56 +0200 Subject: [PATCH 049/197] MS-157 Check project mismatch on QR code scan result instead of login press --- .../login/screens/form/LoginFormFragment.kt | 3 +-- .../login/screens/form/LoginFormViewModel.kt | 12 +++++++---- .../screens/form/LoginFormViewModelTest.kt | 20 ++++++++++++++----- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormFragment.kt b/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormFragment.kt index 348ed5e96e..d7ac4b613d 100644 --- a/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormFragment.kt +++ b/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormFragment.kt @@ -77,7 +77,7 @@ internal class LoginFormFragment : Fragment(R.layout.fragment_login_form) { viewLifecycleOwner, R.id.loginFormFragment, R.id.loginQrScanner - ) { viewModel.handleQrResult(it) } + ) { viewModel.handleQrResult(args.loginParams.projectId, it) } initUi() observeUiState() @@ -150,7 +150,6 @@ internal class LoginFormFragment : Fragment(R.layout.fragment_login_form) { } private fun updateFields(result: QrCodeValid) { - binding.loginProjectId.setText(result.projectId) binding.loginProjectSecret.setText(result.projectSecret) } diff --git a/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormViewModel.kt b/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormViewModel.kt index 401fcd7a37..6ec5d62ec0 100644 --- a/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormViewModel.kt +++ b/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormViewModel.kt @@ -72,16 +72,20 @@ internal class LoginFormViewModel @Inject constructor( userId: String ) = projectId.isNotEmpty() && projectSecret.isNotEmpty() && userId.isNotEmpty() - fun handleQrResult(result: QrScannerResult) { + fun handleQrResult(projectId: String, result: QrScannerResult) { if (result.error != null) { _signInState.send(mapQrError(result.error)) } else if (!result.content.isNullOrEmpty()) { try { val qrContent = jsonHelper.fromJson(result.content) - Simber.tag(CrashReportTag.LOGIN.name).i("QR scanning successful") - qrContent.apiBaseUrl?.let { simNetwork.setApiBaseUrl(it) } - _signInState.send(SignInState.QrCodeValid(qrContent.projectId, qrContent.projectSecret)) + + if (projectId != qrContent.projectId) { + _signInState.send(SignInState.ProjectIdMismatch) + } else { + qrContent.apiBaseUrl?.let { simNetwork.setApiBaseUrl(it) } + _signInState.send(SignInState.QrCodeValid(qrContent.projectId, qrContent.projectSecret)) + } } catch (e: Exception) { Simber.tag(CrashReportTag.LOGIN.name).i("QR scanning unsuccessful") _signInState.send(SignInState.QrInvalidCode) diff --git a/feature/login/src/test/java/com/simprints/feature/login/screens/form/LoginFormViewModelTest.kt b/feature/login/src/test/java/com/simprints/feature/login/screens/form/LoginFormViewModelTest.kt index eb63ccae62..324525ba8e 100644 --- a/feature/login/src/test/java/com/simprints/feature/login/screens/form/LoginFormViewModelTest.kt +++ b/feature/login/src/test/java/com/simprints/feature/login/screens/form/LoginFormViewModelTest.kt @@ -136,7 +136,7 @@ internal class LoginFormViewModelTest { QrScannerError.CameraNotAvailable to SignInState.QrCameraUnavailable::class.java, QrScannerError.UnknownError to SignInState.QrGenericError::class.java, ).forEach { (error, expected) -> - viewModel.handleQrResult(QrScannerResult(null, error)) + viewModel.handleQrResult(PROJECT_ID, QrScannerResult(null, error)) val result = viewModel.signInState.getOrAwaitValue() assertThat(result.getContentIfNotHandled()).isInstanceOf(expected) @@ -145,7 +145,7 @@ internal class LoginFormViewModelTest { @Test fun `returns correct SignInState when empty QR result`() { - viewModel.handleQrResult(QrScannerResult(null, null)) + viewModel.handleQrResult(PROJECT_ID, QrScannerResult(null, null)) val result = viewModel.signInState.getOrAwaitValue() assertThat(result.getContentIfNotHandled()).isInstanceOf(SignInState.QrInvalidCode::class.java) @@ -155,17 +155,27 @@ internal class LoginFormViewModelTest { fun `returns correct SignInState when QR code parsing fails`() { every { jsonHelper.fromJson(any()) } throws RuntimeException("parsing fail") - viewModel.handleQrResult(QrScannerResult(QR_CONTENT, null)) + viewModel.handleQrResult(PROJECT_ID, QrScannerResult(QR_CONTENT, null)) val result = viewModel.signInState.getOrAwaitValue() assertThat(result.getContentIfNotHandled()).isInstanceOf(SignInState.QrInvalidCode::class.java) } + @Test + fun `returns correct SignInState when QR contains wrong project ID`() { + every { jsonHelper.fromJson(eq(QR_CONTENT)) } returns QrCodeContent("differentProjectId", PROJECT_SECRET) + + viewModel.handleQrResult(PROJECT_ID, QrScannerResult(QR_CONTENT, null)) + val result = viewModel.signInState.getOrAwaitValue() + + assertThat(result.getContentIfNotHandled()).isInstanceOf(SignInState.ProjectIdMismatch::class.java) + } + @Test fun `returns correct SignInState when QR code parsing success`() { every { jsonHelper.fromJson(eq(QR_CONTENT)) } returns QrCodeContent(PROJECT_ID, PROJECT_SECRET) - viewModel.handleQrResult(QrScannerResult(QR_CONTENT, null)) + viewModel.handleQrResult(PROJECT_ID, QrScannerResult(QR_CONTENT, null)) val result = viewModel.signInState.getOrAwaitValue() assertThat(result.getContentIfNotHandled()).isInstanceOf(SignInState.QrCodeValid::class.java) @@ -177,7 +187,7 @@ internal class LoginFormViewModelTest { fun `updates base API url when QR code parsing success`() { every { jsonHelper.fromJson(eq(QR_CONTENT)) } returns QrCodeContent(PROJECT_ID, PROJECT_SECRET, URL) - viewModel.handleQrResult(QrScannerResult(QR_CONTENT, null)) + viewModel.handleQrResult(PROJECT_ID, QrScannerResult(QR_CONTENT, null)) verify { simNetwork.setApiBaseUrl(eq(URL)) } } From 922280017cbe2fcf649bb4209105d6b53756b68c Mon Sep 17 00:00:00 2001 From: melad Date: Mon, 4 Mar 2024 16:21:50 +0200 Subject: [PATCH 050/197] MS-246 update release tagging when deploying to prod --- .github/workflows/deploy-to-internal.yml | 6 ------ .github/workflows/promote-artifact.yml | 7 +++++++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploy-to-internal.yml b/.github/workflows/deploy-to-internal.yml index 2c26854d80..0b16dcbc66 100644 --- a/.github/workflows/deploy-to-internal.yml +++ b/.github/workflows/deploy-to-internal.yml @@ -21,9 +21,6 @@ jobs: group: release-internal-workflow #only one instance of this workflow can run at a time cancel-in-progress: true - permissions: - contents: write # A write permission For Auto tagging the releases - environment: internal env: @@ -70,6 +67,3 @@ jobs: - name: Publish Release bundle run: ./gradlew id:publishReleaseBundle - - - name: Set release tag - run: bash ci/deployment/release_tag_setup diff --git a/.github/workflows/promote-artifact.yml b/.github/workflows/promote-artifact.yml index da86531949..4152374b57 100644 --- a/.github/workflows/promote-artifact.yml +++ b/.github/workflows/promote-artifact.yml @@ -23,6 +23,9 @@ jobs: environment: ${{inputs.deployment-track}} # Dynamically set the job environment based on the input + permissions: + contents: write # A write permission For Auto tagging the releases + concurrency: group: promote-release-workflow #only one instance of this workflow can run at a time @@ -56,6 +59,10 @@ jobs: if: ${{inputs.deployment-track == 'Prod-25-Percent-Rollout'}} run: ./gradlew promoteArtifact --from-track alpha --promote-track production --release-status inProgress --user-fraction .25 + - name: Set release tag + if: ${{inputs.deployment-track == 'Prod-25-Percent-Rollout'}} + run: bash ci/deployment/release_tag_setup + - name: Promote to production 50% if: ${{inputs.deployment-track == 'Prod-50-Percent-Rollout'}} run: ./gradlew promoteArtifact --update production --user-fraction .5 From a812c53d496cf8c3c6089ef0dfb613cf41785d3e Mon Sep 17 00:00:00 2001 From: melad Date: Mon, 4 Mar 2024 16:56:08 +0200 Subject: [PATCH 051/197] [skip ci] delete jira.yml as we will use jira automation web hock to sync github issues with jira --- .github/workflows/jira.yml | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 .github/workflows/jira.yml diff --git a/.github/workflows/jira.yml b/.github/workflows/jira.yml deleted file mode 100644 index 74de3b2e5e..0000000000 --- a/.github/workflows/jira.yml +++ /dev/null @@ -1,28 +0,0 @@ -# Auto create jira task for github issues -name: Jira Sync - -on: - workflow_dispatch: - - -# [TODO] enable this workflow once the jira integration is ready -# issues: -# types: [ opened, labeled, unlabeled ] -# issue_comment: -# types: [ created ] -jobs: - sync: - name: Sync Items - runs-on: ubuntu-latest - timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT_MINUTES) }} - steps: - - name: Sync - uses: mheap/github-action-issue-to-jira@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - jiraHost: ${{ secrets.JIRA_HOST }} - jiraUsername: ${{ secrets.JIRA_USERNAME }} - jiraPassword: ${{ secrets.JIRA_PASSWORD }} - project: ${{ vars.JIRA_PROJECT_KEY }} - assignee: ${{ vars.JIRA_ASSIGNEE }} From b03090b7dc5132fd844de9651094bce116d8f570 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Wed, 28 Feb 2024 12:42:10 +0200 Subject: [PATCH 052/197] MS-157 Add an option to change backend base url manually --- .../login/screens/form/LoginFormFragment.kt | 36 ++++++++++++++++++- .../login/screens/form/LoginFormViewModel.kt | 26 ++++++++++++-- .../feature/login/screens/form/SignInState.kt | 2 ++ .../main/res/layout/fragment_login_form.xml | 16 ++++++++- .../main/res/layout/view_url_change_input.xml | 16 +++++++++ .../screens/form/LoginFormViewModelTest.kt | 22 ++++++++++++ .../infra/authstore/domain/LoginInfoStore.kt | 16 +++++++-- .../authstore/domain/LoginInfoStoreTest.kt | 12 +++---- .../com/simprints/infra/network/SimNetwork.kt | 1 + .../simprints/infra/network/SimNetworkImpl.kt | 4 +++ .../infra/network/url/BaseUrlProvider.kt | 1 + .../infra/network/url/BaseUrlProviderImpl.kt | 27 +++++++++++--- .../network/url/BaseUrlProviderImplTest.kt | 16 +++++++-- .../resources/src/main/res/values/strings.xml | 4 +++ 14 files changed, 177 insertions(+), 22 deletions(-) create mode 100644 feature/login/src/main/res/layout/view_url_change_input.xml diff --git a/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormFragment.kt b/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormFragment.kt index d7ac4b613d..3938d103e8 100644 --- a/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormFragment.kt +++ b/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormFragment.kt @@ -8,15 +8,18 @@ import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.IntentSenderRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.StringRes +import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.simprints.feature.login.LoginError import com.simprints.feature.login.LoginResult import com.simprints.feature.login.R import com.simprints.feature.login.databinding.FragmentLoginFormBinding +import com.simprints.feature.login.databinding.ViewUrlChangeInputBinding import com.simprints.feature.login.screens.form.SignInState.BackendMaintenanceError import com.simprints.feature.login.screens.form.SignInState.BadCredentials import com.simprints.feature.login.screens.form.SignInState.IntegrityException @@ -30,6 +33,7 @@ import com.simprints.feature.login.screens.form.SignInState.QrCodeValid import com.simprints.feature.login.screens.form.SignInState.QrGenericError import com.simprints.feature.login.screens.form.SignInState.QrInvalidCode import com.simprints.feature.login.screens.form.SignInState.QrNoCameraPermission +import com.simprints.feature.login.screens.form.SignInState.ShowUrlChangeDialog import com.simprints.feature.login.screens.form.SignInState.Success import com.simprints.feature.login.screens.form.SignInState.TechnicalFailure import com.simprints.feature.login.screens.form.SignInState.Unknown @@ -90,6 +94,11 @@ internal class LoginFormFragment : Fragment(R.layout.fragment_login_form) { binding.loginUserId.setText(args.loginParams.userId.value) binding.loginProjectId.setText(args.loginParams.projectId) + binding.loginChangeUrlButton.setOnClickListener { + Simber.tag(LoggingConstants.CrashReportTag.LOGIN.name).i("Change URL button clicked") + viewModel.changeUrlClicked() + } + binding.loginButtonScanQr.setOnClickListener { Simber.tag(LoggingConstants.CrashReportTag.LOGIN.name).i("Scan QR button clicked") findNavController().navigate(R.id.action_loginFormFragment_to_loginQrScanner) @@ -135,6 +144,8 @@ internal class LoginFormFragment : Fragment(R.layout.fragment_login_form) { QrInvalidCode -> showToast(IDR.string.login_invalid_qr_code_error) QrNoCameraPermission -> showToast(IDR.string.login_qr_code_scanning_camera_permission_error) + is ShowUrlChangeDialog -> createChangeUrlDialog(result).show() + // Showing error card is BackendMaintenanceError -> showOutageErrorCard(result.estimatedOutage) @@ -155,7 +166,12 @@ internal class LoginFormFragment : Fragment(R.layout.fragment_login_form) { private fun showOutageErrorCard(estimatedOutage: String?) { binding.loginErrorText.text = estimatedOutage - ?.let { getString(IDR.string.error_backend_maintenance_with_time_message, estimatedOutage) } + ?.let { + getString( + IDR.string.error_backend_maintenance_with_time_message, + estimatedOutage + ) + } ?: getString(IDR.string.error_backend_maintenance_message) binding.loginErrorCard.isVisible = true } @@ -164,6 +180,24 @@ internal class LoginFormFragment : Fragment(R.layout.fragment_login_form) { Toast.makeText(requireContext(), getString(messageId), Toast.LENGTH_LONG).show() } + private fun createChangeUrlDialog(result: ShowUrlChangeDialog): AlertDialog { + val binding = ViewUrlChangeInputBinding.inflate(layoutInflater) + .apply { loginUrlChangeInput.setText(result.currentUrl) } + return MaterialAlertDialogBuilder(requireContext()) + .setTitle(IDR.string.login_change_url) + .setView(binding.root) + .setNeutralButton(IDR.string.login_change_url_reset) { di, _ -> + viewModel.saveNewUrl(null) + di.dismiss() + } + .setPositiveButton(IDR.string.login_change_url_save) { di, _ -> + viewModel.saveNewUrl(binding.loginUrlChangeInput.text.toString()) + di.dismiss() + } + .setNegativeButton(IDR.string.login_change_url_cancel) { di, _ -> di.dismiss() } + .create() + } + private fun finishWithSuccess() { findNavController().finishWithResult(this, LoginResult(true)) } diff --git a/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormViewModel.kt b/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormViewModel.kt index 6ec5d62ec0..6e2f518217 100644 --- a/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormViewModel.kt +++ b/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormViewModel.kt @@ -46,7 +46,12 @@ internal class LoginFormViewModel @Inject constructor( _signInState.send(SignInState.ProjectIdMismatch) } else { viewModelScope.launch { - val result = authManager.authenticateSafely(loginParams.userId.value, projectId, projectSecret, deviceId) + val result = authManager.authenticateSafely( + loginParams.userId.value, + projectId, + projectSecret, + deviceId + ) _signInState.send(mapAuthDataResult(result)) } } @@ -69,7 +74,7 @@ internal class LoginFormViewModel @Inject constructor( private fun areMandatoryCredentialsPresent( projectId: String, projectSecret: String, - userId: String + userId: String, ) = projectId.isNotEmpty() && projectSecret.isNotEmpty() && userId.isNotEmpty() fun handleQrResult(projectId: String, result: QrScannerResult) { @@ -84,7 +89,12 @@ internal class LoginFormViewModel @Inject constructor( _signInState.send(SignInState.ProjectIdMismatch) } else { qrContent.apiBaseUrl?.let { simNetwork.setApiBaseUrl(it) } - _signInState.send(SignInState.QrCodeValid(qrContent.projectId, qrContent.projectSecret)) + _signInState.send( + SignInState.QrCodeValid( + qrContent.projectId, + qrContent.projectSecret + ) + ) } } catch (e: Exception) { Simber.tag(CrashReportTag.LOGIN.name).i("QR scanning unsuccessful") @@ -102,4 +112,14 @@ internal class LoginFormViewModel @Inject constructor( QrScannerError.UnknownError -> SignInState.QrGenericError } + fun changeUrlClicked() { + _signInState.send(SignInState.ShowUrlChangeDialog(simNetwork.getApiBaseUrlPrefix())) + } + + fun saveNewUrl(newUrl: String?) = if (newUrl.isNullOrEmpty()) { + simNetwork.resetApiBaseUrl() + } else { + simNetwork.setApiBaseUrl(newUrl) + } + } diff --git a/feature/login/src/main/java/com/simprints/feature/login/screens/form/SignInState.kt b/feature/login/src/main/java/com/simprints/feature/login/screens/form/SignInState.kt index 14961452b6..088c47b58d 100644 --- a/feature/login/src/main/java/com/simprints/feature/login/screens/form/SignInState.kt +++ b/feature/login/src/main/java/com/simprints/feature/login/screens/form/SignInState.kt @@ -17,6 +17,8 @@ internal sealed class SignInState { data object QrInvalidCode : SignInState() data object QrGenericError : SignInState() + data class ShowUrlChangeDialog(val currentUrl: String) : SignInState() + data object BadCredentials : SignInState() data object Offline : SignInState() data object TechnicalFailure : SignInState() diff --git a/feature/login/src/main/res/layout/fragment_login_form.xml b/feature/login/src/main/res/layout/fragment_login_form.xml index cdb1712e13..c862fecb54 100644 --- a/feature/login/src/main/res/layout/fragment_login_form.xml +++ b/feature/login/src/main/res/layout/fragment_login_form.xml @@ -22,7 +22,7 @@ + + + diff --git a/feature/login/src/main/res/layout/view_url_change_input.xml b/feature/login/src/main/res/layout/view_url_change_input.xml new file mode 100644 index 0000000000..031d19bd63 --- /dev/null +++ b/feature/login/src/main/res/layout/view_url_change_input.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/feature/login/src/test/java/com/simprints/feature/login/screens/form/LoginFormViewModelTest.kt b/feature/login/src/test/java/com/simprints/feature/login/screens/form/LoginFormViewModelTest.kt index 324525ba8e..0ed73c4a4a 100644 --- a/feature/login/src/test/java/com/simprints/feature/login/screens/form/LoginFormViewModelTest.kt +++ b/feature/login/src/test/java/com/simprints/feature/login/screens/form/LoginFormViewModelTest.kt @@ -192,6 +192,28 @@ internal class LoginFormViewModelTest { verify { simNetwork.setApiBaseUrl(eq(URL)) } } + @Test + fun `updates UI state when change URL clicked`() { + viewModel.changeUrlClicked() + + val result = viewModel.signInState.getOrAwaitValue() + assertThat(result.getContentIfNotHandled()).isInstanceOf(SignInState.ShowUrlChangeDialog::class.java) + } + + @Test + fun `saves provided base URL`() { + viewModel.saveNewUrl(URL) + + verify { simNetwork.setApiBaseUrl(URL) } + } + + @Test + fun `resets provided base URL`() { + viewModel.saveNewUrl(null) + + verify { simNetwork.resetApiBaseUrl() } + } + companion object { diff --git a/infra/auth-store/src/main/java/com/simprints/infra/authstore/domain/LoginInfoStore.kt b/infra/auth-store/src/main/java/com/simprints/infra/authstore/domain/LoginInfoStore.kt index 34208f3d9a..d2304f3cb4 100644 --- a/infra/auth-store/src/main/java/com/simprints/infra/authstore/domain/LoginInfoStore.kt +++ b/infra/auth-store/src/main/java/com/simprints/infra/authstore/domain/LoginInfoStore.kt @@ -50,7 +50,7 @@ internal class LoginInfoStore @Inject constructor( putString(CORE_FIREBASE_APPLICATION_ID, prefs.getString(CORE_FIREBASE_APPLICATION_ID, "")) putString(CORE_FIREBASE_API_KEY, prefs.getString(CORE_FIREBASE_API_KEY, "")) } - prefs.edit(commit = true) { clear() } + prefs.clearValues() } return securePrefs } @@ -119,8 +119,8 @@ internal class LoginInfoStore @Inject constructor( signedInProjectId.isNotEmpty() && signedInProjectId == possibleProjectId fun cleanCredentials() { - securePrefs.edit { clear() } - prefs.edit { clear() } + securePrefs.clearValues() + prefs.clearValues() } fun clearCachedTokenClaims() { @@ -131,4 +131,14 @@ internal class LoginInfoStore @Inject constructor( remove(CORE_FIREBASE_API_KEY) } } + + private fun SharedPreferences.clearValues() = edit(commit = true) { + remove(USER_ID_VALUE) + remove(USER_ID_TOKENIZED) + remove(PROJECT_ID) + remove(PROJECT_ID_CLAIM) + remove(CORE_FIREBASE_PROJECT_ID) + remove(CORE_FIREBASE_APPLICATION_ID) + remove(CORE_FIREBASE_API_KEY) + } } diff --git a/infra/auth-store/src/test/java/com/simprints/infra/authstore/domain/LoginInfoStoreTest.kt b/infra/auth-store/src/test/java/com/simprints/infra/authstore/domain/LoginInfoStoreTest.kt index bc89f6198e..37fddea737 100644 --- a/infra/auth-store/src/test/java/com/simprints/infra/authstore/domain/LoginInfoStoreTest.kt +++ b/infra/auth-store/src/test/java/com/simprints/infra/authstore/domain/LoginInfoStoreTest.kt @@ -65,11 +65,11 @@ class LoginInfoStoreTest { secureEditor.putString(any(), any()) secureEditor.putBoolean(any(), any()) } + // Check that legacy prefs cleared + verify(exactly = 7) { legacyEditor.remove(any()) } verify(exactly = 1) { - secureEditor.commit() - // Check that legacy prefs cleared - legacyEditor.clear() legacyEditor.commit() + secureEditor.commit() } } @@ -261,10 +261,8 @@ class LoginInfoStoreTest { fun `cleanCredentials should reset all the credentials`() { loginInfoStoreImpl.cleanCredentials() - verify(exactly = 1) { - secureEditor.clear() - secureEditor.apply() - } + verify(exactly = 7) { secureEditor.remove(any()) } + verify(exactly = 1) { secureEditor.commit() } } @Test diff --git a/infra/network/src/main/java/com/simprints/infra/network/SimNetwork.kt b/infra/network/src/main/java/com/simprints/infra/network/SimNetwork.kt index 31404ef262..dbcf83ad00 100644 --- a/infra/network/src/main/java/com/simprints/infra/network/SimNetwork.kt +++ b/infra/network/src/main/java/com/simprints/infra/network/SimNetwork.kt @@ -26,6 +26,7 @@ interface SimNetwork { ): SimApiClient fun getApiBaseUrl(): String + fun getApiBaseUrlPrefix(): String fun setApiBaseUrl(apiBaseUrl: String?) fun resetApiBaseUrl() diff --git a/infra/network/src/main/java/com/simprints/infra/network/SimNetworkImpl.kt b/infra/network/src/main/java/com/simprints/infra/network/SimNetworkImpl.kt index b15df20703..8b9a31448a 100644 --- a/infra/network/src/main/java/com/simprints/infra/network/SimNetworkImpl.kt +++ b/infra/network/src/main/java/com/simprints/infra/network/SimNetworkImpl.kt @@ -29,6 +29,10 @@ internal class SimNetworkImpl @Inject constructor( return baseUrlProvider.getApiBaseUrl() } + override fun getApiBaseUrlPrefix(): String { + return baseUrlProvider.getApiBaseUrlPrefix() + } + override fun setApiBaseUrl(apiBaseUrl: String?) { baseUrlProvider.setApiBaseUrl(apiBaseUrl) } diff --git a/infra/network/src/main/java/com/simprints/infra/network/url/BaseUrlProvider.kt b/infra/network/src/main/java/com/simprints/infra/network/url/BaseUrlProvider.kt index 2d6d73f461..06475a448a 100644 --- a/infra/network/src/main/java/com/simprints/infra/network/url/BaseUrlProvider.kt +++ b/infra/network/src/main/java/com/simprints/infra/network/url/BaseUrlProvider.kt @@ -2,6 +2,7 @@ package com.simprints.infra.network.url internal interface BaseUrlProvider { fun getApiBaseUrl(): String + fun getApiBaseUrlPrefix(): String fun setApiBaseUrl(apiBaseUrl: String?) fun resetApiBaseUrl() } diff --git a/infra/network/src/main/java/com/simprints/infra/network/url/BaseUrlProviderImpl.kt b/infra/network/src/main/java/com/simprints/infra/network/url/BaseUrlProviderImpl.kt index e2f25cf892..8ef127001e 100644 --- a/infra/network/src/main/java/com/simprints/infra/network/url/BaseUrlProviderImpl.kt +++ b/infra/network/src/main/java/com/simprints/infra/network/url/BaseUrlProviderImpl.kt @@ -3,18 +3,26 @@ package com.simprints.infra.network.url import android.content.Context import android.content.SharedPreferences import androidx.annotation.VisibleForTesting +import androidx.core.content.edit +import com.simprints.infra.logging.Simber import com.simprints.infra.network.BuildConfig import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject +import javax.inject.Singleton -internal class BaseUrlProviderImpl @Inject constructor(@ApplicationContext context: Context) : BaseUrlProvider { +@Singleton +internal class BaseUrlProviderImpl @Inject constructor( + @ApplicationContext context: Context, +) : BaseUrlProvider { companion object { + private const val PREF_FILE_NAME = "b3f0cf9b-4f3f-4c5b-bf85-7b1f44eddd7a" private const val PREF_MODE = Context.MODE_PRIVATE private const val API_BASE_URL_KEY = "ApiBaseUrl" private const val API_VERSION = "v2" private const val BASE_URL_SUFFIX = "/androidapi/$API_VERSION/" + @VisibleForTesting const val DEFAULT_BASE_URL = "https://${BuildConfig.BASE_URL_PREFIX}.simprints-apis.com$BASE_URL_SUFFIX" @@ -24,6 +32,11 @@ internal class BaseUrlProviderImpl @Inject constructor(@ApplicationContext conte val prefs: SharedPreferences = context.getSharedPreferences(PREF_FILE_NAME, PREF_MODE) override fun getApiBaseUrl(): String = prefs.getString(API_BASE_URL_KEY, DEFAULT_BASE_URL)!! + .also { Simber.d("API base URL is $it") } + + override fun getApiBaseUrlPrefix(): String = prefs.getString(API_BASE_URL_KEY, DEFAULT_BASE_URL) + ?.removeSuffix(BASE_URL_SUFFIX) + ?.also { Simber.d("API base URL prefix is $it") }!! override fun setApiBaseUrl(apiBaseUrl: String?) { val prefix = "https://" @@ -36,11 +49,15 @@ internal class BaseUrlProviderImpl @Inject constructor(@ApplicationContext conte DEFAULT_BASE_URL } - prefs.edit().putString(API_BASE_URL_KEY, newValue).apply() - } + Simber.e("Setting API base URL to $newValue") - override fun resetApiBaseUrl() = - prefs.edit().putString(API_BASE_URL_KEY, DEFAULT_BASE_URL).apply() + prefs.edit(commit = true) { putString(API_BASE_URL_KEY, newValue) } + Simber.e("Setting API base URL to ${getApiBaseUrl()}") + } + override fun resetApiBaseUrl() { + Simber.e("Resetting API base") + prefs.edit(commit = true) { putString(API_BASE_URL_KEY, DEFAULT_BASE_URL) } + } } diff --git a/infra/network/src/test/java/com/simprints/infra/network/url/BaseUrlProviderImplTest.kt b/infra/network/src/test/java/com/simprints/infra/network/url/BaseUrlProviderImplTest.kt index 361ad60672..2d52dc24e2 100644 --- a/infra/network/src/test/java/com/simprints/infra/network/url/BaseUrlProviderImplTest.kt +++ b/infra/network/src/test/java/com/simprints/infra/network/url/BaseUrlProviderImplTest.kt @@ -14,7 +14,10 @@ import org.junit.Test class BaseUrlProviderImplTest { companion object { + private const val URL_SUFFIX = "/androidapi/v2/" + private const val URL = "https://test" + private const val URL_WITH_SUFFIX = "https://test$URL_SUFFIX" } @RelaxedMockK @@ -47,6 +50,15 @@ class BaseUrlProviderImplTest { assertThat(url).isEqualTo(URL) } + @Test + fun `get api base url prefix should return the actual url`() { + every { sharedPreferences.getString(any(), any()) } returns URL_WITH_SUFFIX + + val url = baseUrlProviderImpl.getApiBaseUrlPrefix() + + assertThat(url).isEqualTo(URL) + } + @Test fun `set api base url should set the url to the default one when the one passed is null`() { baseUrlProviderImpl.setApiBaseUrl(null) @@ -59,7 +71,7 @@ class BaseUrlProviderImplTest { val url = "https://url.com" baseUrlProviderImpl.setApiBaseUrl(url) - verify(exactly = 1) { editor.putString(any(), "$url/androidapi/v2/") } + verify(exactly = 1) { editor.putString(any(), "$url$URL_SUFFIX") } } @Test @@ -67,7 +79,7 @@ class BaseUrlProviderImplTest { val url = "url.com" baseUrlProviderImpl.setApiBaseUrl(url) - verify(exactly = 1) { editor.putString(any(), "https://$url/androidapi/v2/") } + verify(exactly = 1) { editor.putString(any(), "https://$url$URL_SUFFIX") } } @Test diff --git a/infra/resources/src/main/res/values/strings.xml b/infra/resources/src/main/res/values/strings.xml index 549c5f3cf4..0ef3ff18ff 100644 --- a/infra/resources/src/main/res/values/strings.xml +++ b/infra/resources/src/main/res/values/strings.xml @@ -91,6 +91,10 @@ Currently offline. Please check your internet connection. A problem occurred trying to contact the Integrity Service. Please try again later. A problem occurred trying to contact the server. Please try again later. + Change backend URL + Save + Use default + Cancel Please wait while we proceed to identification From d162f9bff60e1c94fa5f97b3aba8caafa69629a2 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Wed, 28 Feb 2024 15:32:31 +0200 Subject: [PATCH 053/197] MS-157 Make disabled text input more obvious --- feature/login/src/main/res/layout/fragment_login_form.xml | 4 ++-- infra/resources/src/main/res/color/input_text_background.xml | 5 +++++ infra/resources/src/main/res/values/colors.xml | 1 + infra/resources/src/main/res/values/styles-widget.xml | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 infra/resources/src/main/res/color/input_text_background.xml diff --git a/feature/login/src/main/res/layout/fragment_login_form.xml b/feature/login/src/main/res/layout/fragment_login_form.xml index c862fecb54..f51e0f4cc1 100644 --- a/feature/login/src/main/res/layout/fragment_login_form.xml +++ b/feature/login/src/main/res/layout/fragment_login_form.xml @@ -28,13 +28,13 @@ diff --git a/infra/resources/src/main/res/color/input_text_background.xml b/infra/resources/src/main/res/color/input_text_background.xml new file mode 100644 index 0000000000..a8f23d4fa6 --- /dev/null +++ b/infra/resources/src/main/res/color/input_text_background.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/infra/resources/src/main/res/values/colors.xml b/infra/resources/src/main/res/values/colors.xml index d705426743..5d73616f72 100644 --- a/infra/resources/src/main/res/values/colors.xml +++ b/infra/resources/src/main/res/values/colors.xml @@ -18,6 +18,7 @@ #FFEEEEEE #FF888888 #FFC0C0C0 + #22000000 #000000 #A9B5C2 #384553 diff --git a/infra/resources/src/main/res/values/styles-widget.xml b/infra/resources/src/main/res/values/styles-widget.xml index c2d46bb6f0..727305ee9e 100644 --- a/infra/resources/src/main/res/values/styles-widget.xml +++ b/infra/resources/src/main/res/values/styles-widget.xml @@ -110,6 +110,7 @@ @color/simprints_text_grey_light @color/simprints_text_grey_light @color/simprints_text_black + @color/input_text_background