Repository navigation
fix: run beforeSend once per captured event #885
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
Changes from all commits
ec33bb7
1cbb559
f84d6cd
20ea392
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| 'posthog': patch | ||
| 'posthog-android': patch | ||
| --- | ||
|
|
||
| Fix `beforeSend` running twice for each captured event, which discarded the hook's changes to `distinctId`, `timestamp` and `uuid` and compounded random sampling |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1037,7 +1037,9 @@ public class PostHog private constructor( | |
| }, | ||
| ) | ||
|
|
||
| val postHogEvent = buildEvent(event, newDistinctId, mergedProperties, timestamp) | ||
| val eventProperties = minimizeIfFeatureFlagCalled(event, mergedProperties) | ||
|
|
||
| val postHogEvent = buildEvent(event, newDistinctId, eventProperties, timestamp) | ||
| if (postHogEvent == null) { | ||
| val originalMessage = "PostHog event $event was dropped" | ||
| val message = | ||
|
|
@@ -1060,18 +1062,15 @@ public class PostHog private constructor( | |
| if (isSnapshotEvent) { | ||
| replayQueue?.add(postHogEvent) | ||
| } else { | ||
| val queued = | ||
| captureStatelessInternal( | ||
| postHogEvent.event, | ||
| newDistinctId, | ||
| postHogEvent.properties ?: emptyMap(), | ||
| userProperties, | ||
| userPropertiesSetOnce, | ||
| groups, | ||
| timestamp, | ||
| ) | ||
| if (queued && claimedReplayDebugBundle) { | ||
| commitReplayDebugPropertiesWindow() | ||
| val queue = queue | ||
| // beforeSend may have opted out | ||
| if (isOptedOut()) { | ||
| config?.logger?.log("PostHog is in OptOut state.") | ||
| } else if (queue != null) { | ||
| queue.add(postHogEvent) | ||
|
Comment on lines
+1069
to
+1070
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2 · Recheck SDK state after running beforeSend If |
||
| if (claimedReplayDebugBundle) { | ||
| commitReplayDebugPropertiesWindow() | ||
| } | ||
| } | ||
| // Notify surveys integration about the event | ||
| surveysHandler?.onEvent(event, mergedProperties) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2517,6 +2517,100 @@ internal class PostHogTest { | |
| http.shutdown() | ||
| } | ||
|
|
||
| @Test | ||
| fun `beforeSend runs once per capture`() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
[nit] Worth adding a stateful test that a hook edit to Something like the hook returning
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added in 20ea392: |
||
| val http = mockHttp() | ||
| val url = http.url("/") | ||
| var calls = 0 | ||
|
|
||
| val sut = | ||
| getSut(url.toString(), preloadFeatureFlags = false, beforeSend = { | ||
| calls++ | ||
| it | ||
| }) | ||
|
|
||
| sut.capture("event") | ||
|
|
||
| queueExecutor.shutdownAndAwaitTermination() | ||
|
|
||
| assertEquals(1, calls) | ||
|
|
||
| sut.close() | ||
| http.shutdown() | ||
| } | ||
|
|
||
| @Test | ||
| fun `an event edited by beforeSend is sent as edited`() { | ||
| val http = mockHttp() | ||
| val url = http.url("/") | ||
| var calls = 0 | ||
|
|
||
| val sut = | ||
| getSut(url.toString(), preloadFeatureFlags = false, beforeSend = { | ||
| it.copy(distinctId = "hooked-${++calls}") | ||
| }) | ||
|
|
||
| sut.capture("event") | ||
|
|
||
| queueExecutor.shutdownAndAwaitTermination() | ||
|
|
||
| val batch = serializer.deserialize<PostHogBatchEvent>(http.takeRequest().body.unGzip().reader()) | ||
| assertEquals("hooked-1", batch.batch.first().distinctId) | ||
|
|
||
| sut.close() | ||
| http.shutdown() | ||
| } | ||
|
|
||
| @Test | ||
| fun `an event is dropped when beforeSend opts out`() { | ||
| val http = mockHttp() | ||
| val url = http.url("/") | ||
| lateinit var sut: PostHogInterface | ||
|
|
||
| sut = | ||
| getSut(url.toString(), preloadFeatureFlags = false, beforeSend = { | ||
| sut.optOut() | ||
| it | ||
| }) | ||
|
|
||
| sut.capture("event") | ||
|
|
||
| queueExecutor.shutdownAndAwaitTermination() | ||
|
|
||
| assertEquals(0, http.requestCount) | ||
|
|
||
| sut.close() | ||
| http.shutdown() | ||
| } | ||
|
|
||
| @Test | ||
| fun `beforeSend sees the minimal feature flag called event`() { | ||
| val file = File("src/test/resources/json/basic-flags-minimal-flag-called-events.json") | ||
| val http = mockHttp(response = MockResponse().setBody(file.readText())) | ||
| http.enqueue(MockResponse().setBody("")) | ||
| val url = http.url("/") | ||
| val seenProperties = mutableListOf<Set<String>>() | ||
|
|
||
| val sut = | ||
| getSut(url.toString(), preloadFeatureFlags = false, beforeSend = { | ||
| if (it.event == "\$feature_flag_called") seenProperties.add(it.properties.orEmpty().keys.toSet()) | ||
| it | ||
| }) | ||
| sut.register("myCustomProp", "value") | ||
|
|
||
| sut.reloadFeatureFlags() | ||
| remoteConfigExecutor.shutdownAndAwaitTermination() | ||
|
|
||
| sut.getFeatureFlag("IAmInactive") | ||
| queueExecutor.shutdownAndAwaitTermination() | ||
|
|
||
| assertEquals(1, seenProperties.size) | ||
| assertFalse(seenProperties.first().contains("myCustomProp")) | ||
|
|
||
| sut.close() | ||
| http.shutdown() | ||
| } | ||
|
|
||
| @Test | ||
| fun `creates a group`() { | ||
| val http = mockHttp() | ||
|
|
@@ -3683,10 +3777,8 @@ internal class PostHogTest { | |
| fun `an SDK event captured from inside beforeSend does not also carry the bundle`() { | ||
| val http = mockHttp() | ||
| lateinit var sut: PostHogInterface | ||
| // beforeSend runs twice per stateful capture (PostHog.capture, then captureStateless), so the | ||
| // nested capture fires twice. | ||
| sut = | ||
| replayDebugSut(http, flushAt = 3, beforeSend = { | ||
| replayDebugSut(http, flushAt = 2, beforeSend = { | ||
| if (it.event == "\$outer") sut.capture("\$inner") | ||
| it | ||
| }) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.