-
Notifications
You must be signed in to change notification settings - Fork 27.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add A/B Tests and Feature Flags example (#13629)
## Summary This PR adds a basic example of how [Tesfy](https://tesfy.io/) could be integrated with Next.js. Tesfy is a project that I've working on during quarantine weekends, mainly to learn new stuff and provide **free** and **unlimited** A/B Tests and Feature Flags while keeping a good performance and the library [size](https://bundlephobia.com/[email protected]) as small as possible. The configuration file could be set up using a [web application](https://app.tesfy.io/) (hosted in Vercel 🎉 ) or by your self. ## Implementation - Created `with-tesfy` folder - Added two pages `index.js` and `features.js` to show how experiments and features could be used - The only thing that must be persisted is the `userId`. Used a cookie to save it. - Uses `getServerSideProps` to fetch the configuration file and get/create the `userId`. ## Screenshots There are some screenshots from the web application. Where you can easily configure experiments and audiences per project. Teams and features will soon be added. ![Screenshot 2020-06-01 at 15 40 49](https://user-images.githubusercontent.com/6877967/83414811-60e7ce80-a41e-11ea-9e5c-887c66e80c65.png) ![Screenshot 2020-06-01 at 15 41 02](https://user-images.githubusercontent.com/6877967/83414823-66451900-a41e-11ea-885b-b58e78b042bb.png) ![Screenshot 2020-06-01 at 15 41 11](https://user-images.githubusercontent.com/6877967/83414828-6a713680-a41e-11ea-90a8-8d39a17f19a1.png) This is my first PR! sorry if I made something wrong 😞 . Any feedback is more than welcome. Also I want to thank you all for the awesome work with Next.js ❤️
- Loading branch information
Showing
13 changed files
with
269 additions
and
3 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
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
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
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,50 @@ | ||
# Tesfy Example | ||
|
||
[Tesfy](https://tesfy.io/) allows you to create **unlimited** A/B Tests and Feature Flags for **free** using a [web app](https://app.tesfy.io/) or by your self. | ||
|
||
This example shows how to integrate [react-tesfy](https://github.com/andresz1/react-tesfy) in Next.js. | ||
|
||
To use Tesfy there are only two mandatory things needed. A `userId` and a configuration file known as `datafile`. In the `_app.js` you will notice that those are being get. | ||
|
||
The `userId` must uniquely identify a user even if not logged in, for that reason a [uuid](https://en.wikipedia.org/wiki/Universally_unique_identifier) is created and stored in a cookie so the next time a page is requested a new `userId` won't be created, instead the cookie one will be used. | ||
|
||
The `datafile` is just a `json` that defines the configuration of the experiments and features avaliable. It must be fetched from Tesfy CDN or from your own servers at least everytime a request is performed, later on this configuration could also be fetched if wanted (e.g. during page transitions). | ||
|
||
## Deploy your own | ||
|
||
Deploy the example using [Vercel](https://vercel.com): | ||
|
||
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/vercel/next.js/tree/canary/examples/with-tesfy) | ||
|
||
## How to use | ||
|
||
### Using `create-next-app` | ||
|
||
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: | ||
|
||
```bash | ||
npx create-next-app --example with-tesfy with-tesfy-app | ||
# or | ||
yarn create next-app --example with-tesfy with-tesfy-app | ||
``` | ||
|
||
### Download manually | ||
|
||
Download the example: | ||
|
||
```bash | ||
curl https://codeload.github.com/vercel/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-tesfy | ||
cd with-tesfy | ||
``` | ||
|
||
Install it and run: | ||
|
||
```bash | ||
npm install | ||
npm run dev | ||
# or | ||
yarn | ||
yarn dev | ||
``` | ||
|
||
Deploy it to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). |
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,16 @@ | ||
import Link from 'next/link' | ||
|
||
const Nav = () => { | ||
return ( | ||
<nav> | ||
<Link href="/"> | ||
<a>Experiments</a> | ||
</Link>{' '} | ||
<Link href="/features"> | ||
<a>Features</a> | ||
</Link> | ||
</nav> | ||
) | ||
} | ||
|
||
export default Nav |
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,19 @@ | ||
{ | ||
"name": "with-tesfy", | ||
"version": "1.0.0", | ||
"scripts": { | ||
"dev": "next", | ||
"build": "next build", | ||
"start": "next start" | ||
}, | ||
"dependencies": { | ||
"next": "latest", | ||
"react": "^16.13.1", | ||
"react-dom": "^16.13.1", | ||
"react-tesfy": "latest", | ||
"tesfy": "latest", | ||
"cookie": "0.4.1", | ||
"uuid": "8.1.0" | ||
}, | ||
"license": "ISC" | ||
} |
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,26 @@ | ||
import React from 'react' | ||
import { TesfyProvider, createInstance } from 'react-tesfy' | ||
import Nav from '../components/nav' | ||
|
||
const App = ({ Component, pageProps }) => { | ||
const { datafile = {}, userId } = pageProps | ||
const engine = createInstance({ | ||
datafile, | ||
userId, | ||
}) | ||
|
||
return ( | ||
<TesfyProvider engine={engine}> | ||
<Nav /> | ||
<main> | ||
<p> | ||
Your user identifier is <b>{engine.getUserId()}</b>. Delete{' '} | ||
<b>user_id</b> cookie and refresh to get a new one | ||
</p> | ||
<Component {...pageProps} /> | ||
</main> | ||
</TesfyProvider> | ||
) | ||
} | ||
|
||
export default App |
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,23 @@ | ||
import { Feature } from 'react-tesfy' | ||
import { getTesfyProps } from '../utils' | ||
|
||
export const getServerSideProps = getTesfyProps(async () => { | ||
return { props: {} } | ||
}) | ||
|
||
const FeaturesPage = () => { | ||
return ( | ||
<> | ||
<h1>Features</h1> | ||
|
||
<section> | ||
<h2>Feature 1 - Allocation</h2> | ||
<Feature id="feature-1"> | ||
{(isEnabled) => (isEnabled ? <p>Enabled</p> : <p>Disabled</p>)} | ||
</Feature> | ||
</section> | ||
</> | ||
) | ||
} | ||
|
||
export default FeaturesPage |
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,43 @@ | ||
import { Experiment, Variation } from 'react-tesfy' | ||
import { getTesfyProps } from '../utils' | ||
|
||
export const getServerSideProps = getTesfyProps(async () => { | ||
return { props: {} } | ||
}) | ||
|
||
const IndexPage = () => { | ||
return ( | ||
<> | ||
<h1>Experiments</h1> | ||
|
||
<section> | ||
<h2>Experiment 1 - Allocation</h2> | ||
<Experiment id="experiment-1"> | ||
<Variation> | ||
<p style={{ color: 'yellow' }}>Yellow</p> | ||
</Variation> | ||
<Variation id="0"> | ||
<p style={{ color: 'blue' }}>Blue</p> | ||
</Variation> | ||
<Variation id="1"> | ||
<p style={{ color: 'red' }}>Red</p> | ||
</Variation> | ||
</Experiment> | ||
</section> | ||
|
||
<section> | ||
<h2>Experiment 2 - Audience</h2> | ||
<Experiment id="experiment-2" attributes={{ countryCode: 'us' }}> | ||
<Variation> | ||
<p style={{ fontWeight: 'bold' }}>Bold</p> | ||
</Variation> | ||
<Variation id="0"> | ||
<p>Normal</p> | ||
</Variation> | ||
</Experiment> | ||
</section> | ||
</> | ||
) | ||
} | ||
|
||
export default IndexPage |
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,42 @@ | ||
// Fetch your configuration from tesfy cdn or your own server | ||
const getDatafile = () => { | ||
return { | ||
experiments: { | ||
'experiment-1': { | ||
id: 'experiment-1', | ||
percentage: 90, | ||
variations: [ | ||
{ | ||
id: '0', | ||
percentage: 50, | ||
}, | ||
{ | ||
id: '1', | ||
percentage: 50, | ||
}, | ||
], | ||
}, | ||
'experiment-2': { | ||
id: 'experiment-2', | ||
percentage: 100, | ||
variations: [ | ||
{ | ||
id: '0', | ||
percentage: 100, | ||
}, | ||
], | ||
audience: { | ||
'==': [{ var: 'countryCode' }, 'us'], | ||
}, | ||
}, | ||
}, | ||
features: { | ||
'feature-1': { | ||
id: 'feature-1', | ||
percentage: 50, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
export default getDatafile |
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,13 @@ | ||
import getDatafile from './getDatafile' | ||
import getUserId from './getUserId' | ||
|
||
const getTesfyProps = (getServerSideProps) => async (ctx) => { | ||
const datafile = getDatafile() | ||
const userId = getUserId(ctx) | ||
|
||
const data = (await getServerSideProps(ctx)) || {} | ||
|
||
return { ...data, props: { ...data.props, datafile, userId } } | ||
} | ||
|
||
export default getTesfyProps |
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,31 @@ | ||
import cookie from 'cookie' | ||
import { v4 as uuidv4 } from 'uuid' | ||
|
||
const COOKIE_NAME = 'user_id' | ||
const COOKIE_MAX_AGE = 2147483647 | ||
|
||
const getUserId = (ctx) => { | ||
const { req, res } = ctx | ||
|
||
const cookies = cookie.parse(req.headers.cookie ?? '') | ||
let userId = cookies[COOKIE_NAME] | ||
|
||
if (!userId) { | ||
userId = uuidv4() | ||
res.setHeader( | ||
'Set-Cookie', | ||
cookie.serialize(COOKIE_NAME, userId, { | ||
maxAge: COOKIE_MAX_AGE, | ||
expires: new Date(Date.now() + COOKIE_MAX_AGE), | ||
httpOnly: true, | ||
secure: process.env.NODE_ENV === 'production', | ||
path: '/', | ||
sameSite: 'lax', | ||
}) | ||
) | ||
} | ||
|
||
return userId | ||
} | ||
|
||
export default getUserId |
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,3 @@ | ||
export { default as getDatafile } from './getDatafile' | ||
export { default as getUserId } from './getUserId' | ||
export { default as getTesfyProps } from './getTesfyProps' |