Skip to content

Commit 2236ce3

Browse files
committed
fixup! feat: sendFeatureFlags config is respected
1 parent f13977d commit 2236ce3

File tree

9 files changed

+450
-204
lines changed

9 files changed

+450
-204
lines changed

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

Lines changed: 181 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,40 @@
11
package com.posthog.server
22

33
import com.posthog.PostHogStateless
4-
import com.posthog.PostHogStatelessInterface
4+
import com.posthog.server.internal.EvaluationSource
5+
import com.posthog.server.internal.FeatureFlagResultContext
56
import com.posthog.server.internal.PostHogFeatureFlags
67

7-
public class PostHog : PostHogInterface {
8-
private var instance: PostHogStatelessInterface? = null
9-
private var config: PostHogConfig? = null
10-
private var featureFlags: PostHogFeatureFlags? = null
8+
public class PostHog : PostHogInterface, PostHogStateless() {
9+
private var serverConfig: PostHogConfig? = null
1110

1211
override fun <T : PostHogConfig> setup(config: T) {
13-
this.config = config
14-
instance = PostHogStateless.with(config.asCoreConfig())
15-
featureFlags = config.featureFlags
12+
super.setup(config.asCoreConfig())
13+
this.serverConfig = config
1614
}
1715

1816
override fun close() {
19-
instance?.close()
17+
super.close()
2018
}
2119

2220
override fun identify(
2321
distinctId: String,
2422
userProperties: Map<String, Any>?,
2523
userPropertiesSetOnce: Map<String, Any>?,
2624
) {
27-
instance?.identify(
25+
super<PostHogStateless>.identify(
2826
distinctId,
2927
userProperties,
3028
userPropertiesSetOnce,
3129
)
3230
}
3331

3432
override fun flush() {
35-
instance?.flush()
33+
super.flush()
3634
}
3735

3836
override fun debug(enable: Boolean) {
39-
instance?.debug(enable)
37+
super.debug(enable)
4038
}
4139

4240
override fun capture(
@@ -49,16 +47,22 @@ public class PostHog : PostHogInterface {
4947
timestamp: java.util.Date?,
5048
sendFeatureFlags: PostHogSendFeatureFlagOptions?,
5149
) {
52-
val updatedProperties = if (sendFeatureFlags == null && properties == null) {
53-
null
54-
} else {
55-
mutableMapOf<String, Any>().apply {
56-
properties?.let { putAll(it) }
57-
}.also { updatedProperties ->
58-
featureFlags?.appendFlagEventProperties(distinctId, updatedProperties, groups, sendFeatureFlags)
50+
val updatedProperties =
51+
if (sendFeatureFlags == null) {
52+
properties
53+
} else {
54+
mutableMapOf<String, Any>().apply {
55+
properties?.let { putAll(it) }
56+
}.also { props ->
57+
appendFlagCaptureProperties(
58+
distinctId,
59+
props,
60+
groups,
61+
sendFeatureFlags,
62+
)
63+
}
5964
}
60-
}
61-
instance?.captureStateless(
65+
super.captureStateless(
6266
event,
6367
distinctId,
6468
updatedProperties,
@@ -77,14 +81,24 @@ public class PostHog : PostHogInterface {
7781
personProperties: Map<String, String>?,
7882
groupProperties: Map<String, String>?,
7983
): Boolean {
80-
return instance?.isFeatureEnabledStateless(
81-
distinctId,
82-
key,
83-
defaultValue,
84-
groups,
85-
personProperties,
86-
groupProperties,
87-
) ?: false
84+
(featureFlags as? PostHogFeatureFlags)?.let { featureFlags ->
85+
val result =
86+
featureFlags.resolveFeatureFlag(
87+
key,
88+
distinctId,
89+
groups,
90+
personProperties,
91+
groupProperties,
92+
)
93+
sendFeatureFlagCalled(
94+
distinctId,
95+
key,
96+
result,
97+
)
98+
val flag = result?.results?.get(key)
99+
return flag?.enabled ?: defaultValue
100+
}
101+
return defaultValue
88102
}
89103

90104
override fun getFeatureFlag(
@@ -95,14 +109,24 @@ public class PostHog : PostHogInterface {
95109
personProperties: Map<String, String>?,
96110
groupProperties: Map<String, String>?,
97111
): Any? {
98-
return instance?.getFeatureFlagStateless(
99-
distinctId,
100-
key,
101-
defaultValue,
102-
groups,
103-
personProperties,
104-
groupProperties,
105-
)
112+
(featureFlags as? PostHogFeatureFlags)?.let { featureFlags ->
113+
val result =
114+
featureFlags.resolveFeatureFlag(
115+
key,
116+
distinctId,
117+
groups,
118+
personProperties,
119+
groupProperties,
120+
)
121+
sendFeatureFlagCalled(
122+
distinctId,
123+
key,
124+
result,
125+
)
126+
val flag = result?.results?.get(key)
127+
return flag?.variant ?: flag?.enabled ?: defaultValue
128+
}
129+
return defaultValue
106130
}
107131

108132
override fun getFeatureFlagPayload(
@@ -113,14 +137,24 @@ public class PostHog : PostHogInterface {
113137
personProperties: Map<String, String>?,
114138
groupProperties: Map<String, String>?,
115139
): Any? {
116-
return instance?.getFeatureFlagPayloadStateless(
117-
distinctId,
118-
key,
119-
defaultValue,
120-
groups,
121-
personProperties,
122-
groupProperties,
123-
)
140+
(featureFlags as? PostHogFeatureFlags)?.let { featureFlags ->
141+
val result =
142+
featureFlags.resolveFeatureFlag(
143+
key,
144+
distinctId,
145+
groups,
146+
personProperties,
147+
groupProperties,
148+
)
149+
sendFeatureFlagCalled(
150+
distinctId,
151+
key,
152+
result,
153+
)
154+
val flag = result?.results?.get(key)
155+
return flag?.metadata?.payload ?: defaultValue
156+
}
157+
return defaultValue
124158
}
125159

126160
override fun group(
@@ -129,7 +163,7 @@ public class PostHog : PostHogInterface {
129163
key: String,
130164
groupProperties: Map<String, Any>?,
131165
) {
132-
instance?.groupStateless(
166+
super.groupStateless(
133167
distinctId,
134168
type,
135169
key,
@@ -141,12 +175,112 @@ public class PostHog : PostHogInterface {
141175
distinctId: String,
142176
alias: String,
143177
) {
144-
instance?.aliasStateless(
178+
super.aliasStateless(
145179
distinctId,
146180
alias,
147181
)
148182
}
149183

184+
private fun sendFeatureFlagCalled(
185+
distinctId: String,
186+
key: String,
187+
resultContext: FeatureFlagResultContext?,
188+
) {
189+
if (serverConfig?.sendFeatureFlagEvent == false || distinctId.isEmpty() || key.isEmpty() || resultContext == null) {
190+
return
191+
}
192+
193+
if (config?.sendFeatureFlagEvent == true) {
194+
val requestedFlag = resultContext.results?.get(key)
195+
val requestedFlagValue = requestedFlag?.variant ?: requestedFlag?.enabled
196+
val isNewlySeen = featureFlagsCalled?.add(distinctId, key, requestedFlagValue) ?: false
197+
if (isNewlySeen) {
198+
val props = mutableMapOf<String, Any>()
199+
props["\$feature_flag"] = key
200+
props["\$feature_flag_response"] = requestedFlagValue ?: ""
201+
resultContext.requestId?.let {
202+
props["\$feature_flag_request_id"] = it
203+
}
204+
requestedFlag?.metadata?.let {
205+
props["\$feature_flag_id"] = it.id
206+
props["\$feature_flag_version"] = it.version
207+
}
208+
props["\$feature_flag_reason"] = requestedFlag?.reason?.description ?: ""
209+
resultContext.source?.let {
210+
props["\$feature_flag_source"] = it.toString()
211+
if (it == EvaluationSource.LOCAL) {
212+
props["locally_evaluated"] = true
213+
}
214+
}
215+
216+
var allFlags = resultContext.results
217+
if (!resultContext.exhaustive) {
218+
// we only have partial results so we'll need to resolve the rest
219+
resultContext.parameters?.let { params ->
220+
// this will be cached or evaluated locally
221+
val response =
222+
(featureFlags as? PostHogFeatureFlags)?.resolveFeatureFlags(
223+
distinctId,
224+
params.groups,
225+
params.personProperties,
226+
params.groupProperties,
227+
params.onlyEvaluateLocally,
228+
)
229+
if (response != null) {
230+
allFlags = response.results
231+
}
232+
}
233+
}
234+
235+
allFlags?.let { flags ->
236+
val activeFeatureFlags = mutableListOf<String>()
237+
flags.values.forEach { flag ->
238+
val flagValue = flag.variant ?: flag.enabled
239+
props["\$feature/${flag.key}"] = flagValue
240+
if (flagValue != false) {
241+
activeFeatureFlags.add(flag.key)
242+
}
243+
}
244+
props["\$active_feature_flags"] = activeFeatureFlags.toList()
245+
}
246+
247+
captureStateless("\$feature_flag_called", distinctId, properties = props)
248+
}
249+
}
250+
}
251+
252+
private fun appendFlagCaptureProperties(
253+
distinctId: String,
254+
properties: MutableMap<String, Any>?,
255+
groups: Map<String, String>?,
256+
options: PostHogSendFeatureFlagOptions?,
257+
) {
258+
if (options == null || properties == null) {
259+
return
260+
}
261+
262+
val response =
263+
(featureFlags as? PostHogFeatureFlags)?.resolveFeatureFlags(
264+
distinctId,
265+
groups,
266+
options.personProperties,
267+
options.groupProperties,
268+
options.onlyEvaluateLocally,
269+
)
270+
271+
response?.results?.values?.let {
272+
val activeFeatureFlags = mutableListOf<String>()
273+
it.forEach { flag ->
274+
val flagValue = flag.variant ?: flag.enabled
275+
properties["\$feature/${flag.key}"] = flagValue
276+
if (flagValue != false) {
277+
activeFeatureFlags.add(flag.key)
278+
}
279+
}
280+
properties["\$active_feature_flags"] = activeFeatureFlags.toList()
281+
}
282+
}
283+
150284
public companion object {
151285
/**
152286
* Set up the SDK and returns an instance that you can hold and pass it around

0 commit comments

Comments
 (0)