Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(intercom): upgrade intercom version from 1.4 to 2.10 #2976

Merged
merged 24 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
68fee5e
feat(intercom): upgrade intercom version from 1.4 to 2.10
mihir-4116 Jan 8, 2024
3f7b61e
chore: code review changes
mihir-4116 Jan 8, 2024
6c2c442
chore: code review changes
mihir-4116 Jan 11, 2024
bb2e49c
Merge branch 'develop' into feat.intercom
mihir-4116 Jan 11, 2024
fcddc25
chore: code review changes
mihir-4116 Jan 11, 2024
25b6045
Merge branch 'develop' into feat.intercom
mihir-4116 Jan 15, 2024
e3cd261
Merge branch 'develop' into feat.intercom
mihir-4116 Jan 17, 2024
0037e63
chore: added backward compatibility for updateLastRequestAt field
mihir-4116 Jan 17, 2024
70ee9bd
chore: intercom backward compatibility support
mihir-4116 Jan 24, 2024
80c89a5
chore: pr conflicts resolved
mihir-4116 Jan 25, 2024
f2fa005
chore: pr conflicts resolved
mihir-4116 Jan 25, 2024
03ac000
Merge branch 'develop' into feat.intercom
mihir-4116 Jan 25, 2024
95d114a
Merge branch 'feat.intercom' of github.com:rudderlabs/rudder-transfor…
mihir-4116 Jan 25, 2024
3522b58
chore: added step to check value of apiVersion
mihir-4116 Jan 25, 2024
e2133fa
chore: change default value of apiVersion to latest
mihir-4116 Jan 25, 2024
851e0e2
Merge branch 'develop' into feat.intercom
mihir-4116 Jan 31, 2024
8ca8822
Merge branch 'develop' into feat.intercom
mihir-4116 Jan 31, 2024
ec6f602
fix: intercom router response
mihir-4116 Feb 1, 2024
4834210
chore: code review changes
mihir-4116 Feb 2, 2024
cccb813
chore: code review changes
mihir-4116 Feb 2, 2024
05472f0
Merge branch 'develop' into feat.intercom
mihir-4116 Feb 2, 2024
2d436b2
Merge branch 'feat.intercom' of github.com:rudderlabs/rudder-transfor…
mihir-4116 Feb 2, 2024
e904dbd
Merge branch 'develop' into feat.intercom
mihir-4116 Feb 2, 2024
702c477
Merge branch 'develop' into feat.intercom
mihir-4116 Feb 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions src/cdk/v2/destinations/intercom/config.js
Original file line number Diff line number Diff line change
@@ -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,
};
230 changes: 230 additions & 0 deletions src/cdk/v2/destinations/intercom/procWorkflow.yaml
koladilip marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
bindings:
- name: EventType
mihir-4116 marked this conversation as resolved.
Show resolved Hide resolved
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;
33 changes: 33 additions & 0 deletions src/cdk/v2/destinations/intercom/rtWorkflow.yaml
Original file line number Diff line number Diff line change
@@ -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]
Loading
Loading