diff --git a/duckplayer/duckplayer-impl/src/main/java/com/duckduckgo/duckplayer/impl/DuckPlayerPixelName.kt b/duckplayer/duckplayer-impl/src/main/java/com/duckduckgo/duckplayer/impl/DuckPlayerPixelName.kt index 0a902cc3fbaf..54e9d3ec79c2 100644 --- a/duckplayer/duckplayer-impl/src/main/java/com/duckduckgo/duckplayer/impl/DuckPlayerPixelName.kt +++ b/duckplayer/duckplayer-impl/src/main/java/com/duckduckgo/duckplayer/impl/DuckPlayerPixelName.kt @@ -20,8 +20,11 @@ import com.duckduckgo.app.statistics.pixels.Pixel enum class DuckPlayerPixelName(override val pixelName: String) : Pixel.PixelName { DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS("duckplayer_overlay_youtube_impressions"), + DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS_UNIQUE("duckplayer_overlay_youtube_impressions_unique"), DUCK_PLAYER_VIEW_FROM_YOUTUBE_MAIN_OVERLAY("duckplayer_view-from_youtube_main-overlay"), DUCK_PLAYER_OVERLAY_YOUTUBE_WATCH_HERE("duckplayer_overlay_youtube_watch_here"), + DUCK_PLAYER_OVERLAY_YOUTUBE_DISMISS("duckplayer_overlay_youtube_dismiss-play"), + DUCK_PLAYER_OVERLAY_YOUTUBE_CHOICE_UNIQUE("duckplayer_overlay_youtube_choice_unique"), DUCK_PLAYER_WATCH_ON_YOUTUBE("duckplayer_watch_on_youtube"), DUCK_PLAYER_DAILY_UNIQUE_VIEW("duckplayer_daily-unique-view"), DUCK_PLAYER_VIEW_FROM_YOUTUBE_AUTOMATIC("duckplayer_view-from_youtube_automatic"), diff --git a/duckplayer/duckplayer-impl/src/main/java/com/duckduckgo/duckplayer/impl/RealDuckPlayer.kt b/duckplayer/duckplayer-impl/src/main/java/com/duckduckgo/duckplayer/impl/RealDuckPlayer.kt index 40fe0f73909e..81edf53832ba 100644 --- a/duckplayer/duckplayer-impl/src/main/java/com/duckduckgo/duckplayer/impl/RealDuckPlayer.kt +++ b/duckplayer/duckplayer-impl/src/main/java/com/duckduckgo/duckplayer/impl/RealDuckPlayer.kt @@ -27,7 +27,10 @@ import androidx.fragment.app.FragmentManager import com.duckduckgo.app.di.AppCoroutineScope import com.duckduckgo.app.di.IsMainProcess import com.duckduckgo.app.statistics.pixels.Pixel +import com.duckduckgo.app.statistics.pixels.Pixel.PixelType +import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.Count import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.Daily +import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.Unique import com.duckduckgo.common.utils.DispatcherProvider import com.duckduckgo.common.utils.UrlScheme.Companion.duck import com.duckduckgo.common.utils.UrlScheme.Companion.https @@ -53,7 +56,10 @@ import com.duckduckgo.duckplayer.api.YOUTUBE_MOBILE_HOST import com.duckduckgo.duckplayer.impl.DuckPlayerPixelName.DUCK_PLAYER_DAILY_UNIQUE_VIEW import com.duckduckgo.duckplayer.impl.DuckPlayerPixelName.DUCK_PLAYER_NEWTAB_SETTING_OFF import com.duckduckgo.duckplayer.impl.DuckPlayerPixelName.DUCK_PLAYER_NEWTAB_SETTING_ON +import com.duckduckgo.duckplayer.impl.DuckPlayerPixelName.DUCK_PLAYER_OVERLAY_YOUTUBE_CHOICE_UNIQUE +import com.duckduckgo.duckplayer.impl.DuckPlayerPixelName.DUCK_PLAYER_OVERLAY_YOUTUBE_DISMISS import com.duckduckgo.duckplayer.impl.DuckPlayerPixelName.DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS +import com.duckduckgo.duckplayer.impl.DuckPlayerPixelName.DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS_UNIQUE import com.duckduckgo.duckplayer.impl.DuckPlayerPixelName.DUCK_PLAYER_OVERLAY_YOUTUBE_WATCH_HERE import com.duckduckgo.duckplayer.impl.DuckPlayerPixelName.DUCK_PLAYER_VIEW_FROM_OTHER import com.duckduckgo.duckplayer.impl.DuckPlayerPixelName.DUCK_PLAYER_VIEW_FROM_SERP @@ -73,6 +79,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import timber.log.Timber private const val DUCK_PLAYER_VIDEO_ID_QUERY_PARAM = "videoID" const val DUCK_PLAYER_OPEN_IN_YOUTUBE_PATH = "openInYoutube" @@ -184,15 +191,46 @@ class RealDuckPlayer @Inject constructor( pixelData: Map, ) { if (!isFeatureEnabled) return - val duckPlayerPixelName = when (pixelName) { - "overlay" -> DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS - "play.use" -> DUCK_PLAYER_VIEW_FROM_YOUTUBE_MAIN_OVERLAY - "play.do_not_use" -> DUCK_PLAYER_OVERLAY_YOUTUBE_WATCH_HERE - else -> { null } + val duckPlayerPixelNames: List>? = when (pixelName) { + "overlay" -> { + listOf( + DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS to Count, + DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS_UNIQUE to Unique(), + ) + } + "play.use" -> { + listOf( + DUCK_PLAYER_VIEW_FROM_YOUTUBE_MAIN_OVERLAY to Count, + DUCK_PLAYER_OVERLAY_YOUTUBE_CHOICE_UNIQUE to Unique(), + ) + } + "play.do_not_use" -> { + listOf( + DUCK_PLAYER_OVERLAY_YOUTUBE_WATCH_HERE to Count, + DUCK_PLAYER_OVERLAY_YOUTUBE_CHOICE_UNIQUE to Unique(), + ) + } + "play.do_not_use.dismiss" -> { + listOf( + DUCK_PLAYER_OVERLAY_YOUTUBE_DISMISS to Count, + DUCK_PLAYER_OVERLAY_YOUTUBE_CHOICE_UNIQUE to Unique(), + ) + } + else -> { + Timber.d("Unknown duck player pixel: $pixelName, data: $pixelData") + null + } } - duckPlayerPixelName?.let { - pixel.fire(duckPlayerPixelName, parameters = pixelData) + duckPlayerPixelNames?.forEach { (duckPlayerPixelName, type) -> + val dataToSend = when { + duckPlayerPixelName == DUCK_PLAYER_OVERLAY_YOUTUBE_CHOICE_UNIQUE -> mapOf("choice" to pixelName.split(".").last()) + type is Unique -> emptyMap() + else -> pixelData + } + + pixel.fire(duckPlayerPixelName, dataToSend, type = type) + if (duckPlayerPixelName == DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS) { duckPlayerFeatureRepository.setUserOnboarded() } diff --git a/duckplayer/duckplayer-impl/src/test/kotlin/com/duckduckgo/duckplayer/impl/RealDuckPlayerTest.kt b/duckplayer/duckplayer-impl/src/test/kotlin/com/duckduckgo/duckplayer/impl/RealDuckPlayerTest.kt index 7ba07ba07616..599c5593451a 100644 --- a/duckplayer/duckplayer-impl/src/test/kotlin/com/duckduckgo/duckplayer/impl/RealDuckPlayerTest.kt +++ b/duckplayer/duckplayer-impl/src/test/kotlin/com/duckduckgo/duckplayer/impl/RealDuckPlayerTest.kt @@ -27,6 +27,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.duckduckgo.app.statistics.pixels.Pixel import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.Count import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.Daily +import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.Unique import com.duckduckgo.common.utils.UrlScheme.Companion.duck import com.duckduckgo.common.utils.UrlScheme.Companion.https import com.duckduckgo.duckplayer.api.DuckPlayer.DuckPlayerOrigin.AUTO @@ -40,7 +41,10 @@ import com.duckduckgo.duckplayer.api.PrivatePlayerMode.Enabled import com.duckduckgo.duckplayer.impl.DuckPlayerPixelName.DUCK_PLAYER_DAILY_UNIQUE_VIEW import com.duckduckgo.duckplayer.impl.DuckPlayerPixelName.DUCK_PLAYER_NEWTAB_SETTING_OFF import com.duckduckgo.duckplayer.impl.DuckPlayerPixelName.DUCK_PLAYER_NEWTAB_SETTING_ON +import com.duckduckgo.duckplayer.impl.DuckPlayerPixelName.DUCK_PLAYER_OVERLAY_YOUTUBE_CHOICE_UNIQUE +import com.duckduckgo.duckplayer.impl.DuckPlayerPixelName.DUCK_PLAYER_OVERLAY_YOUTUBE_DISMISS import com.duckduckgo.duckplayer.impl.DuckPlayerPixelName.DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS +import com.duckduckgo.duckplayer.impl.DuckPlayerPixelName.DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS_UNIQUE import com.duckduckgo.duckplayer.impl.DuckPlayerPixelName.DUCK_PLAYER_OVERLAY_YOUTUBE_WATCH_HERE import com.duckduckgo.duckplayer.impl.DuckPlayerPixelName.DUCK_PLAYER_VIEW_FROM_OTHER import com.duckduckgo.duckplayer.impl.DuckPlayerPixelName.DUCK_PLAYER_VIEW_FROM_YOUTUBE_AUTOMATIC @@ -244,6 +248,7 @@ class RealDuckPlayerTest { testee.sendDuckPlayerPixel(pixelName, pixelData) verify(mockPixel).fire(DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS, pixelData, emptyMap(), Count) + verify(mockPixel).fire(DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS_UNIQUE, emptyMap(), emptyMap(), Unique()) } @Test @@ -253,44 +258,64 @@ class RealDuckPlayerTest { testee.sendDuckPlayerPixel(pixelName, emptyMap()) verify(mockPixel).fire(DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS, emptyMap(), emptyMap(), Count) + verify(mockPixel).fire(DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS_UNIQUE, emptyMap(), emptyMap(), Unique()) } @Test fun sendDuckPlayerPixelWithPlayUse_firesPixelWithCorrectNameAndData() = runTest { val pixelName = "play.use" val pixelData = mapOf("key" to "value") + val uniquePixelData = mapOf("choice" to "use") testee.sendDuckPlayerPixel(pixelName, pixelData) verify(mockPixel).fire(DUCK_PLAYER_VIEW_FROM_YOUTUBE_MAIN_OVERLAY, pixelData, emptyMap(), Count) + verify(mockPixel).fire(DUCK_PLAYER_OVERLAY_YOUTUBE_CHOICE_UNIQUE, uniquePixelData, emptyMap(), Unique()) } @Test fun sendDuckPlayerPixelWithPlayUse_firesPixelWithEmptyDataWhenNoDataProvided() = runTest { val pixelName = "play.use" + val uniquePixelData = mapOf("choice" to "use") testee.sendDuckPlayerPixel(pixelName, emptyMap()) verify(mockPixel).fire(DUCK_PLAYER_VIEW_FROM_YOUTUBE_MAIN_OVERLAY, emptyMap(), emptyMap(), Count) + verify(mockPixel).fire(DUCK_PLAYER_OVERLAY_YOUTUBE_CHOICE_UNIQUE, uniquePixelData, emptyMap(), Unique()) } @Test fun sendDuckPlayerPixelWithPlayDoNotUse_firesPixelWithCorrectNameAndData() = runTest { val pixelName = "play.do_not_use" val pixelData = mapOf("key" to "value") + val uniquePixelData = mapOf("choice" to "do_not_use") testee.sendDuckPlayerPixel(pixelName, pixelData) verify(mockPixel).fire(DUCK_PLAYER_OVERLAY_YOUTUBE_WATCH_HERE, pixelData, emptyMap(), Count) + verify(mockPixel).fire(DUCK_PLAYER_OVERLAY_YOUTUBE_CHOICE_UNIQUE, uniquePixelData, emptyMap(), Unique()) } @Test fun sendDuckPlayerPixelWithPlayDoNotUse_firesPixelWithEmptyDataWhenNoDataProvided() = runTest { val pixelName = "play.do_not_use" + val uniquePixelData = mapOf("choice" to "do_not_use") testee.sendDuckPlayerPixel(pixelName, emptyMap()) verify(mockPixel).fire(DUCK_PLAYER_OVERLAY_YOUTUBE_WATCH_HERE, emptyMap(), emptyMap(), Count) + verify(mockPixel).fire(DUCK_PLAYER_OVERLAY_YOUTUBE_CHOICE_UNIQUE, uniquePixelData, emptyMap(), Unique()) + } + + @Test + fun sendDuckPlayerPixelWithPlayDoNotUseDismiss_firesPixelWithEmptyDataWhenNoDataProvided() = runTest { + val pixelName = "play.do_not_use.dismiss" + val uniquePixelData = mapOf("choice" to "dismiss") + + testee.sendDuckPlayerPixel(pixelName, emptyMap()) + + verify(mockPixel).fire(DUCK_PLAYER_OVERLAY_YOUTUBE_DISMISS, emptyMap(), emptyMap(), Count) + verify(mockPixel).fire(DUCK_PLAYER_OVERLAY_YOUTUBE_CHOICE_UNIQUE, uniquePixelData, emptyMap(), Unique()) } // endregion