Skip to content

Commit

Permalink
Merge pull request #454 from splitio/SDKS-7439
Browse files Browse the repository at this point in the history
[SDKS-7439] Flag sets
  • Loading branch information
nmayorsplit authored Nov 2, 2023
2 parents fb66059 + 42be0f6 commit 09cd178
Show file tree
Hide file tree
Showing 90 changed files with 2,851 additions and 918 deletions.
11 changes: 11 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
4.10.0 (Nov 2, 2023)
- Added support for Flag Sets on the SDK, which enables grouping feature flags and interacting with the group rather than individually (more details in our documentation):
- Added new variations of the get treatment methods to support evaluating flags in given flag set/s.
- getTreatmentsByFlagSet and getTreatmentsByFlagSets
- getTreatmentWithConfigByFlagSets and getTreatmentsWithConfigByFlagSets
- Added a new optional Flag Sets Filter configuration option. This allows the SDK and Split services to only synchronize the flags in the specified flag sets, avoiding unused or unwanted flags from being synced on the SDK instance, bringing all the benefits from a reduced payload.
- Note: Only applicable when the SDK is in charge of the rollout data synchronization. When not applicable, the SDK will log a warning on init.
- Updated the following SDK manager methods to expose flag sets on flag views.
- Added `defaultTreatment` property to the `SplitView` object returned by the `split` and `splits` methods of the SDK manager.
- Added new `threadFactory` property in SDK config. It allows to use of Virtual Threading.

