Skip to content
Merged
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
261 changes: 10 additions & 251 deletions src/server/api/auxiaProxyRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,92 +2,22 @@ import express, { Router } from 'express';
import { isProd } from '../lib/env';
import { getSsmValue } from '../utils/ssm';
import { bodyContainsAllFields } from '../middleware';

// --------------------------------
// Basic Types
// --------------------------------
import {
buildGetTreatmentsRequestPayload,
guDefaultGateGetTreatmentsResponseData,
AuxiaAPIGetTreatmentsResponseData,
isValidContentType,
isValidSection,
isValidTagIdCollection,
buildAuxiaProxyGetTreatmentsResponseData,
buildLogTreatmentInteractionRequestPayload,
} from '../signin-gate/lib';

export interface AuxiaRouterConfig {
apiKey: string;
projectId: string;
}

// --------------------------------
// Auxia API Types
// --------------------------------

interface AuxiaAPIContextualAttributeString {
key: string;
stringValue: string;
}

interface AuxiaAPIContextualAttributeBoolean {
key: string;
boolValue: boolean;
}

interface AuxiaAPIContextualAttributeInteger {
key: string;
integerValue: number;
}

type AuxiaAPIGenericContexualAttribute =
| AuxiaAPIContextualAttributeString
| AuxiaAPIContextualAttributeBoolean
| AuxiaAPIContextualAttributeInteger;

interface AuxiaAPISurface {
surface: string;
maximumTreatmentCount: number;
}

interface AuxiaAPIUserTreatment {
treatmentId: string;
treatmentTrackingId: string;
rank: string;
contentLanguageCode: string;
treatmentContent: string;
treatmentType: string;
surface: string;
}

interface AuxiaAPIGetTreatmentsRequestPayload {
projectId: string;
userId: string;
contextualAttributes: AuxiaAPIGenericContexualAttribute[];
surfaces: AuxiaAPISurface[];
languageCode: string;
}

interface AuxiaAPILogTreatmentInteractionRequestPayload {
projectId: string;
userId: string;
treatmentTrackingId: string;
treatmentId: string;
surface: string;
interactionType: string;
interactionTimeMicros: number;
actionName: string;
}

interface AuxiaAPIGetTreatmentsResponseData {
responseId: string;
userTreatments: AuxiaAPIUserTreatment[];
}

// --------------------------------
// Proxy Types
// --------------------------------

interface AuxiaProxyGetTreatmentsResponseData {
responseId: string;
userTreatment?: AuxiaAPIUserTreatment;
}

// --------------------------------
// Proxy Common Functions
// --------------------------------

