From 2bf9a8a1f5531752b7b999c09780d8a0522da958 Mon Sep 17 00:00:00 2001 From: Manish Kumar Date: Tue, 15 Oct 2024 17:03:05 +0530 Subject: [PATCH 1/2] fix: str.replace is not a function --- .../transform.js | 6 +- src/v0/util/index.js | 5 +- src/v0/util/index.test.js | 16 +++ .../router/data.ts | 131 ++++++++++++++++++ 4 files changed, 156 insertions(+), 2 deletions(-) diff --git a/src/v0/destinations/google_adwords_enhanced_conversions/transform.js b/src/v0/destinations/google_adwords_enhanced_conversions/transform.js index 55d0c16c8c..0b6cfc5a4e 100644 --- a/src/v0/destinations/google_adwords_enhanced_conversions/transform.js +++ b/src/v0/destinations/google_adwords_enhanced_conversions/transform.js @@ -3,6 +3,7 @@ const get = require('get-value'); const { cloneDeep } = require('lodash'); const { InstrumentationError, ConfigurationError } = require('@rudderstack/integrations-lib'); +const isString = require('lodash/isString'); const { constructPayload, defaultRequestConfig, @@ -58,7 +59,10 @@ const processTrackEvent = async (metadata, message, destination) => { let flag = 0; const { Config } = destination; const { event } = message; - const { listOfConversions } = Config; + const { customerId, loginCustomerId, listOfConversions } = Config; + if (!isString(customerId) || !isString(loginCustomerId)) { + throw new InstrumentationError('customerId and loginCustomerId should be a string'); + } if (listOfConversions.some((i) => i.conversions === event)) { flag = 1; } diff --git a/src/v0/util/index.js b/src/v0/util/index.js index ca81262f88..2228fb7e43 100644 --- a/src/v0/util/index.js +++ b/src/v0/util/index.js @@ -1622,7 +1622,7 @@ function isHttpStatusRetryable(status) { function generateUUID() { return crypto.randomUUID({ disableEntropyCache: true, - }); /* using disableEntropyCache as true to not cache the generated uuids. + }); /* using disableEntropyCache as true to not cache the generated uuids. For more Info https://nodejs.org/api/crypto.html#cryptorandomuuidoptions:~:text=options%20%3CObject%3E-,disableEntropyCache,-%3Cboolean%3E%20By */ } @@ -1646,6 +1646,9 @@ function isAppleFamily(platform) { } function removeHyphens(str) { + if (typeof str !== 'string') { + return str; + } return str.replace(/-/g, ''); } diff --git a/src/v0/util/index.test.js b/src/v0/util/index.test.js index 6bf689eca7..eaf8b79d54 100644 --- a/src/v0/util/index.test.js +++ b/src/v0/util/index.test.js @@ -10,6 +10,7 @@ const { validateEventAndLowerCaseConversion, groupRouterTransformEvents, isAxiosError, + removeHyphens, } = require('./index'); const exp = require('constants'); @@ -968,3 +969,18 @@ describe('isAxiosError', () => { expect(isAxiosError(error)).toBe(false); }); }); + +describe('removeHyphens', () => { + const data = [ + { input: 'hello-w--orld', expected: 'helloworld' }, + { input: '', expected: '' }, + { input: null, expected: null }, + { input: undefined, expected: undefined }, + { input: 12345, expected: 12345 }, + ]; + it('should remove hyphens from string else return the input as it is', () => { + data.forEach(({ input, expected }) => { + expect(removeHyphens(input)).toBe(expected); + }); + }); +}); diff --git a/test/integrations/destinations/google_adwords_enhanced_conversions/router/data.ts b/test/integrations/destinations/google_adwords_enhanced_conversions/router/data.ts index 1d77b5d774..937421e2c0 100644 --- a/test/integrations/destinations/google_adwords_enhanced_conversions/router/data.ts +++ b/test/integrations/destinations/google_adwords_enhanced_conversions/router/data.ts @@ -225,6 +225,100 @@ const events = [ sentAt: '2019-10-14T11:15:53.296Z', }, }, + { + metadata: { + secret: { + access_token: 'abcd1234', + refresh_token: 'efgh5678', + developer_token: 'ijkl91011', + }, + jobId: 4, + userId: 'u1', + }, + destination: { + Config: { + rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', + customerId: '{{event.context.customerID || "" }}', + subAccount: true, + loginCustomerId: '{{event.context.subaccountID || "" }}', + listOfConversions: [{ conversions: 'Page View' }, { conversions: 'Product Added' }], + authStatus: 'active', + }, + }, + message: { + channel: 'web', + context: { + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.0', + }, + traits: { + phone: '912382193', + firstName: 'John', + lastName: 'Gomes', + city: 'London', + state: 'UK', + streetAddress: '71 Cherry Court SOUTHAMPTON SO53 5PD UK', + }, + library: { name: 'RudderLabs JavaScript SDK', version: '1.0.0' }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + locale: 'en-US', + ip: '0.0.0.0', + os: { name: '', version: '' }, + screen: { density: 2 }, + customerID: 1234567890, + subaccountID: 11, + }, + event: 'Page View', + type: 'track', + messageId: '5e10d13a-bf9a-44bf-b884-43a9e591ea71', + originalTimestamp: '2019-10-14T11:15:18.299Z', + anonymousId: '00000000000000000000000000', + userId: '12345', + properties: { + gclid: 'gclid1234', + conversionDateTime: '2022-01-01 12:32:45-08:00', + adjustedValue: '10', + currency: 'INR', + adjustmentDateTime: '2022-01-01 12:32:45-08:00', + partialFailure: true, + campaignId: '1', + templateId: '0', + order_id: 10000, + total: 1000, + products: [ + { + product_id: '507f1f77bcf86cd799439011', + sku: '45790-32', + name: 'Monopoly: 3rd Edition', + price: '19', + position: '1', + category: 'cars', + url: 'https://www.example.com/product/path', + image_url: 'https://www.example.com/product/path.jpg', + quantity: '2', + }, + { + product_id: '507f1f77bcf86cd7994390112', + sku: '45790-322', + name: 'Monopoly: 3rd Edition2', + price: '192', + quantity: 22, + position: '12', + category: 'Cars2', + url: 'https://www.example.com/product/path2', + image_url: 'https://www.example.com/product/path.jpg2', + }, + ], + }, + integrations: { All: true }, + name: 'ApplicationLoaded', + sentAt: '2019-10-14T11:15:53.296Z', + }, + }, ]; const invalidRtTfCases = [ @@ -500,6 +594,43 @@ export const data = [ module: 'destination', }, }, + { + batched: false, + statusCode: 400, + error: 'customerId and loginCustomerId should be a string', + statTags: { + destType: 'GOOGLE_ADWORDS_ENHANCED_CONVERSIONS', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'router', + implementation: 'native', + module: 'destination', + }, + metadata: [ + { + secret: { + access_token: 'abcd1234', + refresh_token: 'efgh5678', + developer_token: 'ijkl91011', + }, + jobId: 4, + userId: 'u1', + }, + ], + destination: { + Config: { + rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', + customerId: 1234567890, + subAccount: true, + loginCustomerId: 11, + listOfConversions: [ + { conversions: 'Page View' }, + { conversions: 'Product Added' }, + ], + authStatus: 'active', + }, + }, + }, ], }, }, From affe437923cc573a82647b7a8706bd15bf0ba8f8 Mon Sep 17 00:00:00 2001 From: Manish Kumar Date: Wed, 16 Oct 2024 11:52:50 +0530 Subject: [PATCH 2/2] fix: resolving comments --- .../transform.js | 26 ++- src/v0/util/index.js | 3 +- .../router/data.ts | 182 +++++++++++++++++- 3 files changed, 198 insertions(+), 13 deletions(-) diff --git a/src/v0/destinations/google_adwords_enhanced_conversions/transform.js b/src/v0/destinations/google_adwords_enhanced_conversions/transform.js index 0b6cfc5a4e..007f16d7f8 100644 --- a/src/v0/destinations/google_adwords_enhanced_conversions/transform.js +++ b/src/v0/destinations/google_adwords_enhanced_conversions/transform.js @@ -1,7 +1,7 @@ /* eslint-disable no-param-reassign */ const get = require('get-value'); -const { cloneDeep } = require('lodash'); +const { cloneDeep, isNumber } = require('lodash'); const { InstrumentationError, ConfigurationError } = require('@rudderstack/integrations-lib'); const isString = require('lodash/isString'); const { @@ -36,7 +36,18 @@ const updateMappingJson = (mapping) => { const responseBuilder = async (metadata, message, { Config }, payload) => { const response = defaultRequestConfig(); const { event } = message; - const filteredCustomerId = removeHyphens(Config.customerId); + const { subAccount } = Config; + let { customerId, loginCustomerId } = Config; + if (isNumber(customerId)) { + customerId = customerId.toString(); + } + if (isNumber(loginCustomerId)) { + loginCustomerId = loginCustomerId.toString(); + } + if (!isString(customerId) || !isString(loginCustomerId)) { + throw new InstrumentationError('customerId and loginCustomerId should be a string or number'); + } + const filteredCustomerId = removeHyphens(customerId); response.endpoint = `${BASE_ENDPOINT}/${filteredCustomerId}:uploadConversionAdjustments`; response.body.JSON = payload; const accessToken = getAccessToken(metadata, 'access_token'); @@ -46,9 +57,9 @@ const responseBuilder = async (metadata, message, { Config }, payload) => { 'developer-token': getValueFromMessage(metadata, 'secret.developer_token'), }; response.params = { event, customerId: filteredCustomerId }; - if (Config.subAccount) - if (Config.loginCustomerId) { - const filteredLoginCustomerId = removeHyphens(Config.loginCustomerId); + if (subAccount) + if (loginCustomerId) { + const filteredLoginCustomerId = removeHyphens(loginCustomerId); response.headers['login-customer-id'] = filteredLoginCustomerId; } else throw new ConfigurationError(`LoginCustomerId is required as subAccount is true.`); @@ -59,10 +70,7 @@ const processTrackEvent = async (metadata, message, destination) => { let flag = 0; const { Config } = destination; const { event } = message; - const { customerId, loginCustomerId, listOfConversions } = Config; - if (!isString(customerId) || !isString(loginCustomerId)) { - throw new InstrumentationError('customerId and loginCustomerId should be a string'); - } + const { listOfConversions } = Config; if (listOfConversions.some((i) => i.conversions === event)) { flag = 1; } diff --git a/src/v0/util/index.js b/src/v0/util/index.js index 2228fb7e43..f034ab802b 100644 --- a/src/v0/util/index.js +++ b/src/v0/util/index.js @@ -26,6 +26,7 @@ const { } = require('@rudderstack/integrations-lib'); const { JsonTemplateEngine, PathType } = require('@rudderstack/json-template-engine'); +const isString = require('lodash/isString'); const logger = require('../../logger'); const stats = require('../../util/stats'); const { DestCanonicalNames, DestHandlerMap } = require('../../constants/destinationCanonicalNames'); @@ -1646,7 +1647,7 @@ function isAppleFamily(platform) { } function removeHyphens(str) { - if (typeof str !== 'string') { + if (!isString(str)) { return str; } return str.replace(/-/g, ''); diff --git a/test/integrations/destinations/google_adwords_enhanced_conversions/router/data.ts b/test/integrations/destinations/google_adwords_enhanced_conversions/router/data.ts index 937421e2c0..89ce06818b 100644 --- a/test/integrations/destinations/google_adwords_enhanced_conversions/router/data.ts +++ b/test/integrations/destinations/google_adwords_enhanced_conversions/router/data.ts @@ -319,6 +319,100 @@ const events = [ sentAt: '2019-10-14T11:15:53.296Z', }, }, + { + metadata: { + secret: { + access_token: 'abcd1234', + refresh_token: 'efgh5678', + developer_token: 'ijkl91011', + }, + jobId: 5, + userId: 'u1', + }, + destination: { + Config: { + rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', + customerId: '{{event.context.customerID || "" }}', + subAccount: true, + loginCustomerId: '{{event.context.subaccountID || "" }}', + listOfConversions: [{ conversions: 'Page View' }, { conversions: 'Product Added' }], + authStatus: 'active', + }, + }, + message: { + channel: 'web', + context: { + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.0', + }, + traits: { + phone: '912382193', + firstName: 'John', + lastName: 'Gomes', + city: 'London', + state: 'UK', + streetAddress: '71 Cherry Court SOUTHAMPTON SO53 5PD UK', + }, + library: { name: 'RudderLabs JavaScript SDK', version: '1.0.0' }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + locale: 'en-US', + ip: '0.0.0.0', + os: { name: '', version: '' }, + screen: { density: 2 }, + customerID: {}, + subaccountID: 11, + }, + event: 'Page View', + type: 'track', + messageId: '5e10d13a-bf9a-44bf-b884-43a9e591ea71', + originalTimestamp: '2019-10-14T11:15:18.299Z', + anonymousId: '00000000000000000000000000', + userId: '12345', + properties: { + gclid: 'gclid1234', + conversionDateTime: '2022-01-01 12:32:45-08:00', + adjustedValue: '10', + currency: 'INR', + adjustmentDateTime: '2022-01-01 12:32:45-08:00', + partialFailure: true, + campaignId: '1', + templateId: '0', + order_id: 10000, + total: 1000, + products: [ + { + product_id: '507f1f77bcf86cd799439011', + sku: '45790-32', + name: 'Monopoly: 3rd Edition', + price: '19', + position: '1', + category: 'cars', + url: 'https://www.example.com/product/path', + image_url: 'https://www.example.com/product/path.jpg', + quantity: '2', + }, + { + product_id: '507f1f77bcf86cd7994390112', + sku: '45790-322', + name: 'Monopoly: 3rd Edition2', + price: '192', + quantity: 22, + position: '12', + category: 'Cars2', + url: 'https://www.example.com/product/path2', + image_url: 'https://www.example.com/product/path.jpg2', + }, + ], + }, + integrations: { All: true }, + name: 'ApplicationLoaded', + sentAt: '2019-10-14T11:15:53.296Z', + }, + }, ]; const invalidRtTfCases = [ @@ -594,10 +688,92 @@ export const data = [ module: 'destination', }, }, + { + batchedRequest: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: `https://googleads.googleapis.com/${API_VERSION}/customers/1234567890:uploadConversionAdjustments`, + headers: { + Authorization: 'Bearer abcd1234', + 'Content-Type': 'application/json', + 'developer-token': 'ijkl91011', + 'login-customer-id': '11', + }, + params: { event: 'Page View', customerId: '1234567890' }, + body: { + JSON: { + partialFailure: true, + conversionAdjustments: [ + { + gclidDateTimePair: { + gclid: 'gclid1234', + conversionDateTime: '2022-01-01 12:32:45-08:00', + }, + restatementValue: { adjustedValue: 10, currencyCode: 'INR' }, + orderId: '10000', + adjustmentDateTime: '2022-01-01 12:32:45-08:00', + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + userIdentifiers: [ + { + hashedPhoneNumber: + '04387707e6cbed8c4538c81cc570ed9252d579469f36c273839b26d784e4bdbe', + }, + { + addressInfo: { + hashedFirstName: + 'a8cfcd74832004951b4408cdb0a5dbcd8c7e52d43f7fe244bf720582e05241da', + hashedLastName: + '1c574b17eefa532b6d61c963550a82d2d3dfca4a7fb69e183374cfafd5328ee4', + state: 'UK', + city: 'London', + hashedStreetAddress: + '9a4d2e50828448f137f119a3ebdbbbab8d6731234a67595fdbfeb2a2315dd550', + }, + }, + ], + adjustmentType: 'ENHANCEMENT', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + }, + metadata: [ + { + secret: { + access_token: 'abcd1234', + refresh_token: 'efgh5678', + developer_token: 'ijkl91011', + }, + jobId: 4, + userId: 'u1', + }, + ], + batched: false, + statusCode: 200, + destination: { + Config: { + rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', + customerId: 1234567890, + subAccount: true, + loginCustomerId: 11, + listOfConversions: [ + { conversions: 'Page View' }, + { conversions: 'Product Added' }, + ], + authStatus: 'active', + }, + }, + }, { batched: false, statusCode: 400, - error: 'customerId and loginCustomerId should be a string', + error: 'customerId and loginCustomerId should be a string or number', statTags: { destType: 'GOOGLE_ADWORDS_ENHANCED_CONVERSIONS', errorCategory: 'dataValidation', @@ -613,14 +789,14 @@ export const data = [ refresh_token: 'efgh5678', developer_token: 'ijkl91011', }, - jobId: 4, + jobId: 5, userId: 'u1', }, ], destination: { Config: { rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', - customerId: 1234567890, + customerId: {}, subAccount: true, loginCustomerId: 11, listOfConversions: [