diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/featureflags/FeatureFlagEnum.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/featureflags/FeatureFlagEnum.java index 98e0c0c9f2..86cca7a3a5 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/featureflags/FeatureFlagEnum.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/featureflags/FeatureFlagEnum.java @@ -20,6 +20,7 @@ public enum FeatureFlagEnum { // ------------------- Couldn't find a better way to do this ---------------------------------------------------- // TEST_FEATURE_1, TEST_FEATURE_2, + TEST_FEATURE_3, // ------------------- End of features for testing -------------------------------------------------------------- // // ------------------- These are actual feature flags meant to be used across the product ----------------------- // diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/featureflags/strategies/EmailBasedRolloutStrategy.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/featureflags/strategies/EmailBasedRolloutStrategy.java new file mode 100644 index 0000000000..7734646c2e --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/featureflags/strategies/EmailBasedRolloutStrategy.java @@ -0,0 +1,48 @@ +package com.appsmith.server.featureflags.strategies; + +import com.appsmith.server.constants.FieldName; +import com.appsmith.server.domains.User; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.ff4j.core.FeatureStore; +import org.ff4j.core.FlippingExecutionContext; +import org.ff4j.strategy.AbstractFlipStrategy; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +@Slf4j +public class EmailBasedRolloutStrategy extends AbstractFlipStrategy { + + List validDomains = new ArrayList<>(); + + private static final String PARAM_EMAIL_DOMAINS = "emailDomains"; + + /** {@inheritDoc} */ + @Override + public void init(String featureName, Map initParam) { + super.init(featureName, initParam); + assertRequiredParameter(PARAM_EMAIL_DOMAINS); + if (!StringUtils.isEmpty(initParam.get(PARAM_EMAIL_DOMAINS))) { + this.validDomains = Arrays.asList(initParam.get(PARAM_EMAIL_DOMAINS).split(",")); + } + } + + /** {@inheritDoc} */ + @Override + public boolean evaluate(String featureName, FeatureStore store, FlippingExecutionContext executionContext) { + User user = (User) executionContext.getValue(FieldName.USER, true); + log.debug("Checking if feature {} is active for user {}", featureName, user.getEmail()); + int atIndex = user.getEmail().indexOf("@"); + + if (atIndex > 0) { + // If the email domain is valid, check the user's email ID against the list of validated domains + String domain = user.getEmail().substring(atIndex + 1).toLowerCase(); + return validDomains.contains(domain); + } + return false; + + } +} diff --git a/app/server/appsmith-server/src/main/resources/features/init-flags.yml b/app/server/appsmith-server/src/main/resources/features/init-flags.yml index 96857110d9..fdeff0379f 100644 --- a/app/server/appsmith-server/src/main/resources/features/init-flags.yml +++ b/app/server/appsmith-server/src/main/resources/features/init-flags.yml @@ -22,7 +22,7 @@ ff4j: enable: true description: Enable this feature based on email ID of the user flipstrategy: - class: com.appsmith.server.featureflags.strategies.AppsmithUserStrategy + class: com.appsmith.server.featureflags.strategies.EmailBasedRolloutStrategy param: - - name: requiredKey - value: requiredValue \ No newline at end of file + - name: emailDomains + value: appsmith.com,wazirx.com,pharmeasy.in,allround.club \ No newline at end of file diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/featureflags/strategies/EmailBasedRolloutStrategyTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/featureflags/strategies/EmailBasedRolloutStrategyTest.java new file mode 100644 index 0000000000..1d131a679d --- /dev/null +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/featureflags/strategies/EmailBasedRolloutStrategyTest.java @@ -0,0 +1,60 @@ +package com.appsmith.server.featureflags.strategies; + +import com.appsmith.server.domains.User; +import lombok.extern.slf4j.Slf4j; +import org.ff4j.core.FlippingExecutionContext; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.test.context.support.WithUserDetails; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +@RunWith(SpringRunner.class) +@SpringBootTest +@Slf4j +public class EmailBasedRolloutStrategyTest { + + EmailBasedRolloutStrategy strategy = new EmailBasedRolloutStrategy(); + + @Before + @WithUserDetails(value = "api_user") + public void setup() { + Map initParams = new HashMap<>(); + initParams.put("emailDomains", "example.com,another-example.com"); + strategy.init("test-feature", initParams); + } + + @Test + @WithUserDetails(value = "api_user") + public void testValidFeatureCheck() { + FlippingExecutionContext executionContext = Mockito.mock(FlippingExecutionContext.class); + + User user = new User(); + user.setEmail("test@EXAMPLE.com"); + Mockito.when(executionContext.getValue(Mockito.anyString(), Mockito.anyBoolean())).thenReturn(user); + + boolean evaluate = strategy.evaluate("test-feature", null, executionContext); + assertTrue(evaluate); + } + + @Test + @WithUserDetails(value = "api_user") + public void testInvalidFeatureCheck() { + FlippingExecutionContext executionContext = Mockito.mock(FlippingExecutionContext.class); + + User user = new User(); + user.setEmail("test@random.com"); + Mockito.when(executionContext.getValue(Mockito.anyString(), Mockito.anyBoolean())).thenReturn(user); + + boolean evaluate = strategy.evaluate("test-feature", null, executionContext); + assertFalse(evaluate); + } +} diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/FeatureFlagServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/FeatureFlagServiceTest.java index d3057e0771..01df38a815 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/FeatureFlagServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/FeatureFlagServiceTest.java @@ -85,4 +85,18 @@ public class FeatureFlagServiceTest { .verifyComplete(); } + @Test + @WithUserDetails(value = "api_user") + public void testFeatureCheckForEmailStrategy() { + StepVerifier.create(featureFlagService.getAllFeatureFlagsForUser()) + .assertNext(result -> { + assertNotNull(result); + assertTrue("There should be a flag TEST_FEATURE_3", + result.keySet().contains(FeatureFlagEnum.TEST_FEATURE_3.toString()) + ); + assertFalse(result.get(FeatureFlagEnum.TEST_FEATURE_3.toString())); + }) + .verifyComplete(); + } + } diff --git a/app/server/appsmith-server/src/test/resources/features/init-flags-test.yml b/app/server/appsmith-server/src/test/resources/features/init-flags-test.yml index 7b4aa1be30..944d2c6a85 100644 --- a/app/server/appsmith-server/src/test/resources/features/init-flags-test.yml +++ b/app/server/appsmith-server/src/test/resources/features/init-flags-test.yml @@ -25,4 +25,13 @@ ff4j: class: org.ff4j.strategy.PonderationStrategy param: - name: weight - value: 1 \ No newline at end of file + value: 1 + + - uid: TEST_FEATURE_3 + enable: true + description: The test feature should only be visible to certain users + flipstrategy: + class: com.appsmith.server.featureflags.strategies.EmailBasedRolloutStrategy + param: + - name: emailDomains + value: test@example.com \ No newline at end of file