diff --git a/src/v0/destinations/facebook_pixel/config.js b/src/v0/destinations/facebook_pixel/config.js index 09be8e043d..99c3c70b2d 100644 --- a/src/v0/destinations/facebook_pixel/config.js +++ b/src/v0/destinations/facebook_pixel/config.js @@ -15,31 +15,37 @@ const CONFIG_CATEGORIES = { PRODUCT_LIST_VIEWED: { standard: true, type: 'product list viewed', + eventName: 'ViewContent', name: 'FBPIXELPSimpleCustomConfig', }, PRODUCT_VIEWED: { standard: true, type: 'product viewed', + eventName: 'ViewContent', name: 'FBPIXELPSimpleCustomConfig', }, PRODUCT_ADDED: { standard: true, type: 'product added', + eventName: 'AddToCart', name: 'FBPIXELPSimpleCustomConfig', }, ORDER_COMPLETED: { standard: true, type: 'order completed', + eventName: 'Purchase', name: 'FBPIXELPSimpleCustomConfig', }, PRODUCTS_SEARCHED: { standard: true, type: 'products searched', + eventName: 'Search', name: 'FBPIXELPSimpleCustomConfig', }, CHECKOUT_STARTED: { standard: true, type: 'checkout started', + eventName: 'InitiateCheckout', name: 'FBPIXELPSimpleCustomConfig', }, OTHER_STANDARD: { @@ -50,9 +56,15 @@ const CONFIG_CATEGORIES = { PAGE_VIEW: { standard: true, type: 'page_view', + eventName: 'PageView', + name: 'FBPIXELPSimpleCustomConfig', + }, + PAGE: { + standard: false, + type: 'page', + eventName: 'PageView', name: 'FBPIXELPSimpleCustomConfig', }, - PAGE: { standard: false, type: 'page', name: 'FBPIXELPSimpleCustomConfig' }, }; const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname); @@ -67,6 +79,21 @@ const ACTION_SOURCES_VALUES = [ 'other', ]; +const OTHER_STANDARD_EVENTS = [ + 'AddToWishlist', + 'AddPaymentInfo', + 'Lead', + 'CompleteRegistration', + 'Contact', + 'CustomizeProduct', + 'Donate', + 'FindLocation', + 'Schedule', + 'StartTrial', + 'SubmitApplication', + 'Subscribe', +]; + const FB_PIXEL_DEFAULT_EXCLUSION = ['opt_out', 'event_id', 'action_source']; const STANDARD_ECOMM_EVENTS_TYPE = [ CONFIG_CATEGORIES.PRODUCT_LIST_VIEWED.type, @@ -83,5 +110,6 @@ module.exports = { ACTION_SOURCES_VALUES, FB_PIXEL_DEFAULT_EXCLUSION, STANDARD_ECOMM_EVENTS_TYPE, + OTHER_STANDARD_EVENTS, DESTINATION: 'FACEBOOK_PIXEL', }; diff --git a/src/v0/destinations/facebook_pixel/transform.js b/src/v0/destinations/facebook_pixel/transform.js index c81718dea0..48c4c30563 100644 --- a/src/v0/destinations/facebook_pixel/transform.js +++ b/src/v0/destinations/facebook_pixel/transform.js @@ -17,6 +17,7 @@ const { getIntegrationsObj, getValidDynamicFormConfig, simpleProcessRouterDest, + getHashFromArray, } = require('../../util'); const { @@ -28,13 +29,19 @@ const { handleProductListViewed, handleOrder, formingFinalResponse, + populateCustomDataBasedOnCategory, + getCategoryFromEvent, } = require('./utils'); const { InstrumentationError, ConfigurationError } = require('../../util/errorTypes'); -const responseBuilderSimple = (message, category, destination, categoryToContent) => { - const { Config } = destination; +const responseBuilderSimple = (message, category, destination) => { + const { Config, ID } = destination; const { pixelId, accessToken } = Config; + let { categoryToContent } = Config; + if (Array.isArray(categoryToContent)) { + categoryToContent = getValidDynamicFormConfig(categoryToContent, 'from', 'to', 'FB_PIXEL', ID); + } if (!pixelId) { throw new ConfigurationError('Pixel Id not found. Aborting'); @@ -59,12 +66,15 @@ const responseBuilderSimple = (message, category, destination, categoryToContent const userData = fetchUserData(message, Config); - let customData = {}; - let commonData = {}; - - commonData = constructPayload(message, MAPPING_CONFIG[CONFIG_CATEGORIES.COMMON.name], 'fb_pixel'); + const commonData = constructPayload( + message, + MAPPING_CONFIG[CONFIG_CATEGORIES.COMMON.name], + 'fb_pixel', + ); commonData.action_source = getActionSource(commonData, message?.channel); + let customData = {}; + if (category.type !== 'identify') { customData = flattenJson( extractCustomFields(message, customData, ['properties'], FB_PIXEL_DEFAULT_EXCLUSION), @@ -82,74 +92,20 @@ const responseBuilderSimple = (message, category, destination, categoryToContent customData, blacklistPiiProperties, whitelistPiiProperties, - category.standard, integrationsObj, ); message.properties = message.properties || {}; if (category.standard) { - switch (category.type) { - case 'product list viewed': - customData = { - ...customData, - ...handleProductListViewed(message, categoryToContent), - }; - commonData.event_name = 'ViewContent'; - break; - case 'product viewed': - customData = { - ...customData, - ...handleProduct(message, categoryToContent, valueFieldIdentifier), - }; - commonData.event_name = 'ViewContent'; - break; - case 'product added': - customData = { - ...customData, - ...handleProduct(message, categoryToContent, valueFieldIdentifier), - }; - commonData.event_name = 'AddToCart'; - break; - case 'order completed': - customData = { - ...customData, - ...handleOrder(message, categoryToContent), - }; - commonData.event_name = 'Purchase'; - break; - case 'products searched': { - customData = { - ...customData, - ...handleSearch(message), - }; - commonData.event_name = 'Search'; - break; - } - case 'checkout started': { - const orderPayload = handleOrder(message, categoryToContent); - delete orderPayload.content_name; - customData = { - ...customData, - ...orderPayload, - }; - commonData.event_name = 'InitiateCheckout'; - break; - } - case 'page_view': // executed when sending track calls but with standard type PageView - case 'page': // executed when page call is done with standard PageView turned on - customData = { ...customData }; - commonData.event_name = 'PageView'; - break; - case 'otherStandard': - customData = { ...customData }; - commonData.event_name = category.event; - break; - default: - throw new InstrumentationError( - `${category.standard} type of standard event does not exist`, - ); - } + commonData.event_name = category.eventName; + customData = populateCustomDataBasedOnCategory( + customData, + message, + category, + categoryToContent, + valueFieldIdentifier, + ); customData.currency = STANDARD_ECOMM_EVENTS_TYPE.includes(category.type) - ? message.properties.currency || 'USD' + ? message.properties?.currency || 'USD' : undefined; } else { const { type } = category; @@ -159,7 +115,7 @@ const responseBuilderSimple = (message, category, destination, categoryToContent : `Viewed a ${type}`; } if (type === 'simple track') { - customData.value = message.properties ? message.properties.revenue : undefined; + customData.value = message.properties?.revenue; delete customData.revenue; } } @@ -189,57 +145,6 @@ const responseBuilderSimple = (message, category, destination, categoryToContent ); }; -function getCategoryFromEvent(checkEvent) { - let category; - switch (checkEvent) { - case CONFIG_CATEGORIES.PRODUCT_LIST_VIEWED.type: - case 'ViewContent': - category = CONFIG_CATEGORIES.PRODUCT_LIST_VIEWED; - break; - case CONFIG_CATEGORIES.PRODUCT_VIEWED.type: - category = CONFIG_CATEGORIES.PRODUCT_VIEWED; - break; - case CONFIG_CATEGORIES.PRODUCT_ADDED.type: - case 'AddToCart': - category = CONFIG_CATEGORIES.PRODUCT_ADDED; - break; - case CONFIG_CATEGORIES.ORDER_COMPLETED.type: - case 'Purchase': - category = CONFIG_CATEGORIES.ORDER_COMPLETED; - break; - case CONFIG_CATEGORIES.PRODUCTS_SEARCHED.type: - case 'Search': - category = CONFIG_CATEGORIES.PRODUCTS_SEARCHED; - break; - case CONFIG_CATEGORIES.CHECKOUT_STARTED.type: - case 'InitiateCheckout': - category = CONFIG_CATEGORIES.CHECKOUT_STARTED; - break; - case 'AddToWishlist': - case 'AddPaymentInfo': - case 'Lead': - case 'CompleteRegistration': - case 'Contact': - case 'CustomizeProduct': - case 'Donate': - case 'FindLocation': - case 'Schedule': - case 'StartTrial': - case 'SubmitApplication': - case 'Subscribe': - category = CONFIG_CATEGORIES.OTHER_STANDARD; - category.event = checkEvent; - break; - case 'PageView': - category = CONFIG_CATEGORIES.PAGE_VIEW; - break; - default: - category = CONFIG_CATEGORIES.SIMPLE_TRACK; - break; - } - return category; -} - const processEvent = (message, destination) => { if (!message.type) { throw new InstrumentationError("'type' is missing"); @@ -265,7 +170,7 @@ const processEvent = (message, destination) => { } let eventsToEvents; - if (destination.Config.eventsToEvents) + if (Array.isArray(destination.Config.eventsToEvents)) { eventsToEvents = getValidDynamicFormConfig( destination.Config.eventsToEvents, 'from', @@ -273,21 +178,12 @@ const processEvent = (message, destination) => { 'FB_PIXEL', destination.ID, ); - let categoryToContent; - if (destination.Config.categoryToContent) - categoryToContent = getValidDynamicFormConfig( - destination.Config.categoryToContent, - 'from', - 'to', - 'FB_PIXEL', - destination.ID, - ); + } + const { advancedMapping } = destination.Config; - let standard; - let standardTo = ''; - let checkEvent; const messageType = message.type.toLowerCase(); let category; + let mappedEvent; switch (messageType) { case EventType.IDENTIFY: if (advancedMapping) { @@ -309,24 +205,17 @@ const processEvent = (message, destination) => { if (typeof message.event !== 'string') { throw new InstrumentationError('event name should be string'); } - standard = eventsToEvents; - if (standard) { - standardTo = standard.reduce((filtered, standards) => { - if (standards.from.toLowerCase() === message.event.toLowerCase()) { - filtered = standards.to; - } - return filtered; - }, ''); + if (eventsToEvents) { + const eventMappingHash = getHashFromArray(eventsToEvents); + mappedEvent = eventMappingHash[message.event.toLowerCase()]; } - checkEvent = standardTo !== '' ? standardTo : message.event.toLowerCase(); - - category = getCategoryFromEvent(checkEvent); + category = getCategoryFromEvent(mappedEvent || message.event.toLowerCase()); break; default: throw new InstrumentationError(`Message type ${messageType} not supported`); } // build the response - return responseBuilderSimple(message, category, destination, categoryToContent); + return responseBuilderSimple(message, category, destination); }; const process = (event) => processEvent(event.message, event.destination); diff --git a/src/v0/destinations/facebook_pixel/utils.js b/src/v0/destinations/facebook_pixel/utils.js index e1347278bf..7e4a644a4a 100644 --- a/src/v0/destinations/facebook_pixel/utils.js +++ b/src/v0/destinations/facebook_pixel/utils.js @@ -7,8 +7,14 @@ const { constructPayload, defaultPostRequestConfig, defaultRequestConfig, + getHashFromArray, } = require('../../util'); -const { ACTION_SOURCES_VALUES, CONFIG_CATEGORIES, MAPPING_CONFIG } = require('./config'); +const { + ACTION_SOURCES_VALUES, + CONFIG_CATEGORIES, + MAPPING_CONFIG, + OTHER_STANDARD_EVENTS, +} = require('./config'); const { InstrumentationError, TransformationError } = require('../../util/errorTypes'); @@ -18,7 +24,7 @@ const { InstrumentationError, TransformationError } = require('../../util/errorT */ const formatRevenue = (revenue) => { - const formattedRevenue = parseFloat(parseFloat(revenue || 0).toFixed(2)); + const formattedRevenue = parseFloat(parseFloat(revenue || '0').toFixed(2)); if (!Number.isNaN(formattedRevenue)) { return formattedRevenue; } @@ -29,7 +35,7 @@ const formatRevenue = (revenue) => { * * @param {*} message Rudder Payload * @param {*} defaultValue product / product_group - * @param {*} categoryToContent [ { from: 'clothing', to: 'product' } ] + * @param {*} categoryToContent example: [ { from: 'clothing', to: 'product' } ] * * We will be mapping properties.category to user provided content else taking the default value as per ecomm spec * If category is clothing it will be set to ["product"] @@ -37,7 +43,6 @@ const formatRevenue = (revenue) => { * - https://developers.facebook.com/docs/facebook-pixel/reference/#object-properties */ const getContentType = (message, defaultValue, categoryToContent) => { - let tempCategoryToContent = categoryToContent; const { properties } = message; const integrationsObj = getIntegrationsObj(message, 'fb_pixel'); @@ -51,27 +56,19 @@ const getContentType = (message, defaultValue, categoryToContent) => { if (products && products.length > 0 && Array.isArray(products) && isObject(products[0])) { category = products[0].category; } - } else { - if (tempCategoryToContent === undefined) { - tempCategoryToContent = []; - } - const mapped = tempCategoryToContent; - const mappedTo = mapped.reduce((filtered, map) => { - let filter = filtered; - if (map.from === category) { - filter = map.to; - } - return filter; - }, ''); - if (mappedTo.length > 0) { - return mappedTo; + } + + if (Array.isArray(categoryToContent) && category) { + const categoryToContentHash = getHashFromArray(categoryToContent, 'from', 'to', false); + if (categoryToContentHash[category]) { + return categoryToContentHash[category]; } } + return defaultValue; }; /** This function transforms the payloads according to the config settings and adds, removes or hashes pii data. -Also checks if it is a standard event and sends properties only if it is mentioned in our configs. @param message --> the rudder payload { @@ -119,8 +116,8 @@ Also checks if it is a standard event and sends properties only if it is mention [ { whitelistPiiProperties: 'email' } ] // sets email -@param eventCustomProperties --> -[ { eventCustomProperties: 'leadId' } ] // leadId if present will be set +@param integrationsObj --> +{ hashed: true } */ @@ -190,7 +187,7 @@ const transformedPayloadData = ( /** * * @param {*} message - * @returns fbc parameter which is a combined string of the parameters below + * @returns string which is fbc parameter * * version : "fb" (default) * @@ -305,7 +302,7 @@ const fetchUserData = (message, Config) => { /** * * @param {*} message Rudder element - * @param {*} categoryToContent [ { from: 'clothing', to: 'product' } ] + * @param {*} categoryToContent example: [ { from: 'clothing', to: 'product' } ] * * Handles order completed and checkout started types of specific events */ @@ -320,7 +317,7 @@ const handleOrder = (message, categoryToContent) => { if (products) { if (products.length > 0 && Array.isArray(products)) { products.forEach((singleProduct) => { - const pId = singleProduct.product_id || singleProduct.sku || singleProduct.id; + const pId = singleProduct?.product_id || singleProduct?.sku || singleProduct?.id; if (pId) { contentIds.push(pId); // required field for content @@ -353,7 +350,7 @@ const handleOrder = (message, categoryToContent) => { /** * * @param {*} message Rudder element - * @param {*} categoryToContent [ { from: 'clothing', to: 'product' } ] + * @param {*} categoryToContent example [ { from: 'clothing', to: 'product' } ] * * Handles product list viewed */ @@ -405,7 +402,7 @@ const handleProductListViewed = (message, categoryToContent) => { /** * * @param {*} message Rudder Payload - * @param {*} categoryToContent [ { from: 'clothing', to: 'product' } ] + * @param {*} categoryToContent Example: [ { from: 'clothing', to: 'product' } ] * @param {*} valueFieldIdentifier it can be either value or price which will be matched from properties and assigned to value for fb payload */ const handleProduct = (message, categoryToContent, valueFieldIdentifier) => { @@ -476,6 +473,103 @@ const handleSearch = (message) => { }; }; +const populateCustomDataBasedOnCategory = ( + customData, + message, + category, + categoryToContent, + valueFieldIdentifier, +) => { + let updatedCustomData; + switch (category.type) { + case 'product list viewed': + updatedCustomData = { + ...customData, + ...handleProductListViewed(message, categoryToContent), + }; + break; + case 'product viewed': + case 'product added': + updatedCustomData = { + ...customData, + ...handleProduct(message, categoryToContent, valueFieldIdentifier), + }; + break; + case 'order completed': + updatedCustomData = { + ...customData, + ...handleOrder(message, categoryToContent), + }; + break; + case 'products searched': { + updatedCustomData = { + ...customData, + ...handleSearch(message), + }; + break; + } + case 'checkout started': { + const orderPayload = handleOrder(message, categoryToContent); + delete orderPayload.content_name; + updatedCustomData = { + ...customData, + ...orderPayload, + }; + break; + } + case 'page_view': // executed when sending track calls but with standard type PageView + case 'page': // executed when page call is done with standard PageView turned on + case 'otherStandard': + updatedCustomData = { ...customData }; + break; + default: + throw new InstrumentationError(`${category.standard} type of standard event does not exist`); + } + return updatedCustomData; +}; + +const getCategoryFromEvent = (eventName) => { + let category; + switch (eventName) { + case CONFIG_CATEGORIES.PRODUCT_LIST_VIEWED.type: + case CONFIG_CATEGORIES.PRODUCT_LIST_VIEWED.eventName: + category = CONFIG_CATEGORIES.PRODUCT_LIST_VIEWED; + break; + case CONFIG_CATEGORIES.PRODUCT_VIEWED.type: + category = CONFIG_CATEGORIES.PRODUCT_VIEWED; + break; + case CONFIG_CATEGORIES.PRODUCT_ADDED.type: + case CONFIG_CATEGORIES.PRODUCT_ADDED.eventName: + category = CONFIG_CATEGORIES.PRODUCT_ADDED; + break; + case CONFIG_CATEGORIES.ORDER_COMPLETED.type: + case CONFIG_CATEGORIES.ORDER_COMPLETED.eventName: + category = CONFIG_CATEGORIES.ORDER_COMPLETED; + break; + case CONFIG_CATEGORIES.PRODUCTS_SEARCHED.type: + case CONFIG_CATEGORIES.PRODUCTS_SEARCHED.eventName: + category = CONFIG_CATEGORIES.PRODUCTS_SEARCHED; + break; + case CONFIG_CATEGORIES.CHECKOUT_STARTED.type: + case CONFIG_CATEGORIES.CHECKOUT_STARTED.eventName: + category = CONFIG_CATEGORIES.CHECKOUT_STARTED; + break; + case CONFIG_CATEGORIES.PAGE_VIEW.eventName: + category = CONFIG_CATEGORIES.PAGE_VIEW; + break; + default: + category = CONFIG_CATEGORIES.SIMPLE_TRACK; + break; + } + + if (OTHER_STANDARD_EVENTS.includes(eventName)) { + category = CONFIG_CATEGORIES.OTHER_STANDARD; + category.eventName = eventName; + } + + return category; +}; + const formingFinalResponse = ( userData, commonData, @@ -521,4 +615,6 @@ module.exports = { handleProductListViewed, handleOrder, formingFinalResponse, + populateCustomDataBasedOnCategory, + getCategoryFromEvent, }; diff --git a/src/v0/destinations/mp/util.js b/src/v0/destinations/mp/util.js index 35c530855e..409092106c 100644 --- a/src/v0/destinations/mp/util.js +++ b/src/v0/destinations/mp/util.js @@ -214,11 +214,19 @@ const groupEventsByEndpoint = (events) => { const generateBatchedPayloadForArray = (events) => { const { batchedRequest } = defaultBatchRequestConfig(); + const firstEvent = events[0]; + batchedRequest.endpoint = firstEvent.endpoint; + batchedRequest.headers = firstEvent.headers; + batchedRequest.params = firstEvent.params; + const batchResponseList = events.flatMap((event) => JSON.parse(event.body.JSON_ARRAY.batch)); - batchedRequest.body.JSON_ARRAY = { batch: JSON.stringify(batchResponseList) }; - batchedRequest.endpoint = events[0].endpoint; - batchedRequest.headers = events[0].headers; - batchedRequest.params = events[0].params; + // Gzipping the payload for /import endpoint + if (firstEvent.endpoint.includes('import')) { + batchedRequest.body.GZIP = { payload: JSON.stringify(batchResponseList) }; + } else { + batchedRequest.body.JSON_ARRAY = { batch: JSON.stringify(batchResponseList) }; + } + return batchedRequest; }; @@ -286,6 +294,7 @@ module.exports = { createIdentifyResponse, isImportAuthCredentialsAvailable, groupEventsByEndpoint, + generateBatchedPayloadForArray, batchEvents, combineBatchRequestsWithSameJobIds, }; diff --git a/src/v0/destinations/mp/util.test.js b/src/v0/destinations/mp/util.test.js index 43594fba6d..c707d09dfe 100644 --- a/src/v0/destinations/mp/util.test.js +++ b/src/v0/destinations/mp/util.test.js @@ -1,8 +1,8 @@ const { combineBatchRequestsWithSameJobIds, - groupEventsByType, groupEventsByEndpoint, batchEvents, + generateBatchedPayloadForArray, } = require('./util'); const destinationMock = { @@ -488,4 +488,81 @@ describe('Mixpanel utils test', () => { expect(combineBatchRequestsWithSameJobIds(input)).toEqual(expectedOutput); }); }); + + describe('Unit test cases for generateBatchedPayloadForArray', () => { + it('should generate a batched payload with GZIP payload for /import endpoint when given an array of events', () => { + const events = [ + { + body: { JSON_ARRAY: { batch: '[{"event": "event1"}]' } }, + endpoint: '/import', + headers: { 'Content-Type': 'application/json' }, + params: {}, + }, + { + body: { JSON_ARRAY: { batch: '[{"event": "event2"}]' } }, + endpoint: '/import', + headers: { 'Content-Type': 'application/json' }, + params: {}, + }, + ]; + const expectedBatchedRequest = { + body: { + FORM: {}, + JSON: {}, + JSON_ARRAY: {}, + XML: {}, + GZIP: { + payload: '[{"event":"event1"},{"event":"event2"}]', + }, + }, + endpoint: '/import', + files: {}, + headers: { 'Content-Type': 'application/json' }, + method: 'POST', + params: {}, + type: 'REST', + version: '1', + }; + + const result = generateBatchedPayloadForArray(events); + + expect(result).toEqual(expectedBatchedRequest); + }); + + it('should generate a batched payload with JSON_ARRAY body when given an array of events', () => { + const events = [ + { + body: { JSON_ARRAY: { batch: '[{"event": "event1"}]' } }, + endpoint: '/endpoint', + headers: { 'Content-Type': 'application/json' }, + params: {}, + }, + { + body: { JSON_ARRAY: { batch: '[{"event": "event2"}]' } }, + endpoint: '/endpoint', + headers: { 'Content-Type': 'application/json' }, + params: {}, + }, + ]; + const expectedBatchedRequest = { + body: { + FORM: {}, + JSON: {}, + JSON_ARRAY: { batch: '[{"event":"event1"},{"event":"event2"}]' }, + XML: {}, + }, + endpoint: '/endpoint', + files: {}, + headers: { 'Content-Type': 'application/json' }, + method: 'POST', + params: {}, + type: 'REST', + version: '1', + }; + + const result = generateBatchedPayloadForArray(events); + + expect(result).toEqual(expectedBatchedRequest); + }); + }); }); diff --git a/src/v0/destinations/snapchat_conversion/config.js b/src/v0/destinations/snapchat_conversion/config.js index fce2edf8e4..e0126ea3b1 100644 --- a/src/v0/destinations/snapchat_conversion/config.js +++ b/src/v0/destinations/snapchat_conversion/config.js @@ -4,7 +4,8 @@ const ENDPOINT = 'https://tr.snapchat.com/v2/conversion'; const MAX_BATCH_SIZE = 2000; const ConfigCategory = { - COMMON: { name: 'SnapchatCommonConfig' }, + DEFAULT: { name: 'SnapchatTrackDefaultConfig' }, + TRACK_COMMON: { name: 'TrackEventCommonConfig' }, /* E-Commerce Events */ // Ref - https://www.rudderstack.com/docs/rudderstack-api/api-specification/rudderstack-ecommerce-events-specification/ /* Browsing Section */ @@ -80,7 +81,6 @@ module.exports = { ConfigCategory, eventNameMapping, mappingConfig, - trackCommonConfig: mappingConfig[ConfigCategory.COMMON.name], MAX_BATCH_SIZE, - pageTypeToTrackEvent + pageTypeToTrackEvent, }; diff --git a/src/v0/destinations/snapchat_conversion/data/SnapchatCommonConfig.json b/src/v0/destinations/snapchat_conversion/data/SnapchatTrackDefaultConfig.json similarity index 100% rename from src/v0/destinations/snapchat_conversion/data/SnapchatCommonConfig.json rename to src/v0/destinations/snapchat_conversion/data/SnapchatTrackDefaultConfig.json diff --git a/src/v0/destinations/snapchat_conversion/data/TrackEventCommonConfig.json b/src/v0/destinations/snapchat_conversion/data/TrackEventCommonConfig.json new file mode 100644 index 0000000000..39187f47b0 --- /dev/null +++ b/src/v0/destinations/snapchat_conversion/data/TrackEventCommonConfig.json @@ -0,0 +1,90 @@ +[ + { + "destKey": "description", + "sourceKeys": "properties.description", + "required": false + }, + { + "destKey": "brands", + "sourceKeys": "properties.brands", + "required": false, + "metadata": { + "type": "IsArray" + } + }, + { + "destKey": "event_tag", + "sourceKeys": "properties.event_tag", + "required": false + }, + { + "destKey": "click_id", + "sourceKeys": "properties.click_id", + "required": false + }, + { + "destKey": "level", + "sourceKeys": "properties.level", + "required": false + }, + { + "destKey": "uuid_c1", + "sourceKeys": "properties.uuid_c1", + "required": false + }, + { + "destKey": "customer_status", + "sourceKeys": "properties.customer_status", + "required": false + }, + { + "destKey": "data", + "sourceKeys": "properties.data", + "required": false + }, + { + "destKey": "att_status", + "sourceKeys": "properties.att_status", + "required": false + }, + { + "destKey": "sign_up_method", + "sourceKeys": "properties.sign_up_method", + "required": false + }, + { + "destKey": "advertiser_cookie_1", + "sourceKeys": "properties.advertiser_cookie_1", + "required": false + }, + { + "destKey": "delivery_method", + "sourceKeys": "properties.delivery_method", + "required": false + }, + { + "sourceKeys": "context.device.model", + "destKey": "device_model", + "required": false + }, + { + "sourceKeys": "country", + "destKey": "country", + "sourceFromGenericMap": true, + "required": false + }, + { + "sourceKeys": "region", + "destKey": "region", + "sourceFromGenericMap": true, + "required": false + }, + { + "sourceKeys": "context.userAgent", + "destKey": "user_agent", + "metadata": { + "type": "toLower" + }, + "required": false + } +] \ No newline at end of file diff --git a/src/v0/destinations/snapchat_conversion/transform.js b/src/v0/destinations/snapchat_conversion/transform.js index f4360cc458..c48ab5b3f8 100644 --- a/src/v0/destinations/snapchat_conversion/transform.js +++ b/src/v0/destinations/snapchat_conversion/transform.js @@ -110,18 +110,8 @@ const populateHashedValues = (payload, message) => { } return updatedPayload; }; -const getEventProperties = (message) => ({ - description: get(message, 'properties.description'), - brands: Array.isArray(message.properties?.brands) ? get(message, 'properties.brands') : undefined, - customer_status: get(message, 'properties.customer_status'), - uuid_c1: get(message, 'properties.uuid_c1'), - level: get(message, 'properties.level'), - click_id: get(message, 'properties.click_id'), - event_tag: get(message, 'properties.event_tag'), - country: getFieldValueFromMessage(message, 'country'), - region: getFieldValueFromMessage(message, 'region'), - user_agent: message.context?.userAgent?.toString()?.toLowerCase(), -}); +const getEventCommonProperties = (message) => + constructPayload(message, mappingConfig[ConfigCategory.TRACK_COMMON.name]); const validateEventConfiguration = (eventConversionType, pixelId, snapAppId, appId) => { if ((eventConversionType === 'WEB' || eventConversionType === 'OFFLINE') && !pixelId) { throw new ConfigurationError('Pixel Id is required for web and offline events'); @@ -274,7 +264,7 @@ const trackResponseBuilder = (message, { Config }, mappedEvent) => { payload.event_type = eventNameMapping[event.toLowerCase()]; break; default: - payload = constructPayload(message, mappingConfig[ConfigCategory.COMMON.name]); + payload = constructPayload(message, mappingConfig[ConfigCategory.DEFAULT.name]); payload.event_type = eventNameMapping[event.toLowerCase()]; break; } @@ -282,7 +272,7 @@ const trackResponseBuilder = (message, { Config }, mappedEvent) => { throw new InstrumentationError(`Event ${event} doesn't match with Snapchat Events!`); } - payload = { ...payload, ...getEventProperties(message) }; + payload = { ...payload, ...getEventCommonProperties(message) }; payload = populateHashedValues(payload, message); validateRequiredFields(payload); payload.timestamp = getFieldValueFromMessage(message, 'timestamp'); @@ -364,7 +354,7 @@ const process = (event) => { const messageType = message.type.toLowerCase(); let response; if (messageType === EventType.PAGE) { - response = trackResponseBuilder(message, destination, pageTypeToTrackEvent); + response = [trackResponseBuilder(message, destination, pageTypeToTrackEvent)]; } else if (messageType === EventType.TRACK) { const mappedEvents = eventMappingHandler(message, destination); if (mappedEvents.length > 0) { diff --git a/src/v0/sources/ortto/event_mapping.json b/src/v0/sources/ortto/event_mapping.json new file mode 100644 index 0000000000..f2075c7cd3 --- /dev/null +++ b/src/v0/sources/ortto/event_mapping.json @@ -0,0 +1,95 @@ +{ + "act::s": "Sent email", + "act::c": "Clicked email", + "act::o": "Opened email", + "act::r": "Received email", + "act::b": "Bounced email", + "act::sk": "Skipped email", + "act::rt": "Redacted email", + "act::f": "Forwarded email", + "act::i": "Invalid email", + "act::v": "Viewed email online", + "act::d": "Deferred email", + + "act::ss": "Incident Triggered", + "act::ds": "Incident Unacknowledged", + "act::cs": "Service Created", + "act::is": "Service Deleted", + "act::bs": "Service Updated", + "act::des": "Service Updated", + "act::rs": "Service Updated", + "act::rtss": "Service Updated", + "act::ras": "Service Updated", + "act::cos": "Service Updated", + "act::rtns": "Service Updated", + "act::sf": "Submitted form", + "act::oos-a": "Opted out from SMS audience", + "act::ois-a": "Opted in to SMS audience", + "act::oos-all": "Opted out from all SMS", + "act::ros-all": "Opted in to all SMS", + "act::ss-ab": "SMS subscription abuse", + "act::scr": "SMS consent requested", + "act::scf": "SMS consent failed", + + "act::sph": "Sent push", + "act::dph": "Delivered push", + "act::cph": "Clicked push", + "act::iph": "Invalid push", + "act::oiip-all": "Opted in to all iOS push", + "act::ooip-all": "Opted out of all iOS push", + "act::oiip-a": "Opted in to iOS push audience", + "act::oips-a": "Opted out of iOS push audience", + "act::oigp-all": "Opted in to all Android push", + "act::oogp-all": "Opted out of all Android push", + "act::oigp-a": "Opted out of Android push audience", + "act::oogs-a": "Opted in to Android push audience", + "act::oiwp-all": "Opted in to all web push", + "act::oowp-all": "Opted out of all web push", + "act::oiwp-a": "Opted in to web push audience", + "act::oows-a": "Opted out of to web push audience", + "act::iws-ab": "Web push subscription abuse", + + "act::ws": "Website session", + "act::us": "User session", + + "act::enter-audience": "Entered audience", + "act::leave-audience": "Left audience", + "act::ta": "Tag added", + "act::tr": "Tag removed", + "act::u-a": "Unsubscribe from audience", + "act::s-a": "Subscribe to audience", + "act::u-all": "Un subscribe from all email", + "act::s-all": "Resubscribe globally", + "act::s-ab": "Subscription abuse", + + "act::ep": "Entered playbook", + "act::lp": "Left playbook", + "act::spa": "Successful playbook action", + "act::fpa": "Failed playbook action", + + "act::ej": "Entered journey", + "act::lj": "Left journey", + "act::sja": "Successful journey action", + "act::fja": "Failed journey action", + + "act::cw": "Clicked widget", + "act::sw": "Shown widget", + "act::dw": "Dismissed widget", + "act::rw": "Reacted to widget", + "act::swf": "Submitted widget form", + "act::rws": "Responded to widget survey", + "act::wwv": "Watched widget video", + "act::sww": "Spun widget wheel", + + "act::cop": "Conversation opened", + "act::cro": "Conversation re-opened", + "act::cas": "Conversation assigned", + "act::clc": "Conversation closed", + "act::csn": "Conversation snoozed", + "act::crt": "Conversation rated", + "act::cms": "Conversation marked as spam", + "act::cvs": "Conversation voice call started", + "act::cve": "Conversation voice call ended", + "act::ctg": "Conversation tagged", + "act::cut": "Conversation un-tagged" +} diff --git a/src/v0/sources/ortto/mapping.json b/src/v0/sources/ortto/mapping.json new file mode 100644 index 0000000000..30ac8e153c --- /dev/null +++ b/src/v0/sources/ortto/mapping.json @@ -0,0 +1,38 @@ +[ + { + "sourceKeys": "contact.email", + "destKeys": "context.traits.email" + }, + { + "sourceKeys": "contact.external_id", + "destKeys": "userId" + }, + { + "sourceKeys": "contact.country.name", + "destKeys": "context.traits.address.country" + }, + { + "sourceKeys": "contact.city.name", + "destKeys": "context.traits.address.city" + }, + { + "sourceKeys": "contact.postal", + "destKeys": "context.traits.address.postalCode" + }, + { + "sourceKeys": "id", + "destKeys": "messageId" + }, + { + "sourceKeys": "time", + "destKeys": "originalTimestamp" + }, + { + "sourceKeys": "contact.first_name", + "destKeys": "context.traits.firstName" + }, + { + "sourceKeys": "contact.last_name", + "destKeys": "context.traits.lastName" + } +] diff --git a/src/v0/sources/ortto/transform.js b/src/v0/sources/ortto/transform.js new file mode 100644 index 0000000000..b40163f567 --- /dev/null +++ b/src/v0/sources/ortto/transform.js @@ -0,0 +1,114 @@ +const path = require('path'); +const fs = require('fs'); +const { flattenJson, removeUndefinedAndNullValues, generateUUID } = require('../../util'); +const Message = require('../message'); +const eventMapping = require('./event_mapping.json'); +const { JSON_MIME_TYPE } = require('../../util/constant'); + +// import mapping json using JSON.parse to preserve object key order +const mapping = JSON.parse(fs.readFileSync(path.resolve(__dirname, './mapping.json'), 'utf-8')); + +function settingProperties(event, message) { + const messageReplica = message; + + // flattening the event and assigning it to properties + messageReplica.properties = removeUndefinedAndNullValues(flattenJson(event)); + + // fields that are already mapped + const excludeFields = [ + 'contact.email', + 'contact.contact_id', + 'id', + 'time', + 'activity.field_id', + 'contact.external_id', + 'contact.country.name', + 'contact.city.name', + 'contact.postal', + 'contact.first_name', + 'contact.last_name', + 'contact.birthday.day', + 'contact.birthday.month', + 'contact.birthday.year', + 'contact.phone_number.c', + 'contact.phone_number', + 'contact.phone_number.n', + ]; + + // deleting already mapped fields + excludeFields.forEach((field) => { + delete messageReplica.properties[field]; + }); + + return message; +} + +function process(event) { + let message = new Message(`ortto`); + + // Here, we are checking for the test event to discard them + if (event.activity?.field_id === 'act::test_webhook') { + return { + outputToSource: { + body: Buffer.from(JSON.stringify(event)).toString('base64'), + contentType: JSON_MIME_TYPE, + }, + statusCode: 200, + }; + } + + // we are setting event type as track always + message.setEventType('track'); + + message.setPropertiesV2(event, mapping); + + // setting anonymousId + if (!message.userId) { + message.anonymousId = generateUUID(); + } + + if (event.contact?.birthday) { + const month = + event.contact.birthday?.month < 10 + ? `0${event.contact.birthday?.month}` + : event.contact.birthday?.month; + const day = + event.contact.birthday?.day < 10 + ? `0${event.contact.birthday?.day}` + : event.contact.birthday?.day; + message.context.traits.birthday = `${event.contact.birthday?.year}-${month}-${day}`; + } + + if (event.contact?.phone_number) { + message.context.traits.phone = `${event.contact.phone_number?.c}${event.contact.phone_number?.n}`; + } + + // Updating timestamp to acceptable timestamp format ["2023-10-10T06:24:19.103820974Z" -> "2023-10-10T06:24:19.000Z"] + if (message.originalTimestamp) { + const date = `${Math.floor(new Date(message.originalTimestamp).getTime() / 1000)}`; + message.originalTimestamp = new Date(date * 1000).toISOString(); + } + + // setting event Name + message.setEventName(eventMapping[event.activity.field_id]); + + if (!message.event) { + message.event = 'custom event triggered'; + } + + // setting up ortto contact.contact_id to externalId + if (event.contact?.contact_id) { + message.context.externalId = [ + { + type: 'orttoPersonId', + id: event.contact.contact_id, + }, + ]; + } + + message = settingProperties(event, message); + + return message; +} + +module.exports = { process }; diff --git a/src/v0/util/index.js b/src/v0/util/index.js index 7a31b95a63..34bd5e34ca 100644 --- a/src/v0/util/index.js +++ b/src/v0/util/index.js @@ -821,6 +821,13 @@ function formatValues(formattedVal, formattingType, typeFormat, integrationsObj) curFormattedVal = false; } }, + IsArray: () => { + curFormattedVal = formattedVal; + if (!Array.isArray(formattedVal)) { + logger.debug('Array value missing, so dropping it'); + curFormattedVal = undefined; + } + }, trim: () => { if (typeof formattedVal === 'string') { curFormattedVal = formattedVal.trim(); diff --git a/test/__tests__/data/snapchat_conversion.json b/test/__tests__/data/snapchat_conversion.json index 930c19c98d..ad21fb0d55 100644 --- a/test/__tests__/data/snapchat_conversion.json +++ b/test/__tests__/data/snapchat_conversion.json @@ -22,7 +22,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -65,35 +64,37 @@ } } }, - "output": { - "version": "1", - "type": "REST", - "method": "POST", - "endpoint": "https://tr.snapchat.com/v2/conversion", - "headers": { - "Authorization": "Bearer dummyApiKey", - "Content-Type": "application/json" - }, - "params": {}, - "body": { - "JSON": { - "event_type": "PAGE_VIEW", - "hashed_email": "73062d872926c2a556f17b36f50e328ddf9bff9d403939bd14b6c3b7f5a33fc2", - "hashed_phone_number": "bc77d64d7045fe44795ed926df37231a0cfb6ec6b74588c512790e9f143cc492", - "hashed_mobile_ad_id": "f9779d734aaee50f16ee0011260bae7048f1d9a128c62b6a661077875701edd2", - "hashed_idfv": "54bd0b26a3d39dad90f5149db49b9fd9ba885f8e35d1d94cae69273f5e657b9f", - "user_agent": "mozilla/5.0 (macintosh; intel mac os x 10_15_2) applewebkit/537.36 (khtml, like gecko) chrome/79.0.3945.88 safari/537.36", - "timestamp": "1650625078", - "event_conversion_type": "WEB", - "page_url": "http://www.rudderstack.com", - "pixel_id": "dummyPixelId" + "output": [ + { + "version": "1", + "type": "REST", + "method": "POST", + "endpoint": "https://tr.snapchat.com/v2/conversion", + "headers": { + "Authorization": "Bearer dummyApiKey", + "Content-Type": "application/json" }, - "JSON_ARRAY": {}, - "XML": {}, - "FORM": {} - }, - "files": {} - } + "params": {}, + "body": { + "JSON": { + "event_type": "PAGE_VIEW", + "hashed_email": "73062d872926c2a556f17b36f50e328ddf9bff9d403939bd14b6c3b7f5a33fc2", + "hashed_phone_number": "bc77d64d7045fe44795ed926df37231a0cfb6ec6b74588c512790e9f143cc492", + "hashed_mobile_ad_id": "f9779d734aaee50f16ee0011260bae7048f1d9a128c62b6a661077875701edd2", + "hashed_idfv": "54bd0b26a3d39dad90f5149db49b9fd9ba885f8e35d1d94cae69273f5e657b9f", + "user_agent": "mozilla/5.0 (macintosh; intel mac os x 10_15_2) applewebkit/537.36 (khtml, like gecko) chrome/79.0.3945.88 safari/537.36", + "timestamp": "1650625078", + "event_conversion_type": "WEB", + "page_url": "http://www.rudderstack.com", + "pixel_id": "dummyPixelId" + }, + "JSON_ARRAY": {}, + "XML": {}, + "FORM": {} + }, + "files": {} + } + ] }, { "description": "Test case for Prodcuts Searched event for conversion type offline", @@ -122,7 +123,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -150,6 +150,7 @@ "type": "track", "event": "Products Searched", "properties": { + "brands": "abc", "query": "t-shirts", "event_conversion_type": "web", "number_items": 4, @@ -230,7 +231,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -439,7 +439,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -535,7 +534,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -606,7 +604,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -706,6 +703,7 @@ "type": "track", "event": "Products Searched", "properties": { + "delivery_method": "in_store", "query": "t-shirts", "event_conversion_type": "web" }, @@ -735,6 +733,8 @@ "params": {}, "body": { "JSON": { + "delivery_method": "in_store", + "device_model": "AOSP on IA Emulator", "search_string": "t-shirts", "event_type": "SEARCH", "hashed_email": "73062d872926c2a556f17b36f50e328ddf9bff9d403939bd14b6c3b7f5a33fc2", @@ -780,7 +780,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -847,7 +846,10 @@ "body": { "JSON": { "event_type": "VIEW_CONTENT", - "item_ids": ["123", "123"], + "item_ids": [ + "123", + "123" + ], "price": "56", "country": "IN", "hashed_zip": "cbb2704f5b334a0cec32e5463d1fd9355f6ef73987bfe0ebb8389b7617452152", @@ -892,7 +894,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -959,7 +960,10 @@ "body": { "JSON": { "event_type": "START_CHECKOUT", - "item_ids": ["123", "123"], + "item_ids": [ + "123", + "123" + ], "price": "42", "hashed_email": "73062d872926c2a556f17b36f50e328ddf9bff9d403939bd14b6c3b7f5a33fc2", "hashed_phone_number": "bc77d64d7045fe44795ed926df37231a0cfb6ec6b74588c512790e9f143cc492", @@ -1001,7 +1005,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -1029,7 +1032,10 @@ "type": "track", "event": "Order Completed", "properties": { - "brands": ["brand01", "brand02"], + "brands": [ + "brand01", + "brand02" + ], "products": [ { "product_id": "123", @@ -1070,8 +1076,14 @@ "body": { "JSON": { "event_type": "PURCHASE", - "item_ids": ["123", "124"], - "brands": ["brand01", "brand02"], + "item_ids": [ + "123", + "124" + ], + "brands": [ + "brand01", + "brand02" + ], "price": "56", "hashed_email": "73062d872926c2a556f17b36f50e328ddf9bff9d403939bd14b6c3b7f5a33fc2", "hashed_phone_number": "bc77d64d7045fe44795ed926df37231a0cfb6ec6b74588c512790e9f143cc492", @@ -1113,7 +1125,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -1217,7 +1228,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -1323,7 +1333,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -1428,7 +1437,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -1525,7 +1533,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -1627,7 +1634,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -1729,7 +1735,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -1831,7 +1836,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -1932,7 +1936,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -2036,7 +2039,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -2139,7 +2141,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -2237,7 +2238,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -2340,7 +2340,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -2443,7 +2442,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -2514,7 +2512,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -2611,7 +2608,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -2709,7 +2705,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -2778,7 +2773,10 @@ "body": { "JSON": { "event_type": "PURCHASE", - "item_ids": ["123", "123"], + "item_ids": [ + "123", + "123" + ], "price": "100", "hashed_email": "73062d872926c2a556f17b36f50e328ddf9bff9d403939bd14b6c3b7f5a33fc2", "hashed_phone_number": "bc77d64d7045fe44795ed926df37231a0cfb6ec6b74588c512790e9f143cc492", @@ -2820,7 +2818,6 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 @@ -2888,7 +2885,10 @@ "body": { "JSON": { "event_type": "VIEW_CONTENT", - "item_ids": ["123", "123"], + "item_ids": [ + "123", + "123" + ], "price": "100", "hashed_email": "73062d872926c2a556f17b36f50e328ddf9bff9d403939bd14b6c3b7f5a33fc2", "hashed_phone_number": "bc77d64d7045fe44795ed926df37231a0cfb6ec6b74588c512790e9f143cc492", @@ -2907,4 +2907,4 @@ "files": {} } } -] +] \ No newline at end of file diff --git a/test/__tests__/data/snapchat_conversion_router_input.json b/test/__tests__/data/snapchat_conversion_router_input.json index cc2110f775..0509cef312 100644 --- a/test/__tests__/data/snapchat_conversion_router_input.json +++ b/test/__tests__/data/snapchat_conversion_router_input.json @@ -19,12 +19,10 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 }, - "library": { "name": "RudderLabs JavaScript SDK", "version": "1.0.0" @@ -86,12 +84,10 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 }, - "library": { "name": "RudderLabs JavaScript SDK", "version": "1.0.0" @@ -153,12 +149,10 @@ "advertisingId": "T0T0T072-5e28-45a1-9eda-ce22a3e36d1a", "id": "3f034872-5e28-45a1-9eda-ce22a3e36d1a", "manufacturer": "Google", - "model": "AOSP on IA Emulator", "name": "generic_x86_arm", "type": "ios", "attTrackingStatus": 3 }, - "library": { "name": "RudderLabs JavaScript SDK", "version": "1.0.0" @@ -318,4 +312,4 @@ } } } -] +] \ No newline at end of file diff --git a/test/integrations/destinations/mp/router/data.ts b/test/integrations/destinations/mp/router/data.ts index 389e19b047..7e0de0693d 100644 --- a/test/integrations/destinations/mp/router/data.ts +++ b/test/integrations/destinations/mp/router/data.ts @@ -564,8 +564,9 @@ export const data = [ }, body: { JSON: {}, - JSON_ARRAY: { - batch: + JSON_ARRAY: {}, + GZIP: { + payload: '[{"event":"Loaded a Page","properties":{"ip":"0.0.0.0","$user_id":"hjikl","$current_url":"https://docs.rudderstack.com/destinations/mixpanel","$screen_dpi":2,"mp_lib":"RudderLabs JavaScript SDK","$app_build_number":"1.0.0","$app_version_string":"1.0.5","$insert_id":"dd266c67-9199-4a52-ba32-f46ddde67312","token":"test_api_token","distinct_id":"hjikl","time":1688624942,"name":"Contact Us","$browser":"Chrome","$browser_version":"79.0.3945.117"}}]', }, XML: {}, @@ -636,8 +637,9 @@ export const data = [ }, body: { JSON: {}, - JSON_ARRAY: { - batch: + JSON_ARRAY: {}, + GZIP: { + payload: '[{"event":"Product Viewed","properties":{"name":"T-Shirt","revenue":18.9,"$user_id":"userId01","$os":"iOS","$screen_height":1794,"$screen_width":1080,"$screen_dpi":420,"$carrier":"Android","$os_version":"8.1.0","$device":"generic_x86","$manufacturer":"Google","$model":"Android SDK built for x86","mp_device_model":"Android SDK built for x86","$wifi":true,"$bluetooth_enabled":false,"mp_lib":"com.rudderstack.android.sdk.core","$app_build_number":"1","$app_version_string":"1.0","$insert_id":"id2","token":"test_api_token","distinct_id":"userId01","time":1688624942,"$device_id":"anonId01"}}]', }, XML: {}, @@ -709,8 +711,9 @@ export const data = [ }, body: { JSON: {}, - JSON_ARRAY: { - batch: + JSON_ARRAY: {}, + GZIP: { + payload: '[{"event":"$merge","properties":{"$distinct_ids":["test_user_id","5094f5704b9cf2b3"],"token":"test_api_token"}}]', }, XML: {}, diff --git a/test/integrations/sources/ortto/data.ts b/test/integrations/sources/ortto/data.ts new file mode 100644 index 0000000000..6feed3f43a --- /dev/null +++ b/test/integrations/sources/ortto/data.ts @@ -0,0 +1,393 @@ +import utils from '../../../../src/v0/util'; + +const defaultMockFns = () => { + jest.spyOn(utils, 'generateUUID').mockReturnValue('97fcd7b2-cc24-47d7-b776-057b7b199513'); +}; + +export const data = [ + { + name: 'ortto', + description: 'Simple track call', + module: 'source', + version: 'v0', + input: { + request: { + body: [ + { + activity: { + id: '00651b946bfef7e80478efee', + field_id: 'act::s-all', + created: '2023-10-03T04:11:23Z', + attr: { + 'str::is': 'API', + 'str::s-ctx': 'Subscribed via API', + }, + }, + contact: { + contact_id: '00651b946baa9be6b2edad00', + email: 'abhi@example.com', + }, + id: '00651b946cef87c7af64f4f3', + time: '2023-10-03T04:11:24.25726779Z', + webhook_id: '651b8aec8002153e16319fd3', + }, + ], + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }, + pathSuffix: '', + }, + output: { + response: { + status: 200, + body: [ + { + output: { + batch: [ + { + context: { + library: { name: 'unknown', version: 'unknown' }, + integration: { name: 'ortto' }, + traits: { email: 'abhi@example.com' }, + externalId: [ + { + id: '00651b946baa9be6b2edad00', + type: 'orttoPersonId', + }, + ], + }, + event: 'Resubscribe globally', + integrations: { ortto: false }, + type: 'track', + anonymousId: '97fcd7b2-cc24-47d7-b776-057b7b199513', + messageId: '00651b946cef87c7af64f4f3', + originalTimestamp: '2023-10-03T04:11:24.000Z', + properties: { + 'activity.id': '00651b946bfef7e80478efee', + 'activity.created': '2023-10-03T04:11:23Z', + 'activity.attr.str::is': 'API', + 'activity.attr.str::s-ctx': 'Subscribed via API', + webhook_id: '651b8aec8002153e16319fd3', + }, + }, + ], + }, + }, + ], + }, + }, + mockFns: () => { + defaultMockFns(); + }, + }, + { + name: 'ortto', + description: 'Simple track call', + module: 'source', + version: 'v0', + input: { + request: { + body: [ + { + activity: { + id: '00651b946bfef7e80478efee', + field_id: 'act::s-all', + created: '2023-10-03T04:11:23Z', + attr: { + 'str::is': 'API', + 'str::s-ctx': 'Subscribed via API', + }, + }, + contact: { + external_id: 'user_x', + city: { + name: 'Kolkata', + id: 0, + lat: 37751000, + lng: -97822000, + }, + country: { + name: 'United States', + id: 6252001, + lat: 0, + lng: 0, + }, + email: 'xyz@email.com', + first_name: 'Ujjwal', + last_name: 'Ujjwal', + birthday: { + year: 1980, + month: 12, + day: 11, + timezone: 'Australia/Sydney', + }, + phone_number: { + c: '91', + n: '401234567', + }, + }, + id: '00651b946cef87c7af64f4f3', + time: '2023-10-03T04:11:24.25726779Z', + webhook_id: '651b8aec8002153e16319fd3', + }, + ], + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }, + pathSuffix: '', + }, + output: { + response: { + status: 200, + body: [ + { + output: { + batch: [ + { + userId: 'user_x', + context: { + library: { name: 'unknown', version: 'unknown' }, + integration: { name: 'ortto' }, + traits: { + email: 'xyz@email.com', + birthday: '1980-12-11', + firstName: 'Ujjwal', + lastName: 'Ujjwal', + phone: '91401234567', + address: { + city: 'Kolkata', + country: 'United States', + }, + }, + }, + event: 'Resubscribe globally', + integrations: { ortto: false }, + type: 'track', + messageId: '00651b946cef87c7af64f4f3', + originalTimestamp: '2023-10-03T04:11:24.000Z', + properties: { + 'activity.id': '00651b946bfef7e80478efee', + 'activity.created': '2023-10-03T04:11:23Z', + 'activity.attr.str::is': 'API', + 'activity.attr.str::s-ctx': 'Subscribed via API', + 'contact.birthday.timezone': 'Australia/Sydney', + 'contact.city.id': 0, + 'contact.city.lat': 37751000, + 'contact.city.lng': -97822000, + 'contact.country.id': 6252001, + 'contact.country.lat': 0, + 'contact.country.lng': 0, + webhook_id: '651b8aec8002153e16319fd3', + }, + }, + ], + }, + }, + ], + }, + }, + mockFns: () => { + defaultMockFns(); + }, + }, + { + name: 'ortto', + description: 'Simple track call with unknown field id', + module: 'source', + version: 'v0', + input: { + request: { + body: [ + { + activity: { + id: '00651b946bfef7e80478efee', + field_id: 'act::s-ccc', + created: '2023-10-03T04:11:23Z', + attr: { + 'str::is': 'API', + 'str::s-ctx': 'Subscribed via API', + }, + }, + contact: { + external_id: 'user_x', + city: { + name: 'Kolkata', + id: 0, + lat: 37751000, + lng: -97822000, + }, + contact_id: '006524f0b8d370050056e400', + country: { + name: 'United States', + id: 6252001, + lat: 0, + lng: 0, + }, + email: 'xyz@email.com', + first_name: 'Ujjwal', + last_name: 'Ujjwal', + birthday: { + year: 1980, + month: 3, + day: 4, + timezone: 'Australia/Sydney', + }, + phone_number: { + c: '91', + n: '401234567', + }, + }, + id: '00651b946cef87c7af64f4f3', + time: '2023-10-03T04:11:24.25726779Z', + webhook_id: '651b8aec8002153e16319fd3', + }, + ], + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }, + pathSuffix: '', + }, + output: { + response: { + status: 200, + body: [ + { + output: { + batch: [ + { + userId: 'user_x', + context: { + externalId: [ + { + id: '006524f0b8d370050056e400', + type: 'orttoPersonId', + }, + ], + library: { name: 'unknown', version: 'unknown' }, + integration: { name: 'ortto' }, + traits: { + email: 'xyz@email.com', + birthday: '1980-03-04', + firstName: 'Ujjwal', + lastName: 'Ujjwal', + phone: '91401234567', + address: { + city: 'Kolkata', + country: 'United States', + }, + }, + }, + event: 'custom event triggered', + integrations: { ortto: false }, + type: 'track', + messageId: '00651b946cef87c7af64f4f3', + originalTimestamp: '2023-10-03T04:11:24.000Z', + properties: { + 'activity.id': '00651b946bfef7e80478efee', + 'activity.created': '2023-10-03T04:11:23Z', + 'activity.attr.str::is': 'API', + 'activity.attr.str::s-ctx': 'Subscribed via API', + 'contact.birthday.timezone': 'Australia/Sydney', + 'contact.city.id': 0, + 'contact.city.lat': 37751000, + 'contact.city.lng': -97822000, + 'contact.country.id': 6252001, + 'contact.country.lat': 0, + 'contact.country.lng': 0, + webhook_id: '651b8aec8002153e16319fd3', + }, + }, + ], + }, + }, + ], + }, + }, + mockFns: () => { + defaultMockFns(); + }, + }, + { + name: 'ortto', + description: 'Simple track call with unknown field id', + module: 'source', + version: 'v0', + input: { + request: { + body: [ + { + activity: { + id: '00651b946bfef7e80478efee', + field_id: 'act::test_webhook', + created: '2023-10-03T04:11:23Z', + attr: { + 'str::is': 'API', + 'str::s-ctx': 'Subscribed via API', + }, + }, + contact: { + external_id: 'user_x', + city: { + name: 'Kolkata', + id: 0, + lat: 37751000, + lng: -97822000, + }, + contact_id: '006524f0b8d370050056e400', + country: { + name: 'United States', + id: 6252001, + lat: 0, + lng: 0, + }, + email: 'xyz@email.com', + first_name: 'Ujjwal', + last_name: 'Ujjwal', + birthday: { + year: 1980, + month: 3, + day: 4, + timezone: 'Australia/Sydney', + }, + phone_number: { + c: '91', + n: '401234567', + }, + }, + id: '00651b946cef87c7af64f4f3', + time: '2023-10-03T04:11:24.25726779Z', + webhook_id: '651b8aec8002153e16319fd3', + }, + ], + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }, + pathSuffix: '', + }, + output: { + response: { + status: 200, + body: [ + { + outputToSource: { + body: 'eyJhY3Rpdml0eSI6eyJpZCI6IjAwNjUxYjk0NmJmZWY3ZTgwNDc4ZWZlZSIsImZpZWxkX2lkIjoiYWN0Ojp0ZXN0X3dlYmhvb2siLCJjcmVhdGVkIjoiMjAyMy0xMC0wM1QwNDoxMToyM1oiLCJhdHRyIjp7InN0cjo6aXMiOiJBUEkiLCJzdHI6OnMtY3R4IjoiU3Vic2NyaWJlZCB2aWEgQVBJIn19LCJjb250YWN0Ijp7ImV4dGVybmFsX2lkIjoidXNlcl94IiwiY2l0eSI6eyJuYW1lIjoiS29sa2F0YSIsImlkIjowLCJsYXQiOjM3NzUxMDAwLCJsbmciOi05NzgyMjAwMH0sImNvbnRhY3RfaWQiOiIwMDY1MjRmMGI4ZDM3MDA1MDA1NmU0MDAiLCJjb3VudHJ5Ijp7Im5hbWUiOiJVbml0ZWQgU3RhdGVzIiwiaWQiOjYyNTIwMDEsImxhdCI6MCwibG5nIjowfSwiZW1haWwiOiJ4eXpAZW1haWwuY29tIiwiZmlyc3RfbmFtZSI6IlVqandhbCIsImxhc3RfbmFtZSI6IlVqandhbCIsImJpcnRoZGF5Ijp7InllYXIiOjE5ODAsIm1vbnRoIjozLCJkYXkiOjQsInRpbWV6b25lIjoiQXVzdHJhbGlhL1N5ZG5leSJ9LCJwaG9uZV9udW1iZXIiOnsiYyI6IjkxIiwibiI6IjQwMTIzNDU2NyJ9fSwiaWQiOiIwMDY1MWI5NDZjZWY4N2M3YWY2NGY0ZjMiLCJ0aW1lIjoiMjAyMy0xMC0wM1QwNDoxMToyNC4yNTcyNjc3OVoiLCJ3ZWJob29rX2lkIjoiNjUxYjhhZWM4MDAyMTUzZTE2MzE5ZmQzIn0=', + contentType: 'application/json', + }, + statusCode: 200, + }, + ], + }, + }, + mockFns: () => { + defaultMockFns(); + }, + }, +];