Skip to content

Commit 26fda12

Browse files
committed
feat: sendFeatureFlags config is respected
1 parent f00a786 commit 26fda12

File tree

7 files changed

+197
-35
lines changed

7 files changed

+197
-35
lines changed

posthog-server/src/main/java/com/posthog/server/PostHog.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@ package com.posthog.server
22

33
import com.posthog.PostHogStateless
44
import com.posthog.PostHogStatelessInterface
5+
import com.posthog.server.internal.PostHogFeatureFlags
56

67
public class PostHog : PostHogInterface {
78
private var instance: PostHogStatelessInterface? = null
9+
private var config: PostHogConfig? = null
10+
private var featureFlags: PostHogFeatureFlags? = null
811

912
override fun <T : PostHogConfig> setup(config: T) {
13+
this.config = config
1014
instance = PostHogStateless.with(config.asCoreConfig())
15+
featureFlags = config.featureFlags
1116
}
1217

1318
override fun close() {
@@ -42,11 +47,16 @@ public class PostHog : PostHogInterface {
4247
userPropertiesSetOnce: Map<String, Any>?,
4348
groups: Map<String, String>?,
4449
timestamp: java.util.Date?,
50+
sendFeatureFlags: PostHogSendFeatureFlagOptions?,
4551
) {
52+
val updatedProperties = mutableMapOf<String, Any>().apply {
53+
putAll(properties ?: emptyMap())
54+
}
55+
featureFlags?.appendFlagEventProperties(distinctId, updatedProperties, groups, sendFeatureFlags)
4656
instance?.captureStateless(
4757
event,
4858
distinctId,
49-
properties,
59+
updatedProperties,
5060
userProperties,
5161
userPropertiesSetOnce,
5262
groups,

posthog-server/src/main/java/com/posthog/server/PostHogCaptureOptions.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ public class PostHogCaptureOptions private constructor(
1414
public val userPropertiesSetOnce: Map<String, Any>?,
1515
public val groups: Map<String, String>?,
1616
public val timestamp: Date? = null,
17+
public val sendFeatureFlags: PostHogSendFeatureFlagOptions? = null,
1718
) {
1819
public class Builder {
1920
public var properties: MutableMap<String, Any>? = null
2021
public var userProperties: MutableMap<String, Any>? = null
2122
public var userPropertiesSetOnce: MutableMap<String, Any>? = null
2223
public var groups: MutableMap<String, String>? = null
2324
public var timestamp: Date? = null
25+
public var sendFeatureFlags: PostHogSendFeatureFlagOptions? = null
2426

2527
/**
2628
* Add a single custom property to the capture options
@@ -155,13 +157,28 @@ public class PostHogCaptureOptions private constructor(
155157
return this
156158
}
157159

160+
public fun sendFeatureFlags(toggle: Boolean?): Builder {
161+
if (toggle == true) {
162+
this.sendFeatureFlags = PostHogSendFeatureFlagOptions.builder().build()
163+
} else {
164+
this.sendFeatureFlags = null
165+
}
166+
return this
167+
}
168+
169+
public fun sendFeatureFlags(options: PostHogSendFeatureFlagOptions?): Builder {
170+
this.sendFeatureFlags = options
171+
return this
172+
}
173+
158174
public fun build(): PostHogCaptureOptions =
159175
PostHogCaptureOptions(
160176
properties,
161177
userProperties,
162178
userPropertiesSetOnce,
163179
groups,
164180
timestamp,
181+
sendFeatureFlags,
165182
)
166183
}
167184

posthog-server/src/main/java/com/posthog/server/PostHogConfig.kt

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ public open class PostHogConfig constructor(
129129
) {
130130
private val beforeSendCallbacks = mutableListOf<PostHogBeforeSend>()
131131
private val integrations = mutableListOf<PostHogIntegration>()
132+
internal var featureFlags: PostHogFeatureFlags? = null
132133

133134
public fun addBeforeSend(beforeSend: PostHogBeforeSend) {
134135
beforeSendCallbacks.add(beforeSend)
@@ -160,7 +161,7 @@ public open class PostHogConfig constructor(
160161
onFeatureFlags = onFeatureFlags,
161162
proxy = proxy,
162163
remoteConfigProvider = { config, api, _ ->
163-
PostHogFeatureFlags(
164+
val featureFlags = PostHogFeatureFlags(
164165
config,
165166
api,
166167
cacheMaxAgeMs = featureFlagCacheMaxAgeMs,
@@ -170,6 +171,10 @@ public open class PostHogConfig constructor(
170171
pollIntervalSeconds = pollIntervalSeconds,
171172
onFeatureFlags = onFeatureFlags,
172173
)
174+
// TODO: Ideally we'd construct this outside of the config, maybe in PostHog.setup.
175+
//. Then we could avoid capturing this value as a side effect.
176+
this.featureFlags = featureFlags
177+
featureFlags
173178
},
174179
queueProvider = { config, api, endpoint, _, executor ->
175180
PostHogMemoryQueue(config, api, endpoint, executor)
@@ -235,39 +240,51 @@ public open class PostHogConfig constructor(
235240

236241
public fun debug(debug: Boolean): Builder = apply { this.debug = debug }
237242

238-
public fun sendFeatureFlagEvent(sendFeatureFlagEvent: Boolean): Builder = apply { this.sendFeatureFlagEvent = sendFeatureFlagEvent }
243+
public fun sendFeatureFlagEvent(sendFeatureFlagEvent: Boolean): Builder =
244+
apply { this.sendFeatureFlagEvent = sendFeatureFlagEvent }
239245

240-
public fun preloadFeatureFlags(preloadFeatureFlags: Boolean): Builder = apply { this.preloadFeatureFlags = preloadFeatureFlags }
246+
public fun preloadFeatureFlags(preloadFeatureFlags: Boolean): Builder =
247+
apply { this.preloadFeatureFlags = preloadFeatureFlags }
241248

242-
public fun remoteConfig(remoteConfig: Boolean): Builder = apply { this.remoteConfig = remoteConfig }
249+
public fun remoteConfig(remoteConfig: Boolean): Builder =
250+
apply { this.remoteConfig = remoteConfig }
243251

244252
public fun flushAt(flushAt: Int): Builder = apply { this.flushAt = flushAt }
245253

246-
public fun maxQueueSize(maxQueueSize: Int): Builder = apply { this.maxQueueSize = maxQueueSize }
254+
public fun maxQueueSize(maxQueueSize: Int): Builder =
255+
apply { this.maxQueueSize = maxQueueSize }
247256

248-
public fun maxBatchSize(maxBatchSize: Int): Builder = apply { this.maxBatchSize = maxBatchSize }
257+
public fun maxBatchSize(maxBatchSize: Int): Builder =
258+
apply { this.maxBatchSize = maxBatchSize }
249259

250-
public fun flushIntervalSeconds(flushIntervalSeconds: Int): Builder = apply { this.flushIntervalSeconds = flushIntervalSeconds }
260+
public fun flushIntervalSeconds(flushIntervalSeconds: Int): Builder =
261+
apply { this.flushIntervalSeconds = flushIntervalSeconds }
251262

252-
public fun encryption(encryption: PostHogEncryption?): Builder = apply { this.encryption = encryption }
263+
public fun encryption(encryption: PostHogEncryption?): Builder =
264+
apply { this.encryption = encryption }
253265

254-
public fun onFeatureFlags(onFeatureFlags: PostHogOnFeatureFlags?): Builder = apply { this.onFeatureFlags = onFeatureFlags }
266+
public fun onFeatureFlags(onFeatureFlags: PostHogOnFeatureFlags?): Builder =
267+
apply { this.onFeatureFlags = onFeatureFlags }
255268

256269
public fun proxy(proxy: Proxy?): Builder = apply { this.proxy = proxy }
257270

258-
public fun featureFlagCacheSize(featureFlagCacheSize: Int): Builder = apply { this.featureFlagCacheSize = featureFlagCacheSize }
271+
public fun featureFlagCacheSize(featureFlagCacheSize: Int): Builder =
272+
apply { this.featureFlagCacheSize = featureFlagCacheSize }
259273

260274
public fun featureFlagCacheMaxAgeMs(featureFlagCacheMaxAgeMs: Int): Builder =
261275
apply { this.featureFlagCacheMaxAgeMs = featureFlagCacheMaxAgeMs }
262276

263277
public fun featureFlagCalledCacheSize(featureFlagCalledCacheSize: Int): Builder =
264278
apply { this.featureFlagCalledCacheSize = featureFlagCalledCacheSize }
265279

266-
public fun localEvaluation(localEvaluation: Boolean): Builder = apply { this.localEvaluation = localEvaluation }
280+
public fun localEvaluation(localEvaluation: Boolean): Builder =
281+
apply { this.localEvaluation = localEvaluation }
267282

268-
public fun personalApiKey(personalApiKey: String?): Builder = apply { this.personalApiKey = personalApiKey }
283+
public fun personalApiKey(personalApiKey: String?): Builder =
284+
apply { this.personalApiKey = personalApiKey }
269285

270-
public fun pollIntervalSeconds(pollIntervalSeconds: Int): Builder = apply { this.pollIntervalSeconds = pollIntervalSeconds }
286+
public fun pollIntervalSeconds(pollIntervalSeconds: Int): Builder =
287+
apply { this.pollIntervalSeconds = pollIntervalSeconds }
271288

272289
public fun build(): PostHogConfig =
273290
PostHogConfig(

posthog-server/src/main/java/com/posthog/server/PostHogInterface.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public sealed interface PostHogInterface {
7474
* @param userProperties the user properties, set as a "$set" property, Docs https://posthog.com/docs/product-analytics/user-properties
7575
* @param userPropertiesSetOnce the user properties to set only once, set as a "$set_once" property, Docs https://posthog.com/docs/product-analytics/user-properties
7676
* @param groups the groups, set as a "$groups" property, Docs https://posthog.com/docs/product-analytics/group-analytics
77+
* @param sendFeatureFlags whether to send feature flags with this event, if not provided the default config value will be used
7778
*/
7879
@JvmSynthetic
7980
public fun capture(
@@ -84,13 +85,14 @@ public sealed interface PostHogInterface {
8485
userPropertiesSetOnce: Map<String, Any>? = null,
8586
groups: Map<String, String>? = null,
8687
timestamp: Date? = null,
88+
sendFeatureFlags: PostHogSendFeatureFlagOptions? = null,
8789
)
8890

8991
/**
9092
* Captures events
9193
* @param event the event name
9294
* @param distinctId the distinctId
93-
* @param options the capture options containing properties, userProperties, userPropertiesSetOnce, and groups
95+
* @param options the capture options containing properties, userProperties, userPropertiesSetOnce, groups, and sendFeatureFlags
9496
*/
9597
public fun capture(
9698
distinctId: String,
@@ -105,6 +107,7 @@ public sealed interface PostHogInterface {
105107
options.userPropertiesSetOnce,
106108
options.groups,
107109
options.timestamp,
110+
options.sendFeatureFlags,
108111
)
109112
}
110113

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package com.posthog.server
2+
3+
import java.time.Instant
4+
import java.util.Date
5+
6+
/**
7+
* Provides an ergonomic interface when providing options for capturing events
8+
* This is mainly meant to be used from Java, as Kotlin can use named parameters.
9+
* @see <a href="https://posthog.com/docs/product-analytics/capture-events">Documentation: Capturing events</a>
10+
*/
11+
public class PostHogSendFeatureFlagOptions private constructor(
12+
public val onlyEvaluateLocally: Boolean = false,
13+
public val personProperties: Map<String, String>?,
14+
public val groupProperties: Map<String, String>?,
15+
) {
16+
public class Builder {
17+
public var onlyEvaluateLocally: Boolean = false
18+
public var personProperties: MutableMap<String, String>? = null
19+
public var groupProperties: MutableMap<String, String>? = null
20+
21+
/**
22+
* Sets whether to only evaluate the feature flags locally.
23+
*/
24+
public fun onlyEvaluateLocally(onlyEvaluateLocally: Boolean): Builder {
25+
this.onlyEvaluateLocally = onlyEvaluateLocally
26+
return this
27+
}
28+
29+
/**
30+
* Adds a single user property to the capture options
31+
* @see <a href="https://posthog.com/docs/product-analytics/user-properties">Documentation: User Properties</a>
32+
*/
33+
public fun personProperty(
34+
key: String,
35+
value: String,
36+
): Builder {
37+
personProperties =
38+
(personProperties ?: mutableMapOf()).apply {
39+
put(key, value)
40+
}
41+
return this
42+
}
43+
44+
/**
45+
* Appends multiple user properties to the capture options.
46+
* @see <a href="https://posthog.com/docs/product-analytics/user-properties">Documentation: User Properties</a>
47+
*/
48+
public fun personProperties(userProperties: Map<String, String>): Builder {
49+
this.personProperties =
50+
(this.personProperties ?: mutableMapOf()).apply {
51+
putAll(userProperties)
52+
}
53+
return this
54+
}
55+
56+
/**
57+
* Adds a single user property (set once) to the capture options.
58+
* @see <a href="https://posthog.com/docs/product-analytics/user-properties">Documentation: User Properties</a>
59+
*/
60+
public fun groupProperty(
61+
key: String,
62+
value: String,
63+
): Builder {
64+
groupProperties =
65+
(groupProperties ?: mutableMapOf()).apply {
66+
put(key, value)
67+
}
68+
return this
69+
}
70+
71+
/**
72+
* Appends multiple user properties (set once) to the capture options.
73+
* @see <a href="https://posthog.com/docs/product-analytics/user-properties">Documentation: User Properties</a>
74+
*/
75+
public fun groupProperties(groupProperties: Map<String, String>): Builder {
76+
this.groupProperties =
77+
(this.groupProperties ?: mutableMapOf()).apply {
78+
putAll(groupProperties)
79+
}
80+
return this
81+
}
82+
83+
public fun build(): PostHogSendFeatureFlagOptions =
84+
PostHogSendFeatureFlagOptions(
85+
onlyEvaluateLocally = onlyEvaluateLocally,
86+
personProperties = personProperties,
87+
groupProperties = groupProperties,
88+
)
89+
}
90+
91+
public companion object {
92+
@JvmStatic
93+
public fun builder(): Builder = Builder()
94+
}
95+
}

posthog-server/src/main/java/com/posthog/server/internal/PostHogFeatureFlags.kt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.posthog.PostHogOnFeatureFlags
55
import com.posthog.internal.FeatureFlag
66
import com.posthog.internal.PostHogApi
77
import com.posthog.internal.PostHogFeatureFlagsInterface
8+
import com.posthog.server.PostHogSendFeatureFlagOptions
89

910
internal class PostHogFeatureFlags(
1011
private val config: PostHogConfig,
@@ -527,4 +528,41 @@ internal class PostHogFeatureFlags(
527528
evaluationCache = evaluationCache,
528529
)
529530
}
531+
532+
public fun appendFlagEventProperties(
533+
distinctId: String,
534+
properties: MutableMap<String, Any>?,
535+
groups: Map<String, String>?,
536+
options: PostHogSendFeatureFlagOptions?,
537+
) {
538+
if (options == null || properties == null) {
539+
return
540+
}
541+
542+
val featureFlags = if (options.onlyEvaluateLocally) {
543+
getFeatureFlagsFromLocalEvaluation(distinctId, groups, options.personProperties, options.groupProperties, true)
544+
} else {
545+
getFeatureFlags(
546+
distinctId,
547+
groups,
548+
options.personProperties,
549+
options.groupProperties,
550+
)
551+
}
552+
if (featureFlags == null) {
553+
return
554+
}
555+
556+
val activeFeatureFlags = mutableListOf<String>()
557+
558+
for (featureFlag in featureFlags?.values ?: emptyList()) {
559+
val flagValue = featureFlag.variant ?: featureFlag.enabled
560+
properties.put("\$feature/${featureFlag.key}", flagValue )
561+
if (flagValue != false) {
562+
activeFeatureFlags.add(featureFlag.key)
563+
}
564+
}
565+
566+
properties.put("\$active_feature_flags", activeFeatureFlags)
567+
}
530568
}

posthog/src/main/java/com/posthog/PostHogStateless.kt

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -147,25 +147,6 @@ public open class PostHogStateless protected constructor(
147147
props.putAll(it)
148148
}
149149

150-
if (config?.sendFeatureFlagEvent == true) {
151-
featureFlags?.getFeatureFlags()?.let {
152-
if (it.isNotEmpty()) {
153-
val keys = mutableListOf<String>()
154-
for (entry in it.entries) {
155-
props["\$feature/${entry.key}"] = entry.value
156-
157-
// only add active feature flags
158-
val active = entry.value as? Boolean ?: true
159-
160-
if (active) {
161-
keys.add(entry.key)
162-
}
163-
}
164-
props["\$active_feature_flags"] = keys
165-
}
166-
}
167-
}
168-
169150
userProperties?.let {
170151
props["\$set"] = it
171152
}
@@ -277,7 +258,8 @@ public open class PostHogStateless protected constructor(
277258
timestamp: Date? = null,
278259
): PostHogEvent? {
279260
// sanitize the properties or fallback to the original properties
280-
val sanitizedProperties = config?.propertiesSanitizer?.sanitize(properties)?.toMutableMap() ?: properties
261+
val sanitizedProperties =
262+
config?.propertiesSanitizer?.sanitize(properties)?.toMutableMap() ?: properties
281263
val postHogEvent =
282264
PostHogEvent(
283265
event,

0 commit comments

Comments
 (0)