-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move opticks readme and create root readme (#89)
* move readme * add monorepo readme * add changeset
- Loading branch information
1 parent
2cbd454
commit 7fb5dee
Showing
7 changed files
with
281 additions
and
213 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'opticks': patch | ||
--- | ||
|
||
Add opticks readme |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,236 +1,63 @@ | ||
Feature toggles and A/B tests are wonderful, but can easily introduce debt in | ||
your codebase. | ||
# Opticks Monorepo | ||
|
||
This project allows you to experiment and automatically clean up your JavaScript | ||
experimentation code after the experiment concludes using JSCodeShift codemods. | ||
Welcome to the Opticks monorepo! This repository includes three main packages: | ||
|
||
The library consists of two related concepts: | ||
- **lib:** (https://www.npmjs.com/package/opticks) | ||
- **cli:** (https://www.npmjs.com/package/opticks-cli) | ||
- **eslint-plugin:** (https://www.npmjs.com/package/eslint-plugin-opticks) | ||
|
||
- The Toggle library, a simple API for introducing toggles and A/B tests in your | ||
codebase | ||
- Codemods for cleaning up toggle code | ||
Each package has its own directory and corresponding README file with specific instructions and documentation. | ||
|
||
# Toggle Library | ||
## Table of Contents | ||
|
||
At the heart of our experimentation framework is the `toggle` function. | ||
- [Opticks Monorepo](#opticks-monorepo) | ||
- [Table of Contents](#table-of-contents) | ||
- [Setup](#setup) | ||
- [Building and Testing](#building-and-testing) | ||
- [Publishing Packages](#publishing-packages) | ||
|
||
A toggle allows you to switch between multiple experiment variants (a/b/c/...) | ||
and also turn functionality on or off (feature flags) | ||
## Setup | ||
|
||
It can be used in a variety of ways: | ||
To get started with the Opticks monorepo, ensure you have the following prerequisites installed: | ||
|
||
1. Reading the value of the toggle (boolean, or a/b/c for multi toggles ) | ||
1. Execute code or for a variant of a multi toggle | ||
1. Execute code when a boolean toggle is on | ||
- Node.js (version 20.8.0) | ||
- Yarn | ||
|
||
We use React at vio.com and some of the code examples use JSX, but the code | ||
and concept is compatible with any front-end framework or architecture. | ||
Clone the repository and install dependencies: | ||
|
||
### Opticks vs other experimentation frameworks | ||
|
||
The main reason for using the Opticks library is to be able to clean your code | ||
afterwards by providing a predictable experimentation API. | ||
|
||
We don't intend to reinvent the wheel and aim to keep it easy to integrate | ||
existing frameworks and services such as Optimizely, LaunchDarkly and | ||
Conductrics behind a simple facade. | ||
|
||
## Usage and Integrations | ||
|
||
Currently Opticks has two 'integrations' or adapters, a simple one based on a | ||
local key/value store, and one wrapping the Optimizely Full Stack SDK. Once | ||
initialized, using/consuming the toggle decisions is the same. | ||
|
||
In the future the integrations will be packaged separately so you can include | ||
the one you need, for now the "simple" is the default and the Optimizely adapter | ||
can be included directly via: | ||
`import Opticks from 'opticks/lib/optimizely'` | ||
|
||
## Integrations | ||
|
||
### Simple | ||
|
||
See the [Simple integration documentation](docs/simple-integration.md). | ||
|
||
### Optimizely | ||
|
||
See the [Optimizely integration documentation](docs/optimizely-integration.md). | ||
|
||
## Toggles | ||
|
||
Toggles can be used to implement a/b/c style MVT testing and on/off feature flags as well. | ||
We specify multiple variants of which only one is active at any time. | ||
By convention the variants are named `a` (control), `b`, `c` etc. | ||
|
||
### Reading values | ||
|
||
While it's recommended to use the strategy described in | ||
[executing code for variants](#executing-code-for-variants), the following shows | ||
how the variant executing and code clean up works under the hood. | ||
|
||
The simplest signature is as follows, to read the toggle value directly: | ||
|
||
``` | ||
toggle(experimentId: string) => { variant: 'a' | 'b' | 'c' | 'd' | ... } | ||
``` | ||
|
||
For example when the user is assigned to the `b` side: | ||
|
||
``` | ||
const fooResult = toggle('foo') | ||
if (fooResult === 'b') console.log('b side of the foo experiment') | ||
``` | ||
|
||
This works fine, but will result in code that will be hard to clean up | ||
automatically. Considering the codemods replace the toggle with the 'winning' | ||
value (`b` in this case): | ||
|
||
``` | ||
const fooResult = 'b' | ||
if (fooResult === 'b') console.log('b variant of the foo experiment') | ||
``` | ||
|
||
This would leave your code more messy than necessary. You could skip the | ||
intermediate value but it will still result in awkward leftover code: | ||
|
||
``` | ||
if (toggle('foo') === 'b') ... | ||
// becomes | ||
if ('b' === 'b') ... | ||
// or | ||
if ('a' === 'b') ... | ||
```bash | ||
git clone https://github.com/viodotcom/opticks.git | ||
cd opticks | ||
yarn install | ||
``` | ||
|
||
Rather than reading the return value directly, you can map your variant to | ||
arguments to the toggle function, like so: | ||
## Building and Testing | ||
|
||
``` | ||
// Defines the result if that toggle or experiment wins, either a function that | ||
// will be executed, or any other value | ||
type ToggleResultType = function | any | ||
// do something for a/b/c variant: | ||
toggle( | ||
experimentId, | ||
variantA: ToggleResultType, | ||
variantB: ToggleResultType, | ||
?variantC: ToggleResultType, | ||
... | ||
) | ||
``` | ||
To build and test all packages, you can use the following Yarn commands: | ||
|
||
The signature might look more complicated, but it allows you to define what the | ||
results for your variants a, b, c etc map to. The first value is the | ||
experimentId, then the values for `a`, `b`, etc. | ||
|
||
For instance: | ||
```bash | ||
# Build all packages | ||
yarn workspaces foreach -A run build | ||
|
||
# Test all packages | ||
yarn workspaces foreach -A run test | ||
``` | ||
// simple boolean switch | ||
const shouldDoSomething = toggle('foo', false, true) | ||
|
||
// multiple variants as strings | ||
// 'black' is the default, red and green are variants to experiment with | ||
const buttonColor = toggle('foo', 'black', 'green', 'red') | ||
``` | ||
## Publishing Packages | ||
|
||
The benefit is that after concluding your experiment, you can integrate the | ||
winning variation's code directly without awkward references to `a` or `b` etc. | ||
To publish a package after making changes, follow these steps: | ||
|
||
After you run the codemods to declare `b` the winner, the corresponding raw | ||
value is kept: | ||
1. **Generate a Changeset:** | ||
After making changes to a package, run the following command to generate a changeset: | ||
|
||
``` | ||
const shouldDoSomething = toggle('foo', false, true) | ||
const buttonColor = toggle('foo', 'black', 'green', 'red') | ||
```bash | ||
npx changeset | ||
``` | ||
|
||
// becomes: | ||
const shouldDoSomething = true | ||
const buttonColor = 'green' | ||
``` | ||
|
||
Much better already, but there is more room for improvement, especially if you | ||
want to do things conditionally for a variant. | ||
|
||
Consider the following set up: | ||
|
||
``` | ||
const shouldShowWarning = toggle('shouldShowWarning', false, true) | ||
if (shouldShowWarning) showWarning() | ||
// or directly: | ||
if (toggle('shouldShowWarning', false, true)) showWarning() | ||
``` | ||
|
||
This would end up with an orphaned conditional after the codemods did their | ||
cleaning: | ||
|
||
``` | ||
const shouldShowWarning = true | ||
if (shouldShowWarning) showWarning() | ||
// or directly | ||
if (true) showWarning() | ||
``` | ||
|
||
The next section explains a more useful concept for this type of conditional | ||
branching. | ||
|
||
### Executing code for variants | ||
|
||
A better approach that allows you to clean the code easier would be to | ||
encapsulate variant logic by executing code from the toggle: | ||
|
||
``` | ||
const price = 100 | ||
const savings = 20 | ||
const CTA = toggle( | ||
'CTAWording', | ||
() => `Buy now for just ${price}` // variant a (default) | ||
() => `Buy now for ${price} and save ${savings}`, // variant b | ||
() => `From ${price + savings} to ${price}` // variant c | ||
) | ||
``` | ||
|
||
Then after running the cleaning codemods when the `b` variant wins: | ||
|
||
``` | ||
const price = 100 | ||
const savings = 20 | ||
const CTA = `Buy now for ${price} and save ${savings}` | ||
``` | ||
|
||
Or for an example of conditionally calling code: | ||
|
||
``` | ||
alwaysDoSomething() | ||
toggle('shouldShowWarning', null, () => shouldShowWarning()) | ||
``` | ||
|
||
This shows two special concepts of the codemods, passing a `null` and the use of | ||
arrow functions. | ||
Passing `null` allows for full clean up when that branch loses. | ||
For winners, the _body_ of the arrow function is kept as-is. | ||
|
||
After running the codemods with the winning `b` variant: | ||
|
||
``` | ||
alwaysDoSomething() | ||
shouldShowWarning() // no trace this was the result of a winning experiment | ||
``` | ||
|
||
But if `a` would have won: | ||
|
||
``` | ||
alwaysDoSomething() | ||
// no trace that something was experimented with but lost | ||
``` | ||
Follow the prompts to describe your changes. This will create a changeset file in the `.changeset` directory. | ||
|
||
## Removal of dead code | ||
2. **Push Your Changes:** | ||
Push your changes and create a pull request. Once the pull request is merged, a new pull request will be automatically created. This new pull request will remove the changeset file and update the changelog. | ||
|
||
Above are just a few examples of how the codemods operate on the code. | ||
For instructions and more recipes, see | ||
[Removal of dead code](docs/dead-code-removal.md). | ||
3. **Merge the Release Pull Request:** | ||
When this automatically created pull request is merged, the relevant package will be published to the npm registry. |
Oops, something went wrong.