diff --git a/src/v0/destinations/google_adwords_remarketing_lists/config.js b/src/v0/destinations/google_adwords_remarketing_lists/config.js index 0478a1b11b..1e943aee56 100644 --- a/src/v0/destinations/google_adwords_remarketing_lists/config.js +++ b/src/v0/destinations/google_adwords_remarketing_lists/config.js @@ -7,6 +7,7 @@ const CONFIG_CATEGORIES = { AUDIENCE_LIST: { type: 'audienceList', name: 'offlineDataJobs' }, ADDRESSINFO: { type: 'addressInfo', name: 'addressInfo' }, }; +const ADDRESS_INFO_ATTRIBUTES = ['firstName', 'lastName', 'country', 'postalCode']; const attributeMapping = { email: 'hashedEmail', phone: 'hashedPhoneNumber', @@ -31,6 +32,7 @@ module.exports = { hashAttributes, offlineDataJobsMapping: MAPPING_CONFIG[CONFIG_CATEGORIES.AUDIENCE_LIST.name], addressInfoMapping: MAPPING_CONFIG[CONFIG_CATEGORIES.ADDRESSINFO.name], + ADDRESS_INFO_ATTRIBUTES, consentConfigMap, destType: 'google_adwords_remarketing_lists', }; diff --git a/src/v0/destinations/google_adwords_remarketing_lists/recordTransform.js b/src/v0/destinations/google_adwords_remarketing_lists/recordTransform.js index f8a2b0e586..1c6284cd09 100644 --- a/src/v0/destinations/google_adwords_remarketing_lists/recordTransform.js +++ b/src/v0/destinations/google_adwords_remarketing_lists/recordTransform.js @@ -11,7 +11,11 @@ const { isEventSentByVDMV2Flow, } = require('../../util'); const { populateConsentFromConfig } = require('../../util/googleUtils'); -const { populateIdentifiers, responseBuilder, getOperationAudienceId } = require('./util'); +const { + populateIdentifiersForRecordEvent, + responseBuilder, + getOperationAudienceId, +} = require('./util'); const { getErrorResponse, createFinalResponse } = require('../../util/recordUtils'); const { offlineDataJobsMapping, consentConfigMap } = require('./config'); @@ -23,6 +27,7 @@ const processRecordEventArray = ( developerToken, audienceId, typeOfList, + userSchema, isHashRequired, operationType, ) => { @@ -36,10 +41,10 @@ const processRecordEventArray = ( metadata.push(record.metadata); }); - const userIdentifiersList = populateIdentifiers( + const userIdentifiersList = populateIdentifiersForRecordEvent( fieldsArray, - destination, typeOfList, + userSchema, isHashRequired, ); @@ -91,7 +96,7 @@ function preparepayload(events, config) { const { destination, message, metadata } = events[0]; const accessToken = getAccessToken(metadata, 'access_token'); const developerToken = getValueFromMessage(metadata, 'secret.developer_token'); - const { audienceId, typeOfList, isHashRequired } = config; + const { audienceId, typeOfList, isHashRequired, userSchema } = config; const groupedRecordsByAction = lodash.groupBy(events, (record) => record.message.action?.toLowerCase(), @@ -110,6 +115,7 @@ function preparepayload(events, config) { developerToken, audienceId, typeOfList, + userSchema, isHashRequired, 'remove', ); @@ -124,6 +130,7 @@ function preparepayload(events, config) { developerToken, audienceId, typeOfList, + userSchema, isHashRequired, 'add', ); @@ -138,6 +145,7 @@ function preparepayload(events, config) { developerToken, audienceId, typeOfList, + userSchema, isHashRequired, 'add', ); @@ -161,19 +169,26 @@ function preparepayload(events, config) { function processRecordInputsV0(groupedRecordInputs) { const { destination, message } = groupedRecordInputs[0]; - const { audienceId, typeOfList, isHashRequired } = destination.Config; + const { audienceId, typeOfList, isHashRequired, userSchema } = destination.Config; return preparepayload(groupedRecordInputs, { audienceId: getOperationAudienceId(audienceId, message), typeOfList, + userSchema, isHashRequired, }); } function processRecordInputsV1(groupedRecordInputs) { - const { connection } = groupedRecordInputs[0]; + const { connection, message } = groupedRecordInputs[0]; const { audienceId, typeOfList, isHashRequired } = connection.config.destination; + const identifiers = message?.identifiers; + let userSchema; + if (identifiers) { + userSchema = Object.keys(identifiers); + } + const events = groupedRecordInputs.map((record) => ({ ...record, message: { @@ -185,6 +200,7 @@ function processRecordInputsV1(groupedRecordInputs) { return preparepayload(events, { audienceId, typeOfList, + userSchema, isHashRequired, }); } diff --git a/src/v0/destinations/google_adwords_remarketing_lists/transform.js b/src/v0/destinations/google_adwords_remarketing_lists/transform.js index 299ab94846..4d173589e8 100644 --- a/src/v0/destinations/google_adwords_remarketing_lists/transform.js +++ b/src/v0/destinations/google_adwords_remarketing_lists/transform.js @@ -37,7 +37,7 @@ function extraKeysPresent(dictionary, keyList) { const createPayload = (message, destination) => { const { listData } = message.properties; const properties = ['add', 'remove']; - const { typeOfList, isHashRequired } = destination.Config; + const { typeOfList, userSchema, isHashRequired } = destination.Config; let outputPayloads = {}; const typeOfOperation = Object.keys(listData); @@ -45,8 +45,8 @@ const createPayload = (message, destination) => { if (properties.includes(key)) { const userIdentifiersList = populateIdentifiers( listData[key], - destination, typeOfList, + userSchema, isHashRequired, ); if (userIdentifiersList.length === 0) { diff --git a/src/v0/destinations/google_adwords_remarketing_lists/util.js b/src/v0/destinations/google_adwords_remarketing_lists/util.js index f4c33a9a6f..8e0aed0365 100644 --- a/src/v0/destinations/google_adwords_remarketing_lists/util.js +++ b/src/v0/destinations/google_adwords_remarketing_lists/util.js @@ -18,6 +18,7 @@ const { TYPEOFLIST, BASE_ENDPOINT, hashAttributes, + ADDRESS_INFO_ATTRIBUTES, } = require('./config'); const hashEncrypt = (object) => { @@ -68,14 +69,13 @@ const responseBuilder = ( * Logics: Here we are creating an array with all the attributes provided in the add/remove array * inside listData. * @param {Array} attributeArray rudder event message properties listData add - * @param {object} Config rudder event destination * @param {string} typeOfList + * @param {Array} userSchema * @param {boolean} isHashRequired * @returns */ -const populateIdentifiers = (attributeArray, { Config }, typeOfList, isHashRequired) => { +const populateIdentifiers = (attributeArray, typeOfList, userSchema, isHashRequired) => { const userIdentifier = []; - const { userSchema } = Config; let attribute; if (TYPEOFLIST[typeOfList]) { attribute = TYPEOFLIST[typeOfList]; @@ -115,6 +115,40 @@ const populateIdentifiers = (attributeArray, { Config }, typeOfList, isHashRequi return userIdentifier; }; +const populateIdentifiersForRecordEvent = ( + identifiersArray, + typeOfList, + userSchema, + isHashRequired, +) => { + const userIdentifiers = []; + + if (isDefinedAndNotNullAndNotEmpty(identifiersArray)) { + // traversing through every element in the add array + identifiersArray.forEach((identifiers) => { + if (isHashRequired) { + hashEncrypt(identifiers); + } + if (TYPEOFLIST[typeOfList] && identifiers[TYPEOFLIST[typeOfList]]) { + userIdentifiers.push({ [TYPEOFLIST[typeOfList]]: identifiers[TYPEOFLIST[typeOfList]] }); + } else { + Object.entries(attributeMapping).forEach(([key, mappedKey]) => { + if (identifiers[key] && userSchema.includes(key)) + userIdentifiers.push({ [mappedKey]: identifiers[key] }); + }); + const addressInfo = constructPayload(identifiers, addressInfoMapping); + if ( + isDefinedAndNotNullAndNotEmpty(addressInfo) && + (userSchema.includes('addressInfo') || + userSchema.some((schema) => ADDRESS_INFO_ATTRIBUTES.includes(schema))) + ) + userIdentifiers.push({ addressInfo }); + } + }); + } + return userIdentifiers; +}; + const getOperationAudienceId = (audienceId, message) => { let operationAudienceId = audienceId; const mappedToDestination = get(message, MappedToDestinationKey); @@ -132,4 +166,5 @@ module.exports = { populateIdentifiers, responseBuilder, getOperationAudienceId, + populateIdentifiersForRecordEvent, }; diff --git a/src/v0/destinations/google_adwords_remarketing_lists/util.test.js b/src/v0/destinations/google_adwords_remarketing_lists/util.test.js index 0b74b07b8e..a41c00f12f 100644 --- a/src/v0/destinations/google_adwords_remarketing_lists/util.test.js +++ b/src/v0/destinations/google_adwords_remarketing_lists/util.test.js @@ -199,8 +199,8 @@ describe('GARL utils test', () => { const { typeOfList, isHashRequired } = baseDestination.Config; const identifier = populateIdentifiers( attributeArray, - baseDestination, typeOfList, + baseDestination.Config.userSchema, isHashRequired, ); expect(identifier).toEqual(hashedArray); diff --git a/test/integrations/destinations/google_adwords_remarketing_lists/router/data.ts b/test/integrations/destinations/google_adwords_remarketing_lists/router/data.ts index a5e28996b1..6878e81f0d 100644 --- a/test/integrations/destinations/google_adwords_remarketing_lists/router/data.ts +++ b/test/integrations/destinations/google_adwords_remarketing_lists/router/data.ts @@ -1,5 +1,9 @@ import { rETLAudienceRouterRequest } from './audience'; -import { rETLRecordRouterRequest } from './record'; +import { + rETLRecordRouterRequest, + rETLRecordRouterRequestVDMv2General, + rETLRecordRouterRequestVDMv2UserId, +} from './record'; import { API_VERSION } from '../../../../../src/v0/destinations/google_adwords_remarketing_lists/config'; export const data = [ @@ -732,4 +736,221 @@ export const data = [ }, }, }, + { + name: 'google_adwords_remarketing_lists record event tests VDMv2 General typeOfList', + description: 'Test 2', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: rETLRecordRouterRequestVDMv2General, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batchedRequest: [ + { + version: '1', + type: 'REST', + method: 'POST', + endpoint: `https://googleads.googleapis.com/${API_VERSION}/customers/7693729833/offlineUserDataJobs`, + headers: { + Authorization: 'Bearer default-accessToken', + 'Content-Type': 'application/json', + }, + params: { + listId: '7090784486', + customerId: '7693729833', + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, + }, + body: { + JSON: { + operations: [ + { + create: { + userIdentifiers: [ + { + hashedEmail: + 'd3142c8f9c9129484daf28df80cc5c955791efed5e69afabb603bc8cb9ffd419', + }, + { + hashedPhoneNumber: + '8846dcb6ab2d73a0e67dbd569fa17cec2d9d391e5b05d1dd42919bc21ae82c45', + }, + { + addressInfo: { + hashedFirstName: + '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', + hashedLastName: + 'dcf000c2386fb76d22cefc0d118a8511bb75999019cd373df52044bccd1bd251', + countryCode: 'US', + postalCode: '1245', + }, + }, + ], + }, + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + }, + ], + metadata: [ + { + attemptNum: 1, + destinationId: 'default-destinationId', + dontBatch: false, + secret: { + access_token: 'default-accessToken', + }, + sourceId: 'default-sourceId', + userId: 'default-userId', + workspaceId: 'default-workspaceId', + jobId: 1, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + rudderAccountId: '258Yea7usSKNpbkIaesL9oJ9iYw', + audienceId: '7090784486', + customerId: '7693729833', + loginCustomerId: '', + subAccount: false, + }, + DestinationDefinition: { + Config: {}, + DisplayName: 'GOOGLE_ADWORDS_REMARKETING_LISTS', + ID: '1aIXqM806xAVm92nx07YwKbRrO9', + Name: 'GOOGLE_ADWORDS_REMARKETING_LISTS', + }, + Enabled: true, + ID: '1mMy5cqbtfuaKZv1IhVQKnBdVwe', + IsConnectionEnabled: true, + IsProcessorEnabled: true, + Name: 'GOOGLE_ADWORDS_REMARKETING_LISTS', + Transformations: [], + WorkspaceID: '1TSN08muJTZwH8iCDmnnRt1pmLd', + }, + }, + ], + }, + }, + }, + }, + { + name: 'google_adwords_remarketing_lists record event tests VDMv2 UserId typeOfList', + description: 'Test 3', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: rETLRecordRouterRequestVDMv2UserId, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batchedRequest: [ + { + version: '1', + type: 'REST', + method: 'POST', + endpoint: `https://googleads.googleapis.com/${API_VERSION}/customers/7693729833/offlineUserDataJobs`, + headers: { + Authorization: 'Bearer default-accessToken', + 'Content-Type': 'application/json', + }, + params: { + listId: '7090784486', + customerId: '7693729833', + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, + }, + body: { + JSON: { + operations: [ + { + create: { + userIdentifiers: [ + { + thirdPartyUserId: 'useri1234', + }, + ], + }, + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + }, + ], + metadata: [ + { + attemptNum: 1, + destinationId: 'default-destinationId', + dontBatch: false, + secret: { + access_token: 'default-accessToken', + }, + sourceId: 'default-sourceId', + userId: 'default-userId', + workspaceId: 'default-workspaceId', + jobId: 2, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + rudderAccountId: '258Yea7usSKNpbkIaesL9oJ9iYw', + audienceId: '7090784486', + customerId: '7693729833', + loginCustomerId: '', + subAccount: false, + }, + DestinationDefinition: { + Config: {}, + DisplayName: 'GOOGLE_ADWORDS_REMARKETING_LISTS', + ID: '1aIXqM806xAVm92nx07YwKbRrO9', + Name: 'GOOGLE_ADWORDS_REMARKETING_LISTS', + }, + Enabled: true, + ID: '1mMy5cqbtfuaKZv1IhVQKnBdVwe', + IsConnectionEnabled: true, + IsProcessorEnabled: true, + Name: 'GOOGLE_ADWORDS_REMARKETING_LISTS', + Transformations: [], + WorkspaceID: '1TSN08muJTZwH8iCDmnnRt1pmLd', + }, + }, + ], + }, + }, + }, + }, ]; diff --git a/test/integrations/destinations/google_adwords_remarketing_lists/router/record.ts b/test/integrations/destinations/google_adwords_remarketing_lists/router/record.ts index de76aae17c..2661500b4d 100644 --- a/test/integrations/destinations/google_adwords_remarketing_lists/router/record.ts +++ b/test/integrations/destinations/google_adwords_remarketing_lists/router/record.ts @@ -1,4 +1,5 @@ -import { Destination, RouterTransformationRequest } from '../../../../../src/types'; +import { Connection, Destination, RouterTransformationRequest } from '../../../../../src/types'; +import { VDM_V2_SCHEMA_VERSION } from '../../../../../src/v0/util/constant'; import { generateGoogleOAuthMetadata } from '../../../testUtils'; const destination: Destination = { @@ -27,6 +28,57 @@ const destination: Destination = { IsProcessorEnabled: true, }; +const destination2: Destination = { + Config: { + rudderAccountId: '258Yea7usSKNpbkIaesL9oJ9iYw', + audienceId: '7090784486', + customerId: '7693729833', + loginCustomerId: '', + subAccount: false, + }, + ID: '1mMy5cqbtfuaKZv1IhVQKnBdVwe', + Name: 'GOOGLE_ADWORDS_REMARKETING_LISTS', + Enabled: true, + WorkspaceID: '1TSN08muJTZwH8iCDmnnRt1pmLd', + DestinationDefinition: { + ID: '1aIXqM806xAVm92nx07YwKbRrO9', + Name: 'GOOGLE_ADWORDS_REMARKETING_LISTS', + DisplayName: 'GOOGLE_ADWORDS_REMARKETING_LISTS', + Config: {}, + }, + Transformations: [], + IsConnectionEnabled: true, + IsProcessorEnabled: true, +}; + +const connection1: Connection = { + sourceId: '2MUWghI7u85n91dd1qzGyswpZan', + destinationId: '1mMy5cqbtfuaKZv1IhVQKnBdVwe', + enabled: true, + config: { + destination: { + schemaVersion: VDM_V2_SCHEMA_VERSION, + isHashRequired: true, + typeOfList: 'General', + audienceId: '7090784486', + }, + }, +}; + +const connection2: Connection = { + sourceId: '2MUWghI7u85n91dd1qzGyswpZan', + destinationId: '1mMy5cqbtfuaKZv1IhVQKnBdVwe', + enabled: true, + config: { + destination: { + schemaVersion: VDM_V2_SCHEMA_VERSION, + isHashRequired: true, + typeOfList: 'userID', + audienceId: '7090784486', + }, + }, +}; + export const rETLRecordRouterRequest: RouterTransformationRequest = { input: [ { @@ -153,6 +205,71 @@ export const rETLRecordRouterRequest: RouterTransformationRequest = { destType: 'google_adwords_remarketing_lists', }; +export const rETLRecordRouterRequestVDMv2General: RouterTransformationRequest = { + input: [ + { + destination: destination2, + connection: connection1, + message: { + action: 'insert', + context: { + ip: '14.5.67.21', + library: { + name: 'http', + }, + }, + recordId: '2', + rudderId: '2', + identifiers: { + email: 'test@abc.com', + phone: '@09876543210', + firstName: 'test', + lastName: 'rudderlabs', + country: 'US', + postalCode: '1245', + }, + type: 'record', + }, + metadata: generateGoogleOAuthMetadata(1), + }, + ], + destType: 'google_adwords_remarketing_lists', +}; + +export const rETLRecordRouterRequestVDMv2UserId: RouterTransformationRequest = { + input: [ + { + destination: destination2, + connection: connection2, + message: { + action: 'insert', + context: { + ip: '14.5.67.21', + library: { + name: 'http', + }, + }, + recordId: '2', + rudderId: '2', + identifiers: { + email: 'test@abc.com', + phone: '@09876543210', + firstName: 'test', + lastName: 'rudderlabs', + country: 'US', + postalCode: '1245', + thirdPartyUserId: 'useri1234', + }, + type: 'record', + }, + metadata: generateGoogleOAuthMetadata(2), + }, + ], + destType: 'google_adwords_remarketing_lists', +}; + module.exports = { rETLRecordRouterRequest, + rETLRecordRouterRequestVDMv2General, + rETLRecordRouterRequestVDMv2UserId, };