From eb1464d7c351baafdea68c5dd72b13bb9af52bf7 Mon Sep 17 00:00:00 2001 From: James Irwin Date: Mon, 4 Nov 2024 19:11:32 +0100 Subject: [PATCH] move code to the homepage and instructions to Readme. --- contentful/README.md | 119 +++++++++++++- contentful/src/app/[slug]/page.tsx | 81 --------- contentful/src/app/page.tsx | 253 +++++++++-------------------- 3 files changed, 185 insertions(+), 268 deletions(-) delete mode 100644 contentful/src/app/[slug]/page.tsx diff --git a/contentful/README.md b/contentful/README.md index 995fc1b..05e4f5c 100644 --- a/contentful/README.md +++ b/contentful/README.md @@ -1,12 +1,117 @@ -## Getting Started +## Description -First, run the development server: +This is an example front-end app that uses Contentful as its Content Management System. In Contentful, there is a Growthbook app plugin that, once installed, will let your content creators create experiments on their own. This app has an example of how you can take a `Growthbook Experiment` content model and show the right variation to the user. -```bash -npm run dev +## Configuration + +1. **Setting up Growthbook** + + a. First, create a free [GrowthBook Account](https://www.growthbook.io) and add a new SDK Connection, selecting JavaScript (or React) as the language. + + b. Create a new [datasource](https://docs.growthbook.io/warehouses) and connect it to your data warehouse. + + c. Go to **Settings -> API keys** and create a key with an `admin` role. (This is needed for Contentful to connect to Growthbook and add experiments and feature flags.) + +2. **Setting up Contentful** + + a. Next, create a free [Contentful Account](https://www.contentful.com). + + b. Click the gear icon in the upper right and go to **API keys** and create a new API key. (This is used by your front-end app/this example app to connect to Contentful to get the content.) + + c. Also go to the gear icon and click on **CMA tokens** and create a new token. (This is used by a script within this example app to create example Content Models and Content within Contentful without you having to set it up manually.) + + d. Add the Growthbook App to your Contentful Space by going to **Apps -> Marketplace -> A/B tests -> Growthbook** and clicking Install. On the configuration page, enter the URL of your Growthbook instance, the API Key from step 1c, and the Datasource from step 1b. This will create a new content model called `Growthbook Experiment` that you can use to create experiments in Contentful. + +3. **Setting up this example app** + + a. Next, in this example root, copy `.env.local.example` to `.env.local` and fill in the values from your GrowthBook SDK Connection (step 1a), the Contentful API key info (step 2b), and the Contentful CMA token (step 2c). + + b. In the terminal run: `npm run create-contentful-content`. This will create the `Growthbook Example Page` and the `Growthbook Example Product` Content Models in Contentful. It will also create a `Growthbook Example Product Page` and four products as Contentful Content. + + c. Run `npm run dev` and go to the [product page](http://localhost:3000) to see the Contentful content. + +4. **Pretend to be a Content Editor creating a GB A/B test** + + a. In the Contentful app, go to the **Growthbook Example Product Page**, click the three dots next to the **Control** t-shirt in the Products collection, and click **Remove**. + + b. Click **Add content** and select **Growthbook Experiment** as the Content Type. + + c. Enter `Contentful Example` for the Experiment name. + + d. Click "Add Variation" and select the Link an existing variant option. Select the **Control t-shirt**. + + e. Click "Add Variation" and select the Link an existing variant option. Select the **Variant t-shirt**. + + f. The **Create New Experiment** should now be clickable. Click it. + + g. Click start experiment. + + h. Click **publish**. + + i. Click `<-` to go back to the Product Page. + + j. Click **Publish** on the Product Page. + + k. Go to the [product page](http://localhost:3000) to see which content Growthbook has chosen to show. As this example initialized Growthbook with a random targeting attribute id, it pretends each page load is a new user. You can refresh a few times to see Growthbook serve the control t-shirt sometimes and the variant t-shirt other times. + +## How it Works + +This example uses server-side Growthbook. For client-side Growthbook, see the next-js example. + +The code in `src/app/lib/growthbookExperiment.ts` shows the GraphQL to get the **Growthbook Experiment** content model which contains the `featureFlagId` and the `variationsCollection`. In order to get the Variation you can either load the variation content by it's id and \_\_typename in a separate call to Contentful API, or add the fields for each Content Type you want to be able to experiment on like in the example below: + +``` +export const GROWTHBOOK_EXPERIMENT_GRAPHQL_FIELDS = ` + sys { + id + } + featureFlagId + variationsCollection { + items { + sys { + id + } + __typename + ... on GbExampleProduct { + ${PRODUCT_GRAPHQL_FIELDS} + } + } + } +`; +``` + +The `getVariation` function shows how to get the variation from it. Basically, calling `gb.getFeatureValue` with the `featureFlagId` will return the index of the variation to show in the `variationsCollection`. + +``` +export function getVariation( + gb: GrowthBook, + growthbookExperiment: GrowthbookExperimentInterface +) { + const featureFlagId = growthbookExperiment.featureFlagId; + const variationsCollection = growthbookExperiment.variationsCollection; + const index = gb.getFeatureValue(featureFlagId ?? "", 0); + + // This can only happen if the experiment is out of sync with the published version. + if (index > variationsCollection.items.length + 1) { + return variationsCollection.items[0]; + } + const variation = variationsCollection.items[index]; + + return variation; +} +``` + +The `src/app/page.tsx` file contains the Growthbook object and initialization. It also shows how you can convert GrowthbookExperiment to the variation the user should see. + +``` + const items = page.productsCollection.items.map((item) => { + if (item.__typename === "GrowthbookExperiment") { + return getVariation(gb, item); + } + return item; + }); ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +Server-fetched feature flags and experiment definitions are persisted in the Next.js data cache for 60 seconds (configurable in `src/app/lib/growthbookServer.ts`). For faster updates, there is a `POST /revalidate` route handler that can be triggered from an SDK Webhook in GrowthBook. -Follow the instructions on the homepage to create an example for how Content creators can create -Experiments within Contentful all on their own. +Data about which variation the user saw is sent to the client where an analytics event is triggered (or console.log in these examples). This happens via the `GrowthBookTracking` client component defined in `src/app/lib/GrowthBookTracking`. diff --git a/contentful/src/app/[slug]/page.tsx b/contentful/src/app/[slug]/page.tsx deleted file mode 100644 index ae33323..0000000 --- a/contentful/src/app/[slug]/page.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { getPage } from "@/app/lib/page"; -import { ProductInterface } from "@/app/lib/product"; -import { getVariation } from "@/app/lib/growthbookExperiment"; -import { GrowthBook } from "@growthbook/growthbook"; -import { randomUUID } from "crypto"; -import { GrowthBookTracking } from "@/app/lib/growthbookTracking"; - -interface PageProps { - params: { - slug: string; - }; -} - -const Product = ({ product }: { product: ProductInterface }) => ( -
-
-
-

