-
Notifications
You must be signed in to change notification settings - Fork 32
[FSSDK-11170] update: decision service methods for cmab #583
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 18 commits
26e6393
ad63201
fbed362
53d754a
9905026
78f45bf
9757d49
ecf9199
5e0808f
36d2b4c
5796cb7
d8b0134
b2f270f
a4c3f1c
e4fe788
e75693d
af210d8
42053e4
9a12d72
416bcbd
64f378f
8539166
3cee65c
47c65b5
fe75a85
b0d5090
6db2e88
6fc6446
a80c0d3
a9ae805
f25f824
7363a2f
1c52366
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ | |
import com.optimizely.ab.bucketing.DecisionService; | ||
import com.optimizely.ab.bucketing.FeatureDecision; | ||
import com.optimizely.ab.bucketing.UserProfileService; | ||
import com.optimizely.ab.cmab.service.CmabService; | ||
import com.optimizely.ab.config.AtomicProjectConfigManager; | ||
import com.optimizely.ab.config.DatafileProjectConfig; | ||
import com.optimizely.ab.config.EventType; | ||
|
@@ -141,8 +142,11 @@ public class Optimizely implements AutoCloseable { | |
@Nullable | ||
private final ODPManager odpManager; | ||
|
||
private final CmabService cmabService; | ||
|
||
private final ReentrantLock lock = new ReentrantLock(); | ||
|
||
|
||
private Optimizely(@Nonnull EventHandler eventHandler, | ||
@Nonnull EventProcessor eventProcessor, | ||
@Nonnull ErrorHandler errorHandler, | ||
|
@@ -152,8 +156,9 @@ private Optimizely(@Nonnull EventHandler eventHandler, | |
@Nullable OptimizelyConfigManager optimizelyConfigManager, | ||
@Nonnull NotificationCenter notificationCenter, | ||
@Nonnull List<OptimizelyDecideOption> defaultDecideOptions, | ||
@Nullable ODPManager odpManager | ||
) { | ||
@Nullable ODPManager odpManager, | ||
@Nonnull CmabService cmabService | ||
) { | ||
this.eventHandler = eventHandler; | ||
this.eventProcessor = eventProcessor; | ||
this.errorHandler = errorHandler; | ||
|
@@ -164,6 +169,7 @@ private Optimizely(@Nonnull EventHandler eventHandler, | |
this.notificationCenter = notificationCenter; | ||
this.defaultDecideOptions = defaultDecideOptions; | ||
this.odpManager = odpManager; | ||
this.cmabService = cmabService; | ||
|
||
if (odpManager != null) { | ||
odpManager.getEventManager().start(); | ||
|
@@ -1444,7 +1450,17 @@ private Map<String, OptimizelyDecision> decideForKeys(@Nonnull OptimizelyUserCon | |
|
||
for (int i = 0; i < flagsWithoutForcedDecision.size(); i++) { | ||
DecisionResponse<FeatureDecision> decision = decisionList.get(i); | ||
boolean error = decision.isError(); | ||
String flagKey = flagsWithoutForcedDecision.get(i).getKey(); | ||
|
||
if (error) { | ||
OptimizelyDecision optimizelyDecision = OptimizelyDecision.newErrorDecision(flagKey, user, DecisionMessage.DECISION_ERROR.reason(flagKey)); | ||
decisionMap.put(flagKey, optimizelyDecision); | ||
if (validKeys.contains(flagKey)) { | ||
validKeys.remove(flagKey); | ||
} | ||
} | ||
|
||
flagDecisions.put(flagKey, decision.getResult()); | ||
decisionReasonsMap.get(flagKey).merge(decision.getReasons()); | ||
} | ||
|
@@ -1482,6 +1498,151 @@ Map<String, OptimizelyDecision> decideAll(@Nonnull OptimizelyUserContext user, | |
return decideForKeys(user, allFlagKeys, options); | ||
} | ||
|
||
/** | ||
* Returns a decision result ({@link OptimizelyDecision}) for a given flag key and a user context, | ||
* skipping CMAB logic and using only traditional A/B testing. | ||
FarhanAnjum-opti marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* | ||
* @param user An OptimizelyUserContext associated with this OptimizelyClient. | ||
* @param key A flag key for which a decision will be made. | ||
* @param options A list of options for decision-making. | ||
* @return A decision result using traditional A/B testing logic only. | ||
*/ | ||
OptimizelyDecision decideWithoutCmab(@Nonnull OptimizelyUserContext user, | ||
FarhanAnjum-opti marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
@Nonnull String key, | ||
@Nonnull List<OptimizelyDecideOption> options) { | ||
ProjectConfig projectConfig = getProjectConfig(); | ||
if (projectConfig == null) { | ||
return OptimizelyDecision.newErrorDecision(key, user, DecisionMessage.SDK_NOT_READY.reason()); | ||
} | ||
|
||
List<OptimizelyDecideOption> allOptions = getAllOptions(options); | ||
allOptions.remove(OptimizelyDecideOption.ENABLED_FLAGS_ONLY); | ||
|
||
return decideForKeysWithoutCmab(user, Arrays.asList(key), allOptions, true).get(key); | ||
} | ||
|
||
/** | ||
* Returns decision results for multiple flag keys, skipping CMAB logic and using only traditional A/B testing. | ||
* | ||
* @param user An OptimizelyUserContext associated with this OptimizelyClient. | ||
* @param keys A list of flag keys for which decisions will be made. | ||
* @param options A list of options for decision-making. | ||
* @return All decision results mapped by flag keys, using traditional A/B testing logic only. | ||
*/ | ||
Map<String, OptimizelyDecision> decideForKeysWithoutCmab(@Nonnull OptimizelyUserContext user, | ||
FarhanAnjum-opti marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
@Nonnull List<String> keys, | ||
@Nonnull List<OptimizelyDecideOption> options) { | ||
return decideForKeysWithoutCmab(user, keys, options, false); | ||
} | ||
|
||
/** | ||
* Returns decision results for all active flag keys, skipping CMAB logic and using only traditional A/B testing. | ||
* | ||
* @param user An OptimizelyUserContext associated with this OptimizelyClient. | ||
* @param options A list of options for decision-making. | ||
* @return All decision results mapped by flag keys, using traditional A/B testing logic only. | ||
*/ | ||
Map<String, OptimizelyDecision> decideAllWithoutCmab(@Nonnull OptimizelyUserContext user, | ||
FarhanAnjum-opti marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
@Nonnull List<OptimizelyDecideOption> options) { | ||
Map<String, OptimizelyDecision> decisionMap = new HashMap<>(); | ||
|
||
ProjectConfig projectConfig = getProjectConfig(); | ||
if (projectConfig == null) { | ||
logger.error("Optimizely instance is not valid, failing decideAllWithoutCmab call."); | ||
return decisionMap; | ||
} | ||
|
||
List<FeatureFlag> allFlags = projectConfig.getFeatureFlags(); | ||
List<String> allFlagKeys = new ArrayList<>(); | ||
for (int i = 0; i < allFlags.size(); i++) allFlagKeys.add(allFlags.get(i).getKey()); | ||
|
||
return decideForKeysWithoutCmab(user, allFlagKeys, options); | ||
} | ||
|
||
private Map<String, OptimizelyDecision> decideForKeysWithoutCmab(@Nonnull OptimizelyUserContext user, | ||
@Nonnull List<String> keys, | ||
@Nonnull List<OptimizelyDecideOption> options, | ||
boolean ignoreDefaultOptions) { | ||
Map<String, OptimizelyDecision> decisionMap = new HashMap<>(); | ||
|
||
|
||
ProjectConfig projectConfig = getProjectConfig(); | ||
if (projectConfig == null) { | ||
logger.error("Optimizely instance is not valid, failing decideForKeysWithoutCmab call."); | ||
return decisionMap; | ||
} | ||
|
||
if (keys.isEmpty()) return decisionMap; | ||
|
||
List<OptimizelyDecideOption> allOptions = ignoreDefaultOptions ? options : getAllOptions(options); | ||
|
||
Map<String, FeatureDecision> flagDecisions = new HashMap<>(); | ||
Map<String, DecisionReasons> decisionReasonsMap = new HashMap<>(); | ||
|
||
List<FeatureFlag> flagsWithoutForcedDecision = new ArrayList<>(); | ||
|
||
List<String> validKeys = new ArrayList<>(); | ||
|
||
for (String key : keys) { | ||
FeatureFlag flag = projectConfig.getFeatureKeyMapping().get(key); | ||
if (flag == null) { | ||
decisionMap.put(key, OptimizelyDecision.newErrorDecision(key, user, DecisionMessage.FLAG_KEY_INVALID.reason(key))); | ||
continue; | ||
} | ||
|
||
validKeys.add(key); | ||
|
||
DecisionReasons decisionReasons = DefaultDecisionReasons.newInstance(allOptions); | ||
decisionReasonsMap.put(key, decisionReasons); | ||
|
||
OptimizelyDecisionContext optimizelyDecisionContext = new OptimizelyDecisionContext(key, null); | ||
DecisionResponse<Variation> forcedDecisionVariation = decisionService.validatedForcedDecision(optimizelyDecisionContext, projectConfig, user); | ||
decisionReasons.merge(forcedDecisionVariation.getReasons()); | ||
if (forcedDecisionVariation.getResult() != null) { | ||
flagDecisions.put(key, | ||
new FeatureDecision(null, forcedDecisionVariation.getResult(), FeatureDecision.DecisionSource.FEATURE_TEST)); | ||
} else { | ||
flagsWithoutForcedDecision.add(flag); | ||
} | ||
} | ||
|
||
// Use DecisionService method that skips CMAB logic | ||
List<DecisionResponse<FeatureDecision>> decisionList = | ||
decisionService.getVariationsForFeatureList(flagsWithoutForcedDecision, user, projectConfig, allOptions, false); | ||
|
||
for (int i = 0; i < flagsWithoutForcedDecision.size(); i++) { | ||
DecisionResponse<FeatureDecision> decision = decisionList.get(i); | ||
boolean error = decision.isError(); | ||
String flagKey = flagsWithoutForcedDecision.get(i).getKey(); | ||
|
||
if (error) { | ||
OptimizelyDecision optimizelyDecision = OptimizelyDecision.newErrorDecision(flagKey, user, DecisionMessage.DECISION_ERROR.reason(flagKey)); | ||
decisionMap.put(flagKey, optimizelyDecision); | ||
if (validKeys.contains(flagKey)) { | ||
validKeys.remove(flagKey); | ||
} | ||
} | ||
|
||
flagDecisions.put(flagKey, decision.getResult()); | ||
decisionReasonsMap.get(flagKey).merge(decision.getReasons()); | ||
} | ||
|
||
for (String key : validKeys) { | ||
FeatureDecision flagDecision = flagDecisions.get(key); | ||
DecisionReasons decisionReasons = decisionReasonsMap.get((key)); | ||
|
||
OptimizelyDecision optimizelyDecision = createOptimizelyDecision( | ||
user, key, flagDecision, decisionReasons, allOptions, projectConfig | ||
); | ||
|
||
if (!allOptions.contains(OptimizelyDecideOption.ENABLED_FLAGS_ONLY) || optimizelyDecision.getEnabled()) { | ||
decisionMap.put(key, optimizelyDecision); | ||
} | ||
} | ||
|
||
return decisionMap; | ||
} | ||
|
||
|
||
private List<OptimizelyDecideOption> getAllOptions(List<OptimizelyDecideOption> options) { | ||
List<OptimizelyDecideOption> copiedOptions = new ArrayList(defaultDecideOptions); | ||
if (options != null) { | ||
|
@@ -1731,6 +1892,7 @@ public static class Builder { | |
private NotificationCenter notificationCenter; | ||
private List<OptimizelyDecideOption> defaultDecideOptions; | ||
private ODPManager odpManager; | ||
private CmabService cmabService; | ||
|
||
// For backwards compatibility | ||
private AtomicProjectConfigManager fallbackConfigManager = new AtomicProjectConfigManager(); | ||
|
@@ -1842,6 +2004,11 @@ public Builder withODPManager(ODPManager odpManager) { | |
return this; | ||
} | ||
|
||
public Builder withCmabService(CmabService cmabService) { | ||
this.cmabService = cmabService; | ||
return this; | ||
} | ||
|
||
// Helper functions for making testing easier | ||
protected Builder withBucketing(Bucketer bucketer) { | ||
this.bucketer = bucketer; | ||
|
@@ -1873,7 +2040,7 @@ public Optimizely build() { | |
} | ||
|
||
if (decisionService == null) { | ||
decisionService = new DecisionService(bucketer, errorHandler, userProfileService); | ||
decisionService = new DecisionService(bucketer, errorHandler, userProfileService, cmabService); | ||
} | ||
|
||
if (projectConfig == null && datafile != null && !datafile.isEmpty()) { | ||
|
@@ -1916,7 +2083,7 @@ public Optimizely build() { | |
defaultDecideOptions = Collections.emptyList(); | ||
} | ||
|
||
return new Optimizely(eventHandler, eventProcessor, errorHandler, decisionService, userProfileService, projectConfigManager, optimizelyConfigManager, notificationCenter, defaultDecideOptions, odpManager); | ||
return new Optimizely(eventHandler, eventProcessor, errorHandler, decisionService, userProfileService, projectConfigManager, optimizelyConfigManager, notificationCenter, defaultDecideOptions, odpManager, cmabService); | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.