From e2c2d8677a0693e1ec3fe2c6148322b2c74d786c Mon Sep 17 00:00:00 2001 From: thiago Date: Sun, 22 Sep 2024 21:42:32 -0300 Subject: [PATCH 1/5] chore: add support to set screen name to all event --- posthog/src/main/java/com/posthog/PostHog.kt | 8 ++++ .../src/test/java/com/posthog/PostHogTest.kt | 40 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/posthog/src/main/java/com/posthog/PostHog.kt b/posthog/src/main/java/com/posthog/PostHog.kt index e4372300..defc18d2 100644 --- a/posthog/src/main/java/com/posthog/PostHog.kt +++ b/posthog/src/main/java/com/posthog/PostHog.kt @@ -68,6 +68,8 @@ public class PostHog private constructor( private var isIdentifiedLoaded: Boolean = false private var isPersonProcessingLoaded: Boolean = false + private lateinit var currentScreen: String + public override fun setup(config: T) { synchronized(setupLock) { try { @@ -317,6 +319,10 @@ public class PostHog private constructor( } } + currentScreen.let { + props["\$screen_name"] = currentScreen + } + userProperties?.let { props["\$set"] = it } @@ -506,6 +512,8 @@ public class PostHog private constructor( val props = mutableMapOf() props["\$screen_name"] = screenTitle + currentScreen = screenTitle + properties?.let { props.putAll(it) } diff --git a/posthog/src/test/java/com/posthog/PostHogTest.kt b/posthog/src/test/java/com/posthog/PostHogTest.kt index fbfd49e6..ef4d9c3e 100644 --- a/posthog/src/test/java/com/posthog/PostHogTest.kt +++ b/posthog/src/test/java/com/posthog/PostHogTest.kt @@ -1044,6 +1044,46 @@ internal class PostHogTest { sut.close() } + @Test + fun `captures screen event and alias event with screen_name`() { + val http = mockHttp() + val url = http.url("/") + + val sut = getSut(url.toString(), preloadFeatureFlags = false) + + val screenName = "HomeScreen" + + val alias = "UserAlias" + sut.screen(screenName) + + sut.alias(alias) + + queueExecutor.shutdownAndAwaitTermination() + + var request = http.takeRequest() + + assertEquals(2, http.requestCount) + + var content = request.body.unGzip() + var batch = serializer.deserialize(content.reader()) + + var theEvent = batch.batch.first() + + assertEquals(screenName, theEvent.properties!!["\$screen_name"]) + + request = http.takeRequest() + + assertEquals(2, http.requestCount) + + content = request.body.unGzip() + batch = serializer.deserialize(content.reader()) + + theEvent = batch.batch.first() + assertEquals(alias, theEvent.properties!!["alias"]) + assertEquals(screenName, theEvent.properties!!["\$screen_name"]) + sut.close() + } + @Test fun `reset session id when reset is called`() { val http = mockHttp() From 8bf3765483f2c3723128b1a4add25e1258d81a86 Mon Sep 17 00:00:00 2001 From: thiago Date: Thu, 26 Sep 2024 04:06:13 -0300 Subject: [PATCH 2/5] chore: added screen processor --- .../java/com/posthog/android/PostHogAndroid.kt | 5 +++++ ...tHogActivityLifecycleCallbackIntegration.kt | 1 + .../android/internal/PostHogScreenProcessor.kt | 17 +++++++++++++++++ .../posthog/android/internal/ScreenTracker.kt | 13 +++++++++++++ posthog/src/main/java/com/posthog/PostHog.kt | 16 +++++++--------- .../src/main/java/com/posthog/PostHogConfig.kt | 18 ++++++++++++++++++ .../com/posthog/PostHogPropertiesProcessor.kt | 5 +++++ 7 files changed, 66 insertions(+), 9 deletions(-) create mode 100644 posthog-android/src/main/java/com/posthog/android/internal/PostHogScreenProcessor.kt create mode 100644 posthog-android/src/main/java/com/posthog/android/internal/ScreenTracker.kt create mode 100644 posthog/src/main/java/com/posthog/PostHogPropertiesProcessor.kt diff --git a/posthog-android/src/main/java/com/posthog/android/PostHogAndroid.kt b/posthog-android/src/main/java/com/posthog/android/PostHogAndroid.kt index 76e12dd2..2c08f2a3 100644 --- a/posthog-android/src/main/java/com/posthog/android/PostHogAndroid.kt +++ b/posthog-android/src/main/java/com/posthog/android/PostHogAndroid.kt @@ -11,6 +11,7 @@ import com.posthog.android.internal.PostHogAndroidLogger import com.posthog.android.internal.PostHogAndroidNetworkStatus import com.posthog.android.internal.PostHogAppInstallIntegration import com.posthog.android.internal.PostHogLifecycleObserverIntegration +import com.posthog.android.internal.PostHogScreenProcessor import com.posthog.android.internal.PostHogSharedPreferences import com.posthog.android.internal.appContext import com.posthog.android.replay.PostHogReplayIntegration @@ -88,6 +89,10 @@ public class PostHogAndroid private constructor() { if (config.captureDeepLinks || config.captureScreenViews || config.sessionReplay) { config.addIntegration(PostHogActivityLifecycleCallbackIntegration(context, config)) } + // if the processor depends on captureScreenViews, only adds if its enabled + if (config.captureScreenViews) { + config.addProcessor(PostHogScreenProcessor()) + } } if (config.captureApplicationLifecycleEvents) { config.addIntegration(PostHogAppInstallIntegration(context, config)) diff --git a/posthog-android/src/main/java/com/posthog/android/internal/PostHogActivityLifecycleCallbackIntegration.kt b/posthog-android/src/main/java/com/posthog/android/internal/PostHogActivityLifecycleCallbackIntegration.kt index 9bf75dc0..6a5f4931 100644 --- a/posthog-android/src/main/java/com/posthog/android/internal/PostHogActivityLifecycleCallbackIntegration.kt +++ b/posthog-android/src/main/java/com/posthog/android/internal/PostHogActivityLifecycleCallbackIntegration.kt @@ -47,6 +47,7 @@ internal class PostHogActivityLifecycleCallbackIntegration( if (!screenName.isNullOrEmpty()) { PostHog.screen(screenName) + ScreenTracker.setCurrentScreen(screenName) } } } diff --git a/posthog-android/src/main/java/com/posthog/android/internal/PostHogScreenProcessor.kt b/posthog-android/src/main/java/com/posthog/android/internal/PostHogScreenProcessor.kt new file mode 100644 index 00000000..2bca34fc --- /dev/null +++ b/posthog-android/src/main/java/com/posthog/android/internal/PostHogScreenProcessor.kt @@ -0,0 +1,17 @@ +package com.posthog.android.internal + +import com.posthog.PostHogPropertiesProcessor + +internal class PostHogScreenProcessor : PostHogPropertiesProcessor { + override fun process(properties: MutableMap): Map { + if (properties.containsKey("\$screen_name")) { + return properties + } + + val currentScreen = ScreenTracker.getCurrentScreenName() + + properties["\$screen_name"] = currentScreen + + return properties + } +} diff --git a/posthog-android/src/main/java/com/posthog/android/internal/ScreenTracker.kt b/posthog-android/src/main/java/com/posthog/android/internal/ScreenTracker.kt new file mode 100644 index 00000000..41ba9f76 --- /dev/null +++ b/posthog-android/src/main/java/com/posthog/android/internal/ScreenTracker.kt @@ -0,0 +1,13 @@ +package com.posthog.android.internal + +public object ScreenTracker { + @Volatile private lateinit var currentScreen: String + + public fun setCurrentScreen(screenName: String) { + currentScreen = screenName + } + + public fun getCurrentScreenName(): String { + return currentScreen + } +} diff --git a/posthog/src/main/java/com/posthog/PostHog.kt b/posthog/src/main/java/com/posthog/PostHog.kt index defc18d2..03141f11 100644 --- a/posthog/src/main/java/com/posthog/PostHog.kt +++ b/posthog/src/main/java/com/posthog/PostHog.kt @@ -68,8 +68,6 @@ public class PostHog private constructor( private var isIdentifiedLoaded: Boolean = false private var isPersonProcessingLoaded: Boolean = false - private lateinit var currentScreen: String - public override fun setup(config: T) { synchronized(setupLock) { try { @@ -319,10 +317,6 @@ public class PostHog private constructor( } } - currentScreen.let { - props["\$screen_name"] = currentScreen - } - userProperties?.let { props["\$set"] = it } @@ -447,8 +441,14 @@ public class PostHog private constructor( appendGroups = !groupIdentify, ) + var currentProperties = mergedProperties + // has to run before the sanitizer so people can remove stuff processors add + config?.processors?.forEach { + currentProperties = it.process(currentProperties.toMutableMap()) + } + // sanitize the properties or fallback to the original properties - val sanitizedProperties = config?.propertiesSanitizer?.sanitize(mergedProperties.toMutableMap()) ?: mergedProperties + val sanitizedProperties = config?.propertiesSanitizer?.sanitize(currentProperties.toMutableMap()) ?: currentProperties val postHogEvent = PostHogEvent( @@ -512,8 +512,6 @@ public class PostHog private constructor( val props = mutableMapOf() props["\$screen_name"] = screenTitle - currentScreen = screenTitle - properties?.let { props.putAll(it) } diff --git a/posthog/src/main/java/com/posthog/PostHogConfig.kt b/posthog/src/main/java/com/posthog/PostHogConfig.kt index 7cae8f72..b045e71b 100644 --- a/posthog/src/main/java/com/posthog/PostHogConfig.kt +++ b/posthog/src/main/java/com/posthog/PostHogConfig.kt @@ -156,6 +156,9 @@ public open class PostHogConfig( private val integrationsList: MutableList = mutableListOf() private val integrationLock = Any() + private val processorsList: MutableList = mutableListOf() + private val processorsLock = Any() + /** * The integrations list */ @@ -188,6 +191,21 @@ public open class PostHogConfig( } } + public val processors: List + get() { + val list: List + synchronized(processorsLock) { + list = processorsList.toList() + } + return list + } + + public fun addProcessor(processor: PostHogPropertiesProcessor) { + synchronized(processorsLock) { + processorsList.add(processor) + } + } + public companion object { public const val DEFAULT_HOST: String = "https://us.i.posthog.com" } diff --git a/posthog/src/main/java/com/posthog/PostHogPropertiesProcessor.kt b/posthog/src/main/java/com/posthog/PostHogPropertiesProcessor.kt new file mode 100644 index 00000000..8a6c7970 --- /dev/null +++ b/posthog/src/main/java/com/posthog/PostHogPropertiesProcessor.kt @@ -0,0 +1,5 @@ +package com.posthog + +public fun interface PostHogPropertiesProcessor { + public fun process(properties: MutableMap): Map +} From 1858fcd2da401c3f5c40ceb2ce73c787ebbc0101 Mon Sep 17 00:00:00 2001 From: thiago Date: Thu, 28 Nov 2024 07:01:52 -0300 Subject: [PATCH 3/5] chore: add unit tests and fix Processor classes modules --- .../com/posthog/android/PostHogAndroid.kt | 2 +- .../com/posthog}/PostHogScreenProcessor.kt | 6 +- .../main/java/com/posthog}/ScreenTracker.kt | 2 +- .../src/test/java/com/posthog/PostHogTest.kt | 99 +++++++++++-------- 4 files changed, 64 insertions(+), 45 deletions(-) rename {posthog-android/src/main/java/com/posthog/android/internal => posthog/src/main/java/com/posthog}/PostHogScreenProcessor.kt (68%) rename {posthog-android/src/main/java/com/posthog/android/internal => posthog/src/main/java/com/posthog}/ScreenTracker.kt (87%) diff --git a/posthog-android/src/main/java/com/posthog/android/PostHogAndroid.kt b/posthog-android/src/main/java/com/posthog/android/PostHogAndroid.kt index 2c08f2a3..c1e64711 100644 --- a/posthog-android/src/main/java/com/posthog/android/PostHogAndroid.kt +++ b/posthog-android/src/main/java/com/posthog/android/PostHogAndroid.kt @@ -4,6 +4,7 @@ import android.app.Application import android.content.Context import com.posthog.PostHog import com.posthog.PostHogInterface +import com.posthog.PostHogScreenProcessor import com.posthog.android.internal.MainHandler import com.posthog.android.internal.PostHogActivityLifecycleCallbackIntegration import com.posthog.android.internal.PostHogAndroidContext @@ -11,7 +12,6 @@ import com.posthog.android.internal.PostHogAndroidLogger import com.posthog.android.internal.PostHogAndroidNetworkStatus import com.posthog.android.internal.PostHogAppInstallIntegration import com.posthog.android.internal.PostHogLifecycleObserverIntegration -import com.posthog.android.internal.PostHogScreenProcessor import com.posthog.android.internal.PostHogSharedPreferences import com.posthog.android.internal.appContext import com.posthog.android.replay.PostHogReplayIntegration diff --git a/posthog-android/src/main/java/com/posthog/android/internal/PostHogScreenProcessor.kt b/posthog/src/main/java/com/posthog/PostHogScreenProcessor.kt similarity index 68% rename from posthog-android/src/main/java/com/posthog/android/internal/PostHogScreenProcessor.kt rename to posthog/src/main/java/com/posthog/PostHogScreenProcessor.kt index 2bca34fc..4b07d30b 100644 --- a/posthog-android/src/main/java/com/posthog/android/internal/PostHogScreenProcessor.kt +++ b/posthog/src/main/java/com/posthog/PostHogScreenProcessor.kt @@ -1,8 +1,6 @@ -package com.posthog.android.internal +package com.posthog -import com.posthog.PostHogPropertiesProcessor - -internal class PostHogScreenProcessor : PostHogPropertiesProcessor { +public class PostHogScreenProcessor : PostHogPropertiesProcessor { override fun process(properties: MutableMap): Map { if (properties.containsKey("\$screen_name")) { return properties diff --git a/posthog-android/src/main/java/com/posthog/android/internal/ScreenTracker.kt b/posthog/src/main/java/com/posthog/ScreenTracker.kt similarity index 87% rename from posthog-android/src/main/java/com/posthog/android/internal/ScreenTracker.kt rename to posthog/src/main/java/com/posthog/ScreenTracker.kt index 41ba9f76..1ae4e8d8 100644 --- a/posthog-android/src/main/java/com/posthog/android/internal/ScreenTracker.kt +++ b/posthog/src/main/java/com/posthog/ScreenTracker.kt @@ -1,4 +1,4 @@ -package com.posthog.android.internal +package com.posthog public object ScreenTracker { @Volatile private lateinit var currentScreen: String diff --git a/posthog/src/test/java/com/posthog/PostHogTest.kt b/posthog/src/test/java/com/posthog/PostHogTest.kt index ef4d9c3e..2e65b80c 100644 --- a/posthog/src/test/java/com/posthog/PostHogTest.kt +++ b/posthog/src/test/java/com/posthog/PostHogTest.kt @@ -45,6 +45,7 @@ internal class PostHogTest { reloadFeatureFlags: Boolean = true, sendFeatureFlagEvent: Boolean = true, integration: PostHogIntegration? = null, + processor: PostHogPropertiesProcessor? = null, cachePreferences: PostHogMemoryPreferences = PostHogMemoryPreferences(), propertiesSanitizer: PostHogPropertiesSanitizer? = null, ): PostHogInterface { @@ -58,6 +59,9 @@ internal class PostHogTest { if (integration != null) { addIntegration(integration) } + if (processor != null) { + addProcessor(processor) + } this.sendFeatureFlagEvent = sendFeatureFlagEvent this.cachePreferences = cachePreferences this.propertiesSanitizer = propertiesSanitizer @@ -1044,45 +1048,6 @@ internal class PostHogTest { sut.close() } - @Test - fun `captures screen event and alias event with screen_name`() { - val http = mockHttp() - val url = http.url("/") - - val sut = getSut(url.toString(), preloadFeatureFlags = false) - - val screenName = "HomeScreen" - - val alias = "UserAlias" - sut.screen(screenName) - - sut.alias(alias) - - queueExecutor.shutdownAndAwaitTermination() - - var request = http.takeRequest() - - assertEquals(2, http.requestCount) - - var content = request.body.unGzip() - var batch = serializer.deserialize(content.reader()) - - var theEvent = batch.batch.first() - - assertEquals(screenName, theEvent.properties!!["\$screen_name"]) - - request = http.takeRequest() - - assertEquals(2, http.requestCount) - - content = request.body.unGzip() - batch = serializer.deserialize(content.reader()) - - theEvent = batch.batch.first() - assertEquals(alias, theEvent.properties!!["alias"]) - assertEquals(screenName, theEvent.properties!!["\$screen_name"]) - sut.close() - } @Test fun `reset session id when reset is called`() { @@ -1168,4 +1133,60 @@ internal class PostHogTest { sut.close() } + + @Test + fun `processor does not override existing screen_name in event properties`() { + val http = mockHttp() + val url = http.url("/") + + val sut = getSut(url.toString(), preloadFeatureFlags = false, reloadFeatureFlags = false, processor = PostHogScreenProcessor()) + + ScreenTracker.setCurrentScreen("CurrentScreen") + + sut.capture( + "Test Event", + properties = mapOf( + "test_prop" to "test_value", + "\$screen_name" to "ProvidedScreen" + ) + ) + + queueExecutor.shutdownAndAwaitTermination() + + val request = http.takeRequest() + + val content = request.body.unGzip() + val batch = serializer.deserialize(content.reader()) + + val theEvent = batch.batch.first() + + assertEquals("ProvidedScreen", theEvent.properties?.get("\$screen_name")) + + sut.close() + } + + @Test + fun `processor adds screen_name to event properties`() { + val http = mockHttp() + val url = http.url("/") + + val sut = getSut(url.toString(), preloadFeatureFlags = false, reloadFeatureFlags = false, processor = PostHogScreenProcessor()) + + ScreenTracker.setCurrentScreen("TestScreen") + + sut.capture("Test Event", properties = mapOf("test_prop" to "test_value")) + + queueExecutor.shutdownAndAwaitTermination() + + val request = http.takeRequest() + + val content = request.body.unGzip() + val batch = serializer.deserialize(content.reader()) + + val theEvent = batch.batch.first() + + assertEquals("TestScreen", theEvent.properties?.get("\$screen_name")) + + sut.close() + } } From e75d5656ec6eb0681a7260812385353b12ea3d8f Mon Sep 17 00:00:00 2001 From: thiago Date: Thu, 28 Nov 2024 07:08:42 -0300 Subject: [PATCH 4/5] fix format --- posthog/src/test/java/com/posthog/PostHogTest.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/posthog/src/test/java/com/posthog/PostHogTest.kt b/posthog/src/test/java/com/posthog/PostHogTest.kt index 2e65b80c..6722e219 100644 --- a/posthog/src/test/java/com/posthog/PostHogTest.kt +++ b/posthog/src/test/java/com/posthog/PostHogTest.kt @@ -1048,7 +1048,6 @@ internal class PostHogTest { sut.close() } - @Test fun `reset session id when reset is called`() { val http = mockHttp() @@ -1145,10 +1144,11 @@ internal class PostHogTest { sut.capture( "Test Event", - properties = mapOf( - "test_prop" to "test_value", - "\$screen_name" to "ProvidedScreen" - ) + properties = + mapOf( + "test_prop" to "test_value", + "\$screen_name" to "ProvidedScreen", + ), ) queueExecutor.shutdownAndAwaitTermination() From b765efbaf8138c66c714b9f712795ad22504bc99 Mon Sep 17 00:00:00 2001 From: Thiago-Santos-SI Date: Tue, 24 Dec 2024 00:49:48 -0300 Subject: [PATCH 5/5] chore: generate api --- ...stHogActivityLifecycleCallbackIntegration.kt | 1 + posthog/api/posthog.api | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/posthog-android/src/main/java/com/posthog/android/internal/PostHogActivityLifecycleCallbackIntegration.kt b/posthog-android/src/main/java/com/posthog/android/internal/PostHogActivityLifecycleCallbackIntegration.kt index 6a5f4931..350001d2 100644 --- a/posthog-android/src/main/java/com/posthog/android/internal/PostHogActivityLifecycleCallbackIntegration.kt +++ b/posthog-android/src/main/java/com/posthog/android/internal/PostHogActivityLifecycleCallbackIntegration.kt @@ -6,6 +6,7 @@ import android.app.Application.ActivityLifecycleCallbacks import android.os.Bundle import com.posthog.PostHog import com.posthog.PostHogIntegration +import com.posthog.ScreenTracker import com.posthog.android.PostHogAndroidConfig /** diff --git a/posthog/api/posthog.api b/posthog/api/posthog.api index bfe07b5c..836cdbfe 100644 --- a/posthog/api/posthog.api +++ b/posthog/api/posthog.api @@ -75,6 +75,7 @@ public class com/posthog/PostHogConfig { public fun (Ljava/lang/String;Ljava/lang/String;ZZZZIIIILcom/posthog/PostHogEncryption;Lcom/posthog/PostHogOnFeatureFlags;ZLcom/posthog/PostHogPropertiesSanitizer;Lkotlin/jvm/functions/Function1;Lcom/posthog/PersonProfiles;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;ZZZZIIIILcom/posthog/PostHogEncryption;Lcom/posthog/PostHogOnFeatureFlags;ZLcom/posthog/PostHogPropertiesSanitizer;Lkotlin/jvm/functions/Function1;Lcom/posthog/PersonProfiles;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun addIntegration (Lcom/posthog/PostHogIntegration;)V + public final fun addProcessor (Lcom/posthog/PostHogPropertiesProcessor;)V public final fun getApiKey ()Ljava/lang/String; public final fun getCachePreferences ()Lcom/posthog/internal/PostHogPreferences; public final fun getContext ()Lcom/posthog/internal/PostHogContext; @@ -95,6 +96,7 @@ public class com/posthog/PostHogConfig { public final fun getOptOut ()Z public final fun getPersonProfiles ()Lcom/posthog/PersonProfiles; public final fun getPreloadFeatureFlags ()Z + public final fun getProcessors ()Ljava/util/List; public final fun getPropertiesSanitizer ()Lcom/posthog/PostHogPropertiesSanitizer; public final fun getReplayStoragePrefix ()Ljava/lang/String; public final fun getSdkName ()Ljava/lang/String; @@ -236,13 +238,28 @@ public abstract interface class com/posthog/PostHogOnFeatureFlags { public abstract fun loaded ()V } +public abstract interface class com/posthog/PostHogPropertiesProcessor { + public abstract fun process (Ljava/util/Map;)Ljava/util/Map; +} + public abstract interface class com/posthog/PostHogPropertiesSanitizer { public abstract fun sanitize (Ljava/util/Map;)Ljava/util/Map; } +public final class com/posthog/PostHogScreenProcessor : com/posthog/PostHogPropertiesProcessor { + public fun ()V + public fun process (Ljava/util/Map;)Ljava/util/Map; +} + public abstract interface annotation class com/posthog/PostHogVisibleForTesting : java/lang/annotation/Annotation { } +public final class com/posthog/ScreenTracker { + public static final field INSTANCE Lcom/posthog/ScreenTracker; + public final fun getCurrentScreenName ()Ljava/lang/String; + public final fun setCurrentScreen (Ljava/lang/String;)V +} + public abstract interface class com/posthog/internal/PostHogContext { public abstract fun getDynamicContext ()Ljava/util/Map; public abstract fun getSdkInfo ()Ljava/util/Map;