Skip to content

Commit 7068ffe

Browse files
authored
Merge branch 'main' into sub-1
2 parents 6143a24 + fbcaa75 commit 7068ffe

File tree

94 files changed

+14397
-11819
lines changed

Some content is hidden

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

94 files changed

+14397
-11819
lines changed

.github/workflows/ab-testing-checks.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@ on:
2020
jobs:
2121
checks:
2222
runs-on: ubuntu-latest
23+
name: Checks
2324
defaults:
2425
run:
2526
working-directory: ab-testing
2627
env:
2728
FASTLY_AB_TESTING_CONFIG: ${{ secrets.FASTLY_AB_TESTING_CONFIG }}
2829
FASTLY_API_TOKEN: ${{ secrets.FASTLY_API_TOKEN }}
2930
steps:
30-
- uses: actions/checkout@v4
31+
- uses: actions/checkout@v5
3132

3233
# https://github.com/denoland/setup-deno#latest-stable-for-a-major
3334
- uses: denoland/setup-deno@v1

.github/workflows/ab-testing-deploy-code.yml renamed to .github/workflows/ab-testing-ci-code.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: 🧪 Deploy CODE AB Testing Config
1+
name: 🧪 AB testing CI (CODE)
22

33
permissions:
44
contents: read

.github/workflows/ab-testing-ci.yml

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,31 @@ on:
77
pull_request:
88
paths:
99
- 'ab-testing/**'
10+
- '.github/workflows/ab-testing-*.yml'
11+
push:
12+
branches:
13+
- main
14+
paths:
15+
- 'ab-testing/**'
16+
- '.github/workflows/ab-testing-*.yml'
1017

1118
jobs:
12-
checks:
19+
ci:
20+
name: CI
1321
uses: ./.github/workflows/ab-testing-checks.yml
22+
with:
23+
save_build_artifact: true
24+
secrets:
25+
FASTLY_AB_TESTING_CONFIG: ${{ secrets.FASTLY_PROD_AB_TESTING_CONFIG }}
26+
FASTLY_API_TOKEN: ${{ secrets.FASTLY_PROD_API_TOKEN }}
27+
28+
deploy:
29+
name: Deploy
30+
needs: ci
31+
if: github.ref == 'refs/heads/main'
32+
uses: ./.github/workflows/ab-testing-deploy.yml
33+
with:
34+
stage: prod
1435
secrets:
15-
FASTLY_AB_TESTING_CONFIG: ${{ secrets.FASTLY_CODE_AB_TESTING_CONFIG }}
16-
FASTLY_API_TOKEN: ${{ secrets.FASTLY_CODE_API_TOKEN }}
36+
FASTLY_AB_TESTING_CONFIG: ${{ secrets.FASTLY_PROD_AB_TESTING_CONFIG }}
37+
FASTLY_API_TOKEN: ${{ secrets.FASTLY_PROD_API_TOKEN }}

.github/workflows/ab-testing-deploy-prod.yml

Lines changed: 0 additions & 31 deletions
This file was deleted.

.github/workflows/ab-testing-deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
run:
2525
working-directory: ab-testing
2626
steps:
27-
- uses: actions/checkout@v4
27+
- uses: actions/checkout@v5
2828

2929
# https://github.com/denoland/setup-deno#latest-stable-for-a-major
3030
- uses: denoland/setup-deno@v1

.github/workflows/ab-testing-ui.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ on:
33
pull_request:
44
paths:
55
- 'ab-testing/**'
6+
- '.github/workflows/ab-testing-*.yml'
67
push:
78
branches:
89
- main
910
paths:
1011
- 'ab-testing/**'
12+
- '.github/workflows/ab-testing-*.yml'
1113

1214
jobs:
1315
build-ui:

