|
42 | 42 | import javax.annotation.Nonnull;
|
43 | 43 | import javax.annotation.Nullable;
|
44 | 44 | import javax.annotation.concurrent.ThreadSafe;
|
| 45 | +import javax.xml.catalog.CatalogFeatures.Feature; |
| 46 | + |
45 | 47 | import java.io.Closeable;
|
46 | 48 | import java.util.*;
|
47 | 49 |
|
@@ -1296,6 +1298,174 @@ OptimizelyDecision decide(@Nonnull OptimizelyUserContext user,
|
1296 | 1298 | reasonsToReport);
|
1297 | 1299 | }
|
1298 | 1300 |
|
| 1301 | + Optional<FeatureDecision> getForcedDecision(@Nonnull String flagKey, |
| 1302 | + @Nonnull DecisionReasons decisionReasons, |
| 1303 | + @Nonnull ProjectConfig projectConfig, |
| 1304 | + @Nonnull OptimizelyUserContext user) { |
| 1305 | + |
| 1306 | + OptimizelyDecisionContext optimizelyDecisionContext = new OptimizelyDecisionContext(flagKey, null); |
| 1307 | + DecisionResponse<Variation> forcedDecisionVariation = decisionService.validatedForcedDecision(optimizelyDecisionContext, projectConfig, user); |
| 1308 | + decisionReasons.merge(forcedDecisionVariation.getReasons()); |
| 1309 | + if (forcedDecisionVariation.getResult() != null) { |
| 1310 | + return Optional.of(new FeatureDecision(null, forcedDecisionVariation.getResult(), FeatureDecision.DecisionSource.FEATURE_TEST)); |
| 1311 | + } |
| 1312 | + |
| 1313 | + return Optional.empty(); |
| 1314 | + } |
| 1315 | + |
| 1316 | + OptimizelyDecision decideInternal(@Nonnull OptimizelyUserContext user, |
| 1317 | + @Nonnull String key, |
| 1318 | + @Nonnull List<OptimizelyDecideOption> options) { |
| 1319 | + |
| 1320 | + ProjectConfig projectConfig = getProjectConfig(); |
| 1321 | + if (projectConfig == null) { |
| 1322 | + return OptimizelyDecision.newErrorDecision(key, user, DecisionMessage.SDK_NOT_READY.reason()); |
| 1323 | + } |
| 1324 | + |
| 1325 | + FeatureFlag flag = projectConfig.getFeatureKeyMapping().get(key); |
| 1326 | + if (flag == null) { |
| 1327 | + return OptimizelyDecision.newErrorDecision(key, user, DecisionMessage.FLAG_KEY_INVALID.reason(key)); |
| 1328 | + } |
| 1329 | + |
| 1330 | + String userId = user.getUserId(); |
| 1331 | + Map<String, Object> attributes = user.getAttributes(); |
| 1332 | + Boolean decisionEventDispatched = false; |
| 1333 | + List<OptimizelyDecideOption> allOptions = getAllOptions(options); |
| 1334 | + DecisionReasons decisionReasons = DefaultDecisionReasons.newInstance(allOptions); |
| 1335 | + |
| 1336 | + Map<String, ?> copiedAttributes = new HashMap<>(attributes); |
| 1337 | + FeatureDecision flagDecision; |
| 1338 | + |
| 1339 | + // Check Forced Decision |
| 1340 | + OptimizelyDecisionContext optimizelyDecisionContext = new OptimizelyDecisionContext(flag.getKey(), null); |
| 1341 | + DecisionResponse<Variation> forcedDecisionVariation = decisionService.validatedForcedDecision(optimizelyDecisionContext, projectConfig, user); |
| 1342 | + decisionReasons.merge(forcedDecisionVariation.getReasons()); |
| 1343 | + if (forcedDecisionVariation.getResult() != null) { |
| 1344 | + flagDecision = new FeatureDecision(null, forcedDecisionVariation.getResult(), FeatureDecision.DecisionSource.FEATURE_TEST); |
| 1345 | + } else { |
| 1346 | + // Regular decision |
| 1347 | + DecisionResponse<FeatureDecision> decisionVariation = decisionService.getVariationForFeature( |
| 1348 | + flag, |
| 1349 | + user, |
| 1350 | + projectConfig, |
| 1351 | + allOptions); |
| 1352 | + flagDecision = decisionVariation.getResult(); |
| 1353 | + decisionReasons.merge(decisionVariation.getReasons()); |
| 1354 | + } |
| 1355 | + |
| 1356 | + Boolean flagEnabled = false; |
| 1357 | + if (flagDecision.variation != null) { |
| 1358 | + if (flagDecision.variation.getFeatureEnabled()) { |
| 1359 | + flagEnabled = true; |
| 1360 | + } |
| 1361 | + } |
| 1362 | + logger.info("Feature \"{}\" is enabled for user \"{}\"? {}", key, userId, flagEnabled); |
| 1363 | + |
| 1364 | + Map<String, Object> variableMap = new HashMap<>(); |
| 1365 | + if (!allOptions.contains(OptimizelyDecideOption.EXCLUDE_VARIABLES)) { |
| 1366 | + DecisionResponse<Map<String, Object>> decisionVariables = getDecisionVariableMap( |
| 1367 | + flag, |
| 1368 | + flagDecision.variation, |
| 1369 | + flagEnabled); |
| 1370 | + variableMap = decisionVariables.getResult(); |
| 1371 | + decisionReasons.merge(decisionVariables.getReasons()); |
| 1372 | + } |
| 1373 | + OptimizelyJSON optimizelyJSON = new OptimizelyJSON(variableMap); |
| 1374 | + |
| 1375 | + FeatureDecision.DecisionSource decisionSource = FeatureDecision.DecisionSource.ROLLOUT; |
| 1376 | + if (flagDecision.decisionSource != null) { |
| 1377 | + decisionSource = flagDecision.decisionSource; |
| 1378 | + } |
| 1379 | + |
| 1380 | + List<String> reasonsToReport = decisionReasons.toReport(); |
| 1381 | + String variationKey = flagDecision.variation != null ? flagDecision.variation.getKey() : null; |
| 1382 | + // TODO: add ruleKey values when available later. use a copy of experimentKey until then. |
| 1383 | + // add to event metadata as well (currently set to experimentKey) |
| 1384 | + String ruleKey = flagDecision.experiment != null ? flagDecision.experiment.getKey() : null; |
| 1385 | + |
| 1386 | + if (!allOptions.contains(OptimizelyDecideOption.DISABLE_DECISION_EVENT)) { |
| 1387 | + decisionEventDispatched = sendImpression( |
| 1388 | + projectConfig, |
| 1389 | + flagDecision.experiment, |
| 1390 | + userId, |
| 1391 | + copiedAttributes, |
| 1392 | + flagDecision.variation, |
| 1393 | + key, |
| 1394 | + decisionSource.toString(), |
| 1395 | + flagEnabled); |
| 1396 | + } |
| 1397 | + |
| 1398 | + DecisionNotification decisionNotification = DecisionNotification.newFlagDecisionNotificationBuilder() |
| 1399 | + .withUserId(userId) |
| 1400 | + .withAttributes(copiedAttributes) |
| 1401 | + .withFlagKey(key) |
| 1402 | + .withEnabled(flagEnabled) |
| 1403 | + .withVariables(variableMap) |
| 1404 | + .withVariationKey(variationKey) |
| 1405 | + .withRuleKey(ruleKey) |
| 1406 | + .withReasons(reasonsToReport) |
| 1407 | + .withDecisionEventDispatched(decisionEventDispatched) |
| 1408 | + .build(); |
| 1409 | + notificationCenter.send(decisionNotification); |
| 1410 | + |
| 1411 | + return new OptimizelyDecision( |
| 1412 | + variationKey, |
| 1413 | + flagEnabled, |
| 1414 | + optimizelyJSON, |
| 1415 | + ruleKey, |
| 1416 | + key, |
| 1417 | + user, |
| 1418 | + reasonsToReport); |
| 1419 | + } |
| 1420 | + |
| 1421 | + Map<String, OptimizelyDecision> decideForKeysInternal(@Nonnull OptimizelyUserContext user, |
| 1422 | + @Nonnull List<String> keys, |
| 1423 | + @Nonnull List<OptimizelyDecideOption> options) { |
| 1424 | + Map<String, OptimizelyDecision> decisionMap = new HashMap<>(); |
| 1425 | + |
| 1426 | + ProjectConfig projectConfig = getProjectConfig(); |
| 1427 | + if (projectConfig == null) { |
| 1428 | + logger.error("Optimizely instance is not valid, failing isFeatureEnabled call."); |
| 1429 | + return decisionMap; |
| 1430 | + } |
| 1431 | + |
| 1432 | + if (keys.isEmpty()) return decisionMap; |
| 1433 | + |
| 1434 | + String userId = user.getUserId(); |
| 1435 | + Map<String, Object> attributes = user.getAttributes(); |
| 1436 | + Boolean decisionEventDispatched = false; |
| 1437 | + List<OptimizelyDecideOption> allOptions = getAllOptions(options); |
| 1438 | + |
| 1439 | + Map<String, FeatureDecision> flagDecisions = new HashMap<>(); |
| 1440 | + Map<String, DecisionReasons> decisionReasonsMap = new HashMap<>(); |
| 1441 | + |
| 1442 | + List<String> keysWithoutForcedDecision = new ArrayList<>(); |
| 1443 | + |
| 1444 | + for (String key : keys) { |
| 1445 | + FeatureFlag flag = projectConfig.getFeatureKeyMapping().get(key); |
| 1446 | + if (flag == null) { |
| 1447 | + decisionMap.put(key, OptimizelyDecision.newErrorDecision(key, user, DecisionMessage.FLAG_KEY_INVALID.reason(key))); |
| 1448 | + continue; |
| 1449 | + } |
| 1450 | + |
| 1451 | + DecisionReasons decisionReasons = DefaultDecisionReasons.newInstance(allOptions); |
| 1452 | + Optional<FeatureDecision> forcedDecision = getForcedDecision(key, decisionReasons, projectConfig, user); |
| 1453 | + decisionReasonsMap.put(key, decisionReasons); |
| 1454 | + |
| 1455 | + if (forcedDecision.isPresent()) { |
| 1456 | + flagDecisions.put(key, forcedDecision.get()); |
| 1457 | + } else { |
| 1458 | + keysWithoutForcedDecision.add(key); |
| 1459 | + } |
| 1460 | + // OptimizelyDecision decision = decide(user, key, options); |
| 1461 | + // if (!allOptions.contains(OptimizelyDecideOption.ENABLED_FLAGS_ONLY) || decision.getEnabled()) { |
| 1462 | + // decisionMap.put(key, decision); |
| 1463 | + // } |
| 1464 | + } |
| 1465 | + |
| 1466 | + return decisionMap; |
| 1467 | + } |
| 1468 | + |
1299 | 1469 | Map<String, OptimizelyDecision> decideForKeys(@Nonnull OptimizelyUserContext user,
|
1300 | 1470 | @Nonnull List<String> keys,
|
1301 | 1471 | @Nonnull List<OptimizelyDecideOption> options) {
|
|
0 commit comments