npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@kiwicom/darwin

v0.6.6

Published

AB testing tool 2.0. :monkey:

Downloads

3,340

Readme

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:

// A single AB test
export type Test = {
  name: string; // The test's name
  value: string; // The value string
};

// One of AB test's treatment group configurations
export type Value = {
  value: string; // The treatment group name
  weight: number; // The relative traffic size of the treatment group
};

// Traffic group configuration
export type Traffic = {
  userAgent?: string | null; // Pattern against which to compare the user-agent
  languages?: string[] | null; // List of accpeted languages
  affiliates?: string[] | null; // List of accepted affiliates
};

// Configuration of an AB test
export type TestConfig = {
  name: string; // The AB test name
  values: Value[]; // List of treatment group configurations
};

// Population group with an assigned AB test, or `null` for placeholder slot
export type Group = {
  traffic?: Traffic; // The group's traffic configuration
  tests: TestConfig[]; // The test configurations for this population group
};

// Feature configuration
export type Feature = {
  traffic?: Traffic; // The feature's traffic configuration
  name: string; // The feature's name
  enabled: boolean; // Flag indicating whether the feature is enabled or not
};

// A record of enabled/disabled features
export type Features = Record<string, boolean>;

// The root configuration object
export type Config = {
  features?: Feature[]; // The feature configuration
  groups?: Group[]; // Population group configurations
  winners?: Test[]; // Winning tests and their values
};

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-1column 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 = {
  /* ... */
};

// Returns the group's name and the `Test` array, or
// `null` group name and empty array for no active test
const { group, tests } = getTests({
  config,
  userAgent: req.headers["User-Agent"], // optional
  language: "en-GB", // optional
  affiliate: "shrek", // optional
  salt: "some-user-id-string", // length at least 10 for optimal results
  savedGroup: req.cookies[Cookies.DARWIN_GROUP], // optional, saved group for the user
});

// optionally, save/remove test name from storage
if (group === null) {
  // remove `group` from e.g. cookies
} else {
  // save `group` to e.g. cookies
}

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"], // optional
  language: "en-GB", // optional
  affiliate: "shrek", // optional
});

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() {
    // call these as you'd call the `useTest` and `useFeature` hooks
    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 tests
  • types for adjusting .js.flow files or type signatures
  • src for library changes, features, patches...

Examples:

  • docs(dev): document commits
  • feat(src): add new hook
  • chore(types): new spread syntax

Release

  • yarn release
  • yarn publish

License

MIT