Skip to content
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

feat: add support for getBooleanEvaluation with default value #722

Merged
merged 6 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
6 changes: 2 additions & 4 deletions providers/statsig/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ StatsigProviderConfig statsigProviderConfig = StatsigProviderConfig.builder().sd
statsigProvider = new StatsigProvider(statsigProviderConfig);
OpenFeatureAPI.getInstance().setProviderAndWait(statsigProvider);

MutableContext evaluationContext = new MutableContext();
evaluationContext.setTargetingKey(TARGETING_KEY);
boolean featureEnabled = client.getBooleanValue(FLAG_NAME, false);

MutableContext evaluationContext = new MutableContext();
MutableContext featureConfig = new MutableContext();
featureConfig.add("type", "CONFIG");
featureConfig.add("name", "product");
Expand Down Expand Up @@ -73,7 +74,4 @@ As it is limited, evaluation context based tests are limited.
See [statsigProviderTest](./src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java)
for more information.

## Known issues
- Gate BooleanEvaluation with default value true cannot fallback to true.
https://github.com/statsig-io/java-server-sdk/issues/22

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
* Transformer from OpenFeature context to statsig User.
Expand All @@ -29,27 +30,30 @@ static StatsigUser transform(EvaluationContext ctx) {
Map<String, String> customMap = new HashMap<>();
ctx.asObjectMap().forEach((k, v) -> {
switch (k) {
case "targetingKey":
user.setUserID(Objects.toString(v, null));
break;
case CONTEXT_APP_VERSION:
user.setAppVersion(String.valueOf(v));
user.setAppVersion(Objects.toString(v, null));
break;
case CONTEXT_COUNTRY:
user.setCountry(String.valueOf(v));
user.setCountry(Objects.toString(v, null));
break;
case CONTEXT_EMAIL:
user.setEmail(String.valueOf(v));
user.setEmail(Objects.toString(v, null));
break;
case CONTEXT_IP:
user.setIp(String.valueOf(v));
user.setIp(Objects.toString(v, null));
break;
case CONTEXT_USER_AGENT:
user.setUserAgent(String.valueOf(v));
user.setUserAgent(Objects.toString(v, null));
break;
case CONTEXT_LOCALE:
user.setLocale(String.valueOf(v));
user.setLocale(Objects.toString(v, null));
break;
default:
if (!CONTEXT_PRIVATE_ATTRIBUTES.equals(k)) {
customMap.put(k, String.valueOf(v));
customMap.put(k, Objects.toString(v, null));
}
break;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package dev.openfeature.contrib.providers.statsig;

import com.statsig.sdk.APIFeatureGate;
import com.statsig.sdk.DynamicConfig;
import com.statsig.sdk.EvaluationReason;
import com.statsig.sdk.Layer;
import com.statsig.sdk.Statsig;
import com.statsig.sdk.StatsigUser;
Expand All @@ -14,6 +16,7 @@
import dev.openfeature.sdk.Value;
import dev.openfeature.sdk.exceptions.GeneralError;
import dev.openfeature.sdk.exceptions.ProviderNotReadyError;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.SneakyThrows;
Expand Down Expand Up @@ -82,11 +85,21 @@ public Metadata getMetadata() {

@SneakyThrows
@Override
@SuppressFBWarnings(value = {"NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"}, justification = "reason can be null")
public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
verifyEvaluation();
StatsigUser user = ContextTransformer.transform(ctx);
Boolean evaluatedValue = defaultValue;
try {
Value featureConfigValue = ctx.getValue(FEATURE_CONFIG_KEY);
String reason = null;
if (featureConfigValue == null) {
APIFeatureGate featureGate = Statsig.getFeatureGate(user, key);
if (featureGate.getReason() != null && assumeFailure(featureGate)) {
reason = featureGate.getReason().getReason();
} else {
toddbaert marked this conversation as resolved.
Show resolved Hide resolved
evaluatedValue = featureGate.getValue();
}
} else {
FeatureConfig featureConfig = parseFeatureConfig(ctx);
switch (featureConfig.getType()) {
case CONFIG:
Expand All @@ -100,17 +113,22 @@ public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defa
default:
break;
}
} catch (Exception e) {
log.debug("could not fetch feature config. checking gate {}.", key);
Future<Boolean> featureOn = Statsig.checkGateAsync(user, key);
evaluatedValue = featureOn.get();
}

return ProviderEvaluation.<Boolean>builder()
.value(evaluatedValue)
.reason(reason)
.build();
}

private boolean assumeFailure(APIFeatureGate featureGate) {
EvaluationReason reason = featureGate.getReason();
return EvaluationReason.DEFAULT.equals(reason)
|| EvaluationReason.UNINITIALIZED.equals(reason)
|| EvaluationReason.UNRECOGNIZED.equals(reason)
|| EvaluationReason.UNSUPPORTED.equals(reason);
}

@Override
public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
verifyEvaluation();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.statsig.sdk.StatsigOptions;
import com.statsig.sdk.StatsigUser;
import dev.openfeature.sdk.Client;
import dev.openfeature.sdk.FlagEvaluationDetails;
import dev.openfeature.sdk.ImmutableContext;
import dev.openfeature.sdk.MutableContext;
import dev.openfeature.sdk.OpenFeatureAPI;
Expand Down Expand Up @@ -54,6 +55,7 @@ class StatsigProviderTest {
public static final Double DOUBLE_FLAG_VALUE = 3.14;
public static final String USERS_FLAG_NAME = "userIdMatching";
public static final String PROPERTIES_FLAG_NAME = "emailMatching";
public static final String TARGETING_KEY = "user1";
private static StatsigProvider statsigProvider;
private static Client client;

Expand Down Expand Up @@ -115,15 +117,18 @@ static void shutdown() {

@Test
void getBooleanEvaluation() {
assertEquals(true, statsigProvider.getBooleanEvaluation(FLAG_NAME, false, new ImmutableContext()).getValue());
assertEquals(true, client.getBooleanValue(FLAG_NAME, false));
assertEquals(false, statsigProvider.getBooleanEvaluation("non-existing", false, new ImmutableContext()).getValue());
assertEquals(false, client.getBooleanValue("non-existing", false));

// expected to succeed when https://github.com/statsig-io/java-server-sdk/issues/22 is resolved and adopted
// assertEquals(true, client.getBooleanValue("non-existing", true));
FlagEvaluationDetails<Boolean> flagEvaluationDetails = client.getBooleanDetails(FLAG_NAME, false, new ImmutableContext());
assertEquals(false, flagEvaluationDetails.getValue());
assertEquals("ERROR", flagEvaluationDetails.getReason());

MutableContext evaluationContext = new MutableContext();
evaluationContext.setTargetingKey(TARGETING_KEY);
assertEquals(true, statsigProvider.getBooleanEvaluation(FLAG_NAME, false, evaluationContext).getValue());
assertEquals(true, client.getBooleanValue(FLAG_NAME, false, evaluationContext));
assertEquals(false, statsigProvider.getBooleanEvaluation("non-existing", false, evaluationContext).getValue());
assertEquals(false, client.getBooleanValue("non-existing", false, evaluationContext));
assertEquals(true, client.getBooleanValue("non-existing", true));

MutableContext featureConfig = new MutableContext();
featureConfig.add("type", "CONFIG");
featureConfig.add("name", "product");
Expand All @@ -135,6 +140,7 @@ void getBooleanEvaluation() {
@Test
void getStringEvaluation() {
MutableContext evaluationContext = new MutableContext();
evaluationContext.setTargetingKey(TARGETING_KEY);
MutableContext featureConfig = new MutableContext();
featureConfig.add("type", "CONFIG");
featureConfig.add("name", "product");
Expand All @@ -149,6 +155,7 @@ void getStringEvaluation() {
@Test
void getObjectConfigEvaluation() {
MutableContext evaluationContext = new MutableContext();
evaluationContext.setTargetingKey(TARGETING_KEY);
MutableContext featureConfig = new MutableContext();
featureConfig.add("type", "CONFIG");
featureConfig.add("name", "object-config-name");
Expand All @@ -164,6 +171,7 @@ void getObjectConfigEvaluation() {
@Test
void getObjectLayerEvaluation() {
MutableContext evaluationContext = new MutableContext();
evaluationContext.setTargetingKey(TARGETING_KEY);
MutableContext featureConfig = new MutableContext();
featureConfig.add("type", "LAYER");
featureConfig.add("name", "layer-name");
Expand All @@ -180,6 +188,7 @@ void getObjectLayerEvaluation() {
@Test
void getIntegerEvaluation() {
MutableContext evaluationContext = new MutableContext();
evaluationContext.setTargetingKey(TARGETING_KEY);
MutableContext featureConfig = new MutableContext();
featureConfig.add("type", "CONFIG");
featureConfig.add("name", "product");
Expand All @@ -197,6 +206,7 @@ void getIntegerEvaluation() {
@Test
void getDoubleEvaluation() {
MutableContext evaluationContext = new MutableContext();
evaluationContext.setTargetingKey(TARGETING_KEY);
MutableContext featureConfig = new MutableContext();
featureConfig.add("type", "CONFIG");
featureConfig.add("name", "product");
Expand All @@ -214,6 +224,7 @@ void getDoubleEvaluation() {
@Test
void getBooleanEvaluationByUser() {
MutableContext evaluationContext = new MutableContext();
evaluationContext.setTargetingKey(TARGETING_KEY);
MutableContext featureConfig = new MutableContext();
featureConfig.add("type", "CONFIG");
featureConfig.add("name", "product");
Expand Down
Loading