diff --git a/src/cdk/v2/destinations/intercom/config.js b/src/cdk/v2/destinations/intercom/config.js new file mode 100644 index 0000000000..518d805d41 --- /dev/null +++ b/src/cdk/v2/destinations/intercom/config.js @@ -0,0 +1,81 @@ +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'; + +const SEARCH_CONTACT_ENDPOINT = 'contacts/search'; +const CREATE_OR_UPDATE_COMPANY_ENDPOINT = 'companies'; + +const ReservedAttributes = { + v1UserAttributes: [ + 'userId', + 'email', + 'phone', + 'name', + 'createdAt', + 'firstName', + 'lastName', + 'firstname', + 'lastname', + 'company', + ], + v2UserAttributes: [ + 'userId', + 'role', + 'email', + 'phone', + 'name', + 'avatar', + 'company', + 'ownerId', + 'lastName', + 'lastname', + 'firstName', + 'firstname', + 'createdAt', + 'timestamp', + 'lastSeenAt', + 'originalTimestamp', + 'unsubscribedFromEmails', + ], + v1CompanyAttributes: [ + 'remoteCreatedAt', + 'monthlySpend', + 'industry', + 'website', + 'size', + 'plan', + 'name', + 'userId', + ], + v2CompanyAttributes: [ + 'tags', + 'size', + 'plan', + 'name', + 'email', + 'userId', + 'website', + 'industry', + 'segments', + 'userCount', + 'createdAt', + 'sessionCount', + 'monthlySpend', + 'remoteCreatedAt', + ], +}; + +const ReservedCompanyProperties = ['id', 'name', 'industry']; + +const MetadataTypes = { richLink: ['url', 'value'], monetaryAmount: ['amount', 'currency'] }; + +module.exports = { + BASE_ENDPOINT, + MetadataTypes, + BASE_EU_ENDPOINT, + BASE_AU_ENDPOINT, + ReservedAttributes, + SEARCH_CONTACT_ENDPOINT, + ReservedCompanyProperties, + CREATE_OR_UPDATE_COMPANY_ENDPOINT, +}; diff --git a/src/cdk/v2/destinations/intercom/procWorkflow.yaml b/src/cdk/v2/destinations/intercom/procWorkflow.yaml new file mode 100644 index 0000000000..04afba9a25 --- /dev/null +++ b/src/cdk/v2/destinations/intercom/procWorkflow.yaml @@ -0,0 +1,230 @@ +bindings: + - name: EventType + path: ../../../../constants + - path: ./utils + 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 + - path: ../../bindings/jsontemplate + +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.apiKey, "Access Token is not present. Aborting"); + + - name: apiVersion + template: | + const version = $.isDefinedAndNotNull(.destination.Config.apiVersion) ? .destination.Config.apiVersion : "v2"; + version; + + - 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}}) && $.outputs.apiVersion !== "v1" + template: | + const contactId = await $.searchContact(.message, .destination); + contactId; + + - name: identifyTransformationForLatestVersion + condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} && $.outputs.apiVersion !== "v1" && !.message.context.mappedToDestination + template: | + const payload = .message.({ + external_id: {{{{$.getGenericPaths("userIdOnly")}}}}, + email: {{{{$.getGenericPaths("email")}}}}, + phone: {{{{$.getGenericPaths("phone")}}}}, + avatar: {{{{$.getGenericPaths("avatar")}}}}, + last_seen_at: $.toSeconds(.context.traits.lastSeenAt), + role: .traits.role || .context.traits.role, + signed_up_at: $.toSeconds(.traits.createdAt || .context.traits.createdAt), + owner_id: Number(.traits.ownerId || .context.traits.ownerId) || undefined, + unsubscribed_from_emails: .traits.unsubscribedFromEmails || .context.traits.unsubscribedFromEmails + }); + !(payload.external_id) && .destination.Config.sendAnonymousId ? payload.external_id = .message.anonymousId; + payload; + + - name: identifyPayloadForLatestVersion + condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} && $.outputs.apiVersion !== "v1" + template: | + const payload = .message.context.mappedToDestination ? $.outputs.rEtlPayload : $.outputs.identifyTransformationForLatestVersion; + payload.name = $.getName(.message); + payload.custom_attributes = .message.context.traits || {}; + payload.custom_attributes = $.filterCustomAttributes(payload, "user", .destination); + payload.external_id = !payload.external_id && .destination.Config.sendAnonymousId && .message.anonymousId ? .message.anonymousId : payload.external_id; + $.context.payload = payload; + $.assert($.context.payload.external_id || $.context.payload.email, "Either email or userId is required for Identify call"); + const endpoint = $.getBaseEndpoint(.destination) + "/" + "contacts"; + $.context.requestMethod = $.outputs.searchContact ? 'PUT' : 'POST'; + $.context.endpoint = $.outputs.searchContact ? endpoint + "/" + $.outputs.searchContact : endpoint; + $.context.payload = $.removeUndefinedAndNullValues($.context.payload); + + - name: identifyTransformationForOlderVersion + condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} && $.outputs.apiVersion === "v1" && !.message.context.mappedToDestination + template: | + const payload = .message.({ + user_id: {{{{$.getGenericPaths("userIdOnly")}}}}, + email: {{{{$.getGenericPaths("email")}}}}, + phone: {{{{$.getGenericPaths("phone")}}}}, + signed_up_at: $.toSeconds(.traits.createdAt || .context.traits.createdAt), + last_seen_user_agent: .context.userAgent, + }); + !(payload.user_id) && .destination.Config.sendAnonymousId ? payload.user_id = .message.anonymousId; + payload; + + - name: identifyPayloadForOlderVersion + condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} && $.outputs.apiVersion === "v1" + template: | + let payload = .message.context.mappedToDestination ? $.outputs.rEtlPayload : $.outputs.identifyTransformationForOlderVersion; + payload = { + ...payload, + name : $.getName(.message), + custom_attributes : .message.traits || .message.context.traits || {}, + update_last_request_at: typeof .destination.Config.updateLastRequestAt === 'boolean' ? .destination.Config.updateLastRequestAt : true + } + payload.companies = $.getCompaniesList(payload); + payload.custom_attributes = !.message.context.mappedToDestination ? $.filterCustomAttributes(payload, "user", .destination); + payload.user_id = !payload.user_id && .destination.Config.sendAnonymousId && .message.anonymousId ? .message.anonymousId : payload.user_id; + $.context.payload = payload; + $.assert($.context.payload.user_id || $.context.payload.email, "Either of `email` or `userId` is required for Identify call"); + $.context.requestMethod = 'POST'; + $.context.endpoint = $.getBaseEndpoint(.destination) + "/" + "users"; + $.context.payload = $.removeUndefinedAndNullValues($.context.payload); + + - name: trackTransformation + condition: $.outputs.messageType === {{$.EventType.TRACK}} && !.message.context.mappedToDestination + template: | + const timestamp = .message.().( + {{{{$.getGenericPaths("timestamp")}}}}; + ); + const payload = .message.({ + event_name: .event, + user_id: {{{{$.getGenericPaths("userIdOnly")}}}}, + email: {{{{$.getGenericPaths("email")}}}}, + metadata: .properties + }); + $.outputs.apiVersion !== "v1" ? payload.id = .message.properties.id || .message.traits.id; + $.outputs.apiVersion !== "v1" ? payload.created_at = $.toSeconds(timestamp); + $.outputs.apiVersion === "v1" ? payload.created = $.toSeconds(timestamp); + !(payload.user_id) && .destination.Config.sendAnonymousId ? payload.user_id = .message.anonymousId; + payload; + + - name: trackPayload + condition: $.outputs.messageType === {{$.EventType.TRACK}} + template: | + let payload = .message.context.mappedToDestination ? $.outputs.rEtlPayload : $.outputs.trackTransformation; + payload = $.addMetadataToPayload(payload); + $.context.payload = payload; + $.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"); + $.context.requestMethod = 'POST'; + $.context.endpoint = $.getBaseEndpoint(.destination) + "/" + "events"; + $.context.payload = $.removeUndefinedAndNullValues($.context.payload); + + - name: groupTransformation + 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 + }); + payload; + + - name: groupPayloadForLatestVersion + condition: $.outputs.messageType === {{$.EventType.GROUP}} && $.outputs.apiVersion !== "v1" + steps: + - name: validateMessageAndPreparePayload + template: | + $.assert(.message.groupId, "groupId is required for group call"); + const payload = .message.context.mappedToDestination ? $.outputs.rEtlPayload : $.outputs.groupTransformation; + payload.custom_attributes = .message.traits || {}; + payload.custom_attributes = $.filterCustomAttributes(payload, "company", .destination); + $.context.payload = payload; + - name: whenSearchContactFound + condition: $.isDefinedAndNotNull($.outputs.searchContact) + template: | + const contactId = $.outputs.searchContact; + const companyId = await $.createOrUpdateCompany($.context.payload, .destination); + $.assert(companyId, "Unable to create or update company"); + $.context.payload = { + id: companyId, + }; + $.context.endpoint = $.getBaseEndpoint(.destination) + "/" + "contacts" + "/" + contactId + "/" + "companies"; + else: + name: whenSearchContactNotFound + template: | + $.context.endpoint = $.getBaseEndpoint(.destination) + "/" + "companies"; + - name: prepareFinalPayload + template: + $.context.requestMethod = 'POST'; + $.removeUndefinedAndNullValues($.context.payload); + + - name: groupPayloadForOlderVersion + condition: $.outputs.messageType === {{$.EventType.GROUP}} && $.outputs.apiVersion === "v1" + template: | + $.context.response = []; + const response = $.defaultRequestConfig(); + let payload = .message.context.mappedToDestination ? $.outputs.rEtlPayload : $.outputs.groupTransformation; + payload = { + ...payload, + custom_attributes : $.getFieldValueFromMessage(.message, "traits") || {} + } + payload.custom_attributes = $.filterCustomAttributes(payload, "company", .destination); + response.body.JSON = $.removeUndefinedAndNullValues(payload); + response.endpoint = $.getBaseEndpoint(.destination) + "/" + "companies"; + response.headers = $.getHeaders(.destination, $.outputs.apiVersion); + response.method = "POST"; + response.userId = .message.anonymousId; + $.context.response.push(response); + const attachUserAndCompanyResponse = $.attachUserAndCompany(.message, .destination.Config); + attachUserAndCompanyResponse ? attachUserAndCompanyResponse.userId = .message.anonymousId; + attachUserAndCompanyResponse ? $.context.response.push(attachUserAndCompanyResponse); + + - name: buildResponseForProcessTransformation + description: Build response for multiple transformed event + condition: $.context.response && $.context.response.length > 0 + template: | + $.context.response; + else: + name: buildResponseForProcessTransformation + description: Build response for single transformed event + template: | + const response = $.defaultRequestConfig(); + response.body.JSON = $.context.payload; + response.endpoint = $.context.endpoint; + response.method = $.context.requestMethod; + response.headers = $.getHeaders(.destination, $.outputs.apiVersion); + $.outputs.apiVersion === "v1" && $.outputs.messageType !== {{$.EventType.GROUP}} ? response.userId = .message.anonymousId; + response; diff --git a/src/cdk/v2/destinations/intercom/rtWorkflow.yaml b/src/cdk/v2/destinations/intercom/rtWorkflow.yaml new file mode 100644 index 0000000000..3ed1046959 --- /dev/null +++ b/src/cdk/v2/destinations/intercom/rtWorkflow.yaml @@ -0,0 +1,33 @@ +bindings: + - name: handleRtTfSingleEventError + path: ../../../../v0/util/index + - name: isDefinedAndNotNull + path: ../../../../v0/util + +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{$.isDefinedAndNotNull(.output)}.({ + "batchedRequest": .output, + "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/cdk/v2/destinations/intercom/utils.js b/src/cdk/v2/destinations/intercom/utils.js new file mode 100644 index 0000000000..0f18029f19 --- /dev/null +++ b/src/cdk/v2/destinations/intercom/utils.js @@ -0,0 +1,367 @@ +const md5 = require('md5'); +const get = require('get-value'); +const { NetworkError } = require('@rudderstack/integrations-lib'); +const tags = require('../../../../v0/util/tags'); +const { httpPOST } = require('../../../../adapters/network'); +const { + processAxiosResponse, + getDynamicErrorType, +} = require('../../../../adapters/utils/networkUtils'); +const { + flattenJson, + getIntegrationsObj, + isDefinedAndNotNull, + isHttpStatusSuccess, + defaultRequestConfig, + getFieldValueFromMessage, + defaultPostRequestConfig, + removeUndefinedAndNullValues, +} = require('../../../../v0/util'); +const { JSON_MIME_TYPE } = require('../../../../v0/util/constant'); +const { + BASE_ENDPOINT, + MetadataTypes, + BASE_EU_ENDPOINT, + BASE_AU_ENDPOINT, + ReservedAttributes, + SEARCH_CONTACT_ENDPOINT, + ReservedCompanyProperties, + CREATE_OR_UPDATE_COMPANY_ENDPOINT, +} = require('./config'); + +/** + * Returns destination request headers + * @param {*} destination + * @param {*} apiVersion + * @returns + */ +const getHeaders = (destination, apiVersion) => ({ + 'Content-Type': JSON_MIME_TYPE, + Authorization: `Bearer ${destination.Config.apiKey}`, + Accept: JSON_MIME_TYPE, + 'Intercom-Version': apiVersion === 'v1' ? '1.4' : '2.10', +}); + +/** + * Returns destination request base endpoint + * @param {*} destination + * @returns + */ +const getBaseEndpoint = (destination) => { + const { apiServer } = destination.Config; + let { apiVersion } = destination.Config; + apiVersion = isDefinedAndNotNull(apiVersion) ? apiVersion : 'v2'; + + if (apiVersion === 'v1') return BASE_ENDPOINT; + switch (apiServer) { + case 'eu': + return BASE_EU_ENDPOINT; + case 'au': + return BASE_AU_ENDPOINT; + default: + return BASE_ENDPOINT; + } +}; + +/** + * Returns contact lookup field + * @param {*} message + * @returns + */ +const getLookUpField = (message) => { + let lookupField = 'email'; + const integrationsObj = getIntegrationsObj(message, 'INTERCOM'); + if (integrationsObj && isDefinedAndNotNull(integrationsObj.lookup)) { + lookupField = integrationsObj.lookup; + } + return lookupField; +}; + +/** + * Returns the value of name field + * @param {*} message + * @returns + */ +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 undefined; +}; + +/** + * Returns company payload + * @param {*} payload + * @returns + */ +const getCompaniesList = (payload) => { + const company = get(payload, 'custom_attributes.company'); + if (!company) return undefined; + const companiesList = []; + if (company.name || company.id) { + const customAttributes = {}; + Object.keys(company).forEach((key) => { + // If 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; +}; + +/** + * Returns if email or userId is present in payload or not + * @param {*} message + * @param {*} Config + * @returns + */ +const checkIfEmailOrUserIdPresent = (message, Config) => { + const { context, anonymousId } = message; + let { userId } = message; + if (Config.sendAnonymousId && !userId) { + userId = anonymousId; + } + return !!(userId || context?.traits?.email); +}; + +/** + * Returns add user to company payload + * @param {*} message + * @param {*} Config + * @returns + */ +const attachUserAndCompany = (message, Config) => { + if (!checkIfEmailOrUserIdPresent(message, Config)) return undefined; + const email = message.context?.traits?.email; + const { userId, anonymousId, traits, groupId } = 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: groupId, + }; + if (traits?.name) { + companyObj.name = traits.name; + } + requestBody.companies = [companyObj]; + const response = defaultRequestConfig(); + response.method = defaultPostRequestConfig.requestMethod; + response.endpoint = `${BASE_ENDPOINT}/users`; + response.headers = { + 'Content-Type': JSON_MIME_TYPE, + Authorization: `Bearer ${Config.apiKey}`, + Accept: JSON_MIME_TYPE, + 'Intercom-Version': '1.4', + }; + response.body.JSON = requestBody; + response.userId = anonymousId; + return response; +}; + +/** + * Returns custom attributes for identify and group calls (for contact and company in intercom) + * @param {*} payload + * @param {*} type + * @returns + */ +const filterCustomAttributes = (payload, type, destination) => { + let ReservedAttributesList; + let { apiVersion } = destination.Config; + apiVersion = isDefinedAndNotNull(apiVersion) ? apiVersion : 'v2'; + if (type === 'user') { + ReservedAttributesList = + apiVersion === 'v1' + ? ReservedAttributes.v1UserAttributes + : ReservedAttributes.v2UserAttributes; + } else { + ReservedAttributesList = + apiVersion === 'v1' + ? ReservedAttributes.v1CompanyAttributes + : ReservedAttributes.v2CompanyAttributes; + } + let customAttributes = { ...get(payload, 'custom_attributes') }; + if (customAttributes) { + ReservedAttributesList.forEach((trait) => { + if (customAttributes[trait]) delete customAttributes[trait]; + }); + if (isDefinedAndNotNull(customAttributes) && Object.keys(customAttributes).length > 0) { + customAttributes = + apiVersion === 'v1' ? flattenJson(customAttributes) : flattenJson(customAttributes, '_'); + } + } + return Object.keys(customAttributes).length === 0 ? undefined : customAttributes; +}; + +/** + * 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 + * @returns + */ +const searchContact = async (message, destination) => { + const lookupField = getLookUpField(message); + 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.length > 0 + ? processedUserResponse.response?.data[0]?.id + : null; + } + + throw new NetworkError( + `Unable to search contact due to : ${JSON.stringify(processedUserResponse?.response?.errors)}`, + processedUserResponse?.status, + { + [tags]: getDynamicErrorType(processedUserResponse?.status), + }, + processedUserResponse, + ); +}; + +/** + * 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 + */ +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, { + headers, + destType: 'intercom', + feature: 'transformation', + }); + + const processedResponse = processAxiosResponse(response); + if (isHttpStatusSuccess(processedResponse.status)) { + return processedResponse.response?.id; + } + + throw new NetworkError( + `Unable to Create or Update Company due to : ${JSON.stringify( + processedResponse?.response?.errors, + )}`, + processedResponse?.status, + { + [tags]: getDynamicErrorType(processedResponse?.status), + }, + processedResponse, + ); +}; + +/** + * Returns metadata object + * @param {*} metadata + * @returns + */ +const separateReservedAndRestMetadata = (metadata) => { + const reservedMetadata = {}; + const restMetadata = {}; + if (metadata) { + Object.entries(metadata).forEach(([key, value]) => { + if (value && typeof value === 'object') { + const hasMonetaryAmountKeys = MetadataTypes.monetaryAmount.every((type) => type in value); + const hasRichLinkKeys = MetadataTypes.richLink.every((type) => type in value); + if (hasMonetaryAmountKeys || hasRichLinkKeys) { + reservedMetadata[key] = value; + } else { + restMetadata[key] = value; + } + } else { + restMetadata[key] = value; + } + }); + } + + // Return the separated metadata objects + 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, + searchContact, + getLookUpField, + getBaseEndpoint, + getCompaniesList, + addMetadataToPayload, + attachUserAndCompany, + createOrUpdateCompany, + filterCustomAttributes, + checkIfEmailOrUserIdPresent, + separateReservedAndRestMetadata, +}; diff --git a/src/cdk/v2/destinations/intercom/utils.test.js b/src/cdk/v2/destinations/intercom/utils.test.js new file mode 100644 index 0000000000..e651b4ea5d --- /dev/null +++ b/src/cdk/v2/destinations/intercom/utils.test.js @@ -0,0 +1,765 @@ +const md5 = require('md5'); +const axios = require('axios'); +const { + getName, + getHeaders, + searchContact, + getLookUpField, + getBaseEndpoint, + getCompaniesList, + addMetadataToPayload, + attachUserAndCompany, + createOrUpdateCompany, + filterCustomAttributes, + checkIfEmailOrUserIdPresent, + separateReservedAndRestMetadata, +} = require('./utils'); +const { BASE_ENDPOINT, BASE_EU_ENDPOINT, BASE_AU_ENDPOINT } = require('./config'); + +jest.mock('axios', () => ({ + ...jest.requireActual('axios'), + post: jest.fn(), +})); + +describe('separateReservedAndRestMetadata utility test', () => { + it('separate reserved and rest metadata', () => { + const metadata = { + property1: 1, + property2: 'test', + property3: true, + property4: { + property1: 1, + property2: 'test', + property3: { + subProp1: { + a: 'a', + b: 'b', + }, + subProp2: ['a', 'b'], + }, + }, + property5: {}, + property6: [], + property7: null, + property8: undefined, + revenue: { + amount: 1232, + currency: 'inr', + test: 123, + }, + price: { + amount: 3000, + currency: 'USD', + }, + article: { + url: 'https://example.org/ab1de.html', + value: 'the dude abides', + }, + }; + const expectedReservedMetadata = { + revenue: { + amount: 1232, + currency: 'inr', + test: 123, + }, + price: { + amount: 3000, + currency: 'USD', + }, + article: { + url: 'https://example.org/ab1de.html', + value: 'the dude abides', + }, + }; + const expectedRestMetadata = { + property1: 1, + property2: 'test', + property3: true, + property4: { + property1: 1, + property2: 'test', + property3: { + subProp1: { + a: 'a', + b: 'b', + }, + subProp2: ['a', 'b'], + }, + }, + property5: {}, + property6: [], + property7: null, + property8: undefined, + }; + const { reservedMetadata, restMetadata } = separateReservedAndRestMetadata(metadata); + + expect(expectedReservedMetadata).toEqual(reservedMetadata); + expect(expectedRestMetadata).toEqual(restMetadata); + }); + + it('reserved metadata types not present in input metadata', () => { + const metadata = { + property1: 1, + property2: 'test', + property3: true, + property4: { + property1: 1, + property2: 'test', + property3: { + subProp1: { + a: 'a', + b: 'b', + }, + subProp2: ['a', 'b'], + }, + }, + property5: {}, + property6: [], + property7: null, + property8: undefined, + }; + const expectedRestMetadata = { + property1: 1, + property2: 'test', + property3: true, + property4: { + property1: 1, + property2: 'test', + property3: { + subProp1: { + a: 'a', + b: 'b', + }, + subProp2: ['a', 'b'], + }, + }, + property5: {}, + property6: [], + property7: null, + property8: undefined, + }; + const { reservedMetadata, restMetadata } = separateReservedAndRestMetadata(metadata); + + expect({}).toEqual(reservedMetadata); + expect(expectedRestMetadata).toEqual(restMetadata); + }); + + it('metadata input contains only reserved metadata types', () => { + const metadata = { + revenue: { + amount: 1232, + currency: 'inr', + test: 123, + }, + price: { + amount: 3000, + currency: 'USD', + }, + article: { + url: 'https://example.org/ab1de.html', + value: 'the dude abides', + }, + }; + const expectedReservedMetadata = { + revenue: { + amount: 1232, + currency: 'inr', + test: 123, + }, + price: { + amount: 3000, + currency: 'USD', + }, + article: { + url: 'https://example.org/ab1de.html', + value: 'the dude abides', + }, + }; + const { reservedMetadata, restMetadata } = separateReservedAndRestMetadata(metadata); + + expect(expectedReservedMetadata).toEqual(reservedMetadata); + expect({}).toEqual(restMetadata); + }); + + it('empty metadata object', () => { + const metadata = {}; + const { reservedMetadata, restMetadata } = separateReservedAndRestMetadata(metadata); + expect({}).toEqual(reservedMetadata); + expect({}).toEqual(restMetadata); + }); + + it('null/undefined metadata', () => { + const metadata = null; + const { reservedMetadata, restMetadata } = separateReservedAndRestMetadata(metadata); + expect({}).toEqual(reservedMetadata); + expect({}).toEqual(restMetadata); + }); +}); + +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', + apiVersion: 'v2', + }, + }; + const result = getBaseEndpoint(destination); + expect(result).toBe(BASE_EU_ENDPOINT); + }); + + it('Should return BASE_AU_ENDPOINT when destination.Config.apiServer is "au"', () => { + const destination = { + Config: { + apiServer: 'au', + apiVersion: 'v2', + }, + }; + const result = getBaseEndpoint(destination); + expect(result).toBe(BASE_AU_ENDPOINT); + }); + + it('Should return BASE_ENDPOINT when destination.Config.apiServer is null', () => { + const destination = { + Config: { + apiServer: null, + }, + }; + const result = getBaseEndpoint(destination); + expect(result).toBe(BASE_ENDPOINT); + }); +}); + +describe('getHeaders utility test', () => { + it('Should return an object with the correct headers', () => { + const destination = { + Config: { + apiKey: 'testApiKey', + }, + }; + + const expectedHeaders = { + 'Content-Type': 'application/json', + Authorization: `Bearer ${destination.Config.apiKey}`, + Accept: 'application/json', + 'Intercom-Version': '2.10', + }; + const headers = getHeaders(destination, 'v2'); + expect(headers).toEqual(expectedHeaders); + }); +}); + +describe('getLookUpField utility test', () => { + it('Should return email as default lookup field when no integration object is found', () => { + const message = {}; + 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'); + }); + + it('Should return the lastName field when only lastName exists', () => { + const message = { + context: { + traits: { + lastName: 'Doe', + }, + }, + }; + 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('filterCustomAttributes utility test', () => { + it('Should return an empty object when all custom attributes are reserved attributes', () => { + const payload = { custom_attributes: { email: 'test@rudder.com', name: 'rudder test' } }; + const result = filterCustomAttributes(payload, 'user', { Config: { apiVersion: 'v2' } }); + expect(result).toBeUndefined(); + }); + + it('Should return a flattened object when custom attributes are not null, not reserved attributes and nested', () => { + const payload = { + custom_attributes: { source: 'rudder-js-sdk', data: { nestedAttribute: 'nestedValue' } }, + }; + const result = filterCustomAttributes(payload, 'user', { Config: { apiVersion: 'v2' } }); + 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 result = filterCustomAttributes(payload, 'company', { Config: { apiVersion: 'v2' } }); + expect(result).toBeUndefined(); + }); +}); + +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', + }, + }); + }); +}); + +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"}]', + ); + } + }); +}); + +describe('checkIfEmailOrUserIdPresent utility test', () => { + it('Should return true when userId is present in message', () => { + const message = { + userId: '12345', + context: { + traits: { + email: 'test@example.com', + }, + }, + }; + const Config = { + sendAnonymousId: true, + apiKey: '1234567890', + }; + const result = checkIfEmailOrUserIdPresent(message, Config); + expect(result).toBe(true); + }); + + it('Should return true when email is present in message', () => { + const message = { + context: { + traits: { + email: 'test@example.com', + }, + }, + }; + const Config = { + sendAnonymousId: true, + apiKey: '1234567890', + }; + const result = checkIfEmailOrUserIdPresent(message, Config); + expect(result).toBe(true); + }); + + it('Should return true when both userId and email are present in message', () => { + const message = { + userId: '12345', + context: { + traits: { + email: 'test@example.com', + }, + }, + }; + const Config = { + sendAnonymousId: true, + apiKey: '1234567890', + }; + const result = checkIfEmailOrUserIdPresent(message, Config); + expect(result).toBe(true); + }); + + it('Should return false when no email or userId is present', () => { + const message = { anonymousId: 'anon@123' }; + const Config = { + sendAnonymousId: false, + apiKey: '1234567890', + }; + const result = checkIfEmailOrUserIdPresent(message, Config); + expect(result).toBe(false); + }); +}); + +describe('getCompaniesList utility test', () => { + it('Should return an array with one object containing the company_id, custom_attributes, name and industry properties when the payload contains a company object with name or id properties', () => { + const payload = { + custom_attributes: { + company: { + name: 'rudderlabs', + industry: 'Tech', + }, + }, + }; + + const result = getCompaniesList(payload); + + expect(result).toEqual([ + { + company_id: md5('rudderlabs'), + custom_attributes: {}, + name: 'rudderlabs', + industry: 'Tech', + }, + ]); + }); + + it('Should return undefined when the payload does not contain a company object', () => { + const payload = {}; + const result = getCompaniesList(payload); + expect(result).toBeUndefined(); + }); + + it('Should return an empty array when the company object in the payload does not have name or id properties', () => { + const payload = { + custom_attributes: { + company: {}, + }, + }; + const result = getCompaniesList(payload); + expect(result).toEqual([]); + }); + + it('Should return an array with one object containing the company_id, custom_attributes, name and industry properties when the payload contains a company object with name and id properties', () => { + const payload = { + custom_attributes: { + company: { + name: 'Company A', + id: '123', + industry: 'Tech', + }, + }, + }; + const result = getCompaniesList(payload); + expect(result).toEqual([ + { + company_id: '123', + custom_attributes: {}, + name: 'Company A', + industry: 'Tech', + }, + ]); + }); +}); + +describe('attachUserAndCompany utility test', () => { + it('should return a valid response object when only email and groupId are present', () => { + const message = { + context: { + traits: { + email: 'test@example.com', + }, + }, + groupId: 'group123', + }; + const Config = { + sendAnonymousId: false, + apiKey: 'testApiKey', + }; + + const expectedResponse = { + method: 'POST', + params: {}, + type: 'REST', + version: '1', + endpoint: 'https://api.intercom.io/users', + files: {}, + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer testApiKey', + Accept: 'application/json', + 'Intercom-Version': '1.4', + }, + body: { + FORM: {}, + JSON: { + email: 'test@example.com', + companies: [ + { + company_id: 'group123', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + }, + userId: undefined, + }; + const response = attachUserAndCompany(message, Config); + expect(response).toEqual(expectedResponse); + }); +}); diff --git a/src/constants/destinationCanonicalNames.js b/src/constants/destinationCanonicalNames.js index d1e199c9e2..f019cc9fec 100644 --- a/src/constants/destinationCanonicalNames.js +++ b/src/constants/destinationCanonicalNames.js @@ -141,6 +141,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 deleted file mode 100644 index ae29eebc1e..0000000000 --- a/src/v0/destinations/intercom/config.js +++ /dev/null @@ -1,53 +0,0 @@ -const { getMappingConfig } = require('../../util'); - -const BASE_ENDPOINT = 'https://api.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 ConfigCategory = { - TRACK: { - endpoint: TRACK_ENDPOINT, - name: 'INTERCOMTrackConfig', - }, - IDENTIFY: { - endpoint: IDENTIFY_ENDPOINT, - name: 'INTERCOMIdentifyConfig', - }, - GROUP: { - endpoint: GROUP_ENDPOINT, - name: 'INTERCOMGroupConfig', - }, -}; - -const MappingConfig = getMappingConfig(ConfigCategory, __dirname); - -const ReservedTraitsProperties = [ - 'userId', - 'email', - 'phone', - 'name', - 'createdAt', - 'firstName', - 'lastName', - 'firstname', - 'lastname', - 'company', -]; - -const ReservedCompanyProperties = ['id', 'name', 'industry']; - -// 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, - MetadataTypes, -}; diff --git a/src/v0/destinations/intercom/data/INTERCOMGroupConfig.json b/src/v0/destinations/intercom/data/INTERCOMGroupConfig.json deleted file mode 100644 index 174f828a56..0000000000 --- a/src/v0/destinations/intercom/data/INTERCOMGroupConfig.json +++ /dev/null @@ -1,53 +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 - } -] diff --git a/src/v0/destinations/intercom/data/INTERCOMIdentifyConfig.json b/src/v0/destinations/intercom/data/INTERCOMIdentifyConfig.json deleted file mode 100644 index 726a741161..0000000000 --- a/src/v0/destinations/intercom/data/INTERCOMIdentifyConfig.json +++ /dev/null @@ -1,46 +0,0 @@ -[ - { - "destKey": "user_id", - "sourceKeys": [ - "userId", - "traits.userId", - "traits.id", - "context.traits.userId", - "context.traits.id" - ], - "required": false - }, - { - "destKey": "email", - "sourceKeys": ["traits.email", "context.traits.email"], - "required": false - }, - { - "destKey": "phone", - "sourceKeys": ["traits.phone", "context.traits.phone"], - "required": false - }, - { - "destKey": "name", - "sourceKeys": ["traits.name", "context.traits.name"], - "required": false - }, - { - "destKey": "signed_up_at", - "sourceKeys": ["traits.createdAt", "context.traits.createdAt"], - "required": false, - "metadata": { - "type": "secondTimestamp" - } - }, - { - "destKey": "last_seen_user_agent", - "sourceKeys": "context.userAgent", - "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 f33c9a8a98..0000000000 --- a/src/v0/destinations/intercom/data/INTERCOMTrackConfig.json +++ /dev/null @@ -1,36 +0,0 @@ -[ - { - "destKey": "user_id", - "sourceKeys": [ - "userId", - "traits.userId", - "traits.id", - "context.traits.userId", - "context.traits.id" - ], - "required": false - }, - { - "destKey": "email", - "sourceKeys": ["traits.email", "context.traits.email"], - "required": false - }, - { - "destKey": "event_name", - "sourceKeys": "event", - "required": true - }, - { - "destKey": "created", - "sourceKeys": "timestamp", - "sourceFromGenericMap": true, - "required": true, - "metadata": { - "type": "secondTimestamp" - } - }, - { - "destKey": "metadata", - "sourceKeys": "properties" - } -] diff --git a/src/v0/destinations/intercom/deleteUsers.js b/src/v0/destinations/intercom/deleteUsers.js index 085e842458..b91f520ade 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('api key for deletion not present'); + throw new ConfigurationError('The access token is not available'); } const validUserIds = []; userAttributes.forEach((userAttribute) => { diff --git a/src/v0/destinations/intercom/transform.js b/src/v0/destinations/intercom/transform.js deleted file mode 100644 index 212eaba13b..0000000000 --- a/src/v0/destinations/intercom/transform.js +++ /dev/null @@ -1,252 +0,0 @@ -const md5 = require('md5'); -const get = require('get-value'); -const { InstrumentationError } = require('@rudderstack/integrations-lib'); -const { EventType, MappedToDestinationKey } = require('../../../constants'); -const { - ConfigCategory, - MappingConfig, - ReservedTraitsProperties, - ReservedCompanyProperties, -} = require('./config'); -const { - constructPayload, - removeUndefinedAndNullValues, - defaultRequestConfig, - defaultPostRequestConfig, - getFieldValueFromMessage, - addExternalIdToTraits, - simpleProcessRouterDest, - flattenJson, -} = require('../../util'); -const { separateReservedAndRestMetadata } = require('./util'); -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); - } - - if (finalPayload.custom_attributes) { - ReservedTraitsProperties.forEach((trait) => { - delete finalPayload.custom_attributes[trait]; - }); - finalPayload.custom_attributes = flattenJson(finalPayload.custom_attributes); - } - - return finalPayload; - } - 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'); - } - // 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) } }; - } - - return payload; -} - -const checkIfEmailOrUserIdPresent = (message, Config) => { - const { context, anonymousId } = message; - let { userId } = message; - if (Config.sendAnonymousId && !userId) { - userId = anonymousId; - } - return !!(userId || context.traits?.email); -}; - -function attachUserAndCompany(message, Config) { - const email = message.context?.traits?.email; - const { userId, anonymousId, traits, groupId } = 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: groupId, - }; - if (traits?.name) { - companyObj.name = 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]; - } - }); - } - - if (Object.keys(customAttributes).length > 0) { - finalPayload.custom_attributes = flattenJson(customAttributes); - } - - return finalPayload; -} - -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`); - } - - return messageType === EventType.GROUP ? respList : response; -} - -function processSingleMessage(message, destination) { - if (!message.type) { - throw new InstrumentationError('Message Type is not present. Aborting message.'); - } - const { sendAnonymousId } = destination.Config; - const messageType = message.type.toLowerCase(); - let category; - - switch (messageType) { - case EventType.IDENTIFY: - category = ConfigCategory.IDENTIFY; - break; - case EventType.TRACK: - category = ConfigCategory.TRACK; - break; - case EventType.GROUP: - category = ConfigCategory.GROUP; - 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; -}; - -module.exports = { process, processRouterDest }; diff --git a/src/v0/destinations/intercom/util.js b/src/v0/destinations/intercom/util.js deleted file mode 100644 index 24a2934f7e..0000000000 --- a/src/v0/destinations/intercom/util.js +++ /dev/null @@ -1,32 +0,0 @@ -const { MetadataTypes } = require('./config'); - -/** - * 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 reservedMetadata = {}; - const restMetadata = {}; - if (metadata) { - Object.entries(metadata).forEach(([key, value]) => { - if (value && typeof value === 'object') { - const hasMonetaryAmountKeys = MetadataTypes.monetaryAmount.every((type) => type in value); - const hasRichLinkKeys = MetadataTypes.richLink.every((type) => type in value); - if (hasMonetaryAmountKeys || hasRichLinkKeys) { - reservedMetadata[key] = value; - } else { - restMetadata[key] = value; - } - } else { - restMetadata[key] = value; - } - }); - } - - // Return the separated metadata objects - return { reservedMetadata, restMetadata }; -} - -module.exports = { separateReservedAndRestMetadata }; diff --git a/src/v0/destinations/intercom/util.test.js b/src/v0/destinations/intercom/util.test.js deleted file mode 100644 index 99dbdd1f7e..0000000000 --- a/src/v0/destinations/intercom/util.test.js +++ /dev/null @@ -1,176 +0,0 @@ -const { separateReservedAndRestMetadata } = require('./util'); - -describe('separateReservedAndRestMetadata utility test', () => { - it('separate reserved and rest metadata', () => { - const metadata = { - property1: 1, - property2: 'test', - property3: true, - property4: { - property1: 1, - property2: 'test', - property3: { - subProp1: { - a: 'a', - b: 'b', - }, - subProp2: ['a', 'b'], - }, - }, - property5: {}, - property6: [], - property7: null, - property8: undefined, - revenue: { - amount: 1232, - currency: 'inr', - test: 123, - }, - price: { - amount: 3000, - currency: 'USD', - }, - article: { - url: 'https://example.org/ab1de.html', - value: 'the dude abides', - }, - }; - const expectedReservedMetadata = { - revenue: { - amount: 1232, - currency: 'inr', - test: 123, - }, - price: { - amount: 3000, - currency: 'USD', - }, - article: { - url: 'https://example.org/ab1de.html', - value: 'the dude abides', - }, - }; - const expectedRestMetadata = { - property1: 1, - property2: 'test', - property3: true, - property4: { - property1: 1, - property2: 'test', - property3: { - subProp1: { - a: 'a', - b: 'b', - }, - subProp2: ['a', 'b'], - }, - }, - property5: {}, - property6: [], - property7: null, - property8: undefined, - }; - const { reservedMetadata, restMetadata } = separateReservedAndRestMetadata(metadata); - - expect(expectedReservedMetadata).toEqual(reservedMetadata); - expect(expectedRestMetadata).toEqual(restMetadata); - }); - - it('reserved metadata types not present in input metadata', () => { - const metadata = { - property1: 1, - property2: 'test', - property3: true, - property4: { - property1: 1, - property2: 'test', - property3: { - subProp1: { - a: 'a', - b: 'b', - }, - subProp2: ['a', 'b'], - }, - }, - property5: {}, - property6: [], - property7: null, - property8: undefined, - }; - const expectedRestMetadata = { - property1: 1, - property2: 'test', - property3: true, - property4: { - property1: 1, - property2: 'test', - property3: { - subProp1: { - a: 'a', - b: 'b', - }, - subProp2: ['a', 'b'], - }, - }, - property5: {}, - property6: [], - property7: null, - property8: undefined, - }; - const { reservedMetadata, restMetadata } = separateReservedAndRestMetadata(metadata); - - expect({}).toEqual(reservedMetadata); - expect(expectedRestMetadata).toEqual(restMetadata); - }); - - it('metadata input contains only reserved metadata types', () => { - const metadata = { - revenue: { - amount: 1232, - currency: 'inr', - test: 123, - }, - price: { - amount: 3000, - currency: 'USD', - }, - article: { - url: 'https://example.org/ab1de.html', - value: 'the dude abides', - }, - }; - const expectedReservedMetadata = { - revenue: { - amount: 1232, - currency: 'inr', - test: 123, - }, - price: { - amount: 3000, - currency: 'USD', - }, - article: { - url: 'https://example.org/ab1de.html', - value: 'the dude abides', - }, - }; - const { reservedMetadata, restMetadata } = separateReservedAndRestMetadata(metadata); - - expect(expectedReservedMetadata).toEqual(reservedMetadata); - expect({}).toEqual(restMetadata); - }); - - it('empty metadata object', () => { - const metadata = {}; - const { reservedMetadata, restMetadata } = separateReservedAndRestMetadata(metadata); - expect({}).toEqual(reservedMetadata); - expect({}).toEqual(restMetadata); - }); - - it('null/undefined metadata', () => { - const metadata = null; - const { reservedMetadata, restMetadata } = separateReservedAndRestMetadata(metadata); - expect({}).toEqual(reservedMetadata); - expect({}).toEqual(restMetadata); - }); -}); diff --git a/test/integrations/destinations/intercom/dataDelivery/data.ts b/test/integrations/destinations/intercom/dataDelivery/data.ts index 6b0077ae0f..db7aafc963 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" - } - } - } - } - } - } -] + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/intercom/deleteUsers/data.ts b/test/integrations/destinations/intercom/deleteUsers/data.ts index a45af0a98f..58285ee683 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', + apiKey: 'testApiKey', }, }, ], @@ -57,7 +57,7 @@ export const data = [ }, ], config: { - apiKey: 'API_KEY', + apiKey: 'testApiKey', }, }, ], @@ -140,7 +140,7 @@ export const data = [ body: [ { statusCode: 400, - error: 'api key for deletion not present', + error: 'The access token is not available', }, ], }, diff --git a/test/integrations/destinations/intercom/network.ts b/test/integrations/destinations/intercom/network.ts index e3bba3f260..74c861259f 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', }, }, @@ -89,6 +89,291 @@ 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 testApiKey', + '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 testApiKey', + '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.eu.intercom.io/contacts/search', + data: { + query: { + operator: 'AND', + value: [{ field: 'email', operator: '=', value: 'test+5@rudderlabs.com' }], + }, + }, + headers: { + Accept: 'application/json', + Authorization: 'Bearer testApiKey', + '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 testApiKey', + '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 testApiKey', + '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 invalidApiKey', + '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.eu.intercom.io/companies', + data: { + company_id: 'rudderlabs', + name: 'RudderStack', + website: 'www.rudderstack.com', + plan: 'enterprise', + size: 500, + industry: 'CDP', + }, + headers: { + Accept: 'application/json', + Authorization: 'Bearer testApiKey', + '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, + }, + }, + }, + { + httpReq: { + method: 'post', + url: 'https://api.eu.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 = [ { @@ -142,4 +427,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 index 14d1884ba2..7ed9879b34 100644 --- a/test/integrations/destinations/intercom/processor/data.ts +++ b/test/integrations/destinations/intercom/processor/data.ts @@ -1,7 +1,1206 @@ export const data = [ { name: 'intercom', - description: 'Test 0', + 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: { + apiKey: 'testApiKey', + apiVersion: 'v2', + 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: { + apiKey: 'testApiKey', + apiVersion: 'v2', + 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: { + apiVersion: 'v2', + 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', + lastSeenAt: '2023-11-10T14:42:44.724Z', + }, + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiKey: 'testApiKey', + apiVersion: 'v2', + 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: 1699627364, + 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 testApiKey', + '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: { + apiKey: 'testApiKey', + apiVersion: 'v2', + 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', + 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 testApiKey', + '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: { + apiKey: 'testApiKey', + apiVersion: 'v2', + 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: identifyPayloadForLatestVersion, 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: { + apiKey: 'invalidApiKey', + apiVersion: 'v2', + 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 : [{\\\\\\"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', + 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: { + apiKey: 'testApiKey', + apiVersion: 'v2', + 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: trackPayload, 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: { + apiKey: 'testApiKey', + apiVersion: 'v2', + 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 testApiKey', + '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: { + apiKey: 'testApiKey', + apiVersion: 'v2', + 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: groupPayloadForLatestVersion, ChildStep: validateMessageAndPreparePayload, 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: { + apiKey: 'testApiKey', + apiVersion: 'v2', + apiServer: 'standard', + sendAnonymousId: false, + }, + }, + metadata: { + jobId: 11, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + JSON: { + company_id: 'rudderlabs', + 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 testApiKey', + '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: { + apiKey: 'testApiKey', + apiVersion: 'v2', + apiServer: 'eu', + sendAnonymousId: false, + }, + }, + metadata: { + jobId: 12, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + JSON: { + id: '657264e9018c0a647s45', + }, + XML: {}, + FORM: {}, + JSON_ARRAY: {}, + }, + endpoint: 'https://api.eu.intercom.io/contacts/70701240741e45d040/companies', + headers: { + Accept: 'application/json', + Authorization: 'Bearer testApiKey', + '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: { + apiKey: 'testApiKey', + apiVersion: 'v2', + 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 testApiKey', + '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: { + apiKey: 'testApiKey', + apiVersion: 'v2', + apiServer: 'standard', + sendAnonymousId: false, + }, + }, + metadata: { + jobId: 14, + }, + }, + ], + 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 testApiKey', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'Intercom-Version': '2.10', + }, + userId: '', + version: '1', + type: 'REST', + method: 'POST', + files: {}, + params: {}, + }, + metadata: { jobId: 14 }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'intercom', + description: 'Old version - successful identify call', feature: 'processor', module: 'destination', version: 'v0', @@ -90,12 +1289,21 @@ export const data = [ type: 'identify', }, destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, Config: { apiKey: 'intercomApiKey', + apiVersion: 'v1', appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', collectContext: false, }, }, + metadata: { + jobId: 15, + }, }, ], }, @@ -148,6 +1356,9 @@ export const data = [ userId: '58b21c2d-f8d5-4410-a2d0-b268a26b7e33', }, statusCode: 200, + metadata: { + jobId: 15, + }, }, ], }, @@ -155,7 +1366,7 @@ export const data = [ }, { name: 'intercom', - description: 'Test 1', + description: 'Old version - successful identify call', feature: 'processor', module: 'destination', version: 'v0', @@ -225,12 +1436,21 @@ export const data = [ type: 'identify', }, destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, Config: { apiKey: 'intercomApiKey', + apiVersion: 'v1', appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', collectContext: false, }, }, + metadata: { + jobId: 16, + }, }, ], }, @@ -273,6 +1493,9 @@ export const data = [ userId: '58b21c2d-f8d5-4410-a2d0-b268a26b7e33', }, statusCode: 200, + metadata: { + jobId: 16, + }, }, ], }, @@ -280,7 +1503,7 @@ export const data = [ }, { name: 'intercom', - description: 'Test 2', + description: 'Old version - successful identify call', feature: 'processor', module: 'destination', version: 'v0', @@ -349,12 +1572,21 @@ export const data = [ type: 'identify', }, destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, Config: { apiKey: 'intercomApiKey', + apiVersion: 'v1', appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', collectContext: false, }, }, + metadata: { + jobId: 17, + }, }, ], }, @@ -397,6 +1629,9 @@ export const data = [ userId: '58b21c2d-f8d5-4410-a2d0-b268a26b7e33', }, statusCode: 200, + metadata: { + jobId: 17, + }, }, ], }, @@ -404,7 +1639,7 @@ export const data = [ }, { name: 'intercom', - description: 'Test 3', + description: 'Old version - successful identify call', feature: 'processor', module: 'destination', version: 'v0', @@ -473,12 +1708,21 @@ export const data = [ type: 'identify', }, destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, Config: { apiKey: 'intercomApiKey', + apiVersion: 'v1', appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', collectContext: false, }, }, + metadata: { + jobId: 18, + }, }, ], }, @@ -521,6 +1765,9 @@ export const data = [ userId: '58b21c2d-f8d5-4410-a2d0-b268a26b7e33', }, statusCode: 200, + metadata: { + jobId: 18, + }, }, ], }, @@ -528,7 +1775,7 @@ export const data = [ }, { name: 'intercom', - description: 'Test 4: ERROR - Either of `email` or `userId` is required for Identify call', + description: 'Old Version: Identify call without email and userId', feature: 'processor', module: 'destination', version: 'v0', @@ -596,12 +1843,21 @@ export const data = [ type: 'identify', }, destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, Config: { apiKey: 'intercomApiKey', + apiVersion: 'v1', appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', collectContext: false, }, }, + metadata: { + jobId: 19, + }, }, ], }, @@ -612,15 +1868,19 @@ export const data = [ body: [ { statusCode: 400, - error: 'Either of `email` or `userId` is required for Identify call', + error: + 'Either of `email` or `userId` is required for Identify call: Workflow: procWorkflow, Step: identifyPayloadForOlderVersion, ChildStep: undefined, OriginalError: Either of `email` or `userId` is required for Identify call', statTags: { errorCategory: 'dataValidation', errorType: 'instrumentation', destType: 'INTERCOM', module: 'destination', - implementation: 'native', + implementation: 'cdkV2', feature: 'processor', }, + metadata: { + jobId: 19, + }, }, ], }, @@ -628,7 +1888,7 @@ export const data = [ }, { name: 'intercom', - description: 'Test 5', + description: 'Old version - successful identify call', feature: 'processor', module: 'destination', version: 'v0', @@ -707,12 +1967,21 @@ export const data = [ type: 'identify', }, destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, Config: { apiKey: 'intercomApiKey', + apiVersion: 'v1', appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', collectContext: false, }, }, + metadata: { + jobId: 20, + }, }, ], }, @@ -767,6 +2036,9 @@ export const data = [ userId: '58b21c2d-f8d5-4410-a2d0-b268a26b7e33', }, statusCode: 200, + metadata: { + jobId: 20, + }, }, ], }, @@ -774,7 +2046,7 @@ export const data = [ }, { name: 'intercom', - description: 'Test 6', + description: 'Old version - successful identify call', feature: 'processor', module: 'destination', version: 'v0', @@ -853,13 +2125,22 @@ export const data = [ type: 'identify', }, destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, Config: { apiKey: 'intercomApiKey', + apiVersion: 'v1', appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', collectContext: false, updateLastRequestAt: false, }, }, + metadata: { + jobId: 21, + }, }, ], }, @@ -914,6 +2195,9 @@ export const data = [ userId: '58b21c2d-f8d5-4410-a2d0-b268a26b7e33', }, statusCode: 200, + metadata: { + jobId: 21, + }, }, ], }, @@ -921,7 +2205,7 @@ export const data = [ }, { name: 'intercom', - description: 'Test 7', + description: 'Old version - successful identify call', feature: 'processor', module: 'destination', version: 'v0', @@ -995,12 +2279,21 @@ export const data = [ type: 'identify', }, destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, Config: { apiKey: 'intercomApiKey', + apiVersion: 'v1', appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', collectContext: false, }, }, + metadata: { + jobId: 22, + }, }, ], }, @@ -1044,6 +2337,9 @@ export const data = [ userId: '58b21c2d-f8d5-4410-a2d0-b268a26b7e33', }, statusCode: 200, + metadata: { + jobId: 22, + }, }, ], }, @@ -1051,7 +2347,7 @@ export const data = [ }, { name: 'intercom', - description: 'Test 8', + description: 'Old version - successful track call', feature: 'processor', module: 'destination', version: 'v0', @@ -1154,12 +2450,21 @@ export const data = [ type: 'track', }, destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, Config: { apiKey: 'intercomApiKey', + apiVersion: 'v1', appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', collectContext: false, }, }, + metadata: { + jobId: 23, + }, }, ], }, @@ -1222,6 +2527,9 @@ export const data = [ userId: '58b21c2d-f8d5-4410-a2d0-b268a26b7e33', }, statusCode: 200, + metadata: { + jobId: 23, + }, }, ], }, @@ -1229,7 +2537,7 @@ export const data = [ }, { name: 'intercom', - description: 'Test 9', + description: 'Old version - successful track call', feature: 'processor', module: 'destination', version: 'v0', @@ -1300,12 +2608,21 @@ export const data = [ type: 'track', }, destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, Config: { apiKey: 'intercomApiKey', + apiVersion: 'v1', appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', collectContext: false, }, }, + metadata: { + jobId: 24, + }, }, ], }, @@ -1341,6 +2658,9 @@ export const data = [ userId: '58b21c2d-f8d5-4410-a2d0-b268a26b7e33', }, statusCode: 200, + metadata: { + jobId: 24, + }, }, ], }, @@ -1348,7 +2668,7 @@ export const data = [ }, { name: 'intercom', - description: 'Test 10: ERROR - Either of `email` or `userId` is required for Track call', + description: 'Old version : Track call without email or userId', feature: 'processor', module: 'destination', version: 'v0', @@ -1418,12 +2738,21 @@ export const data = [ type: 'track', }, destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, Config: { apiKey: 'intercomApiKey', + apiVersion: 'v1', appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', collectContext: false, }, }, + metadata: { + jobId: 25, + }, }, ], }, @@ -1434,15 +2763,19 @@ export const data = [ body: [ { statusCode: 400, - error: 'Either of `email` or `userId` is required for Track call', + error: + 'Either email or userId is required for Track call: Workflow: procWorkflow, Step: trackPayload, ChildStep: undefined, OriginalError: Either email or userId is required for Track call', statTags: { errorCategory: 'dataValidation', errorType: 'instrumentation', destType: 'INTERCOM', module: 'destination', - implementation: 'native', + implementation: 'cdkV2', feature: 'processor', }, + metadata: { + jobId: 25, + }, }, ], }, @@ -1450,7 +2783,7 @@ export const data = [ }, { name: 'intercom', - description: 'Test 11', + description: 'Old version : successful identify call', feature: 'processor', module: 'destination', version: 'v0', @@ -1527,12 +2860,21 @@ export const data = [ type: 'identify', }, destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, Config: { apiKey: 'intercomApiKey', + apiVersion: 'v1', appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', collectContext: false, }, }, + metadata: { + jobId: 26, + }, }, ], }, @@ -1574,6 +2916,9 @@ export const data = [ userId: '58b21c2d-f8d5-4410-a2d0-b268a26b7e33', }, statusCode: 200, + metadata: { + jobId: 26, + }, }, ], }, @@ -1581,7 +2926,7 @@ export const data = [ }, { name: 'intercom', - description: 'Test 12', + description: 'Old version : successful identify call', feature: 'processor', module: 'destination', version: 'v0', @@ -1651,13 +2996,22 @@ export const data = [ type: 'identify', }, destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, Config: { apiKey: 'intercomApiKey', + apiVersion: 'v1', appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', collectContext: false, sendAnonymousId: true, }, }, + metadata: { + jobId: 27, + }, }, ], }, @@ -1700,6 +3054,9 @@ export const data = [ userId: '58b21c2d-f8d5-4410-a2d0-b268a26b7e33', }, statusCode: 200, + metadata: { + jobId: 27, + }, }, ], }, @@ -1707,7 +3064,7 @@ export const data = [ }, { name: 'intercom', - description: 'Test 13: ERROR - Either of `email` or `userId` is required for Identify call', + description: 'Old version : Identify call without email or userId', feature: 'processor', module: 'destination', version: 'v0', @@ -1777,13 +3134,22 @@ export const data = [ type: 'identify', }, destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, Config: { apiKey: 'intercomApiKey', + apiVersion: 'v1', appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', collectContext: false, sendAnonymousId: false, }, }, + metadata: { + jobId: 28, + }, }, ], }, @@ -1794,15 +3160,19 @@ export const data = [ body: [ { statusCode: 400, - error: 'Either of `email` or `userId` is required for Identify call', + error: + 'Either of `email` or `userId` is required for Identify call: Workflow: procWorkflow, Step: identifyPayloadForOlderVersion, ChildStep: undefined, OriginalError: Either of `email` or `userId` is required for Identify call', statTags: { errorCategory: 'dataValidation', errorType: 'instrumentation', destType: 'INTERCOM', module: 'destination', - implementation: 'native', + implementation: 'cdkV2', feature: 'processor', }, + metadata: { + jobId: 28, + }, }, ], }, @@ -1810,7 +3180,7 @@ export const data = [ }, { name: 'intercom', - description: 'Test 14', + description: 'Old version : successful group call', feature: 'processor', module: 'destination', version: 'v0', @@ -1840,12 +3210,21 @@ export const data = [ userId: 'sdfrsdfsdfsf', }, destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, Config: { apiKey: 'abcd=', appId: 'asdasdasd', + apiVersion: 'v1', collectContext: false, }, }, + metadata: { + jobId: 29, + }, }, ], }, @@ -1890,6 +3269,9 @@ export const data = [ userId: 'sdfrsdfsdfsf', }, statusCode: 200, + metadata: { + jobId: 29, + }, }, { output: { @@ -1922,6 +3304,9 @@ export const data = [ userId: 'sdfrsdfsdfsf', }, statusCode: 200, + metadata: { + jobId: 29, + }, }, ], }, @@ -1929,7 +3314,7 @@ export const data = [ }, { name: 'intercom', - description: 'Test 15', + description: 'Old version : successful group call', feature: 'processor', module: 'destination', version: 'v0', @@ -1992,12 +3377,21 @@ export const data = [ type: 'group', }, destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, Config: { apiKey: 'abcd=', + apiVersion: 'v1', appId: 'asdasdasd', collectContext: false, }, }, + metadata: { + jobId: 30, + }, }, ], }, @@ -2039,6 +3433,9 @@ export const data = [ userId: '12312312', }, statusCode: 200, + metadata: { + jobId: 30, + }, }, ], }, @@ -2046,7 +3443,7 @@ export const data = [ }, { name: 'intercom', - description: 'Test 16', + description: 'Old version : successful group call', feature: 'processor', module: 'destination', version: 'v0', @@ -2081,12 +3478,21 @@ export const data = [ userId: 'sdfrsdfsdfsf', }, destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, Config: { apiKey: 'abcd=', + apiVersion: 'v1', appId: 'asdasdasd', collectContext: false, }, }, + metadata: { + jobId: 31, + }, }, ], }, @@ -2132,6 +3538,9 @@ export const data = [ userId: 'sdfrsdfsdfsf', }, statusCode: 200, + metadata: { + jobId: 31, + }, }, { output: { @@ -2165,6 +3574,9 @@ export const data = [ userId: 'sdfrsdfsdfsf', }, statusCode: 200, + metadata: { + jobId: 31, + }, }, ], }, @@ -2172,7 +3584,7 @@ export const data = [ }, { name: 'intercom', - description: 'Test 17', + description: 'Old version : successful group call', feature: 'processor', module: 'destination', version: 'v0', @@ -2212,13 +3624,22 @@ export const data = [ type: 'group', }, destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, Config: { apiKey: 'abcd=', appId: 'asdasdasd', + apiVersion: 'v1', collectContext: false, sendAnonymousId: true, }, }, + metadata: { + jobId: 32, + }, }, ], }, @@ -2270,6 +3691,9 @@ export const data = [ userId: 'anonId', }, statusCode: 200, + metadata: { + jobId: 32, + }, }, { output: { @@ -2303,6 +3727,9 @@ export const data = [ userId: 'anonId', }, statusCode: 200, + metadata: { + jobId: 32, + }, }, ], }, diff --git a/test/integrations/destinations/intercom/router/data.ts b/test/integrations/destinations/intercom/router/data.ts index 766161bac8..7ce3c7351a 100644 --- a/test/integrations/destinations/intercom/router/data.ts +++ b/test/integrations/destinations/intercom/router/data.ts @@ -1,4 +1,379 @@ 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', + integrations: { All: true }, + 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', + apiVersion: 'v2', + sendAnonymousId: false, + updateLastRequestAt: true, + }, + }, + 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: { + apiKey: 'testApiKey', + apiServer: 'standard', + apiVersion: 'v2', + sendAnonymousId: false, + updateLastRequestAt: 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: { + apiKey: 'testApiKey', + apiVersion: 'v2', + apiServer: 'eu', + sendAnonymousId: false, + }, + }, + metadata: { + 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', + apiVersion: 'v2', + apiServer: 'eu', + sendAnonymousId: false, + }, + }, + metadata: { + jobId: 4, + }, + }, + ], + destType: 'intercom', + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batched: false, + batchedRequest: { + body: { + JSON: { + email: 'test@rudderlabs.com', + external_id: 'user@1', + 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 testApiKey', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'Intercom-Version': '2.10', + }, + method: 'POST', + params: {}, + type: 'REST', + version: '1', + }, + destination: { + Config: { + apiKey: 'testApiKey', + apiServer: 'standard', + apiVersion: 'v2', + sendAnonymousId: false, + updateLastRequestAt: true, + }, + 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 testApiKey', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'Intercom-Version': '2.10', + }, + method: 'POST', + params: {}, + type: 'REST', + version: '1', + }, + destination: { + Config: { + apiKey: 'testApiKey', + apiServer: 'standard', + apiVersion: 'v2', + sendAnonymousId: false, + updateLastRequestAt: false, + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + metadata: [{ jobId: 2 }], + statusCode: 200, + }, + { + batched: false, + batchedRequest: { + body: { + JSON: { + id: '657264e9018c0a647s45', + }, + XML: {}, + FORM: {}, + JSON_ARRAY: {}, + }, + endpoint: 'https://api.eu.intercom.io/contacts/70701240741e45d040/companies', + files: {}, + headers: { + Authorization: 'Bearer testApiKey', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'Intercom-Version': '2.10', + }, + method: 'POST', + params: {}, + type: 'REST', + version: '1', + }, + destination: { + Config: { + apiKey: 'testApiKey', + apiServer: 'eu', + apiVersion: 'v2', + sendAnonymousId: false, + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + metadata: [ + { + jobId: 3, + }, + ], + 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: 'eu', + apiVersion: 'v2', + sendAnonymousId: false, + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + metadata: [ + { + jobId: 4, + }, + ], + statusCode: 401, + }, + ], + }, + }, + }, + }, { name: 'intercom', description: 'Test 0', @@ -27,7 +402,10 @@ export const data = [ name: 'iPod touch (7th generation)', type: 'iOS', }, - library: { name: 'test-ios-library', version: '1.0.7' }, + library: { + name: 'test-ios-library', + version: '1.0.7', + }, locale: 'en-US', network: { bluetooth: false, @@ -35,8 +413,15 @@ export const data = [ cellular: false, wifi: true, }, - os: { name: 'iOS', version: '14.0' }, - screen: { density: 2, height: 320, width: 568 }, + os: { + name: 'iOS', + version: '14.0', + }, + screen: { + density: 2, + height: 320, + width: 568, + }, timezone: 'Asia/Kolkata', traits: { anonymousId: '58b21c2d-f8d5-4410-a2d0-b268a26b7e33', @@ -52,7 +437,9 @@ export const data = [ userAgent: 'unknown', }, event: 'Test Event 2', - integrations: { All: true }, + 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', @@ -61,11 +448,20 @@ export const data = [ timestamp: '2020-10-01T00:41:01.324+05:30', type: 'identify', }, - metadata: { jobId: 1, userId: 'u1' }, + metadata: { + jobId: 1, + }, destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, Config: { - apiKey: 'intercomApiKey', - appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', + apiKey: 'testApiKey', + apiVersion: 'v1', + sendAnonymousId: false, + updateLastRequestAt: false, collectContext: false, }, }, @@ -88,7 +484,10 @@ export const data = [ name: 'iPod touch (7th generation)', type: 'iOS', }, - library: { name: 'test-ios-library', version: '1.0.7' }, + library: { + name: 'test-ios-library', + version: '1.0.7', + }, locale: 'en-US', network: { bluetooth: false, @@ -96,8 +495,15 @@ export const data = [ cellular: false, wifi: true, }, - os: { name: 'iOS', version: '14.0' }, - screen: { density: 2, height: 320, width: 568 }, + os: { + name: 'iOS', + version: '14.0', + }, + screen: { + density: 2, + height: 320, + width: 568, + }, timezone: 'Asia/Kolkata', traits: { anonymousId: '58b21c2d-f8d5-4410-a2d0-b268a26b7e33', @@ -111,7 +517,9 @@ export const data = [ userAgent: 'unknown', }, event: 'Test Event 2', - integrations: { All: true }, + 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', @@ -120,14 +528,65 @@ export const data = [ timestamp: '2020-10-01T00:41:01.324+05:30', type: 'identify', }, - metadata: { jobId: 2, userId: 'u1' }, + metadata: { + jobId: 2, + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiKey: 'testApiKey', + apiVersion: 'v1', + sendAnonymousId: false, + updateLastRequestAt: false, + collectContext: false, + }, + }, + }, + { + 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: { - apiKey: 'intercomApiKey', - appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', + apiKey: 'testApiKey', + apiVersion: 'v1', + sendAnonymousId: false, collectContext: false, }, }, + metadata: { + jobId: 3, + }, }, ], destType: 'intercom', @@ -147,7 +606,7 @@ export const data = [ endpoint: 'https://api.intercom.io/users', headers: { 'Content-Type': 'application/json', - Authorization: 'Bearer intercomApiKey', + Authorization: 'Bearer testApiKey', Accept: 'application/json', 'Intercom-Version': '1.4', }, @@ -159,7 +618,7 @@ export const data = [ name: 'Test Name', signed_up_at: 1601493060, last_seen_user_agent: 'unknown', - update_last_request_at: true, + update_last_request_at: false, user_id: 'test_user_id_1', custom_attributes: { anonymousId: '58b21c2d-f8d5-4410-a2d0-b268a26b7e33', @@ -173,14 +632,25 @@ export const data = [ files: {}, userId: '58b21c2d-f8d5-4410-a2d0-b268a26b7e33', }, - metadata: [{ jobId: 1, userId: 'u1' }], + metadata: [ + { + jobId: 1, + }, + ], batched: false, statusCode: 200, destination: { Config: { - apiKey: 'intercomApiKey', - appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', + apiKey: 'testApiKey', + apiVersion: 'v1', collectContext: false, + sendAnonymousId: false, + updateLastRequestAt: false, + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, }, }, }, @@ -192,7 +662,7 @@ export const data = [ endpoint: 'https://api.intercom.io/users', headers: { 'Content-Type': 'application/json', - Authorization: 'Bearer intercomApiKey', + Authorization: 'Bearer testApiKey', Accept: 'application/json', 'Intercom-Version': '1.4', }, @@ -204,7 +674,7 @@ export const data = [ signed_up_at: 1601493060, name: 'Test Name', last_seen_user_agent: 'unknown', - update_last_request_at: true, + update_last_request_at: false, custom_attributes: { anonymousId: '58b21c2d-f8d5-4410-a2d0-b268a26b7e33', key1: 'value1', @@ -217,17 +687,108 @@ export const data = [ files: {}, userId: '58b21c2d-f8d5-4410-a2d0-b268a26b7e33', }, - metadata: [{ jobId: 2, userId: 'u1' }], + metadata: [ + { + jobId: 2, + }, + ], batched: false, statusCode: 200, destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, Config: { - apiKey: 'intercomApiKey', - appId: '9e9cdea1-78fa-4829-a9b2-5d7f7e96d1a0', + apiKey: 'testApiKey', + apiVersion: 'v1', collectContext: false, + sendAnonymousId: false, + updateLastRequestAt: false, }, }, }, + { + batched: false, + batchedRequest: [ + { + body: { + FORM: {}, + JSON: { + company_id: 'rudderlabs', + industry: 'CDP', + name: 'RudderStack', + plan: 'enterprise', + size: 500, + website: 'www.rudderstack.com', + }, + JSON_ARRAY: {}, + XML: {}, + }, + endpoint: 'https://api.intercom.io/companies', + files: {}, + headers: { + Accept: 'application/json', + Authorization: 'Bearer testApiKey', + 'Content-Type': 'application/json', + 'Intercom-Version': '1.4', + }, + method: 'POST', + params: {}, + type: 'REST', + version: '1', + }, + { + body: { + FORM: {}, + JSON: { + companies: [ + { + company_id: 'rudderlabs', + name: 'RudderStack', + }, + ], + email: 'test+5@rudderlabs.com', + user_id: 'user@5', + }, + JSON_ARRAY: {}, + XML: {}, + }, + endpoint: 'https://api.intercom.io/users', + files: {}, + headers: { + Accept: 'application/json', + Authorization: 'Bearer testApiKey', + 'Content-Type': 'application/json', + 'Intercom-Version': '1.4', + }, + method: 'POST', + params: {}, + type: 'REST', + version: '1', + }, + ], + destination: { + Config: { + apiKey: 'testApiKey', + apiVersion: 'v1', + collectContext: false, + sendAnonymousId: false, + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + metadata: [ + { + jobId: 3, + }, + ], + statusCode: 200, + }, ], }, },