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

[FB Custom Audiences] Audience Selection/Creation RETL Hook #2197

Merged
merged 28 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0a20b76
Removes delete action, add action. Introduces single sync action
nick-Ag Jul 16, 2024
5ba6c09
Introduces shared adAccountId top-level setting and audienceSetting
nick-Ag Jul 16, 2024
60d2291
WIP - first draft of a FacebookClient class, including an untested cr…
nick-Ag Jul 16, 2024
27e4903
Adds required customer_file_source field
nick-Ag Jul 16, 2024
151d26c
Adds a dynamic audience field, pulls down all customaudiences as choices
nick-Ag Jul 16, 2024
1e9d616
WIP on a pagination mechanism for dynamic fields
nick-Ag Jul 16, 2024
037534c
Fixes build
nick-Ag Jul 17, 2024
365690e
Can pass dynamicFieldContext & paging into local server request
nick-Ag Jul 17, 2024
b831f7b
Sets paging return such that the previous page was the current page
nick-Ag Jul 17, 2024
6078690
Merge branch 'main' into fbca-retl-hook
nick-Ag Jul 18, 2024
c2f3e32
Removes pagination related code - that work will be saved for later :)
nick-Ag Jul 18, 2024
49add30
Pulls down 200 most recent audiences in dynamic field response
nick-Ag Jul 18, 2024
a022704
Removes description param from creating an audience per PRD
nick-Ag Jul 18, 2024
a2a642c
WIP - verifying selected audience exists and saving it's ID as output
nick-Ag Jul 18, 2024
fc4f5ed
Checks for existence of user selected audience. Saves audience name a…
nick-Ag Jul 18, 2024
8f5f5ed
WIP on an inexplicably not passing unit test
nick-Ag Jul 18, 2024
0a1285a
Generates types
nick-Ag Jul 18, 2024
3ea1444
Splits off engage and retl AdAccountId settings, fun fact: these cann…
nick-Ag Jul 23, 2024
945c7eb
Adding support for FieldDisplayModes & disabling certain input method…
pooyaj Jul 19, 2024
95b4593
aws s3 bug fixes (#2200)
joe-ayoub-segment Jul 19, 2024
b9f0391
Merge branch 'main' into fbca-retl-hook
nick-Ag Jul 23, 2024
d7b8c1e
Fixes unit tests
nick-Ag Jul 23, 2024
8a7a08e
Introduces a selector for users to choose whether they want to create…
nick-Ag Jul 23, 2024
a54e845
Generates types
nick-Ag Jul 24, 2024
185d425
Updates hook logic to consider the operation the user selected, if cr…
nick-Ag Jul 25, 2024
1bac8c3
Removes stray change to aws destination
nick-Ag Jul 25, 2024
2cf86bf
Merge remote-tracking branch 'origin/main' into fbca-retl-hook
nick-Ag Jul 25, 2024
dbb9df7
[Facebook Custom Audiences] Sync users to an audience (#2233)
nick-Ag Aug 5, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
import createRequestClient from '../../../../../core/src/request-client'
import FacebookClient, { BASE_URL, generateData } from '../fbca-operations'
import { Settings } from '../generated-types'
import nock from 'nock'
import { Payload } from '../sync/generated-types'
import { createHash } from 'crypto'
import { normalizationFunctions } from '../fbca-properties'

const requestClient = createRequestClient()
const settings: Settings = {
retlAdAccountId: 'act_123456'
}
const EMPTY = ''

// clone of the hash function in fbca-operations.ts since it's a private method
const hash = (value: string): string => {
return createHash('sha256').update(value).digest('hex')
}

describe('Facebook Custom Audiences', () => {
const facebookClient = new FacebookClient(requestClient, settings.retlAdAccountId)
describe('retlOnMappingSave hook', () => {
const hookInputs = {
audienceName: 'test-audience'
}

it('should create a custom audience in facebook', async () => {
nock(`${BASE_URL}`)
.post(`/${settings.retlAdAccountId}/customaudiences`, {
name: hookInputs.audienceName,
subtype: 'CUSTOM',
customer_file_source: 'BOTH_USER_AND_PARTNER_PROVIDED'
})
.reply(201, { id: '123' })

await facebookClient.createAudience(hookInputs.audienceName)
})
})

describe('generateData', () => {
it('should generate data correctly for a single user', async () => {
const payloads: Payload[] = [
{
email: 'haaron@braves.com',
phone: '555-555-5555',
name: {
first: 'Henry',
last: 'Aaron'
},
externalId: '5',
// Batching fields should be ignored when generating data
enable_batching: true,
batch_size: 10000
}
]

expect(generateData(payloads)).toEqual([
[
'5', // external_id is not hashed or normalized
hash(normalizationFunctions.get('email')!(payloads[0].email || '')), // email
hash(normalizationFunctions.get('phone')!(payloads[0].phone || '')), // phone
EMPTY, // gender
EMPTY, // year
EMPTY, // month
EMPTY, // day
hash(normalizationFunctions.get('last')!(payloads[0].name?.last || '')), // last_name
hash(normalizationFunctions.get('first')!(payloads[0].name?.first || '')), // first_name
EMPTY, // first_initial
EMPTY, // city
EMPTY, // state
EMPTY, // zip
EMPTY, // mobile_advertiser_id,
EMPTY // country
]
])
})

it('should generate data correctly for multiple users', async () => {
const payloads: Payload[] = new Array(2)

payloads[0] = {
email: 'haaron@braves.com',
phone: '555-555-5555',
name: {
first: 'Henry',
last: 'Aaron'
},
externalId: '5',
enable_batching: true,
batch_size: 10000
}

payloads[1] = {
externalId: '6',
email: 'tony@padres.com',
gender: 'male',
birth: {
year: '1960',
month: 'May',
day: '9'
},
name: {
first: 'Tony',
last: 'Gwynn',
firstInitial: 'T'
},
city: 'San Diego',
state: 'CA',
zip: '92000',
country: 'US',
enable_batching: true,
batch_size: 10000
}

expect(generateData(payloads)).toEqual([
[
'5', // external_id
hash(normalizationFunctions.get('email')!(payloads[0].email || '')),
hash(normalizationFunctions.get('phone')!(payloads[0].phone || '')),
EMPTY, // gender
EMPTY, // year
EMPTY, // month
EMPTY, // day
hash(normalizationFunctions.get('last')!(payloads[0].name?.last || '')),
hash(normalizationFunctions.get('first')!(payloads[0].name?.first || '')),
EMPTY, // first_initial
EMPTY, // city
EMPTY, // state
EMPTY, // zip
EMPTY, // mobile_advertiser_id,
EMPTY // country
],
[
'6', // external_id
hash(normalizationFunctions.get('email')!(payloads[1].email || '')),
EMPTY, // phone
hash(normalizationFunctions.get('gender')!(payloads[1].gender || '')),
hash(normalizationFunctions.get('year')!(payloads[1].birth?.year || '')),
hash(normalizationFunctions.get('month')!(payloads[1].birth?.month || '')),
hash(normalizationFunctions.get('day')!(payloads[1].birth?.day || '')),
hash(normalizationFunctions.get('last')!(payloads[1].name?.last || '')),
hash(normalizationFunctions.get('first')!(payloads[1].name?.first || '')),
hash(normalizationFunctions.get('firstInitial')!(payloads[1].name?.firstInitial || '')),
hash(normalizationFunctions.get('city')!(payloads[1].city || '')),
hash(normalizationFunctions.get('state')!(payloads[1].state || '')),
hash(normalizationFunctions.get('zip')!(payloads[1].zip || '')),
EMPTY, // mobile_advertiser_id,
hash(normalizationFunctions.get('country')!(payloads[1].country || ''))
]
])
})
})

describe('data normalization', () => {
// Test cases derived from a CSV provided by Facebook
// https://developers.facebook.com/docs/marketing-api/audiences/guides/custom-audiences#example_sha256

const emails = [
['NICK@EMAIL.com', 'nick@email.com'],
[' John_Smith@gmail.com ', 'john_smith@gmail.com'],
['someone@domain.com', 'someone@domain.com'],
[' SomeOne@domain.com ', 'someone@domain.com']
]

const phones = [
['+1 (616) 954-78 88', '16169547888'],
['1(650)123-4567', '16501234567'],
['+001 (616) 954-78 88', '16169547888'],
['01(650)123-4567', '16501234567'],
['4792813113', '4792813113'],
['3227352263', '3227352263']
]

const genders = [
['male', 'm'],
['Male', 'm'],
['Boy', 'm'],
['M', 'm'],
['m', 'm'],
['Girl', 'f'],
[' Woman ', 'f'],
['Female', 'f'],
['female', 'f']
]

const years = [[' 2000 ', '2000']]
const months = [
[' January', '01'],
['October', '10'],
[' 12 ', '12']
]

const names = [
['John', 'john'],
[" Na'than-Boile ", 'nathanboile'],
['정', '정'],
['Valéry', 'valéry'],
['Doe', 'doe'],
[' Doe-Doe ', 'doedoe']
]

const firstInitials = [['J', 'j', ' a ', 'a']]
const cities = [
['London', 'london'],
['Menlo Park', 'menlopark'],
[' Menlo-Park ', 'menlopark']
]
const states = [
[' California. ', 'california'],
['CA', 'ca'],
[' TE', 'te'],
[' C/a/lifo,rnia. ', 'california']
]
const zips = [
['37221', '37221'],
['37221-3312', '37221']
]
const countries = [
[' United States ', 'unitedstates'],
[' US ', 'us']
]

const dataTypes = new Map([
['email', emails],
['phone', phones],
['gender', genders],
['year', years],
['month', months],
['first', names],
['last', names],
['firstInitial', firstInitials],
['city', cities],
['state', states],
['zip', zips],
['country', countries]
])

dataTypes.forEach((testExamples, dataType) => {
describe(`should normalize ${dataType}`, () => {
const normalizationFunction = normalizationFunctions.get(dataType)

if (!normalizationFunction || typeof normalizationFunction !== 'function') {
throw new Error(`Normalization function not found for ${dataType}`)
}

testExamples.forEach(([input, expected]) => {
it(`should normalize ${input} to ${expected}`, async () => {
try {
expect(normalizationFunction(input)).toBe(expected)
} catch (e) {
throw new Error(`Failed for ${dataType} with input ${input}: ${e}`)
}
})
})
})
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,20 @@ const getAudienceUrl = `https://graph.facebook.com/${FACEBOOK_API_VERSION}/`
const createAudienceUrl = `https://graph.facebook.com/${FACEBOOK_API_VERSION}/act_${adAccountId}`

const createAudienceInput = {
settings: {},
settings: {
retlAdAccountId: '123'
},
audienceName: '',
audienceSettings: {
adAccountId: adAccountId,
engageAdAccountId: adAccountId,
audienceDescription: 'We are the Mario Brothers and plumbing is our game.'
}
}
const getAudienceInput = {
externalId: audienceId,
settings: {}
settings: {
retlAdAccountId: '123'
}
}

describe('Facebook Custom Audiences', () => {
Expand All @@ -29,15 +33,15 @@ describe('Facebook Custom Audiences', () => {

it('should fail if no ad account ID is set', async () => {
createAudienceInput.audienceName = 'The Void'
createAudienceInput.audienceSettings.adAccountId = 0
createAudienceInput.audienceSettings.engageAdAccountId = 0
await expect(testDestination.createAudience(createAudienceInput)).rejects.toThrowError(IntegrationError)
})

it('should create a new Facebook Audience', async () => {
nock(createAudienceUrl).post('/customaudiences').reply(200, { id: '88888888888888888' })

createAudienceInput.audienceName = 'The Super Mario Brothers Fans'
createAudienceInput.audienceSettings.adAccountId = adAccountId
createAudienceInput.audienceSettings.engageAdAccountId = adAccountId

const r = await testDestination.createAudience(createAudienceInput)
expect(r).toEqual({ externalId: '88888888888888888' })
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
export const US_STATE_CODES = new Map<string, string>([
['arizona', 'az'],
['alabama', 'al'],
['alaska', 'ak'],
['arkansas', 'ar'],
['california', 'ca'],
['colorado', 'co'],
['connecticut', 'ct'],
['delaware', 'de'],
['florida', 'fl'],
['georgia', 'ga'],
['hawaii', 'hi'],
['idaho', 'id'],
['illinois', 'il'],
['indiana', 'in'],
['iowa', 'ia'],
['kansas', 'ks'],
['kentucky', 'ky'],
['louisiana', 'la'],
['maine', 'me'],
['maryland', 'md'],
['massachusetts', 'ma'],
['michigan', 'mi'],
['minnesota', 'mn'],
['mississippi', 'ms'],
['missouri', 'mo'],
['montana', 'mt'],
['nebraska', 'ne'],
['nevada', 'nv'],
['newhampshire', 'nh'],
['newjersey', 'nj'],
['newmexico', 'nm'],
['newyork', 'ny'],
['northcarolina', 'nc'],
['northdakota', 'nd'],
['ohio', 'oh'],
['oklahoma', 'ok'],
['oregon', 'or'],
['pennsylvania', 'pa'],
['rhodeisland', 'ri'],
['southcarolina', 'sc'],
['southdakota', 'sd'],
['tennessee', 'tn'],
['texas', 'tx'],
['utah', 'ut'],
['vermont', 'vt'],
['virginia', 'va'],
['washington', 'wa'],
['westvirginia', 'wv'],
['wisconsin', 'wi'],
['wyoming', 'wy']
])
Loading
Loading