Skip to content

Commit dec0567

Browse files
authored
Merge pull request #1305 from guardian/ph-20250306-1432-tests
auxia experiment: move functions to lib and give them tests
2 parents cc7ac4d + b224f05 commit dec0567

File tree

3 files changed

+467
-251
lines changed

3 files changed

+467
-251
lines changed

src/server/api/auxiaProxyRouter.ts

Lines changed: 10 additions & 251 deletions
Original file line numberDiff line numberDiff line change
@@ -2,92 +2,22 @@ import express, { Router } from 'express';
22
import { isProd } from '../lib/env';
33
import { getSsmValue } from '../utils/ssm';
44
import { bodyContainsAllFields } from '../middleware';
5-
6-
// --------------------------------
7-
// Basic Types
8-
// --------------------------------
5+
import {
6+
buildGetTreatmentsRequestPayload,
7+
guDefaultGateGetTreatmentsResponseData,
8+
AuxiaAPIGetTreatmentsResponseData,
9+
isValidContentType,
10+
isValidSection,
11+
isValidTagIdCollection,
12+
buildAuxiaProxyGetTreatmentsResponseData,
13+
buildLogTreatmentInteractionRequestPayload,
14+
} from '../signin-gate/lib';
915

1016
export interface AuxiaRouterConfig {
1117
apiKey: string;
1218
projectId: string;
1319
}
1420

15-
// --------------------------------
16-
// Auxia API Types
17-
// --------------------------------
18-
19-
interface AuxiaAPIContextualAttributeString {
20-
key: string;
21-
stringValue: string;
22-
}
23-
24-
interface AuxiaAPIContextualAttributeBoolean {
25-
key: string;
26-
boolValue: boolean;
27-
}
28-
29-
interface AuxiaAPIContextualAttributeInteger {
30-
key: string;
31-
integerValue: number;
32-
}
33-
34-
type AuxiaAPIGenericContexualAttribute =
35-
| AuxiaAPIContextualAttributeString
36-
| AuxiaAPIContextualAttributeBoolean
37-
| AuxiaAPIContextualAttributeInteger;
38-
39-
interface AuxiaAPISurface {
40-
surface: string;
41-
maximumTreatmentCount: number;
42-
}
43-
44-
interface AuxiaAPIUserTreatment {
45-
treatmentId: string;
46-
treatmentTrackingId: string;
47-
rank: string;
48-
contentLanguageCode: string;
49-
treatmentContent: string;
50-
treatmentType: string;
51-
surface: string;
52-
}
53-
54-
interface AuxiaAPIGetTreatmentsRequestPayload {
55-
projectId: string;
56-
userId: string;
57-
contextualAttributes: AuxiaAPIGenericContexualAttribute[];
58-
surfaces: AuxiaAPISurface[];
59-
languageCode: string;
60-
}
61-
62-
interface AuxiaAPILogTreatmentInteractionRequestPayload {
63-
projectId: string;
64-
userId: string;
65-
treatmentTrackingId: string;
66-
treatmentId: string;
67-
surface: string;
68-
interactionType: string;
69-
interactionTimeMicros: number;
70-
actionName: string;
71-
}
72-
73-
interface AuxiaAPIGetTreatmentsResponseData {
74-
responseId: string;
75-
userTreatments: AuxiaAPIUserTreatment[];
76-
}
77-
78-
// --------------------------------
79-
// Proxy Types
80-
// --------------------------------
81-
82-
interface AuxiaProxyGetTreatmentsResponseData {
83-
responseId: string;
84-
userTreatment?: AuxiaAPIUserTreatment;
85-
}
86-
87-
// --------------------------------
88-
// Proxy Common Functions
89-
// --------------------------------
90-
9121
export const getAuxiaRouterConfig = async (): Promise<AuxiaRouterConfig> => {
9222
const stage = isProd ? 'PROD' : 'CODE';
9323

@@ -107,138 +37,6 @@ export const getAuxiaRouterConfig = async (): Promise<AuxiaRouterConfig> => {
10737
});
10838
};
10939

