Skip to content

Commit

Permalink
feat: add option to disable screen view usage (#474)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrehan27 authored Dec 17, 2024
1 parent 9b3efeb commit b5ea8e9
Show file tree
Hide file tree
Showing 15 changed files with 331 additions and 5 deletions.
26 changes: 24 additions & 2 deletions datapipelines/api/datapipelines.api
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
public final class io/customer/datapipelines/config/DataPipelinesModuleConfig : io/customer/sdk/core/module/CustomerIOModuleConfig {
public fun <init> (Ljava/lang/String;Lio/customer/sdk/data/model/Region;Ljava/lang/String;Ljava/lang/String;IILjava/util/List;ZZZZLjava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;Lio/customer/sdk/data/model/Region;Ljava/lang/String;Ljava/lang/String;IILjava/util/List;ZZZZLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Ljava/lang/String;Lio/customer/sdk/data/model/Region;Ljava/lang/String;Ljava/lang/String;IILjava/util/List;ZZZZLjava/lang/String;Lio/customer/datapipelines/config/ScreenView;)V
public synthetic fun <init> (Ljava/lang/String;Lio/customer/sdk/data/model/Region;Ljava/lang/String;Ljava/lang/String;IILjava/util/List;ZZZZLjava/lang/String;Lio/customer/datapipelines/config/ScreenView;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getApiHost ()Ljava/lang/String;
public final fun getAutoAddCustomerIODestination ()Z
public final fun getAutoTrackActivityScreens ()Z
Expand All @@ -11,9 +11,30 @@ public final class io/customer/datapipelines/config/DataPipelinesModuleConfig :
public final fun getFlushInterval ()I
public final fun getFlushPolicies ()Ljava/util/List;
public final fun getMigrationSiteId ()Ljava/lang/String;
public final fun getScreenViewUse ()Lio/customer/datapipelines/config/ScreenView;
public final fun getTrackApplicationLifecycleEvents ()Z
}

public abstract class io/customer/datapipelines/config/ScreenView {
public static final field Companion Lio/customer/datapipelines/config/ScreenView$Companion;
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getName ()Ljava/lang/String;
}

public final class io/customer/datapipelines/config/ScreenView$All : io/customer/datapipelines/config/ScreenView {
public static final field INSTANCE Lio/customer/datapipelines/config/ScreenView$All;
}

public final class io/customer/datapipelines/config/ScreenView$Companion {
public final fun getScreenView (Ljava/lang/String;)Lio/customer/datapipelines/config/ScreenView;
public final fun getScreenView (Ljava/lang/String;Lio/customer/datapipelines/config/ScreenView;)Lio/customer/datapipelines/config/ScreenView;
public static synthetic fun getScreenView$default (Lio/customer/datapipelines/config/ScreenView$Companion;Ljava/lang/String;Lio/customer/datapipelines/config/ScreenView;ILjava/lang/Object;)Lio/customer/datapipelines/config/ScreenView;
}

public final class io/customer/datapipelines/config/ScreenView$InApp : io/customer/datapipelines/config/ScreenView {
public static final field INSTANCE Lio/customer/datapipelines/config/ScreenView$InApp;
}

public final class io/customer/datapipelines/extensions/JsonExtensionsKt {
public static final fun toJsonArray (Lorg/json/JSONArray;)Lkotlinx/serialization/json/JsonArray;
public static final fun toJsonObject (Lorg/json/JSONObject;)Lkotlinx/serialization/json/JsonObject;
Expand Down Expand Up @@ -183,6 +204,7 @@ public final class io/customer/sdk/CustomerIOBuilder {
public final fun logLevel (Lio/customer/sdk/core/util/CioLogLevel;)Lio/customer/sdk/CustomerIOBuilder;
public final fun migrationSiteId (Ljava/lang/String;)Lio/customer/sdk/CustomerIOBuilder;
public final fun region (Lio/customer/sdk/data/model/Region;)Lio/customer/sdk/CustomerIOBuilder;
public final fun screenViewUse (Lio/customer/datapipelines/config/ScreenView;)Lio/customer/sdk/CustomerIOBuilder;
public final fun trackApplicationLifecycleEvents (Z)Lio/customer/sdk/CustomerIOBuilder;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ class DataPipelinesModuleConfig(
// Track screen views for Activities
val autoTrackActivityScreens: Boolean,
// Configuration options required for migration from earlier versions
val migrationSiteId: String? = null
val migrationSiteId: String? = null,
// Determines how SDK should handle screen view events
val screenViewUse: ScreenView
) : CustomerIOModuleConfig {
val apiHost: String = apiHostOverride ?: region.apiHost()
val cdnHost: String = cdnHostOverride ?: region.cdnHost()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.customer.datapipelines.config

/**
* Enum class to define how CustomerIO SDK should handle screen view events.
*/
sealed class ScreenView(val name: String) {
/**
* Screen view events are sent to destinations for analytics purposes.
* They are also used to display in-app messages based on page rules.
*/
object All : ScreenView(name = "all")

/**
* Screen view events are kept on device only. They are used to display in-app messages based on
* page rules. Events are not sent to our back end servers.
*/

object InApp : ScreenView(name = "inapp")

companion object {
/**
* Returns the [ScreenView] enum constant for the given name.
* Returns fallback if the specified enum type has no constant with the given name.
* Defaults to [All].
*/
@JvmOverloads
fun getScreenView(screenView: String?, fallback: ScreenView = All): ScreenView {
if (screenView.isNullOrBlank()) {
return fallback
}

return listOf(
All,
InApp
).find { value -> value.name.equals(screenView, ignoreCase = true) } ?: fallback
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.customer.datapipelines.plugins

import com.segment.analytics.kotlin.core.Analytics
import com.segment.analytics.kotlin.core.BaseEvent
import com.segment.analytics.kotlin.core.ScreenEvent
import com.segment.analytics.kotlin.core.platform.EventPlugin
import com.segment.analytics.kotlin.core.platform.Plugin
import io.customer.datapipelines.config.ScreenView

/**
* Plugin to filter screen events based on the configuration provided by customer app.
* This plugin is used to filter out screen events that should not be processed further.
*/
internal class ScreenFilterPlugin(private val screenViewUse: ScreenView) : EventPlugin {
override lateinit var analytics: Analytics
override val type: Plugin.Type = Plugin.Type.Enrichment

override fun screen(payload: ScreenEvent): BaseEvent? {
// Filter out screen events based on the configuration provided by customer app
// Using when expression so it enforce right check for all possible values of ScreenView in future
return when (screenViewUse) {
ScreenView.All -> payload
// Do not send screen events to server if ScreenView is not Analytics
ScreenView.InApp -> null
}
}
}
4 changes: 4 additions & 0 deletions datapipelines/src/main/kotlin/io/customer/sdk/CustomerIO.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import io.customer.datapipelines.plugins.AutomaticActivityScreenTrackingPlugin
import io.customer.datapipelines.plugins.ContextPlugin
import io.customer.datapipelines.plugins.CustomerIODestination
import io.customer.datapipelines.plugins.DataPipelinePublishedEvents
import io.customer.datapipelines.plugins.ScreenFilterPlugin
import io.customer.datapipelines.util.EventNames
import io.customer.sdk.communication.Event
import io.customer.sdk.communication.subscribe
Expand Down Expand Up @@ -121,6 +122,9 @@ class CustomerIO private constructor(
// Add plugin to publish events to EventBus for other modules to consume
analytics.add(DataPipelinePublishedEvents())

// Add plugin to filter events based on SDK configuration
analytics.add(ScreenFilterPlugin(moduleConfig.screenViewUse))

// subscribe to journey events emitted from push/in-app module to send them via data pipelines
subscribeToJourneyEvents()
// if profile is already identified, republish identifier for late-added modules.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.customer.sdk
import android.app.Application
import com.segment.analytics.kotlin.core.platform.policies.FlushPolicy
import io.customer.datapipelines.config.DataPipelinesModuleConfig
import io.customer.datapipelines.config.ScreenView
import io.customer.sdk.core.di.SDKComponent
import io.customer.sdk.core.di.setupAndroidComponent
import io.customer.sdk.core.module.CustomerIOModule
Expand Down Expand Up @@ -69,6 +70,9 @@ class CustomerIOBuilder(
// Configuration options required for migration from earlier versions
private var migrationSiteId: String? = null

// Determines how SDK should handle screen view events
private var screenViewUse: ScreenView = ScreenView.All

/**
* Specifies the log level for the SDK.
* Default value is [CioLogLevel.ERROR].
Expand Down Expand Up @@ -167,6 +171,16 @@ class CustomerIOBuilder(
return this
}

/**
* Set the screen view configuration for the SDK.
*
* @see ScreenView for more details.
*/
fun screenViewUse(screenView: ScreenView): CustomerIOBuilder {
this.screenViewUse = screenView
return this
}

fun <Config : CustomerIOModuleConfig> addCustomerIOModule(module: CustomerIOModule<Config>): CustomerIOBuilder {
registeredModules.add(module)
return this
Expand All @@ -191,7 +205,8 @@ class CustomerIOBuilder(
trackApplicationLifecycleEvents = trackApplicationLifecycleEvents,
autoTrackDeviceAttributes = autoTrackDeviceAttributes,
autoTrackActivityScreens = autoTrackActivityScreens,
migrationSiteId = migrationSiteId
migrationSiteId = migrationSiteId,
screenViewUse = screenViewUse
)

// Initialize CustomerIO instance before initializing the modules
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.customer.datapipelines.config

import io.customer.commontest.core.JUnit5Test
import org.amshove.kluent.shouldBeEqualTo
import org.junit.jupiter.api.Test

class ScreenViewTest : JUnit5Test() {
@Test
fun getScreenView_givenNamesWithMatchingCase_expectCorrectScreenView() {
val screenViewAnalytics = ScreenView.getScreenView("All")
val screenViewInApp = ScreenView.getScreenView("InApp")

screenViewAnalytics shouldBeEqualTo ScreenView.All
screenViewInApp shouldBeEqualTo ScreenView.InApp
}

@Test
fun getScreenView_givenNamesWithDifferentCase_expectCorrectScreenView() {
val screenViewAnalytics = ScreenView.getScreenView("all")
val screenViewInApp = ScreenView.getScreenView("inapp")

screenViewAnalytics shouldBeEqualTo ScreenView.All
screenViewInApp shouldBeEqualTo ScreenView.InApp
}

@Test
fun getScreenView_givenInvalidValue_expectFallbackScreenView() {
val parsedValue = ScreenView.getScreenView("none")

parsedValue shouldBeEqualTo ScreenView.All
}

@Test
fun getScreenView_givenEmptyValue_expectFallbackScreenView() {
val parsedValue = ScreenView.getScreenView(screenView = "", fallback = ScreenView.InApp)

parsedValue shouldBeEqualTo ScreenView.InApp
}

@Test
fun getScreenView_givenNull_expectFallbackScreenView() {
val parsedValue = ScreenView.getScreenView(null)

parsedValue shouldBeEqualTo ScreenView.All
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package io.customer.datapipelines.plugins

import io.customer.commontest.config.TestConfig
import io.customer.commontest.extensions.random
import io.customer.datapipelines.config.ScreenView
import io.customer.datapipelines.testutils.core.DataPipelinesTestConfig
import io.customer.datapipelines.testutils.core.JUnitTest
import io.customer.datapipelines.testutils.core.testConfiguration
import io.customer.datapipelines.testutils.extensions.shouldMatchTo
import io.customer.datapipelines.testutils.utils.OutputReaderPlugin
import io.customer.datapipelines.testutils.utils.screenEvents
import io.customer.sdk.data.model.CustomAttributes
import org.amshove.kluent.shouldBeEmpty
import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldHaveSingleItem
import org.junit.jupiter.api.Test

class ScreenFilterPluginTest : JUnitTest() {
private lateinit var outputReaderPlugin: OutputReaderPlugin

override fun setup(testConfig: TestConfig) {
// Keep setup empty to avoid calling super.setup() as it will initialize the SDK
// and we want to test the SDK with different configurations in each test
}

private fun setupWithConfig(screenViewUse: ScreenView, testConfig: DataPipelinesTestConfig = testConfiguration {}) {
super.setup(
testConfiguration {
analytics { add(ScreenFilterPlugin(screenViewUse = screenViewUse)) }
} + testConfig
)

outputReaderPlugin = OutputReaderPlugin()
analytics.add(outputReaderPlugin)
}

@Test
fun process_givenScreenViewUseAnalytics_expectScreenEventWithoutPropertiesProcessed() {
setupWithConfig(screenViewUse = ScreenView.All)

val givenScreenTitle = String.random
sdkInstance.screen(givenScreenTitle)

val result = outputReaderPlugin.screenEvents.shouldHaveSingleItem()
result.name shouldBeEqualTo givenScreenTitle
result.properties.shouldBeEmpty()
}

@Test
fun process_givenScreenViewUseAnalytics_expectScreenEventWithPropertiesProcessed() {
setupWithConfig(screenViewUse = ScreenView.All)

val givenScreenTitle = String.random
val givenProperties: CustomAttributes = mapOf("source" to "push", "discount" to 10)
sdkInstance.screen(givenScreenTitle, givenProperties)

val screenEvent = outputReaderPlugin.screenEvents.shouldHaveSingleItem()
screenEvent.name shouldBeEqualTo givenScreenTitle
screenEvent.properties shouldMatchTo givenProperties
}

@Test
fun process_givenScreenViewUseInApp_expectAllScreenEventsIgnored() {
setupWithConfig(screenViewUse = ScreenView.InApp)

for (i in 1..5) {
sdkInstance.screen(String.random)
}

outputReaderPlugin.allEvents.shouldBeEmpty()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import io.customer.commontest.extensions.assertCalledNever
import io.customer.commontest.extensions.assertCalledOnce
import io.customer.commontest.extensions.random
import io.customer.commontest.module.CustomerIOGenericModule
import io.customer.datapipelines.config.ScreenView
import io.customer.datapipelines.plugins.AutomaticActivityScreenTrackingPlugin
import io.customer.datapipelines.plugins.CustomerIODestination
import io.customer.datapipelines.plugins.DataPipelinePublishedEvents
import io.customer.datapipelines.plugins.ScreenFilterPlugin
import io.customer.sdk.CustomerIO
import io.customer.sdk.CustomerIOBuilder
import io.customer.sdk.core.di.SDKComponent
Expand Down Expand Up @@ -105,13 +107,15 @@ class CustomerIOBuilderTest : RobolectricTest() {
dataPipelinesModuleConfig.apiHost shouldBe "cdp.customer.io/v1"
dataPipelinesModuleConfig.cdnHost shouldBe "cdp.customer.io/v1"
dataPipelinesModuleConfig.autoAddCustomerIODestination shouldBe true
dataPipelinesModuleConfig.screenViewUse shouldBe ScreenView.All
}

@Test
fun build_givenConfiguration_expectSameDataPipelinesModuleConfig() {
val givenCdpApiKey = String.random
val givenMigrationSiteId = String.random
val givenRegion = Region.EU
val givenScreenViewUse = ScreenView.InApp

createCustomerIOBuilder(givenCdpApiKey)
.logLevel(CioLogLevel.DEBUG)
Expand All @@ -123,6 +127,7 @@ class CustomerIOBuilderTest : RobolectricTest() {
.flushAt(100)
.flushInterval(2)
.flushPolicies(emptyList())
.screenViewUse(givenScreenViewUse)
.build()

// verify the customerIOBuilder config with DataPipelinesModuleConfig
Expand All @@ -137,6 +142,7 @@ class CustomerIOBuilderTest : RobolectricTest() {
dataPipelinesModuleConfig.flushInterval shouldBe 2
dataPipelinesModuleConfig.apiHost shouldBe "cdp-eu.customer.io/v1"
dataPipelinesModuleConfig.cdnHost shouldBe "cdp-eu.customer.io/v1"
dataPipelinesModuleConfig.screenViewUse shouldBe givenScreenViewUse

// verify the shared logger has updated log level
SDKComponent.logger.logLevel shouldBe CioLogLevel.DEBUG
Expand Down Expand Up @@ -165,6 +171,13 @@ class CustomerIOBuilderTest : RobolectricTest() {
CustomerIO.instance().analytics.find(AutomaticActivityScreenTrackingPlugin::class) shouldBe null
}

@Test
fun build_givenModuleInitialized_expectScreenFilterPluginPluginAdded() {
createCustomerIOBuilder().build()

CustomerIO.instance().analytics.find(ScreenFilterPlugin::class) shouldNotBe null
}

@Test
fun build_givenHostConfiguration_expectCorrectHostDataPipelinesModuleConfig() {
val givenRegion = Region.EU
Expand Down
Loading

0 comments on commit b5ea8e9

Please sign in to comment.