Adding the email rollout strategy to feature flag (#6541)

This PR adds the email based rollout strategy to features. We can now define a list of domain names in the init-flags.yml file and define the domains for which the feature will be active.

Also added tests to assert the functionality for this flag flipping strategy.
This commit is contained in:
Arpit Mohan 2021-08-12 10:27:45 +05:30 committed by GitHub
parent b4762a2cae
commit be675fbc6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 136 additions and 4 deletions

View File

@ -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 ----------------------- //

View File

@ -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<String> validDomains = new ArrayList<>();
private static final String PARAM_EMAIL_DOMAINS = "emailDomains";
/** {@inheritDoc} */
@Override
public void init(String featureName, Map<String, String> 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;
}
}

View File

@ -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
- name: emailDomains
value: appsmith.com,wazirx.com,pharmeasy.in,allround.club

View File

@ -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<String, String> 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);
}
}

View File

@ -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();
}
}

View File

@ -25,4 +25,13 @@ ff4j:
class: org.ff4j.strategy.PonderationStrategy
param:
- name: weight
value: 1
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