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

feat: changes for supporting record event in FB audience #3351

Merged
merged 17 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
2 changes: 2 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ type Destination = {
WorkspaceID: string;
Transformations: UserTransformationInput[];
RevisionID?: string;
Vikas26021999 marked this conversation as resolved.
Show resolved Hide resolved
IsProcessorEnabled?: boolean;
IsConnectionEnabled?: boolean;
};

type UserTransformationLibrary = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
});

if (nullUserData) {
stats.increment('fb_custom_audience_event_having_all_null_field_values_for_a_user', {

Check warning on line 83 in src/v0/destinations/fb_custom_audience/recordTransform.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/fb_custom_audience/recordTransform.js#L83

Added line #L83 was not covered by tests
destinationId: destination.ID,
nullFields: userSchema,
});
Expand Down Expand Up @@ -136,18 +136,18 @@
// maxUserCount validation
const maxUserCountNumber = parseInt(maxUserCount, 10);
if (Number.isNaN(maxUserCountNumber)) {
throw new ConfigurationError('Batch size must be an Integer.');

Check warning on line 139 in src/v0/destinations/fb_custom_audience/recordTransform.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/fb_custom_audience/recordTransform.js#L139

Added line #L139 was not covered by tests
}

// audience id validation
let operationAudienceId = audienceId;
const mappedToDestination = get(message, MappedToDestinationKey);
if (!operationAudienceId && mappedToDestination) {
if (mappedToDestination) {
const { objectType } = getDestinationExternalIDInfoForRetl(message, 'FB_CUSTOM_AUDIENCE');
operationAudienceId = objectType;
}
if (!isDefinedAndNotNullAndNotEmpty(operationAudienceId)) {
throw new ConfigurationError('Audience ID is a mandatory field');

Check warning on line 150 in src/v0/destinations/fb_custom_audience/recordTransform.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/fb_custom_audience/recordTransform.js#L150

Added line #L150 was not covered by tests
}

// user schema validation
Expand All @@ -156,21 +156,21 @@
userSchema = getSchemaForEventMappedToDest(message);
krishna2020 marked this conversation as resolved.
Show resolved Hide resolved
}
if (!Array.isArray(userSchema)) {
userSchema = [userSchema];

Check warning on line 159 in src/v0/destinations/fb_custom_audience/recordTransform.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/fb_custom_audience/recordTransform.js#L159

Added line #L159 was not covered by tests
}
if (!checkSubsetOfArray(schemaFields, userSchema)) {
throw new ConfigurationError('One or more of the schema fields are not supported');

Check warning on line 162 in src/v0/destinations/fb_custom_audience/recordTransform.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/fb_custom_audience/recordTransform.js#L162

Added line #L162 was not covered by tests
}

const paramsPayload = {};

if (isRaw) {
paramsPayload.is_raw = isRaw;

Check warning on line 168 in src/v0/destinations/fb_custom_audience/recordTransform.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/fb_custom_audience/recordTransform.js#L168

Added line #L168 was not covered by tests
}

const dataSource = getDataSource(type, subType);
if (Object.keys(dataSource).length > 0) {
paramsPayload.data_source = dataSource;

Check warning on line 173 in src/v0/destinations/fb_custom_audience/recordTransform.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/fb_custom_audience/recordTransform.js#L173

Added line #L173 was not covered by tests
}

const groupedRecordsByAction = lodash.groupBy(groupedRecordInputs, (record) =>
Expand Down Expand Up @@ -265,7 +265,7 @@
}

if (finalResponse.length === 0) {
throw new InstrumentationError(

Check warning on line 268 in src/v0/destinations/fb_custom_audience/recordTransform.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/fb_custom_audience/recordTransform.js#L268

Added line #L268 was not covered by tests
'Missing valid parameters, unable to generate transformed payload',
);
}
Expand Down
13 changes: 8 additions & 5 deletions src/v0/destinations/fb_custom_audience/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,20 @@

const { MappedToDestinationKey } = require('../../../constants');
const { processRecordInputs } = require('./recordTransform');
const logger = require('../../../logger');

function extraKeysPresent(dictionary, keyList) {
function checkForUnsupportedEventTypes(dictionary, keyList) {
const unsupportedEventTypes = [];
// eslint-disable-next-line no-restricted-syntax
for (const key in dictionary) {
if (!keyList.includes(key)) {
return true;
unsupportedEventTypes.push(key);

Check warning on line 31 in src/v0/destinations/fb_custom_audience/transform.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/fb_custom_audience/transform.js#L31

Added line #L31 was not covered by tests
}
}
return false;
return unsupportedEventTypes;
}

// Function responsible prepare the payload field of every event parameter

const preparePayload = (
userUpdateList,
userSchema,
Expand Down Expand Up @@ -244,8 +245,10 @@
let transformedAudienceEvent = [];

const eventTypes = ['record', 'audiencelist'];
if (extraKeysPresent(groupedInputs, eventTypes)) {
const unsupportedEventList = checkForUnsupportedEventTypes(groupedInputs, eventTypes);
if (unsupportedEventList.length > 0) {
logger.info(`unsupported events found ${unsupportedEventList}`);
throw new ConfigurationError('unsupported events present in the event');

Check warning on line 251 in src/v0/destinations/fb_custom_audience/transform.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/fb_custom_audience/transform.js#L250-L251

Added lines #L250 - L251 were not covered by tests
krishna2020 marked this conversation as resolved.
Show resolved Hide resolved
}

if (groupedInputs.record) {
Expand Down
122 changes: 122 additions & 0 deletions src/v0/destinations/fb_custom_audience/util.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
const { getDataSource, responseBuilderSimple, getUpdatedDataElement } = require('./util');

const basePayload = {
responseField: {
access_token: 'ABC',
payload: {
schema: ['EMAIL', 'FI'],
data: [
[
'b100c2ec0718fe6b4805b623aeec6710719d042ceea55f5c8135b010ec1c7b36',
'1e14a2f476f7611a8b22bc85d14237fdc88aac828737e739416c32c5bce3bd16',
],
],
},
},
};

const baseResponse = {
version: '1',
type: 'REST',
endpoint: 'https://graph.facebook.com/v18.0/23848494844100489/users',
headers: {},
params: {
access_token: 'ABC',
payload: {
schema: ['EMAIL', 'FI'],
data: [
[
'b100c2ec0718fe6b4805b623aeec6710719d042ceea55f5c8135b010ec1c7b36',
'1e14a2f476f7611a8b22bc85d14237fdc88aac828737e739416c32c5bce3bd16',
],
],
},
},
body: {
JSON: {},
JSON_ARRAY: {},
XML: {},
FORM: {},
},
files: {},
};

describe('FB_custom_audience utils test', () => {
describe('getDataSource function tests', () => {
it('Should return empty datasource if type and subType are both NA', () => {
const expectedDataSource = {};
const dataSource = getDataSource('NA', 'NA');
expect(dataSource).toEqual(expectedDataSource);
});
it('Should set subType and type if value present in destination config macthes with preset list', () => {
const expectedDataSource = {
type: 'EVENT_BASED',
};
const dataSource = getDataSource('EVENT_BASED', 'something');
expect(dataSource).toEqual(expectedDataSource);
});
});

describe('responseBuilderSimple function tests', () => {
it('Should return correct response for add payload', () => {
const payload = basePayload;
payload.operationCategory = 'add';
const expectedResponse = baseResponse;
expectedResponse.method = 'POST';
const response = responseBuilderSimple(payload, '23848494844100489');
expect(response).toEqual(expectedResponse);
});

it('Should return correct response for delete payload', () => {
const payload = basePayload;
payload.operationCategory = 'remove';
const expectedResponse = baseResponse;
expectedResponse.method = 'DELETE';
const response = responseBuilderSimple(payload, '23848494844100489');
expect(response).toEqual(expectedResponse);
});

it('Should throw error if payload is empty', () => {
try {
const response = responseBuilderSimple(payload, '');
expect(response).toEqual();
} catch (error) {
expect(error.message).toEqual(`payload is not defined`);
}
});
});

describe('getUpdatedDataElement function tests', () => {
it('Should hash field if isHashRequired is set to true', () => {
const expectedDataElement = [
'59107c750fd5ee2758d1988f2bf12d9f110439221ebdb7997e70d6a2c1c5afda',
];
let dataElement = [];
dataElement = getUpdatedDataElement(dataElement, true, 'FN', 'some-name');
expect(dataElement).toEqual(expectedDataElement);
});

it('Should not hash field if isHashRequired is set to false', () => {
const expectedDataElement = ['some-name'];
let dataElement = [];
dataElement = getUpdatedDataElement(dataElement, false, 'FN', 'some-name');
expect(dataElement).toEqual(expectedDataElement);
});

it('Should not hash MADID or EXTERN_ID and just pass value', () => {
const expectedDataElement = ['some-id', 'some-ext-id'];
let dataElement = [];
dataElement = getUpdatedDataElement(dataElement, true, 'MADID', 'some-id');
dataElement = getUpdatedDataElement(dataElement, true, 'EXTERN_ID', 'some-ext-id');
expect(dataElement).toEqual(expectedDataElement);
});

it('Should not hash MADID or EXTERN_ID and just pass empty value if value does not exist', () => {
const expectedDataElement = ['', ''];
let dataElement = [];
dataElement = getUpdatedDataElement(dataElement, true, 'MADID', '');
dataElement = getUpdatedDataElement(dataElement, true, 'EXTERN_ID', '');
expect(dataElement).toEqual(expectedDataElement);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { Destination, RouterTransformationRequest } from '../../../../../src/types';
import { generateMetadata } from '../../../testUtils';

const destination: Destination = {
Config: {
accessToken: 'ABC',
disableFormat: false,
isHashRequired: true,
isRaw: false,
maxUserCount: '50',
oneTrustCookieCategories: [],
skipVerify: false,
subType: 'NA',
type: 'NA',
userSchema: ['EMAIL'],
},
ID: '1mMy5cqbtfuaKZv1IhVQKnBdVwe',
Name: 'FB_CUSTOM_AUDIENCE',
Enabled: true,
WorkspaceID: '1TSN08muJTZwH8iCDmnnRt1pmLd',
DestinationDefinition: {
ID: '1aIXqM806xAVm92nx07YwKbRrO9',
Name: 'FB_CUSTOM_AUDIENCE',
DisplayName: 'FB_CUSTOM_AUDIENCE',
Config: {},
},
Transformations: [],
IsConnectionEnabled: true,
IsProcessorEnabled: true,
};

export const rETLAudienceRouterRequest: RouterTransformationRequest = {
input: [
{
message: {
sentAt: '2023-03-30 06:42:55.991938402 +0000 UTC',
userId: '2MUWghI7u85n91dd1qzGyswpZan-2MUWqbQqvctyfMGqU9QCNadpKNy',
channel: 'sources',
messageId: '4d906837-031d-4d34-b97a-62fdf51b4d3a',
event: 'Add_Audience',
context: {
destinationFields: 'EMAIL, FN',
externalId: [{ type: 'FB_CUSTOM_AUDIENCE-23848494844100489', identifierType: 'EMAIL' }],
mappedToDestination: 'true',
sources: {
job_run_id: 'cgiiurt8um7k7n5dq480',
task_run_id: 'cgiiurt8um7k7n5dq48g',
job_id: '2MUWghI7u85n91dd1qzGyswpZan',
version: '895/merge',
},
},
recordId: '725ad989-6750-4839-b46b-0ddb3b8e5aa2/1/10',
rudderId: '85c49666-c628-4835-937b-8f1d9ee7a724',
properties: {
listData: {
add: [
{ EMAIL: 'dede@gmail.com', FN: 'vishwa' },
{ EMAIL: 'fchsjjn@gmail.com', FN: 'hskks' },
{ EMAIL: 'fghjnbjk@gmail.com', FN: 'ghfry' },
{ EMAIL: 'gvhjkk@gmail.com', FN: 'hbcwqe' },
{ EMAIL: 'qsdwert@egf.com', FN: 'dsfds' },
{ EMAIL: 'ascscxsaca@com', FN: 'scadscdvcda' },
{ EMAIL: 'abc@gmail.com', FN: 'subscribed' },
{ EMAIL: 'ddwnkl@gmail.com', FN: 'subscribed' },
{ EMAIL: 'subscribed@eewrfrd.com', FN: 'pending' },
{ EMAIL: 'acsdvdf@ddfvf.com', FN: 'pending' },
],
},
},
type: 'audienceList',
anonymousId: '63228b51-394e-4ca2-97a0-427f6187480b',
},
destination: destination,
metadata: generateMetadata(3),
},
{
message: {
sentAt: '2023-03-30 06:42:55.991938402 +0000 UTC',
userId: '2MUWghI7u85n91dd1qzGyswpZan-2MUWqbQqvctyfMGqU9QCNadpKNy',
channel: 'sources',
messageId: '4d906837-031d-4d34-b97a-62fdf51b4d3a',
event: 'Add_Audience',
context: {
externalId: [{ type: 'FB_CUSTOM_AUDIENCE-23848494844100489', identifierType: 'EMAIL' }],
mappedToDestination: 'true',
sources: {
job_run_id: 'cgiiurt8um7k7n5dq480',
task_run_id: 'cgiiurt8um7k7n5dq48g',
job_id: '2MUWghI7u85n91dd1qzGyswpZan',
version: '895/merge',
},
},
recordId: '725ad989-6750-4839-b46b-0ddb3b8e5aa2/1/10',
rudderId: '85c49666-c628-4835-937b-8f1d9ee7a724',
properties: {
listData: {
add: [
{ EMAIL: 'dede@gmail.com', FN: 'vishwa' },
{ EMAIL: 'fchsjjn@gmail.com', FN: 'hskks' },
{ EMAIL: 'fghjnbjk@gmail.com', FN: 'ghfry' },
{ EMAIL: 'gvhjkk@gmail.com', FN: 'hbcwqe' },
{ EMAIL: 'qsdwert@egf.com', FN: 'dsfds' },
{ EMAIL: 'ascscxsaca@com', FN: 'scadscdvcda' },
{ EMAIL: 'abc@gmail.com', FN: 'subscribed' },
{ EMAIL: 'ddwnkl@gmail.com', FN: 'subscribed' },
{ EMAIL: 'subscribed@eewrfrd.com', FN: 'pending' },
{ EMAIL: 'acsdvdf@ddfvf.com', FN: 'pending' },
],
},
},
type: 'audienceList',
anonymousId: '63228b51-394e-4ca2-97a0-427f6187480b',
},
destination: destination,
metadata: generateMetadata(4),
},
],
destType: 'fb_custom_audience',
};

module.exports = {
rETLAudienceRouterRequest,
};
Loading
Loading