Darwin
Our shiny new AB testing tool! :monkey:
yarn add @kiwicom/darwin
API
Download the test configuration file. On a request take:
- the
config
object - request's
userAgent
- request's language code in ISO 639-1 with the country postfix —
xx-XX
- some salt — usually user's ID
Config
The configuration object is either a JSON file or a JS object with the following structure:
export type Test = {
name: string;
value: string;
};
export type Value = {
value: string;
weight: number;
};
export type Traffic = {
userAgent?: string | null;
languages?: string[] | null;
affiliates?: string[] | null;
};
export type TestConfig = {
name: string;
values: Value[];
};
export type Group = {
traffic?: Traffic;
tests: TestConfig[];
};
export type Feature = {
traffic?: Traffic;
name: string;
enabled: boolean;
};
export type Features = Record<string, boolean>;
export type Config = {
features?: Feature[];
groups?: Group[];
winners?: Test[];
};
An example Traffic
object containing the English and Russian language, and is iPhone only, for
shrek
and donkey
affiliates:
{
"languages": ["en-US", "ru-RU"],
"userAgent": "iPhone",
"affiliates": ["shrek", "donkey"]
}
The language list is the 639-1
column here
.
Be sure to include the -XX
country postfix, as Kiwi.com recognizes different countries'
language variants.
For user agents, see this website. Pick a chunk of
the user agent you want and put it in the userAgent
field in the traffic object, like in the
example.
getTests
Feed the config
object function together with the userAgent
, language, affiliate and the salt:
import { Config, Test, getTest } from "@kiwicom/darwin";
import { Cookies } from "@kiwicom/cookies";
const config: Config = {
};
const { group, tests } = getTests({
config,
userAgent: req.headers["User-Agent"],
language: "en-GB",
affiliate: "shrek",
salt: "some-user-id-string",
savedGroup: req.cookies[Cookies.DARWIN_GROUP],
});
if (group === null) {
} else {
}
This object has no functions and thus is serializable — send it to the client encoded as JSON.
getFeatures
Feed the config
object function, optionally together with the userAgent
, language and affiliate:
import { Config, Features, getFeatures } from "@kiwicom/darwin";
const config: Config = {
};
const features: Features = getFeatures({
config,
userAgent: req.headers["User-Agent"],
language: "en-GB",
affiliate: "shrek",
});
This object has no functions and thus is serializable — send it to the client encoded as JSON.
DarwinProvider
Server:
Use the tests
array in your provider:
import { DarwinProvider } from "@kiwicom/darwin";
return ReactDOMServer.renderToString(
<DarwinProvider tests={tests} features={features} winners={winners}>
<Root />
</DarwinProvider>,
);
Client:
Take the tests
and features
sent from the server and feed it to the provider.
Avoid sending the whole downloaded configuration file to the client to save bundle size and avoid
leaking test information.
import { DarwinProvider } from "@kiwicom/darwin";
import { handleLogDarwin } from "src/logger";
const { tests, features, winners } = window.__DARWIN__;
ReactDOM.hydrate(
<DarwinProvider tests={tests} features={features} winners={winners} onTest={handleLogDarwin}>
<Root />
</DarwinProvider>,
document.getElementById("root"),
);
useTest
hook
Checks if the queried test is running and returns its value, null
for reference group.
It also saves the queried test to session data if it returned a non-null
result.
import { useTest } from "@kiwicom/darwin";
const version = useTest("valdoge");
if (version === "g") {
}
You can then retrieve the session data with loadSession
;
useFeature
hook
Tells you if a feature is on / off.
import { useFeature } from "@kiwicom/darwin";
const hasNavbar = useFeature("navbar");
context
If you cannot use React's hook API, you can also access the context directly:
import { context as contextDarwin } from "@kiwicom/darwin";
class MyComponent extends Component {
static contextType = contextDarwin;
handleDarwin() {
const { onTest, onFeature } = this.context;
}
}
loadSession
Loads the session test data, a Record<string, string>
of active
record names and values.
Only use on the client!
Useful mainly for logging.
import { loadSession } from "@kiwicom/darwin";
logger.log("Stuff has happened", {
abTests: loadSession(),
});
Testing
Use the @kiwicom/darwin/mock
module for testing purposes.
mockFormat
Appends the URL with specified testing parameters and returns
the formatted URL search:
import { mockFormat } from "@kiwicom/darwin/mock";
window.location.search = mockFormat([{ name: "yolotest", value: "B" }]);
Takes an optional third parameter as the current URL search that defaults
to window.location.search
.
mockRetrieve
Retrieves mock tests from the URL search.
import { mockRetrieve } from "@kiwicom/darwin/mock";
const mockTests = mockRetrieve(url.search);
const { tests } = getTests({
});
const allTests = [...mockTests, ...tests];
mockFeatureFormat
Appends the URL with specified features and returns the formatted URL
search:
import { mockFeatureFormat } from "@kiwicom/darwin/mock";
window.location.search = mockFeatureFormat({ navbar: true, footer: false });
Takes an optional second parameter as the current URL search that defaults
to window.location.search
.
mockFeatureRetrieve
Retrieves the mock features from the URL search. Merge the mock feature
set with features from the config object:
import { mockFeatureRetrieve } from "@kiwicom/darwin/mock";
const features = {
...getFeatures({
}),
...mockFeatureRetrieve(url.search),
};
mockWinnersFormat
Appends the URL with specified test winners and returns the formatted URL
search:
import { mockWinnersFormat } from "@kiwicom/darwin/mock";
window.location.search = mockWinnersFormat([{ name: "test", value: "off" }]);
Note that the function uses __
for grouping test name and value. Please
avoid using double-underscore in test names or values!
Takes an optional second parameter as the current URL search that defaults
to window.location.search
.
mockWinnersRetrieve
Retrieves the mock test winners from the URL search. Merge the test winners
set with winners from the config object:
import { mockWinnersRetrieve } from "@kiwicom/darwin/mock";
const winners = [...config.winners, ...mockWinnersRetrieve(url.search)];
Development
Clone and yarn
.
Commits
Follow @commitlint/conventional
with a mandatory scope of:
dev
for non-production things like CI or teststypes
for adjusting .js.flow
files or type signaturessrc
for library changes, features, patches...
Examples:
docs(dev): document commits
feat(src): add new hook
chore(types): new spread syntax
Release
License
MIT