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

chore: add support to set screen name to all event #181

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand Down Expand Up @@ -47,6 +48,7 @@ internal class PostHogActivityLifecycleCallbackIntegration(

if (!screenName.isNullOrEmpty()) {
PostHog.screen(screenName)
ScreenTracker.setCurrentScreen(screenName)
}
}
}
Expand Down
17 changes: 17 additions & 0 deletions posthog/api/posthog.api
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public class com/posthog/PostHogConfig {
public fun <init> (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 <init> (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;
Expand All @@ -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;
Expand Down Expand Up @@ -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 <init> ()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;
Expand Down
8 changes: 7 additions & 1 deletion posthog/src/main/java/com/posthog/PostHog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -441,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(
Expand Down
18 changes: 18 additions & 0 deletions posthog/src/main/java/com/posthog/PostHogConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ public open class PostHogConfig(
private val integrationsList: MutableList<PostHogIntegration> = mutableListOf()
private val integrationLock = Any()

private val processorsList: MutableList<PostHogPropertiesProcessor> = mutableListOf()
private val processorsLock = Any()

/**
* The integrations list
*/
Expand Down Expand Up @@ -188,6 +191,21 @@ public open class PostHogConfig(
}
}

public val processors: List<PostHogPropertiesProcessor>
get() {
val list: List<PostHogPropertiesProcessor>
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"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.posthog

public fun interface PostHogPropertiesProcessor {
public fun process(properties: MutableMap<String, Any>): Map<String, Any>
}
15 changes: 15 additions & 0 deletions posthog/src/main/java/com/posthog/PostHogScreenProcessor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.posthog

public class PostHogScreenProcessor : PostHogPropertiesProcessor {
override fun process(properties: MutableMap<String, Any>): Map<String, Any> {
if (properties.containsKey("\$screen_name")) {
return properties
}

val currentScreen = ScreenTracker.getCurrentScreenName()

properties["\$screen_name"] = currentScreen

return properties
}
}
13 changes: 13 additions & 0 deletions posthog/src/main/java/com/posthog/ScreenTracker.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.posthog

public object ScreenTracker {
@Volatile private lateinit var currentScreen: String

public fun setCurrentScreen(screenName: String) {
currentScreen = screenName
}

public fun getCurrentScreenName(): String {
return currentScreen
}
}
61 changes: 61 additions & 0 deletions posthog/src/test/java/com/posthog/PostHogTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -1128,4 +1132,61 @@ 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<PostHogBatchEvent>(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<PostHogBatchEvent>(content.reader())

val theEvent = batch.batch.first()

assertEquals("TestScreen", theEvent.properties?.get("\$screen_name"))

sut.close()
}
}