Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add A/B Tests and Feature Flags example #13629

Merged
merged 9 commits into from
Jun 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/cms-agilitycms/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Once you have access to [the environment variables you'll need](#step-15-set-up-
Execute [`create-next-app`](https://github.com/zeit/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
npm init next-app --example cms-agilitycms cms-agilitycms-app
npx create-next-app --example cms-agilitycms cms-agilitycms-app
# or
yarn create next-app --example cms-agilitycms cms-agilitycms-app
```
Expand Down
2 changes: 1 addition & 1 deletion examples/cms-cosmic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Once you have access to [the environment variables you'll need](#step-3-set-up-e
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
npm init next-app --example cms-cosmic cms-cosmic-app
npx create-next-app --example cms-cosmic cms-cosmic-app
# or
yarn create next-app --example cms-cosmic cms-cosmic-app
```
Expand Down
2 changes: 1 addition & 1 deletion examples/cms-graphcms/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Once you have access to [the environment variables you'll need](#step-3-set-up-e
Execute [`create-next-app`](https://github.com/zeit/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
npm init next-app --example cms-graphcms cms-graphcms-app
npx create-next-app --example cms-graphcms cms-graphcms-app
# or
yarn create next-app --example cms-graphcms cms-graphcms-app
```
Expand Down
50 changes: 50 additions & 0 deletions examples/with-tesfy/README.md
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)).
16 changes: 16 additions & 0 deletions examples/with-tesfy/components/nav.js
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
19 changes: 19 additions & 0 deletions examples/with-tesfy/package.json
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"
}
26 changes: 26 additions & 0 deletions examples/with-tesfy/pages/_app.js
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
23 changes: 23 additions & 0 deletions examples/with-tesfy/pages/features.js
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
43 changes: 43 additions & 0 deletions examples/with-tesfy/pages/index.js
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
42 changes: 42 additions & 0 deletions examples/with-tesfy/utils/getDatafile.js
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
13 changes: 13 additions & 0 deletions examples/with-tesfy/utils/getTesfyProps.js
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
31 changes: 31 additions & 0 deletions examples/with-tesfy/utils/getUserId.js
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
3 changes: 3 additions & 0 deletions examples/with-tesfy/utils/index.js
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'