Skip to content

Commit 4c6a834

Browse files
authored
feat: add getFeatureVariableJSON and getAllFeatureVariables apis (#375)
1 parent 8609f3d commit 4c6a834

File tree

10 files changed

+593
-50
lines changed

10 files changed

+593
-50
lines changed

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

Lines changed: 150 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,24 @@
2525
import com.optimizely.ab.error.ErrorHandler;
2626
import com.optimizely.ab.error.NoOpErrorHandler;
2727
import com.optimizely.ab.event.*;
28-
import com.optimizely.ab.event.internal.*;
28+
import com.optimizely.ab.event.internal.ClientEngineInfo;
29+
import com.optimizely.ab.event.internal.EventFactory;
30+
import com.optimizely.ab.event.internal.UserEvent;
31+
import com.optimizely.ab.event.internal.UserEventFactory;
2932
import com.optimizely.ab.event.internal.payload.EventBatch;
3033
import com.optimizely.ab.notification.*;
3134
import com.optimizely.ab.optimizelyconfig.OptimizelyConfig;
3235
import com.optimizely.ab.optimizelyconfig.OptimizelyConfigManager;
3336
import com.optimizely.ab.optimizelyconfig.OptimizelyConfigService;
37+
import com.optimizely.ab.optimizelyjson.OptimizelyJSON;
3438
import org.slf4j.Logger;
3539
import org.slf4j.LoggerFactory;
3640

3741
import javax.annotation.Nonnull;
3842
import javax.annotation.Nullable;
3943
import javax.annotation.concurrent.ThreadSafe;
4044
import java.io.Closeable;
41-
import java.util.ArrayList;
42-
import java.util.Collections;
43-
import java.util.HashMap;
44-
import java.util.List;
45-
import java.util.Map;
45+
import java.util.*;
4646

4747
import static com.optimizely.ab.internal.SafetyUtils.tryClose;
4848

@@ -601,6 +601,46 @@ public String getFeatureVariableString(@Nonnull String featureKey,
601601
FeatureVariable.STRING_TYPE);
602602
}
603603

604+
/**
605+
* Get the JSON value of the specified variable in the feature.
606+
*
607+
* @param featureKey The unique key of the feature.
608+
* @param variableKey The unique key of the variable.
609+
* @param userId The ID of the user.
610+
* @return An OptimizelyJSON instance for the JSON variable value.
611+
* Null if the feature or variable could not be found.
612+
*/
613+
@Nullable
614+
public OptimizelyJSON getFeatureVariableJSON(@Nonnull String featureKey,
615+
@Nonnull String variableKey,
616+
@Nonnull String userId) {
617+
return getFeatureVariableJSON(featureKey, variableKey, userId, Collections.<String, String>emptyMap());
618+
}
619+
620+
/**
621+
* Get the JSON value of the specified variable in the feature.
622+
*
623+
* @param featureKey The unique key of the feature.
624+
* @param variableKey The unique key of the variable.
625+
* @param userId The ID of the user.
626+
* @param attributes The user's attributes.
627+
* @return An OptimizelyJSON instance for the JSON variable value.
628+
* Null if the feature or variable could not be found.
629+
*/
630+
@Nullable
631+
public OptimizelyJSON getFeatureVariableJSON(@Nonnull String featureKey,
632+
@Nonnull String variableKey,
633+
@Nonnull String userId,
634+
@Nonnull Map<String, ?> attributes) {
635+
636+
return getFeatureVariableValueForType(
637+
featureKey,
638+
variableKey,
639+
userId,
640+
attributes,
641+
FeatureVariable.JSON_TYPE);
642+
}
643+
604644
@VisibleForTesting
605645
<T> T getFeatureVariableValueForType(@Nonnull String featureKey,
606646
@Nonnull String variableKey,
@@ -671,6 +711,10 @@ <T> T getFeatureVariableValueForType(@Nonnull String featureKey,
671711
}
672712

673713
Object convertedValue = convertStringToType(variableValue, variableType);
714+
Object notificationValue = convertedValue;
715+
if (convertedValue instanceof OptimizelyJSON) {
716+
notificationValue = ((OptimizelyJSON) convertedValue).toMap();
717+
}
674718

675719
DecisionNotification decisionNotification = DecisionNotification.newFeatureVariableDecisionNotificationBuilder()
676720
.withUserId(userId)
@@ -679,7 +723,7 @@ <T> T getFeatureVariableValueForType(@Nonnull String featureKey,
679723
.withFeatureEnabled(featureEnabled)
680724
.withVariableKey(variableKey)
681725
.withVariableType(variableType)
682-
.withVariableValue(convertedValue)
726+
.withVariableValue(notificationValue)
683727
.withFeatureDecision(featureDecision)
684728
.build();
685729

@@ -714,6 +758,8 @@ Object convertStringToType(String variableValue, String type) {
714758
"\" as Integer. " + exception.toString());
715759
}
716760
break;
761+
case FeatureVariable.JSON_TYPE:
762+
return new OptimizelyJSON(variableValue);
717763
default:
718764
return variableValue;
719765
}
@@ -722,6 +768,103 @@ Object convertStringToType(String variableValue, String type) {
722768
return null;
723769
}
724770