4.9.0 (Sep 8, 2023)
- Added InputStream config for localhost mode providing a solution when the file is inside a jar.
- Fixed track impressions to send all impressions to the listener.
Expand Down
4 changes: 2 additions & 2 deletions client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>io.split.client</groupId>
<artifactId>java-client-parent</artifactId>
<version>4.9.0</version>
<version>4.10.0</version>
</parent>
<artifactId>java-client</artifactId>
<packaging>jar</packaging>
Expand Down Expand Up @@ -149,7 +149,7 @@
<dependency>
<groupId>io.split.client</groupId>
<artifactId>pluggable-storage</artifactId>
<version>2.0.0</version>
<version>2.1.0</version>
<scope>compile</scope>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import io.split.storages.SplitCacheProducer;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -51,7 +52,7 @@ public void updateCache(Map<SplitAndKey, LocalhostSplit> map) {
String treatment = conditions.size() > 0 ? Treatments.CONTROL : localhostSplit.treatment;
configurations.put(localhostSplit.treatment, localhostSplit.config);

split = new ParsedSplit(splitName, 0, false, treatment,conditions, LOCALHOST, 0, 100, 0, 0, configurations);
split = new ParsedSplit(splitName, 0, false, treatment,conditions, LOCALHOST, 0, 100, 0, 0, configurations, new HashSet<>());
parsedSplits.removeIf(parsedSplit -> parsedSplit.feature().equals(splitName));
parsedSplits.add(split);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.google.common.annotations.VisibleForTesting;
import io.split.client.dtos.SplitChange;
import io.split.client.exceptions.UriTooLongException;
import io.split.client.utils.Json;
import io.split.client.utils.Utils;
import io.split.engine.common.FetchOptions;
Expand Down Expand Up @@ -35,6 +36,7 @@ public final class HttpSplitChangeFetcher implements SplitChangeFetcher {

private static final String SINCE = "since";
private static final String TILL = "till";
private static final String SETS = "sets";

private static final String HEADER_CACHE_CONTROL_NAME = "Cache-Control";
private static final String HEADER_CACHE_CONTROL_VALUE = "no-cache";
Expand Down Expand Up @@ -75,6 +77,9 @@ public SplitChange fetch(long since, FetchOptions options) {
if (options.hasCustomCN()) {
uriBuilder.addParameter(TILL, "" + options.targetCN());
}
if (!options.flagSetsFilter().isEmpty()) {
uriBuilder.addParameter(SETS, "" + options.flagSetsFilter());
}
URI uri = uriBuilder.build();

HttpGet request = new HttpGet(uri);
Expand All @@ -98,6 +103,10 @@ public SplitChange fetch(long since, FetchOptions options) {

if (statusCode < HttpStatus.SC_OK || statusCode >= HttpStatus.SC_MULTIPLE_CHOICES) {
_telemetryRuntimeProducer.recordSyncError(ResourceEnum.SPLIT_SYNC, statusCode);
if (statusCode == HttpStatus.SC_REQUEST_URI_TOO_LONG) {
_log.error("The amount of flag sets provided are big causing uri length error.");
throw new UriTooLongException(String.format("Status code: %s. Message: %s", statusCode, response.getReasonPhrase()));
}
_log.warn(String.format("Response status was: %s. Reason: %s", statusCode , response.getReasonPhrase()));
throw new IllegalStateException(String.format("Could not retrieve splitChanges since %s; http return code %s", since, statusCode));
}
Expand Down
132 changes: 132 additions & 0 deletions client/src/main/java/io/split/client/SplitClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,138 @@ public interface SplitClient {
*/
Map<String, SplitResult> getTreatmentsWithConfig(Key key, List<String> featureFlagNames, Map<String, Object> attributes);

/**
* Same as {@link #getTreatments(String, List<String>, Map)} but it returns for each feature flag the configuration associated to the
* matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
* <p/>
* <p/>
* Examples include showing a different treatment to users on trial plan
* vs. premium plan. Another example is to show a different treatment
* to users created after a certain date.
*
* @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty.
* @param flagSet the Flag Set name that you want to evaluate. MUST not be null or empty.
* @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
* @return for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control'.
*/
Map<String, String> getTreatmentsByFlagSet(String key, String flagSet, Map<String, Object> attributes);

/**
* Same as {@link #getTreatments(String, List<String>, Map)} but it returns for each feature flag the configuration associated to the
* matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
* <p/>
* <p/>
* Examples include showing a different treatment to users on trial plan
* vs. premium plan. Another example is to show a different treatment
* to users created after a certain date.
*
* @param key the matching and bucketing keys. MUST not be null or empty.
* @param flagSet the Flag Set name that you want to evaluate. MUST not be null or empty.
* @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
* @return for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control'.
*/
Map<String, String> getTreatmentsByFlagSet(Key key, String flagSet, Map<String, Object> attributes);

/**
* Same as {@link #getTreatments(String, List<String>, Map)} but it returns for each feature flag the configuration associated to the
* matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
* <p/>
* <p/>
* Examples include showing a different treatment to users on trial plan
* vs. premium plan. Another example is to show a different treatment
* to users created after a certain date.
*
* @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty.
* @param flagSets the names of Flag Sets that you want to evaluate. MUST not be null or empty.
* @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
* @return for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control'.
*/
Map<String, String> getTreatmentsByFlagSets(String key, List<String> flagSets, Map<String, Object> attributes);

/**
* Same as {@link #getTreatments(String, List<String>, Map)} but it returns for each feature flag the configuration associated to the
* matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
* <p/>
* <p/>
* Examples include showing a different treatment to users on trial plan
* vs. premium plan. Another example is to show a different treatment
* to users created after a certain date.
*
* @param key the matching and bucketing keys. MUST not be null or empty.
* @param flagSets the names of Flag Sets that you want to evaluate. MUST not be null or empty.
* @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
* @return for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control'.
*/
Map<String, String> getTreatmentsByFlagSets(Key key, List<String> flagSets, Map<String, Object> attributes);

/**
* Same as {@link #getTreatments(String, List<String>, Map)} but it returns for each feature flag the configuration associated to the
* matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
* <p/>
* <p/>
* Examples include showing a different treatment to users on trial plan
* vs. premium plan. Another example is to show a different treatment
* to users created after a certain date.
*
* @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty.
* @param flagSet the Flag Set name that you want to evaluate. MUST not be null or empty.
* @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
* @return for each feature flag the evaluated treatment (the default treatment of this feature flag, or 'control') and a configuration
* associated to this treatment if set.
*/
Map<String, SplitResult> getTreatmentsWithConfigByFlagSet(String key, String flagSet, Map<String, Object> attributes);

/**
* Same as {@link #getTreatments(String, List<String>, Map)} but it returns for each feature flag the configuration associated to the
* matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
* <p/>
* <p/>
* Examples include showing a different treatment to users on trial plan
* vs. premium plan. Another example is to show a different treatment
* to users created after a certain date.
*
* @param key the matching and bucketing keys. MUST not be null or empty.
* @param flagSet the Flag Set name that you want to evaluate. MUST not be null or empty.
* @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
* @return for each feature flag the evaluated treatment (the default treatment of this feature flag, or 'control') and a configuration
* associated to this treatment if set.
*/
Map<String, SplitResult> getTreatmentsWithConfigByFlagSet(Key key, String flagSet, Map<String, Object> attributes);

/**
* Same as {@link #getTreatments(String, List<String>, Map)} but it returns for each feature flag the configuration associated to the
* matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
* <p/>
* <p/>
* Examples include showing a different treatment to users on trial plan
* vs. premium plan. Another example is to show a different treatment
* to users created after a certain date.
*
* @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty.
* @param flagSets the names of Flag Sets that you want to evaluate. MUST not be null or empty.
* @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
* @return for each feature flag the evaluated treatment (the default treatment of this feature flag, or 'control') and a configuration
* associated to this treatment if set.
*/
Map<String, SplitResult> getTreatmentsWithConfigByFlagSets(String key, List<String> flagSets, Map<String, Object> attributes);

/**
* Same as {@link #getTreatments(String, List<String>, Map)} but it returns for each feature flag the configuration associated to the
* matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
* <p/>
* <p/>
* Examples include showing a different treatment to users on trial plan
* vs. premium plan. Another example is to show a different treatment
* to users created after a certain date.
*
* @param key the matching and bucketing keys. MUST not be null or empty.
* @param flagSets the names of Flag Sets that you want to evaluate. MUST not be null or empty.
* @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
* @return for each feature flag the evaluated treatment (the default treatment of this feature flag, or 'control') and a configuration
* associated to this treatment if set.
*/
Map<String, SplitResult> getTreatmentsWithConfigByFlagSets(Key key, List<String> flagSets, Map<String, Object> attributes);

/**
* Destroys the background processes and clears the cache, releasing the resources used by
* the any instances of SplitClient or SplitManager generated by the client's parent SplitFactory
Expand Down
41 changes: 38 additions & 3 deletions client/src/main/java/io/split/client/SplitClientConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@
import pluggable.CustomStorageWrapper;

import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.io.InputStream;
import java.util.Properties;
import java.util.concurrent.ThreadFactory;

import static io.split.inputValidation.FlagSetsValidator.cleanup;

/**
* Configurations for the SplitClient.
*
Expand Down Expand Up @@ -84,6 +89,8 @@ public class SplitClientConfig {
// To be set during startup
public static String splitSdkVersion;
private final long _lastSeenCacheSize;
private final HashSet<String> _flagSetsFilter;
private final int _invalidSets;

public static Builder builder() {
return new Builder();
Expand Down Expand Up @@ -138,7 +145,9 @@ private SplitClientConfig(String endpoint,
int uniqueKeysRefreshRateRedis,
int filterUniqueKeysRefreshRate,
long lastSeenCacheSize,
ThreadFactory threadFactory) {
ThreadFactory threadFactory,
HashSet<String> flagSetsFilter,
int invalidSets) {
_endpoint = endpoint;
_eventsEndpoint = eventsEndpoint;
_featuresRefreshRate = pollForFeatureChangesEveryNSeconds;
Expand Down Expand Up @@ -189,7 +198,8 @@ private SplitClientConfig(String endpoint,
_customStorageWrapper = customStorageWrapper;
_lastSeenCacheSize = lastSeenCacheSize;
_threadFactory = threadFactory;

_flagSetsFilter = flagSetsFilter;
_invalidSets = invalidSets;

Properties props = new Properties();
try {
Expand Down Expand Up @@ -375,10 +385,19 @@ public CustomStorageWrapper customStorageWrapper() {
public long getLastSeenCacheSize() {
return _lastSeenCacheSize;
}

public ThreadFactory getThreadFactory() {
return _threadFactory;
}

public HashSet<String> getSetsFilter() {
return _flagSetsFilter;
}

public int getInvalidSets() {
return _invalidSets;
}

public static final class Builder {

private String _endpoint = SDK_ENDPOINT;
Expand Down Expand Up @@ -434,6 +453,8 @@ public static final class Builder {
private StorageMode _storageMode = StorageMode.MEMORY;
private final long _lastSeenCacheSize = 500000;
private ThreadFactory _threadFactory;
private HashSet<String> _flagSetsFilter = new HashSet<>();
private int _invalidSetsCount = 0;

public Builder() {
}
Expand Down Expand Up @@ -905,6 +926,18 @@ public Builder customStorageWrapper(CustomStorageWrapper customStorageWrapper) {
return this;
}

/**
* Flag Sets Filter
*
* @param flagSetsFilter
* @return this builder
*/
public Builder flagSetsFilter(List<String> flagSetsFilter) {
_flagSetsFilter = new LinkedHashSet<>(cleanup(flagSetsFilter));
_invalidSetsCount = flagSetsFilter.size() - _flagSetsFilter.size();
return this;
}

/**
* Thread Factory
*
Expand Down Expand Up @@ -1063,7 +1096,9 @@ public SplitClientConfig build() {
_uniqueKeysRefreshRateRedis,
_filterUniqueKeysRefreshRate,
_lastSeenCacheSize,
_threadFactory);
_threadFactory,
_flagSetsFilter,
_invalidSetsCount);
}
}
}
Loading

0 comments on commit 09cd178

Please sign in to comment.