From 136dabb82e25be8a13655ab6ca11bba83398876e Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 18 Sep 2024 18:10:10 +0300 Subject: [PATCH 1/2] [MS-791] Adding CreateErrorCallbackEventIfNeeded use case to ClientApiViewModel so that error callbacks can be saved after the error/alert screen is closed --- .../feature/clientapi/ClientApiViewModel.kt | 5 +- ...CreateErrorCallbackEventIfNeededUseCase.kt | 57 +++++++++++++++++++ .../clientapi/ClientApiViewModelTest.kt | 5 ++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/CreateErrorCallbackEventIfNeededUseCase.kt diff --git a/feature/client-api/src/main/java/com/simprints/feature/clientapi/ClientApiViewModel.kt b/feature/client-api/src/main/java/com/simprints/feature/clientapi/ClientApiViewModel.kt index c256754b69..9314d4142b 100644 --- a/feature/client-api/src/main/java/com/simprints/feature/clientapi/ClientApiViewModel.kt +++ b/feature/client-api/src/main/java/com/simprints/feature/clientapi/ClientApiViewModel.kt @@ -17,6 +17,7 @@ import com.simprints.feature.clientapi.usecases.CreateSessionIfRequiredUseCase import com.simprints.feature.clientapi.usecases.DeleteSessionEventsIfNeededUseCase import com.simprints.feature.clientapi.usecases.GetCurrentSessionIdUseCase import com.simprints.feature.clientapi.usecases.GetEnrolmentCreationEventForSubjectUseCase +import com.simprints.feature.clientapi.usecases.CreateErrorCallbackEventIfNeededUseCase import com.simprints.feature.clientapi.usecases.IsFlowCompletedWithErrorUseCase import com.simprints.feature.clientapi.usecases.SimpleEventReporter import com.simprints.infra.authstore.AuthStore @@ -45,6 +46,7 @@ class ClientApiViewModel @Inject internal constructor( private val getEnrolmentCreationEventForSubject: GetEnrolmentCreationEventForSubjectUseCase, private val deleteSessionEventsIfNeeded: DeleteSessionEventsIfNeededUseCase, private val isFlowCompletedWithError: IsFlowCompletedWithErrorUseCase, + private val createErrorCallbackEventIfNeeded: CreateErrorCallbackEventIfNeededUseCase, private val authStore: AuthStore, private val configManager: ConfigManager, ) : ViewModel() { @@ -203,7 +205,8 @@ class ClientApiViewModel @Inject internal constructor( val flowCompleted = isFlowCompletedWithError(errorResponse) simpleEventReporter.addCompletionCheckEvent(flowCompleted = flowCompleted) simpleEventReporter.closeCurrentSessionNormally() - + // [MS-719] Some error responses need to explicitly send the callback event + createErrorCallbackEventIfNeeded(errorReason = errorResponse.reason) deleteSessionEventsIfNeeded(currentSessionId) _returnResponse.send( diff --git a/feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/CreateErrorCallbackEventIfNeededUseCase.kt b/feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/CreateErrorCallbackEventIfNeededUseCase.kt new file mode 100644 index 0000000000..1ff52fdbfd --- /dev/null +++ b/feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/CreateErrorCallbackEventIfNeededUseCase.kt @@ -0,0 +1,57 @@ +package com.simprints.feature.clientapi.usecases + +import com.simprints.core.ExternalScope +import com.simprints.core.domain.response.AppErrorReason +import com.simprints.core.tools.time.TimeHelper +import com.simprints.infra.events.SessionEventRepository +import com.simprints.infra.events.event.domain.models.callback.ErrorCallbackEvent +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import javax.inject.Inject + +/** + * [MS-719] Some errors (i.e.: LICENSE_MISSING, LICENSE_INVALID) are bypassing the orchestrator view + * model which is responsible for creating the callback events. Alert screens are currently + * not calling the orchestrator for error handling, and this use case is responsible for creating + * the necessary error callbacks events from the Alert Screen + */ +internal class CreateErrorCallbackEventIfNeededUseCase @Inject constructor( + private val eventRepository: SessionEventRepository, + private val timeHelper: TimeHelper, + @ExternalScope private val externalScope: CoroutineScope, +) { + operator fun invoke(errorReason: AppErrorReason) { + val event: ErrorCallbackEvent? = when (errorReason) { + AppErrorReason.LICENSE_MISSING, + AppErrorReason.LICENSE_INVALID -> buildErrorCallbackEvent(errorReason) + + AppErrorReason.DIFFERENT_PROJECT_ID_SIGNED_IN, + AppErrorReason.DIFFERENT_USER_ID_SIGNED_IN, + AppErrorReason.GUID_NOT_FOUND_ONLINE, + AppErrorReason.GUID_NOT_FOUND_OFFLINE, + AppErrorReason.BLUETOOTH_NOT_SUPPORTED, + AppErrorReason.LOGIN_NOT_COMPLETE, + AppErrorReason.UNEXPECTED_ERROR, + AppErrorReason.ROOTED_DEVICE, + AppErrorReason.ENROLMENT_LAST_BIOMETRICS_FAILED, + AppErrorReason.FINGERPRINT_CONFIGURATION_ERROR, + AppErrorReason.FACE_CONFIGURATION_ERROR, + AppErrorReason.BACKEND_MAINTENANCE_ERROR, + AppErrorReason.PROJECT_PAUSED, + AppErrorReason.BLUETOOTH_NO_PERMISSION, + AppErrorReason.PROJECT_ENDING, + AppErrorReason.AGE_GROUP_NOT_SUPPORTED -> null + } + + event?.let { + externalScope.launch { eventRepository.addOrUpdateEvent(event) } + } + } + + private fun buildErrorCallbackEvent(reason: AppErrorReason) = ErrorCallbackEvent( + createdAt = timeHelper.now(), + reason = ErrorCallbackEvent.ErrorCallbackPayload.Reason.fromAppResponseErrorReasonToEventReason( + reason + ), + ) +} diff --git a/feature/client-api/src/test/java/com/simprints/feature/clientapi/ClientApiViewModelTest.kt b/feature/client-api/src/test/java/com/simprints/feature/clientapi/ClientApiViewModelTest.kt index dcd45e1dff..c6cd9733c2 100644 --- a/feature/client-api/src/test/java/com/simprints/feature/clientapi/ClientApiViewModelTest.kt +++ b/feature/client-api/src/test/java/com/simprints/feature/clientapi/ClientApiViewModelTest.kt @@ -7,6 +7,7 @@ import com.jraska.livedata.test import com.simprints.feature.clientapi.exceptions.InvalidRequestException import com.simprints.feature.clientapi.mappers.request.IntentToActionMapper import com.simprints.feature.clientapi.mappers.response.ActionToIntentMapper +import com.simprints.feature.clientapi.usecases.CreateErrorCallbackEventIfNeededUseCase import com.simprints.feature.clientapi.usecases.CreateSessionIfRequiredUseCase import com.simprints.feature.clientapi.usecases.DeleteSessionEventsIfNeededUseCase import com.simprints.feature.clientapi.usecases.GetCurrentSessionIdUseCase @@ -65,6 +66,9 @@ internal class ClientApiViewModelTest { @MockK lateinit var isFlowCompletedWithError: IsFlowCompletedWithErrorUseCase + @MockK + lateinit var createCallbackEventIfNeeded: CreateErrorCallbackEventIfNeededUseCase + @MockK lateinit var authStore: AuthStore @@ -93,6 +97,7 @@ internal class ClientApiViewModelTest { getEnrolmentCreationEventForSubject = getEnrolmentCreationEventForSubject, deleteSessionEventsIfNeeded = deleteSessionEventsIfNeeded, isFlowCompletedWithError = isFlowCompletedWithError, + createErrorCallbackEventIfNeeded = createCallbackEventIfNeeded, authStore = authStore, configManager = configManager ) From 017351bbf72f8c73a961f5b9066a676f36ac6c33 Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 19 Sep 2024 14:07:45 +0300 Subject: [PATCH 2/2] [MS-791] All AppError results are now handled by the OrchestratorViewModel --- .../feature/clientapi/ClientApiViewModel.kt | 4 -- ...CreateErrorCallbackEventIfNeededUseCase.kt | 57 ------------------- .../clientapi/ClientApiViewModelTest.kt | 5 -- .../orchestrator/OrchestratorFragment.kt | 6 +- .../orchestrator/OrchestratorViewModel.kt | 6 +- 5 files changed, 5 insertions(+), 73 deletions(-) delete mode 100644 feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/CreateErrorCallbackEventIfNeededUseCase.kt diff --git a/feature/client-api/src/main/java/com/simprints/feature/clientapi/ClientApiViewModel.kt b/feature/client-api/src/main/java/com/simprints/feature/clientapi/ClientApiViewModel.kt index 9314d4142b..a9359767de 100644 --- a/feature/client-api/src/main/java/com/simprints/feature/clientapi/ClientApiViewModel.kt +++ b/feature/client-api/src/main/java/com/simprints/feature/clientapi/ClientApiViewModel.kt @@ -17,7 +17,6 @@ import com.simprints.feature.clientapi.usecases.CreateSessionIfRequiredUseCase import com.simprints.feature.clientapi.usecases.DeleteSessionEventsIfNeededUseCase import com.simprints.feature.clientapi.usecases.GetCurrentSessionIdUseCase import com.simprints.feature.clientapi.usecases.GetEnrolmentCreationEventForSubjectUseCase -import com.simprints.feature.clientapi.usecases.CreateErrorCallbackEventIfNeededUseCase import com.simprints.feature.clientapi.usecases.IsFlowCompletedWithErrorUseCase import com.simprints.feature.clientapi.usecases.SimpleEventReporter import com.simprints.infra.authstore.AuthStore @@ -46,7 +45,6 @@ class ClientApiViewModel @Inject internal constructor( private val getEnrolmentCreationEventForSubject: GetEnrolmentCreationEventForSubjectUseCase, private val deleteSessionEventsIfNeeded: DeleteSessionEventsIfNeededUseCase, private val isFlowCompletedWithError: IsFlowCompletedWithErrorUseCase, - private val createErrorCallbackEventIfNeeded: CreateErrorCallbackEventIfNeededUseCase, private val authStore: AuthStore, private val configManager: ConfigManager, ) : ViewModel() { @@ -205,8 +203,6 @@ class ClientApiViewModel @Inject internal constructor( val flowCompleted = isFlowCompletedWithError(errorResponse) simpleEventReporter.addCompletionCheckEvent(flowCompleted = flowCompleted) simpleEventReporter.closeCurrentSessionNormally() - // [MS-719] Some error responses need to explicitly send the callback event - createErrorCallbackEventIfNeeded(errorReason = errorResponse.reason) deleteSessionEventsIfNeeded(currentSessionId) _returnResponse.send( diff --git a/feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/CreateErrorCallbackEventIfNeededUseCase.kt b/feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/CreateErrorCallbackEventIfNeededUseCase.kt deleted file mode 100644 index 1ff52fdbfd..0000000000 --- a/feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/CreateErrorCallbackEventIfNeededUseCase.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.simprints.feature.clientapi.usecases - -import com.simprints.core.ExternalScope -import com.simprints.core.domain.response.AppErrorReason -import com.simprints.core.tools.time.TimeHelper -import com.simprints.infra.events.SessionEventRepository -import com.simprints.infra.events.event.domain.models.callback.ErrorCallbackEvent -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import javax.inject.Inject - -/** - * [MS-719] Some errors (i.e.: LICENSE_MISSING, LICENSE_INVALID) are bypassing the orchestrator view - * model which is responsible for creating the callback events. Alert screens are currently - * not calling the orchestrator for error handling, and this use case is responsible for creating - * the necessary error callbacks events from the Alert Screen - */ -internal class CreateErrorCallbackEventIfNeededUseCase @Inject constructor( - private val eventRepository: SessionEventRepository, - private val timeHelper: TimeHelper, - @ExternalScope private val externalScope: CoroutineScope, -) { - operator fun invoke(errorReason: AppErrorReason) { - val event: ErrorCallbackEvent? = when (errorReason) { - AppErrorReason.LICENSE_MISSING, - AppErrorReason.LICENSE_INVALID -> buildErrorCallbackEvent(errorReason) - - AppErrorReason.DIFFERENT_PROJECT_ID_SIGNED_IN, - AppErrorReason.DIFFERENT_USER_ID_SIGNED_IN, - AppErrorReason.GUID_NOT_FOUND_ONLINE, - AppErrorReason.GUID_NOT_FOUND_OFFLINE, - AppErrorReason.BLUETOOTH_NOT_SUPPORTED, - AppErrorReason.LOGIN_NOT_COMPLETE, - AppErrorReason.UNEXPECTED_ERROR, - AppErrorReason.ROOTED_DEVICE, - AppErrorReason.ENROLMENT_LAST_BIOMETRICS_FAILED, - AppErrorReason.FINGERPRINT_CONFIGURATION_ERROR, - AppErrorReason.FACE_CONFIGURATION_ERROR, - AppErrorReason.BACKEND_MAINTENANCE_ERROR, - AppErrorReason.PROJECT_PAUSED, - AppErrorReason.BLUETOOTH_NO_PERMISSION, - AppErrorReason.PROJECT_ENDING, - AppErrorReason.AGE_GROUP_NOT_SUPPORTED -> null - } - - event?.let { - externalScope.launch { eventRepository.addOrUpdateEvent(event) } - } - } - - private fun buildErrorCallbackEvent(reason: AppErrorReason) = ErrorCallbackEvent( - createdAt = timeHelper.now(), - reason = ErrorCallbackEvent.ErrorCallbackPayload.Reason.fromAppResponseErrorReasonToEventReason( - reason - ), - ) -} diff --git a/feature/client-api/src/test/java/com/simprints/feature/clientapi/ClientApiViewModelTest.kt b/feature/client-api/src/test/java/com/simprints/feature/clientapi/ClientApiViewModelTest.kt index c6cd9733c2..dcd45e1dff 100644 --- a/feature/client-api/src/test/java/com/simprints/feature/clientapi/ClientApiViewModelTest.kt +++ b/feature/client-api/src/test/java/com/simprints/feature/clientapi/ClientApiViewModelTest.kt @@ -7,7 +7,6 @@ import com.jraska.livedata.test import com.simprints.feature.clientapi.exceptions.InvalidRequestException import com.simprints.feature.clientapi.mappers.request.IntentToActionMapper import com.simprints.feature.clientapi.mappers.response.ActionToIntentMapper -import com.simprints.feature.clientapi.usecases.CreateErrorCallbackEventIfNeededUseCase import com.simprints.feature.clientapi.usecases.CreateSessionIfRequiredUseCase import com.simprints.feature.clientapi.usecases.DeleteSessionEventsIfNeededUseCase import com.simprints.feature.clientapi.usecases.GetCurrentSessionIdUseCase @@ -66,9 +65,6 @@ internal class ClientApiViewModelTest { @MockK lateinit var isFlowCompletedWithError: IsFlowCompletedWithErrorUseCase - @MockK - lateinit var createCallbackEventIfNeeded: CreateErrorCallbackEventIfNeededUseCase - @MockK lateinit var authStore: AuthStore @@ -97,7 +93,6 @@ internal class ClientApiViewModelTest { getEnrolmentCreationEventForSubject = getEnrolmentCreationEventForSubject, deleteSessionEventsIfNeeded = deleteSessionEventsIfNeeded, isFlowCompletedWithError = isFlowCompletedWithError, - createErrorCallbackEventIfNeeded = createCallbackEventIfNeeded, authStore = authStore, configManager = configManager ) diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorFragment.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorFragment.kt index bf02fd574e..3a827ad6c8 100644 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorFragment.kt +++ b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorFragment.kt @@ -95,8 +95,7 @@ internal class OrchestratorFragment : Fragment(R.layout.fragment_orchestrator) { observeOrchestratorVm() handleResult(AlertContract.DESTINATION) { alertResult -> - clientApiVm.handleErrorResponse( - args.requestAction, + orchestratorVm.handleErrorResponse( AppErrorResponse(alertResult.appErrorReason ?: AppErrorReason.UNEXPECTED_ERROR) ) } @@ -151,8 +150,7 @@ internal class OrchestratorFragment : Fragment(R.layout.fragment_orchestrator) { }) loginCheckVm.returnLoginNotComplete.observe(viewLifecycleOwner, LiveDataEventObserver { - clientApiVm.handleErrorResponse( - args.requestAction, + orchestratorVm.handleErrorResponse( AppErrorResponse(AppErrorReason.LOGIN_NOT_COMPLETE) ) }) 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 52026c8be1..8a49ea7c77 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 @@ -80,7 +80,7 @@ internal class OrchestratorViewModel @Inject constructor( try { steps = stepsBuilder.build(action, projectConfiguration) } catch (e: SubjectAgeNotSupportedException) { - sendErrorResponse(AppErrorResponse(AppErrorReason.AGE_GROUP_NOT_SUPPORTED)) + handleErrorResponse(AppErrorResponse(AppErrorReason.AGE_GROUP_NOT_SUPPORTED)) return@launch } @@ -94,7 +94,7 @@ internal class OrchestratorViewModel @Inject constructor( val errorResponse = mapRefusalOrErrorResult(result, projectConfiguration) if (errorResponse != null) { // Shortcut the flow execution if any refusal or error result is found - sendErrorResponse(errorResponse) + handleErrorResponse(errorResponse) return@launch } @@ -121,7 +121,7 @@ internal class OrchestratorViewModel @Inject constructor( doNextStep() } - private fun sendErrorResponse(errorResponse: AppResponse) { + fun handleErrorResponse(errorResponse: AppResponse) { addCallbackEvent(errorResponse) _appResponse.send(OrchestratorResult(actionRequest, errorResponse)) }