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 #2867

Closed
wants to merge 30 commits into from
Closed
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a01a96e
feat(intercom): refactor
mihir-4116 Sep 14, 2023
fb11bca
feat(intercom): additional improvements
mihir-4116 Sep 14, 2023
33bdccd
fix(intercom): test cases
mihir-4116 Sep 15, 2023
10d7838
Merge branch 'develop' into feat.intercom-refactor
mihir-4116 Sep 19, 2023
e3a269f
Merge branch 'develop' into feat.intercom-refactor
mihir-4116 Sep 22, 2023
c619099
Merge branch 'develop' into feat.intercom-refactor
mihir-4116 Sep 25, 2023
fe15ecc
chore: code review changes
mihir-4116 Sep 26, 2023
071a395
Merge branch 'develop' into feat.intercom-refactor
mihir-4116 Sep 26, 2023
1151d90
chore: develop pull
mihir-4116 Nov 14, 2023
9c23c39
feat(intercom): move it to router transform
mihir-4116 Dec 4, 2023
1edac22
feat(intercom): move it to router transform
mihir-4116 Dec 4, 2023
a6ff85a
chore: pr conflicts resolved
mihir-4116 Dec 4, 2023
345853b
feat(intercom): upgrade intercom version from 1.4 to 2.10
mihir-4116 Dec 7, 2023
39e93ce
feat(intercom): upgrade intercom version from 1.4 to 2.10
mihir-4116 Dec 7, 2023
a4bdb14
Merge branch 'develop' into feat.intercom-refactor
mihir-4116 Dec 7, 2023
3c3dd42
chore: reverted apiKey changes
mihir-4116 Dec 9, 2023
1fbdacc
chore: added utility tests for searchContact and createOrUpdateCompan…
mihir-4116 Dec 9, 2023
ee6aab1
Merge branch 'develop' into feat.intercom-refactor
mihir-4116 Dec 9, 2023
2759026
chore: monir refactors
mihir-4116 Dec 9, 2023
170e094
chore: added negative test in router
mihir-4116 Dec 9, 2023
9b8ec30
chore: code review changes
mihir-4116 Dec 11, 2023
c9465ac
chore: pr conflicts resolved
mihir-4116 Dec 11, 2023
fbdf23d
Merge branch 'develop' into feat.intercom-refactor
mihir-4116 Dec 11, 2023
1b9900e
Merge branch 'develop' into feat.intercom-refactor
mihir-4116 Dec 12, 2023
2b880d6
Merge branch 'develop' into feat.intercom-refactor
mihir-4116 Dec 12, 2023
75e8dc5
chore: pr conflicts resolved
mihir-4116 Dec 20, 2023
e3325da
chore: code review changes
mihir-4116 Dec 20, 2023
c47bf6d
chore: pr conflicts resolved
mihir-4116 Dec 27, 2023
c9684ec
Merge branch 'develop' into feat.intercom-refactor
mihir-4116 Jan 2, 2024
561b4ce
chore: pr conflicts resolved
mihir-4116 Jan 8, 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
55 changes: 55 additions & 0 deletions src/cdk/v2/destinations/intercom/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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 ReservedUserAttributes = [
'userId',
'role',
'email',
'phone',
'name',
'avatar',
'company',
'ownerId',
'lastName',
'lastname',
'firstName',
'firstname',
'createdAt',
'timestamp',
'originalTimestamp',
'unsubscribedFromEmails',
];

const ReservedCompanyAttributes = [
'tags',
'size',
'plan',
'name',
'email',
'userId',
'website',
'industry',
'segments',
'userCount',
'createdAt',
'sessionCount',
'monthlySpend',
'remoteCreatedAt',
];

const MetadataTypes = { richLink: ['url', 'value'], monetaryAmount: ['amount', 'currency'] };

module.exports = {
BASE_ENDPOINT,
MetadataTypes,
BASE_EU_ENDPOINT,
BASE_AU_ENDPOINT,
ReservedUserAttributes,
SEARCH_CONTACT_ENDPOINT,
ReservedCompanyAttributes,
CREATE_OR_UPDATE_COMPANY_ENDPOINT,
};
170 changes: 170 additions & 0 deletions src/cdk/v2/destinations/intercom/procWorkflow.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
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

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: rEtlPayload
condition: .message.context.mappedToDestination === true
template: |
$.addExternalIdToTraits(.message);
const payload = $.getFieldValueFromMessage(.message, "traits");
payload;