export const getAuxiaRouterConfig = async (): Promise<AuxiaRouterConfig> => {
const stage = isProd ? 'PROD' : 'CODE';

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

// --------------------------------
// Proxy Implementation GetTreatments
// --------------------------------

const buildGetTreatmentsRequestPayload = (
projectId: string,
browserId: string,
isSupporter: boolean,
dailyArticleCount: number,
articleIdentifier: string,
editionId: string,
): AuxiaAPIGetTreatmentsRequestPayload => {
// For the moment we are hard coding the data provided in contextualAttributes and surfaces.
return {
projectId: projectId,
userId: browserId, // In our case the userId is the browserId.
contextualAttributes: [
{
key: 'is_supporter',
boolValue: isSupporter,
},
{
key: 'daily_article_count',
integerValue: dailyArticleCount,
},
{
key: 'article_identifier',
stringValue: articleIdentifier,
},
{
key: 'edition',
stringValue: editionId,
},
],
surfaces: [
{
surface: 'ARTICLE_PAGE',
maximumTreatmentCount: 1,
},
],
languageCode: 'en-GB',
};
};

const guDefaultShouldShowTheGate = (daily_article_count: number): boolean => {
// We show the GU gate every 10 pageviews
return daily_article_count % 10 == 0;
};

const guDefaultGateGetTreatmentsResponseData = (
daily_article_count: number,
gateDismissCount: number,
): AuxiaAPIGetTreatmentsResponseData => {
// This function is called in the case of non consenting users, which is detected by the absence of the browserId.

const responseId = ''; // This value is not important, it is not used by the client.

// First we enforce the GU policy of not showing the gate if the user has dismissed it more than 5 times.
// (We do not want users to have to dismiss the gate 6 times)

if (gateDismissCount > 5) {
return {
responseId,
userTreatments: [],
};
}

// Then to prevent showing the gate too many times, we only show the gate every 10 pages views

if (!guDefaultShouldShowTheGate(daily_article_count)) {
return {
responseId,
userTreatments: [],
};
}

// We are now clear to show the default gu gate.

const title = 'Register: it’s quick and easy';
const subtitle = 'It’s still free to read – this is not a paywall';
const body =
'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.';
const secondCtaName = 'I’ll do it later';
const treatmentContent = {
title,
subtitle,
body,
first_cta_name: 'Sign in',
first_cta_link: 'https://profile.theguardian.com/signin?',
second_cta_name: secondCtaName,
second_cta_link: 'https://profile.theguardian.com/signin?',
};
const treatmentContentEncoded = JSON.stringify(treatmentContent);
const userTreatment: AuxiaAPIUserTreatment = {
treatmentId: 'default-treatment-id',
treatmentTrackingId: 'default-treatment-tracking-id',
rank: '1',
contentLanguageCode: 'en-GB',
treatmentContent: treatmentContentEncoded,
treatmentType: 'DISMISSABLE_SIGN_IN_GATE',
surface: 'ARTICLE_PAGE',
};
const data: AuxiaAPIGetTreatmentsResponseData = {
responseId,
userTreatments: [userTreatment],
};
return data;
};

const isValidContentType = (contentType: string): boolean => {
const validTypes = ['Article'];
return validTypes.includes(contentType);
};

const isValidSection = (sectionId: string): boolean => {
const invalidSections = [
'about',
'info',
'membership',
'help',
'guardian-live-australia',
'gnm-archive',
];
return !invalidSections.some((section: string): boolean => sectionId === section);
};

const isValidTagIdCollection = (tagIds: string[]): boolean => {
const invalidTagIds = ['info/newsletter-sign-up'];
// Check that no tagId is in the invalidTagIds list.
return !tagIds.some((tagId: string): boolean => invalidTagIds.includes(tagId));
};

const callGetTreatments = async (
apiKey: string,
projectId: string,
Expand Down Expand Up @@ -316,45 +114,6 @@ const callGetTreatments = async (
}
};

const buildAuxiaProxyGetTreatmentsResponseData = (
auxiaData: AuxiaAPIGetTreatmentsResponseData,
): AuxiaProxyGetTreatmentsResponseData | undefined => {
// Note the small difference between AuxiaAPIResponseData and AuxiaProxyResponseData
// In the case of AuxiaProxyResponseData, we have an optional userTreatment field, instead of an array of userTreatments.
// This is to reflect the what the client expect semantically.

return {
responseId: auxiaData.responseId,
userTreatment: auxiaData.userTreatments[0],
};
};

// --------------------------------
// LogTreatmentInteraction Implementation
// --------------------------------

const buildLogTreatmentInteractionRequestPayload = (
projectId: string,
browserId: string,
treatmentTrackingId: string,
treatmentId: string,
surface: string,
interactionType: string,
interactionTimeMicros: number,
actionName: string,
): AuxiaAPILogTreatmentInteractionRequestPayload => {
return {
projectId: projectId,
userId: browserId, // In our case the userId is the browserId.
treatmentTrackingId,
treatmentId,
surface,
interactionType,
interactionTimeMicros,
actionName,
};
};

const callLogTreatmentInteration = async (
apiKey: string,
projectId: string,
Expand Down
Loading