Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Analytics #1591

Merged
merged 107 commits into from
May 28, 2024
Merged

Analytics #1591

merged 107 commits into from
May 28, 2024

Conversation

araratthehero
Copy link
Contributor

@araratthehero araratthehero commented Apr 30, 2024

Description

Add Analytics

Checklist

  • PR is labelled
  • Code is unit tested
  • Changes are tested manually

COAND-846

@araratthehero araratthehero added the Feature [PRs only] Indicates a new feature addition label Apr 30, 2024
@OscarSpruit OscarSpruit added Chore [PRs only] Indicates any task that does not need to be mentioned in the public release notes and removed Feature [PRs only] Indicates a new feature addition labels May 16, 2024
@OscarSpruit OscarSpruit force-pushed the feature/analytics branch 3 times, most recently from 9397fd3 to 6c4799f Compare May 22, 2024 13:22
@Adyen Adyen deleted a comment from sonarcloud bot May 22, 2024
@OscarSpruit OscarSpruit force-pushed the feature/analytics branch 5 times, most recently from 6c4799f to f0c7301 Compare May 23, 2024 15:07
@OscarSpruit OscarSpruit marked this pull request as ready for review May 24, 2024 08:17
@OscarSpruit OscarSpruit requested a review from a team as a code owner May 24, 2024 08:17
@OscarSpruit OscarSpruit force-pushed the feature/analytics branch 3 times, most recently from b6d5936 to 35086e9 Compare May 28, 2024 09:18
OscarSpruit and others added 12 commits May 28, 2024 15:15
COAND-844

diff --git a/checkout-core/src/main/java/com/adyen/checkout/core/internal/analytics/AdyenAnalytics.kt b/checkout-core/src/main/java/com/adyen/checkout/core/internal/analytics/AdyenAnalytics.kt
deleted file mode 100644
index 4106710ea..000000000
--- a/checkout-core/src/main/java/com/adyen/checkout/core/internal/analytics/AdyenAnalytics.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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 oscars on 6/2/2024.
- */
-
-package com.adyen.checkout.core.internal.analytics
-
-import androidx.annotation.RestrictTo
-
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class AdyenAnalytics {
-
-    fun setup() {
-        // See DefaultAnalyticsRepository.setupAnalytics
-    }
-
-    fun track(event: AnalyticsEvent) {
-        // Queue the event
-        // Send it
-    }
-}
diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/AdyenAnalytics.kt b/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/AdyenAnalytics.kt
new file mode 100644
index 000000000..d012b5721
--- /dev/null
+++ b/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/AdyenAnalytics.kt
@@ -0,0 +1,92 @@
+/*
+ * 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 oscars on 12/2/2024.
+ */
+
+package com.adyen.checkout.components.core.internal.analytics
+
+import androidx.annotation.RestrictTo
+import androidx.annotation.VisibleForTesting
+import com.adyen.checkout.components.core.internal.data.api.AnalyticsService
+import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParams
+import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParamsLevel
+import com.adyen.checkout.core.AdyenLogLevel
+import com.adyen.checkout.core.internal.util.adyenLog
+import com.adyen.checkout.core.internal.util.runSuspendCatching
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.launch
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class AdyenAnalytics(
+    private val analyticsProvider: AnalyticsProvider,
+    private val analyticsParams: AnalyticsParams,
+    private val analyticsService: AnalyticsService,
+    coroutineDispatcher: CoroutineDispatcher = Dispatchers.Default,
+) {
+
+    // TODO: Check if Job or SupervisorJob is better for us
+    private val coroutineScope = CoroutineScope(coroutineDispatcher + SupervisorJob())
+
+    @volatile
+    var checkoutAttemptId: String? = null
+        private set
+
+    @volatile
+    private var state: State = State.Uninitialized
+
+    fun setup() {
+        if (cannotSendEvent()) {
+            checkoutAttemptId = CHECKOUT_ATTEMPT_ID_FOR_DISABLED_ANALYTICS
+            return
+        }
+
+        if (state != State.Uninitialized) return
+        state = State.InProgress
+        adyenLog(AdyenLogLevel.VERBOSE) { "Setting up analytics" }
+
+        coroutineScope.launch {
+            runSuspendCatching {
+                val analyticsSetupRequest = analyticsProvider.provide()
+                val response = analyticsService.setupAnalytics(analyticsSetupRequest, analyticsParams.clientKey)
+                checkoutAttemptId = response.checkoutAttemptId
+                state = State.Ready
+                adyenLog(AdyenLogLevel.VERBOSE) { "Analytics setup call successful" }
+            }.onFailure { e ->
+                state = State.Failed
+                adyenLog(AdyenLogLevel.ERROR) {
+                    "Failed to send analytics setup call - ${e::class.simpleName}: ${e.message}"
+                }
+            }
+        }
+    }
+
+    fun track(event: AnalyticsEvent) {
+        // TODO: Check if we can send events anyway, because attempt id is anonymous already
+        if (cannotSendEvent()) return
+
+        // Queue the event
+        // Send it
+    }
+
+    private fun cannotSendEvent(): Boolean {
+        return analyticsParams.level.priority <= AnalyticsParamsLevel.NONE.priority
+    }
+
+    companion object {
+        private const val CHECKOUT_ATTEMPT_ID_FOR_DISABLED_ANALYTICS = "do-not-track"
+    }
+
+    @VisibleForTesting
+    internal sealed class State {
+        data object Uninitialized : State()
+        data object InProgress : State()
+        data object Ready : State()
+        data object Failed : State()
+    }
+}
diff --git a/checkout-core/src/main/java/com/adyen/checkout/core/internal/analytics/AnalyticsEventApi.kt b/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/AnalyticsEventApi.kt
similarity index 78%
rename from checkout-core/src/main/java/com/adyen/checkout/core/internal/analytics/AnalyticsEventApi.kt
rename to components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/AnalyticsEventApi.kt
index 8a98d58f6..d3775c469 100644
--- a/checkout-core/src/main/java/com/adyen/checkout/core/internal/analytics/AnalyticsEventApi.kt
+++ b/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/AnalyticsEventApi.kt
@@ -3,10 +3,10 @@
  *
  * This file is open source and available under the MIT license. See the LICENSE file for more info.
  *
