diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fa2869b..a2d5fff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,18 +41,6 @@ jobs: - name: Run Android test run: cd splitio/example/android/; ./gradlew :splitio:testReleaseUnitTest; - build-ios: - name: Build iOS - runs-on: [ macos-latest ] - - steps: - - uses: actions/checkout@v3.5.2 - - uses: subosito/flutter-action@v2.10.0 - with: - channel: 'stable' - - name: Run Build iOS - run: cd splitio/example/ios; flutter build ios --no-codesign; - test-ios: name: Test iOS runs-on: [ macos-latest ] diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a503a2a..b8eb93c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,3 +21,7 @@ jobs: run: cd splitio/; flutter pub get - name: Run flutter test run: cd splitio/; flutter test + - name: Run flutter splitio_android test + run: cd splitio_android/; flutter test + - name: Run flutter splitio_ios test + run: cd splitio_ios/; flutter test diff --git a/splitio/CHANGELOG.md b/splitio/CHANGELOG.md index 9d0be90..65f5688 100644 --- a/splitio/CHANGELOG.md +++ b/splitio/CHANGELOG.md @@ -1,3 +1,15 @@ +# 0.1.7 (Nov 9, 2023) +* Added support for Flag Sets, 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 Split 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. + * Added `defaultTreatment` property to the `SplitView` object returned by the `split` and `splits` methods of the SDK manager. +* Updated iOS SDK to `2.23.0` +* Updated Android SDK to `3.4.0` + +# 0.1.7-rc.1 (Nov 9, 2023) + # 0.1.6 (Aug 15, 2023) * Added `readyTimeout` configuration option. If the SDK is not ready after the amount of time (in seconds) specified by this option, the `whenTimeout` future of the client will be completed. Defaults to 10 seconds. A negative value means no timeout. diff --git a/splitio/example/pubspec.lock b/splitio/example/pubspec.lock index cad1492..bf2b499 100644 --- a/splitio/example/pubspec.lock +++ b/splitio/example/pubspec.lock @@ -150,28 +150,28 @@ packages: path: ".." relative: true source: path - version: "0.1.6-rc.1" + version: "0.1.6" splitio_android: dependency: transitive description: path: "../../splitio_android" relative: true source: path - version: "0.1.6-rc.1" + version: "0.1.6" splitio_ios: dependency: transitive description: path: "../../splitio_ios" relative: true source: path - version: "0.1.6-rc.1" + version: "0.1.6" splitio_platform_interface: dependency: transitive description: path: "../../splitio_platform_interface" relative: true source: path - version: "1.3.0-rc.1" + version: "1.3.0" stack_trace: dependency: transitive description: diff --git a/splitio/lib/split_client.dart b/splitio/lib/split_client.dart index 7252206..cb9b27f 100644 --- a/splitio/lib/split_client.dart +++ b/splitio/lib/split_client.dart @@ -49,6 +49,46 @@ abstract class SplitClient { Future> getTreatments(List featureFlagNames, [Map attributes = const {}]); + /// Convenience method to perform multiple evaluations by flag set. Returns a [Map] in + /// which the keys are feature flag names and the values are treatments. + /// + /// A flag set needs to be specified in [flagSet]. + /// + /// Optionally, a [Map] can be specified with the [attributes] parameter to + /// take into account when evaluating. + Future> getTreatmentsByFlagSet(String flagSet, + [Map attributes = const {}]); + + /// Convenience method to perform multiple evaluations by flag sets. Returns a [Map] in + /// which the keys are feature flag names and the values are treatments. + /// + /// A list of flag sets needs to be specified in [flagSets]. + /// + /// Optionally, a [Map] can be specified with the [attributes] parameter to + /// take into account when evaluating. + Future> getTreatmentsByFlagSets(List flagSets, + [Map attributes = const {}]); + + /// Convenience method to perform multiple evaluations by flag set. Returns a [Map] in + /// which the keys are feature flag names and the values are [SplitResult] objects. + /// + /// A flag set needs to be specified in [flagSet]. + /// + /// Optionally, a [Map] can be specified with the [attributes] parameter to + /// take into account when evaluating. + Future> getTreatmentsWithConfigByFlagSet(String flagSet, + [Map attributes = const {}]); + + /// Convenience method to perform multiple evaluations by flag sets. Returns a [Map] in + /// which the keys are feature flag names and the values are [SplitResult] objects. + /// + /// A list of flag sets needs to be specified in [flagSets]. + /// + /// Optionally, a [Map] can be specified with the [attributes] parameter to + /// take into account when evaluating. + Future> getTreatmentsWithConfigByFlagSets(List flagSets, + [Map attributes = const {}]); + /// Convenience method to perform multiple evaluations. Returns a [Map] in /// which the keys are feature flag names and the values are [SplitResult] objects. /// @@ -185,6 +225,42 @@ class DefaultSplitClient implements SplitClient { attributes: attributes); } + @override + Future> getTreatmentsByFlagSet(String flagSet, [Map attributes = const {}]) { + return _platform.getTreatmentsByFlagSet( + matchingKey: _matchingKey, + bucketingKey: _bucketingKey, + flagSet: flagSet, + attributes: attributes); + } + + @override + Future> getTreatmentsByFlagSets(List flagSets, [Map attributes = const {}]) { + return _platform.getTreatmentsByFlagSets( + matchingKey: _matchingKey, + bucketingKey: _bucketingKey, + flagSets: flagSets, + attributes: attributes); + } + + @override + Future> getTreatmentsWithConfigByFlagSet(String flagSet, [Map attributes = const {}]) { + return _platform.getTreatmentsWithConfigByFlagSet( + matchingKey: _matchingKey, + bucketingKey: _bucketingKey, + flagSet: flagSet, + attributes: attributes); + } + + @override + Future> getTreatmentsWithConfigByFlagSets(List flagSets, [Map attributes = const {}]) { + return _platform.getTreatmentsWithConfigByFlagSets( + matchingKey: _matchingKey, + bucketingKey: _bucketingKey, + flagSets: flagSets, + attributes: attributes); + } + @override Future track(String eventType, {String? trafficType, diff --git a/splitio/pubspec.yaml b/splitio/pubspec.yaml index 8b566fe..8f2c101 100644 --- a/splitio/pubspec.yaml +++ b/splitio/pubspec.yaml @@ -1,6 +1,6 @@ name: splitio description: Official plugin for split.io, the platform for controlled rollouts, which serves features to your users via feature flags to manage your complete customer experience. -version: 0.1.6 +version: 0.1.7 homepage: https://split.io/ repository: https://github.com/splitio/flutter-sdk-plugin/tree/main/splitio/ @@ -19,9 +19,9 @@ flutter: dependencies: flutter: sdk: flutter - splitio_android: ^0.1.6 - splitio_ios: ^0.1.6 - splitio_platform_interface: ^1.3.0 + splitio_android: ^0.1.7 + splitio_ios: ^0.1.7 + splitio_platform_interface: ^1.4.0 dev_dependencies: flutter_test: diff --git a/splitio/test/splitio_client_test.dart b/splitio/test/splitio_client_test.dart index 25e638f..de984a8 100644 --- a/splitio/test/splitio_client_test.dart +++ b/splitio/test/splitio_client_test.dart @@ -133,6 +133,118 @@ void main() { 'attributes': {'attr1': true} }); }); + + test('getTreatmentsByFlagSet without attributes', () async { + SplitClient client = _getClient(); + + client.getTreatmentsByFlagSet('set_1'); + + expect(_platform.methodName, 'getTreatmentsByFlagSet'); + expect(_platform.methodArguments, { + 'flagSet': 'set_1', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {} + }); + }); + + test('getTreatmentsByFlagSet with attributes', () async { + SplitClient client = _getClient(); + + client.getTreatmentsByFlagSet('set_1', {'attr1': true}); + + expect(_platform.methodName, 'getTreatmentsByFlagSet'); + expect(_platform.methodArguments, { + 'flagSet': 'set_1', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {'attr1': true} + }); + }); + + test('getTreatmentsByFlagSets without attributes', () async { + SplitClient client = _getClient(); + + client.getTreatmentsByFlagSets(['set_1', 'set_2']); + + expect(_platform.methodName, 'getTreatmentsByFlagSets'); + expect(_platform.methodArguments, { + 'flagSets': ['set_1', 'set_2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {} + }); + }); + + test('getTreatmentsByFlagSets with attributes', () async { + SplitClient client = _getClient(); + + client.getTreatmentsByFlagSets(['set_1', 'set_2'], {'attr1': true}); + + expect(_platform.methodName, 'getTreatmentsByFlagSets'); + expect(_platform.methodArguments, { + 'flagSets': ['set_1', 'set_2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {'attr1': true} + }); + }); + + test('getTreatmentsWithConfigByFlagSet without attributes', () async { + SplitClient client = _getClient(); + + client.getTreatmentsWithConfigByFlagSet('set_1'); + + expect(_platform.methodName, 'getTreatmentsWithConfigByFlagSet'); + expect(_platform.methodArguments, { + 'flagSet': 'set_1', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {} + }); + }); + + test('getTreatmentsWithConfigByFlagSet with attributes', () async { + SplitClient client = _getClient(); + + client.getTreatmentsWithConfigByFlagSet('set_1', {'attr1': true}); + + expect(_platform.methodName, 'getTreatmentsWithConfigByFlagSet'); + expect(_platform.methodArguments, { + 'flagSet': 'set_1', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {'attr1': true} + }); + }); + + test('getTreatmentsWithConfigByFlagSets without attributes', () async { + SplitClient client = _getClient(); + + client.getTreatmentsWithConfigByFlagSets(['set_1', 'set_2']); + + expect(_platform.methodName, 'getTreatmentsWithConfigByFlagSets'); + expect(_platform.methodArguments, { + 'flagSets': ['set_1', 'set_2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {} + }); + }); + + test('getTreatmentsWithConfigByFlagSets with attributes', () async { + SplitClient client = _getClient(); + + client.getTreatmentsWithConfigByFlagSets(['set_1', 'set_2'], {'attr1': true}); + + expect(_platform.methodName, 'getTreatmentsWithConfigByFlagSets'); + expect(_platform.methodArguments, { + 'flagSets': ['set_1', 'set_2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {'attr1': true} + }); + }); }); group('track', () { diff --git a/splitio/test/splitio_platform_stub.dart b/splitio/test/splitio_platform_stub.dart index 369cc83..a996c97 100644 --- a/splitio/test/splitio_platform_stub.dart +++ b/splitio/test/splitio_platform_stub.dart @@ -156,6 +156,78 @@ class SplitioPlatformStub return Future.value({}); } + @override + Future> getTreatmentsByFlagSet( + {required String matchingKey, + required String? bucketingKey, + required String flagSet, + Map attributes = const {}}) { + methodName = 'getTreatmentsByFlagSet'; + + methodArguments = { + 'matchingKey': matchingKey, + 'bucketingKey': bucketingKey, + 'flagSet': flagSet, + 'attributes': attributes, + }; + + return Future.value({}); + } + + @override + Future> getTreatmentsByFlagSets( + {required String matchingKey, + required String? bucketingKey, + required List flagSets, + Map attributes = const {}}) { + methodName = 'getTreatmentsByFlagSets'; + + methodArguments = { + 'matchingKey': matchingKey, + 'bucketingKey': bucketingKey, + 'flagSets': flagSets, + 'attributes': attributes, + }; + + return Future.value({}); + } + + @override + Future> getTreatmentsWithConfigByFlagSet( + {required String matchingKey, + required String? bucketingKey, + required String flagSet, + Map attributes = const {}}) { + methodName = 'getTreatmentsWithConfigByFlagSet'; + + methodArguments = { + 'matchingKey': matchingKey, + 'bucketingKey': bucketingKey, + 'flagSet': flagSet, + 'attributes': attributes, + }; + + return Future.value({}); + } + + @override + Future> getTreatmentsWithConfigByFlagSets( + {required String matchingKey, + required String? bucketingKey, + required List flagSets, + Map attributes = const {}}) { + methodName = 'getTreatmentsWithConfigByFlagSets'; + + methodArguments = { + 'matchingKey': matchingKey, + 'bucketingKey': bucketingKey, + 'flagSets': flagSets, + 'attributes': attributes, + }; + + return Future.value({}); + } + @override Stream impressionsStream() { methodName = 'impressionsStream'; diff --git a/splitio_android/CHANGELOG.md b/splitio_android/CHANGELOG.md index ce894f5..f1df5f4 100644 --- a/splitio_android/CHANGELOG.md +++ b/splitio_android/CHANGELOG.md @@ -1,3 +1,9 @@ +# 0.1.7 (Nov 9, 2023) + +* Updated Android SDK to `3.4.0` + +# 0.1.7-rc.1 (Nov 9, 2023) + # 0.1.6 (Aug 15, 2023) * Added `readyTimeout` configuration option. diff --git a/splitio_android/android/build.gradle b/splitio_android/android/build.gradle index 7a017ac..a2871bd 100644 --- a/splitio_android/android/build.gradle +++ b/splitio_android/android/build.gradle @@ -22,7 +22,7 @@ rootProject.allprojects { apply plugin: 'com.android.library' android { - compileSdkVersion 31 + compileSdk 31 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -30,13 +30,13 @@ android { } defaultConfig { - minSdkVersion 16 + minSdk 16 consumerProguardFiles 'split-proguard-rules.pro' } dependencies { - implementation 'io.split.client:android-client:3.3.0' + implementation 'io.split.client:android-client:3.4.0' implementation 'com.squareup.okhttp3:okhttp:3.12.13' testImplementation 'junit:junit:4.13.2' diff --git a/splitio_android/android/src/main/java/io/split/splitio/Constants.java b/splitio_android/android/src/main/java/io/split/splitio/Constants.java index 35ceb63..4537843 100644 --- a/splitio_android/android/src/main/java/io/split/splitio/Constants.java +++ b/splitio_android/android/src/main/java/io/split/splitio/Constants.java @@ -15,6 +15,10 @@ static class Method { static final String GET_TREATMENTS = "getTreatments"; static final String GET_TREATMENT_WITH_CONFIG = "getTreatmentWithConfig"; static final String GET_TREATMENTS_WITH_CONFIG = "getTreatmentsWithConfig"; + static final String GET_TREATMENTS_BY_FLAG_SET = "getTreatmentsByFlagSet"; + static final String GET_TREATMENTS_BY_FLAG_SETS = "getTreatmentsByFlagSets"; + static final String GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET = "getTreatmentsWithConfigByFlagSet"; + static final String GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS = "getTreatmentsWithConfigByFlagSets"; static final String TRACK = "track"; static final String GET_ATTRIBUTE = "getAttribute"; static final String GET_ALL_ATTRIBUTES = "getAllAttributes"; @@ -42,6 +46,8 @@ static class Argument { static final String VALUE = "value"; static final String PROPERTIES = "properties"; static final String ATTRIBUTE_NAME = "attributeName"; + static final String FLAG_SET = "flagSet"; + static final String FLAG_SETS = "flagSets"; } static class Error { diff --git a/splitio_android/android/src/main/java/io/split/splitio/EvaluationWrapper.java b/splitio_android/android/src/main/java/io/split/splitio/EvaluationWrapper.java index c6dc7fa..62ed6d0 100644 --- a/splitio_android/android/src/main/java/io/split/splitio/EvaluationWrapper.java +++ b/splitio_android/android/src/main/java/io/split/splitio/EvaluationWrapper.java @@ -13,4 +13,12 @@ interface EvaluationWrapper { SplitResult getTreatmentWithConfig(String matchingKey, String bucketingKey, String splitName, Map attributes); Map getTreatmentsWithConfig(String matchingKey, String bucketingKey, List splitNames, Map attributes); + + Map getTreatmentsByFlagSet(String matchingKey, String bucketingKey, String flagSet, Map attributes); + + Map getTreatmentsByFlagSets(String matchingKey, String bucketingKey, List flagSets, Map attributes); + + Map getTreatmentsWithConfigByFlagSet(String matchingKey, String bucketingKey, String flagSet, Map attributes); + + Map getTreatmentsWithConfigByFlagSets(String matchingKey, String bucketingKey, List flagSets, Map attributes); } diff --git a/splitio_android/android/src/main/java/io/split/splitio/SplitClientConfigHelper.java b/splitio_android/android/src/main/java/io/split/splitio/SplitClientConfigHelper.java index 80f58cc..8aa0e78 100644 --- a/splitio_android/android/src/main/java/io/split/splitio/SplitClientConfigHelper.java +++ b/splitio_android/android/src/main/java/io/split/splitio/SplitClientConfigHelper.java @@ -39,6 +39,7 @@ class SplitClientConfigHelper { private static final String SYNC_CONFIG = "syncConfig"; private static final String SYNC_CONFIG_NAMES = "syncConfigNames"; private static final String SYNC_CONFIG_PREFIXES = "syncConfigPrefixes"; + private static final String SYNC_CONFIG_SETS = "syncConfigFlagSets"; private static final String IMPRESSIONS_MODE = "impressionsMode"; private static final String SYNC_ENABLED = "syncEnabled"; private static final String USER_CONSENT = "userConsent"; @@ -152,14 +153,19 @@ static SplitClientConfig fromMap(@NonNull Map configurationMap, if (syncConfig != null) { List names = syncConfig.get(SYNC_CONFIG_NAMES); List prefixes = syncConfig.get(SYNC_CONFIG_PREFIXES); + List flagSets = syncConfig.get(SYNC_CONFIG_SETS); SyncConfig.Builder syncConfigBuilder = SyncConfig.builder(); - if (names != null && !names.isEmpty()) { - syncConfigBuilder.addSplitFilter(SplitFilter.byName(names)); - } + if (flagSets != null && !flagSets.isEmpty()) { + syncConfigBuilder.addSplitFilter(SplitFilter.bySet(flagSets)); + } else { + if (names != null && !names.isEmpty()) { + syncConfigBuilder.addSplitFilter(SplitFilter.byName(names)); + } - if (prefixes != null && !prefixes.isEmpty()) { - syncConfigBuilder.addSplitFilter(SplitFilter.byPrefix(prefixes)); + if (prefixes != null && !prefixes.isEmpty()) { + syncConfigBuilder.addSplitFilter(SplitFilter.byPrefix(prefixes)); + } } builder.syncConfig(syncConfigBuilder.build()); diff --git a/splitio_android/android/src/main/java/io/split/splitio/SplitMethodParserImpl.java b/splitio_android/android/src/main/java/io/split/splitio/SplitMethodParserImpl.java index fb3ad5d..812143c 100644 --- a/splitio_android/android/src/main/java/io/split/splitio/SplitMethodParserImpl.java +++ b/splitio_android/android/src/main/java/io/split/splitio/SplitMethodParserImpl.java @@ -5,6 +5,8 @@ import static io.split.splitio.Constants.Argument.ATTRIBUTE_NAME; import static io.split.splitio.Constants.Argument.BUCKETING_KEY; import static io.split.splitio.Constants.Argument.EVENT_TYPE; +import static io.split.splitio.Constants.Argument.FLAG_SET; +import static io.split.splitio.Constants.Argument.FLAG_SETS; import static io.split.splitio.Constants.Argument.MATCHING_KEY; import static io.split.splitio.Constants.Argument.PROPERTIES; import static io.split.splitio.Constants.Argument.SDK_CONFIGURATION; @@ -25,7 +27,11 @@ import static io.split.splitio.Constants.Method.GET_ATTRIBUTE; import static io.split.splitio.Constants.Method.GET_TREATMENT; import static io.split.splitio.Constants.Method.GET_TREATMENTS; +import static io.split.splitio.Constants.Method.GET_TREATMENTS_BY_FLAG_SET; +import static io.split.splitio.Constants.Method.GET_TREATMENTS_BY_FLAG_SETS; import static io.split.splitio.Constants.Method.GET_TREATMENTS_WITH_CONFIG; +import static io.split.splitio.Constants.Method.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET; +import static io.split.splitio.Constants.Method.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS; import static io.split.splitio.Constants.Method.GET_TREATMENT_WITH_CONFIG; import static io.split.splitio.Constants.Method.GET_USER_CONSENT; import static io.split.splitio.Constants.Method.INIT; @@ -132,6 +138,34 @@ public void onMethodCall(String methodName, Object arguments, @NonNull MethodCha mArgumentParser.getStringListArgument(SPLIT_NAME, arguments), mArgumentParser.getMapArgument(ATTRIBUTES, arguments))); break; + case GET_TREATMENTS_BY_FLAG_SET: + result.success(getTreatmentsByFlagSet( + mArgumentParser.getStringArgument(MATCHING_KEY, arguments), + mArgumentParser.getStringArgument(BUCKETING_KEY, arguments), + mArgumentParser.getStringArgument(FLAG_SET, arguments), + mArgumentParser.getMapArgument(ATTRIBUTES, arguments))); + break; + case GET_TREATMENTS_BY_FLAG_SETS: + result.success(getTreatmentsByFlagSets( + mArgumentParser.getStringArgument(MATCHING_KEY, arguments), + mArgumentParser.getStringArgument(BUCKETING_KEY, arguments), + mArgumentParser.getStringListArgument(FLAG_SETS, arguments), + mArgumentParser.getMapArgument(ATTRIBUTES, arguments))); + break; + case GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET: + result.success(getTreatmentsWithConfigByFlagSet( + mArgumentParser.getStringArgument(MATCHING_KEY, arguments), + mArgumentParser.getStringArgument(BUCKETING_KEY, arguments), + mArgumentParser.getStringArgument(FLAG_SET, arguments), + mArgumentParser.getMapArgument(ATTRIBUTES, arguments))); + break; + case GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS: + result.success(getTreatmentsWithConfigByFlagSets( + mArgumentParser.getStringArgument(MATCHING_KEY, arguments), + mArgumentParser.getStringArgument(BUCKETING_KEY, arguments), + mArgumentParser.getStringListArgument(FLAG_SETS, arguments), + mArgumentParser.getMapArgument(ATTRIBUTES, arguments))); + break; case TRACK: result.success(track( mArgumentParser.getStringArgument(MATCHING_KEY, arguments), @@ -270,13 +304,41 @@ private Map> getTreatmentsWithConfig( List splitNames, Map attributes) { Map treatmentsWithConfig = mSplitWrapper.getTreatmentsWithConfig(matchingKey, bucketingKey, splitNames, attributes); - Map> resultMap = new HashMap<>(); + return mapToSplitResults(treatmentsWithConfig); + } - for (Map.Entry entry : treatmentsWithConfig.entrySet()) { - resultMap.put(entry.getKey(), getSplitResultMap(entry.getValue())); - } + private Map getTreatmentsByFlagSet( + String matchingKey, + String bucketingKey, + String flagSet, + Map attributes) { + return mSplitWrapper.getTreatmentsByFlagSet(matchingKey, bucketingKey, flagSet, attributes); + } - return resultMap; + private Map getTreatmentsByFlagSets( + String matchingKey, + String bucketingKey, + List flagSets, + Map attributes) { + return mSplitWrapper.getTreatmentsByFlagSets(matchingKey, bucketingKey, flagSets, attributes); + } + + private Map> getTreatmentsWithConfigByFlagSet( + String matchingKey, + String bucketingKey, + String flagSet, + Map attributes) { + Map treatmentsWithConfig = mSplitWrapper.getTreatmentsWithConfigByFlagSet(matchingKey, bucketingKey, flagSet, attributes); + return mapToSplitResults(treatmentsWithConfig); + } + + private Map> getTreatmentsWithConfigByFlagSets( + String matchingKey, + String bucketingKey, + List flagSets, + Map attributes) { + Map treatmentsWithConfig = mSplitWrapper.getTreatmentsWithConfigByFlagSets(matchingKey, bucketingKey, flagSets, attributes); + return mapToSplitResults(treatmentsWithConfig); } private boolean track(String matchingKey, @@ -356,6 +418,21 @@ private static void invokeCallback(MethodChannel methodChannel, String matchingK methodChannel.invokeMethod(methodName, arguments); } + @NonNull + private static Map> mapToSplitResults(Map treatmentsWithConfig) { + Map> resultMap = new HashMap<>(); + + if (treatmentsWithConfig == null) { + return resultMap; + } + + for (Map.Entry entry : treatmentsWithConfig.entrySet()) { + resultMap.put(entry.getKey(), getSplitResultMap(entry.getValue())); + } + + return resultMap; + } + private static Map getSplitResultMap(SplitResult splitResult) { if (splitResult == null) { return new HashMap<>(); @@ -395,6 +472,8 @@ private static Map getSplitViewAsMap(@Nullable SplitView splitVi splitViewMap.put("treatments", splitView.treatments); splitViewMap.put("changeNumber", splitView.changeNumber); splitViewMap.put("configs", splitView.configs); + splitViewMap.put("defaultTreatment", splitView.defaultTreatment); + splitViewMap.put("sets", splitView.sets); return splitViewMap; } diff --git a/splitio_android/android/src/main/java/io/split/splitio/SplitWrapperImpl.java b/splitio_android/android/src/main/java/io/split/splitio/SplitWrapperImpl.java index b445908..4e765ef 100644 --- a/splitio_android/android/src/main/java/io/split/splitio/SplitWrapperImpl.java +++ b/splitio_android/android/src/main/java/io/split/splitio/SplitWrapperImpl.java @@ -127,6 +127,46 @@ public Map getTreatmentsWithConfig(String matchingKey, Stri return client.getTreatmentsWithConfig(splitNames, attributes); } + @Override + public Map getTreatmentsByFlagSet(String matchingKey, String bucketingKey, String flagSet, Map attributes) { + SplitClient client = getInitializedClient(matchingKey, bucketingKey); + if (client == null) { + return new HashMap<>(); + } + + return client.getTreatmentsByFlagSet(flagSet, attributes); + } + + @Override + public Map getTreatmentsByFlagSets(String matchingKey, String bucketingKey, List flagSets, Map attributes) { + SplitClient client = getInitializedClient(matchingKey, bucketingKey); + if (client == null) { + return new HashMap<>(); + } + + return client.getTreatmentsByFlagSets(flagSets, attributes); + } + + @Override + public Map getTreatmentsWithConfigByFlagSet(String matchingKey, String bucketingKey, String flagSet, Map attributes) { + SplitClient client = getInitializedClient(matchingKey, bucketingKey); + if (client == null) { + return new HashMap<>(); + } + + return client.getTreatmentsWithConfigByFlagSet(flagSet, attributes); + } + + @Override + public Map getTreatmentsWithConfigByFlagSets(String matchingKey, String bucketingKey, List flagSets, Map attributes) { + SplitClient client = getInitializedClient(matchingKey, bucketingKey); + if (client == null) { + return new HashMap<>(); + } + + return client.getTreatmentsWithConfigByFlagSets(flagSets, attributes); + } + @Override public boolean setAttribute(String matchingKey, @Nullable String bucketingKey, String attributeName, Object value) { SplitClient client = getInitializedClient(matchingKey, bucketingKey); diff --git a/splitio_android/android/src/test/java/io/split/splitio/SplitClientConfigHelperTest.java b/splitio_android/android/src/test/java/io/split/splitio/SplitClientConfigHelperTest.java index aa9b7f4..6c16b8f 100644 --- a/splitio_android/android/src/test/java/io/split/splitio/SplitClientConfigHelperTest.java +++ b/splitio_android/android/src/test/java/io/split/splitio/SplitClientConfigHelperTest.java @@ -44,10 +44,6 @@ public void configValuesAreMappedCorrectly() { configValues.put("authServiceEndpoint", "sseAuthServiceEndpoint.split.io"); configValues.put("streamingServiceEndpoint", "streamingServiceEndpoint.split.io"); configValues.put("telemetryServiceEndpoint", "telemetryServiceEndpoint.split.io"); - Map> syncConfigMap = new HashMap<>(); - syncConfigMap.put("syncConfigNames", Arrays.asList("split1", "split2")); - syncConfigMap.put("syncConfigPrefixes", Arrays.asList("split_", "my_split_")); - configValues.put("syncConfig", syncConfigMap); configValues.put("impressionsMode", "none"); configValues.put("syncEnabled", false); configValues.put("userConsent", "declined"); @@ -73,10 +69,6 @@ public void configValuesAreMappedCorrectly() { assertEquals("sseAuthServiceEndpoint.split.io", splitClientConfig.authServiceUrl()); assertEquals("streamingServiceEndpoint.split.io", splitClientConfig.streamingServiceUrl()); assertEquals("telemetryServiceEndpoint.split.io", splitClientConfig.telemetryEndpoint()); - assertEquals(Arrays.asList("split1", "split2"), splitClientConfig.syncConfig().getFilters().get(0).getValues()); - assertEquals(SplitFilter.Type.BY_NAME, splitClientConfig.syncConfig().getFilters().get(0).getType()); - assertEquals(Arrays.asList("split_", "my_split_"), splitClientConfig.syncConfig().getFilters().get(1).getValues()); - assertEquals(SplitFilter.Type.BY_PREFIX, splitClientConfig.syncConfig().getFilters().get(1).getType()); assertEquals(ImpressionsMode.NONE, splitClientConfig.impressionsMode()); assertFalse(splitClientConfig.syncEnabled()); assertEquals(UserConsent.DECLINED, splitClientConfig.userConsent()); @@ -151,4 +143,39 @@ public void impressionsModeValuesAreMappedCorrectly() { assertEquals(ImpressionsMode.NONE, noneConfig.impressionsMode()); assertEquals(ImpressionsMode.OPTIMIZED, optimizedConfig.impressionsMode()); } + + @Test + public void syncConfigWithoutFlagSetsIsMappedCorrectly() { + Map configValues = new HashMap<>(); + Map> syncConfigMap = new HashMap<>(); + syncConfigMap.put("syncConfigNames", Arrays.asList("split1", "split2")); + syncConfigMap.put("syncConfigPrefixes", Arrays.asList("split_", "my_split_")); + configValues.put("syncConfig", syncConfigMap); + + SplitClientConfig splitClientConfig = SplitClientConfigHelper + .fromMap(configValues, mock(ImpressionListener.class)); + + assertEquals(2, splitClientConfig.syncConfig().getFilters().size()); + assertEquals(Arrays.asList("split1", "split2"), splitClientConfig.syncConfig().getFilters().get(0).getValues()); + assertEquals(SplitFilter.Type.BY_NAME, splitClientConfig.syncConfig().getFilters().get(0).getType()); + assertEquals(Arrays.asList("split_", "my_split_"), splitClientConfig.syncConfig().getFilters().get(1).getValues()); + assertEquals(SplitFilter.Type.BY_PREFIX, splitClientConfig.syncConfig().getFilters().get(1).getType()); + } + + @Test + public void syncConfigWithFlagSetsIsMappedCorrectly() { + Map configValues = new HashMap<>(); + Map> syncConfigMap = new HashMap<>(); + syncConfigMap.put("syncConfigNames", Arrays.asList("split1", "split2")); + syncConfigMap.put("syncConfigPrefixes", Arrays.asList("split_", "my_split_")); + syncConfigMap.put("syncConfigFlagSets", Arrays.asList("set_1", "set_2")); + configValues.put("syncConfig", syncConfigMap); + + SplitClientConfig splitClientConfig = SplitClientConfigHelper + .fromMap(configValues, mock(ImpressionListener.class)); + + assertEquals(1, splitClientConfig.syncConfig().getFilters().size()); + assertEquals(Arrays.asList("set_1", "set_2"), splitClientConfig.syncConfig().getFilters().get(0).getValues()); + assertEquals(SplitFilter.Type.BY_SET, splitClientConfig.syncConfig().getFilters().get(0).getType()); + } } diff --git a/splitio_android/android/src/test/java/io/split/splitio/SplitMethodParserImplTest.java b/splitio_android/android/src/test/java/io/split/splitio/SplitMethodParserImplTest.java index 538ea0e..7f7a225 100644 --- a/splitio_android/android/src/test/java/io/split/splitio/SplitMethodParserImplTest.java +++ b/splitio_android/android/src/test/java/io/split/splitio/SplitMethodParserImplTest.java @@ -504,4 +504,92 @@ public void setUserConsentDisabled() { verify(mResult).success(null); verify(mSplitWrapper).setUserConsent(false); } + + @Test + public void getTreatmentsByFlagSetWorksCorrectly() { + Map map = new HashMap<>(); + map.put("matchingKey", "user-key"); + map.put("bucketingKey", "bucketing-key"); + map.put("flagSet", "set_1"); + map.put("attributes", Collections.singletonMap("age", 10)); + + when(mArgumentParser.getStringArgument("matchingKey", map)).thenReturn("user-key"); + when(mArgumentParser.getStringArgument("bucketingKey", map)).thenReturn("bucketing-key"); + when(mArgumentParser.getStringArgument("flagSet", map)).thenReturn("set_1"); + when(mArgumentParser.getMapArgument("attributes", map)).thenReturn(Collections.singletonMap("age", 10)); + when(mSplitWrapper.getTreatmentsByFlagSet(any(), any(), any(), any())).thenReturn(Collections.singletonMap("flag_1", "on")); + + mMethodParser.onMethodCall("getTreatmentsByFlagSet", map, mResult); + + verify(mSplitWrapper).getTreatmentsByFlagSet("user-key", "bucketing-key", "set_1", Collections.singletonMap("age", 10)); + verify(mResult).success(Collections.singletonMap("flag_1", "on")); + } + + @Test + public void getTreatmentsByFlagSetsWorksCorrectly() { + Map map = new HashMap<>(); + map.put("matchingKey", "user-key"); + map.put("bucketingKey", "bucketing-key"); + map.put("flagSets", Arrays.asList("set_1", "set_2")); + map.put("attributes", Collections.singletonMap("age", 10)); + + when(mArgumentParser.getStringArgument("matchingKey", map)).thenReturn("user-key"); + when(mArgumentParser.getStringArgument("bucketingKey", map)).thenReturn("bucketing-key"); + when(mArgumentParser.getStringListArgument("flagSets", map)).thenReturn(Arrays.asList("set_1", "set_2")); + when(mArgumentParser.getMapArgument("attributes", map)).thenReturn(Collections.singletonMap("age", 10)); + when(mSplitWrapper.getTreatmentsByFlagSets(any(), any(), any(), any())).thenReturn(Collections.singletonMap("flag_1", "on")); + + mMethodParser.onMethodCall("getTreatmentsByFlagSets", map, mResult); + + verify(mSplitWrapper).getTreatmentsByFlagSets("user-key", "bucketing-key", Arrays.asList("set_1", "set_2"), Collections.singletonMap("age", 10)); + verify(mResult).success(Collections.singletonMap("flag_1", "on")); + } + + @Test + public void getTreatmentsWithConfigByFlagSetWorksCorrectly() { + Map map = new HashMap<>(); + map.put("matchingKey", "user-key"); + map.put("bucketingKey", "bucketing-key"); + map.put("flagSet", "set_1"); + map.put("attributes", Collections.singletonMap("age", 10)); + + Map resultMap1 = new HashMap<>(); + resultMap1.put("treatment", "on"); + resultMap1.put("config", "{config}"); + + when(mArgumentParser.getStringArgument("matchingKey", map)).thenReturn("user-key"); + when(mArgumentParser.getStringArgument("bucketingKey", map)).thenReturn("bucketing-key"); + when(mArgumentParser.getStringArgument("flagSet", map)).thenReturn("set_1"); + when(mArgumentParser.getMapArgument("attributes", map)).thenReturn(Collections.singletonMap("age", 10)); + when(mSplitWrapper.getTreatmentsWithConfigByFlagSet(any(), any(), any(), any())).thenReturn(Collections.singletonMap("flag_1", new SplitResult("on", "{config}"))); + + mMethodParser.onMethodCall("getTreatmentsWithConfigByFlagSet", map, mResult); + + verify(mSplitWrapper).getTreatmentsWithConfigByFlagSet("user-key", "bucketing-key", "set_1", Collections.singletonMap("age", 10)); + verify(mResult).success(Collections.singletonMap("flag_1", resultMap1)); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsWorksCorrectly() { + Map map = new HashMap<>(); + map.put("matchingKey", "user-key"); + map.put("bucketingKey", "bucketing-key"); + map.put("flagSets", Arrays.asList("set_1", "set_2")); + map.put("attributes", Collections.singletonMap("age", 10)); + + Map resultMap1 = new HashMap<>(); + resultMap1.put("treatment", "on"); + resultMap1.put("config", "{config}"); + + when(mArgumentParser.getStringArgument("matchingKey", map)).thenReturn("user-key"); + when(mArgumentParser.getStringArgument("bucketingKey", map)).thenReturn("bucketing-key"); + when(mArgumentParser.getStringListArgument("flagSets", map)).thenReturn(Arrays.asList("set_1", "set_2")); + when(mArgumentParser.getMapArgument("attributes", map)).thenReturn(Collections.singletonMap("age", 10)); + when(mSplitWrapper.getTreatmentsWithConfigByFlagSets(any(), any(), any(), any())).thenReturn(Collections.singletonMap("flag_1", new SplitResult("on", "{config}"))); + + mMethodParser.onMethodCall("getTreatmentsWithConfigByFlagSets", map, mResult); + + verify(mSplitWrapper).getTreatmentsWithConfigByFlagSets("user-key", "bucketing-key", Arrays.asList("set_1", "set_2"), Collections.singletonMap("age", 10)); + verify(mResult).success(Collections.singletonMap("flag_1", resultMap1)); + } } diff --git a/splitio_android/android/src/test/java/io/split/splitio/SplitWrapperImplTest.java b/splitio_android/android/src/test/java/io/split/splitio/SplitWrapperImplTest.java index 200f785..733fb31 100644 --- a/splitio_android/android/src/test/java/io/split/splitio/SplitWrapperImplTest.java +++ b/splitio_android/android/src/test/java/io/split/splitio/SplitWrapperImplTest.java @@ -15,6 +15,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -100,6 +101,56 @@ public void testGetTreatmentsWithConfig() { verify(clientMock).getTreatmentsWithConfig(Arrays.asList("split1", "split2"), Collections.singletonMap("age", 50)); } + @Test + public void testGetTreatmentsByFlagSet() { + SplitClient clientMock = mock(SplitClient.class); + when(mSplitFactory.client("key", null)).thenReturn(clientMock); + when(mUsedKeys.contains(new Key("key", null))).thenReturn(true); + + Map attrs = Collections.singletonMap("age", 50); + mSplitWrapper.getTreatmentsByFlagSet("key", null, "flag-set", attrs); + + verify(clientMock).getTreatmentsByFlagSet("flag-set", attrs); + } + + @Test + public void testGetTreatmentsByFlagSets() { + SplitClient clientMock = mock(SplitClient.class); + when(mSplitFactory.client("key", null)).thenReturn(clientMock); + when(mUsedKeys.contains(new Key("key", null))).thenReturn(true); + + Map attrs = Collections.singletonMap("age", 50); + List sets = Arrays.asList("set_1", "set_2"); + mSplitWrapper.getTreatmentsByFlagSets("key", null, sets, attrs); + + verify(clientMock).getTreatmentsByFlagSets(sets, attrs); + } + + @Test + public void testGetTreatmentsWithConfigByFlagSet() { + SplitClient clientMock = mock(SplitClient.class); + when(mSplitFactory.client("key", null)).thenReturn(clientMock); + when(mUsedKeys.contains(new Key("key", null))).thenReturn(true); + + Map attrs = Collections.singletonMap("age", 50); + mSplitWrapper.getTreatmentsWithConfigByFlagSet("key", null,"set_1", attrs); + + verify(clientMock).getTreatmentsWithConfigByFlagSet("set_1", attrs); + } + + @Test + public void testGetTreatmentsWithConfigByFlagSets() { + SplitClient clientMock = mock(SplitClient.class); + when(mSplitFactory.client("key", null)).thenReturn(clientMock); + when(mUsedKeys.contains(new Key("key", null))).thenReturn(true); + + Map attrs = Collections.singletonMap("age", 50); + List sets = Arrays.asList("set_1", "set_2"); + mSplitWrapper.getTreatmentsWithConfigByFlagSets("key", null, sets, attrs); + + verify(clientMock).getTreatmentsWithConfigByFlagSets(sets, attrs); + } + @Test public void testTrack() { SplitClient clientMock = mock(SplitClient.class); diff --git a/splitio_android/pubspec.yaml b/splitio_android/pubspec.yaml index 3381173..3bcabde 100644 --- a/splitio_android/pubspec.yaml +++ b/splitio_android/pubspec.yaml @@ -1,7 +1,7 @@ name: splitio_android description: The official Android implementation of splitio Flutter plugin. repository: https://github.com/splitio/flutter-sdk-plugin/tree/main/splitio_android -version: 0.1.6 +version: 0.1.7 environment: sdk: ">=2.16.2 <4.0.0" @@ -19,7 +19,7 @@ flutter: dependencies: flutter: sdk: flutter - splitio_platform_interface: ^1.3.0 + splitio_platform_interface: ^1.4.0 dev_dependencies: flutter_test: diff --git a/splitio_android/test/splitio_android_test.dart b/splitio_android/test/splitio_android_test.dart index 7cc1f68..554d4cd 100644 --- a/splitio_android/test/splitio_android_test.dart +++ b/splitio_android/test/splitio_android_test.dart @@ -33,8 +33,12 @@ void main() { case 'getTreatment': return ''; case 'getTreatments': + case 'getTreatmentsByFlagSet': + case 'getTreatmentsByFlagSets': return {'split1': 'on', 'split2': 'off'}; case 'getTreatmentsWithConfig': + case 'getTreatmentsWithConfigByFlagSet': + case 'getTreatmentsWithConfigByFlagSets': return { 'split1': {'treatment': 'on', 'config': null}, 'split2': {'treatment': 'off', 'config': null} @@ -54,6 +58,8 @@ void main() { case 'removeAttribute': case 'clearAttributes': return true; + case 'getUserConsent': + return 'declined'; } return null; }); @@ -183,6 +189,130 @@ void main() { 'attributes': {'attr1': true} }); }); + + test('getTreatmentsByFlagSet without attributes', () async { + _platform.getTreatmentsByFlagSet( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSet: 'set_1'); + + expect(methodName, 'getTreatmentsByFlagSet'); + expect(methodArguments, { + 'flagSet': 'set_1', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {} + }); + }); + + test('getTreatmentsByFlagSet with attributes', () async { + _platform.getTreatmentsByFlagSet( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSet: 'set_1', + attributes: {'attr1': true}); + + expect(methodName, 'getTreatmentsByFlagSet'); + expect(methodArguments, { + 'flagSet': 'set_1', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {'attr1': true} + }); + }); + + test('getTreatmentsByFlagSets without attributes', () async { + _platform.getTreatmentsByFlagSets( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSets: ['set_1', 'set_2']); + + expect(methodName, 'getTreatmentsByFlagSets'); + expect(methodArguments, { + 'flagSets': ['set_1', 'set_2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {} + }); + }); + + test('getTreatmentsByFlagSets with attributes', () async { + _platform.getTreatmentsByFlagSets( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSets: ['set_1', 'set_2'], + attributes: {'attr1': true}); + + expect(methodName, 'getTreatmentsByFlagSets'); + expect(methodArguments, { + 'flagSets': ['set_1', 'set_2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {'attr1': true} + }); + }); + + test('getTreatmentsWithConfigByFlagSet without attributes', () async { + _platform.getTreatmentsWithConfigByFlagSet( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSet: 'set_1'); + + expect(methodName, 'getTreatmentsWithConfigByFlagSet'); + expect(methodArguments, { + 'flagSet': 'set_1', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {} + }); + }); + + test('getTreatmentsWithConfigByFlagSet with attributes', () async { + _platform.getTreatmentsWithConfigByFlagSet( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSet: 'set_1', + attributes: {'attr1': true}); + + expect(methodName, 'getTreatmentsWithConfigByFlagSet'); + expect(methodArguments, { + 'flagSet': 'set_1', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {'attr1': true} + }); + }); + + test('getTreatmentsWithConfigByFlagSets without attributes', () async { + _platform.getTreatmentsWithConfigByFlagSets( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSets: ['set_1', 'set_2']); + + expect(methodName, 'getTreatmentsWithConfigByFlagSets'); + expect(methodArguments, { + 'flagSets': ['set_1', 'set_2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {} + }); + }); + + test('getTreatmentsWithConfigByFlagSets with attributes', () async { + _platform.getTreatmentsWithConfigByFlagSets( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSets: ['set_1', 'set_2'], + attributes: {'attr1': true}); + + expect(methodName, 'getTreatmentsWithConfigByFlagSets'); + expect(methodArguments, { + 'flagSets': ['set_1', 'set_2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {'attr1': true} + }); + }); }); group('track', () { @@ -349,7 +479,7 @@ void main() { expect(methodArguments, { 'apiKey': 'api-key', 'matchingKey': 'matching-key', - 'sdkConfiguration': {} + 'sdkConfiguration': {'readyTimeout': 10} }); }); @@ -363,7 +493,7 @@ void main() { 'apiKey': 'api-key', 'matchingKey': 'matching-key', 'bucketingKey': 'bucketing-key', - 'sdkConfiguration': {} + 'sdkConfiguration': {'readyTimeout': 10} }); }); @@ -527,14 +657,14 @@ void main() { _platform.setUserConsent(true); expect(methodName, 'setUserConsent'); - expect(methodArguments, {'enabled': true}); + expect(methodArguments, {'value': true}); }); test('set user consent disabled', () { _platform.setUserConsent(false); expect(methodName, 'setUserConsent'); - expect(methodArguments, {'enabled': false}); + expect(methodArguments, {'value': false}); }); }); } diff --git a/splitio_ios/CHANGELOG.md b/splitio_ios/CHANGELOG.md index b84abfb..09be62b 100644 --- a/splitio_ios/CHANGELOG.md +++ b/splitio_ios/CHANGELOG.md @@ -1,3 +1,9 @@ +# 0.1.7 (Nov 9, 2023) + +* Updated iOS SDK to `2.23.0` + +# 0.1.7-rc.1 (Nov 9, 2023) + # 0.1.6 (Aug 15, 2023) * Added `readyTimeout` configuration option. diff --git a/splitio_ios/example/ios/Podfile b/splitio_ios/example/ios/Podfile index e3a7db2..8629561 100644 --- a/splitio_ios/example/ios/Podfile +++ b/splitio_ios/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '11.0' +platform :ios, '12.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/splitio_ios/example/ios/Podfile.lock b/splitio_ios/example/ios/Podfile.lock index 1040a02..6f6ec46 100644 --- a/splitio_ios/example/ios/Podfile.lock +++ b/splitio_ios/example/ios/Podfile.lock @@ -1,9 +1,9 @@ PODS: - Flutter (1.0.0) - - Split (2.21.0) - - splitio_ios (0.3.0): + - Split (2.23.0) + - splitio_ios (0.4.0): - Flutter - - Split (~> 2.21.0) + - Split (~> 2.23.0) DEPENDENCIES: - Flutter (from `Flutter`) @@ -21,9 +21,9 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 - Split: e4324a8fe3fcb19c36a9e4fd84a893fc6d4bbeb0 - splitio_ios: 8293cb4e46661f9403f92a0d143c5bf6ad5053d4 + Split: 8140864905cd12cb66e2cf31e6ba9052d09c086b + splitio_ios: 1c1d7cd88e6756b3b40cf37cdf8610b8720164b0 -PODFILE CHECKSUM: 9eb4e99975f80022b373afce31dc8b3feb22df78 +PODFILE CHECKSUM: aed42fc5c94ade572556b7ed357c5c57f1bd83a2 COCOAPODS: 1.11.3 diff --git a/splitio_ios/example/ios/SplitTests/SplitClientConfigHelperTests.swift b/splitio_ios/example/ios/SplitTests/SplitClientConfigHelperTests.swift index e107644..339e629 100644 --- a/splitio_ios/example/ios/SplitTests/SplitClientConfigHelperTests.swift +++ b/splitio_ios/example/ios/SplitTests/SplitClientConfigHelperTests.swift @@ -21,7 +21,6 @@ class SplitClientConfigHelperTests: XCTestCase { "ready": 25, "streamingEnabled": true, "persistentAttributesEnabled": true, - "syncConfig": ["syncConfigNames": ["split1", "split2"], "syncConfigPrefixes": ["split_", "my_split_"]], "impressionsMode": "none", "syncEnabled": false, "userConsent": "declined", @@ -89,4 +88,30 @@ class SplitClientConfigHelperTests: XCTestCase { XCTAssertEqual(expectedImpressionsModes[index].rawValue, config.impressionsMode) } } + + func testSyncConfigWithoutFlagSetsIsMappedCorrectly() { + let configValues = [ + "syncConfig": ["syncConfigNames": ["split1", "split2"], "syncConfigPrefixes": ["split_", "my_split_"]], + ] + + let splitClientConfig: SplitClientConfig = SplitClientConfigHelper.fromMap(configurationMap: configValues, impressionListener: nil) + + XCTAssertEqual(2, splitClientConfig.sync.filters.count) + XCTAssertEqual(.byName, splitClientConfig.sync.filters[0].type) + XCTAssertEqual(["split1", "split2"], splitClientConfig.sync.filters[0].values) + XCTAssertEqual(.byPrefix, splitClientConfig.sync.filters[1].type) + XCTAssertEqual(["split_", "my_split_"], splitClientConfig.sync.filters[1].values) + } + + func testSyncConfigWithFlagSetsIsMappedCorrectly() { + let configValues = [ + "syncConfig": ["syncConfigNames": ["split1", "split2"], "syncConfigPrefixes": ["split_", "my_split_"] , "syncConfigFlagSets": ["set_1", "set_2"]], + ] + + let splitClientConfig: SplitClientConfig = SplitClientConfigHelper.fromMap(configurationMap: configValues, impressionListener: nil) + + XCTAssertEqual(1, splitClientConfig.sync.filters.count) + XCTAssertEqual(.bySet, splitClientConfig.sync.filters[0].type) + XCTAssertEqual(["set_1", "set_2"], splitClientConfig.sync.filters[0].values) + } } diff --git a/splitio_ios/example/ios/SplitTests/SplitMethodParserTests.swift b/splitio_ios/example/ios/SplitTests/SplitMethodParserTests.swift index e2d96a2..fd2c688 100644 --- a/splitio_ios/example/ios/SplitTests/SplitMethodParserTests.swift +++ b/splitio_ios/example/ios/SplitTests/SplitMethodParserTests.swift @@ -86,6 +86,59 @@ class SplitMethodParserTests: XCTestCase { } } + func testGetTreatmentsByFlagSet() { + methodParser?.onMethodCall(methodName: "getTreatmentsByFlagSet", arguments: ["matchingKey": "user-key", "bucketingKey": "bucketing-key", "flagSet": "set_1", "attributes": ["age": 50]], result: { (_: Any?) in + return + }) + + if let splitWrapper = (splitWrapper as? SplitWrapperStub) { + XCTAssert(splitWrapper.matchingKeyValue == "user-key") + XCTAssert(splitWrapper.bucketingKeyValue == "bucketing-key") + XCTAssert(splitWrapper.flagSetValue == "set_1") + print(splitWrapper.attributesValue) + XCTAssert(NSDictionary(dictionary: ["age": 50]).isEqual(to: splitWrapper.attributesValue!)) + } + } + + func testGetTreatmentsByFlagSets() { + methodParser?.onMethodCall(methodName: "getTreatmentsByFlagSets", arguments: ["matchingKey": "user-key", "bucketingKey": "bucketing-key", "flagSets": ["set_1", "set_2"], "attributes": ["age": 50]], result: { (_: Any?) in + return + }) + + if let splitWrapper = (splitWrapper as? SplitWrapperStub) { + XCTAssert(splitWrapper.matchingKeyValue == "user-key") + XCTAssert(splitWrapper.bucketingKeyValue == "bucketing-key") + XCTAssert(splitWrapper.flagSetsValue == ["set_1", "set_2"]) + XCTAssert(NSDictionary(dictionary: ["age": 50]).isEqual(to: splitWrapper.attributesValue!)) + } + } + + func testGetTreatmentsWithConfigByFlagSet() { + methodParser?.onMethodCall(methodName: "getTreatmentsWithConfigByFlagSet", arguments: ["matchingKey": "user-key", "bucketingKey": "bucketing-key", "flagSet": "set_1", "attributes": ["age": 50]], result: { (_: Any?) in + return + }) + + if let splitWrapper = (splitWrapper as? SplitWrapperStub) { + XCTAssert(splitWrapper.matchingKeyValue == "user-key") + XCTAssert(splitWrapper.bucketingKeyValue == "bucketing-key") + XCTAssert(splitWrapper.flagSetValue == "set_1") + XCTAssert(NSDictionary(dictionary: ["age": 50]).isEqual(to: splitWrapper.attributesValue!)) + } + } + + func testGetTreatmentsWithConfigByFlagSets() { + methodParser?.onMethodCall(methodName: "getTreatmentsWithConfigByFlagSets", arguments: ["matchingKey": "user-key", "bucketingKey": "bucketing-key", "flagSets": ["set_1", "set_2"], "attributes": ["age": 50]], result: { (_: Any?) in + return + }) + + if let splitWrapper = (splitWrapper as? SplitWrapperStub) { + XCTAssert(splitWrapper.matchingKeyValue == "user-key") + XCTAssert(splitWrapper.bucketingKeyValue == "bucketing-key") + XCTAssert(splitWrapper.flagSetsValue == ["set_1", "set_2"]) + XCTAssert(NSDictionary(dictionary: ["age": 50]).isEqual(to: splitWrapper.attributesValue!)) + } + } + func testTrackWithValue() { methodParser?.onMethodCall(methodName: "track", arguments: ["matchingKey": "user-key", "bucketingKey": "bucketing-key", "eventType": "my_event", "value": 25.20], result: { (_: Any?) in return @@ -328,6 +381,8 @@ class SplitWrapperStub: SplitWrapper { var matchingKeyValue = "" var bucketingKeyValue: String? var splitNameValue = "" + var flagSetValue = "" + var flagSetsValue: [String] = [] var splitsValue: [String]? var attributesValue: [String: Any]? var eventTypeValue: String = "" @@ -389,6 +444,42 @@ class SplitWrapperStub: SplitWrapper { return result } + func getTreatmentsByFlagSet(matchingKey: String, flagSet: String, bucketingKey: String?, attributes: [String : Any]?) -> [String : String] { + matchingKeyValue = matchingKey + bucketingKeyValue = bucketingKey + flagSetValue = flagSet + attributesValue = attributes + + return [:] + } + + func getTreatmentsByFlagSets(matchingKey: String, flagSets: [String], bucketingKey: String?, attributes: [String : Any]?) -> [String : String] { + matchingKeyValue = matchingKey + bucketingKeyValue = bucketingKey + flagSetsValue = flagSets + attributesValue = attributes + + return [:] + } + + func getTreatmentsWithConfigByFlagSet(matchingKey: String, flagSet: String, bucketingKey: String?, attributes: [String : Any]?) -> [String : SplitResult] { + matchingKeyValue = matchingKey + bucketingKeyValue = bucketingKey + flagSetValue = flagSet + attributesValue = attributes + + return [:] + } + + func getTreatmentsWithConfigByFlagSets(matchingKey: String, flagSets: [String], bucketingKey: String?, attributes: [String : Any]?) -> [String : SplitResult] { + matchingKeyValue = matchingKey + bucketingKeyValue = bucketingKey + flagSetsValue = flagSets + attributesValue = attributes + + return [:] + } + func track(matchingKey: String, bucketingKey: String?, eventType: String, trafficType: String?, value: Double?, properties: [String: Any]) -> Bool { matchingKeyValue = matchingKey bucketingKeyValue = bucketingKey diff --git a/splitio_ios/example/ios/SplitTests/SplitTests.swift b/splitio_ios/example/ios/SplitTests/SplitTests.swift index e4c02ad..c01d314 100644 --- a/splitio_ios/example/ios/SplitTests/SplitTests.swift +++ b/splitio_ios/example/ios/SplitTests/SplitTests.swift @@ -18,7 +18,7 @@ class SplitTests: XCTestCase { splitWrapper.getClient(matchingKey: "key", bucketingKey: "bucketing") let treatment = splitWrapper.getTreatment(matchingKey: "key", splitName: "split", bucketingKey: "bucketing", attributes: nil) XCTAssert(treatment != nil) - XCTAssert(client.getTreatmentCalled) + XCTAssert(client.methodCalls["getTreatment"] == true) } func testGetTreatments() { @@ -27,7 +27,7 @@ class SplitTests: XCTestCase { splitWrapper.getClient(matchingKey: "key", bucketingKey: "bucketing") let treatment = splitWrapper.getTreatments(matchingKey: "key", splits: ["split"], bucketingKey: "bucketing", attributes: nil) XCTAssert(!treatment.isEmpty) - XCTAssert(client.getTreatmentsCalled) + XCTAssert(client.methodCalls["getTreatments"] == true) } func testGetTreatmentWithConfig() { @@ -36,7 +36,7 @@ class SplitTests: XCTestCase { splitWrapper.getClient(matchingKey: "key", bucketingKey: "bucketing") let treatment = splitWrapper.getTreatmentWithConfig(matchingKey: "key", splitName: "split", bucketingKey: "bucketing", attributes: nil) XCTAssert(treatment != nil) - XCTAssert(client.getTreatmentWithConfigCalled) + XCTAssert(client.methodCalls["getTreatmentWithConfig"] == true) } func testGetTreatmentsWithConfig() { @@ -45,7 +45,40 @@ class SplitTests: XCTestCase { splitWrapper.getClient(matchingKey: "key", bucketingKey: "bucketing") let treatment = splitWrapper.getTreatmentsWithConfig(matchingKey: "key", splits: ["split"], bucketingKey: "bucketing", attributes: nil) XCTAssert(!treatment.isEmpty) - XCTAssert(client.getTreatmentsWithConfigCalled) + XCTAssert(client.methodCalls["getTreatmentsWithConfig"] == true) + } + + func testGetTreatmentsByFlagSet() { + let client = SplitClientStub() + splitWrapper = DefaultSplitWrapper(splitFactoryProvider: SplitFactoryProviderStubWithClient(client: client)) + splitWrapper.getClient(matchingKey: "key", bucketingKey: "bucketing") + let treatment = splitWrapper.getTreatmentsByFlagSet(matchingKey: "key", flagSet: "set_1", bucketingKey: "bucketing", attributes: nil) + XCTAssert(client.methodCalls["getTreatmentsByFlagSet"] == true) + } + + func testGetTreatmentsByFlagSets() { + let client = SplitClientStub() + splitWrapper = DefaultSplitWrapper(splitFactoryProvider: SplitFactoryProviderStubWithClient(client: client)) + splitWrapper.getClient(matchingKey: "key", bucketingKey: "bucketing") + let treatment = splitWrapper.getTreatmentsByFlagSets(matchingKey: "key", flagSets: ["set_1"], bucketingKey: "bucketing", attributes: nil) + XCTAssert(client.methodCalls["getTreatmentsByFlagSets"] == true) + } + + func testGetTreatmentsWithConfigByFlagSet() { + let client = SplitClientStub() + splitWrapper = DefaultSplitWrapper(splitFactoryProvider: SplitFactoryProviderStubWithClient(client: client)) + splitWrapper.getClient(matchingKey: "key", bucketingKey: "bucketing") + let treatment = splitWrapper.getTreatmentsWithConfigByFlagSet(matchingKey: "key", flagSet: "set_1", bucketingKey: "bucketing", attributes: nil) + XCTAssert(client.methodCalls["getTreatmentsWithConfigByFlagSet"] == true) + } + + func testGetTreatmentsWithConfigByFlagSets() { + let client = SplitClientStub() + splitWrapper = DefaultSplitWrapper(splitFactoryProvider: SplitFactoryProviderStubWithClient(client: client)) + splitWrapper.getClient(matchingKey: "key", bucketingKey: "bucketing") + let treatment = splitWrapper.getTreatmentsWithConfigByFlagSets(matchingKey: "key", flagSets: ["set_1"], bucketingKey: "bucketing", attributes: nil) + + XCTAssert(client.methodCalls["getTreatmentsWithConfigByFlagSets"] == true) } func testTrack() { @@ -146,7 +179,7 @@ class SplitTests: XCTestCase { usedKeys.insert(Key(matchingKey: "key", bucketingKey: "bucketing")) splitWrapper = DefaultSplitWrapper(splitFactoryProvider: SplitFactoryProviderStubWithClient(client: client), usedKeys: usedKeys) splitWrapper.flush(matchingKey: "key", bucketingKey: "bucketing") - XCTAssert(client.flushCalled) + XCTAssert((client.methodCalls["flush"] != nil)) } func testDestroy() { @@ -303,11 +336,17 @@ class SplitFactoryStub: SplitFactory { class SplitClientStub: SplitClient { var destroyCalled: Bool = false - var getTreatmentCalled: Bool = false - var getTreatmentWithConfigCalled: Bool = false - var getTreatmentsCalled: Bool = false - var getTreatmentsWithConfigCalled: Bool = false - var flushCalled: Bool = false + var methodCalls = [ + "getTreatment": false, + "getTreatmentWithConfig": false, + "getTreatments": false, + "getTreatmentsWithConfig": false, + "getTreatmentsByFlagSet": false, + "getTreatmentsByFlagSets": false, + "getTreatmentsWithConfigByFlagSet": false, + "getTreatmentsWithConfigByFlagSets": false, + "flush": false, + ] var eventTypeValue: String = "" var trafficTypeValue: String? var valueValue: Double? @@ -319,35 +358,55 @@ class SplitClientStub: SplitClient { var sdkReadyEventAction: SplitAction? func getTreatment(_ split: String, attributes: [String: Any]?) -> String { - getTreatmentCalled = true + methodCalls["getTreatment"] = true return SplitConstants.control } func getTreatment(_ split: String) -> String { - getTreatmentCalled = true + methodCalls["getTreatment"] = true return SplitConstants.control } func getTreatments(splits: [String], attributes: [String: Any]?) -> [String: String] { - getTreatmentsCalled = true + methodCalls["getTreatments"] = true return ["feature": SplitConstants.control] } func getTreatmentWithConfig(_ split: String) -> SplitResult { - getTreatmentWithConfigCalled = true + methodCalls["getTreatmentWithConfig"] = true return SplitResult(treatment: SplitConstants.control) } func getTreatmentWithConfig(_ split: String, attributes: [String: Any]?) -> SplitResult { - getTreatmentWithConfigCalled = true + methodCalls["getTreatmentWithConfig"] = true return SplitResult(treatment: SplitConstants.control) } func getTreatmentsWithConfig(splits: [String], attributes: [String: Any]?) -> [String: SplitResult] { - getTreatmentsWithConfigCalled = true + methodCalls["getTreatmentsWithConfig"] = true return ["feature": SplitResult(treatment: SplitConstants.control)] } + func getTreatmentsByFlagSet(_ flagSet: String, attributes: [String : Any]?) -> [String : String] { + methodCalls["getTreatmentsByFlagSet"] = true + return [:] + } + + func getTreatmentsByFlagSets(_ flagSets: [String], attributes: [String : Any]?) -> [String : String] { + methodCalls["getTreatmentsByFlagSets"] = true + return [:] + } + + func getTreatmentsWithConfigByFlagSet(_ flagSet: String, attributes: [String : Any]?) -> [String : SplitResult] { + methodCalls["getTreatmentsWithConfigByFlagSet"] = true + return [:] + } + + func getTreatmentsWithConfigByFlagSets(_ flagSets: [String], attributes: [String : Any]?) -> [String : SplitResult] { + methodCalls["getTreatmentsWithConfigByFlagSets"] = true + return [:] + } + func on(event: SplitEvent, execute action: @escaping SplitAction) { if event == SplitEvent.sdkReady { sdkReadyEventAction = action @@ -430,7 +489,7 @@ class SplitClientStub: SplitClient { } func flush() { - flushCalled = true + methodCalls["flush"] = true } func destroy() { diff --git a/splitio_ios/ios/Classes/Constants.swift b/splitio_ios/ios/Classes/Constants.swift index bea9d65..ea6e2e6 100644 --- a/splitio_ios/ios/Classes/Constants.swift +++ b/splitio_ios/ios/Classes/Constants.swift @@ -13,6 +13,10 @@ enum Method: String { case getTreatments = "getTreatments" case getTreatmentWithConfig = "getTreatmentWithConfig" case getTreatmentsWithConfig = "getTreatmentsWithConfig" + case getTreatmentsByFlagSet = "getTreatmentsByFlagSet" + case getTreatmentsByFlagSets = "getTreatmentsByFlagSets" + case getTreatmentsWithConfigByFlagSet = "getTreatmentsWithConfigByFlagSet" + case getTreatmentsWithConfigByFlagSets = "getTreatmentsWithConfigByFlagSets" case track = "track" case getAttribute = "getAttribute" case getAllAttributes = "getAllAttributes" @@ -40,4 +44,6 @@ enum Argument: String { case value = "value" case properties = "properties" case attributeName = "attributeName" + case flagSet = "flagSet" + case flagSets = "flagSets" } diff --git a/splitio_ios/ios/Classes/Extensions.swift b/splitio_ios/ios/Classes/Extensions.swift index 5ff3bc7..d17250c 100644 --- a/splitio_ios/ios/Classes/Extensions.swift +++ b/splitio_ios/ios/Classes/Extensions.swift @@ -22,7 +22,10 @@ extension SplitView { "killed": splitView.killed, "treatments": splitView.treatments, "changeNumber": splitView.changeNumber, - "configs": splitView.configs] + "configs": splitView.configs, + "defaultTreatment": splitView.defaultTreatment, + "sets": splitView.sets + ] } else { return [:] } diff --git a/splitio_ios/ios/Classes/SplitClientConfigHelper.swift b/splitio_ios/ios/Classes/SplitClientConfigHelper.swift index 2febd19..feaaa5d 100644 --- a/splitio_ios/ios/Classes/SplitClientConfigHelper.swift +++ b/splitio_ios/ios/Classes/SplitClientConfigHelper.swift @@ -24,6 +24,7 @@ class SplitClientConfigHelper { static private let SYNC_CONFIG = "syncConfig" static private let SYNC_CONFIG_NAMES = "syncConfigNames" static private let SYNC_CONFIG_PREFIXES = "syncConfigPrefixes" + static private let SYNC_CONFIG_SETS = "syncConfigFlagSets" static private let IMPRESSIONS_MODE = "impressionsMode" static private let SYNC_ENABLED = "syncEnabled" static private let USER_CONSENT = "userConsent" @@ -149,12 +150,19 @@ class SplitClientConfigHelper { if configurationMap[SYNC_CONFIG] != nil { if let syncConfig = configurationMap[SYNC_CONFIG] as? [String: [String]] { let syncConfigBuilder = SyncConfig.builder() - if let syncNames = syncConfig[SYNC_CONFIG_NAMES] as? [String] { - syncConfigBuilder.addSplitFilter(SplitFilter.byName(syncNames)) - } - if let syncPrefixes = syncConfig[SYNC_CONFIG_NAMES] as? [String] { - syncConfigBuilder.addSplitFilter(SplitFilter.byPrefix(syncPrefixes)) + if (syncConfig[SYNC_CONFIG_SETS] != nil && syncConfig[SYNC_CONFIG_SETS]?.isEmpty == false) { + if let syncFlagSets = syncConfig[SYNC_CONFIG_SETS] as? [String] { + syncConfigBuilder.addSplitFilter(SplitFilter.bySet(syncFlagSets)) + } + } else { + if let syncNames = syncConfig[SYNC_CONFIG_NAMES] as? [String] { + syncConfigBuilder.addSplitFilter(SplitFilter.byName(syncNames)) + } + + if let syncPrefixes = syncConfig[SYNC_CONFIG_PREFIXES] as? [String] { + syncConfigBuilder.addSplitFilter(SplitFilter.byPrefix(syncPrefixes)) + } } config.sync = syncConfigBuilder.build() diff --git a/splitio_ios/ios/Classes/SplitMethodParser.swift b/splitio_ios/ios/Classes/SplitMethodParser.swift index 6c8a216..d54abd6 100644 --- a/splitio_ios/ios/Classes/SplitMethodParser.swift +++ b/splitio_ios/ios/Classes/SplitMethodParser.swift @@ -75,6 +75,34 @@ class DefaultSplitMethodParser: SplitMethodParser { splits: argumentParser.getStringListArgument(argumentName: .splitName, arguments: arguments), attributes: argumentParser.getMapArgument(argumentName: .attributes, arguments: arguments) as [String: Any])) break + case .getTreatmentsByFlagSet: + result(getTreatmentsByFlagSet( + matchingKey: argumentParser.getStringArgument(argumentName: .matchingKey, arguments: arguments) ?? "", + bucketingKey: argumentParser.getStringArgument(argumentName: .bucketingKey, arguments: arguments), + flagSet: argumentParser.getStringArgument(argumentName: .flagSet, arguments: arguments) ?? "", + attributes: argumentParser.getMapArgument(argumentName: .attributes, arguments: arguments) as [String: Any])) + break + case .getTreatmentsByFlagSets: + result(getTreatmentsByFlagSets( + matchingKey: argumentParser.getStringArgument(argumentName: .matchingKey, arguments: arguments) ?? "", + bucketingKey: argumentParser.getStringArgument(argumentName: .bucketingKey, arguments: arguments), + flagSets: argumentParser.getStringListArgument(argumentName: .flagSets, arguments: arguments), + attributes: argumentParser.getMapArgument(argumentName: .attributes, arguments: arguments) as [String: Any])) + break + case .getTreatmentsWithConfigByFlagSet: + result(getTreatmentsWithConfigByFlagSet( + matchingKey: argumentParser.getStringArgument(argumentName: .matchingKey, arguments: arguments) ?? "", + bucketingKey: argumentParser.getStringArgument(argumentName: .bucketingKey, arguments: arguments), + flagSet: argumentParser.getStringArgument(argumentName: .flagSet, arguments: arguments) ?? "", + attributes: argumentParser.getMapArgument(argumentName: .attributes, arguments: arguments) as [String: Any])) + break + case .getTreatmentsWithConfigByFlagSets: + result(getTreatmentsWithConfigByFlagSets( + matchingKey: argumentParser.getStringArgument(argumentName: .matchingKey, arguments: arguments) ?? "", + bucketingKey: argumentParser.getStringArgument(argumentName: .bucketingKey, arguments: arguments), + flagSets: argumentParser.getStringListArgument(argumentName: .flagSets, arguments: arguments), + attributes: argumentParser.getMapArgument(argumentName: .attributes, arguments: arguments) as [String: Any])) + break case .track: result(track(matchingKey: argumentParser.getStringArgument(argumentName: .matchingKey, arguments: arguments) ?? "", bucketingKey: argumentParser.getStringArgument(argumentName: .bucketingKey, arguments: arguments), @@ -215,6 +243,50 @@ class DefaultSplitMethodParser: SplitMethodParser { } } + private func getTreatmentsByFlagSet(matchingKey: String, bucketingKey: String? = nil, flagSet: String, attributes: [String: Any]? = [:]) -> [String: String] { + guard let splitWrapper = getSplitWrapper() else { + return [:] + } + + let treatments = splitWrapper.getTreatmentsByFlagSet(matchingKey: matchingKey, flagSet: flagSet, bucketingKey: bucketingKey, attributes: attributes) + + return treatments + } + + private func getTreatmentsByFlagSets(matchingKey: String, bucketingKey: String? = nil, flagSets: [String], attributes: [String: Any]? = [:]) -> [String: String] { + guard let splitWrapper = getSplitWrapper() else { + return [:] + } + + let treatments = splitWrapper.getTreatmentsByFlagSets(matchingKey: matchingKey, flagSets: flagSets, bucketingKey: bucketingKey, attributes: attributes) + + return treatments + } + + private func getTreatmentsWithConfigByFlagSet(matchingKey: String, bucketingKey: String? = nil, flagSet: String, attributes: [String: Any]? = [:]) -> [String: [String: String?]] { + guard let splitWrapper = getSplitWrapper() else { + return [:] + } + + let treatments = splitWrapper.getTreatmentsWithConfigByFlagSet(matchingKey: matchingKey, flagSet: flagSet, bucketingKey: bucketingKey, attributes: attributes) + + return treatments.mapValues { + ["treatment": $0.treatment, "config": $0.config] + } + } + + private func getTreatmentsWithConfigByFlagSets(matchingKey: String, bucketingKey: String? = nil, flagSets: [String], attributes: [String: Any]? = [:]) -> [String: [String: String?]] { + guard let splitWrapper = getSplitWrapper() else { + return [:] + } + + let treatments = splitWrapper.getTreatmentsWithConfigByFlagSets(matchingKey: matchingKey, flagSets: flagSets, bucketingKey: bucketingKey, attributes: attributes) + + return treatments.mapValues { + ["treatment": $0.treatment, "config": $0.config] + } + } + private func track(matchingKey: String, bucketingKey: String? = nil, eventType: String, trafficType: String? = nil, value: Double? = nil, properties: [String: Any?]) -> Bool { guard let splitWrapper = getSplitWrapper() else { return false @@ -320,18 +392,4 @@ class DefaultSplitMethodParser: SplitMethodParser { return splitWrapper } - - private func getSplitViewAsMap(splitView: SplitView?) -> [String: Any?] { - if let splitView = splitView { - return [ - "name": splitView.name, - "trafficType": splitView.trafficType, - "killed": splitView.killed, - "treatments": splitView.treatments, - "changeNumber": splitView.changeNumber, - "configs": splitView.configs] - } else { - return [:] - } - } } diff --git a/splitio_ios/ios/Classes/SplitWrapper.swift b/splitio_ios/ios/Classes/SplitWrapper.swift index 69e764f..b25f9d2 100644 --- a/splitio_ios/ios/Classes/SplitWrapper.swift +++ b/splitio_ios/ios/Classes/SplitWrapper.swift @@ -31,6 +31,14 @@ protocol EvaluationWrapper { func getTreatmentWithConfig(matchingKey: String, splitName: String, bucketingKey: String?, attributes: [String: Any]?) -> SplitResult? func getTreatmentsWithConfig(matchingKey: String, splits: [String], bucketingKey: String?, attributes: [String: Any]?) -> [String: SplitResult] + + func getTreatmentsByFlagSet(matchingKey: String, flagSet: String, bucketingKey: String?, attributes: [String: Any]?) -> [String: String] + + func getTreatmentsByFlagSets(matchingKey: String, flagSets: [String], bucketingKey: String?, attributes: [String: Any]?) -> [String: String] + + func getTreatmentsWithConfigByFlagSet(matchingKey: String, flagSet: String, bucketingKey: String?, attributes: [String: Any]?) -> [String: SplitResult] + + func getTreatmentsWithConfigByFlagSets(matchingKey: String, flagSets: [String], bucketingKey: String?, attributes: [String: Any]?) -> [String: SplitResult] } protocol AttributesWrapper { @@ -121,6 +129,38 @@ class DefaultSplitWrapper: SplitWrapper { return client.getTreatmentsWithConfig(splits: splits, attributes: attributes) } + func getTreatmentsByFlagSet(matchingKey: String, flagSet: String, bucketingKey: String? = nil, attributes: [String: Any]? = [:]) -> [String: String] { + guard let client = getInitializedClient(matchingKey: matchingKey, bucketingKey: bucketingKey) else { + return [:] + } + + return client.getTreatmentsByFlagSet(flagSet, attributes: attributes) + } + + func getTreatmentsByFlagSets(matchingKey: String, flagSets: [String], bucketingKey: String? = nil, attributes: [String: Any]? = [:]) -> [String: String] { + guard let client = getInitializedClient(matchingKey: matchingKey, bucketingKey: bucketingKey) else { + return [:] + } + + return client.getTreatmentsByFlagSets(flagSets, attributes: attributes) + } + + func getTreatmentsWithConfigByFlagSet(matchingKey: String, flagSet: String, bucketingKey: String? = nil, attributes: [String: Any]? = [:]) -> [String: SplitResult] { + guard let client = getInitializedClient(matchingKey: matchingKey, bucketingKey: bucketingKey) else { + return [:] + } + + return client.getTreatmentsWithConfigByFlagSet(flagSet, attributes: attributes) + } + + func getTreatmentsWithConfigByFlagSets(matchingKey: String, flagSets: [String], bucketingKey: String? = nil, attributes: [String: Any]? = [:]) -> [String: SplitResult] { + guard let client = getInitializedClient(matchingKey: matchingKey, bucketingKey: bucketingKey) else { + return [:] + } + + return client.getTreatmentsWithConfigByFlagSets(flagSets, attributes: attributes) + } + func track(matchingKey: String, bucketingKey: String?, eventType: String, trafficType: String?, value: Double?, properties: [String: Any]) -> Bool { guard let client = getInitializedClient(matchingKey: matchingKey, bucketingKey: bucketingKey) else { return false diff --git a/splitio_ios/ios/splitio_ios.podspec b/splitio_ios/ios/splitio_ios.podspec index 61453f6..7371662 100644 --- a/splitio_ios/ios/splitio_ios.podspec +++ b/splitio_ios/ios/splitio_ios.podspec @@ -4,7 +4,7 @@ # Pod::Spec.new do |s| s.name = 'splitio_ios' - s.version = '0.3.0' + s.version = '0.4.0' s.summary = 'split.io official Flutter plugin.' s.description = <<-DESC split.io official Flutter plugin. @@ -15,7 +15,7 @@ split.io official Flutter plugin. s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency 'Split', '~> 2.21.0' + s.dependency 'Split', '~> 2.23.0' s.platform = :ios, '9.0' # Flutter.framework does not contain a i386 slice. diff --git a/splitio_ios/pubspec.yaml b/splitio_ios/pubspec.yaml index 9539b32..7e46f71 100644 --- a/splitio_ios/pubspec.yaml +++ b/splitio_ios/pubspec.yaml @@ -1,7 +1,7 @@ name: splitio_ios description: The official iOS implementation of splitio Flutter plugin. repository: https://github.com/splitio/flutter-sdk-plugin/tree/main/splitio_ios -version: 0.1.6 +version: 0.1.7 environment: sdk: ">=2.16.2 <4.0.0" @@ -18,7 +18,7 @@ flutter: dependencies: flutter: sdk: flutter - splitio_platform_interface: ^1.3.0 + splitio_platform_interface: ^1.4.0 dev_dependencies: flutter_test: diff --git a/splitio_ios/test/splitio_ios_test.dart b/splitio_ios/test/splitio_ios_test.dart index d037cb3..ab04f62 100644 --- a/splitio_ios/test/splitio_ios_test.dart +++ b/splitio_ios/test/splitio_ios_test.dart @@ -33,8 +33,12 @@ void main() { case 'getTreatment': return ''; case 'getTreatments': + case 'getTreatmentsByFlagSet': + case 'getTreatmentsByFlagSets': return {'split1': 'on', 'split2': 'off'}; case 'getTreatmentsWithConfig': + case 'getTreatmentsWithConfigByFlagSet': + case 'getTreatmentsWithConfigByFlagSets': return { 'split1': {'treatment': 'on', 'config': null}, 'split2': {'treatment': 'off', 'config': null} @@ -54,6 +58,8 @@ void main() { case 'removeAttribute': case 'clearAttributes': return true; + case 'getUserConsent': + return 'declined'; } return null; }); @@ -183,6 +189,131 @@ void main() { 'attributes': {'attr1': true} }); }); + + + test('getTreatmentsByFlagSet without attributes', () async { + _platform.getTreatmentsByFlagSet( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSet: 'set_1'); + + expect(methodName, 'getTreatmentsByFlagSet'); + expect(methodArguments, { + 'flagSet': 'set_1', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {} + }); + }); + + test('getTreatmentsByFlagSet with attributes', () async { + _platform.getTreatmentsByFlagSet( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSet: 'set_1', + attributes: {'attr1': true}); + + expect(methodName, 'getTreatmentsByFlagSet'); + expect(methodArguments, { + 'flagSet': 'set_1', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {'attr1': true} + }); + }); + + test('getTreatmentsByFlagSets without attributes', () async { + _platform.getTreatmentsByFlagSets( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSets: ['set_1', 'set_2']); + + expect(methodName, 'getTreatmentsByFlagSets'); + expect(methodArguments, { + 'flagSets': ['set_1', 'set_2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {} + }); + }); + + test('getTreatmentsByFlagSets with attributes', () async { + _platform.getTreatmentsByFlagSets( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSets: ['set_1', 'set_2'], + attributes: {'attr1': true}); + + expect(methodName, 'getTreatmentsByFlagSets'); + expect(methodArguments, { + 'flagSets': ['set_1', 'set_2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {'attr1': true} + }); + }); + + test('getTreatmentsWithConfigByFlagSet without attributes', () async { + _platform.getTreatmentsWithConfigByFlagSet( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSet: 'set_1'); + + expect(methodName, 'getTreatmentsWithConfigByFlagSet'); + expect(methodArguments, { + 'flagSet': 'set_1', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {} + }); + }); + + test('getTreatmentsWithConfigByFlagSet with attributes', () async { + _platform.getTreatmentsWithConfigByFlagSet( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSet: 'set_1', + attributes: {'attr1': true}); + + expect(methodName, 'getTreatmentsWithConfigByFlagSet'); + expect(methodArguments, { + 'flagSet': 'set_1', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {'attr1': true} + }); + }); + + test('getTreatmentsWithConfigByFlagSets without attributes', () async { + _platform.getTreatmentsWithConfigByFlagSets( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSets: ['set_1', 'set_2']); + + expect(methodName, 'getTreatmentsWithConfigByFlagSets'); + expect(methodArguments, { + 'flagSets': ['set_1', 'set_2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {} + }); + }); + + test('getTreatmentsWithConfigByFlagSets with attributes', () async { + _platform.getTreatmentsWithConfigByFlagSets( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSets: ['set_1', 'set_2'], + attributes: {'attr1': true}); + + expect(methodName, 'getTreatmentsWithConfigByFlagSets'); + expect(methodArguments, { + 'flagSets': ['set_1', 'set_2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {'attr1': true} + }); + }); }); group('track', () { @@ -349,7 +480,7 @@ void main() { expect(methodArguments, { 'apiKey': 'api-key', 'matchingKey': 'matching-key', - 'sdkConfiguration': {} + 'sdkConfiguration': {'readyTimeout': 10} }); }); @@ -363,7 +494,7 @@ void main() { 'apiKey': 'api-key', 'matchingKey': 'matching-key', 'bucketingKey': 'bucketing-key', - 'sdkConfiguration': {} + 'sdkConfiguration': {'readyTimeout': 10} }); }); @@ -373,13 +504,13 @@ void main() { matchingKey: 'matching-key', bucketingKey: 'bucketing-key', sdkConfiguration: - SplitConfiguration(logLevel: SplitLogLevel.warning, streamingEnabled: false)); + SplitConfiguration(logLevel: SplitLogLevel.error, streamingEnabled: false, readyTimeout: 1)); expect(methodName, 'init'); expect(methodArguments, { 'apiKey': 'api-key', 'matchingKey': 'matching-key', 'bucketingKey': 'bucketing-key', - 'sdkConfiguration': {'logLevel': 'warning', 'streamingEnabled': false}, + 'sdkConfiguration': {'logLevel': 'error', 'streamingEnabled': false, 'readyTimeout': 1}, }); }); }); @@ -514,4 +645,28 @@ void main() { 'attributes': {} }); }); + + + group('userConsent', () { + test('get user consent', () async { + UserConsent userConsent = await _platform.getUserConsent(); + + expect(methodName, 'getUserConsent'); + expect(userConsent, UserConsent.declined); + }); + + test('set user consent enabled', () { + _platform.setUserConsent(true); + + expect(methodName, 'setUserConsent'); + expect(methodArguments, {'value': true}); + }); + + test('set user consent disabled', () { + _platform.setUserConsent(false); + + expect(methodName, 'setUserConsent'); + expect(methodArguments, {'value': false}); + }); + }); } diff --git a/splitio_platform_interface/CHANGELOG.md b/splitio_platform_interface/CHANGELOG.md index a46f530..83981ae 100644 --- a/splitio_platform_interface/CHANGELOG.md +++ b/splitio_platform_interface/CHANGELOG.md @@ -1,3 +1,13 @@ +# 1.4.0 (Nov 9, 2023) +* Added support for Flag Sets, 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 Split 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. + * Added `defaultTreatment` property to the `SplitView` object returned by the `split` and `splits` methods of the SDK manager. + +# 1.4.0-rc.1 (Nov 9, 2023) + # 1.3.0 (Aug 15, 2023) * Added `readyTimeout` configuration option. diff --git a/splitio_platform_interface/lib/method_channel_platform.dart b/splitio_platform_interface/lib/method_channel_platform.dart index 4bc0400..d3fb11d 100644 --- a/splitio_platform_interface/lib/method_channel_platform.dart +++ b/splitio_platform_interface/lib/method_channel_platform.dart @@ -33,7 +33,9 @@ class MethodChannelPlatform extends SplitioPlatform { Map arguments = { 'apiKey': apiKey, 'matchingKey': matchingKey, - 'sdkConfiguration': sdkConfiguration?.configurationMap ?? SplitConfiguration().configurationMap, // If sdkConfiguration is null, create a new SplitConfiguration to apply default values + 'sdkConfiguration': sdkConfiguration?.configurationMap ?? + SplitConfiguration().configurationMap, + // If sdkConfiguration is null, create a new SplitConfiguration to apply default values }; if (bucketingKey != null) { @@ -165,6 +167,70 @@ class MethodChannelPlatform extends SplitioPlatform { {for (var item in splitNames) item: _controlResult}; } + @override + Future> getTreatmentsByFlagSet( + {required String matchingKey, + required String? bucketingKey, + required String flagSet, + Map attributes = const {}}) async { + Map? treatments = await methodChannel.invokeMapMethod( + 'getTreatmentsByFlagSet', + _buildParameters(matchingKey, bucketingKey, + {'flagSet': flagSet, 'attributes': attributes})); + + return treatments + ?.map((key, value) => MapEntry(key, value)) ?? + {}; + } + + @override + Future> getTreatmentsByFlagSets( + {required String matchingKey, + required String? bucketingKey, + required List flagSets, + Map attributes = const {}}) async { + Map? treatments = await methodChannel.invokeMapMethod( + 'getTreatmentsByFlagSets', + _buildParameters(matchingKey, bucketingKey, + {'flagSets': flagSets, 'attributes': attributes})); + + return treatments + ?.map((key, value) => MapEntry(key, value)) ?? + {}; + } + + @override + Future> getTreatmentsWithConfigByFlagSet( + {required String matchingKey, + required String? bucketingKey, + required String flagSet, + Map attributes = const {}}) async { + Map? treatments = await methodChannel.invokeMapMethod( + 'getTreatmentsWithConfigByFlagSet', + _buildParameters(matchingKey, bucketingKey, + {'flagSet': flagSet, 'attributes': attributes})); + + return treatments?.map((key, value) => + MapEntry(key, SplitResult(value['treatment'], value['config']))) ?? + {}; + } + + @override + Future> getTreatmentsWithConfigByFlagSets( + {required String matchingKey, + required String? bucketingKey, + required List flagSets, + Map attributes = const {}}) async { + Map? treatments = await methodChannel.invokeMapMethod( + 'getTreatmentsWithConfigByFlagSets', + _buildParameters(matchingKey, bucketingKey, + {'flagSets': flagSets, 'attributes': attributes})); + + return treatments?.map((key, value) => + MapEntry(key, SplitResult(value['treatment'], value['config']))) ?? + {}; + } + @override Future removeAttribute( {required String matchingKey, diff --git a/splitio_platform_interface/lib/split_configuration.dart b/splitio_platform_interface/lib/split_configuration.dart index 08496aa..4a56668 100644 --- a/splitio_platform_interface/lib/split_configuration.dart +++ b/splitio_platform_interface/lib/split_configuration.dart @@ -147,7 +147,8 @@ class SplitConfiguration { if (syncConfig != null) { configurationMap['syncConfig'] = { 'syncConfigNames': syncConfig.names.toList(growable: false), - 'syncConfigPrefixes': syncConfig.prefixes.toList(growable: false) + 'syncConfigPrefixes': syncConfig.prefixes.toList(growable: false), + 'syncConfigFlagSets': syncConfig.sets.toList(growable: false) }; } diff --git a/splitio_platform_interface/lib/split_result.dart b/splitio_platform_interface/lib/split_result.dart index 80b452b..f1c0896 100644 --- a/splitio_platform_interface/lib/split_result.dart +++ b/splitio_platform_interface/lib/split_result.dart @@ -13,8 +13,8 @@ class SplitResult { String toString() { return '{"treatment": "' + treatment + - '", config: "' + - (config ?? 'null') + - '"}'; + '", config: ' + + (config != null ? '"$config"' : 'null') + + '}'; } } diff --git a/splitio_platform_interface/lib/split_sync_config.dart b/splitio_platform_interface/lib/split_sync_config.dart index 6eec23d..bc47f8d 100644 --- a/splitio_platform_interface/lib/split_sync_config.dart +++ b/splitio_platform_interface/lib/split_sync_config.dart @@ -1,15 +1,19 @@ class SyncConfig { late Set _names; late Set _prefixes; + late Set _sets; Set get names => _names; Set get prefixes => _prefixes; + Set get sets => _sets; + SyncConfig( {List names = const [], List prefixes = const []}) { _names = names.toSet(); _prefixes = prefixes.toSet(); + _sets = {}; } SyncConfig.fromSet( @@ -17,4 +21,10 @@ class SyncConfig { _names = names; _prefixes = prefixes; } + + SyncConfig.flagSets(List sets) { + _sets = sets.toSet(); + _names = {}; + _prefixes = {}; + } } diff --git a/splitio_platform_interface/lib/split_view.dart b/splitio_platform_interface/lib/split_view.dart index be05ae0..789daa1 100644 --- a/splitio_platform_interface/lib/split_view.dart +++ b/splitio_platform_interface/lib/split_view.dart @@ -7,9 +7,12 @@ class SplitView { List treatments = []; int changeNumber; Map configs = {}; + String defaultTreatment; + List sets = []; SplitView(this.name, this.trafficType, this.killed, this.treatments, - this.changeNumber, this.configs); + this.changeNumber, this.configs, + [this.defaultTreatment = '', this.sets = const []]); static SplitView? fromEntry(Map? entry) { if (entry == null || entry.isEmpty) { @@ -21,13 +24,24 @@ class SplitView { mappedConfig.addAll({entry.key.toString(): entry.value.toString()}) }); + if (entry['treatments'] == null) { + entry['treatments'] = entry['treatments'] ?? []; + } + + if (entry['sets'] == null) { + entry['sets'] = []; + } + return SplitView( entry['name'], entry['trafficType'], entry['killed'], (entry['treatments'] as List).map((el) => el as String).toList(), entry['changeNumber'], - mappedConfig); + mappedConfig, + entry['defaultTreatment'] ?? '', + (entry['sets'] as List).map((el) => el as String).toList() + ); } @override @@ -38,7 +52,9 @@ class SplitView { killed: $killed, treatments: ${treatments.toString()}, changeNumber: $changeNumber, - config: $configs + config: $configs, + defaultTreatment: $defaultTreatment, + sets: ${sets.toString()} }'''; } } diff --git a/splitio_platform_interface/lib/splitio_platform_interface.dart b/splitio_platform_interface/lib/splitio_platform_interface.dart index 3e71adb..a367fdf 100644 --- a/splitio_platform_interface/lib/splitio_platform_interface.dart +++ b/splitio_platform_interface/lib/splitio_platform_interface.dart @@ -90,6 +90,38 @@ abstract class _ClientPlatform { throw UnimplementedError(); } + Future> getTreatmentsByFlagSet( + {required String matchingKey, + required String? bucketingKey, + required String flagSet, + Map attributes = const {}}) { + throw UnimplementedError(); + } + + Future> getTreatmentsByFlagSets( + {required String matchingKey, + required String? bucketingKey, + required List flagSets, + Map attributes = const {}}) { + throw UnimplementedError(); + } + + Future> getTreatmentsWithConfigByFlagSet( + {required String matchingKey, + required String? bucketingKey, + required String flagSet, + Map attributes = const {}}) { + throw UnimplementedError(); + } + + Future> getTreatmentsWithConfigByFlagSets( + {required String matchingKey, + required String? bucketingKey, + required List flagSets, + Map attributes = const {}}) { + throw UnimplementedError(); + } + Future> getAllAttributes( {required String matchingKey, required String? bucketingKey}) { throw UnimplementedError(); diff --git a/splitio_platform_interface/pubspec.yaml b/splitio_platform_interface/pubspec.yaml index 0a73b0f..7869a30 100644 --- a/splitio_platform_interface/pubspec.yaml +++ b/splitio_platform_interface/pubspec.yaml @@ -2,7 +2,7 @@ name: splitio_platform_interface description: A common platform interface for the splitio plugin. # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 1.3.0 +version: 1.4.0 repository: https://github.com/splitio/flutter-sdk-plugin/tree/main/splitio_platform_interface environment: diff --git a/splitio_platform_interface/test/method_channel_platform_test.dart b/splitio_platform_interface/test/method_channel_platform_test.dart index d03c4f5..5dceddf 100644 --- a/splitio_platform_interface/test/method_channel_platform_test.dart +++ b/splitio_platform_interface/test/method_channel_platform_test.dart @@ -33,8 +33,12 @@ void main() { case 'getTreatment': return ''; case 'getTreatments': + case 'getTreatmentsByFlagSet': + case 'getTreatmentsByFlagSets': return {'split1': 'on', 'split2': 'off'}; case 'getTreatmentsWithConfig': + case 'getTreatmentsWithConfigByFlagSet': + case 'getTreatmentsWithConfigByFlagSets': return { 'split1': {'treatment': 'on', 'config': null}, 'split2': {'treatment': 'off', 'config': null} @@ -185,6 +189,130 @@ void main() { 'attributes': {'attr1': true} }); }); + + test('getTreatmentsByFlagSet without attributes', () async { + _platform.getTreatmentsByFlagSet( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSet: 'set_1'); + + expect(methodName, 'getTreatmentsByFlagSet'); + expect(methodArguments, { + 'flagSet': 'set_1', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {} + }); + }); + + test('getTreatmentsByFlagSet with attributes', () async { + _platform.getTreatmentsByFlagSet( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSet: 'set_1', + attributes: {'attr1': true}); + + expect(methodName, 'getTreatmentsByFlagSet'); + expect(methodArguments, { + 'flagSet': 'set_1', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {'attr1': true} + }); + }); + + test('getTreatmentsByFlagSets without attributes', () async { + _platform.getTreatmentsByFlagSets( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSets: ['set_1', 'set_2']); + + expect(methodName, 'getTreatmentsByFlagSets'); + expect(methodArguments, { + 'flagSets': ['set_1', 'set_2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {} + }); + }); + + test('getTreatmentsByFlagSets with attributes', () async { + _platform.getTreatmentsByFlagSets( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSets: ['set_1', 'set_2'], + attributes: {'attr1': true}); + + expect(methodName, 'getTreatmentsByFlagSets'); + expect(methodArguments, { + 'flagSets': ['set_1', 'set_2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {'attr1': true} + }); + }); + + test('getTreatmentsWithConfigByFlagSet without attributes', () async { + _platform.getTreatmentsWithConfigByFlagSet( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSet: 'set_1'); + + expect(methodName, 'getTreatmentsWithConfigByFlagSet'); + expect(methodArguments, { + 'flagSet': 'set_1', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {} + }); + }); + + test('getTreatmentsWithConfigByFlagSet with attributes', () async { + _platform.getTreatmentsWithConfigByFlagSet( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSet: 'set_1', + attributes: {'attr1': true}); + + expect(methodName, 'getTreatmentsWithConfigByFlagSet'); + expect(methodArguments, { + 'flagSet': 'set_1', + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {'attr1': true} + }); + }); + + test('getTreatmentsWithConfigByFlagSets without attributes', () async { + _platform.getTreatmentsWithConfigByFlagSets( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSets: ['set_1', 'set_2']); + + expect(methodName, 'getTreatmentsWithConfigByFlagSets'); + expect(methodArguments, { + 'flagSets': ['set_1', 'set_2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {} + }); + }); + + test('getTreatmentsWithConfigByFlagSets with attributes', () async { + _platform.getTreatmentsWithConfigByFlagSets( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSets: ['set_1', 'set_2'], + attributes: {'attr1': true}); + + expect(methodName, 'getTreatmentsWithConfigByFlagSets'); + expect(methodArguments, { + 'flagSets': ['set_1', 'set_2'], + 'matchingKey': 'matching-key', + 'bucketingKey': 'bucketing-key', + 'attributes': {'attr1': true} + }); + }); }); group('track', () { diff --git a/splitio_platform_interface/test/split_result_test.dart b/splitio_platform_interface/test/split_result_test.dart new file mode 100644 index 0000000..a69928a --- /dev/null +++ b/splitio_platform_interface/test/split_result_test.dart @@ -0,0 +1,20 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:splitio_platform_interface/split_result.dart'; + +void main() { + test('result with config', () { + const result = SplitResult('on', 'config'); + + expect(result.treatment, 'on'); + expect(result.config, 'config'); + expect(result.toString(), '{"treatment": "on", config: "config"}'); + }); + + test('result with null config', () { + const result = SplitResult('on', null); + + expect(result.treatment, 'on'); + expect(result.config, null); + expect(result.toString(), '{"treatment": "on", config: null}'); + }); +} diff --git a/splitio_platform_interface/test/split_sync_config_test.dart b/splitio_platform_interface/test/split_sync_config_test.dart new file mode 100644 index 0000000..254dd0c --- /dev/null +++ b/splitio_platform_interface/test/split_sync_config_test.dart @@ -0,0 +1,20 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:splitio_platform_interface/split_sync_config.dart'; + +void main() { + test('names and prefixes can be added simultaneously', () { + var syncConfig = SyncConfig(names: ['name1'], prefixes: ['prefix1']); + + expect(syncConfig.names, ['name1']); + expect(syncConfig.prefixes, ['prefix1']); + expect(syncConfig.sets, []); + }); + + test('flagSets constructor allows only sets', () { + var syncConfig = SyncConfig.flagSets(['set1','set2']); + + expect(syncConfig.names, []); + expect(syncConfig.prefixes, []); + expect(syncConfig.sets, ['set1','set2']); + }); +} diff --git a/splitio_platform_interface/test/split_view_test.dart b/splitio_platform_interface/test/split_view_test.dart index 0268b42..9a5aee1 100644 --- a/splitio_platform_interface/test/split_view_test.dart +++ b/splitio_platform_interface/test/split_view_test.dart @@ -22,6 +22,8 @@ void main() { 'configs': {'yes': '{"abc"}', 'no': '"wasd"'}, 'changeNumber': 156246, 'trafficType': 'default', + 'defaultTreatment': 'on', + 'sets': ['set1', 'set2'], }); expect(splitView?.name, 'my_split'); @@ -30,5 +32,7 @@ void main() { expect(splitView?.configs, {'yes': '{"abc"}', 'no': '"wasd"'}); expect(splitView?.changeNumber, 156246); expect(splitView?.trafficType, 'default'); + expect(splitView?.defaultTreatment, 'on'); + expect(splitView?.sets, ['set1', 'set2']); }); } diff --git a/splitio_platform_interface/test/splitio_configuration_test.dart b/splitio_platform_interface/test/splitio_configuration_test.dart index 0e22f77..9921f57 100644 --- a/splitio_platform_interface/test/splitio_configuration_test.dart +++ b/splitio_platform_interface/test/splitio_configuration_test.dart @@ -3,7 +3,7 @@ import 'package:splitio_platform_interface/split_configuration.dart'; import 'package:splitio_platform_interface/split_sync_config.dart'; void main() { - test('valuesAreMappedCorrectly', () async { + test('values are mapped correctly', () async { final SplitConfiguration config = SplitConfiguration( eventFlushInterval: 2000, eventsPerPush: 300, @@ -65,10 +65,21 @@ void main() { expect(config.configurationMap['readyTimeout'], 1); }); - test('noSpecialValuesLeavesMapEmpty', () async { + test('no special values leaves map empty', () async { final SplitConfiguration config = SplitConfiguration(); expect(config.configurationMap.length, 1); expect(config.configurationMap['readyTimeout'], 10); }); + + test('sets values are mapped correctly', () async { + final SplitConfiguration config = SplitConfiguration( + syncConfig: SyncConfig.flagSets(['one', 'two']), + ); + + expect(config.configurationMap['syncConfig']['syncConfigNames'], []); + expect(config.configurationMap['syncConfig']['syncConfigPrefixes'], []); + expect(config.configurationMap['syncConfig']['syncConfigFlagSets'], + ['one', 'two']); + }); }