Skip to content

Commit dd147ff

Browse files
feat: support overrides with user/custom IDs (#405)
atlassian published a pr for this: #35, but we wanna do it in our own way and also support for other core apis ### **NOTE:** I only did for `removeXXX` and `overrideXXX`, but not those `removeXXXAsync`, we only maintain sync ones right?
1 parent 7b1e9ab commit dd147ff

File tree

5 files changed

+228
-52
lines changed

5 files changed

+228
-52
lines changed

src/main/kotlin/com/statsig/sdk/Evaluator.kt

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ internal class Evaluator(
3838
}
3939
}
4040
private val persistentStore: UserPersistentStorageHandler
41-
private var gateOverrides: MutableMap<String, Boolean> = HashMap()
42-
private var configOverrides: MutableMap<String, Map<String, Any>> = HashMap()
43-
private var layerOverrides: MutableMap<String, Map<String, Any>> = HashMap()
41+
private var gateOverrides: MutableMap<String, MutableMap<String?, Boolean>> = HashMap()
42+
private var configOverrides: MutableMap<String, MutableMap<String?, Map<String, Any>>> = HashMap()
43+
private var layerOverrides: MutableMap<String, MutableMap<String?, Map<String, Any>>> = HashMap()
4444
private var hashLookupTable: MutableMap<String, ULong> = HashMap()
4545
private val gson = Utils.getGson()
4646
private val logger = options.customLogger
@@ -147,33 +147,37 @@ internal class Evaluator(
147147
return false
148148
}
149149