ab-testing/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ To add a test where there is not enough space in the default audience space (`A`
6363

6464
For example if there are already 3 25% tests in space `A` totalling 75%, and you want to run a 50% test, you can set the `audienceSpace` to `B` to allow this test to overlap with the existing tests.
6565

66+
### Test Status
67+
68+
Tests can be set to `ON` or `OFF` using the `status` field. Only tests with status `ON` will be validated and deployed.
69+
6670
## How it works
6771

6872
The AB testing framework uses Deno to run scripts that validate and deploy the tests. The `deno.json` file contains the tasks that can be run, such as `validate`, `deploy`, and `build`.
@@ -74,3 +78,5 @@ The algorithm allocates tests available MVT IDs based on the audience size and s
7478
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.
7579

7680
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.
81+
82+
See the [fastly-edge-cache documentation](https://github.com/guardian/fastly-edge-cache/blob/main/theguardiancom/docs/ab-testing.md) for even more details.

ab-testing/abTest.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,8 @@ import type { ABTest } from './types';
1919
* - 100% Test variant MVT 500-999
2020
*/
2121

22-
export const ABTests: ABTest[] = [];
22+
const ABTests: ABTest[] = [];
23+
24+
const activeABtests = ABTests.filter((test) => test.status === 'ON');
25+
26+
export { ABTests as allABTests, activeABtests };

ab-testing/frontend/src/lib/components/AudienceBreakdown.svelte

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
1818
const BAR_HEIGHT = 40;
1919
20-
// Account for legend bar and vertical padding in chart height
21-
const chartHeight = tests.length * BAR_HEIGHT + BAR_HEIGHT + 16;
20+
const BAR_MARGIN_X = 0.1;
21+
const BAR_MARGIN_Y = 2;
2222
2323
const testSpaces = ['A', 'B', 'C'];
2424
25+
const chartHeight = testSpaces.length * BAR_HEIGHT + BAR_HEIGHT + 16;
26+
2527
const testsBySpace = testSpaces.map((space) => {
2628
if (space === 'A') {
2729
return tests.filter(
@@ -33,32 +35,32 @@
3335
});
3436
3537
function getBars(testList: ABTest[], rowPosition: number) {
36-
return testList.reduce<Array<ABTestBarData>>(
37-
(barsList, test, index) => {
38-
const previousBar = barsList.slice(-1)[0];
39-
const offset: number = Number(previousBar?.width ?? 0);
40-
const rowYLevel = index + rowPosition;
41-
const testSize = getSize(test);
42-
43-
return [
44-
...barsList,
45-
{
46-
x: offset,
47-
y: rowYLevel * BAR_HEIGHT + BAR_HEIGHT,
48-
width: testSize,
49-
name: test.name,
50-
segments: `${offset}% to ${offset + testSize}%`,
51-
},
52-
];
53-
},
54-
[],
55-
);
38+
return testList.reduce<Array<ABTestBarData>>((barsList, test) => {
39+
const previousBarsWidth = barsList.reduce(
40+
(acc, bar) => acc + bar.width,
41+
0,
42+
);
43+
const offset: number = Number(previousBarsWidth);
44+
const rowYLevel = rowPosition;
45+
const testSize = getSize(test);
46+
47+
return [
48+
...barsList,
49+
{
50+
x: offset,
51+
y: rowYLevel * BAR_HEIGHT + BAR_HEIGHT,
52+
width: testSize,
53+
name: test.name,
54+
segments: `${offset}% to ${offset + testSize}%`,
55+
},
56+
];
57+
}, []);
5658
}
5759
5860
function getAllRows(testsBySpace: ABTest[][]) {
5961
return testsBySpace.reduce<Array<ABTestBarData>>(
60-
(barsList, testsInSpace) => {
61-
return [...barsList, ...getBars(testsInSpace, barsList.length)];
62+
(barsList, testsInSpace, i) => {
63+
return [...barsList, ...getBars(testsInSpace, i)];
6264
},
6365
[],
6466
);
@@ -86,13 +88,13 @@
8688
</svg>
8789
{#each getAllRows(testsBySpace) as bar}
8890
<svg
89-
x={`${bar.x}%`}
91+
x={`${bar.x + BAR_MARGIN_X}%`}
9092
y={bar.y}
91-
width={`${bar.width}%`}
93+
width={`${bar.width - BAR_MARGIN_X * 2}%`}
9294
height={BAR_HEIGHT}
9395
>
9496
<g class="bar">
95-
<rect height={BAR_HEIGHT} width="100%" rx="4" />
97+
<rect height={BAR_HEIGHT - BAR_MARGIN_Y} width="100%" rx="4" />
9698
<text class="name" x="50%" y="50%">{bar.name}</text>
9799
<text class="segments" x="50%" y="50%">{bar.segments}</text>
98100
</g>

ab-testing/frontend/src/lib/components/TableFixed.svelte

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
function daysToExpiry(expires: string) {
1313
const today = new Date();
1414
const expiresDate = new Date(expires);
15-
const differenceInMilliseconds = expiresDate.getTime() - today.getTime();
15+
const differenceInMilliseconds =
16+
expiresDate.getTime() - today.getTime();
1617
const differenceInDays =
1718
differenceInMilliseconds / (1000 * 60 * 60 * 24);
1819
return Math.floor(differenceInDays);
@@ -21,43 +22,69 @@
2122

2223
<section class="tests">
2324
{#each tests as test}
25+
{@const expired = daysToExpiry(test.expirationDate) < 0}
2426
<table>
27+
<colgroup>
28+
<col span="1" style="width: 25%;" />
29+
<col span="1" style="width: 10%;" />
30+
<col span="1" style="width: 35%;" />
31+
<col span="1" style="width: 10%;" />
32+
<col span="1" style="width: 10%;" />
33+
<col span="1" style="width: 10%;" />
34+
</colgroup>
2535
<thead>
2636
<tr>
2737
<th scope="col">Name</th>
2838
<th scope="col">State</th>
29-
<th scope="col">Variants</th>
39+
<th scope="col">Test Groups</th>
3040
<th scope="col">Expires In</th>
3141
<th scope="col">Audience</th>
32-
<th scope="col">Offset</th>
3342
<th scope="col">Ophan</th>
3443
</tr>
3544
</thead>
3645
<tbody>
3746
<tr>
38-
<th scope="row" class="test-name">{test.name}</th>
39-
<td>{test.status}</td>
47+
<th scope="row" class="test-name"
48+
>{test.name} ({test.type})</th
49+
>
50+
<td
51+
class="status"
52+
class:off={test.status === 'OFF'}
53+
class:expired
54+
>
55+
{#if expired}
56+
EXPIRED
57+
{:else}
58+
{test.status}
59+
{/if}
60+
</td>
4061
<td>
4162
<TestVariants
63+
size={test.audienceSize * 100}
4264
testName={test.name}
4365
testGroups={test.groups}
4466
/>
4567
</td>
46-
<td>{daysToExpiry(test.expirationDate)} days</td>
68+
<td class:expired
69+
>{daysToExpiry(test.expirationDate)} days</td
70+
>
4771
<td>{test.audienceSize * 100}%</td>
48-
<td>0</td>
4972
<td><OphanLink testName={test.name} /></td>
5073
</tr>
5174
<tr>
5275
<th scope="row">Description</th>
53-
<td colspan="6">{test.description}</td>
76+
<td colspan="5">{test.description}</td>
5477
</tr>
5578
</tbody>
5679
</table>
5780
{/each}
5881
</section>
5982

6083
<style>
84+
:root {
85+
--ok-green: #00823b;
86+
--error-red: #d5281b;
87+
}
6188
.tests {
6289
border: 1px solid var(--border-grey);
6390
padding: 8px;
@@ -87,4 +114,20 @@
87114
.test-name {
88115
font-weight: 100;
89116
}
117+
118+
.status {
119+
text-transform: uppercase;
120+
font-weight: bold;
121+
122+
color: var(--ok-green);
123+
124+
&.off {
125+
color: #767676;
126+
}
127+
}
128+
129+
.expired {
130+
color: var(--error-red);
131+
font-weight: bold;
132+
}
90133
</style>

0 commit comments

Comments
 (0)