Skip to content

Commit

Permalink
bugfix/1.0-final-fixbug-expt: check if sending to experiment (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsun0720 committed May 1, 2022
1 parent e536785 commit 44eb717
Show file tree
Hide file tree
Showing 14 changed files with 107 additions and 39 deletions.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
b4280218cd39bc02fb558bd8e4ee7efa
bf6cb6ce0162212ec2f534788e7264b1
Original file line number Diff line number Diff line change
@@ -1 +1 @@
05a5df22aa259f776f41df77193ab19712937f53
bdbd67ae949d9c6127bad5865a1511013c43800c
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0a768077917d309cbef0dd3e7713a368
50508a2ceb15c3ea17131d0bf61a6e19
Original file line number Diff line number Diff line change
@@ -1 +1 @@
92fa2340edca34e151ffdcbaba978634a44fc5bd
406e3369901536ad3889e4b053da0e450329088c
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ef631ba0bea93d6d90e7f70a51f6516b
a80cc2fe473675144a47c8754d5aeb9d
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ce98eaf40dc2cf0a3e5d18febc3dd602784f4912
e3c8e03d08b98344f89b67bbfffa5dc2db42d77a
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
<version>1.0</version>
<version>1.1</version>
</versions>
<lastUpdated>20220428163657</lastUpdated>
<lastUpdated>20220501092645</lastUpdated>
</versioning>
</metadata>
Original file line number Diff line number Diff line change
@@ -1 +1 @@
8bc9241f9d4c37ff08affad639963aab
a363338cd7110ff51aa9f95a28b6b3fc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
05da31cac057fe4e11ff7a07059ca12bce1e7dd9
075755c6f614d4522bebba839abee8eac60c5baa
6 changes: 3 additions & 3 deletions src/main/java/co/featureflags/server/DataModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ public Integer getType() {
}

public Boolean isExptIncludeAllRules() {
return exptIncludeAllRules == null ? Boolean.FALSE : exptIncludeAllRules;
return exptIncludeAllRules;
}

public FeatureFlagBasicInfo getInfo() {
Expand Down Expand Up @@ -447,7 +447,7 @@ public String getStatus() {
}

public Boolean isDefaultRulePercentageRolloutsIncludedInExpt() {
return isDefaultRulePercentageRolloutsIncludedInExpt == null ? Boolean.FALSE : isDefaultRulePercentageRolloutsIncludedInExpt;
return isDefaultRulePercentageRolloutsIncludedInExpt;
}

public Date getLastUpdatedTime() {
Expand Down Expand Up @@ -505,7 +505,7 @@ public String getRuleName() {
}

public Boolean isIncludedInExpt() {
return isIncludedInExpt == null ? Boolean.FALSE : isIncludedInExpt;
return isIncludedInExpt;
}

public List<RuleItem> getRuleJsonContent() {
Expand Down
122 changes: 95 additions & 27 deletions src/main/java/co/featureflags/server/EvaluatorImp.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -54,49 +56,64 @@ private EvalResult matchUserVariation(DataModel.FeatureFlag flag, FFCUser user,
// TODO useless code
er = EvalResult.of(flag.getInfo().getVariationOptionWhenDisabled(), REASON_FALLTHROUGH, false, flag.getInfo().getKeyName(), flag.getInfo().getName());
return er;
} catch (Exception e) {
throw e;
} finally {
logger.info("FFC JAVA SDK: User {}, Feature Flag {}, Flag Value {}", user.getKey(), flag.getInfo().getKeyName(), er.getValue());
if (er != null && event != null) {
event.add(InsightTypes.FlagEventVariation.of(flag.getInfo().getKeyName(), er));
if (er != null) {
logger.info("FFC JAVA SDK: User {}, Feature Flag {}, Flag Value {}", user.getKey(), flag.getInfo().getKeyName(), er.getValue());
if (event != null) {
event.add(InsightTypes.FlagEventVariation.of(flag.getInfo().getKeyName(), er));
}
}
}
}

private EvalResult matchFeatureFlagDisabledUserVariation(DataModel.FeatureFlag flag, FFCUser user, InsightTypes.Event event) {
private EvalResult matchFeatureFlagDisabledUserVariation(DataModel.FeatureFlag flag, FFCUser
user, InsightTypes.Event event) {
// case flag is off
if (FLAG_DISABLE_STATS.equals(flag.getInfo().getStatus())) {
return EvalResult.of(flag.getInfo().getVariationOptionWhenDisabled(), REASON_FLAG_OFF, false, flag.getInfo().getKeyName(), flag.getInfo().getName());
}
// case prerequisite is set
return flag.getPrerequisites().stream().filter(prerequisite -> {
String preFlagId = prerequisite.getPrerequisiteFeatureFlagId();
if (!preFlagId.equals(flag.getInfo().getId())) {
DataModel.FeatureFlag preFlag = this.flagGetter.get(preFlagId);
if (preFlag == null) {
String preFlagKey = FeatureFlagKeyExtension.unpackFeatureFlagId(preFlagId, 4);
logger.warn("prerequisite flag {} not found", preFlagKey);
return true;
}
EvalResult er = matchUserVariation(preFlag, user, event);
// even if prerequisite flag is off, check if default value of prerequisite flag matches expected value
// if prerequisite failed, return the default value of this flag
return !er.getIndex().equals(prerequisite.getValueOptionsVariationValue().getLocalId());

}
return false;
}).findFirst().map(prerequisite -> EvalResult.of(flag.getInfo().getVariationOptionWhenDisabled(), REASON_PREREQUISITE_FAILED, false, flag.getInfo().getKeyName(), flag.getInfo().getName())).orElse(null);
String preFlagId = prerequisite.getPrerequisiteFeatureFlagId();
if (!preFlagId.equals(flag.getInfo().getId())) {
DataModel.FeatureFlag preFlag = this.flagGetter.get(preFlagId);
if (preFlag == null) {
String preFlagKey = FeatureFlagKeyExtension.unpackFeatureFlagId(preFlagId, 4);
logger.warn("prerequisite flag {} not found", preFlagKey);
return true;
}
EvalResult er = matchUserVariation(preFlag, user, event);
// even if prerequisite flag is off, check if default value of prerequisite flag matches expected value
// if prerequisite failed, return the default value of this flag
return !er.getIndex().equals(prerequisite.getValueOptionsVariationValue().getLocalId());

}
return false;
}).findFirst()
.map(prerequisite -> EvalResult.of(flag.getInfo().getVariationOptionWhenDisabled(), REASON_PREREQUISITE_FAILED, false, flag.getInfo().getKeyName(), flag.getInfo().getName()))
.orElse(null);
}

private EvalResult matchTargetedUserVariation(DataModel.FeatureFlag featureFlag, FFCUser user) {
return featureFlag.getTargets().stream().filter(target -> target.isTargeted(user.getKey())).findFirst().map(target -> EvalResult.of(target.getValueOption(), REASON_TARGET_MATCH, featureFlag.isExptIncludeAllRules(), featureFlag.getInfo().getKeyName(), featureFlag.getInfo().getName())).orElse(null);
return featureFlag.getTargets().stream()
.filter(target -> target.isTargeted(user.getKey()))
.findFirst()
.map(target -> EvalResult.of(target.getValueOption(), REASON_TARGET_MATCH, isSendToExperimentForTargetedUserVariation(featureFlag.isExptIncludeAllRules()), featureFlag.getInfo().getKeyName(), featureFlag.getInfo().getName()))
.orElse(null);
}

private EvalResult matchConditionedUserVariation(DataModel.FeatureFlag featureFlag, FFCUser user) {
DataModel.TargetRule targetRule = featureFlag.getRules().stream().filter(rule -> ifUserMatchRule(user, rule.getRuleJsonContent())).findFirst().orElse(null);
// optional flatmap can't infer inner type of collection
return targetRule == null ? null : getRollOutVariationOption(targetRule.getValueOptionsVariationRuleValues(), user, REASON_RULE_MATCH, targetRule.isIncludedInExpt(), featureFlag.getInfo().getKeyName(), featureFlag.getInfo().getName());


return targetRule == null ? null : getRollOutVariationOption(targetRule.getValueOptionsVariationRuleValues(),
user,
REASON_RULE_MATCH,
featureFlag.isExptIncludeAllRules(),
targetRule.isIncludedInExpt(),
featureFlag.getInfo().getKeyName(),
featureFlag.getInfo().getName());
}

private boolean ifUserMatchRule(FFCUser user, List<DataModel.RuleItem> clauses) {
Expand Down Expand Up @@ -240,11 +257,62 @@ private boolean oneOfClause(FFCUser user, DataModel.RuleItem clause) {
}

private EvalResult matchDefaultUserVariation(DataModel.FeatureFlag featureFlag, FFCUser user) {
return getRollOutVariationOption(featureFlag.getInfo().getDefaultRulePercentageRollouts(), user, REASON_FALLTHROUGH, featureFlag.getInfo().isDefaultRulePercentageRolloutsIncludedInExpt(), featureFlag.getInfo().getKeyName(), featureFlag.getInfo().getName());
return getRollOutVariationOption(featureFlag.getInfo().getDefaultRulePercentageRollouts(),
user,
REASON_FALLTHROUGH,
featureFlag.isExptIncludeAllRules(),
featureFlag.getInfo().isDefaultRulePercentageRolloutsIncludedInExpt(),
featureFlag.getInfo().getKeyName(),
featureFlag.getInfo().getName());
}

private EvalResult getRollOutVariationOption
(Collection<DataModel.VariationOptionPercentageRollout> rollouts,
FFCUser user,
String reason,
Boolean exptIncludeAllRules,
Boolean ruleIncludedInExperiment,
String flagKeyName,
String flagName) {
String newUserKey = Base64.getEncoder().encodeToString(user.getKey().getBytes());
return rollouts.stream()
.filter(rollout -> VariationSplittingAlgorithm.ifKeyBelongsPercentage(user.getKey(), rollout.getRolloutPercentage()))
.findFirst()
.map(rollout -> EvalResult.of(rollout.getValueOption(), reason, isSendToExperiment(newUserKey, rollout, exptIncludeAllRules, ruleIncludedInExperiment), flagKeyName, flagName))
.orElse(null);
}

private boolean isSendToExperimentForTargetedUserVariation(Boolean exptIncludeAllRules) {
return exptIncludeAllRules == null || exptIncludeAllRules.booleanValue();
}

private EvalResult getRollOutVariationOption(Collection<DataModel.VariationOptionPercentageRollout> rollouts, FFCUser user, String reason, boolean sendToExperiment, String flagKeyName, String flagName) {
return rollouts.stream().filter(rollout -> VariationSplittingAlgorithm.ifKeyBelongsPercentage(user.getKey(), rollout.getRolloutPercentage())).findFirst().map(rollout -> EvalResult.of(rollout.getValueOption(), reason, sendToExperiment, flagKeyName, flagName)).orElse(null);
private boolean isSendToExperiment(String userKey,
DataModel.VariationOptionPercentageRollout rollout,
Boolean exptIncludeAllRules,
Boolean ruleIncludedInExperiment) {
if (exptIncludeAllRules == null || exptIncludeAllRules.booleanValue()) {
return true;
}

if (ruleIncludedInExperiment == null || rollout.getExptRollout() == null) {
return true;
}

if (!ruleIncludedInExperiment) {
return false;
}

double sendToExperimentPercentage = rollout.getExptRollout();
double splittingPercentage = rollout.getRolloutPercentage().get(1) - rollout.getRolloutPercentage().get(0);
if (sendToExperimentPercentage == 0D || splittingPercentage == 0D) {
return false;
}

double upperBound = sendToExperimentPercentage / splittingPercentage;
if (upperBound > 1D) {
upperBound = 1D;
}
return VariationSplittingAlgorithm.ifKeyBelongsPercentage(userKey, Arrays.asList(0D, upperBound));
}


Expand Down

0 comments on commit 44eb717

Please sign in to comment.