Skip to content

Commit

Permalink
Merge pull request #1830 from Adyen/feature/analytics_error_events_base
Browse files Browse the repository at this point in the history
Analytics - Add support for error events
  • Loading branch information
araratthehero authored Oct 31, 2024
2 parents abda40d + 8f82398 commit ec1db9b
Show file tree
Hide file tree
Showing 16 changed files with 374 additions and 91 deletions.
95 changes: 8 additions & 87 deletions components-core/api/components-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -1667,93 +1667,6 @@ public abstract interface class com/adyen/checkout/components/core/internal/Paym
public abstract fun isAvailable (Landroid/app/Application;Lcom/adyen/checkout/components/core/PaymentMethod;Lcom/adyen/checkout/components/core/internal/Configuration;Lcom/adyen/checkout/components/core/ComponentAvailableCallback;)V
}

public final class com/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Info : com/adyen/checkout/components/core/internal/analytics/AnalyticsEvent {
public fun <init> (Ljava/lang/String;JZLjava/lang/String;Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Info$Type;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V
public synthetic fun <init> (Ljava/lang/String;JZLjava/lang/String;Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Info$Type;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component10 ()Ljava/lang/String;
public final fun component11 ()Ljava/lang/String;
public final fun component12 ()Ljava/util/Map;
public final fun component2 ()J
public final fun component3 ()Z
public final fun component4 ()Ljava/lang/String;
public final fun component5 ()Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Info$Type;
public final fun component6 ()Ljava/lang/String;
public final fun component7 ()Ljava/lang/Boolean;
public final fun component8 ()Ljava/lang/String;
public final fun component9 ()Ljava/lang/String;
public final fun copy (Ljava/lang/String;JZLjava/lang/String;Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Info$Type;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Info;
public static synthetic fun copy$default (Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Info;Ljava/lang/String;JZLjava/lang/String;Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Info$Type;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Info;
public fun equals (Ljava/lang/Object;)Z
public final fun getBrand ()Ljava/lang/String;
public fun getComponent ()Ljava/lang/String;
public final fun getConfigData ()Ljava/util/Map;
public fun getId ()Ljava/lang/String;
public final fun getIssuer ()Ljava/lang/String;
public fun getShouldForceSend ()Z
public final fun getTarget ()Ljava/lang/String;
public fun getTimestamp ()J
public final fun getType ()Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Info$Type;
public final fun getValidationErrorCode ()Ljava/lang/String;
public final fun getValidationErrorMessage ()Ljava/lang/String;
public fun hashCode ()I
public final fun isStoredPaymentMethod ()Ljava/lang/Boolean;
public fun toString ()Ljava/lang/String;
}

public final class com/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Info$Type : java/lang/Enum {
public static final field DISPLAYED Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Info$Type;
public static final field DOWNLOAD Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Info$Type;
public static final field FOCUS Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Info$Type;
public static final field INPUT Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Info$Type;
public static final field RENDERED Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Info$Type;
public static final field SELECTED Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Info$Type;
public static final field UNFOCUS Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Info$Type;
public static final field VALIDATION_ERROR Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Info$Type;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public final fun getValue ()Ljava/lang/String;
public static fun valueOf (Ljava/lang/String;)Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Info$Type;
public static fun values ()[Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Info$Type;
}

public final class com/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Log : com/adyen/checkout/components/core/internal/analytics/AnalyticsEvent {
public fun <init> (Ljava/lang/String;JZLjava/lang/String;Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Log$Type;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;JZLjava/lang/String;Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Log$Type;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()J
public final fun component3 ()Z
public final fun component4 ()Ljava/lang/String;
public final fun component5 ()Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Log$Type;
public final fun component6 ()Ljava/lang/String;
public final fun component7 ()Ljava/lang/String;
public final fun component8 ()Ljava/lang/String;
public final fun component9 ()Ljava/lang/String;
public final fun copy (Ljava/lang/String;JZLjava/lang/String;Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Log$Type;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Log;
public static synthetic fun copy$default (Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Log;Ljava/lang/String;JZLjava/lang/String;Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Log$Type;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Log;
public fun equals (Ljava/lang/Object;)Z
public fun getComponent ()Ljava/lang/String;
public fun getId ()Ljava/lang/String;
public final fun getMessage ()Ljava/lang/String;
public final fun getResult ()Ljava/lang/String;
public fun getShouldForceSend ()Z
public final fun getSubType ()Ljava/lang/String;
public final fun getTarget ()Ljava/lang/String;
public fun getTimestamp ()J
public final fun getType ()Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Log$Type;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class com/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Log$Type : java/lang/Enum {
public static final field ACTION Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Log$Type;
public static final field SUBMIT Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Log$Type;
public static final field THREEDS2 Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Log$Type;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public final fun getValue ()Ljava/lang/String;
public static fun valueOf (Ljava/lang/String;)Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Log$Type;
public static fun values ()[Lcom/adyen/checkout/components/core/internal/analytics/AnalyticsEvent$Log$Type;
}

public final class com/adyen/checkout/components/core/internal/analytics/AnalyticsManagerFactory$Companion {
}

Expand Down Expand Up @@ -1801,6 +1714,14 @@ public final class com/adyen/checkout/components/core/internal/data/model/Analyt
public synthetic fun newArray (I)[Ljava/lang/Object;
}

public final class com/adyen/checkout/components/core/internal/data/model/AnalyticsTrackError$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/adyen/checkout/components/core/internal/data/model/AnalyticsTrackError;
public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
public final fun newArray (I)[Lcom/adyen/checkout/components/core/internal/data/model/AnalyticsTrackError;
public synthetic fun newArray (I)[Ljava/lang/Object;
}

public final class com/adyen/checkout/components/core/internal/data/model/AnalyticsTrackInfo$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/adyen/checkout/components/core/internal/data/model/AnalyticsTrackInfo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ sealed interface AnalyticsEvent {
val shouldForceSend: Boolean
val component: String

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
data class Info @DirectAnalyticsEventCreation constructor(
override val id: String = UUID.randomUUID().toString(),
override val timestamp: Long = Date().time,
Expand All @@ -34,6 +35,8 @@ sealed interface AnalyticsEvent {
val validationErrorMessage: String? = null,
val configData: Map<String, String>? = null,
) : AnalyticsEvent {

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
enum class Type(val value: String) {
DISPLAYED("displayed"),
DOWNLOAD("download"),
Expand All @@ -46,6 +49,7 @@ sealed interface AnalyticsEvent {
}
}

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
data class Log @DirectAnalyticsEventCreation constructor(
override val id: String = UUID.randomUUID().toString(),
override val timestamp: Long = Date().time,
Expand All @@ -57,10 +61,34 @@ sealed interface AnalyticsEvent {
val target: String? = null,
val message: String? = null,
) : AnalyticsEvent {

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
enum class Type(val value: String) {
ACTION("action"),
SUBMIT("submit"),
THREEDS2("ThreeDS2"),
}
}

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
data class Error @DirectAnalyticsEventCreation constructor(
override val id: String = UUID.randomUUID().toString(),
override val timestamp: Long = Date().time,
override val shouldForceSend: Boolean = true,
override val component: String,
val errorType: Type? = null,
val code: String? = null,
val target: String? = null,
val message: String? = null,
) : AnalyticsEvent {

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
enum class Type(val value: String) {
REDIRECT("Redirect"),
INTERNAL("Internal"),
THIRD_PARTY("ThirdParty"),
API_ERROR("ApiError"),
THREEDS2("ThreeDS2"),
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import android.app.Application
import androidx.annotation.RestrictTo
import com.adyen.checkout.components.core.Amount
import com.adyen.checkout.components.core.internal.analytics.data.DefaultAnalyticsRepository
import com.adyen.checkout.components.core.internal.analytics.data.local.ErrorAnalyticsLocalDataStore
import com.adyen.checkout.components.core.internal.analytics.data.local.InfoAnalyticsLocalDataStore
import com.adyen.checkout.components.core.internal.analytics.data.local.LogAnalyticsLocalDataStore
import com.adyen.checkout.components.core.internal.analytics.data.remote.AnalyticsTrackRequestProvider
Expand Down Expand Up @@ -59,13 +60,15 @@ class AnalyticsManagerFactory {
analyticsRepository = DefaultAnalyticsRepository(
localInfoDataStore = InfoAnalyticsLocalDataStore(),
localLogDataStore = LogAnalyticsLocalDataStore(),
localErrorDataStore = ErrorAnalyticsLocalDataStore(),
remoteDataStore = DefaultAnalyticsRemoteDataStore(
analyticsService = AnalyticsService(
HttpClientFactory.getAnalyticsHttpClient(environment),
),
clientKey = clientKey,
infoSize = INFO_SIZE,
logSize = LOG_SIZE,
errorSize = ERROR_SIZE,
),
analyticsSetupProvider = DefaultAnalyticsSetupProvider(
application = application,
Expand All @@ -83,5 +86,6 @@ class AnalyticsManagerFactory {
companion object {
private const val INFO_SIZE = 50
private const val LOG_SIZE = 5
private const val ERROR_SIZE = 5
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2024 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by ararat on 14/10/2024.
*/

package com.adyen.checkout.components.core.internal.analytics

import androidx.annotation.RestrictTo
import com.adyen.checkout.components.core.internal.analytics.AnalyticsEvent.Error.Type

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
enum class ErrorEvent(val errorType: Type, val errorCode: String) {

// Redirect
REDIRECT_FAILED(Type.REDIRECT, "600"),
REDIRECT_PARSE_FAILED(Type.REDIRECT, "601"),
REDIRECT_CANCELLED(Type.REDIRECT, "602"),

// Encryption
ENCRYPTION(Type.INTERNAL, "610"),

// Third party
THIRD_PARTY(Type.THIRD_PARTY, "611"),

// API
API_PAYMENTS(Type.API_ERROR, "620"),
API_PAYMENTS_DETAILS(Type.API_ERROR, "621"),
API_THREEDS2(Type.API_ERROR, "622"),
API_ORDER(Type.API_ERROR, "623"),
API_PUBLIC_KEY(Type.API_ERROR, "624"),
API_NATIVE_REDIRECT(Type.API_ERROR, "625"),

// 3DS2
THREEDS2_TOKEN_DECODING(Type.THREEDS2, "704"),
THREEDS2_FINGERPRINT_CREATION(Type.THREEDS2, "705"),
THREEDS2_TRANSACTION_CREATION(Type.THREEDS2, "706"),
THREEDS2_TRANSACTION_MISSING(Type.THREEDS2, "707"),
THREEDS2_FINGERPRINT_HANDLING(Type.THREEDS2, "708"),
THREEDS2_CHALLENGE_HANDLING(Type.THREEDS2, "709"),
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,15 @@ object GenericEvents {
subType = subType,
message = message,
)

// Error events
@Suppress("Unused") // Should be removed once error events are implemented
fun error(
component: String,
event: ErrorEvent,
) = AnalyticsEvent.Error(
component = component,
errorType = event.errorType,
code = event.errorCode,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.adyen.checkout.core.internal.util.adyenLog
internal class DefaultAnalyticsRepository(
private val localInfoDataStore: AnalyticsLocalDataStore<AnalyticsEvent.Info>,
private val localLogDataStore: AnalyticsLocalDataStore<AnalyticsEvent.Log>,
private val localErrorDataStore: AnalyticsLocalDataStore<AnalyticsEvent.Error>,
private val remoteDataStore: AnalyticsRemoteDataStore,
private val analyticsSetupProvider: AnalyticsSetupProvider,
private val analyticsTrackRequestProvider: AnalyticsTrackRequestProvider,
Expand All @@ -33,6 +34,7 @@ internal class DefaultAnalyticsRepository(
when (event) {
is AnalyticsEvent.Info -> localInfoDataStore.storeEvent(event)
is AnalyticsEvent.Log -> localLogDataStore.storeEvent(event)
is AnalyticsEvent.Error -> localErrorDataStore.storeEvent(event)
}
}

Expand All @@ -41,18 +43,30 @@ internal class DefaultAnalyticsRepository(
) {
val infoEvents = localInfoDataStore.fetchEvents(remoteDataStore.infoSize)
val logEvents = localLogDataStore.fetchEvents(remoteDataStore.logSize)
val errorEvents = localErrorDataStore.fetchEvents(remoteDataStore.errorSize)

if (infoEvents.isEmpty() && logEvents.isEmpty()) return
if (!hasEventsToTrack(infoEvents, logEvents, errorEvents)) return

val request = analyticsTrackRequestProvider(
infoList = infoEvents,
logList = logEvents,
errorList = errorEvents,
)
remoteDataStore.sendEvents(request, checkoutAttemptId)

localInfoDataStore.removeEvents(infoEvents)
localLogDataStore.removeEvents(logEvents)
localErrorDataStore.removeEvents(errorEvents)

adyenLog(AdyenLogLevel.DEBUG) { "Analytics events successfully sent" }
}

private fun hasEventsToTrack(vararg eventLists: List<AnalyticsEvent>): Boolean {
for (events in eventLists) {
if (events.isNotEmpty()) {
return true
}
}
return false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2024 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by ararat on 15/10/2024.
*/

package com.adyen.checkout.components.core.internal.analytics.data.local

import com.adyen.checkout.components.core.internal.analytics.AnalyticsEvent
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.util.LinkedList

internal class ErrorAnalyticsLocalDataStore : AnalyticsLocalDataStore<AnalyticsEvent.Error> {

private val list = LinkedList<AnalyticsEvent.Error>()

private val mutex = Mutex()

override suspend fun storeEvent(event: AnalyticsEvent.Error) {
mutex.withLock {
list.add(event)
}
}

override suspend fun fetchEvents(size: Int) = mutex.withLock {
list.takeLast(size)
}

override suspend fun removeEvents(events: List<AnalyticsEvent.Error>) {
mutex.withLock {
list.removeAll(events.toSet())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ internal interface AnalyticsRemoteDataStore {

val infoSize: Int
val logSize: Int
val errorSize: Int

suspend fun fetchCheckoutAttemptId(request: AnalyticsSetupRequest): AnalyticsSetupResponse

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package com.adyen.checkout.components.core.internal.analytics.data.remote

import com.adyen.checkout.components.core.internal.analytics.AnalyticsEvent
import com.adyen.checkout.components.core.internal.analytics.AnalyticsPlatformParams
import com.adyen.checkout.components.core.internal.data.model.AnalyticsTrackError
import com.adyen.checkout.components.core.internal.data.model.AnalyticsTrackInfo
import com.adyen.checkout.components.core.internal.data.model.AnalyticsTrackLog
import com.adyen.checkout.components.core.internal.data.model.AnalyticsTrackRequest
Expand All @@ -19,12 +20,14 @@ internal class AnalyticsTrackRequestProvider {
operator fun invoke(
infoList: List<AnalyticsEvent.Info>,
logList: List<AnalyticsEvent.Log>,
errorList: List<AnalyticsEvent.Error>,
): AnalyticsTrackRequest {
return AnalyticsTrackRequest(
channel = AnalyticsPlatformParams.channel,
platform = AnalyticsPlatformParams.platform,
info = infoList.map { event -> event.mapToTrackEvent() },
logs = logList.map { event -> event.mapToTrackEvent() },
errors = errorList.map { event -> event.mapToErrorEvent() },
)
}

Expand Down Expand Up @@ -52,4 +55,14 @@ internal class AnalyticsTrackRequestProvider {
message = message,
result = result,
)

private fun AnalyticsEvent.Error.mapToErrorEvent() = AnalyticsTrackError(
id = id,
timestamp = timestamp,
component = component,
errorType = errorType?.value,
code = code,
target = target,
message = message,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ internal class DefaultAnalyticsRemoteDataStore(
private val clientKey: String,
override val infoSize: Int,
override val logSize: Int,
override val errorSize: Int,
) : AnalyticsRemoteDataStore {

override suspend fun fetchCheckoutAttemptId(request: AnalyticsSetupRequest): AnalyticsSetupResponse {
Expand Down
Loading

0 comments on commit ec1db9b

Please sign in to comment.