Skip to content

Commit 09cd178

Browse files
authored
Merge pull request #454 from splitio/SDKS-7439
[SDKS-7439] Flag sets
2 parents fb66059 + 42be0f6 commit 09cd178

File tree

90 files changed

+2851
-918
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+2851
-918
lines changed

CHANGES.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
4.10.0 (Nov 2, 2023)
2+
- 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):
3+
- Added new variations of the get treatment methods to support evaluating flags in given flag set/s.
4+
- getTreatmentsByFlagSet and getTreatmentsByFlagSets
5+
- getTreatmentWithConfigByFlagSets and getTreatmentsWithConfigByFlagSets
6+
- 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.
7+
- 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.
8+
- Updated the following SDK manager methods to expose flag sets on flag views.
9+
- Added `defaultTreatment` property to the `SplitView` object returned by the `split` and `splits` methods of the SDK manager.
10+
- Added new `threadFactory` property in SDK config. It allows to use of Virtual Threading.
11+
112
4.9.0 (Sep 8, 2023)
213
- Added InputStream config for localhost mode providing a solution when the file is inside a jar.
314
- Fixed track impressions to send all impressions to the listener.

client/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>io.split.client</groupId>
77
<artifactId>java-client-parent</artifactId>
8-
<version>4.9.0</version>
8+
<version>4.10.0</version>
99
</parent>
1010
<artifactId>java-client</artifactId>
1111
<packaging>jar</packaging>
@@ -149,7 +149,7 @@
149149
<dependency>
150150
<groupId>io.split.client</groupId>
151151
<artifactId>pluggable-storage</artifactId>
152-
<version>2.0.0</version>
152+
<version>2.1.0</version>
153153
<scope>compile</scope>
154154
</dependency>
155155
<dependency>

client/src/main/java/io/split/client/CacheUpdaterService.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import io.split.storages.SplitCacheProducer;
1616

1717
import java.util.ArrayList;
18+
import java.util.HashSet;
1819
import java.util.List;
1920
import java.util.Map;
2021
import java.util.Optional;
@@ -51,7 +52,7 @@ public void updateCache(Map<SplitAndKey, LocalhostSplit> map) {
5152
String treatment = conditions.size() > 0 ? Treatments.CONTROL : localhostSplit.treatment;
5253
configurations.put(localhostSplit.treatment, localhostSplit.config);
5354

54-
split = new ParsedSplit(splitName, 0, false, treatment,conditions, LOCALHOST, 0, 100, 0, 0, configurations);
55+
split = new ParsedSplit(splitName, 0, false, treatment,conditions, LOCALHOST, 0, 100, 0, 0, configurations, new HashSet<>());
5556
parsedSplits.removeIf(parsedSplit -> parsedSplit.feature().equals(splitName));
5657
parsedSplits.add(split);
5758
}

