Skip to content

Commit 5b6e798

Browse files
committed
move decide core to optimizely
1 parent 7ce768d commit 5b6e798

File tree

2 files changed

+183
-162
lines changed

2 files changed

+183
-162
lines changed

core-api/src/main/java/com/optimizely/ab/Optimizely.java

Lines changed: 173 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,11 @@
3434
import com.optimizely.ab.optimizelyconfig.OptimizelyConfig;
3535
import com.optimizely.ab.optimizelyconfig.OptimizelyConfigManager;
3636
import com.optimizely.ab.optimizelyconfig.OptimizelyConfigService;
37-
import com.optimizely.ab.optimizelyjson.OptimizelyJSON;
37+
import com.optimizely.ab.optimizelydecision.DecisionMessage;
38+
import com.optimizely.ab.optimizelydecision.DecisionReasons;
3839
import com.optimizely.ab.optimizelydecision.OptimizelyDecideOption;
40+
import com.optimizely.ab.optimizelydecision.OptimizelyDecision;
41+
import com.optimizely.ab.optimizelyjson.OptimizelyJSON;
3942
import org.slf4j.Logger;
4043
import org.slf4j.LoggerFactory;
4144

@@ -1089,6 +1092,8 @@ public OptimizelyConfig getOptimizelyConfig() {
10891092
return new OptimizelyConfigService(projectConfig).getConfig();
10901093
}
10911094

