Basic Usage
After the init call, all the variants for all running experiments will be cached in memory. The init call should take a single-digit number of milliseconds on the server side. On the client side, it may take a little longer (it’s another roundtrip to the server) unless you pass the data directly in the HTML which makes it ready immediately. Any experiment that is evaluated and is not running will return Variant 0 (the control group).
Selecting a Treatment
- Javascript
- Python3
- React
- Swift
- Vue2
- Vue3
- Java
- Go
- .NET
- PHP
- Ruby
- Flutter/Dart
To make sure the SDK is properly loaded before asking it for a treatment, block your code until the SDK is ready as shown below.
The ready()
method returns a promise. You can also just check if it is
currently ready with the isReady()
method.
After the SDK is loaded you can use the treatment()
method to return the proper
treatment based on the experiment_name
and the units data that was passed when
the SDK was instantiated.
Then use an if-else-if-else
block as shown below and insert the code for the
different treatments that you plan to create.
context.ready().then(function () {
// A/B/C Test
if (context.treatment("experiment_name") == 1) {
// insert code to show on variant 1
} else if (context.treatment("experiment_name") == 2) {
// insert code to show on variant 2
} else {
// insert the control treatment code
}
});
// or using async/await
async function runExperiment() {
await context.ready();
// A/B Test
if (context.treatment("experiment_name")) {
// insert code to show on variant 1
} else {
// insert the control treatment code
}
}
res, _ = context.get_treatment("exp_test_experiment")
if res == 0:
# user is in control group (variant 0)
else:
# user is in treatment group
Using the Treatment component
In React, the easiest way to select your treatment is to use the useTreatment
hook.
const MyComponent = () => {
const {
variant,
// loading,
// error,
// context
} = useTreatment("experiment_name");
if (variant === 1) return <VariantB />;
if (variant === 2) return <VariantC />;
if (variant === 3) return <VariantD />;
// `variant` is 0
return <DefaultComponent />;
};
Or, you can use the Treatment
and TreatmentVariant
components.
return (
<Treatment name="experiment_name" loadingComponent={<MySpinner />}>
{/* The variant prop can have numbers or letters passed to it */}
<TreatmentVariant variant="A">
{/* Variant 0 */}
<DefaultButton />
</TreatmentVariant>
<TreatmentVariant variant="B">
{/* Variant 1 */}
<ButtonVariant1 />
</TreatmentVariant>
<TreatmentVariant variant="C">
{/* Variant 2 */}
<ButtonVariant2 />
</TreatmentVariant>
</Treatment>
);
If the loading prop is not provided the default variant will be shown instead.
Using the ternary operator
If you need some more control over the way your variants are rendered, you can
use the TreatmentFunction
component and the ternary operator, like so:
return (
<TreatmentFunction name="experiment_name" loadingComponent={<MySpinner />}>
{({ variant }) =>
variant === 1 ? (
<ButtonVariant1 />
) : variant === 2 ? (
<ButtonVariant2 />
) : (
<DefaultButton />
)
}
</TreatmentFunction>
);
let treatment = context.getTreatment("exp_test_experiment")
if treatment == 0 {
// user is in control group (variant 0)
} else {
// user is in treatment group
}
The preferred method to select a treatment is using the Treatment
component
with a named and scoped slot per treatment.
- If the context is not ready:
- If the loading slot exists, select it.
- Otherwise, select the default slot.
- Otherwise, if the context is ready:
- If a slot with the treatment alias (A, B, C, ...) exists, select it.
- Otherwise, if a slot with the treatment index exists, select it.
- Otherwise, select the default.
- If the selected slot doesn't exist, nothing will be rendered.
Example using the treatment alias
<treatment name="exp_test_experiment">
<template #A>
<my-button></my-button>
</template>
<template #B="{ config }">
<my-button :color="config.color"></my-button>
</template>
<template #loading>
<my-spinner></my-spinner>
</template>
</treatment>
Example using the treatment index
<treatment name="exp_test_experiment">
<template #0>
<my-button></my-button>
</template>
<template #1="{ config }">
<my-button :color="config.color"></my-button>
</template>
<template #2="{ config }">
<my-other-button :color="config.color"></my-other-button>
</template>
<template #loading>
<my-spinner></my-spinner>
</template>
</treatment>
Example using only the default slot
<treatment name="exp_test_experiment">
<template #default="{ config, treatment, ready }">
<template v-if="ready">
<my-button v-if="treatment == 0"></my-button>
<my-button v-else-if="treatment == 1" :color="config.color"></my-button>
<my-other-button v-else-if="treatment == 2" :color="config.color"></my-other-button>
</template>
<template v-else><my-spinner></my-spinner></template>
</template>
</treatment>
The scoped slot properties contain information about the ABsmartly context and the selected treatment:
{
"treatment": 1,
"config": {
"color": "red"
},
"ready": true,
"failed": false
}
If the experiment is not running, or the context creation failed, the slot will be rendered with the following properties:
{
"treatment": 0,
"config": {},
"ready": true,
"failed": false
}
The preferred method to select a treatment is using the Treatment
component
with a named and scoped slot per treatment.
- If the context is not ready:
- If the loading slot exists, select it.
- Otherwise, select the default slot.
- Otherwise, if the context is ready:
- If a slot with the treatment alias (A, B, C, ...) exists, select it.
- Otherwise, if a slot with the treatment index exists, select it.
- Otherwise, select the default.
- If the selected slot doesn't exist, nothing will be rendered.
Example using the treatment alias
<treatment name="exp_test_experiment">
<template #A>
<my-button></my-button>
</template>
<template #B="{ config }">
<my-button :color="config.color"></my-button>
</template>
<template #loading>
<my-spinner></my-spinner>
</template>
</treatment>
Example using the treatment index
<treatment name="exp_test_experiment">
<template #0>
<my-button></my-button>
</template>
<template #1="{ config }">
<my-button :color="config.color"></my-button>
</template>
<template #2="{ config }">
<my-other-button :color="config.color"></my-other-button>
</template>
<template #loading>
<my-spinner></my-spinner>
</template>
</treatment>
Example using only the default slot
<treatment name="exp_test_experiment">
<template #default="{ config, treatment, ready }">
<template v-if="ready">
<my-button v-if="treatment == 0"></my-button>
<my-button v-else-if="treatment == 1" :color="config.color"></my-button>
<my-other-button v-else-if="treatment == 2" :color="config.color"></my-other-button>
</template>
<template v-else><my-spinner></my-spinner></template>
</template>
</treatment>
The scoped slot properties contain information about the ABsmartly context and the selected treatment:
{
"treatment": 1,
"config": {
"color": "red"
},
"ready": true,
"failed": false
}
If the experiment is not running, or the context creation failed, the slot will be rendered with the following properties:
{
"treatment": 0,
"config": {},
"ready": true,
"failed": false
}
if (context.getTreatment("exp_test_experiment") == 0) {
// user is in control group (variant 0)
} else {
// user is in treatment group
}
var res, _ = context.GetTreatment("exp_test_experiment")
if res == 0 {
// user is in control group (variant 0)
} else {
// user is in treatment group
}
if (context.GetTreatment("exp_test_experiment") == 0) {
// user is in control group (variant 0)
} else {
// user is in treatment group
}
$treatment = $context->getTreatment('exp_test_experiment');
if ($treatment === 0) {
// user is in control group (variant 0)
}
else {
// user is in treatment group
}
treatment = context.treatment('exp_test_experiment')
if treatment.zero?
# user is in control group (variant 0)
else
# user is in treatment group
end
if((await context.getTreatment("exp_test_experiment") == 0) {
// user is in control group (variant 0)
} else {
// user is in treatment group
}
Treatment Variables
Treatment variables are a powerful tool that can be used to automate your experiments. When creating an experiment on your ABsmartly Web Console, you can give each variant a set of variables. You can then use your context to pull the values of these variables into your code.
For example, let's say you have an experiment to find out what color of button generates the most clicks on your homepage. In this experiment, you have two variants - Variant 1 and Variant 2. On both of these variants, you have assigned a variable:
- Variant 1:
{ "button.color": "green" }
- Variant 2:
{ "button.color": "blue" }
And you wish for the control group (Variant 0) to have { "button.color": red }
.
You can select these values in your code like so:
- Javascript
- Python3
- React
- Swift
- Vue2
- Vue3
- Java
- Go
- .NET
- PHP
- Ruby
- Flutter/Dart
const defaultButtonColorValue = "red";
const buttonColor = context.variableValue(
"button.color",
defaultButtonColorValue
);
Config API
If you use configuration files to change different aspects of your application, then you can use the Config API to greatly improve your workflow and save a lot of coding time.
// Import the mergeConfig function.
import { mergeConfig } from "@absmartly/javascript-sdk";
/*
Your current config might be something like:
const myAppConfig = { ... };
or
const myAppConfig = getConfigFromFile(config.json);
then you just need to add the mergeConfig function like this:
*/
const myAppConfig = mergeConfig(getConfigFromFile(config.json));
Basic Example
Let's say you use a configuration file to change various parameters in your application:
let cfg = {
button: {
color: "blue",
cta: "Click me",
},
hero_image: "http://cdn.com/img1.png",
some_other_stuff: {
/* ... */
},
};
Then you could use the Config API to run experiments that change those parameters.
Imagine you start two experiments:
- button_color
- Control group variables =
{ button.color: "blue" }
- Variant 1 variables =
{ button.color: "green" }
- Control group variables =
- homepage_cta
- Control group variables =
{ button.cta: "Click me", hero_image: "https://cdn.com/img1.png" }
- Variant 1 Variables =
{ button.cta: "Click here", hero_image: "https://cdn.com/img2.jpg" }
- Control group variables =
For each user the SDK receives a payload similar to this:
{
"guid": "dhsUiLJ7xgQBEbivw_0cjiKo9O6UlnSg",
"units": [],
"assignments": [
{
"name": "button_color",
"variant": 1,
"config": {
"button.color": "green"
}
},
{
"name": "homepage_cta",
"variant": 0,
"config": {
"button.cta": "Click here",
"hero_image": "http://cdn.com/img1.png"
}
}
]
}
This user is in Variant 1 of the button_color experiment and in the
control group for the homepage_cta experiment. Calling
context.mergeConfig(cfg)
from the Javascript SDK would return a config
object like this:
{
button: {
get color: () => { context.treatment("button_color"); return "green"; },
get cta: () => { context.treatment("homepage_cta"); return "Click here"; },
}
get hero_image: () => { context.treatment("homepage_cta"); return "http://cdn.com/img1.png"; },
some_other_stuff: { /* ... */ },
}
When the config API is used, you don't need to call the treatment()
method.
It will be called automatically when keys from the config are used.
So you can continue using your configuration file as you were before, but now, the correct experiments will be triggered when a value is being overridden by said experiment.
This greatly simplifies the process of setting up experiments and cleaning up your code. If, at somepoint, a large part of the code is setup in this way you will be able to create different experiments without touching a single line of code.
Translations Example
Let's look at a slightly more complex example where you have to run an experiment whilst accomodating for multiple languages.
Here, we have a languagesConfig.js file:
export default // Copy text for English and Dutch
{
header: {
en: "Our nice header!",
nl: "Onze leuk kop!",
},
call_to_action1: {
en: "Click here",
nl: "Klik hier",
},
};
In our code, we can then get all of our variant variables using:
// Get the keys of the variant variables from the context
const tagsToFetch = context.variableKeys();
const translationVariations = fetchTranslations(tagsToFetch);
The translationVariations
variable would then be returned as something like:
{
"header_v1": {
"en": "Our beautiful header!",
"nl": "Onze mooie kop!"
},
"call_to_action1_v1": {
"en": "Continue",
"nl": "Doorgaan"
}
}
...which you could then merge with your original translations, like so:
import appTranslations from "../languagesConfig.js";
const translations = mergeConfig(appTranslations, translationVariations);
...which would return an object similar to:
{
get header: () => {
exp.treatment("experiment1");
return {
en: "Our beautiful header!",
nl: "Onze mooie kop!",
};
}
get call_to_action1: () => {
exp.treatment("experiment2");
return {
en: "Click here!",
nl: "Klik Hier"
};
}
}
You can now use this object to run your experiments whilst still supporting multiple languages!
res = context.get_variable_value(key, 17)
Directly in the TreatmentFunction
component
const defaultButtonColorValue = "red";
return (
<TreatmentFunction
context={context}
name="experiment_name"
loadingComponent={<MySpinner />}
>
{({ variant, variables }) => (
<ButtonComponent
buttonColor={variables["button.color"] || defaultButtonColorValue}
/>
)}
</TreatmentFunction>
);
In a useEffect()
const [buttonColor, setButtonColor] = React.useState("red");
useEffect(() => {
const defaultButtonColorValue = "red";
context
.ready()
.then(() => {
setButtonColor(
context.variableValue("button.color", defaultButtonColorValue)
);
})
.catch((error) => console.error(error));
}, [context]);
This can be accomplished in a class component without using react hooks.
Replace the above useEffect hook with a componentDidMount
lifecycle hook and
store the variables in your component's state there.
let buttonColor = context.getVariableValue("button.color", defaultValue: "red")
const defaultButtonColorValue = "red";
const buttonColor = this.$absmartly.variableValue(
"button.color",
defaultButtonColorValue
);
const defaultButtonColorValue = "red";
const buttonColor = this.$absmartly.variableValue(
"button.color",
defaultButtonColorValue
);
final String defaultButtonColorValue = "red";
final Object buttonColor = context.getVariableValue("button.color", defaultButtonColorValue);
var defaultButtonColorValue = "red"
var buttonColor, _ = context.GetVariableValue("button.color", defaultButtonColorValue)
var variable = context.GetVariableValue("my_variable");
$defaultButtonColorValue = 'red';
$buttonColor = $context->getVariableValue('button.color');
default_button_color_value = 'red'
context.variable_value('experiment_name', default_button_color_value)
var defaultButtonColorValue = "red"
context.getVariableValue("experiment_name", defaultButtonColorValue)
We recommend that control group does not have variables set on the Web Console except in very specific use cases. Usually, they should be set using the "default value" parameter as shown above.
You can then use this variable to style your button appropriately for the variant that the current user is on. The benefit of this is that you can now make more variants of this experiment on the Web Console and they will automatically be implemented in your application!
Peek at Treatment Variants
- Javascript
- Python3
- React
- Swift
- Vue2
- Vue3
- Java
- Go
- .NET
- PHP
- Ruby
- Flutter/Dart
Although generally not recommended, it is sometimes necessary to peek at a
treatment without triggering an exposure. The ABsmartly SDK provides a
peek()
method for that.
if (context.peek("exp_test_experiment") == 0) {
// user is in control group (variant 0)
} else {
// user is in treatment group
}
Although generally not recommended, it is sometimes necessary to peek at a
treatment without triggering an exposure. The ABsmartly SDK provides a
peek_treatment()
method for that.
res = context.peek_treatment("exp_test_experiment")
if res == 0:
# user is in control group (variant 0)
else:
# user is in treatment group
Peeking at variables
variable = context.peek_variable("my_variable")
Although generally not recommended, it is sometimes necessary to peek at a
treatment without triggering an exposure. The ABsmartly SDK provides a
peek()
method for that.
useEffect(() => {
context
.ready()
.then(() => {
if (context.peek("exp_test_experiment") === 0) {
// User is in control group (variant 0)
} else {
// User is in treatment group
}
})
.catch((error) => console.error(error));
}, [context]);
Although generally not recommended, it is sometimes necessary to peek at a
treatment without triggering an exposure. The ABsmartly SDK provides a
peekTreatment()
method for that.
let treatment = context.peekTreatment(experimentName: "exp_test_experiment")
if treatment == 0 {
// user is in control group (variant 0)
} else {
// user is in treatment group
}
Peeking at Variables
let color = context.peekVariableValue("colorGComponent", defaultValue: 255)
Although generally not recommended, it is sometimes necessary to peek at a
treatment without triggering an exposure. The ABsmartly SDK provides a
peek()
method for that.
if (this.$absmartly.peek("exp_test_experiment") == 0) {
// user is in control group (variant 0)
} else {
// user is in treatment group
}
Although generally not recommended, it is sometimes necessary to peek at a
treatment without triggering an exposure. The ABsmartly SDK provides a
peek()
method for that.
if (this.$absmartly.peek("exp_test_experiment") == 0) {
// user is in control group (variant 0)
} else {
// user is in treatment group
}
Although generally not recommended, it is sometimes necessary to peek at a
treatment or variable without triggering an exposure. The ABsmartly SDK
provides a peekTreatment()
method for that.
if (context.peekTreatment("exp_test_experiment") == 0) {
// user is in control group (variant 0)
} else {
// user is in treatment group
}
Peeking at variables
final Object variable = context.peekVariable("my_variable");
Although generally not recommended, it is sometimes necessary to peek at a
treatment or variable without triggering an exposure. The ABsmartly SDK
provides a PeekTreatment()
method for that.
var res, _ = context.PeekTreatment("exp_test_experiment")
if res == 0 {
// user is in control group (variant 0)
} else {
// user is in treatment group
}
Peeking at variables
var variable = context.PeekVariable("my_variable")
Although generally not recommended, it is sometimes necessary to peek at
a treatment or variable without triggering an exposure. The ABsmartly
SDK provides a PeekTreatment()
method for that.
if (context.PeekTreatment("exp_test_experiment") == 0) {
// user is in control group (variant 0)
} else {
// user is in treatment group
}
Peeking at variables
var variable = context.PeekVariableValue("my_variable");
Although generally not recommended, it is sometimes necessary to peek at a
treatment or variable without triggering an exposure. The ABsmartly SDK
provides a Context->peekTreatment()
method for that.
$treatment = $context->peekTreatment('exp_test_experiment');
if ($treatment === 0) {
// user is in control group (variant 0)
}
else {
// user is in treatment group
}
Peeking at variables
$buttonColor = $context->peekVariableValue('button.color', 'red');
Although generally not recommended, it is sometimes necessary to peek at a
treatment or variable without triggering an exposure. The ABsmartly SDK
provides a peek_treatment()
method for that.
treatment = context.peek_treatment('exp_test_experiment')
Peeking at variables
treatment = context.peek_variable_value('exp_test_experiment')
Although generally not recommended, it is sometimes necessary to peek at a
treatment or variable without triggering an exposure. The ABsmartly SDK
provides a peekTreatment()
method for that.
var value = await ctx.peekTreatment("experimentName");
Peeking at variables
final dynamic variable = context.getVariable("my_variable");
Overriding Treatment Variants
Overriden events are typed as ineligible and are ignored by the ABsmartly statistics engines.
If you want to force a particular experiment's variant and have the event be counted, you
can use the customAssignment
methods
instead, although this is not recommended.
- Javascript
- Python3
- React
- Swift
- Vue2
- Vue3
- Java
- Go
- .NET
- PHP
- Ruby
- Flutter/Dart
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 override()
and overrides()
methods can be called before the context is
ready.
context.override("exp_test_experiment", 1); // force variant 1 of treatment
context.overrides({
exp_test_experiment: 1,
exp_another_experiment: 0,
});
During development, for example, it is useful to force a treatment for an
experiment. This can be achieved with the set_override()
and/or set_overrides()
methods. The set_override()
and set_overrides()
methods can be called before the context is
ready.
context.set_override("exp_test_experiment", 1) # force variant 1 of treatment
context.set_overrides({
"exp_test_experiment": 1,
"exp_another_experiment": 0
})
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 override()
and overrides()
methods can be called before the context is
ready.
context.override("exp_test_experiment", 1); // force variant 1 of treatment
context.overrides({
exp_test_experiment: 1,
exp_another_experiment: 0,
});
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.
context.setOverride(experimentName: "exp_test_experiment", variant: 1) // force variant 1 of treatment
context.setOverrides(["exp_test_experiment": 1, "exp_another_experiment": 0])
During development, for example, it is useful to force a treatment for an
experiment. This can be achieved in the initialization of the SDK or with the
override()
and/or overrides()
methods.
In the SDK initialization
Vue.use(absmartly.ABSmartlyVue, {
sdkOptions: {
/* ... */
},
context: {
/* ... */
},
overrides: {
exp_test_development: 1,
},
});
With the override methods
this.$absmartly.override("exp_test_experiment", 1); // force variant 1 of treatment
this.$absmartly.overrides({
exp_test_experiment: 1,
exp_another_experiment: 0,
});
The override()
and overrides()
methods can be called before the context is ready.
During development, for example, it is useful to force a treatment for an
experiment. This can be achieved in the initialization of the SDK or with the
override()
and/or overrides()
methods.
In the SDK initialization
Vue.use(absmartly.ABSmartlyVue, {
sdkOptions: {
/* ... */
},
context: {
/* ... */
},
overrides: {
exp_test_development: 1,
},
});
With the override methods
this.$absmartly.override("exp_test_experiment", 1); // force variant 1 of treatment
this.$absmartly.overrides({
exp_test_experiment: 1,
exp_another_experiment: 0,
});
The override()
and overrides()
methods can be called before the context is ready.
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.
context.setOverride("exp_test_experiment", 1); // force variant 1 of treatment
context.setOverrides(Map.of(
"exp_test_experiment", 1,
"exp_another_experiment", 0
));
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.
context.SetOverride("exp_test_experiment", 1) // force variant 1 of treatment
context.SetOverrides(map[string]int{
"exp_test_experiment": 1,
"exp_another_experiment": 0
})
During development, for example, it is useful to force a treatment for an
experiment. This can be achieved with the SetOverride()
and/or
SetOverrides()
methods. The SetOverride()
and SetOverrides()
methods can
be called before the context is ready.
context.SetOverride("exp_test_experiment", 1); // force variant 1 of treatment
context.SetOverrides(new Dictionary<string, int>() {
{ "exp_test_experiment", 1 },
{ "exp_another_experiment", 0 }
});
During development, for example, it is useful to force a treatment for an
experiment. This can be achieved with the Context->setOverride()
and/or
Context->setOverrides()
methods. These methods can be called before the
context is ready.
$context->setOverride("exp_test_experiment", 1); // force variant 1 of treatment
$context->setOverrides(
[
'exp_test_experiment' => 1,
'exp_another_experiment' => 0,
]
);
During development, for example, it is useful to force a treatment for an
experiment. This can be achieved with the set_override()
and/or
set_overrides()
methods. These methods can be called before the context is
ready.
context.set_override("exp_test_experiment", 1) # force variant 1 of treatment
context.set_overrides(
'exp_test_experiment' => 1,
'exp_another_experiment' => 0,
)
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.
context.setOverride(experimentName, variant)
context.setOverrides({
"exp_test_experiment": 1,
"exp_another_experiment": 0,
});
Overriding Based On URL Params
The most common use case for overriding is to override a treatment based on
params in the URL. This allows for greater flexibility in the development and
QA stages. The following Javascript function can be ported to any language and
used to parse the URL query parameters and return an object of overrides. This
object can then be passed to the overrides()
context method to force a particular
variant for one or multiple experiment(s).
Here we using absmartly_
as a prefix for the query parameters, but you can
use whatever prefix you like. It could be exp_
, test_
, or even a simple _
!
function getABsmartlyOverridesFromQuery(req) {
const overrides = {};
// Iterate through all query parameters
for (const [key, value] of Object.entries(req.query)) {
// Check if the query parameter starts with "absmartly_"
if (key.startsWith('absmartly_')) {
// Extract the experiment name (remove "absmartly_" prefix)
const experimentName = key.slice(9);
// Convert the value to a number if possible, otherwise keep it as a string
const variantValue = isNaN(value) ? value : Number(value);
// Add to overrides object
overrides[experimentName] = variantValue;
}
}
return overrides;
}