- * Created by oscars on 7/2/2024.
+ * Created by oscars on 12/2/2024.
  */

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

 import androidx.annotation.RestrictTo

diff --git a/checkout-core/src/main/java/com/adyen/checkout/core/internal/analytics/AnalyticsEvents.kt b/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/AnalyticsEvents.kt
similarity index 96%
rename from checkout-core/src/main/java/com/adyen/checkout/core/internal/analytics/AnalyticsEvents.kt
rename to components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/AnalyticsEvents.kt
index 28027debc..d90089e3e 100644
--- a/checkout-core/src/main/java/com/adyen/checkout/core/internal/analytics/AnalyticsEvents.kt
+++ b/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/AnalyticsEvents.kt
@@ -6,7 +6,7 @@
  * Created by oscars on 7/2/2024.
  */

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

 import androidx.annotation.RestrictTo
 import java.util.Date
diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/AnalyticsProvider.kt b/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/AnalyticsProvider.kt
index fd6f27593..70a792048 100644
--- a/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/AnalyticsProvider.kt
+++ b/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/AnalyticsProvider.kt
@@ -21,7 +21,6 @@ import com.adyen.checkout.components.core.internal.ui.model.ComponentParams
 class AnalyticsProvider(
     val application: Application,
     val componentParams: ComponentParams,
-    // drop-in or txVariant
     val source: AnalyticsSource,
     val sessionId: String?,
 ) {
diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/AnalyticsSource.kt b/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/AnalyticsSource.kt
index 4b4c58185..7938e93c7 100644
--- a/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/AnalyticsSource.kt
+++ b/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/AnalyticsSource.kt
@@ -12,11 +12,12 @@ import androidx.annotation.RestrictTo

 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 sealed class AnalyticsSource {
-    data class DropIn(val paymentMethods: List<String>) : AnalyticsSource()
+    data class DropIn(val paymentMethodList: List<String>) : AnalyticsSource()
     data class PaymentComponent(val paymentMethodType: String) : AnalyticsSource()

+    // TODO: Check if we can rename paymentMethodList and not make it clash with this function
     fun getPaymentMethods(): List<String> = when(this) {
-        is DropIn -> paymentMethods
+        is DropIn -> paymentMethodList
         is PaymentComponent -> listOf(paymentMethodType)
     }
 }
diff --git a/checkout-core/src/main/java/com/adyen/checkout/core/internal/analytics/GenericEvents.kt b/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/GenericEvents.kt
similarity index 80%
rename from checkout-core/src/main/java/com/adyen/checkout/core/internal/analytics/GenericEvents.kt
rename to components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/GenericEvents.kt
index f9e46dd02..b99a56ce0 100644
--- a/checkout-core/src/main/java/com/adyen/checkout/core/internal/analytics/GenericEvents.kt
+++ b/components-core/src/main/java/com/adyen/checkout/components/core/internal/analytics/GenericEvents.kt
@@ -3,10 +3,10 @@
  *
  * This file is open source and available under the MIT license. See the LICENSE file for more info.
  *
- * Created by oscars on 7/2/2024.
+ * Created by oscars on 12/2/2024.
  */

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

 import androidx.annotation.RestrictTo

@@ -22,7 +22,7 @@ object GenericEvents {
         brand: String? = null,
     ) = AnalyticsEvent.Info(
         component = component,
-        type = InfoEventType.RENDERED,
+        type = AnalyticsEvent.Info.Type.RENDERED,
         isStoredPaymentMethod = isStoredPaymentMethod,
         brand = brand,
     )
@@ -32,7 +32,7 @@ object GenericEvents {
         target: String,
     ) = AnalyticsEvent.Info(
         component = component,
-        type = InfoEventType.DISPLAYED,
+        type = AnalyticsEvent.Info.Type.DISPLAYED,
         target = target,
     )

@@ -42,7 +42,7 @@ object GenericEvents {
         issuer: String,
     ) = AnalyticsEvent.Info(
         component = component,
-        type = InfoEventType.SELECTED,
+        type = AnalyticsEvent.Info.Type.SELECTED,
         target = target,
         issuer = issuer,
     )
@@ -52,7 +52,7 @@ object GenericEvents {
         target: String,
     ) = AnalyticsEvent.Info(
         component = component,
-        type = InfoEventType.INPUT,
+        type = AnalyticsEvent.Info.Type.INPUT,
         target = target,
     )

@@ -63,7 +63,7 @@ object GenericEvents {
         target: String,
     ) = AnalyticsEvent.Info(
         component = component,
-        type = InfoEventType.FOCUS,
+        type = AnalyticsEvent.Info.Type.FOCUS,
         target = target,
     )

@@ -72,7 +72,7 @@ object GenericEvents {
         target: String,
     ) = AnalyticsEvent.Info(
         component = component,
-        type = InfoEventType.UNFOCUS,
+        type = AnalyticsEvent.Info.Type.UNFOCUS,
         target = target,
     )

@@ -81,7 +81,7 @@ object GenericEvents {
         target: String,
     ) = AnalyticsEvent.Info(
         component = component,
-        type = InfoEventType.DOWNLOAD,
+        type = AnalyticsEvent.Info.Type.DOWNLOAD,
         target = target,
     )

@@ -92,7 +92,7 @@ object GenericEvents {
         validationErrorMessage: String?,
     ) = AnalyticsEvent.Info(
         component = component,
-        type = InfoEventType.VALIDATION_ERROR,
+        type = AnalyticsEvent.Info.Type.VALIDATION_ERROR,
         target = target,
         validationErrorCode = validationErrorCode,
         validationErrorMessage = validationErrorMessage,
@@ -103,7 +103,7 @@ object GenericEvents {
         component: String,
     ) = AnalyticsEvent.Log(
         component = component,
-        type = LogEventType.SUBMIT,
+        type = AnalyticsEvent.Log.Type.SUBMIT,
     )

     fun threeDS2(
@@ -111,7 +111,7 @@ object GenericEvents {
         message: String,
     ) = AnalyticsEvent.Log(
         component = component,
-        type = LogEventType.THREEDS2,
+        type = AnalyticsEvent.Log.Type.THREEDS2,
         message = message,
     )

@@ -121,7 +121,7 @@ object GenericEvents {
         message: String,
     ) = AnalyticsEvent.Log(
         component = component,
-        type = LogEventType.ACTION,
+        type = AnalyticsEvent.Log.Type.ACTION,
         subType = subType,
         message = message,
     )
diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/internal/data/api/AnalyticsRepositoryData.kt b/components-core/src/main/java/com/adyen/checkout/components/core/internal/data/api/AnalyticsRepositoryData.kt
index 5d215f7ea..f5fea4d92 100644
--- a/components-core/src/main/java/com/adyen/checkout/components/core/internal/data/api/AnalyticsRepositoryData.kt
+++ b/components-core/src/main/java/com/adyen/checkout/components/core/internal/data/api/AnalyticsRepositoryData.kt
@@ -40,7 +40,7 @@ data class AnalyticsRepositoryData(
     ) : this(
         application = application,
         componentParams = componentParams,
-        source = AnalyticsSource.PaymentComponent(componentParams.isCreatedByDropIn, paymentMethod),
+        source = AnalyticsSource.PaymentComponent(paymentMethod.type.orEmpty()),
         paymentMethodType = paymentMethod.type,
         sessionId = sessionId,
     )
@@ -53,7 +53,7 @@ data class AnalyticsRepositoryData(
     ) : this(
         application = application,
         componentParams = componentParams,
-        source = AnalyticsSource.PaymentComponent(componentParams.isCreatedByDropIn, storedPaymentMethod),
+        source = AnalyticsSource.PaymentComponent(storedPaymentMethod.type.orEmpty()),
         paymentMethodType = storedPaymentMethod.type,
         sessionId = sessionId,
     )
diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/internal/data/api/DefaultAnalyticsRepository.kt b/components-core/src/main/java/com/adyen/checkout/components/core/internal/data/api/DefaultAnalyticsRepository.kt
index b9557b82a..fbd5e7918 100644
--- a/components-core/src/main/java/com/adyen/checkout/components/core/internal/data/api/DefaultAnalyticsRepository.kt
+++ b/components-core/src/main/java/com/adyen/checkout/components/core/internal/data/api/DefaultAnalyticsRepository.kt
@@ -40,33 +40,33 @@ class DefaultAnalyticsRepository(
         state = State.InProgress
         adyenLog(AdyenLogLevel.VERBOSE) { "Setting up analytics" }

-        runSuspendCatching {
-            val analyticsSetupRequest = with(analyticsRepositoryData) {
-                analyticsMapper.getAnalyticsSetupRequest(
-                    packageName = packageName,
-                    locale = locale,
-                    source = source,
-                    amount = amount,
-                    screenWidth = screenWidth.toLong(),
-                    paymentMethods = paymentMethods,
-                    sessionId = sessionId,
-                )
-            }
-            val response = analyticsService.setupAnalytics(analyticsSetupRequest, analyticsRepositoryData.clientKey)
-            checkoutAttemptId = response.checkoutAttemptId
-            state = State.Ready
-            adyenLog(AdyenLogLevel.VERBOSE) { "Analytics setup call successful" }
-        }.onFailure { e ->
-            state = State.Failed
-            adyenLog(AdyenLogLevel.ERROR) {
-                "Failed to send analytics setup call - ${e::class.simpleName}: ${e.message}"
-            }
-        }
+//        runSuspendCatching {
+//            val analyticsSetupRequest = with(analyticsRepositoryData) {
+//                analyticsMapper.getAnalyticsSetupRequest(
+//                    packageName = packageName,
+//                    locale = locale,
+//                    source = source,
+//                    amount = amount,
+//                    screenWidth = screenWidth.toLong(),
+//                    paymentMethods = paymentMethods,
+//                    sessionId = sessionId,
+//                )
+//            }
+//            val response = analyticsService.setupAnalytics(analyticsSetupRequest, analyticsRepositoryData.clientKey)
+//            checkoutAttemptId = response.checkoutAttemptId
+//            state = State.Ready
+//            adyenLog(AdyenLogLevel.VERBOSE) { "Analytics setup call successful" }
+//        }.onFailure { e ->
+//            state = State.Failed
+//            adyenLog(AdyenLogLevel.ERROR) {
+//                "Failed to send analytics setup call - ${e::class.simpleName}: ${e.message}"
+//            }
+//        }
     }

     private fun canSendAnalytics(requiredLevel: AnalyticsParamsLevel): Boolean {
         require(requiredLevel != NONE) { "Analytics are not allowed with level NONE" }
-        return !analyticsRepositoryData.level.hasHigherPriorityThan(requiredLevel)
+        return true
     }

     companion object {
diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/internal/ui/model/AnalyticsParams.kt b/components-core/src/main/java/com/adyen/checkout/components/core/internal/ui/model/AnalyticsParams.kt
index c95ed163f..8214b77f5 100644
--- a/components-core/src/main/java/com/adyen/checkout/components/core/internal/ui/model/AnalyticsParams.kt
+++ b/components-core/src/main/java/com/adyen/checkout/components/core/internal/ui/model/AnalyticsParams.kt
@@ -15,20 +15,19 @@ import com.adyen.checkout.components.core.AnalyticsLevel
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 data class AnalyticsParams(
     val level: AnalyticsParamsLevel,
+    val clientKey: String,
 ) {

-    constructor(analyticsConfiguration: AnalyticsConfiguration?) :
-        this(level = getLevel(analyticsConfiguration))
+    constructor(
+        analyticsConfiguration: AnalyticsConfiguration?,
+        clientKey: String,
+    ) : this(level = getLevel(analyticsConfiguration), clientKey = clientKey)
 }

 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-enum class AnalyticsParamsLevel(private val priority: Int) {
-    ALL(1),
-    NONE(2);
-
-    internal fun hasHigherPriorityThan(level: AnalyticsParamsLevel): Boolean {
-        return priority > level.priority
-    }
+enum class AnalyticsParamsLevel(val priority: Int) {
+    NONE(1),
+    ALL(2),
 }

 private fun getLevel(analyticsConfiguration: AnalyticsConfiguration?): AnalyticsParamsLevel {
diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInViewModelFactory.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInViewModelFactory.kt
index f65293023..fd98091ce 100644
--- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInViewModelFactory.kt
+++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInViewModelFactory.kt
@@ -13,6 +13,7 @@ import androidx.lifecycle.AbstractSavedStateViewModelFactory
 import androidx.lifecycle.SavedStateHandle
 import androidx.lifecycle.ViewModel
 import com.adyen.checkout.components.core.CheckoutConfiguration
+import com.adyen.checkout.components.core.internal.analytics.AnalyticsSource
 import com.adyen.checkout.components.core.internal.data.api.AnalyticsMapper
 import com.adyen.checkout.components.core.internal.data.api.AnalyticsRepositoryData
 import com.adyen.checkout.components.core.internal.data.api.AnalyticsService
araratthehero and others added 25 commits May 28, 2024 15:15
The line was logged even when no events were actually sent. Now it should only log
when events are actually successfully sent.

COAND-845
This will prevent an edge case where we would accidentally clear events that were not sent yet.

COAND-845
initState can be called from other places, so it's not the right place to send the event.

COAND-845
Copy link

sonarcloud bot commented May 28, 2024

@araratthehero araratthehero merged commit c502daa into develop May 28, 2024
8 checks passed
@araratthehero araratthehero deleted the feature/analytics branch May 28, 2024 13:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Chore [PRs only] Indicates any task that does not need to be mentioned in the public release notes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants