Skip to content

Commit

Permalink
feat: Add Statsig provider (#641)
Browse files Browse the repository at this point in the history
Signed-off-by: liran2000 <[email protected]>
Signed-off-by: Todd Baert <[email protected]>
Co-authored-by: Todd Baert <[email protected]>
  • Loading branch information
liran2000 and toddbaert authored Feb 2, 2024
1 parent 677e07a commit f814696
Show file tree
Hide file tree
Showing 12 changed files with 862 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .github/component_owners.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ components:
- z4kn4fein
- laliconfigcat
- novalisdenahi
providers/statsig:
- liran2000

ignored-authors:
- renovate-bot
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<module>providers/unleash</module>
<module>providers/flipt</module>
<module>providers/configcat</module>
<module>providers/statsig</module>
</modules>

<scm>
Expand Down
1 change: 1 addition & 0 deletions providers/statsig/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Changelog
73 changes: 73 additions & 0 deletions providers/statsig/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Unofficial Statsig OpenFeature Provider for Java

[Statsig](https://statsig.com/) OpenFeature Provider can provide usage for Statsig via OpenFeature Java SDK.

## Installation

<!-- x-release-please-start-version -->

```xml

<dependency>
<groupId>dev.openfeature.contrib.providers</groupId>
<artifactId>statsig</artifactId>
<version>0.0.1</version>
</dependency>
```

<!-- x-release-please-end-version -->

## Concepts
* Boolean evaluation gets [gate](https://docs.statsig.com/server/javaSdk#checking-a-gate) status.
* String/Integer/Double evaluations evaluation gets [Dynamic config](https://docs.statsig.com/server/javaSdk#reading-a-dynamic-config) or [Layer](https://docs.statsig.com/server/javaSdk#getting-an-layerexperiment) evaluation.
As the key represents an inner attribute, feature config is required as a parameter with data needed for evaluation.
For an example of dynamic config of product alias, need to differentiate between dynamic config or layer, and the dynamic config name.
* Object evaluation gets a structure representing the dynamic config or layer.
* [Private Attributes](https://docs.statsig.com/server/javaSdk#private-attributes) are supported as 'privateAttributes' context key.

## Usage
Statsig OpenFeature Provider is based on [Statsig Java SDK documentation](https://docs.statsig.com/server/javaSdk).

### Usage Example

```
StatsigOptions statsigOptions = new StatsigOptions();
StatsigProviderConfig statsigProviderConfig = StatsigProviderConfig.builder().sdkKey(sdkKey)
.options(statsigOptions).build();
statsigProvider = new StatsigProvider(statsigProviderConfig);
OpenFeatureAPI.getInstance().setProviderAndWait(statsigProvider);
boolean featureEnabled = client.getBooleanValue(FLAG_NAME, false);
MutableContext evaluationContext = new MutableContext();
MutableContext featureConfig = new MutableContext();
featureConfig.add("type", "CONFIG");
featureConfig.add("name", "product");
evaluationContext.add("feature_config", featureConfig);
String value = statsigProvider.getStringEvaluation("alias", "fallback", evaluationContext).getValue());
MutableContext evaluationContext = new MutableContext();
evaluationContext.setTargetingKey("test-id");
evaluationContext.add("Email", "[email protected]");
MutableContext privateAttributes = new MutableContext();
privateAttributes.add("locale", locale);
evaluationContext.add("privateAttributes", privateAttributes);
featureEnabled = client.getBooleanValue(USERS_FLAG_NAME, false, evaluationContext);
```

See [StatsigProviderTest](./src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java)
for more information.

## Notes
Some Statsig custom operations are supported from the Statsig client via:

```java
Statsig...
```

## Statsig Provider Tests Strategies

Unit test based on Statsig [Local Overrides](https://docs.statsig.com/server/javaSdk#local-overrides) and mocking.
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.
5 changes: 5 additions & 0 deletions providers/statsig/lombok.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# This file is needed to avoid errors throw by findbugs when working with lombok.
lombok.addSuppressWarnings = true
lombok.addLombokGeneratedAnnotation = true
config.stopBubbling = true
lombok.extern.findbugs.addSuppressFBWarnings = true
40 changes: 40 additions & 0 deletions providers/statsig/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>dev.openfeature.contrib</groupId>
<artifactId>parent</artifactId>
<version>0.1.0</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<groupId>dev.openfeature.contrib.providers</groupId>
<artifactId>statsig</artifactId>
<version>0.0.1</version> <!--x-release-please-version -->

<name>statsig</name>
<description>Statsig provider for Java</description>
<url>https://statsig.com/</url>

<dependencies>
<dependency>
<groupId>com.statsig</groupId>
<artifactId>serversdk</artifactId>
<version>1.10.0</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.11</version>
</dependency>

<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.22.1</version>
<scope>test</scope>
</dependency>

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

import com.statsig.sdk.StatsigUser;
import dev.openfeature.sdk.EvaluationContext;
import dev.openfeature.sdk.Structure;
import dev.openfeature.sdk.Value;
import dev.openfeature.sdk.exceptions.TargetingKeyMissingError;

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

/**
* Transformer from OpenFeature context to statsig User.
*/
class ContextTransformer {
public static final String CONTEXT_APP_VERSION = "appVersion";
public static final String CONTEXT_COUNTRY = "country";
public static final String CONTEXT_EMAIL = "email";
public static final String CONTEXT_IP = "ip";
public static final String CONTEXT_LOCALE = "locale";
public static final String CONTEXT_USER_AGENT = "userAgent";
public static final String CONTEXT_PRIVATE_ATTRIBUTES = "privateAttributes";

static StatsigUser transform(EvaluationContext ctx) {
if (ctx.getTargetingKey() == null) {
throw new TargetingKeyMissingError("targeting key is required.");
}
StatsigUser user = new StatsigUser(ctx.getTargetingKey());
Map<String, String> customMap = new HashMap<>();
ctx.asObjectMap().forEach((k, v) -> {
switch (k) {
case CONTEXT_APP_VERSION:
user.setAppVersion(String.valueOf(v));
break;
case CONTEXT_COUNTRY:
user.setCountry(String.valueOf(v));
break;
case CONTEXT_EMAIL:
user.setEmail(String.valueOf(v));
break;
case CONTEXT_IP:
user.setIp(String.valueOf(v));
break;
case CONTEXT_USER_AGENT:
user.setUserAgent(String.valueOf(v));
break;
case CONTEXT_LOCALE:
user.setLocale(String.valueOf(v));
break;
default:
if (!CONTEXT_PRIVATE_ATTRIBUTES.equals(k)) {
customMap.put(k, String.valueOf(v));
}
break;
}
});
user.setCustomIDs(customMap);

Map<String, String> privateMap = new HashMap<>();
Value privateAttributes = ctx.getValue(CONTEXT_PRIVATE_ATTRIBUTES);
if (privateAttributes != null && privateAttributes.isStructure()) {
Structure privateAttributesStructure = privateAttributes.asStructure();
privateAttributesStructure.asObjectMap().forEach((k, v) -> privateMap.put(k, String.valueOf(v)));
user.setPrivateAttributes(privateMap);
}
return user;
}

}
Loading

0 comments on commit f814696

Please sign in to comment.