From 9cc72f2288f99ee394977ffeb209faaae657f6d2 Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Fri, 27 Sep 2024 18:55:03 +0530 Subject: [PATCH 1/7] fix: add correct validation for purchase events (#3766) --- src/cdk/v2/destinations/bluecore/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cdk/v2/destinations/bluecore/utils.js b/src/cdk/v2/destinations/bluecore/utils.js index 91eda60d0d..543b6de745 100644 --- a/src/cdk/v2/destinations/bluecore/utils.js +++ b/src/cdk/v2/destinations/bluecore/utils.js @@ -46,12 +46,12 @@ const verifyPayload = (payload, message) => { } break; case 'purchase': - if (!payload?.properties?.order_id) { + if (!isDefinedAndNotNull(payload?.properties?.order_id)) { throw new InstrumentationError( '[Bluecore] property:: order_id is required for purchase event', ); } - if (!payload?.properties?.total) { + if (!isDefinedAndNotNull(payload?.properties?.total)) { throw new InstrumentationError( '[Bluecore] property:: total is required for purchase event', ); From 12996d7a7ce23de7c150c1c1e012d4dda8668977 Mon Sep 17 00:00:00 2001 From: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com> Date: Mon, 30 Sep 2024 01:39:53 +0530 Subject: [PATCH 2/7] feat: add unity source support in Singular (#3634) * feat: add unity source support in Singular * chore: address comment --- src/v0/destinations/singular/config.js | 16 + .../data/SINGULARUnityEventConfig.json | 112 +++++++ .../data/SINGULARUnitySessionConfig.json | 76 +++++ src/v0/destinations/singular/transform.js | 2 +- src/v0/destinations/singular/util.js | 94 +++--- .../destinations/singular/processor/data.ts | 285 ++++++++++++++++++ 6 files changed, 544 insertions(+), 41 deletions(-) create mode 100644 src/v0/destinations/singular/data/SINGULARUnityEventConfig.json create mode 100644 src/v0/destinations/singular/data/SINGULARUnitySessionConfig.json diff --git a/src/v0/destinations/singular/config.js b/src/v0/destinations/singular/config.js index d3fd284963..97824b809b 100644 --- a/src/v0/destinations/singular/config.js +++ b/src/v0/destinations/singular/config.js @@ -13,6 +13,10 @@ const CONFIG_CATEGORIES = { name: 'SINGULARIosSessionConfig', type: 'track', }, + SESSION_UNITY: { + name: 'SINGULARUnitySessionConfig', + type: 'track', + }, EVENT_ANDROID: { name: 'SINGULARAndroidEventConfig', type: 'track', @@ -21,6 +25,10 @@ const CONFIG_CATEGORIES = { name: 'SINGULARIosEventConfig', type: 'track', }, + EVENT_UNITY: { + name: 'SINGULARUnityEventConfig', + type: 'track', + }, PRODUCT_PROPERTY: { name: 'SINGULAREventProductConfig', }, @@ -29,8 +37,15 @@ const CONFIG_CATEGORIES = { const SUPPORTED_PLATFORM = { android: 'ANDROID', ios: 'IOS', + pc: 'unity', + xbox: 'unity', + playstation: 'unity', + nintendo: 'unity', + metaquest: 'unity', }; +const SUPPORTED_UNTIY_SUBPLATFORMS = ['pc', 'xbox', 'playstation', 'nintendo', 'metaquest']; + const SINGULAR_SESSION_ANDROID_EXCLUSION = [ 'referring_application', 'asid', @@ -93,5 +108,6 @@ module.exports = { SINGULAR_EVENT_ANDROID_EXCLUSION, SINGULAR_EVENT_IOS_EXCLUSION, SUPPORTED_PLATFORM, + SUPPORTED_UNTIY_SUBPLATFORMS, BASE_URL, }; diff --git a/src/v0/destinations/singular/data/SINGULARUnityEventConfig.json b/src/v0/destinations/singular/data/SINGULARUnityEventConfig.json new file mode 100644 index 0000000000..97cdfda229 --- /dev/null +++ b/src/v0/destinations/singular/data/SINGULARUnityEventConfig.json @@ -0,0 +1,112 @@ +[ + { + "destKey": "p", + "sourceKeys": "context.os.name", + "required": true + }, + { + "destKey": "i", + "sourceKeys": "context.app.namespace", + "required": true + }, + { + "destKey": "sdid", + "sourceKeys": "context.device.id", + "required": false + }, + { + "destKey": "is_revenue_event", + "sourceKeys": "properties.is_revenue_event", + "required": false + }, + { + "destKey": "n", + "sourceKeys": "event", + "required": true + }, + { + "destKey": "av", + "sourceKeys": "context.app.version", + "required": false + }, + { + "destKey": "ve", + "sourceKeys": "context.os.version", + "required": false + }, + { + "destKey": "os", + "sourceKeys": "properties.os", + "required": true + }, + { + "destKey": "ip", + "sourceKeys": ["context.ip", "request_ip"], + "required": true + }, + { + "destKey": "use_ip", + "sourceKeys": "properties.use_ip", + "required": false + }, + { + "destKey": "install_source", + "sourceKeys": "properties.install_source", + "required": true + }, + { + "destKey": "data_sharing_options", + "sourceKeys": "properties.data_sharing_options", + "required": false + }, + { + "destKey": "amt", + "sourceKeys": [ + "properties.total", + "properties.value", + "properties.revenue", + { + "operation": "multiplication", + "args": [ + { + "sourceKeys": "properties.price" + }, + { + "sourceKeys": "properties.quantity", + "default": 1 + } + ] + } + ], + "required": false + }, + { + "destKey": "cur", + "sourceKeys": "properties.currency", + "required": false + }, + { + "destKey": "ua", + "sourceKeys": "context.userAgent", + "required": false + }, + { + "destKey": "utime", + "sourceKeys": "timestamp", + "sourceFromGenericMap": true, + "required": false, + "metadata": { + "type": "secondTimestamp" + } + }, + { + "destKey": "custom_user_id", + "sourceKeys": "properties.custom_user_id", + "required": false + }, + { + "destKey": "install", + "sourceKeys": "properties.install", + "required": false + } +] diff --git a/src/v0/destinations/singular/data/SINGULARUnitySessionConfig.json b/src/v0/destinations/singular/data/SINGULARUnitySessionConfig.json new file mode 100644 index 0000000000..aca561bc59 --- /dev/null +++ b/src/v0/destinations/singular/data/SINGULARUnitySessionConfig.json @@ -0,0 +1,76 @@ +[ + { + "destKey": "p", + "sourceKeys": "context.os.name", + "required": true + }, + { + "destKey": "i", + "sourceKeys": "context.app.namespace", + "required": true + }, + { + "destKey": "sdid", + "sourceKeys": "context.device.id", + "required": false + }, + { + "destKey": "av", + "sourceKeys": "context.app.version", + "required": false + }, + { + "destKey": "ve", + "sourceKeys": "context.os.version", + "required": false + }, + { + "destKey": "os", + "sourceKeys": "properties.os", + "required": true + }, + { + "destKey": "ip", + "sourceKeys": ["context.ip", "request_ip"], + "required": true + }, + { + "destKey": "use_ip", + "sourceKeys": "properties.use_ip", + "required": false + }, + { + "destKey": "install_source", + "sourceKeys": "properties.install_source", + "required": true + }, + { + "destKey": "ua", + "sourceKeys": "context.userAgent", + "required": false + }, + { + "destKey": "utime", + "sourceKeys": "timestamp", + "sourceFromGenericMap": true, + "required": false, + "metadata": { + "type": "secondTimestamp" + } + }, + { + "destKey": "data_sharing_options", + "sourceKeys": "properties.data_sharing_options", + "required": false + }, + { + "destKey": "custom_user_id", + "sourceKeys": "properties.custom_user_id", + "required": false + }, + { + "destKey": "install", + "sourceKeys": "properties.install", + "required": false + } +] diff --git a/src/v0/destinations/singular/transform.js b/src/v0/destinations/singular/transform.js index ff5d18db9a..ed6757c47b 100644 --- a/src/v0/destinations/singular/transform.js +++ b/src/v0/destinations/singular/transform.js @@ -20,7 +20,7 @@ const responseBuilderSimple = (message, { Config }) => { } const sessionEvent = isSessionEvent(Config, eventName); - const { eventAttributes, payload } = platformWisePayloadGenerator(message, sessionEvent); + const { eventAttributes, payload } = platformWisePayloadGenerator(message, sessionEvent, Config); const endpoint = sessionEvent ? `${BASE_URL}/launch` : `${BASE_URL}/evt`; // If we have an event where we have an array of Products, example Order Completed diff --git a/src/v0/destinations/singular/util.js b/src/v0/destinations/singular/util.js index 4c5aeb8964..61db0472ab 100644 --- a/src/v0/destinations/singular/util.js +++ b/src/v0/destinations/singular/util.js @@ -9,6 +9,7 @@ const { SINGULAR_EVENT_IOS_EXCLUSION, BASE_URL, SUPPORTED_PLATFORM, + SUPPORTED_UNTIY_SUBPLATFORMS, SESSIONEVENTS, } = require('./config'); const { @@ -85,7 +86,7 @@ const isSessionEvent = (Config, eventName) => { * @param {*} sessionEvent * @returns */ -const platformWisePayloadGenerator = (message, sessionEvent) => { +const platformWisePayloadGenerator = (message, sessionEvent, Config) => { let eventAttributes; const clonedMessage = { ...message }; let platform = getValueFromMessage(clonedMessage, 'context.os.name'); @@ -99,55 +100,68 @@ const platformWisePayloadGenerator = (message, sessionEvent) => { platform = 'iOS'; } platform = platform.toLowerCase(); - if (!SUPPORTED_PLATFORM[platform]) { + if (!SUPPORTED_PLATFORM[platform] && !SUPPORTED_UNTIY_SUBPLATFORMS[platform]) { throw new InstrumentationError(`Platform ${platform} is not supported`); } - - const payload = constructPayload( - clonedMessage, - MAPPING_CONFIG[CONFIG_CATEGORIES[`${typeOfEvent}_${SUPPORTED_PLATFORM[platform]}`].name], - ); - - if (!payload) { - throw new TransformationError(`Failed to Create ${platform} ${typeOfEvent} Payload`); - } - if (sessionEvent) { - // context.device.adTrackingEnabled = true implies Singular's do not track (dnt) - // to be 0 and vice-versa. - const adTrackingEnabled = getValueFromMessage( + let payload; + if (SUPPORTED_UNTIY_SUBPLATFORMS.includes(platform)) { + payload = constructPayload( clonedMessage, - 'context.device.adTrackingEnabled', + MAPPING_CONFIG[CONFIG_CATEGORIES[`${typeOfEvent}_UNITY`].name], ); - if (adTrackingEnabled === true) { - payload.dnt = 0; - } else { - payload.dnt = 1; - } - // by default, the value of openuri and install_source should be "", i.e empty string if nothing is passed - payload.openuri = clonedMessage.properties.url || ''; - if (platform === 'android' || platform === 'Android') { - payload.install_source = clonedMessage.properties.referring_application || ''; - } } else { - // Custom Attribues is not supported by session events - eventAttributes = extractExtraFields( + payload = constructPayload( clonedMessage, - exclusionList[`${SUPPORTED_PLATFORM[platform]}_${typeOfEvent}_EXCLUSION_LIST`], + MAPPING_CONFIG[CONFIG_CATEGORIES[`${typeOfEvent}_${SUPPORTED_PLATFORM[platform]}`].name], ); - eventAttributes = removeUndefinedAndNullValues(eventAttributes); + } - // If anyone out of value, revenue, total is set,we will have amt in payload - // and we will consider the event as revenue event. - if (!isDefinedAndNotNull(payload.is_revenue_event) && payload.amt) { - payload.is_revenue_event = true; - } + if (!payload) { + throw new TransformationError(`Failed to Create ${platform} ${typeOfEvent} Payload`); } + if (!SUPPORTED_UNTIY_SUBPLATFORMS.includes(platform)) { + if (sessionEvent) { + // context.device.adTrackingEnabled = true implies Singular's do not track (dnt) + // to be 0 and vice-versa. + const adTrackingEnabled = getValueFromMessage( + clonedMessage, + 'context.device.adTrackingEnabled', + ); + if (adTrackingEnabled === true) { + payload.dnt = 0; + } else { + payload.dnt = 1; + } + // by default, the value of openuri and install_source should be "", i.e empty string if nothing is passed + payload.openuri = clonedMessage.properties.url || ''; + if (platform === 'android' || platform === 'Android') { + payload.install_source = clonedMessage.properties.referring_application || ''; + } + } else { + // Custom Attribues is not supported by session events + eventAttributes = extractExtraFields( + clonedMessage, + exclusionList[`${SUPPORTED_PLATFORM[platform]}_${typeOfEvent}_EXCLUSION_LIST`], + ); + eventAttributes = removeUndefinedAndNullValues(eventAttributes); - // Singular maps Connection Type to either wifi or carrier - if (clonedMessage.context?.network?.wifi) { - payload.c = 'wifi'; - } else { - payload.c = 'carrier'; + // If anyone out of value, revenue, total is set,we will have amt in payload + // and we will consider the event as revenue event. + if (!isDefinedAndNotNull(payload.is_revenue_event) && payload.amt) { + payload.is_revenue_event = true; + } + } + + // Singular maps Connection Type to either wifi or carrier + if (clonedMessage.context?.network?.wifi) { + payload.c = 'wifi'; + } else { + payload.c = 'carrier'; + } + } else if (Config.match_id === 'advertisingId') { + payload.match_id = clonedMessage?.context?.device?.advertisingId; + } else if (message.properties.match_id) { + payload.match_id = message.properties.match_id; } return { payload, eventAttributes }; }; diff --git a/test/integrations/destinations/singular/processor/data.ts b/test/integrations/destinations/singular/processor/data.ts index 22c075fffc..6e749dd0a0 100644 --- a/test/integrations/destinations/singular/processor/data.ts +++ b/test/integrations/destinations/singular/processor/data.ts @@ -1903,4 +1903,289 @@ export const data = [ }, }, }, + { + name: 'singular', + description: '(Unity) Session Event', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: { + Config: { + apiKey: 'dummyApiKey', + sessionEventList: [ + { sessionEventName: 'mysessionevent' }, + { sessionEventName: 'randomuser' }, + { sessionEventName: 'titanium' }, + ], + }, + }, + message: { + type: 'track', + event: 'mysessionevent', + userId: 'ruddersampleX3', + request_ip: '14.5.67.21', + context: { + app: { + build: '1', + name: 'RudderAndroidClient', + namespace: 'com.singular.game', + version: '1.1.5.581823alpha', + }, + device: { + manufacturer: 'Google', + model: 'Android SDK built for x86', + name: 'generic_x86', + type: 'android', + advertisingId: '8ecd7512-2864-440c-93f3-a3cabe62525b', + attStatus: true, + id: '49c2d3a6-326e-4ec5-a16b-0a47e34ed953', + adTrackingEnabled: true, + token: 'testDeviceToken', + }, + library: { name: 'com.rudderstack.android.sdk.core', version: '0.1.4' }, + locale: 'en-US', + network: { carrier: 'Android', bluetooth: false, cellular: true, wifi: true }, + campaign: { + source: 'google', + medium: 'medium', + term: 'keyword', + content: 'some content', + }, + os: { name: 'nintendo', version: '360v2-2024h1' }, + screen: { density: 420, height: 1794, width: 1080 }, + timezone: 'Asia/Mumbai', + userAgent: + 'Mozilla/5.0 (Nintendo Switch; WebApplet) AppleWebKit/613.0 (KHTML, like Gecko) NF/6.0.3.25.0 NintendoBrowser/5.1.0.32061', + }, + properties: { + asid: 'IISqwYJKoZIcNqts0jvcNvPc', + url: 'myapp%3A%2F%2Fhome%2Fpage%3Fqueryparam1%3Dvalue1', + install: 'false', + install_source: 'nintendo', + category: 'Games', + affiliation: 'Apple Store', + receipt_signature: '1234dfghnh', + referring_application: '2134dfg', + os: 'nintendo_switch', + data_sharing_options: '%7B%22limit_data_sharing%22%3Atrue%7D', + }, + timestamp: '2024-06-01T11:26:50.000Z', + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'GET', + endpoint: 'https://s2s.singular.net/api/v1/launch', + headers: {}, + params: { + a: 'dummyApiKey', + av: '1.1.5.581823alpha', + data_sharing_options: '%7B%22limit_data_sharing%22%3Atrue%7D', + i: 'com.singular.game', + install: 'false', + install_source: 'nintendo', + ip: '14.5.67.21', + os: 'nintendo_switch', + p: 'nintendo', + sdid: '49c2d3a6-326e-4ec5-a16b-0a47e34ed953', + ua: 'Mozilla/5.0 (Nintendo Switch; WebApplet) AppleWebKit/613.0 (KHTML, like Gecko) NF/6.0.3.25.0 NintendoBrowser/5.1.0.32061', + utime: 1717241210, + ve: '360v2-2024h1', + }, + body: { JSON: {}, JSON_ARRAY: {}, XML: {}, FORM: {} }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'singular', + description: '(Unity) Custom Event with multiple products', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: { + Config: { + apiKey: 'dummyApiKey', + sessionEventList: [ + { sessionEventName: 'mysessionevent' }, + { sessionEventName: 'randomuser' }, + { sessionEventName: 'titanium' }, + ], + }, + }, + message: { + type: 'track', + event: 'myevent', + userId: 'ruddersampleX4', + request_ip: '14.5.67.21', + context: { + app: { + build: '1', + name: 'RudderAndroidClient', + namespace: 'com.singular.game', + version: '1.1.5.581823alpha', + }, + device: { + manufacturer: 'Google', + model: 'Android SDK built for x86', + name: 'generic_x86', + type: 'android', + advertisingId: '8ecd7512-2864-440c-93f3-a3cabe62525b', + attStatus: true, + id: '49c2d3a6-326e-4ec5-a16b-0a47e34ed953', + adTrackingEnabled: true, + token: 'testDeviceToken', + }, + library: { name: 'com.rudderstack.android.sdk.core', version: '0.1.4' }, + locale: 'en-US', + network: { carrier: 'Android', bluetooth: false, cellular: true, wifi: true }, + campaign: { + source: 'google', + medium: 'medium', + term: 'keyword', + content: 'some content', + }, + os: { name: 'metaquest', version: 'qst2-2023h2' }, + screen: { density: 420, height: 1794, width: 1080 }, + timezone: 'Asia/Mumbai', + userAgent: + 'Mozilla/5.0 (Nintendo Switch; WebApplet) AppleWebKit/613.0 (KHTML, like Gecko) NF/6.0.3.25.0 NintendoBrowser/5.1.0.32061', + }, + properties: { + asid: 'IISqwYJKoZIcNqts0jvcNvPc', + url: 'myapp%3A%2F%2Fhome%2Fpage%3Fqueryparam1%3Dvalue1', + install: 'SM-G935F', + install_source: 'selfdistributed', + category: 'Games', + checkout_id: '12345', + order_id: '1234', + receipt_signature: '1234dfghnh', + referring_application: '2134dfg', + total: 20, + revenue: 15, + shipping: 22, + tax: 1, + discount: 1.5, + coupon: 'ImagePro', + currency: 'USD', + fetch_token: 'dummyFetchToken', + product_id: '123', + is_revenue_event: true, + os: 'metaquest_pro', + products: [ + { + product_id: '789', + sku: 'G-32', + name: 'Monopoly', + price: 14, + quantity: 2, + category: 'Games', + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.jpg', + }, + { sku: 'F-32', name: 'UNO', price: 3.45, quantity: 2, category: 'Games' }, + ], + }, + timestamp: '2021-09-01T15:46:51.000Z', + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'GET', + endpoint: 'https://s2s.singular.net/api/v1/evt', + headers: {}, + params: { + n: 'myevent', + ip: '14.5.67.21', + av: '1.1.5.581823alpha', + is_revenue_event: true, + i: 'com.singular.game', + utime: 1630511211, + cur: 'USD', + amt: 28, + purchase_product_id: '789', + a: 'dummyApiKey', + install_source: 'selfdistributed', + os: 'metaquest_pro', + p: 'metaquest', + sdid: '49c2d3a6-326e-4ec5-a16b-0a47e34ed953', + ua: 'Mozilla/5.0 (Nintendo Switch; WebApplet) AppleWebKit/613.0 (KHTML, like Gecko) NF/6.0.3.25.0 NintendoBrowser/5.1.0.32061', + ve: 'qst2-2023h2', + install: 'SM-G935F', + }, + body: { JSON: {}, JSON_ARRAY: {}, XML: {}, FORM: {} }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + { + output: { + version: '1', + type: 'REST', + method: 'GET', + endpoint: 'https://s2s.singular.net/api/v1/evt', + headers: {}, + params: { + n: 'myevent', + i: 'com.singular.game', + ip: '14.5.67.21', + is_revenue_event: true, + utime: 1630511211, + cur: 'USD', + purchase_product_id: 'F-32', + install: 'SM-G935F', + install_source: 'selfdistributed', + amt: 6.9, + os: 'metaquest_pro', + p: 'metaquest', + a: 'dummyApiKey', + sdid: '49c2d3a6-326e-4ec5-a16b-0a47e34ed953', + ua: 'Mozilla/5.0 (Nintendo Switch; WebApplet) AppleWebKit/613.0 (KHTML, like Gecko) NF/6.0.3.25.0 NintendoBrowser/5.1.0.32061', + ve: 'qst2-2023h2', + av: '1.1.5.581823alpha', + }, + body: { JSON: {}, JSON_ARRAY: {}, XML: {}, FORM: {} }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, ]; From 43a8a304f9f66a79bb91a2eb305a7db5ccfde2d2 Mon Sep 17 00:00:00 2001 From: Gauravudia <60897972+Gauravudia@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:31:51 +0530 Subject: [PATCH 3/7] refactor: trade desk component testcases (#3763) --- .../the_trade_desk/router/business.ts | 330 +++++ .../the_trade_desk/router/data.ts | 1067 +---------------- .../the_trade_desk/router/validation.ts | 743 ++++++++++++ 3 files changed, 1076 insertions(+), 1064 deletions(-) create mode 100644 test/integrations/destinations/the_trade_desk/router/business.ts create mode 100644 test/integrations/destinations/the_trade_desk/router/validation.ts diff --git a/test/integrations/destinations/the_trade_desk/router/business.ts b/test/integrations/destinations/the_trade_desk/router/business.ts new file mode 100644 index 0000000000..556e69a909 --- /dev/null +++ b/test/integrations/destinations/the_trade_desk/router/business.ts @@ -0,0 +1,330 @@ +import { defaultMockFns } from '../mocks'; +import { + destType, + advertiserId, + dataProviderId, + segmentName, + sampleDestination, + sampleContext, +} from '../common'; + +export const business = [ + { + id: 'trade_desk-business-test-1', + name: destType, + description: 'Add IDs to the segment', + scenario: 'Framework', + successCriteria: 'Response should contain all the mapping and status code should be 200', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + message: { + type: 'record', + action: 'insert', + fields: { + DAID: 'test-daid-1', + UID2: 'test-uid2-1', + }, + channel: 'sources', + context: sampleContext, + recordId: '1', + }, + destination: sampleDestination, + metadata: { + jobId: 1, + userId: 'u1', + }, + }, + { + message: { + type: 'record', + action: 'insert', + fields: { + DAID: 'test-daid-2', + UID2: 'test-uid2-2', + }, + channel: 'sources', + context: sampleContext, + recordId: '2', + }, + destination: sampleDestination, + metadata: { + jobId: 2, + userId: 'u1', + }, + }, + ], + destType, + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batchedRequest: [ + { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://sin-data.adsrvr.org/data/advertiser', + headers: {}, + params: {}, + body: { + JSON: { + DataProviderId: dataProviderId, + AdvertiserId: advertiserId, + Items: [ + { + DAID: 'test-daid-1', + Data: [ + { + Name: segmentName, + TTLInMinutes: 43200, + }, + ], + }, + { + UID2: 'test-uid2-1', + Data: [ + { + Name: segmentName, + TTLInMinutes: 43200, + }, + ], + }, + { + DAID: 'test-daid-2', + Data: [ + { + Name: segmentName, + TTLInMinutes: 43200, + }, + ], + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + }, + { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://sin-data.adsrvr.org/data/advertiser', + headers: {}, + params: {}, + body: { + JSON: { + DataProviderId: dataProviderId, + AdvertiserId: advertiserId, + Items: [ + { + UID2: 'test-uid2-2', + Data: [ + { + Name: segmentName, + TTLInMinutes: 43200, + }, + ], + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + }, + ], + metadata: [ + { + jobId: 1, + userId: 'u1', + }, + { + jobId: 2, + userId: 'u1', + }, + ], + batched: true, + statusCode: 200, + destination: sampleDestination, + }, + ], + }, + }, + }, + mockFns: defaultMockFns, + }, + { + id: 'trade_desk-business-test-2', + name: destType, + description: + 'Add/Remove IDs to/from the segment and split into multiple requests based on size', + successCriteria: 'Response should contain all the mapping and status code should be 200', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + message: { + type: 'record', + action: 'insert', + fields: { + DAID: 'test-daid-1', + UID2: 'test-uid2-1', + }, + channel: 'sources', + context: sampleContext, + recordId: '1', + }, + destination: sampleDestination, + metadata: { + jobId: 1, + userId: 'u1', + }, + }, + { + message: { + type: 'record', + action: 'delete', + fields: { + DAID: 'test-daid-2', + UID2: 'test-uid2-2', + }, + channel: 'sources', + context: sampleContext, + recordId: '2', + }, + destination: sampleDestination, + metadata: { + jobId: 2, + userId: 'u1', + }, + }, + ], + destType, + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batchedRequest: [ + { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://sin-data.adsrvr.org/data/advertiser', + headers: {}, + params: {}, + body: { + JSON: { + DataProviderId: dataProviderId, + AdvertiserId: advertiserId, + Items: [ + { + DAID: 'test-daid-1', + Data: [ + { + Name: segmentName, + TTLInMinutes: 43200, + }, + ], + }, + { + UID2: 'test-uid2-1', + Data: [ + { + Name: segmentName, + TTLInMinutes: 43200, + }, + ], + }, + { + DAID: 'test-daid-2', + Data: [ + { + Name: segmentName, + TTLInMinutes: 0, + }, + ], + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + }, + { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://sin-data.adsrvr.org/data/advertiser', + headers: {}, + params: {}, + body: { + JSON: { + DataProviderId: dataProviderId, + AdvertiserId: advertiserId, + Items: [ + { + UID2: 'test-uid2-2', + Data: [ + { + Name: segmentName, + TTLInMinutes: 0, + }, + ], + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + }, + ], + metadata: [ + { + jobId: 1, + userId: 'u1', + }, + { + jobId: 2, + userId: 'u1', + }, + ], + batched: true, + statusCode: 200, + destination: sampleDestination, + }, + ], + }, + }, + }, + mockFns: defaultMockFns, + }, +]; diff --git a/test/integrations/destinations/the_trade_desk/router/data.ts b/test/integrations/destinations/the_trade_desk/router/data.ts index d2dbf9a7cc..6de2069ff4 100644 --- a/test/integrations/destinations/the_trade_desk/router/data.ts +++ b/test/integrations/destinations/the_trade_desk/router/data.ts @@ -1,1064 +1,3 @@ -import { overrideDestination } from '../../../testUtils'; -import { defaultMockFns } from '../mocks'; -import { - destType, - destTypeInUpperCase, - advertiserId, - dataProviderId, - segmentName, - sampleDestination, - sampleContext, -} from '../common'; - -export const data = [ - { - name: destType, - description: 'Add IDs to the segment', - feature: 'router', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - input: [ - { - message: { - type: 'record', - action: 'insert', - fields: { - DAID: 'test-daid-1', - UID2: 'test-uid2-1', - }, - channel: 'sources', - context: sampleContext, - recordId: '1', - }, - destination: sampleDestination, - metadata: { - jobId: 1, - userId: 'u1', - }, - }, - { - message: { - type: 'record', - action: 'insert', - fields: { - DAID: 'test-daid-2', - UID2: 'test-uid2-2', - }, - channel: 'sources', - context: sampleContext, - recordId: '2', - }, - destination: sampleDestination, - metadata: { - jobId: 2, - userId: 'u1', - }, - }, - ], - destType, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: [ - { - batchedRequest: [ - { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://sin-data.adsrvr.org/data/advertiser', - headers: {}, - params: {}, - body: { - JSON: { - DataProviderId: dataProviderId, - AdvertiserId: advertiserId, - Items: [ - { - DAID: 'test-daid-1', - Data: [ - { - Name: segmentName, - TTLInMinutes: 43200, - }, - ], - }, - { - UID2: 'test-uid2-1', - Data: [ - { - Name: segmentName, - TTLInMinutes: 43200, - }, - ], - }, - { - DAID: 'test-daid-2', - Data: [ - { - Name: segmentName, - TTLInMinutes: 43200, - }, - ], - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://sin-data.adsrvr.org/data/advertiser', - headers: {}, - params: {}, - body: { - JSON: { - DataProviderId: dataProviderId, - AdvertiserId: advertiserId, - Items: [ - { - UID2: 'test-uid2-2', - Data: [ - { - Name: segmentName, - TTLInMinutes: 43200, - }, - ], - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - ], - metadata: [ - { - jobId: 1, - userId: 'u1', - }, - { - jobId: 2, - userId: 'u1', - }, - ], - batched: true, - statusCode: 200, - destination: sampleDestination, - }, - ], - }, - }, - }, - mockFns: defaultMockFns, - }, - { - name: destType, - description: - 'Add/Remove IDs to/from the segment and split into multiple requests based on size', - feature: 'router', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - input: [ - { - message: { - type: 'record', - action: 'insert', - fields: { - DAID: 'test-daid-1', - UID2: 'test-uid2-1', - }, - channel: 'sources', - context: sampleContext, - recordId: '1', - }, - destination: sampleDestination, - metadata: { - jobId: 1, - userId: 'u1', - }, - }, - { - message: { - type: 'record', - action: 'delete', - fields: { - DAID: 'test-daid-2', - UID2: 'test-uid2-2', - }, - channel: 'sources', - context: sampleContext, - recordId: '2', - }, - destination: sampleDestination, - metadata: { - jobId: 2, - userId: 'u1', - }, - }, - ], - destType, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: [ - { - batchedRequest: [ - { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://sin-data.adsrvr.org/data/advertiser', - headers: {}, - params: {}, - body: { - JSON: { - DataProviderId: dataProviderId, - AdvertiserId: advertiserId, - Items: [ - { - DAID: 'test-daid-1', - Data: [ - { - Name: segmentName, - TTLInMinutes: 43200, - }, - ], - }, - { - UID2: 'test-uid2-1', - Data: [ - { - Name: segmentName, - TTLInMinutes: 43200, - }, - ], - }, - { - DAID: 'test-daid-2', - Data: [ - { - Name: segmentName, - TTLInMinutes: 0, - }, - ], - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://sin-data.adsrvr.org/data/advertiser', - headers: {}, - params: {}, - body: { - JSON: { - DataProviderId: dataProviderId, - AdvertiserId: advertiserId, - Items: [ - { - UID2: 'test-uid2-2', - Data: [ - { - Name: segmentName, - TTLInMinutes: 0, - }, - ], - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - ], - metadata: [ - { - jobId: 1, - userId: 'u1', - }, - { - jobId: 2, - userId: 'u1', - }, - ], - batched: true, - statusCode: 200, - destination: sampleDestination, - }, - ], - }, - }, - }, - mockFns: defaultMockFns, - }, - { - name: destType, - description: - 'Missing segment name (audienceId) in the config (segment name will be populated from vdm)', - feature: 'router', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - input: [ - { - message: { - type: 'record', - action: 'insert', - fields: { - DAID: 'test-daid-1', - UID2: 'test-uid2-1', - }, - channel: 'sources', - context: sampleContext, - recordId: '1', - }, - destination: overrideDestination(sampleDestination, { audienceId: '' }), - metadata: { - jobId: 1, - userId: 'u1', - }, - }, - { - message: { - type: 'record', - action: 'insert', - fields: { - DAID: 'test-daid-2', - UID2: 'test-uid2-2', - }, - channel: 'sources', - context: sampleContext, - recordId: '2', - }, - destination: overrideDestination(sampleDestination, { audienceId: '' }), - metadata: { - jobId: 2, - userId: 'u1', - }, - }, - ], - destType, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: [ - { - batched: false, - metadata: [ - { jobId: 1, userId: 'u1' }, - { jobId: 2, userId: 'u1' }, - ], - statusCode: 400, - error: 'Segment name/Audience ID is not present. Aborting', - statTags: { - destType: destTypeInUpperCase, - implementation: 'cdkV2', - feature: 'router', - module: 'destination', - errorCategory: 'dataValidation', - errorType: 'configuration', - }, - }, - ], - }, - }, - }, - }, - { - name: destType, - description: 'Missing advertiser ID in the config', - feature: 'router', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - input: [ - { - message: { - type: 'record', - action: 'insert', - fields: { - DAID: 'test-daid-1', - UID2: 'test-uid2-1', - }, - channel: 'sources', - context: sampleContext, - recordId: '1', - }, - destination: overrideDestination(sampleDestination, { advertiserId: '' }), - metadata: { - jobId: 1, - userId: 'u1', - }, - }, - { - message: { - type: 'record', - action: 'insert', - fields: { - DAID: 'test-daid-2', - UID2: 'test-uid2-2', - }, - channel: 'sources', - context: sampleContext, - recordId: '1', - }, - destination: overrideDestination(sampleDestination, { advertiserId: '' }), - metadata: { - jobId: 2, - userId: 'u1', - }, - }, - ], - destType, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: [ - { - batched: false, - metadata: [ - { jobId: 1, userId: 'u1' }, - { jobId: 2, userId: 'u1' }, - ], - statusCode: 400, - error: 'Advertiser ID is not present. Aborting', - statTags: { - destType: destTypeInUpperCase, - implementation: 'cdkV2', - feature: 'router', - module: 'destination', - errorCategory: 'dataValidation', - errorType: 'configuration', - }, - }, - ], - }, - }, - }, - }, - { - name: destType, - description: 'Missing advertiser secret key in the config', - feature: 'router', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - input: [ - { - message: { - type: 'record', - action: 'insert', - fields: { - DAID: 'test-daid-1', - UID2: 'test-uid2-1', - }, - channel: 'sources', - context: sampleContext, - recordId: '1', - }, - destination: overrideDestination(sampleDestination, { advertiserSecretKey: '' }), - metadata: { - jobId: 1, - userId: 'u1', - }, - }, - ], - destType, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: [ - { - batched: false, - metadata: [{ jobId: 1, userId: 'u1' }], - statusCode: 400, - error: 'Advertiser Secret Key is not present. Aborting', - statTags: { - destType: destTypeInUpperCase, - implementation: 'cdkV2', - feature: 'router', - module: 'destination', - errorCategory: 'dataValidation', - errorType: 'configuration', - }, - }, - ], - }, - }, - }, - }, - { - name: destType, - description: 'TTL is out of range', - feature: 'router', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - input: [ - { - message: { - type: 'record', - action: 'insert', - fields: { - DAID: 'test-daid-1', - UID2: 'test-uid2-1', - }, - channel: 'sources', - context: sampleContext, - recordId: '1', - }, - destination: overrideDestination(sampleDestination, { ttlInDays: 190 }), - metadata: { - jobId: 1, - userId: 'u1', - }, - }, - ], - destType, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: [ - { - batched: false, - metadata: [{ jobId: 1, userId: 'u1' }], - statusCode: 400, - error: 'TTL is out of range. Allowed values are 0 to 180 days', - statTags: { - destType: destTypeInUpperCase, - implementation: 'cdkV2', - feature: 'router', - module: 'destination', - errorCategory: 'dataValidation', - errorType: 'configuration', - }, - }, - ], - }, - }, - }, - }, - { - name: destType, - description: 'Invalid action type', - feature: 'router', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - input: [ - { - message: { - type: 'record', - action: 'insert', - fields: { - DAID: 'test-daid-1', - UID2: 'test-uid2-1', - }, - channel: 'sources', - context: sampleContext, - recordId: '1', - }, - destination: sampleDestination, - metadata: { - jobId: 1, - }, - }, - { - message: { - type: 'record', - action: 'update', - fields: { - DAID: 'test-daid-2', - UID2: null, - }, - channel: 'sources', - context: sampleContext, - recordId: '2', - }, - destination: sampleDestination, - metadata: { - jobId: 2, - }, - }, - ], - destType, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: [ - { - batchedRequest: [ - { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://sin-data.adsrvr.org/data/advertiser', - headers: {}, - params: {}, - body: { - JSON: { - DataProviderId: dataProviderId, - AdvertiserId: advertiserId, - Items: [ - { - DAID: 'test-daid-1', - Data: [ - { - Name: segmentName, - TTLInMinutes: 43200, - }, - ], - }, - { - UID2: 'test-uid2-1', - Data: [ - { - Name: segmentName, - TTLInMinutes: 43200, - }, - ], - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - ], - metadata: [ - { - jobId: 1, - }, - ], - batched: true, - statusCode: 200, - destination: sampleDestination, - }, - { - batched: false, - metadata: [{ jobId: 2 }], - statusCode: 400, - error: - 'Invalid action type. You can only add or remove IDs from the audience/segment', - statTags: { - destType: destTypeInUpperCase, - implementation: 'cdkV2', - feature: 'router', - module: 'destination', - errorCategory: 'dataValidation', - errorType: 'instrumentation', - }, - destination: sampleDestination, - }, - ], - }, - }, - }, - mockFns: defaultMockFns, - }, - { - name: destType, - description: 'Empty fields in the message', - feature: 'router', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - input: [ - { - message: { - type: 'record', - action: 'insert', - fields: {}, - channel: 'sources', - context: sampleContext, - recordId: '1', - }, - destination: sampleDestination, - metadata: { - jobId: 1, - }, - }, - { - message: { - type: 'record', - action: 'insert', - fields: { - DAID: 'test-daid-1', - UID2: 'test-uid2-1', - EUID: 'test-euid-1', - }, - channel: 'sources', - context: sampleContext, - recordId: '2', - }, - destination: sampleDestination, - metadata: { - jobId: 2, - }, - }, - { - message: { - type: 'record', - action: 'insert', - fields: { - DAID: 'test-daid-2', - UID2: null, - EUID: null, - }, - channel: 'sources', - context: sampleContext, - recordId: '3', - }, - destination: sampleDestination, - metadata: { - jobId: 3, - }, - }, - ], - destType, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: [ - { - batchedRequest: [ - { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://sin-data.adsrvr.org/data/advertiser', - headers: {}, - params: {}, - body: { - JSON: { - DataProviderId: dataProviderId, - AdvertiserId: advertiserId, - Items: [ - { - DAID: 'test-daid-1', - Data: [ - { - Name: segmentName, - TTLInMinutes: 43200, - }, - ], - }, - { - UID2: 'test-uid2-1', - Data: [ - { - Name: segmentName, - TTLInMinutes: 43200, - }, - ], - }, - { - EUID: 'test-euid-1', - Data: [ - { - Name: segmentName, - TTLInMinutes: 43200, - }, - ], - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - ], - metadata: [ - { - jobId: 2, - }, - ], - batched: true, - statusCode: 200, - destination: sampleDestination, - }, - { - batched: false, - metadata: [{ jobId: 1 }], - statusCode: 400, - error: '`fields` cannot be empty', - statTags: { - destType: destTypeInUpperCase, - implementation: 'cdkV2', - feature: 'router', - module: 'destination', - errorCategory: 'dataValidation', - errorType: 'instrumentation', - }, - destination: sampleDestination, - }, - { - batched: false, - metadata: [{ jobId: 3 }], - statusCode: 400, - error: '`fields` cannot be empty', - statTags: { - destType: destTypeInUpperCase, - implementation: 'cdkV2', - feature: 'router', - module: 'destination', - errorCategory: 'dataValidation', - errorType: 'instrumentation', - }, - destination: sampleDestination, - }, - ], - }, - }, - }, - mockFns: defaultMockFns, - }, - { - name: destType, - description: '`fields` is missing', - feature: 'router', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - input: [ - { - message: { - type: 'record', - action: 'insert', - channel: 'sources', - context: sampleContext, - recordId: '1', - }, - destination: sampleDestination, - metadata: { - jobId: 1, - }, - }, - ], - destType, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: [ - { - batched: false, - metadata: [{ jobId: 1 }], - statusCode: 400, - error: '`fields` cannot be empty', - statTags: { - destType: destTypeInUpperCase, - implementation: 'cdkV2', - feature: 'router', - module: 'destination', - errorCategory: 'dataValidation', - errorType: 'instrumentation', - }, - destination: sampleDestination, - }, - ], - }, - }, - }, - mockFns: defaultMockFns, - }, - { - name: destType, - description: 'Batch call with different event types', - feature: 'router', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - input: [ - { - message: { - type: 'record', - action: 'insert', - fields: { - DAID: 'test-daid-1', - }, - channel: 'sources', - context: sampleContext, - recordId: '1', - }, - destination: sampleDestination, - metadata: { - jobId: 1, - }, - }, - { - message: { - type: 'identify', - context: { - traits: { - name: 'John Doe', - email: 'johndoe@gmail.com', - age: 25, - }, - }, - }, - destination: sampleDestination, - metadata: { - jobId: 2, - }, - }, - ], - destType, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: [ - { - batchedRequest: [ - { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://sin-data.adsrvr.org/data/advertiser', - headers: {}, - params: {}, - body: { - JSON: { - DataProviderId: dataProviderId, - AdvertiserId: advertiserId, - Items: [ - { - DAID: 'test-daid-1', - Data: [ - { - Name: segmentName, - TTLInMinutes: 43200, - }, - ], - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - ], - metadata: [ - { - jobId: 1, - }, - ], - batched: true, - statusCode: 200, - destination: sampleDestination, - }, - { - batched: false, - metadata: [{ jobId: 2 }], - statusCode: 400, - error: 'Event type identify is not supported', - statTags: { - errorCategory: 'dataValidation', - errorType: 'instrumentation', - destType: 'THE_TRADE_DESK', - module: 'destination', - implementation: 'cdkV2', - feature: 'router', - }, - destination: sampleDestination, - }, - ], - }, - }, - }, - }, -]; +import { business } from './business'; +import { validation } from './validation'; +export const data = [...business, ...validation]; diff --git a/test/integrations/destinations/the_trade_desk/router/validation.ts b/test/integrations/destinations/the_trade_desk/router/validation.ts new file mode 100644 index 0000000000..be7caf079a --- /dev/null +++ b/test/integrations/destinations/the_trade_desk/router/validation.ts @@ -0,0 +1,743 @@ +import { defaultMockFns } from '../mocks'; +import { generateMetadata } from '../../../testUtils'; +import { overrideDestination } from '../../../testUtils'; +import { + destType, + destTypeInUpperCase, + advertiserId, + dataProviderId, + segmentName, + sampleDestination, + sampleContext, +} from '../common'; + +export const validation = [ + { + id: 'trade_desk-validation-test-1', + name: destType, + description: 'Missing advertiser ID in the config', + scenario: 'Framework', + successCriteria: 'Partial Failure: Configuration Error', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + message: { + type: 'record', + action: 'insert', + fields: { + DAID: 'test-daid-1', + UID2: 'test-uid2-1', + }, + channel: 'sources', + context: sampleContext, + recordId: '1', + }, + destination: overrideDestination(sampleDestination, { advertiserId: '' }), + metadata: generateMetadata(1, 'u1'), + }, + { + message: { + type: 'record', + action: 'insert', + fields: { + DAID: 'test-daid-2', + UID2: 'test-uid2-2', + }, + channel: 'sources', + context: sampleContext, + recordId: '1', + }, + destination: overrideDestination(sampleDestination, { advertiserId: '' }), + metadata: generateMetadata(2, 'u1'), + }, + ], + destType, + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batched: false, + metadata: [generateMetadata(1, 'u1'), generateMetadata(2, 'u1')], + statusCode: 400, + error: 'Advertiser ID is not present. Aborting', + statTags: { + destType: destTypeInUpperCase, + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + implementation: 'cdkV2', + feature: 'router', + module: 'destination', + errorCategory: 'dataValidation', + errorType: 'configuration', + }, + }, + ], + }, + }, + }, + }, + { + id: 'trade_desk-validation-test-2', + name: destType, + description: + 'Missing segment name (audienceId) in the config (segment name will be populated from vdm)', + scenario: 'Framework', + successCriteria: 'Partial Failure: Configuration Error', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + message: { + type: 'record', + action: 'insert', + fields: { + DAID: 'test-daid-1', + UID2: 'test-uid2-1', + }, + channel: 'sources', + context: sampleContext, + recordId: '1', + }, + destination: overrideDestination(sampleDestination, { audienceId: '' }), + metadata: generateMetadata(1, 'u1'), + }, + { + message: { + type: 'record', + action: 'insert', + fields: { + DAID: 'test-daid-2', + UID2: 'test-uid2-2', + }, + channel: 'sources', + context: sampleContext, + recordId: '2', + }, + destination: overrideDestination(sampleDestination, { audienceId: '' }), + metadata: generateMetadata(2, 'u1'), + }, + ], + destType, + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batched: false, + metadata: [generateMetadata(1, 'u1'), generateMetadata(2, 'u1')], + statusCode: 400, + error: 'Segment name/Audience ID is not present. Aborting', + statTags: { + destType: destTypeInUpperCase, + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + implementation: 'cdkV2', + feature: 'router', + module: 'destination', + errorCategory: 'dataValidation', + errorType: 'configuration', + }, + }, + ], + }, + }, + }, + }, + { + id: 'trade_desk-validation-test-3', + name: destType, + description: 'Missing advertiser secret key in the config', + scenario: 'Framework', + successCriteria: 'Partial Failure: Configuration Error', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + message: { + type: 'record', + action: 'insert', + fields: { + DAID: 'test-daid-1', + UID2: 'test-uid2-1', + }, + channel: 'sources', + context: sampleContext, + recordId: '1', + }, + destination: overrideDestination(sampleDestination, { advertiserSecretKey: '' }), + metadata: generateMetadata(1, 'u1'), + }, + ], + destType, + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batched: false, + metadata: [generateMetadata(1, 'u1')], + statusCode: 400, + error: 'Advertiser Secret Key is not present. Aborting', + statTags: { + destType: destTypeInUpperCase, + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + implementation: 'cdkV2', + feature: 'router', + module: 'destination', + errorCategory: 'dataValidation', + errorType: 'configuration', + }, + }, + ], + }, + }, + }, + }, + { + id: 'trade_desk-validation-test-4', + name: destType, + description: 'Invalid action type', + scenario: 'Framework', + successCriteria: 'Partial Failure: Instrumentation Error', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + message: { + type: 'record', + action: 'insert', + fields: { + DAID: 'test-daid-1', + UID2: 'test-uid2-1', + }, + channel: 'sources', + context: sampleContext, + recordId: '1', + }, + destination: sampleDestination, + metadata: generateMetadata(1, 'u1'), + }, + { + message: { + type: 'record', + action: 'update', + fields: { + DAID: 'test-daid-2', + UID2: null, + }, + channel: 'sources', + context: sampleContext, + recordId: '2', + }, + destination: sampleDestination, + metadata: generateMetadata(2, 'u1'), + }, + ], + destType, + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batchedRequest: [ + { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://sin-data.adsrvr.org/data/advertiser', + headers: {}, + params: {}, + body: { + JSON: { + DataProviderId: dataProviderId, + AdvertiserId: advertiserId, + Items: [ + { + DAID: 'test-daid-1', + Data: [ + { + Name: segmentName, + TTLInMinutes: 43200, + }, + ], + }, + { + UID2: 'test-uid2-1', + Data: [ + { + Name: segmentName, + TTLInMinutes: 43200, + }, + ], + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + }, + ], + metadata: [generateMetadata(1, 'u1')], + batched: true, + statusCode: 200, + destination: sampleDestination, + }, + { + batched: false, + metadata: [generateMetadata(2, 'u1')], + statusCode: 400, + error: + 'Invalid action type. You can only add or remove IDs from the audience/segment', + statTags: { + destType: destTypeInUpperCase, + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + implementation: 'cdkV2', + feature: 'router', + module: 'destination', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + }, + destination: sampleDestination, + }, + ], + }, + }, + }, + mockFns: defaultMockFns, + }, + { + id: 'trade_desk-validation-test-5', + name: destType, + description: 'Invalid action type', + scenario: 'Framework', + successCriteria: 'Partial Failure: Instrumentation Error', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + message: { + type: 'record', + action: 'insert', + fields: {}, + channel: 'sources', + context: sampleContext, + recordId: '1', + }, + destination: sampleDestination, + metadata: generateMetadata(1, 'u1'), + }, + { + message: { + type: 'record', + action: 'insert', + fields: { + DAID: 'test-daid-1', + UID2: 'test-uid2-1', + EUID: 'test-euid-1', + }, + channel: 'sources', + context: sampleContext, + recordId: '2', + }, + destination: sampleDestination, + metadata: generateMetadata(2, 'u1'), + }, + { + message: { + type: 'record', + action: 'insert', + fields: { + DAID: 'test-daid-2', + UID2: null, + EUID: null, + }, + channel: 'sources', + context: sampleContext, + recordId: '3', + }, + destination: sampleDestination, + metadata: generateMetadata(3, 'u1'), + }, + ], + destType, + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batchedRequest: [ + { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://sin-data.adsrvr.org/data/advertiser', + headers: {}, + params: {}, + body: { + JSON: { + DataProviderId: dataProviderId, + AdvertiserId: advertiserId, + Items: [ + { + DAID: 'test-daid-1', + Data: [ + { + Name: segmentName, + TTLInMinutes: 43200, + }, + ], + }, + { + UID2: 'test-uid2-1', + Data: [ + { + Name: segmentName, + TTLInMinutes: 43200, + }, + ], + }, + { + EUID: 'test-euid-1', + Data: [ + { + Name: segmentName, + TTLInMinutes: 43200, + }, + ], + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + }, + ], + metadata: [generateMetadata(2, 'u1')], + batched: true, + statusCode: 200, + destination: sampleDestination, + }, + { + batched: false, + metadata: [generateMetadata(1, 'u1')], + statusCode: 400, + error: '`fields` cannot be empty', + statTags: { + destType: destTypeInUpperCase, + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + implementation: 'cdkV2', + feature: 'router', + module: 'destination', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + }, + destination: sampleDestination, + }, + { + batched: false, + metadata: [generateMetadata(3, 'u1')], + statusCode: 400, + error: '`fields` cannot be empty', + statTags: { + destType: destTypeInUpperCase, + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + implementation: 'cdkV2', + feature: 'router', + module: 'destination', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + }, + destination: sampleDestination, + }, + ], + }, + }, + }, + mockFns: defaultMockFns, + }, + { + id: 'trade_desk-validation-test-6', + name: destType, + description: '`fields` is missing', + scenario: 'Framework', + successCriteria: 'Partial Failure: Instrumentation Error', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + message: { + type: 'record', + action: 'insert', + channel: 'sources', + context: sampleContext, + recordId: '1', + }, + destination: sampleDestination, + metadata: generateMetadata(1, 'u1'), + }, + ], + destType, + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batched: false, + metadata: [generateMetadata(1, 'u1')], + statusCode: 400, + error: '`fields` cannot be empty', + statTags: { + destType: destTypeInUpperCase, + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + implementation: 'cdkV2', + feature: 'router', + module: 'destination', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + }, + destination: sampleDestination, + }, + ], + }, + }, + }, + mockFns: defaultMockFns, + }, + { + id: 'trade_desk-validation-test-7', + name: destType, + description: 'Batch call with different event types', + scenario: 'Framework', + successCriteria: 'Partial Failure: Instrumentation Error', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + message: { + type: 'record', + action: 'insert', + fields: { + DAID: 'test-daid-1', + }, + channel: 'sources', + context: sampleContext, + recordId: '1', + }, + destination: sampleDestination, + metadata: generateMetadata(1, 'u1'), + }, + { + message: { + type: 'identify', + context: { + traits: { + name: 'John Doe', + email: 'johndoe@gmail.com', + age: 25, + }, + }, + }, + destination: sampleDestination, + metadata: generateMetadata(2, 'u1'), + }, + ], + destType, + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batchedRequest: [ + { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://sin-data.adsrvr.org/data/advertiser', + headers: {}, + params: {}, + body: { + JSON: { + DataProviderId: dataProviderId, + AdvertiserId: advertiserId, + Items: [ + { + DAID: 'test-daid-1', + Data: [ + { + Name: segmentName, + TTLInMinutes: 43200, + }, + ], + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + }, + ], + metadata: [generateMetadata(1, 'u1')], + batched: true, + statusCode: 200, + destination: sampleDestination, + }, + { + batched: false, + metadata: [generateMetadata(2, 'u1')], + statusCode: 400, + error: 'Event type identify is not supported', + statTags: { + errorCategory: 'dataValidation', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + errorType: 'instrumentation', + destType: destTypeInUpperCase, + module: 'destination', + implementation: 'cdkV2', + feature: 'router', + }, + destination: sampleDestination, + }, + ], + }, + }, + }, + }, + { + id: 'trade_desk-validation-test-8', + name: destType, + description: 'TTL is out of range', + scenario: 'Framework', + successCriteria: 'Configurations= Error', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + message: { + type: 'record', + action: 'insert', + fields: { + DAID: 'test-daid-1', + UID2: 'test-uid2-1', + }, + channel: 'sources', + context: sampleContext, + recordId: '1', + }, + destination: overrideDestination(sampleDestination, { ttlInDays: 190 }), + metadata: { + jobId: 1, + userId: 'u1', + }, + }, + ], + destType, + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batched: false, + metadata: [{ jobId: 1, userId: 'u1' }], + statusCode: 400, + error: 'TTL is out of range. Allowed values are 0 to 180 days', + statTags: { + destType: destTypeInUpperCase, + implementation: 'cdkV2', + feature: 'router', + module: 'destination', + errorCategory: 'dataValidation', + errorType: 'configuration', + }, + }, + ], + }, + }, + }, + }, +]; From 173b9895fb2a0bed615f6e3a9c670abe42d5754f Mon Sep 17 00:00:00 2001 From: Anant Jain <62471433+anantjain45823@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:46:36 +0530 Subject: [PATCH 4/7] fix: braze include fields_to_export to lookup users (#3761) * fix: braze include fields_to_export to lookup users * chore: fix test cases * fix: add user_aliases as export field for anonymous users * chore: fix test cases --------- Co-authored-by: Utsab Chowdhury --- src/v0/destinations/braze/braze.util.test.js | 14 +++++++++ src/v0/destinations/braze/util.js | 21 ++++++++++++-- .../destinations/braze/network.ts | 29 +++++++++---------- 3 files changed, 47 insertions(+), 17 deletions(-) diff --git a/src/v0/destinations/braze/braze.util.test.js b/src/v0/destinations/braze/braze.util.test.js index 5ec48d29f1..71052f8d77 100644 --- a/src/v0/destinations/braze/braze.util.test.js +++ b/src/v0/destinations/braze/braze.util.test.js @@ -304,6 +304,20 @@ describe('dedup utility tests', () => { { external_ids: ['user1', 'user2'], user_aliases: [{ alias_name: 'user3', alias_label: 'rudder_id' }], + fields_to_export: [ + 'created_at', + 'custom_attributes', + 'dob', + 'email', + 'first_name', + 'gender', + 'home_city', + 'last_name', + 'phone', + 'time_zone', + 'external_id', + 'user_aliases', + ], }, { headers: { diff --git a/src/v0/destinations/braze/util.js b/src/v0/destinations/braze/util.js index e5df75b562..6c8cf64265 100644 --- a/src/v0/destinations/braze/util.js +++ b/src/v0/destinations/braze/util.js @@ -141,19 +141,36 @@ const BrazeDedupUtility = { const identfierChunks = _.chunk(identifiers, 50); return identfierChunks; }, - + getFieldsToExport() { + return [ + 'created_at', + 'custom_attributes', + 'dob', + 'email', + 'first_name', + 'gender', + 'home_city', + 'last_name', + 'phone', + 'time_zone', + 'external_id', + 'user_aliases', + // 'country' and 'language' not needed because it is not billable so we don't use it + ]; + }, async doApiLookup(identfierChunks, { destination, metadata }) { return Promise.all( identfierChunks.map(async (ids) => { const externalIdentifiers = ids.filter((id) => id.external_id); const aliasIdentifiers = ids.filter((id) => id.alias_name !== undefined); - + const fieldsToExport = this.getFieldsToExport(); const { processedResponse: lookUpResponse } = await handleHttpRequest( 'post', `${getEndpointFromConfig(destination)}/users/export/ids`, { external_ids: externalIdentifiers.map((extId) => extId.external_id), user_aliases: aliasIdentifiers, + fields_to_export: fieldsToExport, }, { headers: { diff --git a/test/integrations/destinations/braze/network.ts b/test/integrations/destinations/braze/network.ts index ae093ce1f4..bcfa78de5d 100644 --- a/test/integrations/destinations/braze/network.ts +++ b/test/integrations/destinations/braze/network.ts @@ -406,6 +406,20 @@ const deleteNwData = [ { alias_name: '77e278c9-e984-4cdd-950c-cd0b61befd03', alias_label: 'rudder_id' }, { alias_name: 'e6ab2c5e-2cda-44a9-a962-e2f67df78bca', alias_label: 'rudder_id' }, ], + fields_to_export: [ + 'created_at', + 'custom_attributes', + 'dob', + 'email', + 'first_name', + 'gender', + 'home_city', + 'last_name', + 'phone', + 'time_zone', + 'external_id', + 'user_aliases', + ], }, headers: { Authorization: 'Bearer dummyApiKey' }, url: 'https://rest.iad-03.braze.com/users/export/ids', @@ -416,12 +430,8 @@ const deleteNwData = [ { created_at: '2023-03-17T20:51:58.297Z', external_id: 'braze_test_user', - user_aliases: [], - appboy_id: '6414d2ee33326e3354e3040b', - braze_id: '6414d2ee33326e3354e3040b', first_name: 'Jackson', last_name: 'Miranda', - random_bucket: 8134, email: 'jackson24miranda@gmail.com', custom_attributes: { pwa: false, @@ -437,17 +447,6 @@ const deleteNwData = [ }, custom_arr: [1, 2, 'str1'], }, - custom_events: [ - { - name: 'Sign In Completed', - first: '2023-03-10T18:36:05.028Z', - last: '2023-03-10T18:36:05.028Z', - count: 2, - }, - ], - total_revenue: 0, - push_subscribe: 'subscribed', - email_subscribe: 'subscribed', }, ], }, From b6240d06a9d1f7f3bc8f245807f72a72ab40f170 Mon Sep 17 00:00:00 2001 From: shrouti1507 <60211312+shrouti1507@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:47:23 +0530 Subject: [PATCH 5/7] fix: posthog alias mapping swap (#3765) * fix: posthog swapped alias ids (#3507) [Posthog] fix swapped alias ids Co-authored-by: shrouti1507 <60211312+shrouti1507@users.noreply.github.com> * fix: test case update for posthog --------- Co-authored-by: Marc Jeffrey --- src/v0/destinations/posthog/data/PHAliasConfig.json | 4 ++-- test/integrations/destinations/posthog/processor/data.ts | 4 ++-- test/integrations/destinations/posthog/router/data.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/v0/destinations/posthog/data/PHAliasConfig.json b/src/v0/destinations/posthog/data/PHAliasConfig.json index 1992349e22..26fb61ea78 100644 --- a/src/v0/destinations/posthog/data/PHAliasConfig.json +++ b/src/v0/destinations/posthog/data/PHAliasConfig.json @@ -1,12 +1,12 @@ [ { - "destKey": "properties.alias", + "destKey": "properties.distinct_id", "sourceKeys": "userId", "sourceFromGenericMap": true, "required": true }, { - "destKey": "properties.distinct_id", + "destKey": "properties.alias", "sourceKeys": "previousId", "required": true }, diff --git a/test/integrations/destinations/posthog/processor/data.ts b/test/integrations/destinations/posthog/processor/data.ts index f33aa268b7..35e12b4aab 100644 --- a/test/integrations/destinations/posthog/processor/data.ts +++ b/test/integrations/destinations/posthog/processor/data.ts @@ -74,7 +74,7 @@ export const data = [ $ip: '0.0.0.0', $timestamp: '2020-11-04T13:21:09.712Z', $anon_distinct_id: 'f3cf54d8-f237-45d2-89f7-ccd70d42cf31', - distinct_id: 'prevId_1', + distinct_id: 'uid-1', $device_manufacturer: 'Xiaomi', $os_version: '8.1.0', $app_version: '1.1.7', @@ -84,7 +84,7 @@ export const data = [ $device_model: 'Redmi 6', $app_namespace: 'com.rudderlabs.javascript', $app_build: '1.0.0', - alias: 'uid-1', + alias: 'prevId_1', }, timestamp: '2020-11-04T13:21:09.712Z', event: '$create_alias', diff --git a/test/integrations/destinations/posthog/router/data.ts b/test/integrations/destinations/posthog/router/data.ts index dab8ba8b1c..20870670dc 100644 --- a/test/integrations/destinations/posthog/router/data.ts +++ b/test/integrations/destinations/posthog/router/data.ts @@ -79,7 +79,7 @@ export const data = [ $ip: '0.0.0.0', $timestamp: '2020-11-04T13:21:09.712Z', $anon_distinct_id: 'f3cf54d8-f237-45d2-89f7-ccd70d42cf31', - distinct_id: 'prevId_1', + distinct_id: 'uid-1', $device_manufacturer: 'Xiaomi', $os_version: '8.1.0', $app_version: '1.1.7', @@ -89,7 +89,7 @@ export const data = [ $device_model: 'Redmi 6', $app_namespace: 'com.rudderlabs.javascript', $app_build: '1.0.0', - alias: 'uid-1', + alias: 'prevId_1', }, timestamp: '2020-11-04T13:21:09.712Z', event: '$create_alias', From 08cfdb9ada4d22901916425484167ecc1a3bc543 Mon Sep 17 00:00:00 2001 From: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com> Date: Mon, 30 Sep 2024 11:32:33 +0530 Subject: [PATCH 6/7] chore: reuse ga4 network handler for ga4v2 (#3768) --- src/v1/destinations/ga4_v2/networkHandler.ts | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/v1/destinations/ga4_v2/networkHandler.ts diff --git a/src/v1/destinations/ga4_v2/networkHandler.ts b/src/v1/destinations/ga4_v2/networkHandler.ts new file mode 100644 index 0000000000..aece0411c1 --- /dev/null +++ b/src/v1/destinations/ga4_v2/networkHandler.ts @@ -0,0 +1,3 @@ +import { networkHandler } from '../../../v0/destinations/ga4/networkHandler'; + +module.exports = { networkHandler }; From 45b1067d81f3883e19d35634ffec52434fef452f Mon Sep 17 00:00:00 2001 From: shrouti1507 <60211312+shrouti1507@users.noreply.github.com> Date: Mon, 30 Sep 2024 13:02:46 +0530 Subject: [PATCH 7/7] fix: fixing lytics user_id and anonymousId mapping (#3745) * fix: fixing lytics user_id and anonymousId mapping * fix: adding mapping config for identify call and clean up of native code * fix: adding user and anonymousId mapping for other calls as well --------- Co-authored-by: Krishna Chaitanya --- src/cdk/v2/destinations/lytics/config.ts | 9 + .../lytics/data/LYTICSIdentifyConfig.json | 25 ++ .../v2/destinations/lytics/procWorkflow.yaml | 16 +- src/v0/destinations/lytics/config.js | 27 -- .../lytics/data/LYTICSIdentifyConfig.json | 19 -- .../lytics/data/LYTICSPageScreenConfig.json | 12 - .../lytics/data/LYTICSTrackConfig.json | 12 - src/v0/destinations/lytics/transform.js | 77 ----- .../destinations/lytics/processor/data.ts | 122 +++++++ .../destinations/lytics/router/data.ts | 299 ------------------ 10 files changed, 165 insertions(+), 453 deletions(-) create mode 100644 src/cdk/v2/destinations/lytics/config.ts create mode 100644 src/cdk/v2/destinations/lytics/data/LYTICSIdentifyConfig.json delete mode 100644 src/v0/destinations/lytics/config.js delete mode 100644 src/v0/destinations/lytics/data/LYTICSIdentifyConfig.json delete mode 100644 src/v0/destinations/lytics/data/LYTICSPageScreenConfig.json delete mode 100644 src/v0/destinations/lytics/data/LYTICSTrackConfig.json delete mode 100644 src/v0/destinations/lytics/transform.js delete mode 100644 test/integrations/destinations/lytics/router/data.ts diff --git a/src/cdk/v2/destinations/lytics/config.ts b/src/cdk/v2/destinations/lytics/config.ts new file mode 100644 index 0000000000..6756834caa --- /dev/null +++ b/src/cdk/v2/destinations/lytics/config.ts @@ -0,0 +1,9 @@ +import { getMappingConfig } from '../../../../v0/util'; + +const CONFIG_CATEGORIES = { + CUSTOMER_PROPERTIES_CONFIG: { name: 'LYTICSIdentifyConfig' }, +}; + +const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname); +export const CUSTOMER_PROPERTIES_CONFIG = + MAPPING_CONFIG[CONFIG_CATEGORIES.CUSTOMER_PROPERTIES_CONFIG.name]; diff --git a/src/cdk/v2/destinations/lytics/data/LYTICSIdentifyConfig.json b/src/cdk/v2/destinations/lytics/data/LYTICSIdentifyConfig.json new file mode 100644 index 0000000000..c1996d34f7 --- /dev/null +++ b/src/cdk/v2/destinations/lytics/data/LYTICSIdentifyConfig.json @@ -0,0 +1,25 @@ +[ + { + "destKey": "user_id", + "sourceKeys": "userIdOnly", + "sourceFromGenericMap": true, + "required": false + }, + { + "destKey": "anonymous_id", + "sourceKeys": "anonymousId", + "required": false + }, + { + "destKey": "first_name", + "sourceKeys": "firstName", + "sourceFromGenericMap": true, + "required": false + }, + { + "destKey": "last_name", + "sourceKeys": "lastName", + "sourceFromGenericMap": true, + "required": false + } +] diff --git a/src/cdk/v2/destinations/lytics/procWorkflow.yaml b/src/cdk/v2/destinations/lytics/procWorkflow.yaml index 2622146221..834493e79c 100644 --- a/src/cdk/v2/destinations/lytics/procWorkflow.yaml +++ b/src/cdk/v2/destinations/lytics/procWorkflow.yaml @@ -5,9 +5,12 @@ bindings: path: ../../../../v0/util - name: removeUndefinedAndNullValues path: ../../../../v0/util + - name: constructPayload + path: ../../../../v0/util - path: ../../bindings/jsontemplate - name: defaultRequestConfig path: ../../../../v0/util + - path: ./config steps: - name: validateInput @@ -24,20 +27,19 @@ steps: condition: $.context.messageType === {{$.EventType.IDENTIFY}} template: | const flattenTraits = $.flattenJson(.message.traits ?? .message.context.traits); - $.context.payload = .message.({ + const payload = $.constructPayload(.message, $.CUSTOMER_PROPERTIES_CONFIG); + $.context.payload = { ...flattenTraits, - first_name: {{{{$.getGenericPaths("firstName")}}}}, - last_name: {{{{$.getGenericPaths("lastName")}}}}, - user_id: {{{{$.getGenericPaths("userId")}}}} - }) + ...payload, + } else: name: payloadForOthers template: | const flattenProperties = $.flattenJson(.message.properties); + const customerPropertiesInfo = $.constructPayload(.message, $.CUSTOMER_PROPERTIES_CONFIG); $.context.payload = .message.({ ...flattenProperties, - first_name: .properties.firstName ?? .properties.firstname, - last_name: .properties.lastName ?? .properties.lastname + ...customerPropertiesInfo }) - name: trackPayload condition: $.context.messageType === {{$.EventType.TRACK}} diff --git a/src/v0/destinations/lytics/config.js b/src/v0/destinations/lytics/config.js deleted file mode 100644 index c7843eda46..0000000000 --- a/src/v0/destinations/lytics/config.js +++ /dev/null @@ -1,27 +0,0 @@ -const { getMappingConfig } = require('../../util'); - -const ENDPOINT = 'https://api.lytics.io/collect/json'; -const CONFIG_CATEGORIES = { - IDENTIFY: { - name: 'LYTICSIdentifyConfig', - }, - PAGESCREEN: { - name: 'LYTICSPageScreenConfig', - }, - TRACK: { - name: 'LYTICSTrackConfig', - }, -}; - -const forFirstName = ['firstname', 'firstName']; -const forLastName = ['lastname', 'lastName']; - -const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname); - -module.exports = { - ENDPOINT, - MAPPING_CONFIG, - CONFIG_CATEGORIES, - forFirstName, - forLastName, -}; diff --git a/src/v0/destinations/lytics/data/LYTICSIdentifyConfig.json b/src/v0/destinations/lytics/data/LYTICSIdentifyConfig.json deleted file mode 100644 index d9765490e3..0000000000 --- a/src/v0/destinations/lytics/data/LYTICSIdentifyConfig.json +++ /dev/null @@ -1,19 +0,0 @@ -[ - { - "destKey": "user_id", - "sourceKeys": [ - "userId", - "traits.userId", - "traits.id", - "context.traits.userId", - "context.traits.id", - "anonymousId" - ], - "required": false - }, - { - "destKey": "", - "sourceKeys": ["traits", "context.traits"], - "required": false - } -] diff --git a/src/v0/destinations/lytics/data/LYTICSPageScreenConfig.json b/src/v0/destinations/lytics/data/LYTICSPageScreenConfig.json deleted file mode 100644 index f925dbc5bd..0000000000 --- a/src/v0/destinations/lytics/data/LYTICSPageScreenConfig.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "destKey": "event", - "sourceKeys": "name", - "required": false - }, - { - "destKey": "", - "sourceKeys": "properties", - "required": false - } -] diff --git a/src/v0/destinations/lytics/data/LYTICSTrackConfig.json b/src/v0/destinations/lytics/data/LYTICSTrackConfig.json deleted file mode 100644 index 5de09eb10b..0000000000 --- a/src/v0/destinations/lytics/data/LYTICSTrackConfig.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "destKey": "_e", - "sourceKeys": "event", - "required": false - }, - { - "destKey": "", - "sourceKeys": "properties", - "required": false - } -] diff --git a/src/v0/destinations/lytics/transform.js b/src/v0/destinations/lytics/transform.js deleted file mode 100644 index c3a971adba..0000000000 --- a/src/v0/destinations/lytics/transform.js +++ /dev/null @@ -1,77 +0,0 @@ -const { InstrumentationError } = require('@rudderstack/integrations-lib'); -const { EventType } = require('../../../constants'); -const { - CONFIG_CATEGORIES, - MAPPING_CONFIG, - ENDPOINT, - forFirstName, - forLastName, -} = require('./config'); -const { - constructPayload, - defaultPostRequestConfig, - removeUndefinedAndNullValues, - defaultRequestConfig, - flattenJson, - simpleProcessRouterDest, -} = require('../../util'); -const { JSON_MIME_TYPE } = require('../../util/constant'); - -const responseBuilderSimple = (message, category, destination) => { - const payload = constructPayload(message, MAPPING_CONFIG[category.name]); - const response = defaultRequestConfig(); - const { stream, apiKey } = destination.Config; - response.endpoint = `${ENDPOINT}/${stream}?access_token=${apiKey}`; - response.method = defaultPostRequestConfig.requestMethod; - const flattenedPayload = removeUndefinedAndNullValues(flattenJson(payload)); - forFirstName.forEach((key) => { - if (flattenedPayload[key]) { - flattenedPayload.first_name = flattenedPayload[key]; - delete flattenedPayload[key]; - } - }); - forLastName.forEach((key) => { - if (flattenedPayload[key]) { - flattenedPayload.last_name = flattenedPayload[key]; - delete flattenedPayload[key]; - } - }); - response.body.JSON = flattenedPayload; - response.headers = { - 'Content-Type': JSON_MIME_TYPE, - }; - return response; -}; - -const processEvent = (message, destination) => { - if (!message.type) { - throw new InstrumentationError('Event type is required'); - } - const messageType = message.type; - let category; - switch (messageType.toLowerCase()) { - case EventType.IDENTIFY: - category = CONFIG_CATEGORIES.IDENTIFY; - break; - case EventType.PAGE: - case EventType.SCREEN: - category = CONFIG_CATEGORIES.PAGESCREEN; - break; - case EventType.TRACK: - category = CONFIG_CATEGORIES.TRACK; - break; - default: - throw new InstrumentationError(`Event type ${messageType} is not supported`); - } - // build the response - return responseBuilderSimple(message, category, destination); -}; - -const process = (event) => processEvent(event.message, event.destination); - -const processRouterDest = async (inputs, reqMetadata) => { - const respList = await simpleProcessRouterDest(inputs, process, reqMetadata); - return respList; -}; - -module.exports = { process, processRouterDest }; diff --git a/test/integrations/destinations/lytics/processor/data.ts b/test/integrations/destinations/lytics/processor/data.ts index 344eecc3cd..dd5511140a 100644 --- a/test/integrations/destinations/lytics/processor/data.ts +++ b/test/integrations/destinations/lytics/processor/data.ts @@ -143,6 +143,7 @@ export const data = [ body: { JSON: { _e: 'Order Completed', + anonymous_id: '4eb021e9-a2af-4926-ae82-fe996d12f3c5', checkout_id: 'what is checkout id here??', coupon: 'APPARELSALE', currency: 'GBP', @@ -190,6 +191,7 @@ export const data = [ 'products[2].url': 'https://www.example.com/product/offer-t-shirt', 'products[2].value': 12.99, 'products[2].variant': 'Black', + user_id: 'rudder123', revenue: 31.98, shipping: 4, value: 31.98, @@ -293,6 +295,7 @@ export const data = [ params: {}, body: { JSON: { + anonymous_id: '4eb021e9-a2af-4926-ae82-fe996d12f3c5', user_id: 'rudder123', 'company.id': 'abc123', createdAt: 'Thu Mar 24 2016 17:46:45 GMT+0000 (UTC)', @@ -407,6 +410,7 @@ export const data = [ params: {}, body: { JSON: { + anonymous_id: '4eb021e9-a2af-4926-ae82-fe996d12f3c5', user_id: 'rudder123', 'company.id': 'abc123', createdAt: 'Thu Mar 24 2016 17:46:45 GMT+0000 (UTC)', @@ -514,6 +518,7 @@ export const data = [ params: {}, body: { JSON: { + anonymous_id: '4eb021e9-a2af-4926-ae82-fe996d12f3c5', user_id: 'rudder123', 'company.id': 'abc123', createdAt: 'Thu Mar 24 2016 17:46:45 GMT+0000 (UTC)', @@ -626,6 +631,7 @@ export const data = [ email: 'rudderTest@gmail.com', name: 'Rudder Test', plan: 'Enterprise', + anonymous_id: '4eb021e9-a2af-4926-ae82-fe996d12f3c5', }, XML: {}, JSON_ARRAY: {}, @@ -732,6 +738,7 @@ export const data = [ email: 'rudderTest@gmail.com', name: 'Rudder Test', plan: 'Enterprise', + anonymous_id: '4eb021e9-a2af-4926-ae82-fe996d12f3c5', }, XML: {}, JSON_ARRAY: {}, @@ -1116,6 +1123,7 @@ export const data = [ revenue: 31.98, shipping: 4, value: 31.98, + user_id: 'rudder123', }, XML: {}, JSON_ARRAY: {}, @@ -1209,11 +1217,13 @@ export const data = [ body: { JSON: { event: 'ApplicationLoaded', + anonymous_id: '00000000000000000000000000', path: '', referrer: '', search: '', title: '', url: '', + user_id: '12345', }, XML: {}, JSON_ARRAY: {}, @@ -1307,11 +1317,13 @@ export const data = [ body: { JSON: { event: 'ApplicationLoaded', + anonymous_id: '00000000000000000000000000', path: '', referrer: '', search: '', title: '', url: '', + user_id: '12345', }, XML: {}, JSON_ARRAY: {}, @@ -1414,6 +1426,7 @@ export const data = [ params: {}, body: { JSON: { + anonymous_id: '4eb021e9-a2af-4926-ae82-fe996d12f3c5', user_id: 'rudder123', 'company.id': 'abc123', createdAt: 'Thu Mar 24 2016 17:46:45 GMT+0000 (UTC)', @@ -1440,4 +1453,113 @@ export const data = [ }, }, }, + { + name: 'lytics', + description: 'Test 11: user_id is mapped to userIdOnly', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + anonymousId: '4eb021e9-a2af-4926-ae82-fe996d12f3c5', + channel: 'web', + context: { + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.1.6', + }, + library: { name: 'RudderLabs JavaScript SDK', version: '1.1.6' }, + locale: 'en-GB', + os: { name: '', version: '' }, + page: { + path: '/testing/script-test.html', + referrer: '', + search: '', + title: '', + url: 'http://localhost:3243/testing/script-test.html', + }, + screen: { density: 2 }, + traits: { + company: { id: 'abc123' }, + createdAt: 'Thu Mar 24 2016 17:46:45 GMT+0000 (UTC)', + email: 'rudderTest@gmail.com', + name: 'Rudder Test', + plan: 'Enterprise', + firstName: 'Rudderstack', + lastname: 'Test', + }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.80 Safari/537.36', + }, + integrations: { All: true }, + messageId: 'e108eb05-f6cd-4624-ba8c-568f2e2b3f92', + originalTimestamp: '2020-10-16T08:26:14.938Z', + receivedAt: '2020-10-16T13:56:14.945+05:30', + request_ip: '[::1]', + sentAt: '2020-10-16T08:26:14.939Z', + timestamp: '2020-10-16T13:56:14.944+05:30', + type: 'identify', + }, + destination: { + DestinationDefinition: { Config: { cdkV2Enabled: true } }, + Config: { apiKey: 'dummyApiKey', stream: 'default' }, + Enabled: true, + Transformations: [], + IsProcessorEnabled: true, + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + method: 'POST', + }, + pathSuffix: '', + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://api.lytics.io/collect/json/default?access_token=dummyApiKey', + headers: { 'Content-Type': 'application/json' }, + params: {}, + body: { + JSON: { + anonymous_id: '4eb021e9-a2af-4926-ae82-fe996d12f3c5', + 'company.id': 'abc123', + createdAt: 'Thu Mar 24 2016 17:46:45 GMT+0000 (UTC)', + email: 'rudderTest@gmail.com', + name: 'Rudder Test', + plan: 'Enterprise', + first_name: 'Rudderstack', + last_name: 'Test', + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + statusCode: 200, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + }, ]; diff --git a/test/integrations/destinations/lytics/router/data.ts b/test/integrations/destinations/lytics/router/data.ts deleted file mode 100644 index e5d9adae5c..0000000000 --- a/test/integrations/destinations/lytics/router/data.ts +++ /dev/null @@ -1,299 +0,0 @@ -export const data = [ - { - name: 'lytics', - description: 'Test 0', - feature: 'router', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - input: [ - { - message: { - anonymousId: '4eb021e9-a2af-4926-ae82-fe996d12f3c5', - channel: 'web', - context: { - app: { - build: '1.0.0', - name: 'RudderLabs JavaScript SDK', - namespace: 'com.rudderlabs.javascript', - version: '1.1.6', - }, - library: { name: 'RudderLabs JavaScript SDK', version: '1.1.6' }, - locale: 'en-GB', - os: { name: '', version: '' }, - page: { - path: '/testing/script-test.html', - referrer: '', - search: '', - title: '', - url: 'http://localhost:3243/testing/script-test.html', - }, - screen: { density: 2 }, - traits: { - company: { id: 'abc123' }, - createdAt: 'Thu Mar 24 2016 17:46:45 GMT+0000 (UTC)', - email: 'rudderTest@gmail.com', - name: 'Rudder Test', - plan: 'Enterprise', - }, - userAgent: - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.80 Safari/537.36', - }, - event: 'Order Completed', - integrations: { All: true }, - messageId: 'a0adfab9-baf7-4e09-a2ce-bbe2844c324a', - timestamp: '2020-10-16T08:10:12.782Z', - properties: { - checkout_id: 'what is checkout id here??', - coupon: 'APPARELSALE', - currency: 'GBP', - order_id: 'transactionId', - products: [ - { - brand: '', - category: 'Merch', - currency: 'GBP', - image_url: 'https://www.example.com/product/bacon-jam.jpg', - name: 'Food/Drink', - position: 1, - price: 3, - product_id: 'product-bacon-jam', - quantity: 2, - sku: 'sku-1', - typeOfProduct: 'Food', - url: 'https://www.example.com/product/bacon-jam', - value: 6, - variant: 'Extra topped', - }, - { - brand: 'Levis', - category: 'Merch', - currency: 'GBP', - image_url: 'https://www.example.com/product/t-shirt.jpg', - name: 'T-Shirt', - position: 2, - price: 12.99, - product_id: 'product-t-shirt', - quantity: 1, - sku: 'sku-2', - typeOfProduct: 'Shirt', - url: 'https://www.example.com/product/t-shirt', - value: 12.99, - variant: 'White', - }, - { - brand: 'Levis', - category: 'Merch', - coupon: 'APPARELSALE', - currency: 'GBP', - image_url: 'https://www.example.com/product/offer-t-shirt.jpg', - name: 'T-Shirt-on-offer', - position: 1, - price: 12.99, - product_id: 'offer-t-shirt', - quantity: 1, - sku: 'sku-3', - typeOfProduct: 'Shirt', - url: 'https://www.example.com/product/offer-t-shirt', - value: 12.99, - variant: 'Black', - }, - ], - revenue: 31.98, - shipping: 4, - value: 31.98, - }, - receivedAt: '2020-10-16T13:40:12.792+05:30', - request_ip: '[::1]', - sentAt: '2020-10-16T08:10:12.783Z', - type: 'track', - userId: 'rudder123', - }, - metadata: { jobId: 1, userId: 'u1' }, - destination: { - Config: { apiKey: 'dummyApiKey', stream: 'default' }, - Enabled: true, - Transformations: [], - IsProcessorEnabled: true, - }, - }, - { - message: { - anonymousId: '4eb021e9-a2af-4926-ae82-fe996d12f3c5', - channel: 'web', - context: { - app: { - build: '1.0.0', - name: 'RudderLabs JavaScript SDK', - namespace: 'com.rudderlabs.javascript', - version: '1.1.6', - }, - library: { name: 'RudderLabs JavaScript SDK', version: '1.1.6' }, - locale: 'en-GB', - os: { name: '', version: '' }, - page: { - path: '/testing/script-test.html', - referrer: '', - search: '', - title: '', - url: 'http://localhost:3243/testing/script-test.html', - }, - screen: { density: 2 }, - traits: { - company: { id: 'abc123' }, - createdAt: 'Thu Mar 24 2016 17:46:45 GMT+0000 (UTC)', - email: 'rudderTest@gmail.com', - name: 'Rudder Test', - plan: 'Enterprise', - }, - userAgent: - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.80 Safari/537.36', - }, - integrations: { All: true }, - messageId: 'e108eb05-f6cd-4624-ba8c-568f2e2b3f92', - originalTimestamp: '2020-10-16T08:26:14.938Z', - receivedAt: '2020-10-16T13:56:14.945+05:30', - request_ip: '[::1]', - sentAt: '2020-10-16T08:26:14.939Z', - timestamp: '2020-10-16T13:56:14.944+05:30', - type: 'identify', - userId: 'rudder123', - }, - metadata: { jobId: 2, userId: 'u1' }, - destination: { - Config: { apiKey: 'dummyApiKey', stream: 'default' }, - Enabled: true, - Transformations: [], - IsProcessorEnabled: true, - }, - }, - ], - destType: 'lytics', - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: [ - { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://api.lytics.io/collect/json/default?access_token=dummyApiKey', - headers: { 'Content-Type': 'application/json' }, - params: {}, - body: { - JSON: { - _e: 'Order Completed', - checkout_id: 'what is checkout id here??', - coupon: 'APPARELSALE', - currency: 'GBP', - order_id: 'transactionId', - 'products[0].brand': '', - 'products[0].category': 'Merch', - 'products[0].currency': 'GBP', - 'products[0].image_url': 'https://www.example.com/product/bacon-jam.jpg', - 'products[0].name': 'Food/Drink', - 'products[0].position': 1, - 'products[0].price': 3, - 'products[0].product_id': 'product-bacon-jam', - 'products[0].quantity': 2, - 'products[0].sku': 'sku-1', - 'products[0].typeOfProduct': 'Food', - 'products[0].url': 'https://www.example.com/product/bacon-jam', - 'products[0].value': 6, - 'products[0].variant': 'Extra topped', - 'products[1].brand': 'Levis', - 'products[1].category': 'Merch', - 'products[1].currency': 'GBP', - 'products[1].image_url': 'https://www.example.com/product/t-shirt.jpg', - 'products[1].name': 'T-Shirt', - 'products[1].position': 2, - 'products[1].price': 12.99, - 'products[1].product_id': 'product-t-shirt', - 'products[1].quantity': 1, - 'products[1].sku': 'sku-2', - 'products[1].typeOfProduct': 'Shirt', - 'products[1].url': 'https://www.example.com/product/t-shirt', - 'products[1].value': 12.99, - 'products[1].variant': 'White', - 'products[2].brand': 'Levis', - 'products[2].category': 'Merch', - 'products[2].coupon': 'APPARELSALE', - 'products[2].currency': 'GBP', - 'products[2].image_url': 'https://www.example.com/product/offer-t-shirt.jpg', - 'products[2].name': 'T-Shirt-on-offer', - 'products[2].position': 1, - 'products[2].price': 12.99, - 'products[2].product_id': 'offer-t-shirt', - 'products[2].quantity': 1, - 'products[2].sku': 'sku-3', - 'products[2].typeOfProduct': 'Shirt', - 'products[2].url': 'https://www.example.com/product/offer-t-shirt', - 'products[2].value': 12.99, - 'products[2].variant': 'Black', - revenue: 31.98, - shipping: 4, - value: 31.98, - }, - XML: {}, - JSON_ARRAY: {}, - FORM: {}, - }, - files: {}, - }, - metadata: [{ jobId: 1, userId: 'u1' }], - batched: false, - statusCode: 200, - destination: { - Config: { apiKey: 'dummyApiKey', stream: 'default' }, - Enabled: true, - Transformations: [], - IsProcessorEnabled: true, - }, - }, - { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://api.lytics.io/collect/json/default?access_token=dummyApiKey', - headers: { 'Content-Type': 'application/json' }, - params: {}, - body: { - JSON: { - user_id: 'rudder123', - 'company.id': 'abc123', - createdAt: 'Thu Mar 24 2016 17:46:45 GMT+0000 (UTC)', - email: 'rudderTest@gmail.com', - name: 'Rudder Test', - plan: 'Enterprise', - }, - XML: {}, - JSON_ARRAY: {}, - FORM: {}, - }, - files: {}, - }, - metadata: [{ jobId: 2, userId: 'u1' }], - batched: false, - statusCode: 200, - destination: { - Config: { apiKey: 'dummyApiKey', stream: 'default' }, - Enabled: true, - Transformations: [], - IsProcessorEnabled: true, - }, - }, - ], - }, - }, - }, - }, -];