Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .changeset/android-v4-major.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
- **Breaking:** add an `options` argument to `capture`, `screen`, `identify`, `setPersonProperties`, `alias`, `group` and `captureException` for per-event capture options such as `cookieless_mode`, sent apart from the properties and editable in `beforeSend` through `PostHogEvent.options`; Java callers pass the trailing argument or `null`, and classes that implement `PostHogInterface` add the parameter
- **Breaking:** move `identify(distinctId, userProperties, userPropertiesSetOnce)` from `PostHogCoreInterface` to `PostHogStatelessInterface`; `PostHogInterface` declares `identify` with the trailing `options` parameter
- **Breaking:** registered super properties now override the properties the SDK sets itself, such as `$os_name`; super and event properties also override `$is_identified`, `$process_person_profile` and the session replay debug properties, which the SDK no longer re-applies after the caller's values
- **Breaking:** the SDK sets the `process_person_profile` option to `false` only when person processing is off, and no longer adds a `$process_person_profile` property to every event; a passed or registered `$process_person_profile` property no longer overrides that `false`, so pass the `process_person_profile` option instead
- **Breaking:** the SDK sets the `process_person_profile` option to `false` only when person processing is off, and no longer adds a `$process_person_profile` property to every event
- **Breaking:** keep the uuid of its own capture on the event `beforeSend` returns, so a uuid set in `beforeSend`, for example with `copy(uuid = …)`, is replaced
- **Breaking:** add `captureAi` to send AI events such as `$ai_generation` to PostHog's AI endpoint (`/i/v1/ai/events`), which accepts events whose properties are up to 8 MiB, on its own disk queue; it returns the event uuid, or `null` when the event isn't queued, and classes that implement `PostHogInterface` add the method
- use the current distinct ID when the `distinctId` argument to `capture` is empty or whitespace-only, instead of dropping the event
Expand Down
2 changes: 1 addition & 1 deletion docs/migration-3.x-to-4.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ When the event is sent, the SDK removes each one from `properties` and uses it f

- An option you pass to any of these methods or set in `beforeSend` wins over its legacy property. An option set to `null` counts as unset, so the legacy property fills it.
- To change person processing in `beforeSend`, edit `event.options`. A `$process_person_profile` property the hook adds only fills an option that is still unset, so it doesn't override the `false` the SDK set for an anonymous event.
- When `personProfiles` turns person processing off, the SDK sets the `process_person_profile` option to `false`. Otherwise it sends neither, because PostHog processes person profiles by default. In 3.x, every event carried a `$process_person_profile` property. To change person processing for one event, pass the `process_person_profile` option. A `$process_person_profile` property, passed or registered, only fills that option when it's unset, so it can't override the `false` the SDK sets when person processing is off.
- When `personProfiles` turns person processing off, the SDK sets the `process_person_profile` option to `false`. Otherwise it sends neither, because PostHog processes person profiles by default. In 3.x, every event carried a `$process_person_profile` property. To change person processing for one event, pass the `process_person_profile` option or the `$process_person_profile` property. A registered `$process_person_profile` changes it for every event.
- A `$set` sent with `process_person_profile: false` doesn't update the person, so it no longer suppresses a later `setPersonProperties` or `identify` call with the same properties as a duplicate.
- The SDK sends option values as given. PostHog reads common forms of a value, such as `"false"` or `0` for a boolean, and drops an event whose option value it can't read.
- Other option keys are sent unchanged, and PostHog ignores keys it doesn't know.
Expand Down
20 changes: 11 additions & 9 deletions posthog/src/main/java/com/posthog/PostHog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -809,8 +809,7 @@ public class PostHog private constructor(
props["\$is_identified"] = isIdentified
// No `$process_person_profile`: person processing is sent as the `process_person_profile`
// option, only when it's off (see personProcessingOptions). A registered or caller
// `$process_person_profile` only fills that option when the send hoists the property and
// the option is still unset.
// `$process_person_profile` wins over it when the send hoists the property.
}

