From 7e4809a2258012264a7857d598b68fe60d0a06fc Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Fri, 10 Mar 2023 11:14:58 -0500 Subject: [PATCH 01/39] [FSSDK-8962] Update branding names --- README.md | 6 ++---- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5995611..dd9e842 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,8 @@ [![Pub](https://img.shields.io/pub/v/optimizely_flutter_sdk.svg)](https://pub.dev/packages/optimizely_flutter_sdk) [![Coverage Status](https://coveralls.io/repos/github/optimizely/optimizely-flutter-sdk/badge.svg?branch=master)](https://coveralls.io/github/optimizely/optimizely-flutter-sdk?branch=master) -This project is a starting point for a Flutter -[plug-in package](https://flutter.dev/developing-packages/), -a specialized package that includes platform-specific implementation code for -Android and/or iOS. +This project is the official release for a Flutter +[plug-in package](https://flutter.dev/developing-packages/) to support Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts. It is a specialized package that includes platform-specific implementation code for Android and/or iOS. ## Getting Started diff --git a/pubspec.yaml b/pubspec.yaml index ac0b6f6..a7ad36a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: optimizely_flutter_sdk -description: This repository houses the Flutter SDK for use with Optimizely Full Stack and Optimizely Rollouts. +description: This repository houses the Flutter SDK for use with Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts. version: 1.0.0-beta homepage: https://github.com/optimizely/optimizely-flutter-sdk From af6cf76476d32a7b0d0683e3be2adf15915812c5 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Fri, 10 Mar 2023 12:44:49 -0500 Subject: [PATCH 02/39] [FSSDK-8962] Add CONTRIBUTING.md --- CONTRIBUTING.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..1fd41f7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,45 @@ +# Contributing to the Optimizely Flutter SDK +We welcome contributions and feedback! All contributors must sign our [Contributor License Agreement (CLA)](https://docs.google.com/a/optimizely.com/forms/d/e/1FAIpQLSf9cbouWptIpMgukAKZZOIAhafvjFCV8hS00XJLWQnWDFtwtA/viewform) to be eligible to contribute. Please read the [README](README.md) to set up your development environment, then read the guidelines below for information on submitting your code. + +## Development process + +1. Fork the repository and create your branch from master. +2. Please follow the [commit message guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-guidelines) for each commit message. +3. Make sure to add tests! +4. `git push` your changes to GitHub. +5. Open a PR from your fork into the master branch of the original repo. +6. Make sure that all unit tests are passing and that there are no merge conflicts between your branch and `master`. +7. Open a pull request from `YOUR_NAME/branch_name` to `master`. +8. A repository maintainer will review your pull request and, if all goes well, squash and merge it! + +## Pull request acceptance criteria + +* **All code must have test coverage.** We use Open Cover. Changes in functionality should have accompanying unit tests. Bug fixes should have accompanying regression tests. +* Tests are located in `OptimizelySDK.Tests` with one file per class. + +## License + +All contributions are under the CLA mentioned above. For this project, Optimizely uses the Apache 2.0 license, and so asks that by contributing your code, you agree to license your contribution under the terms of the [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0). Your contributions should also include the following header: + +``` +/** + * Copyright YEAR, Optimizely, Inc. and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +``` + +The YEAR above should be the year of the contribution. If work on the file has been done over multiple years, list each year in the section above. Example: Optimizely writes the file and releases it in 2014. No changes are made in 2015. Change made in 2016. YEAR should be �2014, 2016�. + +## Contact +If you have questions, please contact developers@optimizely.com. \ No newline at end of file From 3587b091e3bf21f2bc16f4c7271fc7fb243e863c Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Fri, 10 Mar 2023 12:46:50 -0500 Subject: [PATCH 03/39] [FSSDK-8962] Update README.md --- README.md | 71 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index dd9e842..0f45768 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,15 @@ [![Pub](https://img.shields.io/pub/v/optimizely_flutter_sdk.svg)](https://pub.dev/packages/optimizely_flutter_sdk) [![Coverage Status](https://coveralls.io/repos/github/optimizely/optimizely-flutter-sdk/badge.svg?branch=master)](https://coveralls.io/github/optimizely/optimizely-flutter-sdk?branch=master) -This project is the official release for a Flutter -[plug-in package](https://flutter.dev/developing-packages/) to support Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts. It is a specialized package that includes platform-specific implementation code for Android and/or iOS. +This repository houses the Flutter SDK for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy). -## Getting Started +Optimizely Feature Experimentation is an A/B testing and feature management tool for product development teams that enables you to experiment at every step. Using Optimizely Feature Experimentation allows for every feature on your roadmap to be an opportunity to discover hidden insights. Learn more at [Optimizely.com](https://www.optimizely.com/products/experiment/feature-experimentation/), or see the [developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/welcome). -### Using the SDK -Refer to the [Flutter SDK developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/install-sdk-flutter) for instructions on getting started with using the SDK. +Optimizely Rollouts is [free feature flags](https://www.optimizely.com/free-feature-flagging/) for development teams. You can easily roll out and roll back features in any application without code deploys, mitigating risk for every feature on your roadmap. + +## Get Started + +Refer to the [Flutter SDK's developer documentation](https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/flutter-sdk) for detailed instructions on getting started with using the SDK. ### Requirements @@ -22,23 +24,27 @@ On the iOS platform, the SDK requires a minimum version of 10.0. Other Flutter platforms are not currently supported by this SDK. -### Installing the SDK +### Install the SDK + +### Packages To add the flutter-sdk to your project dependencies, include the following in your app's pubspec.yaml: ``` - optimizely_flutter_sdk: ^1.0.0-beta + optimizely_flutter_sdk: ^1.0.1-beta ``` -Then, import the package in your application code: +Then run ``` - import 'package:optimizely_flutter_sdk/optimizely_flutter_sdk.dart'; +flutter pub get ``` -## Usage +### Configuration options + +{{ List any configuration options, if the SDK has any (for example, Agent's configuration can be overwritten by a yaml config file) }} -### Instantiation +## Use the Flutter SDK A sample code for SDK initialization: @@ -47,6 +53,14 @@ A sample code for SDK initialization: var response = await flutterSDK.initializeClient(); ``` +### Initialization + +Then, import the package in your application code: + +``` + import 'package:optimizely_flutter_sdk/optimizely_flutter_sdk.dart'; +``` + ### Feature Rollouts ``` import 'package:optimizely_flutter_sdk/optimizely_flutter_sdk.dart'; @@ -66,7 +80,40 @@ A sample code for SDK initialization: var decideReponse = await user!.decide("binary_feature"); ``` -## Testing in Terminal +## SDK Development + +### Unit Tests 1. To run [unit tests](https://docs.flutter.dev/cookbook/testing/unit/introduction) using terminal, simply use the following command: `flutter test test/optimizely_flutter_sdk_test.dart` + +### Contributing + +Please see [CONTRIBUTING](CONTRIBUTING.md). + + +### Other Optimizely SDKs + +- Agent - https://github.com/optimizely/agent + +- Android - https://github.com/optimizely/android-sdk + +- C# - https://github.com/optimizely/csharp-sdk + +- Flutter - https://github.com/optimizely/optimizely-flutter-sdk + +- Go - https://github.com/optimizely/go-sdk + +- Java - https://github.com/optimizely/java-sdk + +- JavaScript - https://github.com/optimizely/javascript-sdk + +- PHP - https://github.com/optimizely/php-sdk + +- Python - https://github.com/optimizely/python-sdk + +- React - https://github.com/optimizely/react-sdk + +- Ruby - https://github.com/optimizely/ruby-sdk + +- Swift - https://github.com/optimizely/swift-sdk From bc677f565032c091cc6ccbcd9c163750dda3b9d8 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Fri, 10 Mar 2023 13:09:35 -0500 Subject: [PATCH 04/39] [FSSDK-8962] PR review requested changes --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 0f45768..785df04 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # Optimizely Flutter SDK [![Apache 2.0](https://img.shields.io/github/license/nebula-plugins/gradle-extra-configurations-plugin.svg)](https://www.apache.org/licenses/LICENSE-2.0) [![Build Status](https://github.com/optimizely/optimizely-flutter-sdk/actions/workflows/flutter.yml/badge.svg?branch=master)](https://github.com/optimizely/optimizely-flutter-sdk/actions) -[![Pub](https://img.shields.io/pub/v/optimizely_flutter_sdk.svg)](https://pub.dev/packages/optimizely_flutter_sdk) -[![Coverage Status](https://coveralls.io/repos/github/optimizely/optimizely-flutter-sdk/badge.svg?branch=master)](https://coveralls.io/github/optimizely/optimizely-flutter-sdk?branch=master) + This repository houses the Flutter SDK for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy). @@ -40,10 +39,6 @@ Then run flutter pub get ``` -### Configuration options - -{{ List any configuration options, if the SDK has any (for example, Agent's configuration can be overwritten by a yaml config file) }} - ## Use the Flutter SDK A sample code for SDK initialization: From 81d467c819b2839712772f7cb4ebaf811a86ac73 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Fri, 10 Mar 2023 13:13:11 -0500 Subject: [PATCH 05/39] [FSSDK-8962] Update Pod::Spec homepage link --- ios/optimizely_flutter_sdk.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/optimizely_flutter_sdk.podspec b/ios/optimizely_flutter_sdk.podspec index 195d857..15d0f02 100644 --- a/ios/optimizely_flutter_sdk.podspec +++ b/ios/optimizely_flutter_sdk.podspec @@ -7,7 +7,7 @@ Pod::Spec.new do |s| s.name = 'optimizely_flutter_sdk' s.version = '0.0.1' s.summary = 'Optimizely experiment framework for iOS' - s.homepage = "https://docs.developers.optimizely.com/full-stack/docs" + s.homepage = "https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs" s.license = { :type => "Apache License, Version 2.0", :file => "../LICENSE" } s.author = { "Optimizely" => "support@optimizely.com" } s.source = { :path => '.' } From a990444b805b845ecd188eb30579be1ce09c90fb Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Fri, 10 Mar 2023 13:18:52 -0500 Subject: [PATCH 06/39] [FSSDK-8962] Update CONTRIBUTING.md --- CONTRIBUTING.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1fd41f7..f25fd55 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ We welcome contributions and feedback! All contributors must sign our [Contribut ## Pull request acceptance criteria -* **All code must have test coverage.** We use Open Cover. Changes in functionality should have accompanying unit tests. Bug fixes should have accompanying regression tests. +* **All code must have test coverage.** Changes in functionality should have accompanying unit tests. Bug fixes should have accompanying regression tests. * Tests are located in `OptimizelySDK.Tests` with one file per class. ## License @@ -22,21 +22,21 @@ We welcome contributions and feedback! All contributors must sign our [Contribut All contributions are under the CLA mentioned above. For this project, Optimizely uses the Apache 2.0 license, and so asks that by contributing your code, you agree to license your contribution under the terms of the [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0). Your contributions should also include the following header: ``` -/** - * Copyright YEAR, Optimizely, Inc. and contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/// ************************************************************************** +/// Copyright YEAR, Optimizely, Inc. and contributors * +/// * +/// Licensed under the Apache License, Version 2.0 (the "License"); * +/// you may not use this file except in compliance with the License. * +/// You may obtain a copy of the License at * +/// * +/// https://www.apache.org/licenses/LICENSE-2.0 * +/// * +/// Unless required by applicable law or agreed to in writing, software * +/// distributed under the License is distributed on an "AS IS" BASIS, * +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +/// See the License for the specific language governing permissions and * +/// limitations under the License. * +///**************************************************************************/ ``` The YEAR above should be the year of the contribution. If work on the file has been done over multiple years, list each year in the section above. Example: Optimizely writes the file and releases it in 2014. No changes are made in 2015. Change made in 2016. YEAR should be �2014, 2016�. From bad8c5d92c1f57eb310031caed53ee89671cdcb5 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Fri, 10 Mar 2023 13:29:50 -0500 Subject: [PATCH 07/39] [FSSDK-8962] Restore & reorder badges --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 785df04..fd489c5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # Optimizely Flutter SDK -[![Apache 2.0](https://img.shields.io/github/license/nebula-plugins/gradle-extra-configurations-plugin.svg)](https://www.apache.org/licenses/LICENSE-2.0) +[![Pub Version](https://img.shields.io/pub/v/optimizely_flutter_sdk?color=blueviolet)](https://pub.dev/packages/optimizely_flutter_sdk) [![Build Status](https://github.com/optimizely/optimizely-flutter-sdk/actions/workflows/flutter.yml/badge.svg?branch=master)](https://github.com/optimizely/optimizely-flutter-sdk/actions) - +[![Coverage Status](https://coveralls.io/repos/github/optimizely/optimizely-flutter-sdk/badge.svg?branch=master)](https://coveralls.io/github/optimizely/optimizely-flutter-sdk?branch=master) +[![Pub](https://img.shields.io/pub/v/optimizely_flutter_sdk.svg)](https://pub.dev/packages/optimizely_flutter_sdk) +[![Apache 2.0](https://img.shields.io/github/license/nebula-plugins/gradle-extra-configurations-plugin.svg)](https://www.apache.org/licenses/LICENSE-2.0) This repository houses the Flutter SDK for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy). From aa5ae01e4eee5185c9a915216d5bf7a5c6f713e4 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Fri, 10 Mar 2023 13:34:25 -0500 Subject: [PATCH 08/39] [FSSDK-8962] Remove excess H3; Add code typing --- README.md | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index fd489c5..5439e6c 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,6 @@ Other Flutter platforms are not currently supported by this SDK. ### Install the SDK -### Packages - To add the flutter-sdk to your project dependencies, include the following in your app's pubspec.yaml: ``` @@ -37,15 +35,15 @@ To add the flutter-sdk to your project dependencies, include the following in yo Then run -``` -flutter pub get +```bash + flutter pub get ``` ## Use the Flutter SDK A sample code for SDK initialization: -``` +```dart var flutterSDK = OptimizelyFlutterSdk("my_sdk_key"); var response = await flutterSDK.initializeClient(); ``` @@ -54,14 +52,12 @@ A sample code for SDK initialization: Then, import the package in your application code: -``` +```dart import 'package:optimizely_flutter_sdk/optimizely_flutter_sdk.dart'; ``` ### Feature Rollouts -``` - import 'package:optimizely_flutter_sdk/optimizely_flutter_sdk.dart'; - +```dart // Also supports eventOptions, datafilePeriodicDownloadInterval, datafileHostOptions and defaultDecideOptions var flutterSDK = OptimizelyFlutterSdk("my_sdk_key"); @@ -81,8 +77,11 @@ Then, import the package in your application code: ### Unit Tests -1. To run [unit tests](https://docs.flutter.dev/cookbook/testing/unit/introduction) using terminal, simply use the following command: -`flutter test test/optimizely_flutter_sdk_test.dart` +1. To run [unit tests](https://docs.flutter.dev/cookbook/testing/unit/introduction) using terminal, simply use the following command + +```bash +flutter test test/optimizely_flutter_sdk_test.dart +``` ### Contributing From 8c9c08d7ccadfacfcea840c7c28ca966c7ed204e Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Fri, 10 Mar 2023 13:46:38 -0500 Subject: [PATCH 09/39] [FSSDK-8962] Reorganize sample + other stuff --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 5439e6c..621e0b5 100644 --- a/README.md +++ b/README.md @@ -41,27 +41,28 @@ Then run ## Use the Flutter SDK -A sample code for SDK initialization: +### Initialization + +Import the package in your application code: ```dart - var flutterSDK = OptimizelyFlutterSdk("my_sdk_key"); - var response = await flutterSDK.initializeClient(); + import 'package:optimizely_flutter_sdk/optimizely_flutter_sdk.dart'; ``` -### Initialization - -Then, import the package in your application code: +Instantiate the SDK, adding your SDK Key and initializing the client: ```dart - import 'package:optimizely_flutter_sdk/optimizely_flutter_sdk.dart'; + var flutterSDK = OptimizelyFlutterSdk("your_sdk_key"); + var response = await flutterSDK.initializeClient(); ``` ### Feature Rollouts ```dart + import 'package:optimizely_flutter_sdk/optimizely_flutter_sdk.dart'; + // Also supports eventOptions, datafilePeriodicDownloadInterval, datafileHostOptions and defaultDecideOptions - var flutterSDK = OptimizelyFlutterSdk("my_sdk_key"); + var flutterSDK = OptimizelyFlutterSdk("your_sdk_key"); - // instantiate a client var response = await flutterSDK.initializeClient(); // User attributes are optional and used for targeting and results segmentation @@ -69,7 +70,7 @@ Then, import the package in your application code: "state": "California", "likes_donuts": true }; - var user = await flutterSDK.createUserContext("optimizely end user", attributes); + var user = await flutterSDK.createUserContext("user_id", attributes); var decideReponse = await user!.decide("binary_feature"); ``` @@ -77,7 +78,7 @@ Then, import the package in your application code: ### Unit Tests -1. To run [unit tests](https://docs.flutter.dev/cookbook/testing/unit/introduction) using terminal, simply use the following command +To run [unit tests](https://docs.flutter.dev/cookbook/testing/unit/introduction) using terminal, simply use the following command: ```bash flutter test test/optimizely_flutter_sdk_test.dart @@ -87,7 +88,6 @@ flutter test test/optimizely_flutter_sdk_test.dart Please see [CONTRIBUTING](CONTRIBUTING.md). - ### Other Optimizely SDKs - Agent - https://github.com/optimizely/agent From d8f8ab812653edcf77d176dc50f3ec4be37c9ebc Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Fri, 10 Mar 2023 14:23:28 -0500 Subject: [PATCH 10/39] [FSSDK-8962] Remove build & coverage badges --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 621e0b5..9f6b00a 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # Optimizely Flutter SDK [![Pub Version](https://img.shields.io/pub/v/optimizely_flutter_sdk?color=blueviolet)](https://pub.dev/packages/optimizely_flutter_sdk) -[![Build Status](https://github.com/optimizely/optimizely-flutter-sdk/actions/workflows/flutter.yml/badge.svg?branch=master)](https://github.com/optimizely/optimizely-flutter-sdk/actions) -[![Coverage Status](https://coveralls.io/repos/github/optimizely/optimizely-flutter-sdk/badge.svg?branch=master)](https://coveralls.io/github/optimizely/optimizely-flutter-sdk?branch=master) [![Pub](https://img.shields.io/pub/v/optimizely_flutter_sdk.svg)](https://pub.dev/packages/optimizely_flutter_sdk) [![Apache 2.0](https://img.shields.io/github/license/nebula-plugins/gradle-extra-configurations-plugin.svg)](https://www.apache.org/licenses/LICENSE-2.0) + This repository houses the Flutter SDK for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy). From 6631c4ef1a39dd544768d0538b654b0fa68cb52d Mon Sep 17 00:00:00 2001 From: Mike Chu <104384559+mikechu-optimizely@users.noreply.github.com> Date: Fri, 10 Mar 2023 18:09:20 -0500 Subject: [PATCH 11/39] [FSSDK-8962] Bump version & changelog (#46) --- CHANGELOG.md | 8 +++++++- pubspec.yaml | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a7fb68..ea7efaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Optimizely Flutter SDK Changelog -## [1.0.0-beta] - November 3rd, 2022 +## 1.0.1-beta +March 10, 2022 + +* We updated our README.md and other non-functional code to reflect that this SDK supports both Optimizely Feature Experimentation and Optimizely Full Stack. ([#44](https://github.com/optimizely/optimizely-flutter-sdk/pull/44)). + +## 1.0.0-beta +November 3, 2022 **Beta release of the Optimizely X Full Stack Flutter SDK.** ### New Features diff --git a/pubspec.yaml b/pubspec.yaml index a7ad36a..6944681 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: optimizely_flutter_sdk description: This repository houses the Flutter SDK for use with Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts. -version: 1.0.0-beta +version: 1.0.1-beta homepage: https://github.com/optimizely/optimizely-flutter-sdk environment: From 59e998dbd11e73d7a748f28f44bf14b6fbb813fb Mon Sep 17 00:00:00 2001 From: Yasir Folio3 <39988750+yasirfolio3@users.noreply.github.com> Date: Wed, 3 May 2023 16:36:25 -0400 Subject: [PATCH 12/39] [FSSDK-9029]: Adds Top-level ODP Interface. (#48) --- example/lib/main.dart | 3 +- lib/optimizely_flutter_sdk.dart | 64 +++-- .../get_qualified_segments_response.dart | 32 +++ lib/src/data_objects/get_vuid_response.dart | 31 +++ lib/src/data_objects/sdk_settings.dart | 37 +++ lib/src/optimizely_client_wrapper.dart | 63 ++++- .../user_context/optimizely_user_context.dart | 69 ++++- lib/src/utils/constants.dart | 27 +- lib/src/utils/utils.dart | 10 + test/optimizely_flutter_sdk_test.dart | 263 ++++++++++++++++-- 10 files changed, 550 insertions(+), 49 deletions(-) create mode 100644 lib/src/data_objects/get_qualified_segments_response.dart create mode 100644 lib/src/data_objects/get_vuid_response.dart create mode 100644 lib/src/data_objects/sdk_settings.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 18f89e2..a717223 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -43,7 +43,8 @@ class _MyAppState extends State { var randomUserName = "${rng.nextInt(1000)}"; // Create user context - var userContext = await flutterSDK.createUserContext(randomUserName); + var userContext = + await flutterSDK.createUserContext(userId: randomUserName); // Set attributes response = await userContext!.setAttributes({ diff --git a/lib/optimizely_flutter_sdk.dart b/lib/optimizely_flutter_sdk.dart index d534c9d..775130f 100644 --- a/lib/optimizely_flutter_sdk.dart +++ b/lib/optimizely_flutter_sdk.dart @@ -1,5 +1,5 @@ /// ************************************************************************** -/// Copyright 2022, Optimizely, Inc. and contributors * +/// Copyright 2022-2023, Optimizely, Inc. and contributors * /// * /// Licensed under the Apache License, Version 2.0 (the "License"); * /// you may not use this file except in compliance with the License. * @@ -21,6 +21,8 @@ import 'package:optimizely_flutter_sdk/src/data_objects/activate_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/base_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/datafile_options.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/event_options.dart'; +import 'package:optimizely_flutter_sdk/src/data_objects/get_vuid_response.dart'; +import 'package:optimizely_flutter_sdk/src/data_objects/sdk_settings.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/get_variation_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/optimizely_config_response.dart'; import 'package:optimizely_flutter_sdk/src/optimizely_client_wrapper.dart'; @@ -33,7 +35,7 @@ export 'package:optimizely_flutter_sdk/src/user_context/optimizely_forced_decisi export 'package:optimizely_flutter_sdk/src/user_context/optimizely_decision_context.dart' show OptimizelyDecisionContext; export 'package:optimizely_flutter_sdk/src/user_context/optimizely_user_context.dart' - show OptimizelyUserContext, OptimizelyDecideOption; + show OptimizelyUserContext, OptimizelyDecideOption, OptimizelySegmentOption; export 'package:optimizely_flutter_sdk/src/data_objects/decide_response.dart' show Decision; export 'package:optimizely_flutter_sdk/src/data_objects/track_listener_response.dart' @@ -44,6 +46,8 @@ export 'package:optimizely_flutter_sdk/src/data_objects/logevent_listener_respon show LogEventListenerResponse; export 'package:optimizely_flutter_sdk/src/data_objects/event_options.dart' show EventOptions; +export 'package:optimizely_flutter_sdk/src/data_objects/sdk_settings.dart' + show SDKSettings; export 'package:optimizely_flutter_sdk/src/data_objects/datafile_options.dart' show DatafileHostOptions; @@ -59,17 +63,19 @@ class OptimizelyFlutterSdk { final int _datafilePeriodicDownloadInterval; final Map _datafileHostOptions; final Set _defaultDecideOptions; - OptimizelyFlutterSdk( - this._sdkKey, { - EventOptions eventOptions = const EventOptions(), - int datafilePeriodicDownloadInterval = - 10 * 60, // Default time interval in seconds - Map datafileHostOptions = const {}, - Set defaultDecideOptions = const {}, - }) : _eventOptions = eventOptions, + final SDKSettings _sdkSettings; + OptimizelyFlutterSdk(this._sdkKey, + {EventOptions eventOptions = const EventOptions(), + int datafilePeriodicDownloadInterval = + 10 * 60, // Default time interval in seconds + Map datafileHostOptions = const {}, + Set defaultDecideOptions = const {}, + SDKSettings sdkSettings = const SDKSettings()}) + : _eventOptions = eventOptions, _datafilePeriodicDownloadInterval = datafilePeriodicDownloadInterval, _datafileHostOptions = datafileHostOptions, - _defaultDecideOptions = defaultDecideOptions; + _defaultDecideOptions = defaultDecideOptions, + _sdkSettings = sdkSettings; /// Starts Optimizely SDK (Synchronous) with provided sdkKey. Future initializeClient() async { @@ -78,7 +84,8 @@ class OptimizelyFlutterSdk { _eventOptions, _datafilePeriodicDownloadInterval, _datafileHostOptions, - _defaultDecideOptions); + _defaultDecideOptions, + _sdkSettings); } /// Use the activate method to start an experiment. @@ -140,17 +147,40 @@ class OptimizelyFlutterSdk { return await OptimizelyClientWrapper.getOptimizelyConfig(_sdkKey); } + /// Send an event to the ODP server. + /// + /// Takes [action] The event action name. + /// Optional [type] The event type (default = "fullstack"). + /// Optional [identifiers] A dictionary for identifiers. + /// Optional [data] A dictionary for associated data. The default event data will be added to this data before sending to the ODP server. + /// Returns [BaseResponse] A object containing success result or reason of failure. + Future sendOdpEvent(String action, + {String? type, + Map identifiers = const {}, + Map data = const {}}) async { + return await OptimizelyClientWrapper.sendOdpEvent(_sdkKey, action, + type: type, identifiers: identifiers, data: data); + } + + /// Returns the device vuid. + /// + /// Returns [GetVuidResponse] A object containing device vuid + Future getVuid() async { + return await OptimizelyClientWrapper.getVuid(_sdkKey); + } + /// Creates a context of the user for which decision APIs will be called. /// /// NOTE: A user context will only be created successfully when the SDK is fully configured using initializeClient. /// - /// Takes [userId] the [String] user ID to be used for bucketing. + /// Optional [userId] the [String] user ID to be used for bucketing. + /// The device vuid will be used as an userId when userId is not provided. /// Takes [attributes] An Optional [Map] of attribute names to current user attribute values. /// Returns An [OptimizelyUserContext] associated with this OptimizelyClient. - Future createUserContext(String userId, - [Map attributes = const {}]) async { - return await OptimizelyClientWrapper.createUserContext( - _sdkKey, userId, attributes); + Future createUserContext( + {String? userId, Map attributes = const {}}) async { + return await OptimizelyClientWrapper.createUserContext(_sdkKey, + userId: userId, attributes: attributes); } /// Allows user to remove notification listener using id. diff --git a/lib/src/data_objects/get_qualified_segments_response.dart b/lib/src/data_objects/get_qualified_segments_response.dart new file mode 100644 index 0000000..130c79e --- /dev/null +++ b/lib/src/data_objects/get_qualified_segments_response.dart @@ -0,0 +1,32 @@ +/// ************************************************************************** +/// Copyright 2023, Optimizely, Inc. and contributors * +/// * +/// Licensed under the Apache License, Version 2.0 (the "License"); * +/// you may not use this file except in compliance with the License. * +/// You may obtain a copy of the License at * +/// * +/// http://www.apache.org/licenses/LICENSE-2.0 * +/// * +/// Unless required by applicable law or agreed to in writing, software * +/// distributed under the License is distributed on an "AS IS" BASIS, * +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +/// See the License for the specific language governing permissions and * +/// limitations under the License. * +///**************************************************************************/ + +import 'package:optimizely_flutter_sdk/src/data_objects/base_response.dart'; +import 'package:optimizely_flutter_sdk/src/utils/constants.dart'; + +class GetQualifiedSegmentsResponse extends BaseResponse { + List qualifiedSegments = []; + + GetQualifiedSegmentsResponse(Map json) : super(json) { + if (json[Constants.responseResult] is Map) { + var response = Map.from(json[Constants.responseResult]); + if (response[Constants.qualifiedSegments] is List) { + qualifiedSegments = + List.from(response[Constants.qualifiedSegments]); + } + } + } +} diff --git a/lib/src/data_objects/get_vuid_response.dart b/lib/src/data_objects/get_vuid_response.dart new file mode 100644 index 0000000..4be045b --- /dev/null +++ b/lib/src/data_objects/get_vuid_response.dart @@ -0,0 +1,31 @@ +/// ************************************************************************** +/// Copyright 2023, Optimizely, Inc. and contributors * +/// * +/// Licensed under the Apache License, Version 2.0 (the "License"); * +/// you may not use this file except in compliance with the License. * +/// You may obtain a copy of the License at * +/// * +/// http://www.apache.org/licenses/LICENSE-2.0 * +/// * +/// Unless required by applicable law or agreed to in writing, software * +/// distributed under the License is distributed on an "AS IS" BASIS, * +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +/// See the License for the specific language governing permissions and * +/// limitations under the License. * +///**************************************************************************/ + +import 'package:optimizely_flutter_sdk/src/data_objects/base_response.dart'; +import 'package:optimizely_flutter_sdk/src/utils/constants.dart'; + +class GetVuidResponse extends BaseResponse { + String vuid = ""; + + GetVuidResponse(Map json) : super(json) { + if (json[Constants.responseResult] is Map) { + var response = Map.from(json[Constants.responseResult]); + if (response[Constants.vuid] is String) { + vuid = response[Constants.vuid]; + } + } + } +} diff --git a/lib/src/data_objects/sdk_settings.dart b/lib/src/data_objects/sdk_settings.dart new file mode 100644 index 0000000..412432a --- /dev/null +++ b/lib/src/data_objects/sdk_settings.dart @@ -0,0 +1,37 @@ +/// ************************************************************************** +/// Copyright 2023, Optimizely, Inc. and contributors * +/// * +/// Licensed under the Apache License, Version 2.0 (the "License"); * +/// you may not use this file except in compliance with the License. * +/// You may obtain a copy of the License at * +/// * +/// http://www.apache.org/licenses/LICENSE-2.0 * +/// * +/// Unless required by applicable law or agreed to in writing, software * +/// distributed under the License is distributed on an "AS IS" BASIS, * +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +/// See the License for the specific language governing permissions and * +/// limitations under the License. * +///**************************************************************************/ + +class SDKSettings { + // The maximum size of audience segments cache (optional. default = 100). Set to zero to disable caching. + final int segmentsCacheSize; + // The timeout in seconds of audience segments cache (optional. default = 600). Set to zero to disable timeout. + final int segmentsCacheTimeoutInSecs; + // The timeout in seconds of odp segment fetch (optional. default = 10) - OS default timeout will be used if this is set to zero. + final int timeoutForSegmentFetchInSecs; + // The timeout in seconds of odp event dispatch (optional. default = 10) - OS default timeout will be used if this is set to zero. + final int timeoutForOdpEventInSecs; + // Set this flag to true (default = false) to disable ODP features + final bool disableOdp; + + const SDKSettings({ + this.segmentsCacheSize = 100, // Default segmentsCacheSize + this.segmentsCacheTimeoutInSecs = 600, // Default segmentsCacheTimeoutInSecs + this.timeoutForSegmentFetchInSecs = + 10, // Default timeoutForSegmentFetchInSecs + this.timeoutForOdpEventInSecs = 10, // Default timeoutForOdpEventInSecs + this.disableOdp = false, // Default disableOdp + }); +} diff --git a/lib/src/optimizely_client_wrapper.dart b/lib/src/optimizely_client_wrapper.dart index 0d7737d..e26b74f 100644 --- a/lib/src/optimizely_client_wrapper.dart +++ b/lib/src/optimizely_client_wrapper.dart @@ -1,5 +1,5 @@ /// ************************************************************************** -/// Copyright 2022, Optimizely, Inc. and contributors * +/// Copyright 2022-2023, Optimizely, Inc. and contributors * /// * /// Licensed under the Apache License, Version 2.0 (the "License"); * /// you may not use this file except in compliance with the License. * @@ -22,6 +22,7 @@ import 'package:optimizely_flutter_sdk/src/data_objects/activate_listener_respon import 'package:optimizely_flutter_sdk/src/data_objects/activate_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/base_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/get_variation_response.dart'; +import 'package:optimizely_flutter_sdk/src/data_objects/get_vuid_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/optimizely_config_response.dart'; import 'package:optimizely_flutter_sdk/src/utils/constants.dart'; import 'package:optimizely_flutter_sdk/src/utils/utils.dart'; @@ -59,7 +60,8 @@ class OptimizelyClientWrapper { EventOptions eventOptions, int datafilePeriodicDownloadInterval, Map datafileHostOptions, - Set defaultDecideOptions) async { + Set defaultDecideOptions, + SDKSettings sdkSettings) async { _channel.setMethodCallHandler(methodCallHandler); final convertedOptions = Utils.convertDecideOptions(defaultDecideOptions); Map requestDict = { @@ -72,6 +74,18 @@ class OptimizelyClientWrapper { Constants.eventMaxQueueSize: eventOptions.maxQueueSize, }; + // Odp Request params + Map optimizelySdkSettings = { + Constants.segmentsCacheSize: sdkSettings.segmentsCacheSize, + Constants.segmentsCacheTimeoutInSecs: + sdkSettings.segmentsCacheTimeoutInSecs, + Constants.timeoutForSegmentFetchInSecs: + sdkSettings.timeoutForSegmentFetchInSecs, + Constants.timeoutForOdpEventInSecs: sdkSettings.timeoutForOdpEventInSecs, + Constants.disableOdp: sdkSettings.disableOdp, + }; + requestDict[Constants.optimizelySdkSettings] = optimizelySdkSettings; + // clearing notification listeners, if they are mapped to the same sdkKey. activateCallbacksById.remove(sdkKey); decisionCallbacksById.remove(sdkKey); @@ -164,6 +178,35 @@ class OptimizelyClientWrapper { return OptimizelyConfigResponse(result); } + /// Send an event to the ODP server. + static Future sendOdpEvent(String sdkKey, String action, + {String? type, + Map identifiers = const {}, + Map data = const {}}) async { + Map request = { + Constants.sdkKey: sdkKey, + Constants.action: action, + Constants.identifiers: identifiers, + Constants.data: Utils.convertToTypedMap(data) + }; + if (type != null) { + request[Constants.type] = type; + } + + final result = Map.from( + await _channel.invokeMethod(Constants.sendOdpEventMethod, request)); + return BaseResponse(result); + } + + /// Returns the device vuid (read only) + static Future getVuid(String sdkKey) async { + final result = Map.from( + await _channel.invokeMethod(Constants.getVuidMethod, { + Constants.sdkKey: sdkKey, + })); + return GetVuidResponse(result); + } + /// Remove notification listener by notification id. static Future removeNotificationListener( String sdkKey, int id) async { @@ -217,15 +260,17 @@ class OptimizelyClientWrapper { /// Creates a context of the user for which decision APIs will be called. /// /// A user context will only be created successfully when the SDK is fully configured using initializeClient. - static Future createUserContext( - String sdkKey, String userId, - [Map attributes = const {}]) async { - final result = Map.from( - await _channel.invokeMethod(Constants.createUserContextMethod, { + static Future createUserContext(String sdkKey, + {String? userId, Map attributes = const {}}) async { + Map request = { Constants.sdkKey: sdkKey, - Constants.userId: userId, Constants.attributes: Utils.convertToTypedMap(attributes) - })); + }; + if (userId != null) { + request[Constants.userId] = userId; + } + final result = Map.from(await _channel.invokeMethod( + Constants.createUserContextMethod, request)); if (result[Constants.responseSuccess] == true) { final response = diff --git a/lib/src/user_context/optimizely_user_context.dart b/lib/src/user_context/optimizely_user_context.dart index 4f261d7..906951f 100644 --- a/lib/src/user_context/optimizely_user_context.dart +++ b/lib/src/user_context/optimizely_user_context.dart @@ -1,5 +1,5 @@ /// ************************************************************************** -/// Copyright 2022, Optimizely, Inc. and contributors * +/// Copyright 2022-2023, Optimizely, Inc. and contributors * /// * /// Licensed under the Apache License, Version 2.0 (the "License"); * /// you may not use this file except in compliance with the License. * @@ -21,6 +21,7 @@ import 'package:optimizely_flutter_sdk/src/data_objects/decide_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/get_attributes_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/get_forced_decision_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/get_user_id_response.dart'; +import 'package:optimizely_flutter_sdk/src/data_objects/get_qualified_segments_response.dart'; import 'package:optimizely_flutter_sdk/src/utils/constants.dart'; import 'package:optimizely_flutter_sdk/src/utils/utils.dart'; @@ -43,6 +44,16 @@ enum OptimizelyDecideOption { excludeVariables } +/// Options controlling audience segments. +/// +enum OptimizelySegmentOption { + /// ignore odp cache (save/lookup) + ignoreCache, + + /// resets odp cache + resetCache, +} + /// An object for user contexts that the SDK will use to make decisions for. /// class OptimizelyUserContext { @@ -86,6 +97,62 @@ class OptimizelyUserContext { return BaseResponse(result); } + /// Returns [GetQualifiedSegmentsResponse] object containing an array of segment names that the user is qualified for. + Future getQualifiedSegments() async { + final result = Map.from( + await _channel.invokeMethod(Constants.getQualifiedSegmentsMethod, { + Constants.sdkKey: _sdkKey, + Constants.userContextId: _userContextId, + })); + return GetQualifiedSegmentsResponse(result); + } + + /// Sets qualified segments for the user context. + /// + /// Takes [qualifiedSegments] A [List] of strings specifying qualified segments for the user. + /// Returns [BaseResponse] + Future setQualifiedSegments( + List qualifiedSegments) async { + final result = Map.from( + await _channel.invokeMethod(Constants.setQualifiedSegmentsMethod, { + Constants.sdkKey: _sdkKey, + Constants.userContextId: _userContextId, + Constants.qualifiedSegments: qualifiedSegments + })); + return BaseResponse(result); + } + + /// Checks if the user is qualified for the given segment. + /// + /// Takes [segment] The segment name to check qualification for. + /// Returns [BaseResponse] + Future isQualifiedFor(String segment) async { + final result = Map.from( + await _channel.invokeMethod(Constants.isQualifiedForMethod, { + Constants.sdkKey: _sdkKey, + Constants.userContextId: _userContextId, + Constants.segment: segment + })); + return BaseResponse(result); + } + + /// Fetch all qualified segments for the user context. + /// + /// The segments fetched will be saved in **qualifiedSegments** and can be accessed any time using **getQualifiedSegments**. + /// On failure, **qualifiedSegments** will be nil and an error will be returned. + /// Optional [options] A set of [OptimizelySegmentOption] for fetching qualified segments. + /// Returns [BaseResponse] + Future fetchQualifiedSegments( + [Set options = const {}]) async { + final result = Map.from( + await _channel.invokeMethod(Constants.fetchQualifiedSegmentsMethod, { + Constants.sdkKey: _sdkKey, + Constants.userContextId: _userContextId, + Constants.optimizelySegmentOption: Utils.convertSegmentOptions(options), + })); + return BaseResponse(result); + } + /// Tracks an event. /// /// Takes [eventKey] The event name. diff --git a/lib/src/utils/constants.dart b/lib/src/utils/constants.dart index 07f0f0a..f6a61d5 100644 --- a/lib/src/utils/constants.dart +++ b/lib/src/utils/constants.dart @@ -1,5 +1,5 @@ /// ************************************************************************** -/// Copyright 2022, Optimizely, Inc. and contributors * +/// Copyright 2022-2023, Optimizely, Inc. and contributors * /// * /// Licensed under the Apache License, Version 2.0 (the "License"); * /// you may not use this file except in compliance with the License. * @@ -46,6 +46,15 @@ class Constants { "clearNotificationListeners"; static const String clearAllNotificationListenersMethod = "clearAllNotificationListeners"; + + // Odp Supported Method Names + static const String sendOdpEventMethod = "sendOdpEvent"; + static const String getVuidMethod = "getVuid"; + static const String getQualifiedSegmentsMethod = "getQualifiedSegments"; + static const String setQualifiedSegmentsMethod = "setQualifiedSegments"; + static const String isQualifiedForMethod = "isQualifiedFor"; + static const String fetchQualifiedSegmentsMethod = "fetchQualifiedSegments"; + // Request parameter keys static const String id = "id"; static const String sdkKey = "sdkKey"; @@ -54,8 +63,11 @@ class Constants { static const String experiment = "experiment"; static const String variation = "variation"; static const String userId = "userId"; + static const String vuid = "vuid"; static const String experimentKey = "experimentKey"; static const String attributes = "attributes"; + static const String qualifiedSegments = "qualifiedSegments"; + static const String segment = "segment"; static const String decisionInfo = "decisionInfo"; static const String variables = "variables"; static const String reasons = "reasons"; @@ -69,9 +81,14 @@ class Constants { static const String ruleKey = "ruleKey"; static const String enabled = "enabled"; static const String optimizelyDecideOption = "optimizelyDecideOption"; + static const String optimizelySegmentOption = "optimizelySegmentOption"; + static const String optimizelySdkSettings = "optimizelySdkSettings"; static const String payload = "payload"; static const String value = "value"; static const String type = "type"; + static const String action = "action"; + static const String identifiers = "identifiers"; + static const String data = "data"; static const String callbackIds = "callbackIds"; static const String eventBatchSize = "eventBatchSize"; static const String eventTimeInterval = "eventTimeInterval"; @@ -105,6 +122,14 @@ class Constants { static const String variationsMap = "variationsMap"; static const String variablesMap = "variablesMap"; + // Odp Request params + static const String segmentsCacheSize = "segmentsCacheSize"; + static const String segmentsCacheTimeoutInSecs = "segmentsCacheTimeoutInSecs"; + static const String timeoutForSegmentFetchInSecs = + "timeoutForSegmentFetchInSecs"; + static const String timeoutForOdpEventInSecs = "timeoutForOdpEventInSecs"; + static const String disableOdp = "disableOdp"; + // Response keys static const String responseSuccess = "success"; static const String responseResult = "result"; diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index 58344ae..da36841 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -28,6 +28,11 @@ class Utils { OptimizelyDecideOption.excludeVariables: "excludeVariables", }; + static Map segmentOptions = { + OptimizelySegmentOption.ignoreCache: "ignoreCache", + OptimizelySegmentOption.resetCache: "resetCache", + }; + static Map convertToTypedMap(Map map) { if (map.isEmpty) { return map; @@ -84,4 +89,9 @@ class Utils { Set options) { return options.map((option) => Utils.decideOptions[option]!).toList(); } + + static List convertSegmentOptions( + Set options) { + return options.map((option) => Utils.segmentOptions[option]!).toList(); + } } diff --git a/test/optimizely_flutter_sdk_test.dart b/test/optimizely_flutter_sdk_test.dart index 104355a..fa81adb 100644 --- a/test/optimizely_flutter_sdk_test.dart +++ b/test/optimizely_flutter_sdk_test.dart @@ -1,5 +1,5 @@ /// ************************************************************************** -/// Copyright 2022, Optimizely, Inc. and contributors * +/// Copyright 2022-2023, Optimizely, Inc. and contributors * /// * /// Licensed under the Apache License, Version 2.0 (the "License"); * /// you may not use this file except in compliance with the License. * @@ -35,15 +35,26 @@ void main() { const String ruleKey = "rule_1"; const String variationKey = "var_1"; const String eventKey = "event-key"; + const String segment = "segment"; + const String action = "action1"; + const String type = "type1"; + const String vuid = "vuid_123"; + const Map identifiers = {"abc": "123"}; + const Map data = {"abc": 12345}; const Map attributes = {"abc": 123}; const Map attributes1 = {"abc": 1234}; const Map eventTags = {"abcd": 1234}; + const List qualifiedSegments = ["1", "2", "3"]; + const String userContextId = "123"; // To check if decide options properly reached the native sdk through channel List decideOptions = []; - // To check if event options and datafileOptions reached the native sdk through channel + // To check if event options, datafileOptions and sdkSettings reached the native sdk through channel EventOptions eventOptions = const EventOptions(); + // To check if segment options properly reached the native sdk through channel + List segmentOptions = []; DatafileHostOptions datafileHostOptions = const DatafileHostOptions("", ""); + SDKSettings sdkSettings = const SDKSettings(); int datafilePeriodicDownloadInterval = 0; const MethodChannel channel = MethodChannel("optimizely_flutter_sdk"); @@ -80,6 +91,21 @@ void main() { datafilePeriodicDownloadInterval = methodCall.arguments[Constants.datafilePeriodicDownloadInterval]; + // To Check if sdkSettings were received + var settings = methodCall.arguments[Constants.optimizelySdkSettings]; + if (settings is Map) { + sdkSettings = SDKSettings( + segmentsCacheSize: settings[Constants.segmentsCacheSize], + segmentsCacheTimeoutInSecs: + settings[Constants.segmentsCacheTimeoutInSecs], + timeoutForSegmentFetchInSecs: + settings[Constants.timeoutForSegmentFetchInSecs], + timeoutForOdpEventInSecs: + settings[Constants.timeoutForOdpEventInSecs], + disableOdp: settings[Constants.disableOdp], + ); + } + // Resetting to default for every test datafileHostOptions = const DatafileHostOptions("", ""); if (methodCall.arguments[Constants.datafileHostPrefix] != null && @@ -146,9 +172,13 @@ void main() { }; case Constants.createUserContextMethod: expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); - expect(methodCall.arguments[Constants.userId], equals(userId)); - expect(methodCall.arguments[Constants.attributes]["abc"], - equals(attributes["abc"])); + if (methodCall.arguments[Constants.userId] != null) { + expect(methodCall.arguments[Constants.userId], equals(userId)); + } + if (methodCall.arguments[Constants.attributes]["abc"] != null) { + expect(methodCall.arguments[Constants.attributes]["abc"], + equals(attributes["abc"])); + } expect(methodCall.arguments[Constants.userContextId], isNull); return { Constants.responseSuccess: true, @@ -183,6 +213,60 @@ void main() { return { Constants.responseSuccess: true, }; + case Constants.getQualifiedSegmentsMethod: + expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); + expect(methodCall.arguments[Constants.userContextId], + equals(userContextId)); + return { + Constants.responseSuccess: true, + Constants.responseResult: { + Constants.qualifiedSegments: qualifiedSegments, + }, + }; + case Constants.setQualifiedSegmentsMethod: + expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); + expect(methodCall.arguments[Constants.userContextId], + equals(userContextId)); + expect(methodCall.arguments[Constants.qualifiedSegments], + equals(qualifiedSegments)); + return { + Constants.responseSuccess: true, + }; + case Constants.fetchQualifiedSegmentsMethod: + expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); + expect(methodCall.arguments[Constants.userContextId], + equals(userContextId)); + segmentOptions.addAll(List.from( + methodCall.arguments[Constants.optimizelySegmentOption])); + return { + Constants.responseSuccess: true, + }; + case Constants.isQualifiedForMethod: + expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); + expect(methodCall.arguments[Constants.userContextId], + equals(userContextId)); + expect(methodCall.arguments[Constants.segment], equals(segment)); + return { + Constants.responseSuccess: true, + }; + case Constants.sendOdpEventMethod: + expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); + expect(methodCall.arguments[Constants.userContextId], isNull); + expect(methodCall.arguments[Constants.action], equals(action)); + expect(methodCall.arguments[Constants.type], equals(type)); + expect( + methodCall.arguments[Constants.identifiers], equals(identifiers)); + expect(methodCall.arguments[Constants.data], equals(data)); + return { + Constants.responseSuccess: true, + }; + case Constants.getVuidMethod: + expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); + expect(methodCall.arguments[Constants.userContextId], isNull); + return { + Constants.responseSuccess: true, + Constants.responseResult: {Constants.vuid: vuid}, + }; case Constants.trackEventMethod: expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); expect(methodCall.arguments[Constants.userContextId], @@ -311,12 +395,19 @@ void main() { expect(response.success, isTrue); }); - test("with no eventOptions and no datafileOptions", () async { + test("with no eventOptions, datafileOptions and sdkSettings", () async { // default values const expectedEventOptions = EventOptions(batchSize: 10, timeInterval: 60, maxQueueSize: 10000); debugDefaultTargetPlatformOverride = TargetPlatform.iOS; const expectedDatafileHostOptions = DatafileHostOptions("", ""); + const expectedSDKSettings = SDKSettings( + segmentsCacheSize: 100, + segmentsCacheTimeoutInSecs: 600, + timeoutForSegmentFetchInSecs: 10, + timeoutForOdpEventInSecs: 10, + disableOdp: false, + ); const expectedDatafilePeriodicDownloadInterval = 10 * 60; var sdk = OptimizelyFlutterSdk(testSDKKey); var response = await sdk.initializeClient(); @@ -333,22 +424,40 @@ void main() { equals(expectedDatafileHostOptions.datafileHostPrefix)); expect(datafileHostOptions.datafileHostSuffix, equals(expectedDatafileHostOptions.datafileHostSuffix)); + + expect(sdkSettings.segmentsCacheSize, + equals(expectedSDKSettings.segmentsCacheSize)); + expect(sdkSettings.segmentsCacheTimeoutInSecs, + equals(expectedSDKSettings.segmentsCacheTimeoutInSecs)); + expect(sdkSettings.timeoutForSegmentFetchInSecs, + equals(expectedSDKSettings.timeoutForSegmentFetchInSecs)); + expect(sdkSettings.timeoutForOdpEventInSecs, + equals(expectedSDKSettings.timeoutForOdpEventInSecs)); + expect(sdkSettings.disableOdp, equals(expectedSDKSettings.disableOdp)); debugDefaultTargetPlatformOverride = null; }); - test("with eventOptions and datafileOptions", () async { + test("with eventOptions, datafileOptions and sdkSettings", () async { const expectedEventOptions = EventOptions(batchSize: 20, timeInterval: 30, maxQueueSize: 200); debugDefaultTargetPlatformOverride = TargetPlatform.iOS; const expectedDatafileHostOptions = DatafileHostOptions("123", "456"); const expectedDatafilePeriodicDownloadInterval = 40; + const expectedSDKSettings = SDKSettings( + segmentsCacheSize: 111, + segmentsCacheTimeoutInSecs: 222, + timeoutForSegmentFetchInSecs: 333, + timeoutForOdpEventInSecs: 444, + disableOdp: true, + ); var sdk = OptimizelyFlutterSdk(testSDKKey, eventOptions: expectedEventOptions, datafilePeriodicDownloadInterval: expectedDatafilePeriodicDownloadInterval, datafileHostOptions: { ClientPlatform.iOS: expectedDatafileHostOptions - }); + }, + sdkSettings: expectedSDKSettings); var response = await sdk.initializeClient(); expect(response.success, isTrue); @@ -363,6 +472,16 @@ void main() { equals(expectedDatafileHostOptions.datafileHostPrefix)); expect(datafileHostOptions.datafileHostSuffix, equals(expectedDatafileHostOptions.datafileHostSuffix)); + + expect(sdkSettings.segmentsCacheSize, + equals(expectedSDKSettings.segmentsCacheSize)); + expect(sdkSettings.segmentsCacheTimeoutInSecs, + equals(expectedSDKSettings.segmentsCacheTimeoutInSecs)); + expect(sdkSettings.timeoutForSegmentFetchInSecs, + equals(expectedSDKSettings.timeoutForSegmentFetchInSecs)); + expect(sdkSettings.timeoutForOdpEventInSecs, + equals(expectedSDKSettings.timeoutForOdpEventInSecs)); + expect(sdkSettings.disableOdp, equals(expectedSDKSettings.disableOdp)); debugDefaultTargetPlatformOverride = null; }); @@ -495,7 +614,26 @@ void main() { group("createUserContext()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); + expect(userContext, isNotNull); + }); + + test("should succeed null userId", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + var userContext = await sdk.createUserContext(attributes: attributes); + expect(userContext, isNotNull); + }); + + test("should succeed null attributes", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + var userContext = await sdk.createUserContext(userId: userId); + expect(userContext, isNotNull); + }); + + test("should succeed null userId and attributes", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + var userContext = await sdk.createUserContext(); expect(userContext, isNotNull); }); }); @@ -503,7 +641,8 @@ void main() { group("getUserId()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); var response = await userContext!.getUserId(); expect(response.success, isTrue); @@ -513,7 +652,8 @@ void main() { group("getAttributes()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); var response = await userContext!.getAttributes(); expect(response.success, isTrue); @@ -524,17 +664,93 @@ void main() { group("setAttributes()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); var response = await userContext!.setAttributes(attributes1); expect(response.success, isTrue); }); }); + group("getQualifiedSegments()", () { + test("should succeed", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + var userContext = await sdk.createUserContext(userId: userId); + var response = await userContext!.getQualifiedSegments(); + + expect(response.qualifiedSegments, qualifiedSegments); + }); + }); + + group("setQualifiedSegments()", () { + test("should succeed", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + var userContext = await sdk.createUserContext(userId: userId); + var response = + await userContext!.setQualifiedSegments(qualifiedSegments); + + expect(response.success, isTrue); + }); + }); + + group("isQualifiedFor()", () { + test("should succeed", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + var userContext = await sdk.createUserContext(userId: userId); + var response = await userContext!.isQualifiedFor(segment); + + expect(response.success, isTrue); + }); + }); + + group("fetchQualifiedSegments()", () { + bool assertSegmentOptions( + Set options, List convertedOptions) { + for (var option in options) { + if (!convertedOptions.contains(option.name)) { + return false; + } + } + return true; + } + + test("should succeed", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + var userContext = await sdk.createUserContext(userId: userId); + Set options = { + OptimizelySegmentOption.ignoreCache, + OptimizelySegmentOption.resetCache, + }; + var response = await userContext!.fetchQualifiedSegments(options); + expect(response.success, isTrue); + expect(segmentOptions.length == 2, isTrue); + expect(assertSegmentOptions(options, segmentOptions), isTrue); + }); + }); + + group("sendOdpEvent()", () { + test("should succeed", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + var response = await sdk.sendOdpEvent(action, + type: type, identifiers: identifiers, data: data); + expect(response.success, isTrue); + }); + }); + + group("getVuid()", () { + test("should succeed", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + var response = await sdk.getVuid(); + expect(response.success, isTrue); + expect(response.vuid, equals(vuid)); + }); + }); + group("trackEvent()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); var response = await userContext!.trackEvent(eventKey, eventTags); expect(response.success, isTrue); @@ -569,7 +785,8 @@ void main() { }; var sdk = OptimizelyFlutterSdk(testSDKKey, defaultDecideOptions: defaultDecideOptions); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); var decideKey = "decide-key"; var response = await userContext!.decide(decideKey, options); @@ -588,7 +805,8 @@ void main() { test("decideForKeys should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey, defaultDecideOptions: defaultDecideOptions); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); var decideKeys = ["decide-key-1", "decide-key-2"]; var response = await userContext!.decideForKeys(decideKeys, options); @@ -604,7 +822,8 @@ void main() { test("decideAll() should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey, defaultDecideOptions: defaultDecideOptions); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); var response = await userContext!.decideAll(options); @@ -636,7 +855,8 @@ void main() { group("setForcedDecision()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); var response = await userContext!.setForcedDecision( OptimizelyDecisionContext(flagKey, ruleKey), @@ -649,7 +869,8 @@ void main() { group("getForcedDecision()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); var response = await userContext! .getForcedDecision(OptimizelyDecisionContext(flagKey, ruleKey)); @@ -662,7 +883,8 @@ void main() { group("removeForcedDecision()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); var response = await userContext! .removeForcedDecision(OptimizelyDecisionContext(flagKey, ruleKey)); @@ -674,7 +896,8 @@ void main() { test("removeAllForcedDecisions() should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); var response = await userContext!.removeAllForcedDecisions(); From 8f8fa1bef89c74b55d27ac0df4920d5493b8e398 Mon Sep 17 00:00:00 2001 From: Yasir Folio3 <39988750+yasirfolio3@users.noreply.github.com> Date: Wed, 3 May 2023 16:59:40 -0400 Subject: [PATCH 13/39] Revert "[FSSDK-9029]: Adds Top-level ODP Interface. (#48)" (#49) This reverts commit 59e998dbd11e73d7a748f28f44bf14b6fbb813fb. --- example/lib/main.dart | 3 +- lib/optimizely_flutter_sdk.dart | 64 ++--- .../get_qualified_segments_response.dart | 32 --- lib/src/data_objects/get_vuid_response.dart | 31 --- lib/src/data_objects/sdk_settings.dart | 37 --- lib/src/optimizely_client_wrapper.dart | 63 +---- .../user_context/optimizely_user_context.dart | 69 +---- lib/src/utils/constants.dart | 27 +- lib/src/utils/utils.dart | 10 - test/optimizely_flutter_sdk_test.dart | 263 ++---------------- 10 files changed, 49 insertions(+), 550 deletions(-) delete mode 100644 lib/src/data_objects/get_qualified_segments_response.dart delete mode 100644 lib/src/data_objects/get_vuid_response.dart delete mode 100644 lib/src/data_objects/sdk_settings.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index a717223..18f89e2 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -43,8 +43,7 @@ class _MyAppState extends State { var randomUserName = "${rng.nextInt(1000)}"; // Create user context - var userContext = - await flutterSDK.createUserContext(userId: randomUserName); + var userContext = await flutterSDK.createUserContext(randomUserName); // Set attributes response = await userContext!.setAttributes({ diff --git a/lib/optimizely_flutter_sdk.dart b/lib/optimizely_flutter_sdk.dart index 775130f..d534c9d 100644 --- a/lib/optimizely_flutter_sdk.dart +++ b/lib/optimizely_flutter_sdk.dart @@ -1,5 +1,5 @@ /// ************************************************************************** -/// Copyright 2022-2023, Optimizely, Inc. and contributors * +/// Copyright 2022, Optimizely, Inc. and contributors * /// * /// Licensed under the Apache License, Version 2.0 (the "License"); * /// you may not use this file except in compliance with the License. * @@ -21,8 +21,6 @@ import 'package:optimizely_flutter_sdk/src/data_objects/activate_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/base_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/datafile_options.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/event_options.dart'; -import 'package:optimizely_flutter_sdk/src/data_objects/get_vuid_response.dart'; -import 'package:optimizely_flutter_sdk/src/data_objects/sdk_settings.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/get_variation_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/optimizely_config_response.dart'; import 'package:optimizely_flutter_sdk/src/optimizely_client_wrapper.dart'; @@ -35,7 +33,7 @@ export 'package:optimizely_flutter_sdk/src/user_context/optimizely_forced_decisi export 'package:optimizely_flutter_sdk/src/user_context/optimizely_decision_context.dart' show OptimizelyDecisionContext; export 'package:optimizely_flutter_sdk/src/user_context/optimizely_user_context.dart' - show OptimizelyUserContext, OptimizelyDecideOption, OptimizelySegmentOption; + show OptimizelyUserContext, OptimizelyDecideOption; export 'package:optimizely_flutter_sdk/src/data_objects/decide_response.dart' show Decision; export 'package:optimizely_flutter_sdk/src/data_objects/track_listener_response.dart' @@ -46,8 +44,6 @@ export 'package:optimizely_flutter_sdk/src/data_objects/logevent_listener_respon show LogEventListenerResponse; export 'package:optimizely_flutter_sdk/src/data_objects/event_options.dart' show EventOptions; -export 'package:optimizely_flutter_sdk/src/data_objects/sdk_settings.dart' - show SDKSettings; export 'package:optimizely_flutter_sdk/src/data_objects/datafile_options.dart' show DatafileHostOptions; @@ -63,19 +59,17 @@ class OptimizelyFlutterSdk { final int _datafilePeriodicDownloadInterval; final Map _datafileHostOptions; final Set _defaultDecideOptions; - final SDKSettings _sdkSettings; - OptimizelyFlutterSdk(this._sdkKey, - {EventOptions eventOptions = const EventOptions(), - int datafilePeriodicDownloadInterval = - 10 * 60, // Default time interval in seconds - Map datafileHostOptions = const {}, - Set defaultDecideOptions = const {}, - SDKSettings sdkSettings = const SDKSettings()}) - : _eventOptions = eventOptions, + OptimizelyFlutterSdk( + this._sdkKey, { + EventOptions eventOptions = const EventOptions(), + int datafilePeriodicDownloadInterval = + 10 * 60, // Default time interval in seconds + Map datafileHostOptions = const {}, + Set defaultDecideOptions = const {}, + }) : _eventOptions = eventOptions, _datafilePeriodicDownloadInterval = datafilePeriodicDownloadInterval, _datafileHostOptions = datafileHostOptions, - _defaultDecideOptions = defaultDecideOptions, - _sdkSettings = sdkSettings; + _defaultDecideOptions = defaultDecideOptions; /// Starts Optimizely SDK (Synchronous) with provided sdkKey. Future initializeClient() async { @@ -84,8 +78,7 @@ class OptimizelyFlutterSdk { _eventOptions, _datafilePeriodicDownloadInterval, _datafileHostOptions, - _defaultDecideOptions, - _sdkSettings); + _defaultDecideOptions); } /// Use the activate method to start an experiment. @@ -147,40 +140,17 @@ class OptimizelyFlutterSdk { return await OptimizelyClientWrapper.getOptimizelyConfig(_sdkKey); } - /// Send an event to the ODP server. - /// - /// Takes [action] The event action name. - /// Optional [type] The event type (default = "fullstack"). - /// Optional [identifiers] A dictionary for identifiers. - /// Optional [data] A dictionary for associated data. The default event data will be added to this data before sending to the ODP server. - /// Returns [BaseResponse] A object containing success result or reason of failure. - Future sendOdpEvent(String action, - {String? type, - Map identifiers = const {}, - Map data = const {}}) async { - return await OptimizelyClientWrapper.sendOdpEvent(_sdkKey, action, - type: type, identifiers: identifiers, data: data); - } - - /// Returns the device vuid. - /// - /// Returns [GetVuidResponse] A object containing device vuid - Future getVuid() async { - return await OptimizelyClientWrapper.getVuid(_sdkKey); - } - /// Creates a context of the user for which decision APIs will be called. /// /// NOTE: A user context will only be created successfully when the SDK is fully configured using initializeClient. /// - /// Optional [userId] the [String] user ID to be used for bucketing. - /// The device vuid will be used as an userId when userId is not provided. + /// Takes [userId] the [String] user ID to be used for bucketing. /// Takes [attributes] An Optional [Map] of attribute names to current user attribute values. /// Returns An [OptimizelyUserContext] associated with this OptimizelyClient. - Future createUserContext( - {String? userId, Map attributes = const {}}) async { - return await OptimizelyClientWrapper.createUserContext(_sdkKey, - userId: userId, attributes: attributes); + Future createUserContext(String userId, + [Map attributes = const {}]) async { + return await OptimizelyClientWrapper.createUserContext( + _sdkKey, userId, attributes); } /// Allows user to remove notification listener using id. diff --git a/lib/src/data_objects/get_qualified_segments_response.dart b/lib/src/data_objects/get_qualified_segments_response.dart deleted file mode 100644 index 130c79e..0000000 --- a/lib/src/data_objects/get_qualified_segments_response.dart +++ /dev/null @@ -1,32 +0,0 @@ -/// ************************************************************************** -/// Copyright 2023, Optimizely, Inc. and contributors * -/// * -/// Licensed under the Apache License, Version 2.0 (the "License"); * -/// you may not use this file except in compliance with the License. * -/// You may obtain a copy of the License at * -/// * -/// http://www.apache.org/licenses/LICENSE-2.0 * -/// * -/// Unless required by applicable law or agreed to in writing, software * -/// distributed under the License is distributed on an "AS IS" BASIS, * -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * -/// See the License for the specific language governing permissions and * -/// limitations under the License. * -///**************************************************************************/ - -import 'package:optimizely_flutter_sdk/src/data_objects/base_response.dart'; -import 'package:optimizely_flutter_sdk/src/utils/constants.dart'; - -class GetQualifiedSegmentsResponse extends BaseResponse { - List qualifiedSegments = []; - - GetQualifiedSegmentsResponse(Map json) : super(json) { - if (json[Constants.responseResult] is Map) { - var response = Map.from(json[Constants.responseResult]); - if (response[Constants.qualifiedSegments] is List) { - qualifiedSegments = - List.from(response[Constants.qualifiedSegments]); - } - } - } -} diff --git a/lib/src/data_objects/get_vuid_response.dart b/lib/src/data_objects/get_vuid_response.dart deleted file mode 100644 index 4be045b..0000000 --- a/lib/src/data_objects/get_vuid_response.dart +++ /dev/null @@ -1,31 +0,0 @@ -/// ************************************************************************** -/// Copyright 2023, Optimizely, Inc. and contributors * -/// * -/// Licensed under the Apache License, Version 2.0 (the "License"); * -/// you may not use this file except in compliance with the License. * -/// You may obtain a copy of the License at * -/// * -/// http://www.apache.org/licenses/LICENSE-2.0 * -/// * -/// Unless required by applicable law or agreed to in writing, software * -/// distributed under the License is distributed on an "AS IS" BASIS, * -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * -/// See the License for the specific language governing permissions and * -/// limitations under the License. * -///**************************************************************************/ - -import 'package:optimizely_flutter_sdk/src/data_objects/base_response.dart'; -import 'package:optimizely_flutter_sdk/src/utils/constants.dart'; - -class GetVuidResponse extends BaseResponse { - String vuid = ""; - - GetVuidResponse(Map json) : super(json) { - if (json[Constants.responseResult] is Map) { - var response = Map.from(json[Constants.responseResult]); - if (response[Constants.vuid] is String) { - vuid = response[Constants.vuid]; - } - } - } -} diff --git a/lib/src/data_objects/sdk_settings.dart b/lib/src/data_objects/sdk_settings.dart deleted file mode 100644 index 412432a..0000000 --- a/lib/src/data_objects/sdk_settings.dart +++ /dev/null @@ -1,37 +0,0 @@ -/// ************************************************************************** -/// Copyright 2023, Optimizely, Inc. and contributors * -/// * -/// Licensed under the Apache License, Version 2.0 (the "License"); * -/// you may not use this file except in compliance with the License. * -/// You may obtain a copy of the License at * -/// * -/// http://www.apache.org/licenses/LICENSE-2.0 * -/// * -/// Unless required by applicable law or agreed to in writing, software * -/// distributed under the License is distributed on an "AS IS" BASIS, * -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * -/// See the License for the specific language governing permissions and * -/// limitations under the License. * -///**************************************************************************/ - -class SDKSettings { - // The maximum size of audience segments cache (optional. default = 100). Set to zero to disable caching. - final int segmentsCacheSize; - // The timeout in seconds of audience segments cache (optional. default = 600). Set to zero to disable timeout. - final int segmentsCacheTimeoutInSecs; - // The timeout in seconds of odp segment fetch (optional. default = 10) - OS default timeout will be used if this is set to zero. - final int timeoutForSegmentFetchInSecs; - // The timeout in seconds of odp event dispatch (optional. default = 10) - OS default timeout will be used if this is set to zero. - final int timeoutForOdpEventInSecs; - // Set this flag to true (default = false) to disable ODP features - final bool disableOdp; - - const SDKSettings({ - this.segmentsCacheSize = 100, // Default segmentsCacheSize - this.segmentsCacheTimeoutInSecs = 600, // Default segmentsCacheTimeoutInSecs - this.timeoutForSegmentFetchInSecs = - 10, // Default timeoutForSegmentFetchInSecs - this.timeoutForOdpEventInSecs = 10, // Default timeoutForOdpEventInSecs - this.disableOdp = false, // Default disableOdp - }); -} diff --git a/lib/src/optimizely_client_wrapper.dart b/lib/src/optimizely_client_wrapper.dart index e26b74f..0d7737d 100644 --- a/lib/src/optimizely_client_wrapper.dart +++ b/lib/src/optimizely_client_wrapper.dart @@ -1,5 +1,5 @@ /// ************************************************************************** -/// Copyright 2022-2023, Optimizely, Inc. and contributors * +/// Copyright 2022, Optimizely, Inc. and contributors * /// * /// Licensed under the Apache License, Version 2.0 (the "License"); * /// you may not use this file except in compliance with the License. * @@ -22,7 +22,6 @@ import 'package:optimizely_flutter_sdk/src/data_objects/activate_listener_respon import 'package:optimizely_flutter_sdk/src/data_objects/activate_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/base_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/get_variation_response.dart'; -import 'package:optimizely_flutter_sdk/src/data_objects/get_vuid_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/optimizely_config_response.dart'; import 'package:optimizely_flutter_sdk/src/utils/constants.dart'; import 'package:optimizely_flutter_sdk/src/utils/utils.dart'; @@ -60,8 +59,7 @@ class OptimizelyClientWrapper { EventOptions eventOptions, int datafilePeriodicDownloadInterval, Map datafileHostOptions, - Set defaultDecideOptions, - SDKSettings sdkSettings) async { + Set defaultDecideOptions) async { _channel.setMethodCallHandler(methodCallHandler); final convertedOptions = Utils.convertDecideOptions(defaultDecideOptions); Map requestDict = { @@ -74,18 +72,6 @@ class OptimizelyClientWrapper { Constants.eventMaxQueueSize: eventOptions.maxQueueSize, }; - // Odp Request params - Map optimizelySdkSettings = { - Constants.segmentsCacheSize: sdkSettings.segmentsCacheSize, - Constants.segmentsCacheTimeoutInSecs: - sdkSettings.segmentsCacheTimeoutInSecs, - Constants.timeoutForSegmentFetchInSecs: - sdkSettings.timeoutForSegmentFetchInSecs, - Constants.timeoutForOdpEventInSecs: sdkSettings.timeoutForOdpEventInSecs, - Constants.disableOdp: sdkSettings.disableOdp, - }; - requestDict[Constants.optimizelySdkSettings] = optimizelySdkSettings; - // clearing notification listeners, if they are mapped to the same sdkKey. activateCallbacksById.remove(sdkKey); decisionCallbacksById.remove(sdkKey); @@ -178,35 +164,6 @@ class OptimizelyClientWrapper { return OptimizelyConfigResponse(result); } - /// Send an event to the ODP server. - static Future sendOdpEvent(String sdkKey, String action, - {String? type, - Map identifiers = const {}, - Map data = const {}}) async { - Map request = { - Constants.sdkKey: sdkKey, - Constants.action: action, - Constants.identifiers: identifiers, - Constants.data: Utils.convertToTypedMap(data) - }; - if (type != null) { - request[Constants.type] = type; - } - - final result = Map.from( - await _channel.invokeMethod(Constants.sendOdpEventMethod, request)); - return BaseResponse(result); - } - - /// Returns the device vuid (read only) - static Future getVuid(String sdkKey) async { - final result = Map.from( - await _channel.invokeMethod(Constants.getVuidMethod, { - Constants.sdkKey: sdkKey, - })); - return GetVuidResponse(result); - } - /// Remove notification listener by notification id. static Future removeNotificationListener( String sdkKey, int id) async { @@ -260,17 +217,15 @@ class OptimizelyClientWrapper { /// Creates a context of the user for which decision APIs will be called. /// /// A user context will only be created successfully when the SDK is fully configured using initializeClient. - static Future createUserContext(String sdkKey, - {String? userId, Map attributes = const {}}) async { - Map request = { + static Future createUserContext( + String sdkKey, String userId, + [Map attributes = const {}]) async { + final result = Map.from( + await _channel.invokeMethod(Constants.createUserContextMethod, { Constants.sdkKey: sdkKey, + Constants.userId: userId, Constants.attributes: Utils.convertToTypedMap(attributes) - }; - if (userId != null) { - request[Constants.userId] = userId; - } - final result = Map.from(await _channel.invokeMethod( - Constants.createUserContextMethod, request)); + })); if (result[Constants.responseSuccess] == true) { final response = diff --git a/lib/src/user_context/optimizely_user_context.dart b/lib/src/user_context/optimizely_user_context.dart index 906951f..4f261d7 100644 --- a/lib/src/user_context/optimizely_user_context.dart +++ b/lib/src/user_context/optimizely_user_context.dart @@ -1,5 +1,5 @@ /// ************************************************************************** -/// Copyright 2022-2023, Optimizely, Inc. and contributors * +/// Copyright 2022, Optimizely, Inc. and contributors * /// * /// Licensed under the Apache License, Version 2.0 (the "License"); * /// you may not use this file except in compliance with the License. * @@ -21,7 +21,6 @@ import 'package:optimizely_flutter_sdk/src/data_objects/decide_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/get_attributes_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/get_forced_decision_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/get_user_id_response.dart'; -import 'package:optimizely_flutter_sdk/src/data_objects/get_qualified_segments_response.dart'; import 'package:optimizely_flutter_sdk/src/utils/constants.dart'; import 'package:optimizely_flutter_sdk/src/utils/utils.dart'; @@ -44,16 +43,6 @@ enum OptimizelyDecideOption { excludeVariables } -/// Options controlling audience segments. -/// -enum OptimizelySegmentOption { - /// ignore odp cache (save/lookup) - ignoreCache, - - /// resets odp cache - resetCache, -} - /// An object for user contexts that the SDK will use to make decisions for. /// class OptimizelyUserContext { @@ -97,62 +86,6 @@ class OptimizelyUserContext { return BaseResponse(result); } - /// Returns [GetQualifiedSegmentsResponse] object containing an array of segment names that the user is qualified for. - Future getQualifiedSegments() async { - final result = Map.from( - await _channel.invokeMethod(Constants.getQualifiedSegmentsMethod, { - Constants.sdkKey: _sdkKey, - Constants.userContextId: _userContextId, - })); - return GetQualifiedSegmentsResponse(result); - } - - /// Sets qualified segments for the user context. - /// - /// Takes [qualifiedSegments] A [List] of strings specifying qualified segments for the user. - /// Returns [BaseResponse] - Future setQualifiedSegments( - List qualifiedSegments) async { - final result = Map.from( - await _channel.invokeMethod(Constants.setQualifiedSegmentsMethod, { - Constants.sdkKey: _sdkKey, - Constants.userContextId: _userContextId, - Constants.qualifiedSegments: qualifiedSegments - })); - return BaseResponse(result); - } - - /// Checks if the user is qualified for the given segment. - /// - /// Takes [segment] The segment name to check qualification for. - /// Returns [BaseResponse] - Future isQualifiedFor(String segment) async { - final result = Map.from( - await _channel.invokeMethod(Constants.isQualifiedForMethod, { - Constants.sdkKey: _sdkKey, - Constants.userContextId: _userContextId, - Constants.segment: segment - })); - return BaseResponse(result); - } - - /// Fetch all qualified segments for the user context. - /// - /// The segments fetched will be saved in **qualifiedSegments** and can be accessed any time using **getQualifiedSegments**. - /// On failure, **qualifiedSegments** will be nil and an error will be returned. - /// Optional [options] A set of [OptimizelySegmentOption] for fetching qualified segments. - /// Returns [BaseResponse] - Future fetchQualifiedSegments( - [Set options = const {}]) async { - final result = Map.from( - await _channel.invokeMethod(Constants.fetchQualifiedSegmentsMethod, { - Constants.sdkKey: _sdkKey, - Constants.userContextId: _userContextId, - Constants.optimizelySegmentOption: Utils.convertSegmentOptions(options), - })); - return BaseResponse(result); - } - /// Tracks an event. /// /// Takes [eventKey] The event name. diff --git a/lib/src/utils/constants.dart b/lib/src/utils/constants.dart index f6a61d5..07f0f0a 100644 --- a/lib/src/utils/constants.dart +++ b/lib/src/utils/constants.dart @@ -1,5 +1,5 @@ /// ************************************************************************** -/// Copyright 2022-2023, Optimizely, Inc. and contributors * +/// Copyright 2022, Optimizely, Inc. and contributors * /// * /// Licensed under the Apache License, Version 2.0 (the "License"); * /// you may not use this file except in compliance with the License. * @@ -46,15 +46,6 @@ class Constants { "clearNotificationListeners"; static const String clearAllNotificationListenersMethod = "clearAllNotificationListeners"; - - // Odp Supported Method Names - static const String sendOdpEventMethod = "sendOdpEvent"; - static const String getVuidMethod = "getVuid"; - static const String getQualifiedSegmentsMethod = "getQualifiedSegments"; - static const String setQualifiedSegmentsMethod = "setQualifiedSegments"; - static const String isQualifiedForMethod = "isQualifiedFor"; - static const String fetchQualifiedSegmentsMethod = "fetchQualifiedSegments"; - // Request parameter keys static const String id = "id"; static const String sdkKey = "sdkKey"; @@ -63,11 +54,8 @@ class Constants { static const String experiment = "experiment"; static const String variation = "variation"; static const String userId = "userId"; - static const String vuid = "vuid"; static const String experimentKey = "experimentKey"; static const String attributes = "attributes"; - static const String qualifiedSegments = "qualifiedSegments"; - static const String segment = "segment"; static const String decisionInfo = "decisionInfo"; static const String variables = "variables"; static const String reasons = "reasons"; @@ -81,14 +69,9 @@ class Constants { static const String ruleKey = "ruleKey"; static const String enabled = "enabled"; static const String optimizelyDecideOption = "optimizelyDecideOption"; - static const String optimizelySegmentOption = "optimizelySegmentOption"; - static const String optimizelySdkSettings = "optimizelySdkSettings"; static const String payload = "payload"; static const String value = "value"; static const String type = "type"; - static const String action = "action"; - static const String identifiers = "identifiers"; - static const String data = "data"; static const String callbackIds = "callbackIds"; static const String eventBatchSize = "eventBatchSize"; static const String eventTimeInterval = "eventTimeInterval"; @@ -122,14 +105,6 @@ class Constants { static const String variationsMap = "variationsMap"; static const String variablesMap = "variablesMap"; - // Odp Request params - static const String segmentsCacheSize = "segmentsCacheSize"; - static const String segmentsCacheTimeoutInSecs = "segmentsCacheTimeoutInSecs"; - static const String timeoutForSegmentFetchInSecs = - "timeoutForSegmentFetchInSecs"; - static const String timeoutForOdpEventInSecs = "timeoutForOdpEventInSecs"; - static const String disableOdp = "disableOdp"; - // Response keys static const String responseSuccess = "success"; static const String responseResult = "result"; diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index da36841..58344ae 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -28,11 +28,6 @@ class Utils { OptimizelyDecideOption.excludeVariables: "excludeVariables", }; - static Map segmentOptions = { - OptimizelySegmentOption.ignoreCache: "ignoreCache", - OptimizelySegmentOption.resetCache: "resetCache", - }; - static Map convertToTypedMap(Map map) { if (map.isEmpty) { return map; @@ -89,9 +84,4 @@ class Utils { Set options) { return options.map((option) => Utils.decideOptions[option]!).toList(); } - - static List convertSegmentOptions( - Set options) { - return options.map((option) => Utils.segmentOptions[option]!).toList(); - } } diff --git a/test/optimizely_flutter_sdk_test.dart b/test/optimizely_flutter_sdk_test.dart index fa81adb..104355a 100644 --- a/test/optimizely_flutter_sdk_test.dart +++ b/test/optimizely_flutter_sdk_test.dart @@ -1,5 +1,5 @@ /// ************************************************************************** -/// Copyright 2022-2023, Optimizely, Inc. and contributors * +/// Copyright 2022, Optimizely, Inc. and contributors * /// * /// Licensed under the Apache License, Version 2.0 (the "License"); * /// you may not use this file except in compliance with the License. * @@ -35,26 +35,15 @@ void main() { const String ruleKey = "rule_1"; const String variationKey = "var_1"; const String eventKey = "event-key"; - const String segment = "segment"; - const String action = "action1"; - const String type = "type1"; - const String vuid = "vuid_123"; - const Map identifiers = {"abc": "123"}; - const Map data = {"abc": 12345}; const Map attributes = {"abc": 123}; const Map attributes1 = {"abc": 1234}; const Map eventTags = {"abcd": 1234}; - const List qualifiedSegments = ["1", "2", "3"]; - const String userContextId = "123"; // To check if decide options properly reached the native sdk through channel List decideOptions = []; - // To check if event options, datafileOptions and sdkSettings reached the native sdk through channel + // To check if event options and datafileOptions reached the native sdk through channel EventOptions eventOptions = const EventOptions(); - // To check if segment options properly reached the native sdk through channel - List segmentOptions = []; DatafileHostOptions datafileHostOptions = const DatafileHostOptions("", ""); - SDKSettings sdkSettings = const SDKSettings(); int datafilePeriodicDownloadInterval = 0; const MethodChannel channel = MethodChannel("optimizely_flutter_sdk"); @@ -91,21 +80,6 @@ void main() { datafilePeriodicDownloadInterval = methodCall.arguments[Constants.datafilePeriodicDownloadInterval]; - // To Check if sdkSettings were received - var settings = methodCall.arguments[Constants.optimizelySdkSettings]; - if (settings is Map) { - sdkSettings = SDKSettings( - segmentsCacheSize: settings[Constants.segmentsCacheSize], - segmentsCacheTimeoutInSecs: - settings[Constants.segmentsCacheTimeoutInSecs], - timeoutForSegmentFetchInSecs: - settings[Constants.timeoutForSegmentFetchInSecs], - timeoutForOdpEventInSecs: - settings[Constants.timeoutForOdpEventInSecs], - disableOdp: settings[Constants.disableOdp], - ); - } - // Resetting to default for every test datafileHostOptions = const DatafileHostOptions("", ""); if (methodCall.arguments[Constants.datafileHostPrefix] != null && @@ -172,13 +146,9 @@ void main() { }; case Constants.createUserContextMethod: expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); - if (methodCall.arguments[Constants.userId] != null) { - expect(methodCall.arguments[Constants.userId], equals(userId)); - } - if (methodCall.arguments[Constants.attributes]["abc"] != null) { - expect(methodCall.arguments[Constants.attributes]["abc"], - equals(attributes["abc"])); - } + expect(methodCall.arguments[Constants.userId], equals(userId)); + expect(methodCall.arguments[Constants.attributes]["abc"], + equals(attributes["abc"])); expect(methodCall.arguments[Constants.userContextId], isNull); return { Constants.responseSuccess: true, @@ -213,60 +183,6 @@ void main() { return { Constants.responseSuccess: true, }; - case Constants.getQualifiedSegmentsMethod: - expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); - expect(methodCall.arguments[Constants.userContextId], - equals(userContextId)); - return { - Constants.responseSuccess: true, - Constants.responseResult: { - Constants.qualifiedSegments: qualifiedSegments, - }, - }; - case Constants.setQualifiedSegmentsMethod: - expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); - expect(methodCall.arguments[Constants.userContextId], - equals(userContextId)); - expect(methodCall.arguments[Constants.qualifiedSegments], - equals(qualifiedSegments)); - return { - Constants.responseSuccess: true, - }; - case Constants.fetchQualifiedSegmentsMethod: - expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); - expect(methodCall.arguments[Constants.userContextId], - equals(userContextId)); - segmentOptions.addAll(List.from( - methodCall.arguments[Constants.optimizelySegmentOption])); - return { - Constants.responseSuccess: true, - }; - case Constants.isQualifiedForMethod: - expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); - expect(methodCall.arguments[Constants.userContextId], - equals(userContextId)); - expect(methodCall.arguments[Constants.segment], equals(segment)); - return { - Constants.responseSuccess: true, - }; - case Constants.sendOdpEventMethod: - expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); - expect(methodCall.arguments[Constants.userContextId], isNull); - expect(methodCall.arguments[Constants.action], equals(action)); - expect(methodCall.arguments[Constants.type], equals(type)); - expect( - methodCall.arguments[Constants.identifiers], equals(identifiers)); - expect(methodCall.arguments[Constants.data], equals(data)); - return { - Constants.responseSuccess: true, - }; - case Constants.getVuidMethod: - expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); - expect(methodCall.arguments[Constants.userContextId], isNull); - return { - Constants.responseSuccess: true, - Constants.responseResult: {Constants.vuid: vuid}, - }; case Constants.trackEventMethod: expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); expect(methodCall.arguments[Constants.userContextId], @@ -395,19 +311,12 @@ void main() { expect(response.success, isTrue); }); - test("with no eventOptions, datafileOptions and sdkSettings", () async { + test("with no eventOptions and no datafileOptions", () async { // default values const expectedEventOptions = EventOptions(batchSize: 10, timeInterval: 60, maxQueueSize: 10000); debugDefaultTargetPlatformOverride = TargetPlatform.iOS; const expectedDatafileHostOptions = DatafileHostOptions("", ""); - const expectedSDKSettings = SDKSettings( - segmentsCacheSize: 100, - segmentsCacheTimeoutInSecs: 600, - timeoutForSegmentFetchInSecs: 10, - timeoutForOdpEventInSecs: 10, - disableOdp: false, - ); const expectedDatafilePeriodicDownloadInterval = 10 * 60; var sdk = OptimizelyFlutterSdk(testSDKKey); var response = await sdk.initializeClient(); @@ -424,40 +333,22 @@ void main() { equals(expectedDatafileHostOptions.datafileHostPrefix)); expect(datafileHostOptions.datafileHostSuffix, equals(expectedDatafileHostOptions.datafileHostSuffix)); - - expect(sdkSettings.segmentsCacheSize, - equals(expectedSDKSettings.segmentsCacheSize)); - expect(sdkSettings.segmentsCacheTimeoutInSecs, - equals(expectedSDKSettings.segmentsCacheTimeoutInSecs)); - expect(sdkSettings.timeoutForSegmentFetchInSecs, - equals(expectedSDKSettings.timeoutForSegmentFetchInSecs)); - expect(sdkSettings.timeoutForOdpEventInSecs, - equals(expectedSDKSettings.timeoutForOdpEventInSecs)); - expect(sdkSettings.disableOdp, equals(expectedSDKSettings.disableOdp)); debugDefaultTargetPlatformOverride = null; }); - test("with eventOptions, datafileOptions and sdkSettings", () async { + test("with eventOptions and datafileOptions", () async { const expectedEventOptions = EventOptions(batchSize: 20, timeInterval: 30, maxQueueSize: 200); debugDefaultTargetPlatformOverride = TargetPlatform.iOS; const expectedDatafileHostOptions = DatafileHostOptions("123", "456"); const expectedDatafilePeriodicDownloadInterval = 40; - const expectedSDKSettings = SDKSettings( - segmentsCacheSize: 111, - segmentsCacheTimeoutInSecs: 222, - timeoutForSegmentFetchInSecs: 333, - timeoutForOdpEventInSecs: 444, - disableOdp: true, - ); var sdk = OptimizelyFlutterSdk(testSDKKey, eventOptions: expectedEventOptions, datafilePeriodicDownloadInterval: expectedDatafilePeriodicDownloadInterval, datafileHostOptions: { ClientPlatform.iOS: expectedDatafileHostOptions - }, - sdkSettings: expectedSDKSettings); + }); var response = await sdk.initializeClient(); expect(response.success, isTrue); @@ -472,16 +363,6 @@ void main() { equals(expectedDatafileHostOptions.datafileHostPrefix)); expect(datafileHostOptions.datafileHostSuffix, equals(expectedDatafileHostOptions.datafileHostSuffix)); - - expect(sdkSettings.segmentsCacheSize, - equals(expectedSDKSettings.segmentsCacheSize)); - expect(sdkSettings.segmentsCacheTimeoutInSecs, - equals(expectedSDKSettings.segmentsCacheTimeoutInSecs)); - expect(sdkSettings.timeoutForSegmentFetchInSecs, - equals(expectedSDKSettings.timeoutForSegmentFetchInSecs)); - expect(sdkSettings.timeoutForOdpEventInSecs, - equals(expectedSDKSettings.timeoutForOdpEventInSecs)); - expect(sdkSettings.disableOdp, equals(expectedSDKSettings.disableOdp)); debugDefaultTargetPlatformOverride = null; }); @@ -614,26 +495,7 @@ void main() { group("createUserContext()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = - await sdk.createUserContext(userId: userId, attributes: attributes); - expect(userContext, isNotNull); - }); - - test("should succeed null userId", () async { - var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(attributes: attributes); - expect(userContext, isNotNull); - }); - - test("should succeed null attributes", () async { - var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId: userId); - expect(userContext, isNotNull); - }); - - test("should succeed null userId and attributes", () async { - var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(); + var userContext = await sdk.createUserContext(userId, attributes); expect(userContext, isNotNull); }); }); @@ -641,8 +503,7 @@ void main() { group("getUserId()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = - await sdk.createUserContext(userId: userId, attributes: attributes); + var userContext = await sdk.createUserContext(userId, attributes); var response = await userContext!.getUserId(); expect(response.success, isTrue); @@ -652,8 +513,7 @@ void main() { group("getAttributes()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = - await sdk.createUserContext(userId: userId, attributes: attributes); + var userContext = await sdk.createUserContext(userId, attributes); var response = await userContext!.getAttributes(); expect(response.success, isTrue); @@ -664,93 +524,17 @@ void main() { group("setAttributes()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = - await sdk.createUserContext(userId: userId, attributes: attributes); + var userContext = await sdk.createUserContext(userId, attributes); var response = await userContext!.setAttributes(attributes1); expect(response.success, isTrue); }); }); - group("getQualifiedSegments()", () { - test("should succeed", () async { - var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId: userId); - var response = await userContext!.getQualifiedSegments(); - - expect(response.qualifiedSegments, qualifiedSegments); - }); - }); - - group("setQualifiedSegments()", () { - test("should succeed", () async { - var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId: userId); - var response = - await userContext!.setQualifiedSegments(qualifiedSegments); - - expect(response.success, isTrue); - }); - }); - - group("isQualifiedFor()", () { - test("should succeed", () async { - var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId: userId); - var response = await userContext!.isQualifiedFor(segment); - - expect(response.success, isTrue); - }); - }); - - group("fetchQualifiedSegments()", () { - bool assertSegmentOptions( - Set options, List convertedOptions) { - for (var option in options) { - if (!convertedOptions.contains(option.name)) { - return false; - } - } - return true; - } - - test("should succeed", () async { - var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId: userId); - Set options = { - OptimizelySegmentOption.ignoreCache, - OptimizelySegmentOption.resetCache, - }; - var response = await userContext!.fetchQualifiedSegments(options); - expect(response.success, isTrue); - expect(segmentOptions.length == 2, isTrue); - expect(assertSegmentOptions(options, segmentOptions), isTrue); - }); - }); - - group("sendOdpEvent()", () { - test("should succeed", () async { - var sdk = OptimizelyFlutterSdk(testSDKKey); - var response = await sdk.sendOdpEvent(action, - type: type, identifiers: identifiers, data: data); - expect(response.success, isTrue); - }); - }); - - group("getVuid()", () { - test("should succeed", () async { - var sdk = OptimizelyFlutterSdk(testSDKKey); - var response = await sdk.getVuid(); - expect(response.success, isTrue); - expect(response.vuid, equals(vuid)); - }); - }); - group("trackEvent()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = - await sdk.createUserContext(userId: userId, attributes: attributes); + var userContext = await sdk.createUserContext(userId, attributes); var response = await userContext!.trackEvent(eventKey, eventTags); expect(response.success, isTrue); @@ -785,8 +569,7 @@ void main() { }; var sdk = OptimizelyFlutterSdk(testSDKKey, defaultDecideOptions: defaultDecideOptions); - var userContext = - await sdk.createUserContext(userId: userId, attributes: attributes); + var userContext = await sdk.createUserContext(userId, attributes); var decideKey = "decide-key"; var response = await userContext!.decide(decideKey, options); @@ -805,8 +588,7 @@ void main() { test("decideForKeys should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey, defaultDecideOptions: defaultDecideOptions); - var userContext = - await sdk.createUserContext(userId: userId, attributes: attributes); + var userContext = await sdk.createUserContext(userId, attributes); var decideKeys = ["decide-key-1", "decide-key-2"]; var response = await userContext!.decideForKeys(decideKeys, options); @@ -822,8 +604,7 @@ void main() { test("decideAll() should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey, defaultDecideOptions: defaultDecideOptions); - var userContext = - await sdk.createUserContext(userId: userId, attributes: attributes); + var userContext = await sdk.createUserContext(userId, attributes); var response = await userContext!.decideAll(options); @@ -855,8 +636,7 @@ void main() { group("setForcedDecision()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = - await sdk.createUserContext(userId: userId, attributes: attributes); + var userContext = await sdk.createUserContext(userId, attributes); var response = await userContext!.setForcedDecision( OptimizelyDecisionContext(flagKey, ruleKey), @@ -869,8 +649,7 @@ void main() { group("getForcedDecision()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = - await sdk.createUserContext(userId: userId, attributes: attributes); + var userContext = await sdk.createUserContext(userId, attributes); var response = await userContext! .getForcedDecision(OptimizelyDecisionContext(flagKey, ruleKey)); @@ -883,8 +662,7 @@ void main() { group("removeForcedDecision()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = - await sdk.createUserContext(userId: userId, attributes: attributes); + var userContext = await sdk.createUserContext(userId, attributes); var response = await userContext! .removeForcedDecision(OptimizelyDecisionContext(flagKey, ruleKey)); @@ -896,8 +674,7 @@ void main() { test("removeAllForcedDecisions() should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = - await sdk.createUserContext(userId: userId, attributes: attributes); + var userContext = await sdk.createUserContext(userId, attributes); var response = await userContext!.removeAllForcedDecisions(); From 4d9468f290154465e6e1e2caa65cec1c031bfc5d Mon Sep 17 00:00:00 2001 From: Muhammad Noman Date: Fri, 5 May 2023 01:13:49 +0500 Subject: [PATCH 14/39] [FSSDK-9105] Fix: Added proguard rules to resolve "no serializer found" error (#51) * Added proguard rules * nit fix --------- Co-authored-by: NomanShoaib --- android/build.gradle | 5 ++++- android/proguard-rules.txt | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 android/proguard-rules.txt diff --git a/android/build.gradle b/android/build.gradle index d2e1bf2..d707cf2 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -49,6 +49,9 @@ android { versionName version_name buildConfigField "String", "CLIENT_VERSION", "\"$version_name\"" multiDexEnabled true + + // these rules will be merged to app's proguard rules + consumerProguardFiles './proguard-rules.txt' } compileOptions { @@ -61,7 +64,7 @@ android { buildTypes { release { - minifyEnabled false + minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } diff --git a/android/proguard-rules.txt b/android/proguard-rules.txt new file mode 100644 index 0000000..7fede45 --- /dev/null +++ b/android/proguard-rules.txt @@ -0,0 +1,14 @@ +# Add project specific ProGuard rules here. +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# Optimizely +-keep class com.optimizely.optimizely_flutter_sdk.** {*;} +-keep class com.fasterxml.jackson.** {*;} +##---------------End: proguard configuration ---------- + From e8e0b91bea321e585fb0796f2d82f84675abc693 Mon Sep 17 00:00:00 2001 From: Mike Chu <104384559+mikechu-optimizely@users.noreply.github.com> Date: Tue, 9 May 2023 10:49:39 -0400 Subject: [PATCH 15/39] [FSSDK-9359] chore: Catch up the main branch from release branches (#55) * [FSSDK-8962] Update branding names * [FSSDK-8962] Add CONTRIBUTING.md * [FSSDK-8962] Update README.md * [FSSDK-8962] PR review requested changes * [FSSDK-8962] Update Pod::Spec homepage link * [FSSDK-8962] Update CONTRIBUTING.md * [FSSDK-8962] Restore & reorder badges * [FSSDK-8962] Remove excess H3; Add code typing * [FSSDK-8962] Reorganize sample + other stuff * [FSSDK-8962] Remove build & coverage badges * [FSSDK-8962] Bump version & changelog (#46) * [FSSDK-9359] chore Prepare for v1.0.1 GA release (#53) * Fix gradle & proguard rules * Update CHANGELOG * Bump version numbers * Fix year in CHANGELOG --- CHANGELOG.md | 11 +++++++++++ README.md | 2 +- pubspec.yaml | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea7efaf..55ba984 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,14 @@ # Optimizely Flutter SDK Changelog + +## 1.0.1 +May 8, 2023 + +**Official General Availability (GA) release** + +### Bug Fixes + +* Fix "no serializer found" error ([#51](https://github.com/optimizely/optimizely-flutter-sdk/pull/51)). + ## 1.0.1-beta March 10, 2022 @@ -6,6 +16,7 @@ March 10, 2022 ## 1.0.0-beta November 3, 2022 + **Beta release of the Optimizely X Full Stack Flutter SDK.** ### New Features diff --git a/README.md b/README.md index 9f6b00a..5da0c09 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Other Flutter platforms are not currently supported by this SDK. To add the flutter-sdk to your project dependencies, include the following in your app's pubspec.yaml: ``` - optimizely_flutter_sdk: ^1.0.1-beta + optimizely_flutter_sdk: ^1.0.1 ``` Then run diff --git a/pubspec.yaml b/pubspec.yaml index 6944681..66d28c3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: optimizely_flutter_sdk description: This repository houses the Flutter SDK for use with Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts. -version: 1.0.1-beta +version: 1.0.1 homepage: https://github.com/optimizely/optimizely-flutter-sdk environment: From 66400f38d35b9b3101f3b926353b4b839e2f7f07 Mon Sep 17 00:00:00 2001 From: Yasir Folio3 <39988750+yasirfolio3@users.noreply.github.com> Date: Tue, 9 May 2023 13:04:37 -0400 Subject: [PATCH 16/39] [FSSDK-9029]: Adds Top-level ODP Interface. (#50) * Adding support for sendOdpEvent, SdkSettings and getVuid. * 1. Adding odp api's to user context. 2. Adding new unit tests. --- example/lib/main.dart | 3 +- lib/optimizely_flutter_sdk.dart | 64 +++-- .../get_qualified_segments_response.dart | 32 +++ lib/src/data_objects/get_vuid_response.dart | 31 +++ lib/src/data_objects/sdk_settings.dart | 37 +++ lib/src/optimizely_client_wrapper.dart | 63 ++++- .../user_context/optimizely_user_context.dart | 69 ++++- lib/src/utils/constants.dart | 27 +- lib/src/utils/utils.dart | 10 + test/optimizely_flutter_sdk_test.dart | 263 ++++++++++++++++-- 10 files changed, 550 insertions(+), 49 deletions(-) create mode 100644 lib/src/data_objects/get_qualified_segments_response.dart create mode 100644 lib/src/data_objects/get_vuid_response.dart create mode 100644 lib/src/data_objects/sdk_settings.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 18f89e2..a717223 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -43,7 +43,8 @@ class _MyAppState extends State { var randomUserName = "${rng.nextInt(1000)}"; // Create user context - var userContext = await flutterSDK.createUserContext(randomUserName); + var userContext = + await flutterSDK.createUserContext(userId: randomUserName); // Set attributes response = await userContext!.setAttributes({ diff --git a/lib/optimizely_flutter_sdk.dart b/lib/optimizely_flutter_sdk.dart index d534c9d..775130f 100644 --- a/lib/optimizely_flutter_sdk.dart +++ b/lib/optimizely_flutter_sdk.dart @@ -1,5 +1,5 @@ /// ************************************************************************** -/// Copyright 2022, Optimizely, Inc. and contributors * +/// Copyright 2022-2023, Optimizely, Inc. and contributors * /// * /// Licensed under the Apache License, Version 2.0 (the "License"); * /// you may not use this file except in compliance with the License. * @@ -21,6 +21,8 @@ import 'package:optimizely_flutter_sdk/src/data_objects/activate_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/base_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/datafile_options.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/event_options.dart'; +import 'package:optimizely_flutter_sdk/src/data_objects/get_vuid_response.dart'; +import 'package:optimizely_flutter_sdk/src/data_objects/sdk_settings.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/get_variation_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/optimizely_config_response.dart'; import 'package:optimizely_flutter_sdk/src/optimizely_client_wrapper.dart'; @@ -33,7 +35,7 @@ export 'package:optimizely_flutter_sdk/src/user_context/optimizely_forced_decisi export 'package:optimizely_flutter_sdk/src/user_context/optimizely_decision_context.dart' show OptimizelyDecisionContext; export 'package:optimizely_flutter_sdk/src/user_context/optimizely_user_context.dart' - show OptimizelyUserContext, OptimizelyDecideOption; + show OptimizelyUserContext, OptimizelyDecideOption, OptimizelySegmentOption; export 'package:optimizely_flutter_sdk/src/data_objects/decide_response.dart' show Decision; export 'package:optimizely_flutter_sdk/src/data_objects/track_listener_response.dart' @@ -44,6 +46,8 @@ export 'package:optimizely_flutter_sdk/src/data_objects/logevent_listener_respon show LogEventListenerResponse; export 'package:optimizely_flutter_sdk/src/data_objects/event_options.dart' show EventOptions; +export 'package:optimizely_flutter_sdk/src/data_objects/sdk_settings.dart' + show SDKSettings; export 'package:optimizely_flutter_sdk/src/data_objects/datafile_options.dart' show DatafileHostOptions; @@ -59,17 +63,19 @@ class OptimizelyFlutterSdk { final int _datafilePeriodicDownloadInterval; final Map _datafileHostOptions; final Set _defaultDecideOptions; - OptimizelyFlutterSdk( - this._sdkKey, { - EventOptions eventOptions = const EventOptions(), - int datafilePeriodicDownloadInterval = - 10 * 60, // Default time interval in seconds - Map datafileHostOptions = const {}, - Set defaultDecideOptions = const {}, - }) : _eventOptions = eventOptions, + final SDKSettings _sdkSettings; + OptimizelyFlutterSdk(this._sdkKey, + {EventOptions eventOptions = const EventOptions(), + int datafilePeriodicDownloadInterval = + 10 * 60, // Default time interval in seconds + Map datafileHostOptions = const {}, + Set defaultDecideOptions = const {}, + SDKSettings sdkSettings = const SDKSettings()}) + : _eventOptions = eventOptions, _datafilePeriodicDownloadInterval = datafilePeriodicDownloadInterval, _datafileHostOptions = datafileHostOptions, - _defaultDecideOptions = defaultDecideOptions; + _defaultDecideOptions = defaultDecideOptions, + _sdkSettings = sdkSettings; /// Starts Optimizely SDK (Synchronous) with provided sdkKey. Future initializeClient() async { @@ -78,7 +84,8 @@ class OptimizelyFlutterSdk { _eventOptions, _datafilePeriodicDownloadInterval, _datafileHostOptions, - _defaultDecideOptions); + _defaultDecideOptions, + _sdkSettings); } /// Use the activate method to start an experiment. @@ -140,17 +147,40 @@ class OptimizelyFlutterSdk { return await OptimizelyClientWrapper.getOptimizelyConfig(_sdkKey); } + /// Send an event to the ODP server. + /// + /// Takes [action] The event action name. + /// Optional [type] The event type (default = "fullstack"). + /// Optional [identifiers] A dictionary for identifiers. + /// Optional [data] A dictionary for associated data. The default event data will be added to this data before sending to the ODP server. + /// Returns [BaseResponse] A object containing success result or reason of failure. + Future sendOdpEvent(String action, + {String? type, + Map identifiers = const {}, + Map data = const {}}) async { + return await OptimizelyClientWrapper.sendOdpEvent(_sdkKey, action, + type: type, identifiers: identifiers, data: data); + } + + /// Returns the device vuid. + /// + /// Returns [GetVuidResponse] A object containing device vuid + Future getVuid() async { + return await OptimizelyClientWrapper.getVuid(_sdkKey); + } + /// Creates a context of the user for which decision APIs will be called. /// /// NOTE: A user context will only be created successfully when the SDK is fully configured using initializeClient. /// - /// Takes [userId] the [String] user ID to be used for bucketing. + /// Optional [userId] the [String] user ID to be used for bucketing. + /// The device vuid will be used as an userId when userId is not provided. /// Takes [attributes] An Optional [Map] of attribute names to current user attribute values. /// Returns An [OptimizelyUserContext] associated with this OptimizelyClient. - Future createUserContext(String userId, - [Map attributes = const {}]) async { - return await OptimizelyClientWrapper.createUserContext( - _sdkKey, userId, attributes); + Future createUserContext( + {String? userId, Map attributes = const {}}) async { + return await OptimizelyClientWrapper.createUserContext(_sdkKey, + userId: userId, attributes: attributes); } /// Allows user to remove notification listener using id. diff --git a/lib/src/data_objects/get_qualified_segments_response.dart b/lib/src/data_objects/get_qualified_segments_response.dart new file mode 100644 index 0000000..130c79e --- /dev/null +++ b/lib/src/data_objects/get_qualified_segments_response.dart @@ -0,0 +1,32 @@ +/// ************************************************************************** +/// Copyright 2023, Optimizely, Inc. and contributors * +/// * +/// Licensed under the Apache License, Version 2.0 (the "License"); * +/// you may not use this file except in compliance with the License. * +/// You may obtain a copy of the License at * +/// * +/// http://www.apache.org/licenses/LICENSE-2.0 * +/// * +/// Unless required by applicable law or agreed to in writing, software * +/// distributed under the License is distributed on an "AS IS" BASIS, * +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +/// See the License for the specific language governing permissions and * +/// limitations under the License. * +///**************************************************************************/ + +import 'package:optimizely_flutter_sdk/src/data_objects/base_response.dart'; +import 'package:optimizely_flutter_sdk/src/utils/constants.dart'; + +class GetQualifiedSegmentsResponse extends BaseResponse { + List qualifiedSegments = []; + + GetQualifiedSegmentsResponse(Map json) : super(json) { + if (json[Constants.responseResult] is Map) { + var response = Map.from(json[Constants.responseResult]); + if (response[Constants.qualifiedSegments] is List) { + qualifiedSegments = + List.from(response[Constants.qualifiedSegments]); + } + } + } +} diff --git a/lib/src/data_objects/get_vuid_response.dart b/lib/src/data_objects/get_vuid_response.dart new file mode 100644 index 0000000..4be045b --- /dev/null +++ b/lib/src/data_objects/get_vuid_response.dart @@ -0,0 +1,31 @@ +/// ************************************************************************** +/// Copyright 2023, Optimizely, Inc. and contributors * +/// * +/// Licensed under the Apache License, Version 2.0 (the "License"); * +/// you may not use this file except in compliance with the License. * +/// You may obtain a copy of the License at * +/// * +/// http://www.apache.org/licenses/LICENSE-2.0 * +/// * +/// Unless required by applicable law or agreed to in writing, software * +/// distributed under the License is distributed on an "AS IS" BASIS, * +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +/// See the License for the specific language governing permissions and * +/// limitations under the License. * +///**************************************************************************/ + +import 'package:optimizely_flutter_sdk/src/data_objects/base_response.dart'; +import 'package:optimizely_flutter_sdk/src/utils/constants.dart'; + +class GetVuidResponse extends BaseResponse { + String vuid = ""; + + GetVuidResponse(Map json) : super(json) { + if (json[Constants.responseResult] is Map) { + var response = Map.from(json[Constants.responseResult]); + if (response[Constants.vuid] is String) { + vuid = response[Constants.vuid]; + } + } + } +} diff --git a/lib/src/data_objects/sdk_settings.dart b/lib/src/data_objects/sdk_settings.dart new file mode 100644 index 0000000..412432a --- /dev/null +++ b/lib/src/data_objects/sdk_settings.dart @@ -0,0 +1,37 @@ +/// ************************************************************************** +/// Copyright 2023, Optimizely, Inc. and contributors * +/// * +/// Licensed under the Apache License, Version 2.0 (the "License"); * +/// you may not use this file except in compliance with the License. * +/// You may obtain a copy of the License at * +/// * +/// http://www.apache.org/licenses/LICENSE-2.0 * +/// * +/// Unless required by applicable law or agreed to in writing, software * +/// distributed under the License is distributed on an "AS IS" BASIS, * +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +/// See the License for the specific language governing permissions and * +/// limitations under the License. * +///**************************************************************************/ + +class SDKSettings { + // The maximum size of audience segments cache (optional. default = 100). Set to zero to disable caching. + final int segmentsCacheSize; + // The timeout in seconds of audience segments cache (optional. default = 600). Set to zero to disable timeout. + final int segmentsCacheTimeoutInSecs; + // The timeout in seconds of odp segment fetch (optional. default = 10) - OS default timeout will be used if this is set to zero. + final int timeoutForSegmentFetchInSecs; + // The timeout in seconds of odp event dispatch (optional. default = 10) - OS default timeout will be used if this is set to zero. + final int timeoutForOdpEventInSecs; + // Set this flag to true (default = false) to disable ODP features + final bool disableOdp; + + const SDKSettings({ + this.segmentsCacheSize = 100, // Default segmentsCacheSize + this.segmentsCacheTimeoutInSecs = 600, // Default segmentsCacheTimeoutInSecs + this.timeoutForSegmentFetchInSecs = + 10, // Default timeoutForSegmentFetchInSecs + this.timeoutForOdpEventInSecs = 10, // Default timeoutForOdpEventInSecs + this.disableOdp = false, // Default disableOdp + }); +} diff --git a/lib/src/optimizely_client_wrapper.dart b/lib/src/optimizely_client_wrapper.dart index 0d7737d..e26b74f 100644 --- a/lib/src/optimizely_client_wrapper.dart +++ b/lib/src/optimizely_client_wrapper.dart @@ -1,5 +1,5 @@ /// ************************************************************************** -/// Copyright 2022, Optimizely, Inc. and contributors * +/// Copyright 2022-2023, Optimizely, Inc. and contributors * /// * /// Licensed under the Apache License, Version 2.0 (the "License"); * /// you may not use this file except in compliance with the License. * @@ -22,6 +22,7 @@ import 'package:optimizely_flutter_sdk/src/data_objects/activate_listener_respon import 'package:optimizely_flutter_sdk/src/data_objects/activate_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/base_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/get_variation_response.dart'; +import 'package:optimizely_flutter_sdk/src/data_objects/get_vuid_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/optimizely_config_response.dart'; import 'package:optimizely_flutter_sdk/src/utils/constants.dart'; import 'package:optimizely_flutter_sdk/src/utils/utils.dart'; @@ -59,7 +60,8 @@ class OptimizelyClientWrapper { EventOptions eventOptions, int datafilePeriodicDownloadInterval, Map datafileHostOptions, - Set defaultDecideOptions) async { + Set defaultDecideOptions, + SDKSettings sdkSettings) async { _channel.setMethodCallHandler(methodCallHandler); final convertedOptions = Utils.convertDecideOptions(defaultDecideOptions); Map requestDict = { @@ -72,6 +74,18 @@ class OptimizelyClientWrapper { Constants.eventMaxQueueSize: eventOptions.maxQueueSize, }; + // Odp Request params + Map optimizelySdkSettings = { + Constants.segmentsCacheSize: sdkSettings.segmentsCacheSize, + Constants.segmentsCacheTimeoutInSecs: + sdkSettings.segmentsCacheTimeoutInSecs, + Constants.timeoutForSegmentFetchInSecs: + sdkSettings.timeoutForSegmentFetchInSecs, + Constants.timeoutForOdpEventInSecs: sdkSettings.timeoutForOdpEventInSecs, + Constants.disableOdp: sdkSettings.disableOdp, + }; + requestDict[Constants.optimizelySdkSettings] = optimizelySdkSettings; + // clearing notification listeners, if they are mapped to the same sdkKey. activateCallbacksById.remove(sdkKey); decisionCallbacksById.remove(sdkKey); @@ -164,6 +178,35 @@ class OptimizelyClientWrapper { return OptimizelyConfigResponse(result); } + /// Send an event to the ODP server. + static Future sendOdpEvent(String sdkKey, String action, + {String? type, + Map identifiers = const {}, + Map data = const {}}) async { + Map request = { + Constants.sdkKey: sdkKey, + Constants.action: action, + Constants.identifiers: identifiers, + Constants.data: Utils.convertToTypedMap(data) + }; + if (type != null) { + request[Constants.type] = type; + } + + final result = Map.from( + await _channel.invokeMethod(Constants.sendOdpEventMethod, request)); + return BaseResponse(result); + } + + /// Returns the device vuid (read only) + static Future getVuid(String sdkKey) async { + final result = Map.from( + await _channel.invokeMethod(Constants.getVuidMethod, { + Constants.sdkKey: sdkKey, + })); + return GetVuidResponse(result); + } + /// Remove notification listener by notification id. static Future removeNotificationListener( String sdkKey, int id) async { @@ -217,15 +260,17 @@ class OptimizelyClientWrapper { /// Creates a context of the user for which decision APIs will be called. /// /// A user context will only be created successfully when the SDK is fully configured using initializeClient. - static Future createUserContext( - String sdkKey, String userId, - [Map attributes = const {}]) async { - final result = Map.from( - await _channel.invokeMethod(Constants.createUserContextMethod, { + static Future createUserContext(String sdkKey, + {String? userId, Map attributes = const {}}) async { + Map request = { Constants.sdkKey: sdkKey, - Constants.userId: userId, Constants.attributes: Utils.convertToTypedMap(attributes) - })); + }; + if (userId != null) { + request[Constants.userId] = userId; + } + final result = Map.from(await _channel.invokeMethod( + Constants.createUserContextMethod, request)); if (result[Constants.responseSuccess] == true) { final response = diff --git a/lib/src/user_context/optimizely_user_context.dart b/lib/src/user_context/optimizely_user_context.dart index 4f261d7..906951f 100644 --- a/lib/src/user_context/optimizely_user_context.dart +++ b/lib/src/user_context/optimizely_user_context.dart @@ -1,5 +1,5 @@ /// ************************************************************************** -/// Copyright 2022, Optimizely, Inc. and contributors * +/// Copyright 2022-2023, Optimizely, Inc. and contributors * /// * /// Licensed under the Apache License, Version 2.0 (the "License"); * /// you may not use this file except in compliance with the License. * @@ -21,6 +21,7 @@ import 'package:optimizely_flutter_sdk/src/data_objects/decide_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/get_attributes_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/get_forced_decision_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/get_user_id_response.dart'; +import 'package:optimizely_flutter_sdk/src/data_objects/get_qualified_segments_response.dart'; import 'package:optimizely_flutter_sdk/src/utils/constants.dart'; import 'package:optimizely_flutter_sdk/src/utils/utils.dart'; @@ -43,6 +44,16 @@ enum OptimizelyDecideOption { excludeVariables } +/// Options controlling audience segments. +/// +enum OptimizelySegmentOption { + /// ignore odp cache (save/lookup) + ignoreCache, + + /// resets odp cache + resetCache, +} + /// An object for user contexts that the SDK will use to make decisions for. /// class OptimizelyUserContext { @@ -86,6 +97,62 @@ class OptimizelyUserContext { return BaseResponse(result); } + /// Returns [GetQualifiedSegmentsResponse] object containing an array of segment names that the user is qualified for. + Future getQualifiedSegments() async { + final result = Map.from( + await _channel.invokeMethod(Constants.getQualifiedSegmentsMethod, { + Constants.sdkKey: _sdkKey, + Constants.userContextId: _userContextId, + })); + return GetQualifiedSegmentsResponse(result); + } + + /// Sets qualified segments for the user context. + /// + /// Takes [qualifiedSegments] A [List] of strings specifying qualified segments for the user. + /// Returns [BaseResponse] + Future setQualifiedSegments( + List qualifiedSegments) async { + final result = Map.from( + await _channel.invokeMethod(Constants.setQualifiedSegmentsMethod, { + Constants.sdkKey: _sdkKey, + Constants.userContextId: _userContextId, + Constants.qualifiedSegments: qualifiedSegments + })); + return BaseResponse(result); + } + + /// Checks if the user is qualified for the given segment. + /// + /// Takes [segment] The segment name to check qualification for. + /// Returns [BaseResponse] + Future isQualifiedFor(String segment) async { + final result = Map.from( + await _channel.invokeMethod(Constants.isQualifiedForMethod, { + Constants.sdkKey: _sdkKey, + Constants.userContextId: _userContextId, + Constants.segment: segment + })); + return BaseResponse(result); + } + + /// Fetch all qualified segments for the user context. + /// + /// The segments fetched will be saved in **qualifiedSegments** and can be accessed any time using **getQualifiedSegments**. + /// On failure, **qualifiedSegments** will be nil and an error will be returned. + /// Optional [options] A set of [OptimizelySegmentOption] for fetching qualified segments. + /// Returns [BaseResponse] + Future fetchQualifiedSegments( + [Set options = const {}]) async { + final result = Map.from( + await _channel.invokeMethod(Constants.fetchQualifiedSegmentsMethod, { + Constants.sdkKey: _sdkKey, + Constants.userContextId: _userContextId, + Constants.optimizelySegmentOption: Utils.convertSegmentOptions(options), + })); + return BaseResponse(result); + } + /// Tracks an event. /// /// Takes [eventKey] The event name. diff --git a/lib/src/utils/constants.dart b/lib/src/utils/constants.dart index 07f0f0a..f6a61d5 100644 --- a/lib/src/utils/constants.dart +++ b/lib/src/utils/constants.dart @@ -1,5 +1,5 @@ /// ************************************************************************** -/// Copyright 2022, Optimizely, Inc. and contributors * +/// Copyright 2022-2023, Optimizely, Inc. and contributors * /// * /// Licensed under the Apache License, Version 2.0 (the "License"); * /// you may not use this file except in compliance with the License. * @@ -46,6 +46,15 @@ class Constants { "clearNotificationListeners"; static const String clearAllNotificationListenersMethod = "clearAllNotificationListeners"; + + // Odp Supported Method Names + static const String sendOdpEventMethod = "sendOdpEvent"; + static const String getVuidMethod = "getVuid"; + static const String getQualifiedSegmentsMethod = "getQualifiedSegments"; + static const String setQualifiedSegmentsMethod = "setQualifiedSegments"; + static const String isQualifiedForMethod = "isQualifiedFor"; + static const String fetchQualifiedSegmentsMethod = "fetchQualifiedSegments"; + // Request parameter keys static const String id = "id"; static const String sdkKey = "sdkKey"; @@ -54,8 +63,11 @@ class Constants { static const String experiment = "experiment"; static const String variation = "variation"; static const String userId = "userId"; + static const String vuid = "vuid"; static const String experimentKey = "experimentKey"; static const String attributes = "attributes"; + static const String qualifiedSegments = "qualifiedSegments"; + static const String segment = "segment"; static const String decisionInfo = "decisionInfo"; static const String variables = "variables"; static const String reasons = "reasons"; @@ -69,9 +81,14 @@ class Constants { static const String ruleKey = "ruleKey"; static const String enabled = "enabled"; static const String optimizelyDecideOption = "optimizelyDecideOption"; + static const String optimizelySegmentOption = "optimizelySegmentOption"; + static const String optimizelySdkSettings = "optimizelySdkSettings"; static const String payload = "payload"; static const String value = "value"; static const String type = "type"; + static const String action = "action"; + static const String identifiers = "identifiers"; + static const String data = "data"; static const String callbackIds = "callbackIds"; static const String eventBatchSize = "eventBatchSize"; static const String eventTimeInterval = "eventTimeInterval"; @@ -105,6 +122,14 @@ class Constants { static const String variationsMap = "variationsMap"; static const String variablesMap = "variablesMap"; + // Odp Request params + static const String segmentsCacheSize = "segmentsCacheSize"; + static const String segmentsCacheTimeoutInSecs = "segmentsCacheTimeoutInSecs"; + static const String timeoutForSegmentFetchInSecs = + "timeoutForSegmentFetchInSecs"; + static const String timeoutForOdpEventInSecs = "timeoutForOdpEventInSecs"; + static const String disableOdp = "disableOdp"; + // Response keys static const String responseSuccess = "success"; static const String responseResult = "result"; diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index 58344ae..da36841 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -28,6 +28,11 @@ class Utils { OptimizelyDecideOption.excludeVariables: "excludeVariables", }; + static Map segmentOptions = { + OptimizelySegmentOption.ignoreCache: "ignoreCache", + OptimizelySegmentOption.resetCache: "resetCache", + }; + static Map convertToTypedMap(Map map) { if (map.isEmpty) { return map; @@ -84,4 +89,9 @@ class Utils { Set options) { return options.map((option) => Utils.decideOptions[option]!).toList(); } + + static List convertSegmentOptions( + Set options) { + return options.map((option) => Utils.segmentOptions[option]!).toList(); + } } diff --git a/test/optimizely_flutter_sdk_test.dart b/test/optimizely_flutter_sdk_test.dart index 104355a..fa81adb 100644 --- a/test/optimizely_flutter_sdk_test.dart +++ b/test/optimizely_flutter_sdk_test.dart @@ -1,5 +1,5 @@ /// ************************************************************************** -/// Copyright 2022, Optimizely, Inc. and contributors * +/// Copyright 2022-2023, Optimizely, Inc. and contributors * /// * /// Licensed under the Apache License, Version 2.0 (the "License"); * /// you may not use this file except in compliance with the License. * @@ -35,15 +35,26 @@ void main() { const String ruleKey = "rule_1"; const String variationKey = "var_1"; const String eventKey = "event-key"; + const String segment = "segment"; + const String action = "action1"; + const String type = "type1"; + const String vuid = "vuid_123"; + const Map identifiers = {"abc": "123"}; + const Map data = {"abc": 12345}; const Map attributes = {"abc": 123}; const Map attributes1 = {"abc": 1234}; const Map eventTags = {"abcd": 1234}; + const List qualifiedSegments = ["1", "2", "3"]; + const String userContextId = "123"; // To check if decide options properly reached the native sdk through channel List decideOptions = []; - // To check if event options and datafileOptions reached the native sdk through channel + // To check if event options, datafileOptions and sdkSettings reached the native sdk through channel EventOptions eventOptions = const EventOptions(); + // To check if segment options properly reached the native sdk through channel + List segmentOptions = []; DatafileHostOptions datafileHostOptions = const DatafileHostOptions("", ""); + SDKSettings sdkSettings = const SDKSettings(); int datafilePeriodicDownloadInterval = 0; const MethodChannel channel = MethodChannel("optimizely_flutter_sdk"); @@ -80,6 +91,21 @@ void main() { datafilePeriodicDownloadInterval = methodCall.arguments[Constants.datafilePeriodicDownloadInterval]; + // To Check if sdkSettings were received + var settings = methodCall.arguments[Constants.optimizelySdkSettings]; + if (settings is Map) { + sdkSettings = SDKSettings( + segmentsCacheSize: settings[Constants.segmentsCacheSize], + segmentsCacheTimeoutInSecs: + settings[Constants.segmentsCacheTimeoutInSecs], + timeoutForSegmentFetchInSecs: + settings[Constants.timeoutForSegmentFetchInSecs], + timeoutForOdpEventInSecs: + settings[Constants.timeoutForOdpEventInSecs], + disableOdp: settings[Constants.disableOdp], + ); + } + // Resetting to default for every test datafileHostOptions = const DatafileHostOptions("", ""); if (methodCall.arguments[Constants.datafileHostPrefix] != null && @@ -146,9 +172,13 @@ void main() { }; case Constants.createUserContextMethod: expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); - expect(methodCall.arguments[Constants.userId], equals(userId)); - expect(methodCall.arguments[Constants.attributes]["abc"], - equals(attributes["abc"])); + if (methodCall.arguments[Constants.userId] != null) { + expect(methodCall.arguments[Constants.userId], equals(userId)); + } + if (methodCall.arguments[Constants.attributes]["abc"] != null) { + expect(methodCall.arguments[Constants.attributes]["abc"], + equals(attributes["abc"])); + } expect(methodCall.arguments[Constants.userContextId], isNull); return { Constants.responseSuccess: true, @@ -183,6 +213,60 @@ void main() { return { Constants.responseSuccess: true, }; + case Constants.getQualifiedSegmentsMethod: + expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); + expect(methodCall.arguments[Constants.userContextId], + equals(userContextId)); + return { + Constants.responseSuccess: true, + Constants.responseResult: { + Constants.qualifiedSegments: qualifiedSegments, + }, + }; + case Constants.setQualifiedSegmentsMethod: + expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); + expect(methodCall.arguments[Constants.userContextId], + equals(userContextId)); + expect(methodCall.arguments[Constants.qualifiedSegments], + equals(qualifiedSegments)); + return { + Constants.responseSuccess: true, + }; + case Constants.fetchQualifiedSegmentsMethod: + expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); + expect(methodCall.arguments[Constants.userContextId], + equals(userContextId)); + segmentOptions.addAll(List.from( + methodCall.arguments[Constants.optimizelySegmentOption])); + return { + Constants.responseSuccess: true, + }; + case Constants.isQualifiedForMethod: + expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); + expect(methodCall.arguments[Constants.userContextId], + equals(userContextId)); + expect(methodCall.arguments[Constants.segment], equals(segment)); + return { + Constants.responseSuccess: true, + }; + case Constants.sendOdpEventMethod: + expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); + expect(methodCall.arguments[Constants.userContextId], isNull); + expect(methodCall.arguments[Constants.action], equals(action)); + expect(methodCall.arguments[Constants.type], equals(type)); + expect( + methodCall.arguments[Constants.identifiers], equals(identifiers)); + expect(methodCall.arguments[Constants.data], equals(data)); + return { + Constants.responseSuccess: true, + }; + case Constants.getVuidMethod: + expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); + expect(methodCall.arguments[Constants.userContextId], isNull); + return { + Constants.responseSuccess: true, + Constants.responseResult: {Constants.vuid: vuid}, + }; case Constants.trackEventMethod: expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); expect(methodCall.arguments[Constants.userContextId], @@ -311,12 +395,19 @@ void main() { expect(response.success, isTrue); }); - test("with no eventOptions and no datafileOptions", () async { + test("with no eventOptions, datafileOptions and sdkSettings", () async { // default values const expectedEventOptions = EventOptions(batchSize: 10, timeInterval: 60, maxQueueSize: 10000); debugDefaultTargetPlatformOverride = TargetPlatform.iOS; const expectedDatafileHostOptions = DatafileHostOptions("", ""); + const expectedSDKSettings = SDKSettings( + segmentsCacheSize: 100, + segmentsCacheTimeoutInSecs: 600, + timeoutForSegmentFetchInSecs: 10, + timeoutForOdpEventInSecs: 10, + disableOdp: false, + ); const expectedDatafilePeriodicDownloadInterval = 10 * 60; var sdk = OptimizelyFlutterSdk(testSDKKey); var response = await sdk.initializeClient(); @@ -333,22 +424,40 @@ void main() { equals(expectedDatafileHostOptions.datafileHostPrefix)); expect(datafileHostOptions.datafileHostSuffix, equals(expectedDatafileHostOptions.datafileHostSuffix)); + + expect(sdkSettings.segmentsCacheSize, + equals(expectedSDKSettings.segmentsCacheSize)); + expect(sdkSettings.segmentsCacheTimeoutInSecs, + equals(expectedSDKSettings.segmentsCacheTimeoutInSecs)); + expect(sdkSettings.timeoutForSegmentFetchInSecs, + equals(expectedSDKSettings.timeoutForSegmentFetchInSecs)); + expect(sdkSettings.timeoutForOdpEventInSecs, + equals(expectedSDKSettings.timeoutForOdpEventInSecs)); + expect(sdkSettings.disableOdp, equals(expectedSDKSettings.disableOdp)); debugDefaultTargetPlatformOverride = null; }); - test("with eventOptions and datafileOptions", () async { + test("with eventOptions, datafileOptions and sdkSettings", () async { const expectedEventOptions = EventOptions(batchSize: 20, timeInterval: 30, maxQueueSize: 200); debugDefaultTargetPlatformOverride = TargetPlatform.iOS; const expectedDatafileHostOptions = DatafileHostOptions("123", "456"); const expectedDatafilePeriodicDownloadInterval = 40; + const expectedSDKSettings = SDKSettings( + segmentsCacheSize: 111, + segmentsCacheTimeoutInSecs: 222, + timeoutForSegmentFetchInSecs: 333, + timeoutForOdpEventInSecs: 444, + disableOdp: true, + ); var sdk = OptimizelyFlutterSdk(testSDKKey, eventOptions: expectedEventOptions, datafilePeriodicDownloadInterval: expectedDatafilePeriodicDownloadInterval, datafileHostOptions: { ClientPlatform.iOS: expectedDatafileHostOptions - }); + }, + sdkSettings: expectedSDKSettings); var response = await sdk.initializeClient(); expect(response.success, isTrue); @@ -363,6 +472,16 @@ void main() { equals(expectedDatafileHostOptions.datafileHostPrefix)); expect(datafileHostOptions.datafileHostSuffix, equals(expectedDatafileHostOptions.datafileHostSuffix)); + + expect(sdkSettings.segmentsCacheSize, + equals(expectedSDKSettings.segmentsCacheSize)); + expect(sdkSettings.segmentsCacheTimeoutInSecs, + equals(expectedSDKSettings.segmentsCacheTimeoutInSecs)); + expect(sdkSettings.timeoutForSegmentFetchInSecs, + equals(expectedSDKSettings.timeoutForSegmentFetchInSecs)); + expect(sdkSettings.timeoutForOdpEventInSecs, + equals(expectedSDKSettings.timeoutForOdpEventInSecs)); + expect(sdkSettings.disableOdp, equals(expectedSDKSettings.disableOdp)); debugDefaultTargetPlatformOverride = null; }); @@ -495,7 +614,26 @@ void main() { group("createUserContext()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); + expect(userContext, isNotNull); + }); + + test("should succeed null userId", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + var userContext = await sdk.createUserContext(attributes: attributes); + expect(userContext, isNotNull); + }); + + test("should succeed null attributes", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + var userContext = await sdk.createUserContext(userId: userId); + expect(userContext, isNotNull); + }); + + test("should succeed null userId and attributes", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + var userContext = await sdk.createUserContext(); expect(userContext, isNotNull); }); }); @@ -503,7 +641,8 @@ void main() { group("getUserId()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); var response = await userContext!.getUserId(); expect(response.success, isTrue); @@ -513,7 +652,8 @@ void main() { group("getAttributes()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); var response = await userContext!.getAttributes(); expect(response.success, isTrue); @@ -524,17 +664,93 @@ void main() { group("setAttributes()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); var response = await userContext!.setAttributes(attributes1); expect(response.success, isTrue); }); }); + group("getQualifiedSegments()", () { + test("should succeed", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + var userContext = await sdk.createUserContext(userId: userId); + var response = await userContext!.getQualifiedSegments(); + + expect(response.qualifiedSegments, qualifiedSegments); + }); + }); + + group("setQualifiedSegments()", () { + test("should succeed", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + var userContext = await sdk.createUserContext(userId: userId); + var response = + await userContext!.setQualifiedSegments(qualifiedSegments); + + expect(response.success, isTrue); + }); + }); + + group("isQualifiedFor()", () { + test("should succeed", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + var userContext = await sdk.createUserContext(userId: userId); + var response = await userContext!.isQualifiedFor(segment); + + expect(response.success, isTrue); + }); + }); + + group("fetchQualifiedSegments()", () { + bool assertSegmentOptions( + Set options, List convertedOptions) { + for (var option in options) { + if (!convertedOptions.contains(option.name)) { + return false; + } + } + return true; + } + + test("should succeed", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + var userContext = await sdk.createUserContext(userId: userId); + Set options = { + OptimizelySegmentOption.ignoreCache, + OptimizelySegmentOption.resetCache, + }; + var response = await userContext!.fetchQualifiedSegments(options); + expect(response.success, isTrue); + expect(segmentOptions.length == 2, isTrue); + expect(assertSegmentOptions(options, segmentOptions), isTrue); + }); + }); + + group("sendOdpEvent()", () { + test("should succeed", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + var response = await sdk.sendOdpEvent(action, + type: type, identifiers: identifiers, data: data); + expect(response.success, isTrue); + }); + }); + + group("getVuid()", () { + test("should succeed", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + var response = await sdk.getVuid(); + expect(response.success, isTrue); + expect(response.vuid, equals(vuid)); + }); + }); + group("trackEvent()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); var response = await userContext!.trackEvent(eventKey, eventTags); expect(response.success, isTrue); @@ -569,7 +785,8 @@ void main() { }; var sdk = OptimizelyFlutterSdk(testSDKKey, defaultDecideOptions: defaultDecideOptions); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); var decideKey = "decide-key"; var response = await userContext!.decide(decideKey, options); @@ -588,7 +805,8 @@ void main() { test("decideForKeys should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey, defaultDecideOptions: defaultDecideOptions); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); var decideKeys = ["decide-key-1", "decide-key-2"]; var response = await userContext!.decideForKeys(decideKeys, options); @@ -604,7 +822,8 @@ void main() { test("decideAll() should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey, defaultDecideOptions: defaultDecideOptions); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); var response = await userContext!.decideAll(options); @@ -636,7 +855,8 @@ void main() { group("setForcedDecision()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); var response = await userContext!.setForcedDecision( OptimizelyDecisionContext(flagKey, ruleKey), @@ -649,7 +869,8 @@ void main() { group("getForcedDecision()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); var response = await userContext! .getForcedDecision(OptimizelyDecisionContext(flagKey, ruleKey)); @@ -662,7 +883,8 @@ void main() { group("removeForcedDecision()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); var response = await userContext! .removeForcedDecision(OptimizelyDecisionContext(flagKey, ruleKey)); @@ -674,7 +896,8 @@ void main() { test("removeAllForcedDecisions() should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); - var userContext = await sdk.createUserContext(userId, attributes); + var userContext = + await sdk.createUserContext(userId: userId, attributes: attributes); var response = await userContext!.removeAllForcedDecisions(); From 0ac62f30d9b6c121a2ceb321f26d98a49aff2f77 Mon Sep 17 00:00:00 2001 From: Yasir Folio3 <39988750+yasirfolio3@users.noreply.github.com> Date: Tue, 9 May 2023 13:47:53 -0400 Subject: [PATCH 17/39] [FSSDK-9028]: Adds native iOS implementation for ODP (#52) --- ios/Classes/HelperClasses/Constants.swift | 31 ++++ ios/Classes/HelperClasses/Utils.swift | 18 ++ .../SwiftOptimizelyFlutterSdkPlugin.swift | 155 +++++++++++++++++- ios/optimizely_flutter_sdk.podspec | 2 +- .../get_qualified_segments_response.dart | 3 +- 5 files changed, 198 insertions(+), 11 deletions(-) diff --git a/ios/Classes/HelperClasses/Constants.swift b/ios/Classes/HelperClasses/Constants.swift index 22fdef9..c674389 100644 --- a/ios/Classes/HelperClasses/Constants.swift +++ b/ios/Classes/HelperClasses/Constants.swift @@ -38,6 +38,14 @@ struct API { static let removeNotificationListener = "removeNotificationListener" static let clearNotificationListeners = "clearNotificationListeners" static let clearAllNotificationListeners = "clearAllNotificationListeners" + + // ODP + static let sendOdpEvent = "sendOdpEvent" + static let getVuid = "getVuid" + static let getQualifiedSegments = "getQualifiedSegments" + static let setQualifiedSegments = "setQualifiedSegments" + static let isQualifiedFor = "isQualifiedFor" + static let fetchQualifiedSegments = "fetchQualifiedSegments" } struct NotificationType { @@ -56,6 +64,11 @@ struct DecideOption { static let excludeVariables = "excludeVariables" } +struct SegmentOption { + static let ignoreCache = "ignoreCache" + static let resetCache = "resetCache" +} + struct RequestParameterKey { static let sdkKey = "sdkKey" static let userId = "userId" @@ -83,6 +96,23 @@ struct RequestParameterKey { static let datafilePeriodicDownloadInterval = "datafilePeriodicDownloadInterval" static let datafileHostPrefix = "datafileHostPrefix" static let datafileHostSuffix = "datafileHostSuffix" + + // ODP + static let vuid = "vuid" + static let qualifiedSegments = "qualifiedSegments" + static let segment = "segment" + static let action = "action" + static let identifiers = "identifiers" + static let data = "data" + static let type = "type" + static let optimizelySegmentOption = "optimizelySegmentOption" + + static let optimizelySdkSettings = "optimizelySdkSettings" + static let segmentsCacheSize = "segmentsCacheSize" + static let segmentsCacheTimeoutInSecs = "segmentsCacheTimeoutInSecs" + static let timeoutForSegmentFetchInSecs = "timeoutForSegmentFetchInSecs" + static let timeoutForOdpEventInSecs = "timeoutForOdpEventInSecs" + static let disableOdp = "disableOdp" } struct ResponseKey { @@ -97,6 +127,7 @@ struct ErrorMessage { static let optimizelyConfigNotFound = "No optimizely config found." static let optlyClientNotFound = "Optimizely client not found." static let userContextNotFound = "User context not found." + static let qualifiedSegmentsNotFound = "Qualified Segments not found." } //Sohail: There is one issue, can we make sure the types remain same, probably we will need to write unit test separately for type. diff --git a/ios/Classes/HelperClasses/Utils.swift b/ios/Classes/HelperClasses/Utils.swift index 7062bae..82f858a 100644 --- a/ios/Classes/HelperClasses/Utils.swift +++ b/ios/Classes/HelperClasses/Utils.swift @@ -152,6 +152,24 @@ public class Utils: NSObject { return convertedOptions } + /// Converts and returns string segment options to array of OptimizelySegmentOption + static func getSegmentOptions(options: [String]?) -> [OptimizelySegmentOption]? { + guard let finalOptions = options else { + return nil + } + var convertedOptions = [OptimizelySegmentOption]() + for option in finalOptions { + switch option { + case SegmentOption.ignoreCache: + convertedOptions.append(OptimizelySegmentOption.ignoreCache) + case SegmentOption.resetCache: + convertedOptions.append(OptimizelySegmentOption.resetCache) + default: break + } + } + return convertedOptions + } + static func convertDecisionToDictionary(decision: OptimizelyDecision?) -> [String: Any?] { let userContext: [String: Any?] = [RequestParameterKey.userId : decision?.userContext.userId, diff --git a/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift b/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift index 2d1e594..89bac9e 100644 --- a/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift +++ b/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift @@ -45,7 +45,7 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin { /// Part of FlutterPlugin protocol to handle communication with flutter sdk public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - + switch call.method { case API.initialize: initialize(call, result: result) case API.addNotificationListener: addNotificationListener(call, result: result) @@ -67,6 +67,14 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin { case API.removeForcedDecision: removeForcedDecision(call, result: result) case API.removeAllForcedDecisions: removeAllForcedDecisions(call, result: result) case API.close: close(call, result: result) + + // ODP + case API.getQualifiedSegments: getQualifiedSegments(call, result: result) + case API.setQualifiedSegments: setQualifiedSegments(call, result: result) + case API.getVuid: getVuid(call, result: result) + case API.isQualifiedFor: isQualifiedFor(call, result: result) + case API.sendOdpEvent: sendOdpEvent(call, result: result) + case API.fetchQualifiedSegments: fetchQualifiedSegments(call, result: result) default: result(FlutterMethodNotImplemented) } } @@ -99,6 +107,31 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin { } let defaultDecideOptions = Utils.getDecideOptions(options: decideOptions) + // SDK Settings Default Values + var segmentsCacheSize: Int = 100 + var segmentsCacheTimeoutInSecs: Int = 600 + var timeoutForSegmentFetchInSecs: Int = 10 + var timeoutForOdpEventInSecs: Int = 10 + var disableOdp: Bool = false + if let sdkSettings = parameters[RequestParameterKey.optimizelySdkSettings] as? Dictionary { + if let cacheSize = sdkSettings[RequestParameterKey.segmentsCacheSize] as? Int { + segmentsCacheSize = cacheSize + } + if let segmentsCacheTimeout = sdkSettings[RequestParameterKey.segmentsCacheTimeoutInSecs] as? Int { + segmentsCacheTimeoutInSecs = segmentsCacheTimeout + } + if let timeoutForSegmentFetch = sdkSettings[RequestParameterKey.timeoutForSegmentFetchInSecs] as? Int { + timeoutForSegmentFetchInSecs = timeoutForSegmentFetch + } + if let timeoutForOdpEvent = sdkSettings[RequestParameterKey.timeoutForOdpEventInSecs] as? Int { + timeoutForOdpEventInSecs = timeoutForOdpEvent + } + if let isOdpDisabled = sdkSettings[RequestParameterKey.disableOdp] as? Bool { + disableOdp = isOdpDisabled + } + } + let optimizelySdkSettings = OptimizelySdkSettings(segmentsCacheSize: segmentsCacheSize, segmentsCacheTimeoutInSecs: segmentsCacheTimeoutInSecs, timeoutForSegmentFetchInSecs: timeoutForSegmentFetchInSecs, timeoutForOdpEventInSecs: timeoutForOdpEventInSecs, disableOdp: disableOdp) + // Datafile Download Interval var datafilePeriodicDownloadInterval = 10 * 60 // seconds @@ -119,7 +152,7 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin { optimizelyClientsTracker.removeValue(forKey: sdkKey) // Creating new instance - let optimizelyInstance = OptimizelyClient(sdkKey:sdkKey, eventDispatcher: eventDispatcher, datafileHandler: datafileHandler, periodicDownloadInterval: datafilePeriodicDownloadInterval, defaultDecideOptions: defaultDecideOptions) + let optimizelyInstance = OptimizelyClient(sdkKey:sdkKey, eventDispatcher: eventDispatcher, datafileHandler: datafileHandler, periodicDownloadInterval: datafilePeriodicDownloadInterval, defaultDecideOptions: defaultDecideOptions, settings: optimizelySdkSettings) optimizelyInstance.start{ [weak self] res in switch res { @@ -198,7 +231,7 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin { guard let optimizelyClient = getOptimizelyClient(sdkKey: sdkKey, result: result) else { return } - + if let type = parameters[RequestParameterKey.notificationType] as? String, let convertedNotificationType = Utils.getNotificationType(type: type) { // Remove listeners only for the provided type optimizelyClient.notificationCenter?.clearNotificationListeners(type: convertedNotificationType) @@ -302,7 +335,7 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin { let success = optimizelyClient.setForcedVariation(experimentKey: experimentKey, userId: userId, variationKey: variationKey) result(self.createResponse(success: success)) } - + /// Creates a context of the user for which decision APIs will be called. /// A user context will only be created successfully when the SDK is fully configured using initializeClient. func createUserContext(_ call: FlutterMethodCall, result: @escaping FlutterResult) { @@ -312,13 +345,15 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin { guard let optimizelyClient = getOptimizelyClient(sdkKey: sdkKey, result: result) else { return } - guard let userId = parameters[RequestParameterKey.userId] as? String else { - result(createResponse(success: false, reason: ErrorMessage.invalidParameters)) - return - } let userContextId = uuid - let userContext = optimizelyClient.createUserContext(userId: userId, attributes: Utils.getTypedMap(arguments: parameters[RequestParameterKey.attributes] as? Any)) + var userContext: OptimizelyUserContext! + + if let userId = parameters[RequestParameterKey.userId] as? String { + userContext = optimizelyClient.createUserContext(userId: userId, attributes: Utils.getTypedMap(arguments: parameters[RequestParameterKey.attributes] as? Any)) + } else { + userContext = optimizelyClient.createUserContext(attributes: Utils.getTypedMap(arguments: parameters[RequestParameterKey.attributes] as? Any)) + } if userContextsTracker[sdkKey] != nil { userContextsTracker[sdkKey]![userContextId] = userContext } else { @@ -359,6 +394,108 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin { result(createResponse(success: true)) } + /// Returns an array of segments that the user is qualified for. + func getQualifiedSegments(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + guard let (_, userContext) = getParametersAndUserContext(arguments: call.arguments, result: result) else { + return + } + guard let qualifiedSegments = userContext.qualifiedSegments else { + result(createResponse(success: false, reason: ErrorMessage.qualifiedSegmentsNotFound)) + return + } + result(createResponse(success: true, result: [RequestParameterKey.qualifiedSegments: qualifiedSegments])) + } + + /// Sets qualified segments for the user context. + func setQualifiedSegments(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + guard let (parameters, userContext) = getParametersAndUserContext(arguments: call.arguments, result: result) else { + return + } + guard let qualifiedSegments = parameters[RequestParameterKey.qualifiedSegments] as? [String] else { + result(createResponse(success: false, reason: ErrorMessage.invalidParameters)) + return + } + userContext.qualifiedSegments = qualifiedSegments + result(createResponse(success: true)) + } + + /// Returns the device vuid. + func getVuid(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + guard let (_, sdkKey) = getParametersAndSdkKey(arguments: call.arguments, result: result) else { + return + } + guard let optimizelyClient = getOptimizelyClient(sdkKey: sdkKey, result: result) else { + return + } + result(self.createResponse(success: true, result: [RequestParameterKey.vuid: optimizelyClient.vuid])) + } + + /// Checks if the user is qualified for the given segment. + func isQualifiedFor(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + guard let (parameters, userContext) = getParametersAndUserContext(arguments: call.arguments, result: result) else { + return + } + guard let segment = parameters[RequestParameterKey.segment] as? String else { + result(createResponse(success: false, reason: ErrorMessage.invalidParameters)) + return + } + result(self.createResponse(success: userContext.isQualifiedFor(segment: segment))) + } + + /// Send an event to the ODP server. + func sendOdpEvent(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + guard let (parameters, sdkKey) = getParametersAndSdkKey(arguments: call.arguments, result: result) else { + return + } + guard let optimizelyClient = getOptimizelyClient(sdkKey: sdkKey, result: result) else { + return + } + guard let action = parameters[RequestParameterKey.action] as? String else { + result(createResponse(success: false, reason: ErrorMessage.invalidParameters)) + return + } + + var type: String? + var identifiers: [String: String] = [:] + var data: [String: Any?] = [:] + + if let _type = parameters[RequestParameterKey.type] as? String { + type = _type + } + if let _identifiers = parameters[RequestParameterKey.identifiers] as? Dictionary { + identifiers = _identifiers + } + if let _data = Utils.getTypedMap(arguments: parameters[RequestParameterKey.data] as? Any) { + data = _data + } + + do { + try optimizelyClient.sendOdpEvent(type: type, action: action, identifiers: identifiers, data: data) + result(self.createResponse(success: true)) + } catch { + result(self.createResponse(success: false, reason: error.localizedDescription)) + } + } + + /// Fetch all qualified segments for the user context. + func fetchQualifiedSegments(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + guard let (parameters, userContext) = getParametersAndUserContext(arguments: call.arguments, result: result) else { + return + } + var segmentOptions: [String]? + if let options = parameters[RequestParameterKey.optimizelySegmentOption] as? [String] { + segmentOptions = options + } + + let options = Utils.getSegmentOptions(options: segmentOptions) + do { + try userContext.fetchQualifiedSegments(options: options ?? []) + result(createResponse(success: true)) + } catch { + result(self.createResponse(success: false, reason: error.localizedDescription)) + } + } + /// Tracks an event. func trackEvent(_ call: FlutterMethodCall, result: @escaping FlutterResult) { guard let (parameters, userContext) = getParametersAndUserContext(arguments: call.arguments, result: result) else { diff --git a/ios/optimizely_flutter_sdk.podspec b/ios/optimizely_flutter_sdk.podspec index 15d0f02..2a528d5 100644 --- a/ios/optimizely_flutter_sdk.podspec +++ b/ios/optimizely_flutter_sdk.podspec @@ -13,7 +13,7 @@ Pod::Spec.new do |s| s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency 'OptimizelySwiftSDK', '3.10.1' + s.dependency 'OptimizelySwiftSDK', '4.0.0-beta' s.platform = :ios, '10.0' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/lib/src/data_objects/get_qualified_segments_response.dart b/lib/src/data_objects/get_qualified_segments_response.dart index 130c79e..e687ebe 100644 --- a/lib/src/data_objects/get_qualified_segments_response.dart +++ b/lib/src/data_objects/get_qualified_segments_response.dart @@ -18,9 +18,10 @@ import 'package:optimizely_flutter_sdk/src/data_objects/base_response.dart'; import 'package:optimizely_flutter_sdk/src/utils/constants.dart'; class GetQualifiedSegmentsResponse extends BaseResponse { - List qualifiedSegments = []; + List? qualifiedSegments = []; GetQualifiedSegmentsResponse(Map json) : super(json) { + qualifiedSegments = null; if (json[Constants.responseResult] is Map) { var response = Map.from(json[Constants.responseResult]); if (response[Constants.qualifiedSegments] is List) { From 977cbeaa55ba91d862e597acf017f0a22152e5f9 Mon Sep 17 00:00:00 2001 From: Yasir Folio3 <39988750+yasirfolio3@users.noreply.github.com> Date: Wed, 10 May 2023 12:41:53 -0400 Subject: [PATCH 18/39] fixes warnings by `pub publish --dry-run` (#56) --- lib/src/data_objects/optimizely_config.dart | 22 +++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/src/data_objects/optimizely_config.dart b/lib/src/data_objects/optimizely_config.dart index 955bfde..c5963c2 100644 --- a/lib/src/data_objects/optimizely_config.dart +++ b/lib/src/data_objects/optimizely_config.dart @@ -1,5 +1,5 @@ /// ************************************************************************** -/// Copyright 2022, Optimizely, Inc. and contributors * +/// Copyright 2022-2023, Optimizely, Inc. and contributors * /// * /// Licensed under the Apache License, Version 2.0 (the "License"); * /// you may not use this file except in compliance with the License. * @@ -101,11 +101,17 @@ class OptimizelyConfig { Map dynamicFeaturesMap = {}; featuresMap.forEach((k, v) => {dynamicFeaturesMap[k] = v.toJson()}); var dynamicAttributes = []; - attributes.forEach((v) => {dynamicAttributes.add(v.toJson())}); + for (var attribute in attributes) { + dynamicAttributes.add(attribute.toJson()); + } var dynamicEvents = []; - events.forEach((v) => {dynamicEvents.add(v.toJson())}); + for (var event in events) { + dynamicEvents.add(event.toJson()); + } var dynamicAudiences = []; - audiences.forEach((v) => {dynamicAudiences.add(v.toJson())}); + for (var audience in audiences) { + dynamicAudiences.add(audience.toJson()); + } return { 'experimentsMap': dynamicExpMap, @@ -244,10 +250,14 @@ class OptimizelyFeature { Map toJson() { var dynamicDeliveryRules = []; - deliveryRules.forEach((v) => {dynamicDeliveryRules.add(v.toJson())}); + for (var deliveryRule in deliveryRules) { + dynamicDeliveryRules.add(deliveryRule.toJson()); + } var dynamicExperimentRules = []; - experimentRules.forEach((v) => {dynamicExperimentRules.add(v.toJson())}); + for (var experimentRule in experimentRules) { + dynamicExperimentRules.add(experimentRule.toJson()); + } return { 'id': id, 'key': key, From 9471ff0dc8bfc055253ac7ba3ccbe745a430966d Mon Sep 17 00:00:00 2001 From: Yasir Folio3 <39988750+yasirfolio3@users.noreply.github.com> Date: Thu, 18 May 2023 13:35:21 -0400 Subject: [PATCH 19/39] [FSSDK-9375]: Makes SendOdpEvent for iOS consistent with android. (#59) * making SendOdpEvent consistent with android. --- ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift b/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift index 89bac9e..cdc1549 100644 --- a/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift +++ b/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift @@ -450,7 +450,7 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin { guard let optimizelyClient = getOptimizelyClient(sdkKey: sdkKey, result: result) else { return } - guard let action = parameters[RequestParameterKey.action] as? String else { + guard let action = parameters[RequestParameterKey.action] as? String, action != "" else { result(createResponse(success: false, reason: ErrorMessage.invalidParameters)) return } @@ -471,10 +471,10 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin { do { try optimizelyClient.sendOdpEvent(type: type, action: action, identifiers: identifiers, data: data) - result(self.createResponse(success: true)) } catch { - result(self.createResponse(success: false, reason: error.localizedDescription)) + print(error.localizedDescription) } + result(self.createResponse(success: true)) } /// Fetch all qualified segments for the user context. From 69959d483910da98da2a685a53e9a09cc74d8f2c Mon Sep 17 00:00:00 2001 From: Muhammad Noman Date: Fri, 19 May 2023 23:15:13 +0500 Subject: [PATCH 20/39] [FSSDK-8840]: Adds native Android implementation for ODP (#57) * Adds native Android implementation for ODP --------- Co-authored-by: NomanShoaib --- README.md | 2 +- android/build.gradle | 4 +- .../OptimizelyFlutterClient.java | 161 ++++++++++++++++-- .../OptimizelyFlutterSdkPlugin.java | 26 ++- .../helper_classes/ArgumentsParser.java | 35 +++- .../helper_classes/Constants.java | 31 +++- .../helper_classes/Utils.java | 23 ++- example/android/app/build.gradle | 2 +- example/android/build.gradle | 2 +- 9 files changed, 267 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 5da0c09..eaadfbf 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Refer to the [Flutter SDK's developer documentation](https://docs.developers.opt See the [pubspec.yaml](https://github.com/optimizely/optimizely-flutter-sdk/blob/master/pubspec.yaml) file for Flutter version requirements. -On the Android platform, the SDK requires a minimum SDK version of 14 or higher and compile SDK version of 32. +On the Android platform, the SDK requires a minimum SDK version of 21 or higher and compile SDK version of 32. On the iOS platform, the SDK requires a minimum version of 10.0. diff --git a/android/build.gradle b/android/build.gradle index d707cf2..df5b447 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -34,7 +34,7 @@ apply plugin: 'com.android.library' ext { compile_sdk_version = 32 build_tools_version = "30.0.3" - min_sdk_version = 14 + min_sdk_version = 21 target_sdk_version = 29 } @@ -77,7 +77,7 @@ dependencies { implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' implementation group: 'org.slf4j', name: 'slf4j-android', version: '1.7.25' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10" - implementation "com.optimizely.ab:android-sdk:3.13.2" + implementation "com.optimizely.ab:android-sdk:4.0.0-beta2" implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8' implementation ('com.google.guava:guava:19.0') { exclude group:'com.google.guava', module:'listenablefuture' diff --git a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterClient.java b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterClient.java index 2e3bd69..8d0ca28 100644 --- a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterClient.java +++ b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterClient.java @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2022, Optimizely, Inc. and contributors * + * Copyright 2022-2023, Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * @@ -46,6 +46,7 @@ import com.optimizely.ab.notification.NotificationCenter; import com.optimizely.ab.notification.TrackNotification; import com.optimizely.ab.notification.UpdateConfigNotification; +import com.optimizely.ab.odp.ODPSegmentOption; import com.optimizely.ab.optimizelyconfig.OptimizelyConfig; import com.optimizely.ab.optimizelydecision.OptimizelyDecideOption; import com.optimizely.ab.optimizelydecision.OptimizelyDecision; @@ -53,6 +54,11 @@ import com.optimizely.optimizely_flutter_sdk.helper_classes.Utils; import static com.optimizely.optimizely_flutter_sdk.helper_classes.Constants.*; +import static com.optimizely.optimizely_flutter_sdk.helper_classes.Constants.RequestParameterKey.DISABLE_ODP; +import static com.optimizely.optimizely_flutter_sdk.helper_classes.Constants.RequestParameterKey.SEGMENTS_CACHE_SIZE; +import static com.optimizely.optimizely_flutter_sdk.helper_classes.Constants.RequestParameterKey.SEGMENTS_CACHE_TIMEOUT_IN_SECONDS; +import static com.optimizely.optimizely_flutter_sdk.helper_classes.Constants.RequestParameterKey.TIMEOUT_FOR_ODP_EVENT_IN_SECONDS; +import static com.optimizely.optimizely_flutter_sdk.helper_classes.Constants.RequestParameterKey.TIMEOUT_FOR_SEGMENT_FETCH_IN_SECONDS; import static com.optimizely.optimizely_flutter_sdk.helper_classes.Utils.getNotificationListenerType; import java.util.Collections; @@ -127,16 +133,48 @@ protected void initializeOptimizely(@NonNull ArgumentsParser argumentsParser, @N notificationIdsTracker.remove(sdkKey); List defaultDecideOptions = argumentsParser.getDecideOptions(); + + // SDK Settings Default Values + int segmentsCacheSize = 100; + int segmentsCacheTimeoutInSecs = 600; + int timeoutForSegmentFetchInSecs = 10; + int timeoutForOdpEventInSecs = 10; + boolean disableOdp = false; + Map sdkSettings = argumentsParser.getOptimizelySdkSettings(); + if (sdkSettings != null) { + if (sdkSettings.containsKey(SEGMENTS_CACHE_SIZE)) { + segmentsCacheSize = (Integer) sdkSettings.get(SEGMENTS_CACHE_SIZE); + } + if (sdkSettings.containsKey(SEGMENTS_CACHE_TIMEOUT_IN_SECONDS)) { + segmentsCacheTimeoutInSecs = (Integer) sdkSettings.get(SEGMENTS_CACHE_TIMEOUT_IN_SECONDS); + } + if (sdkSettings.containsKey(TIMEOUT_FOR_SEGMENT_FETCH_IN_SECONDS)) { + timeoutForSegmentFetchInSecs = (Integer) sdkSettings.get(TIMEOUT_FOR_SEGMENT_FETCH_IN_SECONDS); + } + if (sdkSettings.containsKey(TIMEOUT_FOR_ODP_EVENT_IN_SECONDS)) { + timeoutForOdpEventInSecs = (Integer) sdkSettings.get(TIMEOUT_FOR_ODP_EVENT_IN_SECONDS); + } + if (sdkSettings.containsKey(DISABLE_ODP)) { + disableOdp = (boolean) sdkSettings.get(DISABLE_ODP); + } + } // Creating new instance - OptimizelyManager optimizelyManager = OptimizelyManager.builder() + OptimizelyManager.Builder optimizelyManagerBuilder = OptimizelyManager.builder() .withEventProcessor(batchProcessor) .withEventHandler(eventHandler) .withNotificationCenter(notificationCenter) .withDatafileDownloadInterval(datafilePeriodicDownloadInterval, TimeUnit.SECONDS) .withErrorHandler(new RaiseExceptionErrorHandler()) .withDefaultDecideOptions(defaultDecideOptions) - .withSDKKey(sdkKey) - .build(context); + .withODPSegmentCacheSize(segmentsCacheSize) + .withODPSegmentCacheTimeout(segmentsCacheTimeoutInSecs, TimeUnit.SECONDS) + .withTimeoutForODPSegmentFetch(timeoutForSegmentFetchInSecs) + .withTimeoutForODPEventDispatch(timeoutForOdpEventInSecs) + .withSDKKey(sdkKey); + if (disableOdp) { + optimizelyManagerBuilder.withODPDisabled(); + } + OptimizelyManager optimizelyManager = optimizelyManagerBuilder.build(context); optimizelyManager.initialize(context, null, (OptimizelyClient client) -> { if (client.isValid()) { @@ -158,14 +196,14 @@ protected void createUserContext(ArgumentsParser argumentsParser, @NonNull Resul String userId = argumentsParser.getUserId(); Map attributes = argumentsParser.getAttributes(); - if (userId == null) { - result.success(createResponse(ErrorMessage.INVALID_PARAMS)); - return; - } try { String userContextId = Utils.getRandomUUID(); - - OptimizelyUserContext optlyUserContext = optimizelyClient.createUserContext(userId, attributes); + OptimizelyUserContext optlyUserContext; + if (userId != null) { + optlyUserContext = optimizelyClient.createUserContext(userId, attributes); + } else { + optlyUserContext = optimizelyClient.createUserContext(attributes); + } if (optlyUserContext != null) { if (userContextsTracker.containsKey(sdkKey)) { userContextsTracker.get(sdkKey).put(userContextId, optlyUserContext); @@ -390,6 +428,109 @@ protected void removeAllForcedDecisions(ArgumentsParser argumentsParser, @NonNul result.success(createResponse()); } + /// Returns an array of segments that the user is qualified for. + protected void getQualifiedSegments(ArgumentsParser argumentsParser, @NonNull Result result) { + String sdkKey = argumentsParser.getSdkKey(); + OptimizelyUserContext userContext = getUserContext(argumentsParser); + if (!isUserContextValid(sdkKey, userContext, result)) { + return; + } + List qualifiedSegments = userContext.getQualifiedSegments(); + if (qualifiedSegments != null) { + result.success(createResponse(Collections.singletonMap(RequestParameterKey.QUALIFIED_SEGMENTS, qualifiedSegments))); + } else { + result.success(createResponse(ErrorMessage.QUALIFIED_SEGMENTS_NOT_FOUND)); + } + } + + /// Sets qualified segments for the user context. + protected void setQualifiedSegments(ArgumentsParser argumentsParser, @NonNull Result result) { + String sdkKey = argumentsParser.getSdkKey(); + OptimizelyUserContext userContext = getUserContext(argumentsParser); + if (!isUserContextValid(sdkKey, userContext, result)) { + return; + } + List qualifiedSegments = argumentsParser.getQualifiedSegments(); + if (qualifiedSegments == null) { + result.success(createResponse(ErrorMessage.INVALID_PARAMS)); + return; + } + userContext.setQualifiedSegments(qualifiedSegments); + result.success(createResponse()); + } + + /// Returns the device vuid. + protected void getVuid(ArgumentsParser argumentsParser, @NonNull Result result) { + String sdkKey = argumentsParser.getSdkKey(); + OptimizelyClient optimizelyClient = getOptimizelyClient(sdkKey); + if (!isOptimizelyClientValid(sdkKey, optimizelyClient, result)) { + return; + } + result.success(createResponse(true, Collections.singletonMap(RequestParameterKey.VUID, optimizelyClient.getVuid()), "")); + } + + /// Checks if the user is qualified for the given segment. + protected void isQualifiedFor(ArgumentsParser argumentsParser, @NonNull Result result) { + String sdkKey = argumentsParser.getSdkKey(); + OptimizelyUserContext userContext = getUserContext(argumentsParser); + if (!isUserContextValid(sdkKey, userContext, result)) { + return; + } + String segment = argumentsParser.getSegment(); + if (segment == null) { + result.success(createResponse(ErrorMessage.INVALID_PARAMS)); + return; + } + + result.success(createResponse(userContext.isQualifiedFor(segment))); + } + + /// Send an event to the ODP server. + protected void sendODPEvent(ArgumentsParser argumentsParser, @NonNull Result result) { + String sdkKey = argumentsParser.getSdkKey(); + OptimizelyClient optimizelyClient = getOptimizelyClient(sdkKey); + if (!isOptimizelyClientValid(sdkKey, optimizelyClient, result)) { + return; + } + String action = argumentsParser.getAction(); + if (action == null || action.isEmpty()) { + result.success(createResponse(ErrorMessage.INVALID_PARAMS)); + return; + } + + String type = argumentsParser.getType(); + Map identifiers = argumentsParser.getIdentifiers(); + if (identifiers == null) { + identifiers = new HashMap<>(); + } + Map data = argumentsParser.getData(); + if (data == null) { + data = new HashMap<>(); + } + + optimizelyClient.sendODPEvent(type, action, identifiers, data); + result.success(createResponse()); + } + + /// Fetch all qualified segments for the user context. + protected void fetchQualifiedSegments(ArgumentsParser argumentsParser, @NonNull Result result) { + String sdkKey = argumentsParser.getSdkKey(); + OptimizelyUserContext userContext = getUserContext(argumentsParser); + if (!isUserContextValid(sdkKey, userContext, result)) { + return; + } + List segmentOptions = argumentsParser.getSegmentOptions(); + + try { + userContext.fetchQualifiedSegments((fetchQualifiedResult) -> { + result.success(createResponse(fetchQualifiedResult)); + },segmentOptions); + + } catch (Exception ex) { + result.success(createResponse(ex.getMessage())); + } + } + protected void close(ArgumentsParser argumentsParser, @NonNull Result result) { String sdkKey = argumentsParser.getSdkKey(); OptimizelyClient optimizelyClient = getOptimizelyClient(sdkKey); diff --git a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.java b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.java index edc49c7..89f787c 100644 --- a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.java +++ b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.java @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2022, Optimizely, Inc. and contributors * + * Copyright 2022-2023, Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * @@ -119,6 +119,30 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { removeAllForcedDecisions(argumentsParser, result); break; } + case APIs.GET_QUALIFIED_SEGMENTS: { + getQualifiedSegments(argumentsParser, result); + break; + } + case APIs.SET_QUALIFIED_SEGMENTS: { + setQualifiedSegments(argumentsParser, result); + break; + } + case APIs.GET_VUID: { + getVuid(argumentsParser, result); + break; + } + case APIs.IS_QUALIFIED_FOR: { + isQualifiedFor(argumentsParser, result); + break; + } + case APIs.SEND_ODP_EVENT: { + sendODPEvent(argumentsParser, result); + break; + } + case APIs.FETCH_QUALIFIED_SEGMENTS: { + fetchQualifiedSegments(argumentsParser, result); + break; + } case APIs.CLOSE: { close(argumentsParser, result); break; diff --git a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/ArgumentsParser.java b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/ArgumentsParser.java index d726f21..605b0aa 100644 --- a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/ArgumentsParser.java +++ b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/ArgumentsParser.java @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2022, Optimizely, Inc. and contributors * + * Copyright 2022-2023, Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * @@ -15,6 +15,7 @@ ***************************************************************************/ package com.optimizely.optimizely_flutter_sdk.helper_classes; +import com.optimizely.ab.odp.ODPSegmentOption; import com.optimizely.ab.optimizelydecision.OptimizelyDecideOption; import java.util.List; @@ -110,4 +111,36 @@ public String getDatafileHostPrefix() { public String getExperimentKey() { return (String) arguments.get(Constants.RequestParameterKey.EXPERIMENT_KEY); } + + public List getQualifiedSegments() { + return (List) arguments.get(Constants.RequestParameterKey.QUALIFIED_SEGMENTS); + } + + public String getSegment() { + return (String) arguments.get(Constants.RequestParameterKey.SEGMENT); + } + + public String getAction() { + return (String) arguments.get(Constants.RequestParameterKey.ACTION); + } + + public String getType() { + return (String) arguments.get(Constants.RequestParameterKey.ODP_EVENT_TYPE); + } + + public Map getIdentifiers() { + return (Map) arguments.get(Constants.RequestParameterKey.IDENTIFIERS); + } + + public Map getData() { + return (Map) arguments.get(Constants.RequestParameterKey.DATA); + } + + public List getSegmentOptions() { + return Utils.getSegmentOptions((List) arguments.get(Constants.RequestParameterKey.OPTIMIZELY_SEGMENT_OPTION)); + } + + public Map getOptimizelySdkSettings() { + return (Map) arguments.get(Constants.RequestParameterKey.OPTIMIZELY_SDK_SETTINGS); + } } diff --git a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Constants.java b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Constants.java index 8e1b40b..2f200db 100644 --- a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Constants.java +++ b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Constants.java @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2022, Optimizely, Inc. and contributors * + * Copyright 2022-2023, Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * @@ -38,6 +38,14 @@ public static class APIs { public static final String REMOVE_NOTIFICATION_LISTENER = "removeNotificationListener"; public static final String CLEAR_ALL_NOTIFICATION_LISTENERS = "clearAllNotificationListeners"; public static final String CLEAR_NOTIFICATION_LISTENERS = "clearNotificationListeners"; + + // ODP APIs constants + public static final String SEND_ODP_EVENT = "sendOdpEvent"; + public static final String GET_VUID = "getVuid"; + public static final String GET_QUALIFIED_SEGMENTS = "getQualifiedSegments"; + public static final String SET_QUALIFIED_SEGMENTS = "setQualifiedSegments"; + public static final String IS_QUALIFIED_FOR = "isQualifiedFor"; + public static final String FETCH_QUALIFIED_SEGMENTS = "fetchQualifiedSegments"; } public static class NotificationType { @@ -71,6 +79,21 @@ public static class RequestParameterKey { public static final String VARIATION_KEY = "variationKey"; public static final String DATAFILE_HOST_PREFIX = "datafileHostPrefix"; public static final String DATAFILE_HOST_SUFFIX = "datafileHostSuffix"; + + public static final String VUID = "vuid"; + public static final String QUALIFIED_SEGMENTS = "qualifiedSegments"; + public static final String SEGMENT = "segment"; + public static final String ACTION = "action"; + public static final String IDENTIFIERS = "identifiers"; + public static final String DATA = "data"; + public static final String ODP_EVENT_TYPE = "type"; + public static final String OPTIMIZELY_SEGMENT_OPTION = "optimizelySegmentOption"; + public static final String OPTIMIZELY_SDK_SETTINGS = "optimizelySdkSettings"; + public static final String SEGMENTS_CACHE_SIZE = "segmentsCacheSize"; + public static final String SEGMENTS_CACHE_TIMEOUT_IN_SECONDS = "segmentsCacheTimeoutInSecs"; + public static final String TIMEOUT_FOR_SEGMENT_FETCH_IN_SECONDS = "timeoutForSegmentFetchInSecs"; + public static final String TIMEOUT_FOR_ODP_EVENT_IN_SECONDS = "timeoutForOdpEventInSecs"; + public static final String DISABLE_ODP = "disableOdp"; } public static class ErrorMessage { @@ -80,6 +103,7 @@ public static class ErrorMessage { public static final String OPTIMIZELY_CLIENT_NOT_FOUND = "Optimizely client not found."; public static final String USER_CONTEXT_NOT_FOUND = "User context not found."; public static final String USER_CONTEXT_NOT_CREATED = "User context not created."; + public static final String QUALIFIED_SEGMENTS_NOT_FOUND = "Qualified Segments not found."; } public static class DecisionListenerKeys { @@ -124,4 +148,9 @@ public static class DecideOption { public static final String INCLUDE_REASONS = "includeReasons"; public static final String EXCLUDE_VARIABLES = "excludeVariables"; } + + public static class SegmentOption { + public static final String IGNORE_CACHE = "ignoreCache"; + public static final String RESET_CACHE = "resetCache"; + } } diff --git a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Utils.java b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Utils.java index 7a4fb5d..76ec8c8 100644 --- a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Utils.java +++ b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Utils.java @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2022, Optimizely, Inc. and contributors * + * Copyright 2022-2023, Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * @@ -29,6 +29,7 @@ import com.optimizely.ab.notification.DecisionNotification; import com.optimizely.ab.notification.TrackNotification; import com.optimizely.ab.notification.UpdateConfigNotification; +import com.optimizely.ab.odp.ODPSegmentOption; import com.optimizely.ab.optimizelydecision.OptimizelyDecideOption; public class Utils { @@ -66,6 +67,26 @@ public static List getDecideOptions(List options return convertedOptions; } + public static List getSegmentOptions(List options) { + if(options == null || options.isEmpty()) { + return null; + } + List convertedOptions = new ArrayList<>(); + for(String option: options) { + switch(option) { + case Constants.SegmentOption.IGNORE_CACHE: + convertedOptions.add(ODPSegmentOption.IGNORE_CACHE); + break; + case Constants.SegmentOption.RESET_CACHE: + convertedOptions.add(ODPSegmentOption.RESET_CACHE); + break; + default: + break; + } + } + return convertedOptions; + } + public static Class getNotificationListenerType(String notificationType) { if (notificationType == null || notificationType.isEmpty()) { return null; diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index e643787..ceec9a6 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -39,7 +39,7 @@ android { applicationId "com.optimizely.optimizely_flutter_sdk_example" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. - minSdkVersion flutter.minSdkVersion + minSdkVersion 21 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/example/android/build.gradle b/example/android/build.gradle index fbba653..57c3218 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { } } ext { - android_sdk_version = "3.13.2" + android_sdk_version = "4.0.0-beta2" } allprojects { repositories { From a3a859b419758b5022a8e670a679feeb9d00b0cf Mon Sep 17 00:00:00 2001 From: Yasir Folio3 <39988750+yasirfolio3@users.noreply.github.com> Date: Tue, 13 Jun 2023 15:37:43 -0400 Subject: [PATCH 21/39] suggested fixes by pub.dev (#60) --- lib/src/data_objects/optimizely_config.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/data_objects/optimizely_config.dart b/lib/src/data_objects/optimizely_config.dart index c5963c2..51afbef 100644 --- a/lib/src/data_objects/optimizely_config.dart +++ b/lib/src/data_objects/optimizely_config.dart @@ -97,9 +97,9 @@ class OptimizelyConfig { Map toJson() { Map dynamicExpMap = {}; - experimentsMap.forEach((k, v) => {dynamicExpMap[k] = v.toJson()}); + experimentsMap.forEach((k, v) => dynamicExpMap[k] = v.toJson()); Map dynamicFeaturesMap = {}; - featuresMap.forEach((k, v) => {dynamicFeaturesMap[k] = v.toJson()}); + featuresMap.forEach((k, v) => dynamicFeaturesMap[k] = v.toJson()); var dynamicAttributes = []; for (var attribute in attributes) { dynamicAttributes.add(attribute.toJson()); @@ -305,7 +305,7 @@ class OptimizelyExperiment { Map toJson() { Map dynamicVariationsMap = {}; - variationsMap.forEach((k, v) => {dynamicVariationsMap[k] = v.toJson()}); + variationsMap.forEach((k, v) => dynamicVariationsMap[k] = v.toJson()); return { 'id': id, @@ -355,7 +355,7 @@ class OptimizelyVariation { Map toJson() { Map dynamicVariablesMap = {}; - variablesMap.forEach((k, v) => {dynamicVariablesMap[k] = v.toJson()}); + variablesMap.forEach((k, v) => dynamicVariablesMap[k] = v.toJson()); return { 'id': id, From b0adb359e24d24d8a54fb633f698850eea967c9c Mon Sep 17 00:00:00 2001 From: Yasir Folio3 <39988750+yasirfolio3@users.noreply.github.com> Date: Tue, 13 Jun 2023 15:48:56 -0400 Subject: [PATCH 22/39] [FSSDK-9372]: Fixes code coverage (#61) --- .github/workflows/flutter.yml | 2 +- README.md | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/flutter.yml b/.github/workflows/flutter.yml index 03fb086..ed8d977 100644 --- a/.github/workflows/flutter.yml +++ b/.github/workflows/flutter.yml @@ -24,7 +24,7 @@ jobs: - name: Upload coverage to Coveralls uses: coverallsapp/github-action@master with: - github-token: ${{ secrets.CI_USER_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} integration_android_tests: runs-on: ubuntu-latest diff --git a/README.md b/README.md index eaadfbf..e8a6edf 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,8 @@ [![Pub Version](https://img.shields.io/pub/v/optimizely_flutter_sdk?color=blueviolet)](https://pub.dev/packages/optimizely_flutter_sdk) [![Pub](https://img.shields.io/pub/v/optimizely_flutter_sdk.svg)](https://pub.dev/packages/optimizely_flutter_sdk) [![Apache 2.0](https://img.shields.io/github/license/nebula-plugins/gradle-extra-configurations-plugin.svg)](https://www.apache.org/licenses/LICENSE-2.0) - This repository houses the Flutter SDK for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy). From 603b651f050d2ef51e080a9b68e3a8c47d19c091 Mon Sep 17 00:00:00 2001 From: Yasir Folio3 <39988750+yasirfolio3@users.noreply.github.com> Date: Tue, 13 Jun 2023 19:49:27 -0400 Subject: [PATCH 23/39] Updating minor dependency and removing maximum dart version limit. (#62) --- pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 66d28c3..18d9c15 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ version: 1.0.1 homepage: https://github.com/optimizely/optimizely-flutter-sdk environment: - sdk: ">=2.16.2 <3.0.0" + sdk: ">=2.16.2" flutter: ">=2.5.0" dependencies: @@ -16,7 +16,7 @@ dev_dependencies: sdk: flutter flutter_test: sdk: flutter - flutter_lints: ^1.0.4 + flutter_lints: ^2.0.1 collection: ^1.16.0 flutter: From e44524e8411b29af08ca2a00e9d96af46e224e1d Mon Sep 17 00:00:00 2001 From: Jae Kim <45045038+jaeopt@users.noreply.github.com> Date: Fri, 16 Jun 2023 08:55:41 -0700 Subject: [PATCH 24/39] [FSSDK-9430] feat: add configurable log level support (#63) Clients can control default log-level of native swift-sdk and android-sdk with a parameter value when initializing SDK. - swift-adk already supports configurable log-level. - android-sdk does not support configurable log-level programmatically. A global slf4j log-level control added into the android plugin wrapper. Example: OptimizelyFlutterSDK(defaultLogLevel: OptimizelyLogLevel.debug) --- android/build.gradle | 10 ++- android/src/main/assets/logback.xml | 18 ++++++ .../OptimizelyFlutterClient.java | 2 + .../helper_classes/ArgumentsParser.java | 4 ++ .../helper_classes/Constants.java | 8 +++ .../helper_classes/Utils.java | 33 ++++++++++ example/android/app/build.gradle | 4 +- example/ios/Podfile | 2 +- example/lib/main.dart | 1 + .../macos/Runner.xcodeproj/project.pbxproj | 62 ++++++++++++++++++- .../contents.xcworkspacedata | 3 + ios/Classes/HelperClasses/Constants.swift | 1 + ios/Classes/HelperClasses/Utils.swift | 13 ++++ .../SwiftOptimizelyFlutterSdkPlugin.swift | 16 ++++- lib/optimizely_flutter_sdk.dart | 7 +++ lib/src/data_objects/log_level.dart | 22 +++++++ lib/src/optimizely_client_wrapper.dart | 5 ++ lib/src/utils/constants.dart | 1 + lib/src/utils/utils.dart | 7 +++ test/optimizely_flutter_sdk_test.dart | 31 ++++++++++ 20 files changed, 242 insertions(+), 8 deletions(-) create mode 100644 android/src/main/assets/logback.xml create mode 100644 lib/src/data_objects/log_level.dart diff --git a/android/build.gradle b/android/build.gradle index df5b447..03427ea 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -75,10 +75,16 @@ android { dependencies { implementation 'androidx.multidex:multidex:2.0.0' implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' - implementation group: 'org.slf4j', name: 'slf4j-android', version: '1.7.25' + + //"logback-android" required for programmatic control of global sl4j log level. + // - default log configuration in /assets/logback.xml + // - [ref] https://github.com/tony19/logback-android + implementation 'com.github.tony19:logback-android:3.0.0' + implementation 'org.slf4j:slf4j-api:2.0.7' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10" implementation "com.optimizely.ab:android-sdk:4.0.0-beta2" - implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.4' implementation ('com.google.guava:guava:19.0') { exclude group:'com.google.guava', module:'listenablefuture' } diff --git a/android/src/main/assets/logback.xml b/android/src/main/assets/logback.xml new file mode 100644 index 0000000..8e6e0d6 --- /dev/null +++ b/android/src/main/assets/logback.xml @@ -0,0 +1,18 @@ + + + + Optimizely + + + %msg + + + + + + + diff --git a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterClient.java b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterClient.java index 8d0ca28..82e3384 100644 --- a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterClient.java +++ b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterClient.java @@ -97,6 +97,8 @@ protected void initializeOptimizely(@NonNull ArgumentsParser argumentsParser, @N maxQueueSize = argumentsParser.getEventMaxQueueSize(); } + Utils.setDefaultLogLevel(argumentsParser.getDefaultLogLevel()); + DefaultEventHandler eventHandler = DefaultEventHandler.getInstance(context); eventHandler.setDispatchInterval(-1L); NotificationCenter notificationCenter = new NotificationCenter(); diff --git a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/ArgumentsParser.java b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/ArgumentsParser.java index 605b0aa..88087c7 100644 --- a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/ArgumentsParser.java +++ b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/ArgumentsParser.java @@ -72,6 +72,10 @@ public List getDecideOptions() { return Utils.getDecideOptions((List) arguments.get(Constants.RequestParameterKey.DECIDE_OPTIONS)); } + public String getDefaultLogLevel() { + return (String) arguments.get(Constants.RequestParameterKey.DEFAULT_LOG_LEVEL); + } + public String getFlagKey() { return (String) arguments.get(Constants.RequestParameterKey.FLAG_KEY); } diff --git a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Constants.java b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Constants.java index 2f200db..453b1d6 100644 --- a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Constants.java +++ b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Constants.java @@ -67,6 +67,7 @@ public static class RequestParameterKey { public static final String ATTRIBUTES = "attributes"; public static final String DECIDE_KEYS = "keys"; public static final String DECIDE_OPTIONS = "optimizelyDecideOption"; + public static final String DEFAULT_LOG_LEVEL = "defaultLogLevel"; public static final String EVENT_BATCH_SIZE = "eventBatchSize"; public static final String EVENT_TIME_INTERVAL = "eventTimeInterval"; public static final String EVENT_MAX_QUEUE_SIZE = "eventMaxQueueSize"; @@ -153,4 +154,11 @@ public static class SegmentOption { public static final String IGNORE_CACHE = "ignoreCache"; public static final String RESET_CACHE = "resetCache"; } + + public static class LogLevel { + public static final String ERROR = "error"; + public static final String WARNING = "warning"; + public static final String INFO = "info"; + public static final String DEBUG = "debug"; + } } diff --git a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Utils.java b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Utils.java index 76ec8c8..a4353b5 100644 --- a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Utils.java +++ b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Utils.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import androidx.annotation.Nullable; import static com.optimizely.ab.notification.DecisionNotification.FeatureVariableDecisionNotificationBuilder.SOURCE_INFO; @@ -31,6 +32,9 @@ import com.optimizely.ab.notification.UpdateConfigNotification; import com.optimizely.ab.odp.ODPSegmentOption; import com.optimizely.ab.optimizelydecision.OptimizelyDecideOption; +import org.slf4j.LoggerFactory; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; public class Utils { @@ -104,4 +108,33 @@ public static Class getNotificationListenerType(String notificationType) { } return listenerClass; } + + // SLF4J log level control: + // - logback logger (ch.qos.logback) is the only option available that supports global log level control programmatically (not only via configuration file) + // - "logback-android" logger (com.github.tony19:logback-android) is integrated in build.gradle. + // - log-level control is not integrated into the native android-sdk core since this solution depends on logback logger. + + public static void setDefaultLogLevel(@Nullable String logLevel) { + Level defaultLogLevel = Utils.mapLogLevel(logLevel); + Logger rootLogger = (Logger) LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME); + rootLogger.setLevel(defaultLogLevel); + } + + public static Level mapLogLevel(@Nullable String logLevel) { + Level level = Level.INFO; + + if (logLevel == null || logLevel.isEmpty()) { + return level; + } + + switch (logLevel) { + case Constants.LogLevel.ERROR: level = Level.ERROR; break; + case Constants.LogLevel.WARNING: level = Level.WARN; break; + case Constants.LogLevel.INFO: level = Level.INFO; break; + case Constants.LogLevel.DEBUG: level = Level.DEBUG; break; + default: {} + } + return level; + } + } diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index ceec9a6..89fc349 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion flutter.compileSdkVersion + compileSdkVersion 32 ndkVersion flutter.ndkVersion compileOptions { @@ -40,7 +40,7 @@ android { // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. minSdkVersion 21 - targetSdkVersion flutter.targetSdkVersion + targetSdkVersion 32 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } diff --git a/example/ios/Podfile b/example/ios/Podfile index 9411102..313ea4a 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '10.0' +platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/lib/main.dart b/example/lib/main.dart index a717223..e7db8fa 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -32,6 +32,7 @@ class _MyAppState extends State { datafilePeriodicDownloadInterval: 10 * 60, eventOptions: const EventOptions( batchSize: 1, timeInterval: 60, maxQueueSize: 10000), + defaultLogLevel: OptimizelyLogLevel.debug, defaultDecideOptions: defaultOptions); var response = await flutterSDK.initializeClient(); diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index 154f82d..9f89128 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 90B9B075FD9D5B075E83BE96 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A8DB69DD1CEB9DE6D5A4070 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -52,9 +53,10 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 019D04A8C81D7599D2E638FA /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* optimizely_flutter_sdk_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "optimizely_flutter_sdk_example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* optimizely_flutter_sdk_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = optimizely_flutter_sdk_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -66,7 +68,10 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 5A8DB69DD1CEB9DE6D5A4070 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 7EEA78DE428A25C4C9195EC6 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 8D05B77804EBF39FD6D6D079 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; /* End PBXFileReference section */ @@ -75,6 +80,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 90B9B075FD9D5B075E83BE96 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -99,6 +105,7 @@ 33CEB47122A05771004F2AC0 /* Flutter */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + F74646C1D5F338EF32BE10E1 /* Pods */, ); sourceTree = ""; }; @@ -148,10 +155,22 @@ D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + 5A8DB69DD1CEB9DE6D5A4070 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; }; + F74646C1D5F338EF32BE10E1 /* Pods */ = { + isa = PBXGroup; + children = ( + 7EEA78DE428A25C4C9195EC6 /* Pods-Runner.debug.xcconfig */, + 8D05B77804EBF39FD6D6D079 /* Pods-Runner.release.xcconfig */, + 019D04A8C81D7599D2E638FA /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -159,11 +178,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 834CC0346DE4E8E368DFDFD4 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + F96AA93D7120056FD027BD50 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -270,6 +291,45 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + 834CC0346DE4E8E368DFDFD4 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + F96AA93D7120056FD027BD50 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/example/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/example/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/ios/Classes/HelperClasses/Constants.swift b/ios/Classes/HelperClasses/Constants.swift index c674389..e5be618 100644 --- a/ios/Classes/HelperClasses/Constants.swift +++ b/ios/Classes/HelperClasses/Constants.swift @@ -90,6 +90,7 @@ struct RequestParameterKey { static let eventTags = "eventTags" static let reasons = "reasons" static let decideOptions = "optimizelyDecideOption" + static let defaultLogLevel = "defaultLogLevel" static let eventBatchSize = "eventBatchSize" static let eventTimeInterval = "eventTimeInterval" static let eventMaxQueueSize = "eventMaxQueueSize" diff --git a/ios/Classes/HelperClasses/Utils.swift b/ios/Classes/HelperClasses/Utils.swift index 82f858a..6cac144 100644 --- a/ios/Classes/HelperClasses/Utils.swift +++ b/ios/Classes/HelperClasses/Utils.swift @@ -202,4 +202,17 @@ public class Utils: NSObject { return nil } } + + static func getDefaultLogLevel(_ logLevel: String) -> OptimizelyLogLevel { + var defaultLogLevel: OptimizelyLogLevel + switch logLevel { + case "error": defaultLogLevel = OptimizelyLogLevel.error + case "warning": defaultLogLevel = OptimizelyLogLevel.warning + case "info": defaultLogLevel = OptimizelyLogLevel.info + case "debug": defaultLogLevel = OptimizelyLogLevel.debug + default: defaultLogLevel = OptimizelyLogLevel.info + } + return defaultLogLevel; + } + } diff --git a/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift b/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift index cdc1549..287b056 100644 --- a/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift +++ b/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift @@ -106,7 +106,12 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin { decideOptions = options } let defaultDecideOptions = Utils.getDecideOptions(options: decideOptions) - + + var defaultLogLevel = OptimizelyLogLevel.info + if let logLevel = parameters[RequestParameterKey.defaultLogLevel] as? String { + defaultLogLevel = Utils.getDefaultLogLevel(logLevel) + } + // SDK Settings Default Values var segmentsCacheSize: Int = 100 var segmentsCacheTimeoutInSecs: Int = 600 @@ -152,7 +157,14 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin { optimizelyClientsTracker.removeValue(forKey: sdkKey) // Creating new instance - let optimizelyInstance = OptimizelyClient(sdkKey:sdkKey, eventDispatcher: eventDispatcher, datafileHandler: datafileHandler, periodicDownloadInterval: datafilePeriodicDownloadInterval, defaultDecideOptions: defaultDecideOptions, settings: optimizelySdkSettings) + let optimizelyInstance = OptimizelyClient( + sdkKey:sdkKey, + eventDispatcher: eventDispatcher, + datafileHandler: datafileHandler, + periodicDownloadInterval: datafilePeriodicDownloadInterval, + defaultLogLevel: defaultLogLevel, + defaultDecideOptions: defaultDecideOptions, + settings: optimizelySdkSettings) optimizelyInstance.start{ [weak self] res in switch res { diff --git a/lib/optimizely_flutter_sdk.dart b/lib/optimizely_flutter_sdk.dart index 775130f..51dc9af 100644 --- a/lib/optimizely_flutter_sdk.dart +++ b/lib/optimizely_flutter_sdk.dart @@ -27,6 +27,7 @@ import 'package:optimizely_flutter_sdk/src/data_objects/get_variation_response.d import 'package:optimizely_flutter_sdk/src/data_objects/optimizely_config_response.dart'; import 'package:optimizely_flutter_sdk/src/optimizely_client_wrapper.dart'; import 'package:optimizely_flutter_sdk/src/user_context/optimizely_user_context.dart'; +import 'package:optimizely_flutter_sdk/src/data_objects/log_level.dart'; export 'package:optimizely_flutter_sdk/src/optimizely_client_wrapper.dart' show ClientPlatform, ListenerType; @@ -50,6 +51,8 @@ export 'package:optimizely_flutter_sdk/src/data_objects/sdk_settings.dart' show SDKSettings; export 'package:optimizely_flutter_sdk/src/data_objects/datafile_options.dart' show DatafileHostOptions; +export 'package:optimizely_flutter_sdk/src/data_objects/log_level.dart' + show OptimizelyLogLevel; /// The main client class for the Optimizely Flutter SDK. /// @@ -63,6 +66,7 @@ class OptimizelyFlutterSdk { final int _datafilePeriodicDownloadInterval; final Map _datafileHostOptions; final Set _defaultDecideOptions; + final OptimizelyLogLevel _defaultLogLevel; final SDKSettings _sdkSettings; OptimizelyFlutterSdk(this._sdkKey, {EventOptions eventOptions = const EventOptions(), @@ -70,11 +74,13 @@ class OptimizelyFlutterSdk { 10 * 60, // Default time interval in seconds Map datafileHostOptions = const {}, Set defaultDecideOptions = const {}, + OptimizelyLogLevel defaultLogLevel = OptimizelyLogLevel.info, SDKSettings sdkSettings = const SDKSettings()}) : _eventOptions = eventOptions, _datafilePeriodicDownloadInterval = datafilePeriodicDownloadInterval, _datafileHostOptions = datafileHostOptions, _defaultDecideOptions = defaultDecideOptions, + _defaultLogLevel = defaultLogLevel, _sdkSettings = sdkSettings; /// Starts Optimizely SDK (Synchronous) with provided sdkKey. @@ -85,6 +91,7 @@ class OptimizelyFlutterSdk { _datafilePeriodicDownloadInterval, _datafileHostOptions, _defaultDecideOptions, + _defaultLogLevel, _sdkSettings); } diff --git a/lib/src/data_objects/log_level.dart b/lib/src/data_objects/log_level.dart new file mode 100644 index 0000000..c2eacd3 --- /dev/null +++ b/lib/src/data_objects/log_level.dart @@ -0,0 +1,22 @@ +/// ************************************************************************** +/// Copyright 2023, Optimizely, Inc. and contributors * +/// * +/// Licensed under the Apache License, Version 2.0 (the "License"); * +/// you may not use this file except in compliance with the License. * +/// You may obtain a copy of the License at * +/// * +/// http://www.apache.org/licenses/LICENSE-2.0 * +/// * +/// Unless required by applicable law or agreed to in writing, software * +/// distributed under the License is distributed on an "AS IS" BASIS, * +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * +/// See the License for the specific language governing permissions and * +/// limitations under the License. * +///**************************************************************************/ + +enum OptimizelyLogLevel { + error, + warning, + info, + debug +} diff --git a/lib/src/optimizely_client_wrapper.dart b/lib/src/optimizely_client_wrapper.dart index e26b74f..bb2c4c0 100644 --- a/lib/src/optimizely_client_wrapper.dart +++ b/lib/src/optimizely_client_wrapper.dart @@ -27,6 +27,8 @@ import 'package:optimizely_flutter_sdk/src/data_objects/optimizely_config_respon import 'package:optimizely_flutter_sdk/src/utils/constants.dart'; import 'package:optimizely_flutter_sdk/src/utils/utils.dart'; +import 'data_objects/log_level.dart'; + enum ListenerType { activate, track, decision, logEvent, projectConfigUpdate } enum ClientPlatform { iOS, android } @@ -61,14 +63,17 @@ class OptimizelyClientWrapper { int datafilePeriodicDownloadInterval, Map datafileHostOptions, Set defaultDecideOptions, + OptimizelyLogLevel defaultLogLevel, SDKSettings sdkSettings) async { _channel.setMethodCallHandler(methodCallHandler); final convertedOptions = Utils.convertDecideOptions(defaultDecideOptions); + final convertedLogLevel = Utils.convertLogLevel(defaultLogLevel); Map requestDict = { Constants.sdkKey: sdkKey, Constants.datafilePeriodicDownloadInterval: datafilePeriodicDownloadInterval, Constants.optimizelyDecideOption: convertedOptions, + Constants.defaultLogLevel: convertedLogLevel, Constants.eventBatchSize: eventOptions.batchSize, Constants.eventTimeInterval: eventOptions.timeInterval, Constants.eventMaxQueueSize: eventOptions.maxQueueSize, diff --git a/lib/src/utils/constants.dart b/lib/src/utils/constants.dart index f6a61d5..d5bc3ff 100644 --- a/lib/src/utils/constants.dart +++ b/lib/src/utils/constants.dart @@ -83,6 +83,7 @@ class Constants { static const String optimizelyDecideOption = "optimizelyDecideOption"; static const String optimizelySegmentOption = "optimizelySegmentOption"; static const String optimizelySdkSettings = "optimizelySdkSettings"; + static const String defaultLogLevel = "defaultLogLevel"; static const String payload = "payload"; static const String value = "value"; static const String type = "type"; diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index da36841..782178e 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -18,6 +18,7 @@ import 'dart:io' show Platform; import 'package:optimizely_flutter_sdk/src/user_context/optimizely_user_context.dart'; import 'package:optimizely_flutter_sdk/src/utils/constants.dart'; +import 'package:optimizely_flutter_sdk/src/data_objects/log_level.dart'; class Utils { static Map decideOptions = { @@ -94,4 +95,10 @@ class Utils { Set options) { return options.map((option) => Utils.segmentOptions[option]!).toList(); } + + static String convertLogLevel(OptimizelyLogLevel logLevel) { + // OptimizelyLogLevel.error -> "error" + // OptimizelyLogLevel.debug -> "debug" + return logLevel.toString().split('.').last; + } } diff --git a/test/optimizely_flutter_sdk_test.dart b/test/optimizely_flutter_sdk_test.dart index fa81adb..debf769 100644 --- a/test/optimizely_flutter_sdk_test.dart +++ b/test/optimizely_flutter_sdk_test.dart @@ -56,6 +56,7 @@ void main() { DatafileHostOptions datafileHostOptions = const DatafileHostOptions("", ""); SDKSettings sdkSettings = const SDKSettings(); int datafilePeriodicDownloadInterval = 0; + String defaultLogLevel = "error"; const MethodChannel channel = MethodChannel("optimizely_flutter_sdk"); dynamic mockOptimizelyConfig; @@ -83,6 +84,9 @@ void main() { case Constants.initializeMethod: expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); expect(methodCall.arguments[Constants.userContextId], isNull); + + defaultLogLevel = methodCall.arguments[Constants.defaultLogLevel]; + // To Check if eventOptions were received eventOptions = EventOptions( batchSize: methodCall.arguments[Constants.eventBatchSize], @@ -542,6 +546,33 @@ void main() { }); }); + group("log level configuration", () { + test("with no defaultLogLevel, log level should be info level", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + + var response = await sdk.initializeClient(); + + expect(response.success, isTrue); + expect(defaultLogLevel, equals("info")); + }); + + test("with a valid defaultLogLevel parameter", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey, defaultLogLevel: OptimizelyLogLevel.debug); + + var response = await sdk.initializeClient(); + + expect(response.success, isTrue); + expect(defaultLogLevel, equals("debug")); + }); + + test("should convert OptimizelyLogLevel to string", () async { + expect(Utils.convertLogLevel(OptimizelyLogLevel.error), "error"); + expect(Utils.convertLogLevel(OptimizelyLogLevel.warning), "warning"); + expect(Utils.convertLogLevel(OptimizelyLogLevel.info), "info"); + expect(Utils.convertLogLevel(OptimizelyLogLevel.debug), "debug"); + }); + }); + group("close()", () { test("should succeed", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); From 75e41eeb3ce30daf88f6a53810b9174b1e56a6e2 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam <129880873+muzahidul-opti@users.noreply.github.com> Date: Wed, 26 Jul 2023 21:08:23 +0600 Subject: [PATCH 25/39] Fetsegment without options, crash fixed (#64) Co-authored-by: Yasir Folio3 <39988750+yasirfolio3@users.noreply.github.com> --- .../optimizely/optimizely_flutter_sdk/helper_classes/Utils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Utils.java b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Utils.java index a4353b5..b3020f1 100644 --- a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Utils.java +++ b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Utils.java @@ -72,7 +72,7 @@ public static List getDecideOptions(List options } public static List getSegmentOptions(List options) { - if(options == null || options.isEmpty()) { + if(options == null) { return null; } List convertedOptions = new ArrayList<>(); From 6476087bf21ea6b62a3e7a3def414fbb4e768d01 Mon Sep 17 00:00:00 2001 From: Yasir Folio3 <39988750+yasirfolio3@users.noreply.github.com> Date: Fri, 4 Aug 2023 16:33:00 -0400 Subject: [PATCH 26/39] Update Github Issue Templates. (#65) --- .github/ISSUE_TEMPLATE/BUG-REPORT.yml | 82 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/ENHANCEMENT.yml | 45 +++++++++++++ .github/ISSUE_TEMPLATE/FEATURE-REQUEST.md | 4 ++ .github/ISSUE_TEMPLATE/config.yml | 5 ++ 4 files changed, 136 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/BUG-REPORT.yml create mode 100644 .github/ISSUE_TEMPLATE/ENHANCEMENT.yml create mode 100644 .github/ISSUE_TEMPLATE/FEATURE-REQUEST.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml new file mode 100644 index 0000000..c4796a2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml @@ -0,0 +1,82 @@ +name: 🐞 Bug +description: File a bug/issue +title: "[BUG] " +labels: ["bug", "needs-triage"] +body: +- type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the bug you encountered. + options: + - label: I have searched the existing issues + required: true +- type: textarea + attributes: + label: SDK Version + description: Version of the SDK in use? + validations: + required: true +- type: textarea + attributes: + label: flutter Version + description: Version of flutter in use? + validations: + required: true +- type: textarea + attributes: + label: Current Behavior + description: A concise description of what you're experiencing. + validations: + required: true +- type: textarea + attributes: + label: Expected Behavior + description: A concise description of what you expected to happen. + validations: + required: true +- type: textarea + attributes: + label: Steps To Reproduce + description: Steps to reproduce the behavior. + placeholder: | + 1. In this environment... + 1. With this config... + 1. Run '...' + 1. See error... + validations: + required: true +- type: textarea + attributes: + label: Link + description: Link to code demonstrating the problem. + validations: + required: false +- type: textarea + attributes: + label: Logs + description: Logs/stack traces related to the problem (⚠️do not include sensitive information). + validations: + required: false +- type: dropdown + attributes: + label: Severity + description: What is the severity of the problem? + multiple: true + options: + - Blocking development + - Affecting users + - Minor issue + validations: + required: false +- type: textarea + attributes: + label: Workaround/Solution + description: Do you have any workaround or solution in mind for the problem? + validations: + required: false +- type: textarea + attributes: + label: "Recent Change" + description: Has this issue started happening after an update or experiment change? + validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/ENHANCEMENT.yml b/.github/ISSUE_TEMPLATE/ENHANCEMENT.yml new file mode 100644 index 0000000..79c5324 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/ENHANCEMENT.yml @@ -0,0 +1,45 @@ +name: ✨Enhancement +description: Create a new ticket for a Enhancement/Tech-initiative for the benefit of the SDK which would be considered for a minor version update. +title: "[ENHANCEMENT] <title>" +labels: ["enhancement"] +body: + - type: textarea + id: description + attributes: + label: "Description" + description: Briefly describe the enhancement in a few sentences. + placeholder: Short description... + validations: + required: true + - type: textarea + id: benefits + attributes: + label: "Benefits" + description: How would the enhancement benefit to your product or usage? + placeholder: Benefits... + validations: + required: true + - type: textarea + id: detail + attributes: + label: "Detail" + description: How would you like the enhancement to work? Please provide as much detail as possible + placeholder: Detailed description... + validations: + required: false + - type: textarea + id: examples + attributes: + label: "Examples" + description: Are there any examples of this enhancement in other products/services? If so, please provide links or references. + placeholder: Links/References... + validations: + required: false + - type: textarea + id: risks + attributes: + label: "Risks/Downsides" + description: Do you think this enhancement could have any potential downsides or risks? + placeholder: Risks/Downsides... + validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.md b/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.md new file mode 100644 index 0000000..a061f33 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.md @@ -0,0 +1,4 @@ +<!-- + Thanks for filing in issue! Are you requesting a new feature? If so, please share your feedback with us on the following link. +--> +## Feedback requesting a new feature can be shared [here.](https://feedback.optimizely.com/) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..d28ef3d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: 💡Feature Requests + url: https://feedback.optimizely.com/ + about: Feedback requesting a new feature can be shared here. \ No newline at end of file From 2f8ddfd48f2b73f25972833cebbb417fe518ded9 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam <129880873+muzahidul-opti@users.noreply.github.com> Date: Fri, 22 Sep 2023 21:57:12 +0600 Subject: [PATCH 27/39] [FSSDK-9491] chore Prepare for 2.0.0-beta release (#67) --- CHANGELOG.md | 17 +++++++++++++++++ example/android/build.gradle | 2 +- lib/src/data_objects/decide_response.dart | 10 +++++----- lib/src/optimizely_client_wrapper.dart | 2 -- pubspec.yaml | 4 ++-- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55ba984..b07c65b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Optimizely Flutter SDK Changelog +## 2.0.0-beta +September 21, 2023 + +### New Features + +* Add ODP for iOS ([#52](https://github.com/optimizely/optimizely-flutter-sdk/pull/52)). +* Add ODP for Android ([#57](https://github.com/optimizely/optimizely-flutter-sdk/pull/57)). + +### Bug Fixes + +* Crash fixed, fetchQualifiedSegments without options ([#64](https://github.com/optimizely/optimizely-flutter-sdk/pull/64)). + +### Functionality Enhancements + +* Update Github Issue Templates ([#65](https://github.com/optimizely/optimizely-flutter-sdk/pull/65)). +* Add configurable log level support ([#63](https://github.com/optimizely/optimizely-flutter-sdk/pull/63)). + ## 1.0.1 May 8, 2023 diff --git a/example/android/build.gradle b/example/android/build.gradle index 57c3218..c133c66 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { } } ext { - android_sdk_version = "4.0.0-beta2" + android_sdk_version = "4.0.0-beta3" } allprojects { repositories { diff --git a/lib/src/data_objects/decide_response.dart b/lib/src/data_objects/decide_response.dart index 1f79439..dc2809a 100644 --- a/lib/src/data_objects/decide_response.dart +++ b/lib/src/data_objects/decide_response.dart @@ -42,14 +42,14 @@ class Decision { enabled = json[Constants.enabled]; } if (json[Constants.userContext] is Map<dynamic, dynamic>) { - Map<String, dynamic> _userContext = + Map<String, dynamic> localUserContext = Map<String, dynamic>.from(json[Constants.userContext]); - if (_userContext[Constants.userId] is String) { - userContext[Constants.userId] = _userContext[Constants.userId]; + if (localUserContext[Constants.userId] is String) { + userContext[Constants.userId] = localUserContext[Constants.userId]; } - if (_userContext[Constants.attributes] is Map<dynamic, dynamic>) { + if (localUserContext[Constants.attributes] is Map<dynamic, dynamic>) { userContext[Constants.attributes] = - Map<String, dynamic>.from(_userContext[Constants.attributes]); + Map<String, dynamic>.from(localUserContext[Constants.attributes]); } } diff --git a/lib/src/optimizely_client_wrapper.dart b/lib/src/optimizely_client_wrapper.dart index bb2c4c0..f96ad24 100644 --- a/lib/src/optimizely_client_wrapper.dart +++ b/lib/src/optimizely_client_wrapper.dart @@ -27,8 +27,6 @@ import 'package:optimizely_flutter_sdk/src/data_objects/optimizely_config_respon import 'package:optimizely_flutter_sdk/src/utils/constants.dart'; import 'package:optimizely_flutter_sdk/src/utils/utils.dart'; -import 'data_objects/log_level.dart'; - enum ListenerType { activate, track, decision, logEvent, projectConfigUpdate } enum ClientPlatform { iOS, android } diff --git a/pubspec.yaml b/pubspec.yaml index 18d9c15..c2f0293 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,10 +1,10 @@ name: optimizely_flutter_sdk description: This repository houses the Flutter SDK for use with Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts. -version: 1.0.1 +version: 2.0.0-beta homepage: https://github.com/optimizely/optimizely-flutter-sdk environment: - sdk: ">=2.16.2" + sdk: '>=2.16.2 <=3.1.2' flutter: ">=2.5.0" dependencies: From 0f4a6ebf5de9a3f819ae76992e0a9db15f38a63c Mon Sep 17 00:00:00 2001 From: Jae Kim <45045038+jaeopt@users.noreply.github.com> Date: Wed, 10 Jan 2024 08:52:45 -0800 Subject: [PATCH 28/39] fix proguard for logback and dart version (#68) --- android/proguard-rules.txt | 3 ++- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/android/proguard-rules.txt b/android/proguard-rules.txt index 7fede45..1564ced 100644 --- a/android/proguard-rules.txt +++ b/android/proguard-rules.txt @@ -10,5 +10,6 @@ # Optimizely -keep class com.optimizely.optimizely_flutter_sdk.** {*;} -keep class com.fasterxml.jackson.** {*;} +# Logback +-keep class ch.qos.** { *; } ##---------------End: proguard configuration ---------- - diff --git a/pubspec.yaml b/pubspec.yaml index c2f0293..bda345b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ version: 2.0.0-beta homepage: https://github.com/optimizely/optimizely-flutter-sdk environment: - sdk: '>=2.16.2 <=3.1.2' + sdk: '>=2.16.2 <4.0.0' flutter: ">=2.5.0" dependencies: From ea0fdc40c20743e59d5126b37a6a9a501347a7eb Mon Sep 17 00:00:00 2001 From: Muzahidul Islam <129880873+muzahidul-opti@users.noreply.github.com> Date: Tue, 23 Jan 2024 23:19:35 +0600 Subject: [PATCH 29/39] [FSSDK-8713] chore: prepare for the release 2.0.0 (#70) * Update version * Update CHANGELOG.md * Update README.md --- CHANGELOG.md | 18 ++++++++++++++++++ README.md | 4 ++-- android/build.gradle | 2 +- example/android/build.gradle | 2 +- ios/optimizely_flutter_sdk.podspec | 2 +- pubspec.yaml | 2 +- 6 files changed, 24 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b07c65b..8aa48a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Optimizely Flutter SDK Changelog +## 2.0.0 +January 23, 2024 + +### New Features + +* Add ODP for iOS ([#52](https://github.com/optimizely/optimizely-flutter-sdk/pull/52)). +* Add ODP for Android ([#57](https://github.com/optimizely/optimizely-flutter-sdk/pull/57)). + +### Bug Fixes + +* Crash fixed, fetchQualifiedSegments without options ([#64](https://github.com/optimizely/optimizely-flutter-sdk/pull/64)). +* Fix proguard for logback and dart version ([#68](https://github.com/optimizely/optimizely-flutter-sdk/pull/68)). + +### Functionality Enhancements + +* Update Github Issue Templates ([#65](https://github.com/optimizely/optimizely-flutter-sdk/pull/65)). +* Add configurable log level support ([#63](https://github.com/optimizely/optimizely-flutter-sdk/pull/63)). + ## 2.0.0-beta September 21, 2023 diff --git a/README.md b/README.md index e8a6edf..c0a2205 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ See the [pubspec.yaml](https://github.com/optimizely/optimizely-flutter-sdk/blob On the Android platform, the SDK requires a minimum SDK version of 21 or higher and compile SDK version of 32. -On the iOS platform, the SDK requires a minimum version of 10.0. +On the iOS platform, the SDK requires a minimum version of 11.0. Other Flutter platforms are not currently supported by this SDK. @@ -30,7 +30,7 @@ Other Flutter platforms are not currently supported by this SDK. To add the flutter-sdk to your project dependencies, include the following in your app's pubspec.yaml: ``` - optimizely_flutter_sdk: ^1.0.1 + optimizely_flutter_sdk: ^2.0.0 ``` Then run diff --git a/android/build.gradle b/android/build.gradle index 03427ea..9934507 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -83,7 +83,7 @@ dependencies { implementation 'org.slf4j:slf4j-api:2.0.7' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10" - implementation "com.optimizely.ab:android-sdk:4.0.0-beta2" + implementation "com.optimizely.ab:android-sdk:4.0.0" implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.4' implementation ('com.google.guava:guava:19.0') { exclude group:'com.google.guava', module:'listenablefuture' diff --git a/example/android/build.gradle b/example/android/build.gradle index c133c66..58051f6 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { } } ext { - android_sdk_version = "4.0.0-beta3" + android_sdk_version = "4.0.0" } allprojects { repositories { diff --git a/ios/optimizely_flutter_sdk.podspec b/ios/optimizely_flutter_sdk.podspec index 2a528d5..4da4408 100644 --- a/ios/optimizely_flutter_sdk.podspec +++ b/ios/optimizely_flutter_sdk.podspec @@ -13,7 +13,7 @@ Pod::Spec.new do |s| s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency 'OptimizelySwiftSDK', '4.0.0-beta' + s.dependency 'OptimizelySwiftSDK', '4.0.0' s.platform = :ios, '10.0' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/pubspec.yaml b/pubspec.yaml index bda345b..7ffd848 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: optimizely_flutter_sdk description: This repository houses the Flutter SDK for use with Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts. -version: 2.0.0-beta +version: 2.0.0 homepage: https://github.com/optimizely/optimizely-flutter-sdk environment: From 52b49ce1dba0372b395a33edb597d64d06a60f8a Mon Sep 17 00:00:00 2001 From: Muzahidul Islam <129880873+muzahidul-opti@users.noreply.github.com> Date: Wed, 24 Jan 2024 16:16:46 +0600 Subject: [PATCH 30/39] Revert "[FSSDK-8713] chore: prepare for the release 2.0.0" (#71) --- CHANGELOG.md | 18 ------------------ README.md | 4 ++-- android/build.gradle | 2 +- example/android/build.gradle | 2 +- ios/optimizely_flutter_sdk.podspec | 2 +- pubspec.yaml | 2 +- 6 files changed, 6 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8aa48a2..b07c65b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,23 +1,5 @@ # Optimizely Flutter SDK Changelog -## 2.0.0 -January 23, 2024 - -### New Features - -* Add ODP for iOS ([#52](https://github.com/optimizely/optimizely-flutter-sdk/pull/52)). -* Add ODP for Android ([#57](https://github.com/optimizely/optimizely-flutter-sdk/pull/57)). - -### Bug Fixes - -* Crash fixed, fetchQualifiedSegments without options ([#64](https://github.com/optimizely/optimizely-flutter-sdk/pull/64)). -* Fix proguard for logback and dart version ([#68](https://github.com/optimizely/optimizely-flutter-sdk/pull/68)). - -### Functionality Enhancements - -* Update Github Issue Templates ([#65](https://github.com/optimizely/optimizely-flutter-sdk/pull/65)). -* Add configurable log level support ([#63](https://github.com/optimizely/optimizely-flutter-sdk/pull/63)). - ## 2.0.0-beta September 21, 2023 diff --git a/README.md b/README.md index c0a2205..e8a6edf 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ See the [pubspec.yaml](https://github.com/optimizely/optimizely-flutter-sdk/blob On the Android platform, the SDK requires a minimum SDK version of 21 or higher and compile SDK version of 32. -On the iOS platform, the SDK requires a minimum version of 11.0. +On the iOS platform, the SDK requires a minimum version of 10.0. Other Flutter platforms are not currently supported by this SDK. @@ -30,7 +30,7 @@ Other Flutter platforms are not currently supported by this SDK. To add the flutter-sdk to your project dependencies, include the following in your app's pubspec.yaml: ``` - optimizely_flutter_sdk: ^2.0.0 + optimizely_flutter_sdk: ^1.0.1 ``` Then run diff --git a/android/build.gradle b/android/build.gradle index 9934507..03427ea 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -83,7 +83,7 @@ dependencies { implementation 'org.slf4j:slf4j-api:2.0.7' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10" - implementation "com.optimizely.ab:android-sdk:4.0.0" + implementation "com.optimizely.ab:android-sdk:4.0.0-beta2" implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.4' implementation ('com.google.guava:guava:19.0') { exclude group:'com.google.guava', module:'listenablefuture' diff --git a/example/android/build.gradle b/example/android/build.gradle index 58051f6..c133c66 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { } } ext { - android_sdk_version = "4.0.0" + android_sdk_version = "4.0.0-beta3" } allprojects { repositories { diff --git a/ios/optimizely_flutter_sdk.podspec b/ios/optimizely_flutter_sdk.podspec index 4da4408..2a528d5 100644 --- a/ios/optimizely_flutter_sdk.podspec +++ b/ios/optimizely_flutter_sdk.podspec @@ -13,7 +13,7 @@ Pod::Spec.new do |s| s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency 'OptimizelySwiftSDK', '4.0.0' + s.dependency 'OptimizelySwiftSDK', '4.0.0-beta' s.platform = :ios, '10.0' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/pubspec.yaml b/pubspec.yaml index 7ffd848..bda345b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: optimizely_flutter_sdk description: This repository houses the Flutter SDK for use with Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts. -version: 2.0.0 +version: 2.0.0-beta homepage: https://github.com/optimizely/optimizely-flutter-sdk environment: From 5521cc9a7e2e1d4b103bb64e5bf4a0b7662fc515 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam <129880873+muzahidul-opti@users.noreply.github.com> Date: Fri, 26 Jan 2024 18:45:31 +0600 Subject: [PATCH 31/39] [FSSDK-9358] chore: add flutter specific client name support to track event (#72) * Client depedency updated to 4.0.0 * PlatformService ready for clinet name * SDK client name and version injectecd to native SDK * Package info added * Remove unused code and file * clean up * Test case added * Clean up --- android/build.gradle | 2 +- .../OptimizelyFlutterClient.java | 5 +- .../helper_classes/ArgumentsParser.java | 4 + .../helper_classes/Constants.java | 1 + .../helper_classes/Utils.java | 3 +- example/android/build.gradle | 2 +- ios/Classes/HelperClasses/Constants.swift | 1 + ios/Classes/HelperClasses/Utils.swift | 2 +- .../SwiftOptimizelyFlutterSdkPlugin.swift | 5 +- ios/optimizely_flutter_sdk.podspec | 2 +- lib/package_info.dart | 7 ++ lib/src/optimizely_client_wrapper.dart | 4 + lib/src/utils/constants.dart | 1 + lib/src/utils/utils.dart | 2 +- test/optimizely_flutter_sdk_test.dart | 76 ++++++++++++++++++- test/test_utils.dart | 35 +++++++++ 16 files changed, 142 insertions(+), 10 deletions(-) create mode 100644 lib/package_info.dart diff --git a/android/build.gradle b/android/build.gradle index 03427ea..9934507 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -83,7 +83,7 @@ dependencies { implementation 'org.slf4j:slf4j-api:2.0.7' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10" - implementation "com.optimizely.ab:android-sdk:4.0.0-beta2" + implementation "com.optimizely.ab:android-sdk:4.0.0" implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.4' implementation ('com.google.guava:guava:19.0') { exclude group:'com.google.guava', module:'listenablefuture' diff --git a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterClient.java b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterClient.java index 82e3384..c571c2f 100644 --- a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterClient.java +++ b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterClient.java @@ -78,6 +78,8 @@ public class OptimizelyFlutterClient { protected void initializeOptimizely(@NonNull ArgumentsParser argumentsParser, @NonNull Result result) { String sdkKey = argumentsParser.getSdkKey(); + String sdkName = Utils.sdkName; + String sdkVersion = argumentsParser.getSdkVersion(); if (sdkKey == null) { result.success(createResponse(ErrorMessage.INVALID_PARAMS)); return; @@ -172,7 +174,8 @@ protected void initializeOptimizely(@NonNull ArgumentsParser argumentsParser, @N .withODPSegmentCacheTimeout(segmentsCacheTimeoutInSecs, TimeUnit.SECONDS) .withTimeoutForODPSegmentFetch(timeoutForSegmentFetchInSecs) .withTimeoutForODPEventDispatch(timeoutForOdpEventInSecs) - .withSDKKey(sdkKey); + .withSDKKey(sdkKey) + .withClientInfo(sdkName, sdkVersion); if (disableOdp) { optimizelyManagerBuilder.withODPDisabled(); } diff --git a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/ArgumentsParser.java b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/ArgumentsParser.java index 88087c7..6d2741f 100644 --- a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/ArgumentsParser.java +++ b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/ArgumentsParser.java @@ -31,6 +31,10 @@ public ArgumentsParser(Map<String, ?> arguments) { public String getSdkKey() { return (String) arguments.get(Constants.RequestParameterKey.SDK_KEY); } + + public String getSdkVersion() { + return (String) arguments.get(Constants.RequestParameterKey.SDK_VERSION); + } public Integer getNotificationID() { return (Integer) arguments.get(Constants.RequestParameterKey.NOTIFICATION_ID); diff --git a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Constants.java b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Constants.java index 453b1d6..0c73d32 100644 --- a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Constants.java +++ b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Constants.java @@ -58,6 +58,7 @@ public static class NotificationType { public static class RequestParameterKey { public static final String SDK_KEY = "sdkKey"; + public static final String SDK_VERSION = "sdkVersion"; public static final String USER_ID = "userId"; public static final String USER_CONTEXT_ID = "userContextId"; public static final String NOTIFICATION_ID = "id"; diff --git a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Utils.java b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Utils.java index b3020f1..ba4e5a4 100644 --- a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Utils.java +++ b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Utils.java @@ -37,7 +37,8 @@ import ch.qos.logback.classic.Logger; public class Utils { - + public static String sdkName = "flutter/android-sdk"; + public static String getRandomUUID() { return UUID.randomUUID().toString(); } diff --git a/example/android/build.gradle b/example/android/build.gradle index c133c66..58051f6 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -11,7 +11,7 @@ buildscript { } } ext { - android_sdk_version = "4.0.0-beta3" + android_sdk_version = "4.0.0" } allprojects { repositories { diff --git a/ios/Classes/HelperClasses/Constants.swift b/ios/Classes/HelperClasses/Constants.swift index e5be618..f3ca6d5 100644 --- a/ios/Classes/HelperClasses/Constants.swift +++ b/ios/Classes/HelperClasses/Constants.swift @@ -114,6 +114,7 @@ struct RequestParameterKey { static let timeoutForSegmentFetchInSecs = "timeoutForSegmentFetchInSecs" static let timeoutForOdpEventInSecs = "timeoutForOdpEventInSecs" static let disableOdp = "disableOdp" + static let sdkVersion = "sdkVersion"; } struct ResponseKey { diff --git a/ios/Classes/HelperClasses/Utils.swift b/ios/Classes/HelperClasses/Utils.swift index 6cac144..41b39c1 100644 --- a/ios/Classes/HelperClasses/Utils.swift +++ b/ios/Classes/HelperClasses/Utils.swift @@ -18,7 +18,7 @@ import Foundation import Optimizely public class Utils: NSObject { - + static var sdkName = "flutter/swift-sdk" /// Converts and returns dart map to native map static func getTypedMap(arguments: Any?) -> [String: Any]? { guard let args = arguments as? Dictionary<String, Any?> else { diff --git a/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift b/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift index 287b056..8a2fd75 100644 --- a/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift +++ b/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift @@ -118,6 +118,9 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin { var timeoutForSegmentFetchInSecs: Int = 10 var timeoutForOdpEventInSecs: Int = 10 var disableOdp: Bool = false + var sdkVersion = parameters[RequestParameterKey.sdkVersion] as? String + var sdkName = Utils.sdkName + if let sdkSettings = parameters[RequestParameterKey.optimizelySdkSettings] as? Dictionary<String, Any?> { if let cacheSize = sdkSettings[RequestParameterKey.segmentsCacheSize] as? Int { segmentsCacheSize = cacheSize @@ -135,7 +138,7 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin { disableOdp = isOdpDisabled } } - let optimizelySdkSettings = OptimizelySdkSettings(segmentsCacheSize: segmentsCacheSize, segmentsCacheTimeoutInSecs: segmentsCacheTimeoutInSecs, timeoutForSegmentFetchInSecs: timeoutForSegmentFetchInSecs, timeoutForOdpEventInSecs: timeoutForOdpEventInSecs, disableOdp: disableOdp) + let optimizelySdkSettings = OptimizelySdkSettings(segmentsCacheSize: segmentsCacheSize, segmentsCacheTimeoutInSecs: segmentsCacheTimeoutInSecs, timeoutForSegmentFetchInSecs: timeoutForSegmentFetchInSecs, timeoutForOdpEventInSecs: timeoutForOdpEventInSecs, disableOdp: disableOdp, sdkName: sdkName, sdkVersion: sdkVersion) // Datafile Download Interval var datafilePeriodicDownloadInterval = 10 * 60 // seconds diff --git a/ios/optimizely_flutter_sdk.podspec b/ios/optimizely_flutter_sdk.podspec index 2a528d5..4da4408 100644 --- a/ios/optimizely_flutter_sdk.podspec +++ b/ios/optimizely_flutter_sdk.podspec @@ -13,7 +13,7 @@ Pod::Spec.new do |s| s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency 'OptimizelySwiftSDK', '4.0.0-beta' + s.dependency 'OptimizelySwiftSDK', '4.0.0' s.platform = :ios, '10.0' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/lib/package_info.dart b/lib/package_info.dart new file mode 100644 index 0000000..4c8df19 --- /dev/null +++ b/lib/package_info.dart @@ -0,0 +1,7 @@ +// Purpose: Package information for the Optimizely Flutter SDK +// Note: This info should be synced with pubspec.yaml + +class PackageInfo { + static const String name = 'optimizely_flutter_sdk'; + static const String version = '2.0.0-beta'; +} \ No newline at end of file diff --git a/lib/src/optimizely_client_wrapper.dart b/lib/src/optimizely_client_wrapper.dart index f96ad24..590d17c 100644 --- a/lib/src/optimizely_client_wrapper.dart +++ b/lib/src/optimizely_client_wrapper.dart @@ -18,6 +18,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:optimizely_flutter_sdk/optimizely_flutter_sdk.dart'; +import 'package:optimizely_flutter_sdk/package_info.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/activate_listener_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/activate_response.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/base_response.dart'; @@ -66,8 +67,11 @@ class OptimizelyClientWrapper { _channel.setMethodCallHandler(methodCallHandler); final convertedOptions = Utils.convertDecideOptions(defaultDecideOptions); final convertedLogLevel = Utils.convertLogLevel(defaultLogLevel); + final sdkVersion = PackageInfo.version; + Map<String, dynamic> requestDict = { Constants.sdkKey: sdkKey, + Constants.sdkVersion: sdkVersion, Constants.datafilePeriodicDownloadInterval: datafilePeriodicDownloadInterval, Constants.optimizelyDecideOption: convertedOptions, diff --git a/lib/src/utils/constants.dart b/lib/src/utils/constants.dart index d5bc3ff..7ef9547 100644 --- a/lib/src/utils/constants.dart +++ b/lib/src/utils/constants.dart @@ -58,6 +58,7 @@ class Constants { // Request parameter keys static const String id = "id"; static const String sdkKey = "sdkKey"; + static const String sdkVersion = "sdkVersion"; static const String userContextId = "userContextId"; static const String userContext = "userContext"; static const String experiment = "experiment"; diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index 782178e..8b18b13 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -15,7 +15,6 @@ ///**************************************************************************/ import 'dart:io' show Platform; - import 'package:optimizely_flutter_sdk/src/user_context/optimizely_user_context.dart'; import 'package:optimizely_flutter_sdk/src/utils/constants.dart'; import 'package:optimizely_flutter_sdk/src/data_objects/log_level.dart'; @@ -101,4 +100,5 @@ class Utils { // OptimizelyLogLevel.debug -> "debug" return logLevel.toString().split('.').last; } + } diff --git a/test/optimizely_flutter_sdk_test.dart b/test/optimizely_flutter_sdk_test.dart index debf769..dd6bd7a 100644 --- a/test/optimizely_flutter_sdk_test.dart +++ b/test/optimizely_flutter_sdk_test.dart @@ -23,7 +23,6 @@ import 'package:optimizely_flutter_sdk/src/utils/constants.dart'; import 'package:optimizely_flutter_sdk/src/utils/utils.dart'; import 'dart:io'; import 'dart:convert'; - import 'test_utils.dart'; void main() { @@ -557,7 +556,8 @@ void main() { }); test("with a valid defaultLogLevel parameter", () async { - var sdk = OptimizelyFlutterSdk(testSDKKey, defaultLogLevel: OptimizelyLogLevel.debug); + var sdk = OptimizelyFlutterSdk(testSDKKey, + defaultLogLevel: OptimizelyLogLevel.debug); var response = await sdk.initializeClient(); @@ -1553,4 +1553,76 @@ void main() { }); }); }); + + group("Test client_name and client_version event parsing", () { + test("Test track event with swift sdk name and version", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + + TrackListenerResponse? response; + + await sdk.addTrackNotificationListener((msg) { + response = msg; + }); + var callHandler = OptimizelyClientWrapper.methodCallHandler; + tester?.setMockMethodCallHandler(channel, callHandler); + TestUtils.sendTestTrackClientNameAndVersion( + callHandler, 0, testSDKKey, "flutter/swift-sdk", "2.0.0"); + + expect(response == null, false); + + expect(response!.eventTags!["client_name"], "flutter/swift-sdk"); + + }); + + test("Test track event with android sdk name and version", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + + var responses = []; + + await sdk.addTrackNotificationListener((msg) { + responses.add(msg); + }); + + var callHandler = OptimizelyClientWrapper.methodCallHandler; + tester?.setMockMethodCallHandler(channel, callHandler); + + TestUtils.sendTestTrackClientNameAndVersion( + callHandler, 0, testSDKKey, "flutter/swift-sdk", "2.0.0"); + TestUtils.sendTestTrackClientNameAndVersion( + callHandler, 0, testSDKKey, "flutter/android-sdk", "2.0.0-beta"); + + expect(responses.length == 2, true); + + expect(responses[0]!.eventTags!["client_name"], "flutter/swift-sdk"); + expect(responses[0]!.eventTags!["client_version"], "2.0.0"); + expect(responses[1]!.eventTags!["client_name"], "flutter/android-sdk"); + expect(responses[1]!.eventTags!["client_version"], "2.0.0-beta"); + + }); + + test("Test log event with client sdk name and version", () async { + var sdk = OptimizelyFlutterSdk(testSDKKey); + + var responses = []; + + await sdk.addLogEventNotificationListener((msg) { + responses.add(msg); + }); + var callHandler = OptimizelyClientWrapper.methodCallHandler; + tester?.setMockMethodCallHandler(channel, callHandler); + TestUtils.sendTestClientNameAndVersionLogEventNotification( + callHandler, 0, testSDKKey, "flutter/android-sdk", "2.0.0-beta"); + + TestUtils.sendTestClientNameAndVersionLogEventNotification( + callHandler, 0, testSDKKey, "flutter/swift-sdk", "2.0.0"); + + expect(responses.length == 2, true); + + expect(responses[0]!.params!["client_name"], "flutter/android-sdk"); + expect(responses[0]!.params!["client_version"], "2.0.0-beta"); + expect(responses[1]!.params!["client_name"], "flutter/swift-sdk"); + expect(responses[1]!.params!["client_version"], "2.0.0"); + }); + + }); } diff --git a/test/test_utils.dart b/test/test_utils.dart index e08eb73..986bd8e 100644 --- a/test/test_utils.dart +++ b/test/test_utils.dart @@ -106,6 +106,23 @@ class TestUtils { })); } + static sendTestClientNameAndVersionLogEventNotification( + Function(MethodCall message) handler, int id, String sdkKey, String clientName, String sdkVersion) { + var payload = { + Constants.url: "$id", + Constants.params: { + "test": id, + "client_name": clientName, + "client_version": sdkVersion + } + }; + handler(MethodCall(Constants.logEventCallbackListener, { + Constants.id: id, + Constants.sdkKey: sdkKey, + Constants.payload: payload + })); + } + static sendTestTrackNotifications( Function(MethodCall message) handler, int id, String sdkKey) { var payload = { @@ -121,6 +138,24 @@ class TestUtils { })); } + static sendTestTrackClientNameAndVersion(Function(MethodCall message) handler, int id, String sdkKey, String clientName, String sdkVersion) { + var payload = { + Constants.eventKey: "$id", + Constants.userId: "test", + Constants.attributes: {"test": id}, + Constants.eventTags: { + "testTag": id, + "client_name": clientName, + "client_version": sdkVersion + } + }; + handler(MethodCall(Constants.trackCallBackListener, { + Constants.id: id, + Constants.sdkKey: sdkKey, + Constants.payload: payload + })); + } + static sendTestUpdateConfigNotifications( Function(MethodCall message) handler, int id, String sdkKey) { handler(MethodCall(Constants.configUpdateCallBackListener, { From eec8fdb9a25d85627d66360c1e8741c0d8760079 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam <129880873+muzahidul-opti@users.noreply.github.com> Date: Mon, 29 Jan 2024 19:19:11 +0600 Subject: [PATCH 32/39] [FSSDK-8713] chore: prepare for release 2.0.0 (#73) * Update package_info.dart * Update pubspec.yaml * Update README.md * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md * clean up --- CHANGELOG.md | 51 +++++++++++++++++++++++++++ README.md | 2 +- lib/package_info.dart | 4 +-- pubspec.yaml | 2 +- test/optimizely_flutter_sdk_test.dart | 8 ++--- 5 files changed, 59 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b07c65b..0e77038 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,56 @@ # Optimizely Flutter SDK Changelog +## 2.0.0 +January 23, 2024 + +### New Features + +The 2.0.0 release introduces a new primary feature, [Advanced Audience Targeting]( https://docs.developers.optimizely.com/feature-experimentation/docs/optimizely-data-platform-advanced-audience-targeting) enabled through integration with [Optimizely Data Platform (ODP)](https://docs.developers.optimizely.com/optimizely-data-platform/docs) ([#52](https://github.com/optimizely/optimizely-flutter-sdk/pull/52), [#57](https://github.com/optimizely/optimizely-flutter-sdk/pull/57), [#72](https://github.com/optimizely/optimizely-flutter-sdk/pull/72)). + +You can use ODP, a high-performance [Customer Data Platform (CDP)]( https://www.optimizely.com/optimization-glossary/customer-data-platform/), to easily create complex real-time segments (RTS) using first-party and 50+ third-party data sources out of the box. You can create custom schemas that support the user attributes important for your business, and stitch together user behavior done on different devices to better understand and target your customers for personalized user experiences. ODP can be used as a single source of truth for these segments in any Optimizely or 3rd party tool. + +With ODP accounts integrated into Optimizely projects, you can build audiences using segments pre-defined in ODP. The SDK will fetch the segments for given users and make decisions using the segments. For access to ODP audience targeting in your Feature Experimentation account, please contact your Customer Success Manager. + +This version includes the following changes: + +* New API added to `OptimizelyUserContext`: + + - `fetchQualifiedSegments()`: this API will retrieve user segments from the ODP server. The fetched segments will be used for audience evaluation. The fetched data will be stored in the local cache to avoid repeated network delays. + + - When an `OptimizelyUserContext` is created, the SDK will automatically send an identify request to the ODP server to facilitate observing user activities. + +* New APIs added to `OptimizelyFlutterSdk`: + + - `sendOdpEvent()`: customers can build/send arbitrary ODP events that will bind user identifiers and data to user profiles in ODP. + + - `createUserContext()` with anonymous user IDs: user-contexts can be created without a userId. The SDK will create and use a persistent `VUID` specific to a device when userId is not provided. + +For details, refer to our documentation pages: + +* [Advanced Audience Targeting](https://docs.developers.optimizely.com/feature-experimentation/docs/optimizely-data-platform-advanced-audience-targeting) + +* [Client SDK Support](https://docs.developers.optimizely.com/feature-experimentation/v1.0/docs/advanced-audience-targeting-for-client-side-sdks) + +* [Initialize Flutter SDK](https://docs.developers.optimizely.com/feature-experimentation/docs/initialize-sdk-flutter) + +* [OptimizelyUserContext Flutter SDK](https://docs.developers.optimizely.com/feature-experimentation/docs/optimizelyusercontext-flutter) + +* [Advanced Audience Targeting segment qualification methods](https://docs.developers.optimizely.com/feature-experimentation/docs/advanced-audience-targeting-segment-qualification-methods-flutter) + +* [Send Optimizely Data Platform data using Advanced Audience Targeting](https://docs.developers.optimizely.com/feature-experimentation/docs/send-odp-data-using-advanced-audience-targeting-flutter) + + +### Bug Fixes + +* Crash fixed, fetchQualifiedSegments without options ([#64](https://github.com/optimizely/optimizely-flutter-sdk/pull/64)). +* Fix proguard for logback and dart version ([#68](https://github.com/optimizely/optimizely-flutter-sdk/pull/68)). + +### Functionality Enhancements + +* Add specific client name support to track event ([#72](https://github.com/optimizely/optimizely-flutter-sdk/pull/72)). +* Update Github Issue Templates ([#65](https://github.com/optimizely/optimizely-flutter-sdk/pull/65)). +* Add configurable log level support ([#63](https://github.com/optimizely/optimizely-flutter-sdk/pull/63)). + ## 2.0.0-beta September 21, 2023 diff --git a/README.md b/README.md index e8a6edf..2de1dd5 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Other Flutter platforms are not currently supported by this SDK. To add the flutter-sdk to your project dependencies, include the following in your app's pubspec.yaml: ``` - optimizely_flutter_sdk: ^1.0.1 + optimizely_flutter_sdk: ^2.0.0 ``` Then run diff --git a/lib/package_info.dart b/lib/package_info.dart index 4c8df19..adef0b3 100644 --- a/lib/package_info.dart +++ b/lib/package_info.dart @@ -3,5 +3,5 @@ class PackageInfo { static const String name = 'optimizely_flutter_sdk'; - static const String version = '2.0.0-beta'; -} \ No newline at end of file + static const String version = '2.0.0'; +} diff --git a/pubspec.yaml b/pubspec.yaml index bda345b..7ffd848 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: optimizely_flutter_sdk description: This repository houses the Flutter SDK for use with Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts. -version: 2.0.0-beta +version: 2.0.0 homepage: https://github.com/optimizely/optimizely-flutter-sdk environment: diff --git a/test/optimizely_flutter_sdk_test.dart b/test/optimizely_flutter_sdk_test.dart index dd6bd7a..821e234 100644 --- a/test/optimizely_flutter_sdk_test.dart +++ b/test/optimizely_flutter_sdk_test.dart @@ -1589,14 +1589,14 @@ void main() { TestUtils.sendTestTrackClientNameAndVersion( callHandler, 0, testSDKKey, "flutter/swift-sdk", "2.0.0"); TestUtils.sendTestTrackClientNameAndVersion( - callHandler, 0, testSDKKey, "flutter/android-sdk", "2.0.0-beta"); + callHandler, 0, testSDKKey, "flutter/android-sdk", "2.0.0"); expect(responses.length == 2, true); expect(responses[0]!.eventTags!["client_name"], "flutter/swift-sdk"); expect(responses[0]!.eventTags!["client_version"], "2.0.0"); expect(responses[1]!.eventTags!["client_name"], "flutter/android-sdk"); - expect(responses[1]!.eventTags!["client_version"], "2.0.0-beta"); + expect(responses[1]!.eventTags!["client_version"], "2.0.0"); }); @@ -1611,7 +1611,7 @@ void main() { var callHandler = OptimizelyClientWrapper.methodCallHandler; tester?.setMockMethodCallHandler(channel, callHandler); TestUtils.sendTestClientNameAndVersionLogEventNotification( - callHandler, 0, testSDKKey, "flutter/android-sdk", "2.0.0-beta"); + callHandler, 0, testSDKKey, "flutter/android-sdk", "2.0.0"); TestUtils.sendTestClientNameAndVersionLogEventNotification( callHandler, 0, testSDKKey, "flutter/swift-sdk", "2.0.0"); @@ -1619,7 +1619,7 @@ void main() { expect(responses.length == 2, true); expect(responses[0]!.params!["client_name"], "flutter/android-sdk"); - expect(responses[0]!.params!["client_version"], "2.0.0-beta"); + expect(responses[0]!.params!["client_version"], "2.0.0"); expect(responses[1]!.params!["client_name"], "flutter/swift-sdk"); expect(responses[1]!.params!["client_version"], "2.0.0"); }); From 25ee944d06b624e495de5987613109144d606c15 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam <129880873+muzahidul-opti@users.noreply.github.com> Date: Tue, 9 Jul 2024 23:06:06 +0600 Subject: [PATCH 33/39] [FSSDK-10374] chore: migration of flutter's gradle plugins (#74) --- android/build.gradle | 29 ++++++++---------------- example/android/app/build.gradle | 20 ++++++++-------- example/android/build.gradle | 14 ++---------- example/android/settings.gradle | 39 +++++++++++++++++++++++++------- 4 files changed, 54 insertions(+), 48 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 9934507..cdec407 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,24 +1,17 @@ +plugins { + id "com.android.library" +} + group 'com.optimizely.optimizely_flutter_sdk' version '1.0' -buildscript { - - def version_name = System.getenv('TRAVIS_TAG') - if (version_name != null) { - rootProject.ext.version_name = version_name - } else { - rootProject.ext.version_name = 'debugVersion' - } - - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' - } +def version_name = System.getenv('TRAVIS_TAG') +if (version_name != null) { + rootProject.ext.version_name = version_name +} else { + rootProject.ext.version_name = 'debugVersion' } + configurations { all*.exclude group: 'com.google.guava', module: 'listenablefuture' } @@ -29,7 +22,6 @@ rootProject.allprojects { } } -apply plugin: 'com.android.library' ext { compile_sdk_version = 32 @@ -71,7 +63,6 @@ android { } - dependencies { implementation 'androidx.multidex:multidex:2.0.0' implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 89fc349..415ec79 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -1,3 +1,10 @@ + +plugins { + id "com.android.application" + id "org.jetbrains.kotlin.android" + id "dev.flutter.flutter-gradle-plugin" +} + def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -6,11 +13,6 @@ if (localPropertiesFile.exists()) { } } -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' @@ -21,10 +23,6 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { compileSdkVersion 32 ndkVersion flutter.ndkVersion @@ -57,3 +55,7 @@ android { flutter { source '../..' } + +dependencies { + implementation 'com.android.support:multidex:1.0.3' +} diff --git a/example/android/build.gradle b/example/android/build.gradle index 58051f6..81717b6 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,18 +1,8 @@ -buildscript { - ext.kotlin_version = '1.6.10' - repositories { - google() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} ext { android_sdk_version = "4.0.0" } + allprojects { repositories { google() @@ -28,6 +18,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 44e62bc..5710b01 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -1,11 +1,34 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + + settings.ext.flutterSdkPath = flutterSdkPath() + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + resolutionStrategy { + eachPlugin { + if (requested.id.namespace == 'dev.flutter') { + useModule("dev.flutter:${requested.id.name}:${requested.version}") + } + } + } +} -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.2.1" apply false + id "org.jetbrains.kotlin.android" version "1.6.10" apply false +} + +include ":app" -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" From 48d4dd5c8ff6d42ad2990c1661f348bb46d99d8e Mon Sep 17 00:00:00 2001 From: Muzahidul Islam <129880873+muzahidul-opti@users.noreply.github.com> Date: Fri, 26 Jul 2024 23:55:55 +0600 Subject: [PATCH 34/39] chore: update version and fix warning (#76) --- CHANGELOG.md | 7 +++++++ README.md | 2 +- lib/package_info.dart | 2 +- lib/src/data_objects/decide_response.dart | 1 + lib/src/data_objects/optimizely_config.dart | 4 ++++ lib/src/optimizely_client_wrapper.dart | 2 +- pubspec.yaml | 2 +- 7 files changed, 16 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e77038..6cf3ba9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Optimizely Flutter SDK Changelog +## 2.0.1 +July 25, 2024 + +### Bug Fixes + +* Migration of flutter's gradle plugins ([#74](https://github.com/optimizely/optimizely-flutter-sdk/pull/74)). + ## 2.0.0 January 23, 2024 diff --git a/README.md b/README.md index 2de1dd5..9fbc097 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Other Flutter platforms are not currently supported by this SDK. To add the flutter-sdk to your project dependencies, include the following in your app's pubspec.yaml: ``` - optimizely_flutter_sdk: ^2.0.0 + optimizely_flutter_sdk: ^2.0.1 ``` Then run diff --git a/lib/package_info.dart b/lib/package_info.dart index adef0b3..2ccb4b5 100644 --- a/lib/package_info.dart +++ b/lib/package_info.dart @@ -3,5 +3,5 @@ class PackageInfo { static const String name = 'optimizely_flutter_sdk'; - static const String version = '2.0.0'; + static const String version = '2.0.1'; } diff --git a/lib/src/data_objects/decide_response.dart b/lib/src/data_objects/decide_response.dart index dc2809a..29f8cdf 100644 --- a/lib/src/data_objects/decide_response.dart +++ b/lib/src/data_objects/decide_response.dart @@ -77,6 +77,7 @@ class BaseDecideResponse extends BaseResponse { if (json[Constants.responseResult] is Map<dynamic, dynamic>) { final decisionsMap = Map<String, dynamic>.from(json[Constants.responseResult]); + // ignore: unnecessary_set_literal decisionsMap.forEach((k, v) => { if (v is Map<dynamic, dynamic>) {_decisions[k] = Decision(Map<String, dynamic>.from(v))} diff --git a/lib/src/data_objects/optimizely_config.dart b/lib/src/data_objects/optimizely_config.dart index 51afbef..b771cc0 100644 --- a/lib/src/data_objects/optimizely_config.dart +++ b/lib/src/data_objects/optimizely_config.dart @@ -33,6 +33,7 @@ class OptimizelyConfig { if (optimizelyConfig[Constants.experimentsMap] is Map<dynamic, dynamic>) { final experimentsMapDynamic = Map<String, dynamic>.from(optimizelyConfig[Constants.experimentsMap]); + // ignore: unnecessary_set_literal experimentsMapDynamic.forEach((k, v) => { if (v is Map<dynamic, dynamic>) { @@ -45,6 +46,7 @@ class OptimizelyConfig { if (optimizelyConfig[Constants.featuresMap] is Map<dynamic, dynamic>) { final featuresMapDynamic = Map<String, dynamic>.from(optimizelyConfig[Constants.featuresMap]); + // ignore: unnecessary_set_literal featuresMapDynamic.forEach((k, v) => { if (v is Map<dynamic, dynamic>) { @@ -288,6 +290,7 @@ class OptimizelyExperiment { if (parsedJson[Constants.variationsMap] is Map<dynamic, dynamic>) { final variationsMapDynamic = Map<String, dynamic>.from(parsedJson[Constants.variationsMap]); + // ignore: unnecessary_set_literal variationsMapDynamic.forEach((k, v) => { if (v is Map<dynamic, dynamic>) { @@ -338,6 +341,7 @@ class OptimizelyVariation { if (parsedJson[Constants.variablesMap] is Map<dynamic, dynamic>) { final variablesMapDynamic = Map<String, dynamic>.from(parsedJson[Constants.variablesMap]); + // ignore: unnecessary_set_literal variablesMapDynamic.forEach((k, v) => { if (v is Map<dynamic, dynamic>) { diff --git a/lib/src/optimizely_client_wrapper.dart b/lib/src/optimizely_client_wrapper.dart index 590d17c..4d6e0c0 100644 --- a/lib/src/optimizely_client_wrapper.dart +++ b/lib/src/optimizely_client_wrapper.dart @@ -67,7 +67,7 @@ class OptimizelyClientWrapper { _channel.setMethodCallHandler(methodCallHandler); final convertedOptions = Utils.convertDecideOptions(defaultDecideOptions); final convertedLogLevel = Utils.convertLogLevel(defaultLogLevel); - final sdkVersion = PackageInfo.version; + const sdkVersion = PackageInfo.version; Map<String, dynamic> requestDict = { Constants.sdkKey: sdkKey, diff --git a/pubspec.yaml b/pubspec.yaml index 7ffd848..53e822c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: optimizely_flutter_sdk description: This repository houses the Flutter SDK for use with Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts. -version: 2.0.0 +version: 2.0.1 homepage: https://github.com/optimizely/optimizely-flutter-sdk environment: From 8598e932bde5a318798f704e3264217bae8fae1f Mon Sep 17 00:00:00 2001 From: Farhan Anjum <Farhan.Anjum@optimizely.com> Date: Fri, 27 Sep 2024 22:25:57 +0600 Subject: [PATCH 35/39] [FSSDK-10665] fix: Github Actions YAML files vulnerable to script injections corrected (#77) --- .github/workflows/flutter.yml | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/workflows/flutter.yml b/.github/workflows/flutter.yml index ed8d977..89d9093 100644 --- a/.github/workflows/flutter.yml +++ b/.github/workflows/flutter.yml @@ -37,14 +37,18 @@ jobs: path: 'home/runner/travisci-tools' ref: 'master' - name: set SDK Branch if PR + env: + HEAD_REF: ${{ github.head_ref }} if: ${{ github.event_name == 'pull_request' }} run: | - echo "SDK_BRANCH=${{ github.head_ref }}" >> $GITHUB_ENV + echo "SDK_BRANCH=$HEAD_REF" >> $GITHUB_ENV - name: set SDK Branch if not pull request + env: + REF_NAME: ${{ github.ref_name }} if: ${{ github.event_name != 'pull_request' }} run: | - echo "SDK_BRANCH=${{ github.ref_name }}" >> $GITHUB_ENV - echo "TRAVIS_BRANCH=${{ github.ref_name }}" >> $GITHUB_ENV + echo "SDK_BRANCH=$REF_NAME" >> $GITHUB_ENV + echo "TRAVIS_BRANCH=$REF_NAME" >> $GITHUB_ENV - name: Trigger build env: SDK: android @@ -75,14 +79,18 @@ jobs: path: 'home/runner/travisci-tools' ref: 'master' - name: set SDK Branch if PR + env: + HEAD_REF: ${{ github.head_ref }} if: ${{ github.event_name == 'pull_request' }} run: | - echo "SDK_BRANCH=${{ github.head_ref }}" >> $GITHUB_ENV + echo "SDK_BRANCH=$HEAD_REF" >> $GITHUB_ENV - name: set SDK Branch if not pull request + env: + REF_NAME: ${{ github.ref_name }} if: ${{ github.event_name != 'pull_request' }} run: | - echo "SDK_BRANCH=${{ github.ref_name }}" >> $GITHUB_ENV - echo "TRAVIS_BRANCH=${{ github.ref_name }}" >> $GITHUB_ENV + echo "SDK_BRANCH=$REF_NAME" >> $GITHUB_ENV + echo "TRAVIS_BRANCH=$REF_NAME" >> $GITHUB_ENV - name: Trigger build env: SDK: ios From dc4afe8d5108aa813cf8742614e33bed72453165 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam <129880873+muzahidul-opti@users.noreply.github.com> Date: Thu, 28 Nov 2024 20:29:12 +0600 Subject: [PATCH 36/39] [FSSDK-10759] feat: make vuid optln (#78) VUID optln implemented for flutter --- .github/workflows/flutter.yml | 25 ++++++++++ android/build.gradle | 2 +- .../OptimizelyFlutterClient.java | 10 +++- .../helper_classes/Constants.java | 1 + ios/Classes/HelperClasses/Constants.swift | 1 + .../SwiftOptimizelyFlutterSdkPlugin.swift | 10 ++-- ios/optimizely_flutter_sdk.podspec | 2 +- lib/src/data_objects/get_vuid_response.dart | 2 +- lib/src/data_objects/sdk_settings.dart | 3 ++ lib/src/optimizely_client_wrapper.dart | 1 + lib/src/utils/constants.dart | 1 + test/optimizely_flutter_sdk_test.dart | 48 +++++++++++++++---- 12 files changed, 91 insertions(+), 15 deletions(-) diff --git a/.github/workflows/flutter.yml b/.github/workflows/flutter.yml index 89d9093..fe24b49 100644 --- a/.github/workflows/flutter.yml +++ b/.github/workflows/flutter.yml @@ -5,6 +5,16 @@ on: branches: [ "master" ] pull_request: branches: [ "master" ] + # workflow_dispatch: + # inputs: + # sdk_branch: + # description: "Specify the SDK branch" + # required: false + # default: "master" + # testapp_branch: + # description: "Specify the test app branch" + # required: false + # default: "master" jobs: unit_test_coverage: @@ -36,6 +46,21 @@ jobs: repository: 'optimizely/travisci-tools' path: 'home/runner/travisci-tools' ref: 'master' + # Set SDK Branch based on input or PR/Push + # - name: Set SDK Branch and Test App Branch + # run: | + # # If manually triggered + # if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + # echo "SDK_BRANCH=${{ github.event.inputs.sdk_branch || 'master' }}" >> $GITHUB_ENV + # echo "TESTAPP_BRANCH=${{ github.event.inputs.testapp_branch || 'master' }}" >> $GITHUB_ENV + # # If triggered by PR + # elif [[ "${{ github.event_name }}" == "pull_request" ]]; then + # echo "SDK_BRANCH=${{ github.head_ref }}" >> $GITHUB_ENV + # # If triggered by push + # else + # echo "SDK_BRANCH=${{ github.ref_name }}" >> $GITHUB_ENV + # echo "TRAVIS_BRANCH=${{ github.ref_name }}" >> $GITHUB_ENV + # fi - name: set SDK Branch if PR env: HEAD_REF: ${{ github.head_ref }} diff --git a/android/build.gradle b/android/build.gradle index cdec407..12f972d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -74,7 +74,7 @@ dependencies { implementation 'org.slf4j:slf4j-api:2.0.7' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10" - implementation "com.optimizely.ab:android-sdk:4.0.0" + implementation "com.optimizely.ab:android-sdk:5.0.0" implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.4' implementation ('com.google.guava:guava:19.0') { exclude group:'com.google.guava', module:'listenablefuture' diff --git a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterClient.java b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterClient.java index c571c2f..cf85e3d 100644 --- a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterClient.java +++ b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterClient.java @@ -55,6 +55,7 @@ import static com.optimizely.optimizely_flutter_sdk.helper_classes.Constants.*; import static com.optimizely.optimizely_flutter_sdk.helper_classes.Constants.RequestParameterKey.DISABLE_ODP; +import static com.optimizely.optimizely_flutter_sdk.helper_classes.Constants.RequestParameterKey.ENABLE_VUID; import static com.optimizely.optimizely_flutter_sdk.helper_classes.Constants.RequestParameterKey.SEGMENTS_CACHE_SIZE; import static com.optimizely.optimizely_flutter_sdk.helper_classes.Constants.RequestParameterKey.SEGMENTS_CACHE_TIMEOUT_IN_SECONDS; import static com.optimizely.optimizely_flutter_sdk.helper_classes.Constants.RequestParameterKey.TIMEOUT_FOR_ODP_EVENT_IN_SECONDS; @@ -144,6 +145,7 @@ protected void initializeOptimizely(@NonNull ArgumentsParser argumentsParser, @N int timeoutForSegmentFetchInSecs = 10; int timeoutForOdpEventInSecs = 10; boolean disableOdp = false; + boolean enableVuid = false; Map<String, Object> sdkSettings = argumentsParser.getOptimizelySdkSettings(); if (sdkSettings != null) { if (sdkSettings.containsKey(SEGMENTS_CACHE_SIZE)) { @@ -161,6 +163,9 @@ protected void initializeOptimizely(@NonNull ArgumentsParser argumentsParser, @N if (sdkSettings.containsKey(DISABLE_ODP)) { disableOdp = (boolean) sdkSettings.get(DISABLE_ODP); } + if (sdkSettings.containsKey(ENABLE_VUID)) { + enableVuid = (boolean) sdkSettings.get(ENABLE_VUID); + } } // Creating new instance OptimizelyManager.Builder optimizelyManagerBuilder = OptimizelyManager.builder() @@ -179,6 +184,9 @@ protected void initializeOptimizely(@NonNull ArgumentsParser argumentsParser, @N if (disableOdp) { optimizelyManagerBuilder.withODPDisabled(); } + if (enableVuid) { + optimizelyManagerBuilder.withVuidEnabled(); + } OptimizelyManager optimizelyManager = optimizelyManagerBuilder.build(context); optimizelyManager.initialize(context, null, (OptimizelyClient client) -> { @@ -471,7 +479,7 @@ protected void getVuid(ArgumentsParser argumentsParser, @NonNull Result result) if (!isOptimizelyClientValid(sdkKey, optimizelyClient, result)) { return; } - result.success(createResponse(true, Collections.singletonMap(RequestParameterKey.VUID, optimizelyClient.getVuid()), "")); + result.success(createResponse(optimizelyClient.getVuid() != null, Collections.singletonMap(RequestParameterKey.VUID, optimizelyClient.getVuid()), "")); } /// Checks if the user is qualified for the given segment. diff --git a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Constants.java b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Constants.java index 0c73d32..62f0ce9 100644 --- a/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Constants.java +++ b/android/src/main/java/com/optimizely/optimizely_flutter_sdk/helper_classes/Constants.java @@ -96,6 +96,7 @@ public static class RequestParameterKey { public static final String TIMEOUT_FOR_SEGMENT_FETCH_IN_SECONDS = "timeoutForSegmentFetchInSecs"; public static final String TIMEOUT_FOR_ODP_EVENT_IN_SECONDS = "timeoutForOdpEventInSecs"; public static final String DISABLE_ODP = "disableOdp"; + public static final String ENABLE_VUID = "enableVuid"; } public static class ErrorMessage { diff --git a/ios/Classes/HelperClasses/Constants.swift b/ios/Classes/HelperClasses/Constants.swift index f3ca6d5..a29370a 100644 --- a/ios/Classes/HelperClasses/Constants.swift +++ b/ios/Classes/HelperClasses/Constants.swift @@ -114,6 +114,7 @@ struct RequestParameterKey { static let timeoutForSegmentFetchInSecs = "timeoutForSegmentFetchInSecs" static let timeoutForOdpEventInSecs = "timeoutForOdpEventInSecs" static let disableOdp = "disableOdp" + static let enableVuid = "enableVuid" static let sdkVersion = "sdkVersion"; } diff --git a/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift b/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift index 8a2fd75..7c093c4 100644 --- a/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift +++ b/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift @@ -118,6 +118,7 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin { var timeoutForSegmentFetchInSecs: Int = 10 var timeoutForOdpEventInSecs: Int = 10 var disableOdp: Bool = false + var enableVuid: Bool = false var sdkVersion = parameters[RequestParameterKey.sdkVersion] as? String var sdkName = Utils.sdkName @@ -137,8 +138,11 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin { if let isOdpDisabled = sdkSettings[RequestParameterKey.disableOdp] as? Bool { disableOdp = isOdpDisabled } + if let isEnableVuid = sdkSettings[RequestParameterKey.enableVuid] as? Bool { + enableVuid = isEnableVuid + } } - let optimizelySdkSettings = OptimizelySdkSettings(segmentsCacheSize: segmentsCacheSize, segmentsCacheTimeoutInSecs: segmentsCacheTimeoutInSecs, timeoutForSegmentFetchInSecs: timeoutForSegmentFetchInSecs, timeoutForOdpEventInSecs: timeoutForOdpEventInSecs, disableOdp: disableOdp, sdkName: sdkName, sdkVersion: sdkVersion) + let optimizelySdkSettings = OptimizelySdkSettings(segmentsCacheSize: segmentsCacheSize, segmentsCacheTimeoutInSecs: segmentsCacheTimeoutInSecs, timeoutForSegmentFetchInSecs: timeoutForSegmentFetchInSecs, timeoutForOdpEventInSecs: timeoutForOdpEventInSecs, disableOdp: disableOdp, enableVuid: enableVuid, sdkName: sdkName, sdkVersion: sdkVersion) // Datafile Download Interval var datafilePeriodicDownloadInterval = 10 * 60 // seconds @@ -374,7 +378,7 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin { } else { userContextsTracker[sdkKey] = [userContextId: userContext] } - result(self.createResponse(success: true, result: [RequestParameterKey.userContextId: userContextId])) + result(self.createResponse(success: userContext != nil, result: [RequestParameterKey.userContextId: userContextId])) } /// Returns userId for the user context. @@ -442,7 +446,7 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin { guard let optimizelyClient = getOptimizelyClient(sdkKey: sdkKey, result: result) else { return } - result(self.createResponse(success: true, result: [RequestParameterKey.vuid: optimizelyClient.vuid])) + result(self.createResponse(success: optimizelyClient.vuid != nil, result: [RequestParameterKey.vuid: optimizelyClient.vuid])) } /// Checks if the user is qualified for the given segment. diff --git a/ios/optimizely_flutter_sdk.podspec b/ios/optimizely_flutter_sdk.podspec index 4da4408..14f4dbc 100644 --- a/ios/optimizely_flutter_sdk.podspec +++ b/ios/optimizely_flutter_sdk.podspec @@ -13,7 +13,7 @@ Pod::Spec.new do |s| s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency 'OptimizelySwiftSDK', '4.0.0' + s.dependency 'OptimizelySwiftSDK', '5.0.0' s.platform = :ios, '10.0' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/lib/src/data_objects/get_vuid_response.dart b/lib/src/data_objects/get_vuid_response.dart index 4be045b..e5fcfb0 100644 --- a/lib/src/data_objects/get_vuid_response.dart +++ b/lib/src/data_objects/get_vuid_response.dart @@ -18,7 +18,7 @@ import 'package:optimizely_flutter_sdk/src/data_objects/base_response.dart'; import 'package:optimizely_flutter_sdk/src/utils/constants.dart'; class GetVuidResponse extends BaseResponse { - String vuid = ""; + String? vuid; GetVuidResponse(Map<String, dynamic> json) : super(json) { if (json[Constants.responseResult] is Map<dynamic, dynamic>) { diff --git a/lib/src/data_objects/sdk_settings.dart b/lib/src/data_objects/sdk_settings.dart index 412432a..448a2e8 100644 --- a/lib/src/data_objects/sdk_settings.dart +++ b/lib/src/data_objects/sdk_settings.dart @@ -25,6 +25,8 @@ class SDKSettings { final int timeoutForOdpEventInSecs; // Set this flag to true (default = false) to disable ODP features final bool disableOdp; + // Set this flag to true (default = false) to enable VUID feature + final bool enableVuid; const SDKSettings({ this.segmentsCacheSize = 100, // Default segmentsCacheSize @@ -33,5 +35,6 @@ class SDKSettings { 10, // Default timeoutForSegmentFetchInSecs this.timeoutForOdpEventInSecs = 10, // Default timeoutForOdpEventInSecs this.disableOdp = false, // Default disableOdp + this.enableVuid = false, // Default disableVuid }); } diff --git a/lib/src/optimizely_client_wrapper.dart b/lib/src/optimizely_client_wrapper.dart index 4d6e0c0..fb4fce0 100644 --- a/lib/src/optimizely_client_wrapper.dart +++ b/lib/src/optimizely_client_wrapper.dart @@ -90,6 +90,7 @@ class OptimizelyClientWrapper { sdkSettings.timeoutForSegmentFetchInSecs, Constants.timeoutForOdpEventInSecs: sdkSettings.timeoutForOdpEventInSecs, Constants.disableOdp: sdkSettings.disableOdp, + Constants.enableVuid: sdkSettings.enableVuid, }; requestDict[Constants.optimizelySdkSettings] = optimizelySdkSettings; diff --git a/lib/src/utils/constants.dart b/lib/src/utils/constants.dart index 7ef9547..fb33033 100644 --- a/lib/src/utils/constants.dart +++ b/lib/src/utils/constants.dart @@ -131,6 +131,7 @@ class Constants { "timeoutForSegmentFetchInSecs"; static const String timeoutForOdpEventInSecs = "timeoutForOdpEventInSecs"; static const String disableOdp = "disableOdp"; + static const String enableVuid = "enableVuid"; // Response keys static const String responseSuccess = "success"; diff --git a/test/optimizely_flutter_sdk_test.dart b/test/optimizely_flutter_sdk_test.dart index 821e234..862c4b0 100644 --- a/test/optimizely_flutter_sdk_test.dart +++ b/test/optimizely_flutter_sdk_test.dart @@ -56,7 +56,6 @@ void main() { SDKSettings sdkSettings = const SDKSettings(); int datafilePeriodicDownloadInterval = 0; String defaultLogLevel = "error"; - const MethodChannel channel = MethodChannel("optimizely_flutter_sdk"); dynamic mockOptimizelyConfig; @@ -106,6 +105,7 @@ void main() { timeoutForOdpEventInSecs: settings[Constants.timeoutForOdpEventInSecs], disableOdp: settings[Constants.disableOdp], + enableVuid: settings[Constants.enableVuid], ); } @@ -175,9 +175,19 @@ void main() { }; case Constants.createUserContextMethod: expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); - if (methodCall.arguments[Constants.userId] != null) { + var resultUserId = userContextId; + if (methodCall.arguments[Constants.userId] == null) { + if (sdkSettings.enableVuid) { + resultUserId = vuid; + } else { + return { + Constants.responseSuccess: false, + }; + } + } else if (methodCall.arguments[Constants.userId] != null) { expect(methodCall.arguments[Constants.userId], equals(userId)); } + if (methodCall.arguments[Constants.attributes]["abc"] != null) { expect(methodCall.arguments[Constants.attributes]["abc"], equals(attributes["abc"])); @@ -185,7 +195,7 @@ void main() { expect(methodCall.arguments[Constants.userContextId], isNull); return { Constants.responseSuccess: true, - Constants.responseResult: {Constants.userContextId: userContextId}, + Constants.responseResult: {Constants.userContextId: resultUserId}, }; case Constants.getUserIdMethod: expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); @@ -266,6 +276,8 @@ void main() { case Constants.getVuidMethod: expect(methodCall.arguments[Constants.sdkKey], isNotEmpty); expect(methodCall.arguments[Constants.userContextId], isNull); + expect(methodCall.arguments[Constants.vuid], isNull); + var vuid = sdkSettings.enableVuid ? "vuid_123" : null; return { Constants.responseSuccess: true, Constants.responseResult: {Constants.vuid: vuid}, @@ -376,6 +388,7 @@ void main() { tearDown(() { tester?.setMockMethodCallHandler(channel, null); + sdkSettings = const SDKSettings(); }); group("Integration: OptimizelyFlutterSdk MethodChannel", () { @@ -650,8 +663,17 @@ void main() { expect(userContext, isNotNull); }); - test("should succeed null userId", () async { + test("should fail when disable vuid and userId null", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); + sdk.initializeClient(); + var userContext = await sdk.createUserContext(attributes: attributes); + expect(userContext, isNull); + }); + + test("should succed when enable vuid and userId null", () async { + const settings = SDKSettings(enableVuid: true); + var sdk = OptimizelyFlutterSdk(testSDKKey, sdkSettings: settings); + sdk.initializeClient(); var userContext = await sdk.createUserContext(attributes: attributes); expect(userContext, isNotNull); }); @@ -662,10 +684,11 @@ void main() { expect(userContext, isNotNull); }); - test("should succeed null userId and attributes", () async { + test("should not succeed null userId and attributes", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); + sdk.initializeClient(); var userContext = await sdk.createUserContext(); - expect(userContext, isNotNull); + expect(userContext, isNull); }); }); @@ -769,11 +792,20 @@ void main() { }); group("getVuid()", () { - test("should succeed", () async { + test("by default should return null vuid", () async { var sdk = OptimizelyFlutterSdk(testSDKKey); + sdk.initializeClient(); + var response = await sdk.getVuid(); + expect(response.success, isTrue); + expect(response.vuid, isNull); + }); + test("should return vuid when enableVuid true", () async { + const settings = SDKSettings(enableVuid: true); + var sdk = OptimizelyFlutterSdk(testSDKKey, sdkSettings: settings); + sdk.initializeClient(); var response = await sdk.getVuid(); expect(response.success, isTrue); - expect(response.vuid, equals(vuid)); + expect(response.vuid, "vuid_123"); }); }); From 48c1b5c58acadcb353c9ac489276f7f0140da37c Mon Sep 17 00:00:00 2001 From: Muzahidul Islam <129880873+muzahidul-opti@users.noreply.github.com> Date: Thu, 28 Nov 2024 22:55:30 +0600 Subject: [PATCH 37/39] chore: version updated (#79) --- CHANGELOG.md | 11 +++++++++++ README.md | 2 +- lib/package_info.dart | 2 +- pubspec.yaml | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cf3ba9..8c2f2b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Optimizely Flutter SDK Changelog +## 3.0.0 +November 28th, 2024 + +### Breaking Changes +* VUID configuration is now independent of ODP ([#78](https://github.com/optimizely/optimizely-flutter-sdk/pull/78)) +* When VUID is disabled: + * `vuid` is not generated or saved. + * `client-initialized` event will not auto fired on SDK init. + * `vuid` is not included in the odp events as a default attribute. + * `createUserContext()` will be rejected if `userId` is not provided. + ## 2.0.1 July 25, 2024 diff --git a/README.md b/README.md index 9fbc097..c851ae5 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Other Flutter platforms are not currently supported by this SDK. To add the flutter-sdk to your project dependencies, include the following in your app's pubspec.yaml: ``` - optimizely_flutter_sdk: ^2.0.1 + optimizely_flutter_sdk: ^3.0.0 ``` Then run diff --git a/lib/package_info.dart b/lib/package_info.dart index 2ccb4b5..cbf0165 100644 --- a/lib/package_info.dart +++ b/lib/package_info.dart @@ -3,5 +3,5 @@ class PackageInfo { static const String name = 'optimizely_flutter_sdk'; - static const String version = '2.0.1'; + static const String version = '3.0.0'; } diff --git a/pubspec.yaml b/pubspec.yaml index 53e822c..fb899c2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: optimizely_flutter_sdk description: This repository houses the Flutter SDK for use with Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts. -version: 2.0.1 +version: 3.0.0 homepage: https://github.com/optimizely/optimizely-flutter-sdk environment: From 4b3c53c27a3dc9bad19e6d33a13a37f705b56bbe Mon Sep 17 00:00:00 2001 From: Muzahidul Islam <129880873+muzahidul-opti@users.noreply.github.com> Date: Wed, 4 Jun 2025 00:16:29 +0600 Subject: [PATCH 38/39] [FSSDK-11465] chore: add netspring support (#80) --- android/build.gradle | 2 +- ios/optimizely_flutter_sdk.podspec | 2 +- lib/src/optimizely_client_wrapper.dart | 4 ---- lib/src/utils/constants.dart | 2 ++ test/test_utils.dart | 31 +++++++++++++++++++++++--- 5 files changed, 32 insertions(+), 9 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 12f972d..5d4e3ff 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -74,7 +74,7 @@ dependencies { implementation 'org.slf4j:slf4j-api:2.0.7' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10" - implementation "com.optimizely.ab:android-sdk:5.0.0" + implementation "com.optimizely.ab:android-sdk:5.0.1" implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.4' implementation ('com.google.guava:guava:19.0') { exclude group:'com.google.guava', module:'listenablefuture' diff --git a/ios/optimizely_flutter_sdk.podspec b/ios/optimizely_flutter_sdk.podspec index 14f4dbc..2aa6953 100644 --- a/ios/optimizely_flutter_sdk.podspec +++ b/ios/optimizely_flutter_sdk.podspec @@ -13,7 +13,7 @@ Pod::Spec.new do |s| s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency 'OptimizelySwiftSDK', '5.0.0' + s.dependency 'OptimizelySwiftSDK', '5.1.1' s.platform = :ios, '10.0' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/lib/src/optimizely_client_wrapper.dart b/lib/src/optimizely_client_wrapper.dart index fb4fce0..a0869b9 100644 --- a/lib/src/optimizely_client_wrapper.dart +++ b/lib/src/optimizely_client_wrapper.dart @@ -371,7 +371,6 @@ class OptimizelyClientWrapper { if (checkCallBackExist(sdkKey, callback)) { // ignore: avoid_print - print("callback already exists."); return -1; } @@ -417,7 +416,6 @@ class OptimizelyClientWrapper { if (checkCallBackExist(sdkKey, callback)) { // ignore: avoid_print - print("callback already exists."); return -1; } @@ -440,7 +438,6 @@ class OptimizelyClientWrapper { if (checkCallBackExist(sdkKey, callback)) { // ignore: avoid_print - print("callback already exists."); return -1; } @@ -464,7 +461,6 @@ class OptimizelyClientWrapper { if (checkCallBackExist(sdkKey, callback)) { // ignore: avoid_print - print("callback already exists."); return -1; } diff --git a/lib/src/utils/constants.dart b/lib/src/utils/constants.dart index fb33033..2bb5421 100644 --- a/lib/src/utils/constants.dart +++ b/lib/src/utils/constants.dart @@ -62,7 +62,9 @@ class Constants { static const String userContextId = "userContextId"; static const String userContext = "userContext"; static const String experiment = "experiment"; + static const String experimentId = "experimentId"; static const String variation = "variation"; + static const String variationId = "variationId"; static const String userId = "userId"; static const String vuid = "vuid"; static const String experimentKey = "experimentKey"; diff --git a/test/test_utils.dart b/test/test_utils.dart index 986bd8e..350c35e 100644 --- a/test/test_utils.dart +++ b/test/test_utils.dart @@ -89,7 +89,14 @@ class TestUtils { handler(MethodCall(Constants.decisionCallBackListener, { Constants.id: id, Constants.sdkKey: sdkKey, - Constants.payload: {Constants.type: "$id", Constants.userId: "test"} + Constants.payload: <String, Object>{ + Constants.type: "$id", + Constants.userId: "test", + Constants.decisionInfo: const { + Constants.experimentId: "experiment_12345", + Constants.variationId: "variation_12345", + }, + } })); } @@ -129,7 +136,15 @@ class TestUtils { Constants.eventKey: "$id", Constants.userId: "test", Constants.attributes: {"test": id}, - Constants.eventTags: {"testTag": id} + Constants.eventTags: { + "testTag": id, + "nestedTag": { + "string_key": "stringValue", + "int_key": 123, + "double_key": 123.456, + "bool_key": true + } + } }; handler(MethodCall(Constants.trackCallBackListener, { Constants.id: id, @@ -145,6 +160,12 @@ class TestUtils { Constants.attributes: {"test": id}, Constants.eventTags: { "testTag": id, + "nestedTag": { + "string_key": "stringValue", + "int_key": 123, + "double_key": 123.456, + "bool_key": true + }, "client_name": clientName, "client_version": sdkVersion } @@ -179,7 +200,11 @@ class TestUtils { static bool testDecisionNotificationPayload( List notifications, int id, int actualID) { if (notifications[id].type != "$actualID" || - notifications[id].userId != "test") { + notifications[id].userId != "test" || + notifications[id].decisionInfo[Constants.experimentId] != + "experiment_12345" || + notifications[id].decisionInfo[Constants.variationId] != + "variation_12345") { return false; } return true; From bbb47903d00babd488b20af9f806b926d215c967 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam <129880873+muzahidul-opti@users.noreply.github.com> Date: Fri, 13 Jun 2025 00:45:15 +0600 Subject: [PATCH 39/39] [FSSDK-11453] chore: prepare for release 3.0.1 (#81) * Update doc for release 3.0.1 * update change log --- CHANGELOG.md | 7 +++++++ README.md | 2 +- lib/package_info.dart | 2 +- pubspec.yaml | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c2f2b8..6e530c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Optimizely Flutter SDK Changelog +## 3.0.1 +Jun 4th, 2025 + +### Functionality Enhancements + +* Add experiment id and variation id added into decision notification payload ([#80](https://github.com/optimizely/optimizely-flutter-sdk/pull/80)) + ## 3.0.0 November 28th, 2024 diff --git a/README.md b/README.md index c851ae5..1b125d5 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Other Flutter platforms are not currently supported by this SDK. To add the flutter-sdk to your project dependencies, include the following in your app's pubspec.yaml: ``` - optimizely_flutter_sdk: ^3.0.0 + optimizely_flutter_sdk: ^3.0.1 ``` Then run diff --git a/lib/package_info.dart b/lib/package_info.dart index cbf0165..0bdd780 100644 --- a/lib/package_info.dart +++ b/lib/package_info.dart @@ -3,5 +3,5 @@ class PackageInfo { static const String name = 'optimizely_flutter_sdk'; - static const String version = '3.0.0'; + static const String version = '3.0.1'; } diff --git a/pubspec.yaml b/pubspec.yaml index fb899c2..188939d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: optimizely_flutter_sdk description: This repository houses the Flutter SDK for use with Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts. -version: 3.0.0 +version: 3.0.1 homepage: https://github.com/optimizely/optimizely-flutter-sdk environment: