Skip to content

Commit

Permalink
feat: onboarding intercom v2 retl support (#3843)
Browse files Browse the repository at this point in the history
* feat: onboarding intercom v2 retl support

* fix: fixing export error

* fix: searching contact for insert record

* fix: added more tests

* fix: addressing comment

* fix: minor change
  • Loading branch information
manish339k authored Nov 15, 2024
1 parent 6554893 commit 3d7db73
Show file tree
Hide file tree
Showing 6 changed files with 558 additions and 8 deletions.
7 changes: 7 additions & 0 deletions src/v0/destinations/intercom_v2/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ const ApiVersions = {
v2: '2.10',
};

const RecordAction = {
INSERT: 'insert',
UPDATE: 'update',
DELETE: 'delete',
};

const ConfigCategory = {
IDENTIFY: {
name: 'IntercomIdentifyConfig',
Expand All @@ -25,4 +31,5 @@ module.exports = {
ConfigCategory,
MappingConfig,
ApiVersions,
RecordAction,
};
51 changes: 47 additions & 4 deletions src/v0/destinations/intercom_v2/transform.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { InstrumentationError } = require('@rudderstack/integrations-lib');
const { InstrumentationError, ConfigurationError } = require('@rudderstack/integrations-lib');
const {
handleRtTfSingleEventError,
getSuccessRespEvents,
Expand All @@ -17,13 +17,14 @@ const {
addOrUpdateTagsToCompany,
getStatusCode,
getBaseEndpoint,
getRecordAction,
} = require('./utils');
const {
getName,
filterCustomAttributes,
addMetadataToPayload,
} = require('../../../cdk/v2/destinations/intercom/utils');
const { MappingConfig, ConfigCategory } = require('./config');
const { MappingConfig, ConfigCategory, RecordAction } = require('./config');

const transformIdentifyPayload = (event) => {
const { message, destination } = event;
Expand All @@ -38,7 +39,7 @@ const transformIdentifyPayload = (event) => {
}
payload.name = getName(message);
payload.custom_attributes = message.traits || message.context.traits || {};
payload.custom_attributes = filterCustomAttributes(payload, 'user', destination);
payload.custom_attributes = filterCustomAttributes(payload, 'user', destination, message);
return payload;
};

Expand Down Expand Up @@ -66,7 +67,7 @@ const transformGroupPayload = (event) => {
const category = ConfigCategory.GROUP;
const payload = constructPayload(message, MappingConfig[category.name]);
payload.custom_attributes = message.traits || message.context.traits || {};
payload.custom_attributes = filterCustomAttributes(payload, 'company', destination);
payload.custom_attributes = filterCustomAttributes(payload, 'company', destination, message);
return payload;
};

Expand Down Expand Up @@ -131,6 +132,45 @@ const constructGroupResponse = async (event) => {
return getResponse(method, endpoint, headers, finalPayload);
};

const constructRecordResponse = async (event) => {
const { message, destination, metadata } = event;
const { identifiers, fields } = message;

let method = 'POST';
let endpoint = `${getBaseEndpoint(destination)}/contacts`;
let payload = {};

const action = getRecordAction(message);
const contactId = await searchContact(event);

if ((action === RecordAction.UPDATE || action === RecordAction.DELETE) && !contactId) {
throw new ConfigurationError('Contact is not present. Aborting.');
}

switch (action) {
case RecordAction.INSERT:
payload = { ...identifiers, ...fields };
if (contactId) {
endpoint += `/${contactId}`;
payload = { ...fields };
method = 'PUT';
}
break;
case RecordAction.UPDATE:
endpoint += `/${contactId}`;
payload = { ...fields };
method = 'PUT';
break;
case RecordAction.DELETE:
endpoint += `/${contactId}`;
method = 'DELETE';
break;
default:
throw new InstrumentationError(`action ${action} is not supported.`);
}
return getResponse(method, endpoint, getHeaders(metadata), payload);
};

const processEvent = async (event) => {
const { message } = event;
const messageType = getEventType(message);
Expand All @@ -145,6 +185,9 @@ const processEvent = async (event) => {
case EventType.GROUP:
response = await constructGroupResponse(event);
break;
case EventType.RECORD:
response = constructRecordResponse(event);
break;
default:
throw new InstrumentationError(`message type ${messageType} is not supported.`);
}
Expand Down
25 changes: 21 additions & 4 deletions src/v0/destinations/intercom_v2/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ const { getAccessToken } = require('../../util');
const { ApiVersions, destType } = require('./config');
const { getDynamicErrorType } = require('../../../adapters/utils/networkUtils');

const getRecordAction = (message) => message?.action?.toLowerCase();

/**
* method to handle error during api call
* ref docs: https://developers.intercom.com/docs/references/rest-api/errors/http-responses/
Expand Down Expand Up @@ -99,11 +101,25 @@ const getResponse = (method, endpoint, headers, payload) => {

const searchContact = async (event) => {
const { message, destination, metadata } = event;
const lookupField = getLookUpField(message);
let lookupFieldValue = getFieldValueFromMessage(message, lookupField);
if (!lookupFieldValue) {
lookupFieldValue = message?.context?.traits?.[lookupField];

const extractLookupFieldAndValue = () => {
const messageType = getEventType(message);
if (messageType === EventType.RECORD) {
const { identifiers } = message;
return Object.entries(identifiers || {})[0] || [null, null];
}
const lookupField = getLookUpField(message);
const lookupFieldValue =
getFieldValueFromMessage(message, lookupField) || message?.context?.traits?.[lookupField];
return [lookupField, lookupFieldValue];
};

const [lookupField, lookupFieldValue] = extractLookupFieldAndValue();

if (!lookupField || !lookupFieldValue) {
throw new InstrumentationError('Missing lookup field or lookup field value for searchContact');
}

const data = JSON.stringify({
query: {
operator: 'AND',
Expand Down Expand Up @@ -329,4 +345,5 @@ module.exports = {
attachContactToCompany,
addOrUpdateTagsToCompany,
getBaseEndpoint,
getRecordAction,
};
102 changes: 102 additions & 0 deletions test/integrations/destinations/intercom_v2/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,108 @@ const deliveryCallsData = [
},
},
},
{
httpReq: {
method: 'post',
url: 'https://api.intercom.io/contacts/search',
data: {
query: {
operator: 'AND',
value: [{ field: 'email', operator: '=', value: '[email protected]' }],
},
},
headers,
},
httpRes: {
status: 200,
statusText: 'ok',
data: {
type: 'list',
total_count: 0,
pages: {
type: 'pages',
page: 1,
per_page: 50,
total_pages: 0,
},
data: [
{
type: 'contact',
id: 'retl-available-contact-id',
workspace_id: 'rudderWorkspace',
external_id: 'detach-company-user-id',
role: 'user',
email: '[email protected]',
},
],
},
},
},
{
httpReq: {
method: 'post',
url: 'https://api.intercom.io/contacts/search',
data: {
query: {
operator: 'AND',
value: [{ field: 'email', operator: '=', value: '[email protected]' }],
},
},
headers,
},
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.au.intercom.io/contacts/search',
data: {
query: {
operator: 'AND',
value: [{ field: 'external_id', operator: '=', value: 'known-user-id-1' }],
},
},
headers,
},
httpRes: {
status: 200,
statusText: 'ok',
data: {
type: 'list',
total_count: 0,
pages: {
type: 'pages',
page: 1,
per_page: 50,
total_pages: 0,
},
data: [
{
type: 'contact',
id: 'contact-id-by-intercom-known-user-id-1',
workspace_id: 'rudderWorkspace',
external_id: 'user-id-1',
role: 'user',
email: '[email protected]',
},
],
},
},
},
];

export const networkCallsData = [...deliveryCallsData];
Loading

0 comments on commit 3d7db73

Please sign in to comment.