Skip to content

Commit 215403e

Browse files
Add new APIs to Toggle (#5922)
Task/Issue URL: https://app.asana.com/1/137249556945/project/1125189844152671/task/1210003928362253?focus=true ### Description This PR adds two new APIs to Toggle to simplify feature flags checks when running experiments ### Steps to test this PR - [ ] Check code - [ ] Tests should pass
1 parent 4e9df2e commit 215403e

File tree

2 files changed

+102
-1
lines changed

2 files changed

+102
-1
lines changed

feature-toggles/feature-toggles-api/src/main/java/com/duckduckgo/feature/toggles/api/FeatureToggles.kt

+19-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,9 @@ interface Toggle {
162162
fun featureName(): FeatureName
163163

164164
/**
165-
* This is the method that SHALL be called to get whether a feature is enabled or not. DO NOT USE [getRawStoredState] for that
165+
* This is the method that SHALL be called to get whether a feature is enabled or not. DO NOT USE [getRawStoredState] for that.
166+
* WARNING: Calling this method with a cohort different from [ANY_COHORT] WILL ALWAYS try to enroll the user into an experiment.
167+
* This method enrolls users as long as they match the targets, even if the feature is disabled or the min version does not match.
166168
* @return `true` if the feature should be enabled, `false` otherwise
167169
*/
168170
fun isEnabled(cohort: CohortName = ANY_COHORT): Boolean
@@ -192,12 +194,24 @@ interface Toggle {
192194
*/
193195
fun getSettings(): String?
194196

197+
/**
198+
* WARNING: This method does not check if the experiment is still enabled or not.
199+
* @return `true` if the user is enrolled in the experiment and `false` otherwise
200+
*/
201+
fun isEnrolled(): Boolean
202+
203+
/**
204+
* @return `true` if the user is enrolled in the given cohort and the experiment is enabled or `false` otherwise
205+
*/
206+
fun isEnrolledAndEnabled(cohort: CohortName): Boolean
207+
195208
/**
196209
* @return the list of domain exceptions`exceptions` of the feature or empty list if not present in the remote config
197210
*/
198211
fun getExceptions(): List<FeatureException>
199212

200213
/**
214+
* WARNING: This method always returns the cohort assigned regardless it the experiment is still enabled or not.
201215
* @return a [Cohort] if one has been assigned or `null` otherwise.
202216
*/
203217
fun getCohort(): Cohort?
@@ -531,4 +545,8 @@ internal class ToggleImpl constructor(
531545
override fun getCohort(): Cohort? {
532546
return store.get(key)?.assignedCohort
533547
}
548+
549+
override fun isEnrolled(): Boolean = getCohort() != null
550+
551+
override fun isEnrolledAndEnabled(cohort: CohortName): Boolean = isEnrolled() && isEnabled(cohort)
534552
}

feature-toggles/feature-toggles-impl/src/test/java/com/duckduckgo/feature/toggles/codegen/ContributesRemoteFeatureCodeGeneratorTest.kt

+83
Original file line numberDiff line numberDiff line change
@@ -2814,13 +2814,19 @@ class ContributesRemoteFeatureCodeGeneratorTest {
28142814
var rawState = testFeature.fooFeature().getRawStoredState()
28152815
assertNotEquals(emptyList<Cohort>(), rawState?.cohorts)
28162816
assertNull(rawState?.assignedCohort)
2817+
assertFalse(testFeature.fooFeature().isEnrolled())
2818+
assertFalse(testFeature.fooFeature().isEnrolledAndEnabled(CONTROL))
2819+
assertFalse(testFeature.fooFeature().isEnrolledAndEnabled(BLUE))
28172820

28182821
// we call isEnabled() without cohort, cohort should not be assigned either
28192822
testFeature.fooFeature().isEnabled()
28202823
rawState = testFeature.fooFeature().getRawStoredState()
28212824
assertNotEquals(emptyList<Cohort>(), rawState?.cohorts)
28222825
assertNull(rawState?.assignedCohort)
28232826
assertNull(testFeature.fooFeature().getCohort())
2827+
assertFalse(testFeature.fooFeature().isEnrolled())
2828+
assertFalse(testFeature.fooFeature().isEnrolledAndEnabled(CONTROL))
2829+
assertFalse(testFeature.fooFeature().isEnrolledAndEnabled(BLUE))
28242830

28252831
// we call isEnabled(cohort), then we should assign cohort
28262832
testFeature.fooFeature().isEnabled(BLUE)
@@ -2829,6 +2835,8 @@ class ContributesRemoteFeatureCodeGeneratorTest {
28292835
assertNotNull(rawState?.assignedCohort)
28302836
assertNotNull(testFeature.fooFeature().getCohort())
28312837
assertFalse(testFeature.fooFeature().isEnabled(CONTROL))
2838+
assertTrue(testFeature.fooFeature().isEnrolled())
2839+
assertFalse(testFeature.fooFeature().isEnrolledAndEnabled(BLUE))
28322840
}
28332841

28342842
@Test
@@ -2875,6 +2883,9 @@ class ContributesRemoteFeatureCodeGeneratorTest {
28752883
assertFalse(testFeature.fooFeature().isEnabled(BLUE))
28762884
assertNotNull(testFeature.fooFeature().getRawStoredState()!!.assignedCohort)
28772885
assertNotNull(testFeature.fooFeature().getCohort())
2886+
assertTrue(testFeature.fooFeature().isEnrolled())
2887+
assertTrue(testFeature.fooFeature().isEnrolledAndEnabled(CONTROL))
2888+
assertFalse(testFeature.fooFeature().isEnrolledAndEnabled(BLUE))
28782889

28792890
// remove blue cohort
28802891
assertTrue(
@@ -2940,6 +2951,9 @@ class ContributesRemoteFeatureCodeGeneratorTest {
29402951
assertNull(testFeature.fooFeature().getRawStoredState()!!.assignedCohort)
29412952
assertNull(testFeature.fooFeature().getCohort())
29422953
assertTrue(testFeature.fooFeature().isEnabled())
2954+
assertFalse(testFeature.fooFeature().isEnrolled())
2955+
assertFalse(testFeature.fooFeature().isEnrolledAndEnabled(CONTROL))
2956+
assertFalse(testFeature.fooFeature().isEnrolledAndEnabled(BLUE))
29432957
}
29442958

29452959
@Test
@@ -2950,6 +2964,9 @@ class ContributesRemoteFeatureCodeGeneratorTest {
29502964

29512965
assertFalse(testFeature.fooFeature().isEnabled(CONTROL))
29522966
assertFalse(testFeature.fooFeature().isEnabled(BLUE))
2967+
assertFalse(testFeature.fooFeature().isEnrolled())
2968+
assertFalse(testFeature.fooFeature().isEnrolledAndEnabled(CONTROL))
2969+
assertFalse(testFeature.fooFeature().isEnrolledAndEnabled(BLUE))
29532970

29542971
assertTrue(
29552972
privacyPlugin.store(
@@ -2987,6 +3004,9 @@ class ContributesRemoteFeatureCodeGeneratorTest {
29873004

29883005
assertTrue(testFeature.fooFeature().isEnabled(CONTROL))
29893006
assertFalse(testFeature.fooFeature().isEnabled(BLUE))
3007+
assertTrue(testFeature.fooFeature().isEnrolled())
3008+
assertTrue(testFeature.fooFeature().isEnrolledAndEnabled(CONTROL))
3009+
assertFalse(testFeature.fooFeature().isEnrolledAndEnabled(BLUE))
29903010

29913011
assertTrue(
29923012
privacyPlugin.store(
@@ -3097,6 +3117,9 @@ class ContributesRemoteFeatureCodeGeneratorTest {
30973117

30983118
assertTrue(testFeature.fooFeature().isEnabled(CONTROL))
30993119
assertFalse(testFeature.fooFeature().isEnabled(BLUE))
3120+
assertTrue(testFeature.fooFeature().isEnrolled())
3121+
assertTrue(testFeature.fooFeature().isEnrolledAndEnabled(CONTROL))
3122+
assertFalse(testFeature.fooFeature().isEnrolledAndEnabled(BLUE))
31003123
}
31013124

31023125
@Test
@@ -3332,6 +3355,9 @@ class ContributesRemoteFeatureCodeGeneratorTest {
33323355

33333356
assertTrue(testFeature.fooFeature().isEnabled(CONTROL))
33343357
assertFalse(testFeature.fooFeature().isEnabled(BLUE))
3358+
assertTrue(testFeature.fooFeature().isEnrolled())
3359+
assertTrue(testFeature.fooFeature().isEnrolledAndEnabled(CONTROL))
3360+
assertFalse(testFeature.fooFeature().isEnrolledAndEnabled(BLUE))
33353361

33363362
// changing cohort targets should not change cohort assignment
33373363
assertTrue(
@@ -3374,6 +3400,9 @@ class ContributesRemoteFeatureCodeGeneratorTest {
33743400
)
33753401
assertTrue(testFeature.fooFeature().isEnabled(CONTROL))
33763402
assertFalse(testFeature.fooFeature().isEnabled(BLUE))
3403+
assertTrue(testFeature.fooFeature().isEnrolled())
3404+
assertTrue(testFeature.fooFeature().isEnrolledAndEnabled(CONTROL))
3405+
assertFalse(testFeature.fooFeature().isEnrolledAndEnabled(BLUE))
33773406

33783407
// changing cohort weight should not change current assignment
33793408
assertTrue(
@@ -3517,6 +3546,54 @@ class ContributesRemoteFeatureCodeGeneratorTest {
35173546
assertEquals(now, parsedDate)
35183547
}
35193548

3549+
@Test
3550+
fun `test calling is enrolled and enabled does not enroll`() {
3551+
val feature = generatedFeatureNewInstance()
3552+
3553+
val privacyPlugin = (feature as PrivacyFeaturePlugin)
3554+
3555+
assertTrue(
3556+
privacyPlugin.store(
3557+
"testFeature",
3558+
"""
3559+
{
3560+
"hash": "1",
3561+
"state": "disabled",
3562+
"features": {
3563+
"fooFeature": {
3564+
"state": "enabled",
3565+
"rollout": {
3566+
"steps": [
3567+
{
3568+
"percent": 100
3569+
}
3570+
]
3571+
},
3572+
"cohorts": [
3573+
{
3574+
"name": "control",
3575+
"weight": 1
3576+
},
3577+
{
3578+
"name": "blue",
3579+
"weight": 0
3580+
}
3581+
]
3582+
}
3583+
}
3584+
}
3585+
""".trimIndent(),
3586+
),
3587+
)
3588+
3589+
assertFalse(testFeature.fooFeature().isEnrolledAndEnabled(CONTROL))
3590+
assertFalse(testFeature.fooFeature().isEnrolledAndEnabled(BLUE))
3591+
assertFalse(testFeature.fooFeature().isEnrolled())
3592+
assertTrue(testFeature.fooFeature().isEnabled(CONTROL))
3593+
assertTrue(testFeature.fooFeature().isEnrolled())
3594+
assertTrue(testFeature.fooFeature().isEnrolledAndEnabled(CONTROL))
3595+
}
3596+
35203597
@Test
35213598
fun `test rollback cohort experiments`() {
35223599
val feature = generatedFeatureNewInstance()
@@ -3560,6 +3637,9 @@ class ContributesRemoteFeatureCodeGeneratorTest {
35603637
val rolloutThreshold = testFeature.fooFeature().getRawStoredState()?.rolloutThreshold!!
35613638
assertTrue(testFeature.fooFeature().isEnabled(CONTROL))
35623639
assertFalse(testFeature.fooFeature().isEnabled(BLUE))
3640+
assertTrue(testFeature.fooFeature().isEnrolled())
3641+
assertTrue(testFeature.fooFeature().isEnrolledAndEnabled(CONTROL))
3642+
assertFalse(testFeature.fooFeature().isEnrolledAndEnabled(BLUE))
35633643

35643644
assertTrue(
35653645
privacyPlugin.store(
@@ -3597,6 +3677,9 @@ class ContributesRemoteFeatureCodeGeneratorTest {
35973677

35983678
assertFalse(testFeature.fooFeature().isEnabled(CONTROL))
35993679
assertFalse(testFeature.fooFeature().isEnabled(BLUE))
3680+
assertTrue(testFeature.fooFeature().isEnrolled())
3681+
assertFalse(testFeature.fooFeature().isEnrolledAndEnabled(CONTROL))
3682+
assertFalse(testFeature.fooFeature().isEnrolledAndEnabled(BLUE))
36003683
}
36013684

36023685
@Test

0 commit comments

Comments
 (0)