Skip to content

feat(ForcedDecisions): add forced-decisions APIs to OptimizelyUserContext #451

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

Merged
merged 43 commits into from
Nov 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
cdaa2e0
Addition of ForcedDecisions
The-inside-man Sep 28, 2021
bd41dd1
Update constructor to use ? instead of object - reverting
The-inside-man Sep 28, 2021
25dfa07
Update constructor to use ? instead of object - reverting
The-inside-man Sep 28, 2021
35ff0ce
Merge branch 'master' into jbrown/forcedDecisions
The-inside-man Sep 28, 2021
00fbc21
Update dodgy tests.
The-inside-man Sep 28, 2021
9079018
Add additional test to OptimizelyTest.java
The-inside-man Sep 28, 2021
f75c04a
Add tests to OptimizelyUserContextTest.java
The-inside-man Sep 28, 2021
225cd1e
Update tests for more coverage.
The-inside-man Sep 28, 2021
2a47e23
Revert javadoc comment test.
The-inside-man Sep 28, 2021
881f802
Update javadocs with titles.
The-inside-man Sep 28, 2021
5cec109
Update deprecations.
The-inside-man Sep 28, 2021
4964852
Remove deprecations for set and get forcedVariations.
The-inside-man Sep 28, 2021
31de5d5
Simplify the way flagVariationsMap is built
The-inside-man Oct 1, 2021
34cc766
Fix removeForcedDecision when no object in map
The-inside-man Oct 1, 2021
65fe3ad
cleanup and remove deprecation warnings.
The-inside-man Oct 8, 2021
8f03cf3
Change forcedDecisions to use a map of Strings to Strings based on a …
The-inside-man Oct 8, 2021
a968525
Add license header
The-inside-man Oct 8, 2021
3e415ca
Dont append ruleKey to key object toString method if null.
The-inside-man Oct 8, 2021
06b12b0
Update logic in decide method.
The-inside-man Oct 12, 2021
bf8a6c7
Merge branch 'master' into jbrown/forcedDecisions
The-inside-man Oct 12, 2021
1d416c8
Change logic to decide and adjust logic for getVariationForFeature.
The-inside-man Oct 12, 2021
443830c
Update logic for forcedDecisions, add in missing methods for Decision…
The-inside-man Oct 13, 2021
2161c00
Update coverage and remove unused class
The-inside-man Oct 14, 2021
b9f4338
Add comment to method
The-inside-man Oct 14, 2021
10b05c1
Remove unused import - cleanup.
The-inside-man Oct 14, 2021
7b6e869
Remove null from findValidatedForcedDecisions call in decide method s…
The-inside-man Oct 20, 2021
2c79e6d
Switch to String.format for info.
The-inside-man Oct 20, 2021
3d0f887
Adding in new classes for forcedDecisionsAPI
The-inside-man Oct 22, 2021
55c0757
Addressing changes and new API implementations.
The-inside-man Oct 22, 2021
d1943f6
Update to fix issue with java PAIR to test.
The-inside-man Oct 22, 2021
d0aced2
Revert to Map for now.
The-inside-man Oct 22, 2021
8bf6faf
Update OptimizelyUserContext to copy FD, update DecisionService to us…
The-inside-man Oct 22, 2021
66cb22f
Updated tests and license headers
The-inside-man Oct 22, 2021
743f913
Added test case to confirm no duplicate variations added to the falgs…
The-inside-man Oct 22, 2021
80b7fb0
Update tests to cover decide with FD and confirm usage.
The-inside-man Oct 22, 2021
81d1c54
Update tests and add logging to match FSC requirement for findValidat…
The-inside-man Oct 26, 2021
88470c5
Updated Test cases
The-inside-man Oct 27, 2021
37323c4
Address comments for OptimizelyDecisionContext and OptimizelyUserContext
The-inside-man Nov 2, 2021
ff79031
Added test cases and fixed Null case when experiment is null during U…
The-inside-man Nov 2, 2021
f236148
Fix OptimizelyDecisionContext tests to use -569XZilms as the divider …
The-inside-man Nov 2, 2021
cef2c13
Revert attributes map back to synchronizedMap.
The-inside-man Nov 3, 2021
39a9ae1
Update core-api/src/main/java/com/optimizely/ab/OptimizelyUserContext…
The-inside-man Nov 3, 2021
2eb2fd2
Fix indenting from auto-commit
The-inside-man Nov 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 49 additions & 17 deletions core-api/src/main/java/com/optimizely/ab/Optimizely.java
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ public void track(@Nonnull String eventName,
@Nonnull
public Boolean isFeatureEnabled(@Nonnull String featureKey,
@Nonnull String userId) {
return isFeatureEnabled(featureKey, userId, Collections.<String, String>emptyMap());
return isFeatureEnabled(featureKey, userId, Collections.emptyMap());
}

/**
Expand Down Expand Up @@ -424,7 +424,7 @@ private Boolean isFeatureEnabled(@Nonnull ProjectConfig projectConfig,

Map<String, ?> copiedAttributes = copyAttributes(attributes);
FeatureDecision.DecisionSource decisionSource = FeatureDecision.DecisionSource.ROLLOUT;
FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, userId, copiedAttributes, projectConfig).getResult();
FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, createUserContext(userId, copiedAttributes), projectConfig).getResult();
Boolean featureEnabled = false;
SourceInfo sourceInfo = new RolloutSourceInfo();
if (featureDecision.decisionSource != null) {
Expand Down Expand Up @@ -733,7 +733,7 @@ <T> T getFeatureVariableValueForType(@Nonnull String featureKey,

String variableValue = variable.getDefaultValue();
Map<String, ?> copiedAttributes = copyAttributes(attributes);
FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, userId, copiedAttributes, projectConfig).getResult();
FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, createUserContext(userId, copiedAttributes), projectConfig).getResult();
Boolean featureEnabled = false;
if (featureDecision.variation != null) {
if (featureDecision.variation.getFeatureEnabled()) {
Expand Down Expand Up @@ -824,6 +824,7 @@ Object convertStringToType(String variableValue, String type) {
* @param userId The ID of the user.
* @return An OptimizelyJSON instance for all variable values.
* Null if the feature could not be found.
*
*/
@Nullable
public OptimizelyJSON getAllFeatureVariables(@Nonnull String featureKey,
Expand All @@ -839,6 +840,7 @@ public OptimizelyJSON getAllFeatureVariables(@Nonnull String featureKey,
* @param attributes The user's attributes.
* @return An OptimizelyJSON instance for all variable values.
* Null if the feature could not be found.
*
*/
@Nullable
public OptimizelyJSON getAllFeatureVariables(@Nonnull String featureKey,
Expand Down Expand Up @@ -866,7 +868,7 @@ public OptimizelyJSON getAllFeatureVariables(@Nonnull String featureKey,
}

Map<String, ?> copiedAttributes = copyAttributes(attributes);
FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, userId, copiedAttributes, projectConfig).getResult();
FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, createUserContext(userId, copiedAttributes), projectConfig, Collections.emptyList()).getResult();
Boolean featureEnabled = false;
Variation variation = featureDecision.variation;

Expand Down Expand Up @@ -922,9 +924,10 @@ public OptimizelyJSON getAllFeatureVariables(@Nonnull String featureKey,
* @param attributes The user's attributes.
* @return List of the feature keys that are enabled for the user if the userId is empty it will
* return Empty List.
*
*/
public List<String> getEnabledFeatures(@Nonnull String userId, @Nonnull Map<String, ?> attributes) {
List<String> enabledFeaturesList = new ArrayList<String>();
List<String> enabledFeaturesList = new ArrayList();
if (!validateUserId(userId)) {
return enabledFeaturesList;
}
Expand All @@ -951,7 +954,7 @@ public List<String> getEnabledFeatures(@Nonnull String userId, @Nonnull Map<Stri
public Variation getVariation(@Nonnull Experiment experiment,
@Nonnull String userId) throws UnknownExperimentException {

return getVariation(experiment, userId, Collections.<String, String>emptyMap());
return getVariation(experiment, userId, Collections.emptyMap());
}

@Nullable
Expand All @@ -967,8 +970,7 @@ private Variation getVariation(@Nonnull ProjectConfig projectConfig,
@Nonnull String userId,
@Nonnull Map<String, ?> attributes) throws UnknownExperimentException {
Map<String, ?> copiedAttributes = copyAttributes(attributes);
Variation variation = decisionService.getVariation(experiment, userId, copiedAttributes, projectConfig).getResult();

Variation variation = decisionService.getVariation(experiment, createUserContext(userId, copiedAttributes), projectConfig).getResult();
String notificationType = NotificationCenter.DecisionNotificationType.AB_TEST.toString();

if (projectConfig.getExperimentFeatureKeyMapping().get(experiment.getId()) != null) {
Expand Down Expand Up @@ -1145,7 +1147,7 @@ public OptimizelyConfig getOptimizelyConfig() {
* @return An OptimizelyUserContext associated with this OptimizelyClient.
*/
public OptimizelyUserContext createUserContext(@Nonnull String userId,
@Nonnull Map<String, Object> attributes) {
@Nonnull Map<String, ?> attributes) {
if (userId == null) {
logger.warn("The userId parameter must be nonnull.");
return null;
Expand Down Expand Up @@ -1179,14 +1181,24 @@ OptimizelyDecision decide(@Nonnull OptimizelyUserContext user,
DecisionReasons decisionReasons = DefaultDecisionReasons.newInstance(allOptions);

Map<String, ?> copiedAttributes = new HashMap<>(attributes);
DecisionResponse<FeatureDecision> decisionVariation = decisionService.getVariationForFeature(
flag,
userId,
copiedAttributes,
projectConfig,
allOptions);
FeatureDecision flagDecision = decisionVariation.getResult();
decisionReasons.merge(decisionVariation.getReasons());
FeatureDecision flagDecision;

// Check Forced Decision
OptimizelyDecisionContext optimizelyDecisionContext = new OptimizelyDecisionContext(flag.getKey(), null);
DecisionResponse<Variation> forcedDecisionVariation = user.findValidatedForcedDecision(optimizelyDecisionContext);
decisionReasons.merge(forcedDecisionVariation.getReasons());
if (forcedDecisionVariation.getResult() != null) {
flagDecision = new FeatureDecision(null, forcedDecisionVariation.getResult(), FeatureDecision.DecisionSource.FEATURE_TEST);
} else {
// Regular decision
DecisionResponse<FeatureDecision> decisionVariation = decisionService.getVariationForFeature(
flag,
user,
projectConfig,
allOptions);
flagDecision = decisionVariation.getResult();
decisionReasons.merge(decisionVariation.getReasons());
}

Boolean flagEnabled = false;
if (flagDecision.variation != null) {
Expand Down Expand Up @@ -1332,6 +1344,26 @@ private DecisionResponse<Map<String, Object>> getDecisionVariableMap(@Nonnull Fe
return new DecisionResponse(valuesMap, reasons);
}

/**
* Gets a variation based on flagKey and variationKey
*
* @param flagKey The flag key for the variation
* @param variationKey The variation key for the variation
* @return Returns a variation based on flagKey and variationKey, otherwise null
*/
public Variation getFlagVariationByKey(String flagKey, String variationKey) {
Map<String, List<Variation>> flagVariationsMap = getProjectConfig().getFlagVariationsMap();
if (flagVariationsMap.containsKey(flagKey)) {
List<Variation> variations = flagVariationsMap.get(flagKey);
for (Variation variation : variations) {
if (variation.getKey().equals(variationKey)) {
return variation;
}
}
}
return null;
}

/**
* Helper method which makes separate copy of attributesMap variable and returns it
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
*
* Copyright 2021, Optimizely and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.optimizely.ab;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public class OptimizelyDecisionContext {
public static final String OPTI_NULL_RULE_KEY = "$opt-null-rule-key";
public static final String OPTI_KEY_DIVIDER = "-$opt$-";

private String flagKey;
private String ruleKey;

public OptimizelyDecisionContext(@Nonnull String flagKey, @Nullable String ruleKey) {
this.flagKey = flagKey;
this.ruleKey = ruleKey;
}

public String getFlagKey() {
return flagKey;
}

public String getRuleKey() {
return ruleKey != null ? ruleKey : OPTI_NULL_RULE_KEY;
}

public String getKey() {
StringBuilder keyBuilder = new StringBuilder();
keyBuilder.append(flagKey);
keyBuilder.append(OPTI_KEY_DIVIDER);
keyBuilder.append(getRuleKey());
return keyBuilder.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
*
* Copyright 2021, Optimizely and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.optimizely.ab;

import javax.annotation.Nonnull;

public class OptimizelyForcedDecision {
private String variationKey;

public OptimizelyForcedDecision(@Nonnull String variationKey) {
this.variationKey = variationKey;
}

public String getVariationKey() {
return variationKey;
}
}
Loading