Skip to content
Closed
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
4 changes: 2 additions & 2 deletions apps/docs/app/.well-known/vercel/flags/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createFlagsDiscoveryEndpoint, getProviderData } from 'flags/next';
import * as flags from '../../../../flags';
import { rootFlags } from '../../../../flags';

export const GET = createFlagsDiscoveryEndpoint(async () => {
return getProviderData(flags);
return getProviderData({ rootFlags });
});
26 changes: 26 additions & 0 deletions apps/docs/app/[lang]/home/[code]/components/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use client';

import { track } from '@vercel/analytics';

export function AndyButton() {
return (
<button
type="button"
onClick={() => {
let visitorId = '';

const cookies = document.cookie.split(';');
cookies.forEach((str) => {
const [key, value] = str.split('=');
if (key === 'visitorId') {
visitorId = value;
}
});

track('purchase', { visitorId });
}}
>
Andy's Button
</button>
);
}
27 changes: 20 additions & 7 deletions apps/docs/app/[lang]/home/[code]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { FlagValues } from 'flags/react';
import type { Metadata } from 'next';
import Link from 'next/link';
import {
andyCheckoutFlag,
enableBannerFlag,
enableDitheredHeroFlag,
enableHeroTextFlag,
Expand All @@ -21,6 +22,7 @@ import {
} from '@/flags';
import { config } from '@/lib/geistdocs/config';
import { buildHomeMetadata } from '@/lib/site/home-metadata';
import { AndyButton } from './components/button';
import HeroImage from './components/hero-image';
import { Adaptable, Effortless, Flexible } from './components/illustrations';
import Testimonials from './components/testimonials';
Expand Down Expand Up @@ -93,13 +95,19 @@ export default async function HomePage({
params: Promise<{ code: string }>;
}) {
const { code } = await params;
const [bannerFlag, ditheredHeroFlag, heroTextFlag, installAudience] =
await Promise.all([
enableBannerFlag(code, rootFlags),
enableDitheredHeroFlag(code, rootFlags),
enableHeroTextFlag(code, rootFlags),
installAudienceFlag(code, rootFlags),
]);
const [
bannerFlag,
ditheredHeroFlag,
heroTextFlag,
installAudience,
checkoutFlag,
] = await Promise.all([
enableBannerFlag(code, rootFlags),
enableDitheredHeroFlag(code, rootFlags),
enableHeroTextFlag(code, rootFlags),
installAudienceFlag(code, rootFlags),
andyCheckoutFlag(code, rootFlags),
]);

return (
<div className="mx-auto w-full max-w-[1448px] px-4 sm:px-6">
Expand All @@ -109,11 +117,16 @@ export default async function HomePage({
[enableDitheredHeroFlag.key]: ditheredHeroFlag,
[enableHeroTextFlag.key]: heroTextFlag,
[installAudienceFlag.key]: installAudience,
[andyCheckoutFlag.key]: checkoutFlag,
}}
/>

{/* Hero */}

{checkoutFlag ? "Andy's flag is on" : "Andy's flag is off"}

<AndyButton />

<section className="mt-(--fd-nav-height) grid grid-cols-12 gap-y-12 pt-16 pb-16 sm:pt-40 lg:gap-x-12">
<div className="col-span-12 flex flex-col justify-center lg:col-span-7">
<h1 className="text-balance text-heading-40 sm:text-heading-48 xl:text-heading-64 max-w-md">
Expand Down
77 changes: 77 additions & 0 deletions apps/docs/flags.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,81 @@
import { createVercelAdapter } from '@flags-sdk/vercel';
import { trackExposure } from '@vercel/analytics/server';
import {
createClient,
type experimental_Exposure as Exposure,
} from '@vercel/flags-core';
import { flag } from 'flags/next';

export const flagsClient = createClient({
experimental_reportExposures: reportExposures,
});

export const vercelAdapter = createVercelAdapter(flagsClient);

function getUnitValue(
entity: unknown,
base: Exposure['base'],
): string | undefined {
const value = base.reduce<unknown>((current, key) => {
if (typeof current !== 'object' || current === null || !(key in current)) {
return undefined;
}

return (current as Record<PropertyKey, unknown>)[key];
}, entity);

return typeof value === 'string' ? value : undefined;
}

/**
* Reports Vercel Experiments exposures to Web Analytics.
*
* Pass it as `experimental_reportExposures` to `createClient` from
* `@vercel/flags-core`. The exposure unit is resolved from the `identify()`
* entity using the experiment's base path, so conversion events only need to
* carry the same unit (for example `visitorId`) to be joined to exposures.
*/
async function reportExposures(
exposures: readonly Exposure[],
entity: any,
): Promise<void> {
await Promise.all(
exposures.map((exposure) => {
const unitValue = getUnitValue(entity, exposure.base);

if (unitValue === undefined) {
return Promise.resolve();
}

return trackExposure({
experimentId: exposure.experimentId,
variantId: exposure.variantId ?? 'override',
unitKey: 'event_data.visitorId',
unitValue: entity.visitor.id,
assignmentReason: exposure.assignmentReason,
rampId: exposure.rampId,
rampPercentage: exposure.rampPercentage,
});
}),
);
}

export const andyCheckoutFlag = flag({
adapter: vercelAdapter,
key: 'andy-checkout-flag',
options: [false, true],
defaultValue: false,
identify({ cookies }) {
return {
visitor: {
id: cookies.get('visitorId')?.value ?? '',
},
};
},
});

// ================================

export const enableBannerFlag = flag({
key: 'enable-banner-flag',
description: 'Full-width callout at the top',
Expand Down Expand Up @@ -53,4 +129,5 @@ export const rootFlags = [
enableHeroTextFlag,
enableDitheredHeroFlag,
installAudienceFlag,
andyCheckoutFlag,
] as const;
4 changes: 3 additions & 1 deletion apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
"translate": "npx @vercel/geistdocs translate"
},
"dependencies": {
"@vercel/analytics": "^1.6.1",
"@flags-sdk/vercel": "1.5.1",
"@vercel/analytics": "2.1.0-canary.5",
"@vercel/flags-core": "1.10.0",
"@vercel/geistdocs": "2.9.2",
"@vercel/speed-insights": "^1.3.1",
"@vercel/toolbar": "0.2.7",
Expand Down
4 changes: 1 addition & 3 deletions packages/adapter-posthog/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ const postHogClientMock = {
vi.mock('posthog-node', () => ({
// PostHog is called with `new`, so the implementation must be a function
// rather than an arrow — Vitest 4 throws "is not a constructor" otherwise.
PostHog: vi.fn(function () {
return postHogClientMock;
}),
PostHog: vi.fn(() => postHogClientMock),
}));

const headers = {} as ReadonlyHeaders;
Expand Down
Loading
Loading