1095+
//============ decide ============//
1096+
10921097
/**
10931098
* Create a context of the user for which decision APIs will be called.
10941099
*
@@ -1107,6 +1112,173 @@ public OptimizelyUserContext createUserContext(@Nonnull String userId) {
11071112
return new OptimizelyUserContext(this, userId);
11081113
}
11091114

1115+
OptimizelyDecision decide(@Nonnull OptimizelyUserContext user,
1116+
@Nonnull String key,
1117+
@Nonnull List<OptimizelyDecideOption> options) {
1118+
1119+
ProjectConfig projectConfig = getProjectConfig();
1120+
if (projectConfig == null) {
1121+
return OptimizelyDecision.createErrorDecision(key, user, DecisionMessage.SDK_NOT_READY.reason());
1122+
}
1123+
1124+
FeatureFlag flag = projectConfig.getFeatureKeyMapping().get(key);
1125+
if (flag == null) {
1126+
return OptimizelyDecision.createErrorDecision(key, user, DecisionMessage.FLAG_KEY_INVALID.reason(key));
1127+
}
1128+
1129+
String userId = user.getUserId();
1130+
Map<String, Object> attributes = user.getAttributes();
1131+
Boolean sentEvent = false;
1132+
Boolean flagEnabled = false;
1133+
List<OptimizelyDecideOption> allOptions = getAllOptions(options);
1134+
DecisionReasons decisionReasons = new DecisionReasons(allOptions);
1135+
1136+
Map<String, ?> copiedAttributes = new HashMap<>(attributes);
1137+
FeatureDecision flagDecision = decisionService.getVariationForFeature(
1138+
flag,
1139+
userId,
1140+
copiedAttributes,
1141+
projectConfig,
1142+
allOptions,
1143+
decisionReasons);
1144+
1145+
if (flagDecision.variation != null) {
1146+
if (flagDecision.decisionSource.equals(FeatureDecision.DecisionSource.FEATURE_TEST)) {
1147+
if (!allOptions.contains(OptimizelyDecideOption.DISABLE_DECISION_EVENT)) {
1148+
sendImpression(
1149+
projectConfig,
1150+
flagDecision.experiment,
1151+
userId,
1152+
copiedAttributes,
1153+
flagDecision.variation);
1154+
sentEvent = true;
1155+
}
1156+
} else {
1157+
String message = String.format("The user \"%s\" is not included in an experiment for flag \"%s\".", userId, key);
1158+
logger.info(message);
1159+
decisionReasons.addInfo(message);
1160+
}
1161+
if (flagDecision.variation.getFeatureEnabled()) {
1162+
flagEnabled = true;
1163+
}
1164+
}
1165+
1166+
Map<String, Object> variableMap = new HashMap<>();
1167+
if (!allOptions.contains(OptimizelyDecideOption.EXCLUDE_VARIABLES)) {
1168+
variableMap = getDecisionVariableMap(
1169+
flag,
1170+
flagDecision.variation,
1171+
flagEnabled,
1172+
decisionReasons);
1173+
}
1174+
1175+
OptimizelyJSON optimizelyJSON = new OptimizelyJSON(variableMap);
1176+
1177+
List<String> reasonsToReport = decisionReasons.toReport();
1178+
String variationKey = flagDecision.variation != null ? flagDecision.variation.getKey() : null;
1179+
// TODO: add ruleKey values when available later. use a copy of experimentKey until then.
1180+
String ruleKey = flagDecision.experiment != null ? flagDecision.experiment.getKey() : null;
1181+
1182+
DecisionNotification decisionNotification = DecisionNotification.newFlagDecisionNotificationBuilder()
1183+
.withUserId(userId)
1184+
.withAttributes(copiedAttributes)
1185+
.withFlagKey(key)
1186+
.withEnabled(flagEnabled)
1187+
.withVariables(variableMap)
1188+
.withVariationKey(variationKey)
1189+
.withRuleKey(ruleKey)
1190+
.withReasons(reasonsToReport)
1191+
.withDecisionEventDispatched(sentEvent)
1192+
.build();
1193+
notificationCenter.send(decisionNotification);
1194+
1195+
logger.info("Feature \"{}\" is enabled for user \"{}\"? {}", key, userId, flagEnabled);
1196+
1197+
return new OptimizelyDecision(
1198+
variationKey,
1199+
flagEnabled,
1200+
optimizelyJSON,
1201+
ruleKey,
1202+
key,
1203+
user,
1204+
reasonsToReport);
1205+
}
1206+
1207+
Map<String, OptimizelyDecision> decideForKeys(@Nonnull OptimizelyUserContext user,
1208+
@Nonnull List<String> keys,
1209+
@Nonnull List<OptimizelyDecideOption> options) {
1210+
Map<String, OptimizelyDecision> decisionMap = new HashMap<>();
1211+
1212+
ProjectConfig projectConfig = getProjectConfig();
1213+
if (projectConfig == null) {
1214+
logger.error("Optimizely instance is not valid, failing isFeatureEnabled call.");
1215+
return decisionMap;
1216+
}
1217+
1218+
if (keys.isEmpty()) return decisionMap;
1219+
1220+
List<OptimizelyDecideOption> allOptions = getAllOptions(options);
1221+
1222+
for (String key : keys) {
1223+
OptimizelyDecision decision = decide(user, key, options);
1224+
if (!allOptions.contains(OptimizelyDecideOption.ENABLED_FLAGS_ONLY) || decision.getEnabled()) {
1225+
decisionMap.put(key, decision);
1226+
}
1227+
}
1228+
1229+
return decisionMap;
1230+
}
1231+
1232+
Map<String, OptimizelyDecision> decideAll(@Nonnull OptimizelyUserContext user,
1233+
@Nonnull List<OptimizelyDecideOption> options) {
1234+
Map<String, OptimizelyDecision> decisionMap = new HashMap<>();
1235+
1236+
ProjectConfig projectConfig = getProjectConfig();
1237+
if (projectConfig == null) {
1238+
logger.error("Optimizely instance is not valid, failing isFeatureEnabled call.");
1239+
return decisionMap;
1240+
}
1241+
1242+
List<FeatureFlag> allFlags = projectConfig.getFeatureFlags();
1243+
List<String> allFlagKeys = new ArrayList<>();
1244+
for (int i = 0; i < allFlags.size(); i++) allFlagKeys.add(allFlags.get(i).getKey());
1245+
1246+
return decideForKeys(user, allFlagKeys, options);
1247+
}
1248+
1249+
private List<OptimizelyDecideOption> getAllOptions(List<OptimizelyDecideOption> options) {
1250+
List<OptimizelyDecideOption> copiedOptions = new ArrayList(defaultDecideOptions);
1251+
copiedOptions.addAll(options);
1252+
return copiedOptions;
1253+
}
1254+
1255+
private Map<String, Object> getDecisionVariableMap(@Nonnull FeatureFlag flag,
1256+
@Nonnull Variation variation,
1257+
@Nonnull Boolean featureEnabled,
1258+
@Nonnull DecisionReasons decisionReasons) {
1259+
Map<String, Object> valuesMap = new HashMap<String, Object>();
1260+
for (FeatureVariable variable : flag.getVariables()) {
1261+
String value = variable.getDefaultValue();
1262+
if (featureEnabled) {
1263+
FeatureVariableUsageInstance instance = variation.getVariableIdToFeatureVariableUsageInstanceMap().get(variable.getId());
1264+
if (instance != null) {
1265+
value = instance.getValue();
1266+
}
1267+
}
1268+
1269+
Object convertedValue = convertStringToType(value, variable.getType());
1270+
if (convertedValue == null) {
1271+
decisionReasons.addError(DecisionMessage.VARIABLE_VALUE_INVALID.reason(variable.getKey()));
1272+
} else if (convertedValue instanceof OptimizelyJSON) {
1273+
convertedValue = ((OptimizelyJSON) convertedValue).toMap();
1274+
}
1275+
1276+
valuesMap.put(variable.getKey(), convertedValue);
1277+
}
1278+
1279+
return valuesMap;
1280+
}
1281+
11101282
/**
11111283
* Helper method which makes separate copy of attributesMap variable and returns it
11121284
*

0 commit comments

Comments
 (0)