Skip to content

Commit

Permalink
MBL-1722: Extended feature flag client to check user enabled feature …
Browse files Browse the repository at this point in the history
…flags (#2129)
  • Loading branch information
Arkariang authored Sep 12, 2024
1 parent 54d7405 commit a2979cd
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 37 deletions.
1 change: 1 addition & 0 deletions app/src/main/graphql/userprivacy.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ query UserPrivacy {
isDeliverable
isEmailVerified
chosenCurrency
enabledFeatures
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,21 @@ import com.kickstarter.libs.Build
import com.kickstarter.libs.Build.isInternal
import com.kickstarter.libs.featureflag.FeatureFlagClient.Companion.INTERNAL_INTERVAL
import com.kickstarter.libs.featureflag.FeatureFlagClient.Companion.RELEASE_INTERVAL
import com.kickstarter.models.UserPrivacy
import io.reactivex.Observable
import timber.log.Timber

interface FeatureFlagClientType {

/**
* Backend list of features flags enabled within `userPrivacy.enabledFeatures` field
*
* Checks if the FlipperFlagKey.name is present within enabledFeatures
*/
fun isBackendEnabledFlag(privacy: Observable<UserPrivacy>, key: FlipperFlagKey): Observable<Boolean> {
return privacy.map { it.enabledFeatures.contains(key.key) }
}

/**
* Will received a callback, that callback will usually
* initialize the external library
Expand Down Expand Up @@ -52,6 +63,9 @@ interface FeatureFlagClientType {
*/
fun getString(FlagKey: FlagKey): String
}
enum class FlipperFlagKey(val key: String) {
FLIPPER_PLEDGED_PROJECTS_OVERVIEW("pledge_projects_overview_2024")
}

enum class FlagKey(val key: String) {
ANDROID_FACEBOOK_LOGIN_REMOVE("android_facebook_login_remove"),
Expand Down Expand Up @@ -137,6 +151,7 @@ class FeatureFlagClient(
}

override fun getString(key: FlagKey): String {

val value = remoteConfig?.getString(key.key) ?: ""
log("${this.javaClass} feature flag ${key.key}: $value")
return value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,8 @@ open class MockApolloClient : ApolloClientType {
true,
true,
true,
"USD"
"USD",
emptyList()
)
)
)
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/java/com/kickstarter/models/UserPrivacy.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ data class UserPrivacy(
val isCreator: Boolean,
val isDeliverable: Boolean,
val isEmailVerified: Boolean,
val chosenCurrency: String
val chosenCurrency: String,
val enabledFeatures: List<String> = emptyList()
)
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,10 @@ fun userPrivacyTransformer(userPrivacy: UserPrivacyQuery.Me): UserPrivacy {
isCreator = userPrivacy.isCreator ?: false,
isDeliverable = userPrivacy.isDeliverable ?: false,
isEmailVerified = userPrivacy.isEmailVerified ?: false,
chosenCurrency = userPrivacy.chosenCurrency() ?: defaultCurrency
chosenCurrency = userPrivacy.chosenCurrency() ?: defaultCurrency,
enabledFeatures = userPrivacy.enabledFeatures().map {
it.rawValue()
}
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.kickstarter.viewmodels
import com.kickstarter.R
import com.kickstarter.libs.Environment
import com.kickstarter.libs.featureflag.FlagKey
import com.kickstarter.libs.featureflag.FlipperFlagKey
import com.kickstarter.libs.rx.transformers.Transformers
import com.kickstarter.libs.utils.extensions.addToDisposable
import com.kickstarter.libs.utils.extensions.intValueOrZero
import com.kickstarter.libs.utils.extensions.isTrue
Expand Down Expand Up @@ -50,8 +52,10 @@ interface LoggedInViewHolderViewModel {

class ViewModel(val environment: Environment) : Inputs, Outputs {

private val user = PublishSubject.create<User>()
private val apolloClient = requireNotNull(environment.apolloClientV2())
private val featureFlagClient = requireNotNull(environment.featureFlagClient())

private val user = PublishSubject.create<User>()
private val activityCount = BehaviorSubject.create<Int>()
private val activityCountTextColor = BehaviorSubject.create<Int>()
private val avatarUrl = BehaviorSubject.create<String>()
Expand Down Expand Up @@ -111,10 +115,13 @@ interface LoggedInViewHolderViewModel {
.subscribe { this.pledgedProjectsIndicatorIsVisible.onNext(it) }
.addToDisposable(disposables)

Observable.just(
environment.featureFlagClient()
?.getBoolean(FlagKey.ANDROID_PLEDGED_PROJECTS_OVERVIEW) ?: false
)
featureFlagClient.isBackendEnabledFlag(this.apolloClient.userPrivacy(), FlipperFlagKey.FLIPPER_PLEDGED_PROJECTS_OVERVIEW)
.compose(Transformers.neverErrorV2())
.map { ffEnabledBackend ->
val ffEnabledMobile = featureFlagClient.getBoolean(FlagKey.ANDROID_PLEDGED_PROJECTS_OVERVIEW)

return@map ffEnabledMobile && ffEnabledBackend
}
.subscribe { this.pledgedProjectsIsVisible.onNext(it) }
.addToDisposable(disposables)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,8 @@ class DiscoveryFragmentViewModelTest : KSRobolectricTestCase() {
true,
false,
false,
""
"",
emptyList()
)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import com.kickstarter.KSRobolectricTestCase
import com.kickstarter.R
import com.kickstarter.libs.Environment
import com.kickstarter.libs.featureflag.FlagKey
import com.kickstarter.libs.featureflag.FlipperFlagKey
import com.kickstarter.libs.utils.extensions.addToDisposable
import com.kickstarter.mock.MockFeatureFlagClient
import com.kickstarter.mock.factories.UserFactory
import com.kickstarter.mock.services.MockApolloClientV2
import com.kickstarter.models.User
import com.kickstarter.models.UserPrivacy
import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.subscribers.TestSubscriber
import org.junit.After
Expand Down Expand Up @@ -161,51 +165,130 @@ class LoggedInViewHolderViewModelTest : KSRobolectricTestCase() {
}

@Test
fun `when pledged projects feature is flag off, should emit false`() {
val mockFeatureFlagClient: MockFeatureFlagClient =
object : MockFeatureFlagClient() {
override fun getBoolean(FlagKey: FlagKey): Boolean {
return false
}
}
fun `when user has project alerts, should emit true`() {
setUpEnvironment(environment())
val user = UserFactory.user().toBuilder().ppoHasAction(true).build()
this.vm.inputs.configureWith(user)

setUpEnvironment(environment().toBuilder().featureFlagClient(mockFeatureFlagClient).build())
val user = UserFactory.user()
this.pledgedProjectsIndicatorIsVisible.assertValue(true)
}

this.pledgedProjectsIsVisible.assertValue(false)
@Test
fun `when user doesnt have project alerts, should emit false`() {
setUpEnvironment(environment())
val user = UserFactory.user().toBuilder().ppoHasAction(false).build()
this.vm.inputs.configureWith(user)

this.pledgedProjectsIndicatorIsVisible.assertValue(false)
}

@Test
fun `when pledged projects feature is flag on, should emit true`() {
val mockFeatureFlagClient: MockFeatureFlagClient =
object : MockFeatureFlagClient() {
override fun getBoolean(FlagKey: FlagKey): Boolean {
return true
}
fun `test feature flag enabled in backed and mobile shows PPO`() {
val privacy = UserPrivacy(
"Some Name",
"some@email.com",
true,
true,
true,
true,
"USD",
enabledFeatures = listOf("some_key_here", FlipperFlagKey.FLIPPER_PLEDGED_PROJECTS_OVERVIEW.key)
)

val apolloClient = object : MockApolloClientV2() {
override fun userPrivacy(): Observable<UserPrivacy> {
return Observable.just(
privacy
)
}
}
val ffClient = object : MockFeatureFlagClient() {
override fun getBoolean(FlagKey: FlagKey): Boolean {
return true
}
}

setUpEnvironment(environment().toBuilder().featureFlagClient(mockFeatureFlagClient).build())
val user = UserFactory.user()
val environment = environment().toBuilder()
.apolloClientV2(apolloClient)
.featureFlagClient(ffClient)
.build()

setUpEnvironment(environment)

this.pledgedProjectsIsVisible.assertValue(true)
}

@Test
fun `when user has project alerts, should emit true`() {
setUpEnvironment(environment())
val user = UserFactory.user().toBuilder().ppoHasAction(true).build()
this.vm.inputs.configureWith(user)
fun `test feature flag disabled in backed and enabled in mobile not show PPO`() {
val privacy = UserPrivacy(
"Some Name",
"some@email.com",
true,
true,
true,
true,
"USD",
enabledFeatures = listOf("some_key_here")
)

val apolloClient = object : MockApolloClientV2() {
override fun userPrivacy(): Observable<UserPrivacy> {
return Observable.just(
privacy
)
}
}
val ffClient = object : MockFeatureFlagClient() {
override fun getBoolean(FlagKey: FlagKey): Boolean {
return true
}
}

this.pledgedProjectsIndicatorIsVisible.assertValue(true)
val environment = environment().toBuilder()
.apolloClientV2(apolloClient)
.featureFlagClient(ffClient)
.build()

setUpEnvironment(environment)

this.pledgedProjectsIsVisible.assertValue(false)
}

@Test
fun `when user doesnt have project alerts, should emit false`() {
setUpEnvironment(environment())
val user = UserFactory.user().toBuilder().ppoHasAction(false).build()
this.vm.inputs.configureWith(user)
fun `test feature flag disabled in backed and disabled in mobile not show PPO`() {
val privacy = UserPrivacy(
"Some Name",
"some@email.com",
true,
true,
true,
true,
"USD",
enabledFeatures = listOf("some_key_here")
)

val apolloClient = object : MockApolloClientV2() {
override fun userPrivacy(): Observable<UserPrivacy> {
return Observable.just(
privacy
)
}
}

this.pledgedProjectsIndicatorIsVisible.assertValue(false)
val ffClient = object : MockFeatureFlagClient() {
override fun getBoolean(FlagKey: FlagKey): Boolean {
return false
}
}

val environment = environment().toBuilder()
.apolloClientV2(apolloClient)
.featureFlagClient(ffClient)
.build()

setUpEnvironment(environment)

this.pledgedProjectsIsVisible.assertValue(false)
}

@After
Expand Down

0 comments on commit a2979cd

Please sign in to comment.