Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MBL-1722: Extended feature flag client to check user enabled feature flags #2129

Merged
merged 4 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take a look at the query:
Screenshot 2024-09-12 at 9 01 40 AM

}

/**
* 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",
"[email protected]",
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",
"[email protected]",
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",
"[email protected]",
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