diff --git a/ab-testing/README.md b/ab-testing/README.md index af5ea58ec72..f56fc3c5335 100644 --- a/ab-testing/README.md +++ b/ab-testing/README.md @@ -74,3 +74,5 @@ The algorithm allocates tests available MVT IDs based on the audience size and s However, the allocation is completely separate for each audience space, so if you have a test in space `A` and move it to space `B`, it will be allocated different MVT IDs. The state of the AB tests is stored in Fastly dictionaries, which are updated when the `deploy` task is run. Logic in fastly VCL will then use these dictionaries to determine which users are in which test groups and set appropriate headers and/or cookies. + +See the [fastly-edge-cache documentation](https://github.com/guardian/fastly-edge-cache/blob/main/theguardiancom/docs/ab-testing.md) for even more details. diff --git a/ab-testing/frontend/src/lib/components/AudienceBreakdown.svelte b/ab-testing/frontend/src/lib/components/AudienceBreakdown.svelte index f59f29fa6ed..474185c0627 100644 --- a/ab-testing/frontend/src/lib/components/AudienceBreakdown.svelte +++ b/ab-testing/frontend/src/lib/components/AudienceBreakdown.svelte @@ -17,11 +17,13 @@ const BAR_HEIGHT = 40; - // Account for legend bar and vertical padding in chart height - const chartHeight = tests.length * BAR_HEIGHT + BAR_HEIGHT + 16; + const BAR_MARGIN_X = 0.1; + const BAR_MARGIN_Y = 2; const testSpaces = ['A', 'B', 'C']; + const chartHeight = testSpaces.length * BAR_HEIGHT + BAR_HEIGHT + 16; + const testsBySpace = testSpaces.map((space) => { if (space === 'A') { return tests.filter( @@ -33,32 +35,32 @@ }); function getBars(testList: ABTest[], rowPosition: number) { - return testList.reduce>( - (barsList, test, index) => { - const previousBar = barsList.slice(-1)[0]; - const offset: number = Number(previousBar?.width ?? 0); - const rowYLevel = index + rowPosition; - const testSize = getSize(test); - - return [ - ...barsList, - { - x: offset, - y: rowYLevel * BAR_HEIGHT + BAR_HEIGHT, - width: testSize, - name: test.name, - segments: `${offset}% to ${offset + testSize}%`, - }, - ]; - }, - [], - ); + return testList.reduce>((barsList, test) => { + const previousBarsWidth = barsList.reduce( + (acc, bar) => acc + bar.width, + 0, + ); + const offset: number = Number(previousBarsWidth); + const rowYLevel = rowPosition; + const testSize = getSize(test); + + return [ + ...barsList, + { + x: offset, + y: rowYLevel * BAR_HEIGHT + BAR_HEIGHT, + width: testSize, + name: test.name, + segments: `${offset}% to ${offset + testSize}%`, + }, + ]; + }, []); } function getAllRows(testsBySpace: ABTest[][]) { return testsBySpace.reduce>( - (barsList, testsInSpace) => { - return [...barsList, ...getBars(testsInSpace, barsList.length)]; + (barsList, testsInSpace, i) => { + return [...barsList, ...getBars(testsInSpace, i)]; }, [], ); @@ -86,13 +88,13 @@ {#each getAllRows(testsBySpace) as bar} - + {bar.name} {bar.segments} diff --git a/ab-testing/frontend/src/lib/components/TableFixed.svelte b/ab-testing/frontend/src/lib/components/TableFixed.svelte index ea298038cf2..7aa24aecc57 100644 --- a/ab-testing/frontend/src/lib/components/TableFixed.svelte +++ b/ab-testing/frontend/src/lib/components/TableFixed.svelte @@ -12,7 +12,8 @@ function daysToExpiry(expires: string) { const today = new Date(); const expiresDate = new Date(expires); - const differenceInMilliseconds = expiresDate.getTime() - today.getTime(); + const differenceInMilliseconds = + expiresDate.getTime() - today.getTime(); const differenceInDays = differenceInMilliseconds / (1000 * 60 * 60 * 24); return Math.floor(differenceInDays); @@ -21,36 +22,58 @@
{#each tests as test} + {@const expired = daysToExpiry(test.expirationDate) < 0}
+ + + + + + + + - + - - - + + - + - - +
Name StateVariantsTest Groups Expires In AudienceOffset Ophan
{test.name}{test.status}{test.name} ({test.type}) + {#if expired} + EXPIRED + {:else} + {test.status} + {/if} + {daysToExpiry(test.expirationDate)} days{daysToExpiry(test.expirationDate)} days {test.audienceSize * 100}%0
Description{test.description}{test.description}
@@ -58,6 +81,10 @@ diff --git a/ab-testing/frontend/src/lib/components/TestVariants.svelte b/ab-testing/frontend/src/lib/components/TestVariants.svelte index db455470daf..de1cbb8a8d7 100644 --- a/ab-testing/frontend/src/lib/components/TestVariants.svelte +++ b/ab-testing/frontend/src/lib/components/TestVariants.svelte @@ -2,20 +2,29 @@ interface Props { testName: string; testGroups: string[]; + size: number; } - const { testName, testGroups }: Props = $props(); + const { testName, testGroups, size }: Props = $props(); + + const formatter = new Intl.NumberFormat('en-US', { + style: 'percent', + minimumFractionDigits: 0, + maximumFractionDigits: 2, + });
    - {#each testGroups as group} + {#each testGroups as group, i}
  • - {group} - + href={`https://www.theguardian.com/ab-tests/opt/in/${testName}:${group}`} + > + {group} ({formatter.format( + ((1 / testGroups.length) * size) / 100, + )}) + {#if i < testGroups.length - 1} | {/if}
  • {/each}
@@ -28,7 +37,6 @@ list-style: none; display: flex; flex-direction: row; - justify-content: space-between; flex-wrap: wrap; } diff --git a/ab-testing/frontend/src/routes/+page.svelte b/ab-testing/frontend/src/routes/+page.svelte index dc90f733f74..abeed7ac261 100644 --- a/ab-testing/frontend/src/routes/+page.svelte +++ b/ab-testing/frontend/src/routes/+page.svelte @@ -2,24 +2,37 @@ import { ABTests } from '../../../abTest'; import Table from '$lib/components/TableFixed.svelte'; import AudienceBreakdown from '$lib/components/AudienceBreakdown.svelte'; - - const clientSideTests = ABTests.filter((test) => test.type === 'client'); - const serverSideTests = ABTests.filter((test) => test.type === 'server');

A/B Tests

-

This page provides an overview of currently running A/B tests on theguardian.com. Please note that the audience segment allocations displayed for non-overlapping tests may not correspond to the actual allocation of MVT IDs, but simply represents how much of the audience is included in each test.

-
-
-

Client-side Tests

- - +

+ This page provides an overview of currently running A/B tests on + theguardian.com. Please note that the audience segment allocations + displayed for non-overlapping tests may not correspond to the actual + allocation of MVT IDs, but simply represents how much of the audience is + included in each test. +

+

+ AB tests are defined in guardian/dotcom-rendering +

+

+ Use the test group links in the table to opt in to those test groups, + this will override any cookie based test assignment, and you will only + be in that test until you opt out. +

+

+ Use this link to opt out of any tests +

-

Server-side Tests

- -
+ +