Skip to content

Commit

Permalink
add support to person profiles (#171)
Browse files Browse the repository at this point in the history
  • Loading branch information
thisames authored Sep 11, 2024
1 parent b35a38e commit 1dca934
Show file tree
Hide file tree
Showing 8 changed files with 479 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Next

- chore: add personProfiles support ([#171](https://github.com/PostHog/posthog-android/pull/171))

## 3.6.1 - 2024-08-30

- fix: do not clear events when reset is called ([#170](https://github.com/PostHog/posthog-android/pull/170))
Expand Down
15 changes: 13 additions & 2 deletions posthog-android/lint-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<issue
id="GradleDependency"
message="A newer version of androidx.lifecycle:lifecycle-process than 2.6.2 is available: 2.8.4"
message="A newer version of androidx.lifecycle:lifecycle-process than 2.6.2 is available: 2.8.5"
errorLine1=" implementation(&quot;androidx.lifecycle:lifecycle-process:${PosthogBuildConfig.Dependencies.LIFECYCLE}&quot;)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
Expand All @@ -14,7 +14,7 @@

<issue
id="GradleDependency"
message="A newer version of androidx.lifecycle:lifecycle-common-java8 than 2.6.2 is available: 2.8.4"
message="A newer version of androidx.lifecycle:lifecycle-common-java8 than 2.6.2 is available: 2.8.5"
errorLine1=" implementation(&quot;androidx.lifecycle:lifecycle-common-java8:${PosthogBuildConfig.Dependencies.LIFECYCLE}&quot;)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
Expand All @@ -34,4 +34,15 @@
column="20"/>
</issue>

<issue
id="GradleDependency"
message="A newer version of org.mockito:mockito-inline than 4.11.0 is available: 5.2.0"
errorLine1=" testImplementation(&quot;org.mockito:mockito-inline:${PosthogBuildConfig.Dependencies.MOCKITO_INLINE}&quot;)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle.kts"
line="107"
column="24"/>
</issue>

</issues>
14 changes: 12 additions & 2 deletions posthog/api/posthog.api
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ public final class com/posthog/PostHog$Companion : com/posthog/PostHogInterface
public class com/posthog/PostHogConfig {
public static final field Companion Lcom/posthog/PostHogConfig$Companion;
public static final field DEFAULT_HOST Ljava/lang/String;
public fun <init> (Ljava/lang/String;Ljava/lang/String;ZZZZIIIILcom/posthog/PostHogEncryption;Lcom/posthog/PostHogOnFeatureFlags;ZLcom/posthog/PostHogPropertiesSanitizer;Lkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;ZZZZIIIILcom/posthog/PostHogEncryption;Lcom/posthog/PostHogOnFeatureFlags;ZLcom/posthog/PostHogPropertiesSanitizer;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Ljava/lang/String;Ljava/lang/String;ZZZZIIIILcom/posthog/PostHogEncryption;Lcom/posthog/PostHogOnFeatureFlags;ZLcom/posthog/PostHogPropertiesSanitizer;Lkotlin/jvm/functions/Function1;Lcom/posthog/internal/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/internal/PersonProfiles;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun addIntegration (Lcom/posthog/PostHogIntegration;)V
public final fun getApiKey ()Ljava/lang/String;
public final fun getCachePreferences ()Lcom/posthog/internal/PostHogPreferences;
Expand All @@ -83,6 +83,7 @@ public class com/posthog/PostHogConfig {
public final fun getNetworkStatus ()Lcom/posthog/internal/PostHogNetworkStatus;
public final fun getOnFeatureFlags ()Lcom/posthog/PostHogOnFeatureFlags;
public final fun getOptOut ()Z
public final fun getPersonProfiles ()Lcom/posthog/internal/PersonProfiles;
public final fun getPreloadFeatureFlags ()Z
public final fun getPropertiesSanitizer ()Lcom/posthog/PostHogPropertiesSanitizer;
public final fun getReplayStoragePrefix ()Ljava/lang/String;
Expand All @@ -109,6 +110,7 @@ public class com/posthog/PostHogConfig {
public final fun setNetworkStatus (Lcom/posthog/internal/PostHogNetworkStatus;)V
public final fun setOnFeatureFlags (Lcom/posthog/PostHogOnFeatureFlags;)V
public final fun setOptOut (Z)V
public final fun setPersonProfiles (Lcom/posthog/internal/PersonProfiles;)V
public final fun setPreloadFeatureFlags (Z)V
public final fun setPropertiesSanitizer (Lcom/posthog/PostHogPropertiesSanitizer;)V
public final fun setReplayStoragePrefix (Ljava/lang/String;)V
Expand Down Expand Up @@ -230,6 +232,14 @@ public abstract interface class com/posthog/PostHogPropertiesSanitizer {
public abstract interface annotation class com/posthog/PostHogVisibleForTesting : java/lang/annotation/Annotation {
}

public final class com/posthog/internal/PersonProfiles : java/lang/Enum {
public static final field ALWAYS Lcom/posthog/internal/PersonProfiles;
public static final field IDENTIFIED_ONLY Lcom/posthog/internal/PersonProfiles;
public static final field NEVER Lcom/posthog/internal/PersonProfiles;
public static fun valueOf (Ljava/lang/String;)Lcom/posthog/internal/PersonProfiles;
public static fun values ()[Lcom/posthog/internal/PersonProfiles;
}

public abstract interface class com/posthog/internal/PostHogContext {
public abstract fun getDynamicContext ()Ljava/util/Map;
public abstract fun getStaticContext ()Ljava/util/Map;
Expand Down
73 changes: 73 additions & 0 deletions posthog/src/main/java/com/posthog/PostHog.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.posthog

import com.posthog.internal.PersonProfiles
import com.posthog.internal.PostHogApi
import com.posthog.internal.PostHogApiEndpoint
import com.posthog.internal.PostHogFeatureFlags
Expand All @@ -13,6 +14,7 @@ import com.posthog.internal.PostHogPreferences.Companion.DISTINCT_ID
import com.posthog.internal.PostHogPreferences.Companion.GROUPS
import com.posthog.internal.PostHogPreferences.Companion.IS_IDENTIFIED
import com.posthog.internal.PostHogPreferences.Companion.OPT_OUT
import com.posthog.internal.PostHogPreferences.Companion.PERSON_PROCESSING
import com.posthog.internal.PostHogPreferences.Companion.VERSION
import com.posthog.internal.PostHogPrintLogger
import com.posthog.internal.PostHogQueue
Expand Down Expand Up @@ -51,6 +53,7 @@ public class PostHog private constructor(
private val optOutLock = Any()
private val anonymousLock = Any()
private val identifiedLock = Any()
private val personProcessingLock = Any()
private val groupsLock = Any()

private val featureFlagsCalledLock = Any()
Expand All @@ -64,6 +67,7 @@ public class PostHog private constructor(
private val featureFlagsCalled = mutableMapOf<String, MutableList<Any?>>()

private var isIdentifiedLoaded: Boolean = false
private var isPersonProcessingLoaded: Boolean = false

public override fun <T : PostHogConfig> setup(config: T) {
synchronized(setupLock) {
Expand Down Expand Up @@ -249,6 +253,27 @@ public class PostHog private constructor(
}
}

private var isPersonProcessingEnabled: Boolean = false
get() {
synchronized(personProcessingLock) {
if (!isPersonProcessingLoaded) {
isPersonProcessingEnabled = getPreferences().getValue(PERSON_PROCESSING) as? Boolean
?: false
isPersonProcessingLoaded = true
}
}
return field
}
set(value) {
synchronized(personProcessingLock) {
// only set if its different to avoid IO since this is called more often
if (field != value) {
field = value
getPreferences().setValue(PERSON_PROCESSING, value)
}
}
}

private fun buildProperties(
distinctId: String,
properties: Map<String, Any>?,
Expand Down Expand Up @@ -294,6 +319,8 @@ public class PostHog private constructor(
}

props["\$is_identified"] = isIdentified

props["\$process_person_profile"] = hasPersonProcessing()
}

PostHogSessionManager.getActiveSessionId()?.let { sessionId ->
Expand Down Expand Up @@ -373,6 +400,12 @@ public class PostHog private constructor(

val newDistinctId = distinctId ?: this.distinctId

// if the user isn't identified but passed userProperties, userPropertiesSetOnce or groups,
// we should still enable person processing since this is intentional
if (userProperties?.isEmpty() == false || userPropertiesSetOnce?.isEmpty() == false || groups?.isEmpty() == false) {
requirePersonProcessing("capture", ignoreMessage = true)
}

if (newDistinctId.isBlank()) {
config?.logger?.log("capture call not allowed, distinctId is invalid: $newDistinctId.")
return
Expand Down Expand Up @@ -478,6 +511,10 @@ public class PostHog private constructor(
return
}

if (!requirePersonProcessing("alias")) {
return
}

val props = mutableMapOf<String, Any>()
props["alias"] = alias

Expand All @@ -493,6 +530,10 @@ public class PostHog private constructor(
return
}

if (!requirePersonProcessing("identify")) {
return
}

if (distinctId.isBlank()) {
config?.logger?.log("identify call not allowed, distinctId is invalid: $distinctId.")
return
Expand Down Expand Up @@ -540,6 +581,31 @@ public class PostHog private constructor(
}
}

private fun hasPersonProcessing(): Boolean {
return !(
config?.personProfiles == PersonProfiles.NEVER ||
(
config?.personProfiles == PersonProfiles.IDENTIFIED_ONLY &&
!isIdentified &&
!isPersonProcessingEnabled
)
)
}

private fun requirePersonProcessing(
functionName: String,
ignoreMessage: Boolean = false,
): Boolean {
if (config?.personProfiles == PersonProfiles.NEVER) {
if (!ignoreMessage) {
config?.logger?.log("$functionName was called, but `personProfiles` is set to `never`. This call will be ignored.")
}
return false
}
isPersonProcessingEnabled = true
return true
}

public override fun group(
type: String,
key: String,
Expand All @@ -549,6 +615,10 @@ public class PostHog private constructor(
return
}

if (!requirePersonProcessing("group")) {
return
}

val props = mutableMapOf<String, Any>()
props["\$group_type"] = type
props["\$group_key"] = key
Expand Down Expand Up @@ -682,6 +752,9 @@ public class PostHog private constructor(
synchronized(identifiedLock) {
isIdentifiedLoaded = false
}
synchronized(personProcessingLock) {
isPersonProcessingLoaded = false
}

endSession()
startSession()
Expand Down
8 changes: 8 additions & 0 deletions posthog/src/main/java/com/posthog/PostHogConfig.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.posthog

import com.posthog.internal.PersonProfiles
import com.posthog.internal.PostHogContext
import com.posthog.internal.PostHogDateProvider
import com.posthog.internal.PostHogDeviceDateProvider
Expand Down Expand Up @@ -102,6 +103,13 @@ public open class PostHogConfig(
* generating anonymous id (which as of now is just random UUID v4)
*/
public var getAnonymousId: ((UUID) -> UUID) = { it },
/**
* Determines the behavior for processing user profiles.
* - `ALWAYS`: We will process persons data for all events.
* - `NEVER`: Never processes user profile data. This means that anonymous users will not be merged when they sign up or log in.
* - `IDENTIFIED_ONLY` (default): we will only process persons when you call `identify`, `alias`, and `group`, Anonymous users won't get person profiles.
*/
public var personProfiles: PersonProfiles = PersonProfiles.IDENTIFIED_ONLY,
) {
@PostHogInternal
public var logger: PostHogLogger = PostHogNoOpLogger()
Expand Down
7 changes: 7 additions & 0 deletions posthog/src/main/java/com/posthog/internal/PersonProfiles.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.posthog.internal

public enum class PersonProfiles {
NEVER,
ALWAYS,
IDENTIFIED_ONLY,
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public interface PostHogPreferences {
internal const val ANONYMOUS_ID = "anonymousId"
internal const val DISTINCT_ID = "distinctId"
internal const val IS_IDENTIFIED = "isIdentified"
internal const val PERSON_PROCESSING = "personProcessingEnabled"
internal const val OPT_OUT = "opt-out"
internal const val FEATURE_FLAGS = "featureFlags"
internal const val FEATURE_FLAGS_PAYLOAD = "featureFlagsPayload"
Expand All @@ -42,6 +43,7 @@ public interface PostHogPreferences {
ANONYMOUS_ID,
DISTINCT_ID,
IS_IDENTIFIED,
PERSON_PROCESSING,
OPT_OUT,
FEATURE_FLAGS,
FEATURE_FLAGS_PAYLOAD,
Expand Down
Loading

0 comments on commit 1dca934

Please sign in to comment.