Skip to content

Commit

Permalink
Add AppTP WAU pixel (#5292)
Browse files Browse the repository at this point in the history
Task/Issue URL: https://app.asana.com/0/69071770703008/1208808769852413/f

### Description
Add a pixel for AppTP MAU.

The pixel fires at most once every 28 days

### Steps to test this PR
You may want to change the polici to `REPLACE` in `scheduleDeviceShieldStatusReporting()` to trigger the pixel on every app  `onCreate`

_Test_
- [x] clean install from this branch and launch the app
- [x] enable AppTP
- [x] force close the app and re-open
- [x] verify the `m_atp_ev_enabled_monthly` is sent
- [x] force close the app and re-open
- [x] verify the `m_atp_ev_enabled_monthly` is NOT sent
- [x] move the device date no more that 28d from now
- [x] force close the app and re-open
- [x] verify the `m_atp_ev_enabled_monthly` is NOT sent
- [x] move the device date no more that 29d from now
- [x] force close the app and re-open
- [x] verify the `m_atp_ev_enabled_monthly` is sent
  • Loading branch information
aitorvs authored Nov 22, 2024
1 parent 227c1fa commit 5e3063d
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ enum class DeviceShieldPixelNames(override val pixelName: String, val enqueue: B
ATP_DISABLE_DAILY("m_atp_ev_disabled_d"),

ATP_ENABLE_UNIQUE("m_atp_ev_enabled_u"),
ATP_ENABLE_MONTHLY("m_atp_ev_enabled_monthly"),
ATP_ENABLE_FROM_REMINDER_NOTIFICATION_UNIQUE("m_atp_ev_enabled_reminder_notification_u"),
ATP_ENABLE_FROM_REMINDER_NOTIFICATION_DAILY("m_atp_ev_enabled_reminder_notification_d"),
ATP_ENABLE_FROM_REMINDER_NOTIFICATION("m_atp_ev_enabled_reminder_notification_c"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@ import com.duckduckgo.di.scopes.AppScope
import com.squareup.anvil.annotations.ContributesBinding
import dagger.SingleInstanceIn
import java.time.Instant
import java.time.LocalDate
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit
import java.util.*
import javax.inject.Inject
import kotlin.math.absoluteValue

interface DeviceShieldPixels {
/** This pixel will be unique on a given day, no matter how many times we call this fun */
Expand Down Expand Up @@ -414,6 +417,7 @@ class RealDeviceShieldPixels @Inject constructor(
override fun reportEnabled() {
tryToFireUniquePixel(DeviceShieldPixelNames.ATP_ENABLE_UNIQUE)
tryToFireDailyPixel(DeviceShieldPixelNames.ATP_ENABLE_DAILY)
tryToFireMonthlyPixel(DeviceShieldPixelNames.ATP_ENABLE_MONTHLY)
}

override fun reportDisabled() {
Expand Down Expand Up @@ -934,6 +938,45 @@ class RealDeviceShieldPixels @Inject constructor(
}
}

private fun tryToFireMonthlyPixel(
pixel: DeviceShieldPixelNames,
payload: Map<String, String> = emptyMap(),
) {
tryToFireMonthlyPixel(pixel.pixelName, payload, pixel.enqueue)
}

private fun tryToFireMonthlyPixel(
pixelName: String,
payload: Map<String, String> = emptyMap(),
enqueue: Boolean = false,
) {
fun isMoreThan28DaysApart(date1: String, date2: String): Boolean {
// Parse the strings into LocalDate objects
val firstDate = LocalDate.parse(date1)
val secondDate = LocalDate.parse(date2)

// Calculate the difference in days
val daysBetween = ChronoUnit.DAYS.between(firstDate, secondDate).absoluteValue

// Check if the difference is more than 28 days
return daysBetween > 28
}

val now = getUtcIsoLocalDate()
val timestamp = preferences.getString(pixelName.appendTimestampSuffix(), null)

// check if pixel was already sent in the current day
if (timestamp == null || isMoreThan28DaysApart(now, timestamp)) {
if (enqueue) {
this.pixel.enqueueFire(pixelName, payload)
.also { preferences.edit { putString(pixelName.appendTimestampSuffix(), now) } }
} else {
this.pixel.fire(pixelName, payload)
.also { preferences.edit { putString(pixelName.appendTimestampSuffix(), now) } }
}
}
}

private fun tryToFireUniquePixel(
pixel: DeviceShieldPixelNames,
tag: String? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@

package com.duckduckgo.mobile.android.vpn.pixels

import androidx.core.content.edit
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.common.test.api.InMemorySharedPreferences
import com.duckduckgo.data.store.api.SharedPreferencesProvider
import java.time.Instant
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit
import java.util.*
import org.junit.Before
import org.junit.Test
Expand All @@ -28,12 +33,12 @@ class RealDeviceShieldPixelsTest {

private val pixel = mock<Pixel>()
private val sharedPreferencesProvider = mock<SharedPreferencesProvider>()
private val prefs = InMemorySharedPreferences()

lateinit var deviceShieldPixels: DeviceShieldPixels

@Before
fun setup() {
val prefs = InMemorySharedPreferences()
whenever(
sharedPreferencesProvider.getSharedPreferences(eq("com.duckduckgo.mobile.android.device.shield.pixels"), eq(true), eq(true)),
).thenReturn(prefs)
Expand All @@ -60,15 +65,48 @@ class RealDeviceShieldPixelsTest {
}

@Test
fun whenReportEnableThenFireUniqueAndDailyPixel() {
fun whenReportEnableThenFireUniqueAndMonthlyAndDailyPixel() {
deviceShieldPixels.reportEnabled()
deviceShieldPixels.reportEnabled()

verify(pixel).fire(DeviceShieldPixelNames.ATP_ENABLE_UNIQUE)
verify(pixel).fire(DeviceShieldPixelNames.ATP_ENABLE_DAILY.pixelName)
verify(pixel).fire(DeviceShieldPixelNames.ATP_ENABLE_MONTHLY.pixelName)
verifyNoMoreInteractions(pixel)
}

@Test
fun whenReportExactly28DaysApartThenDoNotFireMonthlyPixel() {
val pixelName = DeviceShieldPixelNames.ATP_ENABLE_MONTHLY.pixelName

deviceShieldPixels.reportEnabled()

val pastDate = Instant.now()
.minus(28, ChronoUnit.DAYS)
.atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_LOCAL_DATE)
prefs.edit(true) { putString("${pixelName}_timestamp", pastDate) }

deviceShieldPixels.reportEnabled()

verify(pixel).fire(pixelName)
}

@Test
fun whenReportEnableMoreThan28DaysApartReportMonthlyPixel() {
val pixelName = DeviceShieldPixelNames.ATP_ENABLE_MONTHLY.pixelName

deviceShieldPixels.reportEnabled()

val pastDate = Instant.now()
.minus(29, ChronoUnit.DAYS)
.atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_LOCAL_DATE)
prefs.edit(true) { putString("${pixelName}_timestamp", pastDate) }

deviceShieldPixels.reportEnabled()

verify(pixel, times(2)).fire(pixelName)
}

@Test
fun whenReportDisableThenFireDailyPixel() {
deviceShieldPixels.reportDisabled()
Expand Down

0 comments on commit 5e3063d

Please sign in to comment.