From 2aac2a62547b7a7c617735fc3d6e88e0a1bed76e Mon Sep 17 00:00:00 2001 From: shrouti1507 <60211312+shrouti1507@users.noreply.github.com> Date: Fri, 22 Mar 2024 16:03:02 +0530 Subject: [PATCH 1/2] feat: consent field support for gaoc for API v15 and upgrade the api version from v14 to v16 (#3121) * feat: consent field support for GAOC * fix: clean up of commented code * fix: clean up the old implementation of GARL to avoid code duplication * fix: small edit * chore: store sales updated, review comments addressed * fix: relocating the util and test cases * fix: refactoring the util * fix: updating API version to v16 * fix: review comments address * Update src/v0/destinations/google_adwords_offline_conversions/transform.js Co-authored-by: Sai Kumar Battinoju <88789928+saikumarrs@users.noreply.github.com> * Update src/v0/destinations/google_adwords_offline_conversions/transform.js Co-authored-by: Sai Kumar Battinoju <88789928+saikumarrs@users.noreply.github.com> * fix: review comments address * fix: review comments addressed * fix: review comments addressed * fix: utils added * fix: util function made more pluggable * chore: add event validation for movable ink destination (#3190) chore: add validation for movable ink destination --------- Co-authored-by: Sai Kumar Battinoju <88789928+saikumarrs@users.noreply.github.com> Co-authored-by: Gauravudia <60897972+Gauravudia@users.noreply.github.com> Co-authored-by: Krishna Chaitanya --- src/constants/destinationCanonicalNames.js | 12 + .../config.js | 8 +- .../transform.js | 15 +- .../utils.js | 38 +- .../utils.test.js | 139 +++++- .../config.js | 6 + .../transform.js | 5 +- src/v0/util/googleUtils/index.js | 97 +++- src/v0/util/googleUtils/index.test.js | 209 ++++++++- .../dataDelivery/business.ts | 20 +- .../dataDelivery/oauth.ts | 8 +- .../network.ts | 34 +- .../processor/data.ts | 430 +++++++++++++++++- .../router/data.ts | 28 +- 14 files changed, 946 insertions(+), 103 deletions(-) diff --git a/src/constants/destinationCanonicalNames.js b/src/constants/destinationCanonicalNames.js index 17848e6b94..b84aff1089 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', + ], koala: ['Koala', 'koala', 'KOALA'], }; diff --git a/src/v0/destinations/google_adwords_offline_conversions/config.js b/src/v0/destinations/google_adwords_offline_conversions/config.js index a02732894f..f065be946c 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 = 'v16'; const BASE_ENDPOINT = `https://googleads.googleapis.com/${API_VERSION}/customers/:customerId`; @@ -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/transform.js b/src/v0/destinations/google_adwords_offline_conversions/transform.js index 68d4d01fa7..c3be0f7cab 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,16 +10,14 @@ 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 helper = require('./helper'); @@ -41,12 +38,15 @@ const getConversions = (message, metadata, { Config }, event, conversionType) => const { properties, timestamp, originalTimestamp } = message; const filteredCustomerId = removeHyphens(customerId); + const eventLevelConsentsData = getConsentsDataFromIntegrationObj(message); + if (conversionType === 'click') { // click conversion const convertedPayload = getClickConversionPayloadAndEndpoint( message, Config, filteredCustomerId, + eventLevelConsentsData, ); payload = convertedPayload.payload; endpoint = convertedPayload.endpoint; @@ -55,7 +55,7 @@ const getConversions = (message, metadata, { Config }, event, conversionType) => endpoint = STORE_CONVERSION_CONFIG.replace(':customerId', filteredCustomerId); } else { // call conversions - payload = constructPayload(message, trackCallConversionsMapping); + payload = getCallConversionPayload(message, Config, eventLevelConsentsData); endpoint = CALL_CONVERSION.replace(':customerId', filteredCustomerId); } @@ -119,7 +119,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 67c0ef31c8..70b42e2157 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.js @@ -18,6 +18,7 @@ const { isDefinedAndNotNull, getAuthErrCategoryFromStCode, getAccessToken, + getIntegrationsObj, } = require('../../util'); const { SEARCH_STREAM, @@ -27,10 +28,13 @@ const { trackAddStoreAddressConversionsMapping, trackClickConversionsMapping, CLICK_CONVERSION, + trackCallConversionsMapping, + consentConfigMap, } = require('./config'); const { processAxiosResponse } = require('../../../adapters/utils/networkUtils'); const Cache = require('../../util/cache'); const helper = require('./helper'); +const { finaliseConsent } = require('../../util/googleUtils'); const conversionActionIdCache = new Cache(CONVERSION_ACTION_ID_CACHE_TTL); @@ -221,6 +225,17 @@ 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( + consentConfigMap, + eventLevelConsentsData, + Config, + ); + return payload; +}; + /** * This Function create the add conversion payload * and returns the payload @@ -277,6 +292,10 @@ const getAddConversionPayload = (message, Config) => { set(payload, 'operations.create.userIdentifiers[0]', {}); } } + // add consent support for store conversions. Note: No event level consent supported. + 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; }; @@ -292,7 +311,12 @@ const getStoreConversionPayload = (message, Config, event) => { 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 } = @@ -364,9 +388,19 @@ const getClickConversionPayloadAndEndpoint = (message, Config, filteredCustomerI if (!properties.conversionEnvironment && conversionEnvironment !== 'none') { set(payload, 'conversions[0].conversionEnvironment', conversionEnvironment); } + + // add consent support for click conversions + 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 }; }; +const getConsentsDataFromIntegrationObj = (message) => { + const integrationObj = getIntegrationsObj(message, 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS') || {}; + return integrationObj?.consents || {}; +}; + module.exports = { validateDestinationConfig, generateItemListFromProducts, @@ -377,4 +411,6 @@ module.exports = { buildAndGetAddress, getClickConversionPayloadAndEndpoint, getExisitingUserIdentifier, + getConsentsDataFromIntegrationObj, + getCallConversionPayload, }; 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..2d1863413c 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,8 @@ const { getClickConversionPayloadAndEndpoint, buildAndGetAddress, getExisitingUserIdentifier, + getConsentsDataFromIntegrationObj, + getCallConversionPayload, } = require('./utils'); const getTestMessage = () => { @@ -161,12 +163,16 @@ 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/v16/customers/9625812972:uploadClickConversions', payload: { conversions: [ { conversionDateTime: '2022-01-01 12:32:45-08:00', conversionEnvironment: 'WEB', + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, userIdentifiers: [ { hashedEmail: 'fa922cb41ff930664d4c9ced3c472ce7ecf29a0f8248b7018456e990177fff75', @@ -187,11 +193,15 @@ 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/v16/customers/9625812972:uploadClickConversions', payload: { conversions: [ { conversionDateTime: '2022-01-01 12:32:45-08:00', + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, conversionEnvironment: 'WEB', userIdentifiers: [ { @@ -215,7 +225,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/v16/customers/9625812972:uploadClickConversions', payload: { conversions: [ { @@ -237,7 +247,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('finaliseConsent', () => { let fittingPayload = { ...getTestMessage() }; fittingPayload.properties.products = [ { @@ -251,13 +261,17 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => { }, ]; let expectedOutput = { - endpoint: 'https://googleads.googleapis.com/v14/customers/9625812972:uploadClickConversions', + endpoint: 'https://googleads.googleapis.com/v16/customers/9625812972:uploadClickConversions', payload: { conversions: [ { 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', @@ -273,3 +287,118 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => { ); }); }); + +describe('getConsentsDataFromIntegrationObj', () => { + it('should return an empty object when conversionType is "store"', () => { + const message = {}; + const result = getConsentsDataFromIntegrationObj(message); + 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', + }); + }); +}); + +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', + }, + ], + }); + }); +}); 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 9ab415346a..b0dfaa0c35 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, @@ -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 = populateConsentForGoogleDestinations(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 de73b0fb05..c153731e73 100644 --- a/src/v0/util/googleUtils/index.js +++ b/src/v0/util/googleUtils/index.js @@ -1,36 +1,91 @@ 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. + * 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/v15/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 populateConsentForGoogleDestinations = (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[consentType] = UNSPECIFIED_CONSENT; } - } else { - consent.adUserData = 'UNSPECIFIED'; + }); + + return consent; +}; + +/** + * Generates the final consent object based on the provided consent configuration map, event-level consent, and destination configuration. + * + * @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 + * 2) For Call conversion : + * 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/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; + }, {}); + + // 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); } - if (config?.personalizationConsent) { - if (GOOGLE_ALLOWED_CONSENT_STATUS.includes(config.personalizationConsent)) { - consent.adPersonalization = config.personalizationConsent; + const consentObj = {}; + + // Iterate through each key in consentConfigMap to determine the final consent + Object.keys(consentConfigMap).forEach((configKey) => { + const consentKey = consentConfigMap[configKey]; // e.g., 'adUserData' + + // 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 { - consent.adPersonalization = 'UNKNOWN'; + // Fallback to default consent block + consentObj[consentKey] = defaultConsentBlock[consentKey]; } - } else { - consent.adPersonalization = 'UNSPECIFIED'; - } - return consent; + }); + + return consentObj; }; -module.exports = { populateConsentForGoogleDestinations }; +module.exports = { + populateConsentFromConfig, + UNSPECIFIED_CONSENT, + UNKNOWN_CONSENT, + GOOGLE_ALLOWED_CONSENT_STATUS, + finaliseConsent, +}; diff --git a/src/v0/util/googleUtils/index.test.js b/src/v0/util/googleUtils/index.test.js index 9d1aa5e51a..28e0fa9ac8 100644 --- a/src/v0/util/googleUtils/index.test.js +++ b/src/v0/util/googleUtils/index.test.js @@ -1,8 +1,12 @@ -const { populateConsentForGoogleDestinations } = require('./index'); +const { populateConsentFromConfig, finaliseConsent } = require('./index'); -describe('unit test for populateConsentForGoogleDestinations', () => { +describe('unit test for populateConsentFromConfig', () => { + const consentConfigMap = { + personalizationConsent: 'adPersonalization', + userDataConsent: 'adUserData', + }; it('should return an UNSPECIFIED object when no properties are provided', () => { - const result = populateConsentForGoogleDestinations({}); + const result = populateConsentFromConfig({}, consentConfigMap); expect(result).toEqual({ adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED', @@ -11,18 +15,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, 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 = populateConsentForGoogleDestinations(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 = populateConsentForGoogleDestinations(); + const result = populateConsentFromConfig(undefined, consentConfigMap); expect(result).toEqual({ adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED', @@ -30,7 +34,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, consentConfigMap); expect(result).toEqual({ adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED', @@ -38,7 +42,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({}, consentConfigMap); expect(result).toEqual({ adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED', @@ -46,13 +50,196 @@ describe('unit test for populateConsentForGoogleDestinations', () => { }); it('should return UNKNOWN when properties parameter contains adUserData and adPersonalization with non-allowed values', () => { - const result = populateConsentForGoogleDestinations({ - userDataConsent: 'RANDOM', - personalizationConsent: 'RANDOM', + const result = populateConsentFromConfig( + { + userDataConsent: 'RANDOM', + personalizationConsent: 'RANDOM', + }, + consentConfigMap, + ); + expect(result).toEqual({ + adPersonalization: 'UNKNOWN', + adUserData: 'UNKNOWN', + }); + }); +}); + +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 = { + adUserData: 'GRANTED', + adPersonalization: 'DENIED', + }; + const destConfig = { + userDataConsent: 'UNKNOWN', + personalizationConsent: 'GRANTED', + }; + + const result = finaliseConsent(consentConfigMap, eventLevelConsent, destConfig); + + expect(result).toEqual({ + adUserData: 'GRANTED', + adPersonalization: 'DENIED', + }); + }); + + 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(consentConfigMap, 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 result = finaliseConsent(consentConfigMap, eventLevelConsent, undefined); + + // Assert + expect(result).toEqual({ + adUserData: 'GRANTED', + adPersonalization: 'DENIED', + }); + }); + + it('should return UNSPECIFIED_CONSENT when both destConfig and event level consent is not provided', () => { + const result = finaliseConsent(consentConfigMap, undefined, undefined); + + // Assert + expect(result).toEqual({ + adUserData: 'UNSPECIFIED', + adPersonalization: 'UNSPECIFIED', + }); + }); + + it('should return UNKWOWN_CONSENT when destConfig is provided with wrong consent value', () => { + const destConfig = { + userDataConsent: 'UNKNOWN', + personalizationConsent: 'WRONG CONSENT', + }; + + const result = finaliseConsent(consentConfigMap, undefined, destConfig); + + expect(result).toEqual({ + adUserData: 'UNKNOWN', + adPersonalization: 'UNKNOWN', }); + }); + + it('should return UNKWOWN_CONSENT when destConfig is provided with wrong consent value', () => { + const destConfig = { + userDataConsent: 'UNKNOWN', + personalizationConsent: 'WRONG CONSENT', + }; + + 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', + }); + }); }); diff --git a/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/business.ts b/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/business.ts index fbeaf7f250..87aafea0af 100644 --- a/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/business.ts +++ b/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/business.ts @@ -235,7 +235,7 @@ export const testScenariosForV0API = [ params: params.param1, JSON: invalidArgumentRequestPayload, endpoint: - 'https://googleads.googleapis.com/v14/customers/11122233331/offlineUserDataJobs', + 'https://googleads.googleapis.com/v16/customers/11122233331/offlineUserDataJobs', }), method: 'POST', }, @@ -253,7 +253,7 @@ export const testScenariosForV0API = [ code: 400, details: [ { - '@type': 'type.googleapis.com/google.ads.googleads.v14.errors.GoogleAdsFailure', + '@type': 'type.googleapis.com/google.ads.googleads.v16.errors.GoogleAdsFailure', errors: [ { errorCode: { @@ -309,7 +309,7 @@ export const testScenariosForV0API = [ headers: headers.header1, params: params.param1, JSON: validRequestPayload1, - endpoint: 'https://googleads.googleapis.com/v14/customers/1112223333/offlineUserDataJobs', + endpoint: 'https://googleads.googleapis.com/v16/customers/1112223333/offlineUserDataJobs', }), method: 'POST', }, @@ -350,7 +350,7 @@ export const testScenariosForV0API = [ params: params.param2, JSON: validRequestPayload2, endpoint: - 'https://googleads.googleapis.com/v14/customers/1234567891:uploadClickConversions', + 'https://googleads.googleapis.com/v16/customers/1234567891:uploadClickConversions', }), method: 'POST', }, @@ -400,7 +400,7 @@ export const testScenariosForV0API = [ params: params.param3, JSON: validRequestPayload2, endpoint: - 'https://googleads.googleapis.com/v14/customers/1234567891:uploadClickConversions', + 'https://googleads.googleapis.com/v16/customers/1234567891:uploadClickConversions', }), method: 'POST', }, @@ -453,7 +453,7 @@ export const testScenariosForV1API = [ params: params.param1, JSON: invalidArgumentRequestPayload, endpoint: - 'https://googleads.googleapis.com/v14/customers/11122233331/offlineUserDataJobs', + 'https://googleads.googleapis.com/v16/customers/11122233331/offlineUserDataJobs', }, metadataArray, ), @@ -500,7 +500,7 @@ export const testScenariosForV1API = [ params: params.param1, JSON: validRequestPayload1, endpoint: - 'https://googleads.googleapis.com/v14/customers/1112223333/offlineUserDataJobs', + 'https://googleads.googleapis.com/v16/customers/1112223333/offlineUserDataJobs', }, metadataArray, ), @@ -545,7 +545,7 @@ export const testScenariosForV1API = [ params: params.param2, JSON: validRequestPayload2, endpoint: - 'https://googleads.googleapis.com/v14/customers/1234567891:uploadClickConversions', + 'https://googleads.googleapis.com/v16/customers/1234567891:uploadClickConversions', }, metadataArray, ), @@ -591,7 +591,7 @@ export const testScenariosForV1API = [ params: params.param3, JSON: validRequestPayload2, endpoint: - 'https://googleads.googleapis.com/v14/customers/1234567891:uploadClickConversions', + 'https://googleads.googleapis.com/v16/customers/1234567891:uploadClickConversions', }, metadataArray, ), @@ -637,7 +637,7 @@ export const testScenariosForV1API = [ params: params.param4, JSON: notAllowedToAccessFeatureRequestPayload, endpoint: - 'https://googleads.googleapis.com/v14/customers/1234567893:uploadClickConversions', + 'https://googleads.googleapis.com/v16/customers/1234567893:uploadClickConversions', }, metadataArray, ), diff --git a/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/oauth.ts b/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/oauth.ts index 15a150d0e5..e14e4109f0 100644 --- a/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/oauth.ts +++ b/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/oauth.ts @@ -95,7 +95,7 @@ export const v0oauthScenarios = [ request: { body: generateProxyV0Payload({ ...commonRequestParameters, - endpoint: 'https://googleads.googleapis.com/v14/customers/customerid/offlineUserDataJobs', + endpoint: 'https://googleads.googleapis.com/v16/customers/customerid/offlineUserDataJobs', }), method: 'POST', }, @@ -138,7 +138,7 @@ export const v0oauthScenarios = [ request: { body: generateProxyV0Payload({ ...commonRequestParameters, - endpoint: 'https://googleads.googleapis.com/v14/customers/1234/offlineUserDataJobs', + endpoint: 'https://googleads.googleapis.com/v16/customers/1234/offlineUserDataJobs', }), method: 'POST', }, @@ -184,7 +184,7 @@ export const v1oauthScenarios = [ { ...commonRequestParameters, endpoint: - 'https://googleads.googleapis.com/v14/customers/customerid/offlineUserDataJobs', + 'https://googleads.googleapis.com/v16/customers/customerid/offlineUserDataJobs', }, metadataArray, ), @@ -230,7 +230,7 @@ export const v1oauthScenarios = [ body: generateProxyV1Payload( { ...commonRequestParameters, - endpoint: 'https://googleads.googleapis.com/v14/customers/1234/offlineUserDataJobs', + endpoint: 'https://googleads.googleapis.com/v16/customers/1234/offlineUserDataJobs', }, metadataArray, ), diff --git a/test/integrations/destinations/google_adwords_offline_conversions/network.ts b/test/integrations/destinations/google_adwords_offline_conversions/network.ts index 7dc7f97933..4dad9e0d1b 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/v16/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/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/v14/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.v14.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/v14/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/v14/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/v14/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: { @@ -238,7 +238,7 @@ export const networkCallsData = [ description: 'Mock response from destination depicting a request with invalid authentication credentials', httpReq: { - url: 'https://googleads.googleapis.com/v14/customers/customerid/offlineUserDataJobs:create', + url: 'https://googleads.googleapis.com/v16/customers/customerid/offlineUserDataJobs:create', data: { job: { storeSalesMetadata: { @@ -274,7 +274,7 @@ export const networkCallsData = [ description: 'Mock response from destination depicting a request with invalid authentication scopes', httpReq: { - url: 'https://googleads.googleapis.com/v14/customers/1234/offlineUserDataJobs:create', + url: 'https://googleads.googleapis.com/v16/customers/1234/offlineUserDataJobs:create', data: { job: { storeSalesMetadata: { @@ -307,7 +307,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v14/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'`, }, @@ -335,7 +335,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v14/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'", @@ -368,7 +368,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v14/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', @@ -402,7 +402,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v14/customers/1234567891:uploadClickConversions', + url: 'https://googleads.googleapis.com/v16/customers/1234567891:uploadClickConversions', data: { conversions: [ { @@ -469,7 +469,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v14/customers/1234567891:uploadClickConversions', + url: 'https://googleads.googleapis.com/v16/customers/1234567891:uploadClickConversions', data: { conversions: [ { @@ -530,7 +530,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v14/customers/1234567893/googleAds:searchStream', + url: 'https://googleads.googleapis.com/v16/customers/1234567893/googleAds:searchStream', data: { query: "SELECT conversion_action.id FROM conversion_action WHERE conversion_action.name = 'Sign-up - click'", @@ -563,7 +563,7 @@ export const networkCallsData = [ }, { httpReq: { - url: 'https://googleads.googleapis.com/v14/customers/1234567893:uploadClickConversions', + url: 'https://googleads.googleapis.com/v16/customers/1234567893:uploadClickConversions', data: { conversions: [ { @@ -615,7 +615,7 @@ export const networkCallsData = [ 'Customer is not allowlisted for accessing this feature., at conversions[0].conversion_environment', details: [ { - '@type': 'type.googleapis.com/google.ads.googleads.v14.errors.GoogleAdsFailure', + '@type': 'type.googleapis.com/google.ads.googleads.v16.errors.GoogleAdsFailure', errors: [ { errorCode: { 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..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/v14/customers/9625812972:uploadClickConversions', + 'https://googleads.googleapis.com/v16/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/v16/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/v16/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/v16/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/v16/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/v16/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/v16/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/v16/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/v16/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/v16/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/v16/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/v16/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/v16/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -4077,6 +4125,10 @@ export const data = [ addConversionPayload: { operations: { create: { + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: '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/v16/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -4409,6 +4461,10 @@ export const data = [ addConversionPayload: { operations: { create: { + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: '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/v16/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -4604,6 +4660,10 @@ export const data = [ addConversionPayload: { operations: { create: { + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: '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/v16/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -4802,6 +4862,10 @@ export const data = [ addConversionPayload: { operations: { create: { + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: '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/v16/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -4962,6 +5026,10 @@ export const data = [ addConversionPayload: { operations: { create: { + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: '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/v16/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -5118,6 +5186,10 @@ export const data = [ addConversionPayload: { operations: { create: { + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, transaction_attribute: { store_attribute: { store_code: 'store code', @@ -5245,7 +5317,323 @@ export const data = [ type: 'REST', method: 'POST', endpoint: - 'https://googleads.googleapis.com/v14/customers/1112223333/offlineUserDataJobs', + 'https://googleads.googleapis.com/v16/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: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, + 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 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/v16/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/v16/customers/1112223333/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -5272,6 +5660,10 @@ export const data = [ addConversionPayload: { operations: { create: { + consent: { + adPersonalization: 'DENIED', + adUserData: 'GRANTED', + }, 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..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/v14/customers/7693729833/offlineUserDataJobs', + 'https://googleads.googleapis.com/v16/customers/7693729833/offlineUserDataJobs', headers: { Authorization: 'Bearer abcd1234', 'Content-Type': 'application/json', @@ -511,6 +511,10 @@ export const data = [ operations: [ { create: { + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, transaction_attribute: { store_attribute: { store_code: 'store code' }, transaction_amount_micros: '100000000', @@ -528,6 +532,10 @@ export const data = [ }, { create: { + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: '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/v16/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/v16/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/v16/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', From 345c87d7c530c621ae3fd6c504d64e5a14e31f22 Mon Sep 17 00:00:00 2001 From: Sankeerth Date: Tue, 26 Mar 2024 10:18:27 +0530 Subject: [PATCH 2/2] fix: shopify invalid_event metric prometheus label (#3200) --- src/v0/sources/shopify/transform.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v0/sources/shopify/transform.js b/src/v0/sources/shopify/transform.js index 4f09984054..93e3ed0c72 100644 --- a/src/v0/sources/shopify/transform.js +++ b/src/v0/sources/shopify/transform.js @@ -154,7 +154,7 @@ const processEvent = async (inputEvent, metricMetadata) => { default: if (!SUPPORTED_TRACK_EVENTS.includes(shopifyTopic)) { stats.increment('invalid_shopify_event', { - event: shopifyTopic, + writeKey: metricMetadata.writeKey, source: metricMetadata.source, shopifyTopic: metricMetadata.shopifyTopic, });