diff --git a/network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/pixels/NetworkProtectionPixels.kt b/network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/pixels/NetworkProtectionPixels.kt index f60aaa54c467..7fee9e91f5eb 100644 --- a/network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/pixels/NetworkProtectionPixels.kt +++ b/network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/pixels/NetworkProtectionPixels.kt @@ -26,7 +26,9 @@ import com.duckduckgo.networkprotection.impl.pixels.NetworkProtectionPixelNames. import com.squareup.anvil.annotations.ContributesBinding import dagger.SingleInstanceIn import javax.inject.Inject +import javax.inject.Qualifier import org.threeten.bp.Instant +import org.threeten.bp.ZoneId import org.threeten.bp.ZoneOffset import org.threeten.bp.format.DateTimeFormatter @@ -318,6 +320,7 @@ class RealNetworkProtectionPixel @Inject constructor( private val pixel: Pixel, private val vpnSharedPreferencesProvider: VpnSharedPreferencesProvider, private val cohortStore: NetpCohortStore, + private val etTimestamp: ETTimestamp, ) : NetworkProtectionPixels { private val preferences: SharedPreferences by lazy { @@ -559,7 +562,7 @@ class RealNetworkProtectionPixel @Inject constructor( enqueue: Boolean = false, ) { if (enqueue) { - pixel.enqueueFire(pixelName, payload) + pixel.enqueueFire(pixelName, payload.addTimestampAtZoneET()) } else { pixel.fire(pixelName, payload) } @@ -583,7 +586,7 @@ class RealNetworkProtectionPixel @Inject constructor( // check if pixel was already sent in the current day if (timestamp == null || now > timestamp) { if (enqueue) { - this.pixel.enqueueFire(pixelName, payload) + this.pixel.enqueueFire(pixelName, payload.addTimestampAtZoneET()) .also { preferences.edit { putString(pixelName.appendTimestampSuffix(), now) } } } else { this.pixel.fire(pixelName, payload) @@ -602,7 +605,7 @@ class RealNetworkProtectionPixel @Inject constructor( if (didExecuteAlready) return if (pixel.enqueue) { - this.pixel.enqueueFire(pixel, payload).also { preferences.edit { putBoolean(tag ?: pixel.pixelName, true) } } + this.pixel.enqueueFire(pixel, payload.addTimestampAtZoneET()).also { preferences.edit { putBoolean(tag ?: pixel.pixelName, true) } } } else { this.pixel.fire(pixel, payload).also { preferences.edit { putBoolean(tag ?: pixel.pixelName, true) } } } @@ -612,6 +615,12 @@ class RealNetworkProtectionPixel @Inject constructor( return "${this}_timestamp" } + private fun Map.addTimestampAtZoneET(): Map { + return this.toMutableMap().apply { + put(TIMESTAMP_ET_PARAM, etTimestamp.formattedTimestamp()) + } + } + private fun getUtcIsoLocalDate(): String { // returns YYYY-MM-dd return Instant.now().atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_LOCAL_DATE) @@ -619,5 +628,19 @@ class RealNetworkProtectionPixel @Inject constructor( companion object { private const val NETP_PIXELS_PREF_FILE = "com.duckduckgo.networkprotection.pixels.v1" + private const val TIMESTAMP_ET_PARAM = "ts" + } +} + +@Retention(AnnotationRetention.BINARY) +@Qualifier +private annotation class InternalApi + +// This class is here for testing purposes +@InternalApi +open class ETTimestamp @Inject constructor() { + open fun formattedTimestamp(): String { + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss") + return Instant.now().atZone(ZoneId.of("America/New_York")).format(formatter) } } diff --git a/network-protection/network-protection-impl/src/test/java/com/duckduckgo/networkprotection/impl/pixels/RealNetworkProtectionPixelTest.kt b/network-protection/network-protection-impl/src/test/java/com/duckduckgo/networkprotection/impl/pixels/RealNetworkProtectionPixelTest.kt index 3aa8a7940a88..787c1a885654 100644 --- a/network-protection/network-protection-impl/src/test/java/com/duckduckgo/networkprotection/impl/pixels/RealNetworkProtectionPixelTest.kt +++ b/network-protection/network-protection-impl/src/test/java/com/duckduckgo/networkprotection/impl/pixels/RealNetworkProtectionPixelTest.kt @@ -37,6 +37,7 @@ class RealNetworkProtectionPixelTest { @Mock private lateinit var vpnSharedPreferencesProvider: VpnSharedPreferencesProvider + private lateinit var fakeNetpCohortStore: FakeNetpCohortStore private lateinit var testee: RealNetworkProtectionPixel @@ -51,7 +52,16 @@ class RealNetworkProtectionPixelTest { whenever( vpnSharedPreferencesProvider.getSharedPreferences(eq("com.duckduckgo.networkprotection.pixels.v1"), eq(true), eq(false)), ).thenReturn(prefs) - testee = RealNetworkProtectionPixel(pixel, vpnSharedPreferencesProvider, fakeNetpCohortStore) + testee = RealNetworkProtectionPixel( + pixel, + vpnSharedPreferencesProvider, + fakeNetpCohortStore, + object : ETTimestamp() { + override fun formattedTimestamp(): String { + return "2000-01-01" + } + }, + ) } @Test @@ -59,8 +69,8 @@ class RealNetworkProtectionPixelTest { testee.reportErrorInRegistration() testee.reportErrorInRegistration() - verify(pixel).enqueueFire("m_netp_ev_backend_api_error_device_registration_failed_d") - verify(pixel, times(2)).enqueueFire("m_netp_ev_backend_api_error_device_registration_failed_c") + verify(pixel).enqueueFire("m_netp_ev_backend_api_error_device_registration_failed_d", mapOf("ts" to "2000-01-01")) + verify(pixel, times(2)).enqueueFire("m_netp_ev_backend_api_error_device_registration_failed_c", mapOf("ts" to "2000-01-01")) } @Test @@ -68,8 +78,8 @@ class RealNetworkProtectionPixelTest { testee.reportErrorWgInvalidState() testee.reportErrorWgInvalidState() - verify(pixel).enqueueFire("m_netp_ev_wireguard_error_invalid_state_d") - verify(pixel, times(2)).enqueueFire("m_netp_ev_wireguard_error_invalid_state_c") + verify(pixel).enqueueFire("m_netp_ev_wireguard_error_invalid_state_d", mapOf("ts" to "2000-01-01")) + verify(pixel, times(2)).enqueueFire("m_netp_ev_wireguard_error_invalid_state_c", mapOf("ts" to "2000-01-01")) } @Test @@ -77,8 +87,8 @@ class RealNetworkProtectionPixelTest { testee.reportErrorWgBackendCantStart() testee.reportErrorWgBackendCantStart() - verify(pixel).enqueueFire("m_netp_ev_wireguard_error_cannot_start_wireguard_backend_d") - verify(pixel, times(2)).enqueueFire("m_netp_ev_wireguard_error_cannot_start_wireguard_backend_c") + verify(pixel).enqueueFire("m_netp_ev_wireguard_error_cannot_start_wireguard_backend_d", mapOf("ts" to "2000-01-01")) + verify(pixel, times(2)).enqueueFire("m_netp_ev_wireguard_error_cannot_start_wireguard_backend_c", mapOf("ts" to "2000-01-01")) } @Test @@ -86,8 +96,14 @@ class RealNetworkProtectionPixelTest { testee.reportEnabled() testee.reportEnabled() - verify(pixel).enqueueFire("m_netp_ev_enabled_d", mapOf("cohort" to fakeNetpCohortStore.cohortLocalDate?.toString().orEmpty())) - verify(pixel).enqueueFire(NETP_ENABLE_UNIQUE, mapOf("cohort" to fakeNetpCohortStore.cohortLocalDate?.toString().orEmpty())) + verify(pixel).enqueueFire( + "m_netp_ev_enabled_d", + mapOf("cohort" to fakeNetpCohortStore.cohortLocalDate?.toString().orEmpty(), "ts" to "2000-01-01"), + ) + verify(pixel).enqueueFire( + NETP_ENABLE_UNIQUE, + mapOf("cohort" to fakeNetpCohortStore.cohortLocalDate?.toString().orEmpty(), "ts" to "2000-01-01"), + ) } @Test @@ -103,8 +119,8 @@ class RealNetworkProtectionPixelTest { testee.reportVpnConnectivityLoss() testee.reportVpnConnectivityLoss() - verify(pixel).enqueueFire("m_netp_ev_vpn_connectivity_lost_d") - verify(pixel, times(2)).enqueueFire("m_netp_ev_vpn_connectivity_lost_c") + verify(pixel).enqueueFire("m_netp_ev_vpn_connectivity_lost_d", mapOf("ts" to "2000-01-01")) + verify(pixel, times(2)).enqueueFire("m_netp_ev_vpn_connectivity_lost_c", mapOf("ts" to "2000-01-01")) } @Test @@ -112,8 +128,8 @@ class RealNetworkProtectionPixelTest { testee.reportVpnReconnectFailed() testee.reportVpnReconnectFailed() - verify(pixel).enqueueFire("m_netp_ev_vpn_reconnect_failed_d") - verify(pixel, times(2)).enqueueFire("m_netp_ev_vpn_reconnect_failed_c") + verify(pixel).enqueueFire("m_netp_ev_vpn_reconnect_failed_d", mapOf("ts" to "2000-01-01")) + verify(pixel, times(2)).enqueueFire("m_netp_ev_vpn_reconnect_failed_c", mapOf("ts" to "2000-01-01")) } @Test @@ -121,8 +137,8 @@ class RealNetworkProtectionPixelTest { testee.reportWireguardLibraryLoadFailed() testee.reportWireguardLibraryLoadFailed() - verify(pixel).enqueueFire("m_netp_ev_wireguard_error_unable_to_load_wireguard_library_d") - verify(pixel, times(2)).enqueueFire("m_netp_ev_wireguard_error_unable_to_load_wireguard_library_c") + verify(pixel).enqueueFire("m_netp_ev_wireguard_error_unable_to_load_wireguard_library_d", mapOf("ts" to "2000-01-01")) + verify(pixel, times(2)).enqueueFire("m_netp_ev_wireguard_error_unable_to_load_wireguard_library_c", mapOf("ts" to "2000-01-01")) } @Test @@ -130,8 +146,8 @@ class RealNetworkProtectionPixelTest { testee.reportRekeyCompleted() testee.reportRekeyCompleted() - verify(pixel).enqueueFire("m_netp_ev_rekey_completed_d") - verify(pixel, times(2)).enqueueFire("m_netp_ev_rekey_completed_c") + verify(pixel).enqueueFire("m_netp_ev_rekey_completed_d", mapOf("ts" to "2000-01-01")) + verify(pixel, times(2)).enqueueFire("m_netp_ev_rekey_completed_c", mapOf("ts" to "2000-01-01")) } @Test