{product.title}

-

{product.description}

-

From ${product.price}

-
-
- {product.title} - - Buy on Growthbook Store - -
-
-
-); - -const Page = async ({ params }: PageProps) => { - const gb = new GrowthBook({ - apiHost: process.env.NEXT_PUBLIC_GROWTHBOOK_API_HOST, - clientKey: process.env.NEXT_PUBLIC_GROWTHBOOK_CLIENT_KEY, - }); - - await gb.init({ timeout: 1000 }); - - // Set targeting attributes for the user - // Normally this is set based on a cookie on logged in user data, but here - // we use a random ID so we can see different variations - await gb.setAttributes({ - id: randomUUID(), - }); - - const { slug } = await params; - - const page = await getPage(slug); - - if (!page) { - return
Page not found
; - } - - const items = page.productsCollection.items.map((item) => { - if (item.__typename === "GrowthbookExperiment") { - return getVariation(gb, item); - } - return item; - }); - - // If the code above ran any experiments, get the tracking call data - // This is passed into the client component below - const trackingData = gb.getDeferredTrackingCalls(); - - return ( -
-

{page.title}

-

{page.description}

-

Products

-
- {items.map((item) => ( - - ))} -
- -
- ); -}; - -export default Page; diff --git a/contentful/src/app/page.tsx b/contentful/src/app/page.tsx index e441395..6e7f8ea 100644 --- a/contentful/src/app/page.tsx +++ b/contentful/src/app/page.tsx @@ -1,185 +1,78 @@ -import Link from "next/link"; +import { getPage } from "@/app/lib/page"; +import { ProductInterface } from "@/app/lib/product"; +import { getVariation } from "@/app/lib/growthbookExperiment"; +import { GrowthBook } from "@growthbook/growthbook"; +import { randomUUID } from "crypto"; +import { GrowthBookTracking } from "@/app/lib/growthbookTracking"; + +const Product = ({ product }: { product: ProductInterface }) => ( +
+
+
+

{product.title}

+

{product.description}

+

From ${product.price}

+
+
+ {product.title} + + Buy on Growthbook Store + +
+
+
+); + +export default async function Home() { + const slug = "products"; + + const page = await getPage(slug); + + if (!page) { + return ( +
+ Product page not found. Run `npm run create-contentful-content` to add + the exaple Growthbook Page to your Contentful space. +
+ ); + } + + const gb = new GrowthBook({ + apiHost: process.env.NEXT_PUBLIC_GROWTHBOOK_API_HOST, + clientKey: process.env.NEXT_PUBLIC_GROWTHBOOK_CLIENT_KEY, + }); + + await gb.init({ timeout: 1000 }); + + // Set targeting attributes for the user + // Normally this is set based on a cookie on logged in user data, but here + // we use a random ID so we can see different variations + await gb.setAttributes({ + id: randomUUID(), + }); + + const items = page.productsCollection.items.map((item) => { + if (item.__typename === "GrowthbookExperiment") { + return getVariation(gb, item); + } + return item; + }); + + // If the code above ran any experiments, get the tracking call data + // This is passed into the client component below + const trackingData = gb.getDeferredTrackingCalls(); -export default function Home() { return ( -
-
-

Description

-

- This is an example front-end app that uses Contentful as it's Content - Management System. In Contentful there is a Growthbook app plugin that - once installed, will let your content creators create experiments on - their own. This app has an example of how you can take a `Growthbook - Experiment` content model and show the right variation to the user. -

-

Configuration

-
-
    -
  1. Setting up Growthbook
  2. -
      -
    1. - First, create a free{" "} - - GrowthBook Account - {" "} - and add a new SDK Connection, selecting JavaScript (or React) as - the language. -
    2. -
    3. - {" "} - Create a new{" "} - - datasource - {" "} - and connect it to your data warehouse -
    4. -
    5. - {" "} - Go to Settings->API keys and create a key with an - `admin` role. (This is needed for Contentful to connect to - Growthbook and add experiments and feature flags.) -
    6. -
    -
  3. Setting up Contentful
  4. -
      -
    1. - Next, create a free{" "} - - Contentful Account - {" "} -
    2. -
    3. - Click the gear icon in the upper right and go to API keys{" "} - and create a new API key. (This is used by your front-end - app/this example app to connect to Contentful to get the - content.) -
    4. -
    5. - Also go to the gear icon and click on CMA tokens and - create a new token. (This is used by a script within this - example app to create example Content Models and Content within - Contentful without you having to set it up manually.) -
    6. -
    7. - Add the Growthbook App to your Contentful Space by going to{" "} -
      Apps->Marketplace->A/B tests->Growthbook{" "} - and clicking Install. -
      On the configuration page, enter the URL of your - Growthbook instance, the Api Key from step 1c. and the - Datasource from step 1b. This will create a new content model - called Growthbook Experiment that you can use to - create experiments in Contentful. -
    8. -
    -
  5. Setting up this example app
  6. -
      -
    1. - Next, in this example root, copy .env.local.example{" "} - to .env.local and fill in the values from your - GrowthBook SDK Connection (step 1a), the Contentful API key info - (step 2b), and the Contentful CMA token (step 2c). -
    2. -
    3. - In the terminal run:{" "} - npm run create-contentful-content. This will create - the Growthbook Example Page and the{" "} - Growthbook Example Product Content Models in - Contentful. It will also create a{" "} - Growthbook Example Product Page and four products - as Contentful Content. -
    4. -
    5. - Run npm run dev and go to the{" "} - - product page - {" "} - to see the contentful content. -
    6. -
    -
  7. Pretend to be a Content Editor creating a GB A/B test.
  8. -
      -
    1. - In the Contentful app go to the{" "} - Growthbook Example Product Page click the three dots next - to the Control t-shirt in the Products collection and - click Remove. -
    2. -
    3. - Click Add content and select Growthbook Experiment{" "} - as the Content Type.{" "} -
    4. -
    5. - Enter Contentful Example for the Experiment name. -
    6. -
    7. - Click "Add Variation" and select the Link an existing variant - option. Select the Control t-shirt -
    8. -
    9. - Click "Add Variation" and select the Link an existing variant - option. Select the Variant t-shirt -
    10. -
    11. - The Create New Experiment should now be clickable. Click - it. -
    12. -
    13. Click start experiment.
    14. -
    15. - Click publish. -
    16. -
    17. Click <- to go back to the Product Page
    18. -
    19. - Click Publish on the Product Page -
    20. -
    21. - Go to the{" "} - - product page - {" "} - to see which content Growthbook has chosen to show. As this - example initialized growthbook with a random targeting attribute - id, it pretends each page load is a new user. You can refresh a - few times to see Growthbook serve the control t-shirt sometimes - and the variant t-shirt the other times. -
    22. -
    -
-
-
-

How it Works

-

- This example uses server-side Growthbook. For client-side growthbook - see the next-js example. -

-

- The code in src/app/lib/growthbookExperiment.ts shows - the GraphQL to get the Growthbook Experiment content model - which contains the featureFlagId and the{" "} - variationsCollection. The getVariation{" "} - function show how to get the variation from it. Basically calling - gb.getFeatureValue with the featureFlagId will return - the index of the variation to show in the{" "} - variationsCollection. -

-

- The src/app/pages/[slug]/page.tsx file contains the - growthbook object and initialization. -

-

- Server-fetched feature flags and experiment definitions are - persisted in the Next.js data cache for 60 seconds (configurable in{" "} - src/app/lib/growthbookServer.ts). For faster updates, - there is a POST /revalidate route handler that can be - triggered from an SDK Webhook in GrowthBook. -

-

- Data about which variation the user saw is sent to the client where - an analytics event is triggered (or console.log in these examples). - This happens via the GrowthBookTracking client - component defined in src/app/lib/GrowthBookTracking. -

-
+
+

{page.title}

+

{page.description}

+

Products

+
+ {items.map((item) => ( + + ))}
-
+ + ); }