Skip to content

Commit 2095d84

Browse files
authored
add openfeature (#1113)
Adds an example for the OpenFeature Flags SDK adapter.
1 parent 85082af commit 2095d84

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+4535
-1
lines changed

flags-sdk/experimentation-statsig/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ This demo uses two feature gates on Statsig to control the visibility of two ban
1212
Both gates are configured to show/hide each banner 50% of the time.
1313

1414
Once you visit the page, you can see a variation of both/one/none of the banners.
15-
Since this example is using a stable id to identify users, you will see the same variation all the time.
15+
Since this example is using a stable id to identify users, you will see the same variation until you reset your id.
1616

1717
To test different variations, you can use the Dev Tools at the bottom to reset the stable id and reload the page.
1818
This allows you to test different variations of the banners.

flags-sdk/openfeature/.eslintrc.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"root": true,
3+
"extends": "next/core-web-vitals",
4+
"rules": {
5+
"@typescript-eslint/require-await": "off",
6+
"@typescript-eslint/no-misused-promises": "off",
7+
"import/order": "off",
8+
"camelcase": "off",
9+
"no-console": "off"
10+
}
11+
}

flags-sdk/openfeature/.gitignore

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
.yarn/install-state.gz
8+
9+
# testing
10+
/coverage
11+
12+
# next.js
13+
/.next/
14+
/out/
15+
16+
# production
17+
/build
18+
19+
# misc
20+
.DS_Store
21+
*.pem
22+
23+
# debug
24+
npm-debug.log*
25+
yarn-debug.log*
26+
yarn-error.log*
27+
28+
# local env files
29+
.env*.local
30+
31+
# vercel
32+
.vercel
33+
34+
# typescript
35+
*.tsbuildinfo
36+
next-env.d.ts