110-
// --------------------------------
111-
// Proxy Implementation GetTreatments
112-
// --------------------------------
113-
114-
const buildGetTreatmentsRequestPayload = (
115-
projectId: string,
116-
browserId: string,
117-
isSupporter: boolean,
118-
dailyArticleCount: number,
119-
articleIdentifier: string,
120-
editionId: string,
121-
): AuxiaAPIGetTreatmentsRequestPayload => {
122-
// For the moment we are hard coding the data provided in contextualAttributes and surfaces.
123-
return {
124-
projectId: projectId,
125-
userId: browserId, // In our case the userId is the browserId.
126-
contextualAttributes: [
127-
{
128-
key: 'is_supporter',
129-
boolValue: isSupporter,
130-
},
131-
{
132-
key: 'daily_article_count',
133-
integerValue: dailyArticleCount,
134-
},
135-
{
136-
key: 'article_identifier',
137-
stringValue: articleIdentifier,
138-
},
139-
{
140-
key: 'edition',
141-
stringValue: editionId,
142-
},
143-
],
144-
surfaces: [
145-
{
146-
surface: 'ARTICLE_PAGE',
147-
maximumTreatmentCount: 1,
148-
},
149-
],
150-
languageCode: 'en-GB',
151-
};
152-
};
153-
154-
const guDefaultShouldShowTheGate = (daily_article_count: number): boolean => {
155-
// We show the GU gate every 10 pageviews
156-
return daily_article_count % 10 == 0;
157-
};
158-
159-
const guDefaultGateGetTreatmentsResponseData = (
160-
daily_article_count: number,
161-
gateDismissCount: number,
162-
): AuxiaAPIGetTreatmentsResponseData => {
163-
// This function is called in the case of non consenting users, which is detected by the absence of the browserId.
164-
165-
const responseId = ''; // This value is not important, it is not used by the client.
166-
167-
// First we enforce the GU policy of not showing the gate if the user has dismissed it more than 5 times.
168-
// (We do not want users to have to dismiss the gate 6 times)
169-
170-
if (gateDismissCount > 5) {
171-
return {
172-
responseId,
173-
userTreatments: [],
174-
};
175-
}
176-
177-
// Then to prevent showing the gate too many times, we only show the gate every 10 pages views
178-
179-
if (!guDefaultShouldShowTheGate(daily_article_count)) {
180-
return {
181-
responseId,
182-
userTreatments: [],
183-
};
184-
}
185-
186-
// We are now clear to show the default gu gate.
187-
188-
const title = 'Register: it’s quick and easy';
189-
const subtitle = 'It’s still free to read – this is not a paywall';
190-
const body =
191-
'We’re committed to keeping our quality reporting open. By registering and providing us with insight into your preferences, you’re helping us to engage with you more deeply, and that allows us to keep our journalism free for all.';
192-
const secondCtaName = 'I’ll do it later';
193-
const treatmentContent = {
194-
title,
195-
subtitle,
196-
body,
197-
first_cta_name: 'Sign in',
198-
first_cta_link: 'https://profile.theguardian.com/signin?',
199-
second_cta_name: secondCtaName,
200-
second_cta_link: 'https://profile.theguardian.com/signin?',
201-
};
202-
const treatmentContentEncoded = JSON.stringify(treatmentContent);
203-
const userTreatment: AuxiaAPIUserTreatment = {
204-
treatmentId: 'default-treatment-id',
205-
treatmentTrackingId: 'default-treatment-tracking-id',
206-
rank: '1',
207-
contentLanguageCode: 'en-GB',
208-
treatmentContent: treatmentContentEncoded,
209-
treatmentType: 'DISMISSABLE_SIGN_IN_GATE',
210-
surface: 'ARTICLE_PAGE',
211-
};
212-
const data: AuxiaAPIGetTreatmentsResponseData = {
213-
responseId,
214-
userTreatments: [userTreatment],
215-
};
216-
return data;
217-
};
218-
219-
const isValidContentType = (contentType: string): boolean => {
220-
const validTypes = ['Article'];
221-
return validTypes.includes(contentType);
222-
};
223-
224-
const isValidSection = (sectionId: string): boolean => {
225-
const invalidSections = [
226-
'about',
227-
'info',
228-
'membership',
229-
'help',
230-
'guardian-live-australia',
231-
'gnm-archive',
232-
];
233-
return !invalidSections.some((section: string): boolean => sectionId === section);
234-
};
235-
236-
const isValidTagIdCollection = (tagIds: string[]): boolean => {
237-
const invalidTagIds = ['info/newsletter-sign-up'];
238-
// Check that no tagId is in the invalidTagIds list.
239-
return !tagIds.some((tagId: string): boolean => invalidTagIds.includes(tagId));
240-
};
241-
24240
const callGetTreatments = async (
24341
apiKey: string,
24442
projectId: string,
@@ -316,45 +114,6 @@ const callGetTreatments = async (
316114
}
317115
};
318116

319-
const buildAuxiaProxyGetTreatmentsResponseData = (
320-
auxiaData: AuxiaAPIGetTreatmentsResponseData,
321-
): AuxiaProxyGetTreatmentsResponseData | undefined => {
322-
// Note the small difference between AuxiaAPIResponseData and AuxiaProxyResponseData
323-
// In the case of AuxiaProxyResponseData, we have an optional userTreatment field, instead of an array of userTreatments.
324-
// This is to reflect the what the client expect semantically.
325-
326-
return {
327-
responseId: auxiaData.responseId,
328-
userTreatment: auxiaData.userTreatments[0],
329-
};
330-
};
331-
332-
// --------------------------------
333-
// LogTreatmentInteraction Implementation
334-
// --------------------------------
335-
336-
const buildLogTreatmentInteractionRequestPayload = (
337-
projectId: string,
338-
browserId: string,
339-
treatmentTrackingId: string,
340-
treatmentId: string,
341-
surface: string,
342-
interactionType: string,
343-
interactionTimeMicros: number,
344-
actionName: string,
345-
): AuxiaAPILogTreatmentInteractionRequestPayload => {
346-
return {
347-
projectId: projectId,
348-
userId: browserId, // In our case the userId is the browserId.
349-
treatmentTrackingId,
350-
treatmentId,
351-
surface,
352-
interactionType,
353-
interactionTimeMicros,
354-
actionName,
355-
};
356-
};
357-
358117
const callLogTreatmentInteration = async (
359118
apiKey: string,
360119
projectId: string,

0 commit comments

Comments
 (0)