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

233 add user consent #235

Merged
merged 11 commits into from
Sep 11, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import ch.srgssr.pillarbox.analytics.comscore.ComScoreLabel
import ch.srgssr.pillarbox.analytics.comscore.ComScorePageView
import ch.srgssr.pillarbox.analytics.comscore.ComScoreSrg
import ch.srgssr.pillarbox.analytics.comscore.ComScoreUserConsent
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
Expand Down Expand Up @@ -67,6 +68,30 @@ class TestComScoreSrg {
job.cancel()
}

@Test
fun testUserConsentGiven() {
val userConsent = ComScoreUserConsent.ACCEPTED
val expectedLabel = "1"
comScore.setUserConsent(userConsent)
Assert.assertEquals(expectedLabel, comScore.getPersistentLabel(ComScoreLabel.CS_UC_FR.label))
}

@Test
fun testUserConsentNotGiven() {
val userConsent = ComScoreUserConsent.DECLINED
val expectedLabel = "0"
comScore.setUserConsent(userConsent)
Assert.assertEquals(expectedLabel, comScore.getPersistentLabel(ComScoreLabel.CS_UC_FR.label))
}

@Test
fun testUserConsentUnknown() {
val userConsent = ComScoreUserConsent.UNKNOWN
val expectedLabel = ""
comScore.setUserConsent(userConsent)
Assert.assertEquals(expectedLabel, comScore.getPersistentLabel(ComScoreLabel.CS_UC_FR.label))
}

private class PageViewTracking : ComScoreSrg.DebugListener {
val pageViewFlow = MutableSharedFlow<Map<String, String>>(extraBufferCapacity = 1, replay = 1)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package ch.srgssr.pillarbox.analytics

import androidx.test.platform.app.InstrumentationRegistry
import ch.srgssr.pillarbox.analytics.commandersact.CommandersActEvent
import ch.srgssr.pillarbox.analytics.commandersact.CommandersActLabels
import ch.srgssr.pillarbox.analytics.commandersact.CommandersActPageView
import ch.srgssr.pillarbox.analytics.commandersact.CommandersActSrg
import org.junit.Assert
Expand Down Expand Up @@ -39,4 +40,12 @@ class TestCommandersAct {
)
Assert.assertTrue(true)
}

