Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: consent field support for gaoc for API v15 and upgrade the api version from v14 to v16 #3121

Merged
merged 27 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
01167f2
feat: consent field support for GAOC
shrouti1507 Feb 23, 2024
31d3691
fix: clean up of commented code
shrouti1507 Feb 23, 2024
f23127a
fix: clean up the old implementation of GARL to avoid code duplication
shrouti1507 Feb 26, 2024
a0a6885
fix: small edit
shrouti1507 Feb 26, 2024
602f57d
chore: conflict resolved
shrouti1507 Mar 7, 2024
bce1d62
chore: store sales updated, review comments addressed
shrouti1507 Mar 7, 2024
b25d81d
Merge branch 'develop' into feat.consentMode.GAOC
shrouti1507 Mar 7, 2024
3d92ad0
fix: relocating the util and test cases
shrouti1507 Mar 11, 2024
e60117b
fix: refactoring the util
shrouti1507 Mar 11, 2024
f6a30da
Merge branch 'develop' into feat.consentMode.GAOC
shrouti1507 Mar 11, 2024
3d4634e
fix: updating API version to v16
shrouti1507 Mar 11, 2024
e192352
fix: review comments address
shrouti1507 Mar 11, 2024
5d961e8
Update src/v0/destinations/google_adwords_offline_conversions/transfo…
shrouti1507 Mar 11, 2024
a027389
Update src/v0/destinations/google_adwords_offline_conversions/transfo…
shrouti1507 Mar 11, 2024
af7a820
fix: review comments address
shrouti1507 Mar 11, 2024
2be5001
Merge branch 'develop' into feat.consentMode.GAOC
shrouti1507 Mar 11, 2024
76a68ab
Merge branch 'develop' into feat.consentMode.GAOC
shrouti1507 Mar 12, 2024
0f13371
fix: review comments addressed
shrouti1507 Mar 12, 2024
9bae3d4
fix: review comments addressed
shrouti1507 Mar 12, 2024
ad6aea0
fix: utils added
shrouti1507 Mar 12, 2024
3094878
Merge branch 'develop' into feat.consentMode.GAOC
shrouti1507 Mar 12, 2024
05cc867
fix: util function made more pluggable
shrouti1507 Mar 12, 2024
1d99743
Merge branch 'feat.consentMode.GAOC' of github.com:rudderlabs/rudder-…
shrouti1507 Mar 12, 2024
e05e730
fix: conflict resolve
shrouti1507 Mar 13, 2024
b2f4830
Merge branch 'develop' into feat.consentMode.GAOC
shrouti1507 Mar 18, 2024
f3e48c6
chore: add event validation for movable ink destination (#3190)
Gauravudia Mar 18, 2024
b013770
Merge branch 'develop' into feat.consentMode.GAOC
krishna2020 Mar 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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;
}

shrouti1507 marked this conversation as resolved.
Show resolved Hide resolved
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
Loading