From 57fee0496f718837768f493eb4e204e948c34554 Mon Sep 17 00:00:00 2001 From: Anant Jain <62471433+anantjain45823@users.noreply.github.com> Date: Thu, 2 Nov 2023 11:25:09 +0530 Subject: [PATCH 01/11] chore: refactoring amplitude using optional chaining (#2645) * chore: refactoring amplitude using optional chaining * test cases added for source endpoint * chore: minor fixes * chore: comment addressed * chore: solve sonar issues 1 * chore: solve sonar issues 2 * chore: solve sonar issues 3 * chore: resolve sonar hotspot * chore: resolve sonar * chore: solve sonar issues 4 * chore: small fixes and test cases addition * chore: small fixes and test cases addition+1 * chore: merge develop issue fix * Update src/v0/destinations/am/transform.js Co-authored-by: Sankeerth * Update src/v0/destinations/am/transform.js Co-authored-by: Sankeerth * chore: addressed comments * chore: reduce cognitive complexity+1 * chore: add test cases * chore: addressed comments * chore: addressed comments+1 * chore: comments addressed * chore: comments addressed+1 * chore: resolve sonar * chore: comments addressed+2 * sonarcloud fix+1 --------- Co-authored-by: Sai Sankeerth Co-authored-by: Sankeerth --- src/v0/destinations/am/config.js | 5 +- src/v0/destinations/am/transform.js | 742 ++++++++++++---------- src/v0/destinations/am/utils.js | 32 +- src/v0/util/index.js | 11 + src/v0/util/index.test.js | 1 + src/v0/util/testdata/isValidInteger.json | 31 + test/__tests__/data/am_batch_input.json | 63 ++ test/__tests__/data/am_batch_output.json | 59 +- test/__tests__/data/am_input.json | 376 ++++++++++- test/__tests__/data/am_output.json | 323 +++++++++- test/__tests__/data/am_router_output.json | 4 +- 11 files changed, 1224 insertions(+), 423 deletions(-) create mode 100644 src/v0/util/testdata/isValidInteger.json diff --git a/src/v0/destinations/am/config.js b/src/v0/destinations/am/config.js index 5e6fc06c27..3e51a67137 100644 --- a/src/v0/destinations/am/config.js +++ b/src/v0/destinations/am/config.js @@ -122,7 +122,8 @@ events.forEach((event) => { const DELETE_MAX_BATCH_SIZE = 100; const DESTINATION = 'amplitude'; const IDENTIFY_AM = '$identify'; - +const AMBatchSizeLimit = 20 * 1024 * 1024; // 20 MB +const AMBatchEventLimit = 500; // event size limit from sdk is 32KB => 15MB module.exports = { DESTINATION, Event, @@ -134,4 +135,6 @@ module.exports = { DELETE_MAX_BATCH_SIZE, batchEventsWithUserIdLengthLowerThanFive, IDENTIFY_AM, + AMBatchSizeLimit, + AMBatchEventLimit }; diff --git a/src/v0/destinations/am/transform.js b/src/v0/destinations/am/transform.js index e6ccf585df..b74625e273 100644 --- a/src/v0/destinations/am/transform.js +++ b/src/v0/destinations/am/transform.js @@ -1,6 +1,7 @@ /* eslint-disable no-lonely-if */ /* eslint-disable no-nested-ternary */ /* eslint-disable no-param-reassign */ +const cloneDeep = require('lodash/cloneDeep'); const get = require('get-value'); const set = require('set-value'); const { @@ -26,6 +27,7 @@ const { isAppleFamily, isDefinedAndNotNullAndNotEmpty, simpleProcessRouterDest, + isValidInteger, } = require('../../util'); const { BASE_URL, @@ -34,18 +36,17 @@ const { mappingConfig, batchEventsWithUserIdLengthLowerThanFive, IDENTIFY_AM, + AMBatchSizeLimit, + AMBatchEventLimit, } = require('./config'); const tags = require('../../util/tags'); const AMUtils = require('./utils'); const logger = require('../../../logger'); -const { InstrumentationError } = require('../../util/errorTypes'); +const { InstrumentationError, ConfigurationError } = require('../../util/errorTypes'); const { JSON_MIME_TYPE } = require('../../util/constant'); -const AMBatchSizeLimit = 20 * 1024 * 1024; // 20 MB -const AMBatchEventLimit = 500; // event size limit from sdk is 32KB => 15MB - const EVENTS_KEY_PATH = 'body.JSON.events'; const baseEndpoint = (destConfig) => { @@ -79,82 +80,96 @@ const aliasEndpoint = (destConfig) => { return retVal; }; -function handleSessionIdUnderRoot(message) { - const sessionId = get(message, 'session_id'); +const handleSessionIdUnderRoot = (sessionId) => { if (typeof sessionId === 'string') { - return sessionId.substr(sessionId.lastIndexOf(':') + 1, sessionId.length); + const extractedPart = sessionId.split(':').reverse(); + if (!isValidInteger(extractedPart[0])) return -1; + return Number(extractedPart[0]); } - return sessionId; -} + return Number(sessionId); +}; -function handleSessionIdUnderContext(message) { - let sessionId = get(message, 'context.sessionId'); - sessionId = Number(sessionId); - if (Number.isNaN(sessionId)) return -1; - return sessionId; -} +const handleSessionIdUnderContext = (sessionId) => { + if (!isValidInteger(sessionId)) return -1; + return Number(sessionId); +}; -function getSessionId(message) { - return get(message, 'session_id') - ? handleSessionIdUnderRoot(message) - : get(message, 'context.sessionId') - ? handleSessionIdUnderContext(message) - : -1; -} +const checkForJSONAndUserIdLengthAndDeviceId = (jsonBody, userId, deviceId) => + Object.keys(jsonBody).length === 0 || + (userId && + userId.length < 5 && + (!batchEventsWithUserIdLengthLowerThanFive || + (batchEventsWithUserIdLengthLowerThanFive && !deviceId))); +const getSessionId = (message) => { + let sessionId = -1; + const rootSessionId = get(message, 'session_id'); + if (rootSessionId) { + sessionId = handleSessionIdUnderRoot(rootSessionId); + if (sessionId !== -1) { + return sessionId; + } + } + const contextSessionId = get(message, 'context.sessionId'); + if (contextSessionId) { + sessionId = handleSessionIdUnderContext(contextSessionId); + } + return sessionId; +}; -function addMinIdlength() { - return { min_id_length: 1 }; -} +const addMinIdlength = () => ({ min_id_length: 1 }); -function setPriceQuanityInPayload(message, rawPayload) { +const setPriceQuanityInPayload = (message, rawPayload) => { let price; let quantity; - if (isDefinedAndNotNull(message.properties.price)) { + if (isDefinedAndNotNull(message.properties?.price)) { price = message.properties.price; - quantity = message.properties.quantity || 1; + quantity = message.properties?.quantity || 1; } else { - price = message.properties.revenue; + price = message.properties?.revenue; quantity = 1; } rawPayload.price = price; rawPayload.quantity = quantity; - rawPayload.revenue = message.properties.revenue; + rawPayload.revenue = message.properties?.revenue; return rawPayload; -} +}; -function createRevenuePayload(message, rawPayload) { - rawPayload.productId = message.properties.product_id; +const createRevenuePayload = (message, rawPayload) => { + rawPayload.productId = message.properties?.product_id; rawPayload.revenueType = - message.properties.revenueType || message.properties.revenue_type || 'Purchased'; + message.properties?.revenueType || message.properties?.revenue_type || 'Purchased'; rawPayload = setPriceQuanityInPayload(message, rawPayload); return rawPayload; -} +}; -function updateTraitsObject(property, traitsObject, actionKey) { +const updateTraitsObject = (property, traitsObject, actionKey) => { const propertyToUpdate = getValueFromMessage(traitsObject, property); if (traitsObject[actionKey] && property && typeof property === 'string') { traitsObject[actionKey][property] = propertyToUpdate; deleteObjectProperty(traitsObject, property); } return traitsObject; -} +}; -function prepareTraitsConfig(configPropertyTrait, actionKey, traitsObject) { +const prepareTraitsConfig = (configPropertyTrait, actionKey, traitsObject) => { traitsObject[actionKey] = {}; configPropertyTrait.forEach((traitsElement) => { - const property = traitsElement.traits; + const property = traitsElement?.traits; traitsObject = updateTraitsObject(property, traitsObject, actionKey); }); - if (Object.keys(traitsObject[actionKey]).length === 0) { + if ( + typeof traitsObject?.[actionKey] === 'object' && + Object.keys(traitsObject?.[actionKey] || {})?.length === 0 + ) { delete traitsObject[actionKey]; } return traitsObject; -} +}; -function handleTraits(messageTrait, destination) { +const handleTraits = (messageTrait, destination) => { let traitsObject = JSON.parse(JSON.stringify(messageTrait)); - if (destination.Config.traitsToIncrement) { + if (destination.Config?.traitsToIncrement) { const actionKey = '$add'; traitsObject = prepareTraitsConfig( destination.Config.traitsToIncrement, @@ -162,29 +177,41 @@ function handleTraits(messageTrait, destination) { traitsObject, ); } - if (destination.Config.traitsToSetOnce) { + if (destination.Config?.traitsToSetOnce) { const actionKey = '$setOnce'; traitsObject = prepareTraitsConfig(destination.Config.traitsToSetOnce, actionKey, traitsObject); } - if (destination.Config.traitsToAppend) { + if (destination.Config?.traitsToAppend) { const actionKey = '$append'; traitsObject = prepareTraitsConfig(destination.Config.traitsToAppend, actionKey, traitsObject); } - if (destination.Config.traitsToPrepend) { + if (destination.Config?.traitsToPrepend) { const actionKey = '$prepend'; traitsObject = prepareTraitsConfig(destination.Config.traitsToPrepend, actionKey, traitsObject); } return traitsObject; -} +}; + +const getScreenevTypeAndUpdatedProperties = (message, CATEGORY_KEY) => { + const name = message.name || message.event || get(message, CATEGORY_KEY); + const updatedName = name ? `${name} ` : ''; + return { + evType: `Viewed ${updatedName}Screen`, + updatedProperties: { + ...message.properties, + name, + }, + }; +}; -function handleMappingJsonObject( +const handleMappingJsonObject = ( mappingJson, sourceKey, validatePayload, payload, message, Config, -) { +) => { const { isFunc, funcName, outKey } = mappingJson[sourceKey]; if (isFunc) { if (validatePayload) { @@ -203,150 +230,242 @@ function handleMappingJsonObject( if (isDefinedAndNotNull(data)) { set(payload, outKey, data); delete message.traits[outKey]; - } else { - // get the destKey/outKey value from calling the util function - set(payload, outKey, AMUtils[funcName](message, sourceKey, Config)); + return; } + // get the destKey/outKey value from calling the util function + set(payload, outKey, AMUtils[funcName](message, sourceKey, Config)); } } -} +}; -function updateConfigProperty(message, payload, mappingJson, validatePayload, Config) { +const updateConfigProperty = (message, payload, mappingJson, validatePayload, Config) => { const sourceKeys = Object.keys(mappingJson); sourceKeys.forEach((sourceKey) => { // check if custom processing is required on the payload sourceKey ==> destKey if (typeof mappingJson[sourceKey] === 'object') { handleMappingJsonObject(mappingJson, sourceKey, validatePayload, payload, message, Config); - } else { - // For common config - if (validatePayload) { - // if data is present in traits assign - const messageData = get(message.traits, mappingJson[sourceKey]); - if (isDefinedAndNotNull(messageData)) { - set(payload, mappingJson[sourceKey], messageData); - } else { - const data = get(payload, mappingJson[sourceKey]); - if (!isDefinedAndNotNull(data)) { - const val = get(message, sourceKey); - if (val || val === false || val === 0) { - set(payload, mappingJson[sourceKey], val); - } + } else if (validatePayload) { + // if data is present in traits assign + const messageData = get(message.traits, mappingJson[sourceKey]); + if (isDefinedAndNotNull(messageData)) { + set(payload, mappingJson[sourceKey], messageData); + } else { + const data = get(payload, mappingJson[sourceKey]); + if (!isDefinedAndNotNull(data)) { + const val = get(message, sourceKey); + if (val || val === false || val === 0) { + set(payload, mappingJson[sourceKey], val); } } + } + } else { + const data = get(message.traits, mappingJson[sourceKey]); + if (isDefinedAndNotNull(data)) { + set(payload, mappingJson[sourceKey], data); } else { - const data = get(message.traits, mappingJson[sourceKey]); - if (isDefinedAndNotNull(data)) { - set(payload, mappingJson[sourceKey], data); - } else { - set(payload, mappingJson[sourceKey], get(message, sourceKey)); - } + set(payload, mappingJson[sourceKey], get(message, sourceKey)); } } }); -} +}; +const identifyBuilder = (message, destination, rawPayload) => { + // update payload user_properties from userProperties/traits/context.traits/nested traits of Rudder message + // traits like address converted to top level user properties (think we can skip this extra processing as AM supports nesting upto 40 levels) + let traits = getFieldValueFromMessage(message, 'traits'); + if (traits) { + traits = handleTraits(traits, destination); + } + rawPayload.user_properties = { + ...rawPayload.user_properties, + ...message.userProperties, + }; + if (traits) { + Object.keys(traits).forEach((trait) => { + if (SpecedTraits.includes(trait)) { + const mapping = TraitsMapping[trait]; + Object.keys(mapping).forEach((key) => { + const checkKey = get(rawPayload.user_properties, key); + // this is done only if we want to add default values under address to the user_properties + // these values are also sent to the destination at the top level. + if (!isDefinedAndNotNull(checkKey)) { + set(rawPayload, `user_properties.${key}`, get(traits, mapping[key])); + } + }); + } else { + set(rawPayload, `user_properties.${trait}`, get(traits, trait)); + } + }); + } + return rawPayload; +}; -function getResponseData(evType, destination, rawPayload, message, groupInfo) { - let endpoint = defaultEndpoint(destination.Config); - let traits; +const getDefaultResponseData = (message, rawPayload, evType, groupInfo) => { + const traits = getFieldValueFromMessage(message, 'traits'); + set(rawPayload, 'event_properties', message.properties); + + if (traits) { + rawPayload.user_properties = { + ...rawPayload.user_properties, + ...traits, + }; + } + + rawPayload.event_type = evType; + rawPayload.user_id = message.userId; + if (message.isRevenue) { + // making the revenue payload + rawPayload = createRevenuePayload(message, rawPayload); + // deleting the properties price, product_id, quantity and revenue from event_properties since it is already in root + if (rawPayload.event_properties) { + delete rawPayload.event_properties.price; + delete rawPayload.event_properties.product_id; + delete rawPayload.event_properties.quantity; + delete rawPayload.event_properties.revenue; + } + } + const groups = groupInfo && cloneDeep(groupInfo); + return { groups, rawPayload }; +}; +const getResponseData = (evType, destination, rawPayload, message, groupInfo) => { let groups; switch (evType) { case EventType.IDENTIFY: + // event_type for identify event is $identify + rawPayload.event_type = IDENTIFY_AM; + identifyBuilder(message, destination, rawPayload); + break; case EventType.GROUP: - endpoint = defaultEndpoint(destination.Config); // event_type for identify event is $identify rawPayload.event_type = IDENTIFY_AM; - - if (evType === EventType.IDENTIFY) { - // update payload user_properties from userProperties/traits/context.traits/nested traits of Rudder message - // traits like address converted to top level user properties (think we can skip this extra processing as AM supports nesting upto 40 levels) - traits = getFieldValueFromMessage(message, 'traits'); - if (traits) { - traits = handleTraits(traits, destination); - } - rawPayload.user_properties = { - ...rawPayload.user_properties, - ...message.userProperties, - }; - if (traits) { - Object.keys(traits).forEach((trait) => { - if (SpecedTraits.includes(trait)) { - const mapping = TraitsMapping[trait]; - Object.keys(mapping).forEach((key) => { - const checkKey = get(rawPayload.user_properties, key); - // this is done only if we want to add default values under address to the user_properties - // these values are also sent to the destination at the top level. - if (!isDefinedAndNotNull(checkKey)) { - set(rawPayload, `user_properties.${key}`, get(traits, mapping[key])); - } - }); - } else { - set(rawPayload, `user_properties.${trait}`, get(traits, trait)); - } - }); - } - } - - if ( - evType === EventType.GROUP && // for Rudder group call, update the user_properties with group info - // Refer (1.) - groupInfo && - groupInfo.group_type && - groupInfo.group_value - ) { + // for Rudder group call, update the user_properties with group info + if (groupInfo?.group_type && groupInfo?.group_value) { groups = {}; groups[groupInfo.group_type] = groupInfo.group_value; set(rawPayload, `user_properties.${[groupInfo.group_type]}`, groupInfo.group_value); } break; case EventType.ALIAS: - endpoint = aliasEndpoint(destination.Config); break; default: - traits = getFieldValueFromMessage(message, 'traits'); - set(rawPayload, 'event_properties', message.properties); - - if (traits) { - rawPayload.user_properties = { - ...rawPayload.user_properties, - ...traits, - }; - } + ({ groups, rawPayload } = getDefaultResponseData(message, rawPayload, evType, groupInfo)); + } + return { rawPayload, groups }; +}; - rawPayload.event_type = evType; - rawPayload.user_id = message.userId; - if (message.isRevenue) { - // making the revenue payload - rawPayload = createRevenuePayload(message, rawPayload); - // deleting the properties price, product_id, quantity and revenue from event_properties since it is already in root - if (rawPayload.event_properties) { - delete rawPayload.event_properties.price; - delete rawPayload.event_properties.product_id; - delete rawPayload.event_properties.quantity; - delete rawPayload.event_properties.revenue; - } - } - groups = groupInfo && Object.assign(groupInfo); +const buildPayloadForMobileChannel = (message, destination, payload) => { + if (!destination.Config.mapDeviceBrand) { + set(payload, 'device_brand', get(message, 'context.device.manufacturer')); } - return { endpoint, rawPayload, groups }; -} -function responseBuilderSimple( + const deviceId = get(message, 'context.device.id'); + const platform = get(message, 'context.device.type'); + const advertId = get(message, 'context.device.advertisingId'); + + if (platform) { + if (isAppleFamily(platform)) { + set(payload, 'idfa', advertId); + set(payload, 'idfv', deviceId); + } else if (platform.toLowerCase() === 'android') { + set(payload, 'adid', advertId); + } + } +}; +const nonAliasResponsebuilder = ( + message, + payload, + destination, + evType, + groupInfo, + rootElementName, +) => { + const respList = []; + const addOptions = 'options'; + const response = defaultRequestConfig(); + const groupResponse = defaultRequestConfig(); + const endpoint = defaultEndpoint(destination.Config); + if (message.channel === 'mobile') { + buildPayloadForMobileChannel(message, destination, payload); + } + payload.time = new Date(getFieldValueFromMessage(message, 'timestamp')).getTime(); + + // send user_id only when present, for anonymous users not required + if (message.userId && message.userId !== null) { + payload.user_id = message.userId; + } + payload.session_id = getSessionId(message); + + updateConfigProperty( + message, + payload, + mappingConfig[ConfigCategory.COMMON_CONFIG.name], + true, + destination.Config, + ); + + // we are not fixing the verson for android specifically any more because we've put a fix in iOS SDK + // for correct versionName + // ==================== + // fixVersion(payload, message); + + if (payload.user_properties) { + delete payload.user_properties.city; + delete payload.user_properties.country; + if (payload.user_properties.address) { + delete payload.user_properties.address.city; + delete payload.user_properties.address.country; + } + } + + if (!payload.user_id && !payload.device_id) { + logger.debug('Either of user ID or device ID fields must be specified'); + throw new InstrumentationError('Either of user ID or device ID fields must be specified'); + } + + payload.ip = getParsedIP(message); + payload.library = 'rudderstack'; + payload = removeUndefinedAndNullValues(payload); + response.endpoint = endpoint; + response.method = defaultPostRequestConfig.requestMethod; + response.headers = { + 'Content-Type': JSON_MIME_TYPE, + }; + response.userId = message.anonymousId; + response.body.JSON = { + api_key: destination.Config.apiKey, + [rootElementName]: [payload], + [addOptions]: addMinIdlength(), + }; + respList.push(response); + + // https://developers.amplitude.com/docs/group-identify-api + // Refer (1.), Rudder group call updates group propertiees. + if (evType === EventType.GROUP && groupInfo) { + groupResponse.method = defaultPostRequestConfig.requestMethod; + groupResponse.endpoint = groupEndpoint(destination.Config); + let groupPayload = cloneDeep(groupInfo); + groupResponse.userId = message.anonymousId; + groupPayload = removeUndefinedValues(groupPayload); + groupResponse.body.FORM = { + api_key: destination.Config.apiKey, + identification: [JSON.stringify(groupPayload)], + }; + respList.push(groupResponse); + } + return respList; +}; + +const responseBuilderSimple = ( groupInfo, rootElementName, message, evType, mappingJson, destination, -) { - let rawPayload = {}; - const addOptions = 'options'; +) => { + const rawPayload = {}; const respList = []; - const response = defaultRequestConfig(); - const groupResponse = defaultRequestConfig(); const aliasResponse = defaultRequestConfig(); - let endpoint = defaultEndpoint(destination.Config); - if ( EventType.IDENTIFY && // If mapped to destination, Add externalId to traits get(message, MappedToDestinationKey) @@ -375,7 +494,7 @@ function responseBuilderSimple( const oldKeys = Object.keys(campaign); // appends utm_ prefix to all the keys of campaign object. For example the `name` key in campaign object will be changed to `utm_name` oldKeys.forEach((oldKey) => { - Object.assign(campaign, { [`utm_${oldKey}`]: campaign[oldKey] }); + campaign[`utm_${oldKey}`] = campaign[oldKey]; delete campaign[oldKey]; }); @@ -390,14 +509,13 @@ function responseBuilderSimple( }; const respData = getResponseData(evType, destination, rawPayload, message, groupInfo); - const { groups } = respData; - ({ endpoint, rawPayload } = respData); + const { groups, rawPayload: updatedRawPayload } = respData; // for https://api.amplitude.com/2/httpapi , pass the "groups" key // refer (1.) for passing "groups" for Rudder group call // https://developers.amplitude.com/docs/http-api-v2#schemaevent - set(rawPayload, 'groups', groups); - let payload = removeUndefinedValues(rawPayload); + set(updatedRawPayload, 'groups', groups); + let payload = removeUndefinedValues(updatedRawPayload); let unmapUserId; if (evType === EventType.ALIAS) { // By default (1.), Alias config file populates user_id and global_user_id @@ -419,113 +537,60 @@ function responseBuilderSimple( }; respList.push(aliasResponse); } else { - if (message.channel === 'mobile') { - if (!destination.Config.mapDeviceBrand) { - set(payload, 'device_brand', get(message, 'context.device.manufacturer')); - } - - const deviceId = get(message, 'context.device.id'); - const platform = get(message, 'context.device.type'); - const advertId = get(message, 'context.device.advertisingId'); - - if (platform) { - if (isAppleFamily(platform)) { - set(payload, 'idfa', advertId); - set(payload, 'idfv', deviceId); - } else if (platform.toLowerCase() === 'android') { - set(payload, 'adid', advertId); - } - } - } - - payload.time = new Date(getFieldValueFromMessage(message, 'timestamp')).getTime(); - - // send user_id only when present, for anonymous users not required - if ( - message.userId && - message.userId !== '' && - message.userId !== 'null' && - message.userId !== null - ) { - payload.user_id = message.userId; - } - payload.session_id = getSessionId(message); - - updateConfigProperty( + return nonAliasResponsebuilder( message, payload, - mappingConfig[ConfigCategory.COMMON_CONFIG.name], - true, - destination.Config, + destination, + evType, + groupInfo, + rootElementName, ); + } + return respList; +}; - // we are not fixing the verson for android specifically any more because we've put a fix in iOS SDK - // for correct versionName - // ==================== - // fixVersion(payload, message); - - if (payload.user_properties) { - delete payload.user_properties.city; - delete payload.user_properties.country; - if (payload.user_properties.address) { - delete payload.user_properties.address.city; - delete payload.user_properties.address.country; - } - } - - if (!payload.user_id && !payload.device_id) { - logger.debug('Either of user ID or device ID fields must be specified'); - throw new InstrumentationError('Either of user ID or device ID fields must be specified'); - } - - payload.ip = getParsedIP(message); - payload.library = 'rudderstack'; - payload = removeUndefinedAndNullValues(payload); - response.endpoint = endpoint; - response.method = defaultPostRequestConfig.requestMethod; - response.headers = { - 'Content-Type': JSON_MIME_TYPE, - }; - response.userId = message.anonymousId; - response.body.JSON = { - api_key: destination.Config.apiKey, - [rootElementName]: [payload], - [addOptions]: addMinIdlength(), - }; - respList.push(response); - - // https://developers.amplitude.com/docs/group-identify-api - // Refer (1.), Rudder group call updates group propertiees. - if (evType === EventType.GROUP && groupInfo) { - groupResponse.method = defaultPostRequestConfig.requestMethod; - groupResponse.endpoint = groupEndpoint(destination.Config); - let groupPayload = Object.assign(groupInfo); - groupResponse.userId = message.anonymousId; - groupPayload = removeUndefinedValues(groupPayload); - groupResponse.body.FORM = { - api_key: destination.Config.apiKey, - identification: [JSON.stringify(groupPayload)], - }; - respList.push(groupResponse); +const getGroupInfo = (destination, groupInfo, groupTraits) => { + const { groupTypeTrait, groupValueTrait } = destination.Config; + if (groupTypeTrait && groupValueTrait) { + let updatedGroupInfo = { ...groupInfo }; + const groupTypeValue = get(groupTraits, groupTypeTrait); + const groupNameValue = get(groupTraits, groupValueTrait); + // since the property updates on group at https://api2.amplitude.com/groupidentify + // expects a string group name and value , so error out if the keys are not primitive + // Note: This different for groups object at https://api.amplitude.com/2/httpapi where the + // group value can be array of strings as well. + if ( + groupTypeValue && + typeof groupTypeValue === 'string' && + groupNameValue && + (typeof groupNameValue === 'string' || typeof groupNameValue === 'number') + ) { + updatedGroupInfo = {}; + updatedGroupInfo.group_type = groupTypeValue; + updatedGroupInfo.group_value = groupNameValue; + // passing the entire group traits without deleting the above keys + updatedGroupInfo.group_properties = groupTraits; + return updatedGroupInfo; } + logger.debug('Group call parameters are not valid'); + throw new InstrumentationError('Group call parameters are not valid'); } - - return respList; -} + return groupInfo; +}; +const getUpdatedPageNameWithoutUserDefinedPageEventName = (name, message, CATEGORY_KEY) => + name || get(message, CATEGORY_KEY) ? `${name || get(message, CATEGORY_KEY)} ` : undefined; // Generic process function which invokes specific handler functions depending on message type // and event type where applicable -function processSingleMessage(message, destination) { +const processSingleMessage = (message, destination) => { let payloadObjectName = 'events'; let evType; - let groupTraits; - let groupTypeTrait; - let groupValueTrait; // It is expected that Rudder alias. identify group calls won't have this set // To be used for track/page calls to associate the event to a group in AM let groupInfo = get(message, 'integrations.Amplitude.groups') || undefined; let category = ConfigCategory.DEFAULT; - + let { properties } = message; + const { name, event } = message; const messageType = message.type.toLowerCase(); const CATEGORY_KEY = 'properties.category'; const { useUserDefinedPageEventName, userProvidedPageEventString } = destination.Config; @@ -545,26 +610,30 @@ function processSingleMessage(message, destination) { .trim(); evType = userProvidedPageEventString.trim() === '' - ? message.name + ? name : userProvidedPageEventString .trim() .replaceAll(/{{([^{}]+)}}/g, get(message, getMessagePath)); } else { - evType = `Viewed ${message.name || get(message, CATEGORY_KEY) || ''} Page`; + const updatedName = getUpdatedPageNameWithoutUserDefinedPageEventName( + name, + message, + CATEGORY_KEY, + ); + evType = `Viewed ${updatedName || ''}Page`; } - message.properties = { ...message.properties, - name: message.name || get(message, CATEGORY_KEY), + name: name || get(message, CATEGORY_KEY), }; category = ConfigCategory.PAGE; break; case EventType.SCREEN: - evType = `Viewed ${message.name || message.event || get(message, CATEGORY_KEY) || ''} Screen`; - message.properties = { - ...message.properties, - name: message.name || message.event || get(message, CATEGORY_KEY), - }; + ({ evType, updatedProperties: properties } = getScreenevTypeAndUpdatedProperties( + message, + CATEGORY_KEY, + )); + message.properties = properties; category = ConfigCategory.SCREEN; break; case EventType.GROUP: @@ -574,34 +643,13 @@ function processSingleMessage(message, destination) { // read from group traits from message // groupTraits => top level "traits" for JS SDK // groupTraits => "context.traits" for mobile SDKs - groupTraits = getFieldValueFromMessage(message, 'groupTraits'); + groupInfo = getGroupInfo( + destination, + groupInfo, + getFieldValueFromMessage(message, 'groupTraits'), + ); // read destination config related group settings // https://developers.amplitude.com/docs/group-identify-api - groupTypeTrait = get(destination, 'Config.groupTypeTrait'); - groupValueTrait = get(destination, 'Config.groupValueTrait'); - if (groupTypeTrait && groupValueTrait) { - const groupTypeValue = get(groupTraits, groupTypeTrait); - const groupNameValue = get(groupTraits, groupValueTrait); - // since the property updates on group at https://api2.amplitude.com/groupidentify - // expects a string group name and value , so error out if the keys are not primitive - // Note: This different for groups object at https://api.amplitude.com/2/httpapi where the - // group value can be array of strings as well. - if ( - groupTypeValue && - typeof groupTypeValue === 'string' && - groupNameValue && - (typeof groupNameValue === 'string' || typeof groupNameValue === 'number') - ) { - groupInfo = {}; - groupInfo.group_type = groupTypeValue; - groupInfo.group_value = groupNameValue; - // passing the entire group traits without deleting the above keys - groupInfo.group_properties = groupTraits; - } else { - logger.debug('Group call parameters are not valid'); - throw new InstrumentationError('Group call parameters are not valid'); - } - } break; case EventType.ALIAS: evType = 'alias'; @@ -611,21 +659,19 @@ function processSingleMessage(message, destination) { category = ConfigCategory.ALIAS; break; case EventType.TRACK: - evType = message.event; + evType = event; if (!isDefinedAndNotNullAndNotEmpty(evType)) { - throw new InstrumentationError('message type not defined'); + throw new InstrumentationError('Event not present. Please send event field'); } if ( message.properties && - isDefinedAndNotNull(message.properties.revenue) && - isDefinedAndNotNull(message.properties.revenue_type) + isDefinedAndNotNull(message.properties?.revenue) && + isDefinedAndNotNull(message.properties?.revenue_type) ) { // if properties has revenue and revenue_type fields // consider the event as revenue event directly category = ConfigCategory.REVENUE; - break; } - break; default: logger.debug('could not determine type'); @@ -639,10 +685,10 @@ function processSingleMessage(message, destination) { mappingConfig[category.name], destination, ); -} +}; -function createProductPurchasedEvent(message, destination, product, counter) { - const eventClonePurchaseProduct = JSON.parse(JSON.stringify(message)); +const createProductPurchasedEvent = (message, destination, product, counter) => { + const eventClonePurchaseProduct = cloneDeep(message); eventClonePurchaseProduct.event = 'Product Purchased'; // In product purchased event event properties consists of the details of each product @@ -654,17 +700,17 @@ function createProductPurchasedEvent(message, destination, product, counter) { // need to modify the message id of each newly created event, as it is mapped to insert_id and that is used by Amplitude for dedup. eventClonePurchaseProduct.messageId = `${message.messageId}-${counter}`; return eventClonePurchaseProduct; -} +}; -function isProductArrayInPayload(message) { +const isProductArrayInPayload = (message) => { const isProductArray = - (message.properties.products && + (message.properties?.products && Array.isArray(message.properties.products) && message.properties.products.length > 0) === true; return isProductArray; -} +}; -function getProductPurchasedEvents(message, destination) { +const getProductPurchasedEvents = (message, destination) => { const productPurchasedEvents = []; if (isProductArrayInPayload(message)) { let counter = 0; @@ -682,16 +728,16 @@ function getProductPurchasedEvents(message, destination) { }); } return productPurchasedEvents; -} +}; -function trackRevenueEvent(message, destination) { +const trackRevenueEvent = (message, destination) => { let sendEvents = []; - const originalEvent = JSON.parse(JSON.stringify(message)); + const originalEvent = cloneDeep(message); if (destination.Config.trackProductsOnce === false) { if (isProductArrayInPayload(message)) { // when trackProductsOnce false no product array present - delete originalEvent.properties.products; + delete originalEvent.properties?.products; } else { // when product array is not there in payload, will track the revenue of the original event. originalEvent.isRevenue = true; @@ -719,16 +765,19 @@ function trackRevenueEvent(message, destination) { } } return sendEvents; -} +}; -function process(event) { +const process = (event) => { const respList = []; const { message, destination } = event; - const messageType = message.type.toLowerCase(); + const messageType = message.type?.toLowerCase(); const toSendEvents = []; + if (!destination?.Config?.apiKey) { + throw new ConfigurationError('No API Key is Found. Please Configure API key from dashbaord'); + } if (messageType === EventType.TRACK) { const { properties } = message; - if (properties && isDefinedAndNotNull(properties.revenue)) { + if (isDefinedAndNotNull(properties?.revenue)) { const revenueEvents = trackRevenueEvent(message, destination); revenueEvents.forEach((revenueEvent) => { toSendEvents.push(revenueEvent); @@ -744,9 +793,9 @@ function process(event) { respList.push(...processSingleMessage(sendEvent, destination)); }); return respList; -} +}; -function getBatchEvents(message, destination, metadata, batchEventResponse) { +const getBatchEvents = (message, destination, metadata, batchEventResponse) => { let batchComplete = false; const batchEventArray = get(batchEventResponse, 'batchedRequest.body.JSON.events') || []; const batchEventJobs = get(batchEventResponse, 'metadata') || []; @@ -760,18 +809,19 @@ function getBatchEvents(message, destination, metadata, batchEventResponse) { : incomingMessageEvent; const userId = incomingMessageEvent.user_id; - // delete the userId as it is less than 5 as AM is giving 400 - // that is not a documented behviour where it states if either deviceid or userid is present - // batch request won't return 400 - // { - // "code": 400, - // "events_with_invalid_id_lengths": { - // "user_id": [ - // 0 - // ] - // }, - // "error": "Invalid id length for user_id or device_id" - // } + /* delete the userId as it is less than 5 as AM is giving 400 + that is not a documented behviour where it states if either deviceid or userid is present + batch request won't return 400 + { + "code": 400, + "events_with_invalid_id_lengths": { + "user_id": [ + 0 + ] + }, + "error": "Invalid id length for user_id or device_id" + } + */ if (batchEventsWithUserIdLengthLowerThanFive && userId && userId.length < 5) { delete incomingMessageEvent.user_id; } @@ -782,10 +832,7 @@ function getBatchEvents(message, destination, metadata, batchEventResponse) { if (batchEventArray.length === 0) { if (JSON.stringify(incomingMessageJSON).length < AMBatchSizeLimit) { delete message.body.JSON.options; - batchEventResponse = Object.assign(batchEventResponse, { - batchedRequest: message, - }); - + batchEventResponse.batchedRequest = message; set(batchEventResponse, 'batchedRequest.endpoint', BATCH_ENDPOINT); batchEventResponse.metadata = [metadata]; } @@ -807,16 +854,16 @@ function getBatchEvents(message, destination, metadata, batchEventResponse) { } } return batchComplete; -} +}; -function batch(destEvents) { +const getFirstEvent = (messageEvent) => + messageEvent && Array.isArray(messageEvent) ? messageEvent[0] : messageEvent; +const batch = (destEvents) => { const respList = []; let batchEventResponse = defaultBatchRequestConfig(); let response; let isBatchComplete; let jsonBody; - let userId; - let deviceId; let messageEvent; let destinationObject; destEvents.forEach((ev) => { @@ -824,18 +871,11 @@ function batch(destEvents) { destinationObject = { ...destination }; jsonBody = get(message, 'body.JSON'); messageEvent = get(message, EVENTS_KEY_PATH); - userId = - messageEvent && Array.isArray(messageEvent) - ? messageEvent[0].user_id - : messageEvent - ? messageEvent.user_id - : undefined; - deviceId = - messageEvent && Array.isArray(messageEvent) - ? messageEvent[0].device_id - : messageEvent - ? messageEvent.device_id - : undefined; + const firstEvent = getFirstEvent(messageEvent); + + const userId = firstEvent?.user_id ?? undefined; + const deviceId = firstEvent?.device_id ?? undefined; + // this case shold not happen and should be filtered already // by the first pass of single event transformation if (messageEvent && !userId && !deviceId) { @@ -851,16 +891,13 @@ function batch(destEvents) { respList.push(errorResponse); return; } - // check if not a JSON body or (userId length < 5 && batchEventsWithUserIdLengthLowerThanFive is false) or - // (batchEventsWithUserIdLengthLowerThanFive is true and userId is less than 5 but deviceId not present) - // , send the event as is after batching - if ( - Object.keys(jsonBody).length === 0 || - (!batchEventsWithUserIdLengthLowerThanFive && userId && userId.length < 5) || - (batchEventsWithUserIdLengthLowerThanFive && userId && userId.length < 5 && !deviceId) - ) { + /* check if not a JSON body or (userId length < 5 && batchEventsWithUserIdLengthLowerThanFive is false) or + (batchEventsWithUserIdLengthLowerThanFive is true and userId is less than 5 but deviceId not present), + send the event as is after batching + */ + if (checkForJSONAndUserIdLengthAndDeviceId(jsonBody, userId, deviceId)) { response = defaultBatchRequestConfig(); - response = Object.assign(response, { batchedRequest: message }); + response.batchedRequest = message; response.metadata = [metadata]; response.destination = destinationObject; respList.push(response); @@ -868,8 +905,9 @@ function batch(destEvents) { // check if the event can be pushed to an existing batch isBatchComplete = getBatchEvents(message, destination, metadata, batchEventResponse); if (isBatchComplete) { - // if the batch is already complete, push it to response list - // and push the event to a new batch + /* if the batch is already complete, push it to response list + and push the event to a new batch + */ batchEventResponse.destination = destinationObject; respList.push({ ...batchEventResponse }); batchEventResponse = defaultBatchRequestConfig(); @@ -879,12 +917,12 @@ function batch(destEvents) { } }); // if there is some unfinished batch push it to response list - if (isBatchComplete !== undefined && isBatchComplete === false) { + if (isDefinedAndNotNull(isBatchComplete) && !isBatchComplete) { batchEventResponse.destination = destinationObject; respList.push(batchEventResponse); } return respList; -} +}; const processRouterDest = async (inputs, reqMetadata) => { const respList = await simpleProcessRouterDest(inputs, process, reqMetadata); diff --git a/src/v0/destinations/am/utils.js b/src/v0/destinations/am/utils.js index 33040c2146..b9925c20d8 100644 --- a/src/v0/destinations/am/utils.js +++ b/src/v0/destinations/am/utils.js @@ -13,65 +13,65 @@ const uaParser = require('@amplitude/ua-parser-js'); const logger = require('../../../logger'); const { isDefinedAndNotNull } = require('../../util'); -function getInfoFromUA(path, payload, defaultVal) { +const getInfoFromUA = (path, payload, defaultVal) => { const ua = get(payload, 'context.userAgent'); const devInfo = ua ? uaParser(ua) : {}; return get(devInfo, path) || defaultVal; -} +}; -function getOSName(payload, sourceKey) { +const getOSName = (payload, sourceKey) => { const payloadVal = get(payload, sourceKey); if (payload.channel && payload.channel.toLowerCase() === 'web') { return getInfoFromUA('browser.name', payload, payloadVal); } return payloadVal; -} +}; -function getOSVersion(payload, sourceKey) { +const getOSVersion = (payload, sourceKey) => { const payloadVal = get(payload, sourceKey); if (payload.channel && payload.channel.toLowerCase() === 'web') { return getInfoFromUA('browser.version', payload, payloadVal); } return payloadVal; -} +}; -function getDeviceModel(payload, sourceKey) { +const getDeviceModel = (payload, sourceKey) => { const payloadVal = get(payload, sourceKey); if (payload.channel && payload.channel.toLowerCase() === 'web') { return getInfoFromUA('os.name', payload, payloadVal); } return payloadVal; -} +}; -function getDeviceManufacturer(payload, sourceKey) { +const getDeviceManufacturer = (payload, sourceKey) => { const payloadVal = get(payload, sourceKey); if (payload.channel && payload.channel.toLowerCase() === 'web') { return getInfoFromUA('device.vendor', payload, payloadVal); } return payloadVal; -} +}; -function getPlatform(payload, sourceKey) { +const getPlatform = (payload, sourceKey) => { const payloadVal = get(payload, sourceKey); return payload.channel ? payload.channel.toLowerCase() === 'web' ? 'Web' : payloadVal : payloadVal; -} +}; -function getBrand(payload, sourceKey, Config) { +const getBrand = (payload, sourceKey, Config) => { if (Config.mapDeviceBrand) { const payloadVal = get(payload, sourceKey); return payloadVal; } return undefined; -} +}; -function getEventId(payload, sourceKey) { +const getEventId = (payload, sourceKey) => { const eventId = get(payload, sourceKey); if (isDefinedAndNotNull(eventId)) { @@ -80,7 +80,7 @@ function getEventId(payload, sourceKey) { } else return eventId; } return undefined; -} +}; module.exports = { getOSName, diff --git a/src/v0/util/index.js b/src/v0/util/index.js index 4ea3d3783d..ea08d08c8a 100644 --- a/src/v0/util/index.js +++ b/src/v0/util/index.js @@ -2053,6 +2053,16 @@ const getAuthErrCategoryFromStCode = (status) => { return ''; }; +const isValidInteger = (value) => { + if (Number.isNaN(value) || !isDefinedAndNotNull(value)) { + return false; + } + if (typeof value === 'number' && value % 1 === 0) { + return true; + } + // Use a regular expression to check if the string is a valid integer or a valid floating-point number + return typeof value === 'string' ? /^-?\d+$/.test(value) : false; +}; const validateEventType = (event) => { if (!event || typeof event !== 'string') { throw new InstrumentationError('Event is a required field and should be a string'); @@ -2172,6 +2182,7 @@ module.exports = { hasCircularReference, getAuthErrCategoryFromErrDetailsAndStCode, getAuthErrCategoryFromStCode, + isValidInteger, isNewStatusCodesAccepted, IsGzipSupported, }; diff --git a/src/v0/util/index.test.js b/src/v0/util/index.test.js index ce341e8187..e39c583aab 100644 --- a/src/v0/util/index.test.js +++ b/src/v0/util/index.test.js @@ -13,6 +13,7 @@ const functionNames = [ 'batchMultiplexedEvents', 'removeUndefinedNullValuesAndEmptyObjectArray', 'groupEventsByType', + 'isValidInteger', ]; // Names of the utility functions to test which expects multiple arguments as values and not objects diff --git a/src/v0/util/testdata/isValidInteger.json b/src/v0/util/testdata/isValidInteger.json new file mode 100644 index 0000000000..be7be936a6 --- /dev/null +++ b/src/v0/util/testdata/isValidInteger.json @@ -0,0 +1,31 @@ +[ + { + "description": "Number is undefined", + "output": false + }, + { + "description": "Number in string format", + "input": "123", + "output": true + }, + { + "description": "Normal Integer", + "input": 123, + "output": true + }, + { + "description": "Float", + "input": 123.91, + "output": false + }, + { + "description": "Float string", + "input": "123.00", + "output": false + }, + { + "description": "Alphanumeric String", + "input": "abcd1234", + "output": false + } +] diff --git a/test/__tests__/data/am_batch_input.json b/test/__tests__/data/am_batch_input.json index 64fe0b4ec7..5b0440babf 100644 --- a/test/__tests__/data/am_batch_input.json +++ b/test/__tests__/data/am_batch_input.json @@ -1,4 +1,67 @@ [ + [ + { + "message": { + "body": { + "XML": {}, + "JSON_ARRAY": {}, + "FORM": {}, + "JSON": { + "events": [ + { + "ip": "0.0.0.0", + "time": 1603132665557, + "os_name": "", + "app_name": "RudderLabs JavaScript SDK", + "language": "en-US", + "library": "rudderstack", + "event_type": "$identify", + "os_version": "", + "session_id": -1, + "app_version": "1.1.5", + "user_properties": { + "name": "some campaign", + "plan": "Open source", + "term": "keyword", + "test": "other value", + "email": "test@rudderstack.com", + "logins": 5, + "medium": "medium", + "source": "google", + "content": "some content", + "category": "SampleIdentify", + "createdAt": 1599264000 + } + } + ], + "api_key": "4c7ed7573eb73517ee4c26ed4bde9a85", + "options": { + "min_id_length": 1 + } + } + }, + "type": "REST", + "files": {}, + "method": "POST", + "params": {}, + "headers": { + "Content-Type": "application/json" + }, + "version": "1", + "endpoint": "https://api.eu.amplitude.com/2/httpapi" + }, + "metadata": { + "job_id": 1 + }, + "destination": { + "ID": "a", + "url": "a", + "Config": { + "residencyServer": "EU" + } + } + } + ], [ { "message": { diff --git a/test/__tests__/data/am_batch_output.json b/test/__tests__/data/am_batch_output.json index dac6400585..32735b000f 100644 --- a/test/__tests__/data/am_batch_output.json +++ b/test/__tests__/data/am_batch_output.json @@ -1,4 +1,18 @@ [ + [ + { + "batched": false, + "error": "Both userId and deviceId cannot be undefined", + "metadata": { + "job_id": 1 + }, + "statTags": { + "errorCategory": "dataValidation", + "errorType": "instrumentation" + }, + "statusCode": 400 + } + ], [ { "batchedRequest": { @@ -108,7 +122,9 @@ "JSON_ARRAY": {}, "FORM": { "api_key": "4c7ed7573eb73517ee4c26ed4bde9a85", - "mapping": ["{\"global_user_id\":\"newUserIdAlias\",\"user_id\":\"sampleusrRudder3\"}"] + "mapping": [ + "{\"global_user_id\":\"newUserIdAlias\",\"user_id\":\"sampleusrRudder3\"}" + ] }, "JSON": {} }, @@ -361,7 +377,9 @@ "JSON_ARRAY": {}, "FORM": { "api_key": "4c7ed7573eb73517ee4c26ed4bde9a85", - "mapping": ["{\"global_user_id\":\"newUserIdAlias\",\"user_id\":\"sampleusrRudder3\"}"] + "mapping": [ + "{\"global_user_id\":\"newUserIdAlias\",\"user_id\":\"sampleusrRudder3\"}" + ] }, "JSON": {} }, @@ -687,14 +705,29 @@ "DisplayName": "Braze", "Config": { "destConfig": { - "android": ["useNativeSDK"], - "defaultConfig": ["appKey", "dataCenter", "restApiKey"], - "ios": ["useNativeSDK"], - "web": ["useNativeSDK"] + "android": [ + "useNativeSDK" + ], + "defaultConfig": [ + "appKey", + "dataCenter", + "restApiKey" + ], + "ios": [ + "useNativeSDK" + ], + "web": [ + "useNativeSDK" + ] }, "excludeKeys": [], - "includeKeys": ["appKey", "dataCenter"], - "secretKeys": ["restApiKey"], + "includeKeys": [ + "appKey", + "dataCenter" + ], + "secretKeys": [ + "restApiKey" + ], "supportedSourceTypes": [ "android", "ios", @@ -760,7 +793,9 @@ "JSON_ARRAY": {}, "FORM": { "api_key": "4c7ed7573eb73517ee4c26ed4bde9a85", - "mapping": ["{\"global_user_id\":\"newUserIdAlias\",\"user_id\":\"sampleusrRudder3\"}"] + "mapping": [ + "{\"global_user_id\":\"newUserIdAlias\",\"user_id\":\"sampleusrRudder3\"}" + ] }, "JSON": {} }, @@ -1043,7 +1078,9 @@ "JSON_ARRAY": {}, "FORM": { "api_key": "4c7ed7573eb73517ee4c26ed4bde9a85", - "mapping": ["{\"global_user_id\":\"newUserIdAlias\",\"user_id\":\"sampleusrRudder3\"}"] + "mapping": [ + "{\"global_user_id\":\"newUserIdAlias\",\"user_id\":\"sampleusrRudder3\"}" + ] }, "JSON": {} }, @@ -1189,4 +1226,4 @@ } } ] -] +] \ No newline at end of file diff --git a/test/__tests__/data/am_input.json b/test/__tests__/data/am_input.json index 664894f7f5..3e5cde2cca 100644 --- a/test/__tests__/data/am_input.json +++ b/test/__tests__/data/am_input.json @@ -1,4 +1,214 @@ [ + { + "message": { + "type": "track", + "sentAt": "2020-08-14T05:30:30.118Z", + "context": { + "source": "test", + "traits": { + "anonymousId": "50be5c78-6c3f-4b60-be84-97805a316fb1" + }, + "library": { + "name": "rudder-sdk-ruby-sync", + "version": "1.0.6" + } + }, + "messageId": "7208bbb6-2c4e-45bb-bf5b-ad426f3593e9", + "timestamp": "2020-08-14T05:30:30.118Z", + "properties": { + "tax": 2, + "total": 27.5, + "coupon": "hasbros", + "revenue": 48, + "revenue_type": "Purchased", + "quantity": 2, + "currency": "USD", + "discount": 2.5, + "order_id": "50314b8e9bcf000000000000", + "shipping": 3, + "subtotal": 22.5, + "affiliation": "Google Store", + "checkout_id": "fksdjfsdjfisjf9sdfjsd9f" + }, + "anonymousId": "50be5c78-6c3f-4b60-be84-97805a316fb1", + "integrations": { + "S3": false, + "All": true + } + }, + "destination": { + "Config": { + "apiKey": "abcde", + "groupTypeTrait": "email", + "groupValueTrait": "age", + "trackProductsOnce": true, + "trackRevenuePerProduct": false + } + } + }, + { + "message": { + "type": "UNSUPPORTED-TYPE", + "event": "Order Completed", + "sentAt": "2020-08-14T05:30:30.118Z", + "context": {}, + "messageId": "7208bbb6-2c4e-45bb-bf5b-ad426f3593e9", + "timestamp": "2020-08-14T05:30:30.118Z", + "properties": {}, + "anonymousId": "50be5c78-6c3f-4b60-be84-97805a316fb1", + "integrations": { + "S3": false, + "All": true + } + }, + "destination": { + "Config": { + "groupTypeTrait": "email", + "apiKey": "abcde", + "groupValueTrait": "age", + "trackProductsOnce": true, + "trackRevenuePerProduct": false + } + } + }, + { + "message": { + "event": "Order Completed", + "sentAt": "2020-08-14T05:30:30.118Z", + "context": {}, + "messageId": "7208bbb6-2c4e-45bb-bf5b-ad426f3593e9", + "timestamp": "2020-08-14T05:30:30.118Z", + "properties": {}, + "anonymousId": "50be5c78-6c3f-4b60-be84-97805a316fb1", + "integrations": { + "S3": false, + "All": true + } + }, + "destination": { + "Config": { + "groupTypeTrait": "email", + "groupValueTrait": "age", + "trackProductsOnce": true, + "trackRevenuePerProduct": false + } + } + }, + { + "message": { + "type": "track", + "event": "Order Completed", + "sentAt": "2020-08-14T05:30:30.118Z", + "context": { + "source": "test", + "traits": { + "anonymousId": "50be5c78-6c3f-4b60-be84-97805a316fb1" + }, + "library": { + "name": "rudder-sdk-ruby-sync", + "version": "1.0.6" + } + }, + "messageId": "7208bbb6-2c4e-45bb-bf5b-ad426f3593e9", + "timestamp": "2020-08-14T05:30:30.118Z", + "properties": { + "tax": 2, + "total": 27.5, + "coupon": "hasbros", + "revenue": 48, + "revenue_type": "Purchased", + "quantity": 2, + "currency": "USD", + "discount": 2.5, + "order_id": "50314b8e9bcf000000000000", + "shipping": 3, + "subtotal": 22.5, + "affiliation": "Google Store", + "checkout_id": "fksdjfsdjfisjf9sdfjsd9f" + }, + "anonymousId": "50be5c78-6c3f-4b60-be84-97805a316fb1", + "integrations": { + "S3": false, + "All": true + } + }, + "destination": { + "Config": { + "apiKey": "abcde", + "groupTypeTrait": "email", + "groupValueTrait": "age", + "trackProductsOnce": true, + "trackRevenuePerProduct": false + } + } + }, + { + "message": { + "type": "track", + "event": "Order Completed", + "sentAt": "2020-08-14T05:30:30.118Z", + "context": { + "source": "test", + "traits": { + "anonymousId": "50be5c78-6c3f-4b60-be84-97805a316fb1" + }, + "library": { + "name": "rudder-sdk-ruby-sync", + "version": "1.0.6" + } + }, + "messageId": "7208bbb6-2c4e-45bb-bf5b-ad426f3593e9", + "timestamp": "2020-08-14T05:30:30.118Z", + "properties": { + "tax": 2, + "total": 27.5, + "coupon": "hasbros", + "revenue": 48, + "quantity": 2, + "currency": "USD", + "discount": 2.5, + "order_id": "50314b8e9bcf000000000000", + "products": [ + { + "sku": "45790-32", + "url": "https://www.example.com/product/path", + "name": "Monopoly: 3rd Edition", + "price": 19, + "category": "Games", + "quantity": 1, + "image_url": "https:///www.example.com/product/path.jpg", + "product_id": "507f1f77bcf86cd799439011" + }, + { + "sku": "46493-32", + "name": "Uno Card Game", + "price": 3, + "category": "Games", + "quantity": 2, + "product_id": "505bd76785ebb509fc183733" + } + ], + "shipping": 3, + "subtotal": 22.5, + "affiliation": "Google Store", + "checkout_id": "fksdjfsdjfisjf9sdfjsd9f" + }, + "anonymousId": "50be5c78-6c3f-4b60-be84-97805a316fb1", + "integrations": { + "S3": false, + "All": true + } + }, + "destination": { + "Config": { + "apiKey": "abcde", + "groupTypeTrait": "email", + "groupValueTrait": "age", + "trackProductsOnce": true, + "trackRevenuePerProduct": false + } + } + }, { "message": { "channel": "web", @@ -629,6 +839,91 @@ } } }, + { + "message": { + "channel": "web", + "context": { + "app": { + "build": "1.0.0", + "name": "RudderLabs JavaScript SDK", + "namespace": "com.rudderlabs.javascript", + "version": "1.1.5" + }, + "traits": { + "name": "Shehan Study", + "category": "SampleIdentify", + "email": "test@rudderstack.com", + "plan": "Open source", + "logins": 5, + "createdAt": 1599264000 + }, + "library": { + "name": "RudderLabs JavaScript SDK", + "version": "1.1.5" + }, + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36", + "locale": "en-US", + "os": { + "name": "", + "version": "" + }, + "screen": { + "density": 0.8999999761581421 + }, + "campaign": { + "source": "google", + "medium": "medium", + "term": "keyword", + "content": "some content", + "name": "some campaign", + "test": "other value" + }, + "page": { + "path": "/destinations/amplitude", + "referrer": "", + "search": "", + "title": "", + "url": "https://docs.rudderstack.com/destinations/amplitude", + "category": "destination", + "initial_referrer": "https://docs.rudderstack.com", + "initial_referring_domain": "docs.rudderstack.com" + } + }, + "type": "group", + "messageId": "e5034df0-a404-47b4-a463-76df99934fea", + "originalTimestamp": "2020-10-20T07:54:58.983Z", + "anonymousId": "my-anonymous-id-new", + "userId": "sampleusrRudder3", + "integrations": { + "All": true, + "Amplitude": { + "groups": { + "group_type": "Company", + "group_value": "ABC" + } + } + }, + "groupId": "Sample_groupId23", + "traits": { + "KEY_3": { + "CHILD_KEY_92": "value_95", + "CHILD_KEY_102": "value_103" + }, + "KEY_2": { + "CHILD_KEY_92": "value_95", + "CHILD_KEY_102": "value_103" + }, + "name_trait": "Company", + "value_trait": "ABC" + }, + "sentAt": "2020-10-20T07:54:58.983Z" + }, + "destination": { + "Config": { + "apiKey": "abcde" + } + } + }, { "message": { "channel": "web", @@ -698,7 +993,7 @@ "CHILD_KEY_102": "value_103" }, "name_trait": "Company", - "value_trait": "Comapny-ABC" + "value_trait": "ABC" }, "sentAt": "2020-10-20T07:54:58.983Z" }, @@ -779,7 +1074,9 @@ "CHILD_KEY_102": "value_103" }, "name_trait": "Company", - "value_trait": ["Comapny-ABC"] + "value_trait": [ + "ABC" + ] }, "sentAt": "2020-10-20T07:54:58.983Z" }, @@ -3173,7 +3470,7 @@ "CHILD_KEY_102": "value_103" }, "name_trait": "Company", - "value_trait": "Comapny-ABC" + "value_trait": "ABC" }, "sentAt": "2020-10-20T07:54:58.983Z" }, @@ -3718,7 +4015,7 @@ "CHILD_KEY_102": "value_103" }, "name_trait": "Company", - "value_trait": "Comapny-ABC" + "value_trait": "ABC" }, "sentAt": "2020-10-20T07:54:58.983Z" }, @@ -3965,6 +4262,69 @@ } } }, + { + "message": { + "anonymousId": "5d205961641ee6c5", + "channel": "mobile", + "context": { + "app": { + "build": "6", + "name": "Sample Kotlin", + "namespace": "com.example.testapp1mg", + "version": "1.2" + }, + "device": { + "id": "5d205961641ee6c5", + "manufacturer": "Google", + "model": "Android SDK built for x86", + "name": "generic_x86", + "type": "Android" + }, + "library": { + "name": "com.rudderstack.android.sdk.core", + "version": "1.7.0" + }, + "locale": "en-US", + "network": { + "carrier": "Android", + "bluetooth": false, + "cellular": true, + "wifi": true + }, + "os": { + "name": "Android", + "version": "7.1.1" + }, + "screen": { + "density": 440, + "height": 2148, + "width": 1080 + }, + "sessionId": "1662393792", + "timezone": "Asia/Kolkata", + "traits": { + "anonymousId": "5d205961641ee6c5", + "id": "User Android", + "userId": "User Android" + }, + "userAgent": "Dalvik/2.1.0 (Linux; U; Android 7.1.1; Android SDK built for x86 Build/NYC)" + }, + "integrations": { + "All": true + }, + "messageId": "1662393883248-509420bf-b812-4f8d-bdb2-8c811bfde87f", + "properties": { + }, + "originalTimestamp": "2022-09-05T16:04:43.250Z", + "type": "screen", + "userId": "User Android" + }, + "destination": { + "Config": { + "apiKey": "abcde" + } + } + }, { "message": { "channel": "web", @@ -4185,7 +4545,7 @@ "CHILD_KEY_102": "value_103" }, "name_trait": "Company", - "value_trait": "Comapny-ABC" + "value_trait": "ABC" }, "sentAt": "2020-10-20T07:54:58.983Z" }, @@ -4464,7 +4824,6 @@ "search": "", "title": "", "url": "https://docs.rudderstack.com/destinations/amplitude", - "category": "destination", "initial_referrer": "https://docs.rudderstack.com", "initial_referring_domain": "docs.rudderstack.com" }, @@ -4474,15 +4833,14 @@ "event_id": 2 } }, - "name": "ApplicationLoaded", "sentAt": "2019-10-14T11:15:53.296Z" }, "destination": { "Config": { "apiKey": "abcde", - "useUserDefinedPageEventName": true, + "useUserDefinedPageEventName": false, "userProvidedPageEventString": "Viewed {{context.page.title}} event." } } } -] +] \ No newline at end of file diff --git a/test/__tests__/data/am_output.json b/test/__tests__/data/am_output.json index b82df8ae0d..34471f4922 100644 --- a/test/__tests__/data/am_output.json +++ b/test/__tests__/data/am_output.json @@ -1,4 +1,138 @@ [ + { + "error": "Event not present. Please send event field" + }, + { + "error": "message type not supported" + }, + { + "error": "No API Key is Found. Please Configure API key from dashbaord" + }, + { + "version": "1", + "type": "REST", + "method": "POST", + "endpoint": "https://api2.amplitude.com/2/httpapi", + "headers": { + "Content-Type": "application/json" + }, + "params": {}, + "body": { + "JSON": { + "api_key": "abcde", + "events": [ + { + "device_id": "50be5c78-6c3f-4b60-be84-97805a316fb1", + "library": "rudderstack", + "event_type": "Order Completed", + "insert_id": "7208bbb6-2c4e-45bb-bf5b-ad426f3593e9", + "event_properties": { + "tax": 2, + "total": 27.5, + "coupon": "hasbros", + "currency": "USD", + "discount": 2.5, + "order_id": "50314b8e9bcf000000000000", + "revenue_type": "Purchased", + "shipping": 3, + "subtotal": 22.5, + "affiliation": "Google Store", + "checkout_id": "fksdjfsdjfisjf9sdfjsd9f" + }, + "revenueType": "Purchased", + "price": 48, + "quantity": 1, + "revenue": 48, + "time": 1597383030118, + "user_properties": { + "anonymousId": "50be5c78-6c3f-4b60-be84-97805a316fb1" + }, + "session_id": -1 + } + ], + "options": { + "min_id_length": 1 + } + }, + "XML": {}, + "JSON_ARRAY": {}, + "FORM": {} + }, + "files": {}, + "userId": "50be5c78-6c3f-4b60-be84-97805a316fb1" + }, + { + "version": "1", + "type": "REST", + "method": "POST", + "endpoint": "https://api2.amplitude.com/2/httpapi", + "headers": { + "Content-Type": "application/json" + }, + "params": {}, + "body": { + "JSON": { + "api_key": "abcde", + "events": [ + { + "device_id": "50be5c78-6c3f-4b60-be84-97805a316fb1", + "library": "rudderstack", + "event_type": "Order Completed", + "insert_id": "7208bbb6-2c4e-45bb-bf5b-ad426f3593e9", + "event_properties": { + "tax": 2, + "total": 27.5, + "coupon": "hasbros", + "currency": "USD", + "discount": 2.5, + "order_id": "50314b8e9bcf000000000000", + "products": [ + { + "category": "Games", + "image_url": "https:///www.example.com/product/path.jpg", + "name": "Monopoly: 3rd Edition", + "price": 19, + "product_id": "507f1f77bcf86cd799439011", + "quantity": 1, + "sku": "45790-32", + "url": "https://www.example.com/product/path" + }, + { + "category": "Games", + "name": "Uno Card Game", + "price": 3, + "product_id": "505bd76785ebb509fc183733", + "quantity": 2, + "sku": "46493-32" + } + ], + "shipping": 3, + "subtotal": 22.5, + "affiliation": "Google Store", + "checkout_id": "fksdjfsdjfisjf9sdfjsd9f" + }, + "revenueType": "Purchased", + "price": 48, + "quantity": 1, + "revenue": 48, + "time": 1597383030118, + "user_properties": { + "anonymousId": "50be5c78-6c3f-4b60-be84-97805a316fb1" + }, + "session_id": -1 + } + ], + "options": { + "min_id_length": 1 + } + }, + "XML": {}, + "JSON_ARRAY": {}, + "FORM": {} + }, + "files": {}, + "userId": "50be5c78-6c3f-4b60-be84-97805a316fb1" + }, { "version": "1", "type": "REST", @@ -21,7 +155,7 @@ "app_name": "RudderLabs JavaScript SDK", "app_version": "1.0.0", "language": "en-US", - "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22", + "session_id": -1, "insert_id": "84e26acc-56a5-4835-8233-591137fca468", "ip": "0.0.0.0", "user_properties": { @@ -77,7 +211,7 @@ "app_name": "RudderLabs JavaScript SDK", "app_version": "1.0.0", "language": "en-US", - "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22", + "session_id": -1, "insert_id": "84e26acc-56a5-4835-8233-591137fca468", "ip": "0.0.0.0", "user_properties": { @@ -137,7 +271,7 @@ "app_name": "RudderLabs JavaScript SDK", "app_version": "1.0.0", "language": "en-US", - "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22", + "session_id": -1, "event_type": "$identify", "user_properties": { "anonymousId": "123456", @@ -202,7 +336,7 @@ "initial_referring_domain": "docs.rudderstack.com", "initial_referrer": "https://docs.rudderstack.com" }, - "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22", + "session_id": -1, "ip": "1.1.1.1", "time": 1571051718299, "user_id": "12345", @@ -261,7 +395,7 @@ "initial_referring_domain": "docs.rudderstack.com", "initial_referrer": "https://docs.rudderstack.com" }, - "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22", + "session_id": -1, "ip": "1.1.1.1", "groups": { "Company": "ABC" @@ -312,7 +446,7 @@ "app_version": "1.0.0", "language": "en-US", "event_type": "test track event", - "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22", + "session_id": -1, "event_properties": { "user_actual_role": "system_admin", "user_actual_id": 12345, @@ -366,7 +500,7 @@ "app_name": "RudderLabs JavaScript SDK", "app_version": "1.0.0", "language": "en-US", - "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22", + "session_id": -1, "event_type": "$identify", "user_properties": { "anonymousId": "123456", @@ -419,7 +553,7 @@ "timestamp": "2020-08-28 09:00:00" }, "event_type": "$identify", - "session_id": "1598597129", + "session_id": 1598597129, "time": 0, "user_id": "ubcdfghi0001" } @@ -450,7 +584,7 @@ "events": [ { "device_id": "123456", - "session_id": "1598597129", + "session_id": 1598597129, "event_type": "$identify", "library": "rudderstack", "user_properties": { @@ -555,6 +689,83 @@ "files": {}, "userId": "123456" }, + [ + { + "version": "1", + "type": "REST", + "method": "POST", + "endpoint": "https://api2.amplitude.com/2/httpapi", + "headers": { + "Content-Type": "application/json" + }, + "params": {}, + "body": { + "JSON": { + "api_key": "abcde", + "events": [ + { + "os_name": "Chrome", + "os_version": "85.0.4183.121", + "platform": "Web", + "library": "rudderstack", + "device_model": "Mac", + "device_id": "my-anonymous-id-new", + "app_name": "RudderLabs JavaScript SDK", + "app_version": "1.1.5", + "language": "en-US", + "event_type": "$identify", + "groups": { + "Company": "ABC" + }, + "user_properties": { + "Company": "ABC", + "utm_content": "some content", + "utm_medium": "medium", + "utm_name": "some campaign", + "utm_source": "google", + "utm_term": "keyword", + "utm_test": "other value", + "initial_referring_domain": "docs.rudderstack.com", + "initial_referrer": "https://docs.rudderstack.com" + }, + "time": 1603180498983, + "user_id": "sampleusrRudder3", + "session_id": -1 + } + ], + "options": { + "min_id_length": 1 + } + }, + "XML": {}, + "JSON_ARRAY": {}, + "FORM": {} + }, + "files": {}, + "userId": "my-anonymous-id-new" + }, + { + "version": "1", + "type": "REST", + "method": "POST", + "endpoint": "https://api2.amplitude.com/groupidentify", + "headers": {}, + "params": {}, + "body": { + "JSON": {}, + "XML": {}, + "JSON_ARRAY": {}, + "FORM": { + "api_key": "abcde", + "identification": [ + "{\"group_type\":\"Company\",\"group_value\":\"ABC\"}" + ] + } + }, + "files": {}, + "userId": "my-anonymous-id-new" + } + ], [ { "version": "1", @@ -581,7 +792,7 @@ "language": "en-US", "event_type": "$identify", "user_properties": { - "Company": "Comapny-ABC", + "Company": "ABC", "utm_content": "some content", "utm_medium": "medium", "utm_name": "some campaign", @@ -592,7 +803,7 @@ "initial_referrer": "https://docs.rudderstack.com" }, "groups": { - "Company": "Comapny-ABC" + "Company": "ABC" }, "time": 1603180498983, "user_id": "sampleusrRudder3", @@ -624,7 +835,7 @@ "FORM": { "api_key": "abcde", "identification": [ - "{\"group_type\":\"Company\",\"group_value\":\"Comapny-ABC\",\"group_properties\":{\"KEY_3\":{\"CHILD_KEY_92\":\"value_95\",\"CHILD_KEY_102\":\"value_103\"},\"KEY_2\":{\"CHILD_KEY_92\":\"value_95\",\"CHILD_KEY_102\":\"value_103\"},\"name_trait\":\"Company\",\"value_trait\":\"Comapny-ABC\"}}" + "{\"group_type\":\"Company\",\"group_value\":\"ABC\",\"group_properties\":{\"KEY_3\":{\"CHILD_KEY_92\":\"value_95\",\"CHILD_KEY_102\":\"value_103\"},\"KEY_2\":{\"CHILD_KEY_92\":\"value_95\",\"CHILD_KEY_102\":\"value_103\"},\"name_trait\":\"Company\",\"value_trait\":\"ABC\"}}" ] } }, @@ -2244,7 +2455,7 @@ "event_type": "Order Completed", "user_id": "userID123", "revenueType": "Purchased", - "price": 25, + "price": 25.0, "quantity": 2, "revenue": 48, "time": 1597383030118, @@ -2886,7 +3097,7 @@ "app_name": "RudderLabs JavaScript SDK", "app_version": "1.0.0", "language": "en-US", - "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22", + "session_id": -1, "event_type": "$identify", "device_brand": "testBrand", "device_manufacturer": "testManufacturer", @@ -3031,7 +3242,7 @@ "app_name": "RudderLabs JavaScript SDK", "app_version": "1.0.0", "language": "en-US", - "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22", + "session_id": -1, "insert_id": "84e26acc-56a5-4835-8233-591137fca468", "ip": "0.0.0.0", "user_properties": { @@ -3091,7 +3302,7 @@ "language": "en-US", "event_type": "$identify", "user_properties": { - "Company": "Comapny-ABC", + "Company": "ABC", "utm_content": "some content", "utm_medium": "medium", "utm_name": "some campaign", @@ -3102,7 +3313,7 @@ "initial_referrer": "https://docs.rudderstack.com" }, "groups": { - "Company": "Comapny-ABC" + "Company": "ABC" }, "time": 1603180498983, "user_id": "sampleusrRudder3", @@ -3134,7 +3345,7 @@ "FORM": { "api_key": "abcde", "identification": [ - "{\"group_type\":\"Company\",\"group_value\":\"Comapny-ABC\",\"group_properties\":{\"KEY_3\":{\"CHILD_KEY_92\":\"value_95\",\"CHILD_KEY_102\":\"value_103\"},\"KEY_2\":{\"CHILD_KEY_92\":\"value_95\",\"CHILD_KEY_102\":\"value_103\"},\"name_trait\":\"Company\",\"value_trait\":\"Comapny-ABC\"}}" + "{\"group_type\":\"Company\",\"group_value\":\"ABC\",\"group_properties\":{\"KEY_3\":{\"CHILD_KEY_92\":\"value_95\",\"CHILD_KEY_102\":\"value_103\"},\"KEY_2\":{\"CHILD_KEY_92\":\"value_95\",\"CHILD_KEY_102\":\"value_103\"},\"name_trait\":\"Company\",\"value_trait\":\"ABC\"}}" ] } }, @@ -3483,11 +3694,11 @@ "utm_content": "some content", "utm_name": "some campaign", "utm_test": "other value", - "Company": "Comapny-ABC" + "Company": "ABC" }, "event_type": "$identify", "groups": { - "Company": "Comapny-ABC" + "Company": "ABC" }, "time": 1603180498983, "user_id": "sampleusrRudder3", @@ -3520,7 +3731,7 @@ "FORM": { "api_key": "abcde", "identification": [ - "{\"group_type\":\"Company\",\"group_value\":\"Comapny-ABC\",\"group_properties\":{\"KEY_3\":{\"CHILD_KEY_92\":\"value_95\",\"CHILD_KEY_102\":\"value_103\"},\"KEY_2\":{\"CHILD_KEY_92\":\"value_95\",\"CHILD_KEY_102\":\"value_103\"},\"name_trait\":\"Company\",\"value_trait\":\"Comapny-ABC\"}}" + "{\"group_type\":\"Company\",\"group_value\":\"ABC\",\"group_properties\":{\"KEY_3\":{\"CHILD_KEY_92\":\"value_95\",\"CHILD_KEY_102\":\"value_103\"},\"KEY_2\":{\"CHILD_KEY_92\":\"value_95\",\"CHILD_KEY_102\":\"value_103\"},\"name_trait\":\"Company\",\"value_trait\":\"ABC\"}}" ] } }, @@ -3603,7 +3814,7 @@ "initial_referring_domain": "docs.rudderstack.com", "name": "ApplicationLoaded" }, - "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22", + "session_id": -1, "insert_id": "5e10d13a-bf9a-44bf-b884-43a9e591ea71", "ip": "1.1.1.1", "event_id": 2, @@ -3702,6 +3913,56 @@ "files": {}, "userId": "5d205961641ee6c5" }, + { + "version": "1", + "type": "REST", + "method": "POST", + "endpoint": "https://api2.amplitude.com/2/httpapi", + "headers": { + "Content-Type": "application/json" + }, + "params": {}, + "body": { + "JSON": { + "api_key": "abcde", + "events": [ + { + "os_name": "Android", + "os_version": "7.1.1", + "device_model": "Android SDK built for x86", + "device_manufacturer": "Google", + "device_id": "5d205961641ee6c5", + "carrier": "Android", + "app_name": "Sample Kotlin", + "app_version": "1.2", + "platform": "Android", + "language": "en-US", + "event_properties": {}, + "insert_id": "1662393883248-509420bf-b812-4f8d-bdb2-8c811bfde87f", + "user_properties": { + "anonymousId": "5d205961641ee6c5", + "id": "User Android", + "userId": "User Android" + }, + "event_type": "Viewed Screen", + "user_id": "User Android", + "device_brand": "Google", + "time": 1662393883250, + "session_id": 1662393792, + "library": "rudderstack" + } + ], + "options": { + "min_id_length": 1 + } + }, + "JSON_ARRAY": {}, + "XML": {}, + "FORM": {} + }, + "files": {}, + "userId": "5d205961641ee6c5" + }, { "version": "1", "type": "REST", @@ -3741,7 +4002,7 @@ "event_type": "$identify", "time": 1571043797562, "user_id": "123456", - "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22", + "session_id": 1662393792, "country": "India", "city": "kolkata", "library": "rudderstack" @@ -3847,7 +4108,7 @@ "initial_referring_domain": "docs.rudderstack.com", "name": "ApplicationLoaded" }, - "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22", + "session_id": -1, "insert_id": "5e10d13a-bf9a-44bf-b884-43a9e591ea71", "ip": "1.1.1.1", "event_id": 2, @@ -3907,7 +4168,7 @@ "initial_referring_domain": "docs.rudderstack.com", "name": "ApplicationLoaded" }, - "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22", + "session_id": -1, "insert_id": "5e10d13a-bf9a-44bf-b884-43a9e591ea71", "ip": "1.1.1.1", "event_id": 2, @@ -3967,7 +4228,7 @@ "initial_referring_domain": "docs.rudderstack.com", "name": "ApplicationLoaded" }, - "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22", + "session_id": -1, "insert_id": "5e10d13a-bf9a-44bf-b884-43a9e591ea71", "ip": "1.1.1.1", "event_id": 2, @@ -4015,19 +4276,17 @@ "app_name": "RudderLabs JavaScript SDK", "app_version": "1.0.0", "language": "en-US", - "event_type": "Viewed Home Page event.", + "event_type": "Viewed Page", "event_properties": { "path": "/destinations/amplitude", "referrer": "", "search": "", "title": "", "url": "https://docs.rudderstack.com/destinations/amplitude", - "category": "destination", "initial_referrer": "https://docs.rudderstack.com", - "initial_referring_domain": "docs.rudderstack.com", - "name": "ApplicationLoaded" + "initial_referring_domain": "docs.rudderstack.com" }, - "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22", + "session_id": -1, "insert_id": "5e10d13a-bf9a-44bf-b884-43a9e591ea71", "ip": "1.1.1.1", "event_id": 2, @@ -4053,4 +4312,4 @@ "files": {}, "userId": "00000000000000000000000000" } -] +] \ No newline at end of file diff --git a/test/__tests__/data/am_router_output.json b/test/__tests__/data/am_router_output.json index 5c9e23840a..bfccb478b3 100644 --- a/test/__tests__/data/am_router_output.json +++ b/test/__tests__/data/am_router_output.json @@ -24,7 +24,7 @@ "app_name": "RudderLabs JavaScript SDK", "app_version": "1.0.0", "language": "en-US", - "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22", + "session_id": -1, "insert_id": "84e26acc-56a5-4835-8233-591137fca468", "city": "kolkata", "country": "India", @@ -109,7 +109,7 @@ "initial_referring_domain": "docs.rudderstack.com", "name": "ApplicationLoaded" }, - "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22", + "session_id": -1, "insert_id": "5e10d13a-bf9a-44bf-b884-43a9e591ea71", "ip": "1.1.1.1", "user_properties": { From 9bc0fd8efcee44871a190bd6cb9e89c5cf035ff8 Mon Sep 17 00:00:00 2001 From: Sudip Paul <67197965+ItsSudip@users.noreply.github.com> Date: Thu, 2 Nov 2023 19:31:33 +0530 Subject: [PATCH 02/11] feat: add new destination tiktok_audience (#2710) * feat: add new destination tiktok_audience * remove log * refactor code * chore: refactor workflows * add test cases * chore: refactore code * refactor code and add negative test case --------- Co-authored-by: Dilip Kola Co-authored-by: mihir-4116 Co-authored-by: Mihir Bhalala <77438541+mihir-4116@users.noreply.github.com> --- src/cdk/v2/bindings/default.js | 6 + .../v2/destinations/tiktok_audience/config.js | 9 + .../tiktok_audience/procWorkflow.yaml | 67 ++ .../tiktok_audience/rtWorkflow.yaml | 32 + src/features.json | 1 + .../tiktok_audience/processor/data.ts | 854 ++++++++++++++++++ .../tiktok_audience/router/data.ts | 833 +++++++++++++++++ 7 files changed, 1802 insertions(+) create mode 100644 src/cdk/v2/destinations/tiktok_audience/config.js create mode 100644 src/cdk/v2/destinations/tiktok_audience/procWorkflow.yaml create mode 100644 src/cdk/v2/destinations/tiktok_audience/rtWorkflow.yaml create mode 100644 test/integrations/destinations/tiktok_audience/processor/data.ts create mode 100644 test/integrations/destinations/tiktok_audience/router/data.ts diff --git a/src/cdk/v2/bindings/default.js b/src/cdk/v2/bindings/default.js index 0bba7210f0..b86b6d2b63 100644 --- a/src/cdk/v2/bindings/default.js +++ b/src/cdk/v2/bindings/default.js @@ -1,3 +1,4 @@ +const crypto = require('crypto'); const { InstrumentationError, ConfigurationError, @@ -47,7 +48,12 @@ function assertHttpResp(processedResponse, message) { } } +function MD5(data) { + return crypto.createHash('md5').update(data).digest('hex'); +} + module.exports = { + MD5, isValidEventType, assert, assertConfig, diff --git a/src/cdk/v2/destinations/tiktok_audience/config.js b/src/cdk/v2/destinations/tiktok_audience/config.js new file mode 100644 index 0000000000..853f372505 --- /dev/null +++ b/src/cdk/v2/destinations/tiktok_audience/config.js @@ -0,0 +1,9 @@ +const ACTION_MAP = { + add: 'add', + remove: 'delete', +}; +const SHA256_TRAITS = ['IDFA_SHA256', 'AAID_SHA256', 'EMAIL_SHA256', 'PHONE_SHA256']; +module.exports = { + ACTION_MAP, + SHA256_TRAITS, +}; diff --git a/src/cdk/v2/destinations/tiktok_audience/procWorkflow.yaml b/src/cdk/v2/destinations/tiktok_audience/procWorkflow.yaml new file mode 100644 index 0000000000..cd84ecbc87 --- /dev/null +++ b/src/cdk/v2/destinations/tiktok_audience/procWorkflow.yaml @@ -0,0 +1,67 @@ + +bindings: + - name: EventType + path: ../../../../constants + - path: ../../bindings/jsontemplate + exportAll: true + - path: ./config + - name: removeUndefinedAndNullValues + path: ../../../../v0/util + - name: defaultRequestConfig + path: ../../../../v0/util + +steps: + - name: validateInput + template: | + let messageType = .message.type; + $.assert(.message.type, "message Type is not present. Aborting message."); + $.assert(.message.type.toLowerCase() ==='audiencelist', "Event type " + .message.type.toLowerCase() + " is not supported. Aborting message."); + $.assert(.message.properties, "Message properties is not present. Aborting message."); + $.assert(.message.properties.listData, "listData is not present inside properties. Aborting message."); + $.assert($.containsAll(Object.keys(.message.properties.listData), ["add", "remove"]), "unsupported action type. Aborting message.") + + - name: prepareIdentifiersList + description: | + Populate list of identifiers to be updated + template: | + const destinationFields = .message.context.destinationFields.split(", ") + const audienceId = .message.context.externalId[0].type.split("-")[1]; + const isHashRequired = .destination.Config.isHashRequired; + const advertiserIds = .metadata.secret.advertiserIds; + const hashTraits = function(traits) { + traits@trait.(destinationFields@destinationField.( + trait[destinationField] ? { + id: isHashRequired ? + destinationField in $.SHA256_TRAITS ? + $.SHA256(trait[destinationField]) : $.MD5(trait[destinationField]) + : trait[destinationField], + audience_ids:[audienceId] + } : {} + )[]) + }; + const listData = .message.properties.listData; + const actions = Object.keys(listData) + actions@action.({ + "batch_data": hashTraits(listData[action]), + "id_schema": destinationFields, + "advertiser_ids": advertiserIds, + "action": $.ACTION_MAP[action], + })[] + + + - name: buildResponseForProcessTransformation + description: build response + template: | + const accessToken = .metadata.secret.accessToken + const anonymousId = .message.anonymousId; + $.outputs.prepareIdentifiersList@body.( + let response = $.defaultRequestConfig(); + response.body.JSON = body; + response.userId = anonymousId; + response.endpoint = "https://business-api.tiktok.com/open_api/v1.3/segment/mapping/"; + response.headers = { + "Access-Token": accessToken, + "Content-Type": "application/json" + }; + response + ) diff --git a/src/cdk/v2/destinations/tiktok_audience/rtWorkflow.yaml b/src/cdk/v2/destinations/tiktok_audience/rtWorkflow.yaml new file mode 100644 index 0000000000..3db4c405ad --- /dev/null +++ b/src/cdk/v2/destinations/tiktok_audience/rtWorkflow.yaml @@ -0,0 +1,32 @@ +bindings: + - name: handleRtTfSingleEventError + path: ../../../../v0/util/index + +steps: + - name: validateInput + template: | + $.assert(Array.isArray(^) && ^.length > 0, "Invalid event array") + + - name: transform + externalWorkflow: + path: ./procWorkflow.yaml + loopOverInput: true + + - name: successfulEvents + debug: true + template: | + $.outputs.transform#idx{"output" in .}.({ + "batchedRequest": .output, + "batched": true, + "destination": ^[idx].destination, + "metadata": ^[idx].metadata[], + "statusCode": 200 + })[] + - name: failedEvents + template: | + $.outputs.transform#idx.error.( + $.handleRtTfSingleEventError(^[idx], .originalError ?? ., {}) + )[] + - name: finalPayload + template: | + [...$.outputs.failedEvents, ...$.outputs.successfulEvents] diff --git a/src/features.json b/src/features.json index 7de214ab39..fb5b0735f0 100644 --- a/src/features.json +++ b/src/features.json @@ -58,6 +58,7 @@ "OPTIMIZELY_FULLSTACK": true, "TWITTER_ADS": true, "CLEVERTAP": true, + "TIKTOK_AUDIENCE": true, "ORTTO": true } } diff --git a/test/integrations/destinations/tiktok_audience/processor/data.ts b/test/integrations/destinations/tiktok_audience/processor/data.ts new file mode 100644 index 0000000000..a715aa2f72 --- /dev/null +++ b/test/integrations/destinations/tiktok_audience/processor/data.ts @@ -0,0 +1,854 @@ +export const data = [ + { + "name": "tiktok_audience", + "description": "Test 1: Containing SHA256 traits only", + "feature": "processor", + "module": "destination", + "version": "v0", + "input": { + "request": { + "body": [ + { + "message": { + "userId": "user 1", + "type": "audiencelist", + "properties": { + "listData": { + "add": [ + { + "EMAIL_SHA256": "alex@email.com" + }, + { + "EMAIL_SHA256": "amy@abc.com" + }, + { + "EMAIL_SHA256": "van@abc.com" + } + ], + "remove": [ + { + "EMAIL_SHA256": "alex@email.com" + }, + { + "EMAIL_SHA256": "amy@abc.com" + }, + { + "EMAIL_SHA256": "van@abc.com" + } + ] + } + }, + "context": { + "ip": "14.5.67.21", + "library": { + "name": "http" + }, + "externalId": [ + { + "type": "TIKTOK_AUDIENCE-23856594064540489", + "identifierType": "EMAIL_SHA256" + } + ], + "destinationFields": "EMAIL_SHA256" + }, + "timestamp": "2020-02-02T00:23:09.544Z" + }, + "metadata": { + "jobId": 1, + "secret": { + "accessToken": "dummyAccessToken", + "advertiserIds": [ + "dummyAdverTiserID" + ] + } + }, + "destination": { + "DestinationDefinition": { + "Config": { + "cdkV2Enabled": true + } + }, + "Config": { + "isHashRequired": true, + "registerDeviceOrBrowserApiKey": true, + "apiKey": "intercomApiKey", + "appId": "9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0", + "collectContext": false + } + } + } + ] + } + }, + "output": { + "response": { + "status": 200, + "body": [ + { + "output": { + "version": "1", + "type": "REST", + "method": "POST", + "endpoint": "https://business-api.tiktok.com/open_api/v1.3/segment/mapping/", + "headers": { + "Access-Token": "dummyAccessToken", + "Content-Type": "application/json" + }, + "params": {}, + "body": { + "JSON": { + "batch_data": [ + [ + { + "id": "ac0f1baec38a9ef3cfcb56db981df7d9bab2568c7f53ef3776d1c059ec58e72b", + "audience_ids": [ + "23856594064540489" + ] + } + ], + [ + { + "id": "49eaeca26c878f268ad33af8cfa8194ca5b8b8e448b1c775bf9153a2de734579", + "audience_ids": [ + "23856594064540489" + ] + } + ], + [ + { + "id": "2048acfa84a01121060ca2fc8a673a76d427176dc37224d4408c21973bd90e5c", + "audience_ids": [ + "23856594064540489" + ] + } + ] + ], + "id_schema": [ + "EMAIL_SHA256" + ], + "advertiser_ids": [ + "dummyAdverTiserID" + ], + "action": "add" + }, + "JSON_ARRAY": {}, + "XML": {}, + "FORM": {} + }, + "files": {}, + "userId": "" + }, + "metadata": { + "jobId": 1, + "secret": { + "accessToken": "dummyAccessToken", + "advertiserIds": [ + "dummyAdverTiserID" + ] + } + }, + "statusCode": 200 + }, + { + "output": { + "version": "1", + "type": "REST", + "method": "POST", + "endpoint": "https://business-api.tiktok.com/open_api/v1.3/segment/mapping/", + "headers": { + "Access-Token": "dummyAccessToken", + "Content-Type": "application/json" + }, + "params": {}, + "body": { + "JSON": { + "batch_data": [ + [ + { + "id": "ac0f1baec38a9ef3cfcb56db981df7d9bab2568c7f53ef3776d1c059ec58e72b", + "audience_ids": [ + "23856594064540489" + ] + } + ], + [ + { + "id": "49eaeca26c878f268ad33af8cfa8194ca5b8b8e448b1c775bf9153a2de734579", + "audience_ids": [ + "23856594064540489" + ] + } + ], + [ + { + "id": "2048acfa84a01121060ca2fc8a673a76d427176dc37224d4408c21973bd90e5c", + "audience_ids": [ + "23856594064540489" + ] + } + ] + ], + "id_schema": [ + "EMAIL_SHA256" + ], + "advertiser_ids": [ + "dummyAdverTiserID" + ], + "action": "delete" + }, + "JSON_ARRAY": {}, + "XML": {}, + "FORM": {} + }, + "files": {}, + "userId": "" + }, + "metadata": { + "jobId": 1, + "secret": { + "accessToken": "dummyAccessToken", + "advertiserIds": [ + "dummyAdverTiserID" + ] + } + }, + "statusCode": 200 + } + ] + } + } + }, + { + "name": "tiktok_audience", + "description": "Test 2: Containing SHA256 and MD5 traits", + "feature": "processor", + "module": "destination", + "version": "v0", + "input": { + "request": { + "body": [ + { + "message": { + "userId": "user 1", + "type": "audiencelist", + "properties": { + "listData": { + "add": [ + { + "EMAIL_SHA256": "alex@email.com", + "AAID_MD5": "1234567" + }, + { + "EMAIL_SHA256": "amy@abc.com", + "AAID_MD5": "1234568" + }, + { + "EMAIL_SHA256": "van@abc.com", + "AAID_MD5": "1234569" + } + ], + "remove": [ + { + "EMAIL_SHA256": "alex@email.com", + "AAID_MD5": "1234570" + }, + { + "EMAIL_SHA256": "amy@abc.com", + "AAID_MD5": "1234571" + }, + { + "EMAIL_SHA256": "van@abc.com", + "AAID_MD5": "1234572" + } + ] + } + }, + "context": { + "ip": "14.5.67.21", + "library": { + "name": "http" + }, + "externalId": [ + { + "type": "TIKTOK_AUDIENCE-23856594064540489", + "identifierType": "EMAIL_SHA256" + } + ], + "destinationFields": "EMAIL_SHA256, AAID_MD5" + }, + "timestamp": "2020-02-02T00:23:09.544Z" + }, + "metadata": { + "jobId": 1, + "secret": { + "accessToken": "dummyAccessToken", + "advertiserIds": [ + "dummyAdverTiserID" + ] + } + }, + "destination": { + "DestinationDefinition": { + "Config": { + "cdkV2Enabled": true + } + }, + "Config": { + "isHashRequired": true, + "registerDeviceOrBrowserApiKey": true, + "apiKey": "intercomApiKey", + "appId": "9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0", + "collectContext": false + } + } + } + ] + } + }, + "output": { + "response": { + "status": 200, + "body": [ + { + "output": { + "version": "1", + "type": "REST", + "method": "POST", + "endpoint": "https://business-api.tiktok.com/open_api/v1.3/segment/mapping/", + "headers": { + "Access-Token": "dummyAccessToken", + "Content-Type": "application/json" + }, + "params": {}, + "body": { + "JSON": { + "batch_data": [ + [ + { + "id": "ac0f1baec38a9ef3cfcb56db981df7d9bab2568c7f53ef3776d1c059ec58e72b", + "audience_ids": [ + "23856594064540489" + ] + }, + { + "id": "fcea920f7412b5da7be0cf42b8c93759", + "audience_ids": [ + "23856594064540489" + ] + } + ], + [ + { + "id": "49eaeca26c878f268ad33af8cfa8194ca5b8b8e448b1c775bf9153a2de734579", + "audience_ids": [ + "23856594064540489" + ] + }, + { + "id": "fe743d8d97aa7dfc6c93ccdc2e749513", + "audience_ids": [ + "23856594064540489" + ] + } + ], + [ + { + "id": "2048acfa84a01121060ca2fc8a673a76d427176dc37224d4408c21973bd90e5c", + "audience_ids": [ + "23856594064540489" + ] + }, + { + "id": "e36a2f90240e9e84483504fd4a704452", + "audience_ids": [ + "23856594064540489" + ] + } + ] + ], + "id_schema": [ + "EMAIL_SHA256", + "AAID_MD5" + ], + "advertiser_ids": [ + "dummyAdverTiserID" + ], + "action": "add" + }, + "JSON_ARRAY": {}, + "XML": {}, + "FORM": {} + }, + "files": {}, + "userId": "" + }, + "metadata": { + "jobId": 1, + "secret": { + "accessToken": "dummyAccessToken", + "advertiserIds": [ + "dummyAdverTiserID" + ] + } + }, + "statusCode": 200 + }, + { + "output": { + "version": "1", + "type": "REST", + "method": "POST", + "endpoint": "https://business-api.tiktok.com/open_api/v1.3/segment/mapping/", + "headers": { + "Access-Token": "dummyAccessToken", + "Content-Type": "application/json" + }, + "params": {}, + "body": { + "JSON": { + "batch_data": [ + [ + { + "id": "ac0f1baec38a9ef3cfcb56db981df7d9bab2568c7f53ef3776d1c059ec58e72b", + "audience_ids": [ + "23856594064540489" + ] + }, + { + "id": "c1abd65fea29d573ddef1bce925e3276", + "audience_ids": [ + "23856594064540489" + ] + } + ], + [ + { + "id": "49eaeca26c878f268ad33af8cfa8194ca5b8b8e448b1c775bf9153a2de734579", + "audience_ids": [ + "23856594064540489" + ] + }, + { + "id": "7298110702a080dfc6903f13333eb04a", + "audience_ids": [ + "23856594064540489" + ] + } + ], + [ + { + "id": "2048acfa84a01121060ca2fc8a673a76d427176dc37224d4408c21973bd90e5c", + "audience_ids": [ + "23856594064540489" + ] + }, + { + "id": "d9cb68b1fd3b9d32abc5f4cab8b42b68", + "audience_ids": [ + "23856594064540489" + ] + } + ] + ], + "id_schema": [ + "EMAIL_SHA256", + "AAID_MD5" + ], + "advertiser_ids": [ + "dummyAdverTiserID" + ], + "action": "delete" + }, + "JSON_ARRAY": {}, + "XML": {}, + "FORM": {} + }, + "files": {}, + "userId": "" + }, + "metadata": { + "jobId": 1, + "secret": { + "accessToken": "dummyAccessToken", + "advertiserIds": [ + "dummyAdverTiserID" + ] + } + }, + "statusCode": 200 + } + ] + } + } + }, + { + "name": "tiktok_audience", + "description": "Test 3: Containing all possible traits", + "feature": "processor", + "module": "destination", + "version": "v0", + "input": { + "request": { + "body": [ + { + "message": { + "userId": "user 1", + "type": "audiencelist", + "properties": { + "listData": { + "add": [ + { + "EMAIL_SHA256": "alex@email.com", + "PHONE_SHA256": "+129988776655", + "IDFA_SHA256": "1234lkasfjdalj12321", + "AAID_SHA256": "000999OOOQQQQ", + "AAID_MD5": "000999OOOQQQQ", + "IDFA_MD5": "1234lkasfjdalj12321" + }, + { + "EMAIL_SHA256": "amy@abc.com", + "PHONE_SHA256": "+129988776677", + "IDFA_SHA256": "1234lkasfjdalj114455", + "AAID_SHA256": "000999OOOPPPP", + "AAID_MD5": "000999OOOPPPP", + "IDFA_MD5": "1234lkasfjdalj114455" + } + ] + } + }, + "context": { + "ip": "14.5.67.21", + "library": { + "name": "http" + }, + "externalId": [ + { + "type": "TIKTOK_AUDIENCE-23856594064540489", + "identifierType": "EMAIL_SHA256" + } + ], + "destinationFields": "EMAIL_SHA256, PHONE_SHA256, IDFA_SHA256, AAID_SHA256, AAID_MD, IDFA_MD5" + }, + "timestamp": "2020-02-02T00:23:09.544Z" + }, + "metadata": { + "jobId": 1, + "secret": { + "accessToken": "dummyAccessToken", + "advertiserIds": [ + "dummyAdverTiserID" + ] + } + }, + "destination": { + "DestinationDefinition": { + "Config": { + "cdkV2Enabled": true + } + }, + "Config": { + "isHashRequired": true, + "registerDeviceOrBrowserApiKey": true, + "apiKey": "intercomApiKey", + "appId": "9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0", + "collectContext": false + } + } + } + ] + } + }, + "output": { + "response": { + "status": 200, + "body": [ + { + "output": { + "version": "1", + "type": "REST", + "method": "POST", + "endpoint": "https://business-api.tiktok.com/open_api/v1.3/segment/mapping/", + "headers": { + "Access-Token": "dummyAccessToken", + "Content-Type": "application/json" + }, + "params": {}, + "body": { + "JSON": { + "batch_data": [ + [ + { + "id": "ac0f1baec38a9ef3cfcb56db981df7d9bab2568c7f53ef3776d1c059ec58e72b", + "audience_ids": [ + "23856594064540489" + ] + }, + { + "id": "31e78a3bf9ce2b43316f64fe883a531d6266938091e94e2f2480272481163dee", + "audience_ids": [ + "23856594064540489" + ] + }, + { + "id": "0259f595f7172c8dd692a5c37b4d296939555f862aae8adb964391bdb65006ab", + "audience_ids": [ + "23856594064540489" + ] + }, + { + "id": "b06fbe7a29f33576a792ba3df3c9bf838cd26ea88cf574285fa60dc0234a8485", + "audience_ids": [ + "23856594064540489" + ] + }, + {}, + { + "id": "32ee3d063320815a13e0058c2498ff76", + "audience_ids": [ + "23856594064540489" + ] + } + ], + [ + { + "id": "49eaeca26c878f268ad33af8cfa8194ca5b8b8e448b1c775bf9153a2de734579", + "audience_ids": [ + "23856594064540489" + ] + }, + { + "id": "fb40adc7debbf40e7b45b0a4a91886785dff1a28809276f95f1c44f7045f9b4d", + "audience_ids": [ + "23856594064540489" + ] + }, + { + "id": "e6bbdf34c5f3472f31b2923a26811560a599233f3dea4c9971595c3bb7b1e8dc", + "audience_ids": [ + "23856594064540489" + ] + }, + { + "id": "661125f7d337811256c5b55996b22c89047804dcec494db72659e4be71e03091", + "audience_ids": [ + "23856594064540489" + ] + }, + {}, + { + "id": "94162773066d6ae88b2658dc58ca2317", + "audience_ids": [ + "23856594064540489" + ] + } + ] + ], + "id_schema": [ + "EMAIL_SHA256", + "PHONE_SHA256", + "IDFA_SHA256", + "AAID_SHA256", + "AAID_MD", + "IDFA_MD5" + ], + "advertiser_ids": [ + "dummyAdverTiserID" + ], + "action": "add" + }, + "JSON_ARRAY": {}, + "XML": {}, + "FORM": {} + }, + "files": {}, + "userId": "" + }, + "metadata": { + "jobId": 1, + "secret": { + "accessToken": "dummyAccessToken", + "advertiserIds": [ + "dummyAdverTiserID" + ] + } + }, + "statusCode": 200 + } + ] + } + } + }, + { + "name": "tiktok_audience", + "description": "Test 4: Considering some null values", + "feature": "processor", + "module": "destination", + "version": "v0", + "input": { + "request": { + "body": [ + { + "message": { + "userId": "user 1", + "type": "audiencelist", + "properties": { + "listData": { + "add": [ + { + "EMAIL_SHA256": "alex@email.com", + "PHONE_SHA256": "+129988776655", + "AAID_MD5": "000999OOOQQQQ", + "IDFA_MD5": "1234lkasfjdalj12321" + }, + { + "EMAIL_SHA256": "amy@abc.com", + "AAID_SHA256": "000999OOOPPPP", + "AAID_MD5": "000999OOOPPPP", + "IDFA_MD5": "1234lkasfjdalj114455" + } + ] + } + }, + "context": { + "ip": "14.5.67.21", + "library": { + "name": "http" + }, + "externalId": [ + { + "type": "TIKTOK_AUDIENCE-23856594064540489", + "identifierType": "EMAIL_SHA256" + } + ], + "destinationFields": "EMAIL_SHA256, PHONE_SHA256, IDFA_SHA256, AAID_SHA256, AAID_MD, IDFA_MD5" + }, + "timestamp": "2020-02-02T00:23:09.544Z" + }, + "metadata": { + "jobId": 1, + "secret": { + "accessToken": "dummyAccessToken", + "advertiserIds": [ + "dummyAdverTiserID" + ] + } + }, + "destination": { + "DestinationDefinition": { + "Config": { + "cdkV2Enabled": true + } + }, + "Config": { + "isHashRequired": true, + "registerDeviceOrBrowserApiKey": true, + "apiKey": "intercomApiKey", + "appId": "9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0", + "collectContext": false + } + } + } + ] + } + }, + "output": { + "response": { + "status": 200, + "body": [ + { + "output": { + "version": "1", + "type": "REST", + "method": "POST", + "endpoint": "https://business-api.tiktok.com/open_api/v1.3/segment/mapping/", + "headers": { + "Access-Token": "dummyAccessToken", + "Content-Type": "application/json" + }, + "params": {}, + "body": { + "JSON": { + "batch_data": [ + [ + { + "id": "ac0f1baec38a9ef3cfcb56db981df7d9bab2568c7f53ef3776d1c059ec58e72b", + "audience_ids": [ + "23856594064540489" + ] + }, + { + "id": "31e78a3bf9ce2b43316f64fe883a531d6266938091e94e2f2480272481163dee", + "audience_ids": [ + "23856594064540489" + ] + }, + {}, + {}, + {}, + { + "id": "32ee3d063320815a13e0058c2498ff76", + "audience_ids": [ + "23856594064540489" + ] + } + ], + [ + { + "id": "49eaeca26c878f268ad33af8cfa8194ca5b8b8e448b1c775bf9153a2de734579", + "audience_ids": [ + "23856594064540489" + ] + }, + {}, + {}, + { + "id": "661125f7d337811256c5b55996b22c89047804dcec494db72659e4be71e03091", + "audience_ids": [ + "23856594064540489" + ] + }, + {}, + { + "id": "94162773066d6ae88b2658dc58ca2317", + "audience_ids": [ + "23856594064540489" + ] + } + ] + ], + "id_schema": [ + "EMAIL_SHA256", + "PHONE_SHA256", + "IDFA_SHA256", + "AAID_SHA256", + "AAID_MD", + "IDFA_MD5" + ], + "advertiser_ids": [ + "dummyAdverTiserID" + ], + "action": "add" + }, + "JSON_ARRAY": {}, + "XML": {}, + "FORM": {} + }, + "files": {}, + "userId": "" + }, + "metadata": { + "jobId": 1, + "secret": { + "accessToken": "dummyAccessToken", + "advertiserIds": [ + "dummyAdverTiserID" + ] + } + }, + "statusCode": 200 + } + ] + } + } + } +] \ No newline at end of file diff --git a/test/integrations/destinations/tiktok_audience/router/data.ts b/test/integrations/destinations/tiktok_audience/router/data.ts new file mode 100644 index 0000000000..c8a8b93d30 --- /dev/null +++ b/test/integrations/destinations/tiktok_audience/router/data.ts @@ -0,0 +1,833 @@ +export const data = [ + { + name: 'tiktok_audience', + description: 'Multiple jobs with different metadata', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + message: { + userId: 'user 1', + type: 'audiencelist', + properties: { + listData: { + add: [ + { + EMAIL_SHA256: 'alex@email.com', + }, + { + EMAIL_SHA256: 'amy@abc.com', + }, + { + EMAIL_SHA256: 'van@abc.com', + }, + ], + remove: [ + { + EMAIL_SHA256: 'alex@email.com', + }, + { + EMAIL_SHA256: 'amy@abc.com', + }, + { + EMAIL_SHA256: 'van@abc.com', + }, + ], + }, + }, + context: { + ip: '14.5.67.21', + library: { + name: 'http', + }, + externalId: [ + { + type: 'TIKTOK_AUDIENCE-23856594064540489', + identifierType: 'EMAIL_SHA256', + }, + ], + destinationFields: 'EMAIL_SHA256', + }, + timestamp: '2020-02-02T00:23:09.544Z', + }, + metadata: { + jobId: 1, + secret: { + accessToken: 'dummyAccessToken', + advertiserIds: ['dummyAdverTiserID'], + }, + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + isHashRequired: true, + registerDeviceOrBrowserApiKey: true, + apiKey: 'intercomApiKey', + appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', + collectContext: false, + }, + }, + }, + { + message: { + userId: 'user 1', + type: 'audiencelist', + properties: { + listData: { + add: [ + { + EMAIL_SHA256: 'alex@email.com', + AAID_MD5: '1234567', + }, + { + EMAIL_SHA256: 'amy@abc.com', + AAID_MD5: '1234568', + }, + { + EMAIL_SHA256: 'van@abc.com', + AAID_MD5: '1234569', + }, + ], + remove: [ + { + EMAIL_SHA256: 'alex@email.com', + AAID_MD5: '1234570', + }, + { + EMAIL_SHA256: 'amy@abc.com', + AAID_MD5: '1234571', + }, + { + EMAIL_SHA256: 'van@abc.com', + AAID_MD5: '1234572', + }, + ], + }, + }, + context: { + ip: '14.5.67.21', + library: { + name: 'http', + }, + externalId: [ + { + type: 'TIKTOK_AUDIENCE-23856594064540489', + identifierType: 'EMAIL_SHA256', + }, + ], + destinationFields: 'EMAIL_SHA256, AAID_MD5', + }, + timestamp: '2020-02-02T00:23:09.544Z', + }, + metadata: { + jobId: 2, + secret: { + accessToken: 'dummyAccessToken', + advertiserIds: ['dummyAdverTiserID'], + }, + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + isHashRequired: true, + registerDeviceOrBrowserApiKey: true, + apiKey: 'intercomApiKey', + appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', + collectContext: false, + }, + }, + }, + { + message: { + userId: 'user 1', + type: 'audiencelist', + properties: { + listData: { + add: [ + { + EMAIL_SHA256: 'alex@email.com', + PHONE_SHA256: '+129988776655', + IDFA_SHA256: '1234lkasfjdalj12321', + AAID_SHA256: '000999OOOQQQQ', + AAID_MD5: '000999OOOQQQQ', + IDFA_MD5: '1234lkasfjdalj12321', + }, + { + EMAIL_SHA256: 'amy@abc.com', + PHONE_SHA256: '+129988776677', + IDFA_SHA256: '1234lkasfjdalj114455', + AAID_SHA256: '000999OOOPPPP', + AAID_MD5: '000999OOOPPPP', + IDFA_MD5: '1234lkasfjdalj114455', + }, + ], + }, + }, + context: { + ip: '14.5.67.21', + library: { + name: 'http', + }, + externalId: [ + { + type: 'TIKTOK_AUDIENCE-23856594064540489', + identifierType: 'EMAIL_SHA256', + }, + ], + destinationFields: + 'EMAIL_SHA256, PHONE_SHA256, IDFA_SHA256, AAID_SHA256, AAID_MD, IDFA_MD5', + }, + timestamp: '2020-02-02T00:23:09.544Z', + }, + metadata: { + jobId: 3, + secret: { + accessToken: 'dummyAccessToken', + advertiserIds: ['dummyAdverTiserID'], + }, + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + isHashRequired: true, + registerDeviceOrBrowserApiKey: true, + apiKey: 'intercomApiKey', + appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', + collectContext: false, + }, + }, + }, + { + message: { + userId: 'user 1', + type: 'audiencelist', + properties: { + listData: { + add: [ + { + EMAIL_SHA256: 'alex@email.com', + PHONE_SHA256: '+129988776655', + AAID_MD5: '000999OOOQQQQ', + IDFA_MD5: '1234lkasfjdalj12321', + }, + { + EMAIL_SHA256: 'amy@abc.com', + AAID_SHA256: '000999OOOPPPP', + AAID_MD5: '000999OOOPPPP', + IDFA_MD5: '1234lkasfjdalj114455', + }, + ], + }, + }, + context: { + ip: '14.5.67.21', + library: { + name: 'http', + }, + externalId: [ + { + type: 'TIKTOK_AUDIENCE-23856594064540489', + identifierType: 'EMAIL_SHA256', + }, + ], + destinationFields: + 'EMAIL_SHA256, PHONE_SHA256, IDFA_SHA256, AAID_SHA256, AAID_MD, IDFA_MD5', + }, + timestamp: '2020-02-02T00:23:09.544Z', + }, + metadata: { + jobId: 4, + secret: { + accessToken: 'dummyAccessToken', + advertiserIds: ['dummyAdverTiserID'], + }, + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + isHashRequired: true, + registerDeviceOrBrowserApiKey: true, + apiKey: 'intercomApiKey', + appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', + collectContext: false, + }, + }, + }, + { + message: { + userId: 'user 1', + properties: { + listData: { + add: [ + { + EMAIL_SHA256: 'alex@email.com', + PHONE_SHA256: '+129988776655', + AAID_MD5: '000999OOOQQQQ', + IDFA_MD5: '1234lkasfjdalj12321', + }, + { + EMAIL_SHA256: 'amy@abc.com', + AAID_SHA256: '000999OOOPPPP', + AAID_MD5: '000999OOOPPPP', + IDFA_MD5: '1234lkasfjdalj114455', + }, + ], + }, + }, + context: { + ip: '14.5.67.21', + library: { + name: 'http', + }, + externalId: [ + { + type: 'TIKTOK_AUDIENCE-23856594064540489', + identifierType: 'EMAIL_SHA256', + }, + ], + destinationFields: + 'EMAIL_SHA256, PHONE_SHA256, IDFA_SHA256, AAID_SHA256, AAID_MD, IDFA_MD5', + }, + timestamp: '2020-02-02T00:23:09.544Z', + }, + metadata: { + jobId: 1524545, + secret: { + accessToken: 'dummyAccessToken', + advertiserIds: ['dummyAdverTiserID'], + }, + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + isHashRequired: true, + registerDeviceOrBrowserApiKey: true, + apiKey: 'intercomApiKey', + appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', + collectContext: false, + }, + }, + }, + ], + destType: 'tiktok_audience', + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + error: 'message Type is not present. Aborting message.', + batched: false, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + isHashRequired: true, + registerDeviceOrBrowserApiKey: true, + apiKey: 'intercomApiKey', + appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', + collectContext: false, + }, + }, + metadata: [ + { + jobId: 1524545, + secret: { + accessToken: 'dummyAccessToken', + advertiserIds: ['dummyAdverTiserID'], + }, + }, + ], + statTags: { + destType: 'TIKTOK_AUDIENCE', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'router', + implementation: 'cdkV2', + module: 'destination', + }, + statusCode: 400, + }, + { + batchedRequest: [ + { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://business-api.tiktok.com/open_api/v1.3/segment/mapping/', + headers: { + 'Access-Token': 'dummyAccessToken', + 'Content-Type': 'application/json', + }, + params: {}, + body: { + JSON: { + batch_data: [ + [ + { + id: 'ac0f1baec38a9ef3cfcb56db981df7d9bab2568c7f53ef3776d1c059ec58e72b', + audience_ids: ['23856594064540489'], + }, + ], + [ + { + id: '49eaeca26c878f268ad33af8cfa8194ca5b8b8e448b1c775bf9153a2de734579', + audience_ids: ['23856594064540489'], + }, + ], + [ + { + id: '2048acfa84a01121060ca2fc8a673a76d427176dc37224d4408c21973bd90e5c', + audience_ids: ['23856594064540489'], + }, + ], + ], + id_schema: ['EMAIL_SHA256'], + advertiser_ids: ['dummyAdverTiserID'], + action: 'add', + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + }, + { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://business-api.tiktok.com/open_api/v1.3/segment/mapping/', + headers: { + 'Access-Token': 'dummyAccessToken', + 'Content-Type': 'application/json', + }, + params: {}, + body: { + JSON: { + batch_data: [ + [ + { + id: 'ac0f1baec38a9ef3cfcb56db981df7d9bab2568c7f53ef3776d1c059ec58e72b', + audience_ids: ['23856594064540489'], + }, + ], + [ + { + id: '49eaeca26c878f268ad33af8cfa8194ca5b8b8e448b1c775bf9153a2de734579', + audience_ids: ['23856594064540489'], + }, + ], + [ + { + id: '2048acfa84a01121060ca2fc8a673a76d427176dc37224d4408c21973bd90e5c', + audience_ids: ['23856594064540489'], + }, + ], + ], + id_schema: ['EMAIL_SHA256'], + advertiser_ids: ['dummyAdverTiserID'], + action: 'delete', + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + }, + ], + batched: true, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + isHashRequired: true, + registerDeviceOrBrowserApiKey: true, + apiKey: 'intercomApiKey', + appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', + collectContext: false, + }, + }, + metadata: [ + { + jobId: 1, + secret: { + accessToken: 'dummyAccessToken', + advertiserIds: ['dummyAdverTiserID'], + }, + }, + ], + statusCode: 200, + }, + { + batchedRequest: [ + { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://business-api.tiktok.com/open_api/v1.3/segment/mapping/', + headers: { + 'Access-Token': 'dummyAccessToken', + 'Content-Type': 'application/json', + }, + params: {}, + body: { + JSON: { + batch_data: [ + [ + { + id: 'ac0f1baec38a9ef3cfcb56db981df7d9bab2568c7f53ef3776d1c059ec58e72b', + audience_ids: ['23856594064540489'], + }, + { + id: 'fcea920f7412b5da7be0cf42b8c93759', + audience_ids: ['23856594064540489'], + }, + ], + [ + { + id: '49eaeca26c878f268ad33af8cfa8194ca5b8b8e448b1c775bf9153a2de734579', + audience_ids: ['23856594064540489'], + }, + { + id: 'fe743d8d97aa7dfc6c93ccdc2e749513', + audience_ids: ['23856594064540489'], + }, + ], + [ + { + id: '2048acfa84a01121060ca2fc8a673a76d427176dc37224d4408c21973bd90e5c', + audience_ids: ['23856594064540489'], + }, + { + id: 'e36a2f90240e9e84483504fd4a704452', + audience_ids: ['23856594064540489'], + }, + ], + ], + id_schema: ['EMAIL_SHA256', 'AAID_MD5'], + advertiser_ids: ['dummyAdverTiserID'], + action: 'add', + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + }, + { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://business-api.tiktok.com/open_api/v1.3/segment/mapping/', + headers: { + 'Access-Token': 'dummyAccessToken', + 'Content-Type': 'application/json', + }, + params: {}, + body: { + JSON: { + batch_data: [ + [ + { + id: 'ac0f1baec38a9ef3cfcb56db981df7d9bab2568c7f53ef3776d1c059ec58e72b', + audience_ids: ['23856594064540489'], + }, + { + id: 'c1abd65fea29d573ddef1bce925e3276', + audience_ids: ['23856594064540489'], + }, + ], + [ + { + id: '49eaeca26c878f268ad33af8cfa8194ca5b8b8e448b1c775bf9153a2de734579', + audience_ids: ['23856594064540489'], + }, + { + id: '7298110702a080dfc6903f13333eb04a', + audience_ids: ['23856594064540489'], + }, + ], + [ + { + id: '2048acfa84a01121060ca2fc8a673a76d427176dc37224d4408c21973bd90e5c', + audience_ids: ['23856594064540489'], + }, + { + id: 'd9cb68b1fd3b9d32abc5f4cab8b42b68', + audience_ids: ['23856594064540489'], + }, + ], + ], + id_schema: ['EMAIL_SHA256', 'AAID_MD5'], + advertiser_ids: ['dummyAdverTiserID'], + action: 'delete', + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + }, + ], + batched: true, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + isHashRequired: true, + registerDeviceOrBrowserApiKey: true, + apiKey: 'intercomApiKey', + appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', + collectContext: false, + }, + }, + metadata: [ + { + jobId: 2, + secret: { + accessToken: 'dummyAccessToken', + advertiserIds: ['dummyAdverTiserID'], + }, + }, + ], + statusCode: 200, + }, + { + batchedRequest: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://business-api.tiktok.com/open_api/v1.3/segment/mapping/', + headers: { + 'Access-Token': 'dummyAccessToken', + 'Content-Type': 'application/json', + }, + params: {}, + body: { + JSON: { + batch_data: [ + [ + { + id: 'ac0f1baec38a9ef3cfcb56db981df7d9bab2568c7f53ef3776d1c059ec58e72b', + audience_ids: ['23856594064540489'], + }, + { + id: '31e78a3bf9ce2b43316f64fe883a531d6266938091e94e2f2480272481163dee', + audience_ids: ['23856594064540489'], + }, + { + id: '0259f595f7172c8dd692a5c37b4d296939555f862aae8adb964391bdb65006ab', + audience_ids: ['23856594064540489'], + }, + { + id: 'b06fbe7a29f33576a792ba3df3c9bf838cd26ea88cf574285fa60dc0234a8485', + audience_ids: ['23856594064540489'], + }, + {}, + { + id: '32ee3d063320815a13e0058c2498ff76', + audience_ids: ['23856594064540489'], + }, + ], + [ + { + id: '49eaeca26c878f268ad33af8cfa8194ca5b8b8e448b1c775bf9153a2de734579', + audience_ids: ['23856594064540489'], + }, + { + id: 'fb40adc7debbf40e7b45b0a4a91886785dff1a28809276f95f1c44f7045f9b4d', + audience_ids: ['23856594064540489'], + }, + { + id: 'e6bbdf34c5f3472f31b2923a26811560a599233f3dea4c9971595c3bb7b1e8dc', + audience_ids: ['23856594064540489'], + }, + { + id: '661125f7d337811256c5b55996b22c89047804dcec494db72659e4be71e03091', + audience_ids: ['23856594064540489'], + }, + {}, + { + id: '94162773066d6ae88b2658dc58ca2317', + audience_ids: ['23856594064540489'], + }, + ], + ], + id_schema: [ + 'EMAIL_SHA256', + 'PHONE_SHA256', + 'IDFA_SHA256', + 'AAID_SHA256', + 'AAID_MD', + 'IDFA_MD5', + ], + advertiser_ids: ['dummyAdverTiserID'], + action: 'add', + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + }, + batched: true, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + isHashRequired: true, + registerDeviceOrBrowserApiKey: true, + apiKey: 'intercomApiKey', + appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', + collectContext: false, + }, + }, + metadata: [ + { + jobId: 3, + secret: { + accessToken: 'dummyAccessToken', + advertiserIds: ['dummyAdverTiserID'], + }, + }, + ], + statusCode: 200, + }, + { + batchedRequest: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://business-api.tiktok.com/open_api/v1.3/segment/mapping/', + headers: { + 'Access-Token': 'dummyAccessToken', + 'Content-Type': 'application/json', + }, + params: {}, + body: { + JSON: { + batch_data: [ + [ + { + id: 'ac0f1baec38a9ef3cfcb56db981df7d9bab2568c7f53ef3776d1c059ec58e72b', + audience_ids: ['23856594064540489'], + }, + { + id: '31e78a3bf9ce2b43316f64fe883a531d6266938091e94e2f2480272481163dee', + audience_ids: ['23856594064540489'], + }, + {}, + {}, + {}, + { + id: '32ee3d063320815a13e0058c2498ff76', + audience_ids: ['23856594064540489'], + }, + ], + [ + { + id: '49eaeca26c878f268ad33af8cfa8194ca5b8b8e448b1c775bf9153a2de734579', + audience_ids: ['23856594064540489'], + }, + {}, + {}, + { + id: '661125f7d337811256c5b55996b22c89047804dcec494db72659e4be71e03091', + audience_ids: ['23856594064540489'], + }, + {}, + { + id: '94162773066d6ae88b2658dc58ca2317', + audience_ids: ['23856594064540489'], + }, + ], + ], + id_schema: [ + 'EMAIL_SHA256', + 'PHONE_SHA256', + 'IDFA_SHA256', + 'AAID_SHA256', + 'AAID_MD', + 'IDFA_MD5', + ], + advertiser_ids: ['dummyAdverTiserID'], + action: 'add', + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + }, + batched: true, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + isHashRequired: true, + registerDeviceOrBrowserApiKey: true, + apiKey: 'intercomApiKey', + appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', + collectContext: false, + }, + }, + metadata: [ + { + jobId: 4, + secret: { + accessToken: 'dummyAccessToken', + advertiserIds: ['dummyAdverTiserID'], + }, + }, + ], + statusCode: 200, + }, + ], + }, + }, + }, + }, +]; From 570532ce790c05a69621d9289758a1b1a7acda8c Mon Sep 17 00:00:00 2001 From: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com> Date: Fri, 3 Nov 2023 17:41:02 +0530 Subject: [PATCH 03/11] fix: allow support for full url from UI in freshsales and freshmarketer (#2780) * fix: allow support for full url from UI in freshsales * fix: allow support for full url from UI in freshmarketer * fix: tests * fix: testsx2 --- src/v0/destinations/freshmarketer/config.js | 14 ++--- src/v0/destinations/freshmarketer/utils.js | 2 +- src/v0/destinations/freshsales/config.js | 10 +-- test/__tests__/data/freshmarketer.json | 62 +++++++++---------- .../data/freshmarketer_router_input.json | 6 +- .../data/freshmarketer_router_output.json | 6 +- test/__tests__/data/freshsales.json | 50 +++++++-------- .../data/freshsales_router_input.json | 2 +- .../data/freshsales_router_output.json | 2 +- 9 files changed, 77 insertions(+), 77 deletions(-) diff --git a/src/v0/destinations/freshmarketer/config.js b/src/v0/destinations/freshmarketer/config.js index f1018d439c..a0d6449c3a 100644 --- a/src/v0/destinations/freshmarketer/config.js +++ b/src/v0/destinations/freshmarketer/config.js @@ -4,23 +4,23 @@ const CONFIG_CATEGORIES = { IDENTIFY: { name: 'FRESHMARKETERIdentifyConfig', type: 'identify', - baseUrl: '.myfreshworks.com/crm/sales/api/contacts/upsert', + baseUrl: '/crm/sales/api/contacts/upsert', }, GROUP: { name: 'FRESHMARKETERGroupConfig', type: 'group', - baseUrlAccount: '.myfreshworks.com/crm/sales/api/sales_accounts/upsert', - baseUrlList: '.myfreshworks.com/crm/sales/api/lists', + baseUrlAccount: '/crm/sales/api/sales_accounts/upsert', + baseUrlList: '/crm/sales/api/lists', }, SALES_ACTIVITY: { name: 'SalesActivityConfig', - baseUrlCreate: '.myfreshworks.com/crm/sales/api/sales_activities', - baseUrlListAll: '.myfreshworks.com/crm/sales/api/selector/sales_activity_types', + baseUrlCreate: '/crm/sales/api/sales_activities', + baseUrlListAll: '/crm/sales/api/selector/sales_activity_types', }, }; -const DELETE_ENDPOINT = '.myfreshworks.com/crm/sales/api/contacts/'; -const LIFECYCLE_STAGE_ENDPOINT = '.myfreshworks.com/crm/sales/api/selector/lifecycle_stages'; +const DELETE_ENDPOINT = '/crm/sales/api/contacts/'; +const LIFECYCLE_STAGE_ENDPOINT = '/crm/sales/api/selector/lifecycle_stages'; const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname); module.exports = { diff --git a/src/v0/destinations/freshmarketer/utils.js b/src/v0/destinations/freshmarketer/utils.js index 5b47bb9170..f7dcc46b06 100644 --- a/src/v0/destinations/freshmarketer/utils.js +++ b/src/v0/destinations/freshmarketer/utils.js @@ -203,7 +203,7 @@ const updateAccountWOContact = (payload, Config) => { */ const updateContactWithList = (userId, listId, Config) => { const response = defaultRequestConfig(); - response.endpoint = `https://${Config.domain}.myfreshworks.com/crm/sales/api/lists/${listId}/add_contacts`; + response.endpoint = `https://${Config.domain}/crm/sales/api/lists/${listId}/add_contacts`; response.headers = getHeaders(Config.apiKey); response.body.JSON = { ids: [userId], diff --git a/src/v0/destinations/freshsales/config.js b/src/v0/destinations/freshsales/config.js index c6ae4b8165..62f54c1297 100644 --- a/src/v0/destinations/freshsales/config.js +++ b/src/v0/destinations/freshsales/config.js @@ -4,23 +4,23 @@ const CONFIG_CATEGORIES = { IDENTIFY: { name: 'identifyConfig', type: 'identify', - baseUrl: '.myfreshworks.com/crm/sales/api/contacts/upsert', + baseUrl: '/crm/sales/api/contacts/upsert', method: 'POST', }, GROUP: { name: 'groupConfig', type: 'group', - baseUrlAccount: '.myfreshworks.com/crm/sales/api/sales_accounts/upsert', + baseUrlAccount: '/crm/sales/api/sales_accounts/upsert', method: 'POST', }, SALES_ACTIVITY: { name: 'SalesActivityConfig', - baseUrlCreate: '.myfreshworks.com/crm/sales/api/sales_activities', - baseUrlListAll: '.myfreshworks.com/crm/sales/api/selector/sales_activity_types', + baseUrlCreate: '/crm/sales/api/sales_activities', + baseUrlListAll: '/crm/sales/api/selector/sales_activity_types', }, }; -const LIFECYCLE_STAGE_ENDPOINT = '.myfreshworks.com/crm/sales/api/selector/lifecycle_stages'; +const LIFECYCLE_STAGE_ENDPOINT = '/crm/sales/api/selector/lifecycle_stages'; const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname); module.exports = { diff --git a/test/__tests__/data/freshmarketer.json b/test/__tests__/data/freshmarketer.json index 3d30841b30..390c0fb44e 100644 --- a/test/__tests__/data/freshmarketer.json +++ b/test/__tests__/data/freshmarketer.json @@ -5,7 +5,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "rudderstack-476952domain3105" + "domain": "rudderstack-476952domain3105.myfreshworks.com" } }, "message": { @@ -94,7 +94,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "rudderstack-476952domain3105" + "domain": "rudderstack-476952domain3105.myfreshworks.com" } }, "message": { @@ -183,7 +183,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "rudderstack-476952domain3105" + "domain": "rudderstack-476952domain3105.myfreshworks.com" } }, "message": { @@ -248,7 +248,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "rudderstack-476952domain3105" + "domain": "rudderstack-476952domain3105.myfreshworks.com" } }, "message": { @@ -312,7 +312,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } }, "message": { @@ -422,7 +422,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } }, "message": { @@ -491,7 +491,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } }, "message": { @@ -558,7 +558,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } }, "message": { @@ -627,7 +627,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } }, "message": { @@ -760,7 +760,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -843,7 +843,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -927,7 +927,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1019,7 +1019,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1127,7 +1127,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1228,7 +1228,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1315,7 +1315,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1406,7 +1406,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1496,7 +1496,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1586,7 +1586,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1696,7 +1696,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1757,7 +1757,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1788,7 +1788,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1825,7 +1825,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1892,7 +1892,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1937,7 +1937,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1976,7 +1976,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -2043,7 +2043,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -2145,7 +2145,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -2232,7 +2232,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -2334,7 +2334,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -2427,7 +2427,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder", + "domain": "domain-rudder.myfreshworks.com", "rudderEventsToFreshmarketerEvents": [ { "from": "test_activity", diff --git a/test/__tests__/data/freshmarketer_router_input.json b/test/__tests__/data/freshmarketer_router_input.json index 0e05c7f5f8..2cc5ce58de 100644 --- a/test/__tests__/data/freshmarketer_router_input.json +++ b/test/__tests__/data/freshmarketer_router_input.json @@ -3,7 +3,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "rudderstack-476952domain3105" + "domain": "rudderstack-476952domain3105.myfreshworks.com" } }, "metadata": { @@ -59,7 +59,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "rudderstack-476952domain3105" + "domain": "rudderstack-476952domain3105.myfreshworks.com" } }, "metadata": { @@ -115,7 +115,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } }, "metadata": { diff --git a/test/__tests__/data/freshmarketer_router_output.json b/test/__tests__/data/freshmarketer_router_output.json index 01740cb626..3525e4bb16 100644 --- a/test/__tests__/data/freshmarketer_router_output.json +++ b/test/__tests__/data/freshmarketer_router_output.json @@ -43,7 +43,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "rudderstack-476952domain3105" + "domain": "rudderstack-476952domain3105.myfreshworks.com" } } }, @@ -91,7 +91,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "rudderstack-476952domain3105" + "domain": "rudderstack-476952domain3105.myfreshworks.com" } } }, @@ -156,7 +156,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } } diff --git a/test/__tests__/data/freshsales.json b/test/__tests__/data/freshsales.json index 2527e37b90..55193532f4 100644 --- a/test/__tests__/data/freshsales.json +++ b/test/__tests__/data/freshsales.json @@ -69,7 +69,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -90,7 +90,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "rudderstack-476952domain3105" + "domain": "rudderstack-476952domain3105.myfreshworks.com" } }, "message": { @@ -179,7 +179,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "rudderstack-476952domain3105" + "domain": "rudderstack-476952domain3105.myfreshworks.com" } }, "message": { @@ -268,7 +268,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "rudderstack-476952domain3105" + "domain": "rudderstack-476952domain3105.myfreshworks.com" } }, "message": { @@ -356,7 +356,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "rudderstack-476952domain3105" + "domain": "rudderstack-476952domain3105.myfreshworks.com" } }, "message": { @@ -421,7 +421,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } }, "message": { @@ -531,7 +531,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } }, "message": { @@ -600,7 +600,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } }, "message": { @@ -667,7 +667,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } }, "message": { @@ -736,7 +736,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } }, "message": { @@ -869,7 +869,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -952,7 +952,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1036,7 +1036,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1128,7 +1128,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder", + "domain": "domain-rudder.myfreshworks.com", "rudderEventsToFreshsalesEvents": [ { "from": "test", @@ -1244,7 +1244,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1345,7 +1345,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1432,7 +1432,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1523,7 +1523,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1613,7 +1613,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1703,7 +1703,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1813,7 +1813,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1874,7 +1874,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -1966,7 +1966,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -2053,7 +2053,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, @@ -2155,7 +2155,7 @@ "destination": { "Config": { "apiKey": "dummyApiKey", - "domain": "domain-rudder" + "domain": "domain-rudder.myfreshworks.com" } } }, diff --git a/test/__tests__/data/freshsales_router_input.json b/test/__tests__/data/freshsales_router_input.json index 856a127f51..ea4b9887dd 100644 --- a/test/__tests__/data/freshsales_router_input.json +++ b/test/__tests__/data/freshsales_router_input.json @@ -66,7 +66,7 @@ }, "Config": { "apiKey": "hrkjfergeferf", - "domain": "rudderstack-479541159204968909" + "domain": "rudderstack-479541159204968909.myfreshworks.com" }, "Enabled": true, "Transformations": [], diff --git a/test/__tests__/data/freshsales_router_output.json b/test/__tests__/data/freshsales_router_output.json index 449d6eb45a..69d259ff00 100644 --- a/test/__tests__/data/freshsales_router_output.json +++ b/test/__tests__/data/freshsales_router_output.json @@ -72,7 +72,7 @@ }, "Config": { "apiKey": "hrkjfergeferf", - "domain": "rudderstack-479541159204968909" + "domain": "rudderstack-479541159204968909.myfreshworks.com" }, "Enabled": true, "Transformations": [], From 818858e046ce5f9735bbb97715c43a959ad3aa3c Mon Sep 17 00:00:00 2001 From: shrouti1507 <60211312+shrouti1507@users.noreply.github.com> Date: Mon, 6 Nov 2023 11:04:36 +0530 Subject: [PATCH 04/11] feat: onboard one signal to router transform (#2785) --- src/features.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/features.json b/src/features.json index fb5b0735f0..9793f667e3 100644 --- a/src/features.json +++ b/src/features.json @@ -58,7 +58,8 @@ "OPTIMIZELY_FULLSTACK": true, "TWITTER_ADS": true, "CLEVERTAP": true, - "TIKTOK_AUDIENCE": true, - "ORTTO": true + "ORTTO": true, + "ONE_SIGNAL": true, + "TIKTOK_AUDIENCE": true } } From 0947c58948a8192ac16f1a7e9b5b7520d5dc7530 Mon Sep 17 00:00:00 2001 From: Ujjwal Abhishek <63387036+ujjwal-ab@users.noreply.github.com> Date: Mon, 6 Nov 2023 11:32:34 +0530 Subject: [PATCH 05/11] chore: update release owner (#2794) * chore: update release owner * chore: update release-owners --- .github/workflows/create-hotfix-branch.yml | 2 +- .github/workflows/draft-new-release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/create-hotfix-branch.yml b/.github/workflows/create-hotfix-branch.yml index d1397cb608..97611f1eee 100644 --- a/.github/workflows/create-hotfix-branch.yml +++ b/.github/workflows/create-hotfix-branch.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest # Only allow these users to create new hotfix branch from 'main' - if: github.ref == 'refs/heads/main' && (github.actor == 'ItsSudip' || github.actor == 'krishna2020' || github.actor == 'saikumarrs' || github.actor == 'sandeepdsvs' || github.actor == 'shrouti1507' || github.actor == 'anantjain45823' || github.actor == 'chandumlg' || github.actor == 'mihir-4116') && (github.triggering_actor == 'ItsSudip' || github.triggering_actor == 'krishna2020' || github.triggering_actor == 'saikumarrs' || github.triggering_actor == 'sandeepdsvs' || github.triggering_actor == 'shrouti1507' || github.triggering_actor == 'anantjain45823' || github.triggering_actor == 'chandumlg' || github.triggering_actor == 'mihir-4116') + if: github.ref == 'refs/heads/main' && (github.actor == 'ItsSudip' || github.actor == 'krishna2020' || github.actor == 'saikumarrs' || github.actor == 'sandeepdsvs' || github.actor == 'shrouti1507' || github.actor == 'anantjain45823' || github.actor == 'chandumlg' || github.actor == 'mihir-4116' || github.actor == 'ujjwal-ab') && (github.triggering_actor == 'ItsSudip' || github.triggering_actor == 'krishna2020' || github.triggering_actor == 'saikumarrs' || github.triggering_actor == 'sandeepdsvs' || github.triggering_actor == 'shrouti1507' || github.triggering_actor == 'anantjain45823' || github.triggering_actor == 'chandumlg' || github.triggering_actor == 'mihir-4116' || github.triggering_actor == 'ujjwal-ab) steps: - name: Create Branch uses: peterjgrainger/action-create-branch@v2.4.0 diff --git a/.github/workflows/draft-new-release.yml b/.github/workflows/draft-new-release.yml index a0a558440a..23e243918f 100644 --- a/.github/workflows/draft-new-release.yml +++ b/.github/workflows/draft-new-release.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest # Only allow release stakeholders to initiate releases - if: (github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/heads/hotfix/')) && (github.actor == 'ItsSudip' || github.actor == 'krishna2020' || github.actor == 'saikumarrs' || github.actor == 'sandeepdsvs' || github.actor == 'shrouti1507' || github.actor == 'anantjain45823' || github.actor == 'chandumlg' || github.actor == 'mihir-4116' || github.actor == 'yashasvibajpai' || github.actor == 'sanpj2292') && (github.triggering_actor == 'ItsSudip' || github.triggering_actor == 'krishna2020' || github.triggering_actor == 'saikumarrs' || github.triggering_actor == 'sandeepdsvs' || github.triggering_actor == 'shrouti1507' || github.triggering_actor == 'anantjain45823' || github.triggering_actor == 'chandumlg' || github.triggering_actor == 'mihir-4116' || github.triggering_actor == 'yashasvibajpai' || github.triggering_actor == 'sanpj2292') + if: (github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/heads/hotfix/')) && (github.actor == 'ItsSudip' || github.actor == 'krishna2020' || github.actor == 'saikumarrs' || github.actor == 'sandeepdsvs' || github.actor == 'shrouti1507' || github.actor == 'anantjain45823' || github.actor == 'chandumlg' || github.actor == 'mihir-4116' || github.actor == 'yashasvibajpai' || github.actor == 'sanpj2292' || github.actor == 'ujjwal-ab') && (github.triggering_actor == 'ItsSudip' || github.triggering_actor == 'krishna2020' || github.triggering_actor == 'saikumarrs' || github.triggering_actor == 'sandeepdsvs' || github.triggering_actor == 'shrouti1507' || github.triggering_actor == 'anantjain45823' || github.triggering_actor == 'chandumlg' || github.triggering_actor == 'mihir-4116' || github.triggering_actor == 'yashasvibajpai' || github.triggering_actor == 'sanpj2292' || github.triggering_actor == 'ujjwal-ab') steps: - name: Checkout uses: actions/checkout@v3.5.3 From 55f96374b4d73db7013c1d5e72bfc9c8257b224b Mon Sep 17 00:00:00 2001 From: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com> Date: Mon, 6 Nov 2023 12:48:38 +0530 Subject: [PATCH 06/11] feat: onboard revenuecat as a source (#2774) * refactor: revenuecat source code * chore: refactor, address comments --- src/v0/sources/revenuecat/mapping.json | 10 + src/v0/sources/revenuecat/transform.js | 47 +++ test/integrations/sources/revenuecat/data.ts | 286 +++++++++++++++++++ 3 files changed, 343 insertions(+) create mode 100644 src/v0/sources/revenuecat/mapping.json create mode 100644 src/v0/sources/revenuecat/transform.js create mode 100644 test/integrations/sources/revenuecat/data.ts diff --git a/src/v0/sources/revenuecat/mapping.json b/src/v0/sources/revenuecat/mapping.json new file mode 100644 index 0000000000..541568b71b --- /dev/null +++ b/src/v0/sources/revenuecat/mapping.json @@ -0,0 +1,10 @@ +[ + { + "sourceKeys": "event.type", + "destKeys": "event" + }, + { + "sourceKeys": "event.id", + "destKeys": "messageId" + } +] diff --git a/src/v0/sources/revenuecat/transform.js b/src/v0/sources/revenuecat/transform.js new file mode 100644 index 0000000000..36944e10fa --- /dev/null +++ b/src/v0/sources/revenuecat/transform.js @@ -0,0 +1,47 @@ +const { camelCase } = require('lodash'); +const moment = require('moment'); +const { removeUndefinedAndNullValues, isDefinedAndNotNull } = require('../../util'); +const Message = require('../message'); + +function process(event) { + const message = new Message(`RevenueCat`); + + // we are setting event type as track always + message.setEventType('track'); + + const properties = {}; + // dump all event properties to message.properties after converting them to camelCase + if (event.event) { + Object.keys(event.event).forEach((key) => { + properties[camelCase(key)] = event.event[key]; + }); + message.setProperty('properties', properties); + } + + // setting up app_user_id to externalId : revenuecatAppUserId + if (event?.event?.app_user_id) { + message.context.externalId = [ + { + type: 'revenuecatAppUserId', + id: event?.event?.app_user_id, + }, + ]; + } + + if ( + isDefinedAndNotNull(event?.event?.event_timestamp_ms) && + moment(event?.event?.event_timestamp_ms).isValid() + ) { + const validTimestamp = new Date(event.event.event_timestamp_ms).toISOString(); + message.setProperty('originalTimestamp', validTimestamp); + message.setProperty('sentAt', validTimestamp); + } + message.event = event?.event?.type; + message.messageId = event?.event?.id; + + // removing undefined and null values from message + removeUndefinedAndNullValues(message); + return message; +} + +module.exports = { process }; diff --git a/test/integrations/sources/revenuecat/data.ts b/test/integrations/sources/revenuecat/data.ts new file mode 100644 index 0000000000..4963781763 --- /dev/null +++ b/test/integrations/sources/revenuecat/data.ts @@ -0,0 +1,286 @@ +export const data = [ + { + name: 'revenuecat', + description: 'Simple track call', + module: 'source', + version: 'v0', + input: { + request: { + body: [ + { + api_version: '1.0', + event: { + aliases: [ + 'f8e14f51-0c76-49ba-8d67-c229f1875dd9', + '389ad6dd-bb40-4c03-9471-1353da2d55ec', + ], + app_user_id: 'f8e14f51-0c76-49ba-8d67-c229f1875dd9', + commission_percentage: null, + country_code: 'US', + currency: null, + entitlement_id: null, + entitlement_ids: null, + environment: 'SANDBOX', + event_timestamp_ms: 1698617217232, + expiration_at_ms: 1698624417232, + id: '8CF0CD6C-CAF3-41FB-968A-661938235AF0', + is_family_share: null, + offer_code: null, + original_app_user_id: 'f8e14f51-0c76-49ba-8d67-c229f1875dd9', + original_transaction_id: null, + period_type: 'NORMAL', + presented_offering_id: null, + price: null, + price_in_purchased_currency: null, + product_id: 'test_product', + purchased_at_ms: 1698617217232, + store: 'APP_STORE', + subscriber_attributes: { + $displayName: { + updated_at_ms: 1698617217232, + value: 'Mister Mistoffelees', + }, + $email: { + updated_at_ms: 1698617217232, + value: 'tuxedo@revenuecat.com', + }, + $phoneNumber: { + updated_at_ms: 1698617217232, + value: '+19795551234', + }, + my_custom_attribute_1: { + updated_at_ms: 1698617217232, + value: 'catnip', + }, + }, + takehome_percentage: null, + tax_percentage: null, + transaction_id: null, + type: 'TEST', + }, + }, + ], + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }, + pathSuffix: '', + }, + output: { + response: { + status: 200, + body: [ + { + output: { + batch: [ + { + context: { + library: { + name: 'unknown', + version: 'unknown', + }, + integration: { + name: 'RevenueCat', + }, + externalId: [ + { + type: 'revenuecatAppUserId', + id: 'f8e14f51-0c76-49ba-8d67-c229f1875dd9', + }, + ], + }, + integrations: { + RevenueCat: false, + }, + type: 'track', + properties: { + aliases: [ + 'f8e14f51-0c76-49ba-8d67-c229f1875dd9', + '389ad6dd-bb40-4c03-9471-1353da2d55ec', + ], + appUserId: 'f8e14f51-0c76-49ba-8d67-c229f1875dd9', + commissionPercentage: null, + countryCode: 'US', + currency: null, + entitlementId: null, + entitlementIds: null, + environment: 'SANDBOX', + eventTimestampMs: 1698617217232, + expirationAtMs: 1698624417232, + id: '8CF0CD6C-CAF3-41FB-968A-661938235AF0', + isFamilyShare: null, + offerCode: null, + originalAppUserId: 'f8e14f51-0c76-49ba-8d67-c229f1875dd9', + originalTransactionId: null, + periodType: 'NORMAL', + presentedOfferingId: null, + price: null, + priceInPurchasedCurrency: null, + productId: 'test_product', + purchasedAtMs: 1698617217232, + store: 'APP_STORE', + subscriberAttributes: { + $displayName: { + updated_at_ms: 1698617217232, + value: 'Mister Mistoffelees', + }, + $email: { + updated_at_ms: 1698617217232, + value: 'tuxedo@revenuecat.com', + }, + $phoneNumber: { + updated_at_ms: 1698617217232, + value: '+19795551234', + }, + my_custom_attribute_1: { + updated_at_ms: 1698617217232, + value: 'catnip', + }, + }, + takehomePercentage: null, + taxPercentage: null, + transactionId: null, + type: 'TEST', + }, + event: 'TEST', + messageId: '8CF0CD6C-CAF3-41FB-968A-661938235AF0', + originalTimestamp: '2023-10-29T22:06:57.232Z', + sentAt: '2023-10-29T22:06:57.232Z', + }, + ], + }, + }, + ], + }, + }, + }, + { + name: 'revenuecat', + description: 'Initial purchase event', + module: 'source', + version: 'v0', + input: { + request: { + body: [ + { + api_version: '1.0', + event: { + aliases: ['yourCustomerAliasedID', 'yourCustomerAliasedID'], + app_id: 'yourAppID', + app_user_id: 'yourCustomerAppUserID', + commission_percentage: 0.3, + country_code: 'US', + currency: 'USD', + entitlement_id: 'pro_cat', + entitlement_ids: ['pro_cat'], + environment: 'PRODUCTION', + event_timestamp_ms: 1591121855319, + expiration_at_ms: 1591726653000, + id: 'UniqueIdentifierOfEvent', + is_family_share: false, + offer_code: 'free_month', + original_app_user_id: 'OriginalAppUserID', + original_transaction_id: '1530648507000', + period_type: 'NORMAL', + presented_offering_id: 'OfferingID', + price: 2.49, + price_in_purchased_currency: 2.49, + product_id: 'onemonth_no_trial', + purchased_at_ms: 1591121853000, + store: 'APP_STORE', + subscriber_attributes: { + '$Favorite Cat': { + updated_at_ms: 1581121853000, + value: 'Garfield', + }, + }, + takehome_percentage: 0.7, + tax_percentage: 0.3, + transaction_id: '170000869511114', + type: 'INITIAL_PURCHASE', + }, + }, + ], + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }, + pathSuffix: '', + }, + output: { + response: { + status: 200, + body: [ + { + output: { + batch: [ + { + context: { + library: { + name: 'unknown', + version: 'unknown', + }, + integration: { + name: 'RevenueCat', + }, + externalId: [ + { + type: 'revenuecatAppUserId', + id: 'yourCustomerAppUserID', + }, + ], + }, + integrations: { + RevenueCat: false, + }, + type: 'track', + properties: { + aliases: ['yourCustomerAliasedID', 'yourCustomerAliasedID'], + appId: 'yourAppID', + appUserId: 'yourCustomerAppUserID', + commissionPercentage: 0.3, + countryCode: 'US', + currency: 'USD', + entitlementId: 'pro_cat', + entitlementIds: ['pro_cat'], + environment: 'PRODUCTION', + eventTimestampMs: 1591121855319, + expirationAtMs: 1591726653000, + id: 'UniqueIdentifierOfEvent', + isFamilyShare: false, + offerCode: 'free_month', + originalAppUserId: 'OriginalAppUserID', + originalTransactionId: '1530648507000', + periodType: 'NORMAL', + presentedOfferingId: 'OfferingID', + price: 2.49, + priceInPurchasedCurrency: 2.49, + productId: 'onemonth_no_trial', + purchasedAtMs: 1591121853000, + store: 'APP_STORE', + subscriberAttributes: { + '$Favorite Cat': { + updated_at_ms: 1581121853000, + value: 'Garfield', + }, + }, + takehomePercentage: 0.7, + taxPercentage: 0.3, + transactionId: '170000869511114', + type: 'INITIAL_PURCHASE', + }, + event: 'INITIAL_PURCHASE', + messageId: 'UniqueIdentifierOfEvent', + originalTimestamp: '2020-06-02T18:17:35.319Z', + sentAt: '2020-06-02T18:17:35.319Z', + }, + ], + }, + }, + ], + }, + }, + }, +]; From 6e89cd3f67ea887ba17c1cd5ffbca6675f54d96c Mon Sep 17 00:00:00 2001 From: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com> Date: Mon, 6 Nov 2023 12:55:19 +0530 Subject: [PATCH 07/11] fix: add check to remove null and undefined properties before sending (#2796) * fix: add check to remove null and undefined properties before sending * chore: fix test --- src/v0/destinations/adobe_analytics/transform.js | 3 ++- test/__tests__/data/adobe_analytics.json | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/v0/destinations/adobe_analytics/transform.js b/src/v0/destinations/adobe_analytics/transform.js index 54806bf578..67bb66310a 100644 --- a/src/v0/destinations/adobe_analytics/transform.js +++ b/src/v0/destinations/adobe_analytics/transform.js @@ -11,6 +11,7 @@ const { isDefinedAndNotNull, isDefinedAndNotNullAndNotEmpty, getIntegrationsObj, + removeUndefinedAndNullValues, simpleProcessRouterDest, } = require('../../util'); const { @@ -394,7 +395,7 @@ const handleTrack = (message, destinationConfig) => { break; } - return payload; + return removeUndefinedAndNullValues(payload); }; const process = async (event) => { diff --git a/test/__tests__/data/adobe_analytics.json b/test/__tests__/data/adobe_analytics.json index cfffccb8da..6361f92640 100644 --- a/test/__tests__/data/adobe_analytics.json +++ b/test/__tests__/data/adobe_analytics.json @@ -1010,7 +1010,6 @@ }, "messageId": "1578564113557-af022c68-429e-4af4-b99b-2b9174056383", "properties": { - "order_id": "1234", "affiliation": "Apple Store", "value": 20, "revenue": 15.0, @@ -1019,6 +1018,7 @@ "discount": 1.5, "coupon": "ImagePro", "currency": "USD", + "purchaseId": "p101", "products": [ { "product_id": "123", @@ -1205,7 +1205,7 @@ "JSON": {}, "JSON_ARRAY": {}, "XML": { - "payload": "17941080sales campaignwebUSD127.0.0.1en-US12341234Dalvik/2.1.0 (Linux; U; Android 9; Android SDK built for x86 Build/PSR1.180720.075)https://www.google.com/search?q=estore+bestsellerroottval001RudderLabs JavaScript SDKocheckout startedhttps://www.estore.com/best-seller/12020-01-09T10:01:53.558Zmktcloudid001scCheckoutGames;Monopoly;1;14.00,Games;UNO;2;6.90footlockerrudderstackpoc" + "payload": "17941080sales campaignwebUSD127.0.0.1en-USDalvik/2.1.0 (Linux; U; Android 9; Android SDK built for x86 Build/PSR1.180720.075)https://www.google.com/search?q=estore+bestsellerroottval001RudderLabs JavaScript SDKocheckout startedhttps://www.estore.com/best-seller/12020-01-09T10:01:53.558Zmktcloudid001p101scCheckoutGames;Monopoly;1;14.00,Games;UNO;2;6.90footlockerrudderstackpoc" }, "FORM": {} }, From 11fb7c47910681833e37d25a1573d2005e62742b Mon Sep 17 00:00:00 2001 From: Ujjwal Abhishek <63387036+ujjwal-ab@users.noreply.github.com> Date: Mon, 6 Nov 2023 14:28:51 +0530 Subject: [PATCH 08/11] fix: busgnag issues for klaviyo, freshsales, customeio (#2795) * chore(klaviyo): remove error notifier * fix(freshsales): stringify event before lowercase * fix(customerio): convert eventName to string for truncate * fix: add validation for event Name type and update function name * fix: remove unrequired validation --- src/v0/destinations/customerio/transform.js | 3 +++ src/v0/destinations/customerio/util.js | 2 -- src/v0/destinations/freshsales/transform.js | 7 ++----- src/v0/destinations/klaviyo/util.js | 6 ------ src/v0/destinations/monday/transform.js | 4 ++-- src/v0/util/index.js | 4 ++-- 6 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/v0/destinations/customerio/transform.js b/src/v0/destinations/customerio/transform.js index 5f953ee2f0..984fb7e67f 100644 --- a/src/v0/destinations/customerio/transform.js +++ b/src/v0/destinations/customerio/transform.js @@ -12,6 +12,7 @@ const { adduserIdFromExternalId, getFieldValueFromMessage, handleRtTfSingleEventError, + validateEventName, } = require('../../util'); const logger = require('../../../logger'); @@ -101,6 +102,7 @@ function processSingleMessage(message, destination) { break; case EventType.TRACK: evType = 'event'; + validateEventName(message.event); evName = message.event; break; case EventType.ALIAS: @@ -113,6 +115,7 @@ function processSingleMessage(message, destination) { logger.error(`could not determine type ${messageType}`); throw new InstrumentationError(`could not determine type ${messageType}`); } + evName = evName ? String(evName) : evName; const response = responseBuilder(message, evType, evName, destination, messageType); // replace default domain with EU data center domainc for EU based account diff --git a/src/v0/destinations/customerio/util.js b/src/v0/destinations/customerio/util.js index 2e7f000fba..6b4dbc0e11 100644 --- a/src/v0/destinations/customerio/util.js +++ b/src/v0/destinations/customerio/util.js @@ -10,7 +10,6 @@ const { defaultDeleteRequestConfig, isAppleFamily, validateEmail, - validateEventType, } = require('../../util'); const { EventType, SpecedTraits, TraitsMapping } = require('../../../constants'); @@ -288,7 +287,6 @@ const defaultResponseBuilder = (message, evName, userId, evType, destination, me // 100 - len(`Viewed Screen`) = 86 trimmedEvName = `Viewed ${truncate(message.event || message.properties.name, 86)} Screen`; } else { - validateEventType(evName); trimmedEvName = truncate(evName, 100); } // anonymous_id needs to be sent for anon track calls to provide information on which anon user is being tracked diff --git a/src/v0/destinations/freshsales/transform.js b/src/v0/destinations/freshsales/transform.js index cd7518a101..c1e18482ed 100644 --- a/src/v0/destinations/freshsales/transform.js +++ b/src/v0/destinations/freshsales/transform.js @@ -8,7 +8,7 @@ const { defaultPostRequestConfig, getValidDynamicFormConfig, simpleProcessRouterDest, - validateEventType, + validateEventName, } = require('../../util'); const { InstrumentationError, TransformationError } = require('../../util/errorTypes'); const { CONFIG_CATEGORIES, MAPPING_CONFIG } = require('./config'); @@ -67,7 +67,6 @@ const identifyResponseBuilder = (message, { Config }) => { * @returns */ const trackResponseBuilder = async (message, { Config }, event) => { - validateEventType(event); let payload; const response = defaultRequestConfig(); @@ -125,9 +124,6 @@ const groupResponseBuilder = async (message, { Config }) => { // Checks if there are any mapping events for the track event and returns them function eventMappingHandler(message, destination) { const event = get(message, 'event'); - if (!event) { - throw new InstrumentationError('Event name is required'); - } let { rudderEventsToFreshsalesEvents } = destination.Config; const mappedEvents = new Set(); @@ -161,6 +157,7 @@ const processEvent = async (message, destination) => { response = identifyResponseBuilder(message, destination); break; case EventType.TRACK: { + validateEventName(message.event); const mappedEvents = eventMappingHandler(message, destination); if (mappedEvents.length > 0) { const respList = await Promise.all( diff --git a/src/v0/destinations/klaviyo/util.js b/src/v0/destinations/klaviyo/util.js index 4304edd78f..b31dafd78b 100644 --- a/src/v0/destinations/klaviyo/util.js +++ b/src/v0/destinations/klaviyo/util.js @@ -17,7 +17,6 @@ const { handleHttpRequest } = require('../../../adapters/network'); const { JSON_MIME_TYPE, HTTP_STATUS_CODES } = require('../../util/constant'); const { NetworkError, InstrumentationError } = require('../../util/errorTypes'); const { getDynamicErrorType } = require('../../../adapters/utils/networkUtils'); -const { client: errNotificationClient } = require('../../../util/errorNotifier'); const { BASE_ENDPOINT, MAPPING_CONFIG, CONFIG_CATEGORIES, MAX_BATCH_SIZE } = require('./config'); const REVISION_CONSTANT = '2023-02-22'; @@ -69,11 +68,6 @@ const getIdFromNewOrExistingProfile = async (endpoint, payload, requestOptions) let statusCode = resp.status; if (resp.status === 201 || resp.status === 409) { // retryable error if the profile id is not found in the response - errNotificationClient.notify( - new Error('Klaviyo: ProfileId not found'), - 'Profile Id not Found in the response', - JSON.stringify(resp.response), - ); statusCode = 500; } diff --git a/src/v0/destinations/monday/transform.js b/src/v0/destinations/monday/transform.js index 34ada34780..37ee835e50 100644 --- a/src/v0/destinations/monday/transform.js +++ b/src/v0/destinations/monday/transform.js @@ -8,7 +8,7 @@ const { removeUndefinedAndNullValues, simpleProcessRouterDest, getDestinationExternalID, - validateEventType, + validateEventName, } = require('../../util'); const { ConfigurationError, @@ -42,7 +42,7 @@ const trackResponseBuilder = async (message, { Config }) => { const { apiToken } = Config; let boardId = getDestinationExternalID(message, 'boardId'); const event = get(message, 'event'); - validateEventType(event); + validateEventName(event); if (!boardId) { boardId = Config.boardId; } diff --git a/src/v0/util/index.js b/src/v0/util/index.js index e5ec1642b4..d6f6621220 100644 --- a/src/v0/util/index.js +++ b/src/v0/util/index.js @@ -2069,7 +2069,7 @@ const isValidInteger = (value) => { // Use a regular expression to check if the string is a valid integer or a valid floating-point number return typeof value === 'string' ? /^-?\d+$/.test(value) : false; }; -const validateEventType = (event) => { +const validateEventName = (event) => { if (!event || typeof event !== 'string') { throw new InstrumentationError('Event is a required field and should be a string'); } @@ -2177,7 +2177,7 @@ module.exports = { getDestAuthCacheInstance, refinePayload, validateEmail, - validateEventType, + validateEventName, validatePhoneWithCountryCode, getEventReqMetadata, isHybridModeEnabled, From 373535524c9c88f38cfbe558ec38e028221117fb Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 6 Nov 2023 10:17:13 +0000 Subject: [PATCH 09/11] chore(release): 1.49.0 --- CHANGELOG.md | 16 ++++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01631435d2..b9256fa915 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.49.0](https://github.com/rudderlabs/rudder-transformer/compare/v1.48.0...v1.49.0) (2023-11-06) + + +### Features + +* add new destination tiktok_audience ([#2710](https://github.com/rudderlabs/rudder-transformer/issues/2710)) ([9bc0fd8](https://github.com/rudderlabs/rudder-transformer/commit/9bc0fd8efcee44871a190bd6cb9e89c5cf035ff8)) +* onboard one signal to router transform ([#2785](https://github.com/rudderlabs/rudder-transformer/issues/2785)) ([818858e](https://github.com/rudderlabs/rudder-transformer/commit/818858e046ce5f9735bbb97715c43a959ad3aa3c)) +* onboard revenuecat as a source ([#2774](https://github.com/rudderlabs/rudder-transformer/issues/2774)) ([55f9637](https://github.com/rudderlabs/rudder-transformer/commit/55f96374b4d73db7013c1d5e72bfc9c8257b224b)) + + +### Bug Fixes + +* add check to remove null and undefined properties before sending ([#2796](https://github.com/rudderlabs/rudder-transformer/issues/2796)) ([6e89cd3](https://github.com/rudderlabs/rudder-transformer/commit/6e89cd3f67ea887ba17c1cd5ffbca6675f54d96c)) +* allow support for full url from UI in freshsales and freshmarketer ([#2780](https://github.com/rudderlabs/rudder-transformer/issues/2780)) ([570532c](https://github.com/rudderlabs/rudder-transformer/commit/570532ce790c05a69621d9289758a1b1a7acda8c)) +* busgnag issues for klaviyo, freshsales, customeio ([#2795](https://github.com/rudderlabs/rudder-transformer/issues/2795)) ([11fb7c4](https://github.com/rudderlabs/rudder-transformer/commit/11fb7c47910681833e37d25a1573d2005e62742b)) + ## [1.48.0](https://github.com/rudderlabs/rudder-transformer/compare/v1.47.0...v1.48.0) (2023-11-02) diff --git a/package-lock.json b/package-lock.json index 0822a9b42b..5e857f6bb9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "rudder-transformer", - "version": "1.48.0", + "version": "1.49.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rudder-transformer", - "version": "1.48.0", + "version": "1.49.0", "license": "ISC", "dependencies": { "@amplitude/ua-parser-js": "^0.7.24", diff --git a/package.json b/package.json index 46f728664d..ac0ae2bbeb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rudder-transformer", - "version": "1.48.0", + "version": "1.49.0", "description": "", "homepage": "https://github.com/rudderlabs/rudder-transformer#readme", "bugs": { From c99361b951960224b8220c854f9c509ec2844b52 Mon Sep 17 00:00:00 2001 From: Anant Jain Date: Tue, 7 Nov 2023 12:20:39 +0530 Subject: [PATCH 10/11] chore: comments addressed --- src/v0/destinations/am/transform.js | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/v0/destinations/am/transform.js b/src/v0/destinations/am/transform.js index b74625e273..d7d8f95565 100644 --- a/src/v0/destinations/am/transform.js +++ b/src/v0/destinations/am/transform.js @@ -589,8 +589,8 @@ const processSingleMessage = (message, destination) => { // To be used for track/page calls to associate the event to a group in AM let groupInfo = get(message, 'integrations.Amplitude.groups') || undefined; let category = ConfigCategory.DEFAULT; - let { properties } = message; - const { name, event } = message; + let updatedProperties; + const { name, event, properties } = message; const messageType = message.type.toLowerCase(); const CATEGORY_KEY = 'properties.category'; const { useUserDefinedPageEventName, userProvidedPageEventString } = destination.Config; @@ -623,17 +623,14 @@ const processSingleMessage = (message, destination) => { evType = `Viewed ${updatedName || ''}Page`; } message.properties = { - ...message.properties, + ...properties, name: name || get(message, CATEGORY_KEY), }; category = ConfigCategory.PAGE; break; case EventType.SCREEN: - ({ evType, updatedProperties: properties } = getScreenevTypeAndUpdatedProperties( - message, - CATEGORY_KEY, - )); - message.properties = properties; + ({ evType, updatedProperties } = getScreenevTypeAndUpdatedProperties(message, CATEGORY_KEY)); + message.properties = updatedProperties; category = ConfigCategory.SCREEN; break; case EventType.GROUP: @@ -664,9 +661,9 @@ const processSingleMessage = (message, destination) => { throw new InstrumentationError('Event not present. Please send event field'); } if ( - message.properties && - isDefinedAndNotNull(message.properties?.revenue) && - isDefinedAndNotNull(message.properties?.revenue_type) + properties && + isDefinedAndNotNull(properties?.revenue) && + isDefinedAndNotNull(properties?.revenue_type) ) { // if properties has revenue and revenue_type fields // consider the event as revenue event directly From 54120233ab07bb8c095f91a2dcb90a7d8e5fdf8e Mon Sep 17 00:00:00 2001 From: Anant Jain Date: Tue, 7 Nov 2023 12:49:08 +0530 Subject: [PATCH 11/11] chore: refactor screen code for evType and properties --- src/v0/destinations/am/transform.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/v0/destinations/am/transform.js b/src/v0/destinations/am/transform.js index d7d8f95565..04a1c5a107 100644 --- a/src/v0/destinations/am/transform.js +++ b/src/v0/destinations/am/transform.js @@ -196,7 +196,7 @@ const getScreenevTypeAndUpdatedProperties = (message, CATEGORY_KEY) => { const name = message.name || message.event || get(message, CATEGORY_KEY); const updatedName = name ? `${name} ` : ''; return { - evType: `Viewed ${updatedName}Screen`, + eventType: `Viewed ${updatedName}Screen`, updatedProperties: { ...message.properties, name, @@ -589,7 +589,6 @@ const processSingleMessage = (message, destination) => { // To be used for track/page calls to associate the event to a group in AM let groupInfo = get(message, 'integrations.Amplitude.groups') || undefined; let category = ConfigCategory.DEFAULT; - let updatedProperties; const { name, event, properties } = message; const messageType = message.type.toLowerCase(); const CATEGORY_KEY = 'properties.category'; @@ -629,9 +628,15 @@ const processSingleMessage = (message, destination) => { category = ConfigCategory.PAGE; break; case EventType.SCREEN: - ({ evType, updatedProperties } = getScreenevTypeAndUpdatedProperties(message, CATEGORY_KEY)); - message.properties = updatedProperties; - category = ConfigCategory.SCREEN; + { + const { eventType, updatedProperties } = getScreenevTypeAndUpdatedProperties( + message, + CATEGORY_KEY, + ); + evType = eventType; + message.properties = updatedProperties; + category = ConfigCategory.SCREEN; + } break; case EventType.GROUP: evType = 'group';