client/src/main/java/io/split/client/HttpSplitChangeFetcher.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.google.common.annotations.VisibleForTesting;
44
import io.split.client.dtos.SplitChange;
5+
import io.split.client.exceptions.UriTooLongException;
56
import io.split.client.utils.Json;
67
import io.split.client.utils.Utils;
78
import io.split.engine.common.FetchOptions;
@@ -35,6 +36,7 @@ public final class HttpSplitChangeFetcher implements SplitChangeFetcher {
3536

3637
private static final String SINCE = "since";
3738
private static final String TILL = "till";
39+
private static final String SETS = "sets";
3840

3941
private static final String HEADER_CACHE_CONTROL_NAME = "Cache-Control";
4042
private static final String HEADER_CACHE_CONTROL_VALUE = "no-cache";
@@ -75,6 +77,9 @@ public SplitChange fetch(long since, FetchOptions options) {
7577
if (options.hasCustomCN()) {
7678
uriBuilder.addParameter(TILL, "" + options.targetCN());
7779
}
80+
if (!options.flagSetsFilter().isEmpty()) {
81+
uriBuilder.addParameter(SETS, "" + options.flagSetsFilter());
82+
}
7883
URI uri = uriBuilder.build();
7984

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

99104
if (statusCode < HttpStatus.SC_OK || statusCode >= HttpStatus.SC_MULTIPLE_CHOICES) {
100105
_telemetryRuntimeProducer.recordSyncError(ResourceEnum.SPLIT_SYNC, statusCode);
106+
if (statusCode == HttpStatus.SC_REQUEST_URI_TOO_LONG) {
107+
_log.error("The amount of flag sets provided are big causing uri length error.");
108+
throw new UriTooLongException(String.format("Status code: %s. Message: %s", statusCode, response.getReasonPhrase()));
109+
}
101110
_log.warn(String.format("Response status was: %s. Reason: %s", statusCode , response.getReasonPhrase()));
102111
throw new IllegalStateException(String.format("Could not retrieve splitChanges since %s; http return code %s", since, statusCode));
103112
}

client/src/main/java/io/split/client/SplitClient.java

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,138 @@ public interface SplitClient {
268268
*/
269269
Map<String, SplitResult> getTreatmentsWithConfig(Key key, List<String> featureFlagNames, Map<String, Object> attributes);
270270

271+
/**
272+
* Same as {@link #getTreatments(String, List<String>, Map)} but it returns for each feature flag the configuration associated to the
273+
* matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
274+
* <p/>
275+
* <p/>
276+
* Examples include showing a different treatment to users on trial plan
277+
* vs. premium plan. Another example is to show a different treatment
278+
* to users created after a certain date.
279+
*
280+
* @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty.
281+
* @param flagSet the Flag Set name that you want to evaluate. MUST not be null or empty.
282+
* @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
283+
* @return for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control'.
284+
*/
285+
Map<String, String> getTreatmentsByFlagSet(String key, String flagSet, Map<String, Object> attributes);
286+
287+
/**
288+
* Same as {@link #getTreatments(String, List<String>, Map)} but it returns for each feature flag the configuration associated to the
289+
* matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
290+
* <p/>
291+
* <p/>
292+
* Examples include showing a different treatment to users on trial plan
293+
* vs. premium plan. Another example is to show a different treatment
294+
* to users created after a certain date.
295+
*
296+
* @param key the matching and bucketing keys. MUST not be null or empty.
297+
* @param flagSet the Flag Set name that you want to evaluate. MUST not be null or empty.
298+
* @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
299+
* @return for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control'.
300+
*/
301+
Map<String, String> getTreatmentsByFlagSet(Key key, String flagSet, Map<String, Object> attributes);
302+
303+
/**
304+
* Same as {@link #getTreatments(String, List<String>, Map)} but it returns for each feature flag the configuration associated to the
305+
* matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
306+
* <p/>
307+
* <p/>
308+
* Examples include showing a different treatment to users on trial plan
309+
* vs. premium plan. Another example is to show a different treatment
310+
* to users created after a certain date.
311+
*
312+
* @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty.
313+
* @param flagSets the names of Flag Sets that you want to evaluate. MUST not be null or empty.
314+
* @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
315+
* @return for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control'.
316+
*/
317+
Map<String, String> getTreatmentsByFlagSets(String key, List<String> flagSets, Map<String, Object> attributes);
318+
319+
/**
320+
* Same as {@link #getTreatments(String, List<String>, Map)} but it returns for each feature flag the configuration associated to the
321+
* matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
322+
* <p/>
323+
* <p/>
324+
* Examples include showing a different treatment to users on trial plan
325+
* vs. premium plan. Another example is to show a different treatment
326+
* to users created after a certain date.
327+
*
328+
* @param key the matching and bucketing keys. MUST not be null or empty.
329+
* @param flagSets the names of Flag Sets that you want to evaluate. MUST not be null or empty.
330+
* @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
331+
* @return for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control'.
332+
*/
333+
Map<String, String> getTreatmentsByFlagSets(Key key, List<String> flagSets, Map<String, Object> attributes);
334+
335+
/**
336+
* Same as {@link #getTreatments(String, List<String>, Map)} but it returns for each feature flag the configuration associated to the
337+
* matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
338+
* <p/>
339+
* <p/>
340+
* Examples include showing a different treatment to users on trial plan
341+
* vs. premium plan. Another example is to show a different treatment
342+
* to users created after a certain date.
343+
*
344+
* @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty.
345+
* @param flagSet the Flag Set name that you want to evaluate. MUST not be null or empty.
346+
* @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
347+
* @return for each feature flag the evaluated treatment (the default treatment of this feature flag, or 'control') and a configuration
348+
* associated to this treatment if set.
349+
*/
350+
Map<String, SplitResult> getTreatmentsWithConfigByFlagSet(String key, String flagSet, Map<String, Object> attributes);
351+
352+
/**
353+
* Same as {@link #getTreatments(String, List<String>, Map)} but it returns for each feature flag the configuration associated to the
354+
* matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
355+
* <p/>
356+
* <p/>
357+
* Examples include showing a different treatment to users on trial plan
358+
* vs. premium plan. Another example is to show a different treatment
359+
* to users created after a certain date.
360+
*
361+
* @param key the matching and bucketing keys. MUST not be null or empty.
362+
* @param flagSet the Flag Set name that you want to evaluate. MUST not be null or empty.
363+
* @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
364+
* @return for each feature flag the evaluated treatment (the default treatment of this feature flag, or 'control') and a configuration
365+
* associated to this treatment if set.
366+
*/
367+
Map<String, SplitResult> getTreatmentsWithConfigByFlagSet(Key key, String flagSet, Map<String, Object> attributes);
368+
369+
/**
370+
* Same as {@link #getTreatments(String, List<String>, Map)} but it returns for each feature flag the configuration associated to the
371+
* matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
372+
* <p/>
373+
* <p/>
374+
* Examples include showing a different treatment to users on trial plan
375+
* vs. premium plan. Another example is to show a different treatment
376+
* to users created after a certain date.
377+
*
378+
* @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty.
379+
* @param flagSets the names of Flag Sets that you want to evaluate. MUST not be null or empty.
380+
* @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
381+
* @return for each feature flag the evaluated treatment (the default treatment of this feature flag, or 'control') and a configuration
382+
* associated to this treatment if set.
383+
*/
384+
Map<String, SplitResult> getTreatmentsWithConfigByFlagSets(String key, List<String> flagSets, Map<String, Object> attributes);
385+
386+
/**
387+
* Same as {@link #getTreatments(String, List<String>, Map)} but it returns for each feature flag the configuration associated to the
388+
* matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
389+
* <p/>
390+
* <p/>
391+
* Examples include showing a different treatment to users on trial plan
392+
* vs. premium plan. Another example is to show a different treatment
393+
* to users created after a certain date.
394+
*
395+
* @param key the matching and bucketing keys. MUST not be null or empty.
396+
* @param flagSets the names of Flag Sets that you want to evaluate. MUST not be null or empty.
397+
* @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
398+
* @return for each feature flag the evaluated treatment (the default treatment of this feature flag, or 'control') and a configuration
399+
* associated to this treatment if set.
400+
*/
401+
Map<String, SplitResult> getTreatmentsWithConfigByFlagSets(Key key, List<String> flagSets, Map<String, Object> attributes);
402+
271403
/**
272404
* Destroys the background processes and clears the cache, releasing the resources used by
273405
* the any instances of SplitClient or SplitManager generated by the client's parent SplitFactory

client/src/main/java/io/split/client/SplitClientConfig.java

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,15 @@
1010
import pluggable.CustomStorageWrapper;
1111

1212
import java.io.IOException;
13+
import java.util.HashSet;
14+
import java.util.LinkedHashSet;
15+
import java.util.List;
1316
import java.io.InputStream;
1417
import java.util.Properties;
1518
import java.util.concurrent.ThreadFactory;
1619

20+
import static io.split.inputValidation.FlagSetsValidator.cleanup;
21+
1722
/**
1823
* Configurations for the SplitClient.
1924
*
@@ -84,6 +89,8 @@ public class SplitClientConfig {
8489
// To be set during startup
8590
public static String splitSdkVersion;
8691
private final long _lastSeenCacheSize;
92+
private final HashSet<String> _flagSetsFilter;
93+
private final int _invalidSets;
8794

8895
public static Builder builder() {
8996
return new Builder();
@@ -138,7 +145,9 @@ private SplitClientConfig(String endpoint,
138145
int uniqueKeysRefreshRateRedis,
139146
int filterUniqueKeysRefreshRate,
140147
long lastSeenCacheSize,
141-
ThreadFactory threadFactory) {
148+
ThreadFactory threadFactory,
149+
HashSet<String> flagSetsFilter,
150+
int invalidSets) {
142151
_endpoint = endpoint;
143152
_eventsEndpoint = eventsEndpoint;
144153
_featuresRefreshRate = pollForFeatureChangesEveryNSeconds;
@@ -189,7 +198,8 @@ private SplitClientConfig(String endpoint,
189198
_customStorageWrapper = customStorageWrapper;
190199
_lastSeenCacheSize = lastSeenCacheSize;
191200
_threadFactory = threadFactory;
192-
201+
_flagSetsFilter = flagSetsFilter;
202+
_invalidSets = invalidSets;
193203

194204
Properties props = new Properties();
195205
try {
@@ -375,10 +385,19 @@ public CustomStorageWrapper customStorageWrapper() {
375385
public long getLastSeenCacheSize() {
376386
return _lastSeenCacheSize;
377387
}
388+
378389
public ThreadFactory getThreadFactory() {
379390
return _threadFactory;
380391
}
381392

393+
public HashSet<String> getSetsFilter() {
394+
return _flagSetsFilter;
395+
}
396+
397+
public int getInvalidSets() {
398+
return _invalidSets;
399+
}
400+
382401
public static final class Builder {
383402

384403
private String _endpoint = SDK_ENDPOINT;
@@ -434,6 +453,8 @@ public static final class Builder {
434453
private StorageMode _storageMode = StorageMode.MEMORY;
435454
private final long _lastSeenCacheSize = 500000;
436455
private ThreadFactory _threadFactory;
456+
private HashSet<String> _flagSetsFilter = new HashSet<>();
457+
private int _invalidSetsCount = 0;
437458

438459
public Builder() {
439460
}
@@ -905,6 +926,18 @@ public Builder customStorageWrapper(CustomStorageWrapper customStorageWrapper) {
905926
return this;
906927
}
907928

929+
/**
930+
* Flag Sets Filter
931+
*
932+
* @param flagSetsFilter
933+
* @return this builder
934+
*/
935+
public Builder flagSetsFilter(List<String> flagSetsFilter) {
936+
_flagSetsFilter = new LinkedHashSet<>(cleanup(flagSetsFilter));
937+
_invalidSetsCount = flagSetsFilter.size() - _flagSetsFilter.size();
938+
return this;
939+
}
940+
908941
/**
909942
* Thread Factory
910943
*
@@ -1063,7 +1096,9 @@ public SplitClientConfig build() {
10631096
_uniqueKeysRefreshRateRedis,
10641097
_filterUniqueKeysRefreshRate,
10651098
_lastSeenCacheSize,
1066-
_threadFactory);
1099+
_threadFactory,
1100+
_flagSetsFilter,
1101+
_invalidSetsCount);
10671102
}
10681103
}
10691104
}

0 commit comments

Comments
 (0)