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] 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": {