771+
/**
772+
* Get the values of all variables in the feature.
773+
*
774+
* @param featureKey The unique key of the feature.
775+
* @param userId The ID of the user.
776+
* @return An OptimizelyJSON instance for all variable values.
777+
* Null if the feature could not be found.
778+
*/
779+
@Nullable
780+
public OptimizelyJSON getAllFeatureVariables(@Nonnull String featureKey,
781+
@Nonnull String userId) {
782+
return getAllFeatureVariables(featureKey, userId, Collections.<String, String>emptyMap());
783+
}
784+
785+
/**
786+
* Get the values of all variables in the feature.
787+
*
788+
* @param featureKey The unique key of the feature.
789+
* @param userId The ID of the user.
790+
* @param attributes The user's attributes.
791+
* @return An OptimizelyJSON instance for all variable values.
792+
* Null if the feature could not be found.
793+
*/
794+
@Nullable
795+
public OptimizelyJSON getAllFeatureVariables(@Nonnull String featureKey,
796+
@Nonnull String userId,
797+
@Nonnull Map<String, ?> attributes) {
798+
799+
if (featureKey == null) {
800+
logger.warn("The featureKey parameter must be nonnull.");
801+
return null;
802+
} else if (userId == null) {
803+
logger.warn("The userId parameter must be nonnull.");
804+
return null;
805+
}
806+
807+
ProjectConfig projectConfig = getProjectConfig();
808+
if (projectConfig == null) {
809+
logger.error("Optimizely instance is not valid, failing getAllFeatureVariableValues call. type");
810+
return null;
811+
}
812+
813+
FeatureFlag featureFlag = projectConfig.getFeatureKeyMapping().get(featureKey);
814+
if (featureFlag == null) {
815+
logger.info("No feature flag was found for key \"{}\".", featureKey);
816+
return null;
817+
}
818+
819+
Map<String, ?> copiedAttributes = copyAttributes(attributes);
820+
FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, userId, copiedAttributes, projectConfig);
821+
Boolean featureEnabled = false;
822+
Variation variation = featureDecision.variation;
823+
824+
if (variation != null) {
825+
if (!variation.getFeatureEnabled()) {
826+
logger.info("Feature \"{}\" for variation \"{}\" was not enabled. " +
827+
"The default value is being returned.", featureKey, featureDecision.variation.getKey());
828+
}
829+
830+
featureEnabled = variation.getFeatureEnabled();
831+
} else {
832+
logger.info("User \"{}\" was not bucketed into any variation for feature flag \"{}\". " +
833+
"The default values are being returned.", userId, featureKey);
834+
}
835+
836+
Map<String, Object> valuesMap = new HashMap<String, Object>();
837+
for (FeatureVariable variable : featureFlag.getVariables()) {
838+
String value = variable.getDefaultValue();
839+
if (featureEnabled) {
840+
FeatureVariableUsageInstance instance = variation.getVariableIdToFeatureVariableUsageInstanceMap().get(variable.getId());
841+
if (instance != null) {
842+
value = instance.getValue();
843+
}
844+
}
845+
846+
Object convertedValue = convertStringToType(value, variable.getType());
847+
if (convertedValue instanceof OptimizelyJSON) {
848+
convertedValue = ((OptimizelyJSON) convertedValue).toMap();
849+
}
850+
851+
valuesMap.put(variable.getKey(), convertedValue);
852+
}
853+
854+
DecisionNotification decisionNotification = DecisionNotification.newFeatureVariableDecisionNotificationBuilder()
855+
.withUserId(userId)
856+
.withAttributes(copiedAttributes)
857+
.withFeatureKey(featureKey)
858+
.withFeatureEnabled(featureEnabled)
859+
.withVariableValues(valuesMap)
860+
.withFeatureDecision(featureDecision)
861+
.build();
862+
863+
notificationCenter.send(decisionNotification);
864+
865+
return new OptimizelyJSON(valuesMap);
866+
}
867+
725868
/**
726869
* Get the list of features that are enabled for the user.
727870
* TODO revisit this method. Calling this as-is can dramatically increase visitor impression counts.

core-api/src/main/java/com/optimizely/ab/notification/DecisionNotification.java

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
import com.optimizely.ab.OptimizelyRuntimeException;
2121
import com.optimizely.ab.bucketing.FeatureDecision;
22-
import com.optimizely.ab.config.FeatureVariable;
2322
import com.optimizely.ab.config.Variation;
2423

2524
import javax.annotation.Nonnull;
@@ -239,13 +238,16 @@ public static class FeatureVariableDecisionNotificationBuilder {
239238
public static final String VARIABLE_KEY = "variableKey";
240239
public static final String VARIABLE_TYPE = "variableType";
241240
public static final String VARIABLE_VALUE = "variableValue";
241+
public static final String VARIABLE_VALUES = "variableValues";
242242

243+
private NotificationCenter.DecisionNotificationType notificationType;
243244
private String featureKey;
244245
private Boolean featureEnabled;
245246
private FeatureDecision featureDecision;
246247
private String variableKey;
247248
private String variableType;
248249
private Object variableValue;
250+
private Object variableValues;
249251
private String userId;
250252
private Map<String, ?> attributes;
251253
private Map<String, Object> decisionInfo;
@@ -293,6 +295,11 @@ public FeatureVariableDecisionNotificationBuilder withVariableValue(Object varia
293295
return this;
294296
}
295297

298+
public FeatureVariableDecisionNotificationBuilder withVariableValues(Object variableValues) {
299+
this.variableValues = variableValues;
300+
return this;
301+
}
302+
296303
public DecisionNotification build() {
297304
if (featureKey == null) {
298305
throw new OptimizelyRuntimeException("featureKey not set");
@@ -302,20 +309,30 @@ public DecisionNotification build() {
302309
throw new OptimizelyRuntimeException("featureEnabled not set");
303310
}
304311

305-
if (variableKey == null) {
306-
throw new OptimizelyRuntimeException("variableKey not set");
307-
}
308-
309-
if (variableType == null) {
310-
throw new OptimizelyRuntimeException("variableType not set");
311-
}
312312

313313
decisionInfo = new HashMap<>();
314314
decisionInfo.put(FEATURE_KEY, featureKey);
315315
decisionInfo.put(FEATURE_ENABLED, featureEnabled);
316-
decisionInfo.put(VARIABLE_KEY, variableKey);
317-
decisionInfo.put(VARIABLE_TYPE, variableType.toString());
318-
decisionInfo.put(VARIABLE_VALUE, variableValue);
316+
317+
if (variableValues != null) {
318+
notificationType = NotificationCenter.DecisionNotificationType.ALL_FEATURE_VARIABLES;
319+
decisionInfo.put(VARIABLE_VALUES, variableValues);
320+
} else {
321+
notificationType = NotificationCenter.DecisionNotificationType.FEATURE_VARIABLE;
322+
323+
if (variableKey == null) {
324+
throw new OptimizelyRuntimeException("variableKey not set");
325+
}
326+
327+
if (variableType == null) {
328+
throw new OptimizelyRuntimeException("variableType not set");
329+
}
330+
331+
decisionInfo.put(VARIABLE_KEY, variableKey);
332+
decisionInfo.put(VARIABLE_TYPE, variableType.toString());
333+
decisionInfo.put(VARIABLE_VALUE, variableValue);
334+
}
335+
319336
SourceInfo sourceInfo = new RolloutSourceInfo();
320337

321338
if (featureDecision != null && FeatureDecision.DecisionSource.FEATURE_TEST.equals(featureDecision.decisionSource)) {
@@ -327,7 +344,7 @@ public DecisionNotification build() {
327344
decisionInfo.put(SOURCE_INFO, sourceInfo.get());
328345

329346
return new DecisionNotification(
330-
NotificationCenter.DecisionNotificationType.FEATURE_VARIABLE.toString(),
347+
notificationType.toString(),
331348
userId,
332349
attributes,
333350
decisionInfo);

core-api/src/main/java/com/optimizely/ab/notification/NotificationCenter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ public enum DecisionNotificationType {
5454
AB_TEST("ab-test"),
5555
FEATURE("feature"),
5656
FEATURE_TEST("feature-test"),
57-
FEATURE_VARIABLE("feature-variable");
57+
FEATURE_VARIABLE("feature-variable"),
58+
ALL_FEATURE_VARIABLES("all-feature-variables");
5859

5960
private final String key;
6061

0 commit comments

Comments
 (0)