diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..f57cd94 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,49 @@ +# Pull Request Template + +## Description + +Please include a summary of the changes and the related issue. Explain the +motivation for the change and how it addresses the issue. Provide context if necessary. + +Fixes # (issue) + +## Type of Change + +Please delete options that are not relevant and add any that are. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to + not work as expected) +- [ ] Documentation update + +## Checklist + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published in downstream modules + +## How Has This Been Tested? + +Please describe the tests that you ran to verify your changes. Provide +instructions so we can reproduce. Include any relevant details for your test configuration. + +- [ ] Test A +- [ ] Test B + +## Screenshots (if applicable) + +If your changes include UI updates to the docs or other applicable repos, please +provide screenshots or GIFs to demonstrate the changes. + +## Additional Information + +Please add any other information that you think might be useful for the reviewer +to know while reviewing this PR. + +Thank you for contributing to the ABsmartly Java SDK! diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7a3aa2d..2dae825 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,47 +1,64 @@ -# Contributing to the A/B Smartly Java SDK +# Contributing to the ABsmartly Java SDK -The A/B Smartly Java SDK is an open source project and we welcome your feedback and contributions. -The information below describes how to build and test the project, and how to submit a pull request. +The ABsmartly Java SDK is an open-source project, and we welcome your +feedback and contributions. This guide provides information on how to build +and test the project, and how to submit a pull request. ## Development -### Development process +### Development Process -1. Fork the repository and create a topic branch from `main` branch. Please use a descriptive name for your branch. -2. While developing, use descriptive messages in your commits. Avoid short or meaningless sentences like: "fix bug". -3. Make sure to add tests for both positive and negative cases. -4. Run the linter script of the project and fix any issues you find. -5. Run the build script and make sure it runs with no errors. -6. Run all tests and make sure there are no failures. -7. `git push` your changes to GitHub within your topic branch. -8. Open a Pull Request from your forked repo and into the `main` branch of the original repository. -9. When creating your PR, please fill out all the fields of the PR template, as applicable, for the project. -10. Check for conflicts once the pull request is created to make sure your PR can be merged cleanly into `main`. -11. Keep an eye out for any feedback or comments from A/B Smartly's SDK team. +1. **Fork and Branch**: Fork the repository and create a topic branch from the + `main` branch. Use a descriptive name for your branch. +2. **Commit Messages**: Use descriptive commit messages. Avoid short or vague + messages like "fix bug". +3. **Testing**: Add tests for both positive and negative cases to ensure + comprehensive coverage. +4. **Linting**: Run the linter script and fix any issues. This helps maintain + code quality and consistency. +5. **Building**: Run the build script to ensure it completes without errors. +6. **Testing**: Run all tests to ensure there are no failures. +7. **Push Changes**: Push your changes to GitHub in your topic branch. +8. **Pull Request**: Open a pull request from your forked repo into the `main` + branch of the original repository. +9. **PR Template**: Fill out all applicable fields in the pull request template. +10. **Conflict Check**: Ensure there are no conflicts with the `main` branch + when creating the pull request. +11. **Feedback**: Monitor your pull request for any feedback or comments from + the ABsmartly SDK team. ### Building the SDK -``` +```bash ./gradlew build ``` -### Running tests +### Running Tests The project includes unit tests. All tests can be run at once with the following command: -``` +```bash ./gradlew test ``` -### Formatting and static analysis checks +### Formatting and Static Analysis -You can run all formatting and static analysis checks at once with the following command: -``` +You can run all formatting and static analysis checks at once with the following +command: + +```bash ./gradlew check ``` -# Contact +## Contact + +If you have any questions or need further assistance, you can reach us at + or on your company's dedicated ABsmartly Slack Connect +channel. + +--- -If you have any other questions or need to contact us directly we can be reached at sdk@absmartly.com +Thank you for contributing to the ABsmartly Java SDK! Your efforts help us +improve and grow our open-source community. diff --git a/LICENSE b/LICENSE index 57bc88a..a023308 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,4 @@ + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -186,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2024 ABsmartly Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index dc2d045..389926a 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,40 @@ -# A/B Smartly SDK - -A/B Smartly - Java SDK - -## Compatibility - -The A/B Smartly Java SDK is compatible with Java versions 1.6 and later. -It provides both a blocking and an asynchronous interfaces. The asynchronous functions return a [custom backport](https://github.com/stefan-zobel/streamsupport) of the Java 8 `CompletableFuture` API. - -### Android - -The A/B Smartly SDK is compatible with Android 4.4 and later (API level 19+). - -The `android.permission.INTERNET` permission is required. To add this permission to your application ensure the following line is present in the `AndroidManifest.xml` file: -```xml - -``` - -If you target Android 6.0 or earlier, a few extra steps are outlined below for installation and initialization. - + +

