Skip to content

Commit db448a0

Browse files
revise rollout bucketing to check audience in last rollout rule (#174)
* revise rollout bucketing to check audience in last rollout rule * remove unused methods. * remove hash on decision payload object * add test to make sure we are using bucketing ID in rollouts bucketing as well * bring back some log messages * add hashcode back * remove comment
1 parent cde9777 commit db448a0

File tree

5 files changed

+56
-37
lines changed

5 files changed

+56
-37
lines changed

core-api/src/main/java/com/optimizely/ab/bucketing/DecisionService.java

+11-14
Original file line numberDiff line numberDiff line change
@@ -219,32 +219,29 @@ public DecisionService(@Nonnull Bucketer bucketer,
219219
Experiment rolloutRule = rollout.getExperiments().get(i);
220220
Audience audience = projectConfig.getAudienceIdMapping().get(rolloutRule.getAudienceIds().get(0));
221221
if (ExperimentUtils.isUserInExperiment(projectConfig, rolloutRule, filteredAttributes)) {
222-
logger.debug("Attempting to bucket user \"" + userId +
223-
"\" into rollout rule for audience \"" + audience.getName() +
224-
"\".");
225222
variation = bucketer.bucket(rolloutRule, bucketingId);
226223
if (variation == null) {
227-
logger.debug("User \"{}\" was excluded due to traffic allocation.", userId);
228224
break;
229225
}
230226
return new FeatureDecision(rolloutRule, variation,
231227
FeatureDecision.DecisionSource.ROLLOUT);
232-
} else {
228+
}
229+
else {
233230
logger.debug("User \"{}\" did not meet the conditions to be in rollout rule for audience \"{}\".",
234231
userId, audience.getName());
235232
}
236233
}
237234

238-
// get last rule which is the everyone else rule
239-
Experiment everyoneElseRule = rollout.getExperiments().get(rolloutRulesLength - 1);
240-
variation = bucketer.bucket(everyoneElseRule, bucketingId); // ignore audience
241-
if (variation == null) {
242-
logger.debug("User \"{}\" was excluded from the \"Everyone Else\" rule for feature flag \"{}\".",
243-
userId, featureFlag.getKey());
244-
return new FeatureDecision(null, null, null);
235+
// get last rule which is the fall back rule
236+
Experiment finalRule = rollout.getExperiments().get(rolloutRulesLength - 1);
237+
if (ExperimentUtils.isUserInExperiment(projectConfig, finalRule, filteredAttributes)) {
238+
variation = bucketer.bucket(finalRule, bucketingId);
239+
if (variation != null) {
240+
return new FeatureDecision(finalRule, variation,
241+
FeatureDecision.DecisionSource.ROLLOUT);
242+
}
245243
}
246-
return new FeatureDecision(everyoneElseRule, variation,
247-
FeatureDecision.DecisionSource.ROLLOUT);
244+
return new FeatureDecision(null, null, null);
248245
}
249246

250247
/**

core-api/src/main/java/com/optimizely/ab/config/audience/Audience.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import com.fasterxml.jackson.annotation.JsonCreator;
2020
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
2121
import com.fasterxml.jackson.annotation.JsonProperty;
22-
2322
import com.optimizely.ab.config.IdKeyMapped;
2423

2524
import javax.annotation.concurrent.Immutable;
@@ -50,11 +49,11 @@ public String getId() {
5049
return id;
5150
}
5251

53-
public String getName() {
52+
public String getKey() {
5453
return name;
5554
}
5655

57-
public String getKey() {
56+
public String getName() {
5857
return name;
5958
}
6059

core-api/src/main/java/com/optimizely/ab/event/internal/payload/Decision.java

+2-17
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import com.fasterxml.jackson.annotation.JsonProperty;
2020

21+
import java.util.Objects;
22+
2123
public class Decision {
2224
@JsonProperty("campaign_id")
2325
String campaignId;
@@ -43,34 +45,18 @@ public String getCampaignId() {
4345
return campaignId;
4446
}
4547

46-
public void setCampaignId(String campaignId) {
47-
this.campaignId = campaignId;
48-
}
49-
5048
public String getExperimentId() {
5149
return experimentId;
5250
}
5351

54-
public void setExperimentId(String experimentId) {
55-
this.experimentId = experimentId;
56-
}
57-
5852
public String getVariationId() {
5953
return variationId;
6054
}
6155

62-
public void setVariationId(String variationId) {
63-
this.variationId = variationId;
64-
}
65-
6656
public boolean getIsCampaignHoldback() {
6757
return isCampaignHoldback;
6858
}
6959

70-
public void setIsCampaignHoldback(boolean campaignHoldback) {
71-
isCampaignHoldback = campaignHoldback;
72-
}
73-
7460
@Override
7561
public boolean equals(Object o) {
7662
if (this == o) return true;
@@ -83,7 +69,6 @@ public boolean equals(Object o) {
8369
if (!experimentId.equals(that.experimentId)) return false;
8470
return variationId.equals(that.variationId);
8571
}
86-
8772
@Override
8873
public int hashCode() {
8974
int result = campaignId.hashCode();

core-api/src/test/java/com/optimizely/ab/bucketing/DecisionServiceTest.java

+38
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,11 @@
5050
import static com.optimizely.ab.config.ValidProjectConfigV4.AUDIENCE_ENGLISH_CITIZENS_VALUE;
5151
import static com.optimizely.ab.config.ValidProjectConfigV4.AUDIENCE_GRYFFINDOR_VALUE;
5252
import static com.optimizely.ab.config.ValidProjectConfigV4.FEATURE_FLAG_MULTI_VARIATE_FEATURE;
53+
import static com.optimizely.ab.config.ValidProjectConfigV4.FEATURE_FLAG_SINGLE_VARIABLE_INTEGER;
5354
import static com.optimizely.ab.config.ValidProjectConfigV4.FEATURE_MULTI_VARIATE_FEATURE_KEY;
5455
import static com.optimizely.ab.config.ValidProjectConfigV4.ROLLOUT_2;
56+
import static com.optimizely.ab.config.ValidProjectConfigV4.ROLLOUT_3_EVERYONE_ELSE_RULE;
57+
import static com.optimizely.ab.config.ValidProjectConfigV4.ROLLOUT_3_EVERYONE_ELSE_RULE_ENABLED_VARIATION;
5558
import static org.hamcrest.CoreMatchers.is;
5659
import static org.hamcrest.MatcherAssert.assertThat;
5760
import static org.junit.Assert.assertEquals;
@@ -1053,4 +1056,39 @@ public void getVariationBucketingId() throws Exception {
10531056

10541057
}
10551058

1059+
/**
1060+
Verify that {@link DecisionService#getVariationForFeatureInRollout(FeatureFlag, String, Map)}
1061+
uses bucketing ID to bucket the user into rollouts.
1062+
*/
1063+
@Test
1064+
public void getVariationForRolloutWithBucketingId() {
1065+
Experiment rolloutRuleExperiment = ROLLOUT_3_EVERYONE_ELSE_RULE;
1066+
Variation rolloutVariation = ROLLOUT_3_EVERYONE_ELSE_RULE_ENABLED_VARIATION;
1067+
FeatureFlag featureFlag = FEATURE_FLAG_SINGLE_VARIABLE_INTEGER;
1068+
String bucketingId = "user_bucketing_id";
1069+
String userId = "user_id";
1070+
Map<String, String> attributes = new HashMap<String, String>();
1071+
attributes.put(DecisionService.BUCKETING_ATTRIBUTE, bucketingId);
1072+
1073+
Bucketer bucketer = mock(Bucketer.class);
1074+
when(bucketer.bucket(rolloutRuleExperiment, userId)).thenReturn(null);
1075+
when(bucketer.bucket(rolloutRuleExperiment, bucketingId)).thenReturn(rolloutVariation);
1076+
1077+
DecisionService decisionService = spy(new DecisionService(
1078+
bucketer,
1079+
mockErrorHandler,
1080+
v4ProjectConfig,
1081+
null
1082+
));
1083+
1084+
FeatureDecision expectedFeatureDecision = new FeatureDecision(
1085+
rolloutRuleExperiment,
1086+
rolloutVariation,
1087+
FeatureDecision.DecisionSource.ROLLOUT);
1088+
1089+
FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, userId, attributes);
1090+
1091+
assertEquals(expectedFeatureDecision, featureDecision);
1092+
}
1093+
10561094
}

core-api/src/test/java/com/optimizely/ab/config/ValidProjectConfigV4.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -227,14 +227,14 @@ public class ValidProjectConfigV4 {
227227
private static final String ROLLOUT_3_ID = "2048875663";
228228
private static final String ROLLOUT_3_EVERYONE_ELSE_EXPERIMENT_ID = "3794675122";
229229
private static final String ROLLOUT_3_EVERYONE_ELSE_RULE_ENABLED_VARIATION_ID = "589640735";
230-
private static final Boolean ROLLOUT_3_FEATURE_ENABLED_VALUE = true;
231-
private static final Variation ROLLOUT_3_EVERYONE_ELSE_RULE_ENABLED_VARIATION = new Variation(
230+
private static final Boolean ROLLOUT_3_FEATURE_ENABLED_VALUE = true;
231+
public static final Variation ROLLOUT_3_EVERYONE_ELSE_RULE_ENABLED_VARIATION = new Variation(
232232
ROLLOUT_3_EVERYONE_ELSE_RULE_ENABLED_VARIATION_ID,
233233
ROLLOUT_3_EVERYONE_ELSE_RULE_ENABLED_VARIATION_ID,
234234
ROLLOUT_3_FEATURE_ENABLED_VALUE,
235235
Collections.<LiveVariableUsageInstance>emptyList()
236236
);
237-
private static final Experiment ROLLOUT_3_EVERYONE_ELSE_RULE = new Experiment(
237+
public static final Experiment ROLLOUT_3_EVERYONE_ELSE_RULE = new Experiment(
238238
ROLLOUT_3_EVERYONE_ELSE_EXPERIMENT_ID,
239239
ROLLOUT_3_EVERYONE_ELSE_EXPERIMENT_ID,
240240
Experiment.ExperimentStatus.RUNNING.toString(),

0 commit comments

Comments
 (0)