diff --git a/app/src/main/graphql/userprivacy.graphql b/app/src/main/graphql/userprivacy.graphql index 811c6c7ba4..16a1f316f0 100644 --- a/app/src/main/graphql/userprivacy.graphql +++ b/app/src/main/graphql/userprivacy.graphql @@ -16,6 +16,7 @@ query UserPrivacy { isDeliverable isEmailVerified chosenCurrency + enabledFeatures } } diff --git a/app/src/main/java/com/kickstarter/libs/featureflag/FeatureFlagClient.kt b/app/src/main/java/com/kickstarter/libs/featureflag/FeatureFlagClient.kt index db592ecec1..315f67b5a0 100644 --- a/app/src/main/java/com/kickstarter/libs/featureflag/FeatureFlagClient.kt +++ b/app/src/main/java/com/kickstarter/libs/featureflag/FeatureFlagClient.kt @@ -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, key: FlipperFlagKey): Observable { + return privacy.map { it.enabledFeatures.contains(key.key) } + } + /** * Will received a callback, that callback will usually * initialize the external library @@ -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"), @@ -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 diff --git a/app/src/main/java/com/kickstarter/mock/services/MockApolloClient.kt b/app/src/main/java/com/kickstarter/mock/services/MockApolloClient.kt index 0680349f72..9b71805b6b 100644 --- a/app/src/main/java/com/kickstarter/mock/services/MockApolloClient.kt +++ b/app/src/main/java/com/kickstarter/mock/services/MockApolloClient.kt @@ -547,7 +547,8 @@ open class MockApolloClient : ApolloClientType { true, true, true, - "USD" + "USD", + emptyList() ) ) ) diff --git a/app/src/main/java/com/kickstarter/models/UserPrivacy.kt b/app/src/main/java/com/kickstarter/models/UserPrivacy.kt index 933949746f..eb7ad34180 100644 --- a/app/src/main/java/com/kickstarter/models/UserPrivacy.kt +++ b/app/src/main/java/com/kickstarter/models/UserPrivacy.kt @@ -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 = emptyList() ) diff --git a/app/src/main/java/com/kickstarter/services/transformers/GraphQLTransformers.kt b/app/src/main/java/com/kickstarter/services/transformers/GraphQLTransformers.kt index f06fed5209..df320d5674 100644 --- a/app/src/main/java/com/kickstarter/services/transformers/GraphQLTransformers.kt +++ b/app/src/main/java/com/kickstarter/services/transformers/GraphQLTransformers.kt @@ -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() + } ) } diff --git a/app/src/main/java/com/kickstarter/viewmodels/LoggedInViewHolderViewModel.kt b/app/src/main/java/com/kickstarter/viewmodels/LoggedInViewHolderViewModel.kt index 4878303e53..989edc3777 100644 --- a/app/src/main/java/com/kickstarter/viewmodels/LoggedInViewHolderViewModel.kt +++ b/app/src/main/java/com/kickstarter/viewmodels/LoggedInViewHolderViewModel.kt @@ -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 @@ -50,8 +52,10 @@ interface LoggedInViewHolderViewModel { class ViewModel(val environment: Environment) : Inputs, Outputs { - private val user = PublishSubject.create() + private val apolloClient = requireNotNull(environment.apolloClientV2()) + private val featureFlagClient = requireNotNull(environment.featureFlagClient()) + private val user = PublishSubject.create() private val activityCount = BehaviorSubject.create() private val activityCountTextColor = BehaviorSubject.create() private val avatarUrl = BehaviorSubject.create() @@ -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) } diff --git a/app/src/test/java/com/kickstarter/viewmodels/DiscoveryFragmentViewModelTest.kt b/app/src/test/java/com/kickstarter/viewmodels/DiscoveryFragmentViewModelTest.kt index ac6845c35d..d0c597ba8d 100644 --- a/app/src/test/java/com/kickstarter/viewmodels/DiscoveryFragmentViewModelTest.kt +++ b/app/src/test/java/com/kickstarter/viewmodels/DiscoveryFragmentViewModelTest.kt @@ -421,7 +421,8 @@ class DiscoveryFragmentViewModelTest : KSRobolectricTestCase() { true, false, false, - "" + "", + emptyList() ) ) ) diff --git a/app/src/test/java/com/kickstarter/viewmodels/LoggedInViewHolderViewModelTest.kt b/app/src/test/java/com/kickstarter/viewmodels/LoggedInViewHolderViewModelTest.kt index c76f747f70..7e21f9e30c 100644 --- a/app/src/test/java/com/kickstarter/viewmodels/LoggedInViewHolderViewModelTest.kt +++ b/app/src/test/java/com/kickstarter/viewmodels/LoggedInViewHolderViewModelTest.kt @@ -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 @@ -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 { + 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 { + 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 { + 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