+ ABsmartly logo +

+ +# ABsmartly Java SDK + +Welcome to the Java SDK from ABsmartly! This SDK allows you to integrate +Feature Flagging and A/B testing seamlessly into your applications. + +## Table of Contents + +- [About ABsmartly](#about-absmartly) +- [Installation](#installation) + - [Gradle](#gradle) + - [Maven](#maven) +- [Usage](#usage) +- [Contributing](#contributing) +- [License](#license) +- [Support](#support) + +## About ABsmartly + +ABsmartly is a platform that enables you to run A/B tests across your +applications to optimize and improve user experiences. For more information +about our platform and services, please visit our +[website](https://www.absmartly.com) to request a demo. ## Installation -#### Gradle +To install the Java SDK, simply use the following snippet: -To install the ABSmartly SDK, place the following in your `build.gradle` and replace {VERSION} with the latest SDK version available in MavenCentral. +### Gradle ```gradle dependencies { @@ -31,293 +42,41 @@ dependencies { } ``` -#### Maven - -To install the ABSmartly SDK, place the following in your `pom.xml` and replace {VERSION} with the latest SDK version available in MavenCentral. +### Maven ```xml - com.absmartly.sdk - core-api - {VERSION} + com.absmartly.sdk + core-api + {VERSION} ``` -#### Android 6.0 or earlier -When targeting Android 6.0 or earlier, the default Java Security Provider will not work. Using [Conscrypt](https://github.com/google/conscrypt) is recommended. Follow these [instructions](https://github.com/google/conscrypt/blob/master/README.md) to install it as dependency. - -#### Proguard rules -ProGuard is a command-line tool that reduces app size by shrinking bytecode and obfuscates the names of classes, fields and methods. -It’s an ideal fit for developers working with Java or Kotlin who are primarily interested in an Android optimizer. -If you are using [Proguard](https://github.com/Guardsquare/proguard), you will need to add the following rule to your Proguard configuration file. -This prevent proguard to change data classes used by the SDK and the missing of this rule will result in problems in the serialization/deserialization of the data. -```proguard --keep public class com.absmartly.sdk.json.** { *; } -``` - -## Getting Started - -Please follow the [installation](#installation) instructions before trying the following code: +## Usage -#### Initialization -This example assumes an Api Key, an Application, and an Environment have been created in the A/B Smartly web console. -```java -import com.absmartly.sdk.*; +Comprehensive documentation for this SDK is available at +[docs.absmartly.com](https://docs.absmartly.com/docs/SDK-Documentation/getting-started). +There, you will find detailed guides, examples and API references to help you +get started. -public class Example { - static public void main(String[] args) { +## Contributing - final ClientConfig clientConfig = ClientConfig.create() - .setEndpoint("https://your-company.absmartly.io/v1") - .setAPIKey("YOUR-API-KEY") - .setApplication("website") // created in the ABSmartly web console - .setEnvironment("development"); // created in the ABSmartly web console +We welcome contributions from the community and our customers! If you'd like to contribute, +read the [Contributing Guide](./CONTRIBUTING.md) for more information. - final Client absmartlyClient = Client.create(clientConfig); +## License - final ABSmartlyConfig sdkConfig = ABSmartlyConfig.create() - .setClient(absmartlyClient); - - - final ABSmartly sdk = ABSmartly.create(sdkConfig); - // ... - } -} -``` +This project is licensed under the Apache 2.0 license. See the +[LICENSE](./LICENSE) file for details. -#### Android 6.0 or earlier -When targeting Android 6.0 or earlier, set the default Java Security Provider for SSL to *Conscrypt* by creating the *Client* instance as follows: +## Support -```java -import com.absmartly.sdk.*; -import org.conscrypt.Conscrypt; +If you encounter any issues or have any questions, please reach out to us at +, open an issue on GitHub, or reach out to us on your +company's dedicated ABsmartly Slack Connect channel. - // ... - final ClientConfig clientConfig = ClientConfig.create() - .setEndpoint("https://your-company.absmartly.io/v1") - .setAPIKey("YOUR-API-KEY") - .setApplication("website") // created in the ABSmartly web console - .setEnvironment("development"); // created in the ABSmartly web console - - final DefaultHTTPClientConfig httpClientConfig = DefaultHTTPClientConfig.create() - .setSecurityProvider(Conscrypt.newProvider()); - - final DefaultHTTPClient httpClient = DefaultHTTPClient.create(httpClientConfig); - - final Client absmartlyClient = Client.create(clientConfig, httpClient); - - final ABSmartlyConfig sdkConfig = ABSmartlyConfig.create() - .setClient(absmartlyClient); - - final ABSmartly sdk = ABSmartly.create(sdkConfig); - // ... -``` - -#### Creating a new Context synchronously -```java -// define a new context request - final ContextConfig contextConfig = ContextConfig.create() - .setUnit("session_id", "5ebf06d8cb5d8137290c4abb64155584fbdb64d8"); // a unique id identifying the user - - final Context context = sdk.createContext(contextConfig) - .waitUntilReady(); -``` - -#### Creating a new Context asynchronously -```java -// define a new context request - final ContextConfig contextConfig = ContextConfig.create() - .setUnit("session_id", "5ebf06d8cb5d8137290c4abb64155584fbdb64d8"); // a unique id identifying the user - - final Context context = sdk.createContext(contextConfig) - .waitUntilReadyAsync() - .thenAccept(ctx -> System.out.printf("context ready!")); -``` - -#### Creating a new Context with pre-fetched data -Creating a context involves a round-trip to the A/B Smartly event collector. -We can avoid repeating the round-trip on the client-side by re-using data previously retrieved. - -```java - final ContextConfig contextConfig = ContextConfig.create() - .setUnit("session_id", "5ebf06d8cb5d8137290c4abb64155584fbdb64d8"); // a unique id identifying the user - - final Context context = sdk.createContext(contextConfig) - .waitUntilReady(); - - final ContextConfig anotherContextConfig = ContextConfig.create() - .setUnit("session_id", "5ebf06d8cb5d8137290c4abb64155584fbdb64d8"); // a unique id identifying the other user - - final Context anotherContext = sdk.createContextWith(anotherContextConfig, context.getData()); - assert(anotherContext.isReady()); // no need to wait -``` - -#### Setting extra units for a context -You can add additional units to a context by calling the `setUnit()` or the `setUnits()` method. -This method may be used for example, when a user logs in to your application, and you want to use the new unit type to the context. -Please note that **you cannot override an already set unit type** as that would be a change of identity, and will throw an exception. In this case, you must create a new context instead. -The `setUnit()` and `setUnits()` methods can be called before the context is ready. - -```java -context.setUnit("db_user_id", "1000013"); - -context.setUnits(Map.of( - "db_user_id", "1000013" - )); -``` - -#### Setting context attributes -The `setAttribute()` and `setAttributes()` methods can be called before the context is ready. -```java - context.setAttribute('user_agent', req.getHeader("User-Agent")); - - context.setAttributes(Map.of( - "customer_age", "new_customer" - )); -``` - -#### Selecting a treatment -```java - if (context.getTreament("exp_test_experiment") == 0) { - // user is in control group (variant 0) - } else { - // user is in treatment group - } -``` - -#### Selecting a treatment variable -```java - final Object variable = context.getVariable("my_variable"); -``` - -#### Tracking a goal achievement -Goals are created in the A/B Smartly web console. -```java - context.track("payment", Map.of( - "item_count", 1, - "total_amount", 1999.99 - )); -``` - -#### Publishing pending data -Sometimes it is necessary to ensure all events have been published to the A/B Smartly collector, before proceeding. -You can explicitly call the `publish()` or `publishAsync()` methods. -```java - context.publish(); -``` - -#### Finalizing -The `close()` and `closeAsync()` methods will ensure all events have been published to the A/B Smartly collector, like `publish()`, and will also "seal" the context, throwing an error if any method that could generate an event is called. -```java - context.close(); -``` - -#### Refreshing the context with fresh experiment data -For long-running contexts, the context is usually created once when the application is first started. -However, any experiments being tracked in your production code, but started after the context was created, will not be triggered. -To mitigate this, we can use the `setRefreshInterval()` method on the context config. - -```java - final ContextConfig contextConfig = ContextConfig.create() - .setUnit("session_id", "5ebf06d8cb5d8137290c4abb64155584fbdb64d8") - .setRefreshInterval(TimeUnit.HOURS.toMillis(4)); // every 4 hours -``` - -Alternatively, the `refresh()` method can be called manually. -The `refresh()` method pulls updated experiment data from the A/B Smartly collector and will trigger recently started experiments when `getTreatment()` is called again. -```java - context.refresh(); -``` - -#### Using a custom Event Logger -The A/B Smartly SDK can be instantiated with an event logger used for all contexts. -In addition, an event logger can be specified when creating a particular context, in the `ContextConfig`. -```java - // example implementation - public class CustomEventLogger implements ContextEventLogger { - @Override - public void handleEvent(Context context, ContextEventLogger.EventType event, Object data) { - switch (event) { - case Exposure: - final Exposure exposure = (Exposure)data; - System.out.printf("exposed to experiment %s", exposure.name); - break; - case Goal: - final GoalAchievement goal = (GoalAchievement)data; - System.out.printf("goal tracked: %s", goal.name); - break; - case Error: - System.out.printf("error: %s", data); - break; - case Publish: - case Ready: - case Refresh: - case Close: - break; - } - } - } -``` - -```java - // for all contexts, during sdk initialization - final ABSmartlyConfig sdkConfig = ABSmartlyConfig.create(); - sdkConfig.setContextEventLogger(new CustomEventLogger()); - - // OR, alternatively, during a particular context initialization - final ContextConfig contextConfig = ContextConfig.create(); - contextConfig.setEventLogger(new CustomEventLogger()); -``` - -The data parameter depends on the type of event. -Currently, the SDK logs the following events: - -| event | when | data | -|:---: |------------------------------------------------------------|---| -| `Error` | `Context` receives an error | `Throwable` object | -| `Ready` | `Context` turns ready | `ContextData` used to initialize the context | -| `Refresh` | `Context.refresh()` method succeeds | `ContextData` used to refresh the context | -| `Publish` | `Context.publish()` method succeeds | `PublishEvent` sent to the A/B Smartly event collector | -| `Exposure` | `Context.getTreatment()` method succeeds on first exposure | `Exposure` enqueued for publishing | -| `Goal` | `Context.track()` method succeeds | `GoalAchievement` enqueued for publishing | -| `Close` | `Context.close()` method succeeds the first time | `null` | - - -#### Peek at treatment variants -Although generally not recommended, it is sometimes necessary to peek at a treatment or variable without triggering an exposure. -The A/B Smartly SDK provides a `peekTreatment()` method for that. - -```java - if (context.peekTreatment("exp_test_experiment") == 0) { - // user is in control group (variant 0) - } else { - // user is in treatment group - } -``` - -##### Peeking at variables -```java - final Object variable = context.peekVariable("my_variable"); -``` - -#### Overriding treatment variants -During development, for example, it is useful to force a treatment for an experiment. This can be achieved with the `override()` and/or `overrides()` methods. -The `setOverride()` and `setOverrides()` methods can be called before the context is ready. -```java - context.setOverride("exp_test_experiment", 1); // force variant 1 of treatment - context.setOverrides(Map.of( - "exp_test_experiment", 1, - "exp_another_experiment", 0 - )); -``` +--- -## About A/B Smartly -**A/B Smartly** is the leading provider of state-of-the-art, on-premises, full-stack experimentation platforms for engineering and product teams that want to confidently deploy features as fast as they can develop them. -A/B Smartly's real-time analytics helps engineering and product teams ensure that new features will improve the customer experience without breaking or degrading performance and/or business metrics. +Happy Experimenting! -### Have a look at our growing list of clients and SDKs: -- [Java SDK](https://www.github.com/absmartly/java-sdk) -- [JavaScript SDK](https://www.github.com/absmartly/javascript-sdk) -- [PHP SDK](https://www.github.com/absmartly/php-sdk) -- [Swift SDK](https://www.github.com/absmartly/swift-sdk) -- [Vue2 SDK](https://www.github.com/absmartly/vue2-sdk) +The ABsmartly Team