// Session replay should have the SDK info as well
Expand Down Expand Up @@ -986,14 +985,17 @@ public class PostHog private constructor(

/**
* Adds `process_person_profile: false` when the SDK doesn't process person profiles for this
* event, unless the caller set that option. A legacy `$process_person_profile` property doesn't
* prevent it: the send hoists that property only into an unset option. Nothing is added
* otherwise: PostHog processes person profiles by default. Returns a copy, so `beforeSend`
* edits never reach the caller's map.
* event, unless the caller set that option or [properties] carry a non-null legacy
* `$process_person_profile` property (per call or registered). Nothing is added otherwise:
* PostHog processes person profiles by default. Returns a copy, so `beforeSend` edits never
* reach the caller's map.
*/
private fun personProcessingOptions(options: Map<String, Any?>?): MutableMap<String, Any?>? {
private fun personProcessingOptions(
options: Map<String, Any?>?,
properties: Map<String, Any>,
): MutableMap<String, Any?>? {
val eventOptions = options?.toMutableMap()
if (hasPersonProcessing()) {
if (hasPersonProcessing() || properties["\$process_person_profile"] != null) {
return eventOptions
}
val result = eventOptions ?: mutableMapOf()
Expand Down Expand Up @@ -1192,7 +1194,7 @@ public class PostHog private constructor(

// Snapshots don't carry the SDK's person-processing option.
val eventOptions =
if (isSnapshotEvent) options?.toMutableMap() else personProcessingOptions(options)
if (isSnapshotEvent) options?.toMutableMap() else personProcessingOptions(options, mergedProperties)

// This capture's identity, kept through beforeSend.
val uuid = TimeBasedEpochGenerator.generate()
Expand Down
12 changes: 5 additions & 7 deletions posthog/src/test/java/com/posthog/PostHogCaptureOptionsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -218,17 +218,15 @@ internal class PostHogCaptureOptionsTest {
}

@Test
fun `a registered legacy person processing property doesn't override the personless option`() {
val http = httpServers.mockHttp()
val sut = getSut(http.url("/").toString(), personProfiles = PersonProfiles.NEVER, beforeSend = { it })
fun `a registered legacy person processing property suppresses the personless option`() {
val sut = getSut(personProfiles = PersonProfiles.NEVER)
sut.register("\$process_person_profile", true)

sut.capture("test_event")
queueExecutor.shutdownAndAwaitTermination()

val event = sentEvents(http).single()
assertEquals<Map<String, Any?>?>(mapOf("process_person_profile" to false), event.options)
assertFalse(event.properties!!.containsKey("\$process_person_profile"))
val event = captured.single()
assertNull(event.options)
assertEquals(true, event.properties!!["\$process_person_profile"])
sut.close()
}

Expand Down
10 changes: 5 additions & 5 deletions posthog/src/test/java/com/posthog/PostHogPersonProfilesTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ internal class PostHogPersonProfilesTest {
}

@Test
fun `caller person processing properties don't override the personless option if identified only and not identified`() {
fun `caller person processing properties win if identified only and not identified`() {
val http = mockHttp()
val url = http.url("/")

Expand All @@ -138,8 +138,8 @@ internal class PostHogPersonProfilesTest {

val event = batch.batch.first()

// the SDK's personless option wins; the caller's legacy property is removed when sent
assertEquals(false, event.processPersonProfile)
// the caller's legacy property is hoisted into the option when the event is sent
assertEquals(true, event.processPersonProfile)
assertFalse(event.properties!!.containsKey("\$process_person_profile"))
assertEquals(true, event.properties!!["\$is_identified"] as Boolean)

Expand Down Expand Up @@ -424,7 +424,7 @@ internal class PostHogPersonProfilesTest {
}

@Test
fun `caller process person property doesn't override never`() {
fun `caller process person override wins if never`() {
val http = mockHttp()
val url = http.url("/")

Expand All @@ -440,7 +440,7 @@ internal class PostHogPersonProfilesTest {

val event = batch.batch.first()

assertEquals(false, event.processPersonProfile)
assertEquals(true, event.processPersonProfile)
assertFalse(event.properties!!.containsKey("\$process_person_profile"))

sut.close()
Expand Down
Loading