- name: searchContact
condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} || $.outputs.messageType === {{$.EventType.GROUP}}
template: |
const contactId = await $.searchContact(.message, .destination);
contactId;

- name: identifyEtlPayload
condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} && !.message.context.mappedToDestination
template: |
const payload = .message.({
external_id: {{{{$.getGenericPaths("userIdOnly")}}}},
email: {{{{$.getGenericPaths("email")}}}},
phone: {{{{$.getGenericPaths("phone")}}}},
avatar: {{{{$.getGenericPaths("avatar")}}}},
last_seen_at: $.toSeconds({{{{$.getGenericPaths("timestamp")}}}}),
role: .traits.role || .context.traits.role,
signed_up_at: $.toSeconds(.traits.createdAt || .context.traits.createdAt),
owner_id: .traits.ownerId || .context.traits.ownerId ? Number(.traits.ownerId || .context.traits.ownerId) : undefined,
unsubscribed_from_emails: .traits.unsubscribedFromEmails || .context.traits.unsubscribedFromEmails
})
payload.name = $.getName(.message);
payload.custom_attributes = .message.context.traits || {};
payload.custom_attributes = $.filterCustomAttributes(payload, "user");
payload.external_id = !(payload.external_id) && .destination.Config.sendAnonymousId && .message.anonymousId ? .message.anonymousId : payload.external_id;
payload;

- name: prepareIdentifyPayload
condition: $.outputs.messageType === {{$.EventType.IDENTIFY}}
template: |
const endpoint = $.getBaseEndpoint(.destination) + "/" + "contacts";
$.context.payload = .message.context.mappedToDestination === true ? $.outputs.rEtlPayload : $.outputs.identifyEtlPayload;
$.context.requestMethod = $.outputs.searchContact ? 'PUT' : 'POST';
$.context.endpoint = $.outputs.searchContact ? endpoint + "/" + $.outputs.searchContact : endpoint;
$.context.payload = $.removeUndefinedAndNullValues($.context.payload);

- name: validateIdentifyPayload
condition: $.outputs.messageType === {{$.EventType.IDENTIFY}}
template: |
$.assert($.context.payload.external_id || $.context.payload.email, "Either email or userId is required for Identify call");

- name: trackEtlPayload
condition: $.outputs.messageType === {{$.EventType.TRACK}} && !.message.context.mappedToDestination
template: |
let payload = .message.({
event_name: .event,
created_at: $.toSeconds({{{{$.getGenericPaths("timestamp")}}}}),
user_id: {{{{$.getGenericPaths("userIdOnly")}}}},
email: {{{{$.getGenericPaths("email")}}}},
id: .properties.id || .traits.id,
metadata: .properties
})
payload = $.addMetadataToPayload(payload);
payload.user_id = !(payload.user_id) && .destination.Config.sendAnonymousId && .message.anonymousId ? .message.anonymousId : payload.user_id;
payload;

- name: prepareTrackPayload
condition: $.outputs.messageType === {{$.EventType.TRACK}}
template: |
$.context.payload = .message.context.mappedToDestination === true ? $.outputs.rEtlPayload : $.outputs.trackEtlPayload;
$.context.requestMethod = 'POST';
$.context.endpoint = $.getBaseEndpoint(.destination) + "/" + "events";
$.context.payload = $.removeUndefinedAndNullValues($.context.payload);

- name: validateTrackPayload
condition: $.outputs.messageType === {{$.EventType.TRACK}}
template: |
$.assert($.context.payload.event_name, "Event name is required for track call");
$.assert($.context.payload.user_id || $.context.payload.email, "Either email or userId is required for Track call");