@Test
fun testConsentServices() {
val services = listOf("service1", "service2")
val expected = "service1,service2"
commandersAct.setConsentServices(services)
Assert.assertEquals(expected, commandersAct.getPermanentDataLabel(CommandersActLabels.CONSENT_SERVICES.label))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,22 @@ package ch.srgssr.pillarbox.analytics
* SRG Analytics config
*
* @property vendor business unit.
* @property nonLocalizedApplicationName Application name for the analytics, by default the application name defined in the manifest. You can set
* it to null if the application name is not localized.
* @property appSiteName The App/Site name given by the analytics team.
* @property sourceKey The CommandersAct sourceKey given by the analytics team.
* @property nonLocalizedApplicationName Application name for the analytics, by default the application name defined in the manifest. You can set
* it to null if the application name is not localized.
* @property userConsent User consent to transmit to ComScore and CommandersAct.
* @property comScorePersistentLabels initial ComScore persistent labels.
* @property commandersActPersistentLabels initial CommandersAct persistent labels.
*/
data class AnalyticsConfig(
val vendor: Vendor,
val nonLocalizedApplicationName: String? = null,
val appSiteName: String,
val sourceKey: String
val sourceKey: String,
val nonLocalizedApplicationName: String? = null,
val userConsent: UserConsent = UserConsent(),
val comScorePersistentLabels: Map<String, String>? = null,
val commandersActPersistentLabels: Map<String, String>? = null,
) {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,70 @@ object SRGAnalytics {
instance?.sendEvent(event)
}

/**
* Put persistent labels
*
* @param commandersActLabels CommandersAct specific persistent label.
* @param comScoreLabels ComScore specific persistent label.
*/
fun putPersistentLabels(
commandersActLabels: Map<String, String>,
comScoreLabels: Map<String, String>
) {
instance?.putPersistentLabels(commandersActLabels = commandersActLabels, comScoreLabels = comScoreLabels)
}

/**
* Remove persistent label for CommandersAct and/or ComScore.
*
* @param label The label to remove.
*/
fun removePersistentLabel(label: String) {
instance?.removePersistentLabel(label)
}

/**
* Remove multiple persistent labels.
*
* @param labels List of labels to remove.
*/
fun removePersistentLabels(labels: List<String>) {
instance?.let { analytics ->
for (label in labels) {
analytics.removePersistentLabel(label)
}
}
}

/**
* Get ComScore persistent label
*
* @param label The label to get.
* @return associated ComScore label or null if not found.
*/
fun getComScorePersistentLabel(label: String): String? {
return instance?.getComScorePersistentLabel(label)
}

/**
* Get CommandersAct persistent label
*
* @param label The label to get.
* @return associated CommandersAct label or null if not found.
*/
fun getCommandersActPersistentLabel(label: String): String? {
return instance?.getCommandersActPermanentData(label)
}

/**
* Set user consent
*
* @param userConsent The user consent to apply.
*/
fun setUserConsent(userConsent: UserConsent) {
instance?.setUserConsent(userConsent)
}

/**
* Init SRGAnalytics
*
Expand All @@ -115,5 +179,31 @@ object SRGAnalytics {
this.commandersAct.sendEvent(commandersAct)
// Business decision to not send those event to comScore.
}

fun putPersistentLabels(
commandersActLabels: Map<String, String>,
comScoreLabels: Map<String, String>
) {
comScore.putPersistentLabels(comScoreLabels)
commandersAct.putPermanentData(commandersActLabels)
}

fun removePersistentLabel(label: String) {
comScore.removePersistentLabel(label)
commandersAct.removePermanentData(label)
}

fun getComScorePersistentLabel(label: String): String? {
return comScore.getPersistentLabel(label)
}

fun getCommandersActPermanentData(label: String): String? {
return commandersAct.getPermanentDataLabel(label)
}

fun setUserConsent(userConsent: UserConsent) {
comScore.setUserConsent(userConsent.comScore)
commandersAct.setConsentServices(userConsent.commandersActConsentServices)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright (c) 2023. SRG SSR. All rights reserved.
* License information is available from the LICENSE file.
*/
package ch.srgssr.pillarbox.analytics

import ch.srgssr.pillarbox.analytics.comscore.ComScoreUserConsent

/**
* User consent
*
* @property comScore ComScore user consent.
* @property commandersActConsentServices CommandersAct consent services list.
*/
data class UserConsent(
val comScore: ComScoreUserConsent = ComScoreUserConsent.UNKNOWN,
val commandersActConsentServices: List<String> = emptyList()
)
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,33 @@ interface CommandersAct {
* Enable running in background
*/
fun enableRunningInBackground() {}

/**
* Put permanent data
*
* @param labels The values to put.
*/
fun putPermanentData(labels: Map<String, String>)

/**
* Remove permanent data
*
* @param label The label to remove.
*/
fun removePermanentData(label: String)

/**
* Get permanent label
*
* @param label The label to get.
* @return null if not found.
*/
fun getPermanentDataLabel(label: String): String?

/**
* Set consent services
*
* @param consentServices The list of consent services by the user.
*/
fun setConsentServices(consentServices: List<String>)
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ enum class CommandersActLabels(val label: String) {
// Page View
NAVIGATION_LEVEL_I("navigation_level_"),
NAVIGATION_BU_DISTRIBUTER("navigation_bu_distributer"),

// User consent
CONSENT_SERVICES("consent_services")
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ internal class CommandersActSrg(
tcServerSide = TCServerSide(SITE_SRG, config.sourceKey, appContext)
TCDebug.setDebugLevel(if (BuildConfig.DEBUG) Log.DEBUG else Log.INFO)

config.commandersActPersistentLabels?.let {
putPermanentData(it)
}

// Data send with all events that never change
tcServerSide.addPermanentData(APP_LIBRARY_VERSION, "${BuildConfig.VERSION_NAME} ${BuildConfig.BUILD_DATE}")
tcServerSide.addPermanentData(NAVIGATION_APP_SITE_NAME, config.appSiteName)
Expand Down Expand Up @@ -84,6 +88,25 @@ internal class CommandersActSrg(
tcServerSide.enableRunningInBackground()
}

override fun putPermanentData(labels: Map<String, String>) {
if (labels == null) return
for (entry in labels.entries) {
tcServerSide.addPermanentData(entry.key, entry.value)
}
}

override fun removePermanentData(label: String) {
tcServerSide.removePermanentData(label)
}

override fun getPermanentDataLabel(label: String): String? {
return tcServerSide.getPermanentData(label)
}

override fun setConsentServices(consentServices: List<String>) {
tcServerSide.addPermanentData(CommandersActLabels.CONSENT_SERVICES.label, consentServices.joinToString(","))
}

/**
* Override application name if [AnalyticsConfig.nonLocalizedApplicationName] is not empty.
* Useful for application that localized their application name and want to have same name for analytics.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,33 @@ interface ComScore {
* @param pageView The [ComScorePageView] to send.
*/
fun sendPageView(pageView: ComScorePageView)

/**
* Put persistent labels
*
* @param labels The values to put.
*/
fun putPersistentLabels(labels: Map<String, String>)

/**
* Remove persistent label
*
* @param label The label to remove.
*/
fun removePersistentLabel(label: String)

/**
* Get persistent label
*
* @param label The label to get.
* @return null if not found.
*/
fun getPersistentLabel(label: String): String?

/**
* Set user consent
*
* @param userConsent
*/
fun setUserConsent(userConsent: ComScoreUserConsent)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ internal enum class ComScoreLabel(val label: String) {
C8("c8"),
MP_BRAND("mp_brand"),
MP_V("mp_v"),
CS_UC_FR("cs_ucfr")
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,15 @@ internal object ComScoreSrg : ComScore {
return this
}
this.config = config

val persistentLabels = HashMap<String, String>()
config.comScorePersistentLabels?.let { labels ->
persistentLabels.putAll(labels)
}

val userConsentLabel = getUserConsentPair(config.userConsent.comScore)
persistentLabels[userConsentLabel.first] = userConsentLabel.second

val versionName: String = try {
// When unit testing from library packageInfo.versionName is null!
context.applicationContext.packageManager.getPackageInfo(context.applicationContext.packageName, 0).versionName
Expand Down Expand Up @@ -102,6 +110,37 @@ internal object ComScoreSrg : ComScore {
}
}

override fun putPersistentLabels(labels: Map<String, String>) {
val configuration = Analytics.getConfiguration().getPublisherConfiguration(publisherId)
configuration.addPersistentLabels(labels)
}

override fun removePersistentLabel(label: String) {
val configuration = Analytics.getConfiguration().getPublisherConfiguration(publisherId)
configuration.removePersistentLabel(label)
}

override fun getPersistentLabel(label: String): String? {
val configuration = Analytics.getConfiguration().getPublisherConfiguration(publisherId)
return configuration.getPersistentLabel(label)
}

override fun setUserConsent(userConsent: ComScoreUserConsent) {
putPersistentLabels(mapOf(getUserConsentPair(userConsent)))
}

/**
* Values from ComScore documentation section 2.5.2
*/
private fun getUserConsentPair(userConsent: ComScoreUserConsent): Pair<String, String> {
val value = when (userConsent) {
ComScoreUserConsent.ACCEPTED -> "1"
ComScoreUserConsent.DECLINED -> "0"
ComScoreUserConsent.UNKNOWN -> ""
}
return Pair(ComScoreLabel.CS_UC_FR.label, value)
}

private fun checkInitialized() {
requireNotNull(config) { "ComScore init has to be called before start." }
}
Expand Down
Loading