Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions providers/statsig/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,12 @@ 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.

## Release Notes

### 0.3.0
- Migrated to Java Core according to [Migration guide](https://docs.statsig.com/server-core/migration-guides/java#java-migration-steps).

The provider usage is basically unchanged, the underlying implementation is changed.
As the initialization code may change, can refer to the migration guide for details.

- group and secondaryExposures usage removed.
14 changes: 11 additions & 3 deletions providers/statsig/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</parent>
<groupId>dev.openfeature.contrib.providers</groupId>
<artifactId>statsig</artifactId>
<version>0.2.1</version> <!--x-release-please-version -->
<version>0.3.0</version> <!--x-release-please-version -->

<name>statsig</name>
<description>Statsig provider for Java</description>
Expand All @@ -19,8 +19,9 @@
<dependencies>
<dependency>
<groupId>com.statsig</groupId>
<artifactId>serversdk</artifactId>
<version>1.18.1</version>
<artifactId>javacore</artifactId>
<version>0.12.1</version>
<classifier>uber</classifier>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this uber? Does this include all of the transitive compiled versions for various platforms?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From docs:

The uber JAR is recommended for most use cases as it includes native libraries for all popular platforms and simplifies deployment. Use platform-specific JARs only if you need to minimize JAR size or have specific dependency requirements.

Recommended: Using the Uber JAR (All-in-One)
Since version 0.4.0, Statsig provides an “uber” JAR that contains both the core library and native libraries for popular supported platforms in a single package. This is the recommended approach for most users.

Since we are like a library here for multi-platform, this includes all, and consumers can control it more precisely if wanted.

</dependency>

<dependency>
Expand All @@ -36,5 +37,12 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.20.0</version>
<scope>test</scope>
</dependency>

</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package dev.openfeature.contrib.providers.statsig;

import com.statsig.sdk.StatsigUser;
import com.statsig.StatsigUser;
import dev.openfeature.sdk.EvaluationContext;
import dev.openfeature.sdk.Structure;
import dev.openfeature.sdk.Value;
Expand All @@ -23,7 +23,7 @@ static StatsigUser transform(EvaluationContext ctx) {
if (ctx.getTargetingKey() == null) {
throw new TargetingKeyMissingError("targeting key is required.");
}
StatsigUser user = new StatsigUser(ctx.getTargetingKey());
StatsigUser.Builder user = new StatsigUser.Builder().setUserID(ctx.getTargetingKey());
Map<String, String> customMap = new HashMap<>();
ctx.asObjectMap().forEach((k, v) -> {
switch (k) {
Expand Down Expand Up @@ -64,6 +64,6 @@ static StatsigUser transform(EvaluationContext ctx) {
privateAttributesStructure.asObjectMap().forEach((k, v) -> privateMap.put(k, String.valueOf(v)));
user.setPrivateAttributes(privateMap);
}
return user;
return user.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
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;
import com.statsig.DynamicConfig;
import com.statsig.FeatureGate;
import com.statsig.Layer;
import com.statsig.Statsig;
import com.statsig.StatsigUser;
import dev.openfeature.sdk.EvaluationContext;
import dev.openfeature.sdk.EventProvider;
import dev.openfeature.sdk.Metadata;
import dev.openfeature.sdk.MutableContext;
import dev.openfeature.sdk.ProviderEvaluation;
import dev.openfeature.sdk.Structure;
import dev.openfeature.sdk.Value;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.CompletableFuture;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;

/** Provider implementation for Statsig. */
@Slf4j
Expand All @@ -33,6 +28,9 @@ public class StatsigProvider extends EventProvider {
private static final String FEATURE_CONFIG_KEY = "feature_config";
private final StatsigProviderConfig statsigProviderConfig;

@Getter
private Statsig statsig;

/**
* Constructor.
*
Expand All @@ -50,8 +48,8 @@ public StatsigProvider(StatsigProviderConfig statsigProviderConfig) {
*/
@Override
public void initialize(EvaluationContext evaluationContext) throws Exception {
Future<Void> initFuture =
Statsig.initializeAsync(statsigProviderConfig.getSdkKey(), statsigProviderConfig.getOptions());
statsig = new Statsig(statsigProviderConfig.getSdkKey(), statsigProviderConfig.getOptions());
CompletableFuture<Void> initFuture = statsig.initialize();
initFuture.get();

statsigProviderConfig.postInit();
Expand All @@ -65,22 +63,15 @@ 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) {
StatsigUser user = ContextTransformer.transform(ctx);
Boolean evaluatedValue = defaultValue;
Value featureConfigValue = ctx.getValue(FEATURE_CONFIG_KEY);
String reason = null;
if (featureConfigValue == null) {
APIFeatureGate featureGate = Statsig.getFeatureGate(user, key);
reason = featureGate.getReason().getReason();

// in case of evaluation failure, remain with default value.
if (!assumeFailure(featureGate)) {
evaluatedValue = featureGate.getValue();
}
FeatureGate featureGate = statsig.getFeatureGate(user, key);
reason = featureGate.getEvaluationDetails().getReason();
evaluatedValue = featureGate.getValue();
} else {
FeatureConfig featureConfig = parseFeatureConfig(ctx);
switch (featureConfig.getType()) {
Expand All @@ -103,18 +94,6 @@ public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defa
.build();
}

/*
https://github.com/statsig-io/java-server-sdk/issues/22#issuecomment-2002346349
failure is assumed by reason, since success status is not returned.
*/
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) {
StatsigUser user = ContextTransformer.transform(ctx);
Expand Down Expand Up @@ -198,26 +177,19 @@ public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultVa

@SneakyThrows
protected DynamicConfig fetchDynamicConfig(StatsigUser user, FeatureConfig featureConfig) {
return Statsig.getConfigAsync(user, featureConfig.getName()).get();
return statsig.getDynamicConfig(user, featureConfig.getName());
}

@SneakyThrows
protected Layer fetchLayer(StatsigUser user, FeatureConfig featureConfig) {
return Statsig.getLayerAsync(user, featureConfig.getName()).get();
return statsig.getLayer(user, featureConfig.getName());
}

private Value toValue(DynamicConfig dynamicConfig) {
MutableContext mutableContext = new MutableContext();
mutableContext.add("name", dynamicConfig.getName());
mutableContext.add("value", Structure.mapToStructure(dynamicConfig.getValue()));
mutableContext.add("ruleID", dynamicConfig.getRuleID());
mutableContext.add("groupName", dynamicConfig.getGroupName());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these provided in a different way? If so document in the README.md

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

documented

List<Value> secondaryExposures = new ArrayList<>();
dynamicConfig.getSecondaryExposures().forEach(secondaryExposure -> {
Value value = Value.objectToValue(secondaryExposure);
secondaryExposures.add(value);
});
mutableContext.add("secondaryExposures", secondaryExposures);
return new Value(mutableContext);
}

Expand All @@ -227,17 +199,11 @@ private Value toValue(Layer layer) {
mutableContext.add("value", Structure.mapToStructure(layer.getValue()));
mutableContext.add("ruleID", layer.getRuleID());
mutableContext.add("groupName", layer.getGroupName());
List<Value> secondaryExposures = new ArrayList<>();
layer.getSecondaryExposures().forEach(secondaryExposure -> {
Value value = Value.objectToValue(secondaryExposure);
secondaryExposures.add(value);
});
mutableContext.add("secondaryExposures", secondaryExposures);
mutableContext.add("allocatedExperiment", layer.getAllocatedExperiment());
mutableContext.add("allocatedExperiment", layer.getAllocatedExperimentName());
return new Value(mutableContext);
}

@NotNull private static FeatureConfig parseFeatureConfig(EvaluationContext ctx) {
private static FeatureConfig parseFeatureConfig(EvaluationContext ctx) {
Value featureConfigValue = ctx.getValue(FEATURE_CONFIG_KEY);
if (featureConfigValue == null) {
throw new IllegalArgumentException("feature config not found at evaluation context.");
Expand All @@ -262,8 +228,12 @@ private Value toValue(Layer layer) {
@SneakyThrows
@Override
public void shutdown() {
log.info("shutdown");
Statsig.shutdown();
log.info("shutdown begin");
if (statsig != null) {
CompletableFuture<Void> shutdownFuture = statsig.shutdown();
shutdownFuture.get();
}
log.info("shutdown end");
}

/** Feature config, as required for evaluation. */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package dev.openfeature.contrib.providers.statsig;

import com.statsig.sdk.StatsigOptions;
import com.statsig.StatsigOptions;
import lombok.Builder;
import lombok.Getter;

Expand All @@ -10,7 +10,7 @@
public class StatsigProviderConfig {

@Builder.Default
private StatsigOptions options = new StatsigOptions();
private StatsigOptions options = new StatsigOptions.Builder().build();

// Only holding temporary for initialization
private String sdkKey;
Expand Down
Loading
Loading