Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Target by specific countries and/or country group #1271

Merged
merged 27 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
24b9d0c
Added in new field which will accept a list of just country names - m…
charleycampbell Jan 22, 2025
f401054
Refactored the logic so that a country name can be passed in from the…
charleycampbell Jan 23, 2025
e79a33b
Refactored the logic so that a country name can be passed in from the…
charleycampbell Jan 23, 2025
6532a92
removing comment
charleycampbell Jan 23, 2025
16e2a78
removing comment
charleycampbell Jan 23, 2025
4b2d584
remodelled targetedCountries into a schema which takes the (now depre…
charleycampbell Jan 28, 2025
54d1e50
pushing some renaming suggestions
charleycampbell Jan 28, 2025
c9da9b9
updated the name to be clearer
charleycampbell Jan 30, 2025
a3f02b8
We're going to pass through the countrycode from the RRCP so code cha…
charleycampbell Jan 30, 2025
98f1c16
mini changes to test
charleycampbell Jan 30, 2025
31127a7
Update src/server/tests/epics/epicSelection.ts
charleycampbell Feb 3, 2025
22deec6
added new country group match for banners
charleycampbell Feb 3, 2025
87f6992
added tests to support the new countries location for banners
charleycampbell Feb 3, 2025
8eb44eb
have the banner selection logic working on targeting regions/countrie…
charleycampbell Feb 3, 2025
142663d
logic added and tests ammeneded for headers
charleycampbell Feb 3, 2025
ddce3e6
removed locations data from tests so it uses the region targeting
charleycampbell Feb 3, 2025
c463140
adjusted country matches logic and added a unit test for Headers
charleycampbell Feb 4, 2025
20e64c9
same idea but for amp epics
charleycampbell Feb 4, 2025
e85b38a
the test is passing locally so hoping this fixes the build
charleycampbell Feb 4, 2025
573cdca
removed locations test and replaced with region targeting test
charleycampbell Feb 4, 2025
d9045c0
Merge branch 'main' into cc/targeting-by-country
charleycampbell Feb 4, 2025
3ca5b64
Fixed for test in AMP
charleycampbell Feb 5, 2025
deb846c
renamed function which checks if the component is in the targeted cou…
charleycampbell Feb 5, 2025
04f0038
Merge branch 'main' into cc/targeting-by-country
charleycampbell Feb 6, 2025
775aa7b
added in changes to new gutter component
charleycampbell Feb 6, 2025
a4cc666
renaming on inCountryGroup
charleycampbell Feb 6, 2025
cdc5600
prettier fix
charleycampbell Feb 6, 2025
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
92 changes: 92 additions & 0 deletions src/server/tests/epics/epicSelection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@ describe('matchesCountryGroups filter', () => {
const test = {
...testDefault,
locations: [],
regionTargeting: {
targetedCountryGroups: [],
targetedCountryCodes: [],
},
};

const got = matchesCountryGroups.test(test, targetingDefault);
Expand All @@ -293,6 +297,10 @@ describe('matchesCountryGroups filter', () => {
const test: EpicTest = {
...testDefault,
locations: ['GBPCountries'],
regionTargeting: {
targetedCountryGroups: ['GBPCountries'],
targetedCountryCodes: [],
},
};
const targeting = {
...targetingDefault,
Expand All @@ -308,6 +316,10 @@ describe('matchesCountryGroups filter', () => {
const test: EpicTest = {
...testDefault,
locations: ['EURCountries'],
regionTargeting: {
targetedCountryGroups: ['EURCountries'],
targetedCountryCodes: [],
},
};
const targeting: EpicTargeting = {
...targetingDefault,
Expand All @@ -323,6 +335,86 @@ describe('matchesCountryGroups filter', () => {
const test: EpicTest = {
...testDefault,
locations: ['EURCountries'],
regionTargeting: {
targetedCountryGroups: ['EURCountries'],
targetedCountryCodes: [],
},
};
const targeting: EpicTargeting = {
...targetingDefault,
countryCode: 'GB',
};

const got = matchesCountryGroups.test(test, targeting);

expect(got).toBe(false);
});

it('should pass if user is in country group or targeted country', () => {
const test: EpicTest = {
...testDefault,
locations: ['EURCountries'],
regionTargeting: {
targetedCountryGroups: ['EURCountries'],
targetedCountryCodes: ['CA'],
},
};
const targeting: EpicTargeting = {
...targetingDefault,
countryCode: 'DE',
};

const got = matchesCountryGroups.test(test, targeting);

expect(got).toBe(true);
});

it('should pass if user is in targeted country', () => {
const test: EpicTest = {
...testDefault,
locations: [],
regionTargeting: {
targetedCountryGroups: [],
targetedCountryCodes: ['DE'],
},
};
const targeting: EpicTargeting = {
...targetingDefault,
countryCode: 'DE',
};

const got = matchesCountryGroups.test(test, targeting);

expect(got).toBe(true);
});

it('should pass if user is in both country group or targeted country', () => {
const test: EpicTest = {
...testDefault,
locations: ['EURCountries'],
regionTargeting: {
targetedCountryGroups: ['EURCountries'],
targetedCountryCodes: ['DE'],
},
};
const targeting: EpicTargeting = {
...targetingDefault,
countryCode: 'DE',
};

const got = matchesCountryGroups.test(test, targeting);

expect(got).toBe(true);
});

it('should fail if user is not in country group or targeted country', () => {
const test: EpicTest = {
...testDefault,
locations: ['EURCountries'],
regionTargeting: {
targetedCountryGroups: ['EURCountries'],
targetedCountryCodes: ['NZ'],
},
};
const targeting: EpicTargeting = {
...targetingDefault,
Expand Down
14 changes: 13 additions & 1 deletion src/server/tests/epics/epicSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,19 @@ export const hasCountryCode: Filter = {

export const matchesCountryGroups: Filter = {
id: 'matchesCountryGroups',
test: (test, targeting): boolean => inCountryGroups(targeting.countryCode, test.locations),
test: (test, targeting): boolean => {
const targetedRegions = test.regionTargeting
? [
...(test.regionTargeting.targetedCountryGroups || []),
...(test.regionTargeting.targetedCountryCodes || []),
]
: [];
return inCountryGroups(
targeting.countryCode,
test.locations, // Country groups/region
targetedRegions, // Individual country names
);
},
charleycampbell marked this conversation as resolved.
Show resolved Hide resolved
};

export const withinMaxViews = (log: EpicViewLog, now: Date = new Date()): Filter => ({
Expand Down
34 changes: 26 additions & 8 deletions src/shared/lib/geolocation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ export type CountryGroupId = (typeof CountryGroupId)[number];

export const countryGroupIdSchema = z.enum(CountryGroupId);

export const targetedRegionsSchema = z
.object({
targetedCountryGroups: z.array(countryGroupIdSchema),
targetedCountryCodes: z.array(z.string()).optional(),
})
.optional();

// Used to internationalise 'Support the Guardian' links
export type SupportRegionId = 'UK' | 'US' | 'AU' | 'EU' | 'INT' | 'NZ' | 'CA';

Expand Down Expand Up @@ -560,25 +567,31 @@ export const countryCodeToCountryGroupId = (countryCode?: string): CountryGroupI
const foundCountryGroupId = availableCountryGroupIds.find((countryGroupId) =>
countryGroups[countryGroupId].countries.includes(countryCode ?? ''),
);

return foundCountryGroupId || 'International';
};

//inCountryGroups is a bad name now that it accepts country names seperately from country groups
export const inCountryGroups = (
countryCode?: string,
countryCodeFromPayload?: string,
countryGroups: CountryGroupId[] = [],
countryCodes: string[] = [], // Accepts individual country codes
): boolean => {
// Always True if no locations set for the test
if (countryGroups.length === 0) {
// Always True if no locations or targeted countries set for the test (so always displays epic)
if (countryGroups.length === 0 && countryCodes.length === 0) {
return true;
}

// Always False if user location unknown but test has locations set
if (!countryCode) {
// Always false if user location unknown but test has locations set (so never displays epic unless in country)
if (!countryCodeFromPayload) {
return false;
}

return countryGroups.includes(countryCodeToCountryGroupId(countryCode.toUpperCase()));
// Check if the country belongs to the specified country groups
if (countryGroups.includes(countryCodeToCountryGroupId(countryCodeFromPayload.toUpperCase()))) {
return true;
}

// Check if the country is in the targeted countries by name
return countryCodes.includes(countryCodeFromPayload.toUpperCase());
};

const defaultCurrencySymbol = '£';
Expand Down Expand Up @@ -623,3 +636,8 @@ export const addRegionIdToSupportUrl = (originalUrl: string, countryCode?: strin

return originalUrl;
};

const countryNameToCodeMap: Record<string, string> = {};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it can!

for (const [code, name] of Object.entries(countryNames)) {
countryNameToCodeMap[name] = code;
}
5 changes: 3 additions & 2 deletions src/shared/types/abTests/banner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
testSchema,
userCohortSchema,
} from './shared';
import { countryGroupIdSchema } from '../../lib';
import { countryGroupIdSchema, targetedRegionsSchema } from '../../lib';
import { BannerTargeting, PageTracking } from '../targeting';
import { PurchaseInfoTest } from './shared';
import { z } from 'zod';
Expand Down Expand Up @@ -82,7 +82,8 @@ export interface BannerTest extends BannerTestFromTool {

export const bannerTestFromToolSchema = testSchema.extend({
userCohort: userCohortSchema,
locations: z.array(countryGroupIdSchema),
locations: z.array(countryGroupIdSchema).optional(),
regionTargeting: targetedRegionsSchema.optional(),
contextTargeting: pageContextTargetingSchema,
variants: z.array(bannerVariantFromToolSchema),
articlesViewedSettings: articlesViewedSettingsSchema.optional(),
Expand Down
5 changes: 3 additions & 2 deletions src/shared/types/abTests/epic.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CountryGroupId, countryGroupIdSchema } from '../../lib';
import { CountryGroupId, countryGroupIdSchema, targetedRegionsSchema } from '../../lib';
import {
articlesViewedSettingsSchema,
testSchema,
Expand Down Expand Up @@ -112,7 +112,8 @@ export interface EpicTest extends EpicTestFromTool {
export const epicTestFromToolSchema = testSchema.extend({
name: z.string(),
status: testStatusSchema,
locations: z.array(countryGroupIdSchema),
locations: z.array(countryGroupIdSchema).optional(), //deprecated
regionTargeting: targetedRegionsSchema.optional(),
tagIds: z.array(z.string()),
sections: z.array(z.string()),
excludedTagIds: z.array(z.string()),
Expand Down
5 changes: 3 additions & 2 deletions src/shared/types/abTests/header.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { testSchema, userCohortSchema, purchaseInfoTestSchema } from './shared';
import { headerContentSchema } from '../props';
import { countryGroupIdSchema } from '../../lib';
import { countryGroupIdSchema, targetedRegionsSchema } from '../../lib';
import { z } from 'zod';

/**
Expand All @@ -14,7 +14,8 @@ const headerVariantFromToolSchema = z.object({
export type HeaderVariantFromTool = z.infer<typeof headerVariantFromToolSchema>;

export const headerTestFromToolSchema = testSchema.extend({
locations: z.array(countryGroupIdSchema),
locations: z.array(countryGroupIdSchema).optional(),
regionTargeting: targetedRegionsSchema.optional(),
userCohort: userCohortSchema,
purchaseInfo: purchaseInfoTestSchema.optional(),
variants: z.array(headerVariantFromToolSchema),
Expand Down