Skip to content

Commit

Permalink
[Identity] Send generic error analytics (#5246)
Browse files Browse the repository at this point in the history
  • Loading branch information
ccen-stripe committed Jul 6, 2022
1 parent 55a9da7 commit 238b362
Show file tree
Hide file tree
Showing 23 changed files with 180 additions and 117 deletions.
Expand Up @@ -269,16 +269,16 @@ internal class IdentityActivity :
)
}
}
// Display cross icon on error fragment with failed reason, clicking it finishes the flow with Failed
isErrorFragmentWithFailedReason(destination, args) -> {
// Display cross icon on error fragment that should fail, clicking it finishes the flow with Failed
isErrorFragmentThatShouldFail(destination, args) -> {
this.navigationIcon = AppCompatResources.getDrawable(
this@IdentityActivity,
R.drawable.ic_baseline_close_24
)
this.setNavigationOnClickListener {
val failedReason = requireNotNull(
args?.getSerializable(
ErrorFragment.ARG_FAILED_REASON
ErrorFragment.ARG_CAUSE
) as? Throwable
) {
"Failed to get failedReason from $args"
Expand Down Expand Up @@ -341,11 +341,11 @@ internal class IdentityActivity :
VerificationFlowResult.Canceled
)
}
// On error fragment with failed reason, clicking back finishes the flow with Failed
isErrorFragmentWithFailedReason(destination, args) -> {
// On error fragment that should fail, clicking back finishes the flow with Failed
isErrorFragmentThatShouldFail(destination, args) -> {
val failedReason = requireNotNull(
args?.getSerializable(
ErrorFragment.ARG_FAILED_REASON
ErrorFragment.ARG_CAUSE
) as? Throwable
) {
"Failed to get failedReason from $args"
Expand Down Expand Up @@ -388,10 +388,14 @@ internal class IdentityActivity :
private fun isConsentFragment(destination: NavDestination?) =
destination?.id == R.id.consentFragment

private fun isErrorFragmentWithFailedReason(
/**
* Check if this is the final error fragment, which would fail the verification flow when
* back button is clicked.
*/
private fun isErrorFragmentThatShouldFail(
destination: NavDestination?,
args: Bundle?
) = destination?.id == R.id.errorFragment &&
args?.containsKey(ErrorFragment.ARG_FAILED_REASON) == true
args?.getBoolean(ErrorFragment.ARG_SHOULD_FAIL, false) == true
}
}
Expand Up @@ -198,6 +198,17 @@ internal class IdentityAnalyticsRequestFactory @Inject constructor(
)
)

fun genericError(
message: String?,
stackTrace: String
) = requestFactory.createRequest(
eventName = EVENT_GENERIC_ERROR,
additionalParams = additionalParamWithEventMetadata(
PARAM_MESSAGE to message,
PARAM_STACKTRACE to stackTrace
)
)

fun imageUpload(
value: Long,
compressionQuality: Float,
Expand Down Expand Up @@ -264,6 +275,7 @@ internal class IdentityAnalyticsRequestFactory @Inject constructor(
const val EVENT_MODEL_PERFORMANCE = "model_performance"
const val EVENT_TIME_TO_SCREEN = "time_to_screen"
const val EVENT_IMAGE_UPLOAD = "image_upload"
const val EVENT_GENERIC_ERROR = "generic_error"

const val PARAM_EVENT_META_DATA = "event_metadata"
const val PARAM_FROM_FALLBACK_URL = "from_fallback_url"
Expand Down Expand Up @@ -294,6 +306,7 @@ internal class IdentityAnalyticsRequestFactory @Inject constructor(
const val PARAM_NETWORK_TIME = "network_time"
const val PARAM_FROM_SCREEN_NAME = "from_screen_name"
const val PARAM_TO_SCREEN_NAME = "to_screen_name"
const val PARAM_MESSAGE = "message"
const val PARAM_COMPRESSION_QUALITY = "compression_quality"
const val PARAM_ID = "id"
const val PARAM_FILE_NAME = "file_name"
Expand Down
Expand Up @@ -79,7 +79,7 @@ internal class ConfirmationFragment(
},
onFailure = {
Log.e(TAG, "Failed to get VerificationPage")
navigateToDefaultErrorFragment()
navigateToDefaultErrorFragment(it)
}
)

Expand Down
Expand Up @@ -81,7 +81,7 @@ internal class DocSelectionFragment(
}
},
onFailure = {
navigateToDefaultErrorFragment()
navigateToDefaultErrorFragment(it)
}
)
lifecycleScope.launch(identityViewModel.workContext) {
Expand Down Expand Up @@ -150,8 +150,10 @@ internal class DocSelectionFragment(
}
} ?: run {
// Not possible for backend to send an empty list of allowed types.
Log.e(TAG, "Received an empty idDocumentTypeAllowlist.")
navigateToDefaultErrorFragment()
"Received an empty idDocumentTypeAllowlist.".let { msg ->
Log.e(TAG, msg)
navigateToDefaultErrorFragment(msg)
}
}
}

Expand Down Expand Up @@ -252,8 +254,10 @@ internal class DocSelectionFragment(
viewLifecycleOwner,
onSuccess = { verificationPage ->
if (verificationPage.documentCapture.requireLiveCapture) {
Log.e(TAG, "Can't access camera and client has required live capture.")
navigateToDefaultErrorFragment()
"Can't access camera and client has required live capture.".let { msg ->
Log.e(TAG, msg)
navigateToDefaultErrorFragment(msg)
}
} else {
navigateToUploadFragment(
type.toUploadDestinationId(),
Expand All @@ -263,7 +267,7 @@ internal class DocSelectionFragment(
}
},
onFailure = {
navigateToDefaultErrorFragment()
navigateToDefaultErrorFragment(it)
}
)
}
Expand All @@ -289,7 +293,7 @@ internal class DocSelectionFragment(
},
onFailure = {
Log.e(TAG, "failed to observeForVerificationPage: $it")
navigateToDefaultErrorFragment()
navigateToDefaultErrorFragment(it)
}
)
}
Expand Down
Expand Up @@ -31,39 +31,44 @@ internal class ErrorFragment(

topButton.visibility = View.GONE

if (args.getInt(ARG_GO_BACK_BUTTON_DESTINATION) == UNSET_DESTINATION &&
!args.containsKey(ARG_FAILED_REASON)
) {
bottomButton.visibility = View.GONE
} else {
bottomButton.text = args[ARG_GO_BACK_BUTTON_TEXT] as String
bottomButton.visibility = View.VISIBLE
val cause = requireNotNull(args.getSerializable(ARG_CAUSE) as? Throwable) {
"cause of error is null"
}

identityViewModel.sendAnalyticsRequest(
identityViewModel.identityAnalyticsRequestFactory.genericError(
message = cause.message,
stackTrace = cause.stackTraceToString()
)
)

bottomButton.text = args[ARG_GO_BACK_BUTTON_TEXT] as String
bottomButton.visibility = View.VISIBLE

// If this is final destination, clicking bottom button and pressBack would end flow
(args.getSerializable(ARG_FAILED_REASON) as? Throwable)?.let { failedReason ->
// If ARG_SHOULD_FAIL is true, clicking bottom button and pressBack would end flow with Failed
if (args.getBoolean(ARG_SHOULD_FAIL, false)) {
identityViewModel.screenTracker.screenTransitionStart(
SCREEN_NAME_ERROR
)
bottomButton.setOnClickListener {
verificationFlowFinishable.finishWithResult(
Failed(cause)
)
}
} else {
bottomButton.setOnClickListener {
identityViewModel.screenTracker.screenTransitionStart(
SCREEN_NAME_ERROR
)
bottomButton.setOnClickListener {
verificationFlowFinishable.finishWithResult(
Failed(failedReason)
)
}
} ?: run {
bottomButton.setOnClickListener {
identityViewModel.screenTracker.screenTransitionStart(
SCREEN_NAME_ERROR
)
val destination = args[ARG_GO_BACK_BUTTON_DESTINATION] as Int
if (destination == UNEXPECTED_DESTINATION) {
findNavController().navigate(DEFAULT_BACK_BUTTON_NAVIGATION)
} else {
findNavController().let { navController ->
var shouldContinueNavigateUp = true
while (shouldContinueNavigateUp && navController.currentDestination?.id != destination) {
shouldContinueNavigateUp =
navController.navigateUpAndSetArgForUploadFragment()
}
val destination = args[ARG_GO_BACK_BUTTON_DESTINATION] as Int
if (destination == UNEXPECTED_DESTINATION) {
findNavController().navigate(DEFAULT_BACK_BUTTON_NAVIGATION)
} else {
findNavController().let { navController ->
var shouldContinueNavigateUp = true
while (shouldContinueNavigateUp && navController.currentDestination?.id != destination) {
shouldContinueNavigateUp =
navController.navigateUpAndSetArgForUploadFragment()
}
}
}
Expand All @@ -78,8 +83,10 @@ internal class ErrorFragment(
// if set, shows go_back button, clicking it would navigate to the destination.
const val ARG_GO_BACK_BUTTON_TEXT = "goBackButtonText"
const val ARG_GO_BACK_BUTTON_DESTINATION = "goBackButtonDestination"
const val ARG_FAILED_REASON = "failedReason"
private const val UNSET_DESTINATION = 0

// if set to true, clicking bottom button and pressBack would end flow with Failed
const val ARG_SHOULD_FAIL = "shouldFail"
const val ARG_CAUSE = "cause"

// Indicates the server returns a requirementError that doesn't match with current Fragment.
// E.g ConsentFragment->DocSelectFragment could only have BIOMETRICCONSENT error but not IDDOCUMENTFRONT error.
Expand All @@ -104,21 +111,26 @@ internal class ErrorFragment(
} else {
UNEXPECTED_DESTINATION
},
ARG_GO_BACK_BUTTON_TEXT to requirementError.backButtonText
// TODO(ccen) build continue button after backend behavior is finalized
// ARG_CONTINUE_BUTTON_TEXT to requirementError.continueButtonText,
ARG_GO_BACK_BUTTON_TEXT to requirementError.backButtonText,
ARG_SHOULD_FAIL to false,
ARG_CAUSE to IllegalStateException("VerificationPageDataRequirementError: $requirementError")
)
)
}

fun NavController.navigateToErrorFragmentWithDefaultValues(context: Context) {
fun NavController.navigateToErrorFragmentWithDefaultValues(
context: Context,
cause: Throwable
) {
navigate(
R.id.action_global_errorFragment,
bundleOf(
ARG_ERROR_TITLE to context.getString(R.string.error),
ARG_ERROR_CONTENT to context.getString(R.string.unexpected_error_try_again),
ARG_GO_BACK_BUTTON_DESTINATION to R.id.consentFragment,
ARG_GO_BACK_BUTTON_TEXT to context.getString(R.string.go_back)
ARG_GO_BACK_BUTTON_TEXT to context.getString(R.string.go_back),
ARG_SHOULD_FAIL to false,
ARG_CAUSE to cause
)
)
}
Expand All @@ -138,7 +150,8 @@ internal class ErrorFragment(
ARG_ERROR_TITLE to context.getString(R.string.error),
ARG_ERROR_CONTENT to context.getString(R.string.unexpected_error_try_again),
ARG_GO_BACK_BUTTON_TEXT to context.getString(R.string.go_back),
ARG_FAILED_REASON to failedReason
ARG_SHOULD_FAIL to true,
ARG_CAUSE to failedReason
)
)
}
Expand Down
Expand Up @@ -139,7 +139,7 @@ internal abstract class IdentityCameraScanFragment(
},
onFailure = {
Log.e(TAG, "Fail to observeForVerificationPage: $it")
navigateToDefaultErrorFragment()
navigateToDefaultErrorFragment(it)
}
)
stopScanning()
Expand Down
Expand Up @@ -160,7 +160,7 @@ internal abstract class IdentityDocumentScanFragment(
when {
it.hasError() -> {
Log.e(TAG, "Fail to upload files: ${it.getError()}")
navigateToDefaultErrorFragment()
navigateToDefaultErrorFragment(it.getError())
}
it.isAnyLoading() -> {
continueButton.toggleToLoading()
Expand Down Expand Up @@ -207,22 +207,21 @@ internal abstract class IdentityDocumentScanFragment(
TAG,
"fail to submit uploaded files: $throwable"
)
navigateToDefaultErrorFragment()
navigateToDefaultErrorFragment(throwable)
}
}
},
onFailure = { throwable ->
Log.e(TAG, "Fail to observeForVerificationPage: $throwable")
navigateToDefaultErrorFragment()
navigateToDefaultErrorFragment(throwable)
}
)
}
else -> {
Log.e(
TAG,
"observeAndUploadForBothSides reaches unexpected upload state: $it"
)
navigateToDefaultErrorFragment()
"observeAndUploadForBothSides reaches unexpected upload state: $it".let { msg ->
Log.e(TAG, msg)
navigateToDefaultErrorFragment(msg)
}
}
}
}
Expand Down
Expand Up @@ -206,7 +206,7 @@ internal abstract class IdentityUploadFragment(
identityViewModel.documentUploadState.collectLatest { latestState ->
if (latestState.hasError()) {
Log.e(TAG, "Fail to upload files: ${latestState.getError()}")
navigateToDefaultErrorFragment()
navigateToDefaultErrorFragment(latestState.getError())
} else {
if (latestState.isFrontHighResUploaded()) {
showFrontDone(latestState)
Expand Down Expand Up @@ -315,7 +315,7 @@ internal abstract class IdentityUploadFragment(
onSuccess(it.documentCapture)
},
onFailure = {
navigateToDefaultErrorFragment()
navigateToDefaultErrorFragment(it)
}
)
}
Expand Down Expand Up @@ -394,7 +394,7 @@ internal abstract class IdentityUploadFragment(
)
}.onFailure {
Log.d(TAG, "fail to submit uploaded files: $it")
navigateToDefaultErrorFragment()
navigateToDefaultErrorFragment(it)
}
}
}
Expand Down Expand Up @@ -425,7 +425,7 @@ internal abstract class IdentityUploadFragment(
},
onFailure = {
Log.e(TAG, "Fail to observeForVerificationPage: $it")
navigateToDefaultErrorFragment()
navigateToDefaultErrorFragment(it)
}
)
}
Expand Down
Expand Up @@ -55,7 +55,7 @@ internal class PassportScanFragment(
identityViewModel.documentUploadState.collectLatest {
when {
it.hasError() -> {
navigateToDefaultErrorFragment()
navigateToDefaultErrorFragment(it.getError())
}
it.isFrontLoading() -> {
continueButton.toggleToLoading()
Expand Down Expand Up @@ -98,22 +98,21 @@ internal class PassportScanFragment(
TAG,
"fail to submit uploaded files: $throwable"
)
navigateToDefaultErrorFragment()
navigateToDefaultErrorFragment(throwable)
}
}
},
onFailure = { throwable ->
Log.e(TAG, "Fail to observeForVerificationPage: $throwable")
navigateToDefaultErrorFragment()
navigateToDefaultErrorFragment(throwable)
}
)
}
else -> {
Log.d(
TAG,
"observeAndUploadForFrontSide reaches unexpected upload state: $it"
)
navigateToDefaultErrorFragment()
"observeAndUploadForFrontSide reaches unexpected upload state: $it".let { msg ->
Log.d(TAG, msg)
navigateToDefaultErrorFragment(msg)
}
}
}
}
Expand Down

0 comments on commit 238b362

Please sign in to comment.