From a01a96e143e5f7969ba7303d763023712ceebdc3 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Thu, 14 Sep 2023 14:09:55 +0530 Subject: [PATCH 01/14] feat(intercom): refactor --- src/constants/destinationCanonicalNames.js | 1 + src/v0/destinations/intercom/config.js | 63 ++-- .../intercom/data/INTERCOMGroupConfig.json | 8 + .../intercom/data/INTERCOMIdentifyConfig.json | 53 +++- .../intercom/data/INTERCOMTrackConfig.json | 45 +-- src/v0/destinations/intercom/transform.js | 300 ++++++------------ src/v0/destinations/intercom/util.js | 205 +++++++++++- test/__mocks__/axios.js | 6 + test/__mocks__/data/intercom/response.json | 36 +++ test/__mocks__/intercom.mock.js | 29 ++ 10 files changed, 485 insertions(+), 261 deletions(-) create mode 100644 test/__mocks__/data/intercom/response.json create mode 100644 test/__mocks__/intercom.mock.js diff --git a/src/constants/destinationCanonicalNames.js b/src/constants/destinationCanonicalNames.js index 6c7ec83ed9e..9b53a132c5f 100644 --- a/src/constants/destinationCanonicalNames.js +++ b/src/constants/destinationCanonicalNames.js @@ -132,6 +132,7 @@ const DestCanonicalNames = { 'TWITTER_ADS', ], BRAZE: ['BRAZE', 'Braze', 'braze'], + INTERCOM: ['INTERCOM', 'intercom','Intercom'], }; module.exports = { DestHandlerMap, DestCanonicalNames }; diff --git a/src/v0/destinations/intercom/config.js b/src/v0/destinations/intercom/config.js index ae29eebc1eb..930e7764cd3 100644 --- a/src/v0/destinations/intercom/config.js +++ b/src/v0/destinations/intercom/config.js @@ -1,53 +1,74 @@ const { getMappingConfig } = require('../../util'); const BASE_ENDPOINT = 'https://api.intercom.io'; +const BASE_EU_ENDPOINT = 'https://api.eu.intercom.io'; +const BASE_AU_ENDPOINT = 'https://api.au.intercom.io'; -// track events | Track -const TRACK_ENDPOINT = `${BASE_ENDPOINT}/events`; -// Create, Update a user with a company | Identify -const IDENTIFY_ENDPOINT = `${BASE_ENDPOINT}/users`; -// create, update, delete a company | Group -const GROUP_ENDPOINT = `${BASE_ENDPOINT}/companies`; +const SEARCH_CONTACT_ENDPOINT = 'contacts/search'; +const CREATE_OR_UPDATE_COMPANY_ENDPOINT = 'companies'; const ConfigCategory = { TRACK: { - endpoint: TRACK_ENDPOINT, + endpoint: 'events', name: 'INTERCOMTrackConfig', }, IDENTIFY: { - endpoint: IDENTIFY_ENDPOINT, - name: 'INTERCOMIdentifyConfig', + endpoint: 'contacts', + name: 'INTERCOMIdentifyConfig' }, GROUP: { - endpoint: GROUP_ENDPOINT, + endpoint: 'contacts/{id}/companies', name: 'INTERCOMGroupConfig', }, }; const MappingConfig = getMappingConfig(ConfigCategory, __dirname); -const ReservedTraitsProperties = [ +const ReservedUserAttributes = [ 'userId', + 'role', 'email', 'phone', 'name', - 'createdAt', - 'firstName', + 'avatar', + 'company', + 'ownerId', 'lastName', - 'firstname', 'lastname', - 'company', + 'firstName', + 'firstname', + 'last_seen_at', + 'signed_up_at', + 'unsubscribedFromEmails' ]; -const ReservedCompanyProperties = ['id', 'name', 'industry']; +const ReservedCompanyAttributes = [ + 'tags', + 'size', + 'plan', + 'name', + 'userId', + 'website', + 'industry', + 'segments', + 'userCount', + 'createdAt', + 'sessionCount', + 'monthlySpend', + 'remoteCreatedAt', +]; -// ref:- https://developers.intercom.com/intercom-api-reference/v1.4/reference/event-metadata-types const MetadataTypes = { richLink: ['url', 'value'], monetaryAmount: ['amount', 'currency'] }; module.exports = { - ConfigCategory, - MappingConfig, - ReservedCompanyProperties, - ReservedTraitsProperties, + BASE_ENDPOINT, MetadataTypes, + MappingConfig, + ConfigCategory, + BASE_EU_ENDPOINT, + BASE_AU_ENDPOINT, + ReservedUserAttributes, + SEARCH_CONTACT_ENDPOINT, + ReservedCompanyAttributes, + CREATE_OR_UPDATE_COMPANY_ENDPOINT, }; diff --git a/src/v0/destinations/intercom/data/INTERCOMGroupConfig.json b/src/v0/destinations/intercom/data/INTERCOMGroupConfig.json index 174f828a56c..ba495bb1752 100644 --- a/src/v0/destinations/intercom/data/INTERCOMGroupConfig.json +++ b/src/v0/destinations/intercom/data/INTERCOMGroupConfig.json @@ -49,5 +49,13 @@ "type": "toNumber" }, "required": false + }, + { + "destKey": "custom_attributes", + "sourceKeys": [ + "traits", + "context.traits" + ], + "required": false } ] diff --git a/src/v0/destinations/intercom/data/INTERCOMIdentifyConfig.json b/src/v0/destinations/intercom/data/INTERCOMIdentifyConfig.json index 726a741161e..f53e5edb260 100644 --- a/src/v0/destinations/intercom/data/INTERCOMIdentifyConfig.json +++ b/src/v0/destinations/intercom/data/INTERCOMIdentifyConfig.json @@ -1,28 +1,43 @@ [ { - "destKey": "user_id", + "destKey": "role", + "sourceKeys": [ + "traits.role", + "context.traits.role" + ], + "required": false + }, + { + "destKey": "external_id", "sourceKeys": [ "userId", "traits.userId", - "traits.id", - "context.traits.userId", - "context.traits.id" + "context.traits.userId" ], "required": false }, { "destKey": "email", - "sourceKeys": ["traits.email", "context.traits.email"], + "sourceKeys": "email", + "sourceFromGenericMap": true, "required": false }, { "destKey": "phone", - "sourceKeys": ["traits.phone", "context.traits.phone"], + "sourceKeys": "phone", + "sourceFromGenericMap": true, "required": false }, { "destKey": "name", - "sourceKeys": ["traits.name", "context.traits.name"], + "sourceKeys": "name", + "sourceFromGenericMap": true, + "required": false + }, + { + "destKey": "avatar", + "sourceKeys": "avatar", + "sourceFromGenericMap": true, "required": false }, { @@ -34,8 +49,28 @@ } }, { - "destKey": "last_seen_user_agent", - "sourceKeys": "context.userAgent", + "destKey": "last_seen_at", + "sourceKeys": "timestamp", + "sourceFromGenericMap": true, + "required": false, + "metadata": { + "type": "secondTimestamp" + } + }, + { + "destKey": "owner_id", + "sourceKeys": ["traits.ownerId", "context.traits.ownerId"], + "required": false, + "metadata": { + "type": "toNumber" + } + }, + { + "destKey": "unsubscribed_from_emails", + "sourceKeys": [ + "traits.unsubscribedFromEmails", + "context.traits.unsubscribedFromEmails" + ], "required": false }, { diff --git a/src/v0/destinations/intercom/data/INTERCOMTrackConfig.json b/src/v0/destinations/intercom/data/INTERCOMTrackConfig.json index f33c9a8a982..2a6f7f0267c 100644 --- a/src/v0/destinations/intercom/data/INTERCOMTrackConfig.json +++ b/src/v0/destinations/intercom/data/INTERCOMTrackConfig.json @@ -1,36 +1,45 @@ [ + { + "destKey": "event_name", + "sourceKeys": "event", + "required": true + }, + { + "destKey": "created_at", + "sourceKeys": "timestamp", + "sourceFromGenericMap": true, + "required": true, + "metadata": { + "type": "secondTimestamp" + } + }, { "destKey": "user_id", "sourceKeys": [ "userId", "traits.userId", - "traits.id", - "context.traits.userId", - "context.traits.id" + "context.traits.userId" ], "required": false }, { - "destKey": "email", - "sourceKeys": ["traits.email", "context.traits.email"], + "destKey": "id", + "sourceKeys": [ + "traits.id", + "properties.id", + "context.traits.id" + ], "required": false }, { - "destKey": "event_name", - "sourceKeys": "event", - "required": true - }, - { - "destKey": "created", - "sourceKeys": "timestamp", + "destKey": "email", + "sourceKeys": "email", "sourceFromGenericMap": true, - "required": true, - "metadata": { - "type": "secondTimestamp" - } + "required": false }, { "destKey": "metadata", - "sourceKeys": "properties" + "sourceKeys": "properties", + "required": false } -] +] \ No newline at end of file diff --git a/src/v0/destinations/intercom/transform.js b/src/v0/destinations/intercom/transform.js index aa088ee5db1..c98cad508b9 100644 --- a/src/v0/destinations/intercom/transform.js +++ b/src/v0/destinations/intercom/transform.js @@ -1,251 +1,133 @@ -const md5 = require('md5'); -const get = require('get-value'); -const { EventType, MappedToDestinationKey } = require('../../../constants'); +const { EventType } = require('../../../constants'); +const { ConfigCategory, ReservedUserAttributes, ReservedCompanyAttributes } = require('./config'); const { - ConfigCategory, - MappingConfig, - ReservedTraitsProperties, - ReservedCompanyProperties, -} = require('./config'); -const { - constructPayload, - removeUndefinedAndNullValues, + flattenJson, + isDefinedAndNotNull, defaultRequestConfig, - defaultPostRequestConfig, getFieldValueFromMessage, - addExternalIdToTraits, - simpleProcessRouterDest, - flattenJson, + removeUndefinedAndNullValues, } = require('../../util'); -const { separateReservedAndRestMetadata } = require('./util'); +const { + getHeaders, + getPayload, + validateTrack, + fetchContactId, + getBaseEndpoint, + validateIdentify, + getCustomAttributes, + createOrUpdateCompany, + separateReservedAndRestMetadata, +} = require('./util'); const { InstrumentationError } = require('../../util/errorTypes'); -const { JSON_MIME_TYPE } = require('../../util/constant'); - -function getCompanyAttribute(company) { - const companiesList = []; - if (company.name || company.id) { - const customAttributes = {}; - Object.keys(company).forEach((key) => { - // the key is not in ReservedCompanyProperties - if (!ReservedCompanyProperties.includes(key)) { - const val = company[key]; - if (val !== Object(val)) { - customAttributes[key] = val; - } else { - customAttributes[key] = JSON.stringify(val); - } - } - }); - - companiesList.push({ - company_id: company.id || md5(company.name), - custom_attributes: removeUndefinedAndNullValues(customAttributes), - name: company.name, - industry: company.industry, - }); - } - return companiesList; -} - -function validateIdentify(message, payload, config) { - const finalPayload = payload; - - finalPayload.update_last_request_at = - config.updateLastRequestAt !== undefined ? config.updateLastRequestAt : true; - if (payload.user_id || payload.email) { - if (payload.name === undefined || payload.name === '') { - const firstName = getFieldValueFromMessage(message, 'firstName'); - const lastName = getFieldValueFromMessage(message, 'lastName'); - if (firstName && lastName) { - finalPayload.name = `${firstName} ${lastName}`; - } else { - finalPayload.name = firstName || lastName; - } - } - if (get(finalPayload, 'custom_attributes.company')) { - finalPayload.companies = getCompanyAttribute(finalPayload.custom_attributes.company); - } +const responseBuilder = (payload, endpoint, requestMethod, destination) => { + const response = defaultRequestConfig(); + response.method = requestMethod; + response.endpoint = endpoint; + response.headers = getHeaders(destination); + response.body.JSON = removeUndefinedAndNullValues(payload); + return response; +}; - if (finalPayload.custom_attributes) { - ReservedTraitsProperties.forEach((trait) => { - delete finalPayload.custom_attributes[trait]; - }); - finalPayload.custom_attributes = flattenJson(finalPayload.custom_attributes); +const identifyResponseBuilder = async (message, destination) => { + const payload = getPayload(message, ConfigCategory.IDENTIFY); + validateIdentify(payload); + + if (payload.name === undefined || payload.name === '') { + const firstName = getFieldValueFromMessage(message, 'firstName'); + const lastName = getFieldValueFromMessage(message, 'lastName'); + if (firstName && lastName) { + payload.name = `${firstName} ${lastName}`; + } else { + payload.name = firstName || lastName; } + } + + payload.custom_attributes = getCustomAttributes(payload, ReservedUserAttributes); - return finalPayload; + let endpoint; + let requestMethod; + const contactId = await fetchContactId(message, destination); + + const baseEndPoint = getBaseEndpoint(destination); + if (contactId) { + requestMethod = 'PUT'; + endpoint = `${baseEndPoint}/${ConfigCategory.IDENTIFY.endpoint}/${contactId}`; + } else { + requestMethod = 'POST'; + endpoint = `${baseEndPoint}/${ConfigCategory.IDENTIFY.endpoint}`; } - throw new InstrumentationError('Either of `email` or `userId` is required for Identify call'); -} -function validateTrack(payload) { - if (!payload.user_id && !payload.email) { - throw new InstrumentationError('Either of `email` or `userId` is required for Track call'); + const { sendAnonymousId } = destination.Config; + + if (!payload.external_id && sendAnonymousId && message.anonymousId) { + payload.external_id = message.anonymousId; } + + return responseBuilder(payload, endpoint, requestMethod, destination); +}; + +const trackResponseBuilder = (message, destination) => { + let payload = getPayload(message, ConfigCategory.TRACK); + validateTrack(payload); + // pass only string, number, boolean properties if (payload.metadata) { // reserved metadata contains JSON objects that does not requires flattening const { reservedMetadata, restMetadata } = separateReservedAndRestMetadata(payload.metadata); - return { ...payload, metadata: { ...reservedMetadata, ...flattenJson(restMetadata) } }; + payload = { ...payload, metadata: { ...reservedMetadata, ...flattenJson(restMetadata) } }; } - return payload; -} - -const checkIfEmailOrUserIdPresent = (message, Config) => { - let user_id = message.userId; - if (Config.sendAnonymousId && !user_id) { - user_id = message.anonymousId; - } - return !!(user_id || message.context?.traits?.email); -}; + const { endpoint } = ConfigCategory.TRACK; -function attachUserAndCompany(message, Config) { - const email = message.context?.traits?.email; - const { userId, anonymousId } = message; - const requestBody = {}; - if (userId) { - requestBody.user_id = userId; - } - if (Config.sendAnonymousId && !userId) { - requestBody.user_id = anonymousId; - } - if (email) { - requestBody.email = email; - } - const companyObj = { - company_id: message.groupId, - }; - if (message.traits?.name) { - companyObj.name = message.traits.name; - } - requestBody.companies = [companyObj]; - const response = defaultRequestConfig(); - response.method = defaultPostRequestConfig.requestMethod; - response.endpoint = ConfigCategory.IDENTIFY.endpoint; - response.headers = { - 'Content-Type': JSON_MIME_TYPE, - Authorization: `Bearer ${Config.apiKey}`, - Accept: JSON_MIME_TYPE, - 'Intercom-Version': '1.4', - }; - response.body.JSON = requestBody; - return response; -} - -function buildCustomAttributes(message, payload) { - const finalPayload = payload; - const { traits } = message; - const customAttributes = {}; - const companyReservedKeys = [ - 'remoteCreatedAt', - 'monthlySpend', - 'industry', - 'website', - 'size', - 'plan', - 'name', - ]; - - if (traits) { - Object.keys(traits).forEach((key) => { - if (!companyReservedKeys.includes(key) && key !== 'userId') { - customAttributes[key] = traits[key]; - } - }); + const { sendAnonymousId } = destination.Config; + if (!payload.user_id && sendAnonymousId && message.anonymousId) { + payload.user_id = message.anonymousId; } - if (Object.keys(customAttributes).length > 0) { - finalPayload.custom_attributes = flattenJson(customAttributes); - } + return responseBuilder(payload, endpoint, 'POST', destination); +}; - return finalPayload; -} +const groupResponseBuilder = async (message, destination) => { + const payload = getPayload(message, ConfigCategory.GROUP); + payload.custom_attributes = getCustomAttributes(payload, ReservedCompanyAttributes); + const companyId = await createOrUpdateCompany(payload, destination); + const contactId = await fetchContactId(message, destination); -function validateAndBuildResponse(message, payload, category, destination) { - const respList = []; - const response = defaultRequestConfig(); - response.method = defaultPostRequestConfig.requestMethod; - response.endpoint = category.endpoint; - response.headers = { - 'Content-Type': JSON_MIME_TYPE, - Authorization: `Bearer ${destination.Config.apiKey}`, - Accept: JSON_MIME_TYPE, - 'Intercom-Version': '1.4', - }; - response.userId = message.anonymousId; - const messageType = message.type.toLowerCase(); - switch (messageType) { - case EventType.IDENTIFY: - response.body.JSON = removeUndefinedAndNullValues( - validateIdentify(message, payload, destination.Config), - ); - break; - case EventType.TRACK: - response.body.JSON = removeUndefinedAndNullValues(validateTrack(payload)); - break; - case EventType.GROUP: { - response.body.JSON = removeUndefinedAndNullValues(buildCustomAttributes(message, payload)); - respList.push(response); - if (checkIfEmailOrUserIdPresent(message, destination.Config)) { - const attachUserAndCompanyResponse = attachUserAndCompany(message, destination.Config); - attachUserAndCompanyResponse.userId = message.anonymousId; - respList.push(attachUserAndCompanyResponse); - } - break; - } - default: - throw new InstrumentationError(`Message type ${messageType} not supported`); + if (isDefinedAndNotNull(companyId) && isDefinedAndNotNull(contactId)) { + let { endpoint } = ConfigCategory.GROUP; + endpoint = endpoint.replace('{id}', contactId); + return responseBuilder({ id: companyId }, endpoint, 'POST', destination); } + throw new InstrumentationError("Can't attach user with company"); +}; - return messageType === EventType.GROUP ? respList : response; -} - -function processSingleMessage(message, destination) { +const processSingleMessage = async (message, destination) => { if (!message.type) { - throw new InstrumentationError('Message Type is not present. Aborting message.'); + throw new InstrumentationError('Message Type is not present. Aborting message'); } - const { sendAnonymousId } = destination.Config; - const messageType = message.type.toLowerCase(); - let category; + const messageType = message.type.toLowerCase(); + let response; switch (messageType) { case EventType.IDENTIFY: - category = ConfigCategory.IDENTIFY; + response = await identifyResponseBuilder(message, destination); break; case EventType.TRACK: - category = ConfigCategory.TRACK; + response = trackResponseBuilder(message, destination); break; case EventType.GROUP: - category = ConfigCategory.GROUP; + response = await groupResponseBuilder(message, destination); break; default: throw new InstrumentationError(`Message type ${messageType} not supported`); } - - // build the response and return - let payload; - if (get(message, MappedToDestinationKey)) { - addExternalIdToTraits(message); - payload = getFieldValueFromMessage(message, 'traits'); - } else { - payload = constructPayload(message, MappingConfig[category.name]); - } - if (category !== ConfigCategory.GROUP && sendAnonymousId && !payload.user_id) { - payload.user_id = message.anonymousId; - } - return validateAndBuildResponse(message, payload, category, destination); -} - -function process(event) { - const response = processSingleMessage(event.message, event.destination); return response; -} +}; -const processRouterDest = async (inputs, reqMetadata) => { - const respList = await simpleProcessRouterDest(inputs, process, reqMetadata); - return respList; +const process = async (event) => { + const response = await processSingleMessage(event.message, event.destination); + return response; }; -module.exports = { process, processRouterDest }; +module.exports = { process }; diff --git a/src/v0/destinations/intercom/util.js b/src/v0/destinations/intercom/util.js index 24a2934f7e0..4d70360a6c8 100644 --- a/src/v0/destinations/intercom/util.js +++ b/src/v0/destinations/intercom/util.js @@ -1,12 +1,199 @@ +const get = require('get-value'); +const { + BASE_ENDPOINT, + MappingConfig, + BASE_EU_ENDPOINT, + BASE_AU_ENDPOINT, + SEARCH_CONTACT_ENDPOINT, + CREATE_OR_UPDATE_COMPANY_ENDPOINT, +} = require('./config'); +const { + flattenJson, + constructPayload, + getIntegrationsObj, + isDefinedAndNotNull, + isHttpStatusSuccess, + addExternalIdToTraits, + getFieldValueFromMessage, + removeUndefinedAndNullValues, +} = require('../../util'); +const { + processAxiosResponse, + getDynamicErrorType, +} = require('../../../adapters/utils/networkUtils'); +const tags = require('../../util/tags'); const { MetadataTypes } = require('./config'); +const { JSON_MIME_TYPE } = require('../../util/constant'); +const { httpPOST } = require('../../../adapters/network'); +const { MappedToDestinationKey } = require('../../../constants'); +const { InstrumentationError, NetworkError } = require('../../util/errorTypes'); + +/** + * Validated identify call payload + * @param {*} payload + */ +const validateIdentify = (payload) => { + if (!payload.email && !payload.external_id) { + throw new InstrumentationError('Either of `email` or `userId` is required for Identify call'); + } +}; + +/** + * Validates track call payload + * @param {*} payload + */ +const validateTrack = (payload) => { + if (!payload.user_id && !payload.email) { + throw new InstrumentationError('Either of `email` or `userId` is required for Track call'); + } +}; + +/** + * Returns headers + * @param {*} destination + * @returns + */ +const getHeaders = (destination) => ({ + 'Content-Type': JSON_MIME_TYPE, + Authorization: `Bearer ${destination.Config.apiKey}`, + Accept: JSON_MIME_TYPE, + 'Intercom-Version': '2.9', +}); + +/** + * Returns base endpoint + * @param {*} destination + * @returns + */ +const getBaseEndpoint = (destination) => { + const { apiServer } = destination.Config; + if (apiServer === 'eu') { + return BASE_EU_ENDPOINT; + } + if (apiServer === 'au') { + return BASE_AU_ENDPOINT; + } + return BASE_ENDPOINT; +}; + +/** + * Returns custom attributes for identify and group calls + * @param {*} payload + * @param {*} ReservedAttributes + * @returns + */ +const getCustomAttributes = (payload, ReservedAttributes) => { + let customAttributes = payload.custom_attributes || {}; + if (customAttributes) { + ReservedAttributes.forEach((trait) => { + delete customAttributes[trait]; + }); + } + if (isDefinedAndNotNull(customAttributes) && Object.keys(customAttributes).length > 0) { + customAttributes = flattenJson(customAttributes); + } + return customAttributes; +}; + +/** + * Returns transformed payload + * @param {*} message + * @param {*} category + * @returns + */ +const getPayload = (message, category) => { + let payload; + if (get(message, MappedToDestinationKey)) { + addExternalIdToTraits(message); + payload = getFieldValueFromMessage(message, 'traits'); + } else { + payload = constructPayload(message, MappingConfig[category.name]); + } + return payload; +}; + +/** + * Returns contact id based on lookup + * @param {*} message + * @param {*} destination + * @returns + */ +const fetchContactId = async (message, destination) => { + const integrationsObj = getIntegrationsObj(message, 'INTERCOM'); + let lookupField = 'email'; + if (integrationsObj && integrationsObj.lookup && isDefinedAndNotNull(integrationsObj.lookup)) { + lookupField = integrationsObj.lookup; + } + const lookupFieldValue = getFieldValueFromMessage(message, lookupField); + const data = JSON.stringify({ + query: { + operator: 'AND', + value: [ + { + field: lookupField, + operator: '=', + value: lookupFieldValue, + }, + ], + }, + }); + + const headers = getHeaders(destination); + const baseEndPoint = getBaseEndpoint(destination); + const endpoint = `${baseEndPoint}/${SEARCH_CONTACT_ENDPOINT}` + const response = await httpPOST(endpoint, data, { + headers, + destType: 'intercom', + feature: 'transformation', + }); + + const processedUserResponse = processAxiosResponse(response); + if (isHttpStatusSuccess(processedUserResponse.status)) { + return processedUserResponse.response?.data[0]?.id; + } + + return null; +}; + +/** + * Function to create or update company + * @param {*} payload + * @param {*} destination + * @returns + */ +const createOrUpdateCompany = async (payload, destination) => { + const headers = getHeaders(destination); + const finalPayload = removeUndefinedAndNullValues(payload); + + const baseEndPoint = getBaseEndpoint(destination); + const endpoint = `${baseEndPoint}/${CREATE_OR_UPDATE_COMPANY_ENDPOINT}` + const response = await httpPOST(endpoint, finalPayload, { + headers, + destType: 'intercom', + feature: 'transformation', + }); + + const processedResponse = processAxiosResponse(response); + if (isHttpStatusSuccess(processedResponse.status)) { + return processedResponse.response?.id; + } + + throw new NetworkError( + `Unable to Create Company : ${processedResponse?.response?.errors[0]}`, + processedResponse?.status, + { + [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(processedResponse?.status), + }, + processedResponse, + ); +}; /** * Separates reserved metadata from rest of the metadata based on the metadata types - * ref:- https://developers.intercom.com/intercom-api-reference/v1.4/reference/event-metadata-types * @param {*} metadata * @returns */ -function separateReservedAndRestMetadata(metadata) { +const separateReservedAndRestMetadata = (metadata) => { const reservedMetadata = {}; const restMetadata = {}; if (metadata) { @@ -27,6 +214,16 @@ function separateReservedAndRestMetadata(metadata) { // Return the separated metadata objects return { reservedMetadata, restMetadata }; -} +}; -module.exports = { separateReservedAndRestMetadata }; +module.exports = { + getHeaders, + getPayload, + validateTrack, + fetchContactId, + getBaseEndpoint, + validateIdentify, + getCustomAttributes, + createOrUpdateCompany, + separateReservedAndRestMetadata, +}; diff --git a/test/__mocks__/axios.js b/test/__mocks__/axios.js index dc3005d9093..4d3a01880ee 100644 --- a/test/__mocks__/axios.js +++ b/test/__mocks__/axios.js @@ -40,6 +40,7 @@ const { sendgridGetRequestHandler } = require("./sendgrid.mock"); const { sendinblueGetRequestHandler } = require("./sendinblue.mock"); const { courierGetRequestHandler } = require("./courier.mock"); const { brazePostRequestHandler } = require("./braze.mock"); +const { intercomPostRequestHandler } = require("./intercom.mock"); const {optimizelyFullStackGetRequestHandler} = require("./optimizely_fullstack.mock"); const urlDirectoryMap = { @@ -250,6 +251,11 @@ function post(url, payload) { if (url.includes("https://api.custify.com")) { return Promise.resolve(custifyPostRequestHandler(url)); } + if (url.includes("intercom.io")) { + return new Promise((resolve, reject) => { + resolve(intercomPostRequestHandler(url, payload)); + }); + } return new Promise((resolve, reject) => { if (mockData) { resolve({ data: mockData, status: 200 }); diff --git a/test/__mocks__/data/intercom/response.json b/test/__mocks__/data/intercom/response.json new file mode 100644 index 00000000000..996959ff89e --- /dev/null +++ b/test/__mocks__/data/intercom/response.json @@ -0,0 +1,36 @@ +{ + "https://api.intercom.io/contacts/search": { + "type": "list", + "data": [ + { + "type": "contact", + "id": "contact_id_1", + "workspace_id": "work@1", + "external_id": "user@1", + "role": "user", + "email": "test@rudderlabs.com", + "name": "joe bloggs", + "social_profiles": { + "type": "list", + "data": [] + }, + "has_hard_bounced": false, + "marked_email_as_spam": false, + "unsubscribed_from_emails": false, + "created_at": 1694510222, + "updated_at": 1694678038, + "location": { + "type": "location", + "country": "IND" + } + } + ], + "total_count": 1, + "pages": { + "type": "pages", + "page": 1, + "per_page": 50, + "total_pages": 1 + } + } +} \ No newline at end of file diff --git a/test/__mocks__/intercom.mock.js b/test/__mocks__/intercom.mock.js new file mode 100644 index 00000000000..68f533d7712 --- /dev/null +++ b/test/__mocks__/intercom.mock.js @@ -0,0 +1,29 @@ +const fs = require("fs"); +const path = require("path"); +const getData = url => { + const dataFile = fs.readFileSync( + path.resolve(__dirname, "./data/intercom/response.json") + ); + const data = JSON.parse(dataFile); + const response = data[url]; + return response || {}; +}; + +const intercomPostRequestHandler = (url, payload) => { + const mockData = getData(url); + switch (url) { + case "https://api.intercom.io/contacts/search": + //resolve with status 201 and response data contains value for contact created + return { data: mockData, status: 200 }; + default: + return new Promise((resolve, reject) => { + if (mockData) { + resolve({ data: mockData, status: 200 }); + } else { + resolve({ error: "Request failed" }); + } + }); + } +}; + +module.exports = { intercomPostRequestHandler }; \ No newline at end of file From fb11bcae18332ac27a19c34294cc76621fcc6a90 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Thu, 14 Sep 2023 17:12:31 +0530 Subject: [PATCH 02/14] feat(intercom): additional improvements --- src/v0/destinations/intercom/config.js | 5 +- src/v0/destinations/intercom/transform.js | 11 +- src/v0/destinations/intercom/util.js | 8 +- test/__tests__/data/intercom.json | 1 + test/__tests__/data/intercom_input.json | 1232 ----------------- test/__tests__/data/intercom_output.json | 651 --------- .../__tests__/data/intercom_router_input.json | 150 -- .../data/intercom_router_output.json | 99 -- test/__tests__/intercom.test.js | 38 +- 9 files changed, 24 insertions(+), 2171 deletions(-) create mode 100644 test/__tests__/data/intercom.json delete mode 100644 test/__tests__/data/intercom_input.json delete mode 100644 test/__tests__/data/intercom_output.json delete mode 100644 test/__tests__/data/intercom_router_input.json delete mode 100644 test/__tests__/data/intercom_router_output.json diff --git a/src/v0/destinations/intercom/config.js b/src/v0/destinations/intercom/config.js index 930e7764cd3..48f439cb818 100644 --- a/src/v0/destinations/intercom/config.js +++ b/src/v0/destinations/intercom/config.js @@ -37,8 +37,9 @@ const ReservedUserAttributes = [ 'lastname', 'firstName', 'firstname', - 'last_seen_at', - 'signed_up_at', + 'createdAt', + 'timestamp', + 'originalTimestamp', 'unsubscribedFromEmails' ]; diff --git a/src/v0/destinations/intercom/transform.js b/src/v0/destinations/intercom/transform.js index c98cad508b9..c175498e33e 100644 --- a/src/v0/destinations/intercom/transform.js +++ b/src/v0/destinations/intercom/transform.js @@ -14,8 +14,8 @@ const { fetchContactId, getBaseEndpoint, validateIdentify, - getCustomAttributes, createOrUpdateCompany, + filterCustomAttributes, separateReservedAndRestMetadata, } = require('./util'); const { InstrumentationError } = require('../../util/errorTypes'); @@ -43,7 +43,7 @@ const identifyResponseBuilder = async (message, destination) => { } } - payload.custom_attributes = getCustomAttributes(payload, ReservedUserAttributes); + payload.custom_attributes = filterCustomAttributes(payload, ReservedUserAttributes); let endpoint; let requestMethod; @@ -75,7 +75,7 @@ const trackResponseBuilder = (message, destination) => { if (payload.metadata) { // reserved metadata contains JSON objects that does not requires flattening const { reservedMetadata, restMetadata } = separateReservedAndRestMetadata(payload.metadata); - payload = { ...payload, metadata: { ...reservedMetadata, ...flattenJson(restMetadata) } }; + payload = { ...payload, metadata: { ...reservedMetadata, ...flattenJson(restMetadata) } }; } const { endpoint } = ConfigCategory.TRACK; @@ -90,7 +90,7 @@ const trackResponseBuilder = (message, destination) => { const groupResponseBuilder = async (message, destination) => { const payload = getPayload(message, ConfigCategory.GROUP); - payload.custom_attributes = getCustomAttributes(payload, ReservedCompanyAttributes); + payload.custom_attributes = filterCustomAttributes(payload, ReservedCompanyAttributes); const companyId = await createOrUpdateCompany(payload, destination); const contactId = await fetchContactId(message, destination); @@ -126,7 +126,8 @@ const processSingleMessage = async (message, destination) => { }; const process = async (event) => { - const response = await processSingleMessage(event.message, event.destination); + const { message, destination } = event; + const response = await processSingleMessage(message, destination); return response; }; diff --git a/src/v0/destinations/intercom/util.js b/src/v0/destinations/intercom/util.js index 4d70360a6c8..fbc56803bec 100644 --- a/src/v0/destinations/intercom/util.js +++ b/src/v0/destinations/intercom/util.js @@ -1,4 +1,5 @@ const get = require('get-value'); +const cloneDeep = require('lodash/cloneDeep'); const { BASE_ENDPOINT, MappingConfig, @@ -82,8 +83,9 @@ const getBaseEndpoint = (destination) => { * @param {*} ReservedAttributes * @returns */ -const getCustomAttributes = (payload, ReservedAttributes) => { - let customAttributes = payload.custom_attributes || {}; +const filterCustomAttributes = (payload, ReservedAttributes) => { + const { custom_attributes } = payload; + let customAttributes = cloneDeep(custom_attributes) || {}; if (customAttributes) { ReservedAttributes.forEach((trait) => { delete customAttributes[trait]; @@ -223,7 +225,7 @@ module.exports = { fetchContactId, getBaseEndpoint, validateIdentify, - getCustomAttributes, createOrUpdateCompany, + filterCustomAttributes, separateReservedAndRestMetadata, }; diff --git a/test/__tests__/data/intercom.json b/test/__tests__/data/intercom.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/__tests__/data/intercom.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/__tests__/data/intercom_input.json b/test/__tests__/data/intercom_input.json deleted file mode 100644 index 2d19ae57171..00000000000 --- a/test/__tests__/data/intercom_input.json +++ /dev/null @@ -1,1232 +0,0 @@ -[ - { - "message": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "channel": "mobile", - "context": { - "app": { - "build": "1.0", - "name": "Test_Example", - "namespace": "com.example.testapp", - "version": "1.0" - }, - "device": { - "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "manufacturer": "Apple", - "model": "iPhone", - "name": "iPod touch (7th generation)", - "type": "iOS" - }, - "library": { - "name": "test-ios-library", - "version": "1.0.7" - }, - "locale": "en-US", - "network": { - "bluetooth": false, - "carrier": "unavailable", - "cellular": false, - "wifi": true - }, - "os": { - "name": "iOS", - "version": "14.0" - }, - "screen": { - "density": 2, - "height": 320, - "width": 568 - }, - "timezone": "Asia/Kolkata", - "traits": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "name": "Test Name", - "firstName": "Test", - "lastName": "Name", - "createdAt": "2020-09-30T19:11:00.337Z", - "userId": "test_user_id_1", - "email": "test_1@test.com", - "phone": "9876543210", - "key1": "value1", - "address": { - "city": "Kolkata", - "state": "West Bengal" - }, - "originalArray": [ - { - "nested_field": "nested value", - "tags": ["tag_1", "tag_2", "tag_3"] - }, - { - "nested_field": "nested value", - "tags": ["tag_1"] - }, - { - "nested_field": "nested value" - } - ] - }, - "userAgent": "unknown" - }, - "event": "Test Event 2", - "integrations": { - "All": true - }, - "messageId": "1601493060-39010c49-e6e4-4626-a75c-0dbf1925c9e8", - "originalTimestamp": "2020-09-30T19:11:00.337Z", - "receivedAt": "2020-10-01T00:41:11.369+05:30", - "request_ip": "2405:201:8005:9856:7911:25e7:5603:5e18", - "sentAt": "2020-09-30T19:11:10.382Z", - "timestamp": "2020-10-01T00:41:01.324+05:30", - "type": "identify" - }, - "destination": { - "Config": { - "apiKey": "intercomApiKey", - "appId": "9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0", - "collectContext": false - } - } - }, - { - "message": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "channel": "mobile", - "context": { - "app": { - "build": "1.0", - "name": "Test_Example", - "namespace": "com.example.testapp", - "version": "1.0" - }, - "device": { - "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "manufacturer": "Apple", - "model": "iPhone", - "name": "iPod touch (7th generation)", - "type": "iOS" - }, - "library": { - "name": "test-ios-library", - "version": "1.0.7" - }, - "locale": "en-US", - "network": { - "bluetooth": false, - "carrier": "unavailable", - "cellular": false, - "wifi": true - }, - "os": { - "name": "iOS", - "version": "14.0" - }, - "screen": { - "density": 2, - "height": 320, - "width": 568 - }, - "timezone": "Asia/Kolkata", - "traits": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "firstName": "Test", - "lastName": "Name", - "createdAt": "2020-09-30T19:11:00.337Z", - "email": "test_1@test.com", - "phone": "9876543210", - "key1": "value1" - }, - "userAgent": "unknown" - }, - "event": "Test Event 2", - "integrations": { - "All": true - }, - "messageId": "1601493060-39010c49-e6e4-4626-a75c-0dbf1925c9e8", - "originalTimestamp": "2020-09-30T19:11:00.337Z", - "receivedAt": "2020-10-01T00:41:11.369+05:30", - "request_ip": "2405:201:8005:9856:7911:25e7:5603:5e18", - "sentAt": "2020-09-30T19:11:10.382Z", - "timestamp": "2020-10-01T00:41:01.324+05:30", - "type": "identify" - }, - "destination": { - "Config": { - "apiKey": "intercomApiKey", - "appId": "9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0", - "collectContext": false - } - } - }, - { - "message": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "channel": "mobile", - "context": { - "app": { - "build": "1.0", - "name": "Test_Example", - "namespace": "com.example.testapp", - "version": "1.0" - }, - "device": { - "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "manufacturer": "Apple", - "model": "iPhone", - "name": "iPod touch (7th generation)", - "type": "iOS" - }, - "library": { - "name": "test-ios-library", - "version": "1.0.7" - }, - "locale": "en-US", - "network": { - "bluetooth": false, - "carrier": "unavailable", - "cellular": false, - "wifi": true - }, - "os": { - "name": "iOS", - "version": "14.0" - }, - "screen": { - "density": 2, - "height": 320, - "width": 568 - }, - "timezone": "Asia/Kolkata", - "traits": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "lastName": "Name", - "createdAt": "2020-09-30T19:11:00.337Z", - "email": "test_1@test.com", - "phone": "9876543210", - "key1": "value1" - }, - "userAgent": "unknown" - }, - "event": "Test Event 2", - "integrations": { - "All": true - }, - "messageId": "1601493060-39010c49-e6e4-4626-a75c-0dbf1925c9e8", - "originalTimestamp": "2020-09-30T19:11:00.337Z", - "receivedAt": "2020-10-01T00:41:11.369+05:30", - "request_ip": "2405:201:8005:9856:7911:25e7:5603:5e18", - "sentAt": "2020-09-30T19:11:10.382Z", - "timestamp": "2020-10-01T00:41:01.324+05:30", - "type": "identify" - }, - "destination": { - "Config": { - "apiKey": "intercomApiKey", - "appId": "9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0", - "collectContext": false - } - } - }, - { - "message": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "channel": "mobile", - "context": { - "app": { - "build": "1.0", - "name": "Test_Example", - "namespace": "com.example.testapp", - "version": "1.0" - }, - "device": { - "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "manufacturer": "Apple", - "model": "iPhone", - "name": "iPod touch (7th generation)", - "type": "iOS" - }, - "library": { - "name": "test-ios-library", - "version": "1.0.7" - }, - "locale": "en-US", - "network": { - "bluetooth": false, - "carrier": "unavailable", - "cellular": false, - "wifi": true - }, - "os": { - "name": "iOS", - "version": "14.0" - }, - "screen": { - "density": 2, - "height": 320, - "width": 568 - }, - "timezone": "Asia/Kolkata", - "traits": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "firstName": "Name", - "createdAt": "2020-09-30T19:11:00.337Z", - "email": "test_1@test.com", - "phone": "9876543210", - "key1": "value1" - }, - "userAgent": "unknown" - }, - "event": "Test Event 2", - "integrations": { - "All": true - }, - "messageId": "1601493060-39010c49-e6e4-4626-a75c-0dbf1925c9e8", - "originalTimestamp": "2020-09-30T19:11:00.337Z", - "receivedAt": "2020-10-01T00:41:11.369+05:30", - "request_ip": "2405:201:8005:9856:7911:25e7:5603:5e18", - "sentAt": "2020-09-30T19:11:10.382Z", - "timestamp": "2020-10-01T00:41:01.324+05:30", - "type": "identify" - }, - "destination": { - "Config": { - "apiKey": "intercomApiKey", - "appId": "9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0", - "collectContext": false - } - } - }, - { - "message": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "channel": "mobile", - "context": { - "app": { - "build": "1.0", - "name": "Test_Example", - "namespace": "com.example.testapp", - "version": "1.0" - }, - "device": { - "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "manufacturer": "Apple", - "model": "iPhone", - "name": "iPod touch (7th generation)", - "type": "iOS" - }, - "library": { - "name": "test-ios-library", - "version": "1.0.7" - }, - "locale": "en-US", - "network": { - "bluetooth": false, - "carrier": "unavailable", - "cellular": false, - "wifi": true - }, - "os": { - "name": "iOS", - "version": "14.0" - }, - "screen": { - "density": 2, - "height": 320, - "width": 568 - }, - "timezone": "Asia/Kolkata", - "traits": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "firstName": "Name", - "createdAt": "2020-09-30T19:11:00.337Z", - "phone": "9876543210", - "key1": "value1" - }, - "userAgent": "unknown" - }, - "event": "Test Event 2", - "integrations": { - "All": true - }, - "messageId": "1601493060-39010c49-e6e4-4626-a75c-0dbf1925c9e8", - "originalTimestamp": "2020-09-30T19:11:00.337Z", - "receivedAt": "2020-10-01T00:41:11.369+05:30", - "request_ip": "2405:201:8005:9856:7911:25e7:5603:5e18", - "sentAt": "2020-09-30T19:11:10.382Z", - "timestamp": "2020-10-01T00:41:01.324+05:30", - "type": "identify" - }, - "destination": { - "Config": { - "apiKey": "intercomApiKey", - "appId": "9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0", - "collectContext": false - } - } - }, - { - "message": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "channel": "mobile", - "context": { - "app": { - "build": "1.0", - "name": "Test_Example", - "namespace": "com.example.testapp", - "version": "1.0" - }, - "device": { - "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "manufacturer": "Apple", - "model": "iPhone", - "name": "iPod touch (7th generation)", - "type": "iOS" - }, - "library": { - "name": "test-ios-library", - "version": "1.0.7" - }, - "locale": "en-US", - "network": { - "bluetooth": false, - "carrier": "unavailable", - "cellular": false, - "wifi": true - }, - "os": { - "name": "iOS", - "version": "14.0" - }, - "screen": { - "density": 2, - "height": 320, - "width": 568 - }, - "timezone": "Asia/Kolkata", - "traits": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "lastName": "Name", - "createdAt": "2020-09-30T19:11:00.337Z", - "email": "test_1@test.com", - "phone": "9876543210", - "key1": "value1", - "company": { - "name": "Test Comp", - "id": "company_id", - "industry": "test industry", - "key1": "value1", - "key2": { - "a": "a" - }, - "key3": [1, 2, 3] - } - }, - "userAgent": "unknown" - }, - "event": "Test Event 2", - "integrations": { - "All": true - }, - "messageId": "1601493060-39010c49-e6e4-4626-a75c-0dbf1925c9e8", - "originalTimestamp": "2020-09-30T19:11:00.337Z", - "receivedAt": "2020-10-01T00:41:11.369+05:30", - "request_ip": "2405:201:8005:9856:7911:25e7:5603:5e18", - "sentAt": "2020-09-30T19:11:10.382Z", - "timestamp": "2020-10-01T00:41:01.324+05:30", - "type": "identify" - }, - "destination": { - "Config": { - "apiKey": "intercomApiKey", - "appId": "9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0", - "collectContext": false - } - } - }, - { - "message": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "channel": "mobile", - "context": { - "app": { - "build": "1.0", - "name": "Test_Example", - "namespace": "com.example.testapp", - "version": "1.0" - }, - "device": { - "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "manufacturer": "Apple", - "model": "iPhone", - "name": "iPod touch (7th generation)", - "type": "iOS" - }, - "library": { - "name": "test-ios-library", - "version": "1.0.7" - }, - "locale": "en-US", - "network": { - "bluetooth": false, - "carrier": "unavailable", - "cellular": false, - "wifi": true - }, - "os": { - "name": "iOS", - "version": "14.0" - }, - "screen": { - "density": 2, - "height": 320, - "width": 568 - }, - "timezone": "Asia/Kolkata", - "traits": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "lastName": "Name", - "createdAt": "2020-09-30T19:11:00.337Z", - "email": "test_1@test.com", - "phone": "9876543210", - "key1": "value1", - "company": { - "name": "Test Comp", - "industry": "test industry", - "key1": "value1", - "key2": null, - "key3": ["value1", "value2"], - "key4": { - "foo": "bar" - } - } - }, - "userAgent": "unknown" - }, - "event": "Test Event 2", - "integrations": { - "All": true - }, - "messageId": "1601493060-39010c49-e6e4-4626-a75c-0dbf1925c9e8", - "originalTimestamp": "2020-09-30T19:11:00.337Z", - "receivedAt": "2020-10-01T00:41:11.369+05:30", - "request_ip": "2405:201:8005:9856:7911:25e7:5603:5e18", - "sentAt": "2020-09-30T19:11:10.382Z", - "timestamp": "2020-10-01T00:41:01.324+05:30", - "type": "identify" - }, - "destination": { - "Config": { - "apiKey": "intercomApiKey", - "appId": "9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0", - "collectContext": false, - "updateLastRequestAt": false - } - } - }, - { - "message": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "channel": "mobile", - "context": { - "app": { - "build": "1.0", - "name": "Test_Example", - "namespace": "com.example.testapp", - "version": "1.0" - }, - "device": { - "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "manufacturer": "Apple", - "model": "iPhone", - "name": "iPod touch (7th generation)", - "type": "iOS" - }, - "library": { - "name": "test-ios-library", - "version": "1.0.7" - }, - "locale": "en-US", - "network": { - "bluetooth": false, - "carrier": "unavailable", - "cellular": false, - "wifi": true - }, - "os": { - "name": "iOS", - "version": "14.0" - }, - "screen": { - "density": 2, - "height": 320, - "width": 568 - }, - "timezone": "Asia/Kolkata", - "traits": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "lastName": "Name", - "createdAt": "2020-09-30T19:11:00.337Z", - "email": "test_1@test.com", - "phone": "9876543210", - "key1": "value1", - "company": { - "industry": "test industry", - "key1": "value1", - "key2": null - } - }, - "userAgent": "unknown" - }, - "event": "Test Event 2", - "integrations": { - "All": true - }, - "messageId": "1601493060-39010c49-e6e4-4626-a75c-0dbf1925c9e8", - "originalTimestamp": "2020-09-30T19:11:00.337Z", - "receivedAt": "2020-10-01T00:41:11.369+05:30", - "request_ip": "2405:201:8005:9856:7911:25e7:5603:5e18", - "sentAt": "2020-09-30T19:11:10.382Z", - "timestamp": "2020-10-01T00:41:01.324+05:30", - "type": "identify" - }, - "destination": { - "Config": { - "apiKey": "intercomApiKey", - "appId": "9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0", - "collectContext": false - } - } - }, - { - "message": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "channel": "mobile", - "context": { - "app": { - "build": "1.0", - "name": "Test_Example", - "namespace": "com.example.testapp", - "version": "1.0" - }, - "device": { - "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "manufacturer": "Apple", - "model": "iPhone", - "name": "iPod touch (7th generation)", - "type": "iOS" - }, - "library": { - "name": "test-ios-library", - "version": "1.0.7" - }, - "locale": "en-US", - "network": { - "bluetooth": false, - "carrier": "unavailable", - "cellular": false, - "wifi": true - }, - "os": { - "name": "iOS", - "version": "14.0" - }, - "screen": { - "density": 2, - "height": 320, - "width": 568 - }, - "timezone": "Asia/Kolkata", - "traits": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "name": "Test Name", - "firstName": "Test", - "lastName": "Name", - "createdAt": "2020-09-30T19:11:00.337Z", - "userId": "test_user_id_1", - "email": "test_1@test.com", - "phone": "9876543210", - "key1": "value1" - }, - "userAgent": "unknown" - }, - "properties": { - "property1": 1, - "property2": "test", - "property3": true, - "property4": "2020-10-05T09:09:03.731Z", - "property5": { - "property1": 1, - "property2": "test", - "property3": { - "subProp1": { - "a": "a", - "b": "b" - }, - "subProp2": ["a", "b"] - } - }, - "properties6": null, - "revenue": { - "amount": 1232, - "currency": "inr", - "test": 123 - }, - "price": { - "amount": 3000, - "currency": "USD" - }, - "article": { - "url": "https://example.org/ab1de.html", - "value": "the dude abides" - } - }, - "event": "Test Event 2", - "integrations": { - "All": true - }, - "messageId": "1601493060-39010c49-e6e4-4626-a75c-0dbf1925c9e8", - "originalTimestamp": "2020-09-30T19:11:00.337Z", - "receivedAt": "2020-10-01T00:41:11.369+05:30", - "request_ip": "2405:201:8005:9856:7911:25e7:5603:5e18", - "sentAt": "2020-09-30T19:11:10.382Z", - "timestamp": "2020-10-01T00:41:01.324+05:30", - "type": "track" - }, - "destination": { - "Config": { - "apiKey": "intercomApiKey", - "appId": "9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0", - "collectContext": false - } - } - }, - { - "message": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "channel": "mobile", - "context": { - "app": { - "build": "1.0", - "name": "Test_Example", - "namespace": "com.example.testapp", - "version": "1.0" - }, - "device": { - "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "manufacturer": "Apple", - "model": "iPhone", - "name": "iPod touch (7th generation)", - "type": "iOS" - }, - "library": { - "name": "test-ios-library", - "version": "1.0.7" - }, - "locale": "en-US", - "network": { - "bluetooth": false, - "carrier": "unavailable", - "cellular": false, - "wifi": true - }, - "os": { - "name": "iOS", - "version": "14.0" - }, - "screen": { - "density": 2, - "height": 320, - "width": 568 - }, - "timezone": "Asia/Kolkata", - "traits": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "name": "Test Name", - "firstName": "Test", - "lastName": "Name", - "createdAt": "2020-09-30T19:11:00.337Z", - "email": "test_1@test.com", - "phone": "9876543210", - "key1": "value1" - }, - "userAgent": "unknown" - }, - "event": "Test Event 2", - "integrations": { - "All": true - }, - "messageId": "1601493060-39010c49-e6e4-4626-a75c-0dbf1925c9e8", - "originalTimestamp": "2020-09-30T19:11:00.337Z", - "receivedAt": "2020-10-01T00:41:11.369+05:30", - "request_ip": "2405:201:8005:9856:7911:25e7:5603:5e18", - "sentAt": "2020-09-30T19:11:10.382Z", - "timestamp": "2020-10-01T00:41:01.324+05:30", - "type": "track" - }, - "destination": { - "Config": { - "apiKey": "intercomApiKey", - "appId": "9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0", - "collectContext": false - } - } - }, - { - "message": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "channel": "mobile", - "context": { - "app": { - "build": "1.0", - "name": "Test_Example", - "namespace": "com.example.testapp", - "version": "1.0" - }, - "device": { - "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "manufacturer": "Apple", - "model": "iPhone", - "name": "iPod touch (7th generation)", - "type": "iOS" - }, - "library": { - "name": "test-ios-library", - "version": "1.0.7" - }, - "locale": "en-US", - "network": { - "bluetooth": false, - "carrier": "unavailable", - "cellular": false, - "wifi": true - }, - "os": { - "name": "iOS", - "version": "14.0" - }, - "screen": { - "density": 2, - "height": 320, - "width": 568 - }, - "timezone": "Asia/Kolkata", - "traits": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "name": "Test Name", - "firstName": "Test", - "lastName": "Name", - "createdAt": "2020-09-30T19:11:00.337Z", - "phone": "9876543210", - "key1": "value1" - }, - "userAgent": "unknown" - }, - "event": "Test Event 2", - "integrations": { - "All": true - }, - "messageId": "1601493060-39010c49-e6e4-4626-a75c-0dbf1925c9e8", - "originalTimestamp": "2020-09-30T19:11:00.337Z", - "receivedAt": "2020-10-01T00:41:11.369+05:30", - "request_ip": "2405:201:8005:9856:7911:25e7:5603:5e18", - "sentAt": "2020-09-30T19:11:10.382Z", - "timestamp": "2020-10-01T00:41:01.324+05:30", - "type": "track" - }, - "destination": { - "Config": { - "apiKey": "intercomApiKey", - "appId": "9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0", - "collectContext": false - } - } - }, - { - "message": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "channel": "mobile", - "context": { - "app": { - "build": "1.0", - "name": "Test_Example", - "namespace": "com.example.testapp", - "version": "1.0" - }, - "externalId": [ - { - "identifierType": "email", - "id": "test@gmail.com" - } - ], - "mappedToDestination": true, - "device": { - "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "manufacturer": "Apple", - "model": "iPhone", - "name": "iPod touch (7th generation)", - "type": "iOS" - }, - "library": { - "name": "test-ios-library", - "version": "1.0.7" - }, - "locale": "en-US", - "network": { - "bluetooth": false, - "carrier": "unavailable", - "cellular": false, - "wifi": true - }, - "os": { - "name": "iOS", - "version": "14.0" - }, - "screen": { - "density": 2, - "height": 320, - "width": 568 - }, - "timezone": "Asia/Kolkata", - "traits": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "name": "Test Name", - "firstName": "Test", - "lastName": "Name", - "createdAt": "2020-09-30T19:11:00.337Z", - "phone": "9876543210", - "key1": "value1" - }, - "userAgent": "unknown" - }, - "event": "Test Event 2", - "integrations": { - "All": true - }, - "messageId": "1601493060-39010c49-e6e4-4626-a75c-0dbf1925c9e8", - "originalTimestamp": "2020-09-30T19:11:00.337Z", - "receivedAt": "2020-10-01T00:41:11.369+05:30", - "request_ip": "2405:201:8005:9856:7911:25e7:5603:5e18", - "sentAt": "2020-09-30T19:11:10.382Z", - "timestamp": "2020-10-01T00:41:01.324+05:30", - "type": "identify" - }, - "destination": { - "Config": { - "apiKey": "intercomApiKey", - "appId": "9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0", - "collectContext": false - } - } - }, - { - "message": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "channel": "mobile", - "context": { - "app": { - "build": "1.0", - "name": "Test_Example", - "namespace": "com.example.testapp", - "version": "1.0" - }, - "device": { - "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "manufacturer": "Apple", - "model": "iPhone", - "name": "iPod touch (7th generation)", - "type": "iOS" - }, - "library": { - "name": "test-ios-library", - "version": "1.0.7" - }, - "locale": "en-US", - "network": { - "bluetooth": false, - "carrier": "unavailable", - "cellular": false, - "wifi": true - }, - "os": { - "name": "iOS", - "version": "14.0" - }, - "screen": { - "density": 2, - "height": 320, - "width": 568 - }, - "timezone": "Asia/Kolkata", - "traits": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "name": "Test Name", - "firstName": "Test", - "lastName": "Name", - "createdAt": "2020-09-30T19:11:00.337Z", - "phone": "9876543210", - "key1": "value1" - }, - "userAgent": "unknown" - }, - "event": "Test Event 2", - "integrations": { - "All": true - }, - "messageId": "1601493060-39010c49-e6e4-4626-a75c-0dbf1925c9e8", - "originalTimestamp": "2020-09-30T19:11:00.337Z", - "receivedAt": "2020-10-01T00:41:11.369+05:30", - "request_ip": "2405:201:8005:9856:7911:25e7:5603:5e18", - "sentAt": "2020-09-30T19:11:10.382Z", - "timestamp": "2020-10-01T00:41:01.324+05:30", - "type": "identify" - }, - "destination": { - "Config": { - "apiKey": "intercomApiKey", - "appId": "9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0", - "collectContext": false, - "sendAnonymousId": true - } - } - }, - { - "message": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "channel": "mobile", - "context": { - "app": { - "build": "1.0", - "name": "Test_Example", - "namespace": "com.example.testapp", - "version": "1.0" - }, - "device": { - "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "manufacturer": "Apple", - "model": "iPhone", - "name": "iPod touch (7th generation)", - "type": "iOS" - }, - "library": { - "name": "test-ios-library", - "version": "1.0.7" - }, - "locale": "en-US", - "network": { - "bluetooth": false, - "carrier": "unavailable", - "cellular": false, - "wifi": true - }, - "os": { - "name": "iOS", - "version": "14.0" - }, - "screen": { - "density": 2, - "height": 320, - "width": 568 - }, - "timezone": "Asia/Kolkata", - "traits": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "name": "Test Name", - "firstName": "Test", - "lastName": "Name", - "createdAt": "2020-09-30T19:11:00.337Z", - "phone": "9876543210", - "key1": "value1" - }, - "userAgent": "unknown" - }, - "event": "Test Event 2", - "integrations": { - "All": true - }, - "messageId": "1601493060-39010c49-e6e4-4626-a75c-0dbf1925c9e8", - "originalTimestamp": "2020-09-30T19:11:00.337Z", - "receivedAt": "2020-10-01T00:41:11.369+05:30", - "request_ip": "2405:201:8005:9856:7911:25e7:5603:5e18", - "sentAt": "2020-09-30T19:11:10.382Z", - "timestamp": "2020-10-01T00:41:01.324+05:30", - "type": "identify" - }, - "destination": { - "Config": { - "apiKey": "intercomApiKey", - "appId": "9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0", - "collectContext": false, - "sendAnonymousId": false - } - } - }, - { - "message": { - "groupId": "test_company_id_wdasda", - "traits": { - "employees": 450, - "plan": "basic", - "userId": "sdfrsdfsdfsf", - "email": "test@test.com", - "name": "rudderUpdate", - "size": "50", - "industry": "IT", - "monthlySpend": "2131231", - "remoteCreatedAt": "1683017572", - "key1": "val1" - }, - "anonymousId": "sdfrsdfsdfsf", - "integrations": { - "All": true - }, - "type": "group", - "userId": "sdfrsdfsdfsf" - }, - "destination": { - "Config": { - "apiKey": "abcd=", - "appId": "asdasdasd", - "collectContext": false - } - } - }, - { - "message": { - "groupId": "test_company_id", - "traits": { - "plan": "basic", - "name": "rudderUpdate", - "size": 50, - "industry": "IT", - "monthlySpend": "2131231", - "email": "comanyemail@abc.com" - }, - "anonymousId": "12312312", - "context": { - "app": { - "build": "1.0", - "name": "Test_Example", - "namespace": "com.example.testapp", - "version": "1.0" - }, - "device": { - "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "manufacturer": "Apple", - "model": "iPhone", - "name": "iPod touch (7th generation)", - "type": "iOS" - }, - "library": { - "name": "test-ios-library", - "version": "1.0.7" - }, - "locale": "en-US", - "network": { - "bluetooth": false, - "carrier": "unavailable", - "cellular": false, - "wifi": true - }, - "os": { - "name": "iOS", - "version": "14.0" - }, - "screen": { - "density": 2, - "height": 320, - "width": 568 - }, - "timezone": "Asia/Kolkata", - "userAgent": "unknown" - }, - "integrations": { - "All": true - }, - "messageId": "1601493060-39010c49-e6e4-4626-a75c-0dbf1925c9e8", - "type": "group" - }, - "destination": { - "Config": { - "apiKey": "abcd=", - "appId": "asdasdasd", - "collectContext": false - } - } - }, - { - "message": { - "groupId": "test_company_id_wdasda", - "context": { - "traits": { - "email": "testUser@test.com" - } - }, - "traits": { - "employees": 450, - "plan": "basic", - "email": "test@test.com", - "name": "rudderUpdate", - "size": "50", - "industry": "IT", - "website": "url", - "monthlySpend": "2131231", - "remoteCreatedAt": "1683017572", - "key1": "val1" - }, - "anonymousId": "sdfrsdfsdfsf", - "integrations": { - "All": true - }, - "type": "group", - "userId": "sdfrsdfsdfsf" - }, - "destination": { - "Config": { - "apiKey": "abcd=", - "appId": "asdasdasd", - "collectContext": false - } - } - }, - { - "message": { - "groupId": "test_company_id_wdasda", - "context": { - "traits": { - "email": "testUser@test.com" - } - }, - "traits": { - "employees": 450, - "plan": "basic", - "email": "test@test.com", - "name": "rudderUpdate", - "size": "50", - "industry": "IT", - "website": "url", - "monthlySpend": "2131231", - "remoteCreatedAt": "1683017572", - "key1": "val1", - "key2": { - "a": "a", - "b": "b" - }, - "key3": [1, 2, 3], - "key4": null - }, - "anonymousId": "anonId", - "integrations": { - "All": true - }, - "type": "group" - }, - "destination": { - "Config": { - "apiKey": "abcd=", - "appId": "asdasdasd", - "collectContext": false, - "sendAnonymousId": true - } - } - } -] diff --git a/test/__tests__/data/intercom_output.json b/test/__tests__/data/intercom_output.json deleted file mode 100644 index 64ad4133bf8..00000000000 --- a/test/__tests__/data/intercom_output.json +++ /dev/null @@ -1,651 +0,0 @@ -[ - { - "version": "1", - "type": "REST", - "method": "POST", - "endpoint": "https://api.intercom.io/users", - "headers": { - "Content-Type": "application/json", - "Authorization": "Bearer intercomApiKey", - "Accept": "application/json", - "Intercom-Version": "1.4" - }, - "params": {}, - "body": { - "JSON": { - "email": "test_1@test.com", - "phone": "9876543210", - "name": "Test Name", - "signed_up_at": 1601493060, - "last_seen_user_agent": "unknown", - "update_last_request_at": true, - "user_id": "test_user_id_1", - "custom_attributes": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "key1": "value1", - "address.city": "Kolkata", - "address.state": "West Bengal", - "originalArray[0].nested_field": "nested value", - "originalArray[0].tags[0]": "tag_1", - "originalArray[0].tags[1]": "tag_2", - "originalArray[0].tags[2]": "tag_3", - "originalArray[1].nested_field": "nested value", - "originalArray[1].tags[0]": "tag_1", - "originalArray[2].nested_field": "nested value" - } - }, - "XML": {}, - "JSON_ARRAY": {}, - "FORM": {} - }, - "files": {}, - "userId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33" - }, - { - "version": "1", - "type": "REST", - "method": "POST", - "endpoint": "https://api.intercom.io/users", - "headers": { - "Content-Type": "application/json", - "Authorization": "Bearer intercomApiKey", - "Accept": "application/json", - "Intercom-Version": "1.4" - }, - "params": {}, - "body": { - "JSON": { - "email": "test_1@test.com", - "phone": "9876543210", - "signed_up_at": 1601493060, - "name": "Test Name", - "last_seen_user_agent": "unknown", - "update_last_request_at": true, - "custom_attributes": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "key1": "value1" - } - }, - "XML": {}, - "JSON_ARRAY": {}, - "FORM": {} - }, - "files": {}, - "userId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33" - }, - { - "version": "1", - "type": "REST", - "method": "POST", - "endpoint": "https://api.intercom.io/users", - "headers": { - "Content-Type": "application/json", - "Authorization": "Bearer intercomApiKey", - "Accept": "application/json", - "Intercom-Version": "1.4" - }, - "params": {}, - "body": { - "JSON": { - "email": "test_1@test.com", - "phone": "9876543210", - "signed_up_at": 1601493060, - "last_seen_user_agent": "unknown", - "update_last_request_at": true, - "custom_attributes": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "key1": "value1" - }, - "name": "Name" - }, - "XML": {}, - "JSON_ARRAY": {}, - "FORM": {} - }, - "files": {}, - "userId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33" - }, - { - "version": "1", - "type": "REST", - "method": "POST", - "endpoint": "https://api.intercom.io/users", - "headers": { - "Content-Type": "application/json", - "Authorization": "Bearer intercomApiKey", - "Accept": "application/json", - "Intercom-Version": "1.4" - }, - "params": {}, - "body": { - "JSON": { - "email": "test_1@test.com", - "phone": "9876543210", - "signed_up_at": 1601493060, - "last_seen_user_agent": "unknown", - "update_last_request_at": true, - "custom_attributes": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "key1": "value1" - }, - "name": "Name" - }, - "XML": {}, - "JSON_ARRAY": {}, - "FORM": {} - }, - "files": {}, - "userId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33" - }, - { - "statusCode": 400, - "error": "Either of `email` or `userId` is required for Identify call" - }, - { - "version": "1", - "type": "REST", - "method": "POST", - "endpoint": "https://api.intercom.io/users", - "headers": { - "Content-Type": "application/json", - "Authorization": "Bearer intercomApiKey", - "Accept": "application/json", - "Intercom-Version": "1.4" - }, - "params": {}, - "body": { - "JSON": { - "email": "test_1@test.com", - "phone": "9876543210", - "signed_up_at": 1601493060, - "last_seen_user_agent": "unknown", - "update_last_request_at": true, - "custom_attributes": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "key1": "value1" - }, - "name": "Name", - "companies": [ - { - "company_id": "company_id", - "custom_attributes": { - "key1": "value1", - "key2": "{\"a\":\"a\"}", - "key3": "[1,2,3]" - }, - "name": "Test Comp", - "industry": "test industry" - } - ] - }, - "XML": {}, - "JSON_ARRAY": {}, - "FORM": {} - }, - "files": {}, - "userId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33" - }, - { - "version": "1", - "type": "REST", - "method": "POST", - "endpoint": "https://api.intercom.io/users", - "headers": { - "Content-Type": "application/json", - "Authorization": "Bearer intercomApiKey", - "Accept": "application/json", - "Intercom-Version": "1.4" - }, - "params": {}, - "body": { - "JSON": { - "email": "test_1@test.com", - "phone": "9876543210", - "signed_up_at": 1601493060, - "last_seen_user_agent": "unknown", - "update_last_request_at": false, - "custom_attributes": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "key1": "value1" - }, - "name": "Name", - "companies": [ - { - "company_id": "c0277b5c814453e5135f515f943d085a", - "custom_attributes": { - "key1": "value1", - "key3": "[\"value1\",\"value2\"]", - "key4": "{\"foo\":\"bar\"}" - }, - "name": "Test Comp", - "industry": "test industry" - } - ] - }, - "XML": {}, - "JSON_ARRAY": {}, - "FORM": {} - }, - "files": {}, - "userId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33" - }, - { - "version": "1", - "type": "REST", - "method": "POST", - "endpoint": "https://api.intercom.io/users", - "headers": { - "Content-Type": "application/json", - "Authorization": "Bearer intercomApiKey", - "Accept": "application/json", - "Intercom-Version": "1.4" - }, - "params": {}, - "body": { - "JSON": { - "email": "test_1@test.com", - "phone": "9876543210", - "signed_up_at": 1601493060, - "last_seen_user_agent": "unknown", - "update_last_request_at": true, - "custom_attributes": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "key1": "value1" - }, - "name": "Name", - "companies": [] - }, - "XML": {}, - "JSON_ARRAY": {}, - "FORM": {} - }, - "files": {}, - "userId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33" - }, - { - "version": "1", - "type": "REST", - "method": "POST", - "endpoint": "https://api.intercom.io/events", - "headers": { - "Content-Type": "application/json", - "Authorization": "Bearer intercomApiKey", - "Accept": "application/json", - "Intercom-Version": "1.4" - }, - "params": {}, - "body": { - "JSON": { - "user_id": "test_user_id_1", - "email": "test_1@test.com", - "event_name": "Test Event 2", - "created": 1601493061, - "metadata": { - "property1": 1, - "property2": "test", - "property3": true, - "property4": "2020-10-05T09:09:03.731Z", - "property5.property1": 1, - "property5.property2": "test", - "property5.property3.subProp1.a": "a", - "property5.property3.subProp1.b": "b", - "property5.property3.subProp2[0]": "a", - "property5.property3.subProp2[1]": "b", - "properties6": null, - "revenue": { - "amount": 1232, - "currency": "inr", - "test": 123 - }, - "price": { - "amount": 3000, - "currency": "USD" - }, - "article": { - "url": "https://example.org/ab1de.html", - "value": "the dude abides" - } - } - }, - "XML": {}, - "JSON_ARRAY": {}, - "FORM": {} - }, - "files": {}, - "userId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33" - }, - { - "version": "1", - "type": "REST", - "method": "POST", - "endpoint": "https://api.intercom.io/events", - "headers": { - "Content-Type": "application/json", - "Authorization": "Bearer intercomApiKey", - "Accept": "application/json", - "Intercom-Version": "1.4" - }, - "params": {}, - "body": { - "JSON": { - "email": "test_1@test.com", - "event_name": "Test Event 2", - "created": 1601493061 - }, - "XML": {}, - "JSON_ARRAY": {}, - "FORM": {} - }, - "files": {}, - "userId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33" - }, - { - "statusCode": 400, - "error": "Either of `email` or `userId` is required for Track call" - }, - { - "version": "1", - "type": "REST", - "method": "POST", - "endpoint": "https://api.intercom.io/users", - "headers": { - "Content-Type": "application/json", - "Authorization": "Bearer intercomApiKey", - "Accept": "application/json", - "Intercom-Version": "1.4" - }, - "params": {}, - "body": { - "JSON": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "name": "Test Name", - "firstName": "Test", - "lastName": "Name", - "createdAt": "2020-09-30T19:11:00.337Z", - "phone": "9876543210", - "key1": "value1", - "email": "test@gmail.com", - "update_last_request_at": true - }, - "JSON_ARRAY": {}, - "XML": {}, - "FORM": {} - }, - "files": {}, - "userId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33" - }, - { - "version": "1", - "type": "REST", - "method": "POST", - "endpoint": "https://api.intercom.io/users", - "headers": { - "Content-Type": "application/json", - "Authorization": "Bearer intercomApiKey", - "Accept": "application/json", - "Intercom-Version": "1.4" - }, - "params": {}, - "body": { - "JSON": { - "phone": "9876543210", - "name": "Test Name", - "signed_up_at": 1601493060, - "last_seen_user_agent": "unknown", - "custom_attributes": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "key1": "value1" - }, - "user_id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "update_last_request_at": true - }, - "JSON_ARRAY": {}, - "XML": {}, - "FORM": {} - }, - "files": {}, - "userId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33" - }, - { - "statusCode": 400, - "error": "Either of `email` or `userId` is required for Identify call" - }, - [ - { - "body": { - "XML": {}, - "FORM": {}, - "JSON": { - "name": "rudderUpdate", - "plan": "basic", - "size": 50, - "industry": "IT", - "company_id": "test_company_id_wdasda", - "monthly_spend": 2131231, - "remote_created_at": 1683017572, - "custom_attributes": { - "key1": "val1", - "employees": 450, - "email": "test@test.com" - } - }, - "JSON_ARRAY": {} - }, - "type": "REST", - "files": {}, - "method": "POST", - "params": {}, - "userId": "sdfrsdfsdfsf", - "headers": { - "Accept": "application/json", - "Content-Type": "application/json", - "Authorization": "Bearer abcd=", - "Intercom-Version": "1.4" - }, - "version": "1", - "endpoint": "https://api.intercom.io/companies" - }, - { - "body": { - "XML": {}, - "FORM": {}, - "JSON": { - "user_id": "sdfrsdfsdfsf", - "companies": [ - { - "name": "rudderUpdate", - "company_id": "test_company_id_wdasda" - } - ] - }, - "JSON_ARRAY": {} - }, - "type": "REST", - "files": {}, - "method": "POST", - "params": {}, - "userId": "sdfrsdfsdfsf", - "headers": { - "Accept": "application/json", - "Content-Type": "application/json", - "Authorization": "Bearer abcd=", - "Intercom-Version": "1.4" - }, - "version": "1", - "endpoint": "https://api.intercom.io/users" - } - ], - [ - { - "body": { - "XML": {}, - "FORM": {}, - "JSON": { - "name": "rudderUpdate", - "size": 50, - "industry": "IT", - "company_id": "test_company_id", - "monthly_spend": 2131231, - "plan": "basic", - "custom_attributes": { - "email": "comanyemail@abc.com" - } - }, - "JSON_ARRAY": {} - }, - "type": "REST", - "files": {}, - "method": "POST", - "params": {}, - "userId": "12312312", - "headers": { - "Accept": "application/json", - "Content-Type": "application/json", - "Authorization": "Bearer abcd=", - "Intercom-Version": "1.4" - }, - "version": "1", - "endpoint": "https://api.intercom.io/companies" - } - ], - [ - { - "body": { - "XML": {}, - "FORM": {}, - "JSON": { - "name": "rudderUpdate", - "plan": "basic", - "size": 50, - "industry": "IT", - "company_id": "test_company_id_wdasda", - "monthly_spend": 2131231, - "remote_created_at": 1683017572, - "website": "url", - "custom_attributes": { - "key1": "val1", - "employees": 450, - "email": "test@test.com" - } - }, - "JSON_ARRAY": {} - }, - "type": "REST", - "files": {}, - "method": "POST", - "params": {}, - "userId": "sdfrsdfsdfsf", - "headers": { - "Accept": "application/json", - "Content-Type": "application/json", - "Authorization": "Bearer abcd=", - "Intercom-Version": "1.4" - }, - "version": "1", - "endpoint": "https://api.intercom.io/companies" - }, - { - "body": { - "XML": {}, - "FORM": {}, - "JSON": { - "user_id": "sdfrsdfsdfsf", - "email": "testUser@test.com", - "companies": [ - { - "name": "rudderUpdate", - "company_id": "test_company_id_wdasda" - } - ] - }, - "JSON_ARRAY": {} - }, - "type": "REST", - "files": {}, - "method": "POST", - "params": {}, - "userId": "sdfrsdfsdfsf", - "headers": { - "Accept": "application/json", - "Content-Type": "application/json", - "Authorization": "Bearer abcd=", - "Intercom-Version": "1.4" - }, - "version": "1", - "endpoint": "https://api.intercom.io/users" - } - ], - [ - { - "body": { - "XML": {}, - "FORM": {}, - "JSON": { - "name": "rudderUpdate", - "plan": "basic", - "size": 50, - "industry": "IT", - "company_id": "test_company_id_wdasda", - "monthly_spend": 2131231, - "remote_created_at": 1683017572, - "website": "url", - "custom_attributes": { - "key1": "val1", - "employees": 450, - "email": "test@test.com", - "key2.a": "a", - "key2.b": "b", - "key3[0]": 1, - "key3[1]": 2, - "key3[2]": 3, - "key4": null - } - }, - "JSON_ARRAY": {} - }, - "type": "REST", - "files": {}, - "method": "POST", - "params": {}, - "userId": "anonId", - "headers": { - "Accept": "application/json", - "Content-Type": "application/json", - "Authorization": "Bearer abcd=", - "Intercom-Version": "1.4" - }, - "version": "1", - "endpoint": "https://api.intercom.io/companies" - }, - { - "body": { - "XML": {}, - "FORM": {}, - "JSON": { - "user_id": "anonId", - "email": "testUser@test.com", - "companies": [ - { - "name": "rudderUpdate", - "company_id": "test_company_id_wdasda" - } - ] - }, - "JSON_ARRAY": {} - }, - "type": "REST", - "files": {}, - "method": "POST", - "params": {}, - "userId": "anonId", - "headers": { - "Accept": "application/json", - "Content-Type": "application/json", - "Authorization": "Bearer abcd=", - "Intercom-Version": "1.4" - }, - "version": "1", - "endpoint": "https://api.intercom.io/users" - } - ] -] diff --git a/test/__tests__/data/intercom_router_input.json b/test/__tests__/data/intercom_router_input.json deleted file mode 100644 index a3eb28fc105..00000000000 --- a/test/__tests__/data/intercom_router_input.json +++ /dev/null @@ -1,150 +0,0 @@ -[ - { - "message": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "channel": "mobile", - "context": { - "app": { - "build": "1.0", - "name": "Test_Example", - "namespace": "com.example.testapp", - "version": "1.0" - }, - "device": { - "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "manufacturer": "Apple", - "model": "iPhone", - "name": "iPod touch (7th generation)", - "type": "iOS" - }, - "library": { - "name": "test-ios-library", - "version": "1.0.7" - }, - "locale": "en-US", - "network": { - "bluetooth": false, - "carrier": "unavailable", - "cellular": false, - "wifi": true - }, - "os": { - "name": "iOS", - "version": "14.0" - }, - "screen": { - "density": 2, - "height": 320, - "width": 568 - }, - "timezone": "Asia/Kolkata", - "traits": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "name": "Test Name", - "firstName": "Test", - "lastName": "Name", - "createdAt": "2020-09-30T19:11:00.337Z", - "userId": "test_user_id_1", - "email": "test_1@test.com", - "phone": "9876543210", - "key1": "value1" - }, - "userAgent": "unknown" - }, - "event": "Test Event 2", - "integrations": { - "All": true - }, - "messageId": "1601493060-39010c49-e6e4-4626-a75c-0dbf1925c9e8", - "originalTimestamp": "2020-09-30T19:11:00.337Z", - "receivedAt": "2020-10-01T00:41:11.369+05:30", - "request_ip": "2405:201:8005:9856:7911:25e7:5603:5e18", - "sentAt": "2020-09-30T19:11:10.382Z", - "timestamp": "2020-10-01T00:41:01.324+05:30", - "type": "identify" - }, - "metadata": { - "jobId": 1 - }, - "destination": { - "Config": { - "apiKey": "intercomApiKey", - "appId": "9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0", - "collectContext": false - } - } - }, - { - "message": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "channel": "mobile", - "context": { - "app": { - "build": "1.0", - "name": "Test_Example", - "namespace": "com.example.testapp", - "version": "1.0" - }, - "device": { - "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "manufacturer": "Apple", - "model": "iPhone", - "name": "iPod touch (7th generation)", - "type": "iOS" - }, - "library": { - "name": "test-ios-library", - "version": "1.0.7" - }, - "locale": "en-US", - "network": { - "bluetooth": false, - "carrier": "unavailable", - "cellular": false, - "wifi": true - }, - "os": { - "name": "iOS", - "version": "14.0" - }, - "screen": { - "density": 2, - "height": 320, - "width": 568 - }, - "timezone": "Asia/Kolkata", - "traits": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "firstName": "Test", - "lastName": "Name", - "createdAt": "2020-09-30T19:11:00.337Z", - "email": "test_1@test.com", - "phone": "9876543210", - "key1": "value1" - }, - "userAgent": "unknown" - }, - "event": "Test Event 2", - "integrations": { - "All": true - }, - "messageId": "1601493060-39010c49-e6e4-4626-a75c-0dbf1925c9e8", - "originalTimestamp": "2020-09-30T19:11:00.337Z", - "receivedAt": "2020-10-01T00:41:11.369+05:30", - "request_ip": "2405:201:8005:9856:7911:25e7:5603:5e18", - "sentAt": "2020-09-30T19:11:10.382Z", - "timestamp": "2020-10-01T00:41:01.324+05:30", - "type": "identify" - }, - "metadata": { - "jobId": 2 - }, - "destination": { - "Config": { - "apiKey": "intercomApiKey", - "appId": "9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0", - "collectContext": false - } - } - } -] diff --git a/test/__tests__/data/intercom_router_output.json b/test/__tests__/data/intercom_router_output.json deleted file mode 100644 index 494d15e97aa..00000000000 --- a/test/__tests__/data/intercom_router_output.json +++ /dev/null @@ -1,99 +0,0 @@ -[ - { - "batchedRequest": { - "version": "1", - "type": "REST", - "method": "POST", - "endpoint": "https://api.intercom.io/users", - "headers": { - "Content-Type": "application/json", - "Authorization": "Bearer intercomApiKey", - "Accept": "application/json", - "Intercom-Version": "1.4" - }, - "params": {}, - "body": { - "JSON": { - "email": "test_1@test.com", - "phone": "9876543210", - "name": "Test Name", - "signed_up_at": 1601493060, - "last_seen_user_agent": "unknown", - "update_last_request_at": true, - "user_id": "test_user_id_1", - "custom_attributes": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "key1": "value1" - } - }, - "XML": {}, - "JSON_ARRAY": {}, - "FORM": {} - }, - "files": {}, - "userId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33" - }, - "metadata": [ - { - "jobId": 1 - } - ], - "batched": false, - "statusCode": 200, - "destination": { - "Config": { - "apiKey": "intercomApiKey", - "appId": "9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0", - "collectContext": false - } - } - }, - { - "batchedRequest": { - "version": "1", - "type": "REST", - "method": "POST", - "endpoint": "https://api.intercom.io/users", - "headers": { - "Content-Type": "application/json", - "Authorization": "Bearer intercomApiKey", - "Accept": "application/json", - "Intercom-Version": "1.4" - }, - "params": {}, - "body": { - "JSON": { - "email": "test_1@test.com", - "phone": "9876543210", - "signed_up_at": 1601493060, - "name": "Test Name", - "last_seen_user_agent": "unknown", - "update_last_request_at": true, - "custom_attributes": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "key1": "value1" - } - }, - "XML": {}, - "JSON_ARRAY": {}, - "FORM": {} - }, - "files": {}, - "userId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33" - }, - "metadata": [ - { - "jobId": 2 - } - ], - "batched": false, - "statusCode": 200, - "destination": { - "Config": { - "apiKey": "intercomApiKey", - "appId": "9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0", - "collectContext": false - } - } - } -] diff --git a/test/__tests__/intercom.test.js b/test/__tests__/intercom.test.js index 973b3e8a608..15e3b5e252a 100644 --- a/test/__tests__/intercom.test.js +++ b/test/__tests__/intercom.test.js @@ -7,43 +7,23 @@ const path = require("path"); const version = "v0"; const transformer = require(`../../src/${version}/destinations/${integration}/transform`); -const inputDataFile = fs.readFileSync( - path.resolve(__dirname, `./data/${integration}_input.json`) -); -const outputDataFile = fs.readFileSync( - path.resolve(__dirname, `./data/${integration}_output.json`) -); -const inputData = JSON.parse(inputDataFile); -const expectedData = JSON.parse(outputDataFile); // Router Test Data -const inputRouterDataFile = fs.readFileSync( - path.resolve(__dirname, `./data/${integration}_router_input.json`) +const testDataFile = fs.readFileSync( + path.resolve(__dirname, `./data/${integration}.json`) ); -const outputRouterDataFile = fs.readFileSync( - path.resolve(__dirname, `./data/${integration}_router_output.json`) -); -const inputRouterData = JSON.parse(inputRouterDataFile); -const expectedRouterData = JSON.parse(outputRouterDataFile); - +const testData = JSON.parse(testDataFile); describe(`${name} Tests`, () => { - describe("Processor Tests", () => { - inputData.forEach((input, index) => { - it(`${name} - payload: ${index}`, async () => { + describe("Processor", () => { + testData.forEach((dataPoint, index) => { + it(`${index}. ${integration} - ${dataPoint.description}`, async () => { try { - const output = await transformer.process(input); - expect(output).toEqual(expectedData[index]); + const output = await transformer.process(dataPoint.input); + expect(output).toEqual(dataPoint.output); } catch (error) { - expect(error.message).toEqual(expectedData[index].error); + expect(error.message).toEqual(dataPoint.output.message); } }); }); }); - - describe("Router Tests", () => { - it("Payload", async () => { - const routerOutput = await transformer.processRouterDest(inputRouterData); - expect(routerOutput).toEqual(expectedRouterData); - }); - }); }); From 33bdccde1ba39beb371c0e8ab2fc07a20a15ad7b Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Fri, 15 Sep 2023 12:12:25 +0530 Subject: [PATCH 03/14] fix(intercom): test cases --- src/v0/destinations/intercom/config.js | 5 +- src/v0/destinations/intercom/transform.js | 22 +- src/v0/destinations/intercom/util.js | 67 ++- src/v0/destinations/intercom/util.test.js | 60 ++- test/__mocks__/data/intercom/response.json | 87 ++-- test/__mocks__/intercom.mock.js | 27 +- test/__tests__/data/intercom.json | 570 ++++++++++++++++++++- test/__tests__/intercom.test.js | 2 +- 8 files changed, 758 insertions(+), 82 deletions(-) diff --git a/src/v0/destinations/intercom/config.js b/src/v0/destinations/intercom/config.js index 48f439cb818..b33fb3395e8 100644 --- a/src/v0/destinations/intercom/config.js +++ b/src/v0/destinations/intercom/config.js @@ -14,7 +14,7 @@ const ConfigCategory = { }, IDENTIFY: { endpoint: 'contacts', - name: 'INTERCOMIdentifyConfig' + name: 'INTERCOMIdentifyConfig', }, GROUP: { endpoint: 'contacts/{id}/companies', @@ -40,7 +40,7 @@ const ReservedUserAttributes = [ 'createdAt', 'timestamp', 'originalTimestamp', - 'unsubscribedFromEmails' + 'unsubscribedFromEmails', ]; const ReservedCompanyAttributes = [ @@ -48,6 +48,7 @@ const ReservedCompanyAttributes = [ 'size', 'plan', 'name', + 'email', 'userId', 'website', 'industry', diff --git a/src/v0/destinations/intercom/transform.js b/src/v0/destinations/intercom/transform.js index c175498e33e..0dcb3afbdd6 100644 --- a/src/v0/destinations/intercom/transform.js +++ b/src/v0/destinations/intercom/transform.js @@ -12,13 +12,14 @@ const { getPayload, validateTrack, fetchContactId, + getLookUpField, getBaseEndpoint, validateIdentify, createOrUpdateCompany, filterCustomAttributes, separateReservedAndRestMetadata, } = require('./util'); -const { InstrumentationError } = require('../../util/errorTypes'); +const { InstrumentationError, NetworkError } = require('../../util/errorTypes'); const responseBuilder = (payload, endpoint, requestMethod, destination) => { const response = defaultRequestConfig(); @@ -47,8 +48,9 @@ const identifyResponseBuilder = async (message, destination) => { let endpoint; let requestMethod; - const contactId = await fetchContactId(message, destination); - + const lookupField = getLookUpField(message); + const contactId = await fetchContactId(message, destination, lookupField); + const baseEndPoint = getBaseEndpoint(destination); if (contactId) { requestMethod = 'PUT'; @@ -78,7 +80,8 @@ const trackResponseBuilder = (message, destination) => { payload = { ...payload, metadata: { ...reservedMetadata, ...flattenJson(restMetadata) } }; } - const { endpoint } = ConfigCategory.TRACK; + const baseEndPoint = getBaseEndpoint(destination); + const endpoint = `${baseEndPoint}/${ConfigCategory.TRACK.endpoint}`; const { sendAnonymousId } = destination.Config; if (!payload.user_id && sendAnonymousId && message.anonymousId) { @@ -92,13 +95,20 @@ const groupResponseBuilder = async (message, destination) => { const payload = getPayload(message, ConfigCategory.GROUP); payload.custom_attributes = filterCustomAttributes(payload, ReservedCompanyAttributes); const companyId = await createOrUpdateCompany(payload, destination); - const contactId = await fetchContactId(message, destination); + const lookupField = getLookUpField(message); + const contactId = await fetchContactId(message, destination, lookupField); + + if (!isDefinedAndNotNull(contactId)) { + throw new NetworkError(`Can't find any user with given lookupField : ${lookupField}`); + } if (isDefinedAndNotNull(companyId) && isDefinedAndNotNull(contactId)) { - let { endpoint } = ConfigCategory.GROUP; + const baseEndPoint = getBaseEndpoint(destination); + let endpoint = `${baseEndPoint}/${ConfigCategory.GROUP.endpoint}`; endpoint = endpoint.replace('{id}', contactId); return responseBuilder({ id: companyId }, endpoint, 'POST', destination); } + throw new InstrumentationError("Can't attach user with company"); }; diff --git a/src/v0/destinations/intercom/util.js b/src/v0/destinations/intercom/util.js index fbc56803bec..7ebe57e7c2e 100644 --- a/src/v0/destinations/intercom/util.js +++ b/src/v0/destinations/intercom/util.js @@ -31,17 +31,19 @@ const { InstrumentationError, NetworkError } = require('../../util/errorTypes'); /** * Validated identify call payload - * @param {*} payload + * @param {*} payload */ const validateIdentify = (payload) => { if (!payload.email && !payload.external_id) { - throw new InstrumentationError('Either of `email` or `userId` is required for Identify call'); + throw new InstrumentationError( + 'Either of `email` or `userId` is required for an Identify call', + ); } }; /** * Validates track call payload - * @param {*} payload + * @param {*} payload */ const validateTrack = (payload) => { if (!payload.user_id && !payload.email) { @@ -51,20 +53,20 @@ const validateTrack = (payload) => { /** * Returns headers - * @param {*} destination - * @returns + * @param {*} destination + * @returns */ const getHeaders = (destination) => ({ 'Content-Type': JSON_MIME_TYPE, Authorization: `Bearer ${destination.Config.apiKey}`, Accept: JSON_MIME_TYPE, - 'Intercom-Version': '2.9', + 'Intercom-Version': '2.10', }); /** * Returns base endpoint - * @param {*} destination - * @returns + * @param {*} destination + * @returns */ const getBaseEndpoint = (destination) => { const { apiServer } = destination.Config; @@ -79,9 +81,9 @@ const getBaseEndpoint = (destination) => { /** * Returns custom attributes for identify and group calls - * @param {*} payload - * @param {*} ReservedAttributes - * @returns + * @param {*} payload + * @param {*} ReservedAttributes + * @returns */ const filterCustomAttributes = (payload, ReservedAttributes) => { const { custom_attributes } = payload; @@ -99,9 +101,9 @@ const filterCustomAttributes = (payload, ReservedAttributes) => { /** * Returns transformed payload - * @param {*} message - * @param {*} category - * @returns + * @param {*} message + * @param {*} category + * @returns */ const getPayload = (message, category) => { let payload; @@ -115,17 +117,27 @@ const getPayload = (message, category) => { }; /** - * Returns contact id based on lookup - * @param {*} message - * @param {*} destination - * @returns + * Returns lookupField + * @param {*} message + * @returns */ -const fetchContactId = async (message, destination) => { +const getLookUpField = (message) => { const integrationsObj = getIntegrationsObj(message, 'INTERCOM'); let lookupField = 'email'; if (integrationsObj && integrationsObj.lookup && isDefinedAndNotNull(integrationsObj.lookup)) { lookupField = integrationsObj.lookup; } + return lookupField; +}; + +/** + * Returns contact id based on lookup + * @param {*} message + * @param {*} destination + * @param {*} lookupField + * @returns + */ +const fetchContactId = async (message, destination, lookupField) => { const lookupFieldValue = getFieldValueFromMessage(message, lookupField); const data = JSON.stringify({ query: { @@ -142,7 +154,7 @@ const fetchContactId = async (message, destination) => { const headers = getHeaders(destination); const baseEndPoint = getBaseEndpoint(destination); - const endpoint = `${baseEndPoint}/${SEARCH_CONTACT_ENDPOINT}` + const endpoint = `${baseEndPoint}/${SEARCH_CONTACT_ENDPOINT}`; const response = await httpPOST(endpoint, data, { headers, destType: 'intercom', @@ -151,7 +163,9 @@ const fetchContactId = async (message, destination) => { const processedUserResponse = processAxiosResponse(response); if (isHttpStatusSuccess(processedUserResponse.status)) { - return processedUserResponse.response?.data[0]?.id; + return processedUserResponse.response?.data.length > 0 + ? processedUserResponse.response?.data[0]?.id + : null; } return null; @@ -159,16 +173,16 @@ const fetchContactId = async (message, destination) => { /** * Function to create or update company - * @param {*} payload - * @param {*} destination - * @returns + * @param {*} payload + * @param {*} destination + * @returns */ const createOrUpdateCompany = async (payload, destination) => { const headers = getHeaders(destination); - const finalPayload = removeUndefinedAndNullValues(payload); + const finalPayload = JSON.stringify(removeUndefinedAndNullValues(payload)); const baseEndPoint = getBaseEndpoint(destination); - const endpoint = `${baseEndPoint}/${CREATE_OR_UPDATE_COMPANY_ENDPOINT}` + const endpoint = `${baseEndPoint}/${CREATE_OR_UPDATE_COMPANY_ENDPOINT}`; const response = await httpPOST(endpoint, finalPayload, { headers, destType: 'intercom', @@ -223,6 +237,7 @@ module.exports = { getPayload, validateTrack, fetchContactId, + getLookUpField, getBaseEndpoint, validateIdentify, createOrUpdateCompany, diff --git a/src/v0/destinations/intercom/util.test.js b/src/v0/destinations/intercom/util.test.js index 99dbdd1f7ec..23b905e073d 100644 --- a/src/v0/destinations/intercom/util.test.js +++ b/src/v0/destinations/intercom/util.test.js @@ -1,4 +1,5 @@ -const { separateReservedAndRestMetadata } = require('./util'); +const { getLookUpField, getBaseEndpoint, separateReservedAndRestMetadata } = require('./util'); +const { BASE_ENDPOINT, BASE_EU_ENDPOINT, BASE_AU_ENDPOINT } = require('./config'); describe('separateReservedAndRestMetadata utility test', () => { it('separate reserved and rest metadata', () => { @@ -174,3 +175,60 @@ describe('separateReservedAndRestMetadata utility test', () => { expect({}).toEqual(restMetadata); }); }); + +describe('getBaseEndpoint', () => { + // Returns BASE_EU_ENDPOINT when apiServer is 'eu' + it('should return BASE_EU_ENDPOINT when apiServer is "eu"', () => { + const destination = { + Config: { + apiServer: 'eu', + }, + }; + const result = getBaseEndpoint(destination); + expect(result).toBe(BASE_EU_ENDPOINT); + }); + + // Returns BASE_AU_ENDPOINT when apiServer is 'au' + it('should return BASE_AU_ENDPOINT when apiServer is "au"', () => { + const destination = { + Config: { + apiServer: 'au', + }, + }; + const result = getBaseEndpoint(destination); + expect(result).toBe(BASE_AU_ENDPOINT); + }); + + // Returns BASE_ENDPOINT when apiServer is standard + it('should return BASE_ENDPOINT when apiServer is standard', () => { + const destination = { + Config: { + apiServer: 'standard', + }, + }; + const result = getBaseEndpoint(destination); + expect(result).toBe(BASE_ENDPOINT); + }); +}); + +// Generated by CodiumAI + +describe('getLookUpField', () => { + // Returns 'email' by default + it('should return "email" by default', () => { + const message = {}; + expect(getLookUpField(message)).toBe('email'); + }); + + // Returns 'lookup' field from integrationsObj if defined + it('should return "lookup" field from integrationsObj if defined', () => { + const message = { + integrations: { + intercom: { + lookup: 'phone', + }, + }, + }; + expect(getLookUpField(message)).toBe('phone'); + }); +}); diff --git a/test/__mocks__/data/intercom/response.json b/test/__mocks__/data/intercom/response.json index 996959ff89e..f638e844f2e 100644 --- a/test/__mocks__/data/intercom/response.json +++ b/test/__mocks__/data/intercom/response.json @@ -1,36 +1,57 @@ { - "https://api.intercom.io/contacts/search": { - "type": "list", - "data": [ - { - "type": "contact", - "id": "contact_id_1", - "workspace_id": "work@1", - "external_id": "user@1", - "role": "user", - "email": "test@rudderlabs.com", - "name": "joe bloggs", - "social_profiles": { - "type": "list", - "data": [] - }, - "has_hard_bounced": false, - "marked_email_as_spam": false, - "unsubscribed_from_emails": false, - "created_at": 1694510222, - "updated_at": 1694678038, - "location": { - "type": "location", - "country": "IND" - } - } - ], - "total_count": 1, - "pages": { - "type": "pages", - "page": 1, - "per_page": 50, - "total_pages": 1 + "https://api.intercom.io/contacts/search": { + "type": "list", + "data": [ + { + "type": "contact", + "id": "contact_id_2", + "workspace_id": "workspace@2", + "external_id": "user@2", + "role": "user", + "email": "test@rudderlabs.com", + "name": "joe bloggs", + "social_profiles": { + "type": "list", + "data": [] + }, + "has_hard_bounced": false, + "marked_email_as_spam": false, + "unsubscribed_from_emails": false, + "created_at": 1694510222, + "updated_at": 1694678038, + "location": { + "type": "location", + "country": "IND" } + } + ], + "total_count": 1, + "pages": { + "type": "pages", + "page": 1, + "per_page": 50, + "total_pages": 1 } -} \ No newline at end of file + }, + "https://api.intercom.io/companies": { + "type": "company", + "company_id": "group@1", + "id": "company@1", + "app_id": "app-1", + "name": "rudderstack", + "remote_created_at": 1683017572, + "created_at": 1694677006, + "updated_at": 1694757808, + "monthly_spend": 2131231, + "session_count": 0, + "user_count": 2, + "size": 50, + "industry": "IT", + "plan": "basic", + "custom_attributes": { + "employees": 450, + "email": "test@test.com", + "key1": "val1" + } + } +} diff --git a/test/__mocks__/intercom.mock.js b/test/__mocks__/intercom.mock.js index 68f533d7712..4cd9b701d34 100644 --- a/test/__mocks__/intercom.mock.js +++ b/test/__mocks__/intercom.mock.js @@ -10,19 +10,22 @@ const getData = url => { }; const intercomPostRequestHandler = (url, payload) => { + const requestPayload = JSON.parse(payload); const mockData = getData(url); - switch (url) { - case "https://api.intercom.io/contacts/search": - //resolve with status 201 and response data contains value for contact created - return { data: mockData, status: 200 }; - default: - return new Promise((resolve, reject) => { - if (mockData) { - resolve({ data: mockData, status: 200 }); - } else { - resolve({ error: "Request failed" }); - } - }); + if (requestPayload.query?.value[0]?.value === 'test@intercom.com') { + return { data: { data: [], total_count: 0 }, status: 200 }; + } else if (requestPayload.query?.value[0]?.value === 'test@rudderlabs.com') { + return { data: mockData, status: 200 }; + } else if (requestPayload?.company_id === 'rudderstack@1') { + return { data: mockData, status: 200 }; + } else { + return new Promise((resolve, reject) => { + if (mockData) { + resolve({ data: mockData, status: 200 }); + } else { + resolve({ error: "Request failed" }); + } + }); } }; diff --git a/test/__tests__/data/intercom.json b/test/__tests__/data/intercom.json index 0637a088a01..40edb772e3d 100644 --- a/test/__tests__/data/intercom.json +++ b/test/__tests__/data/intercom.json @@ -1 +1,569 @@ -[] \ No newline at end of file +[ + { + "description": "No Message type", + "input": { + "message": { + "channel": "web", + "context": { + "traits": { + "age": 23, + "email": "adc@test.com", + "firstname": "Test", + "birthday": "2022-05-13T12:51:01.470Z" + }, + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36" + }, + "event": "Product Searched", + "originalTimestamp": "2020-09-22T14:42:44.724Z", + "timestamp": "2022-09-22T20:12:44.757+05:30", + "userId": "user@1" + }, + "destination": { + "Config": { + "apiKey": "ABC..." + } + }, + "metadata": { + "secret": { + "access_token": "ABC" + } + } + }, + "output": { + "error": "Message Type is not present. Aborting message" + } + }, + { + "description": "Unsupported Event type", + "input": { + "message": { + "channel": "web", + "context": { + "traits": { + "age": 23, + "email": "adc@test.com", + "firstname": "Test", + "birthday": "2022-05-13T12:51:01.470Z" + }, + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36" + }, + "event": "Product Searched", + "type": "page", + "originalTimestamp": "2020-09-22T14:42:44.724Z", + "timestamp": "2022-09-22T20:12:44.757+05:30", + "userId": "user@1" + }, + "destination": { + "Config": { + "apiKey": "ABC..." + } + } + }, + "output": { + "error": "Message type page not supported" + } + }, + { + "description": "Required parameters are not present", + "input": { + "message": { + "channel": "web", + "context": { + "traits": { + "age": 23, + "firstname": "Test", + "birthday": "2022-05-13T12:51:01.470Z" + }, + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36" + }, + "event": "Product Searched", + "type": "identify", + "originalTimestamp": "2023-09-24T14:42:44.724Z", + "timestamp": "2023-09-22T20:14:44.757+05:30", + "anonymousId": "26508834-c290-4354-9303-11c9b339a58a" + }, + "destination": { + "Config": { + "apiKey": "ABC..." + } + } + }, + "output": { + "error": "Either of `email` or `userId` is required for an Identify call" + } + }, + { + "description": "Create user", + "input": { + "message": { + "channel": "web", + "context": { + "device": { + "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", + "manufacturer": "Apple", + "model": "iPhone", + "name": "iPod touch (7th generation)", + "type": "iOS" + }, + "library": { + "name": "test-ios-library", + "version": "1.0.7" + }, + "locale": "en-US", + "network": { + "bluetooth": false, + "carrier": "unavailable", + "cellular": false, + "wifi": true + }, + "os": { + "name": "iOS", + "version": "14.0" + }, + "screen": { + "density": 2, + "height": 320, + "width": 568 + }, + "traits": { + "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", + "name": "Dummy User", + "firstName": "Dummy", + "lastName": "User", + "createdAt": "2020-09-30T19:11:00.337Z", + "userId": "user@1", + "email": "test@intercom.com", + "phone": "123456789", + "address": { + "city": "Kolkata", + "state": "West Bengal" + }, + "originalArray": [ + { + "nested_field": "nested value", + "tags": ["tag_1", "tag_2", "tag_3"] + } + ] + }, + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36" + }, + "integrations": { + "INTERCOM": { + "lookup": "email" + } + }, + "type": "identify", + "originalTimestamp": "2023-09-24T14:42:44.724Z", + "timestamp": "2023-09-22T20:14:44.757+05:30", + "anonymousId": "26508834-c290-4354-9303-11c9b339a58a", + "userId": "user@1" + }, + "destination": { + "Config": { + "apiKey": "dummyApiKey", + "apiServer": "standard" + } + } + }, + "output": { + "body": { + "FORM": {}, + "JSON": { + "custom_attributes": { + "address.city": "Kolkata", + "address.state": "West Bengal", + "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", + "originalArray[0].nested_field": "nested value", + "originalArray[0].tags[0]": "tag_1", + "originalArray[0].tags[1]": "tag_2", + "originalArray[0].tags[2]": "tag_3" + }, + "email": "test@intercom.com", + "external_id": "user@1", + "last_seen_at": 1695393884, + "name": "Dummy User", + "phone": "123456789", + "signed_up_at": 1601493060 + }, + "JSON_ARRAY": {}, + "XML": {} + }, + "endpoint": "https://api.intercom.io/contacts", + "files": {}, + "headers": { + "Accept": "application/json", + "Authorization": "Bearer dummyApiKey", + "Content-Type": "application/json", + "Intercom-Version": "2.10" + }, + "method": "POST", + "params": {}, + "type": "REST", + "version": "1" + } + }, + { + "description": "Update user", + "input": { + "message": { + "channel": "web", + "context": { + "device": { + "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", + "manufacturer": "Apple", + "model": "iPhone", + "name": "iPod touch (7th generation)", + "type": "iOS" + }, + "library": { + "name": "test-ios-library", + "version": "1.0.7" + }, + "locale": "en-US", + "network": { + "bluetooth": false, + "carrier": "unavailable", + "cellular": false, + "wifi": true + }, + "os": { + "name": "iOS", + "version": "14.0" + }, + "screen": { + "density": 2, + "height": 320, + "width": 568 + }, + "traits": { + "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", + "firstName": "Test", + "lastName": "User", + "createdAt": "2020-09-30T19:11:00.337Z", + "email": "test@rudderlabs.com", + "phone": "123456789", + "address": { + "city": "Kolkata", + "state": "West Bengal" + }, + "originalArray": [ + { + "nested_field": "nested value", + "tags": ["tag_1", "tag_2", "tag_3"] + } + ] + }, + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36" + }, + "integrations": { + "INTERCOM": { + "lookup": "email" + } + }, + "type": "identify", + "originalTimestamp": "2023-09-24T14:42:44.724Z", + "timestamp": "2023-09-22T20:14:44.757+05:30", + "anonymousId": "26508834-c290-4354-9303-11c9b339a58a" + }, + "destination": { + "Config": { + "apiKey": "dummyApiKey", + "apiServer": "standard", + "sendAnonymousId": true + } + } + }, + "output": { + "body": { + "FORM": {}, + "JSON": { + "custom_attributes": { + "address.city": "Kolkata", + "address.state": "West Bengal", + "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", + "originalArray[0].nested_field": "nested value", + "originalArray[0].tags[0]": "tag_1", + "originalArray[0].tags[1]": "tag_2", + "originalArray[0].tags[2]": "tag_3" + }, + "email": "test@rudderlabs.com", + "external_id": "26508834-c290-4354-9303-11c9b339a58a", + "last_seen_at": 1695393884, + "name": "Test User", + "phone": "123456789", + "signed_up_at": 1601493060 + }, + "JSON_ARRAY": {}, + "XML": {} + }, + "endpoint": "https://api.intercom.io/contacts/contact_id_2", + "files": {}, + "headers": { + "Accept": "application/json", + "Authorization": "Bearer dummyApiKey", + "Content-Type": "application/json", + "Intercom-Version": "2.10" + }, + "method": "PUT", + "params": {}, + "type": "REST", + "version": "1" + } + }, + { + "description": "Missing required parameters", + "input": { + "message": { + "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", + "channel": "mobile", + "context": { + "app": { + "build": "1.0", + "name": "Test_Example", + "namespace": "com.example.testapp", + "version": "1.0" + }, + "device": { + "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", + "manufacturer": "Apple", + "model": "iPhone", + "name": "iPod touch (7th generation)", + "type": "iOS" + }, + "library": { + "name": "test-ios-library", + "version": "1.0.7" + }, + "locale": "en-US", + "network": { + "bluetooth": false, + "carrier": "unavailable", + "cellular": false, + "wifi": true + }, + "os": { + "name": "iOS", + "version": "14.0" + }, + "screen": { + "density": 2, + "height": 320, + "width": 568 + }, + "timezone": "Asia/Kolkata", + "traits": { + "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", + "name": "Test Name", + "firstName": "Test", + "lastName": "Name", + "createdAt": "2020-09-30T19:11:00.337Z", + "phone": "123456789", + "key1": "value1" + }, + "userAgent": "unknown" + }, + "properties": { + "revenue": { + "amount": 1232, + "currency": "inr", + "test": 123 + }, + "price": { + "amount": 3000, + "currency": "USD" + } + }, + "event": "User Sign Up", + "integrations": { + "All": true + }, + "messageId": "1601493060-39010c49-e6e4-4626-a75c-0dbf1925c9e8", + "originalTimestamp": "2023-09-22T20:14:44.757+05:30", + "timestamp": "2023-09-22T20:14:44.757+05:30", + "type": "track" + }, + "destination": { + "Config": { + "apiKey": "dummyApiKey", + "apiServer": "eu", + "sendAnonymousId": true + } + } + }, + "output": { + "error": "Either of `email` or `userId` is required for Track call" + } + }, + { + "description": "User Sign up event", + "input": { + "message": { + "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", + "channel": "mobile", + "context": { + "app": { + "build": "1.0", + "name": "Test_Example", + "namespace": "com.example.testapp", + "version": "1.0" + }, + "device": { + "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", + "manufacturer": "Apple", + "model": "iPhone", + "name": "iPod touch (7th generation)", + "type": "iOS" + }, + "library": { + "name": "test-ios-library", + "version": "1.0.7" + }, + "locale": "en-US", + "network": { + "bluetooth": false, + "carrier": "unavailable", + "cellular": false, + "wifi": true + }, + "os": { + "name": "iOS", + "version": "14.0" + }, + "screen": { + "density": 2, + "height": 320, + "width": 568 + }, + "timezone": "Asia/Kolkata", + "traits": { + "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", + "name": "Test Name", + "firstName": "Test", + "lastName": "Name", + "createdAt": "2020-09-30T19:11:00.337Z", + "phone": "123456789", + "key1": "value1" + }, + "userAgent": "unknown" + }, + "properties": { + "revenue": { + "amount": 1232, + "currency": "inr", + "test": 123 + }, + "price": { + "amount": 3000, + "currency": "USD" + } + }, + "event": "User Sign Up", + "integrations": { + "All": true + }, + "messageId": "1601493060-39010c49-e6e4-4626-a75c-0dbf1925c9e8", + "originalTimestamp": "2023-09-22T20:14:44.757+05:30", + "timestamp": "2023-09-22T20:14:44.757+05:30", + "userId": "user@3", + "type": "track" + }, + "destination": { + "Config": { + "apiKey": "dummyApiKey", + "apiServer": "au", + "sendAnonymousId": true + } + } + }, + "output": { + "body": { + "FORM": {}, + "JSON": { + "created_at": 1695393884, + "event_name": "User Sign Up", + "metadata": { + "price": { + "amount": 3000, + "currency": "USD" + }, + "revenue": { + "amount": 1232, + "currency": "inr", + "test": 123 + } + }, + "user_id": "user@3" + }, + "JSON_ARRAY": {}, + "XML": {} + }, + "endpoint": "https://api.au.intercom.io/events", + "files": {}, + "headers": { + "Accept": "application/json", + "Authorization": "Bearer dummyApiKey", + "Content-Type": "application/json", + "Intercom-Version": "2.10" + }, + "method": "POST", + "params": {}, + "type": "REST", + "version": "1" + } + }, + { + "description": "Attach user with company", + "input": { + "message": { + "groupId": "rudderstack@1", + "traits": { + "employees": 450, + "plan": "basic", + "userId": "sdfrsdfsdfsf", + "email": "test@rudderlabs.com", + "name": "rudderstack", + "size": "50", + "industry": "IT", + "monthlySpend": "2131231", + "remoteCreatedAt": "1683017572", + "key1": "val1" + }, + "anonymousId": "anon@1", + "integrations": { + "All": true + }, + "type": "group", + "userId": "sdfrsdfsdfsf" + }, + "destination": { + "Config": { + "apiKey": "dummyApiKey", + "apiServer": "standard", + "sendAnonymousId": true + } + } + }, + "output": { + "body": { + "FORM": {}, + "JSON_ARRAY": {}, + "XML": {}, + "JSON": { + "id": "company@1" + } + }, + "endpoint": "https://api.intercom.io/contacts/contact_id_2/companies", + "files": {}, + "headers": { + "Accept": "application/json", + "Authorization": "Bearer dummyApiKey", + "Content-Type": "application/json", + "Intercom-Version": "2.10" + }, + "method": "POST", + "params": {}, + "type": "REST", + "version": "1" + } + } +] diff --git a/test/__tests__/intercom.test.js b/test/__tests__/intercom.test.js index 15e3b5e252a..c1cc1d255a9 100644 --- a/test/__tests__/intercom.test.js +++ b/test/__tests__/intercom.test.js @@ -21,7 +21,7 @@ describe(`${name} Tests`, () => { const output = await transformer.process(dataPoint.input); expect(output).toEqual(dataPoint.output); } catch (error) { - expect(error.message).toEqual(dataPoint.output.message); + expect(error.message).toEqual(dataPoint.output.error); } }); }); From fe15ecccb1ecb2eaec79190830d3419c3b16be03 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Tue, 26 Sep 2023 14:33:20 +0530 Subject: [PATCH 04/14] chore: code review changes --- src/v0/destinations/intercom/deleteUsers.js | 8 +++---- src/v0/destinations/intercom/util.js | 2 +- test/__tests__/data/intercom.json | 24 ++++++++++----------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/v0/destinations/intercom/deleteUsers.js b/src/v0/destinations/intercom/deleteUsers.js index 9241a229069..b1e35c9dbcb 100644 --- a/src/v0/destinations/intercom/deleteUsers.js +++ b/src/v0/destinations/intercom/deleteUsers.js @@ -12,9 +12,9 @@ const userDeletionHandler = async (userAttributes, config) => { if (!config) { throw new ConfigurationError('Config for deletion not present'); } - const { apiKey } = config; - if (!apiKey) { - throw new ConfigurationError('api key for deletion not present'); + const { accessToken } = config; + if (!accessToken) { + throw new ConfigurationError('access token for deletion not present'); } const validUserIds = []; userAttributes.forEach((userAttribute) => { @@ -31,7 +31,7 @@ const userDeletionHandler = async (userAttributes, config) => { }; const requestOptions = { headers: { - Authorization: `Bearer ${apiKey}`, + Authorization: `Bearer ${accessToken}`, Accept: JSON_MIME_TYPE, }, }; diff --git a/src/v0/destinations/intercom/util.js b/src/v0/destinations/intercom/util.js index 7ebe57e7c2e..d1092a66aaf 100644 --- a/src/v0/destinations/intercom/util.js +++ b/src/v0/destinations/intercom/util.js @@ -58,7 +58,7 @@ const validateTrack = (payload) => { */ const getHeaders = (destination) => ({ 'Content-Type': JSON_MIME_TYPE, - Authorization: `Bearer ${destination.Config.apiKey}`, + Authorization: `Bearer ${destination.Config.accessToken}`, Accept: JSON_MIME_TYPE, 'Intercom-Version': '2.10', }); diff --git a/test/__tests__/data/intercom.json b/test/__tests__/data/intercom.json index 40edb772e3d..327bd631ccb 100644 --- a/test/__tests__/data/intercom.json +++ b/test/__tests__/data/intercom.json @@ -20,7 +20,7 @@ }, "destination": { "Config": { - "apiKey": "ABC..." + "accessToken": "ABC..." } }, "metadata": { @@ -55,7 +55,7 @@ }, "destination": { "Config": { - "apiKey": "ABC..." + "accessToken": "ABC..." } } }, @@ -84,7 +84,7 @@ }, "destination": { "Config": { - "apiKey": "ABC..." + "accessToken": "ABC..." } } }, @@ -160,7 +160,7 @@ }, "destination": { "Config": { - "apiKey": "dummyApiKey", + "accessToken": "dummyaccessToken", "apiServer": "standard" } } @@ -192,7 +192,7 @@ "files": {}, "headers": { "Accept": "application/json", - "Authorization": "Bearer dummyApiKey", + "Authorization": "Bearer dummyaccessToken", "Content-Type": "application/json", "Intercom-Version": "2.10" }, @@ -267,7 +267,7 @@ }, "destination": { "Config": { - "apiKey": "dummyApiKey", + "accessToken": "dummyaccessToken", "apiServer": "standard", "sendAnonymousId": true } @@ -300,7 +300,7 @@ "files": {}, "headers": { "Accept": "application/json", - "Authorization": "Bearer dummyApiKey", + "Authorization": "Bearer dummyaccessToken", "Content-Type": "application/json", "Intercom-Version": "2.10" }, @@ -384,7 +384,7 @@ }, "destination": { "Config": { - "apiKey": "dummyApiKey", + "accessToken": "dummyaccessToken", "apiServer": "eu", "sendAnonymousId": true } @@ -469,7 +469,7 @@ }, "destination": { "Config": { - "apiKey": "dummyApiKey", + "accessToken": "dummyaccessToken", "apiServer": "au", "sendAnonymousId": true } @@ -501,7 +501,7 @@ "files": {}, "headers": { "Accept": "application/json", - "Authorization": "Bearer dummyApiKey", + "Authorization": "Bearer dummyaccessToken", "Content-Type": "application/json", "Intercom-Version": "2.10" }, @@ -537,7 +537,7 @@ }, "destination": { "Config": { - "apiKey": "dummyApiKey", + "accessToken": "dummyaccessToken", "apiServer": "standard", "sendAnonymousId": true } @@ -556,7 +556,7 @@ "files": {}, "headers": { "Accept": "application/json", - "Authorization": "Bearer dummyApiKey", + "Authorization": "Bearer dummyaccessToken", "Content-Type": "application/json", "Intercom-Version": "2.10" }, From 9c23c39d1b00a6ae4446cdff8d650a5a6d42d8c0 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Mon, 4 Dec 2023 15:20:19 +0530 Subject: [PATCH 05/14] feat(intercom): move it to router transform --- src/features.json | 3 ++- src/v0/destinations/intercom/transform.js | 10 ++++++++-- src/v0/destinations/intercom/util.js | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/features.json b/src/features.json index 27da6cfaf2c..acd9ee30b2b 100644 --- a/src/features.json +++ b/src/features.json @@ -61,7 +61,8 @@ "CLEVERTAP": true, "ORTTO": true, "ONE_SIGNAL": true, - "TIKTOK_AUDIENCE": true + "TIKTOK_AUDIENCE": true, + "INTERCOM": true }, "supportSourceTransformV1": true } diff --git a/src/v0/destinations/intercom/transform.js b/src/v0/destinations/intercom/transform.js index 0dcb3afbdd6..1894f8c4ebe 100644 --- a/src/v0/destinations/intercom/transform.js +++ b/src/v0/destinations/intercom/transform.js @@ -4,6 +4,7 @@ const { flattenJson, isDefinedAndNotNull, defaultRequestConfig, + simpleProcessRouterDest, getFieldValueFromMessage, removeUndefinedAndNullValues, } = require('../../util'); @@ -19,7 +20,7 @@ const { filterCustomAttributes, separateReservedAndRestMetadata, } = require('./util'); -const { InstrumentationError, NetworkError } = require('../../util/errorTypes'); +const { InstrumentationError, NetworkError } = require('@rudderstack/integrations-lib'); const responseBuilder = (payload, endpoint, requestMethod, destination) => { const response = defaultRequestConfig(); @@ -141,4 +142,9 @@ const process = async (event) => { return response; }; -module.exports = { process }; +const processRouterDest = async (inputs, reqMetadata) => { + const respList = await simpleProcessRouterDest(inputs, process, reqMetadata); + return respList; +}; + +module.exports = { process, processRouterDest }; diff --git a/src/v0/destinations/intercom/util.js b/src/v0/destinations/intercom/util.js index d1092a66aaf..60e23d18b86 100644 --- a/src/v0/destinations/intercom/util.js +++ b/src/v0/destinations/intercom/util.js @@ -27,7 +27,7 @@ const { MetadataTypes } = require('./config'); const { JSON_MIME_TYPE } = require('../../util/constant'); const { httpPOST } = require('../../../adapters/network'); const { MappedToDestinationKey } = require('../../../constants'); -const { InstrumentationError, NetworkError } = require('../../util/errorTypes'); +const { InstrumentationError, NetworkError } = require('@rudderstack/integrations-lib'); /** * Validated identify call payload From 1edac2252b0353b4ecc71a2c0f7fe37528ecb70a Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Mon, 4 Dec 2023 15:21:50 +0530 Subject: [PATCH 06/14] feat(intercom): move it to router transform --- src/v0/destinations/intercom/util.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/v0/destinations/intercom/util.js b/src/v0/destinations/intercom/util.js index 60e23d18b86..67a109a7df9 100644 --- a/src/v0/destinations/intercom/util.js +++ b/src/v0/destinations/intercom/util.js @@ -86,8 +86,8 @@ const getBaseEndpoint = (destination) => { * @returns */ const filterCustomAttributes = (payload, ReservedAttributes) => { - const { custom_attributes } = payload; - let customAttributes = cloneDeep(custom_attributes) || {}; + let { custom_attributes: customAttributes } = payload; + customAttributes = cloneDeep(customAttributes) || {}; if (customAttributes) { ReservedAttributes.forEach((trait) => { delete customAttributes[trait]; From 345853b90cfb10e539fe6848f47a9ec16d058801 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Thu, 7 Dec 2023 12:51:50 +0530 Subject: [PATCH 07/14] feat(intercom): upgrade intercom version from 1.4 to 2.10 --- .../v2}/destinations/intercom/config.js | 21 - .../destinations/intercom/procWorkflow.yaml | 172 +++ .../v2/destinations/intercom/rtWorkflow.yaml | 31 + .../v2/destinations/intercom/utils.js} | 186 +-- .../v2/destinations/intercom/utils.test.js} | 156 ++- src/constants/destinationCanonicalNames.js | 2 +- .../intercom/data/INTERCOMGroupConfig.json | 61 - .../intercom/data/INTERCOMIdentifyConfig.json | 81 -- .../intercom/data/INTERCOMTrackConfig.json | 45 - src/v0/destinations/intercom/transform.js | 150 --- test/__mocks__/axios.js | 10 - test/__mocks__/intercom.mock.js | 32 - test/__tests__/data/intercom.json | 569 -------- test/__tests__/intercom.test.js | 29 - .../intercom/dataDelivery/data.ts | 161 +-- .../destinations/intercom/deleteUsers/data.ts | 8 +- .../destinations/intercom/network.ts | 254 +++- .../destinations/intercom/processor/data.ts | 1188 +++++++++++++++++ .../destinations/intercom/router/data.ts | 302 +++++ 19 files changed, 2263 insertions(+), 1195 deletions(-) rename src/{v0 => cdk/v2}/destinations/intercom/config.js (71%) create mode 100644 src/cdk/v2/destinations/intercom/procWorkflow.yaml create mode 100644 src/cdk/v2/destinations/intercom/rtWorkflow.yaml rename src/{v0/destinations/intercom/util.js => cdk/v2/destinations/intercom/utils.js} (57%) rename src/{v0/destinations/intercom/util.test.js => cdk/v2/destinations/intercom/utils.test.js} (52%) delete mode 100644 src/v0/destinations/intercom/data/INTERCOMGroupConfig.json delete mode 100644 src/v0/destinations/intercom/data/INTERCOMIdentifyConfig.json delete mode 100644 src/v0/destinations/intercom/data/INTERCOMTrackConfig.json delete mode 100644 src/v0/destinations/intercom/transform.js delete mode 100644 test/__mocks__/intercom.mock.js delete mode 100644 test/__tests__/data/intercom.json delete mode 100644 test/__tests__/intercom.test.js create mode 100644 test/integrations/destinations/intercom/processor/data.ts create mode 100644 test/integrations/destinations/intercom/router/data.ts diff --git a/src/v0/destinations/intercom/config.js b/src/cdk/v2/destinations/intercom/config.js similarity index 71% rename from src/v0/destinations/intercom/config.js rename to src/cdk/v2/destinations/intercom/config.js index b33fb3395e8..96b7abd2802 100644 --- a/src/v0/destinations/intercom/config.js +++ b/src/cdk/v2/destinations/intercom/config.js @@ -1,5 +1,3 @@ -const { getMappingConfig } = require('../../util'); - const BASE_ENDPOINT = 'https://api.intercom.io'; const BASE_EU_ENDPOINT = 'https://api.eu.intercom.io'; const BASE_AU_ENDPOINT = 'https://api.au.intercom.io'; @@ -7,23 +5,6 @@ const BASE_AU_ENDPOINT = 'https://api.au.intercom.io'; const SEARCH_CONTACT_ENDPOINT = 'contacts/search'; const CREATE_OR_UPDATE_COMPANY_ENDPOINT = 'companies'; -const ConfigCategory = { - TRACK: { - endpoint: 'events', - name: 'INTERCOMTrackConfig', - }, - IDENTIFY: { - endpoint: 'contacts', - name: 'INTERCOMIdentifyConfig', - }, - GROUP: { - endpoint: 'contacts/{id}/companies', - name: 'INTERCOMGroupConfig', - }, -}; - -const MappingConfig = getMappingConfig(ConfigCategory, __dirname); - const ReservedUserAttributes = [ 'userId', 'role', @@ -65,8 +46,6 @@ const MetadataTypes = { richLink: ['url', 'value'], monetaryAmount: ['amount', ' module.exports = { BASE_ENDPOINT, MetadataTypes, - MappingConfig, - ConfigCategory, BASE_EU_ENDPOINT, BASE_AU_ENDPOINT, ReservedUserAttributes, diff --git a/src/cdk/v2/destinations/intercom/procWorkflow.yaml b/src/cdk/v2/destinations/intercom/procWorkflow.yaml new file mode 100644 index 00000000000..2c9a904bef1 --- /dev/null +++ b/src/cdk/v2/destinations/intercom/procWorkflow.yaml @@ -0,0 +1,172 @@ +bindings: + - name: EventType + path: ../../../../constants + - path: ./utils + exportAll: true + - path: ./config + exportAll: true + - path: ../../bindings/jsontemplate + exportAll: true + - name: defaultRequestConfig + path: ../../../../v0/util + - name: removeUndefinedAndNullValues + path: ../../../../v0/util + - name: getFieldValueFromMessage + path: ../../../../v0/util + - name: isDefinedAndNotNull + path: ../../../../v0/util + - name: addExternalIdToTraits + path: ../../../../v0/util + +steps: + - name: checkIfProcessed + condition: .message.statusCode + template: | + $.batchMode ? .message.body.JSON : .message; + onComplete: return + + - name: messageType + template: | + .message.type.toLowerCase(); + + - name: validateInput + template: | + let messageType = $.outputs.messageType; + $.assert(messageType, "message Type is not present. Aborting"); + $.assert(messageType in {{$.EventType.([.IDENTIFY, .TRACK, .GROUP])}}, "message type " + messageType + " is not supported"); + $.assertConfig(.destination.Config.accessToken, "Access Token is not present. Aborting"); + + - name: rEtlPayload + condition: .message.context.mappedToDestination === true + template: | + $.addExternalIdToTraits(.message); + const payload = $.getFieldValueFromMessage(.message, "traits"); + payload; + + - name: searchContact + condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} || $.outputs.messageType === {{$.EventType.GROUP}} + template: | + const contactId = await $.searchContact(.message, .destination); + contactId; + + - name: identifyEtlPayload + condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} && !.message.context.mappedToDestination + template: | + const payload = .message.({ + external_id: {{{{$.getGenericPaths("userIdOnly")}}}}, + email: {{{{$.getGenericPaths("email")}}}}, + phone: {{{{$.getGenericPaths("phone")}}}}, + avatar: {{{{$.getGenericPaths("avatar")}}}}, + last_seen_at: $.toSeconds({{{{$.getGenericPaths("timestamp")}}}}), + role: .traits.role || .context.traits.role, + signed_up_at: $.toSeconds(.traits.createdAt || .context.traits.createdAt), + owner_id: .traits.ownerId || .context.traits.ownerId ? Number(.traits.ownerId || .context.traits.ownerId) : undefined, + unsubscribed_from_emails: .traits.unsubscribedFromEmails || .context.traits.unsubscribedFromEmails, + custom_attributes: .context.traits || .traits + }) + payload.name = $.getName(.message); + payload.custom_attributes = $.getCustomAttributes(payload, $.ReservedUserAttributes); + payload.external_id = !(payload.external_id) && .destination.Config.sendAnonymousId && .message.anonymousId ? .message.anonymousId : payload.external_id; + payload; + + - name: prepareIdentifyPayload + condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} + template: | + const endpoint = $.getBaseEndpoint(.destination) + "/" + "contacts"; + $.context.payload = .message.context.mappedToDestination === true ? $.outputs.rEtlPayload : $.outputs.identifyEtlPayload; + $.context.requestMethod = $.outputs.searchContact ? 'PUT' : 'POST'; + $.context.endpoint = $.outputs.searchContact ? endpoint + "/" + $.outputs.searchContact : endpoint; + $.context.payload = $.removeUndefinedAndNullValues($.context.payload); + + - name: validateIdentifyPayload + condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} + template: | + $.assert($.context.payload.external_id || $.context.payload.email, "Either email or userId is required for Identify call"); + + - name: trackEtlPayload + condition: $.outputs.messageType === {{$.EventType.TRACK}} && !.message.context.mappedToDestination + template: | + let payload = .message.({ + event_name: .event, + created_at: $.toSeconds({{{{$.getGenericPaths("timestamp")}}}}), + user_id: {{{{$.getGenericPaths("userIdOnly")}}}}, + email: {{{{$.getGenericPaths("email")}}}}, + id: .properties.id || .traits.id, + metadata: .properties + }) + payload = $.addMetadataToPayload(payload); + payload.user_id = !(payload.user_id) && .destination.Config.sendAnonymousId && .message.anonymousId ? .message.anonymousId : payload.user_id; + payload; + + - name: prepareTrackPayload + condition: $.outputs.messageType === {{$.EventType.TRACK}} + template: | + $.context.payload = .message.context.mappedToDestination === true ? $.outputs.rEtlPayload : $.outputs.trackEtlPayload; + $.context.requestMethod = 'POST'; + $.context.endpoint = $.getBaseEndpoint(.destination) + "/" + "events"; + $.context.payload = $.removeUndefinedAndNullValues($.context.payload); + + - name: validateTrackPayload + condition: $.outputs.messageType === {{$.EventType.TRACK}} + template: | + $.assert($.context.payload.event_name, "Event name is required for track call"); + $.assert($.context.payload.user_id || $.context.payload.email, "Either email or userId is required for Track call"); + + - name: groupEtlPayload + condition: $.outputs.messageType === {{$.EventType.GROUP}} && !.message.context.mappedToDestination + template: | + const payload = .message.({ + company_id: {{{{$.getGenericPaths("groupId")}}}}, + name: {{{{$.getGenericPaths("name")}}}}, + website: {{{{$.getGenericPaths("website")}}}}, + plan: .traits.plan || .context.traits.plan, + size: Number(.traits.size || .context.traits.size), + industry: .traits.industry || .context.traits.industry, + monthly_spend: .traits.monthlySpend || .context.traits.monthlySpend ? Number(.traits.monthlySpend || .context.traits.monthlySpend) : undefined, + remote_created_at: .traits.remoteCreatedAt || .context.traits.remoteCreatedAt ? Number(.traits.remoteCreatedAt || .context.traits.remoteCreatedAt) : undefined, + custom_attributes: .traits + }) + payload.custom_attributes = $.getCustomAttributes(payload, $.ReservedCompanyAttributes); + payload; + + - name: prepareAddUserToCompanyPayload + condition: $.outputs.messageType === {{$.EventType.GROUP}} && $.isDefinedAndNotNull($.outputs.searchContact) + template: | + $.assert(.message.groupId, "groupId is required for group call"); + let payload = .message.context.mappedToDestination === true ? $.outputs.rEtlPayload : $.outputs.groupEtlPayload; + const contactId = $.outputs.searchContact; + const companyId = await $.createOrUpdateCompany(payload, .destination); + $.assert(companyId, "Unable to add user to company"); + payload.requestBodyJson = { + id: companyId + } + payload.endpoint = $.getBaseEndpoint(.destination) + "/" + "contacts" + "/" + contactId + "/" + "companies"; + payload; + + - name: prepareCreateOrUpdateCompanyPayload + condition: $.outputs.messageType === {{$.EventType.GROUP}} && !$.isDefinedAndNotNull($.outputs.searchContact) + template: | + $.assert(.message.groupId, "groupId is required for group call"); + let payload = { + requestBodyJson: .message.context.mappedToDestination === true ? $.outputs.rEtlPayload : $.outputs.groupEtlPayload + } + payload.endpoint = $.getBaseEndpoint(.destination) + "/" + $.CREATE_OR_UPDATE_COMPANY_ENDPOINT; + payload; + + - name: prepareGroupPayload + condition: $.outputs.messageType === {{$.EventType.GROUP}} + template: | + $.context.payload = $.isDefinedAndNotNull($.outputs.searchContact) ? $.outputs.prepareAddUserToCompanyPayload.requestBodyJson : $.outputs.prepareCreateOrUpdateCompanyPayload.requestBodyJson; + $.context.requestMethod = 'POST'; + $.context.endpoint = $.isDefinedAndNotNull($.outputs.searchContact) ? $.outputs.prepareAddUserToCompanyPayload.endpoint : $.outputs.prepareCreateOrUpdateCompanyPayload.endpoint; + $.context.payload = $.removeUndefinedAndNullValues($.context.payload); + + - name: buildResponseForProcessTransformation + description: build response + template: | + const response = $.defaultRequestConfig(); + response.body.JSON = $.removeUndefinedAndNullValues($.context.payload); + response.endpoint = $.context.endpoint; + response.method = $.context.requestMethod; + response.headers = $.getHeaders(.destination); + response; diff --git a/src/cdk/v2/destinations/intercom/rtWorkflow.yaml b/src/cdk/v2/destinations/intercom/rtWorkflow.yaml new file mode 100644 index 00000000000..a907403ca49 --- /dev/null +++ b/src/cdk/v2/destinations/intercom/rtWorkflow.yaml @@ -0,0 +1,31 @@ +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 + template: | + $.outputs.transform#idx.output.({ + "batchedRequest": ., + "batched": false, + "destination": ^[idx].destination, + "metadata": ^[idx].metadata[], + "statusCode": 200 + })[] + - name: failedEvents + template: | + $.outputs.transform#idx.error.( + $.handleRtTfSingleEventError(^[idx], .originalError ?? ., {}) + )[] + - name: finalPayload + template: | + [...$.outputs.successfulEvents, ...$.outputs.failedEvents] \ No newline at end of file diff --git a/src/v0/destinations/intercom/util.js b/src/cdk/v2/destinations/intercom/utils.js similarity index 57% rename from src/v0/destinations/intercom/util.js rename to src/cdk/v2/destinations/intercom/utils.js index 67a109a7df9..f66b2c0c381 100644 --- a/src/v0/destinations/intercom/util.js +++ b/src/cdk/v2/destinations/intercom/utils.js @@ -1,58 +1,30 @@ -const get = require('get-value'); -const cloneDeep = require('lodash/cloneDeep'); +const { NetworkError } = require('@rudderstack/integrations-lib'); +const tags = require('../../../../v0/util/tags'); +const { httpPOST } = require('../../../../adapters/network'); const { - BASE_ENDPOINT, - MappingConfig, - BASE_EU_ENDPOINT, - BASE_AU_ENDPOINT, - SEARCH_CONTACT_ENDPOINT, - CREATE_OR_UPDATE_COMPANY_ENDPOINT, -} = require('./config'); + processAxiosResponse, + getDynamicErrorType, +} = require('../../../../adapters/utils/networkUtils'); const { flattenJson, - constructPayload, getIntegrationsObj, isDefinedAndNotNull, isHttpStatusSuccess, - addExternalIdToTraits, getFieldValueFromMessage, removeUndefinedAndNullValues, -} = require('../../util'); +} = require('../../../../v0/util'); +const { JSON_MIME_TYPE } = require('../../../../v0/util/constant'); const { - processAxiosResponse, - getDynamicErrorType, -} = require('../../../adapters/utils/networkUtils'); -const tags = require('../../util/tags'); -const { MetadataTypes } = require('./config'); -const { JSON_MIME_TYPE } = require('../../util/constant'); -const { httpPOST } = require('../../../adapters/network'); -const { MappedToDestinationKey } = require('../../../constants'); -const { InstrumentationError, NetworkError } = require('@rudderstack/integrations-lib'); - -/** - * Validated identify call payload - * @param {*} payload - */ -const validateIdentify = (payload) => { - if (!payload.email && !payload.external_id) { - throw new InstrumentationError( - 'Either of `email` or `userId` is required for an Identify call', - ); - } -}; - -/** - * Validates track call payload - * @param {*} payload - */ -const validateTrack = (payload) => { - if (!payload.user_id && !payload.email) { - throw new InstrumentationError('Either of `email` or `userId` is required for Track call'); - } -}; + BASE_ENDPOINT, + MetadataTypes, + BASE_EU_ENDPOINT, + BASE_AU_ENDPOINT, + SEARCH_CONTACT_ENDPOINT, + CREATE_OR_UPDATE_COMPANY_ENDPOINT, +} = require('./config'); /** - * Returns headers + * Returns destination request headers * @param {*} destination * @returns */ @@ -64,7 +36,7 @@ const getHeaders = (destination) => ({ }); /** - * Returns base endpoint + * Returns destination request base endpoint * @param {*} destination * @returns */ @@ -80,64 +52,68 @@ const getBaseEndpoint = (destination) => { }; /** - * Returns custom attributes for identify and group calls - * @param {*} payload - * @param {*} ReservedAttributes + * Returns contact lookup field + * @param {*} message * @returns */ -const filterCustomAttributes = (payload, ReservedAttributes) => { - let { custom_attributes: customAttributes } = payload; - customAttributes = cloneDeep(customAttributes) || {}; - if (customAttributes) { - ReservedAttributes.forEach((trait) => { - delete customAttributes[trait]; - }); - } - if (isDefinedAndNotNull(customAttributes) && Object.keys(customAttributes).length > 0) { - customAttributes = flattenJson(customAttributes); +const getLookUpField = (message) => { + let lookupField = 'email'; + const integrationsObj = getIntegrationsObj(message, 'INTERCOM'); + if (integrationsObj && isDefinedAndNotNull(integrationsObj.lookup)) { + lookupField = integrationsObj.lookup; } - return customAttributes; + return lookupField; }; /** - * Returns transformed payload + * Returns the value of name field * @param {*} message - * @param {*} category * @returns */ -const getPayload = (message, category) => { - let payload; - if (get(message, MappedToDestinationKey)) { - addExternalIdToTraits(message); - payload = getFieldValueFromMessage(message, 'traits'); - } else { - payload = constructPayload(message, MappingConfig[category.name]); +const getName = (message) => { + const name = message?.traits?.name || message?.context?.traits?.name; + if (name) return name; + const firstName = getFieldValueFromMessage(message, 'firstName'); + const lastName = getFieldValueFromMessage(message, 'lastName'); + if (firstName && lastName) { + return `${firstName} ${lastName}`; + } + + if (firstName || lastName) { + return firstName || lastName; } - return payload; + return undefined; }; /** - * Returns lookupField - * @param {*} message + * Returns custom attributes for identify and group calls (for contact and company in intercom) + * @param {*} payload + * @param {*} ReservedAttributes * @returns */ -const getLookUpField = (message) => { - const integrationsObj = getIntegrationsObj(message, 'INTERCOM'); - let lookupField = 'email'; - if (integrationsObj && integrationsObj.lookup && isDefinedAndNotNull(integrationsObj.lookup)) { - lookupField = integrationsObj.lookup; +const getCustomAttributes = (payload, ReservedAttributes) => { + let { custom_attributes: customAttributes } = payload; + if (customAttributes && typeof customAttributes === 'object') { + ReservedAttributes.forEach((trait) => { + if (customAttributes[trait]) delete customAttributes[trait]; + }); + + if (isDefinedAndNotNull(customAttributes) && Object.keys(customAttributes).length > 0) { + customAttributes = flattenJson(customAttributes); + } } - return lookupField; + return customAttributes; }; /** - * Returns contact id based on lookup + * Api call to search contact in intercom to returns id of contact + * Ref doc : https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Contacts/SearchContacts/ * @param {*} message * @param {*} destination - * @param {*} lookupField * @returns */ -const fetchContactId = async (message, destination, lookupField) => { +const searchContact = async (message, destination) => { + const lookupField = getLookUpField(message); const lookupFieldValue = getFieldValueFromMessage(message, lookupField); const data = JSON.stringify({ query: { @@ -160,7 +136,6 @@ const fetchContactId = async (message, destination, lookupField) => { destType: 'intercom', feature: 'transformation', }); - const processedUserResponse = processAxiosResponse(response); if (isHttpStatusSuccess(processedUserResponse.status)) { return processedUserResponse.response?.data.length > 0 @@ -168,11 +143,19 @@ const fetchContactId = async (message, destination, lookupField) => { : null; } - return null; + throw new NetworkError( + `Unable to search contact due to : ${JSON.stringify(processedUserResponse?.response?.data)}`, + processedUserResponse?.status, + { + [tags]: getDynamicErrorType(processedUserResponse?.status), + }, + processedUserResponse, + ); }; /** - * Function to create or update company + * Api call to create or update companies in intercom + * Ref doc : https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Companies/createOrUpdateCompany/ * @param {*} payload * @param {*} destination * @returns @@ -195,17 +178,19 @@ const createOrUpdateCompany = async (payload, destination) => { } throw new NetworkError( - `Unable to Create Company : ${processedResponse?.response?.errors[0]}`, + `Unable to Create or Update Company due to : ${JSON.stringify( + processedResponse?.response?.errors, + )}`, processedResponse?.status, { - [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(processedResponse?.status), + [tags]: getDynamicErrorType(processedResponse?.status), }, processedResponse, ); }; /** - * Separates reserved metadata from rest of the metadata based on the metadata types + * Returns metadata object * @param {*} metadata * @returns */ @@ -232,15 +217,34 @@ const separateReservedAndRestMetadata = (metadata) => { return { reservedMetadata, restMetadata }; }; +/** + * Returns final payload with metadata + * @param {*} payload + * @returns + */ +const addMetadataToPayload = (payload) => { + let finalPayload = payload; + if (finalPayload.metadata) { + // reserved metadata contains JSON objects that does not requires flattening + const { reservedMetadata, restMetadata } = separateReservedAndRestMetadata( + finalPayload.metadata, + ); + finalPayload = { + ...finalPayload, + metadata: { ...reservedMetadata, ...flattenJson(restMetadata) }, + }; + } + return finalPayload; +}; + module.exports = { + getName, getHeaders, - getPayload, - validateTrack, - fetchContactId, + searchContact, getLookUpField, getBaseEndpoint, - validateIdentify, + getCustomAttributes, + addMetadataToPayload, createOrUpdateCompany, - filterCustomAttributes, separateReservedAndRestMetadata, }; diff --git a/src/v0/destinations/intercom/util.test.js b/src/cdk/v2/destinations/intercom/utils.test.js similarity index 52% rename from src/v0/destinations/intercom/util.test.js rename to src/cdk/v2/destinations/intercom/utils.test.js index 23b905e073d..61613c1b6c6 100644 --- a/src/v0/destinations/intercom/util.test.js +++ b/src/cdk/v2/destinations/intercom/utils.test.js @@ -1,4 +1,12 @@ -const { getLookUpField, getBaseEndpoint, separateReservedAndRestMetadata } = require('./util'); +const { + getName, + getHeaders, + getLookUpField, + getBaseEndpoint, + getCustomAttributes, + addMetadataToPayload, + separateReservedAndRestMetadata, +} = require('./utils'); const { BASE_ENDPOINT, BASE_EU_ENDPOINT, BASE_AU_ENDPOINT } = require('./config'); describe('separateReservedAndRestMetadata utility test', () => { @@ -176,9 +184,18 @@ describe('separateReservedAndRestMetadata utility test', () => { }); }); -describe('getBaseEndpoint', () => { - // Returns BASE_EU_ENDPOINT when apiServer is 'eu' - it('should return BASE_EU_ENDPOINT when apiServer is "eu"', () => { +describe('getBaseEndpoint utility test', () => { + it('Should return BASE_ENDPOINT when destination.Config.apiServer is not "eu" or "au"', () => { + const destination = { + Config: { + apiServer: 'us', + }, + }; + const result = getBaseEndpoint(destination); + expect(result).toBe(BASE_ENDPOINT); + }); + + it('Should return BASE_EU_ENDPOINT when destination.Config.apiServer is "eu"', () => { const destination = { Config: { apiServer: 'eu', @@ -188,8 +205,7 @@ describe('getBaseEndpoint', () => { expect(result).toBe(BASE_EU_ENDPOINT); }); - // Returns BASE_AU_ENDPOINT when apiServer is 'au' - it('should return BASE_AU_ENDPOINT when apiServer is "au"', () => { + it('Should return BASE_AU_ENDPOINT when destination.Config.apiServer is "au"', () => { const destination = { Config: { apiServer: 'au', @@ -199,11 +215,10 @@ describe('getBaseEndpoint', () => { expect(result).toBe(BASE_AU_ENDPOINT); }); - // Returns BASE_ENDPOINT when apiServer is standard - it('should return BASE_ENDPOINT when apiServer is standard', () => { + it('Should return BASE_ENDPOINT when destination.Config.apiServer is null', () => { const destination = { Config: { - apiServer: 'standard', + apiServer: null, }, }; const result = getBaseEndpoint(destination); @@ -211,24 +226,125 @@ describe('getBaseEndpoint', () => { }); }); -// Generated by CodiumAI +describe('getHeaders utility test', () => { + it('Should return an object with the correct headers', () => { + const destination = { + Config: { + accessToken: 'testAccessToken', + }, + }; -describe('getLookUpField', () => { - // Returns 'email' by default - it('should return "email" by default', () => { + const expectedHeaders = { + 'Content-Type': 'application/json', + Authorization: `Bearer ${destination.Config.accessToken}`, + Accept: 'application/json', + 'Intercom-Version': '2.10', + }; + const headers = getHeaders(destination); + expect(headers).toEqual(expectedHeaders); + }); +}); + +describe('getLookUpField utility test', () => { + it('Should return email as default lookup field when no integration object is found', () => { const message = {}; - expect(getLookUpField(message)).toBe('email'); + const result = getLookUpField(message); + expect(result).toBe('email'); + }); +}); + +describe('getName utility test', () => { + it('Should return the concatenation of firstName and lastName fields when both exist', () => { + const message = { + context: { + traits: { + firstName: 'John', + lastName: 'Doe', + }, + }, + }; + expect(getName(message)).toBe('John Doe'); + }); + + it('Should return the firstName field when only firstName exists', () => { + const message = { + context: { + traits: { + firstName: 'John', + }, + }, + }; + expect(getName(message)).toBe('John'); }); - // Returns 'lookup' field from integrationsObj if defined - it('should return "lookup" field from integrationsObj if defined', () => { + it('Should return the lastName field when only lastName exists', () => { const message = { - integrations: { - intercom: { - lookup: 'phone', + context: { + traits: { + lastName: 'Doe', }, }, }; - expect(getLookUpField(message)).toBe('phone'); + expect(getName(message)).toBe('Doe'); + }); + + it('Should return undefined when both message.traits and message.context.traits are undefined', () => { + const message = {}; + expect(getName(message)).toBeUndefined(); + }); +}); + +describe('getCustomAttributes utility test', () => { + it('Should return an empty object when all custom attributes are reserved attributes', () => { + const payload = { custom_attributes: { attribute1: 'value1', attribute2: 'value2' } }; + const ReservedAttributes = ['attribute1', 'attribute2']; + const result = getCustomAttributes(payload, ReservedAttributes); + expect(result).toEqual({}); + }); + + it('Should return a flattened object when custom attributes are not null, not reserved attributes and nested', () => { + const payload = { + custom_attributes: { attribute1: 'value1', attribute2: { nestedAttribute: 'nestedValue' } }, + }; + const ReservedAttributes = ['attribute3']; + const result = getCustomAttributes(payload, ReservedAttributes); + expect(result).toEqual({ attribute1: 'value1', 'attribute2.nestedAttribute': 'nestedValue' }); + }); + + it('Should return null when custom_attributes is null', () => { + const payload = { custom_attributes: null }; + const ReservedAttributes = []; + const result = getCustomAttributes(payload, ReservedAttributes); + expect(result).toBeNull(); + }); +}); + +describe('addMetadataToPayload utility test', () => { + it('Should return the same payload if metadata is present but empty', () => { + const payload = { data: 'test', metadata: {} }; + const result = addMetadataToPayload(payload); + expect(result).toEqual(payload); + }); + + it('should add flattened metadata to payload if metadata is present and not empty', () => { + const payload = { + data: 'test', + metadata: { + amount: 30, + currency: 'USD', + url: 'https//test.com', + restData: { source: 'rudderStack' }, + }, + }; + const result = addMetadataToPayload(payload); + expect(result).toEqual({ + data: 'test', + metadata: { + amount: 30, + currency: 'USD', + url: 'https//test.com', + 'restData.source': 'rudderStack', + }, + }); }); }); diff --git a/src/constants/destinationCanonicalNames.js b/src/constants/destinationCanonicalNames.js index 44e52101e27..f019cc9fec9 100644 --- a/src/constants/destinationCanonicalNames.js +++ b/src/constants/destinationCanonicalNames.js @@ -141,7 +141,7 @@ const DestCanonicalNames = { 'TWITTER_ADS', ], BRAZE: ['BRAZE', 'Braze', 'braze'], - INTERCOM: ['INTERCOM', 'intercom','Intercom'], + INTERCOM: ['INTERCOM', 'intercom', 'Intercom'], }; module.exports = { DestHandlerMap, DestCanonicalNames }; diff --git a/src/v0/destinations/intercom/data/INTERCOMGroupConfig.json b/src/v0/destinations/intercom/data/INTERCOMGroupConfig.json deleted file mode 100644 index ba495bb1752..00000000000 --- a/src/v0/destinations/intercom/data/INTERCOMGroupConfig.json +++ /dev/null @@ -1,61 +0,0 @@ -[ - { - "destKey": "company_id", - "sourceKeys": "groupId", - "required": true - }, - { - "destKey": "name", - "sourceKeys": "name", - "sourceFromGenericMap": true, - "required": false - }, - { - "destKey": "plan", - "sourceKeys": ["traits.plan","context.traits.plan"], - "required": false - }, - { - "destKey": "size", - "sourceKeys": ["traits.size","context.traits.size"], - "metadata": { - "type": "toNumber" - }, - "required": false - }, - { - "destKey": "website", - "sourceKeys": "website", - "sourceFromGenericMap": true, - "required": false - }, - { - "destKey": "industry", - "sourceKeys": ["traits.industry","context.traits.industry"], - "required": false - }, - { - "destKey": "monthly_spend", - "sourceKeys": ["traits.monthlySpend","context.traits.monthlySpend"], - "metadata": { - "type": "toNumber" - }, - "required": false - }, - { - "destKey": "remote_created_at", - "sourceKeys": ["traits.remoteCreatedAt","context.traits.remoteCreatedAt"], - "metadata": { - "type": "toNumber" - }, - "required": false - }, - { - "destKey": "custom_attributes", - "sourceKeys": [ - "traits", - "context.traits" - ], - "required": false - } -] diff --git a/src/v0/destinations/intercom/data/INTERCOMIdentifyConfig.json b/src/v0/destinations/intercom/data/INTERCOMIdentifyConfig.json deleted file mode 100644 index f53e5edb260..00000000000 --- a/src/v0/destinations/intercom/data/INTERCOMIdentifyConfig.json +++ /dev/null @@ -1,81 +0,0 @@ -[ - { - "destKey": "role", - "sourceKeys": [ - "traits.role", - "context.traits.role" - ], - "required": false - }, - { - "destKey": "external_id", - "sourceKeys": [ - "userId", - "traits.userId", - "context.traits.userId" - ], - "required": false - }, - { - "destKey": "email", - "sourceKeys": "email", - "sourceFromGenericMap": true, - "required": false - }, - { - "destKey": "phone", - "sourceKeys": "phone", - "sourceFromGenericMap": true, - "required": false - }, - { - "destKey": "name", - "sourceKeys": "name", - "sourceFromGenericMap": true, - "required": false - }, - { - "destKey": "avatar", - "sourceKeys": "avatar", - "sourceFromGenericMap": true, - "required": false - }, - { - "destKey": "signed_up_at", - "sourceKeys": ["traits.createdAt", "context.traits.createdAt"], - "required": false, - "metadata": { - "type": "secondTimestamp" - } - }, - { - "destKey": "last_seen_at", - "sourceKeys": "timestamp", - "sourceFromGenericMap": true, - "required": false, - "metadata": { - "type": "secondTimestamp" - } - }, - { - "destKey": "owner_id", - "sourceKeys": ["traits.ownerId", "context.traits.ownerId"], - "required": false, - "metadata": { - "type": "toNumber" - } - }, - { - "destKey": "unsubscribed_from_emails", - "sourceKeys": [ - "traits.unsubscribedFromEmails", - "context.traits.unsubscribedFromEmails" - ], - "required": false - }, - { - "destKey": "custom_attributes", - "sourceKeys": ["traits", "context.traits"], - "required": false - } -] diff --git a/src/v0/destinations/intercom/data/INTERCOMTrackConfig.json b/src/v0/destinations/intercom/data/INTERCOMTrackConfig.json deleted file mode 100644 index 2a6f7f0267c..00000000000 --- a/src/v0/destinations/intercom/data/INTERCOMTrackConfig.json +++ /dev/null @@ -1,45 +0,0 @@ -[ - { - "destKey": "event_name", - "sourceKeys": "event", - "required": true - }, - { - "destKey": "created_at", - "sourceKeys": "timestamp", - "sourceFromGenericMap": true, - "required": true, - "metadata": { - "type": "secondTimestamp" - } - }, - { - "destKey": "user_id", - "sourceKeys": [ - "userId", - "traits.userId", - "context.traits.userId" - ], - "required": false - }, - { - "destKey": "id", - "sourceKeys": [ - "traits.id", - "properties.id", - "context.traits.id" - ], - "required": false - }, - { - "destKey": "email", - "sourceKeys": "email", - "sourceFromGenericMap": true, - "required": false - }, - { - "destKey": "metadata", - "sourceKeys": "properties", - "required": false - } -] \ No newline at end of file diff --git a/src/v0/destinations/intercom/transform.js b/src/v0/destinations/intercom/transform.js deleted file mode 100644 index 1894f8c4ebe..00000000000 --- a/src/v0/destinations/intercom/transform.js +++ /dev/null @@ -1,150 +0,0 @@ -const { EventType } = require('../../../constants'); -const { ConfigCategory, ReservedUserAttributes, ReservedCompanyAttributes } = require('./config'); -const { - flattenJson, - isDefinedAndNotNull, - defaultRequestConfig, - simpleProcessRouterDest, - getFieldValueFromMessage, - removeUndefinedAndNullValues, -} = require('../../util'); -const { - getHeaders, - getPayload, - validateTrack, - fetchContactId, - getLookUpField, - getBaseEndpoint, - validateIdentify, - createOrUpdateCompany, - filterCustomAttributes, - separateReservedAndRestMetadata, -} = require('./util'); -const { InstrumentationError, NetworkError } = require('@rudderstack/integrations-lib'); - -const responseBuilder = (payload, endpoint, requestMethod, destination) => { - const response = defaultRequestConfig(); - response.method = requestMethod; - response.endpoint = endpoint; - response.headers = getHeaders(destination); - response.body.JSON = removeUndefinedAndNullValues(payload); - return response; -}; - -const identifyResponseBuilder = async (message, destination) => { - const payload = getPayload(message, ConfigCategory.IDENTIFY); - validateIdentify(payload); - - if (payload.name === undefined || payload.name === '') { - const firstName = getFieldValueFromMessage(message, 'firstName'); - const lastName = getFieldValueFromMessage(message, 'lastName'); - if (firstName && lastName) { - payload.name = `${firstName} ${lastName}`; - } else { - payload.name = firstName || lastName; - } - } - - payload.custom_attributes = filterCustomAttributes(payload, ReservedUserAttributes); - - let endpoint; - let requestMethod; - const lookupField = getLookUpField(message); - const contactId = await fetchContactId(message, destination, lookupField); - - const baseEndPoint = getBaseEndpoint(destination); - if (contactId) { - requestMethod = 'PUT'; - endpoint = `${baseEndPoint}/${ConfigCategory.IDENTIFY.endpoint}/${contactId}`; - } else { - requestMethod = 'POST'; - endpoint = `${baseEndPoint}/${ConfigCategory.IDENTIFY.endpoint}`; - } - - const { sendAnonymousId } = destination.Config; - - if (!payload.external_id && sendAnonymousId && message.anonymousId) { - payload.external_id = message.anonymousId; - } - - return responseBuilder(payload, endpoint, requestMethod, destination); -}; - -const trackResponseBuilder = (message, destination) => { - let payload = getPayload(message, ConfigCategory.TRACK); - validateTrack(payload); - - // pass only string, number, boolean properties - if (payload.metadata) { - // reserved metadata contains JSON objects that does not requires flattening - const { reservedMetadata, restMetadata } = separateReservedAndRestMetadata(payload.metadata); - payload = { ...payload, metadata: { ...reservedMetadata, ...flattenJson(restMetadata) } }; - } - - const baseEndPoint = getBaseEndpoint(destination); - const endpoint = `${baseEndPoint}/${ConfigCategory.TRACK.endpoint}`; - - const { sendAnonymousId } = destination.Config; - if (!payload.user_id && sendAnonymousId && message.anonymousId) { - payload.user_id = message.anonymousId; - } - - return responseBuilder(payload, endpoint, 'POST', destination); -}; - -const groupResponseBuilder = async (message, destination) => { - const payload = getPayload(message, ConfigCategory.GROUP); - payload.custom_attributes = filterCustomAttributes(payload, ReservedCompanyAttributes); - const companyId = await createOrUpdateCompany(payload, destination); - const lookupField = getLookUpField(message); - const contactId = await fetchContactId(message, destination, lookupField); - - if (!isDefinedAndNotNull(contactId)) { - throw new NetworkError(`Can't find any user with given lookupField : ${lookupField}`); - } - - if (isDefinedAndNotNull(companyId) && isDefinedAndNotNull(contactId)) { - const baseEndPoint = getBaseEndpoint(destination); - let endpoint = `${baseEndPoint}/${ConfigCategory.GROUP.endpoint}`; - endpoint = endpoint.replace('{id}', contactId); - return responseBuilder({ id: companyId }, endpoint, 'POST', destination); - } - - throw new InstrumentationError("Can't attach user with company"); -}; - -const processSingleMessage = async (message, destination) => { - if (!message.type) { - throw new InstrumentationError('Message Type is not present. Aborting message'); - } - - const messageType = message.type.toLowerCase(); - let response; - switch (messageType) { - case EventType.IDENTIFY: - response = await identifyResponseBuilder(message, destination); - break; - case EventType.TRACK: - response = trackResponseBuilder(message, destination); - break; - case EventType.GROUP: - response = await groupResponseBuilder(message, destination); - break; - default: - throw new InstrumentationError(`Message type ${messageType} not supported`); - } - return response; -}; - -const process = async (event) => { - const { message, destination } = event; - const response = await processSingleMessage(message, destination); - return response; -}; - -const processRouterDest = async (inputs, reqMetadata) => { - const respList = await simpleProcessRouterDest(inputs, process, reqMetadata); - return respList; -}; - -module.exports = { process, processRouterDest }; diff --git a/test/__mocks__/axios.js b/test/__mocks__/axios.js index cb29961c41c..6b13a25281b 100644 --- a/test/__mocks__/axios.js +++ b/test/__mocks__/axios.js @@ -40,8 +40,6 @@ const { sendgridGetRequestHandler } = require("./sendgrid.mock"); const { sendinblueGetRequestHandler } = require("./sendinblue.mock"); const { courierGetRequestHandler } = require("./courier.mock"); const { brazePostRequestHandler } = require("./braze.mock"); -const { intercomPostRequestHandler } = require("./intercom.mock"); -const {optimizelyFullStackGetRequestHandler} = require("./optimizely_fullstack.mock"); const urlDirectoryMap = { "api.hubapi.com": "hs", @@ -160,9 +158,6 @@ function get(url, options) { if (url.includes("https://api.courier.com")) { return Promise.resolve(courierGetRequestHandler(url, mockData)); } - if(url.includes("https://cdn.optimizely.com")){ - return Promise.resolve(optimizelyFullStackGetRequestHandler(url, mockData)); - } return new Promise((resolve, reject) => { if (mockData) { @@ -250,11 +245,6 @@ function post(url, payload) { if (url.includes("https://api.custify.com")) { return Promise.resolve(custifyPostRequestHandler(url)); } - if (url.includes("intercom.io")) { - return new Promise((resolve, reject) => { - resolve(intercomPostRequestHandler(url, payload)); - }); - } return new Promise((resolve, reject) => { if (mockData) { resolve({ data: mockData, status: 200 }); diff --git a/test/__mocks__/intercom.mock.js b/test/__mocks__/intercom.mock.js deleted file mode 100644 index 4cd9b701d34..00000000000 --- a/test/__mocks__/intercom.mock.js +++ /dev/null @@ -1,32 +0,0 @@ -const fs = require("fs"); -const path = require("path"); -const getData = url => { - const dataFile = fs.readFileSync( - path.resolve(__dirname, "./data/intercom/response.json") - ); - const data = JSON.parse(dataFile); - const response = data[url]; - return response || {}; -}; - -const intercomPostRequestHandler = (url, payload) => { - const requestPayload = JSON.parse(payload); - const mockData = getData(url); - if (requestPayload.query?.value[0]?.value === 'test@intercom.com') { - return { data: { data: [], total_count: 0 }, status: 200 }; - } else if (requestPayload.query?.value[0]?.value === 'test@rudderlabs.com') { - return { data: mockData, status: 200 }; - } else if (requestPayload?.company_id === 'rudderstack@1') { - return { data: mockData, status: 200 }; - } else { - return new Promise((resolve, reject) => { - if (mockData) { - resolve({ data: mockData, status: 200 }); - } else { - resolve({ error: "Request failed" }); - } - }); - } -}; - -module.exports = { intercomPostRequestHandler }; \ No newline at end of file diff --git a/test/__tests__/data/intercom.json b/test/__tests__/data/intercom.json deleted file mode 100644 index 327bd631ccb..00000000000 --- a/test/__tests__/data/intercom.json +++ /dev/null @@ -1,569 +0,0 @@ -[ - { - "description": "No Message type", - "input": { - "message": { - "channel": "web", - "context": { - "traits": { - "age": 23, - "email": "adc@test.com", - "firstname": "Test", - "birthday": "2022-05-13T12:51:01.470Z" - }, - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36" - }, - "event": "Product Searched", - "originalTimestamp": "2020-09-22T14:42:44.724Z", - "timestamp": "2022-09-22T20:12:44.757+05:30", - "userId": "user@1" - }, - "destination": { - "Config": { - "accessToken": "ABC..." - } - }, - "metadata": { - "secret": { - "access_token": "ABC" - } - } - }, - "output": { - "error": "Message Type is not present. Aborting message" - } - }, - { - "description": "Unsupported Event type", - "input": { - "message": { - "channel": "web", - "context": { - "traits": { - "age": 23, - "email": "adc@test.com", - "firstname": "Test", - "birthday": "2022-05-13T12:51:01.470Z" - }, - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36" - }, - "event": "Product Searched", - "type": "page", - "originalTimestamp": "2020-09-22T14:42:44.724Z", - "timestamp": "2022-09-22T20:12:44.757+05:30", - "userId": "user@1" - }, - "destination": { - "Config": { - "accessToken": "ABC..." - } - } - }, - "output": { - "error": "Message type page not supported" - } - }, - { - "description": "Required parameters are not present", - "input": { - "message": { - "channel": "web", - "context": { - "traits": { - "age": 23, - "firstname": "Test", - "birthday": "2022-05-13T12:51:01.470Z" - }, - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36" - }, - "event": "Product Searched", - "type": "identify", - "originalTimestamp": "2023-09-24T14:42:44.724Z", - "timestamp": "2023-09-22T20:14:44.757+05:30", - "anonymousId": "26508834-c290-4354-9303-11c9b339a58a" - }, - "destination": { - "Config": { - "accessToken": "ABC..." - } - } - }, - "output": { - "error": "Either of `email` or `userId` is required for an Identify call" - } - }, - { - "description": "Create user", - "input": { - "message": { - "channel": "web", - "context": { - "device": { - "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "manufacturer": "Apple", - "model": "iPhone", - "name": "iPod touch (7th generation)", - "type": "iOS" - }, - "library": { - "name": "test-ios-library", - "version": "1.0.7" - }, - "locale": "en-US", - "network": { - "bluetooth": false, - "carrier": "unavailable", - "cellular": false, - "wifi": true - }, - "os": { - "name": "iOS", - "version": "14.0" - }, - "screen": { - "density": 2, - "height": 320, - "width": 568 - }, - "traits": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "name": "Dummy User", - "firstName": "Dummy", - "lastName": "User", - "createdAt": "2020-09-30T19:11:00.337Z", - "userId": "user@1", - "email": "test@intercom.com", - "phone": "123456789", - "address": { - "city": "Kolkata", - "state": "West Bengal" - }, - "originalArray": [ - { - "nested_field": "nested value", - "tags": ["tag_1", "tag_2", "tag_3"] - } - ] - }, - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36" - }, - "integrations": { - "INTERCOM": { - "lookup": "email" - } - }, - "type": "identify", - "originalTimestamp": "2023-09-24T14:42:44.724Z", - "timestamp": "2023-09-22T20:14:44.757+05:30", - "anonymousId": "26508834-c290-4354-9303-11c9b339a58a", - "userId": "user@1" - }, - "destination": { - "Config": { - "accessToken": "dummyaccessToken", - "apiServer": "standard" - } - } - }, - "output": { - "body": { - "FORM": {}, - "JSON": { - "custom_attributes": { - "address.city": "Kolkata", - "address.state": "West Bengal", - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "originalArray[0].nested_field": "nested value", - "originalArray[0].tags[0]": "tag_1", - "originalArray[0].tags[1]": "tag_2", - "originalArray[0].tags[2]": "tag_3" - }, - "email": "test@intercom.com", - "external_id": "user@1", - "last_seen_at": 1695393884, - "name": "Dummy User", - "phone": "123456789", - "signed_up_at": 1601493060 - }, - "JSON_ARRAY": {}, - "XML": {} - }, - "endpoint": "https://api.intercom.io/contacts", - "files": {}, - "headers": { - "Accept": "application/json", - "Authorization": "Bearer dummyaccessToken", - "Content-Type": "application/json", - "Intercom-Version": "2.10" - }, - "method": "POST", - "params": {}, - "type": "REST", - "version": "1" - } - }, - { - "description": "Update user", - "input": { - "message": { - "channel": "web", - "context": { - "device": { - "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "manufacturer": "Apple", - "model": "iPhone", - "name": "iPod touch (7th generation)", - "type": "iOS" - }, - "library": { - "name": "test-ios-library", - "version": "1.0.7" - }, - "locale": "en-US", - "network": { - "bluetooth": false, - "carrier": "unavailable", - "cellular": false, - "wifi": true - }, - "os": { - "name": "iOS", - "version": "14.0" - }, - "screen": { - "density": 2, - "height": 320, - "width": 568 - }, - "traits": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "firstName": "Test", - "lastName": "User", - "createdAt": "2020-09-30T19:11:00.337Z", - "email": "test@rudderlabs.com", - "phone": "123456789", - "address": { - "city": "Kolkata", - "state": "West Bengal" - }, - "originalArray": [ - { - "nested_field": "nested value", - "tags": ["tag_1", "tag_2", "tag_3"] - } - ] - }, - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36" - }, - "integrations": { - "INTERCOM": { - "lookup": "email" - } - }, - "type": "identify", - "originalTimestamp": "2023-09-24T14:42:44.724Z", - "timestamp": "2023-09-22T20:14:44.757+05:30", - "anonymousId": "26508834-c290-4354-9303-11c9b339a58a" - }, - "destination": { - "Config": { - "accessToken": "dummyaccessToken", - "apiServer": "standard", - "sendAnonymousId": true - } - } - }, - "output": { - "body": { - "FORM": {}, - "JSON": { - "custom_attributes": { - "address.city": "Kolkata", - "address.state": "West Bengal", - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "originalArray[0].nested_field": "nested value", - "originalArray[0].tags[0]": "tag_1", - "originalArray[0].tags[1]": "tag_2", - "originalArray[0].tags[2]": "tag_3" - }, - "email": "test@rudderlabs.com", - "external_id": "26508834-c290-4354-9303-11c9b339a58a", - "last_seen_at": 1695393884, - "name": "Test User", - "phone": "123456789", - "signed_up_at": 1601493060 - }, - "JSON_ARRAY": {}, - "XML": {} - }, - "endpoint": "https://api.intercom.io/contacts/contact_id_2", - "files": {}, - "headers": { - "Accept": "application/json", - "Authorization": "Bearer dummyaccessToken", - "Content-Type": "application/json", - "Intercom-Version": "2.10" - }, - "method": "PUT", - "params": {}, - "type": "REST", - "version": "1" - } - }, - { - "description": "Missing required parameters", - "input": { - "message": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "channel": "mobile", - "context": { - "app": { - "build": "1.0", - "name": "Test_Example", - "namespace": "com.example.testapp", - "version": "1.0" - }, - "device": { - "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "manufacturer": "Apple", - "model": "iPhone", - "name": "iPod touch (7th generation)", - "type": "iOS" - }, - "library": { - "name": "test-ios-library", - "version": "1.0.7" - }, - "locale": "en-US", - "network": { - "bluetooth": false, - "carrier": "unavailable", - "cellular": false, - "wifi": true - }, - "os": { - "name": "iOS", - "version": "14.0" - }, - "screen": { - "density": 2, - "height": 320, - "width": 568 - }, - "timezone": "Asia/Kolkata", - "traits": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "name": "Test Name", - "firstName": "Test", - "lastName": "Name", - "createdAt": "2020-09-30T19:11:00.337Z", - "phone": "123456789", - "key1": "value1" - }, - "userAgent": "unknown" - }, - "properties": { - "revenue": { - "amount": 1232, - "currency": "inr", - "test": 123 - }, - "price": { - "amount": 3000, - "currency": "USD" - } - }, - "event": "User Sign Up", - "integrations": { - "All": true - }, - "messageId": "1601493060-39010c49-e6e4-4626-a75c-0dbf1925c9e8", - "originalTimestamp": "2023-09-22T20:14:44.757+05:30", - "timestamp": "2023-09-22T20:14:44.757+05:30", - "type": "track" - }, - "destination": { - "Config": { - "accessToken": "dummyaccessToken", - "apiServer": "eu", - "sendAnonymousId": true - } - } - }, - "output": { - "error": "Either of `email` or `userId` is required for Track call" - } - }, - { - "description": "User Sign up event", - "input": { - "message": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "channel": "mobile", - "context": { - "app": { - "build": "1.0", - "name": "Test_Example", - "namespace": "com.example.testapp", - "version": "1.0" - }, - "device": { - "id": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "manufacturer": "Apple", - "model": "iPhone", - "name": "iPod touch (7th generation)", - "type": "iOS" - }, - "library": { - "name": "test-ios-library", - "version": "1.0.7" - }, - "locale": "en-US", - "network": { - "bluetooth": false, - "carrier": "unavailable", - "cellular": false, - "wifi": true - }, - "os": { - "name": "iOS", - "version": "14.0" - }, - "screen": { - "density": 2, - "height": 320, - "width": 568 - }, - "timezone": "Asia/Kolkata", - "traits": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "name": "Test Name", - "firstName": "Test", - "lastName": "Name", - "createdAt": "2020-09-30T19:11:00.337Z", - "phone": "123456789", - "key1": "value1" - }, - "userAgent": "unknown" - }, - "properties": { - "revenue": { - "amount": 1232, - "currency": "inr", - "test": 123 - }, - "price": { - "amount": 3000, - "currency": "USD" - } - }, - "event": "User Sign Up", - "integrations": { - "All": true - }, - "messageId": "1601493060-39010c49-e6e4-4626-a75c-0dbf1925c9e8", - "originalTimestamp": "2023-09-22T20:14:44.757+05:30", - "timestamp": "2023-09-22T20:14:44.757+05:30", - "userId": "user@3", - "type": "track" - }, - "destination": { - "Config": { - "accessToken": "dummyaccessToken", - "apiServer": "au", - "sendAnonymousId": true - } - } - }, - "output": { - "body": { - "FORM": {}, - "JSON": { - "created_at": 1695393884, - "event_name": "User Sign Up", - "metadata": { - "price": { - "amount": 3000, - "currency": "USD" - }, - "revenue": { - "amount": 1232, - "currency": "inr", - "test": 123 - } - }, - "user_id": "user@3" - }, - "JSON_ARRAY": {}, - "XML": {} - }, - "endpoint": "https://api.au.intercom.io/events", - "files": {}, - "headers": { - "Accept": "application/json", - "Authorization": "Bearer dummyaccessToken", - "Content-Type": "application/json", - "Intercom-Version": "2.10" - }, - "method": "POST", - "params": {}, - "type": "REST", - "version": "1" - } - }, - { - "description": "Attach user with company", - "input": { - "message": { - "groupId": "rudderstack@1", - "traits": { - "employees": 450, - "plan": "basic", - "userId": "sdfrsdfsdfsf", - "email": "test@rudderlabs.com", - "name": "rudderstack", - "size": "50", - "industry": "IT", - "monthlySpend": "2131231", - "remoteCreatedAt": "1683017572", - "key1": "val1" - }, - "anonymousId": "anon@1", - "integrations": { - "All": true - }, - "type": "group", - "userId": "sdfrsdfsdfsf" - }, - "destination": { - "Config": { - "accessToken": "dummyaccessToken", - "apiServer": "standard", - "sendAnonymousId": true - } - } - }, - "output": { - "body": { - "FORM": {}, - "JSON_ARRAY": {}, - "XML": {}, - "JSON": { - "id": "company@1" - } - }, - "endpoint": "https://api.intercom.io/contacts/contact_id_2/companies", - "files": {}, - "headers": { - "Accept": "application/json", - "Authorization": "Bearer dummyaccessToken", - "Content-Type": "application/json", - "Intercom-Version": "2.10" - }, - "method": "POST", - "params": {}, - "type": "REST", - "version": "1" - } - } -] diff --git a/test/__tests__/intercom.test.js b/test/__tests__/intercom.test.js deleted file mode 100644 index c1cc1d255a9..00000000000 --- a/test/__tests__/intercom.test.js +++ /dev/null @@ -1,29 +0,0 @@ -const integration = "intercom"; -const name = "Intercom"; - -const fs = require("fs"); -const path = require("path"); - -const version = "v0"; - -const transformer = require(`../../src/${version}/destinations/${integration}/transform`); - -// Router Test Data -const testDataFile = fs.readFileSync( - path.resolve(__dirname, `./data/${integration}.json`) -); -const testData = JSON.parse(testDataFile); -describe(`${name} Tests`, () => { - describe("Processor", () => { - testData.forEach((dataPoint, index) => { - it(`${index}. ${integration} - ${dataPoint.description}`, async () => { - try { - const output = await transformer.process(dataPoint.input); - expect(output).toEqual(dataPoint.output); - } catch (error) { - expect(error.message).toEqual(dataPoint.output.error); - } - }); - }); - }); -}); diff --git a/test/integrations/destinations/intercom/dataDelivery/data.ts b/test/integrations/destinations/intercom/dataDelivery/data.ts index 23bcdc6af24..db7aafc963e 100644 --- a/test/integrations/destinations/intercom/dataDelivery/data.ts +++ b/test/integrations/destinations/intercom/dataDelivery/data.ts @@ -1,90 +1,91 @@ export const data = [ { - "name": "intercom", - "description": "Test 0", - "feature": "dataDelivery", - "module": "destination", - "version": "v0", - "input": { - "request": { - "body": { - "version": "1", - "type": "REST", - "method": "POST", - "endpoint": "https://api.intercom.io/users/test1", - "headers": { - "Content-Type": "application/json", - "Authorization": "Bearer intercomApiKey", - "Accept": "application/json", - "Intercom-Version": "1.4" + name: 'intercom', + description: 'Test 0', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://api.intercom.io/users/test1', + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer intercomApiKey', + Accept: 'application/json', + 'Intercom-Version': '1.4', }, - "params": {}, - "body": { - "JSON": { - "email": "test_1@test.com", - "phone": "9876543210", - "name": "Test Name", - "signed_up_at": 1601493060, - "last_seen_user_agent": "unknown", - "update_last_request_at": true, - "user_id": "test_user_id_1", - "custom_attributes": { - "anonymousId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33", - "key1": "value1", - "address.city": "Kolkata", - "address.state": "West Bengal", - "originalArray[0].nested_field": "nested value", - "originalArray[0].tags[0]": "tag_1", - "originalArray[0].tags[1]": "tag_2", - "originalArray[0].tags[2]": "tag_3", - "originalArray[1].nested_field": "nested value", - "originalArray[1].tags[0]": "tag_1", - "originalArray[2].nested_field": "nested value" - } + params: {}, + body: { + JSON: { + email: 'test_1@test.com', + phone: '9876543210', + name: 'Test Name', + signed_up_at: 1601493060, + last_seen_user_agent: 'unknown', + update_last_request_at: true, + user_id: 'test_user_id_1', + custom_attributes: { + anonymousId: '58b21c2d-f8d5-4410-a2d0-b268a26b7e33', + key1: 'value1', + 'address.city': 'Kolkata', + 'address.state': 'West Bengal', + 'originalArray[0].nested_field': 'nested value', + 'originalArray[0].tags[0]': 'tag_1', + 'originalArray[0].tags[1]': 'tag_2', + 'originalArray[0].tags[2]': 'tag_3', + 'originalArray[1].nested_field': 'nested value', + 'originalArray[1].tags[0]': 'tag_1', + 'originalArray[2].nested_field': 'nested value', + }, }, - "XML": {}, - "JSON_ARRAY": {}, - "FORM": {} + XML: {}, + JSON_ARRAY: {}, + FORM: {}, }, - "files": {}, - "userId": "58b21c2d-f8d5-4410-a2d0-b268a26b7e33" + files: {}, + userId: '58b21c2d-f8d5-4410-a2d0-b268a26b7e33', }, - "method": "POST" - } + method: 'POST', + }, }, - "output": { - "response": { - "status": 500, - "body": { - "output": { - "status": 500, - "message": "[Intercom Response Handler] Request failed for destination intercom with status: 408", - "destinationResponse": { - "response": { - "type": "error.list", - "request_id": "000on04msi4jpk7d3u60", - "errors": [ + output: { + response: { + status: 500, + body: { + output: { + status: 500, + message: + '[Intercom Response Handler] Request failed for destination intercom with status: 408', + destinationResponse: { + response: { + type: 'error.list', + request_id: '000on04msi4jpk7d3u60', + errors: [ { - "code": "Request Timeout", - "message": "The server would not wait any longer for the client" - } - ] + code: 'Request Timeout', + message: 'The server would not wait any longer for the client', + }, + ], }, - "status": 408 + status: 408, + }, + statTags: { + destType: 'INTERCOM', + errorCategory: 'network', + destinationId: 'Non-determininable', + workspaceId: 'Non-determininable', + errorType: 'retryable', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', }, - "statTags": { - "destType": "INTERCOM", - "errorCategory": "network", - "destinationId": "Non-determininable", - "workspaceId": "Non-determininable", - "errorType": "retryable", - "feature": "dataDelivery", - "implementation": "native", - "module": "destination" - } - } - } - } - } - } -] \ No newline at end of file + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/intercom/deleteUsers/data.ts b/test/integrations/destinations/intercom/deleteUsers/data.ts index a45af0a98f4..ae5d588210c 100644 --- a/test/integrations/destinations/intercom/deleteUsers/data.ts +++ b/test/integrations/destinations/intercom/deleteUsers/data.ts @@ -19,7 +19,7 @@ export const data = [ }, ], config: { - apiKey: 'API_KEY', + accessToken: 'API_KEY', }, }, ], @@ -57,7 +57,7 @@ export const data = [ }, ], config: { - apiKey: 'API_KEY', + accessToken: 'API_KEY', }, }, ], @@ -140,7 +140,7 @@ export const data = [ body: [ { statusCode: 400, - error: 'api key for deletion not present', + error: 'access token for deletion not present', }, ], }, @@ -159,7 +159,7 @@ export const data = [ destType: 'INTERCOM', userAttributes: [{}], config: { - apiKey: 'a=', + accessToken: 'a=', }, }, ], diff --git a/test/integrations/destinations/intercom/network.ts b/test/integrations/destinations/intercom/network.ts index e3bba3f2601..03eb1a84bcf 100644 --- a/test/integrations/destinations/intercom/network.ts +++ b/test/integrations/destinations/intercom/network.ts @@ -89,6 +89,259 @@ const deleteNwData = [ }, }, }, + { + httpReq: { + method: 'post', + url: 'https://api.intercom.io/contacts/search', + data: { + query: { + operator: 'AND', + value: [{ field: 'email', operator: '=', value: 'test@rudderlabs.com' }], + }, + }, + headers: { + Accept: 'application/json', + Authorization: 'Bearer testAccessToken', + 'Content-Type': 'application/json', + }, + }, + httpRes: { + status: 200, + statusText: 'ok', + data: { + type: 'list', + total_count: 0, + pages: { + type: 'pages', + page: 1, + per_page: 50, + total_pages: 0, + }, + data: [], + }, + }, + }, + { + httpReq: { + method: 'post', + url: 'https://api.intercom.io/contacts/search', + data: { + query: { + operator: 'AND', + value: [{ field: 'email', operator: '=', value: 'test+2@rudderlabs.com' }], + }, + }, + headers: { + Accept: 'application/json', + Authorization: 'Bearer testAccessToken', + 'Content-Type': 'application/json', + }, + }, + httpRes: { + status: 200, + statusText: 'ok', + data: { + type: 'list', + total_count: 1, + pages: { + type: 'pages', + page: 1, + per_page: 50, + total_pages: 1, + }, + data: [ + { + type: 'contact', + id: '7070129940741e45d040', + workspace_id: 'rudderWorkspace', + external_id: 'user@2', + role: 'user', + email: 'test+2@rudderlabs.com', + }, + ], + }, + }, + }, + { + httpReq: { + method: 'post', + url: 'https://api.intercom.io/contacts/search', + data: { + query: { + operator: 'AND', + value: [{ field: 'email', operator: '=', value: 'test+5@rudderlabs.com' }], + }, + }, + headers: { + Accept: 'application/json', + Authorization: 'Bearer testAccessToken', + 'Content-Type': 'application/json', + }, + }, + httpRes: { + status: 200, + statusText: 'ok', + data: { + type: 'list', + total_count: 1, + pages: { + type: 'pages', + page: 1, + per_page: 50, + total_pages: 1, + }, + data: [ + { + type: 'contact', + id: '70701240741e45d040', + workspace_id: 'rudderWorkspace', + external_id: 'user@5', + role: 'user', + email: 'test+5@rudderlabs.com', + }, + ], + }, + }, + }, + { + httpReq: { + method: 'post', + url: 'https://api.intercom.io/contacts/search', + data: { + query: { + operator: 'AND', + value: [{ field: 'phone', operator: '=', value: '+91 9299999999' }], + }, + }, + headers: { + Accept: 'application/json', + Authorization: 'Bearer testAccessToken', + 'Content-Type': 'application/json', + }, + }, + httpRes: { + status: 200, + statusText: 'ok', + data: { + type: 'list', + total_count: 1, + pages: { + type: 'pages', + page: 1, + per_page: 50, + total_pages: 1, + }, + data: [ + { + type: 'contact', + id: '7070129940741e45d040', + workspace_id: 'rudderWorkspace', + external_id: 'user@2', + role: 'user', + email: 'test+2@rudderlabs.com', + }, + ], + }, + }, + }, + { + httpReq: { + method: 'post', + url: 'https://api.intercom.io/contacts/search', + data: { + query: { + operator: 'AND', + value: [{ field: 'email', operator: '=', value: 'test+4@rudderlabs.com' }], + }, + }, + headers: { + Accept: 'application/json', + Authorization: 'Bearer testAccessToken', + 'Content-Type': 'application/json', + }, + }, + httpRes: { + status: 200, + statusText: 'ok', + data: { + type: 'list', + total_count: 0, + pages: { + type: 'pages', + page: 1, + per_page: 50, + total_pages: 0, + }, + data: [], + }, + }, + }, + { + httpReq: { + method: 'post', + url: 'https://api.intercom.io/contacts/search', + data: { + query: { + operator: 'AND', + value: [{ field: 'email', operator: '=', value: 'test+3@rudderlabs.com' }], + }, + }, + headers: { + Accept: 'application/json', + Authorization: 'Bearer invalidTestAccessToken', + 'Content-Type': 'application/json', + }, + }, + httpRes: { + status: 401, + data: { + type: 'error.list', + request_id: 'request_1', + errors: [ + { + code: 'unauthorized', + message: 'Access Token Invalid', + }, + ], + }, + }, + }, + { + httpReq: { + method: 'post', + url: 'https://api.intercom.io/companies', + data: { + company_id: 'rudderlabs', + name: 'RudderStack', + website: 'www.rudderstack.com', + plan: 'enterprise', + size: 500, + industry: 'CDP', + custom_attributes: {}, + }, + headers: { + Accept: 'application/json', + Authorization: 'Bearer testAccessToken', + 'Content-Type': 'application/json', + }, + }, + httpRes: { + status: 200, + data: { + type: 'company', + company_id: 'rudderlabs', + id: '657264e9018c0a647s45', + name: 'RudderStack', + website: 'www.rudderstack.com', + plan: 'enterprise', + size: 500, + industry: 'CDP', + remote_created_at: 1374138000, + created_at: 1701930212, + updated_at: 1701930212, + }, + }, + }, ]; const deliveryCallsData = [ { @@ -142,4 +395,3 @@ const deliveryCallsData = [ }, ]; export const networkCallsData = [...deleteNwData, ...deliveryCallsData]; - diff --git a/test/integrations/destinations/intercom/processor/data.ts b/test/integrations/destinations/intercom/processor/data.ts new file mode 100644 index 00000000000..c1d5370ec5b --- /dev/null +++ b/test/integrations/destinations/intercom/processor/data.ts @@ -0,0 +1,1188 @@ +export const data = [ + { + name: 'intercom', + description: 'No message type', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + context: { + traits: { + age: 23, + email: 'adc@test.com', + firstname: 'Test', + birthday: '2022-05-13T12:51:01.470Z', + }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36', + }, + event: 'Product Searched', + originalTimestamp: '2020-09-22T14:42:44.724Z', + timestamp: '2022-09-22T20:12:44.757+05:30', + userId: 'user@1', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + accessToken: 'testAccessToken', + apiServer: 'standard', + sendAnonymousId: false, + }, + }, + metadata: { + jobId: 1, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + metadata: { + jobId: 1, + }, + statusCode: 400, + error: + 'message Type is not present. Aborting: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: message Type is not present. Aborting', + statTags: { + errorCategory: 'dataValidation', + errorType: 'instrumentation', + destType: 'INTERCOM', + module: 'destination', + implementation: 'cdkV2', + feature: 'processor', + }, + }, + ], + }, + }, + }, + { + name: 'intercom', + description: 'Unsupported message type', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + context: { + traits: { + age: 23, + email: 'adc@test.com', + firstname: 'Test', + birthday: '2022-05-13T12:51:01.470Z', + }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36', + }, + event: 'Product Searched', + type: 'page', + originalTimestamp: '2020-09-22T14:42:44.724Z', + timestamp: '2022-09-22T20:12:44.757+05:30', + userId: 'user@1', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + accessToken: 'testAccessToken', + apiServer: 'standard', + sendAnonymousId: false, + }, + }, + metadata: { + jobId: 2, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + metadata: { + jobId: 2, + }, + statusCode: 400, + error: + 'message type page is not supported: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: message type page is not supported', + statTags: { + errorCategory: 'dataValidation', + errorType: 'instrumentation', + destType: 'INTERCOM', + module: 'destination', + implementation: 'cdkV2', + feature: 'processor', + }, + }, + ], + }, + }, + }, + { + name: 'intercom', + description: 'Missing required config', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'user@1', + channel: 'web', + context: { + traits: { + age: 23, + email: 'adc@test.com', + firstName: 'Test', + }, + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiServer: 'standard', + sendAnonymousId: false, + }, + }, + metadata: { + jobId: 3, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + metadata: { + jobId: 3, + }, + statusCode: 400, + error: + 'Access Token is not present. Aborting: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: Access Token is not present. Aborting', + statTags: { + errorCategory: 'dataValidation', + errorType: 'configuration', + destType: 'INTERCOM', + module: 'destination', + implementation: 'cdkV2', + feature: 'processor', + }, + }, + ], + }, + }, + }, + { + name: 'intercom', + description: 'Create customer with email as lookup field', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'user@1', + channel: 'web', + context: { + traits: { + age: 23, + email: 'test@rudderlabs.com', + phone: '+91 9999999999', + firstName: 'Test', + lastName: 'Rudderlabs', + address: 'california usa', + ownerId: '13', + }, + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + accessToken: 'testAccessToken', + apiServer: 'standard', + sendAnonymousId: false, + }, + }, + metadata: { + jobId: 4, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + JSON: { + email: 'test@rudderlabs.com', + external_id: 'user@1', + last_seen_at: 1700628164, + name: 'Test Rudderlabs', + owner_id: 13, + phone: '+91 9999999999', + custom_attributes: { + address: 'california usa', + age: 23, + }, + }, + XML: {}, + FORM: {}, + JSON_ARRAY: {}, + }, + endpoint: 'https://api.intercom.io/contacts', + headers: { + Authorization: 'Bearer testAccessToken', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'Intercom-Version': '2.10', + }, + userId: '', + version: '1', + type: 'REST', + method: 'POST', + files: {}, + params: {}, + }, + metadata: { jobId: 4 }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'intercom', + description: 'Update customer with email as lookup field', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'user@2', + channel: 'web', + context: { + traits: { + age: 32, + email: 'test+2@rudderlabs.com', + phone: '+91 9299999999', + firstName: 'Test', + lastName: 'RudderStack', + ownerId: '14', + }, + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + accessToken: 'testAccessToken', + apiServer: 'standard', + sendAnonymousId: false, + }, + }, + metadata: { + jobId: 5, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + JSON: { + email: 'test+2@rudderlabs.com', + external_id: 'user@2', + last_seen_at: 1700628164, + name: 'Test RudderStack', + owner_id: 14, + phone: '+91 9299999999', + custom_attributes: { + age: 32, + }, + }, + XML: {}, + FORM: {}, + JSON_ARRAY: {}, + }, + endpoint: 'https://api.intercom.io/contacts/7070129940741e45d040', + headers: { + Authorization: 'Bearer testAccessToken', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'Intercom-Version': '2.10', + }, + userId: '', + version: '1', + type: 'REST', + method: 'PUT', + files: {}, + params: {}, + }, + metadata: { jobId: 5 }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'intercom', + description: 'Missing required parameters for an identify call', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + anonymousId: 'anon@2', + channel: 'web', + context: { + traits: { + age: 32, + phone: '+91 9299999999', + firstName: 'Test', + lastName: 'RudderStack', + ownerId: '14', + role: 'user', + source: 'rudder-sdk', + }, + }, + integrations: { + INTERCOM: { + lookup: 'phone', + }, + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + accessToken: 'testAccessToken', + apiServer: 'standard', + sendAnonymousId: false, + }, + }, + metadata: { + jobId: 6, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + metadata: { + jobId: 6, + }, + statusCode: 400, + error: + 'Either email or userId is required for Identify call: Workflow: procWorkflow, Step: validateIdentifyPayload, ChildStep: undefined, OriginalError: Either email or userId is required for Identify call', + statTags: { + errorCategory: 'dataValidation', + errorType: 'instrumentation', + destType: 'INTERCOM', + module: 'destination', + implementation: 'cdkV2', + feature: 'processor', + }, + }, + ], + }, + }, + }, + { + name: 'intercom', + description: 'Unauthorized error while searching contact for an identify call', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'user@3', + channel: 'web', + context: { + traits: { + phone: '+91 9399999999', + email: 'test+3@rudderlabs.com', + firstName: 'Test', + lastName: 'Rudder', + ownerId: '15', + role: 'admin', + source: 'rudder-android-sdk', + }, + }, + integrations: { + INTERCOM: { + lookup: 'email', + }, + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + accessToken: 'invalidTestAccessToken', + apiServer: 'standard', + sendAnonymousId: false, + }, + }, + metadata: { + jobId: 7, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + metadata: { + jobId: 7, + }, + statusCode: 401, + error: + '{"message":"{\\"message\\":\\"Unable to search contact due to : undefined: Workflow: procWorkflow, Step: searchContact, ChildStep: undefined, OriginalError: Unable to search contact due to : undefined\\",\\"destinationResponse\\":{\\"response\\":{\\"type\\":\\"error.list\\",\\"request_id\\":\\"request_1\\",\\"errors\\":[{\\"code\\":\\"unauthorized\\",\\"message\\":\\"Access Token Invalid\\"}]},\\"status\\":401}}","destinationResponse":{"response":{"type":"error.list","request_id":"request_1","errors":[{"code":"unauthorized","message":"Access Token Invalid"}]},"status":401}}', + statTags: { + errorCategory: 'network', + errorType: 'aborted', + destType: 'INTERCOM', + module: 'destination', + implementation: 'cdkV2', + feature: 'processor', + }, + }, + ], + }, + }, + }, + { + name: 'intercom', + description: 'Track call without event name', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'user@3', + channel: 'web', + context: { + traits: { + age: 32, + email: 'test+3@rudderlabs.com', + phone: '+91 9399999999', + firstName: 'Test', + lastName: 'RudderStack', + ownerId: '15', + }, + }, + properties: { + revenue: { + amount: 1232, + currency: 'inr', + test: 123, + }, + price: { + amount: 3000, + currency: 'USD', + }, + }, + type: 'track', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + accessToken: 'testAccessToken', + apiServer: 'standard', + sendAnonymousId: false, + }, + }, + metadata: { + jobId: 8, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + metadata: { + jobId: 8, + }, + statusCode: 400, + error: + 'Event name is required for track call: Workflow: procWorkflow, Step: validateTrackPayload, ChildStep: undefined, OriginalError: Event name is required for track call', + statTags: { + errorCategory: 'dataValidation', + errorType: 'instrumentation', + destType: 'INTERCOM', + module: 'destination', + implementation: 'cdkV2', + feature: 'processor', + }, + }, + ], + }, + }, + }, + { + name: 'intercom', + description: 'Successful track call', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'user@2', + channel: 'web', + context: { + traits: { + age: 32, + email: 'test+2@rudderlabs.com', + phone: '+91 9299999999', + firstName: 'Test', + lastName: 'RudderStack', + ownerId: '14', + }, + }, + properties: { + revenue: { + amount: 1232, + currency: 'inr', + test: 123, + }, + price: { + amount: 3000, + currency: 'USD', + }, + }, + event: 'Product Viewed', + type: 'track', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + accessToken: 'testAccessToken', + apiServer: 'standard', + sendAnonymousId: false, + }, + }, + metadata: { + jobId: 9, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + FORM: {}, + JSON: { + created_at: 1700628164, + email: 'test+2@rudderlabs.com', + event_name: 'Product Viewed', + metadata: { + price: { + amount: 3000, + currency: 'USD', + }, + revenue: { + amount: 1232, + currency: 'inr', + test: 123, + }, + }, + user_id: 'user@2', + }, + JSON_ARRAY: {}, + XML: {}, + }, + endpoint: 'https://api.intercom.io/events', + headers: { + Accept: 'application/json', + Authorization: 'Bearer testAccessToken', + 'Content-Type': 'application/json', + 'Intercom-Version': '2.10', + }, + method: 'POST', + type: 'REST', + userId: '', + version: '1', + params: {}, + files: {}, + }, + statusCode: 200, + metadata: { + jobId: 9, + }, + }, + ], + }, + }, + }, + { + name: 'intercom', + description: 'Group call without groupId', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'user@4', + channel: 'web', + context: { + traits: { + email: 'test+4@rudderlabs.com', + phone: '+91 9499999999', + firstName: 'John', + lastName: 'Doe', + ownerId: '16', + }, + }, + traits: { + name: 'RudderStack', + size: 500, + website: 'www.rudderstack.com', + industry: 'CDP', + plan: 'enterprise', + }, + type: 'group', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + accessToken: 'testAccessToken', + apiServer: 'standard', + sendAnonymousId: false, + }, + }, + metadata: { + jobId: 10, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + metadata: { + jobId: 10, + }, + statusCode: 400, + error: + 'groupId is required for group call: Workflow: procWorkflow, Step: prepareCreateOrUpdateCompanyPayload, ChildStep: undefined, OriginalError: groupId is required for group call', + statTags: { + errorCategory: 'dataValidation', + errorType: 'instrumentation', + destType: 'INTERCOM', + module: 'destination', + implementation: 'cdkV2', + feature: 'processor', + }, + }, + ], + }, + }, + }, + { + name: 'intercom', + description: 'Successful group call to create or update company', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'user@4', + groupId: 'rudderlabs', + channel: 'web', + context: { + traits: { + email: 'test+4@rudderlabs.com', + phone: '+91 9499999999', + firstName: 'John', + lastName: 'Doe', + ownerId: '16', + }, + }, + traits: { + name: 'RudderStack', + size: 500, + website: 'www.rudderstack.com', + industry: 'CDP', + plan: 'enterprise', + }, + type: 'group', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + accessToken: 'testAccessToken', + apiServer: 'standard', + sendAnonymousId: false, + }, + }, + metadata: { + jobId: 11, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + JSON: { + company_id: 'rudderlabs', + custom_attributes: {}, + industry: 'CDP', + name: 'RudderStack', + plan: 'enterprise', + size: 500, + website: 'www.rudderstack.com', + }, + XML: {}, + FORM: {}, + JSON_ARRAY: {}, + }, + endpoint: 'https://api.intercom.io/companies', + headers: { + Accept: 'application/json', + Authorization: 'Bearer testAccessToken', + 'Content-Type': 'application/json', + 'Intercom-Version': '2.10', + }, + method: 'POST', + type: 'REST', + userId: '', + version: '1', + params: {}, + files: {}, + }, + statusCode: 200, + metadata: { + jobId: 11, + }, + }, + ], + }, + }, + }, + { + name: 'intercom', + description: 'Successful group call to add user to company', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'user@5', + groupId: 'rudderlabs', + channel: 'web', + context: { + traits: { + email: 'test+5@rudderlabs.com', + phone: '+91 9599999999', + firstName: 'John', + lastName: 'Snow', + ownerId: '17', + }, + }, + traits: { + name: 'RudderStack', + size: 500, + website: 'www.rudderstack.com', + industry: 'CDP', + plan: 'enterprise', + }, + type: 'group', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + accessToken: 'testAccessToken', + apiServer: 'standard', + sendAnonymousId: false, + }, + }, + metadata: { + jobId: 12, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + JSON: { + id: '657264e9018c0a647s45', + }, + XML: {}, + FORM: {}, + JSON_ARRAY: {}, + }, + endpoint: 'https://api.intercom.io/contacts/70701240741e45d040/companies', + headers: { + Accept: 'application/json', + Authorization: 'Bearer testAccessToken', + 'Content-Type': 'application/json', + 'Intercom-Version': '2.10', + }, + method: 'POST', + type: 'REST', + userId: '', + version: '1', + params: {}, + files: {}, + }, + statusCode: 200, + metadata: { + jobId: 12, + }, + }, + ], + }, + }, + }, + { + name: 'intercom', + description: 'Identify rEtl test', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'user@1', + channel: 'web', + context: { + mappedToDestination: true, + }, + traits: { + email: 'test@rudderlabs.com', + phone: '+91 9999999999', + name: 'Test Rudderlabs', + owner_id: 13, + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + accessToken: 'testAccessToken', + apiServer: 'standard', + sendAnonymousId: false, + }, + }, + metadata: { + jobId: 13, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + JSON: { + email: 'test@rudderlabs.com', + name: 'Test Rudderlabs', + phone: '+91 9999999999', + owner_id: 13, + }, + XML: {}, + FORM: {}, + JSON_ARRAY: {}, + }, + endpoint: 'https://api.intercom.io/contacts', + headers: { + Authorization: 'Bearer testAccessToken', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'Intercom-Version': '2.10', + }, + userId: '', + version: '1', + type: 'REST', + method: 'POST', + files: {}, + params: {}, + }, + metadata: { jobId: 13 }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'intercom', + description: 'Track rEtl test', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'user@1', + channel: 'web', + context: { + mappedToDestination: true, + }, + traits: { + event_name: 'Product Viewed', + user_id: 'user@1', + revenue: { + amount: 1232, + currency: 'inr', + test: 123, + }, + price: { + amount: 3000, + currency: 'USD', + }, + }, + event: 'Product Viewed', + type: 'track', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + accessToken: 'testAccessToken', + apiServer: 'standard', + sendAnonymousId: false, + }, + }, + metadata: { + jobId: 13, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + JSON: { + event_name: 'Product Viewed', + price: { + amount: 3000, + currency: 'USD', + }, + revenue: { + amount: 1232, + currency: 'inr', + test: 123, + }, + user_id: 'user@1', + }, + XML: {}, + FORM: {}, + JSON_ARRAY: {}, + }, + endpoint: 'https://api.intercom.io/events', + headers: { + Authorization: 'Bearer testAccessToken', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'Intercom-Version': '2.10', + }, + userId: '', + version: '1', + type: 'REST', + method: 'POST', + files: {}, + params: {}, + }, + metadata: { jobId: 13 }, + statusCode: 200, + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/destinations/intercom/router/data.ts b/test/integrations/destinations/intercom/router/data.ts new file mode 100644 index 00000000000..a4fae8c3953 --- /dev/null +++ b/test/integrations/destinations/intercom/router/data.ts @@ -0,0 +1,302 @@ +export const data = [ + { + name: 'intercom', + description: 'Intercom router tests', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + message: { + userId: 'user@1', + channel: 'web', + context: { + traits: { + age: 23, + email: 'test@rudderlabs.com', + phone: '+91 9999999999', + firstName: 'Test', + lastName: 'Rudderlabs', + address: 'california usa', + ownerId: '13', + }, + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + accessToken: 'testAccessToken', + apiServer: 'standard', + sendAnonymousId: false, + }, + }, + metadata: { + jobId: 1, + }, + }, + { + message: { + userId: 'user@3', + channel: 'web', + context: { + traits: { + age: 32, + email: 'test+3@rudderlabs.com', + phone: '+91 9399999999', + firstName: 'Test', + lastName: 'RudderStack', + ownerId: '15', + }, + }, + properties: { + revenue: { + amount: 1232, + currency: 'inr', + test: 123, + }, + price: { + amount: 3000, + currency: 'USD', + }, + }, + event: 'Product Viewed', + type: 'track', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + accessToken: 'testAccessToken', + apiServer: 'standard', + sendAnonymousId: false, + }, + }, + metadata: { + jobId: 2, + }, + }, + { + message: { + userId: 'user@5', + groupId: 'rudderlabs', + channel: 'web', + context: { + traits: { + email: 'test+5@rudderlabs.com', + phone: '+91 9599999999', + firstName: 'John', + lastName: 'Snow', + ownerId: '17', + }, + }, + traits: { + name: 'RudderStack', + size: 500, + website: 'www.rudderstack.com', + industry: 'CDP', + plan: 'enterprise', + }, + type: 'group', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + accessToken: 'testAccessToken', + apiServer: 'standard', + sendAnonymousId: false, + }, + }, + metadata: { + jobId: 3, + }, + }, + ], + destType: 'intercom', + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batched: false, + batchedRequest: { + body: { + JSON: { + email: 'test@rudderlabs.com', + external_id: 'user@1', + last_seen_at: 1700628164, + name: 'Test Rudderlabs', + owner_id: 13, + phone: '+91 9999999999', + custom_attributes: { + address: 'california usa', + age: 23, + }, + }, + XML: {}, + FORM: {}, + JSON_ARRAY: {}, + }, + endpoint: 'https://api.intercom.io/contacts', + files: {}, + headers: { + Authorization: 'Bearer testAccessToken', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'Intercom-Version': '2.10', + }, + method: 'POST', + params: {}, + type: 'REST', + version: '1', + }, + destination: { + Config: { + accessToken: 'testAccessToken', + apiServer: 'standard', + sendAnonymousId: false, + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + metadata: [ + { + jobId: 1, + }, + ], + statusCode: 200, + }, + { + batched: false, + batchedRequest: { + body: { + FORM: {}, + JSON: { + created_at: 1700628164, + email: 'test+3@rudderlabs.com', + event_name: 'Product Viewed', + metadata: { + price: { + amount: 3000, + currency: 'USD', + }, + revenue: { + amount: 1232, + currency: 'inr', + test: 123, + }, + }, + user_id: 'user@3', + }, + JSON_ARRAY: {}, + XML: {}, + }, + endpoint: 'https://api.intercom.io/events', + files: {}, + headers: { + Authorization: 'Bearer testAccessToken', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'Intercom-Version': '2.10', + }, + method: 'POST', + params: {}, + type: 'REST', + version: '1', + }, + destination: { + Config: { + accessToken: 'testAccessToken', + apiServer: 'standard', + sendAnonymousId: false, + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + metadata: [ + { + jobId: 2, + }, + ], + statusCode: 200, + }, + { + batched: false, + batchedRequest: { + body: { + JSON: { + id: '657264e9018c0a647s45', + }, + XML: {}, + FORM: {}, + JSON_ARRAY: {}, + }, + endpoint: 'https://api.intercom.io/contacts/70701240741e45d040/companies', + files: {}, + headers: { + Authorization: 'Bearer testAccessToken', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'Intercom-Version': '2.10', + }, + method: 'POST', + params: {}, + type: 'REST', + version: '1', + }, + destination: { + Config: { + accessToken: 'testAccessToken', + apiServer: 'standard', + sendAnonymousId: false, + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + metadata: [ + { + jobId: 3, + }, + ], + statusCode: 200, + }, + ], + }, + }, + }, + }, +]; From 39e93cec4233e7bfc86f3e99adbc770bedd56db8 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Thu, 7 Dec 2023 12:57:50 +0530 Subject: [PATCH 08/14] feat(intercom): upgrade intercom version from 1.4 to 2.10 --- test/integrations/destinations/intercom/processor/data.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integrations/destinations/intercom/processor/data.ts b/test/integrations/destinations/intercom/processor/data.ts index c1d5370ec5b..157748b7ea8 100644 --- a/test/integrations/destinations/intercom/processor/data.ts +++ b/test/integrations/destinations/intercom/processor/data.ts @@ -1133,7 +1133,7 @@ export const data = [ }, }, metadata: { - jobId: 13, + jobId: 14, }, }, ], @@ -1178,7 +1178,7 @@ export const data = [ files: {}, params: {}, }, - metadata: { jobId: 13 }, + metadata: { jobId: 14 }, statusCode: 200, }, ], From 3c3dd42f99641e2500bd123efb66ee5382216c4f Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Sat, 9 Dec 2023 10:01:35 +0530 Subject: [PATCH 09/14] chore: reverted apiKey changes --- .../destinations/intercom/procWorkflow.yaml | 2 +- src/cdk/v2/destinations/intercom/utils.js | 2 +- .../v2/destinations/intercom/utils.test.js | 4 +- src/v0/destinations/intercom/deleteUsers.js | 6 +-- .../destinations/intercom/deleteUsers/data.ts | 6 +-- .../destinations/intercom/network.ts | 22 +++++----- .../destinations/intercom/processor/data.ts | 40 +++++++++---------- .../destinations/intercom/router/data.ts | 18 ++++----- 8 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/cdk/v2/destinations/intercom/procWorkflow.yaml b/src/cdk/v2/destinations/intercom/procWorkflow.yaml index 2c9a904bef1..38230a6cd49 100644 --- a/src/cdk/v2/destinations/intercom/procWorkflow.yaml +++ b/src/cdk/v2/destinations/intercom/procWorkflow.yaml @@ -34,7 +34,7 @@ steps: let messageType = $.outputs.messageType; $.assert(messageType, "message Type is not present. Aborting"); $.assert(messageType in {{$.EventType.([.IDENTIFY, .TRACK, .GROUP])}}, "message type " + messageType + " is not supported"); - $.assertConfig(.destination.Config.accessToken, "Access Token is not present. Aborting"); + $.assertConfig(.destination.Config.apiKey, "Access Token is not present. Aborting"); - name: rEtlPayload condition: .message.context.mappedToDestination === true diff --git a/src/cdk/v2/destinations/intercom/utils.js b/src/cdk/v2/destinations/intercom/utils.js index f66b2c0c381..ae1a258b451 100644 --- a/src/cdk/v2/destinations/intercom/utils.js +++ b/src/cdk/v2/destinations/intercom/utils.js @@ -30,7 +30,7 @@ const { */ const getHeaders = (destination) => ({ 'Content-Type': JSON_MIME_TYPE, - Authorization: `Bearer ${destination.Config.accessToken}`, + Authorization: `Bearer ${destination.Config.apiKey}`, Accept: JSON_MIME_TYPE, 'Intercom-Version': '2.10', }); diff --git a/src/cdk/v2/destinations/intercom/utils.test.js b/src/cdk/v2/destinations/intercom/utils.test.js index 61613c1b6c6..33b992944ad 100644 --- a/src/cdk/v2/destinations/intercom/utils.test.js +++ b/src/cdk/v2/destinations/intercom/utils.test.js @@ -230,13 +230,13 @@ describe('getHeaders utility test', () => { it('Should return an object with the correct headers', () => { const destination = { Config: { - accessToken: 'testAccessToken', + apiKey: 'testApiKey', }, }; const expectedHeaders = { 'Content-Type': 'application/json', - Authorization: `Bearer ${destination.Config.accessToken}`, + Authorization: `Bearer ${destination.Config.apiKey}`, Accept: 'application/json', 'Intercom-Version': '2.10', }; diff --git a/src/v0/destinations/intercom/deleteUsers.js b/src/v0/destinations/intercom/deleteUsers.js index db3da30c394..68b6d621d44 100644 --- a/src/v0/destinations/intercom/deleteUsers.js +++ b/src/v0/destinations/intercom/deleteUsers.js @@ -12,8 +12,8 @@ const userDeletionHandler = async (userAttributes, config) => { if (!config) { throw new ConfigurationError('Config for deletion not present'); } - const { accessToken } = config; - if (!accessToken) { + const { apiKey } = config; + if (!apiKey) { throw new ConfigurationError('access token for deletion not present'); } const validUserIds = []; @@ -31,7 +31,7 @@ const userDeletionHandler = async (userAttributes, config) => { }; const requestOptions = { headers: { - Authorization: `Bearer ${accessToken}`, + Authorization: `Bearer ${apiKey}`, Accept: JSON_MIME_TYPE, }, }; diff --git a/test/integrations/destinations/intercom/deleteUsers/data.ts b/test/integrations/destinations/intercom/deleteUsers/data.ts index ae5d588210c..34aa6633137 100644 --- a/test/integrations/destinations/intercom/deleteUsers/data.ts +++ b/test/integrations/destinations/intercom/deleteUsers/data.ts @@ -19,7 +19,7 @@ export const data = [ }, ], config: { - accessToken: 'API_KEY', + apiKey: 'testApiKey', }, }, ], @@ -57,7 +57,7 @@ export const data = [ }, ], config: { - accessToken: 'API_KEY', + apiKey: 'testApiKey', }, }, ], @@ -159,7 +159,7 @@ export const data = [ destType: 'INTERCOM', userAttributes: [{}], config: { - accessToken: 'a=', + apiKey: 'a=', }, }, ], diff --git a/test/integrations/destinations/intercom/network.ts b/test/integrations/destinations/intercom/network.ts index 03eb1a84bcf..7b9013341f5 100644 --- a/test/integrations/destinations/intercom/network.ts +++ b/test/integrations/destinations/intercom/network.ts @@ -8,7 +8,7 @@ const deleteNwData = [ }, headers: { Accept: 'application/json', - Authorization: 'Bearer API_KEY', + Authorization: 'Bearer testApiKey', 'Content-Type': 'application/json', }, }, @@ -35,7 +35,7 @@ const deleteNwData = [ }, headers: { Accept: 'application/json', - Authorization: 'Bearer API_KEY', + Authorization: 'Bearer testApiKey', 'Content-Type': 'application/json', }, }, @@ -56,7 +56,7 @@ const deleteNwData = [ }, headers: { Accept: 'application/json', - Authorization: 'Bearer API_KEY', + Authorization: 'Bearer testApiKey', 'Content-Type': 'application/json', }, }, @@ -77,7 +77,7 @@ const deleteNwData = [ }, headers: { Accept: 'application/json', - Authorization: 'Bearer API_KEY', + Authorization: 'Bearer testApiKey', 'Content-Type': 'application/json', }, }, @@ -101,7 +101,7 @@ const deleteNwData = [ }, headers: { Accept: 'application/json', - Authorization: 'Bearer testAccessToken', + Authorization: 'Bearer testApiKey', 'Content-Type': 'application/json', }, }, @@ -133,7 +133,7 @@ const deleteNwData = [ }, headers: { Accept: 'application/json', - Authorization: 'Bearer testAccessToken', + Authorization: 'Bearer testApiKey', 'Content-Type': 'application/json', }, }, @@ -174,7 +174,7 @@ const deleteNwData = [ }, headers: { Accept: 'application/json', - Authorization: 'Bearer testAccessToken', + Authorization: 'Bearer testApiKey', 'Content-Type': 'application/json', }, }, @@ -215,7 +215,7 @@ const deleteNwData = [ }, headers: { Accept: 'application/json', - Authorization: 'Bearer testAccessToken', + Authorization: 'Bearer testApiKey', 'Content-Type': 'application/json', }, }, @@ -256,7 +256,7 @@ const deleteNwData = [ }, headers: { Accept: 'application/json', - Authorization: 'Bearer testAccessToken', + Authorization: 'Bearer testApiKey', 'Content-Type': 'application/json', }, }, @@ -288,7 +288,7 @@ const deleteNwData = [ }, headers: { Accept: 'application/json', - Authorization: 'Bearer invalidTestAccessToken', + Authorization: 'Bearer invalidApiKey', 'Content-Type': 'application/json', }, }, @@ -321,7 +321,7 @@ const deleteNwData = [ }, headers: { Accept: 'application/json', - Authorization: 'Bearer testAccessToken', + Authorization: 'Bearer testApiKey', 'Content-Type': 'application/json', }, }, diff --git a/test/integrations/destinations/intercom/processor/data.ts b/test/integrations/destinations/intercom/processor/data.ts index 157748b7ea8..a9871a18a06 100644 --- a/test/integrations/destinations/intercom/processor/data.ts +++ b/test/integrations/destinations/intercom/processor/data.ts @@ -33,7 +33,7 @@ export const data = [ }, }, Config: { - accessToken: 'testAccessToken', + apiKey: 'testApiKey', apiServer: 'standard', sendAnonymousId: false, }, @@ -105,7 +105,7 @@ export const data = [ }, }, Config: { - accessToken: 'testAccessToken', + apiKey: 'testApiKey', apiServer: 'standard', sendAnonymousId: false, }, @@ -244,7 +244,7 @@ export const data = [ }, }, Config: { - accessToken: 'testAccessToken', + apiKey: 'testApiKey', apiServer: 'standard', sendAnonymousId: false, }, @@ -282,7 +282,7 @@ export const data = [ }, endpoint: 'https://api.intercom.io/contacts', headers: { - Authorization: 'Bearer testAccessToken', + Authorization: 'Bearer testApiKey', 'Content-Type': 'application/json', Accept: 'application/json', 'Intercom-Version': '2.10', @@ -335,7 +335,7 @@ export const data = [ }, }, Config: { - accessToken: 'testAccessToken', + apiKey: 'testApiKey', apiServer: 'standard', sendAnonymousId: false, }, @@ -372,7 +372,7 @@ export const data = [ }, endpoint: 'https://api.intercom.io/contacts/7070129940741e45d040', headers: { - Authorization: 'Bearer testAccessToken', + Authorization: 'Bearer testApiKey', 'Content-Type': 'application/json', Accept: 'application/json', 'Intercom-Version': '2.10', @@ -431,7 +431,7 @@ export const data = [ }, }, Config: { - accessToken: 'testAccessToken', + apiKey: 'testApiKey', apiServer: 'standard', sendAnonymousId: false, }, @@ -508,7 +508,7 @@ export const data = [ }, }, Config: { - accessToken: 'invalidTestAccessToken', + apiKey: 'invalidApiKey', apiServer: 'standard', sendAnonymousId: false, }, @@ -590,7 +590,7 @@ export const data = [ }, }, Config: { - accessToken: 'testAccessToken', + apiKey: 'testApiKey', apiServer: 'standard', sendAnonymousId: false, }, @@ -673,7 +673,7 @@ export const data = [ }, }, Config: { - accessToken: 'testAccessToken', + apiKey: 'testApiKey', apiServer: 'standard', sendAnonymousId: false, }, @@ -717,7 +717,7 @@ export const data = [ endpoint: 'https://api.intercom.io/events', headers: { Accept: 'application/json', - Authorization: 'Bearer testAccessToken', + Authorization: 'Bearer testApiKey', 'Content-Type': 'application/json', 'Intercom-Version': '2.10', }, @@ -777,7 +777,7 @@ export const data = [ }, }, Config: { - accessToken: 'testAccessToken', + apiKey: 'testApiKey', apiServer: 'standard', sendAnonymousId: false, }, @@ -855,7 +855,7 @@ export const data = [ }, }, Config: { - accessToken: 'testAccessToken', + apiKey: 'testApiKey', apiServer: 'standard', sendAnonymousId: false, }, @@ -891,7 +891,7 @@ export const data = [ endpoint: 'https://api.intercom.io/companies', headers: { Accept: 'application/json', - Authorization: 'Bearer testAccessToken', + Authorization: 'Bearer testApiKey', 'Content-Type': 'application/json', 'Intercom-Version': '2.10', }, @@ -952,7 +952,7 @@ export const data = [ }, }, Config: { - accessToken: 'testAccessToken', + apiKey: 'testApiKey', apiServer: 'standard', sendAnonymousId: false, }, @@ -982,7 +982,7 @@ export const data = [ endpoint: 'https://api.intercom.io/contacts/70701240741e45d040/companies', headers: { Accept: 'application/json', - Authorization: 'Bearer testAccessToken', + Authorization: 'Bearer testApiKey', 'Content-Type': 'application/json', 'Intercom-Version': '2.10', }, @@ -1035,7 +1035,7 @@ export const data = [ }, }, Config: { - accessToken: 'testAccessToken', + apiKey: 'testApiKey', apiServer: 'standard', sendAnonymousId: false, }, @@ -1067,7 +1067,7 @@ export const data = [ }, endpoint: 'https://api.intercom.io/contacts', headers: { - Authorization: 'Bearer testAccessToken', + Authorization: 'Bearer testApiKey', 'Content-Type': 'application/json', Accept: 'application/json', 'Intercom-Version': '2.10', @@ -1127,7 +1127,7 @@ export const data = [ }, }, Config: { - accessToken: 'testAccessToken', + apiKey: 'testApiKey', apiServer: 'standard', sendAnonymousId: false, }, @@ -1166,7 +1166,7 @@ export const data = [ }, endpoint: 'https://api.intercom.io/events', headers: { - Authorization: 'Bearer testAccessToken', + Authorization: 'Bearer testApiKey', 'Content-Type': 'application/json', Accept: 'application/json', 'Intercom-Version': '2.10', diff --git a/test/integrations/destinations/intercom/router/data.ts b/test/integrations/destinations/intercom/router/data.ts index a4fae8c3953..6e6f6391ac7 100644 --- a/test/integrations/destinations/intercom/router/data.ts +++ b/test/integrations/destinations/intercom/router/data.ts @@ -35,7 +35,7 @@ export const data = [ }, }, Config: { - accessToken: 'testAccessToken', + apiKey: 'testApiKey', apiServer: 'standard', sendAnonymousId: false, }, @@ -81,7 +81,7 @@ export const data = [ }, }, Config: { - accessToken: 'testAccessToken', + apiKey: 'testApiKey', apiServer: 'standard', sendAnonymousId: false, }, @@ -122,7 +122,7 @@ export const data = [ }, }, Config: { - accessToken: 'testAccessToken', + apiKey: 'testApiKey', apiServer: 'standard', sendAnonymousId: false, }, @@ -165,7 +165,7 @@ export const data = [ endpoint: 'https://api.intercom.io/contacts', files: {}, headers: { - Authorization: 'Bearer testAccessToken', + Authorization: 'Bearer testApiKey', 'Content-Type': 'application/json', Accept: 'application/json', 'Intercom-Version': '2.10', @@ -177,7 +177,7 @@ export const data = [ }, destination: { Config: { - accessToken: 'testAccessToken', + apiKey: 'testApiKey', apiServer: 'standard', sendAnonymousId: false, }, @@ -222,7 +222,7 @@ export const data = [ endpoint: 'https://api.intercom.io/events', files: {}, headers: { - Authorization: 'Bearer testAccessToken', + Authorization: 'Bearer testApiKey', 'Content-Type': 'application/json', Accept: 'application/json', 'Intercom-Version': '2.10', @@ -234,7 +234,7 @@ export const data = [ }, destination: { Config: { - accessToken: 'testAccessToken', + apiKey: 'testApiKey', apiServer: 'standard', sendAnonymousId: false, }, @@ -265,7 +265,7 @@ export const data = [ endpoint: 'https://api.intercom.io/contacts/70701240741e45d040/companies', files: {}, headers: { - Authorization: 'Bearer testAccessToken', + Authorization: 'Bearer testApiKey', 'Content-Type': 'application/json', Accept: 'application/json', 'Intercom-Version': '2.10', @@ -277,7 +277,7 @@ export const data = [ }, destination: { Config: { - accessToken: 'testAccessToken', + apiKey: 'testApiKey', apiServer: 'standard', sendAnonymousId: false, }, From 1fbdacc3a9b7b7ef9c22d1d6ad2d0fb355270d06 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Sat, 9 Dec 2023 11:32:04 +0530 Subject: [PATCH 10/14] chore: added utility tests for searchContact and createOrUpdateCompany function --- src/cdk/v2/destinations/intercom/utils.js | 3 +- .../v2/destinations/intercom/utils.test.js | 238 ++++++++++++++++++ .../destinations/intercom/processor/data.ts | 2 +- 3 files changed, 240 insertions(+), 3 deletions(-) diff --git a/src/cdk/v2/destinations/intercom/utils.js b/src/cdk/v2/destinations/intercom/utils.js index ae1a258b451..a66872761ac 100644 --- a/src/cdk/v2/destinations/intercom/utils.js +++ b/src/cdk/v2/destinations/intercom/utils.js @@ -144,7 +144,7 @@ const searchContact = async (message, destination) => { } throw new NetworkError( - `Unable to search contact due to : ${JSON.stringify(processedUserResponse?.response?.data)}`, + `Unable to search contact due to : ${JSON.stringify(processedUserResponse?.response?.errors)}`, processedUserResponse?.status, { [tags]: getDynamicErrorType(processedUserResponse?.status), @@ -163,7 +163,6 @@ const searchContact = async (message, destination) => { const createOrUpdateCompany = async (payload, destination) => { const headers = getHeaders(destination); const finalPayload = JSON.stringify(removeUndefinedAndNullValues(payload)); - const baseEndPoint = getBaseEndpoint(destination); const endpoint = `${baseEndPoint}/${CREATE_OR_UPDATE_COMPANY_ENDPOINT}`; const response = await httpPOST(endpoint, finalPayload, { diff --git a/src/cdk/v2/destinations/intercom/utils.test.js b/src/cdk/v2/destinations/intercom/utils.test.js index 33b992944ad..daab45e6021 100644 --- a/src/cdk/v2/destinations/intercom/utils.test.js +++ b/src/cdk/v2/destinations/intercom/utils.test.js @@ -1,14 +1,19 @@ +const axios = require('axios'); const { getName, getHeaders, + searchContact, getLookUpField, getBaseEndpoint, getCustomAttributes, addMetadataToPayload, + createOrUpdateCompany, separateReservedAndRestMetadata, } = require('./utils'); const { BASE_ENDPOINT, BASE_EU_ENDPOINT, BASE_AU_ENDPOINT } = require('./config'); +jest.mock('axios'); + describe('separateReservedAndRestMetadata utility test', () => { it('separate reserved and rest metadata', () => { const metadata = { @@ -348,3 +353,236 @@ describe('addMetadataToPayload utility test', () => { }); }); }); + +describe('searchContact utility test', () => { + it('Should successfully search contact by email', async () => { + const message = { context: { traits: { email: 'test@rudderlabs.com' } } }; + const destination = { Config: { apiKey: 'testApiKey', apiServer: 'us' } }; + axios.post.mockResolvedValue({ + status: 200, + data: { + type: 'list', + total_count: 1, + pages: { + type: 'pages', + page: 1, + per_page: 50, + total_pages: 1, + }, + data: [ + { + type: 'contact', + id: '1', + email: 'test@rudderlabs.com', + }, + ], + }, + }); + + const result = await searchContact(message, destination); + expect(result).toEqual('1'); + }); + + it('Should return first contact id if multiple contact exist with give search field', async () => { + const message = { + context: { + traits: { email: 'test@rudderlabs.com', phone: '+91 9999999999' }, + integrations: { INTERCOM: { lookup: 'phone' } }, + }, + }; + const destination = { Config: { apiKey: 'testApiKey', apiServer: 'us' } }; + axios.post.mockResolvedValue({ + status: 200, + data: { + type: 'list', + total_count: 1, + pages: { + type: 'pages', + page: 1, + per_page: 50, + total_pages: 1, + }, + data: [ + { + type: 'contact', + id: '1', + email: 'test@rudderlabs.com', + phone: '+91 9999999999', + }, + { + type: 'contact', + id: '2', + email: 'test+1@rudderlabs.com', + phone: '+91 9999999999', + }, + ], + }, + }); + + const result = await searchContact(message, destination); + expect(result).toEqual('1'); + }); + + it('Should return null if no contact is found', async () => { + const message = { + context: { + traits: { email: 'test+10@rudderlabs.com', phone: '+91 9999999999' }, + integrations: { INTERCOM: { lookup: 'email' } }, + }, + }; + const destination = { Config: { apiKey: 'testApiKey', apiServer: 'us' } }; + axios.post.mockResolvedValue({ + status: 200, + data: { + type: 'list', + total_count: 0, + pages: { + type: 'pages', + page: 1, + per_page: 50, + total_pages: 0, + }, + data: [], + }, + }); + + const result = await searchContact(message, destination); + expect(result).toBeNull(); + }); + + it('Should throw an error in case if axios calls returns an error', async () => { + const message = { + context: { + traits: { email: 'test+3@rudderlabs.com', phone: '+91 9999999999' }, + integrations: { INTERCOM: { lookup: 'email' } }, + }, + }; + const destination = { Config: { apiKey: 'invalidTestApiKey', apiServer: 'us' } }; + axios.post.mockRejectedValue({ + status: 401, + data: { + type: 'error.list', + request_id: 'request_400', + errors: [ + { + code: 'unauthorized', + message: 'Access Token Invalid', + }, + ], + }, + }); + + try { + const result = await searchContact(message, destination); + expect(result).toEqual(''); + } catch (error) { + expect(error.message).toEqual( + 'Unable to search contact due to : [{"code":"unauthorized","message":"Access Token Invalid"}]', + ); + } + }); +}); + +describe('createOrUpdateCompany utility test', () => { + it('Should successfully create company', async () => { + const payload = { + company_id: 'rudderlabs', + name: 'RudderStack', + website: 'www.rudderstack.com', + plan: 'enterprise', + size: 500, + industry: 'CDP', + custom_attributes: {}, + }; + const destination = { Config: { apiKey: 'testApiKey', apiServer: 'us' } }; + axios.post.mockResolvedValue({ + status: 200, + data: { + type: 'company', + company_id: 'rudderlabs', + id: '1', + name: 'RudderStack', + website: 'www.rudderstack.com', + plan: 'enterprise', + size: 500, + industry: 'CDP', + remote_created_at: 1374138000, + created_at: 1701930212, + updated_at: 1701930212, + }, + }); + + const result = await createOrUpdateCompany(payload, destination); + expect(result).toEqual('1'); + }); + + it('Should throw an error in case if axios calls returns an error', async () => { + const payload = { + company_id: 'rudderlabs', + name: 'RudderStack', + website: 'www.rudderstack.com', + plan: 'enterprise', + size: 500, + industry: 'CDP', + testData: true, + }; + const destination = { Config: { apiKey: 'testApiKey', apiServer: 'us' } }; + axios.post.mockRejectedValue({ + status: 400, + data: { + type: 'error.list', + request_id: 'request_400', + errors: [ + { + code: 'bad_request', + message: "bad 'testData' parameter", + }, + ], + }, + }); + + try { + const result = await createOrUpdateCompany(payload, destination); + expect(result).toEqual(''); + } catch (error) { + expect(error.message).toEqual( + 'Unable to Create or Update Company due to : [{"code":"bad_request","message":"bad \'testData\' parameter"}]', + ); + } + }); + + it('Should throw an error in case if axios calls returns an error', async () => { + const payload = { + company_id: 'rudderlabs', + name: 'RudderStack', + website: 'www.rudderstack.com', + plan: 'enterprise', + size: 500, + industry: 'CDP', + testData: true, + }; + const destination = { Config: { apiKey: 'invalidTestApiKey', apiServer: 'us' } }; + axios.post.mockRejectedValue({ + status: 400, + data: { + type: 'error.list', + request_id: 'request_400', + errors: [ + { + code: 'unauthorized', + message: 'Access Token Invalid', + }, + ], + }, + }); + + try { + const result = await createOrUpdateCompany(payload, destination); + expect(result).toEqual(''); + } catch (error) { + expect(error.message).toEqual( + 'Unable to Create or Update Company due to : [{"code":"unauthorized","message":"Access Token Invalid"}]', + ); + } + }); +}); diff --git a/test/integrations/destinations/intercom/processor/data.ts b/test/integrations/destinations/intercom/processor/data.ts index a9871a18a06..e8dedcf6ec0 100644 --- a/test/integrations/destinations/intercom/processor/data.ts +++ b/test/integrations/destinations/intercom/processor/data.ts @@ -531,7 +531,7 @@ export const data = [ }, statusCode: 401, error: - '{"message":"{\\"message\\":\\"Unable to search contact due to : undefined: Workflow: procWorkflow, Step: searchContact, ChildStep: undefined, OriginalError: Unable to search contact due to : undefined\\",\\"destinationResponse\\":{\\"response\\":{\\"type\\":\\"error.list\\",\\"request_id\\":\\"request_1\\",\\"errors\\":[{\\"code\\":\\"unauthorized\\",\\"message\\":\\"Access Token Invalid\\"}]},\\"status\\":401}}","destinationResponse":{"response":{"type":"error.list","request_id":"request_1","errors":[{"code":"unauthorized","message":"Access Token Invalid"}]},"status":401}}', + '{"message":"{\\"message\\":\\"Unable to search contact due to : [{\\\\\\"code\\\\\\":\\\\\\"unauthorized\\\\\\",\\\\\\"message\\\\\\":\\\\\\"Access Token Invalid\\\\\\"}]: Workflow: procWorkflow, Step: searchContact, ChildStep: undefined, OriginalError: Unable to search contact due to : [{\\\\\\"code\\\\\\":\\\\\\"unauthorized\\\\\\",\\\\\\"message\\\\\\":\\\\\\"Access Token Invalid\\\\\\"}]\\",\\"destinationResponse\\":{\\"response\\":{\\"type\\":\\"error.list\\",\\"request_id\\":\\"request_1\\",\\"errors\\":[{\\"code\\":\\"unauthorized\\",\\"message\\":\\"Access Token Invalid\\"}]},\\"status\\":401}}","destinationResponse":{"response":{"type":"error.list","request_id":"request_1","errors":[{"code":"unauthorized","message":"Access Token Invalid"}]},"status":401}}', statTags: { errorCategory: 'network', errorType: 'aborted', From 27590261ace7d6d19dde92b517e1543db0df376c Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Sat, 9 Dec 2023 12:22:20 +0530 Subject: [PATCH 11/14] chore: monir refactors --- .../destinations/intercom/procWorkflow.yaml | 16 +++++++--------- src/cdk/v2/destinations/intercom/utils.js | 15 +++++++++------ .../v2/destinations/intercom/utils.test.js | 19 ++++++++----------- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/cdk/v2/destinations/intercom/procWorkflow.yaml b/src/cdk/v2/destinations/intercom/procWorkflow.yaml index 38230a6cd49..a60e5676e5e 100644 --- a/src/cdk/v2/destinations/intercom/procWorkflow.yaml +++ b/src/cdk/v2/destinations/intercom/procWorkflow.yaml @@ -3,8 +3,6 @@ bindings: path: ../../../../constants - path: ./utils exportAll: true - - path: ./config - exportAll: true - path: ../../bindings/jsontemplate exportAll: true - name: defaultRequestConfig @@ -61,11 +59,11 @@ steps: role: .traits.role || .context.traits.role, signed_up_at: $.toSeconds(.traits.createdAt || .context.traits.createdAt), owner_id: .traits.ownerId || .context.traits.ownerId ? Number(.traits.ownerId || .context.traits.ownerId) : undefined, - unsubscribed_from_emails: .traits.unsubscribedFromEmails || .context.traits.unsubscribedFromEmails, - custom_attributes: .context.traits || .traits + unsubscribed_from_emails: .traits.unsubscribedFromEmails || .context.traits.unsubscribedFromEmails }) payload.name = $.getName(.message); - payload.custom_attributes = $.getCustomAttributes(payload, $.ReservedUserAttributes); + payload.custom_attributes = .message.context.traits || {}; + payload.custom_attributes = $.filterCustomAttributes(payload, "user"); payload.external_id = !(payload.external_id) && .destination.Config.sendAnonymousId && .message.anonymousId ? .message.anonymousId : payload.external_id; payload; @@ -123,10 +121,10 @@ steps: size: Number(.traits.size || .context.traits.size), industry: .traits.industry || .context.traits.industry, monthly_spend: .traits.monthlySpend || .context.traits.monthlySpend ? Number(.traits.monthlySpend || .context.traits.monthlySpend) : undefined, - remote_created_at: .traits.remoteCreatedAt || .context.traits.remoteCreatedAt ? Number(.traits.remoteCreatedAt || .context.traits.remoteCreatedAt) : undefined, - custom_attributes: .traits + remote_created_at: .traits.remoteCreatedAt || .context.traits.remoteCreatedAt ? Number(.traits.remoteCreatedAt || .context.traits.remoteCreatedAt) : undefined }) - payload.custom_attributes = $.getCustomAttributes(payload, $.ReservedCompanyAttributes); + payload.custom_attributes = .message.traits || {}; + payload.custom_attributes = $.filterCustomAttributes(payload, "company"); payload; - name: prepareAddUserToCompanyPayload @@ -150,7 +148,7 @@ steps: let payload = { requestBodyJson: .message.context.mappedToDestination === true ? $.outputs.rEtlPayload : $.outputs.groupEtlPayload } - payload.endpoint = $.getBaseEndpoint(.destination) + "/" + $.CREATE_OR_UPDATE_COMPANY_ENDPOINT; + payload.endpoint = $.getBaseEndpoint(.destination) + "/" + "companies"; payload; - name: prepareGroupPayload diff --git a/src/cdk/v2/destinations/intercom/utils.js b/src/cdk/v2/destinations/intercom/utils.js index a66872761ac..8c09a2c37f6 100644 --- a/src/cdk/v2/destinations/intercom/utils.js +++ b/src/cdk/v2/destinations/intercom/utils.js @@ -19,7 +19,9 @@ const { MetadataTypes, BASE_EU_ENDPOINT, BASE_AU_ENDPOINT, + ReservedUserAttributes, SEARCH_CONTACT_ENDPOINT, + ReservedCompanyAttributes, CREATE_OR_UPDATE_COMPANY_ENDPOINT, } = require('./config'); @@ -88,12 +90,13 @@ const getName = (message) => { /** * Returns custom attributes for identify and group calls (for contact and company in intercom) * @param {*} payload - * @param {*} ReservedAttributes + * @param {*} type * @returns */ -const getCustomAttributes = (payload, ReservedAttributes) => { +const filterCustomAttributes = (payload, type) => { + const ReservedAttributes = type === 'user' ? ReservedUserAttributes : ReservedCompanyAttributes; let { custom_attributes: customAttributes } = payload; - if (customAttributes && typeof customAttributes === 'object') { + if (customAttributes) { ReservedAttributes.forEach((trait) => { if (customAttributes[trait]) delete customAttributes[trait]; }); @@ -161,7 +164,7 @@ const searchContact = async (message, destination) => { * @returns */ const createOrUpdateCompany = async (payload, destination) => { - const headers = getHeaders(destination); + const headers = getHeaders(destination); const finalPayload = JSON.stringify(removeUndefinedAndNullValues(payload)); const baseEndPoint = getBaseEndpoint(destination); const endpoint = `${baseEndPoint}/${CREATE_OR_UPDATE_COMPANY_ENDPOINT}`; @@ -175,7 +178,7 @@ const createOrUpdateCompany = async (payload, destination) => { if (isHttpStatusSuccess(processedResponse.status)) { return processedResponse.response?.id; } - + throw new NetworkError( `Unable to Create or Update Company due to : ${JSON.stringify( processedResponse?.response?.errors, @@ -242,8 +245,8 @@ module.exports = { searchContact, getLookUpField, getBaseEndpoint, - getCustomAttributes, addMetadataToPayload, createOrUpdateCompany, + filterCustomAttributes, separateReservedAndRestMetadata, }; diff --git a/src/cdk/v2/destinations/intercom/utils.test.js b/src/cdk/v2/destinations/intercom/utils.test.js index daab45e6021..e9f43b3dc2a 100644 --- a/src/cdk/v2/destinations/intercom/utils.test.js +++ b/src/cdk/v2/destinations/intercom/utils.test.js @@ -5,9 +5,9 @@ const { searchContact, getLookUpField, getBaseEndpoint, - getCustomAttributes, addMetadataToPayload, createOrUpdateCompany, + filterCustomAttributes, separateReservedAndRestMetadata, } = require('./utils'); const { BASE_ENDPOINT, BASE_EU_ENDPOINT, BASE_AU_ENDPOINT } = require('./config'); @@ -299,27 +299,24 @@ describe('getName utility test', () => { }); }); -describe('getCustomAttributes utility test', () => { +describe('filterCustomAttributes utility test', () => { it('Should return an empty object when all custom attributes are reserved attributes', () => { - const payload = { custom_attributes: { attribute1: 'value1', attribute2: 'value2' } }; - const ReservedAttributes = ['attribute1', 'attribute2']; - const result = getCustomAttributes(payload, ReservedAttributes); + const payload = { custom_attributes: { email: 'test@rudder.com', name: 'rudder test' } }; + const result = filterCustomAttributes(payload, 'user'); expect(result).toEqual({}); }); it('Should return a flattened object when custom attributes are not null, not reserved attributes and nested', () => { const payload = { - custom_attributes: { attribute1: 'value1', attribute2: { nestedAttribute: 'nestedValue' } }, + custom_attributes: { source: 'rudder-js-sdk', data: { nestedAttribute: 'nestedValue' } }, }; - const ReservedAttributes = ['attribute3']; - const result = getCustomAttributes(payload, ReservedAttributes); - expect(result).toEqual({ attribute1: 'value1', 'attribute2.nestedAttribute': 'nestedValue' }); + const result = filterCustomAttributes(payload, 'user'); + expect(result).toEqual({ source: 'rudder-js-sdk', 'data.nestedAttribute': 'nestedValue' }); }); it('Should return null when custom_attributes is null', () => { const payload = { custom_attributes: null }; - const ReservedAttributes = []; - const result = getCustomAttributes(payload, ReservedAttributes); + const result = filterCustomAttributes(payload, 'company'); expect(result).toBeNull(); }); }); From 170e0947d1f1743cadc15f4180cc10fc70113912 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Sat, 9 Dec 2023 12:29:44 +0530 Subject: [PATCH 12/14] chore: added negative test in router --- src/cdk/v2/destinations/intercom/utils.js | 2 +- .../destinations/intercom/network.ts | 33 +++++++++ .../destinations/intercom/router/data.ts | 73 +++++++++++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) diff --git a/src/cdk/v2/destinations/intercom/utils.js b/src/cdk/v2/destinations/intercom/utils.js index 8c09a2c37f6..9311d9efb1d 100644 --- a/src/cdk/v2/destinations/intercom/utils.js +++ b/src/cdk/v2/destinations/intercom/utils.js @@ -164,7 +164,7 @@ const searchContact = async (message, destination) => { * @returns */ const createOrUpdateCompany = async (payload, destination) => { - const headers = getHeaders(destination); + const headers = getHeaders(destination); const finalPayload = JSON.stringify(removeUndefinedAndNullValues(payload)); const baseEndPoint = getBaseEndpoint(destination); const endpoint = `${baseEndPoint}/${CREATE_OR_UPDATE_COMPANY_ENDPOINT}`; diff --git a/test/integrations/destinations/intercom/network.ts b/test/integrations/destinations/intercom/network.ts index 7b9013341f5..339ef2746ce 100644 --- a/test/integrations/destinations/intercom/network.ts +++ b/test/integrations/destinations/intercom/network.ts @@ -342,6 +342,39 @@ const deleteNwData = [ }, }, }, + { + httpReq: { + method: 'post', + url: 'https://api.intercom.io/companies', + data: { + company_id: 'rudderlabs', + name: 'RudderStack', + website: 'www.rudderstack.com', + plan: 'enterprise', + size: 500, + industry: 'CDP', + custom_attributes: { isOpenSource: true }, + }, + headers: { + Accept: 'application/json', + Authorization: 'Bearer testApiKey', + 'Content-Type': 'application/json', + }, + }, + httpRes: { + status: 401, + data: { + type: 'error.list', + request_id: 'request_1', + errors: [ + { + code: 'parameter_invalid', + message: "Custom attribute 'isOpenSource' does not exist", + }, + ], + }, + }, + }, ]; const deliveryCallsData = [ { diff --git a/test/integrations/destinations/intercom/router/data.ts b/test/integrations/destinations/intercom/router/data.ts index 6e6f6391ac7..f7c5942703d 100644 --- a/test/integrations/destinations/intercom/router/data.ts +++ b/test/integrations/destinations/intercom/router/data.ts @@ -131,6 +131,48 @@ export const data = [ jobId: 3, }, }, + { + message: { + userId: 'user@6', + groupId: 'rudderlabs', + channel: 'web', + context: { + traits: { + email: 'test+5@rudderlabs.com', + phone: '+91 9599999999', + firstName: 'John', + lastName: 'Snow', + ownerId: '17', + }, + }, + traits: { + name: 'RudderStack', + size: 500, + website: 'www.rudderstack.com', + industry: 'CDP', + plan: 'enterprise', + isOpenSource: true, + }, + type: 'group', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiKey: 'testApiKey', + apiServer: 'standard', + sendAnonymousId: false, + }, + }, + metadata: { + jobId: 4, + }, + }, ], destType: 'intercom', }, @@ -294,6 +336,37 @@ export const data = [ ], statusCode: 200, }, + { + batched: false, + error: + '{"message":"Unable to Create or Update Company due to : [{\\"code\\":\\"parameter_invalid\\",\\"message\\":\\"Custom attribute \'isOpenSource\' does not exist\\"}]","destinationResponse":{"response":{"type":"error.list","request_id":"request_1","errors":[{"code":"parameter_invalid","message":"Custom attribute \'isOpenSource\' does not exist"}]},"status":401}}', + statTags: { + destType: 'INTERCOM', + errorCategory: 'network', + errorType: 'aborted', + feature: 'router', + implementation: 'cdkV2', + module: 'destination', + }, + destination: { + Config: { + apiKey: 'testApiKey', + apiServer: 'standard', + sendAnonymousId: false, + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + metadata: [ + { + jobId: 4, + }, + ], + statusCode: 401, + }, ], }, }, From 9b8ec30f91829a783ce87530d9d6ffff7e2ea501 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Mon, 11 Dec 2023 09:56:18 +0530 Subject: [PATCH 13/14] chore: code review changes --- src/cdk/v2/destinations/intercom/utils.test.js | 5 ++++- src/v0/destinations/intercom/deleteUsers.js | 2 +- test/integrations/destinations/intercom/deleteUsers/data.ts | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/cdk/v2/destinations/intercom/utils.test.js b/src/cdk/v2/destinations/intercom/utils.test.js index e9f43b3dc2a..f9a03fd3868 100644 --- a/src/cdk/v2/destinations/intercom/utils.test.js +++ b/src/cdk/v2/destinations/intercom/utils.test.js @@ -12,7 +12,10 @@ const { } = require('./utils'); const { BASE_ENDPOINT, BASE_EU_ENDPOINT, BASE_AU_ENDPOINT } = require('./config'); -jest.mock('axios'); +jest.mock('axios', () => ({ + ...jest.requireActual('axios'), + post: jest.fn(), +})); describe('separateReservedAndRestMetadata utility test', () => { it('separate reserved and rest metadata', () => { diff --git a/src/v0/destinations/intercom/deleteUsers.js b/src/v0/destinations/intercom/deleteUsers.js index 68b6d621d44..1699ae63cda 100644 --- a/src/v0/destinations/intercom/deleteUsers.js +++ b/src/v0/destinations/intercom/deleteUsers.js @@ -14,7 +14,7 @@ const userDeletionHandler = async (userAttributes, config) => { } const { apiKey } = config; if (!apiKey) { - throw new ConfigurationError('access token for deletion not present'); + throw new ConfigurationError('The access token is not available'); } const validUserIds = []; userAttributes.forEach((userAttribute) => { diff --git a/test/integrations/destinations/intercom/deleteUsers/data.ts b/test/integrations/destinations/intercom/deleteUsers/data.ts index 34aa6633137..58285ee683e 100644 --- a/test/integrations/destinations/intercom/deleteUsers/data.ts +++ b/test/integrations/destinations/intercom/deleteUsers/data.ts @@ -140,7 +140,7 @@ export const data = [ body: [ { statusCode: 400, - error: 'access token for deletion not present', + error: 'The access token is not available', }, ], }, From e3325da338f9db6f45b5ca32055ae7b748ce3c01 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Wed, 20 Dec 2023 10:57:01 +0530 Subject: [PATCH 14/14] chore: code review changes --- .../v2/destinations/intercom/procWorkflow.yaml | 2 +- src/cdk/v2/destinations/intercom/utils.js | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/cdk/v2/destinations/intercom/procWorkflow.yaml b/src/cdk/v2/destinations/intercom/procWorkflow.yaml index a60e5676e5e..ac8c8989e48 100644 --- a/src/cdk/v2/destinations/intercom/procWorkflow.yaml +++ b/src/cdk/v2/destinations/intercom/procWorkflow.yaml @@ -134,7 +134,7 @@ steps: let payload = .message.context.mappedToDestination === true ? $.outputs.rEtlPayload : $.outputs.groupEtlPayload; const contactId = $.outputs.searchContact; const companyId = await $.createOrUpdateCompany(payload, .destination); - $.assert(companyId, "Unable to add user to company"); + $.assert(companyId, "Unable to create or update company"); payload.requestBodyJson = { id: companyId } diff --git a/src/cdk/v2/destinations/intercom/utils.js b/src/cdk/v2/destinations/intercom/utils.js index 9311d9efb1d..a578f8ba414 100644 --- a/src/cdk/v2/destinations/intercom/utils.js +++ b/src/cdk/v2/destinations/intercom/utils.js @@ -44,13 +44,15 @@ const getHeaders = (destination) => ({ */ const getBaseEndpoint = (destination) => { const { apiServer } = destination.Config; - if (apiServer === 'eu') { - return BASE_EU_ENDPOINT; - } - if (apiServer === 'au') { - return BASE_AU_ENDPOINT; + + switch (apiServer) { + case 'eu': + return BASE_EU_ENDPOINT; + case 'au': + return BASE_AU_ENDPOINT; + default: + return BASE_ENDPOINT; } - return BASE_ENDPOINT; }; /** @@ -178,7 +180,7 @@ const createOrUpdateCompany = async (payload, destination) => { if (isHttpStatusSuccess(processedResponse.status)) { return processedResponse.response?.id; } - + throw new NetworkError( `Unable to Create or Update Company due to : ${JSON.stringify( processedResponse?.response?.errors,