Skip to content

Commit

Permalink
feat: consent field support for gaoc for API v15 and upgrade the api …
Browse files Browse the repository at this point in the history
…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 <[email protected]>

* Update src/v0/destinations/google_adwords_offline_conversions/transform.js

Co-authored-by: Sai Kumar Battinoju <[email protected]>

* 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 <[email protected]>
Co-authored-by: Gauravudia <[email protected]>
Co-authored-by: Krishna Chaitanya <[email protected]>
  • Loading branch information
4 people authored Mar 22, 2024
1 parent 906ea3f commit 2aac2a6
Show file tree
Hide file tree
Showing 14 changed files with 946 additions and 103 deletions.
12 changes: 12 additions & 0 deletions src/constants/destinationCanonicalNames.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
};

Expand Down
Original file line number Diff line number Diff line change
@@ -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`;

Expand Down Expand Up @@ -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],
Expand All @@ -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,
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,21 @@ const { InstrumentationError, ConfigurationError } = require('@rudderstack/integ
const { EventType } = require('../../../constants');
const {
getHashFromArrayWithDuplicate,
constructPayload,
removeHyphens,
getHashFromArray,
handleRtTfSingleEventError,
defaultBatchRequestConfig,
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');

Expand All @@ -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;
Expand All @@ -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);
}

Expand Down Expand Up @@ -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.');
}
Expand Down
38 changes: 37 additions & 1 deletion src/v0/destinations/google_adwords_offline_conversions/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const {
isDefinedAndNotNull,
getAuthErrCategoryFromStCode,
getAccessToken,
getIntegrationsObj,
} = require('../../util');
const {
SEARCH_STREAM,
Expand All @@ -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);

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
};

Expand All @@ -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 } =
Expand Down Expand Up @@ -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,
Expand All @@ -377,4 +411,6 @@ module.exports = {
buildAndGetAddress,
getClickConversionPayloadAndEndpoint,
getExisitingUserIdentifier,
getConsentsDataFromIntegrationObj,
getCallConversionPayload,
};
139 changes: 134 additions & 5 deletions src/v0/destinations/google_adwords_offline_conversions/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ const {
getClickConversionPayloadAndEndpoint,
buildAndGetAddress,
getExisitingUserIdentifier,
getConsentsDataFromIntegrationObj,
getCallConversionPayload,
} = require('./utils');

const getTestMessage = () => {
Expand Down Expand Up @@ -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',
Expand All @@ -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: [
{
Expand All @@ -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: [
{
Expand All @@ -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 = [
{
Expand All @@ -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',
Expand All @@ -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',
},
],
});
});
});
Loading

0 comments on commit 2aac2a6

Please sign in to comment.