From 01167f2fb0af26f9a4721d706f11db33064dc8d5 Mon Sep 17 00:00:00 2001 From: shrouti1507 Date: Fri, 23 Feb 2024 18:41:06 +0530 Subject: [PATCH 01/17] feat: consent field support for GAOC --- src/constants/destinationCanonicalNames.js | 12 ++ .../config.js | 2 +- .../transform.js | 24 ++++ .../utils.js | 69 ++++++++++- .../utils.test.js | 103 +++++++++++++++- src/v0/util/googleUtils/index.js | 70 ++++++++--- src/v0/util/googleUtils/index.test.js | 50 -------- .../dataDelivery/data.ts | 14 +-- .../network.ts | 26 ++-- .../processor/data.ts | 114 +++++++++++++++--- .../router/data.ts | 28 ++++- .../processor/data.ts | 5 +- 12 files changed, 396 insertions(+), 121 deletions(-) delete mode 100644 src/v0/util/googleUtils/index.test.js diff --git a/src/constants/destinationCanonicalNames.js b/src/constants/destinationCanonicalNames.js index d1b2b24de0..a476637238 100644 --- a/src/constants/destinationCanonicalNames.js +++ b/src/constants/destinationCanonicalNames.js @@ -152,6 +152,18 @@ const DestCanonicalNames = { 'the trade desk', ], INTERCOM: ['INTERCOM', 'intercom', 'Intercom'], + GOOGLE_ADWORDS_REMARKETING_LISTS: [ + 'GOOGLE_ADWORDS_REMARKETING_LISTS', + 'google_adwords_remarketing_lists', + 'Google Adwords Remarketing Lists', + 'google adwords remarketing lists', + ], + GOOGLE_ADWORDS_OFFLINE_CONVERSIONS: [ + 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS', + 'google_adwords_offline_conversions', + 'Google Adwords Offline Conversions', + 'google adwords offline conversions', + ], }; module.exports = { DestHandlerMap, DestCanonicalNames }; diff --git a/src/v0/destinations/google_adwords_offline_conversions/config.js b/src/v0/destinations/google_adwords_offline_conversions/config.js index a02732894f..ea1a76e555 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/config.js +++ b/src/v0/destinations/google_adwords_offline_conversions/config.js @@ -1,6 +1,6 @@ const { getMappingConfig } = require('../../util'); -const API_VERSION = 'v14'; +const API_VERSION = 'v15'; const BASE_ENDPOINT = `https://googleads.googleapis.com/${API_VERSION}/customers/:customerId`; diff --git a/src/v0/destinations/google_adwords_offline_conversions/transform.js b/src/v0/destinations/google_adwords_offline_conversions/transform.js index 46cde72771..ae866e1659 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/transform.js +++ b/src/v0/destinations/google_adwords_offline_conversions/transform.js @@ -22,6 +22,7 @@ const { getStoreConversionPayload, requestBuilder, getClickConversionPayloadAndEndpoint, + populateConsentForGoogleDestinations, } = require('./utils'); const helper = require('./helper'); @@ -59,7 +60,30 @@ const getConversions = (message, metadata, { Config }, event, conversionType) => payload = constructPayload(message, trackCallConversionsMapping); endpoint = CALL_CONVERSION.replace(':customerId', filteredCustomerId); } + const consentObject = populateConsentForGoogleDestinations(message, conversionType); + if (Object.keys(consentObject)?.length > 0) { + if (payload?.conversions?.length > 0) { + if (conversionType === 'click' || conversionType === 'call') { + payload.conversions[0].consent = consentObject; + } + } else if ( + Object.keys(payload?.addConversionPayload?.operations?.create)?.length > 0 && + conversionType === 'store' + ) { + payload.addConversionPayload.operations.create.consent = consentObject; + } + } + // if(payload?.conversions?.length > 0) { + // // const consentObject = populateConsentForGoogleDestinations(message, conversionType); + // if(Object.keys(consentObject).length > 0) { + // if(conversionType === 'click' || conversionType === 'call') { + // payload.conversions[0].consent = consentObject; + // } else if(conversionType === 'store'){ + // payload.addConversionPayload.create.consent = consentObject; + // } + // } + // } if (conversionType !== 'store') { // transform originalTimestamp to conversionDateTime format (yyyy-mm-dd hh:mm:ss+|-hh:mm) // e.g 2019-10-14T11:15:18.299Z -> 2019-10-14 16:10:29+0530 diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.js b/src/v0/destinations/google_adwords_offline_conversions/utils.js index 19989d0eaa..c7e14a4adb 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.js @@ -17,6 +17,7 @@ const { isDefinedAndNotNull, getAuthErrCategoryFromStCode, getAccessToken, + getIntegrationsObj, } = require('../../util'); const { SEARCH_STREAM, @@ -65,7 +66,7 @@ const getConversionActionId = async (headers, params) => { feature: 'transformation', endpointPath: `/googleAds:searchStream`, requestMethod: 'POST', - module: 'dataDelivery' + module: 'dataDelivery', }); searchStreamResponse = processAxiosResponse(searchStreamResponse); if (!isHttpStatusSuccess(searchStreamResponse.status)) { @@ -362,6 +363,71 @@ const getClickConversionPayloadAndEndpoint = (message, Config, filteredCustomerI return { payload, endpoint }; }; +const GOOGLE_ALLOWED_CONSENT_STATUS = ['UNSPECIFIED', 'UNKNOWN', 'GRANTED', 'DENIED']; + +/** + * Populates the consent object based on the provided properties. + * + * @param {object} properties - message.properties containing properties related to consent. + * @returns {object} - An object containing consent information. + * * ref : + * 1) For click conversion : + * a) https://developers.google.com/google-ads/api/rest/reference/rest/v15/customers/uploadClickConversions#ClickConversion + * b) https://developers.google.com/google-ads/api/reference/rpc/v15/ClickConversion#consent + * 2) For Call conversion : + * a) https://developers.google.com/google-ads/api/rest/reference/rest/v15/customers/uploadCallConversions#CallConversion + * b) https://developers.google.com/google-ads/api/reference/rpc/v15/CallConversion#consent + * 3) For Store sales conversion : + * a) https://developers.google.com/google-ads/api/reference/rpc/v15/UserData + * b) https://developers.google.com/google-ads/api/reference/rpc/v15/UserData#consent + */ + +const populateConsentForGoogleDestinations = (message, conversionName) => { + // Define mappings for different conversion types + const mappings = { + store: { + adUserData: 'ad_user_data', + adPersonalization: 'ad_personalization', + }, + click: { + adUserData: 'adUserData', + adPersonalization: 'adPersonalization', + }, + call: { + adUserData: 'adUserData', + adPersonalization: 'adPersonalization', + }, + }; + + const currentMapping = mappings[conversionName]; + if (!currentMapping) return {}; + + const integrationObj = getIntegrationsObj(message, 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS') || {}; + const consents = integrationObj.consents || {}; + + // Define a function to process consent based on type and key + const processConsent = (consentType) => { + if (!consents[consentType]) return 'UNSPECIFIED'; + if (consents[consentType] && GOOGLE_ALLOWED_CONSENT_STATUS.includes(consents[consentType])) { + return consents[consentType]; + } + if (consents[consentType] && !GOOGLE_ALLOWED_CONSENT_STATUS.includes(consents[consentType])) { + return 'UNKNOWN'; + } + return null; + }; + + // Construct consentObj based on the current mapping + const consentObj = Object.keys(currentMapping).reduce((obj, consentType) => { + const key = currentMapping[consentType]; + // Process each consent type and assign it to the key specified in the mapping + // eslint-disable-next-line no-param-reassign + obj[key] = processConsent(consentType); + return obj; + }, {}); + + return consentObj; +}; module.exports = { validateDestinationConfig, generateItemListFromProducts, @@ -372,4 +438,5 @@ module.exports = { buildAndGetAddress, getClickConversionPayloadAndEndpoint, getExisitingUserIdentifier, + populateConsentForGoogleDestinations, }; diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js index 8deaa3ab0a..cf049ddcc2 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js @@ -2,6 +2,7 @@ const { getClickConversionPayloadAndEndpoint, buildAndGetAddress, getExisitingUserIdentifier, + populateConsentForGoogleDestinations, } = require('./utils'); const getTestMessage = () => { @@ -161,7 +162,7 @@ describe('getExisitingUserIdentifier util tests', () => { describe('getClickConversionPayloadAndEndpoint util tests', () => { it('getClickConversionPayloadAndEndpoint flow check when default field identifier is present', () => { let expectedOutput = { - endpoint: 'https://googleads.googleapis.com/v14/customers/9625812972:uploadClickConversions', + endpoint: 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', payload: { conversions: [ { @@ -187,7 +188,7 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => { delete fittingPayload.traits.email; delete fittingPayload.properties.email; let expectedOutput = { - endpoint: 'https://googleads.googleapis.com/v14/customers/9625812972:uploadClickConversions', + endpoint: 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', payload: { conversions: [ { @@ -215,7 +216,7 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => { delete fittingPayload.traits.phone; delete fittingPayload.properties.email; let expectedOutput = { - endpoint: 'https://googleads.googleapis.com/v14/customers/9625812972:uploadClickConversions', + endpoint: 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', payload: { conversions: [ { @@ -251,7 +252,7 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => { }, ]; let expectedOutput = { - endpoint: 'https://googleads.googleapis.com/v14/customers/9625812972:uploadClickConversions', + endpoint: 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', payload: { conversions: [ { @@ -273,3 +274,97 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => { ); }); }); + +describe('populateConsentForGoogleDestinations', () => { + // Returns an object with ad_user_data and ad_personalization properties set to UNSPECIFIED when no consents are provided + it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : store sales conversion without any mention in integrations object', () => { + const message = {}; + const conversionType = 'store'; + + const result = populateConsentForGoogleDestinations(message, conversionType); + + expect(result).toEqual({ + ad_user_data: 'UNSPECIFIED', + ad_personalization: 'UNSPECIFIED', + }); + }); + + // Returns an empty object when the destination name is not recognized + it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS: store sales conversions with integrations object', () => { + const message = { + integrations: { + google_adwords_offline_conversions: { + consents: { + adUserData: 'GRANTED', + adPersonalization: 'DENIED', + }, + }, + }, + }; + const conversionType = 'store'; + + const result = populateConsentForGoogleDestinations(message, conversionType); + + expect(result).toEqual({ + ad_personalization: 'DENIED', + ad_user_data: 'GRANTED', + }); + }); + + // Returns an object with ad_user_data and ad_personalization properties set to the provided consents when they are valid and present in the message properties + it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS: click conversion with integration object of allowed types', () => { + const message = { + integrations: { + google_adwords_offline_conversions: { + consents: { + adUserData: 'GRANTED', + adPersonalization: 'DENIED', + }, + }, + }, + }; + const conversionType = 'click'; + + const result = populateConsentForGoogleDestinations(message, conversionType); + + expect(result).toEqual({ + adUserData: 'GRANTED', + adPersonalization: 'DENIED', + }); + }); + + // Returns an object with ad_user_data and ad_personalization properties set to UNSPECIFIED when the provided consents are not valid or not present in the message properties + it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : click conversion with invalid consent value', () => { + const message = { + integrations: { + google_adwords_offline_conversions: { + consents: { + adUserData: 'GRANTED', + adPersonalization: 'INVALID', + }, + }, + }, + }; + const conversionType = 'click'; + + const result = populateConsentForGoogleDestinations(message, conversionType); + + expect(result).toEqual({ + adUserData: 'GRANTED', + adPersonalization: 'UNKNOWN', + }); + }); + + // Returns an empty object when the integration object is not present in the message + it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : call conversion without integrations object consent ', () => { + const message = {}; + const conversionType = 'call'; + + const result = populateConsentForGoogleDestinations(message, conversionType); + + expect(result).toEqual({ + adUserData: 'UNSPECIFIED', + adPersonalization: 'UNSPECIFIED', + }); + }); +}); diff --git a/src/v0/util/googleUtils/index.js b/src/v0/util/googleUtils/index.js index c8d872e90e..b1865ca1c6 100644 --- a/src/v0/util/googleUtils/index.js +++ b/src/v0/util/googleUtils/index.js @@ -1,4 +1,8 @@ +const get = require('get-value'); +const { getIntegrationsObj } = require('..'); + const GOOGLE_ALLOWED_CONSENT_STATUS = ['UNSPECIFIED', 'UNKNOWN', 'GRANTED', 'DENIED']; +const { MappedToDestinationKey } = require('../../../constants'); /** * Populates the consent object based on the provided properties. @@ -8,23 +12,53 @@ const GOOGLE_ALLOWED_CONSENT_STATUS = ['UNSPECIFIED', 'UNKNOWN', 'GRANTED', 'DEN * ref : https://developers.google.com/google-ads/api/rest/reference/rest/v15/Consent */ -const populateConsentForGoogleDestinations = (properties) => { - const consent = {}; - - if ( - properties?.userDataConsent && - GOOGLE_ALLOWED_CONSENT_STATUS.includes(properties.userDataConsent) - ) { - consent.adUserData = properties.userDataConsent; - } - - if ( - properties?.personalizationConsent && - GOOGLE_ALLOWED_CONSENT_STATUS.includes(properties.personalizationConsent) - ) { - consent.adPersonalization = properties.personalizationConsent; - } - return consent; +const populateConsentForGoogleDestinations = (message, conversionName) => { + const mappedToDestination = get(message, MappedToDestinationKey); + if (mappedToDestination) return {}; + // Define mappings for different conversion types + const mappings = { + store: { + ad_user_data: 'ad_user_data', + ad_personalization: 'ad_personalization', + }, + click: { + adUserData: 'adUserData', + adPersonalization: 'adPersonalization', + }, + call: { + adUserData: 'adUserData', + adPersonalization: 'adPersonalization', + }, + }; + + const currentMapping = mappings[conversionName]; + if (!currentMapping) return {}; + + const integrationObj = getIntegrationsObj(message, 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS') || {}; + const consents = integrationObj.consents || {}; + + // Define a function to process consent based on type and key + const processConsent = (consentType) => { + if (!consents[consentType]) return 'UNSPECIFIED'; + if (consents[consentType] && GOOGLE_ALLOWED_CONSENT_STATUS.includes(consents[consentType])) { + return consents[consentType]; + } + if (consents[consentType] && !GOOGLE_ALLOWED_CONSENT_STATUS.includes(consents[consentType])) { + return 'UNKNOWN'; + } + return null; + }; + + // Construct consentObj based on the current mapping + const consentObj = Object.keys(currentMapping).reduce((obj, consentType) => { + const key = currentMapping[consentType]; + // Process each consent type and assign it to the key specified in the mapping + // eslint-disable-next-line no-param-reassign + obj[key] = processConsent(consentType); + return obj; + }, {}); + + return consentObj; }; -module.exports = { populateConsentForGoogleDestinations }; +module.exports = { populateConsentForGoogleDestinations, GOOGLE_ALLOWED_CONSENT_STATUS }; diff --git a/src/v0/util/googleUtils/index.test.js b/src/v0/util/googleUtils/index.test.js deleted file mode 100644 index 27eff2a793..0000000000 --- a/src/v0/util/googleUtils/index.test.js +++ /dev/null @@ -1,50 +0,0 @@ -const { populateConsentForGoogleDestinations } = require('./index'); - -describe('unit test for populateConsentForGoogleDestinations', () => { - // Returns an empty object when no properties are provided. - it('should return an empty object when no properties are provided', () => { - const result = populateConsentForGoogleDestinations({}); - expect(result).toEqual({}); - }); - - // Sets adUserData property of consent object when userDataConsent property is provided and its value is one of the allowed consent statuses. - it('should set adUserData property of consent object when userDataConsent property is provided and its value is one of the allowed consent statuses', () => { - const properties = { userDataConsent: 'GRANTED' }; - const result = populateConsentForGoogleDestinations(properties); - expect(result).toEqual({ adUserData: 'GRANTED' }); - }); - - // Sets adPersonalization property of consent object when personalizationConsent property is provided and its value is one of the allowed consent statuses. - it('should set adPersonalization property of consent object when personalizationConsent property is provided and its value is one of the allowed consent statuses', () => { - const properties = { personalizationConsent: 'DENIED' }; - const result = populateConsentForGoogleDestinations(properties); - expect(result).toEqual({ adPersonalization: 'DENIED' }); - }); - - // Returns an empty object when properties parameter is not provided. - it('should return an empty object when properties parameter is not provided', () => { - const result = populateConsentForGoogleDestinations(); - expect(result).toEqual({}); - }); - - // Returns an empty object when properties parameter is null. - it('should return an empty object when properties parameter is null', () => { - const result = populateConsentForGoogleDestinations(null); - expect(result).toEqual({}); - }); - - // Returns an empty object when properties parameter is an empty object. - it('should return an empty object when properties parameter is an empty object', () => { - const result = populateConsentForGoogleDestinations({}); - expect(result).toEqual({}); - }); - - // Returns an empty object when properties parameter is an empty object. - it('should return an empty object when properties parameter contains adUserData and adPersonalization with non-allowed values', () => { - const result = populateConsentForGoogleDestinations({ - adUserData: 'RANDOM', - personalizationConsent: 'RANDOM', - }); - expect(result).toEqual({}); - }); -}); diff --git a/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/data.ts b/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/data.ts index ae75273399..9cc13d890b 100644 --- a/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/data.ts +++ b/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/data.ts @@ -12,7 +12,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/11122233331/offlineUserDataJobs', + 'https://googleads.googleapis.com/v15/customers/11122233331/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -91,7 +91,7 @@ export const data = [ code: 400, details: [ { - '@type': 'type.googleapis.com/google.ads.googleads.v14.errors.GoogleAdsFailure', + '@type': 'type.googleapis.com/google.ads.googleads.v15.errors.GoogleAdsFailure', errors: [ { errorCode: { @@ -152,7 +152,7 @@ export const data = [ version: '1', type: 'REST', method: 'POST', - endpoint: 'https://googleads.googleapis.com/v14/customers/1112223333/offlineUserDataJobs', + endpoint: 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -248,7 +248,7 @@ export const data = [ version: '1', type: 'REST', method: 'POST', - endpoint: 'https://googleads.googleapis.com/v14/customers/customerid/offlineUserDataJobs', + endpoint: 'https://googleads.googleapis.com/v15/customers/customerid/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -358,7 +358,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/1234567890:uploadClickConversions', + 'https://googleads.googleapis.com/v15/customers/1234567890:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -505,7 +505,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/1234567891:uploadClickConversions', + 'https://googleads.googleapis.com/v15/customers/1234567891:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -646,7 +646,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/1234567891:uploadClickConversions', + 'https://googleads.googleapis.com/v15/customers/1234567891:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', diff --git a/test/integrations/destinations/google_adwords_offline_conversions/network.ts b/test/integrations/destinations/google_adwords_offline_conversions/network.ts index 375879727b..2b22cc4a90 100644 --- a/test/integrations/destinations/google_adwords_offline_conversions/network.ts +++ b/test/integrations/destinations/google_adwords_offline_conversions/network.ts @@ -1,7 +1,7 @@ export const networkCallsData = [ { httpReq: { - url: 'https://googleads.googleapis.com/v14/customers/11122233331/offlineUserDataJobs:create', + url: 'https://googleads.googleapis.com/v15/customers/11122233331/offlineUserDataJobs:create', data: { job: { storeSalesMetadata: { @@ -30,7 +30,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v14/customers/1112223333/googleAds:searchStream', + url: 'https://googleads.googleapis.com/v15/customers/1112223333/googleAds:searchStream', data: { query: `SELECT conversion_action.id FROM conversion_action WHERE conversion_action.name = 'Sign-up - click'`, }, @@ -63,7 +63,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v14/customers/11122233331/offlineUserDataJobs/OFFLINE_USER_DATA_JOB_ID_FOR_ADD_FAILURE:addOperations', + url: 'https://googleads.googleapis.com/v15/customers/11122233331/offlineUserDataJobs/OFFLINE_USER_DATA_JOB_ID_FOR_ADD_FAILURE:addOperations', data: { enable_partial_failure: false, enable_warnings: false, @@ -108,7 +108,7 @@ export const networkCallsData = [ status: 'INVALID_ARGUMENT', details: [ { - '@type': 'type.googleapis.com/google.ads.googleads.v14.errors.GoogleAdsFailure', + '@type': 'type.googleapis.com/google.ads.googleads.v15.errors.GoogleAdsFailure', errors: [ { errorCode: { @@ -144,7 +144,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v14/customers/1112223333/offlineUserDataJobs:create', + url: 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs:create', data: { job: { storeSalesMetadata: { @@ -173,7 +173,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v14/customers/1112223333/offlineUserDataJobs/OFFLINE_USER_DATA_JOB_ID:addOperations', + url: 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs/OFFLINE_USER_DATA_JOB_ID:addOperations', data: { enable_partial_failure: false, enable_warnings: false, @@ -216,7 +216,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v14/customers/1112223333/offlineUserDataJobs/OFFLINE_USER_DATA_JOB_ID:run', + url: 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs/OFFLINE_USER_DATA_JOB_ID:run', data: { validate_only: false }, params: { destination: 'google_adwords_offline_conversion' }, headers: { @@ -236,7 +236,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v14/customers/customerid/offlineUserDataJobs:create', + url: 'https://googleads.googleapis.com/v15/customers/customerid/offlineUserDataJobs:create', data: { job: { storeSalesMetadata: { @@ -270,7 +270,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v14/customers/1234567890/googleAds:searchStream', + url: 'https://googleads.googleapis.com/v15/customers/1234567890/googleAds:searchStream', data: { query: `SELECT conversion_action.id FROM conversion_action WHERE conversion_action.name = 'Sign-up - click'`, }, @@ -298,7 +298,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v14/customers/1234567891/googleAds:searchStream', + url: 'https://googleads.googleapis.com/v15/customers/1234567891/googleAds:searchStream', data: { query: "SELECT conversion_action.id FROM conversion_action WHERE conversion_action.name = 'Sign-up - click'", @@ -331,7 +331,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v14/customers/1234567891/googleAds:searchStream', + url: 'https://googleads.googleapis.com/v15/customers/1234567891/googleAds:searchStream', data: { query: 'SELECT conversion_custom_variable.name FROM conversion_custom_variable' }, headers: { Authorization: 'Bearer abcd1234', @@ -365,7 +365,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v14/customers/1234567891:uploadClickConversions', + url: 'https://googleads.googleapis.com/v15/customers/1234567891:uploadClickConversions', data: { conversions: [ { @@ -432,7 +432,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v14/customers/1234567891:uploadClickConversions', + url: 'https://googleads.googleapis.com/v15/customers/1234567891:uploadClickConversions', data: { conversions: [ { diff --git a/test/integrations/destinations/google_adwords_offline_conversions/processor/data.ts b/test/integrations/destinations/google_adwords_offline_conversions/processor/data.ts index f47deaef67..d523ecb1cd 100644 --- a/test/integrations/destinations/google_adwords_offline_conversions/processor/data.ts +++ b/test/integrations/destinations/google_adwords_offline_conversions/processor/data.ts @@ -176,7 +176,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/9625812972:uploadClickConversions', + 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -263,6 +263,10 @@ export const data = [ conversionValue: 1, currencyCode: 'GBP', orderId: 'PL-123QR', + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, }, ], partialFailure: true, @@ -465,7 +469,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/9625812972:uploadClickConversions', + 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -520,6 +524,10 @@ export const data = [ JSON: { conversions: [ { + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, gbraid: 'gbraid', wbraid: 'wbraid', externalAttributionData: { @@ -754,7 +762,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/9625812972:uploadClickConversions', + 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -809,6 +817,10 @@ export const data = [ JSON: { conversions: [ { + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, gbraid: 'gbraid', wbraid: 'wbraid', externalAttributionData: { @@ -1043,7 +1055,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/9625812972:uploadCallConversions', + 'https://googleads.googleapis.com/v15/customers/9625812972:uploadCallConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -1099,6 +1111,10 @@ export const data = [ conversions: [ { callerId: 'callerId', + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, callStartDateTime: '2022-08-28 15:01:30+05:30', conversionDateTime: '2022-01-01 12:32:45-08:00', conversionValue: 1, @@ -2015,7 +2031,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/9625812972:uploadClickConversions', + 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -2072,6 +2088,10 @@ export const data = [ JSON: { conversions: [ { + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, gbraid: 'gbraid', wbraid: 'wbraid', externalAttributionData: { @@ -2128,7 +2148,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/9625812972:uploadCallConversions', + 'https://googleads.googleapis.com/v15/customers/9625812972:uploadCallConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -2185,6 +2205,10 @@ export const data = [ JSON: { conversions: [ { + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, callerId: 'callerId', callStartDateTime: '2022-08-28 15:01:30+05:30', conversionDateTime: '2022-01-01 12:32:45-08:00', @@ -2350,7 +2374,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/9625812972:uploadClickConversions', + 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -2379,6 +2403,10 @@ export const data = [ JSON: { conversions: [ { + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, gclid: 'gclid', conversionDateTime: '2022-01-01 12:32:45-08:00', userIdentifiers: [ @@ -2546,7 +2574,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/9625812972:uploadCallConversions', + 'https://googleads.googleapis.com/v15/customers/9625812972:uploadCallConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -2575,6 +2603,10 @@ export const data = [ JSON: { conversions: [ { + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, callerId: 'callerId', callStartDateTime: '2022-08-28 15:01:30+05:30', conversionDateTime: '2022-01-01 12:32:45-08:00', @@ -2778,7 +2810,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/9625812972:uploadCallConversions', + 'https://googleads.googleapis.com/v15/customers/9625812972:uploadCallConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -2832,6 +2864,10 @@ export const data = [ JSON: { conversions: [ { + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, callerId: 'callerId', callStartDateTime: '2022-08-28 15:01:30+05:30', conversionDateTime: '2022-09-20 08:50:04+05:30', @@ -2997,7 +3033,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/9625812972:uploadClickConversions', + 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -3025,6 +3061,10 @@ export const data = [ JSON: { conversions: [ { + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, gclid: 'gclid', conversionDateTime: '2022-01-01 12:32:45-08:00', userIdentifiers: [ @@ -3501,7 +3541,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/9625812972:uploadClickConversions', + 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -3532,6 +3572,10 @@ export const data = [ JSON: { conversions: [ { + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, cartData: { items: [ { @@ -3861,7 +3905,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/9625812972:uploadClickConversions', + 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -3892,6 +3936,10 @@ export const data = [ JSON: { conversions: [ { + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, cartData: { items: [ { @@ -4049,7 +4097,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/1112223333/offlineUserDataJobs', + 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -4077,6 +4125,10 @@ export const data = [ addConversionPayload: { operations: { create: { + consent: { + ad_personalization: 'UNSPECIFIED', + ad_user_data: 'UNSPECIFIED', + }, transaction_attribute: { store_attribute: { store_code: 'store code', @@ -4382,7 +4434,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/1112223333/offlineUserDataJobs', + 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -4409,6 +4461,10 @@ export const data = [ addConversionPayload: { operations: { create: { + consent: { + ad_personalization: 'UNSPECIFIED', + ad_user_data: 'UNSPECIFIED', + }, transaction_attribute: { store_attribute: { store_code: 'store code', @@ -4577,7 +4633,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/1112223333/offlineUserDataJobs', + 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -4604,6 +4660,10 @@ export const data = [ addConversionPayload: { operations: { create: { + consent: { + ad_personalization: 'UNSPECIFIED', + ad_user_data: 'UNSPECIFIED', + }, transaction_attribute: { store_attribute: { store_code: 'store code', @@ -4775,7 +4835,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/1112223333/offlineUserDataJobs', + 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -4802,6 +4862,10 @@ export const data = [ addConversionPayload: { operations: { create: { + consent: { + ad_personalization: 'UNSPECIFIED', + ad_user_data: 'UNSPECIFIED', + }, transaction_attribute: { store_attribute: { store_code: 'store code', @@ -4935,7 +4999,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/1112223333/offlineUserDataJobs', + 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -4962,6 +5026,10 @@ export const data = [ addConversionPayload: { operations: { create: { + consent: { + ad_personalization: 'UNSPECIFIED', + ad_user_data: 'UNSPECIFIED', + }, transaction_attribute: { store_attribute: { store_code: 'store code', @@ -5091,7 +5159,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/1112223333/offlineUserDataJobs', + 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -5118,6 +5186,10 @@ export const data = [ addConversionPayload: { operations: { create: { + consent: { + ad_personalization: 'UNSPECIFIED', + ad_user_data: 'UNSPECIFIED', + }, transaction_attribute: { store_attribute: { store_code: 'store code', @@ -5245,7 +5317,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/1112223333/offlineUserDataJobs', + 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -5272,6 +5344,10 @@ export const data = [ addConversionPayload: { operations: { create: { + consent: { + ad_personalization: 'UNSPECIFIED', + ad_user_data: 'UNSPECIFIED', + }, transaction_attribute: { store_attribute: { store_code: 'store code', diff --git a/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts b/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts index a38980f0e9..7b0a6ce430 100644 --- a/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts +++ b/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts @@ -487,7 +487,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/7693729833/offlineUserDataJobs', + 'https://googleads.googleapis.com/v15/customers/7693729833/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -511,6 +511,10 @@ export const data = [ operations: [ { create: { + consent: { + ad_personalization: 'UNSPECIFIED', + ad_user_data: 'UNSPECIFIED', + }, transaction_attribute: { store_attribute: { store_code: 'store code' }, transaction_amount_micros: '100000000', @@ -528,6 +532,10 @@ export const data = [ }, { create: { + consent: { + ad_personalization: 'UNSPECIFIED', + ad_user_data: 'UNSPECIFIED', + }, transaction_attribute: { store_attribute: { store_code: 'store code2' }, transaction_amount_micros: '100000000', @@ -561,7 +569,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/7693729833:uploadCallConversions', + 'https://googleads.googleapis.com/v15/customers/7693729833:uploadCallConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -589,6 +597,10 @@ export const data = [ JSON: { conversions: [ { + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, callerId: '1234', callStartDateTime: '2019-10-14T11:15:18.299Z', conversionDateTime: '2019-10-14 16:45:18+05:30', @@ -673,7 +685,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/9625812972:uploadClickConversions', + 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -722,6 +734,10 @@ export const data = [ JSON: { conversions: [ { + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, gbraid: 'gbraid', wbraid: 'wbraid', externalAttributionData: { @@ -806,7 +822,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/9625812972:uploadCallConversions', + 'https://googleads.googleapis.com/v15/customers/9625812972:uploadCallConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -855,6 +871,10 @@ export const data = [ JSON: { conversions: [ { + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, callerId: 'callerId', callStartDateTime: '2022-08-28 15:01:30+05:30', conversionDateTime: '2022-01-01 12:32:45-08:00', diff --git a/test/integrations/destinations/google_adwords_remarketing_lists/processor/data.ts b/test/integrations/destinations/google_adwords_remarketing_lists/processor/data.ts index 804efec220..c15a6d5e34 100644 --- a/test/integrations/destinations/google_adwords_remarketing_lists/processor/data.ts +++ b/test/integrations/destinations/google_adwords_remarketing_lists/processor/data.ts @@ -11905,10 +11905,7 @@ export const data = [ params: { listId: '830441345', customerId: '7693729833', - consent: { - adUserData: 'UNSPECIFIED', - adPersonalization: 'GRANTED', - }, + consent: {}, }, body: { JSON: { From 31d36910e0527457afb654b4c9d4996e2a78dac8 Mon Sep 17 00:00:00 2001 From: shrouti1507 Date: Fri, 23 Feb 2024 18:45:51 +0530 Subject: [PATCH 02/17] fix: clean up of commented code --- .../google_adwords_offline_conversions/transform.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/v0/destinations/google_adwords_offline_conversions/transform.js b/src/v0/destinations/google_adwords_offline_conversions/transform.js index ae866e1659..4a687ca65c 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/transform.js +++ b/src/v0/destinations/google_adwords_offline_conversions/transform.js @@ -73,17 +73,7 @@ const getConversions = (message, metadata, { Config }, event, conversionType) => payload.addConversionPayload.operations.create.consent = consentObject; } } - - // if(payload?.conversions?.length > 0) { - // // const consentObject = populateConsentForGoogleDestinations(message, conversionType); - // if(Object.keys(consentObject).length > 0) { - // if(conversionType === 'click' || conversionType === 'call') { - // payload.conversions[0].consent = consentObject; - // } else if(conversionType === 'store'){ - // payload.addConversionPayload.create.consent = consentObject; - // } - // } - // } + if (conversionType !== 'store') { // transform originalTimestamp to conversionDateTime format (yyyy-mm-dd hh:mm:ss+|-hh:mm) // e.g 2019-10-14T11:15:18.299Z -> 2019-10-14 16:10:29+0530 From f23127acae23600a13edff9f33d549303eca62d8 Mon Sep 17 00:00:00 2001 From: shrouti1507 Date: Mon, 26 Feb 2024 11:31:20 +0530 Subject: [PATCH 03/17] fix: clean up the old implementation of GARL to avoid code duplication --- .../networkHandler.js | 3 - .../transform.js | 5 +- src/v0/util/googleUtils/index.js | 64 ------------------- .../dataDelivery/data.ts | 3 - .../processor/data.ts | 21 ------ .../router/data.ts | 10 +-- 6 files changed, 6 insertions(+), 100 deletions(-) delete mode 100644 src/v0/util/googleUtils/index.js diff --git a/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js b/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js index 3045c1713f..10e6d78694 100644 --- a/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js +++ b/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js @@ -23,9 +23,6 @@ const createJob = async (endpoint, headers, method, params) => { const customerMatchUserListMetadata = { userList: `customers/${params.customerId}/userLists/${params.listId}`, }; - if (Object.keys(params.consent).length > 0) { - customerMatchUserListMetadata.consent = params.consent; - } const jobCreatingRequest = { url: jobCreatingUrl, data: { diff --git a/src/v0/destinations/google_adwords_remarketing_lists/transform.js b/src/v0/destinations/google_adwords_remarketing_lists/transform.js index 9526973fb8..b3db86a05b 100644 --- a/src/v0/destinations/google_adwords_remarketing_lists/transform.js +++ b/src/v0/destinations/google_adwords_remarketing_lists/transform.js @@ -15,8 +15,6 @@ const { getAccessToken, } = require('../../util'); -const { populateConsentForGoogleDestinations } = require('../../util/googleUtils'); - const { offlineDataJobsMapping, addressInfoMapping, @@ -218,8 +216,7 @@ const processEvent = async (metadata, message, destination) => { } Object.values(createdPayload).forEach((data) => { - const consentObj = populateConsentForGoogleDestinations(message.properties); - response.push(responseBuilder(metadata, data, destination, message, consentObj)); + response.push(responseBuilder(metadata, data, destination, message)); }); return response; } diff --git a/src/v0/util/googleUtils/index.js b/src/v0/util/googleUtils/index.js deleted file mode 100644 index b1865ca1c6..0000000000 --- a/src/v0/util/googleUtils/index.js +++ /dev/null @@ -1,64 +0,0 @@ -const get = require('get-value'); -const { getIntegrationsObj } = require('..'); - -const GOOGLE_ALLOWED_CONSENT_STATUS = ['UNSPECIFIED', 'UNKNOWN', 'GRANTED', 'DENIED']; -const { MappedToDestinationKey } = require('../../../constants'); - -/** - * Populates the consent object based on the provided properties. - * - * @param {object} properties - message.properties containing properties related to consent. - * @returns {object} - An object containing consent information. - * ref : https://developers.google.com/google-ads/api/rest/reference/rest/v15/Consent - */ - -const populateConsentForGoogleDestinations = (message, conversionName) => { - const mappedToDestination = get(message, MappedToDestinationKey); - if (mappedToDestination) return {}; - // Define mappings for different conversion types - const mappings = { - store: { - ad_user_data: 'ad_user_data', - ad_personalization: 'ad_personalization', - }, - click: { - adUserData: 'adUserData', - adPersonalization: 'adPersonalization', - }, - call: { - adUserData: 'adUserData', - adPersonalization: 'adPersonalization', - }, - }; - - const currentMapping = mappings[conversionName]; - if (!currentMapping) return {}; - - const integrationObj = getIntegrationsObj(message, 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS') || {}; - const consents = integrationObj.consents || {}; - - // Define a function to process consent based on type and key - const processConsent = (consentType) => { - if (!consents[consentType]) return 'UNSPECIFIED'; - if (consents[consentType] && GOOGLE_ALLOWED_CONSENT_STATUS.includes(consents[consentType])) { - return consents[consentType]; - } - if (consents[consentType] && !GOOGLE_ALLOWED_CONSENT_STATUS.includes(consents[consentType])) { - return 'UNKNOWN'; - } - return null; - }; - - // Construct consentObj based on the current mapping - const consentObj = Object.keys(currentMapping).reduce((obj, consentType) => { - const key = currentMapping[consentType]; - // Process each consent type and assign it to the key specified in the mapping - // eslint-disable-next-line no-param-reassign - obj[key] = processConsent(consentType); - return obj; - }, {}); - - return consentObj; -}; - -module.exports = { populateConsentForGoogleDestinations, GOOGLE_ALLOWED_CONSENT_STATUS }; diff --git a/test/integrations/destinations/google_adwords_remarketing_lists/dataDelivery/data.ts b/test/integrations/destinations/google_adwords_remarketing_lists/dataDelivery/data.ts index 414e46ea19..398d5f0713 100644 --- a/test/integrations/destinations/google_adwords_remarketing_lists/dataDelivery/data.ts +++ b/test/integrations/destinations/google_adwords_remarketing_lists/dataDelivery/data.ts @@ -21,7 +21,6 @@ export const data = [ destination: 'google_adwords_remarketing_lists', listId: '709078448', customerId: '7693729833', - consent: {}, }, body: { JSON: { @@ -101,7 +100,6 @@ export const data = [ listId: '709078448', customerId: '7693729833', destination: 'google_adwords_remarketing_lists', - consent: {}, }, body: { JSON: { @@ -200,7 +198,6 @@ export const data = [ listId: '709078448', customerId: '7693729833', destination: 'google_adwords_remarketing_lists', - consent: {}, }, body: { JSON: { diff --git a/test/integrations/destinations/google_adwords_remarketing_lists/processor/data.ts b/test/integrations/destinations/google_adwords_remarketing_lists/processor/data.ts index c15a6d5e34..24e5f37b3d 100644 --- a/test/integrations/destinations/google_adwords_remarketing_lists/processor/data.ts +++ b/test/integrations/destinations/google_adwords_remarketing_lists/processor/data.ts @@ -79,7 +79,6 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, }, body: { JSON: { @@ -213,7 +212,6 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, }, body: { JSON: { @@ -332,7 +330,6 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, }, body: { JSON: { @@ -1434,7 +1431,6 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, }, body: { JSON: { @@ -2829,7 +2825,6 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, }, body: { JSON: { @@ -2909,7 +2904,6 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, }, body: { JSON: { @@ -4122,7 +4116,6 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, }, body: { JSON: { @@ -5422,7 +5415,6 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, }, body: { JSON: { @@ -6799,7 +6791,6 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, }, body: { JSON: { @@ -8109,7 +8100,6 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, }, body: { JSON: { @@ -9409,7 +9399,6 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, }, body: { JSON: { @@ -10804,7 +10793,6 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, }, body: { JSON: { @@ -10884,7 +10872,6 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, }, body: { JSON: { @@ -11059,7 +11046,6 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, }, body: { JSON: { @@ -11137,7 +11123,6 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, }, body: { JSON: { @@ -11281,7 +11266,6 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, }, body: { JSON: { @@ -11488,7 +11472,6 @@ export const data = [ params: { listId: 'aud1234', customerId: '7693729833', - consent: {}, }, body: { JSON: { @@ -11627,7 +11610,6 @@ export const data = [ params: { listId: '830441345', customerId: '7693729833', - consent: {}, }, body: { JSON: { @@ -11765,7 +11747,6 @@ export const data = [ params: { listId: '830441345', customerId: '7693729833', - consent: {}, }, body: { JSON: { @@ -11905,7 +11886,6 @@ export const data = [ params: { listId: '830441345', customerId: '7693729833', - consent: {}, }, body: { JSON: { @@ -12045,7 +12025,6 @@ export const data = [ params: { listId: '830441345', customerId: '7693729833', - consent: {}, }, body: { JSON: { 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 8c12225400..fa8ead352a 100644 --- a/test/integrations/destinations/google_adwords_remarketing_lists/router/data.ts +++ b/test/integrations/destinations/google_adwords_remarketing_lists/router/data.ts @@ -228,7 +228,7 @@ export const data = [ 'Content-Type': 'application/json', 'developer-token': 'ijkl9101', }, - params: { listId: '7090784486', customerId: '7693729833', consent: {} }, + params: { listId: '7090784486', customerId: '7693729833' }, body: { JSON: { enablePartialFailure: true, @@ -305,7 +305,7 @@ export const data = [ 'Content-Type': 'application/json', 'developer-token': 'ijkl9101', }, - params: { listId: '7090784486', customerId: '7693729833', consent: {} }, + params: { listId: '7090784486', customerId: '7693729833' }, body: { JSON: { enablePartialFailure: true, @@ -359,7 +359,7 @@ export const data = [ 'Content-Type': 'application/json', 'developer-token': 'ijkl9101', }, - params: { listId: '7090784486', customerId: '7693729833', consent: {} }, + params: { listId: '7090784486', customerId: '7693729833' }, body: { JSON: { enablePartialFailure: true, @@ -436,7 +436,7 @@ export const data = [ 'Content-Type': 'application/json', 'developer-token': 'ijkl9101', }, - params: { listId: '7090784486', customerId: '7693729833', consent: {} }, + params: { listId: '7090784486', customerId: '7693729833' }, body: { JSON: { enablePartialFailure: true, @@ -484,7 +484,7 @@ export const data = [ 'Content-Type': 'application/json', 'developer-token': 'ijkl9101', }, - params: { listId: '7090784486', customerId: '7693729833', consent: {} }, + params: { listId: '7090784486', customerId: '7693729833' }, body: { JSON: { enablePartialFailure: true, From a0a6885910e9002ca65093b98a76c39413b63884 Mon Sep 17 00:00:00 2001 From: shrouti1507 Date: Mon, 26 Feb 2024 16:17:51 +0530 Subject: [PATCH 04/17] fix: small edit --- .../utils.js | 44 ++++++------------- .../utils.test.js | 14 +++--- .../processor/data.ts | 28 ++++++------ .../router/data.ts | 8 ++-- 4 files changed, 39 insertions(+), 55 deletions(-) diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.js b/src/v0/destinations/google_adwords_offline_conversions/utils.js index c7e14a4adb..139a467d8b 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.js @@ -382,45 +382,28 @@ const GOOGLE_ALLOWED_CONSENT_STATUS = ['UNSPECIFIED', 'UNKNOWN', 'GRANTED', 'DEN * b) https://developers.google.com/google-ads/api/reference/rpc/v15/UserData#consent */ -const populateConsentForGoogleDestinations = (message, conversionName) => { - // Define mappings for different conversion types - const mappings = { - store: { - adUserData: 'ad_user_data', - adPersonalization: 'ad_personalization', - }, - click: { - adUserData: 'adUserData', - adPersonalization: 'adPersonalization', - }, - call: { - adUserData: 'adUserData', - adPersonalization: 'adPersonalization', - }, - }; - - const currentMapping = mappings[conversionName]; - if (!currentMapping) return {}; - +const populateConsentForGoogleDestinations = (message) => { const integrationObj = getIntegrationsObj(message, 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS') || {}; const consents = integrationObj.consents || {}; - // Define a function to process consent based on type and key + // Define a function to process consent based on type const processConsent = (consentType) => { if (!consents[consentType]) return 'UNSPECIFIED'; - if (consents[consentType] && GOOGLE_ALLOWED_CONSENT_STATUS.includes(consents[consentType])) { + if (GOOGLE_ALLOWED_CONSENT_STATUS.includes(consents[consentType])) { return consents[consentType]; } - if (consents[consentType] && !GOOGLE_ALLOWED_CONSENT_STATUS.includes(consents[consentType])) { - return 'UNKNOWN'; - } - return null; + return 'UNKNOWN'; }; - // Construct consentObj based on the current mapping - const consentObj = Object.keys(currentMapping).reduce((obj, consentType) => { - const key = currentMapping[consentType]; - // Process each consent type and assign it to the key specified in the mapping + // Common consent fields to process + const consentFields = { + adUserData: 'adUserData', + adPersonalization: 'adPersonalization', + }; + + // Construct consentObj based on the common consent fields + const consentObj = Object.keys(consentFields).reduce((obj, consentType) => { + const key = consentFields[consentType]; // eslint-disable-next-line no-param-reassign obj[key] = processConsent(consentType); return obj; @@ -428,6 +411,7 @@ const populateConsentForGoogleDestinations = (message, conversionName) => { return consentObj; }; + module.exports = { validateDestinationConfig, generateItemListFromProducts, diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js index cf049ddcc2..c49a258519 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js @@ -276,7 +276,7 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => { }); describe('populateConsentForGoogleDestinations', () => { - // Returns an object with ad_user_data and ad_personalization properties set to UNSPECIFIED when no consents are provided + // Returns an object with adUserData and adPersonalization properties set to UNSPECIFIED when no consents are provided it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : store sales conversion without any mention in integrations object', () => { const message = {}; const conversionType = 'store'; @@ -284,8 +284,8 @@ describe('populateConsentForGoogleDestinations', () => { const result = populateConsentForGoogleDestinations(message, conversionType); expect(result).toEqual({ - ad_user_data: 'UNSPECIFIED', - ad_personalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + adPersonalization: 'UNSPECIFIED', }); }); @@ -306,12 +306,12 @@ describe('populateConsentForGoogleDestinations', () => { const result = populateConsentForGoogleDestinations(message, conversionType); expect(result).toEqual({ - ad_personalization: 'DENIED', - ad_user_data: 'GRANTED', + adPersonalization: 'DENIED', + adUserData: 'GRANTED', }); }); - // Returns an object with ad_user_data and ad_personalization properties set to the provided consents when they are valid and present in the message properties + // Returns an object with adUserData and adPersonalization properties set to the provided consents when they are valid and present in the message properties it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS: click conversion with integration object of allowed types', () => { const message = { integrations: { @@ -333,7 +333,7 @@ describe('populateConsentForGoogleDestinations', () => { }); }); - // Returns an object with ad_user_data and ad_personalization properties set to UNSPECIFIED when the provided consents are not valid or not present in the message properties + // Returns an object with adUserData and adPersonalization properties set to UNSPECIFIED when the provided consents are not valid or not present in the message properties it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : click conversion with invalid consent value', () => { const message = { integrations: { diff --git a/test/integrations/destinations/google_adwords_offline_conversions/processor/data.ts b/test/integrations/destinations/google_adwords_offline_conversions/processor/data.ts index d523ecb1cd..e1c53c4f2c 100644 --- a/test/integrations/destinations/google_adwords_offline_conversions/processor/data.ts +++ b/test/integrations/destinations/google_adwords_offline_conversions/processor/data.ts @@ -4126,8 +4126,8 @@ export const data = [ operations: { create: { consent: { - ad_personalization: 'UNSPECIFIED', - ad_user_data: 'UNSPECIFIED', + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', }, transaction_attribute: { store_attribute: { @@ -4462,8 +4462,8 @@ export const data = [ operations: { create: { consent: { - ad_personalization: 'UNSPECIFIED', - ad_user_data: 'UNSPECIFIED', + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', }, transaction_attribute: { store_attribute: { @@ -4661,8 +4661,8 @@ export const data = [ operations: { create: { consent: { - ad_personalization: 'UNSPECIFIED', - ad_user_data: 'UNSPECIFIED', + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', }, transaction_attribute: { store_attribute: { @@ -4863,8 +4863,8 @@ export const data = [ operations: { create: { consent: { - ad_personalization: 'UNSPECIFIED', - ad_user_data: 'UNSPECIFIED', + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', }, transaction_attribute: { store_attribute: { @@ -5027,8 +5027,8 @@ export const data = [ operations: { create: { consent: { - ad_personalization: 'UNSPECIFIED', - ad_user_data: 'UNSPECIFIED', + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', }, transaction_attribute: { store_attribute: { @@ -5187,8 +5187,8 @@ export const data = [ operations: { create: { consent: { - ad_personalization: 'UNSPECIFIED', - ad_user_data: 'UNSPECIFIED', + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', }, transaction_attribute: { store_attribute: { @@ -5345,8 +5345,8 @@ export const data = [ operations: { create: { consent: { - ad_personalization: 'UNSPECIFIED', - ad_user_data: 'UNSPECIFIED', + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', }, transaction_attribute: { store_attribute: { diff --git a/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts b/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts index 7b0a6ce430..ff534a05c6 100644 --- a/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts +++ b/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts @@ -512,8 +512,8 @@ export const data = [ { create: { consent: { - ad_personalization: 'UNSPECIFIED', - ad_user_data: 'UNSPECIFIED', + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', }, transaction_attribute: { store_attribute: { store_code: 'store code' }, @@ -533,8 +533,8 @@ export const data = [ { create: { consent: { - ad_personalization: 'UNSPECIFIED', - ad_user_data: 'UNSPECIFIED', + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', }, transaction_attribute: { store_attribute: { store_code: 'store code2' }, From bce1d6224f556ae3ae2e58918bb8e7e3838907f6 Mon Sep 17 00:00:00 2001 From: shrouti1507 Date: Thu, 7 Mar 2024 20:13:33 +0530 Subject: [PATCH 05/17] chore: store sales updated, review comments addressed --- .../transform.js | 15 +- .../utils.js | 119 ++++--- .../utils.test.js | 109 +++++- src/v0/util/googleUtils/index.js | 10 +- .../processor/data.ts | 316 ++++++++++++++++++ 5 files changed, 503 insertions(+), 66 deletions(-) diff --git a/src/v0/destinations/google_adwords_offline_conversions/transform.js b/src/v0/destinations/google_adwords_offline_conversions/transform.js index fb463bb616..cf874ee0ac 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/transform.js +++ b/src/v0/destinations/google_adwords_offline_conversions/transform.js @@ -56,21 +56,10 @@ const getConversions = (message, metadata, { Config }, event, conversionType) => endpoint = STORE_CONVERSION_CONFIG.replace(':customerId', filteredCustomerId); } else { // call conversions + const consentObject = populateConsentForGoogleDestinations(message, conversionType, Config); payload = constructPayload(message, trackCallConversionsMapping); endpoint = CALL_CONVERSION.replace(':customerId', filteredCustomerId); - } - const consentObject = populateConsentForGoogleDestinations(message, conversionType); - if (Object.keys(consentObject)?.length > 0) { - if (payload?.conversions?.length > 0) { - if (conversionType === 'click' || conversionType === 'call') { - payload.conversions[0].consent = consentObject; - } - } else if ( - Object.keys(payload?.addConversionPayload?.operations?.create)?.length > 0 && - conversionType === 'store' - ) { - payload.addConversionPayload.operations.create.consent = consentObject; - } + payload.conversions[0].consent = consentObject; } if (conversionType !== 'store') { diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.js b/src/v0/destinations/google_adwords_offline_conversions/utils.js index 139a467d8b..7e149d0ce0 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.js @@ -31,9 +31,72 @@ const { const { processAxiosResponse } = require('../../../adapters/utils/networkUtils'); const Cache = require('../../util/cache'); const helper = require('./helper'); +const { + GOOGLE_ALLOWED_CONSENT_STATUS, + UNKNOWN_CONSENT, + UNSPECIFIED_CONSENT, +} = require('../../util/googleUtils'); const conversionActionIdCache = new Cache(CONVERSION_ACTION_ID_CACHE_TTL); +/** + * Populates the consent object based on the provided properties. + * + * @param {object} properties - message.properties containing properties related to consent. + * @returns {object} - An object containing consent information. + * * ref : + * 1) For click conversion : + * a) https://developers.google.com/google-ads/api/rest/reference/rest/v15/customers/uploadClickConversions#ClickConversion + * b) https://developers.google.com/google-ads/api/reference/rpc/v15/ClickConversion#consent + * 2) For Call conversion : + * a) https://developers.google.com/google-ads/api/rest/reference/rest/v15/customers/uploadCallConversions#CallConversion + * b) https://developers.google.com/google-ads/api/reference/rpc/v15/CallConversion#consent + * 3) For Store sales conversion : + * a) https://developers.google.com/google-ads/api/reference/rpc/v15/UserData + * b) https://developers.google.com/google-ads/api/reference/rpc/v15/UserData#consent + */ + +const populateConsentForGoogleDestinations = (message, conversionType, destConfig) => { + const integrationObj = + conversionType === 'store' + ? {} + : getIntegrationsObj(message, 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS') || {}; + const consents = integrationObj?.consents || {}; + + // Define a function to process consent based on type + const processConsent = (consentType) => { + // Access the default consent values from destConfig based on the consentType + let defaultConsentValue; + if (consentType === 'adUserData') { + defaultConsentValue = destConfig?.userDataConsent; + } else if (consentType === 'adPersonalization') { + defaultConsentValue = destConfig?.personalizationConsent; + } else { + defaultConsentValue = UNSPECIFIED_CONSENT; + } + + if (!consents[consentType]) { + return defaultConsentValue || UNSPECIFIED_CONSENT; + } + if (GOOGLE_ALLOWED_CONSENT_STATUS.includes(consents[consentType])) { + return consents[consentType]; + } + return defaultConsentValue || UNKNOWN_CONSENT; + }; + + // Common consent fields to process + const consentFields = ['adUserData', 'adPersonalization']; + + // Construct consentObj based on the common consent fields + const consentObj = consentFields.reduce((obj, consentType) => { + // eslint-disable-next-line no-param-reassign + obj[consentType] = processConsent(consentType); + return obj; + }, {}); + + return consentObj; +}; + /** * validate destination config and check for existence of data * @param {*} param0 @@ -273,6 +336,9 @@ const getAddConversionPayload = (message, Config) => { set(payload, 'operations.create.userIdentifiers[0]', {}); } } + // add consent support for store conversions + const consentObject = populateConsentForGoogleDestinations(message, 'store', Config); + set(payload, 'operations.create.consent', consentObject); return payload; }; @@ -360,56 +426,11 @@ const getClickConversionPayloadAndEndpoint = (message, Config, filteredCustomerI if (!properties.conversionEnvironment && conversionEnvironment !== 'none') { set(payload, 'conversions[0].conversionEnvironment', conversionEnvironment); } - return { payload, endpoint }; -}; -const GOOGLE_ALLOWED_CONSENT_STATUS = ['UNSPECIFIED', 'UNKNOWN', 'GRANTED', 'DENIED']; - -/** - * Populates the consent object based on the provided properties. - * - * @param {object} properties - message.properties containing properties related to consent. - * @returns {object} - An object containing consent information. - * * ref : - * 1) For click conversion : - * a) https://developers.google.com/google-ads/api/rest/reference/rest/v15/customers/uploadClickConversions#ClickConversion - * b) https://developers.google.com/google-ads/api/reference/rpc/v15/ClickConversion#consent - * 2) For Call conversion : - * a) https://developers.google.com/google-ads/api/rest/reference/rest/v15/customers/uploadCallConversions#CallConversion - * b) https://developers.google.com/google-ads/api/reference/rpc/v15/CallConversion#consent - * 3) For Store sales conversion : - * a) https://developers.google.com/google-ads/api/reference/rpc/v15/UserData - * b) https://developers.google.com/google-ads/api/reference/rpc/v15/UserData#consent - */ - -const populateConsentForGoogleDestinations = (message) => { - const integrationObj = getIntegrationsObj(message, 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS') || {}; - const consents = integrationObj.consents || {}; - - // Define a function to process consent based on type - const processConsent = (consentType) => { - if (!consents[consentType]) return 'UNSPECIFIED'; - if (GOOGLE_ALLOWED_CONSENT_STATUS.includes(consents[consentType])) { - return consents[consentType]; - } - return 'UNKNOWN'; - }; - - // Common consent fields to process - const consentFields = { - adUserData: 'adUserData', - adPersonalization: 'adPersonalization', - }; - - // Construct consentObj based on the common consent fields - const consentObj = Object.keys(consentFields).reduce((obj, consentType) => { - const key = consentFields[consentType]; - // eslint-disable-next-line no-param-reassign - obj[key] = processConsent(consentType); - return obj; - }, {}); - - return consentObj; + // add consent support for click conversions + const consentObject = populateConsentForGoogleDestinations(message, 'click', Config); + set(payload, 'conversions[0].consent', consentObject); + return { payload, endpoint }; }; module.exports = { diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js index c49a258519..8ad15f6c41 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js @@ -168,6 +168,10 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => { { conversionDateTime: '2022-01-01 12:32:45-08:00', conversionEnvironment: 'WEB', + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, userIdentifiers: [ { hashedEmail: 'fa922cb41ff930664d4c9ced3c472ce7ecf29a0f8248b7018456e990177fff75', @@ -193,6 +197,10 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => { conversions: [ { conversionDateTime: '2022-01-01 12:32:45-08:00', + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, conversionEnvironment: 'WEB', userIdentifiers: [ { @@ -259,6 +267,10 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => { cartData: { items: [{ productId: 1234, quantity: 2, unitPrice: 10 }] }, conversionDateTime: '2022-01-01 12:32:45-08:00', conversionEnvironment: 'WEB', + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, userIdentifiers: [ { hashedEmail: 'fa922cb41ff930664d4c9ced3c472ce7ecf29a0f8248b7018456e990177fff75', @@ -277,7 +289,7 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => { describe('populateConsentForGoogleDestinations', () => { // Returns an object with adUserData and adPersonalization properties set to UNSPECIFIED when no consents are provided - it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : store sales conversion without any mention in integrations object', () => { + it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : store sales conversion without consent related field in destination config', () => { const message = {}; const conversionType = 'store'; @@ -289,8 +301,7 @@ describe('populateConsentForGoogleDestinations', () => { }); }); - // Returns an empty object when the destination name is not recognized - it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS: store sales conversions with integrations object', () => { + it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS: store sales conversions with integrations object but without consent fields in config', () => { const message = { integrations: { google_adwords_offline_conversions: { @@ -305,6 +316,32 @@ describe('populateConsentForGoogleDestinations', () => { const result = populateConsentForGoogleDestinations(message, conversionType); + expect(result).toEqual({ + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }); + }); + + it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS: store sales conversions with integrations object along with consent fields in config', () => { + const message = { + integrations: { + google_adwords_offline_conversions: { + consents: { + adUserData: 'GRANTED', + adPersonalization: 'DENIED', + }, + }, + }, + }; + const conversionType = 'store'; + + const destConfig = { + userDataConsent: 'GRANTED', + personalizationConsent: 'DENIED', + }; + + const result = populateConsentForGoogleDestinations(message, conversionType, destConfig); + expect(result).toEqual({ adPersonalization: 'DENIED', adUserData: 'GRANTED', @@ -367,4 +404,70 @@ describe('populateConsentForGoogleDestinations', () => { adPersonalization: 'UNSPECIFIED', }); }); + + it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : click conversion without integrations', () => { + const message = { + integrations: { + google_adwords_offline_conversions: {}, + }, + }; + const conversionType = 'click'; + + const destConfig = { + userDataConsent: 'GRANTED', + personalizationConsent: 'DENIED', + }; + + const result = populateConsentForGoogleDestinations(message, conversionType, destConfig); + + expect(result).toEqual({ + adUserData: 'GRANTED', + adPersonalization: 'DENIED', + }); + }); + + it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : click conversion without integrations and UI config has partial data', () => { + const message = { + integrations: { + google_adwords_offline_conversions: {}, + }, + }; + const conversionType = 'click'; + + const destConfig = { + userDataConsent: 'GRANTED', + }; + + const result = populateConsentForGoogleDestinations(message, conversionType, destConfig); + + expect(result).toEqual({ + adUserData: 'GRANTED', + adPersonalization: 'UNSPECIFIED', + }); + }); + + it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : click conversion with partial data present in integrations object', () => { + const message = { + integrations: { + google_adwords_offline_conversions: { + consents: { + adUserData: 'GRANTED', + }, + }, + }, + }; + + const destConfig = { + userDataConsent: 'GRANTED', + personalizationConsent: 'DENIED', + }; + const conversionType = 'click'; + + const result = populateConsentForGoogleDestinations(message, conversionType, destConfig); + + expect(result).toEqual({ + adUserData: 'GRANTED', + adPersonalization: 'DENIED', + }); + }); }); diff --git a/src/v0/util/googleUtils/index.js b/src/v0/util/googleUtils/index.js index de73b0fb05..6dc5cb9697 100644 --- a/src/v0/util/googleUtils/index.js +++ b/src/v0/util/googleUtils/index.js @@ -1,5 +1,8 @@ const GOOGLE_ALLOWED_CONSENT_STATUS = ['UNSPECIFIED', 'UNKNOWN', 'GRANTED', 'DENIED']; +const UNSPECIFIED_CONSENT = 'UNSPECIFIED'; +const UNKNOWN_CONSENT = 'UNKNOWN'; + /** * Populates the consent object based on the provided properties. * @@ -33,4 +36,9 @@ const populateConsentForGoogleDestinations = (config) => { return consent; }; -module.exports = { populateConsentForGoogleDestinations }; +module.exports = { + populateConsentForGoogleDestinations, + UNSPECIFIED_CONSENT, + UNKNOWN_CONSENT, + GOOGLE_ALLOWED_CONSENT_STATUS, +}; diff --git a/test/integrations/destinations/google_adwords_offline_conversions/processor/data.ts b/test/integrations/destinations/google_adwords_offline_conversions/processor/data.ts index e1c53c4f2c..8c19e73703 100644 --- a/test/integrations/destinations/google_adwords_offline_conversions/processor/data.ts +++ b/test/integrations/destinations/google_adwords_offline_conversions/processor/data.ts @@ -5388,4 +5388,320 @@ export const data = [ }, mockFns: timestampMock, }, + { + name: 'google_adwords_offline_conversions', + description: 'Test 26 : store conversion consent mapped from UI config', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + context: { + traits: {}, + }, + event: 'Product Clicked', + type: 'track', + messageId: '5e10d13a-bf9a-44bf-b884-43a9e591ea71', + anonymousId: '00000000000000000000000000', + userId: '12345', + properties: { + item_id: 'item id', + merchant_id: 'merchant id', + currency: 'INR', + revenue: '100', + store_code: 'store code', + gclid: 'gclid', + conversionDateTime: '2019-10-14T11:15:18.299Z', + product_id: '123445', + quantity: 123, + }, + integrations: { + All: true, + }, + name: 'ApplicationLoaded', + sentAt: '2019-10-14T11:15:53.296Z', + }, + metadata: { + secret: { + access_token: 'abcd1234', + refresh_token: 'efgh5678', + developer_token: 'ijkl91011', + }, + }, + destination: { + Config: { + isCustomerAllowed: false, + customerId: '111-222-3333', + subAccount: true, + loginCustomerId: 'login-customer-id', + userDataConsent: 'GRANTED', + personalizationConsent: 'DENIED', + eventsToOfflineConversionsTypeMapping: [ + { + from: 'Product Clicked', + to: 'store', + }, + ], + eventsToConversionsNamesMapping: [ + { + from: 'Product Clicked', + to: 'Sign-up - click', + }, + ], + hashUserIdentifier: true, + defaultUserIdentifier: 'phone', + validateOnly: false, + rudderAccountId: '2EOknn1JNH7WK1MfNkgr4t3u4fGYKkRK', + }, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: + 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs', + headers: { + Authorization: 'Bearer abcd1234', + 'Content-Type': 'application/json', + 'developer-token': 'ijkl91011', + 'login-customer-id': 'logincustomerid', + }, + params: { + event: 'Sign-up - click', + customerId: '1112223333', + }, + body: { + JSON: { + event: '1112223333', + isStoreConversion: true, + createJobPayload: { + job: { + type: 'STORE_SALES_UPLOAD_FIRST_PARTY', + storeSalesMetadata: { + loyaltyFraction: '1', + transaction_upload_fraction: '1', + }, + }, + }, + addConversionPayload: { + operations: { + create: { + consent: { + adPersonalization: 'DENIED', + adUserData: 'GRANTED', + }, + transaction_attribute: { + store_attribute: { + store_code: 'store code', + }, + transaction_amount_micros: '100000000', + currency_code: 'INR', + transaction_date_time: '2019-10-14 16:45:18+05:30', + }, + userIdentifiers: [{}], + }, + }, + enable_partial_failure: false, + enable_warnings: false, + validate_only: false, + }, + executeJobPayload: { + validate_only: false, + }, + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + metadata: { + secret: { + access_token: 'abcd1234', + refresh_token: 'efgh5678', + developer_token: 'ijkl91011', + }, + }, + statusCode: 200, + }, + ], + }, + }, + mockFns: timestampMock, + }, + { + name: 'google_adwords_offline_conversions', + description: + 'Test 27 : store conversion consent mapped from UI config even though integration object is present', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + context: { + traits: {}, + }, + event: 'Product Clicked', + type: 'track', + messageId: '5e10d13a-bf9a-44bf-b884-43a9e591ea71', + anonymousId: '00000000000000000000000000', + userId: '12345', + properties: { + item_id: 'item id', + merchant_id: 'merchant id', + currency: 'INR', + revenue: '100', + store_code: 'store code', + gclid: 'gclid', + conversionDateTime: '2019-10-14T11:15:18.299Z', + product_id: '123445', + quantity: 123, + }, + integrations: { + google_adwords_offline_conversion: { + consent: { + adUserdata: 'UNSPECIFIED', + adPersonalization: 'GRANTED', + }, + }, + }, + name: 'ApplicationLoaded', + sentAt: '2019-10-14T11:15:53.296Z', + }, + metadata: { + secret: { + access_token: 'abcd1234', + refresh_token: 'efgh5678', + developer_token: 'ijkl91011', + }, + }, + destination: { + Config: { + isCustomerAllowed: false, + customerId: '111-222-3333', + subAccount: true, + loginCustomerId: 'login-customer-id', + userDataConsent: 'GRANTED', + personalizationConsent: 'DENIED', + eventsToOfflineConversionsTypeMapping: [ + { + from: 'Product Clicked', + to: 'store', + }, + ], + eventsToConversionsNamesMapping: [ + { + from: 'Product Clicked', + to: 'Sign-up - click', + }, + ], + hashUserIdentifier: true, + defaultUserIdentifier: 'phone', + validateOnly: false, + rudderAccountId: '2EOknn1JNH7WK1MfNkgr4t3u4fGYKkRK', + }, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: + 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs', + headers: { + Authorization: 'Bearer abcd1234', + 'Content-Type': 'application/json', + 'developer-token': 'ijkl91011', + 'login-customer-id': 'logincustomerid', + }, + params: { + event: 'Sign-up - click', + customerId: '1112223333', + }, + body: { + JSON: { + event: '1112223333', + isStoreConversion: true, + createJobPayload: { + job: { + type: 'STORE_SALES_UPLOAD_FIRST_PARTY', + storeSalesMetadata: { + loyaltyFraction: '1', + transaction_upload_fraction: '1', + }, + }, + }, + addConversionPayload: { + operations: { + create: { + consent: { + adPersonalization: 'DENIED', + adUserData: 'GRANTED', + }, + transaction_attribute: { + store_attribute: { + store_code: 'store code', + }, + transaction_amount_micros: '100000000', + currency_code: 'INR', + transaction_date_time: '2019-10-14 16:45:18+05:30', + }, + userIdentifiers: [{}], + }, + }, + enable_partial_failure: false, + enable_warnings: false, + validate_only: false, + }, + executeJobPayload: { + validate_only: false, + }, + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + metadata: { + secret: { + access_token: 'abcd1234', + refresh_token: 'efgh5678', + developer_token: 'ijkl91011', + }, + }, + statusCode: 200, + }, + ], + }, + }, + mockFns: timestampMock, + }, ]; From 3d92ad0e86d24add13b87a70ba5c82d5b0872cec Mon Sep 17 00:00:00 2001 From: shrouti1507 Date: Mon, 11 Mar 2024 09:51:55 +0530 Subject: [PATCH 06/17] fix: relocating the util and test cases --- .../transform.js | 4 +- .../utils.js | 71 +----- .../utils.test.js | 22 +- .../transform.js | 4 +- src/v0/util/googleUtils/index.js | 73 ++++++- src/v0/util/googleUtils/index.test.js | 203 +++++++++++++++++- 6 files changed, 280 insertions(+), 97 deletions(-) diff --git a/src/v0/destinations/google_adwords_offline_conversions/transform.js b/src/v0/destinations/google_adwords_offline_conversions/transform.js index cf874ee0ac..dfc3c701d0 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/transform.js +++ b/src/v0/destinations/google_adwords_offline_conversions/transform.js @@ -21,7 +21,7 @@ const { getStoreConversionPayload, requestBuilder, getClickConversionPayloadAndEndpoint, - populateConsentForGoogleDestinations, + populateConsentForGAOC, } = require('./utils'); const helper = require('./helper'); @@ -56,7 +56,7 @@ const getConversions = (message, metadata, { Config }, event, conversionType) => endpoint = STORE_CONVERSION_CONFIG.replace(':customerId', filteredCustomerId); } else { // call conversions - const consentObject = populateConsentForGoogleDestinations(message, conversionType, Config); + const consentObject = populateConsentForGAOC(message, conversionType, Config); payload = constructPayload(message, trackCallConversionsMapping); endpoint = CALL_CONVERSION.replace(':customerId', filteredCustomerId); payload.conversions[0].consent = consentObject; diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.js b/src/v0/destinations/google_adwords_offline_conversions/utils.js index 7e149d0ce0..ff69bf0eb2 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.js @@ -17,7 +17,6 @@ const { isDefinedAndNotNull, getAuthErrCategoryFromStCode, getAccessToken, - getIntegrationsObj, } = require('../../util'); const { SEARCH_STREAM, @@ -31,72 +30,10 @@ const { const { processAxiosResponse } = require('../../../adapters/utils/networkUtils'); const Cache = require('../../util/cache'); const helper = require('./helper'); -const { - GOOGLE_ALLOWED_CONSENT_STATUS, - UNKNOWN_CONSENT, - UNSPECIFIED_CONSENT, -} = require('../../util/googleUtils'); +const { populateConsentForGAOC } = require('../../util/googleUtils'); const conversionActionIdCache = new Cache(CONVERSION_ACTION_ID_CACHE_TTL); -/** - * Populates the consent object based on the provided properties. - * - * @param {object} properties - message.properties containing properties related to consent. - * @returns {object} - An object containing consent information. - * * ref : - * 1) For click conversion : - * a) https://developers.google.com/google-ads/api/rest/reference/rest/v15/customers/uploadClickConversions#ClickConversion - * b) https://developers.google.com/google-ads/api/reference/rpc/v15/ClickConversion#consent - * 2) For Call conversion : - * a) https://developers.google.com/google-ads/api/rest/reference/rest/v15/customers/uploadCallConversions#CallConversion - * b) https://developers.google.com/google-ads/api/reference/rpc/v15/CallConversion#consent - * 3) For Store sales conversion : - * a) https://developers.google.com/google-ads/api/reference/rpc/v15/UserData - * b) https://developers.google.com/google-ads/api/reference/rpc/v15/UserData#consent - */ - -const populateConsentForGoogleDestinations = (message, conversionType, destConfig) => { - const integrationObj = - conversionType === 'store' - ? {} - : getIntegrationsObj(message, 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS') || {}; - const consents = integrationObj?.consents || {}; - - // Define a function to process consent based on type - const processConsent = (consentType) => { - // Access the default consent values from destConfig based on the consentType - let defaultConsentValue; - if (consentType === 'adUserData') { - defaultConsentValue = destConfig?.userDataConsent; - } else if (consentType === 'adPersonalization') { - defaultConsentValue = destConfig?.personalizationConsent; - } else { - defaultConsentValue = UNSPECIFIED_CONSENT; - } - - if (!consents[consentType]) { - return defaultConsentValue || UNSPECIFIED_CONSENT; - } - if (GOOGLE_ALLOWED_CONSENT_STATUS.includes(consents[consentType])) { - return consents[consentType]; - } - return defaultConsentValue || UNKNOWN_CONSENT; - }; - - // Common consent fields to process - const consentFields = ['adUserData', 'adPersonalization']; - - // Construct consentObj based on the common consent fields - const consentObj = consentFields.reduce((obj, consentType) => { - // eslint-disable-next-line no-param-reassign - obj[consentType] = processConsent(consentType); - return obj; - }, {}); - - return consentObj; -}; - /** * validate destination config and check for existence of data * @param {*} param0 @@ -337,7 +274,7 @@ const getAddConversionPayload = (message, Config) => { } } // add consent support for store conversions - const consentObject = populateConsentForGoogleDestinations(message, 'store', Config); + const consentObject = populateConsentForGAOC(message, 'store', Config); set(payload, 'operations.create.consent', consentObject); return payload; }; @@ -428,7 +365,7 @@ const getClickConversionPayloadAndEndpoint = (message, Config, filteredCustomerI } // add consent support for click conversions - const consentObject = populateConsentForGoogleDestinations(message, 'click', Config); + const consentObject = populateConsentForGAOC(message, 'click', Config); set(payload, 'conversions[0].consent', consentObject); return { payload, endpoint }; }; @@ -443,5 +380,5 @@ module.exports = { buildAndGetAddress, getClickConversionPayloadAndEndpoint, getExisitingUserIdentifier, - populateConsentForGoogleDestinations, + populateConsentForGAOC, }; diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js index 8ad15f6c41..2520073337 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js @@ -2,7 +2,7 @@ const { getClickConversionPayloadAndEndpoint, buildAndGetAddress, getExisitingUserIdentifier, - populateConsentForGoogleDestinations, + populateConsentForGAOC, } = require('./utils'); const getTestMessage = () => { @@ -287,13 +287,13 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => { }); }); -describe('populateConsentForGoogleDestinations', () => { +describe('populateConsentForGAOC', () => { // Returns an object with adUserData and adPersonalization properties set to UNSPECIFIED when no consents are provided it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : store sales conversion without consent related field in destination config', () => { const message = {}; const conversionType = 'store'; - const result = populateConsentForGoogleDestinations(message, conversionType); + const result = populateConsentForGAOC(message, conversionType); expect(result).toEqual({ adUserData: 'UNSPECIFIED', @@ -314,7 +314,7 @@ describe('populateConsentForGoogleDestinations', () => { }; const conversionType = 'store'; - const result = populateConsentForGoogleDestinations(message, conversionType); + const result = populateConsentForGAOC(message, conversionType); expect(result).toEqual({ adPersonalization: 'UNSPECIFIED', @@ -340,7 +340,7 @@ describe('populateConsentForGoogleDestinations', () => { personalizationConsent: 'DENIED', }; - const result = populateConsentForGoogleDestinations(message, conversionType, destConfig); + const result = populateConsentForGAOC(message, conversionType, destConfig); expect(result).toEqual({ adPersonalization: 'DENIED', @@ -362,7 +362,7 @@ describe('populateConsentForGoogleDestinations', () => { }; const conversionType = 'click'; - const result = populateConsentForGoogleDestinations(message, conversionType); + const result = populateConsentForGAOC(message, conversionType); expect(result).toEqual({ adUserData: 'GRANTED', @@ -384,7 +384,7 @@ describe('populateConsentForGoogleDestinations', () => { }; const conversionType = 'click'; - const result = populateConsentForGoogleDestinations(message, conversionType); + const result = populateConsentForGAOC(message, conversionType); expect(result).toEqual({ adUserData: 'GRANTED', @@ -397,7 +397,7 @@ describe('populateConsentForGoogleDestinations', () => { const message = {}; const conversionType = 'call'; - const result = populateConsentForGoogleDestinations(message, conversionType); + const result = populateConsentForGAOC(message, conversionType); expect(result).toEqual({ adUserData: 'UNSPECIFIED', @@ -418,7 +418,7 @@ describe('populateConsentForGoogleDestinations', () => { personalizationConsent: 'DENIED', }; - const result = populateConsentForGoogleDestinations(message, conversionType, destConfig); + const result = populateConsentForGAOC(message, conversionType, destConfig); expect(result).toEqual({ adUserData: 'GRANTED', @@ -438,7 +438,7 @@ describe('populateConsentForGoogleDestinations', () => { userDataConsent: 'GRANTED', }; - const result = populateConsentForGoogleDestinations(message, conversionType, destConfig); + const result = populateConsentForGAOC(message, conversionType, destConfig); expect(result).toEqual({ adUserData: 'GRANTED', @@ -463,7 +463,7 @@ describe('populateConsentForGoogleDestinations', () => { }; const conversionType = 'click'; - const result = populateConsentForGoogleDestinations(message, conversionType, destConfig); + const result = populateConsentForGAOC(message, conversionType, destConfig); expect(result).toEqual({ adUserData: 'GRANTED', diff --git a/src/v0/destinations/google_adwords_remarketing_lists/transform.js b/src/v0/destinations/google_adwords_remarketing_lists/transform.js index 9ab415346a..4503b46735 100644 --- a/src/v0/destinations/google_adwords_remarketing_lists/transform.js +++ b/src/v0/destinations/google_adwords_remarketing_lists/transform.js @@ -15,7 +15,7 @@ const { getAccessToken, } = require('../../util'); -const { populateConsentForGoogleDestinations } = require('../../util/googleUtils'); +const { populateConsentFromConfig } = require('../../util/googleUtils'); const { offlineDataJobsMapping, @@ -218,7 +218,7 @@ const processEvent = async (metadata, message, destination) => { } Object.values(createdPayload).forEach((data) => { - const consentObj = populateConsentForGoogleDestinations(destination.Config); + const consentObj = populateConsentFromConfig(destination.Config); response.push(responseBuilder(metadata, data, destination, message, consentObj)); }); return response; diff --git a/src/v0/util/googleUtils/index.js b/src/v0/util/googleUtils/index.js index 6dc5cb9697..ee7172bf6f 100644 --- a/src/v0/util/googleUtils/index.js +++ b/src/v0/util/googleUtils/index.js @@ -1,3 +1,5 @@ +const { getIntegrationsObj } = require('..'); + const GOOGLE_ALLOWED_CONSENT_STATUS = ['UNSPECIFIED', 'UNKNOWN', 'GRANTED', 'DENIED']; const UNSPECIFIED_CONSENT = 'UNSPECIFIED'; @@ -11,34 +13,93 @@ const UNKNOWN_CONSENT = 'UNKNOWN'; * ref : https://developers.google.com/google-ads/api/rest/reference/rest/v15/Consent */ -const populateConsentForGoogleDestinations = (config) => { +const populateConsentFromConfig = (config) => { const consent = {}; if (config?.userDataConsent) { if (GOOGLE_ALLOWED_CONSENT_STATUS.includes(config.userDataConsent)) { consent.adUserData = config.userDataConsent; } else { - consent.adUserData = 'UNKNOWN'; + consent.adUserData = UNKNOWN_CONSENT; } } else { - consent.adUserData = 'UNSPECIFIED'; + consent.adUserData = UNSPECIFIED_CONSENT; } if (config?.personalizationConsent) { if (GOOGLE_ALLOWED_CONSENT_STATUS.includes(config.personalizationConsent)) { consent.adPersonalization = config.personalizationConsent; } else { - consent.adPersonalization = 'UNKNOWN'; + consent.adPersonalization = UNKNOWN_CONSENT; } } else { - consent.adPersonalization = 'UNSPECIFIED'; + consent.adPersonalization = UNSPECIFIED_CONSENT; } return consent; }; +/** + * Populates the consent object based on the provided properties. + * + * @param {object} properties - message.properties containing properties related to consent. + * @returns {object} - An object containing consent information. + * * ref : + * 1) For click conversion : + * a) https://developers.google.com/google-ads/api/rest/reference/rest/v15/customers/uploadClickConversions#ClickConversion + * b) https://developers.google.com/google-ads/api/reference/rpc/v15/ClickConversion#consent + * 2) For Call conversion : + * a) https://developers.google.com/google-ads/api/rest/reference/rest/v15/customers/uploadCallConversions#CallConversion + * b) https://developers.google.com/google-ads/api/reference/rpc/v15/CallConversion#consent + * 3) For Store sales conversion : + * a) https://developers.google.com/google-ads/api/reference/rpc/v15/UserData + * b) https://developers.google.com/google-ads/api/reference/rpc/v15/UserData#consent + */ + +const populateConsentForGAOC = (message, conversionType, destConfig) => { + const integrationObj = + conversionType === 'store' + ? {} + : getIntegrationsObj(message, 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS') || {}; + const consents = integrationObj?.consents || {}; + + // Define a function to process consent based on type + const processConsent = (consentType) => { + // Access the default consent values from destConfig based on the consentType + let defaultConsentValue; + if (consentType === 'adUserData') { + defaultConsentValue = destConfig?.userDataConsent; + } else if (consentType === 'adPersonalization') { + defaultConsentValue = destConfig?.personalizationConsent; + } else { + defaultConsentValue = UNSPECIFIED_CONSENT; + } + + if (!consents[consentType]) { + return defaultConsentValue || UNSPECIFIED_CONSENT; + } + if (GOOGLE_ALLOWED_CONSENT_STATUS.includes(consents[consentType])) { + return consents[consentType]; + } + return defaultConsentValue || UNKNOWN_CONSENT; + }; + + // Common consent fields to process + const consentFields = ['adUserData', 'adPersonalization']; + + // Construct consentObj based on the common consent fields + const consentObj = consentFields.reduce((obj, consentType) => { + // eslint-disable-next-line no-param-reassign + obj[consentType] = processConsent(consentType); + return obj; + }, {}); + + return consentObj; +}; + module.exports = { - populateConsentForGoogleDestinations, + populateConsentFromConfig, UNSPECIFIED_CONSENT, UNKNOWN_CONSENT, GOOGLE_ALLOWED_CONSENT_STATUS, + populateConsentForGAOC, }; diff --git a/src/v0/util/googleUtils/index.test.js b/src/v0/util/googleUtils/index.test.js index 9d1aa5e51a..ba2acee045 100644 --- a/src/v0/util/googleUtils/index.test.js +++ b/src/v0/util/googleUtils/index.test.js @@ -1,8 +1,8 @@ -const { populateConsentForGoogleDestinations } = require('./index'); +const { populateConsentFromConfig, populateConsentForGAOC } = require('./index'); -describe('unit test for populateConsentForGoogleDestinations', () => { +describe('unit test for populateConsentFromConfig', () => { it('should return an UNSPECIFIED object when no properties are provided', () => { - const result = populateConsentForGoogleDestinations({}); + const result = populateConsentFromConfig({}); expect(result).toEqual({ adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED', @@ -11,18 +11,18 @@ describe('unit test for populateConsentForGoogleDestinations', () => { it('should set adUserData property of consent object when userDataConsent property is provided and its value is one of the allowed consent statuses', () => { const properties = { userDataConsent: 'GRANTED' }; - const result = populateConsentForGoogleDestinations(properties); + const result = populateConsentFromConfig(properties); expect(result).toEqual({ adUserData: 'GRANTED', adPersonalization: 'UNSPECIFIED' }); }); it('should set adPersonalization property of consent object when personalizationConsent property is provided and its value is one of the allowed consent statuses', () => { const properties = { personalizationConsent: 'DENIED' }; - const result = populateConsentForGoogleDestinations(properties); + const result = populateConsentFromConfig(properties); expect(result).toEqual({ adPersonalization: 'DENIED', adUserData: 'UNSPECIFIED' }); }); it('should return an UNSPECIFIED object when properties parameter is not provided', () => { - const result = populateConsentForGoogleDestinations(); + const result = populateConsentFromConfig(); expect(result).toEqual({ adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED', @@ -30,7 +30,7 @@ describe('unit test for populateConsentForGoogleDestinations', () => { }); it('should return an UNSPECIFIED object when properties parameter is null', () => { - const result = populateConsentForGoogleDestinations(null); + const result = populateConsentFromConfig(null); expect(result).toEqual({ adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED', @@ -38,7 +38,7 @@ describe('unit test for populateConsentForGoogleDestinations', () => { }); it('should return an UNSPECIFIED object when properties parameter is an UNSPECIFIED object', () => { - const result = populateConsentForGoogleDestinations({}); + const result = populateConsentFromConfig({}); expect(result).toEqual({ adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED', @@ -46,7 +46,7 @@ describe('unit test for populateConsentForGoogleDestinations', () => { }); it('should return UNKNOWN when properties parameter contains adUserData and adPersonalization with non-allowed values', () => { - const result = populateConsentForGoogleDestinations({ + const result = populateConsentFromConfig({ userDataConsent: 'RANDOM', personalizationConsent: 'RANDOM', }); @@ -56,3 +56,188 @@ describe('unit test for populateConsentForGoogleDestinations', () => { }); }); }); + +describe('populateConsentForGAOC', () => { + // Returns an object with adUserData and adPersonalization properties set to UNSPECIFIED when no consents are provided + it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : store sales conversion without consent related field in destination config', () => { + const message = {}; + const conversionType = 'store'; + + const result = populateConsentForGAOC(message, conversionType); + + expect(result).toEqual({ + adUserData: 'UNSPECIFIED', + adPersonalization: 'UNSPECIFIED', + }); + }); + + it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS: store sales conversions with integrations object but without consent fields in config', () => { + const message = { + integrations: { + google_adwords_offline_conversions: { + consents: { + adUserData: 'GRANTED', + adPersonalization: 'DENIED', + }, + }, + }, + }; + const conversionType = 'store'; + + const result = populateConsentForGAOC(message, conversionType); + + expect(result).toEqual({ + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }); + }); + + it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS: store sales conversions with integrations object along with consent fields in config', () => { + const message = { + integrations: { + google_adwords_offline_conversions: { + consents: { + adUserData: 'GRANTED', + adPersonalization: 'DENIED', + }, + }, + }, + }; + const conversionType = 'store'; + + const destConfig = { + userDataConsent: 'GRANTED', + personalizationConsent: 'DENIED', + }; + + const result = populateConsentForGAOC(message, conversionType, destConfig); + + expect(result).toEqual({ + adPersonalization: 'DENIED', + adUserData: 'GRANTED', + }); + }); + + // Returns an object with adUserData and adPersonalization properties set to the provided consents when they are valid and present in the message properties + it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS: click conversion with integration object of allowed types', () => { + const message = { + integrations: { + google_adwords_offline_conversions: { + consents: { + adUserData: 'GRANTED', + adPersonalization: 'DENIED', + }, + }, + }, + }; + const conversionType = 'click'; + + const result = populateConsentForGAOC(message, conversionType); + + expect(result).toEqual({ + adUserData: 'GRANTED', + adPersonalization: 'DENIED', + }); + }); + + // Returns an object with adUserData and adPersonalization properties set to UNSPECIFIED when the provided consents are not valid or not present in the message properties + it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : click conversion with invalid consent value', () => { + const message = { + integrations: { + google_adwords_offline_conversions: { + consents: { + adUserData: 'GRANTED', + adPersonalization: 'INVALID', + }, + }, + }, + }; + const conversionType = 'click'; + + const result = populateConsentForGAOC(message, conversionType); + + expect(result).toEqual({ + adUserData: 'GRANTED', + adPersonalization: 'UNKNOWN', + }); + }); + + // Returns an empty object when the integration object is not present in the message + it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : call conversion without integrations object consent ', () => { + const message = {}; + const conversionType = 'call'; + + const result = populateConsentForGAOC(message, conversionType); + + expect(result).toEqual({ + adUserData: 'UNSPECIFIED', + adPersonalization: 'UNSPECIFIED', + }); + }); + + it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : click conversion without integrations', () => { + const message = { + integrations: { + google_adwords_offline_conversions: {}, + }, + }; + const conversionType = 'click'; + + const destConfig = { + userDataConsent: 'GRANTED', + personalizationConsent: 'DENIED', + }; + + const result = populateConsentForGAOC(message, conversionType, destConfig); + + expect(result).toEqual({ + adUserData: 'GRANTED', + adPersonalization: 'DENIED', + }); + }); + + it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : click conversion without integrations and UI config has partial data', () => { + const message = { + integrations: { + google_adwords_offline_conversions: {}, + }, + }; + const conversionType = 'click'; + + const destConfig = { + userDataConsent: 'GRANTED', + }; + + const result = populateConsentForGAOC(message, conversionType, destConfig); + + expect(result).toEqual({ + adUserData: 'GRANTED', + adPersonalization: 'UNSPECIFIED', + }); + }); + + it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : click conversion with partial data present in integrations object', () => { + const message = { + integrations: { + google_adwords_offline_conversions: { + consents: { + adUserData: 'GRANTED', + }, + }, + }, + }; + + const destConfig = { + userDataConsent: 'GRANTED', + personalizationConsent: 'DENIED', + }; + const conversionType = 'click'; + + const result = populateConsentForGAOC(message, conversionType, destConfig); + + expect(result).toEqual({ + adUserData: 'GRANTED', + adPersonalization: 'DENIED', + }); + }); +}); From e60117bc9de8e80538ad42e7d12955ca36860cce Mon Sep 17 00:00:00 2001 From: shrouti1507 Date: Mon, 11 Mar 2024 10:15:07 +0530 Subject: [PATCH 07/17] fix: refactoring the util --- .../utils.test.js | 185 ------------------ src/v0/util/googleUtils/index.js | 16 +- src/v0/util/googleUtils/index.test.js | 20 +- 3 files changed, 14 insertions(+), 207 deletions(-) diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js index 2520073337..864e1acf55 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js @@ -286,188 +286,3 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => { ); }); }); - -describe('populateConsentForGAOC', () => { - // Returns an object with adUserData and adPersonalization properties set to UNSPECIFIED when no consents are provided - it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : store sales conversion without consent related field in destination config', () => { - const message = {}; - const conversionType = 'store'; - - const result = populateConsentForGAOC(message, conversionType); - - expect(result).toEqual({ - adUserData: 'UNSPECIFIED', - adPersonalization: 'UNSPECIFIED', - }); - }); - - it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS: store sales conversions with integrations object but without consent fields in config', () => { - const message = { - integrations: { - google_adwords_offline_conversions: { - consents: { - adUserData: 'GRANTED', - adPersonalization: 'DENIED', - }, - }, - }, - }; - const conversionType = 'store'; - - const result = populateConsentForGAOC(message, conversionType); - - expect(result).toEqual({ - adPersonalization: 'UNSPECIFIED', - adUserData: 'UNSPECIFIED', - }); - }); - - it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS: store sales conversions with integrations object along with consent fields in config', () => { - const message = { - integrations: { - google_adwords_offline_conversions: { - consents: { - adUserData: 'GRANTED', - adPersonalization: 'DENIED', - }, - }, - }, - }; - const conversionType = 'store'; - - const destConfig = { - userDataConsent: 'GRANTED', - personalizationConsent: 'DENIED', - }; - - const result = populateConsentForGAOC(message, conversionType, destConfig); - - expect(result).toEqual({ - adPersonalization: 'DENIED', - adUserData: 'GRANTED', - }); - }); - - // Returns an object with adUserData and adPersonalization properties set to the provided consents when they are valid and present in the message properties - it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS: click conversion with integration object of allowed types', () => { - const message = { - integrations: { - google_adwords_offline_conversions: { - consents: { - adUserData: 'GRANTED', - adPersonalization: 'DENIED', - }, - }, - }, - }; - const conversionType = 'click'; - - const result = populateConsentForGAOC(message, conversionType); - - expect(result).toEqual({ - adUserData: 'GRANTED', - adPersonalization: 'DENIED', - }); - }); - - // Returns an object with adUserData and adPersonalization properties set to UNSPECIFIED when the provided consents are not valid or not present in the message properties - it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : click conversion with invalid consent value', () => { - const message = { - integrations: { - google_adwords_offline_conversions: { - consents: { - adUserData: 'GRANTED', - adPersonalization: 'INVALID', - }, - }, - }, - }; - const conversionType = 'click'; - - const result = populateConsentForGAOC(message, conversionType); - - expect(result).toEqual({ - adUserData: 'GRANTED', - adPersonalization: 'UNKNOWN', - }); - }); - - // Returns an empty object when the integration object is not present in the message - it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : call conversion without integrations object consent ', () => { - const message = {}; - const conversionType = 'call'; - - const result = populateConsentForGAOC(message, conversionType); - - expect(result).toEqual({ - adUserData: 'UNSPECIFIED', - adPersonalization: 'UNSPECIFIED', - }); - }); - - it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : click conversion without integrations', () => { - const message = { - integrations: { - google_adwords_offline_conversions: {}, - }, - }; - const conversionType = 'click'; - - const destConfig = { - userDataConsent: 'GRANTED', - personalizationConsent: 'DENIED', - }; - - const result = populateConsentForGAOC(message, conversionType, destConfig); - - expect(result).toEqual({ - adUserData: 'GRANTED', - adPersonalization: 'DENIED', - }); - }); - - it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : click conversion without integrations and UI config has partial data', () => { - const message = { - integrations: { - google_adwords_offline_conversions: {}, - }, - }; - const conversionType = 'click'; - - const destConfig = { - userDataConsent: 'GRANTED', - }; - - const result = populateConsentForGAOC(message, conversionType, destConfig); - - expect(result).toEqual({ - adUserData: 'GRANTED', - adPersonalization: 'UNSPECIFIED', - }); - }); - - it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : click conversion with partial data present in integrations object', () => { - const message = { - integrations: { - google_adwords_offline_conversions: { - consents: { - adUserData: 'GRANTED', - }, - }, - }, - }; - - const destConfig = { - userDataConsent: 'GRANTED', - personalizationConsent: 'DENIED', - }; - const conversionType = 'click'; - - const result = populateConsentForGAOC(message, conversionType, destConfig); - - expect(result).toEqual({ - adUserData: 'GRANTED', - adPersonalization: 'DENIED', - }); - }); -}); diff --git a/src/v0/util/googleUtils/index.js b/src/v0/util/googleUtils/index.js index ee7172bf6f..72a4d979f3 100644 --- a/src/v0/util/googleUtils/index.js +++ b/src/v0/util/googleUtils/index.js @@ -62,25 +62,17 @@ const populateConsentForGAOC = (message, conversionType, destConfig) => { : getIntegrationsObj(message, 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS') || {}; const consents = integrationObj?.consents || {}; + const defaultConsentBlock = populateConsentFromConfig(destConfig); + // Define a function to process consent based on type const processConsent = (consentType) => { - // Access the default consent values from destConfig based on the consentType - let defaultConsentValue; - if (consentType === 'adUserData') { - defaultConsentValue = destConfig?.userDataConsent; - } else if (consentType === 'adPersonalization') { - defaultConsentValue = destConfig?.personalizationConsent; - } else { - defaultConsentValue = UNSPECIFIED_CONSENT; - } - if (!consents[consentType]) { - return defaultConsentValue || UNSPECIFIED_CONSENT; + return defaultConsentBlock[consentType] || UNSPECIFIED_CONSENT; } if (GOOGLE_ALLOWED_CONSENT_STATUS.includes(consents[consentType])) { return consents[consentType]; } - return defaultConsentValue || UNKNOWN_CONSENT; + return defaultConsentBlock[consentType] || UNKNOWN_CONSENT; }; // Common consent fields to process diff --git a/src/v0/util/googleUtils/index.test.js b/src/v0/util/googleUtils/index.test.js index ba2acee045..95bb29b3e7 100644 --- a/src/v0/util/googleUtils/index.test.js +++ b/src/v0/util/googleUtils/index.test.js @@ -59,7 +59,7 @@ describe('unit test for populateConsentFromConfig', () => { describe('populateConsentForGAOC', () => { // Returns an object with adUserData and adPersonalization properties set to UNSPECIFIED when no consents are provided - it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : store sales conversion without consent related field in destination config', () => { + it('store sales conversion without consent related field in destination config', () => { const message = {}; const conversionType = 'store'; @@ -71,7 +71,7 @@ describe('populateConsentForGAOC', () => { }); }); - it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS: store sales conversions with integrations object but without consent fields in config', () => { + it('store sales conversions with integrations object but without consent fields in config', () => { const message = { integrations: { google_adwords_offline_conversions: { @@ -92,7 +92,7 @@ describe('populateConsentForGAOC', () => { }); }); - it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS: store sales conversions with integrations object along with consent fields in config', () => { + it('store sales conversions with integrations object along with consent fields in config', () => { const message = { integrations: { google_adwords_offline_conversions: { @@ -119,7 +119,7 @@ describe('populateConsentForGAOC', () => { }); // Returns an object with adUserData and adPersonalization properties set to the provided consents when they are valid and present in the message properties - it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS: click conversion with integration object of allowed types', () => { + it('click conversion with integration object of allowed types', () => { const message = { integrations: { google_adwords_offline_conversions: { @@ -141,7 +141,7 @@ describe('populateConsentForGAOC', () => { }); // Returns an object with adUserData and adPersonalization properties set to UNSPECIFIED when the provided consents are not valid or not present in the message properties - it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : click conversion with invalid consent value', () => { + it('click conversion with invalid consent value', () => { const message = { integrations: { google_adwords_offline_conversions: { @@ -158,12 +158,12 @@ describe('populateConsentForGAOC', () => { expect(result).toEqual({ adUserData: 'GRANTED', - adPersonalization: 'UNKNOWN', + adPersonalization: 'UNSPECIFIED', }); }); // Returns an empty object when the integration object is not present in the message - it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : call conversion without integrations object consent ', () => { + it('call conversion without integrations object consent ', () => { const message = {}; const conversionType = 'call'; @@ -175,7 +175,7 @@ describe('populateConsentForGAOC', () => { }); }); - it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : click conversion without integrations', () => { + it('click conversion without integrations', () => { const message = { integrations: { google_adwords_offline_conversions: {}, @@ -196,7 +196,7 @@ describe('populateConsentForGAOC', () => { }); }); - it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : click conversion without integrations and UI config has partial data', () => { + it('click conversion without integrations and UI config has partial data', () => { const message = { integrations: { google_adwords_offline_conversions: {}, @@ -216,7 +216,7 @@ describe('populateConsentForGAOC', () => { }); }); - it('GOOGLE_ADWORDS_OFFLINE_CONVERSIONS : click conversion with partial data present in integrations object', () => { + it('click conversion with partial data present in integrations object', () => { const message = { integrations: { google_adwords_offline_conversions: { From 3d4634e36c5d4e7296644e60ea4f620d23ba32f2 Mon Sep 17 00:00:00 2001 From: shrouti1507 Date: Mon, 11 Mar 2024 10:32:57 +0530 Subject: [PATCH 08/17] fix: updating API version to v16 --- .../config.js | 2 +- .../utils.test.js | 10 ++--- src/v0/util/googleUtils/index.js | 14 +++---- .../dataDelivery/data.ts | 14 +++---- .../network.ts | 26 ++++++------ .../processor/data.ts | 42 +++++++++---------- .../router/data.ts | 8 ++-- 7 files changed, 58 insertions(+), 58 deletions(-) diff --git a/src/v0/destinations/google_adwords_offline_conversions/config.js b/src/v0/destinations/google_adwords_offline_conversions/config.js index ea1a76e555..412ab543a4 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/config.js +++ b/src/v0/destinations/google_adwords_offline_conversions/config.js @@ -1,6 +1,6 @@ const { getMappingConfig } = require('../../util'); -const API_VERSION = 'v15'; +const API_VERSION = 'v16'; const BASE_ENDPOINT = `https://googleads.googleapis.com/${API_VERSION}/customers/:customerId`; diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js index 864e1acf55..717bba20b8 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js @@ -162,7 +162,7 @@ describe('getExisitingUserIdentifier util tests', () => { describe('getClickConversionPayloadAndEndpoint util tests', () => { it('getClickConversionPayloadAndEndpoint flow check when default field identifier is present', () => { let expectedOutput = { - endpoint: 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', + endpoint: 'https://googleads.googleapis.com/v16/customers/9625812972:uploadClickConversions', payload: { conversions: [ { @@ -192,7 +192,7 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => { delete fittingPayload.traits.email; delete fittingPayload.properties.email; let expectedOutput = { - endpoint: 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', + endpoint: 'https://googleads.googleapis.com/v16/customers/9625812972:uploadClickConversions', payload: { conversions: [ { @@ -224,7 +224,7 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => { delete fittingPayload.traits.phone; delete fittingPayload.properties.email; let expectedOutput = { - endpoint: 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', + endpoint: 'https://googleads.googleapis.com/v16/customers/9625812972:uploadClickConversions', payload: { conversions: [ { @@ -246,7 +246,7 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => { ).toThrow('Either of email or phone is required for user identifier'); }); - it('getClickConversionPayloadAndEndpoint flow check when default field identifier is present and product list present', () => { + it('populateConsentForGAOC', () => { let fittingPayload = { ...getTestMessage() }; fittingPayload.properties.products = [ { @@ -260,7 +260,7 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => { }, ]; let expectedOutput = { - endpoint: 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', + endpoint: 'https://googleads.googleapis.com/v16/customers/9625812972:uploadClickConversions', payload: { conversions: [ { diff --git a/src/v0/util/googleUtils/index.js b/src/v0/util/googleUtils/index.js index 72a4d979f3..8712361ef3 100644 --- a/src/v0/util/googleUtils/index.js +++ b/src/v0/util/googleUtils/index.js @@ -10,7 +10,7 @@ const UNKNOWN_CONSENT = 'UNKNOWN'; * * @param {object} properties - message.properties containing properties related to consent. * @returns {object} - An object containing consent information. - * ref : https://developers.google.com/google-ads/api/rest/reference/rest/v15/Consent + * ref : https://developers.google.com/google-ads/api/rest/reference/rest/v16/Consent */ const populateConsentFromConfig = (config) => { @@ -45,14 +45,14 @@ const populateConsentFromConfig = (config) => { * @returns {object} - An object containing consent information. * * ref : * 1) For click conversion : - * a) https://developers.google.com/google-ads/api/rest/reference/rest/v15/customers/uploadClickConversions#ClickConversion - * b) https://developers.google.com/google-ads/api/reference/rpc/v15/ClickConversion#consent + * a) https://developers.google.com/google-ads/api/rest/reference/rest/v16/customers/uploadClickConversions#ClickConversion + * b) https://developers.google.com/google-ads/api/reference/rpc/v16/ClickConversion#consent * 2) For Call conversion : - * a) https://developers.google.com/google-ads/api/rest/reference/rest/v15/customers/uploadCallConversions#CallConversion - * b) https://developers.google.com/google-ads/api/reference/rpc/v15/CallConversion#consent + * a) https://developers.google.com/google-ads/api/rest/reference/rest/v16/customers/uploadCallConversions#CallConversion + * b) https://developers.google.com/google-ads/api/reference/rpc/v16/CallConversion#consent * 3) For Store sales conversion : - * a) https://developers.google.com/google-ads/api/reference/rpc/v15/UserData - * b) https://developers.google.com/google-ads/api/reference/rpc/v15/UserData#consent + * a) https://developers.google.com/google-ads/api/reference/rpc/v16/UserData + * b) https://developers.google.com/google-ads/api/reference/rpc/v16/UserData#consent */ const populateConsentForGAOC = (message, conversionType, destConfig) => { diff --git a/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/data.ts b/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/data.ts index 9cc13d890b..bf02779ac2 100644 --- a/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/data.ts +++ b/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/data.ts @@ -12,7 +12,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/11122233331/offlineUserDataJobs', + 'https://googleads.googleapis.com/v16/customers/11122233331/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -91,7 +91,7 @@ export const data = [ code: 400, details: [ { - '@type': 'type.googleapis.com/google.ads.googleads.v15.errors.GoogleAdsFailure', + '@type': 'type.googleapis.com/google.ads.googleads.v16.errors.GoogleAdsFailure', errors: [ { errorCode: { @@ -152,7 +152,7 @@ export const data = [ version: '1', type: 'REST', method: 'POST', - endpoint: 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs', + endpoint: 'https://googleads.googleapis.com/v16/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -248,7 +248,7 @@ export const data = [ version: '1', type: 'REST', method: 'POST', - endpoint: 'https://googleads.googleapis.com/v15/customers/customerid/offlineUserDataJobs', + endpoint: 'https://googleads.googleapis.com/v16/customers/customerid/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -358,7 +358,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/1234567890:uploadClickConversions', + 'https://googleads.googleapis.com/v16/customers/1234567890:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -505,7 +505,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/1234567891:uploadClickConversions', + 'https://googleads.googleapis.com/v16/customers/1234567891:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -646,7 +646,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/1234567891:uploadClickConversions', + 'https://googleads.googleapis.com/v16/customers/1234567891:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', diff --git a/test/integrations/destinations/google_adwords_offline_conversions/network.ts b/test/integrations/destinations/google_adwords_offline_conversions/network.ts index 2b22cc4a90..968067b2f0 100644 --- a/test/integrations/destinations/google_adwords_offline_conversions/network.ts +++ b/test/integrations/destinations/google_adwords_offline_conversions/network.ts @@ -1,7 +1,7 @@ export const networkCallsData = [ { httpReq: { - url: 'https://googleads.googleapis.com/v15/customers/11122233331/offlineUserDataJobs:create', + url: 'https://googleads.googleapis.com/v16/customers/11122233331/offlineUserDataJobs:create', data: { job: { storeSalesMetadata: { @@ -30,7 +30,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v15/customers/1112223333/googleAds:searchStream', + url: 'https://googleads.googleapis.com/v16/customers/1112223333/googleAds:searchStream', data: { query: `SELECT conversion_action.id FROM conversion_action WHERE conversion_action.name = 'Sign-up - click'`, }, @@ -63,7 +63,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v15/customers/11122233331/offlineUserDataJobs/OFFLINE_USER_DATA_JOB_ID_FOR_ADD_FAILURE:addOperations', + url: 'https://googleads.googleapis.com/v16/customers/11122233331/offlineUserDataJobs/OFFLINE_USER_DATA_JOB_ID_FOR_ADD_FAILURE:addOperations', data: { enable_partial_failure: false, enable_warnings: false, @@ -108,7 +108,7 @@ export const networkCallsData = [ status: 'INVALID_ARGUMENT', details: [ { - '@type': 'type.googleapis.com/google.ads.googleads.v15.errors.GoogleAdsFailure', + '@type': 'type.googleapis.com/google.ads.googleads.v16.errors.GoogleAdsFailure', errors: [ { errorCode: { @@ -144,7 +144,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs:create', + url: 'https://googleads.googleapis.com/v16/customers/1112223333/offlineUserDataJobs:create', data: { job: { storeSalesMetadata: { @@ -173,7 +173,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs/OFFLINE_USER_DATA_JOB_ID:addOperations', + url: 'https://googleads.googleapis.com/v16/customers/1112223333/offlineUserDataJobs/OFFLINE_USER_DATA_JOB_ID:addOperations', data: { enable_partial_failure: false, enable_warnings: false, @@ -216,7 +216,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs/OFFLINE_USER_DATA_JOB_ID:run', + url: 'https://googleads.googleapis.com/v16/customers/1112223333/offlineUserDataJobs/OFFLINE_USER_DATA_JOB_ID:run', data: { validate_only: false }, params: { destination: 'google_adwords_offline_conversion' }, headers: { @@ -236,7 +236,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v15/customers/customerid/offlineUserDataJobs:create', + url: 'https://googleads.googleapis.com/v16/customers/customerid/offlineUserDataJobs:create', data: { job: { storeSalesMetadata: { @@ -270,7 +270,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v15/customers/1234567890/googleAds:searchStream', + url: 'https://googleads.googleapis.com/v16/customers/1234567890/googleAds:searchStream', data: { query: `SELECT conversion_action.id FROM conversion_action WHERE conversion_action.name = 'Sign-up - click'`, }, @@ -298,7 +298,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v15/customers/1234567891/googleAds:searchStream', + url: 'https://googleads.googleapis.com/v16/customers/1234567891/googleAds:searchStream', data: { query: "SELECT conversion_action.id FROM conversion_action WHERE conversion_action.name = 'Sign-up - click'", @@ -331,7 +331,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v15/customers/1234567891/googleAds:searchStream', + url: 'https://googleads.googleapis.com/v16/customers/1234567891/googleAds:searchStream', data: { query: 'SELECT conversion_custom_variable.name FROM conversion_custom_variable' }, headers: { Authorization: 'Bearer abcd1234', @@ -365,7 +365,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v15/customers/1234567891:uploadClickConversions', + url: 'https://googleads.googleapis.com/v16/customers/1234567891:uploadClickConversions', data: { conversions: [ { @@ -432,7 +432,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v15/customers/1234567891:uploadClickConversions', + url: 'https://googleads.googleapis.com/v16/customers/1234567891:uploadClickConversions', data: { conversions: [ { diff --git a/test/integrations/destinations/google_adwords_offline_conversions/processor/data.ts b/test/integrations/destinations/google_adwords_offline_conversions/processor/data.ts index 8c19e73703..decb1e58c7 100644 --- a/test/integrations/destinations/google_adwords_offline_conversions/processor/data.ts +++ b/test/integrations/destinations/google_adwords_offline_conversions/processor/data.ts @@ -176,7 +176,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', + 'https://googleads.googleapis.com/v16/customers/9625812972:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -469,7 +469,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', + 'https://googleads.googleapis.com/v16/customers/9625812972:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -762,7 +762,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', + 'https://googleads.googleapis.com/v16/customers/9625812972:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -1055,7 +1055,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/9625812972:uploadCallConversions', + 'https://googleads.googleapis.com/v16/customers/9625812972:uploadCallConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -2031,7 +2031,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', + 'https://googleads.googleapis.com/v16/customers/9625812972:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -2148,7 +2148,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/9625812972:uploadCallConversions', + 'https://googleads.googleapis.com/v16/customers/9625812972:uploadCallConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -2374,7 +2374,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', + 'https://googleads.googleapis.com/v16/customers/9625812972:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -2574,7 +2574,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/9625812972:uploadCallConversions', + 'https://googleads.googleapis.com/v16/customers/9625812972:uploadCallConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -2810,7 +2810,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/9625812972:uploadCallConversions', + 'https://googleads.googleapis.com/v16/customers/9625812972:uploadCallConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -3033,7 +3033,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', + 'https://googleads.googleapis.com/v16/customers/9625812972:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -3541,7 +3541,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', + 'https://googleads.googleapis.com/v16/customers/9625812972:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -3905,7 +3905,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', + 'https://googleads.googleapis.com/v16/customers/9625812972:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -4097,7 +4097,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs', + 'https://googleads.googleapis.com/v16/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -4434,7 +4434,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs', + 'https://googleads.googleapis.com/v16/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -4633,7 +4633,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs', + 'https://googleads.googleapis.com/v16/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -4835,7 +4835,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs', + 'https://googleads.googleapis.com/v16/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -4999,7 +4999,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs', + 'https://googleads.googleapis.com/v16/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -5159,7 +5159,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs', + 'https://googleads.googleapis.com/v16/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -5317,7 +5317,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs', + 'https://googleads.googleapis.com/v16/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -5472,7 +5472,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs', + 'https://googleads.googleapis.com/v16/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -5633,7 +5633,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/1112223333/offlineUserDataJobs', + 'https://googleads.googleapis.com/v16/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', diff --git a/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts b/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts index ff534a05c6..596e7550e5 100644 --- a/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts +++ b/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts @@ -487,7 +487,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/7693729833/offlineUserDataJobs', + 'https://googleads.googleapis.com/v16/customers/7693729833/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -569,7 +569,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/7693729833:uploadCallConversions', + 'https://googleads.googleapis.com/v16/customers/7693729833:uploadCallConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -685,7 +685,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/9625812972:uploadClickConversions', + 'https://googleads.googleapis.com/v16/customers/9625812972:uploadClickConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -822,7 +822,7 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v15/customers/9625812972:uploadCallConversions', + 'https://googleads.googleapis.com/v16/customers/9625812972:uploadCallConversions', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', From e19235206f44d4e5b01331747300524c7653f128 Mon Sep 17 00:00:00 2001 From: shrouti1507 Date: Mon, 11 Mar 2024 15:45:09 +0530 Subject: [PATCH 09/17] fix: review comments address --- .../config.js | 3 + .../transform.js | 20 +- .../utils.js | 21 ++- .../utils.test.js | 3 +- src/v0/util/googleUtils/index.js | 17 +- src/v0/util/googleUtils/index.test.js | 178 ++++-------------- 6 files changed, 76 insertions(+), 166 deletions(-) diff --git a/src/v0/destinations/google_adwords_offline_conversions/config.js b/src/v0/destinations/google_adwords_offline_conversions/config.js index 412ab543a4..7cd3a9cf28 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/config.js +++ b/src/v0/destinations/google_adwords_offline_conversions/config.js @@ -42,6 +42,8 @@ const CONVERSION_CUSTOM_VARIABLE_CACHE_TTL = process.env.CONVERSION_CUSTOM_VARIA const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname); +const consentFields = ['adUserData', 'adPersonalization']; + module.exports = { trackClickConversionsMapping: MAPPING_CONFIG[CONFIG_CATEGORIES.TRACK_CLICK_CONVERSIONS_CONFIG.name], @@ -58,4 +60,5 @@ module.exports = { MAPPING_CONFIG[CONFIG_CATEGORIES.TRACK_STORE_CONVERSION_CONFIG_ADD_CONVERSION.name], trackAddStoreAddressConversionsMapping: MAPPING_CONFIG[CONFIG_CATEGORIES.TRACK_STORE_ADDRESS_IDENTIFIER.name], + consentFields, }; diff --git a/src/v0/destinations/google_adwords_offline_conversions/transform.js b/src/v0/destinations/google_adwords_offline_conversions/transform.js index dfc3c701d0..8c80324c21 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/transform.js +++ b/src/v0/destinations/google_adwords_offline_conversions/transform.js @@ -10,21 +10,31 @@ const { defaultBatchRequestConfig, getSuccessRespEvents, combineBatchRequestsWithSameJobIds, + getIntegrationsObj, } = require('../../util'); const { CALL_CONVERSION, trackCallConversionsMapping, STORE_CONVERSION_CONFIG, + consentFields, } = require('./config'); const { validateDestinationConfig, getStoreConversionPayload, requestBuilder, getClickConversionPayloadAndEndpoint, - populateConsentForGAOC, } = require('./utils'); +const { finaliseConsent } = require('../../util/googleUtils'); const helper = require('./helper'); +const getConsentFromIntegrationObj = (message, conversionType) => { + const integrationObj = + conversionType === 'store' + ? {} + : getIntegrationsObj(message, 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS') || {}; + return integrationObj?.consents || {}; +}; + /** * get conversions depending on the type set from dashboard * i.e click conversions or call conversions @@ -42,21 +52,24 @@ const getConversions = (message, metadata, { Config }, event, conversionType) => const { properties, timestamp, originalTimestamp } = message; const filteredCustomerId = removeHyphens(customerId); + const userSentConsentValues = getConsentFromIntegrationObj(message, conversionType); + if (conversionType === 'click') { // click conversion const convertedPayload = getClickConversionPayloadAndEndpoint( message, Config, filteredCustomerId, + userSentConsentValues, ); payload = convertedPayload.payload; endpoint = convertedPayload.endpoint; } else if (conversionType === 'store') { - payload = getStoreConversionPayload(message, Config, filteredCustomerId); + payload = getStoreConversionPayload(message, Config, filteredCustomerId, userSentConsentValues); endpoint = STORE_CONVERSION_CONFIG.replace(':customerId', filteredCustomerId); } else { // call conversions - const consentObject = populateConsentForGAOC(message, conversionType, Config); + const consentObject = finaliseConsent(userSentConsentValues, Config, consentFields); payload = constructPayload(message, trackCallConversionsMapping); endpoint = CALL_CONVERSION.replace(':customerId', filteredCustomerId); payload.conversions[0].consent = consentObject; @@ -122,7 +135,6 @@ const trackResponseBuilder = (message, metadata, destination) => { const process = async (event) => { const { message, metadata, destination } = event; - if (!message.type) { throw new InstrumentationError('Message type is not present. Aborting message.'); } diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.js b/src/v0/destinations/google_adwords_offline_conversions/utils.js index ff69bf0eb2..5a3b67cec0 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.js @@ -26,11 +26,12 @@ const { trackAddStoreAddressConversionsMapping, trackClickConversionsMapping, CLICK_CONVERSION, + consentFields, } = require('./config'); const { processAxiosResponse } = require('../../../adapters/utils/networkUtils'); const Cache = require('../../util/cache'); const helper = require('./helper'); -const { populateConsentForGAOC } = require('../../util/googleUtils'); +const { finaliseConsent } = require('../../util/googleUtils'); const conversionActionIdCache = new Cache(CONVERSION_ACTION_ID_CACHE_TTL); @@ -221,7 +222,7 @@ function getExisitingUserIdentifier(userIdentifierInfo, defaultUserIdentifier) { * This Function create the add conversion payload * and returns the payload */ -const getAddConversionPayload = (message, Config) => { +const getAddConversionPayload = (message, Config, eventLevelConsent) => { const { properties } = message; const { validateOnly, hashUserIdentifier, defaultUserIdentifier } = Config; const payload = constructPayload(message, trackAddStoreConversionsMapping); @@ -274,24 +275,29 @@ const getAddConversionPayload = (message, Config) => { } } // add consent support for store conversions - const consentObject = populateConsentForGAOC(message, 'store', Config); + const consentObject = finaliseConsent(eventLevelConsent, Config, consentFields); set(payload, 'operations.create.consent', consentObject); return payload; }; -const getStoreConversionPayload = (message, Config, event) => { +const getStoreConversionPayload = (message, Config, event, eventLevelConsent) => { const { validateOnly } = Config; const payload = { event, isStoreConversion: true, createJobPayload: getCreateJobPayload(message), - addConversionPayload: getAddConversionPayload(message, Config), + addConversionPayload: getAddConversionPayload(message, Config, eventLevelConsent), executeJobPayload: { validate_only: validateOnly }, }; return payload; }; -const getClickConversionPayloadAndEndpoint = (message, Config, filteredCustomerId) => { +const getClickConversionPayloadAndEndpoint = ( + message, + Config, + filteredCustomerId, + eventLevelConsent, +) => { const email = getFieldValueFromMessage(message, 'emailOnly'); const phone = getFieldValueFromMessage(message, 'phone'); const { hashUserIdentifier, defaultUserIdentifier, UserIdentifierSource, conversionEnvironment } = @@ -365,7 +371,7 @@ const getClickConversionPayloadAndEndpoint = (message, Config, filteredCustomerI } // add consent support for click conversions - const consentObject = populateConsentForGAOC(message, 'click', Config); + const consentObject = finaliseConsent(eventLevelConsent, Config, consentFields); set(payload, 'conversions[0].consent', consentObject); return { payload, endpoint }; }; @@ -380,5 +386,4 @@ module.exports = { buildAndGetAddress, getClickConversionPayloadAndEndpoint, getExisitingUserIdentifier, - populateConsentForGAOC, }; diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js index 717bba20b8..b2c15883d6 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js @@ -2,7 +2,6 @@ const { getClickConversionPayloadAndEndpoint, buildAndGetAddress, getExisitingUserIdentifier, - populateConsentForGAOC, } = require('./utils'); const getTestMessage = () => { @@ -246,7 +245,7 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => { ).toThrow('Either of email or phone is required for user identifier'); }); - it('populateConsentForGAOC', () => { + it('finaliseConsent', () => { let fittingPayload = { ...getTestMessage() }; fittingPayload.properties.products = [ { diff --git a/src/v0/util/googleUtils/index.js b/src/v0/util/googleUtils/index.js index 8712361ef3..a684e45933 100644 --- a/src/v0/util/googleUtils/index.js +++ b/src/v0/util/googleUtils/index.js @@ -1,5 +1,3 @@ -const { getIntegrationsObj } = require('..'); - const GOOGLE_ALLOWED_CONSENT_STATUS = ['UNSPECIFIED', 'UNKNOWN', 'GRANTED', 'DENIED']; const UNSPECIFIED_CONSENT = 'UNSPECIFIED'; @@ -55,12 +53,8 @@ const populateConsentFromConfig = (config) => { * b) https://developers.google.com/google-ads/api/reference/rpc/v16/UserData#consent */ -const populateConsentForGAOC = (message, conversionType, destConfig) => { - const integrationObj = - conversionType === 'store' - ? {} - : getIntegrationsObj(message, 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS') || {}; - const consents = integrationObj?.consents || {}; +const finaliseConsent = (eventLevelConsent, destConfig, destinationAllowedConsentKeys) => { + const consents = eventLevelConsent || {}; const defaultConsentBlock = populateConsentFromConfig(destConfig); @@ -75,11 +69,8 @@ const populateConsentForGAOC = (message, conversionType, destConfig) => { return defaultConsentBlock[consentType] || UNKNOWN_CONSENT; }; - // Common consent fields to process - const consentFields = ['adUserData', 'adPersonalization']; - // Construct consentObj based on the common consent fields - const consentObj = consentFields.reduce((obj, consentType) => { + const consentObj = destinationAllowedConsentKeys.reduce((obj, consentType) => { // eslint-disable-next-line no-param-reassign obj[consentType] = processConsent(consentType); return obj; @@ -93,5 +84,5 @@ module.exports = { UNSPECIFIED_CONSENT, UNKNOWN_CONSENT, GOOGLE_ALLOWED_CONSENT_STATUS, - populateConsentForGAOC, + finaliseConsent, }; diff --git a/src/v0/util/googleUtils/index.test.js b/src/v0/util/googleUtils/index.test.js index 95bb29b3e7..d9d03a9805 100644 --- a/src/v0/util/googleUtils/index.test.js +++ b/src/v0/util/googleUtils/index.test.js @@ -1,4 +1,6 @@ -const { populateConsentFromConfig, populateConsentForGAOC } = require('./index'); +const { populateConsentFromConfig, finaliseConsent } = require('./index'); + +const destinationAllowedConsentKeys = ['adUserData', 'adPersonalization']; describe('unit test for populateConsentFromConfig', () => { it('should return an UNSPECIFIED object when no properties are provided', () => { @@ -57,187 +59,85 @@ describe('unit test for populateConsentFromConfig', () => { }); }); -describe('populateConsentForGAOC', () => { - // Returns an object with adUserData and adPersonalization properties set to UNSPECIFIED when no consents are provided - it('store sales conversion without consent related field in destination config', () => { - const message = {}; - const conversionType = 'store'; - - const result = populateConsentForGAOC(message, conversionType); - - expect(result).toEqual({ - adUserData: 'UNSPECIFIED', - adPersonalization: 'UNSPECIFIED', - }); - }); - - it('store sales conversions with integrations object but without consent fields in config', () => { - const message = { - integrations: { - google_adwords_offline_conversions: { - consents: { - adUserData: 'GRANTED', - adPersonalization: 'DENIED', - }, - }, - }, - }; - const conversionType = 'store'; - - const result = populateConsentForGAOC(message, conversionType); - - expect(result).toEqual({ - adPersonalization: 'UNSPECIFIED', - adUserData: 'UNSPECIFIED', - }); - }); - - it('store sales conversions with integrations object along with consent fields in config', () => { - const message = { - integrations: { - google_adwords_offline_conversions: { - consents: { - adUserData: 'GRANTED', - adPersonalization: 'DENIED', - }, - }, - }, +describe('finaliseConsent', () => { + // Returns an object containing consent information. + it('should return an object containing consent information when eventLevelConsent, destConfig, and destinationAllowedConsentKeys are provided', () => { + const eventLevelConsent = { + adUserData: 'GRANTED', + adPersonalization: 'DENIED', }; - const conversionType = 'store'; - const destConfig = { - userDataConsent: 'GRANTED', - personalizationConsent: 'DENIED', + userDataConsent: 'UNKNOWN', + personalizationConsent: 'GRANTED', }; + const destinationAllowedConsentKeys = ['adUserData', 'adPersonalization']; - const result = populateConsentForGAOC(message, conversionType, destConfig); + const result = finaliseConsent(eventLevelConsent, destConfig, destinationAllowedConsentKeys); expect(result).toEqual({ - adPersonalization: 'DENIED', adUserData: 'GRANTED', + adPersonalization: 'DENIED', }); }); - // Returns an object with adUserData and adPersonalization properties set to the provided consents when they are valid and present in the message properties - it('click conversion with integration object of allowed types', () => { - const message = { - integrations: { - google_adwords_offline_conversions: { - consents: { - adUserData: 'GRANTED', - adPersonalization: 'DENIED', - }, - }, - }, - }; - const conversionType = 'click'; - - const result = populateConsentForGAOC(message, conversionType); - - expect(result).toEqual({ + // If destConfig is not provided, it does not return UNSPECIFIED_CONSENT. + it('should not return UNSPECIFIED_CONSENT when destConfig is not provided but event level consent is provided', () => { + const eventLevelConsent = { adUserData: 'GRANTED', adPersonalization: 'DENIED', - }); - }); - - // Returns an object with adUserData and adPersonalization properties set to UNSPECIFIED when the provided consents are not valid or not present in the message properties - it('click conversion with invalid consent value', () => { - const message = { - integrations: { - google_adwords_offline_conversions: { - consents: { - adUserData: 'GRANTED', - adPersonalization: 'INVALID', - }, - }, - }, }; - const conversionType = 'click'; + const destinationAllowedConsentKeys = ['adUserData', 'adPersonalization']; - const result = populateConsentForGAOC(message, conversionType); + const result = finaliseConsent(eventLevelConsent, undefined, destinationAllowedConsentKeys); + // Assert expect(result).toEqual({ adUserData: 'GRANTED', - adPersonalization: 'UNSPECIFIED', + adPersonalization: 'DENIED', }); }); - // Returns an empty object when the integration object is not present in the message - it('call conversion without integrations object consent ', () => { - const message = {}; - const conversionType = 'call'; + it('should return UNSPECIFIED_CONSENT when both destConfig and event level consent is not provided', () => { + const destinationAllowedConsentKeys = ['adUserData', 'adPersonalization']; - const result = populateConsentForGAOC(message, conversionType); + const result = finaliseConsent(undefined, undefined, destinationAllowedConsentKeys); + // Assert expect(result).toEqual({ adUserData: 'UNSPECIFIED', adPersonalization: 'UNSPECIFIED', }); }); - it('click conversion without integrations', () => { - const message = { - integrations: { - google_adwords_offline_conversions: {}, - }, - }; - const conversionType = 'click'; - - const destConfig = { - userDataConsent: 'GRANTED', - personalizationConsent: 'DENIED', - }; - - const result = populateConsentForGAOC(message, conversionType, destConfig); - - expect(result).toEqual({ - adUserData: 'GRANTED', - adPersonalization: 'DENIED', - }); - }); - - it('click conversion without integrations and UI config has partial data', () => { - const message = { - integrations: { - google_adwords_offline_conversions: {}, - }, - }; - const conversionType = 'click'; + it('should return UNKWOWN_CONSENT when destConfig is provided with wrong consent value', () => { + const destinationAllowedConsentKeys = ['adUserData', 'adPersonalization']; const destConfig = { - userDataConsent: 'GRANTED', + userDataConsent: 'UNKNOWN', + personalizationConsent: 'WRONG CONSENT', }; - const result = populateConsentForGAOC(message, conversionType, destConfig); + const result = finaliseConsent(undefined, destConfig, destinationAllowedConsentKeys); expect(result).toEqual({ - adUserData: 'GRANTED', - adPersonalization: 'UNSPECIFIED', + adUserData: 'UNKNOWN', + adPersonalization: 'UNKNOWN', }); }); - it('click conversion with partial data present in integrations object', () => { - const message = { - integrations: { - google_adwords_offline_conversions: { - consents: { - adUserData: 'GRANTED', - }, - }, - }, - }; + it('should return UNKWOWN_CONSENT when destConfig is provided with wrong consent value', () => { + const destinationAllowedConsentKeys = ['newKey1', 'newKey2']; const destConfig = { - userDataConsent: 'GRANTED', - personalizationConsent: 'DENIED', + userDataConsent: 'UNKNOWN', + personalizationConsent: 'WRONG CONSENT', }; - const conversionType = 'click'; - const result = populateConsentForGAOC(message, conversionType, destConfig); + const result = finaliseConsent(undefined, destConfig, destinationAllowedConsentKeys); expect(result).toEqual({ - adUserData: 'GRANTED', - adPersonalization: 'DENIED', + newKey1: 'UNSPECIFIED', + newKey2: 'UNSPECIFIED', }); }); }); From 5d961e86945f762e8841a28b8ad2ef360a1f06c8 Mon Sep 17 00:00:00 2001 From: shrouti1507 <60211312+shrouti1507@users.noreply.github.com> Date: Mon, 11 Mar 2024 16:55:46 +0530 Subject: [PATCH 10/17] Update src/v0/destinations/google_adwords_offline_conversions/transform.js Co-authored-by: Sai Kumar Battinoju <88789928+saikumarrs@users.noreply.github.com> --- .../google_adwords_offline_conversions/transform.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v0/destinations/google_adwords_offline_conversions/transform.js b/src/v0/destinations/google_adwords_offline_conversions/transform.js index 8c80324c21..2b7984366f 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/transform.js +++ b/src/v0/destinations/google_adwords_offline_conversions/transform.js @@ -27,7 +27,7 @@ const { const { finaliseConsent } = require('../../util/googleUtils'); const helper = require('./helper'); -const getConsentFromIntegrationObj = (message, conversionType) => { +const getConsentsDataFromIntegrationObj = (message, conversionType) => { const integrationObj = conversionType === 'store' ? {} From a027389853b601d5bdebafcd3bb019dda2ff2de6 Mon Sep 17 00:00:00 2001 From: shrouti1507 <60211312+shrouti1507@users.noreply.github.com> Date: Mon, 11 Mar 2024 16:55:53 +0530 Subject: [PATCH 11/17] Update src/v0/destinations/google_adwords_offline_conversions/transform.js Co-authored-by: Sai Kumar Battinoju <88789928+saikumarrs@users.noreply.github.com> --- .../google_adwords_offline_conversions/transform.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v0/destinations/google_adwords_offline_conversions/transform.js b/src/v0/destinations/google_adwords_offline_conversions/transform.js index 2b7984366f..e232a5f7d1 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/transform.js +++ b/src/v0/destinations/google_adwords_offline_conversions/transform.js @@ -52,7 +52,7 @@ const getConversions = (message, metadata, { Config }, event, conversionType) => const { properties, timestamp, originalTimestamp } = message; const filteredCustomerId = removeHyphens(customerId); - const userSentConsentValues = getConsentFromIntegrationObj(message, conversionType); + const eventLevelConsentsData = getConsentFromIntegrationObj(message, conversionType); if (conversionType === 'click') { // click conversion From af7a820b89e87cc088d861a218716b188dc9e924 Mon Sep 17 00:00:00 2001 From: shrouti1507 Date: Mon, 11 Mar 2024 17:30:24 +0530 Subject: [PATCH 12/17] fix: review comments address --- .../transform.js | 23 +++++----- .../utils.js | 10 +++++ .../utils.test.js | 44 +++++++++++++++++++ src/v0/util/googleUtils/index.test.js | 2 - 4 files changed, 64 insertions(+), 15 deletions(-) diff --git a/src/v0/destinations/google_adwords_offline_conversions/transform.js b/src/v0/destinations/google_adwords_offline_conversions/transform.js index e232a5f7d1..5ae92c57e9 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/transform.js +++ b/src/v0/destinations/google_adwords_offline_conversions/transform.js @@ -10,7 +10,6 @@ const { defaultBatchRequestConfig, getSuccessRespEvents, combineBatchRequestsWithSameJobIds, - getIntegrationsObj, } = require('../../util'); const { CALL_CONVERSION, @@ -23,18 +22,11 @@ const { getStoreConversionPayload, requestBuilder, getClickConversionPayloadAndEndpoint, + getConsentsDataFromIntegrationObj, } = require('./utils'); const { finaliseConsent } = require('../../util/googleUtils'); const helper = require('./helper'); -const getConsentsDataFromIntegrationObj = (message, conversionType) => { - const integrationObj = - conversionType === 'store' - ? {} - : getIntegrationsObj(message, 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS') || {}; - return integrationObj?.consents || {}; -}; - /** * get conversions depending on the type set from dashboard * i.e click conversions or call conversions @@ -52,7 +44,7 @@ const getConversions = (message, metadata, { Config }, event, conversionType) => const { properties, timestamp, originalTimestamp } = message; const filteredCustomerId = removeHyphens(customerId); - const eventLevelConsentsData = getConsentFromIntegrationObj(message, conversionType); + const eventLevelConsentsData = getConsentsDataFromIntegrationObj(message, conversionType); if (conversionType === 'click') { // click conversion @@ -60,16 +52,21 @@ const getConversions = (message, metadata, { Config }, event, conversionType) => message, Config, filteredCustomerId, - userSentConsentValues, + eventLevelConsentsData, ); payload = convertedPayload.payload; endpoint = convertedPayload.endpoint; } else if (conversionType === 'store') { - payload = getStoreConversionPayload(message, Config, filteredCustomerId, userSentConsentValues); + payload = getStoreConversionPayload( + message, + Config, + filteredCustomerId, + eventLevelConsentsData, + ); endpoint = STORE_CONVERSION_CONFIG.replace(':customerId', filteredCustomerId); } else { // call conversions - const consentObject = finaliseConsent(userSentConsentValues, Config, consentFields); + const consentObject = finaliseConsent(eventLevelConsentsData, Config, consentFields); payload = constructPayload(message, trackCallConversionsMapping); endpoint = CALL_CONVERSION.replace(':customerId', filteredCustomerId); payload.conversions[0].consent = consentObject; diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.js b/src/v0/destinations/google_adwords_offline_conversions/utils.js index 5a3b67cec0..b5a59fefa6 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.js @@ -17,6 +17,7 @@ const { isDefinedAndNotNull, getAuthErrCategoryFromStCode, getAccessToken, + getIntegrationsObj, } = require('../../util'); const { SEARCH_STREAM, @@ -376,6 +377,14 @@ const getClickConversionPayloadAndEndpoint = ( return { payload, endpoint }; }; +const getConsentsDataFromIntegrationObj = (message, conversionType) => { + const integrationObj = + conversionType === 'store' + ? {} + : getIntegrationsObj(message, 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS') || {}; + return integrationObj?.consents || {}; +}; + module.exports = { validateDestinationConfig, generateItemListFromProducts, @@ -386,4 +395,5 @@ module.exports = { buildAndGetAddress, getClickConversionPayloadAndEndpoint, getExisitingUserIdentifier, + getConsentsDataFromIntegrationObj, }; diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js index b2c15883d6..f9e545e7f3 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js @@ -2,6 +2,7 @@ const { getClickConversionPayloadAndEndpoint, buildAndGetAddress, getExisitingUserIdentifier, + getConsentsDataFromIntegrationObj, } = require('./utils'); const getTestMessage = () => { @@ -285,3 +286,46 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => { ); }); }); + +describe('getConsentsDataFromIntegrationObj', () => { + it('should return an empty object when conversionType is "store"', () => { + const message = {}; + const conversionType = 'store'; + const result = getConsentsDataFromIntegrationObj(message, conversionType); + expect(result).toEqual({}); + }); + + it('should return an empty object when conversionType is not "store" and getIntegrationsObj returns undefined', () => { + const message = {}; + const conversionType = 'click'; + const result = getConsentsDataFromIntegrationObj(message, conversionType); + expect(result).toEqual({}); + }); + + it('should return an empty object when conversionType is not "store" and getIntegrationsObj returns null', () => { + const message = {}; + const conversionType = 'call'; + const getIntegrationsObj = jest.fn(() => null); + const result = getConsentsDataFromIntegrationObj(message, conversionType); + expect(result).toEqual({}); + }); + + it('should return the consent object when conversion type is call', () => { + const message = { + integrations: { + GOOGLE_ADWORDS_OFFLINE_CONVERSIONS: { + consents: { + adUserData: 'GRANTED', + adPersonalization: 'DENIED', + }, + }, + }, + }; + const conversionType = 'call'; + const result = getConsentsDataFromIntegrationObj(message, conversionType); + expect(result).toEqual({ + adPersonalization: 'DENIED', + adUserData: 'GRANTED', + }); + }); +}); diff --git a/src/v0/util/googleUtils/index.test.js b/src/v0/util/googleUtils/index.test.js index d9d03a9805..a0d75851b1 100644 --- a/src/v0/util/googleUtils/index.test.js +++ b/src/v0/util/googleUtils/index.test.js @@ -1,7 +1,5 @@ const { populateConsentFromConfig, finaliseConsent } = require('./index'); -const destinationAllowedConsentKeys = ['adUserData', 'adPersonalization']; - describe('unit test for populateConsentFromConfig', () => { it('should return an UNSPECIFIED object when no properties are provided', () => { const result = populateConsentFromConfig({}); From 0f1337189e81c10cd432a7a30a8af2a284810605 Mon Sep 17 00:00:00 2001 From: shrouti1507 Date: Tue, 12 Mar 2024 12:56:37 +0530 Subject: [PATCH 13/17] fix: review comments addressed --- .../config.js | 3 -- .../transform.js | 12 ++---- .../utils.js | 18 ++++----- .../utils.test.js | 19 +--------- src/v0/util/googleUtils/index.js | 5 ++- src/v0/util/googleUtils/index.test.js | 38 +++++++++++-------- 6 files changed, 36 insertions(+), 59 deletions(-) diff --git a/src/v0/destinations/google_adwords_offline_conversions/config.js b/src/v0/destinations/google_adwords_offline_conversions/config.js index 7cd3a9cf28..412ab543a4 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/config.js +++ b/src/v0/destinations/google_adwords_offline_conversions/config.js @@ -42,8 +42,6 @@ const CONVERSION_CUSTOM_VARIABLE_CACHE_TTL = process.env.CONVERSION_CUSTOM_VARIA const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname); -const consentFields = ['adUserData', 'adPersonalization']; - module.exports = { trackClickConversionsMapping: MAPPING_CONFIG[CONFIG_CATEGORIES.TRACK_CLICK_CONVERSIONS_CONFIG.name], @@ -60,5 +58,4 @@ module.exports = { MAPPING_CONFIG[CONFIG_CATEGORIES.TRACK_STORE_CONVERSION_CONFIG_ADD_CONVERSION.name], trackAddStoreAddressConversionsMapping: MAPPING_CONFIG[CONFIG_CATEGORIES.TRACK_STORE_ADDRESS_IDENTIFIER.name], - consentFields, }; diff --git a/src/v0/destinations/google_adwords_offline_conversions/transform.js b/src/v0/destinations/google_adwords_offline_conversions/transform.js index 5ae92c57e9..69a80d3f8d 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/transform.js +++ b/src/v0/destinations/google_adwords_offline_conversions/transform.js @@ -15,7 +15,6 @@ const { CALL_CONVERSION, trackCallConversionsMapping, STORE_CONVERSION_CONFIG, - consentFields, } = require('./config'); const { validateDestinationConfig, @@ -44,7 +43,7 @@ const getConversions = (message, metadata, { Config }, event, conversionType) => const { properties, timestamp, originalTimestamp } = message; const filteredCustomerId = removeHyphens(customerId); - const eventLevelConsentsData = getConsentsDataFromIntegrationObj(message, conversionType); + const eventLevelConsentsData = getConsentsDataFromIntegrationObj(message); if (conversionType === 'click') { // click conversion @@ -57,16 +56,11 @@ const getConversions = (message, metadata, { Config }, event, conversionType) => payload = convertedPayload.payload; endpoint = convertedPayload.endpoint; } else if (conversionType === 'store') { - payload = getStoreConversionPayload( - message, - Config, - filteredCustomerId, - eventLevelConsentsData, - ); + payload = getStoreConversionPayload(message, Config, filteredCustomerId); endpoint = STORE_CONVERSION_CONFIG.replace(':customerId', filteredCustomerId); } else { // call conversions - const consentObject = finaliseConsent(eventLevelConsentsData, Config, consentFields); + const consentObject = finaliseConsent(eventLevelConsentsData, Config); payload = constructPayload(message, trackCallConversionsMapping); endpoint = CALL_CONVERSION.replace(':customerId', filteredCustomerId); payload.conversions[0].consent = consentObject; diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.js b/src/v0/destinations/google_adwords_offline_conversions/utils.js index b5a59fefa6..ccf6511a5f 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.js @@ -27,7 +27,6 @@ const { trackAddStoreAddressConversionsMapping, trackClickConversionsMapping, CLICK_CONVERSION, - consentFields, } = require('./config'); const { processAxiosResponse } = require('../../../adapters/utils/networkUtils'); const Cache = require('../../util/cache'); @@ -223,7 +222,7 @@ function getExisitingUserIdentifier(userIdentifierInfo, defaultUserIdentifier) { * This Function create the add conversion payload * and returns the payload */ -const getAddConversionPayload = (message, Config, eventLevelConsent) => { +const getAddConversionPayload = (message, Config) => { const { properties } = message; const { validateOnly, hashUserIdentifier, defaultUserIdentifier } = Config; const payload = constructPayload(message, trackAddStoreConversionsMapping); @@ -276,18 +275,18 @@ const getAddConversionPayload = (message, Config, eventLevelConsent) => { } } // add consent support for store conversions - const consentObject = finaliseConsent(eventLevelConsent, Config, consentFields); + const consentObject = finaliseConsent({}, Config); set(payload, 'operations.create.consent', consentObject); return payload; }; -const getStoreConversionPayload = (message, Config, event, eventLevelConsent) => { +const getStoreConversionPayload = (message, Config, event) => { const { validateOnly } = Config; const payload = { event, isStoreConversion: true, createJobPayload: getCreateJobPayload(message), - addConversionPayload: getAddConversionPayload(message, Config, eventLevelConsent), + addConversionPayload: getAddConversionPayload(message, Config), executeJobPayload: { validate_only: validateOnly }, }; return payload; @@ -372,16 +371,13 @@ const getClickConversionPayloadAndEndpoint = ( } // add consent support for click conversions - const consentObject = finaliseConsent(eventLevelConsent, Config, consentFields); + const consentObject = finaliseConsent(eventLevelConsent, Config); set(payload, 'conversions[0].consent', consentObject); return { payload, endpoint }; }; -const getConsentsDataFromIntegrationObj = (message, conversionType) => { - const integrationObj = - conversionType === 'store' - ? {} - : getIntegrationsObj(message, 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS') || {}; +const getConsentsDataFromIntegrationObj = (message) => { + const integrationObj = getIntegrationsObj(message, 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS') || {}; return integrationObj?.consents || {}; }; diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js index f9e545e7f3..56cb77de5f 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js @@ -290,26 +290,9 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => { describe('getConsentsDataFromIntegrationObj', () => { it('should return an empty object when conversionType is "store"', () => { const message = {}; - const conversionType = 'store'; - const result = getConsentsDataFromIntegrationObj(message, conversionType); + const result = getConsentsDataFromIntegrationObj(message); expect(result).toEqual({}); }); - - it('should return an empty object when conversionType is not "store" and getIntegrationsObj returns undefined', () => { - const message = {}; - const conversionType = 'click'; - const result = getConsentsDataFromIntegrationObj(message, conversionType); - expect(result).toEqual({}); - }); - - it('should return an empty object when conversionType is not "store" and getIntegrationsObj returns null', () => { - const message = {}; - const conversionType = 'call'; - const getIntegrationsObj = jest.fn(() => null); - const result = getConsentsDataFromIntegrationObj(message, conversionType); - expect(result).toEqual({}); - }); - it('should return the consent object when conversion type is call', () => { const message = { integrations: { diff --git a/src/v0/util/googleUtils/index.js b/src/v0/util/googleUtils/index.js index a684e45933..3fc8c476e0 100644 --- a/src/v0/util/googleUtils/index.js +++ b/src/v0/util/googleUtils/index.js @@ -2,6 +2,7 @@ const GOOGLE_ALLOWED_CONSENT_STATUS = ['UNSPECIFIED', 'UNKNOWN', 'GRANTED', 'DEN const UNSPECIFIED_CONSENT = 'UNSPECIFIED'; const UNKNOWN_CONSENT = 'UNKNOWN'; +const ADWORDS_CONSENT_FILEDS = ['adUserData', 'adPersonalization']; /** * Populates the consent object based on the provided properties. @@ -53,7 +54,7 @@ const populateConsentFromConfig = (config) => { * b) https://developers.google.com/google-ads/api/reference/rpc/v16/UserData#consent */ -const finaliseConsent = (eventLevelConsent, destConfig, destinationAllowedConsentKeys) => { +const finaliseConsent = (eventLevelConsent, destConfig) => { const consents = eventLevelConsent || {}; const defaultConsentBlock = populateConsentFromConfig(destConfig); @@ -70,7 +71,7 @@ const finaliseConsent = (eventLevelConsent, destConfig, destinationAllowedConsen }; // Construct consentObj based on the common consent fields - const consentObj = destinationAllowedConsentKeys.reduce((obj, consentType) => { + const consentObj = ADWORDS_CONSENT_FILEDS.reduce((obj, consentType) => { // eslint-disable-next-line no-param-reassign obj[consentType] = processConsent(consentType); return obj; diff --git a/src/v0/util/googleUtils/index.test.js b/src/v0/util/googleUtils/index.test.js index a0d75851b1..b9d2c9dd1b 100644 --- a/src/v0/util/googleUtils/index.test.js +++ b/src/v0/util/googleUtils/index.test.js @@ -68,9 +68,8 @@ describe('finaliseConsent', () => { userDataConsent: 'UNKNOWN', personalizationConsent: 'GRANTED', }; - const destinationAllowedConsentKeys = ['adUserData', 'adPersonalization']; - const result = finaliseConsent(eventLevelConsent, destConfig, destinationAllowedConsentKeys); + const result = finaliseConsent(eventLevelConsent, destConfig); expect(result).toEqual({ adUserData: 'GRANTED', @@ -78,15 +77,28 @@ describe('finaliseConsent', () => { }); }); + it('should return an object containing consent information from destConfig when evenLevelConsent is empty object', () => { + const eventLevelConsent = {}; // for store conversion we will use this + const destConfig = { + userDataConsent: 'UNKNOWN', + personalizationConsent: 'GRANTED', + }; + + const result = finaliseConsent(eventLevelConsent, destConfig); + + expect(result).toEqual({ + adUserData: 'UNKNOWN', + adPersonalization: 'GRANTED', + }); + }); + // If destConfig is not provided, it does not return UNSPECIFIED_CONSENT. it('should not return UNSPECIFIED_CONSENT when destConfig is not provided but event level consent is provided', () => { const eventLevelConsent = { adUserData: 'GRANTED', adPersonalization: 'DENIED', }; - const destinationAllowedConsentKeys = ['adUserData', 'adPersonalization']; - - const result = finaliseConsent(eventLevelConsent, undefined, destinationAllowedConsentKeys); + const result = finaliseConsent(eventLevelConsent, undefined); // Assert expect(result).toEqual({ @@ -96,9 +108,7 @@ describe('finaliseConsent', () => { }); it('should return UNSPECIFIED_CONSENT when both destConfig and event level consent is not provided', () => { - const destinationAllowedConsentKeys = ['adUserData', 'adPersonalization']; - - const result = finaliseConsent(undefined, undefined, destinationAllowedConsentKeys); + const result = finaliseConsent(undefined, undefined); // Assert expect(result).toEqual({ @@ -108,14 +118,12 @@ describe('finaliseConsent', () => { }); it('should return UNKWOWN_CONSENT when destConfig is provided with wrong consent value', () => { - const destinationAllowedConsentKeys = ['adUserData', 'adPersonalization']; - const destConfig = { userDataConsent: 'UNKNOWN', personalizationConsent: 'WRONG CONSENT', }; - const result = finaliseConsent(undefined, destConfig, destinationAllowedConsentKeys); + const result = finaliseConsent(undefined, destConfig); expect(result).toEqual({ adUserData: 'UNKNOWN', @@ -124,18 +132,16 @@ describe('finaliseConsent', () => { }); it('should return UNKWOWN_CONSENT when destConfig is provided with wrong consent value', () => { - const destinationAllowedConsentKeys = ['newKey1', 'newKey2']; - const destConfig = { userDataConsent: 'UNKNOWN', personalizationConsent: 'WRONG CONSENT', }; - const result = finaliseConsent(undefined, destConfig, destinationAllowedConsentKeys); + const result = finaliseConsent(undefined, destConfig); expect(result).toEqual({ - newKey1: 'UNSPECIFIED', - newKey2: 'UNSPECIFIED', + adPersonalization: 'UNKNOWN', + adUserData: 'UNKNOWN', }); }); }); From 9bae3d455cf254d0b315ce588bc7ba7bc66e1424 Mon Sep 17 00:00:00 2001 From: shrouti1507 Date: Tue, 12 Mar 2024 13:30:34 +0530 Subject: [PATCH 14/17] fix: review comments addressed --- .../google_adwords_offline_conversions/transform.js | 13 +++---------- .../google_adwords_offline_conversions/utils.js | 13 ++++++++++++- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/v0/destinations/google_adwords_offline_conversions/transform.js b/src/v0/destinations/google_adwords_offline_conversions/transform.js index 69a80d3f8d..95b3ccf4ad 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/transform.js +++ b/src/v0/destinations/google_adwords_offline_conversions/transform.js @@ -3,7 +3,6 @@ const { InstrumentationError, ConfigurationError } = require('@rudderstack/integ const { EventType } = require('../../../constants'); const { getHashFromArrayWithDuplicate, - constructPayload, removeHyphens, getHashFromArray, handleRtTfSingleEventError, @@ -11,19 +10,15 @@ const { getSuccessRespEvents, combineBatchRequestsWithSameJobIds, } = require('../../util'); -const { - CALL_CONVERSION, - trackCallConversionsMapping, - STORE_CONVERSION_CONFIG, -} = require('./config'); +const { CALL_CONVERSION, STORE_CONVERSION_CONFIG } = require('./config'); const { validateDestinationConfig, getStoreConversionPayload, requestBuilder, getClickConversionPayloadAndEndpoint, getConsentsDataFromIntegrationObj, + getCallConversionPayload, } = require('./utils'); -const { finaliseConsent } = require('../../util/googleUtils'); const helper = require('./helper'); /** @@ -60,10 +55,8 @@ const getConversions = (message, metadata, { Config }, event, conversionType) => endpoint = STORE_CONVERSION_CONFIG.replace(':customerId', filteredCustomerId); } else { // call conversions - const consentObject = finaliseConsent(eventLevelConsentsData, Config); - payload = constructPayload(message, trackCallConversionsMapping); + payload = { ...getCallConversionPayload(message, Config, eventLevelConsentsData) }; endpoint = CALL_CONVERSION.replace(':customerId', filteredCustomerId); - payload.conversions[0].consent = consentObject; } if (conversionType !== 'store') { diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.js b/src/v0/destinations/google_adwords_offline_conversions/utils.js index ccf6511a5f..44ee77d5f8 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.js @@ -27,6 +27,7 @@ const { trackAddStoreAddressConversionsMapping, trackClickConversionsMapping, CLICK_CONVERSION, + trackCallConversionsMapping, } = require('./config'); const { processAxiosResponse } = require('../../../adapters/utils/networkUtils'); const Cache = require('../../util/cache'); @@ -218,6 +219,13 @@ function getExisitingUserIdentifier(userIdentifierInfo, defaultUserIdentifier) { return result; } +const getCallConversionPayload = (message, Config, eventLevelConsentsData) => { + const payload = constructPayload(message, trackCallConversionsMapping); + // here conversions[0] should be present because there are some mandatory properties mapped in the mapping json. + payload.conversions[0].consent = finaliseConsent(eventLevelConsentsData, Config); + return payload; +}; + /** * This Function create the add conversion payload * and returns the payload @@ -274,8 +282,9 @@ const getAddConversionPayload = (message, Config) => { set(payload, 'operations.create.userIdentifiers[0]', {}); } } - // add consent support for store conversions + // add consent support for store conversions. Note: No event level consent supported. const consentObject = finaliseConsent({}, Config); + // create property should be present because there are some mandatory properties mapped in the mapping json. set(payload, 'operations.create.consent', consentObject); return payload; }; @@ -372,6 +381,7 @@ const getClickConversionPayloadAndEndpoint = ( // add consent support for click conversions const consentObject = finaliseConsent(eventLevelConsent, Config); + // here conversions[0] is expected to be present there are some mandatory properties mapped in the mapping json. set(payload, 'conversions[0].consent', consentObject); return { payload, endpoint }; }; @@ -392,4 +402,5 @@ module.exports = { getClickConversionPayloadAndEndpoint, getExisitingUserIdentifier, getConsentsDataFromIntegrationObj, + getCallConversionPayload, }; From ad6aea016c890c35e3afec42d0b861adf2a92735 Mon Sep 17 00:00:00 2001 From: shrouti1507 Date: Tue, 12 Mar 2024 18:52:03 +0530 Subject: [PATCH 15/17] fix: utils added --- .../transform.js | 2 +- .../utils.test.js | 90 +++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/src/v0/destinations/google_adwords_offline_conversions/transform.js b/src/v0/destinations/google_adwords_offline_conversions/transform.js index 95b3ccf4ad..c3be0f7cab 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/transform.js +++ b/src/v0/destinations/google_adwords_offline_conversions/transform.js @@ -55,7 +55,7 @@ const getConversions = (message, metadata, { Config }, event, conversionType) => endpoint = STORE_CONVERSION_CONFIG.replace(':customerId', filteredCustomerId); } else { // call conversions - payload = { ...getCallConversionPayload(message, Config, eventLevelConsentsData) }; + payload = getCallConversionPayload(message, Config, eventLevelConsentsData); endpoint = CALL_CONVERSION.replace(':customerId', filteredCustomerId); } diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js index 56cb77de5f..2d1863413c 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js @@ -3,6 +3,7 @@ const { buildAndGetAddress, getExisitingUserIdentifier, getConsentsDataFromIntegrationObj, + getCallConversionPayload, } = require('./utils'); const getTestMessage = () => { @@ -312,3 +313,92 @@ describe('getConsentsDataFromIntegrationObj', () => { }); }); }); + +describe('getCallConversionPayload', () => { + it('should call conversion payload with consent object', () => { + const message = { + properties: { + callerId: '1234', + callStartDateTime: '2022-01-01 12:32:45-08:00', + conversionDateTime: '2022-01-01 12:32:45-08:00', + }, + }; + const result = getCallConversionPayload( + message, + { + userDataConsent: 'GRANTED', + personalizationConsent: 'DENIED', + }, + { + adUserData: 'GRANTED', + adPersonalization: 'GRANTED', + }, + ); + expect(result).toEqual({ + conversions: [ + { + callStartDateTime: '2022-01-01 12:32:45-08:00', + callerId: '1234', + consent: { + adPersonalization: 'GRANTED', + adUserData: 'GRANTED', + }, + conversionDateTime: '2022-01-01 12:32:45-08:00', + }, + ], + }); + }); + it('should call conversion payload with consent object', () => { + const message = { + properties: { + callerId: '1234', + callStartDateTime: '2022-01-01 12:32:45-08:00', + conversionDateTime: '2022-01-01 12:32:45-08:00', + }, + }; + const result = getCallConversionPayload( + message, + { + userDataConsent: 'GRANTED', + personalizationConsent: 'DENIED', + }, + {}, + ); + expect(result).toEqual({ + conversions: [ + { + callStartDateTime: '2022-01-01 12:32:45-08:00', + callerId: '1234', + consent: { + adPersonalization: 'DENIED', + adUserData: 'GRANTED', + }, + conversionDateTime: '2022-01-01 12:32:45-08:00', + }, + ], + }); + }); + it('should call conversion payload with consent object even if no consent input from UI as well as event level', () => { + const message = { + properties: { + callerId: '1234', + callStartDateTime: '2022-01-01 12:32:45-08:00', + conversionDateTime: '2022-01-01 12:32:45-08:00', + }, + }; + const result = getCallConversionPayload(message, {}, {}); + expect(result).toEqual({ + conversions: [ + { + callStartDateTime: '2022-01-01 12:32:45-08:00', + callerId: '1234', + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, + conversionDateTime: '2022-01-01 12:32:45-08:00', + }, + ], + }); + }); +}); From 05cc8676481ef92214c7b52b6c68eea862f08872 Mon Sep 17 00:00:00 2001 From: shrouti1507 Date: Tue, 12 Mar 2024 21:46:26 +0530 Subject: [PATCH 16/17] fix: util function made more pluggable --- .../config.js | 6 + .../utils.js | 11 +- .../config.js | 6 + .../transform.js | 3 +- src/v0/util/googleUtils/index.js | 94 ++++++------- src/v0/util/googleUtils/index.test.js | 130 +++++++++++++++--- 6 files changed, 184 insertions(+), 66 deletions(-) diff --git a/src/v0/destinations/google_adwords_offline_conversions/config.js b/src/v0/destinations/google_adwords_offline_conversions/config.js index 412ab543a4..f065be946c 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/config.js +++ b/src/v0/destinations/google_adwords_offline_conversions/config.js @@ -42,6 +42,11 @@ const CONVERSION_CUSTOM_VARIABLE_CACHE_TTL = process.env.CONVERSION_CUSTOM_VARIA const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname); +const consentConfigMap = { + personalizationConsent: 'adPersonalization', + userDataConsent: 'adUserData', +}; + module.exports = { trackClickConversionsMapping: MAPPING_CONFIG[CONFIG_CATEGORIES.TRACK_CLICK_CONVERSIONS_CONFIG.name], @@ -58,4 +63,5 @@ module.exports = { MAPPING_CONFIG[CONFIG_CATEGORIES.TRACK_STORE_CONVERSION_CONFIG_ADD_CONVERSION.name], trackAddStoreAddressConversionsMapping: MAPPING_CONFIG[CONFIG_CATEGORIES.TRACK_STORE_ADDRESS_IDENTIFIER.name], + consentConfigMap, }; diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.js b/src/v0/destinations/google_adwords_offline_conversions/utils.js index 44ee77d5f8..a589f6459c 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.js @@ -28,6 +28,7 @@ const { trackClickConversionsMapping, CLICK_CONVERSION, trackCallConversionsMapping, + consentConfigMap, } = require('./config'); const { processAxiosResponse } = require('../../../adapters/utils/networkUtils'); const Cache = require('../../util/cache'); @@ -222,7 +223,11 @@ function getExisitingUserIdentifier(userIdentifierInfo, defaultUserIdentifier) { const getCallConversionPayload = (message, Config, eventLevelConsentsData) => { const payload = constructPayload(message, trackCallConversionsMapping); // here conversions[0] should be present because there are some mandatory properties mapped in the mapping json. - payload.conversions[0].consent = finaliseConsent(eventLevelConsentsData, Config); + payload.conversions[0].consent = finaliseConsent( + consentConfigMap, + eventLevelConsentsData, + Config, + ); return payload; }; @@ -283,7 +288,7 @@ const getAddConversionPayload = (message, Config) => { } } // add consent support for store conversions. Note: No event level consent supported. - const consentObject = finaliseConsent({}, Config); + const consentObject = finaliseConsent(consentConfigMap, {}, Config); // create property should be present because there are some mandatory properties mapped in the mapping json. set(payload, 'operations.create.consent', consentObject); return payload; @@ -380,7 +385,7 @@ const getClickConversionPayloadAndEndpoint = ( } // add consent support for click conversions - const consentObject = finaliseConsent(eventLevelConsent, Config); + const consentObject = finaliseConsent(consentConfigMap, eventLevelConsent, Config); // here conversions[0] is expected to be present there are some mandatory properties mapped in the mapping json. set(payload, 'conversions[0].consent', consentObject); return { payload, endpoint }; diff --git a/src/v0/destinations/google_adwords_remarketing_lists/config.js b/src/v0/destinations/google_adwords_remarketing_lists/config.js index 5bf0d8a299..0f08b3866d 100644 --- a/src/v0/destinations/google_adwords_remarketing_lists/config.js +++ b/src/v0/destinations/google_adwords_remarketing_lists/config.js @@ -16,6 +16,11 @@ const TYPEOFLIST = Object.freeze({ mobileDeviceID: 'mobileId', }); +const consentConfigMap = { + personalizationConsent: 'adPersonalization', + userDataConsent: 'adUserData', +}; + module.exports = { BASE_ENDPOINT, TYPEOFLIST, @@ -23,4 +28,5 @@ module.exports = { hashAttributes, offlineDataJobsMapping: MAPPING_CONFIG[CONFIG_CATEGORIES.AUDIENCE_LIST.name], addressInfoMapping: MAPPING_CONFIG[CONFIG_CATEGORIES.ADDRESSINFO.name], + consentConfigMap, }; diff --git a/src/v0/destinations/google_adwords_remarketing_lists/transform.js b/src/v0/destinations/google_adwords_remarketing_lists/transform.js index 4503b46735..b0dfaa0c35 100644 --- a/src/v0/destinations/google_adwords_remarketing_lists/transform.js +++ b/src/v0/destinations/google_adwords_remarketing_lists/transform.js @@ -24,6 +24,7 @@ const { attributeMapping, hashAttributes, TYPEOFLIST, + consentConfigMap, } = require('./config'); const { JSON_MIME_TYPE } = require('../../util/constant'); const { MappedToDestinationKey } = require('../../../constants'); @@ -218,7 +219,7 @@ const processEvent = async (metadata, message, destination) => { } Object.values(createdPayload).forEach((data) => { - const consentObj = populateConsentFromConfig(destination.Config); + const consentObj = populateConsentFromConfig(destination.Config, consentConfigMap); response.push(responseBuilder(metadata, data, destination, message, consentObj)); }); return response; diff --git a/src/v0/util/googleUtils/index.js b/src/v0/util/googleUtils/index.js index 3fc8c476e0..c153731e73 100644 --- a/src/v0/util/googleUtils/index.js +++ b/src/v0/util/googleUtils/index.js @@ -2,47 +2,42 @@ const GOOGLE_ALLOWED_CONSENT_STATUS = ['UNSPECIFIED', 'UNKNOWN', 'GRANTED', 'DEN const UNSPECIFIED_CONSENT = 'UNSPECIFIED'; const UNKNOWN_CONSENT = 'UNKNOWN'; -const ADWORDS_CONSENT_FILEDS = ['adUserData', 'adPersonalization']; /** - * Populates the consent object based on the provided properties. + * Populates the consent object based on the provided configuration and consent mapping. * - * @param {object} properties - message.properties containing properties related to consent. - * @returns {object} - An object containing consent information. - * ref : https://developers.google.com/google-ads/api/rest/reference/rest/v16/Consent + * @param {Object} config - The configuration object containing consent values. + * @param {Object} consentConfigMap - The mapping of consent keys to consent types. + * @returns {Object} - The consent object populated with consent values based on the configuration. + * * ref : https://developers.google.com/google-ads/api/rest/reference/rest/v16/Consent */ - -const populateConsentFromConfig = (config) => { +const populateConsentFromConfig = (config, consentConfigMap) => { const consent = {}; - if (config?.userDataConsent) { - if (GOOGLE_ALLOWED_CONSENT_STATUS.includes(config.userDataConsent)) { - consent.adUserData = config.userDataConsent; + Object.keys(consentConfigMap).forEach((key) => { + const consentType = consentConfigMap[key]; + if (config?.[key]) { + if (GOOGLE_ALLOWED_CONSENT_STATUS.includes(config[key])) { + consent[consentType] = config[key]; + } else { + consent[consentType] = UNKNOWN_CONSENT; + } } else { - consent.adUserData = UNKNOWN_CONSENT; + consent[consentType] = UNSPECIFIED_CONSENT; } - } else { - consent.adUserData = UNSPECIFIED_CONSENT; - } + }); - if (config?.personalizationConsent) { - if (GOOGLE_ALLOWED_CONSENT_STATUS.includes(config.personalizationConsent)) { - consent.adPersonalization = config.personalizationConsent; - } else { - consent.adPersonalization = UNKNOWN_CONSENT; - } - } else { - consent.adPersonalization = UNSPECIFIED_CONSENT; - } return consent; }; /** - * Populates the consent object based on the provided properties. + * Generates the final consent object based on the provided consent configuration map, event-level consent, and destination configuration. * - * @param {object} properties - message.properties containing properties related to consent. - * @returns {object} - An object containing consent information. - * * ref : + * @param {Object} consentConfigMap - The map of consent configuration keys and their corresponding consent types. + * @param {Object} [eventLevelConsent={}] - The event-level consent object. + * @param {Object} [destConfig={}] - The destination configuration object. + * @returns {Object} The final consent object. + * ref : * 1) For click conversion : * a) https://developers.google.com/google-ads/api/rest/reference/rest/v16/customers/uploadClickConversions#ClickConversion * b) https://developers.google.com/google-ads/api/reference/rpc/v16/ClickConversion#consent @@ -53,29 +48,36 @@ const populateConsentFromConfig = (config) => { * a) https://developers.google.com/google-ads/api/reference/rpc/v16/UserData * b) https://developers.google.com/google-ads/api/reference/rpc/v16/UserData#consent */ +const finaliseConsent = (consentConfigMap, eventLevelConsent = {}, destConfig = {}) => { + // Initialize defaultConsentBlock with unspecified consent for all keys defined in consentConfigMap + const defaultConsentBlock = Object.keys(consentConfigMap).reduce((acc, key) => { + const consentType = consentConfigMap[key]; + acc[consentType] = UNSPECIFIED_CONSENT; + return acc; + }, {}); -const finaliseConsent = (eventLevelConsent, destConfig) => { - const consents = eventLevelConsent || {}; + // If destConfig is provided, update defaultConsentBlock based on it using populateConsentFromConfig + if (Object.keys(destConfig).length > 0) { + const populatedConsent = populateConsentFromConfig(destConfig, consentConfigMap); + Object.assign(defaultConsentBlock, populatedConsent); + } - const defaultConsentBlock = populateConsentFromConfig(destConfig); + const consentObj = {}; - // Define a function to process consent based on type - const processConsent = (consentType) => { - if (!consents[consentType]) { - return defaultConsentBlock[consentType] || UNSPECIFIED_CONSENT; - } - if (GOOGLE_ALLOWED_CONSENT_STATUS.includes(consents[consentType])) { - return consents[consentType]; - } - return defaultConsentBlock[consentType] || UNKNOWN_CONSENT; - }; + // Iterate through each key in consentConfigMap to determine the final consent + Object.keys(consentConfigMap).forEach((configKey) => { + const consentKey = consentConfigMap[configKey]; // e.g., 'adUserData' - // Construct consentObj based on the common consent fields - const consentObj = ADWORDS_CONSENT_FILEDS.reduce((obj, consentType) => { - // eslint-disable-next-line no-param-reassign - obj[consentType] = processConsent(consentType); - return obj; - }, {}); + // Prioritize event-level consent if available + if (eventLevelConsent && eventLevelConsent.hasOwnProperty(consentKey)) { + consentObj[consentKey] = GOOGLE_ALLOWED_CONSENT_STATUS.includes(eventLevelConsent[consentKey]) + ? eventLevelConsent[consentKey] + : UNKNOWN_CONSENT; + } else { + // Fallback to default consent block + consentObj[consentKey] = defaultConsentBlock[consentKey]; + } + }); return consentObj; }; diff --git a/src/v0/util/googleUtils/index.test.js b/src/v0/util/googleUtils/index.test.js index b9d2c9dd1b..28e0fa9ac8 100644 --- a/src/v0/util/googleUtils/index.test.js +++ b/src/v0/util/googleUtils/index.test.js @@ -1,8 +1,12 @@ const { populateConsentFromConfig, finaliseConsent } = require('./index'); describe('unit test for populateConsentFromConfig', () => { + const consentConfigMap = { + personalizationConsent: 'adPersonalization', + userDataConsent: 'adUserData', + }; it('should return an UNSPECIFIED object when no properties are provided', () => { - const result = populateConsentFromConfig({}); + const result = populateConsentFromConfig({}, consentConfigMap); expect(result).toEqual({ adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED', @@ -11,18 +15,18 @@ describe('unit test for populateConsentFromConfig', () => { it('should set adUserData property of consent object when userDataConsent property is provided and its value is one of the allowed consent statuses', () => { const properties = { userDataConsent: 'GRANTED' }; - const result = populateConsentFromConfig(properties); + const result = populateConsentFromConfig(properties, consentConfigMap); expect(result).toEqual({ adUserData: 'GRANTED', adPersonalization: 'UNSPECIFIED' }); }); it('should set adPersonalization property of consent object when personalizationConsent property is provided and its value is one of the allowed consent statuses', () => { const properties = { personalizationConsent: 'DENIED' }; - const result = populateConsentFromConfig(properties); + const result = populateConsentFromConfig(properties, consentConfigMap); expect(result).toEqual({ adPersonalization: 'DENIED', adUserData: 'UNSPECIFIED' }); }); it('should return an UNSPECIFIED object when properties parameter is not provided', () => { - const result = populateConsentFromConfig(); + const result = populateConsentFromConfig(undefined, consentConfigMap); expect(result).toEqual({ adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED', @@ -30,7 +34,7 @@ describe('unit test for populateConsentFromConfig', () => { }); it('should return an UNSPECIFIED object when properties parameter is null', () => { - const result = populateConsentFromConfig(null); + const result = populateConsentFromConfig(null, consentConfigMap); expect(result).toEqual({ adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED', @@ -38,7 +42,7 @@ describe('unit test for populateConsentFromConfig', () => { }); it('should return an UNSPECIFIED object when properties parameter is an UNSPECIFIED object', () => { - const result = populateConsentFromConfig({}); + const result = populateConsentFromConfig({}, consentConfigMap); expect(result).toEqual({ adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED', @@ -46,10 +50,13 @@ describe('unit test for populateConsentFromConfig', () => { }); it('should return UNKNOWN when properties parameter contains adUserData and adPersonalization with non-allowed values', () => { - const result = populateConsentFromConfig({ - userDataConsent: 'RANDOM', - personalizationConsent: 'RANDOM', - }); + const result = populateConsentFromConfig( + { + userDataConsent: 'RANDOM', + personalizationConsent: 'RANDOM', + }, + consentConfigMap, + ); expect(result).toEqual({ adPersonalization: 'UNKNOWN', adUserData: 'UNKNOWN', @@ -58,6 +65,10 @@ describe('unit test for populateConsentFromConfig', () => { }); describe('finaliseConsent', () => { + const consentConfigMap = { + personalizationConsent: 'adPersonalization', + userDataConsent: 'adUserData', + }; // Returns an object containing consent information. it('should return an object containing consent information when eventLevelConsent, destConfig, and destinationAllowedConsentKeys are provided', () => { const eventLevelConsent = { @@ -69,7 +80,7 @@ describe('finaliseConsent', () => { personalizationConsent: 'GRANTED', }; - const result = finaliseConsent(eventLevelConsent, destConfig); + const result = finaliseConsent(consentConfigMap, eventLevelConsent, destConfig); expect(result).toEqual({ adUserData: 'GRANTED', @@ -84,7 +95,7 @@ describe('finaliseConsent', () => { personalizationConsent: 'GRANTED', }; - const result = finaliseConsent(eventLevelConsent, destConfig); + const result = finaliseConsent(consentConfigMap, eventLevelConsent, destConfig); expect(result).toEqual({ adUserData: 'UNKNOWN', @@ -98,7 +109,7 @@ describe('finaliseConsent', () => { adUserData: 'GRANTED', adPersonalization: 'DENIED', }; - const result = finaliseConsent(eventLevelConsent, undefined); + const result = finaliseConsent(consentConfigMap, eventLevelConsent, undefined); // Assert expect(result).toEqual({ @@ -108,7 +119,7 @@ describe('finaliseConsent', () => { }); it('should return UNSPECIFIED_CONSENT when both destConfig and event level consent is not provided', () => { - const result = finaliseConsent(undefined, undefined); + const result = finaliseConsent(consentConfigMap, undefined, undefined); // Assert expect(result).toEqual({ @@ -123,7 +134,7 @@ describe('finaliseConsent', () => { personalizationConsent: 'WRONG CONSENT', }; - const result = finaliseConsent(undefined, destConfig); + const result = finaliseConsent(consentConfigMap, undefined, destConfig); expect(result).toEqual({ adUserData: 'UNKNOWN', @@ -137,11 +148,98 @@ describe('finaliseConsent', () => { personalizationConsent: 'WRONG CONSENT', }; - const result = finaliseConsent(undefined, destConfig); + const result = finaliseConsent(consentConfigMap, undefined, destConfig); expect(result).toEqual({ adPersonalization: 'UNKNOWN', adUserData: 'UNKNOWN', }); }); + + it('should return consent block with appropriate fields and values from destConfig', () => { + const consentConfigMap = { + personalizationConsent: 'newKey1', + userDataConsent: 'newKey2', + }; + const destConfig = { + userDataConsent: 'GRANTED', + personalizationConsent: 'GRANTED', + }; + + const result = finaliseConsent(consentConfigMap, undefined, destConfig); + + expect(result).toEqual({ + newKey1: 'GRANTED', + newKey2: 'GRANTED', + }); + }); + + it('should return consent block with appropriate fields from consentConfigMap and values from eventLevel consent', () => { + const consentConfigMap = { + personalizationConsent: 'newKey1', + userDataConsent: 'newKey2', + }; + const destConfig = { + userDataConsent: 'GRANTED', + personalizationConsent: 'GRANTED', + }; + + const eventLevelConsent = { + newKey1: 'UNKNOWN', + newKey2: 'UNSPECIFIED', + }; + + const result = finaliseConsent(consentConfigMap, eventLevelConsent, destConfig); + + expect(result).toEqual({ + newKey1: 'UNKNOWN', + newKey2: 'UNSPECIFIED', + }); + }); + + it('consentConfig and eventLevelConsent should have parity, also the values should be within allowed values otherwise UNKNOWN is returned ', () => { + const consentConfigMap = { + personalizationConsent: 'newKey1', + userDataConsent: 'newKey2', + }; + const destConfig = { + userDataConsent: 'GRANTED', + personalizationConsent: 'GRANTED', + }; + + const eventLevelConsent = { + adUserData: 'UNKNOWN', + adPersonalization: 'UNSPECIFIED', + }; + + const result = finaliseConsent(consentConfigMap, eventLevelConsent, destConfig); + + expect(result).toEqual({ + newKey1: 'GRANTED', + newKey2: 'GRANTED', + }); + }); + + it('consentConfig and eventLevelConsent should have parity, otherwise it will take values from destConfig ', () => { + const consentConfigMap = { + personalizationConsent: 'newKey1', + userDataConsent: 'newKey2', + }; + const destConfig = { + userDataConsent: 'GRANTED', + personalizationConsent: 'GRANTED', + }; + + const eventLevelConsent = { + newKey1: 'DENIED', + newKey2: 'RANDOM', + }; + + const result = finaliseConsent(consentConfigMap, eventLevelConsent, destConfig); + + expect(result).toEqual({ + newKey1: 'DENIED', + newKey2: 'UNKNOWN', + }); + }); }); From f3e48c63d44e09ac8987973ce9c9f2691e4480f6 Mon Sep 17 00:00:00 2001 From: Gauravudia <60897972+Gauravudia@users.noreply.github.com> Date: Mon, 18 Mar 2024 18:18:58 +0530 Subject: [PATCH 17/17] chore: add event validation for movable ink destination (#3190) chore: add validation for movable ink destination --- .../movable_ink/procWorkflow.yaml | 1 + src/cdk/v2/destinations/movable_ink/utils.js | 21 +++++ .../movable_ink/processor/validation.ts | 86 +++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 src/cdk/v2/destinations/movable_ink/utils.js diff --git a/src/cdk/v2/destinations/movable_ink/procWorkflow.yaml b/src/cdk/v2/destinations/movable_ink/procWorkflow.yaml index 25270058c5..43dbb3cbce 100644 --- a/src/cdk/v2/destinations/movable_ink/procWorkflow.yaml +++ b/src/cdk/v2/destinations/movable_ink/procWorkflow.yaml @@ -33,6 +33,7 @@ steps: ); $.assert(userId ?? email ?? .message.anonymousId, "Either one of userId or email or anonymousId is required. Aborting"); + $.validateEventPayload(.message); - name: preparePayload description: Prepare payload for identify and track. This payload schema needs to be configured in the Movable Ink dashboard. Movable Ink will discard any additional fields from the input payload. diff --git a/src/cdk/v2/destinations/movable_ink/utils.js b/src/cdk/v2/destinations/movable_ink/utils.js new file mode 100644 index 0000000000..04d7046b1a --- /dev/null +++ b/src/cdk/v2/destinations/movable_ink/utils.js @@ -0,0 +1,21 @@ +const { InstrumentationError } = require('@rudderstack/integrations-lib'); + +const validateEventPayload = (message) => { + const { event } = message; + const { properties } = message; + if (event === 'Products Searched' && !properties?.query) { + throw new InstrumentationError("Missing 'query' property in properties. Aborting"); + } + + if ( + (event === 'Product Added' || + event === 'Product Removed' || + event === 'Product Viewed' || + event === 'Category Viewed') && + !properties?.product_id + ) { + throw new InstrumentationError("Missing 'product_id' property in properties. Aborting"); + } +}; + +module.exports = { validateEventPayload }; diff --git a/test/integrations/destinations/movable_ink/processor/validation.ts b/test/integrations/destinations/movable_ink/processor/validation.ts index f9f6c6a927..ab6b123eb7 100644 --- a/test/integrations/destinations/movable_ink/processor/validation.ts +++ b/test/integrations/destinations/movable_ink/processor/validation.ts @@ -128,4 +128,90 @@ export const validation: ProcessorTestData[] = [ }, }, }, + { + id: 'MovableInk-validation-test-4', + name: destType, + description: "Products Searched event - Missing 'query' property", + scenario: 'Framework', + successCriteria: 'Instrumentation Error', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + message: { + type: 'track', + userId: 'user123', + integrations: { + All: true, + }, + event: 'Products Searched', + originalTimestamp: '2024-03-04T15:32:56.409Z', + }, + metadata: generateMetadata(1), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + "Missing 'query' property in properties. Aborting: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: Missing 'query' property in properties. Aborting", + metadata: generateMetadata(1), + statTags: processorInstrumentationErrorStatTags, + statusCode: 400, + }, + ], + }, + }, + }, + { + id: 'MovableInk-validation-test-5', + name: destType, + description: "Products Added event - Missing 'product_id' property", + scenario: 'Framework', + successCriteria: 'Instrumentation Error', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + message: { + type: 'track', + userId: 'user123', + integrations: { + All: true, + }, + event: 'Product Added', + originalTimestamp: '2024-03-04T15:32:56.409Z', + }, + metadata: generateMetadata(1), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + "Missing 'product_id' property in properties. Aborting: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: Missing 'product_id' property in properties. Aborting", + metadata: generateMetadata(1), + statTags: processorInstrumentationErrorStatTags, + statusCode: 400, + }, + ], + }, + }, + }, ];