- name: groupEtlPayload
condition: $.outputs.messageType === {{$.EventType.GROUP}} && !.message.context.mappedToDestination
template: |
const payload = .message.({
company_id: {{{{$.getGenericPaths("groupId")}}}},
name: {{{{$.getGenericPaths("name")}}}},
website: {{{{$.getGenericPaths("website")}}}},
plan: .traits.plan || .context.traits.plan,
size: Number(.traits.size || .context.traits.size),
industry: .traits.industry || .context.traits.industry,
monthly_spend: .traits.monthlySpend || .context.traits.monthlySpend ? Number(.traits.monthlySpend || .context.traits.monthlySpend) : undefined,
remote_created_at: .traits.remoteCreatedAt || .context.traits.remoteCreatedAt ? Number(.traits.remoteCreatedAt || .context.traits.remoteCreatedAt) : undefined
})
payload.custom_attributes = .message.traits || {};
payload.custom_attributes = $.filterCustomAttributes(payload, "company");
payload;

- name: prepareAddUserToCompanyPayload
condition: $.outputs.messageType === {{$.EventType.GROUP}} && $.isDefinedAndNotNull($.outputs.searchContact)
template: |
$.assert(.message.groupId, "groupId is required for group call");
let payload = .message.context.mappedToDestination === true ? $.outputs.rEtlPayload : $.outputs.groupEtlPayload;
const contactId = $.outputs.searchContact;
const companyId = await $.createOrUpdateCompany(payload, .destination);
$.assert(companyId, "Unable to add user to company");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are creating/updating the company here and adding the user will be done from server. So we need to update the error messge.

Suggested change
$.assert(companyId, "Unable to add user to company");
$.assert(companyId, "Unable to create or update company");

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed

payload.requestBodyJson = {
id: companyId
}
payload.endpoint = $.getBaseEndpoint(.destination) + "/" + "contacts" + "/" + contactId + "/" + "companies";
payload;

- name: prepareCreateOrUpdateCompanyPayload
condition: $.outputs.messageType === {{$.EventType.GROUP}} && !$.isDefinedAndNotNull($.outputs.searchContact)
template: |
$.assert(.message.groupId, "groupId is required for group call");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is redundant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is required because groupId is mapped with company_id and intercom does lookup based on company_id. so it's always recommended to send groupId

let payload = {
requestBodyJson: .message.context.mappedToDestination === true ? $.outputs.rEtlPayload : $.outputs.groupEtlPayload
}
payload.endpoint = $.getBaseEndpoint(.destination) + "/" + "companies";
payload;

- name: prepareGroupPayload
condition: $.outputs.messageType === {{$.EventType.GROUP}}
template: |
$.context.payload = $.isDefinedAndNotNull($.outputs.searchContact) ? $.outputs.prepareAddUserToCompanyPayload.requestBodyJson : $.outputs.prepareCreateOrUpdateCompanyPayload.requestBodyJson;
$.context.requestMethod = 'POST';
$.context.endpoint = $.isDefinedAndNotNull($.outputs.searchContact) ? $.outputs.prepareAddUserToCompanyPayload.endpoint : $.outputs.prepareCreateOrUpdateCompanyPayload.endpoint;
$.context.payload = $.removeUndefinedAndNullValues($.context.payload);

- name: buildResponseForProcessTransformation
description: build response
template: |
const response = $.defaultRequestConfig();
response.body.JSON = $.removeUndefinedAndNullValues($.context.payload);
response.endpoint = $.context.endpoint;
response.method = $.context.requestMethod;
response.headers = $.getHeaders(.destination);
response;
31 changes: 31 additions & 0 deletions src/cdk/v2/destinations/intercom/rtWorkflow.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
bindings:
- name: handleRtTfSingleEventError
path: ../../../../v0/util/index

steps:
- name: validateInput
template: |
$.assert(Array.isArray(^) && ^.length > 0, "Invalid event array")

- name: transform
externalWorkflow:
path: ./procWorkflow.yaml
loopOverInput: true

- name: successfulEvents
template: |
$.outputs.transform#idx.output.({
"batchedRequest": .,
"batched": false,
"destination": ^[idx].destination,
"metadata": ^[idx].metadata[],
"statusCode": 200
})[]
- name: failedEvents
template: |
$.outputs.transform#idx.error.(
$.handleRtTfSingleEventError(^[idx], .originalError ?? ., {})
)[]
- name: finalPayload
template: |
[...$.outputs.successfulEvents, ...$.outputs.failedEvents]
Loading
Loading