diff --git a/docs/sdks/client-sdks/index.md b/docs/sdks/client-sdks/index.md index c63920ca..458f193c 100644 --- a/docs/sdks/client-sdks/index.md +++ b/docs/sdks/client-sdks/index.md @@ -19,6 +19,6 @@ The SDK uses a background process to fetch and store the experiment data. The p7 ### Language-specific Documentation - [JavaScript](/sdks/client-sdks/javascript/intro) -- [React Native](/sdks/client-sdks/react-native) +- [React Native](/sdks/client-sdks/react-native/intro) - [Android](/sdks/client-sdks/android) - [iOS](/sdks/client-sdks/ios) \ No newline at end of file diff --git a/docs/sdks/client-sdks/react-native/_category_.json b/docs/sdks/client-sdks/react-native/_category_.json new file mode 100644 index 00000000..687c419b --- /dev/null +++ b/docs/sdks/client-sdks/react-native/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "React Native", + "position": 5 + } + \ No newline at end of file diff --git a/docs/sdks/client-sdks/react-native/assignment.mdx b/docs/sdks/client-sdks/react-native/assignment.mdx new file mode 100644 index 00000000..32f8e464 --- /dev/null +++ b/docs/sdks/client-sdks/react-native/assignment.mdx @@ -0,0 +1,434 @@ +--- +title: Assignments +sidebar_position: 4 +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import ApiOptionRef from '@site/src/components/ApiOptionRef'; + + +Assignments are the mechanism through which a given [Subject](/sdks/sdk-features/subjects) is assigned to a variation for a feature flag, experiment, or bandit. + +Currently, the Eppo SDK supports the following assignment types: + +- String +- Boolean +- JSON +- Numeric +- Integer + +Depending on the values you pass to the `getAssignment()` function, the SDK will return different results based on whether the subject details match the assignment rules you set in the Eppo UI. + +This section will cover the different types of assignments that you can make with the Eppo SDK. + +## String Assignments + +String assignment return a string value that is set as the variation for the experiment. String flags are the most common type of flags. They are useful for both A/B/n tests and advanced targeting use cases. + +```js +import * as EppoSdk from "@eppo/react-native-sdk"; + +const client = EppoSdk.getInstance(); + +const flagKey = "flag-key-123"; +const subjectKey = getUserId() || "user-123"; +const defaultAssignment = "version-a"; +const subjectAttributes = { + "country": "US", + "age": 30, + "isReturningUser": true +}; + +const variant = client.getStringAssignment( + flagKey, + subjectKey, + subjectAttributes, + defaultAssignment + +); + +// Use the variant value to determine which component to render +if (variant === "version-a") { + return +} else if (variant === "version-b") { + return +} + +``` + +## Boolean Assignments + +Boolean flags support simple on/off toggles. They're useful for simple, binary feature switches like blue/green deployments or enabling/disabling a new feature. + +```js +import * as EppoSdk from "@eppo/react-native-sdk"; + +const client = EppoSdk.getInstance(); + +const flagKey = "flag-key-123"; +const subjectKey = getUserId() || "user-123"; +const defaultAssignment = false; +const subjectAttributes = { + "country": "US", + "age": 30, + "isReturningUser": true +}; + +const variant = client.getBooleanAssignment( + flagKey, + subjectKey, + subjectAttributes, + defaultAssignment +); + +// Use the variant value to determine which component to render +if (variant) { + return +} else { + return +} +``` + +## JSON Assignments + +JSON flags work best for advanced configuration use cases. The JSON flag can include structured information such as + +- the text of a marketing copy for a promotional campaign +- the address of a different hero image. + +Using this pattern, a team can make minor changes to the copy and design of a website without having to go through an entire code release process. + +For example, the following code shows how to use a JSON flag to configure a campaign. + +```js +import * as EppoSdk from "@eppo/react-native-sdk"; + +const client = EppoSdk.getInstance(); + +const flagKey = "flag-key-123"; +const subjectKey = getUserId() || "user-123"; +const defaultAssignment = { + hero: false, + heroImage: "placeholder.png", + heroTitle: "Placeholder Hero Title", + heroDescription: "Placeholder Hero Description" +}; +const subjectAttributes = { + "country": "US", + "age": 30, + "isReturningUser": true +}; + +const campaignJson = client.getJSONAssignment( + flagKey, + subjectKey, + subjectAttributes, + defaultAssignment +); + +if (campaignJson !== null) { + campaign.hero = true; + campaign.heroImage = campaignJson.heroImage; + campaign.heroTitle = campaignJson.heroTitle || ""; + campaign.heroDescription = campaignJson.heroDescription || ""; +} + +// JSX component sets itself based on the campaignJson + +``` + +## Integer and Numeric Assignments + +Integer and numeric assignments work the same way but return either an integer or a floating point number. These assignments are useful where you want to use a numeric value to drive business logic such as pricing on an item or a number of items to display in a list. + +```js +import * as EppoSdk from "@eppo/react-native-sdk"; + +const client = EppoSdk.getInstance(); +const flagKey = "flag-key-123"; +const subjectKey = getUserId() || "user-123"; +const defaultAssignment = 0; +const subjectAttributes = { + "country": "US", + "age": 30, + "isReturningUser": true +}; +// example of getting an integer assignment +const numberOfItems = client.getIntegerAssignment( + flagKey, + subjectKey, + subjectAttributes, + defaultAssignment +); + +// example of getting a numeric assignment +const price = client.getNumericAssignment( + flagKey, + subjectKey, + subjectAttributes, + defaultAssignment +); + +// use the assignment to drive business logic + + +// use the assignment to drive business logic + + +``` + +## Assignment Logger Schema + +The SDK will invoke the `logAssignment` function with an `assignment` object that contains the following fields: + + + +The time when the subject was assigned to the variation. Example: `"2021-06-22T17:35:12.000Z"` + + + + +An Eppo feature flag key. Example: `"recommendation-algo"` + + + + +An Eppo allocation key. Example: `"allocation-17"` + + + + +An Eppo experiment key. Example: `"recommendation-algo-allocation-17"` + + + + +An identifier of the subject or user assigned to the experiment variation. Example: UUID + + + + +A free-form map of metadata about the subject. These attributes are only logged if passed to the SDK assignment function. Example: `{ "country": "US" }` + + + + +The experiment variation the subject was assigned to. Example: `"control"` + + + + +Metadata around the assignment, such as the version of the SDK. Example: `{ "obfuscated: "true", "sdkLanguage": "javascript", "sdkLibVersion": "3.2.1" }` + + +### Logging data to your data warehouse + +Eppo's unique architecture make it so Eppo never has access to your data. This means that you can use the assignment logging functions to send data to any data warehouse or logging system you want. + +All you need to do is adjust the way you define the `logAssignment()` function within the `IAssignmentLogger` interface at initialization. + + + + +```javascript +// Import Eppo's assignment logger interface and client initializer +import { IAssignmentLogger, init } from "@eppo/node-server-sdk"; + +// Define logAssignment so that it logs events +const assignmentLogger: IAssignmentLogger = { + logAssignment(assignment) { + console.log(assignment); + } +}; +``` +:::note +This example writes to your local machine and is useful for development in your local environment. In production, these logs will need to get written to a table in your data warehouse. +::: + + + + + +```javascript +// Import Eppo's assignment logger interface and client initializer +import { IAssignmentLogger, init } from "@eppo/node-server-sdk"; + +// Connect to Segment +const { Analytics } = require('@segment/analytics-node'); +const analytics = new Analytics({ writeKey: ''}); + +// Define logAssignment so that it logs events to Segment +const assignmentLogger: IAssignmentLogger = { + logAssignment(assignment) { + analytics.track({ + userId: assignment.subject, + event: "Eppo Randomization Event", + properties: assignment, + }); + } +}; +``` + + + + +```javascript +// Import Eppo's assignment logger interface and client initializer +import { IAssignmentLogger, init } from "@eppo/node-server-sdk"; + +// Connect to Rudderstack +const Analytics = require("@rudderstack/rudder-sdk-node"); +const analytics = new Analytics("", { + dataPlaneUrl: DATA_PLANE_URL +}); + +// Define logAssignment so that it logs events to Rudderstack +const assignmentLogger: IAssignmentLogger = { + logAssignment(assignment) { + analytics.track({ + userId: assignment.subject, + event: "Eppo Randomization Event", + properties: assignment + }); + }, +}; +``` + + + + +```javascript +// Import Eppo's assignment logger interface and client initializer +import { IAssignmentLogger, init } from "@eppo/node-server-sdk"; + +// Initialize mParticle +const mParticle = require("mparticle"); +const api = new mParticle.EventsApi( + new mParticle.Configuration("", "") +); + +// Define logAssignment so that it logs events to mParticle +const assignmentLogger: IAssignmentLogger = { + logAssignment(assignment) { + const batch = new mParticle.Batch(mParticle.Batch.Environment.development); + batch.user_identities = new mParticle.UserIdentities(); + batch.user_identities.customerid = assignment.subject; + const event = new mParticle.AppEvent( + mParticle.AppEvent.CustomEventType.navigation, + "Eppo Randomization Event" + ); + event.custom_attributes = assignment; + batch.addEvent(event); + api.uploadEvents([batch]); + }, +}; +``` + + + + +This example shows the setup for Snowplow's Node.js Tracker v3 SDK. + +```javascript +// Import Eppo's assignment logger interface and client initializer +import { IAssignmentLogger, init } from "@eppo/node-server-sdk"; + +// Initialize Snowplow +import { + tracker, + gotEmitter, + buildSelfDescribingEvent, +} from "@snowplow/node-tracker"; +const emit = gotEmitter( + "collector.mydomain.net", // Collector endpoint + snowplow.HttpProtocol.HTTPS, + 8080, + snowplow.HttpMethod.POST, + 1 +); +const track = tracker( + [emit], + "Eppo Randomization Events", + "", + false +); + +// Define logAssignment so that it logs events to Snowplow +const assignmentLogger: IAssignmentLogger = { + logAssignment(assignment) { + track.track( + buildSelfDescribingEvent({ + event: { + schema: "iglu:com.example_company/eppo-event/jsonschema/1-0-2", + data: { + userId: assignment.subject, + properties: assignment + } + } + }) + ); + } +}; + +``` + + + + + +```javascript +// Import Eppo's assignment logger interface and client initializer +import { IAssignmentLogger, init } from "@eppo/node-server-sdk"; + +// Initialize Amplitude +import { track } from '@amplitude/analytics-node'; + + +// Define logAssignment so that it logs events to Amplitude +const assignmentLogger: IAssignmentLogger = { + logAssignment(assignment) { + track('Experiment Viewed', assignment, { + user_id: assignment.subject + }); + }, +}; + +``` + + + + +More details about logging and examples (with Segment, Rudderstack, mParticle, and Snowplow) can be found in the [event logging](/sdks/event-logging/) page. \ No newline at end of file diff --git a/docs/sdks/client-sdks/react-native/initialization.mdx b/docs/sdks/client-sdks/react-native/initialization.mdx new file mode 100644 index 00000000..3bf2b89d --- /dev/null +++ b/docs/sdks/client-sdks/react-native/initialization.mdx @@ -0,0 +1,113 @@ +--- +title: Initialization +sidebar_position: 3 +--- + +import ApiOptionRef from '@site/src/components/ApiOptionRef'; + +The Eppo JavaScript SDK is easy to initialize while offering robust customization options, making it adaptable to various use cases such as offline mode, custom caching requirements, and ultra-low-latency initialization. + +## Initialize the SDK + +To complete basic initialization, you only need to provide an SDK key. [Create an SDK key](/sdks/sdk-keys) if you don't already have one. + +```javascript +import { init } from "@eppo/react-native-sdk"; + +await init({ + apiKey: 'tx6eTRdJh2MD0KV87FB2aQ1uNMhGpzJmucQRifMtvGE', + assignmentLogger: { logAssignment: (assignment: IAssignmentEvent)=> console.log('TODO: send to warehouse', assignment) } +}) +``` + +## Use the SDK instance +After initialization, you can use the SDK instance by importing the SDK at the scope you will use it in and calling `getInstance()`. You can then use the SDK instance to assign a variation to a subject using the `get*Assignment` functions. + +```javascript +import * as EppoSdk from "@eppo/react-native-sdk"; + +const eppoClient = EppoSdk.getInstance(); +``` + + +## Advanced Configuration +Basic initialization is great for most use cases, but the SDK provides options that you can use during initialization to customize the behavior of the SDK. + +### Initialization Options + +How the SDK fetches, serves, and caches experiment configurations is configurable via additional optional initialization options: + + + +**Timeout** in milliseconds for HTTPS requests for the experiment configurations. + + + + +Number of *additional* times the initial configurations request will be attempted if it fails. +This is the request typically synchronously waited (via `await`) for completion. +A small wait will be done between requests. + + + + +The base URL for for the API. Can be overriden to proxy configuration requests. + + + + + +Poll for new configurations even if the initial configurations request failed. + + + + +Throw an error (reject the promise) if unable to fetch initial configurations during initialization. + + + + +If polling for updated configurations after initialization, the number of additional times a request will be attempted before giving up. Subsequent attempts are done using an exponential backoff. + + + + +An asynchronous, persistent storage for caching fetched flag configurations for use in subsequent sessions + + + + +Maximum age, in seconds, that a previously cached configuration is considered valid and the wait-time before fetching a fresh configuration + \ No newline at end of file diff --git a/docs/sdks/client-sdks/react-native/intro.mdx b/docs/sdks/client-sdks/react-native/intro.mdx new file mode 100644 index 00000000..ef5aa585 --- /dev/null +++ b/docs/sdks/client-sdks/react-native/intro.mdx @@ -0,0 +1,33 @@ +--- +title: SDK Guide +sidebar_position: 1 +--- +import FeatureCard from '/src/components/FeatureCard'; + +The Eppo React Native SDK lets you manage feature flags and experiments in your JavaScript based mobile applications. + +
+ + + + +
+ +Looking for a different JavaScript SDK? Check out our other options: +- For Node.js applications, use the [Node.js SDK](/sdks/server-sdks/node) +- For browser JavaScript and frameworks like React, use the [JavaScript SDK](/sdks/client-sdks/javascript/intro) \ No newline at end of file diff --git a/docs/sdks/client-sdks/react-native/quickstart.mdx b/docs/sdks/client-sdks/react-native/quickstart.mdx new file mode 100644 index 00000000..27a3ebf6 --- /dev/null +++ b/docs/sdks/client-sdks/react-native/quickstart.mdx @@ -0,0 +1,236 @@ +--- +title: Quickstart +sidebar_position: 2 +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +The Eppo React Native SDK lets you manage feature flags and experiments in your react native applications + +The SDK handles all the complexity of feature flag evaluation and experiment assignment locally in your application, with no network calls required after initial setup. This guide will walk you through installing the SDK and implementing your first feature flag, experiment, and contextual bandit. + +## Installation + +First, install the SDK into your current project. + +The Eppo React Native SDK is available via a number of package managers or via a CDN. Use whichever one you prefer. + + + + +```bash +yarn add @eppo/react-native-sdk +``` + + + + + +```bash +npm install @eppo/react-native-sdk +``` + + + + + +Depending on whether you are running a feature flag, an experiment, or a bandit, the initialization code will be slightly different. + +## Feature Flags +Feature flags are a way to toggle features on and off without needing to deploy code. + +### Initialize the SDK + +[Create an SDK key](/sdks/sdk-keys) if you don't already have one. + +First, initialize the Eppo SDK at the app root using your SDK key. The SDK key is different from the project API key. + +```js +import {IAssignmentEvent, init} from "@eppo/react-native-sdk"; + +await init({ + apiKey: "", + assignmentLogger: { logAssignment: (assignment: IAssignmentEvent)=> console.log("TODO: send to warehouse", assignment) } +}); +``` + +:::note +The SDK key is different from the project API key. You can find your SDK key in the [SDK Keys section of the Eppo interface](https://eppo.cloud/configuration/environments/keys). +::: + +### Assign a variant + +Once initialized, the SDK uses a singleton pattern, making it accessible from anywhere in your application. To make assignments, import the SDK and use `getInstance()` to access the SDK instance. + +### How assignments work + +The SDK periodically retrieves configuration rules from the Eppo server that define how subjects should be allocated to variants. When you call an assignment function, the SDK evaluates these rules locally without making additional network requests. + +Each assignment requires: + +- **Flag Key**: Identifies which set of configuration rules to use +- **Subject Key**: A unique identifier for the subject (usually a user ID) +- **Subject Attributes**: Optional key-value pairs containing additional information used for rule evaluation +- **Default Value**: Fallback value if assignment fails, rules don't match, an error was thrown during initialization, or the feature is not enabled + + +```js +import * as EppoSdk from "@eppo/react-native-sdk"; + +// Get SDK instance +const eppoClient = EppoSdk.getInstance(); + +// Define assignment parameters +const featureKey = "my-neat-feature"; +const subjectKey = user?.id || "anonymous"; + +const subjectAttributes = { + "country": user?.country, + "device": user?.device, +}; +const defaultValue = "default-value"; + +// Make an assignment +const variation = eppoClient.getStringAssignment( + featureKey, + subjectKey, + subjectAttributes, + defaultValue +); +``` + +### Assignment Types + +The SDK provides different assignment functions based on the type of value you need: + +| Function | Return Type | +|----------|-------------| +| `getStringAssignment()` | String | +| `getBooleanAssignment()` | Boolean | +| `getJSONAssignment()` | JSON object | +| `getIntegerAssignment()` | Integer | +| `getNumericAssignment()` | Float | + +:::note +See more details about assignment functions in the [Assignments](/sdks/client-sdks/javascript/assignments) page. +::: + +### Using Assignments + +After receiving an assignment, your application should implement logic to modify the user experience accordingly: + +```javascript +const LandingPageA = () => { + return
Landing Page A
+} + +const LandingPageB = () => { + return
Landing Page B
+} + +const ControlLandingPage = () => { + return
Landing Page C
+} + +// Example using a boolean assignment +const variation = eppoClient.getStringAssignment( + featureKey, + subjectKey, + subjectAttributes, + defaultValue +); + +// Render different components based on assignment +if (variation === "landing-page-a") { + return +} else if (variation === "landing-page-b") { + return +} else { + return +} +``` + +That's all the code you need to make a basic assignment. After that, you can do the rest of the work running the experiment from the Eppo UI. This is awesome both because it allows you to iterate quickly and because it gives you the ability to change the experiment configuration without redeploying your code. + +You can see the full steps for setting up a feature flag including UI steps in the [feature flag quickstart](/feature-flag-quickstart). + + +## Experiments + +While feature flags are useful, they do not send you any information about how your users are interacting with the feature. Experiments provide a way to collect data about these interactions using whichever logging and data warehousing system you prefer. + +To log events through the SDK, you need to define a `logAssignment()` function using the `IAssignmentLogger` interface to pass to the `init()` function. + +For simplicity, we'll create a `logAssignment()` function that logs the assignment data to the console. + +```javascript +// Import Eppo's assignment logger interface and client initializer +import { IAssignmentLogger, init } from "@eppo/react-native-sdk"; + +// Define logAssignment so that it logs events +const assignmentLogger: IAssignmentLogger = { + logAssignment(assignment) { + console.log(assignment); + } +}; + +// Initialize the SDK +await init({ + apiKey: "", + assignmentLogger, +}); + +``` + + +:::note +In a production application, you would want to replace the `console.log()` statement with an actual logging system. We have documentation on how to set up logging with multiple popular data warehouses and logging systems in the [Assignment page](/sdks/client-sdks/javascript/assignments/#logging-data-to-your-data-warehouse). +::: + +### Assign a variant + +After initializing the SDK with the `logAssignment()` function, you can make assignments in the same way as for feature flags. + +```js +import * as EppoSdk from "@eppo/react-native-sdk"; + +// Get SDK instance +const eppoClient = EppoSdk.getInstance(); + +// Define assignment parameters +const featureKey = "my-neat-feature"; +const subjectKey = getCurrentUser() || "anonymous"; +const subjectAttributes = { + "country": user.country, + "device": user.device, +}; +const defaultValue = "default-value"; + +// Make an assignment +const variation = eppoClient.getStringAssignment( + featureKey, + subjectKey, + subjectAttributes, + defaultValue +); + +if (variation === "landing-page-a") { + return +} else if (variation === "landing-page-b") { + return +} else { + return +} +``` + +You can see the full steps for running an experiment including UI steps in the [experiment quick start](/experiment-allocation-quickstart/). + + +## Next Steps + +Now that you've seen how to make assignments with the Eppo JavaScript SDK, we strongly recommend familiarizing yourself with the following topics: + +- [High Level concepts for the client API](/sdks/client-sdks) +- [Initialization Configuration](/sdks/client-sdks/javascript/Initialization) +- [Assignment details](/sdks/client-sdks/javascript/assignments) \ No newline at end of file diff --git a/docs/sdks/index.md b/docs/sdks/index.md index 437633c1..5e1912a7 100644 --- a/docs/sdks/index.md +++ b/docs/sdks/index.md @@ -122,7 +122,7 @@ The read more about our specific SDKs, check out the SDK-specific docs below: ### Client SDKs - [JavaScript](client-sdks/javascript/intro) -- [React Native](client-sdks/react-native) +- [React Native](client-sdks/react-native/intro) - [Android](client-sdks/android) - [iOS](client-sdks/ios) diff --git a/docs/sdks/server-sdks/ruby.mdx b/docs/sdks/server-sdks/ruby.mdx new file mode 100644 index 00000000..3ee3e4fb --- /dev/null +++ b/docs/sdks/server-sdks/ruby.mdx @@ -0,0 +1,286 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Ruby + +Eppo's Ruby SDK can be used for feature flagging, randomized experiment assignment, and contextual multi armed bandits: + +- [GitHub repository](https://github.com/Eppo-exp/eppo-multiplatform) +- [RubyGems gem](https://rubygems.org/gems/eppo-server-sdk/) + +## Getting Started + +### Installation + +Install the SDK with gem: + +```bash +gem install eppo-server-sdk +``` + +or add to you `Gemfile`: + +``` +gem 'eppo-server-sdk', '~> 3.2.7' +``` + +### Usage + +Begin by initializing a singleton instance of Eppo's client with an SDK key from the [Eppo interface](https://eppo.cloud/feature-flags/keys). Once initialized, the client can be used to make assignments anywhere in your app. Initialization should happen when your application starts up to generate a singleton client instance, once per application lifecycle: + +#### Initialize once + +```ruby +require 'eppo_client' + +config = EppoClient::Config.new('') +EppoClient::init(config) +``` + +#### Assign anywhere + +```ruby +require 'eppo_client' + +client = EppoClient::Client.instance +variation = client.get_string_assignment( + '', + '', + { + # Mapping of any subject metadata for targeting. + }, + '' +) +``` + +Flags and other features become available to the SDK after initialization. + +After initialization, the SDK begins polling Eppo’s CDN every 30 seconds to retrieve the most recent experiment configurations (variation values, traffic allocation, etc.). Note that polling happens independently of assignment calls and is non blocking. + +The SDK stores these configurations in memory so that assignments thereafter are effectively instant. For more information, see the [architecture overview](/sdks/architecture/overview) page. + +:::info +By default, the Eppo client initialization is asynchronous to ensure no critical code paths are blocked. For more information on handling non-blocking initialization, see our [documentation here](/sdks/best-practices/non-blocking-initialization). +::: + +### Connecting an event logger + +Eppo is architected so that raw user data never leaves your system. As part of that, instead of pushing subject-level exposure events to Eppo's servers, Eppo's SDKs integrate with your existing logging system. This is done with a logging callback function defined at SDK initialization. + +```ruby +config = EppoClient::Config.new( + '', + assignment_logger: CustomAssignmentLogger.new +) +EppoClient::init(config) +``` + +This logger takes an analytic event created by Eppo, `assignment`, and writes in to a table in the data warehouse (Snowflake, Databricks, BigQuery, or Redshift). You can read more on the [Event Logging](/sdks/event-logging) page. + +The code below illustrates an example implementation of logging with Segment, but you could also use other event-tracking systems. The only requirement is that the SDK can call a `log_assignment` method. Here we override Eppo's `AssignmentLogger` class with a function named `log_assignment`, then instantiate a config using an instance of the custom logger class, and finally instantiate the client: + +```ruby +require 'segment/analytics' + +# Connect to Segment (or your own event-tracking system) +Analytics = Segment::Analytics.new({ write_key: 'SEGMENT_WRITE_KEY' }) + +class CustomAssignmentLogger < EppoClient::AssignmentLogger + def log_assignment(assignment) + Analytics.track(assignment["subject"], "Eppo Assignment", assignment) + end +end + +config = EppoClient::Config.new( + '', + assignment_logger: CustomAssignmentLogger.new +) +EppoClient::init(config) +``` + +See [below](#assignment-event-schema) for details on the schema of the `assignment` analytic event. + +### Getting variations + +Now that the SDK is initialized and connected to your event logger, you can check what variant a specific subject (typically user) should see by calling the `get__Assignment` functions. Each time this function is called, the SDK will invoke the provided logging function to record the assignment. + +For example, for a string-valued flag, use `get_string_assignment`: + +```ruby +require 'eppo_client' + +client = EppoClient::Client.instance +variation = client.get_string_assignment( + '', + '', + { + # Mapping of any subject metadata for targeting. + }, + '' +) +``` + +Note that Eppo uses a unified API for feature gates, experiments, and mutually exclusive layers. This makes it easy to turn a flag into an experiment or vice versa without having to do a code release. + +The `get_string_assignment` function takes four inputs to assign a variation: + +- `flag_key` - The key for the flag you are evaluating. This key is available on the feature flag detail page (see below). +- `subject_key` - A unique identifier for the subject being experimented on (e.g., user), typically represented by a UUID. This key is used to deterministically assign subjects to variants. +- `subject_attributes` - A map of metadata about the subject used for [targeting](/feature-flagging/concepts/targeting/). If targeting is not needed, pass in an empty object. +- `default_value` - The value that will be returned if no allocation matches the subject, if the flag is not enabled, if `get_string_assignment` is invoked before the SDK has finished initializing, or if the SDK was not able to retrieve the flag configuration. Its type must match the `get__assignment` call. + +![Example flag key](/img/feature-flagging/flag-key.png) + +### Typed assignments + +Every Eppo flag has a return type that is set on creation in the dashboard. Once a flag is created, assignments in code should be made using the corresponding typed function: + +```ruby +get_boolean_assignment(...) +get_numeric_assignment(...) +get_integer_assignment(...) +get_string_assignment(...) +get_json_assignment(...) +``` + +Each function has the same signature, but returns the type in the function name. The only exception is `default_value`, which should be the same type as the flag. To read more about when to use which flag type, see the [flag types](/sdks/sdk-features/flag-types) page. + +## Contextual Bandits + +To leverage Eppo's contextual bandits using the Ruby SDK, there are two additional steps over regular feature flags: +1. Add a bandit action logger to the assignment logger +2. Querying the bandit for an action + +### Logging bandit actions + +In order for the bandit to learn an optimized policy, we need to capture and log the bandit actions. +This requires adding a bandit action logging callback to the AssignmentLogger class +```ruby +class MyLogger < EppoClient::AssignmentLogger + def log_assignment(assignment): + ... + + def log_bandit_action(bandit_action): + # implement me +``` + +We automatically log the following data: + +| Field | Description | Example | +|------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|-------------------------------------| +| `timestamp` (Date) | The time when the action is taken in UTC | 2024-03-22T14:26:55.000Z | +| `flagKey` (String) | The key of the feature flag corresponding to the bandit | "bandit-test-allocation-4" | +| `banditKey` (String) | The key (unique identifier) of the bandit | "ad-bandit-1" | +| `subject` (String) | An identifier of the subject or user assigned to the experiment variation | "ed6f85019080" | +| `action` (String) | The action assigned by the bandit | "promo-20%-off" | +| `subjectNumericAttributes` (Hash\{String => Float\}) | Metadata about numeric attributes of the subject. Hash of the name of attributes their numeric values | `{"age": 30}` | +| `subjectCategoricalAttributes` (Hash\{String => String\}) | Metadata about non-numeric attributes of the subject. Hash of the name of attributes their string values | `{"loyalty_tier": "gold"}` | +| `actionNumericAttributes` (Hash\{String => Float\}) | Metadata about numeric attributes of the assigned action. Hash of the name of attributes their numeric values | `{"discount": 0.1}` | +| `actionCategoricalAttributes` (Hash\{String => String\}) | Metadata about non-numeric attributes of the assigned action. Hash of the name of attributes their string values | `{"promoTextColor": "white"}` | +| `actionProbability` (Float) | The weight between 0 and 1 the bandit valued the assigned action | 0.25 | +| `modelVersion` (String) | Unique identifier for the version (iteration) of the bandit parameters used to determine the action probability | "v123" | + +### Querying the bandit for an action + +To query the bandit for an action, you can use the `get_bandit_action` function. This function takes the following parameters: +- `flag_key` (String): The key of the feature flag corresponding to the bandit +- `subject_key` (String): The key of the subject or user assigned to the experiment variation +- `subject_attributes` (Attributes): The context of the subject +- `actions` (Hash\{String => Attributes\}): A hash that maps available actions to their attributes +- `default` (String): The default *variation* to return if the bandit cannot be queried + +The following code queries the bandit for an action: + +```ruby +require 'eppo_client' + +client = EppoClient::Client.instance +bandit_result = client.get_bandit_action( + "shoe-bandit", + name, + EppoClient::Attributes.new( + numeric_attributes: { "age" => age }, categorical_attributes: { "country" => country } + ), + { + "nike" => EppoClient::Attributes.new( + numeric_attributes: { "brand_affinity" => 2.3 }, + categorical_attributes: { "image_aspect_ratio" => "16:9" } + ), + "adidas" => EppoClient::Attributes.new( + numeric_attributes: { "brand_affinity" => 0.2 }, + categorical_attributes: { "image_aspect_ratio" => "16:9" } + ) + }, + "control" +) +``` + +#### Subject Context + +The subject context contains contextual information about the subject that is independent of bandit actions. +For example, the subject's age or country. + +The subject context has type `Attributes` which has two fields: + +- `numeric_attributes` (Hash\{String => Float\}): A hash of numeric attributes (such as "age") +- `categorical_attributes` (Hash\{String => String\}): A hash of categorical attributes (such as "country") + +:::note +The `categerical_attributes` are also used for targeting rules for the feature flag similar to how `subject_attributes` are used for that with regular feature flags. +::: + +#### Action Contexts + +Next, supply a hash with actions and their attributes: `actions: Hash{String => Attributes}`. +If the user is assigned to the bandit, the bandit selects one of the actions supplied here, +and all actions supplied are considered to be valid; if an action should not be shown to a user, do not include it in this hash. + +The action attributes are similar to the `subject_attributes` but hold action-specific information. +Note that we can use `Attributes.empty` to create an empty attribute context. + +Note that action contexts can contain two kinds of information: +- Action-specific context: e.g., the image aspect ratio of the image corresponding to this action +- User-action interaction context: e.g., there could be a "brand-affinity" model that computes brand affinities of users to brands, and scores of this model can be added to the action context to provide additional context for the bandit. + +#### Result + +The `bandit_result` is an instance of `BanditResult`, which has two fields: + +- `variation` (String): The variation that was assigned to the subject +- `action` (Optional[String]): The action that was assigned to the subject + +The variation returns the feature flag variation; this can be the bandit itself, or the "status quo" variation if the user is not assigned to the bandit. +If we are unable to generate a variation, for example when the flag is turned off, then the `default` variation is returned. +In both of those cases, the `action` is `nil`, and you should use the status-quo algorithm to select an action. + +When `action` is not `nil`, the bandit has selected that action to be shown to the user. + +#### Status quo algorithm + +In order to accurately measure the performance of the bandit, we need to compare it to the status quo algorithm using an experiment. +This status quo algorithm could be a complicated algorithm that selects an action according to a different model, or a simple baseline such as selecting a fixed or random action. +When you create an analysis allocation for the bandit and the `action` in `BanditResult` is `nil`, implement the desired status quo algorithm based on the `variation` value. + +## Appendix + +### Debugging + +You may encounter a situation where a flag assignment produces a value that you did not expect. There are functions [detailed here](/sdks/sdk-features/debugging-flag-assignment/) to help you understand how flags are assigned, which will allow you to take corrective action on potential configuration issues. + +### Assignment event schema + +The SDK will invoke the `log_assignment` function with an `assignment` object that contains the following fields: + +| Field | Description | Example | +| ------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------------- | +| `experiment` (string) | An Eppo experiment key | "recommendation-algo-allocation-17" | +| `subject` (string) | An identifier of the subject or user assigned to the experiment variation | UUID | +| `variation` (string) | The experiment variation the subject was assigned to | "control" | +| `timestamp` (string) | The time when the subject was assigned to the variation | 2021-06-22T17:35:12.000Z | +| `subjectAttributes` (map) | A Hash of metadata about the subject. These attributes are only logged if passed to the SDK assignment function | `{ "country": "US" }` | +| `featureFlag` (string) | An Eppo feature flag key | "recommendation-algo" | +| `allocation` (string) | An Eppo allocation key | "allocation-17" | + +:::note +More details about logging and examples (with Segment, Rudderstack, mParticle, and Snowplow) can be found in the [event logging](/sdks/event-logging/) page. +::: diff --git a/static/_redirects b/static/_redirects index bf7395cf..1e851c11 100644 --- a/static/_redirects +++ b/static/_redirects @@ -136,11 +136,23 @@ http://adoring-yonath-6ecb9d.netlify.app/* http://docs.geteppo.com/:splat 301! /sdks/client-sdks/javascript/ /sdks/client-sdks/javascript/intro 301! /sdks/client-sdks/javascript/#offline-initialization /sdks/client-sdks/javascript/initialization#offline-initialization 301! /sdks/client-sdks/javascript/#usage-in-react /sdks/client-sdks/javascript/react 301! - /guides/engineering/nextjs-setup/ /sdks/client-sdks/javascript/nextjs-setup /sdks/server-sdks/ruby/ /sdks/server-sdks/ruby/intro +/sdks/client-sdks/react-native /sdks/client-sdks/react-native/intro +/sdks/client-sdks/react-native#getting-started /sdks/client-sdks/react-native/quickstart +/sdks/client-sdks/react-native#installation /sdks/client-sdks/react-native/quickstart#installation +/sdks/client-sdks/react-native#usage /sdks/client-sdks/react-native/assignment +/sdks/client-sdks/react-native#connecting-an-event-logger /sdks/client-sdks/react-native/assignment#logging-data-to-your-data-warehouse +/sdks/client-sdks/react-native#getting-variations /sdks/client-sdks/react-native/assignment#assignment-types +/sdks/client-sdks/react-native#advanced-options /sdks/client-sdks/react-native/initialization#advanced-configuration +/sdks/client-sdks/react-native#initialization-options /sdks/client-sdks/react-native/initialization#initialization-options +/sdks/client-sdks/react-native#usage-in-react /sdks/client-sdks/react-native/assignment#using-assignments +/sdks/client-sdks/react-native#local-storage /sdks/client-sdks/react-native/assignment#local-storage +/sdks/client-sdks/react-native#debugging /sdks/client-sdks/react-native/quickstart#next-steps +/sdks/client-sdks/react-native#assignment-logger-schema /sdks/client-sdks/react-native/assignment#assignment-logger-schema + /sdks/server-sdks/rust /sdks/server-sdks/rust/intro 301! /sdks/server-sdks/rust#getting-started /sdks/server-sdks/rust/quickstart /sdks/server-sdks/rust#install-the-sdk /sdks/server-sdks/rust/quickstart#installation