150-
fun overrideGate(gateName: String, gateValue: Boolean) {
151-
gateOverrides[gateName] = gateValue
150+
fun overrideGate(gateName: String, gateValue: Boolean, forID: String?) {
151+
val userOverrides = gateOverrides.getOrPut(gateName) { HashMap() }
152+
userOverrides[forID] = gateValue
152153
}
153154

154-
fun overrideConfig(configName: String, configValue: Map<String, Any>) {
155-
configOverrides[configName] = configValue
155+
fun overrideConfig(configName: String, configValue: Map<String, Any>, forID: String?) {
156+
val userOverrides = configOverrides.getOrPut(configName) { HashMap() }
157+
userOverrides[forID] = configValue
156158
}
157159

158-
fun overrideLayer(layerName: String, layerValue: Map<String, Any>) {
159-
layerOverrides[layerName] = layerValue
160+
fun overrideLayer(layerName: String, layerValue: Map<String, Any>, forID: String?) {
161+
val userOverrides = layerOverrides.getOrPut(layerName) { HashMap() }
162+
userOverrides[forID] = layerValue
160163
}
161164

162-
fun removeLayerOverride(layerName: String) {
163-
layerOverrides.remove(layerName)
165+
fun removeLayerOverride(layerName: String, forID: String?) {
166+
layerOverrides[layerName]?.remove(forID)
164167
}
165168

166-
fun removeConfigOverride(configName: String) {
167-
configOverrides.remove(configName)
169+
fun removeConfigOverride(configName: String, forID: String?) {
170+
configOverrides[configName]?.remove(forID)
168171
}
169172

170-
fun removeGateOverride(gateName: String) {
171-
gateOverrides.remove(gateName)
173+
fun removeGateOverride(gateName: String, forID: String?) {
174+
gateOverrides[gateName]?.remove(forID)
172175
}
173176

174177
fun getConfig(ctx: EvaluationContext, dynamicConfigName: String) {
175178
if (configOverrides.containsKey(dynamicConfigName)) {
176-
ctx.evaluation.jsonValue = configOverrides[dynamicConfigName] ?: mapOf<String, Any>()
179+
ctx.evaluation.jsonValue = configOverrides[dynamicConfigName]?.let { lookupConfigBasedOverride(it, ctx.user) }
180+
?: mapOf<String, Any>()
177181
ctx.evaluation.evaluationDetails = this.createEvaluationDetails((EvaluationReason.LOCAL_OVERRIDE))
178182
return
179183
}
@@ -280,7 +284,7 @@ internal class Evaluator(
280284

281285
fun getLayer(ctx: EvaluationContext, layerName: String) {
282286
if (layerOverrides.containsKey(layerName)) {
283-
val value = layerOverrides[layerName] ?: mapOf()
287+
val value = layerOverrides[layerName]?.let { lookupConfigBasedOverride(it, ctx.user) } ?: mapOf()
284288
ctx.evaluation.jsonValue = value
285289
ctx.evaluation.evaluationDetails = this.createEvaluationDetails(EvaluationReason.LOCAL_OVERRIDE)
286290
return
@@ -308,7 +312,7 @@ internal class Evaluator(
308312
@JvmOverloads
309313
fun checkGate(ctx: EvaluationContext, gateName: String) {
310314
if (gateOverrides.containsKey(gateName)) {
311-
val value = gateOverrides[gateName] ?: false
315+
val value = gateOverrides[gateName]?.let { lookUpGateOverride(it, ctx.user) } ?: false
312316
ctx.evaluation.booleanValue = value
313317
ctx.evaluation.jsonValue = value
314318
ctx.evaluation.evaluationDetails = createEvaluationDetails(EvaluationReason.LOCAL_OVERRIDE)
@@ -375,6 +379,38 @@ internal class Evaluator(
375379
}
376380
}
377381

382+
private fun <T> lookupOverride(
383+
userOverrides: MutableMap<String?, T>,
384+
user: StatsigUser,
385+
): T? {
386+
val overrideVal = user.userID?.let { userOverrides[it] }
387+
if (overrideVal != null) {
388+
return overrideVal
389+
}
390+
391+
user.customIDs?.forEach { (_, customIdVal) ->
392+
userOverrides[customIdVal]?.let {
393+
return it
394+
}
395+
}
396+
397+
return null
398+
}
399+
400+
private fun lookUpGateOverride(
401+
userOverrides: MutableMap<String?, Boolean>,
402+
user: StatsigUser
403+
): Boolean {
404+
return lookupOverride(userOverrides, user) ?: userOverrides[null] ?: false
405+
}
406+
407+
private fun lookupConfigBasedOverride(
408+
userOverrides: MutableMap<String?, Map<String, Any>>,
409+
user: StatsigUser
410+
): Map<String, Any> {
411+
return lookupOverride(userOverrides, user) ?: userOverrides[null] ?: mapOf()
412+
}
413+
378414
private fun evaluateLayer(ctx: EvaluationContext, config: APIConfig) {
379415
this.evaluateLayerImpl(ctx, config)
380416
this.finalizeEvaluation(ctx)

src/main/kotlin/com/statsig/sdk/Statsig.kt

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -256,23 +256,27 @@ class Statsig {
256256
*
257257
* @param layerName the layer to override
258258
* @param value the json value to override the config to
259+
* @param forID userID or customIDs of the user to override
259260
*/
260261
@JvmStatic
261-
fun overrideLayer(layerName: String, value: Map<String, Any>) {
262+
@JvmOverloads
263+
fun overrideLayer(layerName: String, value: Map<String, Any>, forID: String? = null) {
262264
if (checkInitialized()) {
263-
statsigServer.overrideLayer(layerName, value)
265+
statsigServer.overrideLayer(layerName, value, forID)
264266
}
265267
}
266268

267269
/**
268270
* Removes the given layer override
269271
*
270272
* @param layerName
273+
* @param forID userID or customIDs of the user to remove override
271274
*/
272275
@JvmStatic
273-
fun removeLayerOverride(layerName: String) {
276+
@JvmOverloads
277+
fun removeLayerOverride(layerName: String, forID: String? = null) {
274278
if (checkInitialized()) {
275-
statsigServer.removeLayerOverride(layerName)
279+
statsigServer.removeLayerOverride(layerName, forID)
276280
}
277281
}
278282

@@ -291,24 +295,28 @@ class Statsig {
291295
*
292296
* @param gateName The name of the gate to be overridden
293297
* @param gateValue The value that will be returned
298+
* @param forID userID or customIDs of the user to override
294299
*/
295300
@JvmStatic
296-
fun overrideGate(gateName: String, gateValue: Boolean) {
301+
@JvmOverloads
302+
fun overrideGate(gateName: String, gateValue: Boolean, forID: String? = null) {
297303
if (!checkInitialized()) {
298304
return
299305
}
300-
statsigServer.overrideGate(gateName, gateValue)
306+
statsigServer.overrideGate(gateName, gateValue, forID)
301307
}
302308

303309
/**
304310
* Removes the given gate override
305311
*
306312
* @param gateName
313+
* @param forID userID or customIDs of the user to remove override
307314
*/
308315
@JvmStatic
309-
fun removeGateOverride(gateName: String) {
316+
@JvmOverloads
317+
fun removeGateOverride(gateName: String, forID: String? = null) {
310318
if (checkInitialized()) {
311-
statsigServer.removeGateOverride(gateName)
319+
statsigServer.removeGateOverride(gateName, forID)
312320
}
313321
}
314322

@@ -317,24 +325,28 @@ class Statsig {
317325
*
318326
* @param configName The name of the dynamic config or experiment to be overridden
319327
* @param configValue The value that will be returned
328+
* @param forID userID or customIDs of the user to override
320329
*/
321330
@JvmStatic
322-
fun overrideConfig(configName: String, configValue: Map<String, Any>) {
331+
@JvmOverloads
332+
fun overrideConfig(configName: String, configValue: Map<String, Any>, forID: String? = null) {
323333
if (!checkInitialized()) {
324334
return
325335
}
326-
statsigServer.overrideConfig(configName, configValue)
336+
statsigServer.overrideConfig(configName, configValue, forID)
327337
}
328338

329339
/**
330340
* Removes the given config override
331341
*
332342
* @param configName
343+
* @param forID userID or customIDs of the user to remove override
333344
*/
334345
@JvmStatic
335-
fun removeConfigOverride(configName: String) {
346+
@JvmOverloads
347+
fun removeConfigOverride(configName: String, forID: String? = null) {
336348
if (checkInitialized()) {
337-
statsigServer.removeConfigOverride(configName)
349+
statsigServer.removeConfigOverride(configName, forID)
338350
}
339351
}
340352

src/main/kotlin/com/statsig/sdk/StatsigServer.kt

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,19 +76,31 @@ sealed class StatsigServer {
7676
@JvmSynthetic
7777
abstract suspend fun getLayerWithExposureLoggingDisabled(user: StatsigUser, layerName: String): Layer
7878

79+
abstract fun overrideLayer(layerName: String, value: Map<String, Any>, forID: String? = null)
80+
7981
abstract fun overrideLayer(layerName: String, value: Map<String, Any>)
8082

83+
abstract fun removeLayerOverride(layerName: String, forID: String? = null)
84+
8185
abstract fun removeLayerOverride(layerName: String)
8286

8387
@JvmSynthetic
8488
abstract suspend fun shutdownSuspend()
8589

90+
abstract fun overrideGate(gateName: String, gateValue: Boolean, forID: String? = null)
91+
8692
abstract fun overrideGate(gateName: String, gateValue: Boolean)
8793

94+
abstract fun removeGateOverride(gateName: String, forID: String? = null)
95+
8896
abstract fun removeGateOverride(gateName: String)
8997

98+
abstract fun overrideConfig(configName: String, configValue: Map<String, Any>, forID: String? = null)
99+
90100
abstract fun overrideConfig(configName: String, configValue: Map<String, Any>)
91101

102+
abstract fun removeConfigOverride(configName: String, forID: String? = null)
103+
92104
abstract fun removeConfigOverride(configName: String)
93105

94106
@JvmSynthetic
@@ -853,7 +865,17 @@ private class StatsigServerImpl() :
853865
}
854866
this.errorBoundary.captureSync("overrideLayer", {
855867
isSDKInitialized()
856-
evaluator.overrideLayer(layerName, value)
868+
evaluator.overrideLayer(layerName, value, null)
869+
}, { return@captureSync })
870+
}
871+
872+
override fun overrideLayer(layerName: String, value: Map<String, Any>, forID: String?) {
873+
if (!isSDKInitialized()) {
874+
return
875+
}
876+
this.errorBoundary.captureSync("overrideLayer", {
877+
isSDKInitialized()
878+
evaluator.overrideLayer(layerName, value, forID)
857879
}, { return@captureSync })
858880
}
859881

@@ -863,7 +885,17 @@ private class StatsigServerImpl() :
863885
}
864886
this.errorBoundary.captureSync("removeLayerOverride", {
865887
isSDKInitialized()
866-
evaluator.removeLayerOverride(layerName)
888+
evaluator.removeLayerOverride(layerName, null)
889+
}, { return@captureSync })
890+
}
891+
892+
override fun removeLayerOverride(layerName: String, forID: String?) {
893+
if (!isSDKInitialized()) {
894+
return
895+
}
896+
this.errorBoundary.captureSync("removeLayerOverride", {
897+
isSDKInitialized()
898+
evaluator.removeLayerOverride(layerName, forID)
867899
}, { return@captureSync })
868900
}
869901

@@ -933,13 +965,23 @@ private class StatsigServerImpl() :
933965
}
934966
}
935967

968+
override fun overrideGate(gateName: String, gateValue: Boolean, forID: String?) {
969+
if (!isSDKInitialized()) {
970+
return
971+
}
972+
errorBoundary.captureSync("overrideGate", {
973+
isSDKInitialized()
974+
evaluator.overrideGate(gateName, gateValue, forID)
975+
}, { return@captureSync })
976+
}
977+
936978
override fun overrideGate(gateName: String, gateValue: Boolean) {
937979
if (!isSDKInitialized()) {
938980
return
939981
}
940982
errorBoundary.captureSync("overrideGate", {
941983
isSDKInitialized()
942-
evaluator.overrideGate(gateName, gateValue)
984+
evaluator.overrideGate(gateName, gateValue, null)
943985
}, { return@captureSync })
944986
}
945987

@@ -949,7 +991,17 @@ private class StatsigServerImpl() :
949991
}
950992
errorBoundary.captureSync("removeGateOverride", {
951993
isSDKInitialized()
952-
evaluator.removeGateOverride(gateName)
994+
evaluator.removeGateOverride(gateName, null)
995+
}, { return@captureSync })
996+
}
997+
998+
override fun removeGateOverride(gateName: String, forID: String?) {
999+
if (!isSDKInitialized()) {
1000+
return
1001+
}
1002+
errorBoundary.captureSync("removeGateOverride", {
1003+
isSDKInitialized()
1004+
evaluator.removeGateOverride(gateName, forID)
9531005
}, { return@captureSync })
9541006
}
9551007

@@ -959,7 +1011,17 @@ private class StatsigServerImpl() :
9591011
}
9601012
errorBoundary.captureSync("overrideConfig", {
9611013
isSDKInitialized()
962-
evaluator.overrideConfig(configName, configValue)
1014+
evaluator.overrideConfig(configName, configValue, null)
1015+
}, { return@captureSync })
1016+
}
1017+
1018+
override fun overrideConfig(configName: String, configValue: Map<String, Any>, forID: String?) {
1019+
if (!isSDKInitialized()) {
1020+
return
1021+
}
1022+
errorBoundary.captureSync("overrideConfig", {
1023+
isSDKInitialized()
1024+
evaluator.overrideConfig(configName, configValue, forID)
9631025
}, { return@captureSync })
9641026
}
9651027

@@ -969,7 +1031,17 @@ private class StatsigServerImpl() :
9691031
}
9701032
errorBoundary.captureSync("removeConfigOverride", {
9711033
isSDKInitialized()
972-
evaluator.removeConfigOverride(configName)
1034+
evaluator.removeConfigOverride(configName, null)
1035+
}, { return@captureSync })
1036+
}
1037+
1038+
override fun removeConfigOverride(configName: String, forID: String?) {
1039+
if (!isSDKInitialized()) {
1040+
return
1041+
}
1042+
errorBoundary.captureSync("removeConfigOverride", {
1043+
isSDKInitialized()
1044+
evaluator.removeConfigOverride(configName, forID)
9731045
}, { return@captureSync })
9741046
}
9751047

0 commit comments

Comments
 (0)