Skip to content

Commit

Permalink
Move experiment pixels firing to SubscriptionPixelSender
Browse files Browse the repository at this point in the history
  • Loading branch information
nalcalag committed Feb 26, 2025
1 parent 1a9b61e commit 867bbb7
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,6 @@ import com.duckduckgo.subscriptions.impl.billing.PlayBillingManager
import com.duckduckgo.subscriptions.impl.billing.PurchaseState
import com.duckduckgo.subscriptions.impl.billing.RetryPolicy
import com.duckduckgo.subscriptions.impl.billing.retry
import com.duckduckgo.subscriptions.impl.freetrial.FreeTrialPrivacyProPixelsPlugin
import com.duckduckgo.subscriptions.impl.freetrial.onSubscriptionStartedMonthly
import com.duckduckgo.subscriptions.impl.freetrial.onSubscriptionStartedYearly
import com.duckduckgo.subscriptions.impl.pixels.SubscriptionPixelSender
import com.duckduckgo.subscriptions.impl.repository.AccessToken
import com.duckduckgo.subscriptions.impl.repository.Account
Expand Down Expand Up @@ -245,7 +242,6 @@ class RealSubscriptionsManager @Inject constructor(
private val pkceGenerator: PkceGenerator,
private val timeProvider: CurrentTimeProvider,
private val backgroundTokenRefresh: BackgroundTokenRefresh,
private val freeTrialPrivacyProPixelsPlugin: Lazy<FreeTrialPrivacyProPixelsPlugin>,
) : SubscriptionsManager {

private val adapter = Moshi.Builder().build().adapter(ResponseError::class.java)
Expand Down Expand Up @@ -467,9 +463,9 @@ class RealSubscriptionsManager @Inject constructor(
if (subscription.isActive()) {
// Free Trial experiment metrics
if (confirmationResponse.subscription.productId.contains("monthly-renews-us")) {
freeTrialPrivacyProPixelsPlugin.get().onSubscriptionStartedMonthly()
pixelSender.reportFreeTrialOnSubscriptionStartedMonthly()
} else if (confirmationResponse.subscription.productId.contains("yearly-renews-us")) {
freeTrialPrivacyProPixelsPlugin.get().onSubscriptionStartedYearly()
pixelSender.reportFreeTrialOnSubscriptionStartedYearly()
}
pixelSender.reportPurchaseSuccess()
pixelSender.reportSubscriptionActivated()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,15 @@ interface FreeTrialExperimentDataStore {
/**
* Returns the number [Int] of paywall impressions for the given [definition]
*/
suspend fun getMetricForPixelDefinition(definition: PixelDefinition): Int
suspend fun getMetricForPixelDefinition(definition: PixelDefinition): String?

/**
* Increases the count of paywall impressions for the given [definition]
*/
suspend fun increaseMetricForPixelDefinition(definition: PixelDefinition): Int
suspend fun increaseMetricForPixelDefinition(
definition: PixelDefinition,
value: String,
): String?
}

@ContributesBinding(AppScope::class)
Expand All @@ -54,7 +57,11 @@ class FreeTrialExperimentDataStoreImpl @Inject constructor(
private val dispatcherProvider: DispatcherProvider,
) : FreeTrialExperimentDataStore {

private val preferences: SharedPreferences by lazy { sharedPreferencesProvider.getSharedPreferences(FILENAME) }
private val preferences: SharedPreferences by lazy {
sharedPreferencesProvider.getSharedPreferences(
FILENAME,
)
}

override var paywallImpressions: Int
get() = preferences.getInt(KEY_PAYWALL_IMPRESSIONS, 0)
Expand All @@ -66,22 +73,24 @@ class FreeTrialExperimentDataStoreImpl @Inject constructor(
}
}

override suspend fun getMetricForPixelDefinition(definition: PixelDefinition): Int {
override suspend fun getMetricForPixelDefinition(definition: PixelDefinition): String? {
val tag = "$definition"
return withContext(dispatcherProvider.io()) {
preferences.getInt(tag, 0)
preferences.getString(tag, null)
}
}

override suspend fun increaseMetricForPixelDefinition(definition: PixelDefinition): Int =
override suspend fun increaseMetricForPixelDefinition(
definition: PixelDefinition,
value: String,
): String? =
withContext(dispatcherProvider.io()) {
val tag = "$definition"
val count = preferences.getInt(tag, 0)
preferences.edit {
putInt(tag, count + 1)
putString(tag, value)
apply()
}
preferences.getInt(tag, 0)
preferences.getString(tag, null)
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,8 @@ class FreeTrialPrivacyProPixelsPlugin @Inject constructor(
metricsPixel?.let { metric ->
metric.getPixelDefinitions().forEach { definition ->
if (definition.isInConversionWindow()) {
freeTrialExperimentDataStore.getMetricForPixelDefinition(definition).takeIf {
it < freeTrialExperimentDataStore.paywallImpressions
}?.let {
freeTrialExperimentDataStore.increaseMetricForPixelDefinition(definition)
freeTrialExperimentDataStore.getMetricForPixelDefinition(definition).takeIf { it != metric.value }?.let {
freeTrialExperimentDataStore.increaseMetricForPixelDefinition(definition, metric.value)
pixel.fire(definition.pixelName, definition.params)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,14 @@ class SubscriptionMessagingInterface @Inject constructor(
) {
appCoroutineScope.launch {
when (jsMessage.method) {
"subscriptionsMonthlyPriceClicked" -> pixelSender.reportMonthlyPriceClick()
"subscriptionsYearlyPriceClicked" -> pixelSender.reportYearlyPriceClick()
"subscriptionsMonthlyPriceClicked" -> {
pixelSender.reportMonthlyPriceClick()
pixelSender.reportFreeTrialOnStartClickedMonthly()
}
"subscriptionsYearlyPriceClicked" -> {
pixelSender.reportYearlyPriceClick()
pixelSender.reportFreeTrialOnStartClickedYearly()
}
"subscriptionsAddEmailSuccess" -> {
pixelSender.reportAddEmailSuccess()
subscriptionsManager.tryRefreshAccessToken()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.appbuildconfig.api.AppBuildConfig
import com.duckduckgo.common.utils.extensions.toSanitizedLanguageTag
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.subscriptions.impl.freetrial.FreeTrialPrivacyProPixelsPlugin
import com.duckduckgo.subscriptions.impl.freetrial.onPaywallImpression
import com.duckduckgo.subscriptions.impl.freetrial.onStartClickedMonthly
import com.duckduckgo.subscriptions.impl.freetrial.onStartClickedYearly
import com.duckduckgo.subscriptions.impl.freetrial.onSubscriptionStartedMonthly
import com.duckduckgo.subscriptions.impl.freetrial.onSubscriptionStartedYearly
import com.duckduckgo.subscriptions.impl.pixels.SubscriptionPixel.ACTIVATE_SUBSCRIPTION_ENTER_EMAIL_CLICK
import com.duckduckgo.subscriptions.impl.pixels.SubscriptionPixel.ACTIVATE_SUBSCRIPTION_RESTORE_PURCHASE_CLICK
import com.duckduckgo.subscriptions.impl.pixels.SubscriptionPixel.APP_SETTINGS_IDTR_CLICK
Expand Down Expand Up @@ -106,12 +112,18 @@ interface SubscriptionPixelSender {
fun reportAuthV2MigrationFailureOther()
fun reportAuthV2TokenValidationError()
fun reportAuthV2TokenStoreError()
suspend fun reportFreeTrialExperimentOnPaywallImpression()
suspend fun reportFreeTrialOnStartClickedMonthly()
suspend fun reportFreeTrialOnStartClickedYearly()
suspend fun reportFreeTrialOnSubscriptionStartedMonthly()
suspend fun reportFreeTrialOnSubscriptionStartedYearly()
}

@ContributesBinding(AppScope::class)
class SubscriptionPixelSenderImpl @Inject constructor(
private val pixelSender: Pixel,
private val appBuildConfig: AppBuildConfig,
private val freeTrialPrivacyProPixelsPlugin: FreeTrialPrivacyProPixelsPlugin,
) : SubscriptionPixelSender {

override fun reportSubscriptionActive() =
Expand Down Expand Up @@ -252,6 +264,26 @@ class SubscriptionPixelSenderImpl @Inject constructor(
fire(AUTH_V2_TOKEN_STORE_ERROR)
}

override suspend fun reportFreeTrialExperimentOnPaywallImpression() {
freeTrialPrivacyProPixelsPlugin.onPaywallImpression()
}

override suspend fun reportFreeTrialOnStartClickedMonthly() {
freeTrialPrivacyProPixelsPlugin.onStartClickedMonthly()
}

override suspend fun reportFreeTrialOnStartClickedYearly() {
freeTrialPrivacyProPixelsPlugin.onStartClickedYearly()
}

override suspend fun reportFreeTrialOnSubscriptionStartedMonthly() {
freeTrialPrivacyProPixelsPlugin.onSubscriptionStartedMonthly()
}

override suspend fun reportFreeTrialOnSubscriptionStartedYearly() {
freeTrialPrivacyProPixelsPlugin.onSubscriptionStartedYearly()
}

private fun fire(pixel: SubscriptionPixel, params: Map<String, String> = emptyMap()) {
pixel.getPixelNames().forEach { (pixelType, pixelName) ->
pixelSender.fire(pixelName = pixelName, type = pixelType, parameters = params)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,6 @@ import com.duckduckgo.subscriptions.impl.SubscriptionsConstants.YEARLY_PLAN_ROW
import com.duckduckgo.subscriptions.impl.SubscriptionsConstants.YEARLY_PLAN_US
import com.duckduckgo.subscriptions.impl.SubscriptionsManager
import com.duckduckgo.subscriptions.impl.freetrial.FreeTrialExperimentDataStore
import com.duckduckgo.subscriptions.impl.freetrial.FreeTrialPrivacyProPixelsPlugin
import com.duckduckgo.subscriptions.impl.freetrial.onPaywallImpression
import com.duckduckgo.subscriptions.impl.freetrial.onStartClickedMonthly
import com.duckduckgo.subscriptions.impl.freetrial.onStartClickedYearly
import com.duckduckgo.subscriptions.impl.pixels.SubscriptionPixelSender
import com.duckduckgo.subscriptions.impl.repository.isActive
import com.duckduckgo.subscriptions.impl.repository.isExpired
Expand Down Expand Up @@ -88,7 +84,6 @@ class SubscriptionWebViewViewModel @Inject constructor(
private val networkProtectionAccessState: NetworkProtectionAccessState,
private val pixelSender: SubscriptionPixelSender,
private val privacyProFeature: PrivacyProFeature,
private val freeTrialPrivacyProPixelsPlugin: FreeTrialPrivacyProPixelsPlugin,
private val freeTrialExperimentDataStore: FreeTrialExperimentDataStore,
) : ViewModel() {

Expand Down Expand Up @@ -235,13 +230,6 @@ class SubscriptionWebViewViewModel @Inject constructor(
} else {
command.send(SubscriptionSelected(id, offerId))
}

// Free Trial experiment metrics
if (id?.contains("monthly-renews-us") == true) {
freeTrialPrivacyProPixelsPlugin.onStartClickedMonthly()
} else if (id?.contains("yearly-renews-us") == true) {
freeTrialPrivacyProPixelsPlugin.onStartClickedYearly()
}
}
}

Expand Down Expand Up @@ -371,7 +359,7 @@ class SubscriptionWebViewViewModel @Inject constructor(
pixelSender.reportOfferScreenShown()
viewModelScope.launch {
freeTrialExperimentDataStore.increaseMetricForPaywallImpressions()
freeTrialPrivacyProPixelsPlugin.onPaywallImpression()
pixelSender.reportFreeTrialExperimentOnPaywallImpression()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import com.duckduckgo.subscriptions.impl.auth2.RefreshTokenClaims
import com.duckduckgo.subscriptions.impl.auth2.TokenPair
import com.duckduckgo.subscriptions.impl.billing.PlayBillingManager
import com.duckduckgo.subscriptions.impl.billing.PurchaseState
import com.duckduckgo.subscriptions.impl.freetrial.FreeTrialPrivacyProPixelsPlugin
import com.duckduckgo.subscriptions.impl.model.Entitlement
import com.duckduckgo.subscriptions.impl.pixels.SubscriptionPixelSender
import com.duckduckgo.subscriptions.impl.repository.AuthRepository
Expand Down Expand Up @@ -117,7 +116,6 @@ class RealSubscriptionsManagerTest(private val authApiV2Enabled: Boolean) {
private val authJwtValidator: AuthJwtValidator = mock()
private val timeProvider = FakeTimeProvider()
private val backgroundTokenRefresh: BackgroundTokenRefresh = mock()
private val freeTrialPrivacyProPixelsPlugin: FreeTrialPrivacyProPixelsPlugin = mock()
private lateinit var subscriptionsManager: RealSubscriptionsManager

@Before
Expand All @@ -140,7 +138,6 @@ class RealSubscriptionsManagerTest(private val authApiV2Enabled: Boolean) {
pkceGenerator,
timeProvider,
backgroundTokenRefresh,
{ freeTrialPrivacyProPixelsPlugin },
)
}

Expand Down Expand Up @@ -561,7 +558,6 @@ class RealSubscriptionsManagerTest(private val authApiV2Enabled: Boolean) {
pkceGenerator,
timeProvider,
backgroundTokenRefresh,
{ freeTrialPrivacyProPixelsPlugin },
)

manager.subscriptionStatus.test {
Expand Down Expand Up @@ -590,7 +586,6 @@ class RealSubscriptionsManagerTest(private val authApiV2Enabled: Boolean) {
pkceGenerator,
timeProvider,
backgroundTokenRefresh,
{ freeTrialPrivacyProPixelsPlugin },
)

manager.subscriptionStatus.test {
Expand Down Expand Up @@ -624,7 +619,6 @@ class RealSubscriptionsManagerTest(private val authApiV2Enabled: Boolean) {
pkceGenerator,
timeProvider,
backgroundTokenRefresh,
{ freeTrialPrivacyProPixelsPlugin },
)

manager.currentPurchaseState.test {
Expand Down Expand Up @@ -672,7 +666,6 @@ class RealSubscriptionsManagerTest(private val authApiV2Enabled: Boolean) {
pkceGenerator,
timeProvider,
backgroundTokenRefresh,
{ freeTrialPrivacyProPixelsPlugin },
)

manager.currentPurchaseState.test {
Expand Down Expand Up @@ -710,7 +703,6 @@ class RealSubscriptionsManagerTest(private val authApiV2Enabled: Boolean) {
pkceGenerator,
timeProvider,
backgroundTokenRefresh,
{ freeTrialPrivacyProPixelsPlugin },
)

manager.currentPurchaseState.test {
Expand Down Expand Up @@ -1010,7 +1002,6 @@ class RealSubscriptionsManagerTest(private val authApiV2Enabled: Boolean) {
pkceGenerator,
timeProvider,
backgroundTokenRefresh,
{ freeTrialPrivacyProPixelsPlugin },
)
manager.signOut()
verify(mockRepo).setSubscription(null)
Expand Down Expand Up @@ -1056,7 +1047,6 @@ class RealSubscriptionsManagerTest(private val authApiV2Enabled: Boolean) {
pkceGenerator,
timeProvider,
backgroundTokenRefresh,
{ freeTrialPrivacyProPixelsPlugin },
)

manager.subscriptionStatus.test {
Expand Down Expand Up @@ -1238,7 +1228,6 @@ class RealSubscriptionsManagerTest(private val authApiV2Enabled: Boolean) {
pkceGenerator,
timeProvider,
backgroundTokenRefresh,
{ freeTrialPrivacyProPixelsPlugin },
)

assertFalse(subscriptionsManager.canSupportEncryption())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import com.duckduckgo.subscriptions.impl.pixels.SubscriptionPixelSender
import com.duckduckgo.subscriptions.impl.repository.Subscription
import kotlinx.coroutines.test.runTest
import org.json.JSONObject
import org.junit.Assert.*
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
Expand Down Expand Up @@ -567,7 +569,7 @@ class SubscriptionMessagingInterfaceTest {
messagingInterface.process(message, "duckduckgo-android-messaging-secret")

verify(pixelSender).reportMonthlyPriceClick()
verifyNoMoreInteractions(pixelSender)
// verifyNoMoreInteractions(pixelSender) Add it back when Free Trials experiment is removed
}

@Test
Expand All @@ -581,7 +583,33 @@ class SubscriptionMessagingInterfaceTest {
messagingInterface.process(message, "duckduckgo-android-messaging-secret")

verify(pixelSender).reportYearlyPriceClick()
verifyNoMoreInteractions(pixelSender)
// verifyNoMoreInteractions(pixelSender) Add it back when Free Trials experiment is removed
}

@Test
fun `when process and monthly price clicked then experiment pixel sent`() = runTest {
givenInterfaceIsRegistered()

val message = """
{"context":"subscriptionPages","featureName":"useSubscription","method":"subscriptionsMonthlyPriceClicked","id":"myId","params":{}}
""".trimIndent()

messagingInterface.process(message, "duckduckgo-android-messaging-secret")

verify(pixelSender).reportFreeTrialOnStartClickedMonthly()
}

@Test
fun `when process and yearly price clicked then experiment pixel sent`() = runTest {
givenInterfaceIsRegistered()

val message = """
{"context":"subscriptionPages","featureName":"useSubscription","method":"subscriptionsYearlyPriceClicked","id":"myId","params":{}}
""".trimIndent()

messagingInterface.process(message, "duckduckgo-android-messaging-secret")

verify(pixelSender).reportFreeTrialOnStartClickedYearly()
}

@Test
Expand Down
Loading

0 comments on commit 867bbb7

Please sign in to comment.