flags-sdk/openfeature/README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# OpenFeature Flags SDK Example
2+
3+
This example uses [OpenFeature](https://openfeature.dev/) for feature flags with the [Flags SDK](https://flags-sdk.dev) and the [Flags SDK OpenFeature adapter](https://flags-sdk.dev/docs/api-reference/adapters/openfeature) along with the [Flags Explorer](https://vercel.com/docs/workflow-collaboration/feature-flags/using-vercel-toolbar).
4+
5+
## Demo
6+
7+
[https://flags-sdk-openfeature.vercel.app/](https://flags-sdk-openfeature.vercel.app/)
8+
9+
## How it works
10+
11+
This demo uses two feature flags defined in code control the visibility of two banners on the page.
12+
Both flags are configured to show/hide each banner 50% of the time.
13+
14+
Once you visit the page, you can see a variation of both/one/none of the banners.
15+
Since this example is using a stable id to identify users, you will see the same variation until you reset your id.
16+
17+
To test different variations, you can use the Dev Tools at the bottom to reset the stable id and reload the page.
18+
This allows you to test different variations of the banners.
19+
20+
If you deployed your own instance of this example you can also use the [Flags Explorer](https://vercel.com/docs/workflow-collaboration/feature-flags/using-vercel-toolbar) to test different variations by creating overrides.
21+
22+
## Deploy this template
23+
24+
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fexamples%2Ftree%2Fmain%2Fflags-sdk%2Fopenfeature&env=FLAGS_SECRET&envDescription=The+FLAGS_SECRET+will+be+used+by+the+Flags+Explorer+to+securely+overwrite+feature+flags.+Must+be+32+random+bytes%2C+base64-encoded.+Use+the+generated+value+or+set+your+own.&envLink=https%3A%2F%2Fvercel.com%2Fdocs%2Fworkflow-collaboration%2Ffeature-flags%2Fsupporting-feature-flags%23flags_secret-environment-variable&project-name=openfeature-flags-sdk-example&repository-name=openfeature-flags-sdk-example&)
25+
26+
### Step 1: Link the project
27+
28+
In order to use the Flags Explorer, you need to link the project on your local machine.
29+
30+
```bash
31+
vercel link
32+
```
33+
34+
Select the project from the list you just deployed.
35+
36+
### Step 2: Pull all environment variables
37+
38+
This allows the Flags SDK and the Flags Explorer to work correctly, by getting additional metadata.
39+
40+
```bash
41+
vercel env pull
42+
```
43+
44+
### Step 3: Install dependencies
45+
46+
```bash
47+
npm install
48+
```
49+
50+
### Step 4: Run the project
51+
52+
```bash
53+
npm run dev
54+
```
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { type ApiData, verifyAccess } from 'flags'
2+
import { getProviderData } from 'flags/next'
3+
import { NextResponse, type NextRequest } from 'next/server'
4+
import * as flags from '../../../../flags'
5+
6+
export const runtime = 'edge'
7+
export const dynamic = 'force-dynamic' // defaults to auto
8+
9+
export async function GET(request: NextRequest) {
10+
const access = await verifyAccess(request.headers.get('Authorization'))
11+
if (!access) return NextResponse.json(null, { status: 401 })
12+
13+
// Forward info from Flags in Code
14+
const providerData = await getProviderData(flags)
15+
return NextResponse.json<ApiData>(providerData)
16+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use client'
2+
3+
import { track } from '@vercel/analytics'
4+
import { useEffect, useState } from 'react'
5+
import { useRouter } from 'next/navigation'
6+
import { addToCart } from '@/lib/actions'
7+
import { useProductDetailPageContext } from '@/components/utils/product-detail-page-context'
8+
import { AddToCartButton } from '@/components/product-detail-page/add-to-cart-button'
9+
10+
export function AddToCart() {
11+
const router = useRouter()
12+
const { color, size } = useProductDetailPageContext()
13+
const [isLoading, setIsLoading] = useState(false)
14+
15+
useEffect(() => {
16+
track('add_to_cart:viewed')
17+
}, [])
18+
19+
return (
20+
<AddToCartButton
21+
isLoading={isLoading}
22+
onClick={async () => {
23+
setIsLoading(true)
24+
track('add_to_cart:clicked')
25+
await addToCart({ id: 'shirt', color, size, quantity: 1 })
26+
router.push('/cart')
27+
}}
28+
/>
29+
)
30+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { proceedToCheckoutColorFlag } from '@/flags'
2+
import { OrderSummarySection } from '@/components/shopping-cart/order-summary-section'
3+
import { ProceedToCheckout } from './proceed-to-checkout'
4+
5+
export async function OrderSummary({
6+
showSummerBanner,
7+
freeDelivery,
8+
}: {
9+
showSummerBanner: boolean
10+
freeDelivery: boolean
11+
}) {
12+
// This is a fast feature flag so we don't suspend on it
13+
const proceedToCheckoutColor = await proceedToCheckoutColorFlag()
14+
15+
return (
16+
<OrderSummarySection
17+
showSummerBanner={showSummerBanner}
18+
freeDelivery={freeDelivery}
19+
proceedToCheckout={<ProceedToCheckout color={proceedToCheckoutColor} />}
20+
/>
21+
)
22+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { OrderSummary } from '@/app/[code]/cart/order-summary'
2+
import { Main } from '@/components/main'
3+
import { ShoppingCart } from '@/components/shopping-cart/shopping-cart'
4+
import {
5+
productFlags,
6+
showFreeDeliveryBannerFlag,
7+
showSummerBannerFlag,
8+
} from '@/flags'
9+
10+
export default async function CartPage({
11+
params,
12+
}: {
13+
params: Promise<{ code: string }>
14+
}) {
15+
const { code } = await params
16+
const showSummerBanner = await showSummerBannerFlag(code, productFlags)
17+
const freeDeliveryBanner = await showFreeDeliveryBannerFlag(
18+
code,
19+
productFlags
20+
)
21+
22+
return (
23+
<Main>
24+
<div className="lg:grid lg:grid-cols-12 lg:items-start lg:gap-x-12 xl:gap-x-16">
25+
<ShoppingCart />
26+
<OrderSummary
27+
showSummerBanner={showSummerBanner}
28+
freeDelivery={freeDeliveryBanner}
29+
/>
30+
</div>
31+
</Main>
32+
)
33+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use client'
2+
3+
import { ProceedToCheckoutButton } from '@/components/shopping-cart/proceed-to-checkout-button'
4+
import { track } from '@vercel/analytics'
5+
import { toast } from 'sonner'
6+
7+
export function ProceedToCheckout({ color }: { color: string }) {
8+
return (
9+
<ProceedToCheckoutButton
10+
color={color}
11+
onClick={() => {
12+
track('proceed_to_checkout:clicked')
13+
toast('End reached', {
14+
className: 'my-classname',
15+
description: 'The checkout flow is not implemented in this template.',
16+
duration: 5000,
17+
})
18+
}}
19+
/>
20+
)
21+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { deserialize, generatePermutations } from 'flags/next'
2+
import { FlagValues } from 'flags/react'
3+
import { productFlags, showFreeDeliveryBannerFlag } from '@/flags'
4+
import { FreeDelivery } from '@/app/free-delivery'
5+
import { DevTools } from '@/components/dev-tools'
6+
import { Footer } from '@/components/footer'
7+
import { Navigation } from '@/components/navigation'
8+
9+
export async function generateStaticParams() {
10+
// Returning an empty array here is important as it enables ISR, so
11+
// the various combinations stay cached after they first time they were rendered.
12+
//
13+
// return [];
14+
15+
// Instead of returning an empty array you could also call generatePermutations
16+
// to generate the permutations upfront.
17+
const codes = await generatePermutations(productFlags)
18+
return codes.map((code) => ({ code }))
19+
}
20+
21+
export default async function Layout(props: {
22+
children: React.ReactNode
23+
params: Promise<{
24+
code: string
25+
}>
26+
}) {
27+
const params = await props.params
28+
const values = await deserialize(productFlags, params.code)
29+
30+
const showFreeDeliveryBanner = await showFreeDeliveryBannerFlag(
31+
params.code,
32+
productFlags
33+
)
34+
35+
return (
36+
<div className="bg-white">
37+
<FreeDelivery show={showFreeDeliveryBanner} />
38+
<Navigation />
39+
{props.children}
40+
<FlagValues values={values} />
41+
<Footer />
42+
<DevTools />
43+
</div>
44+
)
45+
}

0 commit comments

Comments
 (0)