Skip to content

Commit

Permalink
Add PostHog feature flag example
Browse files Browse the repository at this point in the history
  • Loading branch information
leerob committed Oct 29, 2021
2 parents 6f1f3bf + a5fc5d1 commit e660a99
Show file tree
Hide file tree
Showing 27 changed files with 11,237 additions and 0 deletions.
6 changes: 6 additions & 0 deletions edge-functions/feature-flag-posthog/.env.example
@@ -0,0 +1,6 @@
# This is the PostHog Project API Key
NEXT_PUBLIC_POSTHOG_PROJECT_API_KEY=

# This is the PostHog host.
# For PostHog cloud this would be https://app.posthog.com
NEXT_PUBLIC_POSTHOG_HOST=
34 changes: 34 additions & 0 deletions edge-functions/feature-flag-posthog/.gitignore
@@ -0,0 +1,34 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

# vercel
.vercel
43 changes: 43 additions & 0 deletions edge-functions/feature-flag-posthog/README.md
@@ -0,0 +1,43 @@
# A/B Testing with PostHog

[PostHog](https://posthog.com/) is an open-source product analytics platform that offers a suite of tools, including funnels, heat maps, session recording, feature flags and more, all in a single platform.

## Demo

https://edge-functions-feature-flag-posthog.vercel.sh

### One-Click Deploy

Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme):

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/examples/tree/main/edge-functions/feature-flag-posthog&env=NEXT_PUBLIC_POSTHOG_PROJECT_API_KEY,NEXT_PUBLIC_POSTHOG_HOST&project-name=feature-flag-posthog&repository-name=feature-flag-posthog)

## Getting Started

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 https://github.com/vercel/examples/tree/main/edge-functions/feature-flag-posthog feature-flag-posthog
# or
yarn create next-app --example https://github.com/vercel/examples/tree/main/edge-functions/feature-flag-posthog feature-flag-posthog
```

You'll need to have an account with [PostHog](https://posthog.com/signup). Once that's done, copy the `.env.example` file in this directory to `.env.local` (which will be ignored by Git):

```bash
cp .env.example .env.local
```

Then open `.env.local` and set the environment variables to match the host for your PostHog instance and the Project API key available under Project settings - Project API Key.

Next, run Next.js in development mode:

```bash
npm install
npm run dev

# or

yarn
yarn dev
```
12 changes: 12 additions & 0 deletions edge-functions/feature-flag-posthog/lib/constants.ts
@@ -0,0 +1,12 @@
/**
* List of known active Feature Flags
*/
export const FEATURE_FLAGS = {
NEW_ABOUT_PAGE: 'New_About_Page',
NEW_MARKETING_PAGE: 'New_Marketing_Page',
NEW_PRODUCT_PAGE: 'New_Product_Page',
} as const

export type FEATURE_FLAGS = typeof FEATURE_FLAGS[keyof typeof FEATURE_FLAGS]

export const DISTINCT_ID_COOKIE_NAME = 'distinct_id'
62 changes: 62 additions & 0 deletions edge-functions/feature-flag-posthog/lib/posthog-node.ts
@@ -0,0 +1,62 @@
import { FEATURE_FLAGS } from './constants'

/**
* Checks if a feature flag is enabled.
*
* @param distinctUserId A unique identifier for the user
* @param featureName The name of the feature flag
* @returns true if the feature flag is enabled. Otherwise, false.
*/
export async function isFeatureFlagEnabled(
distinctUserId: string,
featureName: FEATURE_FLAGS
): Promise<boolean> {
console.log('isFeatureEnabled', distinctUserId, featureName)

const featureFlagValue = await getFeatureFlagVariant(
distinctUserId,
featureName
)

const featureEnabled = featureFlagValue ? true : false
console.log('featureEnabled', featureEnabled)

return featureEnabled
}

/**
* Retrieves the value of the feature flag.
*
* @param distinctUserId A unique identifier for the user
* @param featureName The name of the feature flag
* @returns If the feature flag is an A/B test, then the value may be true or undefined.
* If the feature flag is a multvariate, then the value will be a string
*/
export async function getFeatureFlagVariant(
distinctUserId: string,
featureName: FEATURE_FLAGS
): Promise<string | boolean | undefined> {
console.log('getFeatureFlagVariant', distinctUserId, featureName)

const res = await fetch(
`${process.env.NEXT_PUBLIC_POSTHOG_HOST}/decide?v=2`,
{
method: 'POST',
body: JSON.stringify({
api_key: process.env.NEXT_PUBLIC_POSTHOG_PROJECT_API_KEY,
distinct_id: distinctUserId,
}),
}
)

if (!res.ok) {
throw new Error(
`Fetch request to retrieve the ${featureName} flag status failed with: (${res.status}) ${res.statusText}`
)
}

const data = await res.json()
console.log(data)

return data.featureFlags[featureName]
}
47 changes: 47 additions & 0 deletions edge-functions/feature-flag-posthog/lib/posthog.ts
@@ -0,0 +1,47 @@
import { useEffect } from 'react'
import { useRouter } from 'next/router'
import posthog, { PostHog } from 'posthog-js'

let POSTHOG_INSTANCE: PostHog

export const usePostHog = (
apiKey: string,
config?: posthog.Config,
name?: string
): void => {
const router = useRouter()

if (config.loaded) {
// override the existing loaded function so we can store a reference
// to the PostHog instance
const oldLoaded = config.loaded
config.loaded = (posthog: PostHog) => {
setPostHogInstance(posthog)
oldLoaded(POSTHOG_INSTANCE)
}
} else {
config.loaded = setPostHogInstance
}

useEffect((): (() => void) => {
if (typeof window === undefined) return

posthog.init(apiKey, config, name)

// Track page views
const handleRouteChange = () => posthog.capture('$pageview')
router.events.on('routeChangeComplete', handleRouteChange)

return () => {
router.events.off('routeChangeComplete', handleRouteChange)
}
}, [router.events])
}

const setPostHogInstance = (posthog: PostHog) => {
POSTHOG_INSTANCE = posthog
}

export const getPostHogInstance = (): PostHog => {
return POSTHOG_INSTANCE
}
6 changes: 6 additions & 0 deletions edge-functions/feature-flag-posthog/next-env.d.ts
@@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
3 changes: 3 additions & 0 deletions edge-functions/feature-flag-posthog/next.config.js
@@ -0,0 +1,3 @@
const withTM = require('@vercel/edge-functions-ui/transpile')()

module.exports = withTM()

1 comment on commit e660a99

@vercel
Copy link

@vercel vercel bot commented on e660a99 Oct 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

edge-functions-news – ./edge-functions/power-parity-pricing-strategies

edge-functions-news-git-main.vercel.sh
edge-functions-news.vercel.sh

Please sign in to comment.